2026-07-01
The ZK Security Checklist for Teams
An actionable, end-to-end security checklist for teams shipping zero-knowledge systems: circuit development, tooling, setup handling, verifier integration, operations, audit readiness, and incident response.
The ZK Security Checklist for Teams
Zero-knowledge systems fail differently from ordinary software: a soundness bug produces proofs that are supposed to look valid, so there may be no crash, no revert, and no log line — only forged withdrawals or invalid state transitions that the chain happily accepts. That failure profile demands a security process that runs end to end, from constraint-writing culture to incident response. This checklist is organized so a team can adopt it section by section. Treat each unchecked box as an open risk, not a nice-to-have.
1. Circuit Development Practices
Constraint review culture
- A written specification of the intended statement exists for every circuit: exactly what the proof attests, with all public/private inputs and their ranges. Code review references it.
- Every
<--(or equivalent unconstrained assignment in your language) requires a comment justifying it and pointing at the constraint that pins the value. Reviewers treat unpaired<--as a blocking defect, likeunsafewithout a safety comment. - Signal-by-signal review is part of the definition of done: for each signal, someone has answered "what constrains this?"
- Two-person review on all constraint changes, however small — one-character diffs change soundness.
- circomlib (or other library) templates are used with their documented input assumptions checked at the call site — e.g., inputs to comparators are range-checked first; full-width decompositions use strict variants.
Deterministic builds
- Compiler version, flags (including optimization level), and library commits are pinned; the compiled artifact (R1CS/ACIR/etc.) is reproducible byte-for-byte from source.
- CI recompiles and diffs artifact hashes; a hash change without a source change fails the build.
- Constraint counts are tracked per release; unexplained drops are investigated before merge.
Test vectors and negative tests
- Known-answer test vectors exist for every cryptographic gadget (hashes, signatures, Merkle paths), cross-checked against an independent implementation.
- A negative test suite constructs malicious witnesses directly — bypassing the honest witness generator — and asserts that verification fails: out-of-range values, wrong nullifiers, tampered Merkle paths, boundary values (
0,p-1,2^n, field-modulus aliases). - Edge instantiations of parameterized templates (size 0, 1, maximum) are tested or explicitly forbidden.
- Completeness tests confirm honest users at boundary values can still prove (over-constraint is a DoS).
2. Tooling
- Static analysis runs in CI on every PR (circomspect for circom; the nearest equivalent for your stack). Warnings are triaged with written suppressions, never blanket-ignored.
- Determinism/under-constraint checking (Picus, or Ecne-style R1CS analysis) runs on critical gadgets, at minimum: nullifier derivation, decompositions, comparators, and anything guarding value.
- Formal verification is applied where feasible — small, high-value, rarely-changing components first. Document what has been formally verified and against what specification.
- Witness fuzzing runs continuously or on a schedule, mutating valid witnesses and asserting verification failure.
- Tool versions are pinned and upgraded deliberately; a tool upgrade that silences a warning gets the same scrutiny as code.
3. Proving Key and Setup Handling
- The provenance of phase-1 parameters (e.g., a perpetual powers of tau transcript) is documented, and your team has independently run transcript verification — not just trusted the coordinator's claim.
- For Groth16: the phase-2 ceremony corresponds to the exact shipped circuit (compiler version, flags, commit), with a verifiable transcript and, ideally, a contribution from your own team.
- Proving key, verifying key, and verifier contract hashes are pinned in the repo and checked in CI and at deployment.
- The re-ceremony process for circuit patches is written down now: owner, tooling, participant plan, verification steps, expected timeline.
- Old proving/verifying keys are explicitly revoked in deployed systems when circuits change — a forgotten verifier accepting proofs against a deprecated circuit is a live vulnerability.
4. Verifier Integration
The verifier contract is where circuit-level correctness meets chain-level reality; most exploitable gaps live at this seam.
- Public input validation: every public input is checked on-chain to be a canonical field element (
< p) and within its application range. Values the circuit assumes about public inputs must be enforced by either the circuit or the contract — decide which, and document it. - Proof malleability: nothing in the protocol uses proof bytes as an identifier, nullifier, or replay key. (Groth16 proofs are re-randomizable; a valid proof can be transformed into a different valid proof of the same statement.)
- Nullifier handling: nullifiers are recorded and checked atomically with the state change, derivation is constrained inside the circuit, and the mapping is scoped so nullifiers from one circuit/version/domain can't collide with another's.
- Replay and cross-chain scoping: proofs are bound to chain ID, contract address, and version where relevant, so a proof accepted in one context can't be replayed in another.
- Root/state freshness: Merkle roots accepted by the verifier come from a controlled window of recent roots; stale-root policies are explicit.
- The verifier contract itself is audited alongside the circuit — including the generated code (e.g., snarkjs-exported verifiers) and any hand modifications to it.
- Verification failure paths revert cleanly and cannot be used to grief (e.g., unbounded gas in input handling).
5. Operational Security
- Any centrally-operated prover infrastructure treats witness data as secrets: private inputs never hit logs, crash dumps, or third-party telemetry.
- Upgrade paths for verifier contracts and circuits are governed and time-locked where the threat model allows; the party who can swap a verifying key can forge everything, so that capability is guarded like a treasury key.
- Monitoring exists for the things a soundness bug would move: total value locked vs. sum of deposits (conservation invariants), nullifier set growth, proof submission rates, unusual public input distributions. Because forged proofs verify, economic invariants are often your only tripwire.
- Alerting has a runbook and an owner; a conservation-invariant breach pages a human.
- Dependencies (proving libraries, zkVM versions, curve libraries) are pinned and tracked against upstream security advisories.
6. Audit Readiness
- The intended-statement specifications (Section 1) are current and versioned — auditors verifying constraints against a spec find real bugs; auditors reverse-engineering intent find fewer.
- An architecture document covers the full trust boundary: circuits, witness generation, prover infrastructure, keys, verifier contracts, and upgrade authorities.
- Known deviations, accepted risks, and suppressed tool warnings are catalogued with rationale.
- Prior audit reports and fix commits are available, with each finding mapped to its fix and its regression test.
- The negative test suite and fuzzing harnesses are handed to auditors — and you ask auditors to extend them, so their work compounds.
7. Incident Response
- A written plan exists for "we believe proofs can be forged," including: who can pause the system, how (circuit-independent pause switch, not one requiring a valid proof), and the decision authority for using it.
- Because exploitation may be invisible, the plan includes retroactive verification: can you re-derive expected state from deposits/events and detect counterfeit value? Practice this reconciliation before you need it.
- Disclosure policy is decided in advance — including whether a silent-fix-then-disclose path (as Zcash used for BCTV14) is available given your governance, and how users are made whole.
- Contact channels with your auditors, proving-stack maintainers, and relevant bug bounty platforms are established before an incident.
- Post-incident, the forged-witness pattern becomes a permanent negative test.
Closing
No team checks every box on day one. Prioritize in this order: negative testing and constraint review culture (they catch the dominant bug class), verifier integration checks (the cheapest exploits live there), then setup hygiene, monitoring, and response. The unifying principle across all seven sections is the same: assume the prover is malicious, assume forged proofs look perfect, and build every layer — code, keys, contracts, ops — so that the only witnesses that verify are the ones your specification intended.