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 (prime field). is the finite field of order for prime ; is its multiplicative group of non-zero elements.
Definition (cyclic group, additive). is a cyclic group of prime order written additively with generator ; . For an integer , is added to itself times.
Definition (discrete logarithm problem, DLP). Given with , find . Zcash assumes DLP is hard in every group it uses.
Definition (pairing). A non-degenerate bilinear map between three prime-order- groups, satisfying for all , , .
Definition (commitment scheme). An algorithm that is
- binding: hard to find with ;
- hiding: reveals nothing computational about .
Definition (Pedersen commitment). In of prime order with two generators such that is unknown,
Pedersen commitments are additively homomorphic, perfectly hiding, and computationally binding under DLP.
Definition (PRF from BLAKE2b). Sapling defines
, where is a 16-byte
personalisation string fixing the PRF instance. The construction is
in
zcash_spec and reused by
every workspace crate via the
PrfExpand
helper.
Definition (Fiat-Shamir transform). A reduction from an interactive 3-move protocol to a non-interactive one: replace the verifier's challenge with for a public hash in the random-oracle model.
Invariant (one personalisation per call site). Every BLAKE2b invocation in Zcash uses a unique 16-byte personalisation. The reason: cross-protocol replay. If two protocols use the same BLAKE2b on similar inputs and one accepts a value as a hash output, the attacker should not be able to repurpose it elsewhere. Adding a hash invocation means adding a new personalisation tag.
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. - 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.
- 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.
- 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.
- 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.
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.