Skip to main content

04 - Sprout and Sapling

1. Why this chapter exists

Sprout is the original Zerocash protocol embedded in Zcash at launch; it is historically important but largely frozen. Sapling is the production shielded pool and the locus of almost everything in zcash_primitives, zcash_proofs, and zcash_keys. A reader who cannot state the Spend statement from memory will not be able to follow the builder code in zcash_primitives/src/transaction/builder.rs or audit any change to it. By the end of this chapter you will be able to map every field of a SpendDescription and OutputDescription to its mathematical role and locate it in zcash_primitives/src/transaction/components/sapling.rs.

This chapter is the narrative introduction. For the authoritative symbol-by-symbol reference of every key (ask,nsk,ak,nk,ivk,ovk,dk,esk,epk,\mathsf{ask}, \mathsf{nsk}, \mathsf{ak}, \mathsf{nk}, \mathsf{ivk}, \mathsf{ovk}, \mathsf{dk}, \mathsf{esk}, \mathsf{epk}, \ldots), see chapter 23 - The complete key catalog. For the clause-by-clause walk of the Spend and Output circuits with constraint counts, see chapter 24 - Circuits, constraint by constraint.

2. Definitions

Sprout

Definition 2.1 (Sprout note). A Sprout note is a tuple

note  =  (apk,v,ρ,r)    {0,1}256×[0,264)×{0,1}256×{0,1}256,\mathsf{note} \;=\; (a_{\mathsf{pk}}, v, \rho, r) \;\in\; \{0,1\}^{256} \times [0, 2^{64}) \times \{0,1\}^{256} \times \{0,1\}^{256},

where apka_{\mathsf{pk}} is the recipient paying key, vv is the value in zatoshis, ρ{0,1}256\rho \in \{0,1\}^{256} is a uniqueness nonce, and r{0,1}256r \in \{0,1\}^{256} is commitment randomness. Its commitment is the 32-byte string

cm  =  SHA256(0xb0apkvρr)    {0,1}256.\mathsf{cm} \;=\; \mathsf{SHA256}\bigl( \mathrm{0xb0} \mathbin{\|} a_{\mathsf{pk}} \mathbin{\|} v \mathbin{\|} \rho \mathbin{\|} r \bigr) \;\in\; \{0,1\}^{256}.

Definition 2.2 (JoinSplit description). A JoinSplit description is a constant-shape gadget consuming 2 input notes and producing 2 output notes, together with public scalars vpubold,vpubnew[0,264)v_{\text{pub}}^{\text{old}}, v_{\text{pub}}^{\text{new}} \in [0, 2^{64}) encoding transparent inflow and outflow, a public Merkle root rt{0,1}256\mathsf{rt} \in \{0,1\}^{256} (anchor), and a public per-JoinSplit signature digest hsig{0,1}256h_{\mathsf{sig}} \in \{0,1\}^{256} binding the JoinSplit to a fixed transaction context.

Sapling

Definition 2.3 (Pedersen hash, PH\mathsf{PH}). Let GJ\mathbb{G}_J denote the Jubjub prime-order subgroup. The Sapling Pedersen hash PH ⁣:{0,1}GJ\mathsf{PH}\colon \{0,1\}^{*} \to \mathbb{G}_J is defined as follows. Pad the input bit string to a length that is a multiple of 33, group bits into chunks of 33 and segments of c=63c = 63 chunks (189189 bits per segment), encode each chunk (b0,b1,b2){0,1}3(b_0, b_1, b_2) \in \{0,1\}^3 as the signed integer

enc3(b0,b1,b2)  =  (1+b0+2b1)(12b2)    {4,,1,1,,4}Fr,\mathrm{enc}_3(b_0, b_1, b_2) \;=\; (1 + b_0 + 2 b_1)(1 - 2 b_2) \;\in\; \{-4, \ldots, -1, 1, \ldots, 4\} \subseteq \mathbb{F}_r,

multiply each segment sjFrs_j \in \mathbb{F}_r by an independent generator GjGJG_j \in \mathbb{G}_J derived deterministically from a domain-separation string, and return j[sj]Gj\sum_j [s_j] G_j.

Definition 2.4 (Sapling note commitment). For rcmFr\mathsf{rcm} \in \mathbb{F}_r, v[0,264)v \in [0, 2^{64}), and gd,pkdGJg_d, \mathsf{pk}_d \in \mathbb{G}_J,

