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 (), 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
where is the recipient paying key, is the value in zatoshis, is a uniqueness nonce, and is commitment randomness. Its commitment is the 32-byte string
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 encoding transparent inflow and outflow, a public Merkle root (anchor), and a public per-JoinSplit signature digest binding the JoinSplit to a fixed transaction context.
Sapling
Definition 2.3 (Pedersen hash, ). Let denote the Jubjub prime-order subgroup. The Sapling Pedersen hash is defined as follows. Pad the input bit string to a length that is a multiple of , group bits into chunks of and segments of chunks ( bits per segment), encode each chunk as the signed integer
multiply each segment by an independent generator derived deterministically from a domain-separation string, and return .
Definition 2.4 (Sapling note commitment). For , , and ,
where is a fixed generator with unknown.
Lemma 2.5 (Sapling note commitment is perfectly hiding and computationally binding). is perfectly hiding over the randomness \mathsf{rcm} \stackrel{\}{\leftarrow} \mathbb{F}_r\mathbb{G}_J$.
Proof sketch. The map is a bijection on , so the distribution of for uniform is uniform on , independent of . Binding reduces to collision resistance of plus knowledge of in the base , both following from DLP in . See [Zcash Protocol Specification, section 5.4.7].
Definition 2.6 (Sapling Merkle hash). Let denote the Merkle layer index. Define
where returns the -coordinate of its argument. The layer-indexed domain separation string 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 , the Sapling key tree is the sequence of derivations
The full viewing key is sufficient to decrypt every incoming and outgoing-tagged note spendable under .
Definition 2.8 (Sapling nullifier). For a Sapling note with commitment , tree position , mixing input , and key , the nullifier is
Invariant 2.9 (Sapling binding equation). Let be a Sapling bundle with input value commitments , output value commitments , and value balance . For 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
The binding signature certifies knowledge of , which is feasible (under DLP in ) 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 .
Definition 2.10 (Sapling Spend relation ). Let be the BLS12-381 scalar field, the Jubjub prime-order subgroup, and the Merkle-tree domain. Define
Then iff all of the following hold:
- Note well-formedness. With , , , and .
- Commitment. Let .
- Merkle membership. .
- Value commitment. .
- Spend authority. for some known to the prover, and where is the re-randomiser.
- Nullifier integrity. With and , .
- Diversified address. where .
- Value range. and 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 ). Let
Then iff:
- , , and .
- .
- .
- .
- .
The circuit module is
sapling-crypto::circuit::output.
Lemma 2.12 (extraction injectivity). For , implies or . In particular, for inputs constrained to together with the parity bit, the -coordinate uniquely identifies a Jubjub point.
Proof sketch. For a twisted Edwards curve over , the substitution leaves the equation invariant, so points with a given -coordinate differ only in the sign of (equivalently, by negation in the group law). Two distinct points sharing both and the parity of would coincide. The clauses of and enforce membership in , 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:
loading...
The synthesize method enforces, for each input :
- Recompute from the witnessed note.
- Verify a Merkle path of depth 29 from to the public anchor (with the witnessed authentication path).
- Derive the paying key .
- Compute the nullifier .
- Compute the " tag" that binds the JoinSplit to a specific .
For each output it derives a fresh from and indices and recomputes the commitment.
Finally it enforces the balance equation
with each enforced via boolean range constraints.
The PRFs are all "SHA-256 with a tag prefix", for example
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
-
: the scalar field of BLS12-381, , a 255-bit prime.
-
: the base field of Jubjub. Equal to .
-
Jubjub is a twisted Edwards curve over ,
with a prime-order subgroup of order
-
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 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 :
-
Derive
where reduces a 64-byte string modulo .
-
Compute the public points
with and distinct fixed generators on Jubjub.
-
Compute the incoming viewing key
where is BLAKE2s with personalisation
"Zcashivk". -
For each 11-byte diversifier , compute , a hash-to-curve into Jubjub. Not every yields a valid prime-order point; if it does not, the diversifier is invalid and skipped. The diversified transmission key is
-
A Sapling payment address is the pair encoded as 43 plaintext bytes then bech32 with HRP
zs.
Diversified addresses follow: each generates infinitely many payment addresses sharing the same viewing key. A wallet can hand out a fresh to every counterparty without revealing the common .
3.4 Spend description (math)
A Sapling SpendDescription is the tuple
- is the value commitment to the spent note's value with fresh randomness .
- is the Merkle root used for membership.
- is the nullifier.
- is the re-randomised spend-authority public key.
- is the Groth16 proof.
- is a RedJubjub signature under 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):
loading...
The Spend statement (what the circuit enforces): the prover knows such that
- .
- The Merkle path proves is in the tree with root . Special case: if the path check is skipped, allowing "dummy" spends used to mask the input count.
- where .
- (the spender owns this address).
- .
- .
- for known .
- .
Public inputs: .
3.5 Output description (math)
An OutputDescription is
- : value commitment of the new note.
- : the -coordinate of the new note's commitment. (The full commitment is recoverable; only the -coordinate is published to save space.)
- : ephemeral public key for ECDH note encryption.
- : the encrypted note plaintext (recipient, value, , memo).
- : the outgoing ciphertext that lets the sender recover the plaintext using .
- : the Groth16 proof.
The Output statement: the prover knows such that
- and is its -coordinate.
- .
- .
- .
- is a valid prime-order subgroup element (non-zero).
Public inputs: .
3.6 The bundle and binding signature
A Sapling bundle is
The binding equation from Section 2 holds because each is Pedersen and the proofs internally certify well-formedness. The binding signature is a RedJubjub signature over the sighash whose verification key is
If the equation holds, , so the spender holds the secret key to that point. If anything is off by even a single zatoshi or one randomness off, 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
Verification given public inputs and verifying key :
The vector is the input key: one point per public input, plus a constant. For Sapling Spend (witness encoding of ); for Sapling Output .
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 from a note and create a new output of value (plus a change output) with fee , a Sapling transaction:
- Picks anchor from a recent block.
- Constructs SpendDescriptions for each input, sampling and , generating the Groth16 proof and the spend-auth signature.
- Constructs OutputDescriptions, sampling fresh , , and , encrypting the note plaintext.
- Sets
$v_{\text{bal}} = v_{\text{in}} - v_{\text{out}}
- v_{\text{change}}$ (sign convention from the spec).
- Computes 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 , 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:
zcash_primitives/src/transaction/components/sapling.rs: serialization, theBundle<A, Amount>type, authorisation states.zcash_primitives/src/transaction/builder.rs: the high-level Sapling builder shim that delegates intosapling_builderfromsapling-crypto.zcash_proofs/src/lib.rs: parameter loading, verifying-key hashes, prover bindings.zcash_keys/src/keys.rs: spending-key derivation glue, much delegated tozip32andsapling-crypto::zip32.
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_constraintsinzcash_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 theexpensive-testsfeature). - Sapling
InternalHissue andcm-vs-cm^uconfusion. Early Sapling implementations conflated the full commitment with its extracted -coordinate. Any code change that publishes a full where the spec asks for (or the reverse) leaks information and breaks downstream wallets.Caught by:
zcash_primitives::transaction::tests::tx_read_writeinzcash_primitives/src/transaction/tests.rs(the test parses a fixed v4 transaction and checks the txid against a pinned value; any reversal of versus in the OutputDescription reader changes the digest). - Dummy-spend value drift. A Sapling bundle may include dummy
spends with to mask the input count. Builders must
enforce for dummies; non-zero dummies silently corrupt
and break the binding signature.
No automated test in this workspace. Dummy-spend construction and the binding-signature check live in the external
sapling-cryptocrate; this workspace only round-trips the serialized bundle. Caught by audit only. - Wrong reduction.
reduces a 64-byte string modulo . Substituting a 32-byte
truncation biases the key distribution and silently breaks
unlinkability proofs.
Caught by:
zcash_keys::keys::tests::ufvk_round_tripinzcash_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 rotates every derived key and the comparison fails). - Re-randomisation reuse. Each spend must sample a fresh
. Reusing across two spends links their
to the same underlying , defeating
the entire point of RedJubjub re-randomisation.
No automated test in this workspace. Sampling of happens inside the external
sapling-cryptobuilder; 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
- Zcash Protocol Specification, section 4 (Abstract Protocol): the high-level Sapling protocol definitions cited throughout this chapter.
- Zcash Protocol Specification, section 5 (Concrete Protocol): the concrete formulas for , , , and the key tree.
- Zcash Protocol Specification, section 7 (Encodings):
the wire-format encoding that
read_spend_v4andread_spend_v5implement. - ZIP 32: HD derivation for the spending key that seeds the key tree above.
- Ben-Sasson et al., Zerocash, IEEE S&P 2014: the Sprout-era protocol. Background only; Sapling diverges.
- Groth, 2016: the Groth16 paper. Read section 3 for the pairing equation cited in Section 3.7.
6. Exercises
- Map a field. Open
SpendDescriptionand identify the source line of each component of the tuple . - Predict the dispatch. For a v4 transaction, which of
read_spend_v4orread_spend_v5does the parser call? What about a v5 transaction? Cite the call site inzcash_primitives/src/transaction/components/sapling.rs. - Modify and test. In a checkout, add a unit test under
zcash_primitivesthat constructs aSpendDescriptionwith a deliberately wrong (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 theredjubjubcrate as your reference.
Answers in the code
- Sprout JoinSplit shape:
zcash_proofs/src/circuit/sprout/mod.rs#L25-L54. - Sapling Spend v4/v5 readers:
zcash_primitives/src/transaction/components/sapling.rs#L168-L213. - Sapling proving / verifying key hashes:
zcash_proofs/src/lib.rs#L40-L52.
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.