# Trusted setup ceremony — library choice and M-participant design Design note for replacing the throwaway single-party setup behind the ZK recovery circuit (gnark Groth16, BLS12-381, 3,450,403 constraints → domain size N = 2^22 = 4,194,304). Sourced from a five-track literature/code review (June 2026). Primary sources cited inline. ## TL;DR Use **gnark's own `backend/groth16/bls12-381/mpcsetup`** as the cryptographic core, wrapped in a **thin custom Go coordinator** we build. Run a **fresh BLS12-381 Powers of Tau (phase 1)** ourselves rather than trying to reuse an external transcript. Seal each phase with a **drand beacon** at a pre-committed future round. Target **~50–100 diversity-weighted participants** with public attestations, but treat the single most important control as **having at least two independent contributor/verifier implementations** — software monoculture, not participant count, is the failure mode that has actually sunk ceremonies. Two things must be fixed before ceremony day, and they are the real work: 1. gnark issue [#1428](https://github.com/Consensys/gnark/issues/1428) — `Phase2.Initialize` allocates all coefficient slices upfront and can waste tens of GB at our circuit size. Patch it (or upstream the fix) first. 2. Build a second, independent verifier so the ceremony isn't trusting one binary. ## Why gnark's own mpcsetup, and not snarkjs / p0tion / Filecoin Our circuit is gnark on BLS12-381. Almost every mature ceremony tool in the wild is BN254 (snarkjs, PSE p0tion, the Aztec/zkparty coordinators, Perpetual Powers of Tau) or a different proving system (Ethereum KZG, Dusk PLONK). Curve and format both matter, and converting between them is real cryptographic engineering, not a flag. gnark ships a native two-phase MPC setup for all its curves, BLS12-381 included ([pkg.go.dev](https://pkg.go.dev/github.com/consensys/gnark/backend/groth16/bls12-381/mpcsetup)). It implements the BGM17 protocol ([ePrint 2017/1050](https://eprint.iacr.org/2017/1050)) with a proof-of-knowledge per contribution, a SHA-256 hash-chained transcript, and a final beacon step. The API: ```go // Phase 1 — Powers of Tau (circuit-independent, reusable for future circuits ≤ N) func NewPhase1(N uint64) *Phase1 func (p *Phase1) Contribute() // add randomness, emit PoK func (p *Phase1) Verify(next *Phase1) error // check one step (PoK + chain) func (p *Phase1) Seal(beaconChallenge []byte) SrsCommons func VerifyPhase1(N uint64, beaconChallenge []byte, c ...*Phase1) (SrsCommons, error) // Phase 2 — circuit-specific func (p *Phase2) Initialize(r1cs *cs.R1CS, commons *SrsCommons) Phase2Evaluations func (p *Phase2) Contribute() func (p *Phase2) Verify(next *Phase2) error func (p *Phase2) Seal(commons *SrsCommons, evals *Phase2Evaluations, beaconChallenge []byte) (groth16.ProvingKey, groth16.VerifyingKey) func VerifyPhase2(r1cs *cs.R1CS, commons *SrsCommons, beaconChallenge []byte, c ...*Phase2) (ProvingKey, VerifyingKey, error) ``` The decisive advantage: the output of `Seal` is a `groth16.ProvingKey`/`VerifyingKey` that drops straight into the prover and the on-chain verifier we already built. No converter to write, no converter to audit. Anyone can re-verify the whole ceremony by replaying `VerifyPhase1` / `VerifyPhase2` over the public transcript. The honest caveats, both from the research: - The BLS12-381 variant has **zero known production runs** (0 importers on pkg.go.dev). The BN254 twin is battle-tested (Worldcoin's Semaphore-MTB ceremony); BLS12-381 is not. We'd be the first serious user, so we own the risk and should contribute fixes upstream. - Issue #1428's memory blowup in `Phase2.Initialize` is real at 3.45M constraints. Fix before, not during, the ceremony. The alternative — adopt Filecoin's completed BLS12-381 phase 1 (2^27, public at trusted-setup.filecoin.io) and write a bellperson→gnark converter — buys a huge, already-contributed phase 1 but costs us a non-trivial, security-critical converter with no public precedent for Groth16 (Avail extracted KZG params from the same ceremony, which is close but not the same). Not worth it for a first ceremony. Revisit only if we want Filecoin's contributor set. ## Phase 1 path: run our own There is no drop-in reusable BLS12-381 phase 1 for gnark: - Zcash Sapling — BLS12-381 but only 2^21 (too small for 2^22) and the transcript was deliberately destroyed ([zfnd](https://zfnd.org/conclusion-of-the-powers-of-tau-ceremony/)). - Filecoin — BLS12-381, 2^27, public, but bellperson format with no gnark converter. - Ethereum KZG — BLS12-381 but KZG structure (only [τ]G2), missing the full G2 power series Groth16 needs. - Perpetual Powers of Tau / Aztec — BN254, wrong curve. So phase 1 is a gnark-native `NewPhase1(2^22)` ceremony we run. Upside: it's reusable as the phase 1 for any future recovery-family circuit up to 2^22, so we only pay for it once. ## Pre-ceremony engineering checklist - [ ] Patch gnark #1428 (`Phase2.Initialize` per-slice allocation) and validate peak RAM on the real 2^22 R1CS. Upstream the patch. - [ ] Build the coordinator service (below). ~500–1000 LOC of Go around the mpcsetup API. - [ ] Build/commission a **second independent implementation** — at minimum an independent verifier that replays the transcript in a different codebase/language, ideally an independent contributor path too. This is the highest-value control (see threat model). - [ ] Reproducible builds for the contributor binary; publish the expected hash; pin to a commit SHA. - [ ] Dry-run the whole pipeline on a small circuit (2^10) end to end, including beacon and third-party verification. - [ ] Write the on-chain VK equality check: recompute the VK from the sealed transcript and assert it byte-equals the verifier we deploy (Veil Cash lesson, below). ## The ceremony, step by step Two sequential phases. Same machinery, run twice. ### Coordinator (thin Go service around mpcsetup) - HTTP API: `GET /transcript/current`, `POST /contribution`, `GET /transcript/{n}`, registration. - State machine per slot: `waiting → contributing → verifying → published`. - Auth: GitHub OAuth device flow (as p0tion does), record handle + contribution hash publicly. Optional minimum-account-age filter. - Transfer: presigned S3/GCS URLs for the multi-GB transcript up/down; don't stream through the coordinator. (Phase-1 SrsCommons at 2^22 ≈ 1.2 GB; per-participant files run a few GB.) - On receipt: run `Verify(prev, next)`. Pass → advance and publish. Fail → discard, serve the same transcript to the next participant unchanged (a bad contribution can't corrupt the chain). - Per-slot timeout (30–60 min); on expiry evict and move to the next queued participant. The chain is never broken by a dropout. - Publish every accepted contribution + its hash to IPFS; maintain a public index. The coordinator can censor or abort but, by BGM17, **cannot break soundness** — it never holds a secret. Worst case is a re-run. ### Participant flow (each of the M) 1. Register, get a queue slot (order randomized or beacon-selected to defang last-participant games). 2. Download the current transcript via presigned URL. 3. On a **fresh/airgapped machine**, run the contributor (a verified build): `ReadFrom` → `Contribute()` → `WriteTo`. Mix in extra entropy (OS CSPRNG plus physical: dice, camera, audio). 4. Upload the new transcript; the coordinator verifies and publishes. 5. Publish a contribution hash + a short written attestation (hardware used, entropy source, how the secret was destroyed). Destroy the ephemeral machine/key material. ### Sealing with the beacon After the last human contribution closes, apply a public randomness beacon as the final, non-secret contribution via `Seal(beaconChallenge)`. This removes any residual bias the last real participant could introduce, and it's reproducible by every verifier. **Beacon: drand (League of Entropy).** Commit in advance to a specific future drand round number, far enough out that all contributions finish first. drand's unchained scheme produces 48-byte BLS12-381 signatures — same curve as our circuit, clean to feed as `beaconChallenge` ([drand docs](https://docs.drand.love/docs/cryptography/)). For belt-and-suspenders, combine it with a future Ethereum block hash at a pre-announced height: `beaconChallenge = BLAKE2b(drand_round || eth_block_hash)`. An attacker then has to bias both the drand majority and Ethereum block production. (Namada used BTC+ETH+ZEC hashed 2^42 times; Zcash Sapling iterated SHA-256 2^42 times over a Bitcoin block hash. Multi-source + delay is the trend.) Use drand v2.1.0+ after the Feb 2025 incident. ## Choosing M and the participants The guarantee is "sound if at least one participant in each phase honestly samples and deletes its secret" — the M need not trust each other, and phase-1 and phase-2 participant sets can be disjoint ([BGM17](https://eprint.iacr.org/2017/1050), [NCC Group](https://www.nccgroup.com/research/security-considerations-of-zk-snark-parameter-multi-party-computation/)). The toxic waste is τ, α, β in phase 1 and γ, δ in phase 2; each is a uniformly random multiplier the adversary can't factor out unless it compromises *everyone*. So diversity beats raw count. What maximizes "at least one honest": - **Organizational/jurisdictional**: different companies, universities, non-profits, protocols, in different countries — no single entity or state can coerce all of them. - **Hardware/OS**: x86, ARM, SGX; Linux, macOS, BSD; airgapped and not. - **Public figures** with reputational skin in the game (the Aztec/PPoT pattern). Historical reference points: Zcash Sprout 6 (too few by today's bar), Filecoin phase-2 15–20 known institutions per circuit, Zcash Sapling 87–90 (the de facto community floor), Aztec Ignition 176, Namada 2,510, Ethereum KZG 141,416. Above a few dozen diverse, attested participants the marginal cryptographic gain is ~nil — large M is social proof, not security. **Recommendation:** ~50–100 curated, diverse, attested participants for each phase, optionally with a permissionless tail. 90 (Sapling-class) is the comfortable credibility target. Order via beacon. Plan 4–8 weeks of wall-clock if sequential, less if cohort-batched (Namada ran 20-minute cohorts). ## Verifiability - **Per-contribution PoK + hash chain** — native in gnark; reordering/inserting/editing any contribution breaks the chain, and a zero-multiplier ("contribute 1") is rejected by the PoK. - **End-to-end re-verification** — anyone downloads the public transcript and runs `VerifyPhase1` / `VerifyPhase2`; it returns the keys or an error. - **Attestations** — each participant publishes their contribution hash and a written statement; some livestream the hash. Creates accountability and lets the community spot-audit. - **Deployment check (do not skip)** — recompute the VK from the sealed transcript and assert it byte-equals the verifying key embedded in the on-chain validator. The Veil Cash drain ($2.9 ETH, part of $3M+ across protocols) came from a deployed verifier with γ = δ = generator while the ceremony itself was fine. The ceremony output and the deployed verifier must be proven identical, and we should run a forgery test (random proof against the deployed VK must fail). ## Threat model and mitigations | Risk | Mitigation | |------|------------| | **Software monoculture** (the #1 historical mistake — one backdoored binary turns M-of-M into 0-of-M) | ≥2 independent implementations, reproducible builds, published binary hash, pinned commit | | Non-deletion of toxic waste | Ephemeral/airgapped hardware, `zeroize`-style memory wiping, attested physical destruction | | Compromised participant machine | Fresh OS, airgap, optional Faraday/SGX; only one honest participant needed | | Supply chain on the contributor tool | Reproducible builds, binary transparency, encourage alternate implementations | | Weak randomness | Mandate OS CSPRNG + external entropy mixing; health-check distinct outputs | | Transcript withholding / coordinator censorship | Publish each step to IPFS (content-addressed); optionally anchor contribution hashes on-chain | | Adaptive last-participant bias | Beacon seal + beacon/committed ordering | | Protocol bug (cf. Zcash Sprout BCTV14 forgery in the archived transcript) | We use Groth16 (well-analyzed) + the existing on-chain verifier audit; audit ceremony tooling | | **Deployment misconfig** (Veil Cash γ=δ) | VK-equality check + forgery test against the deployed verifier | ## Decisions for Charles 1. Run our own phase 1 (recommended) vs. invest in a Filecoin→gnark converter to inherit their contributor set. 2. Target M and whether to include a permissionless tail or stay curated/attested. 3. Who builds the second independent verifier (in-house vs. commissioned) — it's the load-bearing control. 4. Beacon: drand-only vs. drand + Ethereum block hash combination. ## Key sources gnark mpcsetup ([pkg.go.dev](https://pkg.go.dev/github.com/consensys/gnark/backend/groth16/bls12-381/mpcsetup)), issue [#1428](https://github.com/Consensys/gnark/issues/1428), discussion [#1014](https://github.com/Consensys/gnark/discussions/1014); BGM17 [ePrint 2017/1050](https://eprint.iacr.org/2017/1050); NCC Group [security considerations](https://www.nccgroup.com/research/security-considerations-of-zk-snark-parameter-multi-party-computation/); [ZKProof setup ceremonies](https://zkproof.org/2021/06/30/setup-ceremonies/); drand [cryptography](https://docs.drand.love/docs/cryptography/) and [v2.0 postmortem](https://docs.drand.love/blog/2025/03/21/drand-v2-0-postmortem/); Zcash Sapling [completion](https://electriccoin.co/blog/completion-of-the-sapling-mpc/) and [PoT conclusion](https://zfnd.org/conclusion-of-the-powers-of-tau-ceremony/); Filecoin [trusted setup](https://filecoin.io/blog/posts/trusted-setup-complete/); Aztec [Ignition](https://aztec.network/blog/aztec-crs-the-biggest-mpc-setup-in-history-has-successfully-finished); Ethereum [KZG wrap-up](https://blog.ethereum.org/2024/01/23/kzg-wrap); Namada [trusted setup](https://namada.net/blog/completion-of-the-namada-trusted-setup); Penumbra [summoning deep dive](https://penumbra.exchange/blog/summoning-ceremony-deep-dive); PSE [p0tion](https://github.com/privacy-scaling-explorations/p0tion); Veil Cash [Groth16 forgery](https://www.darknavy.org/web3/exploits/veil-cash-groth16-forgery/).