Skip to main content

Circom Circuits

Reusable Circom circuit templates for implementing ZK-Kit components in zero-knowledge proofs.

Overview

The ZK-Kit Circom package provides circuit templates that correspond to the JavaScript implementations, allowing you to:

  • Verify Merkle tree proofs in-circuit
  • Perform EdDSA signature verification
  • Use Poseidon hash function
  • Implement privacy-preserving applications

Repository

Prerequisites

Before using ZK-Kit circuits, you need:

# Install Circom compiler
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
cargo install circom

# Or use pre-built binaries
# See: https://docs.circom.io/getting-started/installation/

For detailed setup, see Development Setup.

Installation

# Clone the repository
git clone https://github.com/privacy-scaling-explorations/zk-kit.circom.git

# Or add as a submodule to your project
git submodule add https://github.com/privacy-scaling-explorations/zk-kit.circom circuits/zk-kit

Available Circuits

Merkle Tree Circuits

Binary Merkle Tree Verifier

Verifies membership proofs for binary Merkle trees.

Location: circuits/merkle-tree/binary-tree-verifier.circom

Usage:

include "zk-kit/merkle-tree/binary-tree-verifier.circom"

component main {public [root]} = BinaryTreeVerifier(20);

LeanIMT Verifier

Verifies proofs for Lean Incremental Merkle Trees.

Location: circuits/merkle-tree/lean-imt-verifier.circom

Usage:

include "zk-kit/merkle-tree/lean-imt-verifier.circom"

component main {public [root]} = LeanIMTVerifier(20);

Cryptography Circuits

EdDSA Poseidon Verifier

Verifies EdDSA signatures using Poseidon hash.

Location: circuits/eddsa-poseidon/eddsa-poseidon-verifier.circom

Usage:

include "zk-kit/eddsa-poseidon/eddsa-poseidon-verifier.circom"

component main {public [publicKeyX, publicKeyY]} = EdDSAPoseidonVerifier();

Poseidon Hash

Poseidon hash function implementation.

Location: circuits/poseidon/poseidon.circom

Usage:

include "zk-kit/poseidon/poseidon.circom"

component main = Poseidon(2); // 2 inputs

Baby JubJub Circuits

Point Addition

Baby JubJub elliptic curve point addition.

Location: circuits/baby-jubjub/point-add.circom

Usage:

include "zk-kit/baby-jubjub/point-add.circom"

component main = BabyJubJubAdd();

Scalar Multiplication

Baby JubJub scalar multiplication.

Location: circuits/baby-jubjub/scalar-mul.circom

Usage:

include "zk-kit/baby-jubjub/scalar-mul.circom"

component main = BabyJubJubScalarMul();

Quick Start Example

Simple Merkle Proof Verification

pragma circom 2.0.0;

include "zk-kit/merkle-tree/binary-tree-verifier.circom";

// Verify membership in a Merkle tree of depth 20
template MembershipProof() {
// Public inputs
signal input root;

// Private inputs
signal input leaf;
signal input siblings[20];
signal input pathIndices[20];

// Verify the proof
component verifier = BinaryTreeVerifier(20);
verifier.root <== root;
verifier.leaf <== leaf;

for (var i = 0; i < 20; i++) {
verifier.siblings[i] <== siblings[i];
verifier.pathIndices[i] <== pathIndices[i];
}
}

component main {public [root]} = MembershipProof();

Compile and Generate Proof

# Compile circuit
circom membership-proof.circom --r1cs --wasm --sym

# Generate witness
node membership-proof_js/generate_witness.js \
membership-proof_js/membership-proof.wasm \
input.json \
witness.wtns

# Generate proof (using snarkjs)
snarkjs groth16 prove \
membership-proof.zkey \
witness.wtns \
proof.json \
public.json

# Verify proof
snarkjs groth16 verify \
verification_key.json \
public.json \
proof.json

Circuit Templates

Binary Tree Verifier

template BinaryTreeVerifier(depth) {
signal input root;
signal input leaf;
signal input siblings[depth];
signal input pathIndices[depth];

// Implementation verifies:
// - Hash path from leaf to root
// - Path matches provided root
}

Parameters:

  • depth: Maximum depth of the tree (e.g., 20 for 2^20 leaves)

Inputs:

  • root: Expected Merkle root (public)
  • leaf: Leaf value to prove (private)
  • siblings: Sibling hashes along the path (private)
  • pathIndices: Left/right indicators (0 or 1) for each level (private)

Constraints: ~depth * 3 constraints

EdDSA Poseidon Verifier

