APM

>Agent Skill

@eyepyon/circom-dev

skilldevelopment

Zero-knowledge proof circuit development using circom. Use when implementing arithmetic circuits for zkSNARKs, including circuit design, constraint verification, witness generation, and testing. Triggers on tasks like "create a circom circuit", "implement ZK proof", "write Merkle tree circuit", "add range proof", or any zero-knowledge cryptography implementation requiring circom.

testingsecurity
apm::install
$apm install @eyepyon/circom-dev
apm::skill.md
---
name: circom-dev
description: Zero-knowledge proof circuit development using circom. Use when implementing arithmetic circuits for zkSNARKs, including circuit design, constraint verification, witness generation, and testing. Triggers on tasks like "create a circom circuit", "implement ZK proof", "write Merkle tree circuit", "add range proof", or any zero-knowledge cryptography implementation requiring circom.
---

# Circom Development

Expert guidance for designing, implementing, and testing zero-knowledge proof circuits using circom.

## Overview

This skill provides comprehensive support for circom circuit development:

- **Circuit Implementation**: Design and write optimized arithmetic circuits
- **Constraint Verification**: Ensure circuits are properly constrained and secure
- **Witness Generation**: Create and test witness calculators
- **Testing**: Comprehensive testing patterns for circuits
- **Best Practices**: Security guidelines and optimization techniques

## Quick Start

### 1. Project Setup

Initialize a new circom project with required dependencies:

```bash
# Copy package.json template
cp assets/package.json ./package.json

# Install dependencies
npm install

# Create project structure
mkdir -p circuits build scripts
```

### 2. Create Circuit

Use the circuit template as a starting point:

```bash
cp assets/template_circuit.circom circuits/your_circuit.circom
```

Edit the circuit with your logic:

```circom
pragma circom 2.0.0;
include "node_modules/circomlib/circuits/poseidon.circom";

template YourCircuit() {
    signal input in;
    signal output out;

    component hasher = Poseidon(1);
    hasher.inputs[0] <== in;
    out <== hasher.out;
}

component main = YourCircuit();
```

### 3. Compile Circuit

Use the compilation script:

```bash
bash scripts/compile_circuit.sh circuits/your_circuit.circom
```

This generates:
- `build/your_circuit_js/your_circuit.wasm` - Witness calculator
- `build/your_circuit.r1cs` - Constraint system
- `build/your_circuit.sym` - Symbol mapping

### 4. Setup Proving Keys

Generate zkey and verification key:

```bash
bash scripts/setup_keys.sh build/your_circuit.r1cs
```

This generates:
- `build/zkey/your_circuit.zkey` - Proving key
- `build/zkey/verification_key.json` - Verification key
- `build/zkey/your_circuit_verifier.sol` - Solidity verifier

### 5. Create Test

Use the test template:

```bash
cp assets/template_test.js test.js
```

Update test inputs and run:

```bash
node test.js
```

## Workflow Decision Tree

```
┌─────────────────────────────────────┐
│ What do you need to do?             │
└──────────────┬──────────────────────┘

       ┌───────┴────────┐
       │                │
   New Circuit    Modify Existing
       │                │
       ▼                ▼
  Start from      Read existing
   template        circuit first
       │                │
       ▼                ▼
  Implement       Understand
  constraints     constraints
       │                │
       ▼                ▼
   Compile         Make changes
       │                │
       ▼                ▼
  Setup keys      Recompile
       │                │
       ▼                ▼
  Write tests     Update tests
       │                │
       └────────┬───────┘

          Run & verify

        ┌───────┴────────┐
        │                │
    Success         Failure
        │                │
        ▼                ▼
    Complete      Debug & fix

                        └──> Repeat
```

## Circuit Design Guidelines

### 1. Define Requirements

Before writing code, clarify:

- **Private inputs**: What information must remain secret?
- **Public inputs**: What can be revealed to the verifier?
- **Outputs**: What statement are you proving?
- **Constraints**: What rules must be enforced?

**Example:** Password authentication
- Private: password
- Public: passwordHash
- Statement: "I know a password that hashes to passwordHash"

### 2. Choose Components

Consult [circomlib_components.md](references/circomlib_components.md) for standard components:

