Skip to content

docs: partial notes as payment endpoints - #23564

Merged
critesjosh merged 9 commits into
nextfrom
docs/partial-notes-payment-endpoints
Jun 23, 2026
Merged

docs: partial notes as payment endpoints#23564
critesjosh merged 9 commits into
nextfrom
docs/partial-notes-payment-endpoints

Conversation

@critesjosh

Copy link
Copy Markdown
Contributor

Summary

  • Add a new concept page partial_notes_as_payment_endpoints.md covering how partial notes can back a stable name (e.g. alice.aztec) with a rotating supply of unlinkable, one-shot payment endpoints. Walks through the completer choice, the distribution choice (offchain vs. onchain registry), a recommended pattern (offchain distribution + shim contract as completer + private→private completion), and an honest leakage analysis.
  • Expand the existing partial_notes.md to cover single-use semantics, completion in public vs. private contexts, and a forward link to the new page.
  • Correct a subtle but important misconception: single-use is not protocol-enforced. The validity commitment is checked at completion but not consumed, so a partial note can be completed more than once. Reuse is unsafe because completion logs share a tag (privacy break) and PXE discovery only handles the first completion (funds lost).
  • Add unlinkable and unlinkability to docs-words.txt and fix a small pre-existing typo in partial_notes.md.

Context

Started from a Slack thread asking whether Aztec could support a naming service where resolving a name returns "something" other than a public address. The answer that fell out: a partial note. Each partial note is a commitment to be paid; senders complete it later. The recipient does not need to be online for the payment to land. Mike asked for docs reflecting this paradigm.

The new page deliberately stops short of shipping a reference contract. The shim contract sketch is illustrative, not compilable, because the public API decisions (whether to also act as fee payer, support for public→private routing, authwit ergonomics) need product input before being committed to docs.

What I verified against source

  • noir-projects/aztec-nr/uint-note/src/uint_note.nr — the partial, complete, and complete_from_private functions, plus the validity commitment semantics.
  • noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr:340-395prepare_private_balance_increase, finalize_transfer_to_private, finalize_transfer_to_private_from_private, and the token's own warnings about partial-note reuse.
  • noir-projects/aztec-nr/aztec/src/messages/discovery/partial_notes.nr:170 — pending partial notes are removed after the first matching completion log, confirming "discovery fails on reuse."

Codex (gpt-5.5) reviewed both the design plan and the draft and flagged the issues above before posting.

Test plan

  • Run yarn build in docs/ to verify the new page integrates with the Docusaurus build and the #include_code macros resolve.
  • Confirm cspell passes (already verified locally).
  • Subject matter review by @MikeNotPepe and @harshjain-z given the design intent thread.
  • Verify sidebar placement (new page sits at sidebar_position: 2 next to existing partial_notes.md).

Add a concept page on using partial notes as a recipient's stable,
unlinkable payment endpoint, and expand partial_notes.md to cover
single-use semantics, public vs private completion, and a forward
link to the new page.

The new page walks through the design space for an Aztec naming
service that resolves a name to a partial note rather than an
address. Covers the completer choice (per-sender, shim contract,
not the token), the distribution choice (offchain vs onchain),
the recommended pattern (offchain distribution + shim + private
completion), and an honest leakage analysis.

Corrects a subtle misconception that partial-note single-use is
enforced by the protocol. The validity commitment is checked at
completion but not consumed, so reuse is mechanically possible.
Reuse is unsafe because the completion log tag is derived from
the commitment (privacy break) and the recipient's PXE only
discovers the first completion (funds lost).
Address review feedback on the partial-notes payment-endpoints page:
- Clarify that holding a commitment does not grant completion; only the
  designated completer can complete it.
- Distinguish the partial-note primitive (which checks a completer-bound
  validity commitment) from the standard token's msg_sender == completer
  convention.
- Reframe the stealth-address comparison: this pattern requires the recipient
  to pre-mint and refill a pool of commitments, rather than claiming
  non-interactive derivation is impossible.
Rewrite the payment-endpoints page to assume the AIP-20 fungible token
standard, with an explicit callout at the top. AIP-20 takes the completer
as an argument to initialize_transfer_commitment and debits a separately
authorized `from` account, so the example-token shim-routing workaround no
longer applies: the recipient mints directly with any completer, and a
relayer is only needed at completion time so unknown senders can pay.
Update function names and flows to transfer_private_to_commitment /
transfer_public_to_commitment.

