feat: commitment-based payments with custom token contract fork#6
Open
feat: commitment-based payments with custom token contract fork#6
Conversation
Replace transfer_private_to_private with the commitment-based pattern
(prepare_private_balance_increase + finalize_transfer_to_private_from_private)
to close the payment verification gap identified by Fred.
The old approach sent private notes that only the recipient's PXE could
decrypt, making it impossible for the facilitator to verify recipient,
amount, or token. The new commitment pattern works as follows:
1. Facilitator calls prepare_private_balance_increase(facilitatorAddr)
→ creates a partial note and returns a commitment Field
2. Commitment is sent to client in the 402 response (extra.commitment)
3. Client calls finalize_transfer_to_private_from_private(from,
{commitment}, amount, nonce) to complete the transfer
4. Facilitator verifies tx status and note creation
This structurally guarantees recipient correctness: since the facilitator
created the partial note for its own address, the completed note can only
go to the facilitator. Amount verification via PXE note queries remains
as a documented future improvement.
Changes across 13 files:
- Core types: replace transferPrivateToPrivate/verifyPaymentNotes/
registerSender with finalizePayment/prepareCommitment/verifyPayment
- Mechanism: add preparePayment to SchemeNetworkFacilitator interface,
track pending commitments, validate commitment in verify flow
- Middleware: call preparePayment when generating 402 responses,
carry commitment through accepted→verify requirements
- Demo: inject TokenContract into facilitator signer, implement
prepare_private_balance_increase and finalize_transfer_to_private_from_private
- Tests: 64 passing (35 mechanism + 29 demo)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace broken commitment pattern (prepare_private_balance_increase + finalize_transfer_to_private_from_private) with direct transfer_private_to_private via @defi-wonderland/aztec-standards. The official Aztec Token contract's commitment pattern produces mismatched validity commitment hashes at SDK v4.0.0-devnet.2-patch.1. Direct transfer works reliably and simplifies the entire flow. Changes: - Add @defi-wonderland/aztec-standards dependency (same SDK version) - Remove prepareCommitment from FacilitatorAztecSigner interface - Change finalizePayment signature: commitment → payTo - Client uses transfer_private_to_private(from, to, amount, nonce) - Facilitator no longer needs token contract (just node for verification) - Remove commitment tracking from facilitator scheme - Remove commitment forwarding from middleware - Deploy with constructor_with_minter via Wonderland contract - Update all tests (48 pass across demo + mechanism) - Verified e2e on devnet: Alice→Bob 10000 tokens, 200 response Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fork the official Aztec v4.0.4 TokenContract with one change: `prepare_private_balance_increase(to, completer)` accepts an explicit completer parameter, enabling cross-party commitment flows where the server prepares and the client finalizes. - Add packages/contracts/ with forked Noir source, compiled artifact, and TypeScript wrapper - Update all imports to use @aztec-x402/contracts/Token - Update facilitator-signer to pass completerAddress to prepare - Pin SDK to 4.0.4 (required for partial note nullifier fixes) - Update README with commitment flow, version compatibility, and devnet status - Update commitment-pattern-findings.md with resolution Tested on devnet: contract deploys and prepare succeeds, but finalize is blocked by PXE nullifier witness bug in devnet 4.0.0-devnet.2-patch.1. Full flow works on local 4.0.4 sandbox. Devnet upgrade expected ~2 weeks. 119 tests pass. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…on mismatch
EmbeddedWallet overrides simulateViaEntrypoint to use stub account contracts,
causing simulate() and send() to produce different commitments via
unsafe { random() } in UintNote::partial(). This results in "Nullifier witness
not found" errors during finalize_transfer_to_private_from_private.
PXEWallet restores BaseWallet's real-account-entrypoint simulation, matching
what Aztec's own TestWallet does in their e2e tests.
Also adds payment failure tests for wrong address, wrong token, no private
notes, and fabricated commitments. Documents known verification gaps (amount,
token contract) with KNOWN GAP labels.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Owner
Author
Update: EmbeddedWallet → PXEWallet + Payment Failure TestsRoot Cause FoundThe "Nullifier witness not found" error has two root causes:
Fix: PXEWalletNew Payment Failure Tests Added (Frederik's feedback)
Remaining Work
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
prepare_private_balance_increase(to, completer)accepts an explicit completer parameter instead of hardcodingmsg_sender()Custom Token Contract
The official Aztec TokenContract hardcodes
completer = msg_sender()inprepare_private_balance_increase. This means whoever calls prepare must also call finalize — blocking our x402 flow where the server prepares and the client finalizes.Our fork at
packages/contracts/adds one parameter:Security: The
completerparameter only controls who can call finalize for that specific partial note. It cannot steal tokens or change the recipient — both are cryptographically bound.Version Compatibility: 4.0.0-devnet.2-patch.1 vs 4.0.4
We tested the commitment pattern extensively across SDK and node versions:
4.0.44.0.0-devnet.2-patch.1)4.0.0-devnet.2-patch.1utilityGetNullifierMembershipWitnesscannot find the nullifier created by partial note prepare4.0.44.0.4)Root cause on devnet: The PXE/simulator in
4.0.0-devnet.2-patch.1has a bug where it cannot locate nullifiers created by partial notes in the nullifier tree. This was fixed in v4.0.4 via PRs #14379, #14432, #14533.We cannot mix versions: using 4.0.4 SDK with the devnet node fails because the protocol's verification key tree root changed between versions.
Timeline: Devnet upgrade to 4.0.4+ expected in ~2 weeks. Until then, use local 4.0.4 sandbox for commitment flow testing.
What changed
packages/contracts/packages/contracts/token/src/main.nrcompleterparameter on_prepare_private_balance_increasepackages/contracts/src/Token.ts@aztec/noir-contracts.js/Tokenpatternpackages/demo/package.json@aztec-x402/contractsworkspace dep, pin SDK to 4.0.4packages/core/src/types.tspackages/demo/src/aztec/facilitator-signer.tscompleterAddresstoprepare_private_balance_increase(facilitatorAddr, completerAddr)packages/demo/src/aztec/setup.ts@aztec-x402/contracts/Tokenpackages/demo/src/aztec/real-server.ts@aztec-x402/contracts/Tokenpackages/demo/src/aztec/real-client.ts@aztec-x402/contracts/Tokenpackages/demo/src/aztec/test-wonderland-commitment.tsdocs/commitment-pattern-findings.mdREADME.mdDevnet deployment log
Successfully deployed on devnet (
4.0.0-devnet.2-patch.1) with SDK downgraded to match:0x192550f89f95b0864b389ce5bb23725c1fe4d015cbfa251ba5b19605410ea7af(class0x1c6ddc...)Test plan
bun test— 119 pass, 0 fail (all packages)aztecprotocol/aztec:4.0.4Docker imageprepare_private_balance_increaseABI includescompleterparameter🤖 Generated with Claude Code