Skip to main content

15 - Trusted setup ceremonies

1. Why this chapter exists

The Sprout and Sapling SNARKs require a structured reference string (SRS) that must be generated by a procedure no single party can subvert. If the toxic waste leaks, the soundness of Groth16 collapses and an adversary can forge proofs. The integrity of every wallet that uses librustzcash rests on three SHA-256 constants in zcash_proofs/src/lib.rs and the chain of MPC-ceremony transcripts they pin. A contributor touching parameter loading, downloader code, or any MockTxProver call site must understand what the SRS contains, why verify_hash is the load-bearing check, and what Orchard's Halo 2 gains by avoiding the ceremony altogether.

2. Definitions

Definition (structured reference string, SRS)

The Groth16 verifying key has the form

vk  =  (αG1,βG2,γG2,δG2,  {xiG1}i=0)\mathsf{vk} \;=\; \bigl(\alpha G_1, \beta G_2, \gamma G_2, \delta G_2,\; \{x_i G_1\}_{i=0}^{\ell}\bigr)

for uniformly random α,β,γ,δFr\alpha, \beta, \gamma, \delta \in \mathbb{F}_r and a polynomial-in-xx structure that depends on the circuit. The proving key is much larger: many G1\mathbb{G}_1 and G2\mathbb{G}_2 points derived from the same secret scalars. The pair (pk,vk)(\mathsf{pk}, \mathsf{vk}) is the SRS.

Definition (toxic waste)

The set of scalars {α,β,γ,δ,τ}\{\alpha, \beta, \gamma, \delta, \tau\} used to derive the SRS. Anyone who learns them can forge proofs of false statements. The trusted setup is the procedure of generating vk\mathsf{vk} and the proving key without anyone learning the toxic waste.

Definition (multi-party computation, MPC, ceremony)

A sequential protocol where each participant PiP_i contributes a secret τi\tau_i and produces a transcript that the next participant verifies. Concretely, to generate [τ]G[\tau] G for τ=τi\tau = \prod \tau_i:

  1. P1P_1 samples τ1\tau_1 and outputs [τ1]G[\tau_1] G.
  2. P2P_2 samples τ2\tau_2 and outputs [τ2τ1]G[\tau_2 \tau_1] G.
  3. ... PnP_n outputs [τ1τ2τn]G[\tau_1 \tau_2 \cdots \tau_n] G.

If any one τi\tau_i is uniform and the corresponding participant erases their share, no coalition of the others recovers τ\tau. Each participant publishes a verifiable transcript proving they followed the protocol without revealing τi\tau_i.

Definition (Powers of Tau)

For Groth16, τ\tau is not a single scalar but a vector

{[τi]G1}i=0N,  {[τi]G2}i=0N,\{[\tau^i] G_1\}_{i=0}^{N},\; \{[\tau^i] G_2\}_{i=0}^{N},

plus α\alpha-shifted and β\beta-shifted variants. The 2018 Zcash Powers of Tau ceremony generated up to N=2211N = 2^{21} - 1 with 100\sim 100 GB output. The output is the Phase 1 SRS.

Definition (Phase 2 / circuit-specific MPC)

Takes the Phase 1 SRS and a specific R1CS circuit, produces the proving/verifying keys for that circuit. The Sapling MPC ran in early 2018 with 90\sim 90 participants and produced sapling-spend.params, sapling-output.params, and sprout-groth16.params.

Invariant (parameter integrity)

For each parameter file consumed by a wallet, the SHA-512 of the file bytes must match the corresponding constant (SAPLING_SPEND_HASH, SAPLING_OUTPUT_HASH, SPROUT_HASH) in zcash_proofs/src/lib.rs. A mismatch must abort loading. This is the only check that prevents a substituted (potentially backdoored) parameter file from being silently used.

3. The code

3.1 Hardcoded parameter hashes

The three SHA-512 hashes for the parameter files live at the top of zcash_proofs::lib:

zcash_proofs/src/lib.rs
loading...

The byte-length constants are paired with the hashes so a wallet that fails to read the full file (network truncation, disk error) gets an explicit length mismatch rather than a confusing hash mismatch.

3.2 The verify_hash helper

load_parameters and the download path both stream parameter bytes through a hashing reader, then call verify_hash to compare against the hardcoded constant:

zcash_proofs/src/lib.rs
loading...

The function compares the computed hash against expected_hash and returns an InvalidData error on mismatch. The byte-count mismatch path produces an expected vs actual bytes error message that aids debugging without leaking parameter content.

3.3 The MPC ceremonies in summary

