ZK Systems Engineering
A proof only proves the constraints you wrote. The core engineering question is whether those constraints express the intended statement under malicious witness input.
1. Statement before circuit
Write the statement in plain language, then list public inputs, private witness values, assumptions, and forbidden states. Example: “The claimant knows a leaf and Merkle path whose root equals root, and nullifier N has not been used.” This is stronger than “the circuit verifies.”
| Artifact | Must be versioned |
|---|---|
| Circuit source and compiler version | constraint count and build hash |
| Proving/verifying key | ceremony/setup provenance and hash |
| Witness generator | input schema and deterministic test vectors |
| Verifier contract | deployed address, public-input ordering, key hash, upgrade authority |
| Prover service | resource limits, queue policy, timeouts, privacy/logging policy |
2. Constraint discipline
// Pseudocode: this is unsafe if `enabled` is unconstrained.
out <== enabled * secretHash;
// Constrain the selector and intended relation.
enabled * (enabled - 1) === 0;
out === enabled * Poseidon(secret);
Every signal that affects acceptance must be constrained. Add negative tests for invalid paths, wrong public roots, malformed witnesses, reused nullifiers, wrong field ordering, and alternate witnesses that should be rejected. Use circuit review tooling where available, but manually trace the acceptance condition.
3. System tradeoffs
- Groth16 is compact but has setup/key-management implications. PLONKish systems change the proving/key tradeoff; KZG commitments and recursion affect architecture.
- Recursive/aggregated proofs can reduce on-chain verification work but add proving complexity and operational latency.
- zkVMs improve familiar-program ergonomics but may cost more and expose a different soundness/performance surface than hand-written circuits.
- Measure witness generation, proving time, memory, queue depth, proof size, verifier gas, and failure rate under realistic concurrent load.
4. Practical lab — private allowlist claim
- Build a Merkle-membership circuit with a private leaf/path and public root/nullifier. The contract rejects an already-consumed nullifier.
- Write a test-vector generator and prove/verify valid and invalid cases in CI. Add an intentionally underconstrained circuit; demonstrate a forged claim before correcting it.
- Generate verifier artifacts, deploy the verifier to a testnet, and store source hashes, compiler/tool versions, verifier address, and input schema in a manifest.
- Run a small prover worker with a bounded queue, input validation, timeouts, metrics, and no witness logging. Benchmark at least three proof sizes.
Lab repo: labs/phase-24-zk/ — starter circuit is deliberately incomplete.