Notes
The shielded pool does not hold balances. It holds notes, and on-chain it holds only a hash of each one. This page is the reference for what a note contains, how it is spent, and what the chain stores for it.
What a note is
A note is a private record of value. It has four fields:
| Field | What it is |
|---|---|
value | The amount, in planck (1 ORB = 10^18 planck) |
asset_id | Which asset it is; 0 is ORB |
owner_pk | The x-coordinate of the owner's Baby JubJub public key |
blinding | A random field element, or, when a payment names its sender by address, one derived by sender and recipient |
The blinding makes the other three unguessable. Without it, an observer could hash every plausible
(value, asset_id, owner_pk) triple and find a match. A derived blinding keeps that property: it
depends on the note's ephemeral shared secret, which only the two parties can compute, and it is
what lets the commitment itself authenticate the sender. See
Note Discovery.
Where owner_pk and the spending key come from is covered in Keys & Identity.
Commitments
The note never touches the chain. The runtime stores its commitment, a Poseidon hash of the four fields:
commitment = Poseidon4(value, asset_id, owner_pk, blinding)
The commitment is public and reveals nothing about the note. The pool refuses a zero commitment, a value that is not a canonical field element, and any commitment it already holds.
The wallet keeps the four fields on the user's device, encrypted. That is why the vault stores notes rather than a balance, and why losing them means recovering notes from the chain: the chain holds hashes and encrypted memos, not the notes themselves.
A disclosure key carries the four fields, so a third party can recompute the commitment and confirm what the note holds without gaining any ability to spend it. See Note Disclosure.
Nullifiers
A commitment is never deleted. Spending a note publishes its nullifier:
nullifier = Poseidon2(commitment, spending_key)
The runtime keeps every nullifier it has seen (NullifierSet). Spending the same note again
produces the same nullifier, and the runtime rejects it.
One note, one nullifier. Many scalars derive the same Baby JubJub public key: k, k + l,
k + 2l, … where l is the subgroup order. Each would give a different nullifier for the same
note. The circuits therefore require a canonical spending key, spending_key < l, so each note has
exactly one valid nullifier. See
the transfer circuit.
The link runs one way. Computing a nullifier needs the spending key, so an observer who sees a nullifier appear cannot tell which commitment it came from.
The encrypted memo
Every commitment the pool inserts carries an encrypted memo, so the recipient can find and open the note. The memo is exactly 180 bytes:
| Bytes | Field |
|---|---|
| 0–11 | Nonce (12 bytes) |
| 12–131 | Ciphertext (120 bytes) |
| 132–147 | Authentication tag (16 bytes) |
| 148–179 | Ephemeral public key (32 bytes) |
The pool checks only the size; the plaintext and how a recipient decrypts it are covered in Note Discovery. A spend's proof is bound to the exact memos it carries, so they cannot be swapped once it is proven. See the transfer circuit.
A total unshield carries no memo. A partial unshield carries a full memo for its change note.
Merkle forest
Commitments are the leaves of a forest of depth-20 Merkle trees. A spend proves its note is a leaf of one tree by naming that tree's root, never the leaf. Trees, sealing, roots and how a wallet builds a path are covered in The Merkle Forest.
One pool for every asset
asset_id is a field inside the note, not a separate tree. Notes of every asset share the same
trees, and so the same anonymity set.
One tree per asset would split the crowd: a rarely used asset would have a handful of notes to hide among. With one forest, a note of one asset is indistinguishable from a note of another.
Assets are registered and verified by governance (register_asset, verify_asset,
unverify_asset). An unverified asset is frozen: shield, unshield, private transfer and fee claims
of it are refused with AssetNotVerified.
The pool moves the native currency for every operation, so a note of any other asset would be
backed by ORB. Shield, unshield, private transfer and fee claims accept only asset 0 and refuse
any other with AssetNotSupported.
Related
- The Merkle Forest — where commitments live and how paths are built
- Note Discovery — memo plaintext, view tags and scanning
- Keys & Identity — where
owner_pkand the spending key come from - Privacy Architecture — what the pool hides and what it does not
- How Proofs Work — the circuits that enforce all of the above