BenchmarksStack Ranking
APIsPricingDocsWhite PaperTokenBlogAboutSecurity Demo
Log InGet API Key
Zero-Knowledge · 5 min read

Circom Tutorial:
Building Your First ZK Circuit

A hands-on guide to developing zero-knowledge circuits with Circom.

67ns
Proof Verify
SHA3-256
Hash
PQ
Secure
Zero
Knowledge Leaked

Circom is the most popular language for writing ZK circuits. It compiles arithmetic constraints into R1CS (Rank-1 Constraint System) representations that can be consumed by proof systems like Groth16 and PLONK. This hands-on tutorial takes you from installation to your first working proof, then shows you how circuits like these power production-grade authentication systems.

Why Circom Matters

Zero-knowledge proofs allow one party to prove a statement is true without revealing the underlying data. The challenge has always been expressing those statements as arithmetic circuits that a proof system can evaluate. Circom solves this by providing a domain-specific language (DSL) that compiles to R1CS constraints, bridging the gap between human-readable logic and the finite-field arithmetic that zk-SNARKs require.

In production systems, ZK circuits handle everything from identity verification to credential checks. H33 uses zero-knowledge proofs as the verification layer in a pipeline that processes 2,172,518 authentications per second at ~42 microseconds per auth. The ZKP lookup stage itself completes in just 0.085 microseconds via an in-process DashMap cache. Circom is where many developers first learn the constraint-based thinking that underlies these systems.

Installation

# Install Rust (if needed)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

# Install Circom
git clone https://github.com/iden3/circom.git
cd circom
cargo build --release
cargo install --path circom

# Install snarkjs
npm install -g snarkjs

Your First Circuit

Create a simple circuit that proves knowledge of factors:

// multiplier.circom
pragma circom 2.0.0;

template Multiplier() {
    // Private inputs (witness)
    signal private input a;
    signal private input b;

    // Public output
    signal output c;

    // Constraint: a * b must equal c
    c <== a * b;
}

component main = Multiplier();

Understanding the Constraint Model

Every Circom circuit compiles down to a set of R1CS constraints of the form A * B = C, where A, B, and C are linear combinations of signals. The <== operator simultaneously assigns a value and creates a constraint. This is critical: if you only assign with <-- without a separate constraint via ===, the prover can supply any value and the proof will still verify. Unconstrained signals are the single most common source of ZK circuit vulnerabilities.

Constraint Safety Rule

Never use the assignment operator <-- without a corresponding === constraint. The combined <== operator handles both assignment and constraint in one step. If you must separate them (for non-quadratic expressions), always verify that every signal path is fully constrained.

Compiling the Circuit

# Compile to R1CS
circom multiplier.circom --r1cs --wasm --sym

# This generates:
# - multiplier.r1cs (constraint system)
# - multiplier_js/ (WASM for witness generation)
# - multiplier.sym (symbol file for debugging)

The --r1cs flag produces the constraint system, --wasm generates a WebAssembly witness calculator, and --sym emits a symbol table that maps signal names to their wire indices. For larger circuits, add --O1 to enable constraint optimization, which can significantly reduce the number of constraints and speed up both proving and verification.

Trusted Setup

# Powers of tau ceremony (can reuse for circuits up to 2^n constraints)
snarkjs powersoftau new bn128 12 pot12_0000.ptau
snarkjs powersoftau contribute pot12_0000.ptau pot12_0001.ptau
snarkjs powersoftau prepare phase2 pot12_0001.ptau pot12_final.ptau

# Circuit-specific setup
snarkjs groth16 setup multiplier.r1cs pot12_final.ptau multiplier_0000.zkey
snarkjs zkey contribute multiplier_0000.zkey multiplier_final.zkey
snarkjs zkey export verificationkey multiplier_final.zkey verification_key.json

The trusted setup is a two-phase process. Phase 1 (Powers of Tau) is universal and can be reused across any circuit up to a certain size. Phase 2 is circuit-specific. In production deployments, ceremonies involve multiple independent contributors so that the toxic waste (the random values used during setup) is destroyed as long as at least one participant is honest. For applications where setup trust is unacceptable, consider PLONK with a universal SRS or STARKs, which require no trusted setup at all.

Generating a Proof

// Create input file: input.json
{ "a": 3, "b": 5 }

// Generate witness
node multiplier_js/generate_witness.js multiplier_js/multiplier.wasm input.json witness.wtns

// Generate proof
snarkjs groth16 prove multiplier_final.zkey witness.wtns proof.json public.json

Verifying the Proof

# Verify
snarkjs groth16 verify verification_key.json public.json proof.json
# Output: [INFO] OK!

What Was Proven

The prover demonstrated knowledge of a=3 and b=5 such that 3x5=15.
The verifier sees only that c=15 and that the proof is valid.
The values of a and b remain hidden.

More Complex Example: Age Verification

Real-world circuits rarely involve a single multiplication. The age verification example below demonstrates how to compose library components from circomlib to build practical privacy-preserving checks:

pragma circom 2.0.0;

include "circomlib/comparators.circom";

template AgeCheck() {
    signal private input birthYear;
    signal input currentYear;
    signal input minimumAge;
    signal output isOldEnough;

    component gte = GreaterEqThan(16);
    gte.in[0] <== currentYear - birthYear;
    gte.in[1] <== minimumAge;

    isOldEnough <== gte.out;
}

component main = AgeCheck();

The GreaterEqThan(16) comparator uses 16 bits to represent the comparison range, meaning it handles values up to 65,535. Choosing the right bit-width is a common design decision: too few bits and your circuit silently wraps around, too many and you add unnecessary constraints. Each bit of comparison range costs roughly one constraint, so a 16-bit comparator adds about 16 constraints to your circuit.

Constraint Optimization Strategies

As circuits grow in complexity, constraint count directly impacts proving time and memory usage. A circuit with 100,000 constraints might take several seconds to prove on consumer hardware. Here are the techniques that matter most:

OperationApprox. ConstraintsNotes
Multiplication1Single R1CS constraint
Addition0Free (linear combination)
Bit decomposition (n bits)n + 1Range check included
Poseidon hash (2 inputs)~240ZK-friendly, preferred
SHA-256~28,000Expensive but widely compatible
ECDSA verify~500,000Consider EdDSA (~6,000) instead

From Circom to Production ZKP

Circom circuits give you a mental model for how zero-knowledge systems operate: private inputs flow through constraints, producing a proof that reveals nothing beyond the stated public outputs. Production systems like H33 take this principle further by combining ZKP verification with BFV fully homomorphic encryption for biometric matching and Dilithium post-quantum signatures for attestation, all within a single API call.

The same constraint-based reasoning you learn writing Circom circuits applies directly to understanding how H33 batches 32 users into a single BFV ciphertext and verifies each authentication in ~42 microseconds with post-quantum security at every stage.

Best Practices

Circom is the gateway to practical ZK development. Start simple, understand the constraint model, then build toward more complex privacy-preserving proofs. The thinking you develop here -- expressing computation as arithmetic constraints over finite fields -- is the same foundation that powers the ZKP verification layer in production systems processing millions of authentications per second.

Ready to Go Quantum-Secure?

Start protecting your users with post-quantum authentication today. 1,000 free auths, no credit card required.

Get Free API Key →

Build With Post-Quantum Security

Enterprise-grade FHE, ZKP, and post-quantum cryptography. One API call. Sub-millisecond latency.

Get Free API Key → Read the Docs
Free tier · 10,000 API calls/month · No credit card required
Verify It Yourself