- **Hashing**: Poseidon, MiMC
- **Comparisons**: IsZero, LessThan, IsEqual
- **Merkle Trees**: SMTVerifier
- **Signatures**: EdDSA

### 3. Write Constraints

Follow these principles:

**Use `<==` for most operations** (assigns AND constrains):
```circom
output <== input1 * input2;
```

**Use `===` for explicit constraints**:
```circom
component.out === expectedValue;
```

**NEVER use `<--` alone** (no constraint):
```circom
// DANGEROUS - prover can cheat!
temp <-- unconstrained_value;
```

### 4. Validate Inputs

Always constrain input ranges:

```circom
// Ensure value is less than maximum
component check = LessThan(32);
check.in[0] <== value;
check.in[1] <== maxValue;
check.out === 1;
```

See [best_practices.md](references/best_practices.md) for security guidelines.

## Common Circuit Patterns

Consult [circuit_patterns.md](references/circuit_patterns.md) for complete implementations:

### Authentication
- **Password proof**: Prove knowledge of password without revealing it
- **Credential verification**: Prove possession of valid credentials

### Merkle Trees
- **Membership proof**: Prove element is in a set without revealing which
- **Tree update**: Prove correct update of Merkle tree

### Range Proofs
- **Value in range**: Prove value is within bounds (e.g., age > 18)
- **Balance sufficiency**: Prove sufficient balance without revealing amount

### Voting
- **Anonymous voting**: Vote without revealing identity
- **Weighted voting**: Vote with weight based on holdings

### Privacy
- **Private transfer**: Transfer funds without revealing sender/receiver/amount
- **Nullifier pattern**: Prevent double-spending

## Testing Workflow

### 1. Unit Test Components

Test individual templates in isolation:

```javascript
const circuit = await wasm_tester("circuits/component.circom");

// Test valid input
const input = { in: 10 };
const witness = await circuit.calculateWitness(input);
await circuit.checkConstraints(witness);

// Test expected output
await circuit.assertOut(witness, { out: 100 });
```

### 2. Test Edge Cases

Always test:
- **Zero values**: `{ in: 0 }`
- **Maximum values**: Near field prime
- **Boundary conditions**: Min/max range values
- **Invalid inputs**: Should fail constraint checks

### 3. Integration Testing

Test full proof generation and verification:

```javascript
const { proof, publicSignals } = await snarkjs.groth16.fullProve(
  input,
  wasmPath,
  zkeyPath
);

const vKey = JSON.parse(fs.readFileSync(vkeyPath));
const isValid = await snarkjs.groth16.verify(vKey, publicSignals, proof);
assert(isValid);
```

Use `scripts/verify_proof.js` for standalone verification:

```bash
node scripts/verify_proof.js -p proof.json -s public.json -v verification_key.json
```

## Optimization Techniques

### 1. Minimize Constraints

Fewer constraints = faster proving time.

**Check constraint count:**
```bash
npx snarkjs r1cs info build/circuit.r1cs
```

**Optimization tips:**
- Reuse components when possible
- Minimize hash operations (expensive)
- Use `assert` for compile-time checks (no runtime cost)
- Combine operations where possible

### 2. Choose Efficient Components

- **Poseidon > MiMC**: Poseidon is optimized for zkSNARKs
- **Bit operations**: Use Num2Bits/Bits2Num for bit manipulation
- **Range checks**: Use SafeLessThan for range-checked comparisons

### 3. Profile Circuit

Monitor statistics:
```bash
npx snarkjs r1cs info circuit.r1cs
```

Output shows:
- Number of constraints
- Number of public inputs/outputs
- Number of private inputs
- Number of wires

## Security Checklist

Before deploying, verify:

- [ ] All signals properly constrained (no `<--` without verification)
- [ ] Input ranges validated
- [ ] Division operations properly constrained (with remainder check)
- [ ] Public/private signals correctly specified
- [ ] Edge cases tested (0, max values, boundaries)
- [ ] No under-constrained circuits (verify constraint count)
- [ ] Code reviewed
- [ ] Static analysis tools run (Circomspect, PICUS if available)

See [best_practices.md](references/best_practices.md) for detailed security guidelines.

## Troubleshooting

### Common Issues

**"Constraint doesn't match"**
- Check all signals are properly constrained with `<==` or `===`
- Verify arithmetic is correct
- Look for signals using `<--` without corresponding constraints