NoteCommit(rcm,v,gd,pkd)  =  PH(Dnc,repr(v)repr(gd)repr(pkd))  +  [rcm]Rnc    GJ,\mathsf{NoteCommit}(\mathsf{rcm}, v, g_d, \mathsf{pk}_d) \;=\; \mathsf{PH}\bigl(D_{\text{nc}},\, \mathrm{repr}(v) \mathbin{\|} \mathrm{repr}(g_d) \mathbin{\|} \mathrm{repr}(\mathsf{pk}_d)\bigr) \;+\; [\mathsf{rcm}]\, R_{\text{nc}} \;\in\; \mathbb{G}_J,

where RncGJR_{\text{nc}} \in \mathbb{G}_J is a fixed generator with logGJubjubRnc\log_{G_{\text{Jubjub}}} R_{\text{nc}} unknown.

Lemma 2.5 (Sapling note commitment is perfectly hiding and computationally binding). NoteCommit\mathsf{NoteCommit} is perfectly hiding over the randomness \mathsf{rcm} \stackrel{\}{\leftarrow} \mathbb{F}_randcomputationallybindingunderDLPinand computationally binding under DLP in\mathbb{G}_J$.

Proof sketch. The map rcm[rcm]Rnc\mathsf{rcm} \mapsto [\mathsf{rcm}] R_{\text{nc}} is a bijection on GJ\mathbb{G}_J, so the distribution of NoteCommit(rcm,v,gd,pkd)\mathsf{NoteCommit}(\mathsf{rcm}, v, g_d, \mathsf{pk}_d) for uniform rcm\mathsf{rcm} is uniform on GJ\mathbb{G}_J, independent of (v,gd,pkd)(v, g_d, \mathsf{pk}_d). Binding reduces to collision resistance of PH\mathsf{PH} plus knowledge of logRnc\log R_{\text{nc}} in the base GJubjubG_{\text{Jubjub}}, both following from DLP in GJ\mathbb{G}_J. See [Zcash Protocol Specification, section 5.4.7].

Definition 2.6 (Sapling Merkle hash). Let {0,,31}\ell \in \{0, \ldots, 31\} denote the Merkle layer index. Define

MerkleHash(,xleft,xright)  =  ExtractJubjub(PH(DMH,,  xleftxright))    Fr,\mathsf{MerkleHash}(\ell, x_{\text{left}}, x_{\text{right}}) \;=\; \mathsf{ExtractJubjub}\bigl( \mathsf{PH}(D_{\text{MH}, \ell},\; x_{\text{left}} \mathbin{\|} x_{\text{right}}) \bigr) \;\in\; \mathbb{F}_r,

where ExtractJubjub ⁣:GJFr\mathsf{ExtractJubjub}\colon \mathbb{G}_J \to \mathbb{F}_r returns the uu-coordinate of its argument. The layer-indexed domain separation string DMH,D_{\text{MH}, \ell} prevents tree-rotation attacks because the same byte string used at two distinct layers hashes to two distinct outputs.

Definition 2.7 (Sapling key tree). From a spending key sk{0,1}256\mathsf{sk} \in \{0,1\}^{256}, the Sapling key tree is the sequence of derivations

sk    PRFexpand    (ask,nsk,ovk)Fr×Fr×{0,1}256          (ak,nk,ovk)GJ×GJ×{0,1}256          ivkFr          (pkd,gd)GJ×GJ.\mathsf{sk} \;\xrightarrow{\;\mathsf{PRF}^{\text{expand}}\;}\; (\mathsf{ask}, \mathsf{nsk}, \mathsf{ovk}) \in \mathbb{F}_r \times \mathbb{F}_r \times \{0,1\}^{256} \;\xrightarrow{\;\;\;}\; (\mathsf{ak}, \mathsf{nk}, \mathsf{ovk}) \in \mathbb{G}_J \times \mathbb{G}_J \times \{0,1\}^{256} \;\xrightarrow{\;\;\;}\; \mathsf{ivk} \in \mathbb{F}_r \;\xrightarrow{\;\;\;}\; (\mathsf{pk}_d, g_d) \in \mathbb{G}_J \times \mathbb{G}_J.

The full viewing key fvk=(ak,nk,ovk)GJ×GJ×{0,1}256\mathsf{fvk} = (\mathsf{ak}, \mathsf{nk}, \mathsf{ovk}) \in \mathbb{G}_J \times \mathbb{G}_J \times \{0,1\}^{256} is sufficient to decrypt every incoming and outgoing-tagged note spendable under sk\mathsf{sk}.

Definition 2.8 (Sapling nullifier). For a Sapling note with commitment cmGJ\mathsf{cm} \in \mathbb{G}_J, tree position pos[0,232)\mathsf{pos} \in [0, 2^{32}), mixing input ρ=MixingPedersenHash(cm,pos)GJ\rho = \mathsf{MixingPedersenHash}(\mathsf{cm}, \mathsf{pos}) \in \mathbb{G}_J, and key nkGJ\mathsf{nk} \in \mathbb{G}_J, the nullifier is