Powers of Tau (2018). 87 participants. Each generated τi\tau_i on an air-gapped machine and destroyed it. Transcripts (gigabytes each) were transferred on encrypted USB drives. Each new transcript was verified by the next participant using the pairing check

e([τi+1]G1,G2)  =  e([τi]G1,[τ]G2).e([\tau^{i+1}] G_1,\, G_2) \;=\; e([\tau^i] G_1,\, [\tau] G_2).

The final transcript hash was published; anyone can re-verify it.

Sapling MPC (2018, Phase 2). 90\sim 90 participants. Took the Powers of Tau output and produced the per-circuit pk\mathsf{pk} and vk\mathsf{vk} for Sapling Spend and Sapling Output. The same MPC produced the "hybrid Sprout" parameters (post CVE-2019-7167, see chapter 12).

The three resulting files and their hashes:

FileHash constantApprox. size
sapling-spend.paramsSAPLING_SPEND_HASH in zcash_proofs47 MB
sapling-output.paramsSAPLING_OUTPUT_HASH in zcash_proofs3.6 MB
sprout-groth16.paramsSPROUT_HASH in zcash_proofs725 MB

3.4 The Sprout-Groth16 addendum

The original Sprout (BCTV14) parameters were generated in a separate 2016 ceremony. After CVE-2019-7167, the Sprout circuit was re-expressed in bellman for Groth16 and parameters were regenerated in the 2018 Sapling MPC. The proving key is large because the Sprout circuit was tuned for BCTV14 and never re-architected for Groth16 efficiency. Sprout is legacy: closed to new outputs since NU5.

3.5 Verifying the ceremony

Anyone can verify the Powers of Tau and Sapling MPC transcripts. The verification involves:

  • Hash chaining: each contributor's transcript embeds the hash of the prior transcript, forming a tamper-evident chain.
  • Pairing checks: each transcript satisfies a sequence of pairing equations that prove the contributor multiplied the prior SRS by some scalar without revealing which.
  • Public attestation: each contributor signs and publishes their step.

Full re-verification takes hours on a single machine but is deterministic. Volunteers have run it independently after the ceremony. If the SHA-512 of your downloaded sapling-spend.params matches SAPLING_SPEND_HASH, the file has not been substituted.

3.6 What is in the proving key

For Groth16, the proving key contains:

  • The QAP polynomials evaluated at τ\tau, encoded as G1\mathbb{G}_1 points: {[Ai(τ)]G1,[Bi(τ)]G1,[Ci(τ)]G1}\{[A_i(\tau)] G_1, [B_i(\tau)] G_1, [C_i(\tau)] G_1\}.
  • The products βAi(τ)+αBi(τ)+Ci(τ)\beta A_i(\tau) + \alpha B_i(\tau) + C_i(\tau) in G1\mathbb{G}_1.
  • The HH-table {[τi/δ]G1}\{[\tau^i / \delta] G_1\} used to commit to the quotient polynomial.

For a circuit with NN wires and degree DD constraints, this is O(N+D)O(N + D) group elements. For Sapling Spend, 100,000\sim 100{,}000 constraints; hence the 47\sim 47 MB key.

The verifier only needs αG1,βG2,γG2,δG2\alpha G_1, \beta G_2, \gamma G_2, \delta G_2 and {[τi/γ]G1}i=0\{[\tau_i / \gamma] G_1\}_{i=0}^{\ell} for public-input length \ell. The verifying-key file is tens of kilobytes.

3.7 bellman's representation of the proving key

bellman::groth16::Parameters stores:

pub struct Parameters<E: Engine> {
pub vk: VerifyingKey<E>,
pub h: Arc<Vec<E::G1Affine>>,
pub l: Arc<Vec<E::G1Affine>>,
pub a: Arc<Vec<E::G1Affine>>,
pub b_g1: Arc<Vec<E::G1Affine>>,
pub b_g2: Arc<Vec<E::G2Affine>>,
}

The on-disk format is a serialised version of this structure. zcash_proofs::load_parameters parses it after verifying the SHA-512. The proving key is Arc'd because it is shared between threads in multicore proving; it is read-only once loaded.

3.8 PreparedVerifyingKey

bellman::groth16::PreparedVerifyingKey precomputes the pairing ingredients to make verification faster:

pub struct PreparedVerifyingKey<E: Engine> {
pub alpha_g1_beta_g2: E::Gt,
pub neg_gamma_g2: <E::G2Affine as PreparedCurveAffine>::Prepared,
pub neg_delta_g2: ...,
pub ic: Vec<E::G1Affine>,
}

The wallet calls prepare_verifying_key(&vk) once and caches the result. Each verification is then a Miller-loop plus final exponentiation plus a constant-size multi-scalar multiplication.

