compact-zkir-lint
v0.2.0
Published
Find Compact circuit bugs before your users do. Detects JS/ZK divergence patterns where compact-runtime succeeds but proof validation fails.
Maintainers
Readme
compact-zkir-lint
Your Compact circuit compiled fine. Your tests pass. But the proof server rejects your transaction.
compact-zkir-lint tells you why before your users do.
npx compact-zkir-lint -r contracts/src/artifacts/ addLiquidity (v2, k=13): 1 error(s)
instructions: 343 inputs: 5 constrain_bits: 12 cond_select: 8
guarded regions: 3 (max depth 2) proof payload: ~384KB
ERROR [DIV-001] inst 128: constrain_bits(bits=64) on arithmetic in conditional branch (guard=824)
1 error(s) | 4/11 circuits affectedInstall and run
# Scan a single circuit
npx compact-zkir-lint circuit.zkir
# Scan all circuits in your compiled artifacts
npx compact-zkir-lint -r contracts/src/artifacts/
# Profile proving time across environments
npx compact-zkir-lint --profile -r contracts/src/artifacts/
# CI-friendly: SARIF output, non-zero exit on errors
npx compact-zkir-lint -r contracts/src/artifacts/ --format sarif > results.sarifNo dependencies on Midnight packages. Reads the .zkir JSON files that the compiler already produces. Works offline.
What it finds
In ZK circuits, both branches of an if/else execute unconditionally — only the result is selected via cond_select. Constraints inside dead branches fire on invalid intermediate values, causing proof failures that JS testing can't catch.
compact-zkir-lint detects 16 patterns across four categories:
| Category | Rules | Severity | |----------|-------|----------| | Divergence (DIV-) | DIV-001 through DIV-005 | error / warn | | Runtime (RT-) | RT-001 through RT-004 | warn / info | | Statistics (STATS-) | STATS-001, STATS-002 | info | | Performance (PERF-) | PERF-001 through PERF-006 | error / warn / info |
See the full rules reference for details, examples, and fix guidance.
How it works
- Parses compiled
.zkirJSON files (v2 format, zero dependencies) - Builds a data-flow graph: which instruction produces which memory variable
- Tracks guard propagation: which variables are inside conditional branches
- Memoized zero-analysis: determines if dead-branch values default to zero (safe) vs non-zero (dangerous)
- Flags constraint instructions operating on branch-local values before they reach a
cond_selectmerge
Documentation
- Rules reference — all 17 rules with examples and fix guidance
- Circuit profiling — estimate proving time per environment
- Branchless patterns — how to restructure code to avoid divergence
- CI integration — SARIF output, GitHub Actions, exit codes
- Differential testing — JS vs ZKIR fuzz testing for deeper analysis
- Compatibility — version tracking and ZKIR v3 roadmap
Acknowledgements
- OpenZeppelin for the Uint128.subU128 workaround that informed the DIV-001 fix patterns
- The LunarSwap team for the original bug report that led to this tool
License
Apache-2.0