nf  =  PRFnknfSapling(ρ)    {0,1}256.\mathsf{nf} \;=\; \mathsf{PRF}^{\mathsf{nfSapling}}_{\mathsf{nk}}(\rho) \;\in\; \{0,1\}^{256}.

Invariant 2.9 (Sapling binding equation). Let B\mathcal{B} be a Sapling bundle with input value commitments {cviin}i\{\mathsf{cv}_i^{\text{in}}\}_i, output value commitments {cvjout}j\{\mathsf{cv}_j^{\text{out}}\}_j, and value balance vbal[(2631),263)v_{\text{bal}} \in [-(2^{63}-1), 2^{63}). For V,RGJV, R \in \mathbb{G}_J fixed value-commitment generators and $r_{\text{bal}} = \sum_i \mathsf{rcv}_i^{\text{in}}

  • \sum_j \mathsf{rcv}_j^{\text{out}} \in \mathbb{F}_r,, \mathcal{B}$ satisfies
icviin    jcvjout  =  [vbal]V  +  [rbal]R    GJ.\sum_i \mathsf{cv}_i^{\text{in}} \;-\; \sum_j \mathsf{cv}_j^{\text{out}} \;=\; [v_{\text{bal}}]V \;+\; [r_{\text{bal}}]R \;\in\; \mathbb{G}_J.

The binding signature certifies knowledge of rbalr_{\text{bal}}, which is feasible (under DLP in GJ\mathbb{G}_J) only when the equation holds.

The NP relations proven by Sapling

The Sapling proving system produces one Groth16 proof per Spend and one per Output. Each proof attests membership in an NP language defined by an explicit relation RX×WR \subseteq \mathcal{X} \times \mathcal{W}.

Definition 2.10 (Sapling Spend relation RSpendR_{\mathsf{Spend}}). Let Fr\mathbb{F}_r be the BLS12-381 scalar field, GJ\mathbb{G}_J the Jubjub prime-order subgroup, and T\mathcal{T} the Merkle-tree domain. Define

XSpend  =  (rt,cv,nf,rk)    T×GJ×{0,1}256×GJ,\mathcal{X}_{\mathsf{Spend}} \;=\; \bigl(\mathsf{rt}, \mathsf{cv}, \mathsf{nf}, \mathsf{rk}\bigr) \;\in\; \mathcal{T} \times \mathbb{G}_J \times \{0,1\}^{256} \times \mathbb{G}_J, WSpend  =  (d,pkd,v,rcm,rcv,α,ak,nsk,pos,path).\mathcal{W}_{\mathsf{Spend}} \;=\; \bigl(d, \mathsf{pk}_d, v, \mathsf{rcm}, \mathsf{rcv}, \alpha, \mathsf{ak}, \mathsf{nsk}, \mathsf{pos}, \mathsf{path}\bigr).

Then (x,w)RSpend(x, w) \in R_{\mathsf{Spend}} iff all of the following hold:

  1. Note well-formedness. With gd=GroupHash(d)g_d = \mathsf{GroupHash}(d), gdOg_d \neq \mathcal{O}, gdGJg_d \in \mathbb{G}_J, and pkdGJ\mathsf{pk}_d \in \mathbb{G}_J.
  2. Commitment. Let cm=NoteCommit(rcm,v,gd,pkd)\mathsf{cm} = \mathsf{NoteCommit}(\mathsf{rcm}, v, g_d, \mathsf{pk}_d).
  3. Merkle membership. MerklePath(path,pos,ExtractJubjub(cm))=rt\mathsf{MerklePath}(\mathsf{path}, \mathsf{pos}, \mathsf{ExtractJubjub}(\mathsf{cm})) = \mathsf{rt}.
  4. Value commitment. cv=[v]V+[rcv]R\mathsf{cv} = [v]V + [\mathsf{rcv}]R.
  5. Spend authority. ak=[ask]Ga\mathsf{ak} = [\mathsf{ask}]G_{\text{a}} for some ask\mathsf{ask} known to the prover, and rk=ak+[α]Ga\mathsf{rk} = \mathsf{ak} + [\alpha]G_{\text{a}} where α\alpha is the re-randomiser.
  6. Nullifier integrity. With nk=[nsk]Gn\mathsf{nk} = [\mathsf{nsk}]G_{\text{n}} and ρ=MixingPedersenHash(cm,pos)\rho = \mathsf{MixingPedersenHash}(\mathsf{cm}, \mathsf{pos}), nf=PRFnknfSapling(ρ)\mathsf{nf} = \mathsf{PRF}^{\mathsf{nfSapling}}_{\mathsf{nk}} (\rho).
  7. Diversified address. pkd=[ivk]gd\mathsf{pk}_d = [\mathsf{ivk}] g_d where ivk=CRHivk(ak,nk)\mathsf{ivk} = \mathsf{CRH}^{\mathsf{ivk}}(\mathsf{ak}, \mathsf{nk}).
  8. Value range. v[0,264)v \in [0, 2^{64}) and vv admits a 64-bit little-endian binary expansion as part of the witness.

