Skip to main content

Background: From Zerocoin to Orchard

1. Why this chapter exists

halo2_proofs is a generic proving system; halo2_gadgets is a library of chips. Neither file mentions Zcash. But every design choice in halo2_gadgets, from the choice of Pallas + Vesta to the Sinsemilla hash to the lookup-range-check widths, was made to support the Orchard shielded pool. Without knowing what an Orchard note or a nullifier is, the reader has no way to judge why the ECC chip exposes mul_fixed_short, why Sinsemilla matters more than SHA-256 here, or why the specific constant MERKLE_CRH_PERSONALIZATION = "z.cash:Orchard-MerkleCRH" lives in sinsemilla/merkle.rs#L16.

This chapter is the bridge. It walks the lineage from the original Zerocoin protocol (2013), through Zerocash (2014), and into the three Zcash shielded pools (Sprout, Sapling, Orchard). It pins down the vocabulary used in chapters 14 (ECC) and 15 (hash gadgets), so when those chapters say "the chip computes a nullifier", you already know what a nullifier is and why it needs to be a curve point.

If you only ever read one section of this chapter, read Section 4: the primitive vocabulary.

2. Definitions

We use Zerocash / Zcash terminology throughout. Capital-script letters are curve points, lowercase italics are scalars.

Definition (Anonymous payment scheme). A protocol that lets a payer transfer value to a payee such that an observer of the ledger learns no more than is strictly necessary to verify consensus rules (typically: value conservation, no double-spending). The strongest schemes (Zerocash and successors) hide the amount, the sender, and the receiver of every shielded transaction.

Definition (Zerocoin). The first proposal for SNARK-free anonymous payments on top of a Bitcoin-style ledger, due to Miers, Garman, Green, and Rubin (IEEE S&P 2013 [PDF]). Uses an RSA accumulator and double-discrete-log proofs. Coins are fixed-denomination and not hidden in amount; only the linkage between mint and spend is hidden. Zcash does not implement Zerocoin; Zcash implements Zerocash, the SNARK-based successor.

Definition (Zerocash). The zk-SNARK-based successor of Zerocoin, due to Ben-Sasson, Chiesa, Garman, Green, Miers, Tromer, and Virza (IEEE S&P 2014) [PDF]. Hides amounts and addresses by representing payments as commitments ("notes") that are added to a Merkle tree; spends prove "I know the opening of a note in the tree" using a SNARK, and publish a deterministic nullifier per spent note to prevent double-spends. This is the construction Zcash inherited.

Definition (Shielded pool). A subset of UTXOs whose value lives behind the Zerocash construction. A wallet "shields" funds by spending a transparent UTXO and creating shielded notes; it "deshields" by spending shielded notes and creating transparent outputs. The pool is the set of notes whose commitments have been published and whose nullifiers have not.

Definition (Action). Orchard's atomic transaction primitive. An action is the symmetric combination of one note spend and one note output, with shared randomization. Sapling separated spends from outputs (SpendDescription and OutputDescription); Orchard fused them into a single ActionDescription, simplifying the circuit and the transaction format. See ZIP 224, "Orchard Shielded Protocol".

3. From Zerocoin to Orchard: the lineage

3.1 Zerocoin (2013)

  • Crypto: RSA accumulator, Schnorr-style double-discrete-log proofs.
  • What's hidden: the link between a mint and a spend (one-of-N anonymity over the entire mint set).
  • What's not hidden: the amount (each coin is a fixed denomination), and the fact that a shielded operation took place.
  • Cost: proof size around 25 KB per spend; verification fractions of a second.

Reference: Miers et al., 2013.

3.2 Zerocash (2014)

  • Crypto: Groth16-style preprocessing zk-SNARKs (later Pinocchio / BCTV14 / Groth16), an R1CS arithmetization, and a trusted setup.
  • What's hidden: amount, sender, receiver, and the link between input and output notes. Only the existence of a shielded transaction is public.
  • What's not hidden: transaction fee (in early Zcash), and any value transferred between shielded and transparent pools.
  • Cost: proof size around 300 bytes; verification under 10 ms.

Reference: Ben-Sasson et al., 2014.

3.3 Sprout (Zcash launch, Oct 2016)

The first deployed shielded pool. A direct implementation of Zerocash with a few Zcash-specific tweaks (JoinSplit transactions, note structure, address format).

AspectValue
Curvealt_bn128 (Barreto-Naehrig, 254-bit)
Proof systemBCTV14, later Groth16 via Sapling backport
Hash for commitmentsSHA-256 truncated to 256 bits
Tree commitment hashSHA-256-based PRF
Trusted setup"Powers of Tau" / "Sprout MPC", 6 participants
Transaction formatJoinSplit (2-in / 2-out per JoinSplit)
Spec sectionZcash Protocol Spec section 4

