Skip to content

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.json

Public Inputs (7)

IndexFieldPurpose
0rootMerkle tree root
1nullifier_hashDouble-spend prevention
2recipientOutput commitment
3relayerFee recipient
4feeRelay fee
5amount_inVerified solvency
6min_amount_outSlippage protection

Private Inputs

InputPurpose
secretOwnership proof
nullifierPre-image for nullifier_hash
amountBound to commitment
pathElements[20]Merkle siblings
pathIndices[20]Left/right path

What It Proves

  1. Prover knows secret and nullifier for a commitment
  2. commitment = Poseidon(Poseidon(secret, nullifier), amount)
  3. Commitment exists in Merkle tree with given root
  4. nullifier_hash = Poseidon(nullifier)
  5. 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

ParameterValue
Proof systemGroth16
CurveBN254
HashPoseidon
Tree depth20
Constraints~6,800

Performance

StepTime
Load WASM~2s
Witness generation~1s
Proof computation~15-20s
Total~20s

Trusted Setup

Groth16 requires a ceremony:

  1. Phase 1: Powers of tau (universal, reusable)
    • Using Hermez ceremony (54+ participants)
  2. Phase 2: Circuit-specific
    • Generates proving/verification keys

Security: Only one honest participant needed.

Released under the MIT License.