Skip to main content

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:

FieldWhat it is
valueThe amount, in planck (1 ORB = 10^18 planck)
asset_idWhich asset it is; 0 is ORB
owner_pkThe x-coordinate of the owner's Baby JubJub public key
blindingA 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.

Proving a note's contents without spending it

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:

BytesField
0–11Nonce (12 bytes)
12–131Ciphertext (120 bytes)
132–147Authentication tag (16 bytes)
148–179Ephemeral 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.

Only ORB moves through the pool today

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.