Circuit Implementation Guide
Overview
This guide explains how to implement and add new cryptographic hash functions to the Circom benchmarking suite. All circuits follow a consistent pattern for benchmarking purposes.
Circuit Pattern
All hash circuits in Deimos follow this standard pattern:
1pragma circom 2.0.0;23include "./hash_function.circom";45template HashBench(N) {6 signal input in[N]; // Input bytes7 signal output out[32]; // Hash output (32 bytes for 256-bit)8 9 component hash = HashFunctionBytes(N);10 hash.in <== in;11 out <== hash.out;12}1314component main {public[in]} = HashBench(32);Key points:
{public[in]}makes the input public- Default input size is N bytes
- Output size is typically 32 bytes
Implemented Circuits
SHA-256
- Location:
circuits/sha256/ - Library: Built-in circomlib
- Constraints:
- Non-linear: 29,668
- Linear: 1,916
Keccak-256
- Location:
circuits/keccak256/ - Library: vocdoni/keccak256-circom
- Constraints:
- Non-linear: 152,832
- Linear: 86,664
BLAKE2s-256
- Location:
circuits/blake2s256/ - Library: hash-circuits by Faulhorn Labs
- Constraints:
- Non-linear: 32,832
- Linear: 1,719
Poseidon
- Location:
circuits/poseidon/ - Library: circomlib and hash-circuits
- Constraints:
- Non-linear: 405
- Linear: 766
- Note: ZK-friendly hash function optimized for arithmetic circuits. Significantly fewer constraints than traditional hashes.
MiMC-256
- Location:
circuits/mimc256/ - Library: hash-circuits
- Constraints:
- Non-linear: 11,904
- Linear: 65
- Note: Minimal multiplicative complexity hash function for ZK applications.
Pedersen Hash
- Location:
circuits/pedersen/ - Library: circomlib
- Constraints:
- Non-linear: 383
- Linear: 18
- Note: Elliptic curve based hash function commonly used in ZK systems.
Adding New Hash Functions
Step 1: Create Directory Structure
cd benchmarking-suite/frameworks/circom
# Create directory for your circuit
mkdir -p circuits/your_hash
# Create input for your circuit
mkdir -p inputs/your_hash
touch inputs/your_hash/input_9.jsonStep 2: Create Test Input
Create inputs/your_hash/input_9.json with your test data:
{
"in": [
"72", "101", "108", "108", "111", "32", "87", "111",
"114", "108", "100", "33", "32", "84", "104", "105",
"115", "32", "105", "115", "32", "97", "32", "116",
"101", "115", "116", "32", "109", "115", "103", "46"
]
}This is "Hello World! This is a test msg." in decimal bytes.
Step 3: Create Circuit
Create circuits/your_hash/circom.circom:
pragma circom 2.0.0;
include "./your_hash.circom";
template YourHashBench(N) {
signal input in[N];
signal output out[32];
component hash = YourHashBytes(N);
hash.in <== in;
out <== hash.out;
}
component main {public[in]} = YourHashBench(32);Step 4: Compile Circuit
cd circuits/your_hash
# Compile circuit
circom circom.circom --r1cs --wasm --sym --cStep 5: Compute Witness
Copy your input_9.json to the appropriate directory based on your chosen method:
- For WebAssembly:
circuits/your_hash/circom_js/ - For C++:
circuits/your_hash/circom_cpp/
Option 1: Computing Witness with WebAssembly
cd circom_js
node generate_witness.js circom.wasm input_9.json witness.wtnsOption 2: Computing Witness with C++
cd circom_cpp
make
./circom input_9.json witness.wtnsStep 6: Generate Proving Keys
Choose the appropriate Powers of Tau file based on your circuit's constraint count:
- pot14 = 16,384 constraints
- pot16 = 65,536 constraints
- pot18 = 262,144 constraints
- pot20 = 1,048,576 constraints
# Start Powers of Tau ceremony (if not already done)
snarkjs powersoftau new bn128 18 pot18_0000.ptau -v
snarkjs powersoftau contribute pot18_0000.ptau pot18_0001.ptau --name="First contribution" -v
snarkjs powersoftau prepare phase2 pot18_0001.ptau pot18_final.ptau -v
# Generate circuit-specific keys
snarkjs groth16 setup circom.r1cs ../../phase1/pot18_final.ptau your_hash_0000.zkey
snarkjs zkey contribute your_hash_0000.zkey your_hash_0001.zkey --name="1st Contributor" -v
snarkjs zkey export verificationkey your_hash_0001.zkey verification_key.jsonStep 7: Generate and Verify Proof
# Generate proof
snarkjs groth16 prove your_hash_0001.zkey circom_js/witness.wtns proof.json public.json
# Verify proof
snarkjs groth16 verify verification_key.json public.json proof.json
# Expected output: [INFO] snarkJS: OK!Step 8: Document Your Circuit
Add your circuit to the documentation with the following information:
- Circuit name and location
- Library/source used
- Constraint counts (non-linear and linear)
- Any special considerations or notes
Common Issues
Too Many Values for Input Signal
Cause: Input JSON has fields that don't match circuit inputs.
Fix: Circuit expects only 'in', remove any other fields like 'hash', 'N', etc.
Not Enough Coefficients
Cause: Powers of Tau file is too small for your circuit.
Fix: Use a larger pot file (pot14 → pot16 → pot18 → pot20).
Library Import Errors
Cause: Incorrect import paths.
Fix: Use relative paths to circomlib and other libraries. Check existing circuits for examples.
Circuit Verification Checklist
Before considering your circuit complete:
- Circuit compiles without errors
- Proof generation succeeds
- Proof verification succeeds
- Public inputs/outputs are correctly configured
- Constraint count is documented
Performance Optimization
The R1CS / arithmetic circuit format allows limited opportunities for optimization, but some strategies help:
- Use ZK-friendly hash functions (Poseidon, MiMC) when possible - they have far fewer constraints
- Reuse components instead of creating new instances
Resources
- Circom Documentation: https://docs.circom.io
- snarkjs Guide: https://github.com/iden3/snarkjs
- circomlib Repository: https://github.com/iden3/circomlib
- hash-circuits: Faulhorn Labs implementation reference
- Powers of Tau: https://github.com/iden3/snarkjs#powers-of-tau
Reference Implementations
Check existing implementations for guidance:
- SHA-256:
circuits/sha256/- Standard MSB-first pattern - Keccak-256:
circuits/keccak256/- LSB-first pattern with external library - Poseidon:
circuits/poseidon/- ZK-friendly hash example