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:

circom
1pragma circom 2.0.0;
2
3include "./hash_function.circom";
4
5template HashBench(N) {
6 signal input in[N]; // Input bytes
7 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}
13
14component 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

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

Step 2: Create Test Input

Create inputs/your_hash/input_9.json with your test data:

json
{
  "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:

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

Bash
cd circuits/your_hash

# Compile circuit
circom circom.circom --r1cs --wasm --sym --c

Step 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

Bash
cd circom_js

node generate_witness.js circom.wasm input_9.json witness.wtns

Option 2: Computing Witness with C++

Bash
cd circom_cpp

make
./circom input_9.json witness.wtns

Step 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
Bash
# 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.json

Step 7: Generate and Verify Proof

Bash
# 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