Appearance
ZK Circuits
Circom circuits for zero-knowledge proof generation.
Circuit: withdraw_v2
Main privacy circuit proving ownership and solvency.
circuits/
├── withdraw_v2.circom # Main circuit
├── lib/
│ ├── poseidon.circom # Hash function
│ └── merkle.circom # Tree verification
└── build/
├── withdraw_v2.wasm # Browser WASM (~2 MB)
├── withdraw_v2.zkey # Proving key (~15 MB)
└── verification_key_v2.jsonPublic Inputs (7)
| Index | Field | Purpose |
|---|---|---|
| 0 | root | Merkle tree root |
| 1 | nullifier_hash | Double-spend prevention |
| 2 | recipient | Output commitment |
| 3 | relayer | Fee recipient |
| 4 | fee | Relay fee |
| 5 | amount_in | Verified solvency |
| 6 | min_amount_out | Slippage protection |
Private Inputs
| Input | Purpose |
|---|---|
| secret | Ownership proof |
| nullifier | Pre-image for nullifier_hash |
| amount | Bound to commitment |
| pathElements[20] | Merkle siblings |
| pathIndices[20] | Left/right path |
What It Proves
- Prover knows
secretandnullifierfor a commitment commitment = Poseidon(Poseidon(secret, nullifier), amount)- Commitment exists in Merkle tree with given root
nullifier_hash = Poseidon(nullifier)- Public inputs match private computations
Circuit Logic
circom
template WithdrawV2(levels) {
// Public inputs
signal input root;
signal input nullifierHash;
signal input recipient;
signal input relayer;
signal input fee;
signal input amountIn;
signal input minAmountOut;
// Private inputs
signal input secret;
signal input nullifier;
signal input amount;
signal input pathElements[levels];
signal input pathIndices[levels];
// Compute commitment
component innerHash = Poseidon(2);
innerHash.inputs[0] <== secret;
innerHash.inputs[1] <== nullifier;
component commitment = Poseidon(2);
commitment.inputs[0] <== innerHash.out;
commitment.inputs[1] <== amount;
// Verify Merkle membership
component tree = MerkleTreeChecker(levels);
tree.leaf <== commitment.out;
tree.root <== root;
for (var i = 0; i < levels; i++) {
tree.pathElements[i] <== pathElements[i];
tree.pathIndices[i] <== pathIndices[i];
}
// Verify nullifier hash
component nullHash = Poseidon(1);
nullHash.inputs[0] <== nullifier;
nullHash.out === nullifierHash;
// Bind amount
amount === amountIn;
}Parameters
| Parameter | Value |
|---|---|
| Proof system | Groth16 |
| Curve | BN254 |
| Hash | Poseidon |
| Tree depth | 20 |
| Constraints | ~6,800 |
Performance
| Step | Time |
|---|---|
| Load WASM | ~2s |
| Witness generation | ~1s |
| Proof computation | ~15-20s |
| Total | ~20s |
Trusted Setup
Groth16 requires a ceremony:
- Phase 1: Powers of tau (universal, reusable)
- Using Hermez ceremony (54+ participants)
- Phase 2: Circuit-specific
- Generates proving/verification keys
Security: Only one honest participant needed.