The circuit module enforcing these clauses lives in sapling-crypto::circuit::spend. Soundness: under the q-PKE and q-power-DH assumptions in the BLS12-381 bilinear group ([Groth 2016, Theorem 2]).

Definition 2.11 (Sapling Output relation ROutputR_{\mathsf{Output}}). Let

XOutput  =  (cv,cmu,epk)    GJ×Fr×GJ,\mathcal{X}_{\mathsf{Output}} \;=\; \bigl(\mathsf{cv}, \mathsf{cm}_u, \mathsf{epk}\bigr) \;\in\; \mathbb{G}_J \times \mathbb{F}_r \times \mathbb{G}_J, WOutput  =  (d,pkd,v,rcm,rcv,esk).\mathcal{W}_{\mathsf{Output}} \;=\; \bigl(d, \mathsf{pk}_d, v, \mathsf{rcm}, \mathsf{rcv}, \mathsf{esk}\bigr).

Then (x,w)ROutput(x, w) \in R_{\mathsf{Output}} iff:

  1. gd=GroupHash(d)g_d = \mathsf{GroupHash}(d), gdOg_d \neq \mathcal{O}, and pkdGJ\mathsf{pk}_d \in \mathbb{G}_J.
  2. cv=[v]V+[rcv]R\mathsf{cv} = [v]V + [\mathsf{rcv}]R.
  3. cmu=ExtractJubjub(NoteCommit(rcm,v,gd,pkd))\mathsf{cm}_u = \mathsf{ExtractJubjub}( \mathsf{NoteCommit}(\mathsf{rcm}, v, g_d, \mathsf{pk}_d)).
  4. epk=[esk]gd\mathsf{epk} = [\mathsf{esk}]\, g_d.
  5. v[0,264)v \in [0, 2^{64}).

The circuit module is sapling-crypto::circuit::output.

Lemma 2.12 (extraction injectivity). For P,QGJP, Q \in \mathbb{G}_J, ExtractJubjub(P)=ExtractJubjub(Q)\mathsf{ExtractJubjub}(P) = \mathsf{ExtractJubjub}(Q) implies P=QP = Q or P=QP = -Q. In particular, for inputs constrained to GJ\mathbb{G}_J together with the parity bit, the uu-coordinate uniquely identifies a Jubjub point.

Proof sketch. For a twisted Edwards curve u2+v2=1+du2v2-u^2 + v^2 = 1 + d u^2 v^2 over Fr\mathbb{F}_r, the substitution uuu \mapsto -u leaves the equation invariant, so points with a given uu-coordinate differ only in the sign of uu (equivalently, by negation in the group law). Two distinct points sharing both uu and the parity of vv would coincide. The clauses of RSpendR_{\mathsf{Spend}} and ROutputR_{\mathsf{Output}} enforce membership in GJ\mathbb{G}_J, which excludes 2-torsion outside the prime-order subgroup. See [Zcash Protocol Specification, section 5.4.9.1].

3. The code

3.1 Sprout: a one-page tour

The Sprout circuit lives in zcash_proofs/src/circuit/sprout/mod.rs (the "hybrid Sprout" implementation re-encoded for Groth16 instead of the original BCTV14 system). Its top-level types pin the shape:

zcash_proofs/src/circuit/sprout/mod.rs
loading...

The synthesize method enforces, for each input ii:

  1. Recompute cmi\mathsf{cm}_i from the witnessed note.
  2. Verify a Merkle path of depth 29 from cmi\mathsf{cm}_i to the public anchor rt\mathsf{rt} (with the witnessed authentication path).
  3. Derive the paying key apk=PRFaskaddr(0)a_{\mathsf{pk}} = \mathsf{PRF}^{addr}_{a_{\mathsf{sk}}}(0).
  4. Compute the nullifier nfi=PRFasknf(ρi)\mathsf{nf}_i = \mathsf{PRF}^{\mathsf{nf}}_{a_{\mathsf{sk}}}(\rho_i).
  5. Compute the "hih_i tag" hi=PRFaskpk(i,hsig)h_i = \mathsf{PRF}^{pk}_{a_{\mathsf{sk}}}(i, h_{\mathsf{sig}}) that binds the JoinSplit to a specific hsigh_{\mathsf{sig}}.

