A shielded pool on Solana
The Shielded mode holds money as private notes inside a pool. Spending a note publishes a zero-knowledge proof and a nullifier, never the note itself, so nobody can tell which deposit a payment came from, who sent it or how much it was. The proof is a Groth16 proof over BN254 from a circuit of about 62,000 constraints, and the chain verifies it in about 190,000 compute units.
Notes and the tree
note = (amount, owner, blinding) commitment = Poseidon(amount, owner, blinding)
Every new note’s commitment is appended to a 26-level Poseidon Merkle tree, room for 67 million notes. The chain stores only the tree’s right edge and its last 100 roots, which keeps the account to a few kilobytes and lets many people prove against the tree at the same time without invalidating each other.
Spending exactly once
nullifier = Poseidon(commitment, nullifier_key, position)
Spending a note publishes its nullifier, which only its owner can compute. The program stores each nullifier as its own account, and an account can only be created once, so a second spend of the same note fails the moment its nullifier account would be created again.
The proof
Each shielded transaction spends up to two notes and creates two. Its Groth16 proof, built in your browser, shows all of this without revealing any of it:
- Every spent note is in the tree.
- You own it: you know the keys behind its owner.
- Its nullifier is computed correctly.
- Value in equals value out plus the fee, and every amount is in range.
- The recipient, relayer, fee and encrypted outputs are exactly the ones submitted.
The pool program verifies it with Solana’s BN254 pairing syscalls. The verifying key is compiled into the program itself, so it cannot be swapped out, and it comes from a trusted setup ceremony built on the Perpetual Powers of Tau and finalized with a public drand randomness beacon.
Finding your notes
Every note is encrypted to its owner with X25519 and XChaCha20-Poly1305 and posted with the transaction. Your browser tries to decrypt every output in the pool and keeps the ones that open, so no server ever learns which notes are yours.
Keys that do one job each
spend key ─┬─ public key receive
├─ nullifier key spend
└─ view key readAll of them come from one signature your wallet makes. The split is designed in from the start, so reading and spending can be separated, and the circuit carries an association root for proof-of-innocence membership sets.