Sprout is in deprecation: new value cannot enter the pool, only exit. The Sprout circuit is in zcash/librustzcash; it is not a halo2 circuit.

3.4 Sapling (Network Upgrade 1, Oct 2018)

Redesigned for circuit efficiency and for separating spend-authorisation from note-decryption keys.

AspectValue
CurveBLS12-381 (proving) + JubJub (in-circuit)
Proof systemGroth16
Hash for commitmentsPedersen hash over JubJub
Tree commitment hashPedersen hash
Trusted setup"Sapling MPC", ~90 participants
Transaction formatSpendDescription (one per input) + OutputDescription (per output)
Spec sectionZcash Protocol Spec section 4 (Sapling parts)
Notable additionsDiversified addresses; full viewing keys; spend-authority signatures

Sapling is the most-used shielded pool at the time of writing. The Sapling circuit lives in zcash/sapling-crypto and its bellman-based proving code in the bellman library. The Sapling circuit is not a halo2 circuit.

3.5 Orchard (Network Upgrade 5, May 2022)

Redesigned to remove the trusted setup and to share infrastructure with the planned recursive proof system. This is where halo2 appears.

AspectValue
CurvesPallas + Vesta (the Pasta cycle)
Proof systemhalo2 PLONK + IPA (this repository)
Note commitment hashSinsemilla
Tree commitment hashMerkleCRH^Orchard (Sinsemilla)
Nullifier PRFPoseidon over Pallas
Trusted setupnone (transparent setup via Halo's IPA)
Transaction formatActionDescription (one per spend+output bundle)
Spec sectionZcash Protocol Spec section 4 (Orchard parts), ZIP 224

The Orchard circuit lives in zcash/orchard; the chips it uses live in halo2_gadgets (this repository). Chapters 14 and 15 of this course walk those chips one by one.

3.6 At a glance

PropertyZerocoinZerocashSproutSaplingOrchard
SNARKnoyesyes (BCTV14)yes (Groth16)yes (halo2 + IPA)
Trusted setupnoyes (per-pair)yes (MPC)yes (MPC)no
Amounts hiddennoyesyesyesyes
Addresses hiddennoyesyesyesyes
CurveRSAvariousalt_bn128BLS12-381Pallas / Vesta
Note hashn/aSHA-256SHA-256PedersenSinsemilla
Year20132014201620182022

4. The shielded-pool vocabulary

Every term below appears verbatim in the Orchard chip code. The formulas use Orchard notation; the Sapling analogues are similar modulo the choice of hash and curve.

4.1 Note

A record describing a single shielded UTXO. In Orchard, a note is the tuple note=(d,pkd,v,ρ,ψ,rcm)\mathsf{note} = (\mathsf{d}, \mathsf{pk_d}, v, \rho, \psi, \mathsf{rcm}) with:

  • d\mathsf{d}: an 11-byte diversifier (address randomization).
  • pkd\mathsf{pk_d}: the recipient's diversified transmission key, a Pallas point.
  • vv: the note value, a 64-bit unsigned integer.
  • ρ\rho: a 255-bit field element binding the note to a specific prior nullifier (this is what links action chains).
  • ψ\psi: a 255-bit "additional randomness" derived from ρ\rho and the random seed.
  • rcm\mathsf{rcm}: the commitment trapdoor, a 255-bit field element.

Sapling's note has the same shape but without an explicit ρ\rho or ψ\psi field; those role split differently.

4.2 Note commitment

A binding, hiding commitment to a note, published on chain:

cm  =  NoteCommitrcmOrchard(reprP(gd),  reprP(pkd),  v,  ρ,  ψ)\mathsf{cm} \;=\; \mathsf{NoteCommit}^{\mathsf{Orchard}}_{\mathsf{rcm}}\bigl(\mathsf{repr}_{\mathbb{P}}(\mathsf{g_d}),\; \mathsf{repr}_{\mathbb{P}}(\mathsf{pk_d}),\; v,\; \rho,\; \psi\bigr)

where gd\mathsf{g_d} is the diversifier base point (gd=DiversifyHash(d)\mathsf{g_d} = \mathrm{DiversifyHash}(\mathsf{d})) and NoteCommitOrchard\mathsf{NoteCommit}^{\mathsf{Orchard}} is a SinsemillaCommit (Zcash Protocol Spec section 5.4.8.4). Implemented in the Orchard circuit using halo2_gadgets::sinsemilla.

4.3 Note commitment tree and anchor

Note commitments are appended to an incremental Merkle tree of fixed depth MERKLE_DEPTH_ORCHARD = 32:

halo2_gadgets/src/sinsemilla/merkle.rs

/// SWU hash-to-curve personalization for the Merkle CRH generator
pub const MERKLE_CRH_PERSONALIZATION: &str = "z.cash:Orchard-MerkleCRH";

/// Instructions to check the validity of a Merkle path of a given `PATH_LENGTH`.

The hash at each layer is MerkleCRH^Orchard, a 510-bit-input Sinsemilla hash; the personalization string above is the canonical domain tag the Orchard spec mandates. The anchor is the root of this tree at some past block height; an Orchard spend proves membership of one of the leaves under the anchor without revealing which.

4.4 Nullifier

The unique, unlinkable identifier of a spent note:

nf  =  ExtractP([PRFnknfOrchard(ρ)+ψmodp]KOrchard  +  cm)\mathsf{nf} \;=\; \mathrm{Extract}_{\mathbb{P}}\bigl([\mathrm{PRF}^{\mathsf{nfOrchard}}_{\mathsf{nk}}(\rho) + \psi \bmod p]\, K^{\mathsf{Orchard}} \;+\; \mathsf{cm}\bigr)

where:

  • nk\mathsf{nk} is the nullifier-deriving key (private to the spender).
  • PRFnfOrchard\mathrm{PRF}^{\mathsf{nfOrchard}} is implemented with Poseidon (Zcash Protocol Spec section 5.4.2).
  • KOrchardK^{\mathsf{Orchard}} is a fixed base point.
  • ExtractP\mathrm{Extract}_{\mathbb{P}} extracts the xx-coordinate.

Publishing nf\mathsf{nf} on chain marks the note as spent. Without nk\mathsf{nk}, an observer cannot compute nf\mathsf{nf} from cm\mathsf{cm}; with nk\mathsf{nk}, the wallet can. This is what makes shielded chains both unlinkable and double-spend-safe.

4.5 Value commitment

A Pedersen commitment to the note value, in Pallas:

cv  =  [v]VOrchard  +  [rcv]ROrchard.\mathsf{cv} \;=\; [v]\,\mathcal{V}^{\mathsf{Orchard}} \;+\; [\mathsf{rcv}]\,\mathcal{R}^{\mathsf{Orchard}}.

Action descriptions publish cv\mathsf{cv} for inputs and outputs. Consensus checks that the sum of input value commitments minus the sum of output commitments equals [vbalance]V+[0]R[v_{\mathsf{balance}}] \mathcal{V} + [0] \mathcal{R} for the declared net value transferred between the shielded and transparent pools, which is enough to verify amount conservation without learning individual values.

4.6 Diversifier and ivk

The full viewing key derives a per-address diversifier d\mathsf{d} that randomizes the address without changing the underlying spending key. The incoming viewing key is

ivk  =  Commitrivkivk(ak,  nk)\mathsf{ivk} \;=\; \mathrm{Commit}^{\mathsf{ivk}}_{\mathsf{rivk}}(\mathsf{ak},\; \mathsf{nk})

where ak\mathsf{ak} is the spend-authorization public key and Commitivk\mathsf{Commit}^{\mathsf{ivk}} is a SinsemillaCommit.

4.7 Action

The Orchard transaction primitive. An ActionDescription bundles:

  • The input note's nullifier nfold\mathsf{nf_{old}}.
  • The output note's commitment cmnew\mathsf{cm_{new}}.
  • An input value commitment cvold\mathsf{cv_{old}} and an output cvnew\mathsf{cv_{new}}.
  • A randomized spend-auth verification key rk\mathsf{rk}.
  • An encrypted ciphertext containing the new note (so the recipient can decrypt with their ivk).
  • A halo2 proof that all of the above are consistent with some (anchor,vbalance,...)(\mathsf{anchor}, v_{\mathsf{balance}}, ...) public input.

The Orchard circuit is the predicate that proof certifies. It is defined in zcash/orchard/src/circuit.rs and is the largest known consumer of halo2_gadgets.

For the on-wire shape of an action (which fields are serialized, in what order, and how the action differs from a Sapling SpendDescription / OutputDescription or a Sprout JoinSplit), see the dedicated Orchard Transaction Format chapter.

5. How the halo2_gadgets chips map to Orchard primitives

Orchard primitiveHalo2 chip / gadget
NoteCommit^Orchardhalo2_gadgets::sinsemilla::CommitDomain (Sinsemilla commit)
MerkleCRH^Orchardhalo2_gadgets::sinsemilla::merkle::MerklePath
PRF^nfOrchardhalo2_gadgets::poseidon::Hash (Pow5Chip)
Commit^ivkhalo2_gadgets::sinsemilla::CommitDomain
Value commitment [v]V + [r]Rhalo2_gadgets::ecc::EccChip (variable + fixed-base scalar mul)
DiversifyHash(Group-hash-to-curve, outside the in-circuit chip set)
Extract_Phalo2_gadgets::ecc::X (x-coordinate extraction)
Spend-auth randomizationhalo2_gadgets::ecc::EccChip::mul
Range checks on vv, ρ\rho, etc.halo2_gadgets::utilities::lookup_range_check
Mixing^{Pedersen} (Sapling)(Sapling only; not in halo2_gadgets)

The bottom four rows of this table are why the ECC chip (chapter 14) has the exact API it has. The top three rows are why the hash gadgets chapter (chapter 15) emphasizes Sinsemilla and Poseidon, rather than the more familiar SHA-256.

6. Failure modes

  • Cross-pool confusion. Sprout, Sapling, and Orchard each have their own note structure, hash family, and trusted-setup status. A circuit author working on Orchard must not import Sapling personalization strings (or vice versa). The personalization is what binds a commitment to its pool; reusing it would let a Sapling note replay into the Orchard tree.
  • Underrange nullifier scalar. The scalar PRFnknfOrchard(ρ)+ψ\mathrm{PRF}^{\mathsf{nfOrchard}}_{\mathsf{nk}}(\rho) + \psi is reduced modulo the Pallas scalar field. Failing to range-check the addends in-circuit can produce two distinct (nk,ρ,ψ)(\mathsf{nk}, \rho, \psi) tuples with the same scalar, which collides nullifiers and enables double-spends. This is the canonical class of bug that audits target.
  • Reusing ρ\rho across actions. ρ\rho is supposed to be derived from the previous action's nullifier, making the nullifier chain injective. A circuit that does not enforce the derivation rule lets a malicious prover create two notes with the same ρ\rho and therefore the same nullifier.
  • Wrong Merkle depth. Sapling uses depth 32, Orchard uses depth 32. Sprout used 29. A mismatched constant breaks anchor verification.

7. Spec pointers

Primary sources, in order of relevance to the gadgets in this repository:

  • Zcash Protocol Specification (the normative reference for Sprout, Sapling, and Orchard primitives): zips.z.cash/protocol/protocol.pdf. Section 4 ("Abstract Protocol") and section 5 ("Concrete Protocol") are the load-bearing chapters. The Orchard "MerkleCRH" subsection is at #orchardmerklecrh; Orchard nullifier construction is #commitmentsandnullifiers.
  • ZIP 224 "Orchard Shielded Protocol": zips.z.cash/zip-0224. The consensus rules and transaction format.
  • ZIP 225 "Version 5 Transaction Format": zips.z.cash/zip-0225. The serialized form of Orchard transactions, including ActionDescription.
  • Zerocoin paper (Miers, Garman, Green, Rubin; IEEE S&P 2013): PDF. Historical precursor; halo2 does not implement Zerocoin.
  • Zerocash paper (Ben-Sasson et al.; IEEE S&P 2014): PDF. The construction Sprout and its successors inherit.
  • Halo paper (Bowe, Grigg, Hopwood; eprint 2019/1021): eprint 2019/1021. The IPA
    • accumulator construction that lets Orchard avoid a trusted setup.
  • Sinsemilla design: Halo 2 Book "Sinsemilla".
  • Orchard circuit reference: zcash/orchard src/circuit.rs composes the gadgets from this repository into the action predicate.

8. Exercises

  1. Take any Sinsemilla constant in halo2_gadgets/src/sinsemilla/merkle.rs and find the corresponding line in the Zcash Protocol Specification (#orchardmerklecrh). Confirm they agree.
  2. Open zcash/orchard/src/circuit.rs and identify which halo2_gadgets import is used to compute the nullifier. Trace it back to the chip in this repository.
  3. From the diagram in section 5, name one Orchard primitive that does not have a chip in halo2_gadgets. Why is that? (Hint: out-of-circuit primitives.)
  4. Read section 4.2 of the Zcash Protocol Spec and identify exactly which information about a spent note is revealed on chain (i.e. appears in clear in an ActionDescription). Use this to argue that the nullifier is unlinkable to the commitment for an observer without the spending key.

Answers in the code

  • Exercise 1: the MERKLE_CRH_PERSONALIZATION constant in halo2_gadgets/src/sinsemilla/merkle.rs#L16 matches the personalization in the spec's section "Sinsemilla hashes and commitments".
  • Exercise 3: DiversifyHash is an out-of-circuit primitive (the receiver computes their own diversifier off-chain); only the resulting gd\mathsf{g_d} point appears as a witness.

9. Further reading