For each output jj it derives a fresh ρj\rho_j from ϕ\phi and indices and recomputes the commitment.

Finally it enforces the balance equation

vpubold+v1+v2  =  vpubnew+v1+v2,v_{\text{pub}}^{\text{old}} + v_1 + v_2 \;=\; v_{\text{pub}}^{\text{new}} + v'_1 + v'_2,

with each vi,vj[0,264)v_i, v'_j \in [0, 2^{64}) enforced via boolean range constraints.

The PRFs are all "SHA-256 with a tag prefix", for example

PRFasknf(ρ)=SHA256 ⁣(1110askρ),\mathsf{PRF}^{\mathsf{nf}}_{a_{\mathsf{sk}}}(\rho) = \mathsf{SHA256}\!\bigl( 1110 \,\|\, a_{\mathsf{sk}} \,\|\, \rho \bigr),

where 1110 is a 4-bit tag.

The Sprout-Groth16 proving key is 64 MB (sprout-groth16.params, SHA-256 in zcash_proofs/src/lib.rs#L52). Verification is a single Groth16 pairing equation.

Sprout is closed to new outputs since NU5. The code remains for historical sweeps from old Sprout balances; treat it as legacy.

3.2 Sapling: curves and parameters

  • Fr\mathbb{F}_r: the scalar field of BLS12-381, r2255r \approx 2^{255}, a 255-bit prime.

  • Fq\mathbb{F}_q: the base field of Jubjub. Equal to Fr\mathbb{F}_r.

  • Jubjub is a twisted Edwards curve over Fr\mathbb{F}_r,

    x2+y2  =  1  +  dx2y2,d=1024010241, -x^2 + y^2 \;=\; 1 \;+\; d\, x^2 y^2, \qquad d = -\frac{10240}{10241},

    with a prime-order subgroup of order

    =6554484396890773809930967563523245729705921265872317281365359162392183254199. \ell = 6\,554\,484\,396\,890\,773\,809\,930\,967\,563\,523\,245\,729\,705\,921\,265\,872\,317\,281\,365\,359\,162\,392\,183\,254\,199.
  • GJubjubG_{\text{Jubjub}} is the fixed generator of that subgroup.

Why this curve? Twisted Edwards arithmetic is strongly unified (the same formula for addition and doubling), which keeps the in-circuit constraint count small. Because Fr\mathbb{F}_r is the SNARK scalar field, a Jubjub scalar mul costs only roughly 750 R1CS constraints per operation.

3.3 The Sapling key tree

From a 32-byte spending key sk\mathsf{sk}:

  1. Derive

    ask=ToScalar ⁣(PRFskexpand(0)),\mathsf{ask} = \mathsf{ToScalar}\!\bigl( \mathsf{PRF}^{\text{expand}}_{\mathsf{sk}}(0)\bigr), nsk=ToScalar ⁣(PRFskexpand(1)),\mathsf{nsk} = \mathsf{ToScalar}\!\bigl( \mathsf{PRF}^{\text{expand}}_{\mathsf{sk}}(1)\bigr), ovk=PRFskexpand(2)[0..32],\mathsf{ovk} = \mathsf{PRF}^{\text{expand}}_{\mathsf{sk}}(2)[\,0..32\,],

    where ToScalar\mathsf{ToScalar} reduces a 64-byte string modulo \ell.

  2. Compute the public points

    ak=[ask]GSaplingak,nk=[nsk]GSaplingnk,\mathsf{ak} = [\mathsf{ask}] G_{\text{Sapling}}^{\text{ak}}, \qquad \mathsf{nk} = [\mathsf{nsk}] G_{\text{Sapling}}^{\text{nk}},

    with GSaplingakG_{\text{Sapling}}^{\text{ak}} and GSaplingnkG_{\text{Sapling}}^{\text{nk}} distinct fixed generators on Jubjub.

  3. Compute the incoming viewing key

    ivk=CRHivk(ak,nk)  mod  ,\mathsf{ivk} = \mathsf{CRH}^{\mathsf{ivk}}(\mathsf{ak}, \mathsf{nk}) \;\bmod\; \ell,

    where CRHivk\mathsf{CRH}^{\mathsf{ivk}} is BLAKE2s with personalisation "Zcashivk".

  4. For each 11-byte diversifier d{0,1}88d \in \{0,1\}^{88}, compute gd=DiversifyHash(d)g_d = \mathsf{DiversifyHash}(d), a hash-to-curve into Jubjub. Not every dd yields a valid prime-order point; if it does not, the diversifier is invalid and skipped. The diversified transmission key is

    pkd=[ivk]gd.\mathsf{pk}_d = [\mathsf{ivk}] g_d.
  5. A Sapling payment address is the pair (d,pkd)(d, \mathsf{pk}_d) encoded as 43 plaintext bytes then bech32 with HRP zs.

Diversified addresses follow: each ivk\mathsf{ivk} generates infinitely many payment addresses sharing the same viewing key. A wallet can hand out a fresh dd to every counterparty without revealing the common ivk\mathsf{ivk}.

3.4 Spend description (math)

A Sapling SpendDescription is the tuple

SD  =  (cv,anchor,nf,rk,πSpend,σspendAuth).\mathsf{SD} \;=\; (\mathsf{cv}, \mathsf{anchor}, \mathsf{nf}, \mathsf{rk}, \pi_{\text{Spend}}, \sigma_{\text{spendAuth}}).
  • cv=[v]V+[rcv]R\mathsf{cv} = [v]V + [\mathsf{rcv}]R is the value commitment to the spent note's value with fresh randomness rcv\mathsf{rcv}.
  • anchor\mathsf{anchor} is the Merkle root used for membership.
  • nf\mathsf{nf} is the nullifier.
  • rk=ak+[α]Gak\mathsf{rk} = \mathsf{ak} + [\alpha] G^{\mathsf{ak}} is the re-randomised spend-authority public key.
  • πSpend\pi_{\text{Spend}} is the Groth16 proof.
  • σspendAuth\sigma_{\text{spendAuth}} is a RedJubjub signature under rsk=ask+α\mathsf{rsk} = \mathsf{ask} + \alpha over the sighash of the transaction.

The wire format is implemented by read_spend_v4 (Sapling v4 transactions, full per-spend signature and proof) and read_spend_v5 (Sapling v5 transactions, sigs and proofs are factored to the end of the bundle):

zcash_primitives/src/transaction/components/sapling.rs
loading...

The Spend statement (what the circuit enforces): the prover knows (v,gd,pkd,rcm,α,ak,nsk,auth-path)(v, g_d, \mathsf{pk}_d, \mathsf{rcm}, \alpha, \mathsf{ak}, \mathsf{nsk}, \text{auth-path}) such that

  1. cm=NoteCommit(rcm,v,gd,pkd)\mathsf{cm} = \mathsf{NoteCommit}(\mathsf{rcm}, v, g_d, \mathsf{pk}_d).
  2. The Merkle path proves cm\mathsf{cm} is in the tree with root anchor\mathsf{anchor}. Special case: if v=0v = 0 the path check is skipped, allowing "dummy" spends used to mask the input count.
  3. ivk=CRHivk(ak,nk)\mathsf{ivk} = \mathsf{CRH}^{\mathsf{ivk}}(\mathsf{ak}, \mathsf{nk}) where nk=[nsk]Gnk\mathsf{nk} = [\mathsf{nsk}] G^{\mathsf{nk}}.
  4. pkd=[ivk]gd\mathsf{pk}_d = [\mathsf{ivk}] g_d (the spender owns this address).
  5. rk=ak+[α]Gak\mathsf{rk} = \mathsf{ak} + [\alpha] G^{\mathsf{ak}}.
  6. nf=PRFnknfSapling(MixingPedersenHash(cm,pos))\mathsf{nf} = \mathsf{PRF}^{\mathsf{nfSapling}}_{\mathsf{nk}}\bigl( \mathsf{MixingPedersenHash}(\mathsf{cm}, \mathsf{pos})\bigr).
  7. cv=[v]V+[rcv]R\mathsf{cv} = [v]V + [\mathsf{rcv}]R for known rcv\mathsf{rcv}.
  8. v[0,264)v \in [0, 2^{64}).

Public inputs: cv,anchor,nf,rk\mathsf{cv}, \mathsf{anchor}, \mathsf{nf}, \mathsf{rk}.

3.5 Output description (math)

An OutputDescription is

OD  =  (cv,cmu,epk,Cenc,Cout,πOutput).\mathsf{OD} \;=\; (\mathsf{cv}, \mathsf{cm}^u, \mathsf{epk}, C^{\text{enc}}, C^{\text{out}}, \pi_{\text{Output}}).
  • cv\mathsf{cv}: value commitment of the new note.
  • cmu\mathsf{cm}^u: the uu-coordinate of the new note's commitment. (The full commitment is recoverable; only the uu-coordinate is published to save space.)
  • epk=[esk]gd\mathsf{epk} = [\mathsf{esk}] g_d: ephemeral public key for ECDH note encryption.
  • CencC^{\text{enc}}: the encrypted note plaintext (recipient, value, rcm\mathsf{rcm}, memo).
  • CoutC^{\text{out}}: the outgoing ciphertext that lets the sender recover the plaintext using ovk\mathsf{ovk}.
  • πOutput\pi_{\text{Output}}: the Groth16 proof.

The Output statement: the prover knows (v,gd,pkd,rcm,rcv,esk)(v, g_d, \mathsf{pk}_d, \mathsf{rcm}, \mathsf{rcv}, \mathsf{esk}) such that

  1. cm=NoteCommit(rcm,v,gd,pkd)\mathsf{cm} = \mathsf{NoteCommit}(\mathsf{rcm}, v, g_d, \mathsf{pk}_d) and cmu\mathsf{cm}^u is its uu-coordinate.
  2. cv=[v]V+[rcv]R\mathsf{cv} = [v]V + [\mathsf{rcv}]R.
  3. epk=[esk]gd\mathsf{epk} = [\mathsf{esk}] g_d.
  4. v[0,264)v \in [0, 2^{64}).
  5. gdg_d is a valid prime-order subgroup element (non-zero).

Public inputs: cv,cmu,epk\mathsf{cv}, \mathsf{cm}^u, \mathsf{epk}.

3.6 The bundle and binding signature

A Sapling bundle is

Bundle  =  ({SDi},{ODj},vbal,σbind).\mathsf{Bundle} \;=\; \bigl( \{\mathsf{SD}_i\},\, \{\mathsf{OD}_j\},\, v_{\text{bal}},\, \sigma_{\text{bind}} \bigr).

The binding equation from Section 2 holds because each cv\mathsf{cv} is Pedersen and the proofs internally certify well-formedness. The binding signature σbind\sigma_{\text{bind}} is a RedJubjub signature over the sighash whose verification key is

bvk=icviinjcvjout[vbal]V.\mathsf{bvk} = \sum_i \mathsf{cv}_i^{\text{in}} - \sum_j \mathsf{cv}_j^{\text{out}} - [v_{\text{bal}}] V.

If the equation holds, bvk=[rbal]R\mathsf{bvk} = [r_{\text{bal}}]R, so the spender holds the secret key to that point. If anything is off by even a single zatoshi or one randomness off, bvk\mathsf{bvk} is a random-looking point and the signature cannot be forged. Balance is enforced by a signature whose key is a function of the commitments.

3.7 Groth16 specifics

A Groth16 proof is

π=(A,B,C),A,CG1,  BG2.\pi = (A, B, C), \qquad A, C \in \mathbb{G}_1,\; B \in \mathbb{G}_2.

Verification given public inputs (x1,,x)(x_1, \ldots, x_\ell) and verifying key vk=(α,β,γ,δ,{τi}i=0)\mathsf{vk} = (\alpha, \beta, \gamma, \delta, \{\tau_i\}_{i=0}^{\ell}):

e(A,B)  =?  e(α,β)e ⁣(i=0xiτi,γ)e(C,δ).e(A, B) \;\stackrel{?}{=}\; e(\alpha, \beta) \cdot e\!\Bigl(\textstyle\sum_{i=0}^{\ell} x_i \tau_i, \gamma\Bigr) \cdot e(C, \delta).

The vector {τi}\{\tau_i\} is the input key: one G1\mathbb{G}_1 point per public input, plus a constant. For Sapling Spend =7\ell = 7 (witness encoding of cv,anchor,nf,rk\mathsf{cv}, \mathsf{anchor}, \mathsf{nf}, \mathsf{rk}); for Sapling Output =5\ell = 5.

The Sapling trusted setup was performed in two MPC ceremonies in 2017-2018 ("Powers of Tau" then per-circuit). The verifying-key hashes are hardcoded in zcash_proofs/src/lib.rs (SAPLING_SPEND_HASH, SAPLING_OUTPUT_HASH). The wallet downloads the proving keys with download-params and verifies them by SHA-256.

3.8 End-to-end

To spend vinv_{\text{in}} from a note and create a new output of value voutv_{\text{out}} (plus a change output) with fee ff, a Sapling transaction:

  1. Picks anchor rt\mathsf{rt} from a recent block.
  2. Constructs SpendDescriptions for each input, sampling αi\alpha_i and rcviin\mathsf{rcv}_i^{\text{in}}, generating the Groth16 proof and the spend-auth signature.
  3. Constructs OutputDescriptions, sampling fresh rcvjout\mathsf{rcv}_j^{\text{out}}, rcmj\mathsf{rcm}_j, and eskj\mathsf{esk}_j, encrypting the note plaintext.
  4. Sets $v_{\text{bal}} = v_{\text{in}} - v_{\text{out}}
    • v_{\text{change}}$ (sign convention from the spec).
  5. Computes σbind\sigma_{\text{bind}} under the implicit key $\mathsf{bvk} = \sum \mathsf{cv}_i^{\text{in}}
    • \sum \mathsf{cv}j^{\text{out}} - [v{\text{bal}}] V$.

A node verifies each proof against its vk\mathsf{vk}, verifies the spend-auth signatures, verifies the binding signature, and checks nullifier non-membership.

The Sapling protocol implementation moved out of this workspace into the sapling-crypto crate. What remains here:

4. Failure modes

  • Sprout counterfeiting CVE-2019-7167. The original Sprout proving system was BCTV14. A soundness flaw in BCTV14 allowed a prover to forge a JoinSplit proof under a weaker assumption. Mitigation: migration to Groth16 with a fresh MPC and the "hybrid Sprout" wrapper that this repo still ships. See chapter 12 for the full timeline and the ECC remediation post. Any change to the Sprout circuit must preserve the boolean range constraints; removing them silently restores the BCTV14 failure mode at the application layer.

    Caught by: zcash_proofs::circuit::sprout::test_sprout_constraints in zcash_proofs/src/circuit/sprout/mod.rs (the test feeds the Groth16-shaped Sprout circuit a fixed test-vector corpus and asserts the constraint system is satisfied exactly when the inputs are valid; gated on the expensive-tests feature).

  • Sapling InternalH issue and cm-vs-cm^u confusion. Early Sapling implementations conflated the full commitment with its extracted uu-coordinate. Any code change that publishes a full cm\mathsf{cm} where the spec asks for cmu\mathsf{cm}^u (or the reverse) leaks information and breaks downstream wallets.

    Caught by: zcash_primitives::transaction::tests::tx_read_write in zcash_primitives/src/transaction/tests.rs (the test parses a fixed v4 transaction and checks the txid against a pinned value; any reversal of cm\mathsf{cm} versus cmu\mathsf{cm}^u in the OutputDescription reader changes the digest).

  • Dummy-spend value drift. A Sapling bundle may include dummy spends with v=0v = 0 to mask the input count. Builders must enforce v=0v = 0 for dummies; non-zero dummies silently corrupt vbalv_{\text{bal}} and break the binding signature.

    No automated test in this workspace. Dummy-spend construction and the binding-signature check live in the external sapling-crypto crate; this workspace only round-trips the serialized bundle. Caught by audit only.

  • Wrong ToScalar\mathsf{ToScalar} reduction. ToScalar\mathsf{ToScalar} reduces a 64-byte string modulo \ell. Substituting a 32-byte truncation biases the key distribution and silently breaks unlinkability proofs.

    Caught by: zcash_keys::keys::tests::ufvk_round_trip in zcash_keys/src/keys.rs (derives Sapling and Orchard FVKs from a fixed seed and asserts the resulting UFVK encoding against a pinned bech32 string; any change in ToScalar\mathsf{ToScalar} rotates every derived key and the comparison fails).

  • Re-randomisation reuse. Each spend must sample a fresh α\alpha. Reusing α\alpha across two spends links their rk\mathsf{rk} to the same underlying ak\mathsf{ak}, defeating the entire point of RedJubjub re-randomisation.

    No automated test in this workspace. Sampling of α\alpha happens inside the external sapling-crypto builder; this workspace consumes its output. Caught by audit only.

Tests under zcash_primitives/src/transaction/tests.rs exercise the full v4 / v5 serialization round-trip; the sapling-crypto crate carries the circuit-level tests for the Spend and Output statements.

5. Spec pointers

6. Exercises

  1. Map a field. Open SpendDescription and identify the source line of each component of the tuple (cv,anchor,nf,rk,πSpend,σspendAuth)(\mathsf{cv}, \mathsf{anchor}, \mathsf{nf}, \mathsf{rk}, \pi_{\text{Spend}}, \sigma_{\text{spendAuth}}).
  2. Predict the dispatch. For a v4 transaction, which of read_spend_v4 or read_spend_v5 does the parser call? What about a v5 transaction? Cite the call site in zcash_primitives/src/transaction/components/sapling.rs.
  3. Modify and test. In a checkout, add a unit test under zcash_primitives that constructs a SpendDescription with a deliberately wrong rk\mathsf{rk} (e.g. negate it) and confirms that the spend-auth signature verification fails. The test should pass (i.e. the assertion that verification returns an error must hold). Cite the public verification entry point from the redjubjub crate as your reference.

Answers in the code

7. Further reading

  • chapter 05: Orchard and Halo 2, which replace the trusted-setup Groth16 stack with a transparent IPA-based system.
  • chapter 16: the windowed encoding for Pedersen hashes, in-circuit cost, generator derivation.
  • chapter 24: clause-by-clause walk of the Spend and Output circuits with constraint counts.
  • Hopwood, Bowe, Hornby, Wilcox. Sapling design notes.