**"Not enough values"**
- Ensure all inputs are provided in test
- Check array sizes match template parameters

**"Scalar size exceeds field size"**
- Input value is too large for the field
- Use range constraints to validate inputs

**High constraint count**
- Review circuit for optimization opportunities
- Consider refactoring complex logic
- Check for unnecessary component instantiations

### Debugging Tips

1. **Add debug signals**: Create intermediate signals to inspect values in witness
2. **Use circom logger**: Add `log()` statements in circuit
3. **Test incrementally**: Build circuit piece by piece, testing each addition
4. **Verify constraint count**: Monitor constraint growth as you add logic

## Scripts Reference

### compile_circuit.sh

Compile circom circuits to WASM and R1CS.

```bash
./scripts/compile_circuit.sh <circuit_file> [options]

Options:
  -o, --output DIR    Output directory (default: build)
  -h, --help         Show help
```

### setup_keys.sh

Generate proving and verification keys.

```bash
./scripts/setup_keys.sh <r1cs_file> [options]

Options:
  -s, --size N        Circuit size (default: 12)
  -o, --output DIR    Output directory (default: build/zkey)
  -p, --ptau DIR      ptau directory (default: ptau)
  -h, --help         Show help
```

### verify_proof.js

Verify a zero-knowledge proof.

```bash
node scripts/verify_proof.js [options]

Options:
  -p, --proof FILE     Proof JSON file
  -s, --signals FILE   Public signals JSON file
  -v, --vkey FILE      Verification key file
  -h, --help          Show help
```

## References

This skill includes detailed reference documentation:

### [circomlib_components.md](references/circomlib_components.md)

Standard library component reference:
- Hash functions (Poseidon, MiMC)
- Comparators (IsZero, LessThan, IsEqual)
- Multiplexers (Mux1, Mux3)
- Bitwise operations (Num2Bits, Bits2Num)
- Merkle trees (SMTVerifier)
- Signatures (EdDSA)

### [best_practices.md](references/best_practices.md)

Security and optimization guidelines:
- Constraint writing best practices
- Security considerations
- Optimization techniques
- Testing and debugging
- Common pitfalls and how to avoid them

### [circuit_patterns.md](references/circuit_patterns.md)

Implementation patterns for common use cases:
- Authentication patterns
- Merkle tree patterns
- Range proof patterns
- Voting patterns
- Privacy-preserving patterns

## Example: Complete Workflow

Here's a complete example of implementing a password authentication circuit:

```circom
// 1. Create circuit: circuits/password_auth.circom
pragma circom 2.0.0;
include "node_modules/circomlib/circuits/poseidon.circom";

template PasswordAuth() {
    signal input password;
    signal input passwordHash;

    component hasher = Poseidon(1);
    hasher.inputs[0] <== password;
    passwordHash === hasher.out;
}

component main {public [passwordHash]} = PasswordAuth();
```

```bash
# 2. Compile
bash scripts/compile_circuit.sh circuits/password_auth.circom

# 3. Setup keys
bash scripts/setup_keys.sh build/password_auth.r1cs
```

```javascript
// 4. Create test: test_password.js
const snarkjs = require("snarkjs");
const circomlibjs = require("circomlibjs");

async function test() {
  // Calculate expected hash
  const password = 12345;
  const poseidon = await circomlibjs.buildPoseidon();
  const passwordHash = poseidon.F.toString(poseidon([password]));

  // Generate proof
  const { proof, publicSignals } = await snarkjs.groth16.fullProve(
    { password, passwordHash },
    "build/password_auth_js/password_auth.wasm",
    "build/zkey/password_auth.zkey"
  );

  // Verify
  const vKey = require("./build/zkey/verification_key.json");
  const isValid = await snarkjs.groth16.verify(vKey, publicSignals, proof);
  console.log("Valid:", isValid);
}

test();
```

```bash
# 5. Run test
node test_password.js
```

## Additional Resources

- **Circom Documentation**: https://docs.circom.io/
- **circomlib Repository**: https://github.com/iden3/circomlib
- **snarkjs**: https://github.com/iden3/snarkjs
- **0xPARC Learning**: https://learn.0xparc.org/
- **ZK Whiteboard Sessions**: https://zkhack.dev/whiteboard/