template EdDSAPoseidonVerifier() {
signal input message;
signal input R8x;
signal input R8y;
signal input S;
signal input publicKeyX;
signal input publicKeyY;

// Implementation verifies EdDSA signature
}

Inputs:

  • message: Message that was signed (private)
  • R8x, R8y: Signature R point coordinates (private)
  • S: Signature scalar (private)
  • publicKeyX, publicKeyY: Public key coordinates (public)

Constraints: ~2000 constraints

Poseidon Hash

template Poseidon(nInputs) {
signal input inputs[nInputs];
signal output out;

// Implementation of Poseidon hash
}

Parameters:

  • nInputs: Number of inputs to hash (typically 2-16)

Inputs:

  • inputs: Array of field elements to hash

Outputs:

  • out: Hash digest

Constraints: ~nInputs * 50 constraints (approximate)

Integration with JavaScript

Use the JavaScript packages to generate inputs for circuits:

import { IMT } from "@zk-kit/imt"
import { poseidon2 } from "poseidon-lite"
import fs from "fs"

// Create tree
const tree = new IMT(poseidon2, 20, 0, 2)
tree.insert(BigInt(123))
tree.insert(BigInt(456))
tree.insert(BigInt(789))

// Generate proof
const proof = tree.createProof(1) // Prove index 1

// Create circuit input
const input = {
root: proof.root.toString(),
leaf: proof.leaf.toString(),
siblings: proof.siblings.map(s => s.toString()),
pathIndices: proof.pathIndices
}

// Save for circuit
fs.writeFileSync("input.json", JSON.stringify(input))

Best Practices

1. Circuit Design

// ✅ Good: Clear template parameters
template MyCircuit(depth, width) {
// ...
}

// ❌ Bad: Magic numbers
template MyCircuit() {
signal siblings[20]; // Why 20?
}

2. Public vs Private Signals

// ✅ Good: Minimal public inputs
component main {public [root, nullifier]} = MyCircuit();

// ❌ Bad: Exposing private data
component main {public [root, leaf, siblings]} = MyCircuit();

3. Constraint Optimization

// ✅ Good: Reuse components
component hasher = Poseidon(2);
for (var i = 0; i < n; i++) {
hasher.inputs[0] <== values[i];
hasher.inputs[1] <== salts[i];
hashes[i] <== hasher.out;
}

// ❌ Bad: Create multiple instances unnecessarily
for (var i = 0; i < n; i++) {
component hasher = Poseidon(2); // Creates n instances!
}

Proof Systems

ZK-Kit circuits work with multiple proof systems:

Groth16

# Fast verification, trusted setup
snarkjs groth16 setup circuit.r1cs pot.ptau circuit.zkey

PLONK

# No trusted setup per-circuit
snarkjs plonk setup circuit.r1cs pot.ptau circuit.zkey

FFLONK

# Smaller proofs than PLONK
snarkjs fflonk setup circuit.r1cs pot.ptau circuit.zkey

Performance Considerations

CircuitConstraintsProof TimeVerification Time
BinaryTreeVerifier(20)~60,000~2s~5ms
EdDSAPoseidonVerifier~2,000~500ms~5ms
Poseidon(2)~100<100ms~2ms

Times are approximate and depend on hardware

Testing Circuits

# Install circom tester
npm install --save-dev circom_tester

# Create test file
# tests/merkle-tree.test.js
const { wasm } = require("circom_tester")
const path = require("path")

describe("Merkle Tree Circuit", function() {
let circuit

before(async function() {
circuit = await wasm(
path.join(__dirname, "circuits", "merkle-tree.circom")
)
})

it("should verify valid proof", async function() {
const input = {
root: "123",
leaf: "456",
siblings: [/* ... */],
pathIndices: [/* ... */]
}

const witness = await circuit.calculateWitness(input)
await circuit.checkConstraints(witness)
})
})
# Run tests
npx mocha tests/

Common Use Cases

  • ✅ Anonymous voting systems
  • ✅ Private membership proofs
  • ✅ Credential verification
  • ✅ Privacy-preserving authentication
  • ✅ ZK rollups
  • ✅ Private transactions

Troubleshooting

Circuit Won't Compile

# Check Circom version
circom --version

# Use correct pragma
pragma circom 2.0.0;

# Check include paths
include "node_modules/circomlib/circuits/...";

Constraint Failures

// Add debug signals
signal debug;
debug <== intermediate_value;
log(debug);

Performance Issues

  • Reduce circuit depth where possible
  • Use batch operations
  • Consider circuit splitting
  • Profile with circom --inspect

Source

Community

Next Steps