null.
Concepts

The shielded pool

Notes, commitments, nullifiers, and the shield / transfer / unshield lifecycle — with value conservation.

The shielded pool is a set of hidden balances. Your funds live as notes; the pool only ever sees opaque hashes of them. Spending a note reveals a nullifier (so it can't be spent twice) and creates new commitments (the outputs) — but never the amounts or owners.

Notes, commitments, nullifiers

A note is the unit of value. It is never stored on-chain in the clear.

privKey                     random field element (the spending secret)
pubKey      = Poseidon(privKey)
note        = (amount, pubKey, blinding)
commitment  = Poseidon(amount, pubKey, blinding)
nullifier   = Poseidon(commitment, leafIndex, privKey)
  • pubKey = Poseidon(privKey) ties a note to a spending key without revealing it.
  • commitment hides amount + owner + blinding; it is what gets inserted into the pool's Merkle tree.
  • nullifier = Poseidon(commitment, leafIndex, privKey) is published when a note is spent. It is:
    • deterministic for the owner (they hold privKey and know their leafIndex);
    • unlinkable to the commitment for anyone else — you cannot compute it without privKey, and it is not invertible back to the commitment;
    • double-spend-safe — the same note at its unique tree position always yields the same nullifier, so a re-spend collides on-chain;
    • ownership-binding — it needs the privKey whose Poseidon(privKey) is the note's pubKey, so you cannot nullify a note you do not own.

Notes are held client-side and are your funds — see Note management.

The Merkle tree

Commitments are appended to an incremental Poseidon(2) Merkle tree of depth 20 — capacity 1,048,576 notes. Empty leaves are 0, with precomputed per-level zero-subtree hashes. Depth 20 is the Tornado-classic size; production can raise it to 26 (~67M) or 32 (~4.3B) by changing a single circuit parameter.

The three operations

Every operation is one on-chain transaction carrying a 2-in / 2-out join-split proof (see Zero-knowledge).

  • Shield — deposit public lamports into the pool. publicAmountIn > 0, publicAmountOut == 0. The deposit is public; everything after it is not.
  • Transfer — move value between shielded identities. Both public amounts are 0; sender and amount are hidden. Change returns to the sender as a new note.
  • Unshield — withdraw to a public address. publicAmountOut > 0; the destination is bound in the proof. Optionally routed through a relayer so the user's wallet never signs.

Value conservation

Value can never be created or destroyed. The circuit enforces:

Σ inAmount + publicAmountIn == Σ outAmount + publicAmountOut + fee

with a 64-bit range check on every amount so no term can wrap the field. On the chain side this yields the pool's central invariant:

POOL_VAULT.lamports == Σ shielded-in − Σ shielded-out

Nothing enters or leaves the vault without a verified proof. For an unshield with a relayer fee, the value leaving the vault is publicAmountOut + fee — the recipient receives publicAmountOut, the relayer receives fee, and the invariant still holds.

On this page