Module 14 · Privacy protocols in practice: shielded pools, Railgun & the compliance frontier
Zcash: the shielded-pool blueprint
Zcash is the first and longest-running production shielded pool, and almost every later privacy design is a variation on its blueprint. A payment creates a note (value, recipient, randomness) and publishes a note commitment that is appended as a leaf to an append-only commitment tree; spending the note reveals a nullifier, derived from the note and the spender's key, which the network records in a nullifier set so the same note cannot be spent twice. A zero-knowledge proof attests that the spent note exists in the tree and that the spender is authorised, without revealing which note or how much. Sapling made shielded transactions practical; Orchard replaced the proving system with Halo2, removing the per-upgrade trusted setup. A hierarchy of keys — spending key, full viewing key, incoming viewing key — controls who can spend and who can merely see. The hard edge is the transparent↔shielded boundary, the "turnstile", where moving value in or out exposes an amount and an address; and the deeper truth is that the pool only hides you among the people actually transacting in it, so privacy is a function of shielded-pool usage, not of the cryptography alone.
Learning objective
Understand Zcash as the canonical production shielded pool and the template that later privacy systems borrow from: how note commitments are added to a commitment tree, how a nullifier set prevents double-spends, how the pool evolved from Sapling to Orchard (and that Orchard proves in Halo2 with no trusted setup), what spending keys versus full and incoming viewing keys each reveal, why crossing the transparent↔shielded boundary leaks (the "turnstile"), and why the privacy the pool offers depends on how many people actually use it — the shielded-pool usage, not just the cryptography.
- Track
- Research
- Module
- 14 · Privacy protocols in practice: shielded pools, Railgun & the compliance frontier
- Prerequisites
- What is a zero-knowledge proof · Commitment schemes in depth: Pedersen, KZG and IPA · Merkle trees · Nullifiers, Merkle membership and the privacy-pool pattern
Conceptual introduction
Zcash is the reference implementation of a shielded pool in production: a shared cryptographic account into which value is deposited as hidden "notes" and out of which it is spent, with the links between deposits and spends obscured by zero-knowledge proofs rather than by trust in an operator. It matters here because it is not a toy — it has run on a public chain for years, survived upgrades and audits, and in doing so fixed the vocabulary and the component layout that nearly every later privacy system reuses. When a newer design talks about note commitments, a commitment tree, a nullifier set and viewing keys, it is almost always describing a dialect of what Zcash shipped first.
The blueprint is built from primitives covered in earlier lessons working in concert: a hiding, binding commitment to represent a note; an append-only Merkle commitment tree so membership can be proven succinctly; a nullifier so a note can be spent exactly once; and a zero-knowledge proof so the spender demonstrates "I own an unspent note in this tree" while revealing neither the note nor the amount. Reading Zcash well means seeing which of these each design decision touches, and — just as importantly — seeing where the cryptography stops and ordinary observable metadata begins.
Zcash is a real, public protocol; this lesson cites its concepts (Sapling, Orchard, Halo2) generically and the ZK theory from an external educational reference. It does not state network statistics, adoption figures or audit outcomes, because those are not established here. Where a claim about real-world privacy depends on how many people use the pool, that is flagged as a measured, external quantity — UNKNOWN unless a specific measurement is cited — not something the protocol guarantees.
Notes, commitments and the commitment tree
The unit of value in the shielded pool is a note: conceptually a tuple of a recipient address, a value, and fresh randomness. A note is never published in the clear. Instead the sender publishes a note commitment — a hiding, binding commitment to the note — which is appended as a new leaf in a global, append-only Merkle tree. The tree root is a short digest of every note ever created in the pool; proving that a note is in the pool is proving Merkle membership against a recent root, exactly the membership pattern from the privacy-pool lesson.
Because the tree is append-only, commitments are never removed when a note is spent — the tree only grows. "Spentness" is tracked separately, by the nullifier set, so that the commitment tree can keep a stable history that old proofs can still reference. This separation of "membership" (the commitment tree) from "spentness" (the nullifier set) is one of the structural ideas later systems copy wholesale.
synthetic — note, commitment, and the append-only tree (illustrative, not Zcash field layout)# A note (kept private by sender and recipient) note = (recipient_addr, value, rseed) # rseed = fresh per-note randomness # Note commitment: hiding + binding, added as a tree leaf cm = Commit(recipient_addr, value, rseed) tree.append(cm) # append-only; leaves are never removed root = tree.root() # short digest of the whole pool # Membership later proven against a RECENT root, in zero knowledge: # "I know a note whose commitment cm is a leaf under this root" # The amount and the recipient stay inside the witness, never on chain.
The commitment hides the value and recipient (so observers learn nothing about either) and binds them (so the spender cannot later claim the note held a different amount). This is why the commitment scheme's hiding and binding properties, and the Merkle tree's succinct membership, are prerequisites: the whole pool is those two primitives scaled to a public ledger.
Nullifiers: spending a note exactly once
A note must be spendable once and only once, but the pool must not learn which note was spent. Zcash resolves this with a nullifier: a value derived deterministically from the note (via a position/note-specific input) together with a secret derived from the spender's key. When a note is spent, the transaction reveals its nullifier, and the network records it in the nullifier set. A second attempt to spend the same note would reveal the same nullifier, which is already present, and is rejected.
The crucial design property — identical to the general privacy-pool pattern — is that the nullifier is computed from different inputs than the commitment, and under the spender's secret key, so observers cannot link the nullifier revealed at spend time to the commitment published at deposit time. The nullifier proves "this specific note is now spent" without proving which leaf it corresponds to. Uniqueness lives in the nullifier set, never in the proof bytes, because succinct proofs can be malleable.
synthetic — nullifier derivation and the double-spend check (illustrative)# At spend time the spender derives (inside the ZK witness): nk = nullifier_key(spending_key) # secret, from the spender's key nf = PRF(nk, note_position, cm) # the note's nullifier # The ZK proof (public: root, nf, value-balance commitments; private: note, path, keys) assert membership(cm, path) == root # the note is in the pool assert nf == PRF(nk, note_position, cm) # nullifier bound to THIS note + key assert authorised_to_spend(spending_key, note) # Network rule — uniqueness lives in the nullifier set, not in the proof: if nf in nullifier_set: reject # double-spend else: nullifier_set.add(nf)
Note the division of labour that the blueprint fixes: the commitment tree answers "does this note exist?" (membership) and the nullifier set answers "has this note been spent?" (uniqueness). They are deliberately separate stores computed from deliberately different inputs, which is what lets the chain enforce no-double-spend while keeping deposit and spend unlinkable.
Sapling to Orchard, and Halo2
The shielded pool has gone through generations, and the two that matter for this blueprint are Sapling and Orchard. Sapling made shielded transactions practical — proving times and memory dropped enough that ordinary wallets, and later mobile devices, could construct shielded payments, and it introduced the viewing-key hierarchy discussed next. Orchard is the later shielded protocol; its headline change is the proving system.
Earlier shielded constructions relied on a proving system that required a trusted setup — a one-time ceremony producing public parameters whose secret "toxic waste" had to be destroyed, because anyone retaining it could forge proofs and mint value undetectably. Orchard adopts Halo2, a proving system in the family that removes the need for a per-circuit trusted setup. This is a meaningful reduction in trust assumptions: there is no ceremony whose honesty you must assume for that circuit. It does not make the system assumption-free — Halo2 still rests on its own cryptographic hardness assumptions and on the circuit being implemented correctly — but it removes an entire class of setup risk that a shielded pool previously had to carry.
- Sapling
- A shielded-protocol generation that made shielded transactions efficient enough for everyday and mobile wallets, and that defined the practical viewing-key hierarchy (full and incoming viewing keys). Its proofs used a pairing-based system that required a trusted setup.
- Orchard
- The later shielded protocol in the same lineage. It keeps the note-commitment-tree + nullifier-set + ZK-proof blueprint but swaps the proving system.
- Halo2
- The proving system Orchard uses. It belongs to the family that removes the per-circuit trusted setup, so no setup ceremony needs to be trusted for the circuit. It still carries its own hardness assumptions and depends on a correct circuit implementation; "no trusted setup" is a narrower, specific claim, not "no assumptions".
The lesson of the evolution is architectural: the blueprint — notes, commitment tree, nullifier set, viewing keys — stayed constant while the proving machinery underneath was replaced. That stability is exactly why the Zcash layout is a blueprint: the components and their responsibilities outlive the specific cryptography that implements them.
Keys: who can spend, and who can see
A shielded pool is useless if its owner cannot audit their own funds, share read access with an accountant, or let a wallet detect incoming payments without exposing the power to spend. The Zcash key hierarchy is designed around this separation of capabilities: distinct keys grant distinct, strictly ordered amounts of authority, and getting the mental model right is central to reasoning about what a given party can and cannot learn.
- Spending key
- The root secret. It can derive every other key and authorise spends — it produces the authorisation and the nullifier-deriving secret needed to spend notes. Anyone holding it controls the funds completely. Everything below is derived from it and reveals strictly less.
- Full viewing key
- Derived from the spending key. It can see the full shielded activity of the account — both incoming notes and the account's own spends (and thus amounts and the nullifiers it produces) — but it cannot authorise a spend. This is the key you would hand to an auditor to prove what happened without granting any ability to move funds.
- Incoming viewing key
- A narrower key, derivable from the full viewing key. It lets the holder detect and decrypt notes sent TO the account — enough for a wallet or a watch-only service to recognise received payments — but it does not reveal the account's own outgoing spends and cannot spend. It reveals strictly less than the full viewing key.
- Nullifier (in this hierarchy)
- Not a key but the spend-time artifact derived from a note together with the spender's nullifier-deriving secret (itself from the spending key). Revealing a nullifier marks a note spent without revealing which commitment it came from; computing one requires spend authority, so observers and viewing-key holders cannot forge it.
Read the hierarchy as a strict ladder of disclosure: spending key (spend + see everything) ⊃ full viewing key (see everything, spend nothing) ⊃ incoming viewing key (see only receipts). Sharing a viewing key is a deliberate, scoped disclosure — a selective-disclosure feature, not a leak — but it is a one-way gift: once shared, that visibility cannot be revoked for the notes it already covers. Treat "who holds which key" as the first question in any privacy analysis of a shielded account.
The transparent↔shielded boundary (the turnstile)
Zcash supports both transparent addresses (ordinary, fully public ledger entries) and the shielded pool. Value can move between them, and every such crossing is where privacy is most exposed. Moving value from a transparent address into the pool, or out of the pool to a transparent address, necessarily reveals the amount crossing and the transparent address involved — this boundary is often called the turnstile, because like a turnstile it records who and how much passed through even though it does not reveal what happened on the other side.
The consequence is a linkage risk that the shielded cryptography cannot fix: an observer who sees a distinctive amount shielded at one time and the same distinctive amount deshielded shortly after, to or from related transparent addresses, can often bridge across the pool by correlation even though no on-chain link exists inside it. The pool protects activity that stays inside it; the turnstile is where the outside world can reach in.
Honest limits of the blueprint: (1) transparent-transaction linkage — any value that enters or leaves via a transparent address exposes an amount and an address at the boundary, and matching amounts across the turnstile can correlate entry and exit. (2) Timing and metadata — when a transaction is broadcast, its fees, the relay/wallet patterns, and distinctive amounts all leak outside the proof. (3) A small shielded set — if few notes are transacting in the window that matters, "hidden among the pool" can mean hidden among very few. None of these are breaks of the zero-knowledge proof; they are limits of what the proof was ever claiming to hide.
Privacy is a function of usage, not only of cryptography
The most misread property of any shielded pool is the one most important to state plainly: the privacy a user actually gets is bounded by how many other people are transacting in the pool in a way that makes them indistinguishable. The proof can be flawless and the keys perfectly managed, yet if the effective anonymity set — the set of notes a given spend could plausibly be — is small, an observer narrows the possibilities by elimination. Perfect cryptography over a nearly empty pool hides almost nothing.
This is why shielded-pool usage is itself a privacy parameter. A pool with heavy, diverse, well-timed shielded activity gives each user a large crowd to hide in; a pool that most value only passes through quickly (shielding and deshielding in distinctive amounts) gives a thin, correlatable set regardless of the underlying math. For research honesty this must be reported as a measured, external quantity — how large the relevant anonymity set is at a given time is UNKNOWN unless a specific measurement is cited — and never conflated with the protocol's cryptographic guarantee, which is about soundness and unlinkability of individual operations, not about crowd size.
Reading the guarantee honestly (evidence model)
When a shielded spend proof verifies, the network has established a precise statement and no more: the spender knew the opening of some note whose commitment is a leaf under a recent, valid tree root; the spender was authorised to spend it; and the revealed nullifier is correctly bound to that note and has not appeared before — all under the proving system's assumptions (and, for a trusted-setup generation, the setup; for Orchard/Halo2, no per-circuit setup but Halo2's own assumptions and a correct circuit) and correct protocol implementation. That statement is exactly what prevents forgery and double-spends while hiding the note, the amount and the link to the deposit.
It does NOT establish that the funds are legitimate or lawfully held, that the spend was voluntary rather than coerced, that the user achieved the privacy they wanted (that depends on the turnstile, on metadata, and on how many people use the pool), or that the circuit faithfully encodes the intended statement (an under-constraint would silently break soundness). A valid spend proof attests that the spender knew a note's opening and published its nullifier under stated assumptions — it is never evidence that the transaction was proper, safe, or actually private. Report the statement and its assumptions; report the anonymity set, the turnstile crossings and any transparent linkage as separate, measured facts; and keep PROVEN (the verified statement), NOT PROVEN (legitimacy, voluntariness) and UNKNOWN (achieved anonymity-set size) distinct.
Exercise
A user holds funds on a transparent address, shields exactly 7.31 units into the pool, waits a few minutes, then deshields exactly 7.31 units to a second transparent address they also control. Separately, they hand their accountant a key so the accountant can confirm all payments the account received this year, and are worried this also lets the accountant spend. Analyse: (a) what, if anything, an on-chain observer can conclude about the two transparent addresses despite the shielded pool, and whether that is a break of the zero-knowledge proof; and (b) which key the accountant should receive, what it can and cannot do, and whether the spend worry is justified.
Expected direction: (a) The observer can plausibly link the two transparent addresses by the turnstile: a distinctive amount (7.31) shielded and the same distinctive amount deshielded a few minutes later strongly correlates entry and exit across the pool, even though no link exists INSIDE the pool. This is NOT a break of the zero-knowledge proof — the proof never claimed to hide amounts or addresses at the transparent boundary, only to keep in-pool operations sound and unlinkable. The leak is metadata (matching amount + timing at the turnstile), and it is worsened by a small/quiet shielded set; fixes are operational — avoid round-trips in distinctive amounts, split/vary amounts, wait longer, and prefer staying shielded rather than crossing the turnstile. (b) The accountant should receive an INCOMING VIEWING KEY: it lets them detect and decrypt notes sent TO the account (the received payments they need to confirm) but does not reveal the account's own outgoing spends and cannot authorise a spend. The spend worry is NOT justified for a viewing key — only the spending key can authorise spends. A full viewing key would also work for auditing but would additionally expose the account's own spends, which is more than the task requires; neither viewing key can move funds. The disclosure is one-way: once shared it cannot be revoked for notes it already covers, so scope it to the minimum (incoming viewing key).
Check your understanding
- What is the structural division of labour between the commitment tree and the nullifier set, and why are they separate?
- The append-only commitment tree records every note commitment ever created and answers membership ("does this note exist in the pool?"); the nullifier set records revealed nullifiers and answers spentness ("has this note been spent?"). They are kept separate, and computed from different inputs, so the tree can hold a stable history that old proofs reference while the chain still enforces no-double-spend — and so the nullifier revealed at spend time cannot be linked to the commitment published at deposit time.
- What specifically changed from Sapling to Orchard regarding the proving system, and what does "no trusted setup" actually claim?
- Orchard adopts the Halo2 proving system, which removes the per-circuit trusted setup that earlier (pairing-based, Sapling-era) proofs required. "No trusted setup" means there is no setup ceremony whose honesty (and destruction of toxic waste) must be assumed for that circuit; it does NOT mean no assumptions — Halo2 still rests on its own cryptographic hardness assumptions and on the circuit being implemented correctly.
- Order the spending key, full viewing key and incoming viewing key by capability, and give one correct use for each.
- Spending key ⊃ full viewing key ⊃ incoming viewing key. The spending key can spend and see everything (the root secret). The full viewing key can see all shielded activity including the account's own spends but cannot spend — give it to an auditor for full read access. The incoming viewing key can only detect/decrypt notes sent to the account (received payments) and reveals neither outgoing spends nor spend authority — give it to a watch-only wallet. Sharing a viewing key is scoped, deliberate disclosure and cannot authorise a spend.
- Why can a user's real-world privacy be weak even when the shielded proof is flawless and keys are well managed?
- Two reasons outside the proof: the turnstile and usage. Crossing the transparent↔shielded boundary exposes an amount and a transparent address, so matching distinctive amounts/timing across the turnstile can correlate entry and exit; and the achieved anonymity set depends on how many people actually transact in the pool in an indistinguishable way. A flawless proof over a small or quiet shielded set hides little, so privacy is a function of shielded-pool usage (a measured, external quantity — UNKNOWN unless specifically measured), not of the cryptography alone.
References
- [1]Banco ZK — Vol. II/III (2025): ZK infrastructure — note commitments, commitment trees, nullifiers, anonymity set and the shielded deposit/spend flow — external educational reference
- [2]Zcash Protocol Specification (Sapling and Orchard shielded protocols; note commitments, nullifiers, commitment tree, key hierarchy) — public protocol documentation
- [3]Hopwood, Bowe, Hornby & Wilcox — Zcash / Zerocash line of work on shielded transactions, as widely documented
- [4]The Halo2 proving system (recursive/accumulation-based proofs without per-circuit trusted setup), as publicly documented
References point to public, well-known technical sources for further study.
Related articles
