zk-circuit-audit

star 0

Use when modifying deposit, withdrawal, settlement, or commitment code — audits off-chain hash functions against circuit constraints to prevent the

DigiRoninCollective By DigiRoninCollective schedule Updated 2/24/2026

name: zk-circuit-audit description: Use when modifying deposit, withdrawal, settlement, or commitment code — audits off-chain hash functions against circuit constraints to prevent the #1 ZK vulnerability class

ZK Circuit Audit

Verify that off-chain commitment/nullifier computations match the Circom circuit constraints exactly.

The #1 ZK vulnerability (0xPARC bug tracker): hash function mismatch between off-chain code and circuit verification. If the off-chain deposit uses SHA256 but the circuit uses Poseidon, every proof will fail silently.

Announce at start: "Running ZK circuit audit on the affected files."

When to Trigger

This skill MUST run whenever you modify:

  • Any file that computes commitment, nullifierHash, secret, or nullifier values
  • bot/orchestrator.mjs (deposit logic)
  • bot/note-manager.mjs (key derivation)
  • bot/proof-generator.mjs (proof inputs)
  • matchmaker/proof-generator.mjs (settlement proofs)
  • relayer/index.mjs or relayer/batch-relay.mjs (proof serialization)
  • Any new module that touches ZK-related data

The Audit Checklist

1. Hash Function Consistency

Read the circuit files to confirm the expected hash:

circuits/withdraw.circom — uses Poseidon(2) for commitment, Poseidon(1) for nullifierHash
circuits/match.circom — uses Poseidon(2) for deposit commitment, Poseidon(1) for nullifier, Poseidon(3) for order commitment

Then scan the modified files for:

Pattern Expected Danger
commitment = ... Poseidon(secret, nullifier) via hash2() SHA256, keccak, or any non-Poseidon hash
nullifierHash = ... Poseidon(nullifier) via hash1() SHA256 or direct hashing
orderCommitment = ... Poseidon(price, amount, depositCommitment) via hash3() Any non-Poseidon

Action: Grep for createHash, SHA256, keccak near any commitment/nullifier computation. If found, it's almost certainly wrong.

2. BN254 Field Safety

Circuit signals are BN254 field elements (< ~2^254). Off-chain BigInts from 32-byte buffers can exceed the field.

Rule: Any 32-byte HMAC/hash output converted to BigInt for circuit input MUST be truncated to 31 bytes first.

// CORRECT: 31-byte truncation
const secretBig = BigInt("0x" + secret.subarray(0, 31).toString("hex"));

// WRONG: full 32 bytes can exceed field
const secretBig = BigInt("0x" + secret.toString("hex"));

Action: Search for BigInt("0x" conversions in modified files. Verify they use .subarray(0, 31).

3. Proof Serialization

The on-chain Groth16 verifier expects a specific 256-byte layout with y-negation:

[0:32]    pi_a.x       (BE)
[32:64]   BN254 - pi_a.y  (y-negation)
[64:96]   pi_b[0][1]   (BE, x-swapped)
[96:128]  pi_b[0][0]   (BE)
[128:160] pi_b[1][1]   (BE, y-swapped)
[160:192] pi_b[1][0]   (BE)
[192:224] pi_c.x       (BE)
[224:256] pi_c.y       (BE)

Action: If proof serialization code is modified, verify the y-negation uses the correct BN254 prime: 21888242871839275222246405745257275088696311157297823662689037894645226208583n

4. Commitment Encoding for On-Chain

The on-chain deposit instruction expects the commitment as a 32-byte little-endian buffer:

// CORRECT: LE encoding for on-chain
function bigintToLE(bn) {
  const buf = Buffer.alloc(32);
  let tmp = bn;
  for (let i = 0; i < 32; i++) {
    buf[i] = Number(tmp & 0xffn);
    tmp >>= 8n;
  }
  return buf;
}

Action: Verify deposit data uses LE encoding, NOT BE.

5. Run the Automated Check

./scripts/verify-circuit-compat.sh

This script checks SHA256 misuse, random secret generation, Poseidon import coverage, account ordering, and BN254 safety.

Quick Bash Verification

If you want to do a quick manual check:

# Find SHA256 near commitments (should return nothing)
grep -rn 'createHash.*sha256' bot/ matchmaker/ relayer/ --include='*.mjs' | grep -i 'commit\|nullif'

# Find randomBytes for secrets (should only be in non-critical paths)
grep -rn 'randomBytes' bot/ --include='*.mjs' | grep -i 'secret\|nullif'

# Verify Poseidon is imported in all critical modules
for f in bot/orchestrator.mjs bot/note-manager.mjs bot/proof-generator.mjs; do
  grep -l 'poseidon\|hash2\|hash1' "$f" && echo "  ✓ $f" || echo "  ✗ $f MISSING POSEIDON"
done

Output

After the audit, report:

  • PASS — all hash functions match circuits, field safety verified
  • WARN — non-critical findings (e.g., SHA256 used for non-ZK purposes like discriminators)
  • FAIL — hash mismatch found, field overflow possible, or serialization error

If FAIL, do NOT proceed with the commit until fixed.

Install via CLI
npx skills add https://github.com/DigiRoninCollective/dotfiles --skill zk-circuit-audit
Repository Details
star Stars 0
call_split Forks 0
navigation Branch main
article Path SKILL.md
More from Creator
DigiRoninCollective
DigiRoninCollective Explore all skills →