@trynullsec/s1-zk 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (214) hide show
  1. package/LIMITATIONS.md +17 -0
  2. package/README.md +149 -0
  3. package/ROADMAP.md +21 -0
  4. package/RULES.md +229 -0
  5. package/benchmarks/historical/orchard-inspired/partial-ec-mul-halo2.rs +53 -0
  6. package/benchmarks/historical/orchard-inspired/safe-ec-mul-halo2.rs +69 -0
  7. package/dist/ai/prompt-builder.d.ts +2 -0
  8. package/dist/ai/prompt-builder.js +33 -0
  9. package/dist/ai/prompt-builder.js.map +1 -0
  10. package/dist/ai/reasoning-interface.d.ts +15 -0
  11. package/dist/ai/reasoning-interface.js +2 -0
  12. package/dist/ai/reasoning-interface.js.map +1 -0
  13. package/dist/cli.d.ts +2 -0
  14. package/dist/cli.js +71 -0
  15. package/dist/cli.js.map +1 -0
  16. package/dist/config.d.ts +4 -0
  17. package/dist/config.js +41 -0
  18. package/dist/config.js.map +1 -0
  19. package/dist/core/audit-engine.d.ts +3 -0
  20. package/dist/core/audit-engine.js +31 -0
  21. package/dist/core/audit-engine.js.map +1 -0
  22. package/dist/core/file-loader.d.ts +6 -0
  23. package/dist/core/file-loader.js +28 -0
  24. package/dist/core/file-loader.js.map +1 -0
  25. package/dist/core/issue-builder.d.ts +16 -0
  26. package/dist/core/issue-builder.js +27 -0
  27. package/dist/core/issue-builder.js.map +1 -0
  28. package/dist/core/range-check-classifier.d.ts +16 -0
  29. package/dist/core/range-check-classifier.js +194 -0
  30. package/dist/core/range-check-classifier.js.map +1 -0
  31. package/dist/core/rule-engine.d.ts +10 -0
  32. package/dist/core/rule-engine.js +41 -0
  33. package/dist/core/rule-engine.js.map +1 -0
  34. package/dist/core/severity.d.ts +6 -0
  35. package/dist/core/severity.js +21 -0
  36. package/dist/core/severity.js.map +1 -0
  37. package/dist/core/source-map.d.ts +3 -0
  38. package/dist/core/source-map.js +16 -0
  39. package/dist/core/source-map.js.map +1 -0
  40. package/dist/frontends/circom/circom-comments.d.ts +8 -0
  41. package/dist/frontends/circom/circom-comments.js +52 -0
  42. package/dist/frontends/circom/circom-comments.js.map +1 -0
  43. package/dist/frontends/circom/circom-ir-builder.d.ts +2 -0
  44. package/dist/frontends/circom/circom-ir-builder.js +12 -0
  45. package/dist/frontends/circom/circom-ir-builder.js.map +1 -0
  46. package/dist/frontends/circom/circom-parser.d.ts +2 -0
  47. package/dist/frontends/circom/circom-parser.js +218 -0
  48. package/dist/frontends/circom/circom-parser.js.map +1 -0
  49. package/dist/frontends/circom/circom-tokenizer.d.ts +7 -0
  50. package/dist/frontends/circom/circom-tokenizer.js +79 -0
  51. package/dist/frontends/circom/circom-tokenizer.js.map +1 -0
  52. package/dist/frontends/circom/circom-utils.d.ts +9 -0
  53. package/dist/frontends/circom/circom-utils.js +88 -0
  54. package/dist/frontends/circom/circom-utils.js.map +1 -0
  55. package/dist/frontends/gnark/gnark-adapter.d.ts +5 -0
  56. package/dist/frontends/gnark/gnark-adapter.js +7 -0
  57. package/dist/frontends/gnark/gnark-adapter.js.map +1 -0
  58. package/dist/frontends/halo2/halo2-adapter.d.ts +8 -0
  59. package/dist/frontends/halo2/halo2-adapter.js +12 -0
  60. package/dist/frontends/halo2/halo2-adapter.js.map +1 -0
  61. package/dist/frontends/halo2/halo2-constraint-extractor.d.ts +29 -0
  62. package/dist/frontends/halo2/halo2-constraint-extractor.js +38 -0
  63. package/dist/frontends/halo2/halo2-constraint-extractor.js.map +1 -0
  64. package/dist/frontends/halo2/halo2-constraint-graph.d.ts +16 -0
  65. package/dist/frontends/halo2/halo2-constraint-graph.js +96 -0
  66. package/dist/frontends/halo2/halo2-constraint-graph.js.map +1 -0
  67. package/dist/frontends/halo2/halo2-dataflow.d.ts +11 -0
  68. package/dist/frontends/halo2/halo2-dataflow.js +19 -0
  69. package/dist/frontends/halo2/halo2-dataflow.js.map +1 -0
  70. package/dist/frontends/halo2/halo2-expression-parser.d.ts +14 -0
  71. package/dist/frontends/halo2/halo2-expression-parser.js +62 -0
  72. package/dist/frontends/halo2/halo2-expression-parser.js.map +1 -0
  73. package/dist/frontends/halo2/halo2-ir-builder.d.ts +2 -0
  74. package/dist/frontends/halo2/halo2-ir-builder.js +17 -0
  75. package/dist/frontends/halo2/halo2-ir-builder.js.map +1 -0
  76. package/dist/frontends/halo2/halo2-parser.d.ts +2 -0
  77. package/dist/frontends/halo2/halo2-parser.js +316 -0
  78. package/dist/frontends/halo2/halo2-parser.js.map +1 -0
  79. package/dist/frontends/halo2/halo2-patterns.d.ts +6 -0
  80. package/dist/frontends/halo2/halo2-patterns.js +54 -0
  81. package/dist/frontends/halo2/halo2-patterns.js.map +1 -0
  82. package/dist/frontends/halo2/halo2-types.d.ts +100 -0
  83. package/dist/frontends/halo2/halo2-types.js +2 -0
  84. package/dist/frontends/halo2/halo2-types.js.map +1 -0
  85. package/dist/frontends/noir/noir-adapter.d.ts +5 -0
  86. package/dist/frontends/noir/noir-adapter.js +7 -0
  87. package/dist/frontends/noir/noir-adapter.js.map +1 -0
  88. package/dist/index.d.ts +4 -0
  89. package/dist/index.js +4 -0
  90. package/dist/index.js.map +1 -0
  91. package/dist/ir/circuit-ir.d.ts +1 -0
  92. package/dist/ir/circuit-ir.js +2 -0
  93. package/dist/ir/circuit-ir.js.map +1 -0
  94. package/dist/ir/component-table.d.ts +6 -0
  95. package/dist/ir/component-table.js +11 -0
  96. package/dist/ir/component-table.js.map +1 -0
  97. package/dist/ir/constraint-graph.d.ts +26 -0
  98. package/dist/ir/constraint-graph.js +130 -0
  99. package/dist/ir/constraint-graph.js.map +1 -0
  100. package/dist/ir/dataflow.d.ts +3 -0
  101. package/dist/ir/dataflow.js +10 -0
  102. package/dist/ir/dataflow.js.map +1 -0
  103. package/dist/ir/reference-index.d.ts +6 -0
  104. package/dist/ir/reference-index.js +18 -0
  105. package/dist/ir/reference-index.js.map +1 -0
  106. package/dist/ir/signal-table.d.ts +6 -0
  107. package/dist/ir/signal-table.js +16 -0
  108. package/dist/ir/signal-table.js.map +1 -0
  109. package/dist/report/json.d.ts +2 -0
  110. package/dist/report/json.js +4 -0
  111. package/dist/report/json.js.map +1 -0
  112. package/dist/report/markdown.d.ts +2 -0
  113. package/dist/report/markdown.js +45 -0
  114. package/dist/report/markdown.js.map +1 -0
  115. package/dist/report/sarif.d.ts +2 -0
  116. package/dist/report/sarif.js +52 -0
  117. package/dist/report/sarif.js.map +1 -0
  118. package/dist/report/summary.d.ts +2 -0
  119. package/dist/report/summary.js +7 -0
  120. package/dist/report/summary.js.map +1 -0
  121. package/dist/report/terminal.d.ts +2 -0
  122. package/dist/report/terminal.js +50 -0
  123. package/dist/report/terminal.js.map +1 -0
  124. package/dist/rules/NS-ZK-001-dangerous-hint-assignment.d.ts +2 -0
  125. package/dist/rules/NS-ZK-001-dangerous-hint-assignment.js +27 -0
  126. package/dist/rules/NS-ZK-001-dangerous-hint-assignment.js.map +1 -0
  127. package/dist/rules/NS-ZK-002-assigned-but-unconstrained.d.ts +2 -0
  128. package/dist/rules/NS-ZK-002-assigned-but-unconstrained.js +22 -0
  129. package/dist/rules/NS-ZK-002-assigned-but-unconstrained.js.map +1 -0
  130. package/dist/rules/NS-ZK-003-unbound-public-input.d.ts +2 -0
  131. package/dist/rules/NS-ZK-003-unbound-public-input.js +22 -0
  132. package/dist/rules/NS-ZK-003-unbound-public-input.js.map +1 -0
  133. package/dist/rules/NS-ZK-004-unconstrained-output.d.ts +2 -0
  134. package/dist/rules/NS-ZK-004-unconstrained-output.js +27 -0
  135. package/dist/rules/NS-ZK-004-unconstrained-output.js.map +1 -0
  136. package/dist/rules/NS-ZK-005-missing-booleanity.d.ts +2 -0
  137. package/dist/rules/NS-ZK-005-missing-booleanity.js +26 -0
  138. package/dist/rules/NS-ZK-005-missing-booleanity.js.map +1 -0
  139. package/dist/rules/NS-ZK-006-missing-range-check.d.ts +2 -0
  140. package/dist/rules/NS-ZK-006-missing-range-check.js +32 -0
  141. package/dist/rules/NS-ZK-006-missing-range-check.js.map +1 -0
  142. package/dist/rules/NS-ZK-007-unsafe-assertion.d.ts +2 -0
  143. package/dist/rules/NS-ZK-007-unsafe-assertion.js +24 -0
  144. package/dist/rules/NS-ZK-007-unsafe-assertion.js.map +1 -0
  145. package/dist/rules/NS-ZK-008-unsafe-division-or-inverse.d.ts +2 -0
  146. package/dist/rules/NS-ZK-008-unsafe-division-or-inverse.js +39 -0
  147. package/dist/rules/NS-ZK-008-unsafe-division-or-inverse.js.map +1 -0
  148. package/dist/rules/NS-ZK-009-unconstrained-component-output.d.ts +2 -0
  149. package/dist/rules/NS-ZK-009-unconstrained-component-output.js +25 -0
  150. package/dist/rules/NS-ZK-009-unconstrained-component-output.js.map +1 -0
  151. package/dist/rules/NS-ZK-010-alias-overflow-risk.d.ts +2 -0
  152. package/dist/rules/NS-ZK-010-alias-overflow-risk.js +48 -0
  153. package/dist/rules/NS-ZK-010-alias-overflow-risk.js.map +1 -0
  154. package/dist/rules/NS-ZK-011-unused-signal.d.ts +2 -0
  155. package/dist/rules/NS-ZK-011-unused-signal.js +26 -0
  156. package/dist/rules/NS-ZK-011-unused-signal.js.map +1 -0
  157. package/dist/rules/NS-ZK-012-suspicious-selector.d.ts +2 -0
  158. package/dist/rules/NS-ZK-012-suspicious-selector.js +33 -0
  159. package/dist/rules/NS-ZK-012-suspicious-selector.js.map +1 -0
  160. package/dist/rules/halo2/NS-H2-001-assigned-advice-not-constrained.d.ts +2 -0
  161. package/dist/rules/halo2/NS-H2-001-assigned-advice-not-constrained.js +40 -0
  162. package/dist/rules/halo2/NS-H2-001-assigned-advice-not-constrained.js.map +1 -0
  163. package/dist/rules/halo2/NS-H2-002-instance-not-bound.d.ts +2 -0
  164. package/dist/rules/halo2/NS-H2-002-instance-not-bound.js +32 -0
  165. package/dist/rules/halo2/NS-H2-002-instance-not-bound.js.map +1 -0
  166. package/dist/rules/halo2/NS-H2-003-selector-risk.d.ts +2 -0
  167. package/dist/rules/halo2/NS-H2-003-selector-risk.js +37 -0
  168. package/dist/rules/halo2/NS-H2-003-selector-risk.js.map +1 -0
  169. package/dist/rules/halo2/NS-H2-004-unsafe-inverse.d.ts +2 -0
  170. package/dist/rules/halo2/NS-H2-004-unsafe-inverse.js +43 -0
  171. package/dist/rules/halo2/NS-H2-004-unsafe-inverse.js.map +1 -0
  172. package/dist/rules/halo2/NS-H2-005-partial-ec-operation.d.ts +2 -0
  173. package/dist/rules/halo2/NS-H2-005-partial-ec-operation.js +43 -0
  174. package/dist/rules/halo2/NS-H2-005-partial-ec-operation.js.map +1 -0
  175. package/dist/rules/halo2/NS-H2-006-missing-enable-equality.d.ts +2 -0
  176. package/dist/rules/halo2/NS-H2-006-missing-enable-equality.js +31 -0
  177. package/dist/rules/halo2/NS-H2-006-missing-enable-equality.js.map +1 -0
  178. package/dist/rules/halo2/halo2-rule-utils.d.ts +8 -0
  179. package/dist/rules/halo2/halo2-rule-utils.js +24 -0
  180. package/dist/rules/halo2/halo2-rule-utils.js.map +1 -0
  181. package/dist/rules/index.d.ts +2 -0
  182. package/dist/rules/index.js +39 -0
  183. package/dist/rules/index.js.map +1 -0
  184. package/dist/scanner.d.ts +8 -0
  185. package/dist/scanner.js +52 -0
  186. package/dist/scanner.js.map +1 -0
  187. package/dist/types.d.ts +171 -0
  188. package/dist/types.js +2 -0
  189. package/dist/types.js.map +1 -0
  190. package/examples/halo2/safe/bound-instance.rs +23 -0
  191. package/examples/halo2/safe/constrained-advice.rs +27 -0
  192. package/examples/halo2/safe/safe-inverse.rs +30 -0
  193. package/examples/halo2/safe/safe-selector.rs +27 -0
  194. package/examples/halo2/vulnerable/missing-selector-booleanity.rs +20 -0
  195. package/examples/halo2/vulnerable/partial-ec-mul.rs +23 -0
  196. package/examples/halo2/vulnerable/unbound-instance.rs +20 -0
  197. package/examples/halo2/vulnerable/unconstrained-advice.rs +24 -0
  198. package/examples/halo2/vulnerable/unsafe-inverse.rs +20 -0
  199. package/examples/safe/safe-boolean.circom +15 -0
  200. package/examples/safe/safe-component-output.circom +18 -0
  201. package/examples/safe/safe-division.circom +14 -0
  202. package/examples/safe/safe-public-input.circom +15 -0
  203. package/examples/safe/safe-range-check.circom +16 -0
  204. package/examples/safe/safe-transfer.circom +19 -0
  205. package/examples/vulnerable/alias-overflow-risk.circom +12 -0
  206. package/examples/vulnerable/merkle-selector-bug.circom +12 -0
  207. package/examples/vulnerable/missing-boolean.circom +11 -0
  208. package/examples/vulnerable/missing-range-check.circom +11 -0
  209. package/examples/vulnerable/public-input-not-bound.circom +12 -0
  210. package/examples/vulnerable/unconstrained-component-output.circom +15 -0
  211. package/examples/vulnerable/unconstrained-transfer.circom +17 -0
  212. package/examples/vulnerable/unsafe-assertion.circom +12 -0
  213. package/examples/vulnerable/unsafe-division.circom +11 -0
  214. package/package.json +46 -0