Also minor wording and formatting tweaks in partial_notes.md.
- partial_notes.md: add an AIP-20 assumption callout, a "The completer"
  section explaining why completion is gated by a committed completer
  (validity commitment as a capability, unbacked-note risk), map
  complete / complete_from_private to the AIP-20 entrypoints, and use
  consistent terminology (partial note in prose, partial_commitment in
  formulas).
- partial_notes_as_payment_endpoints.md: tighten the completer-choice
  framing, scope the onchain-registry leak, and note the new
  not-yet-fully-constrained onchain_constrained delivery mode.

They are also useful as **payment endpoints**: a recipient can mint a partial note ahead of time and share the commitment with prospective senders. Senders later complete the partial note to pay the recipient, who does not need to be online for the payment to land. See [partial notes as payment endpoints](./partial_notes_as_payment_endpoints.md) for the full design.

## The completer

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add some details about what in the note struct is known at partial note vs completion of the full note (and therefore the direction of the funds as completer -> creator) if that makes sense

@ciaranightingale ciaranightingale left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

small improvements which when resolved is gtg


:::note Assumed token standard
This page assumes the [AIP-20 fungible token standard](../../standards/aip-20.md), which exposes commitment-based transfers directly: the completer is an explicit argument to `initialize_transfer_commitment`, and completion debits a separately authorized account. Tokens that instead hardcode the completer to `msg_sender` (such as the example `token_contract` in aztec-packages) need a more roundabout flow that this page does not cover.
This page assumes the [AIP-20 fungible token standard](../../standards/aip-20.md), which exposes commitment-based transfers directly: the completer is an explicit argument to `initialize_transfer_commitment`, and completion debits a separately authorized account. The example `token_contract` in aztec-packages also supports both flows (the single-call `transfer_to_private` and the two-step `prepare_private_balance_increase` plus `finalize_transfer_to_private`), but its prepare step hardcodes the completer to the caller. A recipient who prepares a partial note there is the only party able to complete it, which rules out the third-party-sender flows this page describes.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

im still not sure this is accurate - maybe Im wrong but I'm pretty sure a different completer is still possible in the token_contract - did you have a look?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the token contract in aztec-packages hardcodes the msg_sender, whereas the wonderland token contract does not

The trick is that the name never maps to an address at all. It maps to a pool of opaque commitments, and senders pay into a commitment. Concretely, for `alice.aztec`:

1. **Alice's wallet creates partial notes ahead of time.** Each one is a commitment `H(alice_address, randomness_i)` with fresh randomness per note. Her address is inside the hash, blinded by the randomness, so the commitment reveals nothing about her.
2. **The chain records a validity commitment.** Creating each partial note records `H(partial_commitment, completer)` in the nullifier tree. Also just a hash; no address is visible.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the term "validity commitment" is confusing - what is it? I'm confused again

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

its literally just a recorded partial note in the note hash tree? where does the nullifier come in?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the "validity commitment" binds the partial commitment to a completer, its a term used in the source code.

also the nullifier tree is used to store the commitment, so it can be checked in the future. i think it's confusing bc the "nullifier tree" is being used for something other than just storing a nullifier. i added a clarifying note later in the doc

- Clarify the validity commitment (what it is, nullifier-tree-as-existence-set)
- Correct token_contract completer behavior: completer = msg_sender of prepare,
  private relayer flow still achievable
- Use 'list' / 'partial note commitments' instead of 'pool' / 'opaque commitments'
@critesjosh
critesjosh marked this pull request as ready for review June 23, 2026 16:51
@AztecBot

Copy link
Copy Markdown
Collaborator

Flakey Tests

🤖 says: This CI run detected 1 tests that failed, but were tolerated due to a .test_patterns.yml entry.

\033FLAKED\033 (8;;http://ci.aztec-labs.com/8355af4676b13fc5�8355af4676b13fc58;;�): yarn-project/end-to-end/scripts/run_test.sh ha src/composed/ha/e2e_ha_full.test.ts (308s) (code: 0)

Merged via the queue into next with commit ad46a1a Jun 23, 2026
19 of 20 checks passed
@critesjosh
critesjosh deleted the docs/partial-notes-payment-endpoints branch June 23, 2026 19:09
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.

3 participants