Trusted setup, explained
Before anyone can prove or verify a Groth16 statement, somebody has to generate the keys: a proving key for provers and a verification key for verifiers. That generation step is called the trusted setup, and it’s the part of ZK most likely to be done wrong in a real deployment. This page explains what’s being trusted, why, and how to do it properly.
The idea in one analogy
Section titled “The idea in one analogy”Imagine a factory that makes tamper-evident seals. To set up the production line, the factory casts a unique mould. Seals made from the mould are public, and anyone can inspect them. Once the line is set up, the mould is supposed to be smashed.
If someone secretly keeps the mould, they can stamp out seals that pass every inspection while sealing nothing at all.
A trusted setup works the same way. Generating the keys requires secret random numbers. The best known is called tau (τ), and Groth16 adds a few companions of its own. The public keys are derived from these secrets, and afterwards the secrets must be destroyed. Anyone who keeps them can forge proofs: create proofs that verify for statements that are false. That’s why these secrets are nicknamed toxic waste.
Two properties make this worse than it sounds:
- Forgeries are undetectable. A forged proof is mathematically indistinguishable from an honest one. No verifier, on-chain or off-chain, can tell them apart.
- The damage is silent and permanent. If the toxic waste leaks, every proof ever verified with those keys becomes suspect, and you’d never know it happened.
Knowing the toxic waste doesn’t, by itself, reveal anyone’s secret inputs from their proofs. The risk is to soundness: fake proofs, such as an under-18 user “proving” they’re over 18, or someone unlocking funds without a valid witness.
Powers of tau
Section titled “Powers of tau”What the setup actually publishes is a long list of elliptic-curve points built from powers of τ:
[1]G1, [τ]G1, [τ²]G1, [τ³]G1, …, [τⁿ]G1[1]G2, [τ]G2, …[x]G1 means “the curve’s generator point G1, multiplied by x”. Because of how elliptic curves
work, you can publish [τ]G1 without revealing τ: going backwards is the hard problem the whole
curve’s security rests on.
This list is called the powers of tau, or a structured reference string (SRS). It’s stored
in a .ptau file. Its length sets the maximum circuit size it can support; a setup with 2²⁰
powers supports circuits up to roughly that many constraints.
Phase 1 and phase 2
Section titled “Phase 1 and phase 2”Groth16 setup happens in two phases:
Phase 1 (universal) Powers of tau for BLS12-381, up to size 2ⁿ once per curve + size ─ reusable by every circuit that fits │ ▼Phase 2 (circuit-specific) Powers of tau + your compiled circuit (R1CS) once per circuit ─ produces this circuit's proving key + verification key (a snarkjs .zkey file)- Phase 1 doesn’t depend on any circuit. You can reuse a large public ceremony. ZeroJ’s ceremony runbook, for example, uses an existing attested BLS12-381 ceremony as its primary phase-1 source.
- Phase 2 is specific to one circuit and introduces its own toxic waste. Any change to the circuit, such as a new constraint, a different number or order of public inputs, or a changed circuit parameter, means a new phase 2 and new keys. Changing input values never does; a new user or a new proof uses the same keys.
Multi-party ceremonies: one honest participant is enough
Section titled “Multi-party ceremonies: one honest participant is enough”If one party generates τ, everyone has to trust that party. A multi-party computation (MPC) ceremony removes that single point of trust:
key_0 ──► Alice mixes in her randomness ──► key_1key_1 ──► Bob mixes in his randomness ──► key_2key_2 ──► Carol mixes in her randomness ──► key_3key_3 ──► public random beacon ──► key_finalEach participant takes the previous file, mixes in fresh secret randomness, publishes the result, and destroys their randomness. The final secret is effectively the product of every participant’s contribution. To forge proofs you would need all of those contributions. So the keys are safe as long as at least one participant was honest and really destroyed their contribution. This is the “1-of-N” trust model.
Each contribution is publicly checkable. Anyone can re-verify the whole chain, for example with
snarkjs zkey verify, without trusting the coordinator. A final random beacon, a public value
nobody can predict in advance such as a future block hash, closes the ceremony so the last
contributor can’t bias the result.
PlonK’s universal setup
Section titled “PlonK’s universal setup”PlonK only needs phase 1. One trusted SRS serves every PlonK circuit up to its size, and each circuit’s keys are derived from it with a public, deterministic computation, with no new toxic waste per circuit. The SRS still has to come from a trustworthy ceremony, and if its τ leaks, every circuit using it is affected at once.
ZeroJ’s PlonK support is experimental; see Groth16, PlonK & BBS.
What ZeroJ provides
Section titled “What ZeroJ provides”| Need | What ZeroJ offers |
|---|---|
| Fast local setup for tests and demos | PowersOfTauBLS381.generate(...), Groth16Keys.setupInMemory(...), Groth16SetupBLS381.setup(...): single-party, in-process, insecure by design, and disabled unless you opt in |
| Using a real phase 1 | PtauImporterBLS381 imports snarkjs .ptau files |
| Using real Groth16 keys | ZkeyImporterBLS381 imports a ceremony .zkey; Groth16PkStore and ZkeyPkStoreImporter stream large keys into an mmap-loaded store |
| Running or joining a ceremony | The zeroj-ceremony CLI (export-r1cs, contribute, finalize) and the ZkeyContributor library in zeroj-tools, producing snarkjs-compatible contributions |
| Independent verification | Contributions made with ZeroJ verify with snarkjs zkey verify, so nobody has to trust ZeroJ’s own tool |
The development opt-in
Section titled “The development opt-in”The in-process setup refuses to run unless you explicitly allow it:
JVM system property: -Dzeroj.allowInsecureTrustedSetup=trueor environment: ZEROJ_ALLOW_INSECURE_TRUSTED_SETUP=trueWithout the opt-in, the call throws an IllegalStateException explaining that the generator knows
the toxic waste and can forge proofs. Installation shows how to set the flag
for Gradle and Maven test and run tasks.
The production path
Section titled “The production path”For anything beyond local testing, the flow is:
1. Compile your circuit and export it: zeroj-ceremony export-r1cs → circuit.r1cs2. Start phase 2 from a trusted .ptau: snarkjs groth16 setup → key_0000.zkey3. Contributors mix in randomness: zeroj-ceremony contribute (or snarkjs zkey contribute)4. Close with a pre-announced beacon, and let anyone re-check: snarkjs zkey verify5. Import the final key into ZeroJ: zeroj-ceremony finalize → proving-key store6. Pin the verification key (and, on Cardano, the resulting script hash).Running a trusted setup ceremony walks through every step.
What needs a new setup?
Section titled “What needs a new setup?”| Change | New setup? |
|---|---|
| New witness values, new users, new proofs | No |
| Different public input values | No |
| Any change to the constraints | Yes, a new phase 2 (Groth16) |
| Different number or order of public inputs | Yes |
| A different circuit parameter (for example Merkle depth) | Yes. It’s a different circuit. |
| Circuit grows beyond the SRS size | Yes, and a larger phase 1 too |
Setup is expensive, so run it once per circuit version and cache the results. Never re-run it per proof.
Next steps
Section titled “Next steps”Design notes: ADR-0031, Groth16 MPC trusted-setup ceremony.