Skip to content

feat: pre-authorized recurring shielded withdrawals (#151) - #170

Merged
tech-adrian merged 2 commits into
Dshield-xyz:devfrom
ifygreg01-best:feat/recurring-shielded-withdrawals
Aug 30, 2026
Merged

feat: pre-authorized recurring shielded withdrawals (#151)#170
tech-adrian merged 2 commits into
Dshield-xyz:devfrom
ifygreg01-best:feat/recurring-shielded-withdrawals

Conversation

@ifygreg01-best

Copy link
Copy Markdown
Contributor

Adds a complete bounded recurring-withdrawal system so users can pre-authorize a series of scheduled payouts from one shielded note without generating a new ZK proof for each occurrence.

Circuit (circuits/recurring/)

  • New Noir circuit that proves note ownership and binds a policy tuple (recipient, max_amount, period_secs, max_uses) into an auth_commitment via a domain-separated Poseidon2 chain (RECURRING_DOMAIN = 0x52454341).
  • Consumes the source note nullifier on setup; creates a change note for amount - max_amount. All values 64-bit constrained; max_uses 1-255.
  • Nargo.toml + Prover.toml added; compiled artifact stub at frontend/src/circuits/recurring.json (replace with real nargo compile output).

Contract (contracts/pool/src/lib.rs)

  • New PoolError variants: AuthNotFound, AuthRevoked, AuthExhausted, PeriodNotElapsed, AmountExceedsCap.
  • RecurringAuth contracttype struct stored in persistent storage.
  • authorize_recurring: verifies proof, consumes nullifier, inserts change note, stores auth record and recipient address.
  • withdraw_recurring: executes one occurrence (payout <= cap, period elapsed, uses_remaining > 0, not revoked); no proof required.
  • revoke_recurring: owner-signed immediate revocation.
  • get_recurring_auth: read-only view.
  • 15 new tests covering every error path and the use-count decrement loop.

Frontend

  • lib/notes.ts: RecurringAuth interface + localStorage helpers with advisory locking (getRecurringAuths, saveRecurringAuth, markRecurringAuthRevoked, getActiveRecurringAuths).
  • lib/poseidon2.ts: computeAuthCommitment() mirrors hash_auth in-circuit.
  • lib/prover.ts: proveRecurringAuthorization() with all 8 circuit inputs; lazy-loads recurring.json so missing artifact only breaks the recurring flow, not the rest of the bundle.
  • app/recurring/page.tsx: setup UX (note selector, recipient, amount, period presets, use count, summary) + management view with on-chain state polling and one-click revocation.
  • app/api/relay-recurring/route.ts: rate-limited server-side trigger for cron/scheduler use; maps PeriodNotElapsed/AuthExhausted/AuthRevoked to HTTP 200 status:skipped so over-eager schedulers don't alarm.
  • components/Header.tsx: Recurring added to nav.

Docs

  • docs/THREAT_MODEL.md: new Recurring authorizations section covering circuit guarantees, per-occurrence contract enforcement, exposure comparison vs one-shot withdrawals, and residual risks.

All 154 frontend tests pass.

Summary

Why

Area

Testing done

  • just test passes (contracts + frontend)
  • just test-e2e passes, if this touches deposit/withdraw/compliance flows
  • If a circuit changed: recompiled and regenerated the checked-in frontend/**/circuits/*.json artifacts
  • pnpm lint / pnpm build pass, if this touches frontend/

Screenshot / recording (required)

Related issue

Closes #

Adds a complete bounded recurring-withdrawal system so users can
pre-authorize a series of scheduled payouts from one shielded note
without generating a new ZK proof for each occurrence.

## Circuit  (circuits/recurring/)
- New Noir circuit that proves note ownership and binds a policy tuple
  (recipient, max_amount, period_secs, max_uses) into an auth_commitment
  via a domain-separated Poseidon2 chain (RECURRING_DOMAIN = 0x52454341).
- Consumes the source note nullifier on setup; creates a change note for
  amount - max_amount.  All values 64-bit constrained; max_uses 1-255.
- Nargo.toml + Prover.toml added; compiled artifact stub at
  frontend/src/circuits/recurring.json (replace with real nargo compile output).

## Contract  (contracts/pool/src/lib.rs)
- New PoolError variants: AuthNotFound, AuthRevoked, AuthExhausted,
  PeriodNotElapsed, AmountExceedsCap.
- RecurringAuth contracttype struct stored in persistent storage.
- authorize_recurring: verifies proof, consumes nullifier, inserts change
  note, stores auth record and recipient address.
- withdraw_recurring: executes one occurrence (payout <= cap, period
  elapsed, uses_remaining > 0, not revoked); no proof required.
- revoke_recurring: owner-signed immediate revocation.
- get_recurring_auth: read-only view.
- 15 new tests covering every error path and the use-count decrement loop.

## Frontend
- lib/notes.ts: RecurringAuth interface + localStorage helpers with
  advisory locking (getRecurringAuths, saveRecurringAuth,
  markRecurringAuthRevoked, getActiveRecurringAuths).
- lib/poseidon2.ts: computeAuthCommitment() mirrors hash_auth in-circuit.
- lib/prover.ts: proveRecurringAuthorization() with all 8 circuit inputs;
  lazy-loads recurring.json so missing artifact only breaks the recurring
  flow, not the rest of the bundle.
- app/recurring/page.tsx: setup UX (note selector, recipient, amount,
  period presets, use count, summary) + management view with on-chain
  state polling and one-click revocation.
- app/api/relay-recurring/route.ts: rate-limited server-side trigger for
  cron/scheduler use; maps PeriodNotElapsed/AuthExhausted/AuthRevoked to
  HTTP 200 status:skipped so over-eager schedulers don't alarm.
- components/Header.tsx: Recurring added to nav.

## Docs
- docs/THREAT_MODEL.md: new Recurring authorizations section covering
  circuit guarantees, per-occurrence contract enforcement, exposure
  comparison vs one-shot withdrawals, and residual risks.

All 154 frontend tests pass.
@tech-adrian
tech-adrian merged commit 2f22036 into Dshield-xyz:dev Aug 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants