Claude-skill-registry circom-dev
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.
git clone https://github.com/majiayu000/claude-skill-registry
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/circom-dev" ~/.claude/skills/majiayu000-claude-skill-registry-circom-dev && rm -rf "$T"
skills/data/circom-dev/SKILL.mdCircom 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:
# 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:
cp assets/template_circuit.circom circuits/your_circuit.circom
Edit the circuit with your logic:
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 scripts/compile_circuit.sh circuits/your_circuit.circom
This generates:
- Witness calculatorbuild/your_circuit_js/your_circuit.wasm
- Constraint systembuild/your_circuit.r1cs
- Symbol mappingbuild/your_circuit.sym
4. Setup Proving Keys
Generate zkey and verification key:
bash scripts/setup_keys.sh build/your_circuit.r1cs
This generates:
- Proving keybuild/zkey/your_circuit.zkey
- Verification keybuild/zkey/verification_key.json
- Solidity verifierbuild/zkey/your_circuit_verifier.sol
5. Create Test
Use the test template:
cp assets/template_test.js test.js
Update test inputs and run:
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 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):<==
output <== input1 * input2;
Use
for explicit constraints:===
component.out === expectedValue;
NEVER use
alone (no constraint):<--
// DANGEROUS - prover can cheat! temp <-- unconstrained_value;
4. Validate Inputs
Always constrain input ranges:
// 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 for security guidelines.
Common Circuit Patterns
Consult 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:
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:
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:
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:
npx snarkjs r1cs info build/circuit.r1cs
Optimization tips:
- Reuse components when possible
- Minimize hash operations (expensive)
- Use
for compile-time checks (no runtime cost)assert - 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:
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 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
- Add debug signals: Create intermediate signals to inspect values in witness
- Use circom logger: Add
statements in circuitlog() - Test incrementally: Build circuit piece by piece, testing each addition
- Verify constraint count: Monitor constraint growth as you add logic
Scripts Reference
compile_circuit.sh
Compile circom circuits to WASM and R1CS.
./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.
./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.
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
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
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
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:
// 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();
# 2. Compile bash scripts/compile_circuit.sh circuits/password_auth.circom # 3. Setup keys bash scripts/setup_keys.sh build/password_auth.r1cs
// 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();
# 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/