Cryptographic Primer
1. Why this chapter exists
The Sapling protocol stacks several primitives on top of each other: two elliptic curves with a particular embedding relationship, a hash that lives on one of them, a Pedersen-style commitment scheme, an incremental Merkle tree using a Pedersen-hash-based collision-resistant function, and Groth16 over BLS12-381. None of those are standard across cryptographic libraries, and the protocol spec is dense enough that you cannot read the source without the primer.
This chapter is the minimum primer. It is deliberately concise: it does not prove anything, it states the definitions you need to read the rest of the course and points at the canonical reference for each.
2. Definitions
2.1 The two curves: BLS12-381 and Jubjub
Definition 3.1 (BLS12-381). BLS12-381 is a pairing-friendly curve over with embedding degree 12, where is a 381-bit prime. Its prime-order subgroup has order , a 255-bit prime. We write , , for the three groups participating in the pairing. The crate uses BLS12-381 only as the underlying curve for Groth16 proofs; the application never manipulates or directly. The base field of BLS12-381 has bit size 381, but the scalar field has bit size 255, and that 255-bit field is what we use as Jubjub's base field. Source: Bowe, "BLS12-381: New zk-SNARK Elliptic Curve Construction".
Definition 3.2 (Jubjub). Jubjub is a twisted Edwards curve defined over (the scalar field of BLS12-381), with equation
in . The full Jubjub curve has cofactor 8; its prime-order
subgroup has order , a 252-bit prime. We write for
the full curve and for its prime-order subgroup. In code:
jubjub::ExtendedPoint is ,
jubjub::SubgroupPoint is ,
jubjub::Base is ,
jubjub::Scalar (a.k.a. jubjub::Fr) is
.
Lemma 3.3 (the embedding). Because Jubjub's base field equals BLS12-381's scalar field, Jubjub point arithmetic can be carried out inside a Groth16 circuit over BLS12-381 without field-emulation overhead. This is the load-bearing fact that makes the Sapling circuit feasible. Without this matching, the in-circuit Pedersen hash would cost an order of magnitude more constraints. Citation: Hopwood et al., Zcash Protocol Specification, §5.4.9.3 (Jubjub).
Invariant 3.4 (subgroup discipline). Sapling primitives operate on
, not on the full curve . The implementation
enforces this both in code (most types are jubjub::SubgroupPoint) and in the
circuit (EdwardsPoint::assert_not_small_order
performs three doublings and checks ). A point outside the subgroup
but inside is called small-order if it has order dividing the
cofactor 8.
2.2 Hashes and PRFs
Definition 3.5 (BLAKE2 personalisation). The Sapling crate uses BLAKE2s for
32-byte outputs (crh_ivk, prf_nf, the group hash) and
BLAKE2b for 64-byte outputs (kdf_sapling, prf_ock,
ZIP 32 child derivations). Both hashes accept an 8-byte (BLAKE2s) or 16-byte
(BLAKE2b) personalisation that domain-separates calls. Every personalisation
string used by Sapling is enumerated in
constants.rs.
Definition 3.6 (Pedersen hash). Given an ordered list of fixed generators and a bit-string , the Sapling Pedersen hash is
where encodes the -th windowed segment of as a signed scalar in . In Sapling, (windows of 3 bits each) and each generator handles up to windows. The output lives in . Collision resistance reduces to discrete log on Jubjub. See chapter Pedersen hash and commitments.
Definition 3.7 (windowed Pedersen commitment). A randomised variant of the Pedersen hash:
where is the
NOTE_COMMITMENT_RANDOMNESS_GENERATOR.
This is what the note commitment uses
(source).
Definition 3.8 (homomorphic value commitment). A second commitment scheme, used for amounts:
Two fixed generators, no Pedersen hash. The point of using this alternative scheme is the additive homomorphism over the value slot: if , then . That homomorphism is what makes the binding signature work. See chapter Value commitments and balance.
2.3 The proof system: Groth16
Definition 3.9 (R1CS). A Rank-1 Constraint System over a field is a triple of matrices in , where is the number of constraints and is the number of variables (one constant "ONE" wire plus assigned variables, partitioned into public inputs and private witnesses). The constraint system is satisfied by an assignment iff for every row :
R1CS has no separate "selector" or "advice" notion; it has only constraints and variables. Selectors are a Plonkish concept, not an R1CS concept.
Definition 3.10 (Groth16). Groth16 (Groth, "On the Size of Pairing-Based
Non-interactive Arguments", EUROCRYPT 2016) is a preprocessing zk-SNARK over a
pairing-friendly curve (here BLS12-381). For a fixed R1CS instance , a
one-time trusted setup produces a proving key and a verification
key . The prover, given a public input and witness with
, outputs a proof
.
The verifier evaluates a single pairing equation. Proof size is fixed at
bytes regardless of , encoded in
GROTH_PROOF_SIZE.
Theorem 3.11 (knowledge soundness, informal). Under the -power knowledge of exponent assumption in and , if an efficient prover produces a Groth16 proof that verifies for public input , then there exists an efficient extractor that recovers a witness with . The "knowledge of exponent" assumption is non-falsifiable but standard. Citation: Groth, EUROCRYPT 2016.
Invariant 3.12 (trusted setup). Groth16 needs a per-circuit trusted setup.
For Sapling, that ceremony was conducted as the
Sapling MPC in
2017-2018. The setup outputs are not in this crate; they are distributed as
binary files (sapling-spend.params, sapling-output.params). The crate
exposes
SpendParameters::read
and
OutputParameters::read
to load them.
3. The code
The two curves and the proof system are external; this crate sits on top of them.
loading...
The relevant crates are bls12_381 and jubjub for the curves, bellman
(gated behind circuit) for the Groth16 backend, redjubjub for the signature
scheme, blake2b_simd and blake2s_simd for the hashes, and subtle for
constant-time primitives.
The hash-to-Jubjub primitive that everything else builds on is small enough to read in one go:
loading...
Note the cofactor clearing on line 33 (CofactorGroup::clear_cofactor), which
is what guarantees the output lives in rather than in
, and the explicit identity rejection on lines 34-39, which keeps
group_hash from returning the neutral element.
4. Failure modes
- Treating Jubjub like a Weierstrass curve. Jubjub is twisted Edwards, so
the affine identity is , not "the point at infinity"; equations use
coordinates, not .
jubjub::AffinePoint::identity()and the related helpers handle this for you. Caught by: the type system (you cannot accidentally call a Weierstrass-only API). - Forgetting to clear the cofactor. A point sampled in by
hash-to-curve without
clear_cofactormay have small order (order ). Operations involving such a point can leak group structure or open up attacks like the onefrom_bytes_not_small_orderdefends against. Caught by:value::ValueCommitment::from_bytes_not_small_orderrejects small-order points, and the circuit assertsg_dis not small order. - Using BLAKE2 without personalisation. Two unrelated Sapling PRFs would
collide if they used the same hash with the same key material. The
personalisation strings in
constants.rsare not optional; they are domain-separation tags. Caught by: a reviewer with the protocol spec. No automated test in this workspace catches "wrong personalisation" because the test vectors hash with the right personalisation by definition.
5. Spec pointers
- Zcash Protocol Specification, §5.4 (Cryptographic Constructions) is the authority on every primitive in this chapter.
- BLS12-381 design notes by Benjamin Edgington, for the curve.
- Jubjub design for parameters and test vectors of the curve used as .
- Groth, "On the Size of Pairing-Based Non-interactive Arguments", EUROCRYPT 2016, for the proof system. Cited as Definition 3.10 above.
- Hopwood, "Pedersen hashes in Sapling" (issue thread) discusses the rationale for the windowed Pedersen construction.
6. Exercises
- Compute one BLAKE2s personalisation. Take the input bytes
b"sapling"and personalise the hash withCRH_IVK_PERSONALIZATION = b"Zcashivk". Compute the 32-byte output by hand using any BLAKE2s reference implementation. Compare against the output ofBlake2sParams::new().hash_length(32).personal(b"Zcashivk") .hash(b"sapling")from theblake2s_simdcrate. - Show the curve embedding numerically. Open a Rust REPL and evaluate
bls12_381::Scalar::NUM_BITS. Then evaluate<jubjub::Base as ff::PrimeField>::NUM_BITS. State the relationship in one sentence. (You should observe that they are both 255, becausejubjub::Baseisbls12_381::Scalar.) - Verify subgroup membership. Write a small program that takes the bytes
[0u8; 32], deserializes them withjubjub::ExtendedPoint::from_bytes, and checksis_small_order(). Observe that the result isSome(true)(the neutral element is in the small-order subset, with order 1). Then take the bytes ofconstants::SPENDING_KEY_GENERATOR.to_bytes()and run the same check. ObserveSome(false).
Answers in the code. For exercise 2, the embedding fact is documented in
src/group_hash.rs line 19:
assert!(bls12_381::Scalar::NUM_BITS == 255);. For exercise 3, the small-order
check is in
src/value.rs::ValueCommitment::from_bytes_not_small_order
and
src/verifier.rs::check_spend.
7. Further reading
- Bowe, Grigg, Hopwood, "Sapling: Improved zk-SNARK Performance for Zcash", explains the design space the curves and Pedersen hash were chosen from.
- Daira Hopwood's internal notes on Pedersen-hash collision attacks motivate the no-duplicate-and-no-linear-relation tests on the seven generators.