package/LIMITATIONS.md ADDED
@@ -0,0 +1,17 @@
1
+ # Limitations
2
+
3
+ Nullsec S1-ZK v1 is deterministic static analysis for ZK circuit auditing. It is intentionally precise about what it can and cannot claim.
4
+
5
+ - Static analysis is approximate and may produce false positives.
6
+ - The Circom parser is best-effort; unsupported syntax records parser warnings where possible.
7
+ - The Halo2 frontend is best-effort Rust source scanning. It is not a full Rust AST, MIR, or Halo2 synthesis analyzer.
8
+ - The Halo2 constraint graph is approximate. It links obvious gates, regions, advice assignments, selector enables, equality/copy edges, lookups, and instance bindings, but it does not execute synthesis code.
9
+ - It is not full formal verification and does not prove circuit soundness.
10
+ - It may miss semantic bugs that require protocol context, cross-circuit reasoning, or full compilation.
11
+ - It does not generate witnesses or counterexamples yet.
12
+ - It does not compile Circom circuits, inspect generated R1CS, or run Halo2 circuit synthesis yet.
13
+ - Halo2 findings may miss behavior hidden behind macros, helper APIs, generics, cross-file abstractions, or dynamic region assignment patterns.
14
+ - Macro-heavy Rust and project-specific Halo2 gadget APIs may require future parser support or rule tuning.
15
+ - Range-check and booleanity detection recognize common patterns, not every custom gadget.
16
+ - Component output inference is heuristic and based on parsed references.
17
+ - AI-native modules prepare prompts for future reasoning but do not call external AI APIs.
package/README.md ADDED
@@ -0,0 +1,149 @@
1
+ # Nullsec S1-ZK
2
+
3
+ AI-native auditing for zero-knowledge circuits. Find underconstraints before they mint infinite money.
4
+
5
+ ## What Nullsec S1-ZK Is
6
+
7
+ Nullsec S1-ZK is a production-grade static analysis foundation for ZK circuit auditing. It scans Circom circuits and Rust-based Halo2 circuits for high-impact soundness risks such as unsafe witness hints, missing constraints, missing range checks, missing booleanity checks, unbound inputs, unsafe assertions, component output binding mistakes, unconstrained advice assignments, and unbound Halo2 instance values.
8
+
9
+ It detects high-signal underconstraint patterns, but it does not prove full circuit soundness and does not replace expert cryptographic audits.
10
+
11
+ ## Why ZK Underconstraints Matter
12
+
13
+ Most catastrophic ZK bugs are not ordinary application bugs. They are proof-system semantic failures where the circuit does not actually constrain what the protocol thinks it constrains. Nullsec S1-ZK is built around the question: what does this circuit claim to prove, and what does it actually constrain?
14
+
15
+ ## What v1 Detects
16
+
17
+ v1 implements deterministic static analysis for Circom and best-effort static analysis for Halo2-style Rust circuits.
18
+
19
+ Circom checks include:
20
+
21
+ - Dangerous `<--` hint assignments.
22
+ - Assigned but unconstrained signals.
23
+ - Input signals that never bind into constraints.
24
+ - Unconstrained outputs.
25
+ - Missing booleanity checks.
26
+ - Missing range checks and aliasing risks.
27
+ - Unsafe `assert(...)` over signals.
28
+ - Unsafe division and inverse patterns.
29
+ - Unconstrained component outputs.
30
+ - Unused signals and suspicious selectors.
31
+
32
+ Halo2 checks include:
33
+
34
+ - Advice assignments that do not appear connected to gates, equality constraints, instance constraints, or lookups.
35
+ - Instance/public values queried without an obvious public binding.
36
+ - Selector discipline risks.
37
+ - Unsafe inverse or division patterns.
38
+ - Suspicious partial elliptic-curve operation assignments.
39
+ - Copy-constraint usage without obvious `enable_equality`.
40
+
41
+ ## Halo2 Constraint Graph
42
+
43
+ The Halo2 frontend builds a best-effort constraint graph from Rust source. It links `create_gate` expressions, advice/fixed/instance queries, selector-gated polynomials, `assign_region` assignments, selector enables, `constrain_equal`, `copy_advice`, lookups, and `constrain_instance` public bindings.
44
+
45
+ This graph lets Nullsec S1-ZK distinguish advice values that are connected through gates, equality edges, lookups, or public instance exposure from values that are merely assigned in a region. It improves high-signal findings for Orchard-style EC gadgets and other Halo2 circuits, but it is still static source analysis, not formal verification or complete Halo2 synthesis.
46
+
47
+ ## Installation
48
+
49
+ ```bash
50
+ npm install
51
+ npm run build
52
+ npm link
53
+ ```
54
+
55
+ ## Usage
56
+
57
+ ```bash
58
+ nullsec-zk scan ./examples
59
+ nullsec-zk scan ./examples/halo2
60
+ nullsec-zk scan ./examples --format json
61
+ nullsec-zk scan ./examples --format sarif
62
+ nullsec-zk scan ./examples --report markdown
63
+ nullsec-zk scan ./examples --out report.json
64
+ nullsec-zk scan ./examples --fail-on HIGH
65
+ nullsec-zk rules
66
+ nullsec-zk explain NS-ZK-001
67
+ ```
68
+
69
+ ## Example Terminal Output
70
+
71
+ ```text
72
+ Nullsec S1-ZK
73
+ AI-native auditing for zero-knowledge circuits
74
+
75
+ Target: ./examples
76
+ Frontend: Mixed
77
+ Files scanned: 24
78
+ Rules executed: 18
79
+ Issues found: 33
80
+ ```
81
+
82
+ ## JSON Output
83
+
84
+ JSON output includes the tool name, version, target, files scanned, rules executed, severity counts, issues, and parser warnings.
85
+
86
+ ```bash
87
+ nullsec-zk scan ./examples --format json
88
+ ```
89
+
90
+ ## Markdown Reports
91
+
92
+ ```bash
93
+ nullsec-zk scan ./examples --report markdown
94
+ ```
95
+
96
+ By default this writes `nullsec-zk-report.md`. Use `--out` to choose another path.
97
+
98
+ ## SARIF / CI Usage
99
+
100
+ ```bash
101
+ nullsec-zk scan ./circuits --format sarif --out nullsec-zk.sarif --fail-on HIGH
102
+ ```
103
+
104
+ The SARIF renderer is structured for GitHub code scanning compatibility.
105
+
106
+ ## Rule List
107
+
108
+ Run:
109
+
110
+ ```bash
111
+ nullsec-zk rules
112
+ ```
113
+
114
+ Detailed rule documentation is in `RULES.md`.
115
+
116
+ ## Configuration
117
+
118
+ Create a config:
119
+
120
+ ```bash
121
+ nullsec-zk init
122
+ ```
123
+
124
+ Example:
125
+
126
+ ```json
127
+ {
128
+ "rules": {
129
+ "NS-ZK-006": "off",
130
+ "NS-ZK-005": "high"
131
+ },
132
+ "ignore": ["node_modules", "circomlib"],
133
+ "failOn": "HIGH",
134
+ "format": "terminal",
135
+ "field": "BN254"
136
+ }
137
+ ```
138
+
139
+ ## Limitations
140
+
141
+ Static analysis is approximate. The Circom and Halo2 frontends are best-effort source analyzers, do not compile circuits, do not generate witnesses, and do not perform formal verification. See `LIMITATIONS.md`.
142
+
143
+ ## Roadmap
144
+
145
+ Future work includes deeper Halo2 analysis, Noir, gnark, Plonky2, R1CS extraction, witness counterexample generation, spec-to-circuit comparison, LLM-assisted exploit reasoning, CI integrations, and hosted reporting. See `ROADMAP.md`.
146
+
147
+ ## Security Philosophy
148
+
149
+ Nullsec S1-ZK treats ZK circuit bugs as proof semantics failures. It is designed to surface places where witness values, public claims, outputs, selectors, and integer domains are not bound by constraints in the way a protocol likely intends.
package/ROADMAP.md ADDED
@@ -0,0 +1,21 @@
1
+ # Roadmap
2
+
3
+ Nullsec S1-ZK is designed as a modular security product foundation.
4
+
5
+ - Deeper Halo2 frontend with Rust AST support, macro-aware extraction, and synthesis tracing.
6
+ - Rust AST parsing via `syn`.
7
+ - Real Halo2 region and gadget semantic models.
8
+ - Noir frontend.
9
+ - gnark frontend.
10
+ - Plonky2 frontend.
11
+ - R1CS extraction and analysis.
12
+ - Constraint graph visualization.
13
+ - Witness counterexample generation.
14
+ - Spec-to-circuit comparison.
15
+ - LLM-assisted exploit reasoning.
16
+ - GitHub app and code scanning workflows.
17
+ - Hosted dashboard.
18
+ - Audit report generation.
19
+ - Benchmark suite of historical ZK bugs.
20
+ - Project-specific rule packs.
21
+ - CI policy management and severity baselines.
package/RULES.md ADDED
@@ -0,0 +1,229 @@
1
+ # Nullsec S1-ZK Rules
2
+
3
+ Each rule reports a deterministic static-analysis finding. Severity can be overridden in `.nullsec-zk.json`.
4
+
5
+ ## NS-ZK-001 Dangerous Hint Assignment
6
+
7
+ Severity: CRITICAL when unconstrained, MEDIUM when later constrained.
8
+
9
+ Detects Circom `<--` assignments. These compute witness values without creating constraints.
10
+
11
+ Vulnerable: `x <-- a * b;`
12
+
13
+ Safe: `x <== a * b;` or `x <-- a * b; x === a * b;`
14
+
15
+ Limitations: The rule cannot fully prove the later constraint is semantically equivalent to the hint expression.
16
+
17
+ ## NS-ZK-002 Assigned But Unconstrained
18
+
19
+ Severity: CRITICAL.
20
+
21
+ Detects signals assigned with `<--` that never appear in constraints.
22
+
23
+ Vulnerable: `commitment <-- hasher.out;`
24
+
25
+ Safe: `commitment <== hasher.out;`
26
+
27
+ Limitations: Best-effort parser support may miss constraints hidden in unsupported syntax.
28
+
29
+ ## NS-ZK-003 Unbound Input Signal
30
+
31
+ Severity: HIGH.
32
+
33
+ Detects `signal input` declarations that never appear in constraints.
34
+
35
+ Vulnerable: `signal input root;` with no later relation to `root`.
36
+
37
+ Safe: `root === computedRoot;`
38
+
39
+ Limitations: v1 treats all input signals as important and does not infer protocol-level public/private intent beyond syntax.
40
+
41
+ ## NS-ZK-004 Unconstrained Output
42
+
43
+ Severity: HIGH or CRITICAL.
44
+
45
+ Detects outputs assigned unsafely or never referenced in constraints.
46
+
47
+ Vulnerable: `signal output out; out <-- value;`
48
+
49
+ Safe: `out <== value;`
50
+
51
+ Limitations: It checks parsed constraint participation, not full semantic derivation.
52
+
53
+ ## NS-ZK-005 Missing Booleanity
54
+
55
+ Severity: HIGH.
56
+
57
+ Detects boolean-like names such as `isAdmin`, `enabled`, `selector`, `pathIndex`, or `flag` without booleanity constraints.
58
+
59
+ Vulnerable: `out <== isAdmin * balance;`
60
+
61
+ Safe: `isAdmin * (isAdmin - 1) === 0;`
62
+
63
+ Limitations: Naming heuristics can produce false positives or miss domain-specific boolean names.
64
+
65
+ ## NS-ZK-006 Missing Range Check
66
+
67
+ Severity: confidence-scored and context-aware.
68
+
69
+ Primary targets: `amount`, `balance`, `fee`, `nonce`, `index`, `timestamp`, `quantity`, `count`, `size`, and `limb`.
70
+
71
+ The rule distinguishes bounded integers from hash-like values such as `nullifierHash`, `commitment`, and Merkle `root` signals. Hash outputs, commitments, roots, and nullifier hashes are not automatically required to have range checks unless they are used as bounded integers in arithmetic or comparators.
72
+
73
+ Confidence examples:
74
+
75
+ - `amount` input without range check: HIGH confidence, HIGH severity
76
+ - `balance` input without range check: HIGH confidence, HIGH severity
77
+ - `nullifierHash` equality binding: not reported
78
+ - `commitment` from Poseidon output: not reported
79
+ - `remainingBalance` with range-checked operands: not reported
80
+
81
+ Vulnerable: `remaining <== balance - amount;` flags `amount` and `balance` inputs.
82
+
83
+ Safe: `component bits = Num2Bits(64); bits.in <== amount;`
84
+
85
+ Limitations: Classification is heuristic. It recognizes common range gadgets and patterns, not every custom range proof or protocol-specific integer encoding.
86
+
87
+ ## NS-ZK-007 Unsafe Assertion
88
+
89
+ Severity: HIGH.
90
+
91
+ Detects `assert(...)` expressions involving signals.
92
+
93
+ Vulnerable: `assert(secret === expected);`
94
+
95
+ Safe: `secret === expected;`
96
+
97
+ Limitations: Constant assertions and template parameter assertions may be legitimate; the rule focuses on signal references.
98
+
99
+ ## NS-ZK-008 Unsafe Division Or Inverse
100
+
101
+ Severity: HIGH or MEDIUM.
102
+
103
+ Detects division, inverse, `inv`, and `IsZero`-like patterns without clear nonzero guards.
104
+
105
+ Vulnerable: `q <-- numerator / denominator;`
106
+
107
+ Safe: `denominator * inverse === 1; q <== numerator * inverse;`
108
+
109
+ Limitations: Custom safe inversion gadgets may require project-specific rule configuration.
110
+
111
+ ## NS-ZK-009 Unconstrained Component Output
112
+
113
+ Severity: CRITICAL.
114
+
115
+ Detects component outputs copied through `<--` and not constrained.
116
+
117
+ Vulnerable: `commitment <-- h.out;`
118
+
119
+ Safe: `commitment <== h.out;`
120
+
121
+ Limitations: Component output names are inferred from references and common Circom conventions.
122
+
123
+ ## NS-ZK-010 Alias Or Overflow Risk
124
+
125
+ Severity: MEDIUM or HIGH.
126
+
127
+ Detects wide bit decompositions near BN254 field size and unbounded limb arrays.
128
+
129
+ Vulnerable: `component bits = Num2Bits(254);` without `AliasCheck`.
130
+
131
+ Safe: Use field-safe bit widths and add `AliasCheck` for large decompositions.
132
+
133
+ Limitations: Reports confidence MEDIUM or LOW where intent is ambiguous.
134
+
135
+ ## NS-ZK-011 Unused Signal
136
+
137
+ Severity: LOW.
138
+
139
+ Detects declared signals that are never assigned and never constrained.
140
+
141
+ Vulnerable: `signal unused;`
142
+
143
+ Safe: Remove dead signals or add the intended constraints.
144
+
145
+ Limitations: Some generated templates may intentionally declare unused compatibility signals.
146
+
147
+ ## NS-ZK-012 Suspicious Selector
148
+
149
+ Severity: HIGH.
150
+
151
+ Detects selector-like signals used in conditional arithmetic without booleanity checks.
152
+
153
+ Vulnerable: `out <== selector * a + (1 - selector) * b;`
154
+
155
+ Safe: `selector * (selector - 1) === 0;`
156
+
157
+ Limitations: Selector naming and multiplexer detection are heuristic.
158
+
159
+ ## NS-H2-001 Assigned Advice Not Constrained
160
+
161
+ Severity: HIGH or CRITICAL depending context.
162
+
163
+ Detects Halo2 `assign_advice` calls that do not appear connected through the Halo2 constraint graph to parsed `create_gate` expressions, equality edges, copy constraints, `constrain_instance`, or lookups.
164
+
165
+ Vulnerable: assigning an advice cell in a region without referencing it in a gate or equality/public binding.
166
+
167
+ Safe: reference the assigned cell in a gate, lookup, copy constraint, or instance exposure as intended.
168
+
169
+ Limitations: This is graph-aware Rust source scanning, not full Halo2 synthesis tracing.
170
+
171
+ ## NS-H2-002 Instance Value Not Bound
172
+
173
+ Severity: HIGH.
174
+
175
+ Detects `query_instance` usage that is not connected through a gate expression or public instance binding.
176
+
177
+ Vulnerable: querying or intending to use a public instance value without connecting an assigned cell to the public statement.
178
+
179
+ Safe: use `layouter.constrain_instance(cell.cell(), config.instance, row)`.
180
+
181
+ Limitations: Cross-file helper abstractions may require suppression or future interprocedural analysis.
182
+
183
+ ## NS-H2-003 Selector Discipline Risk
184
+
185
+ Severity: MEDIUM or HIGH.
186
+
187
+ Detects selectors queried in gates, especially selector multipliers, without clear `.enable(...)` usage in parsed regions.
188
+
189
+ Vulnerable: `meta.query_selector(q_gate)` appears in a gate, but no region enables `q_gate`.
190
+
191
+ Safe: enable the selector on every row where the gate must apply.
192
+
193
+ Limitations: Complex selector abstractions may not be recognized.
194
+
195
+ ## NS-H2-004 Unsafe Inverse Or Division
196
+
197
+ Severity: HIGH.
198
+
199
+ Detects `.invert()`, `.inverse()`, or division-like code without an obvious nearby zero check, nonzero guard, safe gadget, or inverse relation. Apparent safe patterns are downgraded for review instead of treated as fully proven safe.
200
+
201
+ Vulnerable: `let inv = denominator.invert().unwrap();`
202
+
203
+ Safe: guard zero explicitly and constrain the inverse relation in a gate.
204
+
205
+ Limitations: The guard detector is local and heuristic.
206
+
207
+ ## NS-H2-005 Partial Elliptic-Curve Operation Risk
208
+
209
+ Severity: HIGH, confidence MEDIUM unless obvious.
210
+
211
+ Detects EC-related assignments involving names like `curve`, `point`, `scalar`, `base`, `fixed_base`, `variable_base`, `ecc`, or `mul` that are not connected to gate expressions, equality edges, copy constraints, lookups, or public instance bindings.
212
+
213
+ Vulnerable: assigning EC point coordinates from a scalar multiplication without constraining the group law.
214
+
215
+ Safe: bind every EC intermediate through complete gate constraints, lookups, or equality constraints.
216
+
217
+ Limitations: This rule is intentionally conservative for Orchard-class research and may require project-specific tuning.
218
+
219
+ ## NS-H2-006 Missing Enable Equality
220
+
221
+ Severity: MEDIUM.
222
+
223
+ Detects `constrain_equal` usage where the graph cannot connect the copy constraint to a parsed column with obvious `meta.enable_equality(...)`.
224
+
225
+ Vulnerable: calling `constrain_equal` on cells from columns not configured for equality.
226
+
227
+ Safe: call `meta.enable_equality` for advice or instance columns participating in copy constraints.
228
+
229
+ Limitations: The rule depends on local column extraction and may miss wrapper APIs.
@@ -0,0 +1,53 @@
1
+ use halo2_proofs::plonk::{Advice, Column, ConstraintSystem, Selector};
2
+
3
+ struct EcMulConfig {
4
+ base_x: Column<Advice>,
5
+ base_y: Column<Advice>,
6
+ scalar: Column<Advice>,
7
+ acc_x: Column<Advice>,
8
+ acc_y: Column<Advice>,
9
+ out_x: Column<Advice>,
10
+ out_y: Column<Advice>,
11
+ q_ec_mul: Selector,
12
+ }
13
+
14
+ fn configure(meta: &mut ConstraintSystem<Fp>) -> EcMulConfig {
15
+ let base_x = meta.advice_column();
16
+ let base_y = meta.advice_column();
17
+ let scalar = meta.advice_column();
18
+ let acc_x = meta.advice_column();
19
+ let acc_y = meta.advice_column();
20
+ let out_x = meta.advice_column();
21
+ let out_y = meta.advice_column();
22
+ let q_ec_mul = meta.selector();
23
+
24
+ meta.create_gate("synthetic orchard ec mul relation", |meta| {
25
+ let s = meta.query_selector(q_ec_mul);
26
+ let bx = meta.query_advice(base_x, Rotation::cur());
27
+ let scalar = meta.query_advice(scalar, Rotation::cur());
28
+ let ax = meta.query_advice(acc_x, Rotation::cur());
29
+ let ay = meta.query_advice(acc_y, Rotation::cur());
30
+ let ox = meta.query_advice(out_x, Rotation::cur());
31
+ let oy = meta.query_advice(out_y, Rotation::cur());
32
+ vec![
33
+ s.clone() * (ax + bx * scalar - ox),
34
+ s * (ay + scalar - oy),
35
+ ]
36
+ });
37
+
38
+ EcMulConfig { base_x, base_y, scalar, acc_x, acc_y, out_x, out_y, q_ec_mul }
39
+ }
40
+
41
+ fn synthesize(config: EcMulConfig, mut layouter: impl Layouter<Fp>) {
42
+ layouter.assign_region(|| "orchard inspired partial ec mul", |mut region| {
43
+ config.q_ec_mul.enable(&mut region, 0)?;
44
+ region.assign_advice(|| "base x", config.base_x, 0, || Value::known(base_point.x))?;
45
+ region.assign_advice(|| "base y", config.base_y, 0, || Value::known(base_point.y))?;
46
+ region.assign_advice(|| "scalar", config.scalar, 0, || Value::known(scalar))?;
47
+ region.assign_advice(|| "accumulator x", config.acc_x, 0, || Value::known(acc.x))?;
48
+ region.assign_advice(|| "accumulator y", config.acc_y, 0, || Value::known(acc.y))?;
49
+ region.assign_advice(|| "output point x", config.out_x, 0, || Value::known(output.x))?;
50
+ region.assign_advice(|| "output point y", config.out_y, 0, || Value::known(output.y))?;
51
+ Ok(())
52
+ });
53
+ }
@@ -0,0 +1,69 @@
1
+ use halo2_proofs::plonk::{Advice, Column, ConstraintSystem, Selector};
2
+
3
+ struct EcMulConfig {
4
+ base_x: Column<Advice>,
5
+ base_y: Column<Advice>,
6
+ scalar: Column<Advice>,
7
+ acc_x: Column<Advice>,
8
+ acc_y: Column<Advice>,
9
+ out_x: Column<Advice>,
10
+ out_y: Column<Advice>,
11
+ q_ec_mul: Selector,
12
+ }
13
+
14
+ fn configure(meta: &mut ConstraintSystem<Fp>) -> EcMulConfig {
15
+ let base_x = meta.advice_column();
16
+ let base_y = meta.advice_column();
17
+ let scalar = meta.advice_column();
18
+ let acc_x = meta.advice_column();
19
+ let acc_y = meta.advice_column();
20
+ let out_x = meta.advice_column();
21
+ let out_y = meta.advice_column();
22
+ let q_ec_mul = meta.selector();
23
+ meta.enable_equality(base_x);
24
+ meta.enable_equality(base_y);
25
+ meta.enable_equality(scalar);
26
+ meta.enable_equality(acc_x);
27
+ meta.enable_equality(acc_y);
28
+ meta.enable_equality(out_x);
29
+ meta.enable_equality(out_y);
30
+
31
+ meta.create_gate("complete synthetic orchard ec mul relation", |meta| {
32
+ let s = meta.query_selector(q_ec_mul);
33
+ let bx = meta.query_advice(base_x, Rotation::cur());
34
+ let by = meta.query_advice(base_y, Rotation::cur());
35
+ let scalar = meta.query_advice(scalar, Rotation::cur());
36
+ let ax = meta.query_advice(acc_x, Rotation::cur());
37
+ let ay = meta.query_advice(acc_y, Rotation::cur());
38
+ let ox = meta.query_advice(out_x, Rotation::cur());
39
+ let oy = meta.query_advice(out_y, Rotation::cur());
40
+ vec![
41
+ s.clone() * (ax + bx * scalar - ox),
42
+ s.clone() * (ay + by * scalar - oy),
43
+ s * (by * by - bx * bx * bx),
44
+ ]
45
+ });
46
+
47
+ EcMulConfig { base_x, base_y, scalar, acc_x, acc_y, out_x, out_y, q_ec_mul }
48
+ }
49
+
50
+ fn synthesize(config: EcMulConfig, mut layouter: impl Layouter<Fp>) {
51
+ layouter.assign_region(|| "orchard inspired complete ec mul", |mut region| {
52
+ config.q_ec_mul.enable(&mut region, 0)?;
53
+ let base_x_cell = region.assign_advice(|| "base x", config.base_x, 0, || Value::known(base_point.x))?;
54
+ let base_y_cell = region.assign_advice(|| "base y", config.base_y, 0, || Value::known(base_point.y))?;
55
+ let scalar_cell = region.assign_advice(|| "scalar", config.scalar, 0, || Value::known(scalar))?;
56
+ let acc_x_cell = region.assign_advice(|| "accumulator x", config.acc_x, 0, || Value::known(acc.x))?;
57
+ let acc_y_cell = region.assign_advice(|| "accumulator y", config.acc_y, 0, || Value::known(acc.y))?;
58
+ let out_x_cell = region.assign_advice(|| "output point x", config.out_x, 0, || Value::known(output.x))?;
59
+ let out_y_cell = region.assign_advice(|| "output point y", config.out_y, 0, || Value::known(output.y))?;
60
+ region.constrain_equal(base_x_cell.cell(), base_x_cell.cell())?;
61
+ region.constrain_equal(base_y_cell.cell(), base_y_cell.cell())?;
62
+ region.constrain_equal(scalar_cell.cell(), scalar_cell.cell())?;
63
+ region.constrain_equal(acc_x_cell.cell(), acc_x_cell.cell())?;
64
+ region.constrain_equal(acc_y_cell.cell(), acc_y_cell.cell())?;
65
+ region.constrain_equal(out_x_cell.cell(), out_x_cell.cell())?;
66
+ region.constrain_equal(out_y_cell.cell(), out_y_cell.cell())?;
67
+ Ok(())
68
+ });
69
+ }
@@ -0,0 +1,2 @@
1
+ import type { Issue } from "../types.js";
2
+ export declare function buildReasoningPrompt(issue: Issue, surroundingCode: string, circuitIntent?: string): string;
@@ -0,0 +1,33 @@
1
+ export function buildReasoningPrompt(issue, surroundingCode, circuitIntent = "Unknown from static analysis") {
2
+ return `You are reviewing a zero-knowledge circuit issue reported by Nullsec S1-ZK.
3
+
4
+ Circuit intent:
5
+ ${circuitIntent}
6
+
7
+ Issue:
8
+ - Rule: ${issue.ruleId}
9
+ - Title: ${issue.title}
10
+ - Severity: ${issue.severity}
11
+ - Confidence: ${issue.confidence}
12
+ - File: ${issue.file}:${issue.line}
13
+ - Signal: ${issue.signalName ?? "unknown"}
14
+
15
+ Finding:
16
+ ${issue.explanation}
17
+
18
+ Impact:
19
+ ${issue.impact}
20
+
21
+ Surrounding code:
22
+ \`\`\`circom
23
+ ${surroundingCode}
24
+ \`\`\`
25
+
26
+ Reasoning tasks:
27
+ 1. Summarize what the circuit appears to claim to prove.
28
+ 2. Explain the constraint failure without overstating formal guarantees.
29
+ 3. Assess exploitability and assumptions.
30
+ 4. Propose a concrete patch.
31
+ 5. Draft audit-report language suitable for a security review.`;
32
+ }
33
+ //# sourceMappingURL=prompt-builder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompt-builder.js","sourceRoot":"","sources":["../../src/ai/prompt-builder.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,oBAAoB,CAAC,KAAY,EAAE,eAAuB,EAAE,aAAa,GAAG,8BAA8B;IACxH,OAAO;;;EAGP,aAAa;;;UAGL,KAAK,CAAC,MAAM;WACX,KAAK,CAAC,KAAK;cACR,KAAK,CAAC,QAAQ;gBACZ,KAAK,CAAC,UAAU;UACtB,KAAK,CAAC,IAAI,IAAI,KAAK,CAAC,IAAI;YACtB,KAAK,CAAC,UAAU,IAAI,SAAS;;;EAGvC,KAAK,CAAC,WAAW;;;EAGjB,KAAK,CAAC,MAAM;;;;EAIZ,eAAe;;;;;;;;+DAQ8C,CAAC;AAChE,CAAC"}
@@ -0,0 +1,15 @@
1
+ import type { Issue } from "../types.js";
2
+ export interface ReasoningRequest {
3
+ issue: Issue;
4
+ surroundingCode: string;
5
+ circuitIntent?: string;
6
+ }
7
+ export interface ReasoningResponse {
8
+ exploitabilityAssessment: string;
9
+ patchGuidance: string;
10
+ auditLanguage: string;
11
+ }
12
+ export interface ReasoningProvider {
13
+ name: string;
14
+ reason(request: ReasoningRequest): Promise<ReasoningResponse>;
15
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=reasoning-interface.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reasoning-interface.js","sourceRoot":"","sources":["../../src/ai/reasoning-interface.ts"],"names":[],"mappings":""}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,71 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from "commander";
3
+ import chalk from "chalk";
4
+ import { existsSync } from "node:fs";
5
+ import { scanTarget } from "./scanner.js";
6
+ import { writeDefaultConfig } from "./config.js";
7
+ import { allRules } from "./rules/index.js";
8
+ import { normalizeSeverity } from "./core/severity.js";
9
+ const program = new Command();
10
+ program
11
+ .name("nullsec-zk")
12
+ .description("Nullsec S1-ZK: AI-native auditing for zero-knowledge circuits.")
13
+ .version("1.0.0");
14
+ program
15
+ .command("scan")
16
+ .argument("<target>", "Circom file or directory to scan")
17
+ .option("--format <format>", "terminal, json, markdown, or sarif")
18
+ .option("--report <format>", "write a report in the requested format")
19
+ .option("--out <path>", "write report output to a path")
20
+ .option("--fail-on <severity>", "CRITICAL, HIGH, MEDIUM, LOW, or INFO")
21
+ .option("--config <path>", "config file path")
22
+ .action(async (target, options) => {
23
+ try {
24
+ const run = await scanTarget(target, {
25
+ format: options.format,
26
+ report: options.report,
27
+ out: options.out,
28
+ failOn: options.failOn ? normalizeSeverity(options.failOn, "CRITICAL") : undefined,
29
+ configPath: options.config
30
+ });
31
+ if (!options.out && !options.report)
32
+ process.stdout.write(run.output);
33
+ process.exitCode = run.exitCode;
34
+ }
35
+ catch (error) {
36
+ console.error(chalk.red("Nullsec S1-ZK scan failed"));
37
+ console.error(error.message);
38
+ process.exitCode = 2;
39
+ }
40
+ });
41
+ program.command("rules").description("List supported Nullsec S1-ZK rules").action(() => {
42
+ for (const rule of allRules) {
43
+ console.log(`${rule.id} ${rule.defaultSeverity.padEnd(8)} ${rule.title}`);
44
+ }
45
+ });
46
+ program.command("explain").argument("<issue-id>", "Rule ID or issue ID").description("Explain a supported rule").action((issueId) => {
47
+ const ruleId = issueId.match(/NS-(?:ZK|H2)-\d{3}/)?.[0] ?? issueId;
48
+ const rule = allRules.find((candidate) => candidate.id === ruleId);
49
+ if (!rule) {
50
+ console.error(`No rule found for ${issueId}`);
51
+ process.exitCode = 2;
52
+ return;
53
+ }
54
+ console.log(`${rule.id}: ${rule.title}
55
+
56
+ Default severity: ${rule.defaultSeverity}
57
+ Tags: ${rule.tags.join(", ")}
58
+
59
+ ${rule.description}`);
60
+ });
61
+ program.command("init").description("Create a .nullsec-zk.json config file").action(() => {
62
+ if (existsSync(".nullsec-zk.json")) {
63
+ console.error(".nullsec-zk.json already exists");
64
+ process.exitCode = 2;
65
+ return;
66
+ }
67
+ const path = writeDefaultConfig();
68
+ console.log(`Created ${path}`);
69
+ });
70
+ program.parseAsync(process.argv);
71
+ //# sourceMappingURL=cli.js.map