Checklist for Zero Knowledge Circuits¶
No security by obscurity.
Base checks¶
- Completeness: Are all constraints correctly defined and covering all input variables?
- Make sure circuits pass correct computation and reject incorrect computation.
- Each variable should contribute directly or indirectly to a constraint; otherwise, assigned-but-not-constrained bugs may appear.
- Input range validation: Do input variables fall within expected ranges to prevent overflow or undefined behaviour?
- Overflow/underflow check: Do both inputs and operations have adequate range checks?
- Consistency check: If multiple paths compute the same variable, do they yield the same result?
- Zero knowledge property check: Does the circuit leak private information?
- Public input binding: Are all public inputs included in at least one effective constraint?
- Statement completeness: Does the circuit bind the full statement that the verifier or smart contract will execute?
- Domain alignment: Do circuit ranges match the accepted ranges in the application layer?
Integration checks¶
- Document all assumptions, especially implicit assumptions.
- Libraries built for high-performance circuits do not enforce preconditions on inputs.
- Library integration must not leave under-constrained signals.
- When circuits integrate with smart contracts, system-level bugs may appear at the boundary.
- Verifier input order: Do public signals match the verifier ABI and on-chain input array exactly?
- Artifact provenance: Are circuit source commit, R1CS hash, zkey hash, verification key hash, and verifier bytecode hash recorded?
- Hash consistency: Do the circuit, SDK, verifier tests, and on-chain contracts use the same hash implementation and parameters?
- Asset/value binding: If the circuit represents external assets, are token identifiers, denominations, and received amounts bound to the proof statement?
- Proof system configuration: Are CRS, transcript, curve, public input serialization, and verifier configuration consistent with the intended circuit?
Architecture design checks¶
- Double-spending prevention, e.g. if protocol needs nullifier mechanism?
- Front-running.
- Withdrawal intent binding: recipient, relayer, fee, refund/auxiliary data, asset, amount, chain ID, and verifying contract should be bound when relevant.
- Ownership model: Is note ownership based on signatures, public keys, or preimage knowledge, and is this documented consistently?
- Nullifier design: Does the nullifier uniquely bind the spent note and avoid cross-context replay?
- Merkle design: Are leaf format, branch format, zero values, tree depth, path index convention, and root history consistent across circuit, SDK, and contract?
Circom-specific checks¶
- Use
<==or===for constrained assignments; treat<--as witness assignment that needs explicit constraints. - Do not declare signals or components inside loops; declare arrays outside the loop and index them.
- Check every component output that represents a validation result.
- Avoid unused components and signals because they can hide missing constraints.
- Compile every main circuit and fail CI on warnings or compile errors.
- Generate a valid proof, mutate each public input one by one, and assert verification failure.
- Test both valid and invalid witnesses for range checks, Merkle paths, nullifiers, and amount conservation.
- Pin circomlib/circomlibjs versions and compare circuit, SDK, and on-chain hash outputs.
Cryptographic primitive misuse checks¶
- Hash functions: Are primitive choice, domain separation, input encoding, arity, padding, field conversion, constants, and initial state correct?
- Commitments: Do commitments bind every required field and provide the intended hiding and binding properties?
- Nullifiers: Do nullifiers bind the note, context, asset, chain, and application domain needed to prevent replay or double-spending?
- Merkle proofs: Are leaf format, branch format, zero values, path indices, tree depth, and root history consistent across circuit, SDK, and contract?
- Signatures: Are public keys, signature ranges, message hashes, malleability rules, and verification outputs fully constrained?
- Elliptic-curve operations: Are curve membership, subgroup membership, identity points, scalar ranges, and exceptional cases handled?
- Non-native field arithmetic: Are limb ranges, carries, reductions, comparisons, and canonical outputs fully constrained?
- Pairings and recursive verifiers: Are curve checks, subgroup checks, pairing equations, transcript inputs, and verifier outputs complete?
- Fiat-Shamir transcripts: Are challenges domain-separated and bound to all relevant public inputs, commitments, and proof context?
- Encryption and note delivery: Is encrypted note data consistent with the proved commitment and recipient key assumptions?
- Cross-layer consistency: Are shared primitives tested with common vectors across circuit, SDK, contract, prover, and verifier tooling?
- Placeholder implementations: Are mocked, non-cryptographic, or keccak-based placeholders impossible to reach in production paths?
Hard-to-detect classes¶
The following bug classes are usually harder to detect manually and should receive explicit test or tooling support:
- Under-constrained circuits where missing constraints still allow plausible-looking proofs.
- Over-constrained circuits where valid witnesses are rejected only under edge cases.
- Arithmetic field errors, including unintended modular reduction and non-native field arithmetic mistakes.
- Bit decomposition, range-check, overflow, and underflow errors.
- Incorrect public input handling, unused public inputs, signal aliasing, or verifier input order mismatch.
- Unsound proof system implementation or configuration.
- Logic and semantic errors where the circuit proves a different statement from the intended specification.
- Cryptographic primitive misuse inside circuits and integrations.
- Fiat-Shamir transcript, challenge derivation, and domain separation errors.
- Incorrect system integration across circuits, SDKs, contracts, verifiers, and deployment artifacts.
Circuit Optimizations¶
- No redundant constraints: Are there any unnecessary constraints that add computational burden? (Minimum constraint rule)
Misc¶
- Third-party library security:
- Understanding implicit assumptions: Do not overlook any hidden preconditions required by the library.
- Avoiding unsafe parameters: Ensure that cryptographic components are initialized with secure and recommended parameters.
- If customizing a cryptographic scheme, ensure that it has a clear security proof and strictly follows the required implementation guidelines.
- Code readability: Is the circuit code well-structured and easy to audit?