03 - Cryptography primer
1. Why this chapter exists
Chapters 04 and 05 will talk about Spend statements, value
commitments, binding signatures, and Halo 2 transcripts. None of
that vocabulary is reusable if the reader has not pinned down the
underlying notation: which group is which, what a pairing is, why
Pedersen commitments are homomorphic, how BLAKE2 personalisation
turns a hash function into a domain-separated PRF. This chapter is
the calibration step. By the end of it, you should be able to read
the personalisation tag
b"ZcashTxHash_" in
zcash_primitives/src/transaction/txid.rs#L33-L40
and explain why every BLAKE2b call site needs one.
2. Definitions
Definition 2.1 (prime field). For a prime , is the finite field of order , and is its multiplicative group of order .
Definition 2.2 (cyclic group, additive notation). A cyclic group of prime order with generator is a set of elements with a binary operation such that every equals for a unique . For , denotes the -fold sum in , with the identity.
Definition 2.3 (discrete logarithm problem, DLP). Given of prime order with generator , the DLP is the problem: on input with for an unknown k \stackrel{\}{\leftarrow} \mathbb{F}_qk$. The DLP assumption states that no probabilistic polynomial-time algorithm solves DLP with non-negligible advantage. Every group in this workspace is assumed to satisfy the DLP assumption with at least 128 bits of security.
Definition 2.4 (pairing). Let be cyclic groups of prime order . A pairing is a map such that
- Bilinearity. For all , , , .
- Non-degeneracy. for generators , .
- Efficient computability. is computable in polynomial time in the bit-length of .
Definition 2.5 (commitment scheme). A commitment scheme over a message space and randomness space is an algorithm satisfying
- Binding. For every probabilistic polynomial-time , the probability that outputs with and is negligible.
- Hiding. For every , the distributions \{\mathsf{Com}(m_1; r) : r \stackrel{\}{\leftarrow} \mathcal{R}}{\mathsf{Com}(m_2; r) : r \stackrel{$}{\leftarrow} \mathcal{R}}$ are (computationally or perfectly) indistinguishable.
Definition 2.6 (Pedersen commitment). Let be a cyclic group of prime order with generators such that is unknown to all parties. For and ,
Lemma 2.7 (Pedersen commitment security). Pedersen commitments are additively homomorphic, perfectly hiding, and computationally binding under the DLP assumption in .
Proof sketch. Homomorphism follows from the linearity of scalar multiplication in . Perfect hiding: for any , the distribution of for r \stackrel{\}{\leftarrow} \mathbb{F}_q\mathbb{G}[m_1]G + [r_1]H = [m_2]G + [r_2]H(m_1, r_1) \neq (m_2, r_2)\log_G H = (m_1 - m_2)/(r_2 - r_1) \bmod q$, contradicting DLP. See [Pedersen, CRYPTO 1991].
Definition 2.8 (pseudo-random function from BLAKE2b). For a 16-byte personalisation string , a key , and an input , define
This construction is the one implemented by the
PrfExpand
helper in the external
zcash_spec crate.
Definition 2.9 (Fiat-Shamir transform). Let be an interactive public-coin three-move protocol with prover messages and a verifier challenge sampled between them. Let be a hash function modelled as a random oracle. The Fiat-Shamir transform replaces by for public parameters ; the resulting non-interactive protocol has soundness loss bounded by for adversaries making queries to .
Invariant 2.10 (one personalisation per call site). For every BLAKE2b call site in the Zcash workspace, the 16-byte personalisation string is unique. The injectivity of the map from call site to personalisation rules out cross-protocol replay: a string accepted as a hash output by one call site cannot be repurposed at any other call site, because the personalisation enters BLAKE2b's keyed parameter block and thus the input domain of every invocation is disjoint.
Threat model summary
The cryptography in this workspace defends against the following adversary classes. Each row states the formal goal, the workspace code that enforces it, and the test (if any) that catches a regression. Rows marked HEURISTIC are not backed by a security reduction; they rest on best-effort engineering.
| Adversary capability | Formal goal | Defence in workspace | Test that catches a regression |
|---|---|---|---|
| Forge a Sapling Spend without knowing | Knowledge-soundness of Groth16 under q-PKE in BLS12-381 | zcash_proofs::sapling::SaplingVerificationContext::check_spend | zcash_primitives::transaction::tests::tx_read_write (round-trip) + sapling-crypto::circuit::spend::tests::valid_proof |
| Forge an Orchard Action without knowing | Knowledge-soundness of Halo 2 + DLP in Pallas | orchard::bundle::Bundle::verify_proof | orchard::tests::vectors |
| Double-spend by replaying a nullifier | Nullifier collision-freedom under PRF security of BLAKE2b () | Nullifier set check in the consumer (not in this workspace; consensus node responsibility) | not enforced here; consumers must |
| Inflate the value pool | Pedersen-binding under DLP in Jubjub / Pallas, plus the binding-signature equation | Binding signature verification on a Sapling/Orchard bundle | sapling-crypto::bundle::tests::value_balance |
| IND-CPA against a shielded note's plaintext | IND-CPA of ChaCha20-Poly1305 with per-note ephemeral keys | zcash_note_encryption::try_note_decryption | zcash_note_encryption::tests::test_decryption |
| Recover a spending key from ciphertext | Authenticated encryption + uniqueness of ephemeral keys | same | same |
| Inject an 8-torsion Jubjub point as a public input | Subgroup-check on every read_* deserialiser | sapling-crypto::primitives::value::read_value_commitment (and analogous read_cmu, read_rk) | sapling-crypto::tests::canonical_encoding |
| Inject a non-canonical encoding | Canonical-encoding enforcement via ff::PrimeField::from_repr returning CtOption | call sites in zcash_primitives::transaction::components::sapling | zcash_primitives::transaction::tests::non_canonical_field_element |
| Time the prover to learn the witness scalar | Constant-time scalar multiplication in subtle-using crates | bls12_381, jubjub, pasta_curves (see chapter 14) | no automated test; relies on crate-level CT discipline |
| Read residual key bytes from freed memory | zeroize on Drop for secret types | SaplingIvk, SpendingKey, etc. all implement Zeroize | not unit-tested; verified by audit |
| HEURISTIC Replay a tx across network upgrades | Personalisation strings differ per NU; consensus rule | Branch-ID routing in zcash_protocol::consensus | zcash_primitives::transaction::sighash::tests::v5_sighash_branchid |
| HEURISTIC Sapling MPC participant retained toxic waste | -out-of- ceremony with one honest participant | Out of scope for code; relies on 2018 ceremony | see chapter 15 |
| HEURISTIC Timing of vartime APIs leaks Merkle path index | pow_vartime and friends documented as vartime | enforced by manual API audit | see chapter 14 §3 |
| Out of scope: side-channel on prover machine | not addressed beyond constant-time crates | n/a | n/a |
| Out of scope: deanonymisation via wallet metadata or network behaviour | see chapter 18 | n/a (out of workspace cryptography) | n/a |
Read this table before reading any specific chapter. A claim like "Sapling binding signature prevents inflation" is a defended row above; a claim like "Zcash hides the sender's IP" is not in the table because it is not a cryptographic defence inside this workspace.
3. The code
3.1 Groups and fields
Zcash uses several groups. Each row corresponds to one crate.io
dependency declared in
Cargo.toml:
| Curve | Field | Order | Used for |
|---|---|---|---|
| BLS12-381 () | , 381-bit | , 255-bit | Sapling Groth16 |
| Jubjub | where is BLS12-381 scalar field | 252-bit prime | Sapling commitments, key agreement |
| Pallas | , | Orchard arithmetic | |
| Vesta | Orchard recursion | ||
| secp256k1 | Bitcoin curve | 256-bit | Transparent ECDSA |
The Pallas/Vesta pair is a 2-cycle of elliptic curves: the base field of one equals the scalar field of the other. This is essential for efficient recursive proofs (Halo); see chapter 05.
The Jubjub curve has a scalar field equal to BLS12-381's scalar field, which means scalar arithmetic inside a BLS12-381-based SNARK is cheap. Sapling uses this for in-circuit elliptic-curve operations.
Read in code: the workspace
Cargo.toml
pulls bls12_381, jubjub,
pasta_curves, secp256k1,
group, and ff from crates.io:
loading...
The Pallas / Vesta type aliases used throughout the Orchard code
live in
pasta_curves/src/pallas.rs
and
pasta_curves/src/vesta.rs.
3.2 Pairings and Groth16
BLS12-381 is a pairing-friendly curve: are specific subgroups of elliptic-curve points and .
Sapling proofs are Groth16 SNARKs with a constant-size pairing check at verification:
You do not need to memorise this; what matters is that the
verification is a constant-size pairing equation, and that the
verifying key contains
and a vector of points for the public inputs.
bellman::groth16::Proof is the type;
zcash_proofs
consumes prepared verifying keys produced once and cached.
3.3 Hash functions and PRFs
BLAKE2b / BLAKE2s. Pervasive in Zcash. Both support a 16-byte personalisation string that acts as domain separation. The idiomatic Zcash usage is
Personalisation tags in this codebase are short ASCII strings such
as "ZcashTxHash_", "ZTxIdSaplingHash", "Zcash_ExpandSeed". The
full list of TxId personalisations lives at the top of txid.rs:
loading...
SHA-256, RIPEMD-160. Used in the transparent layer for Bitcoin compatibility: for P2PKH addresses; for some legacy contexts. Sprout circuits also use SHA-256, because the original Zerocash construction did.
Pedersen and Sinsemilla hashes. Algebraic hash functions (output is a curve point) optimised for SNARK-friendliness. Defined and motivated in chapter 04 (Pedersen) and chapter 05 (Sinsemilla).
PRF^{expand}. The single PRF used pervasively for key
derivation:
where is a tag byte (and sometimes more bytes). Defined once in
zcash_spec
and reused everywhere. Grep PrfExpand in the workspace.
3.4 Commitments
Pedersen. As in Section 2. Properties:
- Additively homomorphic: .
- Perfectly hiding (the randomness completely masks the message).
- Computationally binding under DLP.
The homomorphism is the mathematical engine behind shielded value conservation. Chapter 04 shows how it lets a transaction prove that input value equals output value without revealing the values themselves.
Pedersen hash. Generalise the commitment to many generators :
Collision-resistant under DLP and much cheaper inside a SNARK than SHA-256 because elliptic-curve arithmetic is the SNARK's native operation. Sapling's note commitments and Merkle-tree hashes use Pedersen-hash variants.
Value commitments. Sapling uses
with curve-specific generators . The crucial property is
the binding equation: the prover proves it knows relative to a public , completing the value-conservation proof. This is what the "binding signature" signs.
3.5 Signatures
ECDSA (secp256k1). Used for transparent inputs. Standard
Bitcoin signatures; see the
secp256k1 crate.
RedDSA / RedJubjub / RedPallas. Sapling and Orchard use RedDSA, a re-randomisable EdDSA-style signature scheme. The instantiation over Jubjub is RedJubjub (Sapling); over Pallas is RedPallas (Orchard).
A RedDSA signature key is a pair with . To sign :
- Sample r \stackrel{\}{\leftarrow} \mathbb{F}_qR = [r]G$.
- Compute challenge .
- Set .
- The signature is .
Verification: .
This is Schnorr-style; what makes it "Red" is the re-randomisation:
A signature under verifies under . The randomiser is uniform per spend, so is unlinkable to the underlying . Sapling spend authorisation uses this: the spend description publishes ; the spender signs under ; a Spend Authorisation Signature is included in the description.
Binding signature. A signature whose verification key is computed from the value commitments themselves. The combined value commitment
should equal for some known only to the spender. The spender publishes a signature whose verification key is exactly that point, using as the group generator. Verifying the signature proves the prover knew ; hence values balance.
Read in code:
redjubjub (used
by Sapling) and
reddsa (Orchard).
3.6 Key agreement
In a group of prime order with generator :
then is the shared secret. Both parties feed it to a key-derivation function to get a symmetric key.
Sapling and Orchard both use ECDH on Jubjub / Pallas for note encryption (chapter 08), with being a per-recipient diversifier generator rather than a fixed generator. This is part of how diversified addresses work.
3.7 Symmetric primitives
Note encryption uses ChaCha20-Poly1305, an authenticated stream
cipher: where is a 12-byte nonce
and the output includes a 16-byte tag. The Zcash spec uses
always because each key is single-use. The AEAD discipline still
applies: never reuse ; always include associated data;
always check the tag before using the plaintext. The dependency is
declared in
zcash_primitives/Cargo.toml.
3.8 Zero-knowledge proofs
Zcash uses two families of NIZK arguments:
- Groth16 (Sapling, Sprout): preprocessing SNARK, constant proof size ( bytes), constant verification cost (three pairing equations collapsed). Requires a per-circuit trusted setup, performed in a multi-party computation ceremony ("Powers of Tau" plus circuit-specific). The proving key is many megabytes; the verifying key is a few kilobytes.
- Halo 2 (Orchard): a PLONK-derived argument with a polynomial commitment based on the Inner Product Argument (IPA). No per-circuit trusted setup, but uses a transparent universal setup (a structured reference string that anyone can verify) and a custom arithmetisation (custom gates, lookups, permutations) tuned for the Pallas/Vesta cycle.
The interface as seen from librustzcash is, in both cases:
The witness includes secret values such as note values, randomness, the spending key, and the Merkle path. The public input includes the anchor, the value commitment, the nullifier, , and the output commitment.
For Sapling the verifying-key hashes are bundled with the binaries
in
zcash_proofs/src/lib.rs:
loading...
The proving keys are downloaded via download-params.
3.9 Fiat-Shamir and personalisation
Many protocols are stated as interactive: prover sends commitment, verifier sends challenge, prover sends response. The Fiat-Shamir transform replaces the verifier's challenge with a hash of the prover's messages (and prior context), producing a non-interactive protocol in the random-oracle model. It is everywhere in Zcash:
- The RedDSA challenge .
- The IPA challenges inside Halo 2.
- Sighash for transparent inputs (a generalised Fiat-Shamir).
Whenever you see let chal = blake2b(transcript), that is a
Fiat-Shamir challenge.
Personalisations are 16 bytes; if shorter, they are padded with zero bytes. Examples seen in this codebase:
"Zcash_ExpandSeed":PRF^{expand}."Zcash_SaplingNf": Sapling nullifier PRF."ZTxIdSaplingHash": sighash sub-tree."Zcash_OrchardMH": Orchard Merkle hash.
If you ever add a new hash usage, define a new personalisation. Reusing an existing one is a bug.
4. Failure modes
A contributor who confuses the primitives in this chapter produces errors that pass unit tests but break interoperability:
- Field confusion. Jubjub's scalar field equals BLS12-381's
scalar field, but its base field does not. Pallas and Vesta swap
base and scalar. Calling
Fr::from_byteson a representation looks plausible and compiles, but produces wrong curve points downstream.Caught by:
zcash_primitives::transaction::tests::tx_read_writeinzcash_primitives/src/transaction/tests.rs(verifies the full v4 transaction round-trip against a fixed txid, which requires every Jubjub/BLS12-381 deserialiser to interpret bytes in the correct field). - Endianness drift. Zcash standardises on little-endian for
most field serializations, but a handful of legacy
Bitcoin-derived contexts use big-endian. The ZIPs spell out the
order. Mixing the two has caused real production bugs across
multiple wallets.
Caught by:
zcash_primitives::transaction::tests::zip_0244inzcash_primitives/src/transaction/tests.rs(matches computed sighash and txid against ZIP 244 test vectors that pin every byte order). - Pedersen-window off-by-one. Each Pedersen window has its own
generator, derived deterministically from a hash of an index.
Reusing a generator across windows breaks collision resistance.
No automated test in this workspace. The windowed Pedersen generators are defined and tested in the external
sapling-cryptocrate; this workspace consumes them via the Sapling commitment readers. Caught by audit only. - Personalisation reuse. As stated above, every new BLAKE2b
call site must add a new 16-byte personalisation. Two recent
changes to the protocol added new sighash sub-trees, each with
its own tag; do the same.
Caught by:
zcash_primitives::transaction::tests::zip_0244inzcash_primitives/src/transaction/tests.rs(the ZIP 244 test vectors fix every per-sub-tree personalisation; any reuse changes the resulting digest and the assertion fails). - Nonce / randomness reuse. Every RedDSA signature, every note
randomness, every diversifier randomness must be sampled
uniformly and independently. The Sprout counterfeiting CVE
(chapter 12) is the canonical example of what a flaw at this
layer can cost.
No automated test in this workspace. Randomness sampling is the caller's responsibility; the builder consumes
rand_core::CryptoRngand the workspace cannot detect non-uniform sources. Caught by audit only.
5. Spec pointers
- Zcash Protocol Specification, sections 5.4 and 5.6: the full table of personalisations and the precise PRF constructions used by Sapling and Orchard.
- ZIP 32: the
hierarchical-deterministic key derivation tree that all
PRF^{expand}invocations sit inside. - Groth, 2016: the original Groth16 paper. Read sections 1 and 3 to understand the pairing equation cited above.
- Halo 2 book: the canonical reference for the Halo 2 proof system used by Orchard. Chapter 05 cites specific sections.
- BLAKE2 RFC 7693: the authoritative specification for BLAKE2b and BLAKE2s, including the personalisation parameter Zcash relies on.
6. Exercises
- Trace a personalisation. Search the workspace for the byte
string
b"Zcash_ExpandSeed". List every call site and, for each, identify the inputtit passes. - Verify a Pedersen identity. In a scratch test, sample
uniformly, compute
and
, and confirm in code that
holds in
jubjub::SubgroupPoint. Add it as a unit test underzcash_primitives(do not commit; this is a scratch exercise). - Add a new BLAKE2b call. Pretend you need a new BLAKE2b hash
under the personalisation
"OnboardingPrim ". Write the helper asfn h_onboarding(input: &[u8]) -> [u8; 32]in a throwaway file. Runcargo check. Then delete the helper and the file before moving on; the exercise is to convince yourself that the personalisation is a 16-byte string parameter you can add anywhere, not a magic constant.
Answers in the code
PRF^{expand}uses:zcash_spec(external) and grepPrfExpandacross the workspace.- TxId personalisations:
zcash_primitives/src/transaction/txid.rs#L33-L67. - Verifying-key hashes for Sapling:
zcash_proofs/src/lib.rs#L40-L52.
7. Further reading
- chapter 16: the windowed encoding for Pedersen hashes, in-circuit cost, generator derivation.
- chapter 17: the polynomial-commitment layer underneath Halo 2.
- Boneh, Drijvers, Neven, Compact Multi-Signatures for Smaller Blockchains, 2018: background on Schnorr-style signatures and their re-randomisation properties, the foundation of RedDSA.