3.9 Halo 2's transparent setup

Halo 2 has no per-circuit setup. The SRS is the set of generators

{G0,G1,,G2k1}EPallas(Fp),\{G_0, G_1, \ldots, G_{2^k - 1}\} \subset E_{\text{Pallas}}( \mathbb{F}_p),

derived deterministically from a hash function applied to a fixed personalisation string and a counter. Any verifier with the hash function definition can re-derive the generators independently.

No toxic waste; no MPC. The trade-off is larger prover/verifier work. The generators come from halo2_proofs::poly::commitment::Params::new(k). The Orchard verifier constructs them on demand or caches them.

3.10 Soundness implications

For Groth16: as long as one MPC participant was honest, soundness holds. The Sapling parameters have 90\sim 90 participants; even if 89 are malicious, soundness is preserved.

For Halo 2 / IPA: soundness reduces to the discrete-log assumption on Pallas. No participant honesty is needed; the curve and the hash-to-curve are public. This is one of the main reasons Orchard chose Halo 2: it removes "what if the ceremony was rigged" from the trust model.

3.11 The download-params and bundled-prover flows

zcash_proofs::download_parameters (feature-gated behind download-params) fetches parameters from a CDN, verifies the SHA-512, and caches locally at ~/.zcash-params/. Production wallets download once at install time.

The bundled-prover feature embeds parameters directly into the binary, eliminating the runtime download at the cost of 50\sim 50 MB of binary size. The bundled parameters still have their SHA-512 verified at runtime against the hardcoded hashes; bundling is an optimisation, not a trust short-cut.

3.12 Future directions

A rebuild today would likely use Halo 2 (no trusted setup). The migration cost is high (different proof system, new circuit, parameter compatibility break) and has not been deemed worth it given the existing setup's strong public-attestation record. For future shielded-asset proposals (chapter 21), the proof system choice is open: Halo 2 for transparency, or one of the newer trusted-setup-free SNARKs (HyperPlonk, Spartan, Nova-style folding, Plonky3). The codebase is set up so the proving system is modular per pool.

4. Failure modes

  • Skipping verify_hash. A substituted parameter file passes silently. The entire trusted-setup argument depends on this check; removing it is a critical vulnerability. The hash must be checked even when the file is bundled into the binary.
  • Hardcoding a wrong hash constant. A typo in SAPLING_SPEND_HASH causes every honest wallet to reject the ceremony output. The byte-length constants exist partly to catch this earlier.
  • Re-reading the parameter file per proof. The file is large (Sprout is 725 MB). Re-reading wastes I/O and CPU; production code loads once into an Arc and reuses. A regression that re-loads will not break correctness but will make the wallet appear unresponsive.
  • Using MockTxProver outside #[cfg(test)]. The mock prover generates parameters locally with fresh toxic waste every time. This is unsound for shipping cryptocurrencies and must remain feature-gated to tests.
  • Generating parameters locally in production. Same as above. bellman supports it but doing so per wallet defeats the ceremony entirely.
  • Disabling bundled-prover SHA-512 verification under a benchmark flag. A common temptation when measuring startup cost; never ship with this disabled.

5. Spec pointers

6. Exercises

  1. Inspect the hashes. Open zcash_proofs/src/lib.rs and confirm that SAPLING_SPEND_HASH, SAPLING_OUTPUT_HASH, and SPROUT_HASH match the published ceremony attestations. List which hash algorithm is used (hint: the constant is 128 hex chars).
  2. Trace the load path. Starting from load_parameters in zcash_proofs/src/lib.rs, follow the read path to verify_hash (lines 468 onward). Identify the exact line where a mismatch becomes an InvalidData error. What error would a wallet display if the file were truncated?
  3. Add a negative test. In a checkout, write an integration test that constructs a "parameter" file consisting of SAPLING_SPEND_BYTES random bytes and asserts that verify_hash rejects it. The test should pass; the assertion is that the returned error is io::ErrorKind::InvalidData.

Answers in the code

7. Further reading

  • Chapter 12: CVE-2019-7167, the reason the Sprout-Groth16 parameters had to be regenerated.
  • Chapter 16: the Pedersen hash that dominates the Sapling Spend circuit's constraint count and thus the proving-key size.
  • Chapter 17: the Halo 2 / IPA machinery that makes the transparent-setup alternative feasible.
  • Ben-Sasson, Bentov, Horesh, Riabzev, Scalable, transparent, and post-quantum secure computational integrity (ePrint 2018/046): the STARK family that motivates several "no trusted setup" research directions.