auditinfra-io/o1js-scan

GitHub: auditinfra-io/o1js-scan

一款零依赖的静态分析器,专门检测 o1js/Mina zkApps 和 Noir 零知识电路中的约束不足漏洞。

Stars: 1 | Forks: 0

# o1js-scan [![CI](https://static.pigsec.cn/wp-content/uploads/repos/cas/ad/ad5834178f7599af9fdda11629d49cae07f2997beec49821b2920eff5bfd50e7.svg)](https://github.com/auditinfra-io/o1js-scan/actions/workflows/ci.yml) ![Python](https://img.shields.io/badge/python-3.8%2B-blue) [![License](https://img.shields.io/badge/license-Apache--2.0-blue)](LICENSE) [![PyPI](https://img.shields.io/pypi/v/o1js-scan.svg)](https://pypi.org/project/o1js-scan/) 一个快速、零依赖的静态分析器,用于检测以下项目中的 **zk 电路可靠性漏洞**: - **o1js / Mina zkApps** (TypeScript `.ts` / `.js`) — 源自 `@method` 函数体的 Kimchi 电路 - **Noir** (`.nr`) — Aztec 的类 Rust ZK DSL(包括 aztec-nr 风格的模式) 安全关键的 bug 通常不出在证明系统中 —— 它们存在于 **应用自身的约束**中:证明者控制但电路 从未绑定的 witness。`o1js-scan` 是针对 Mina 和 Noir 生态系统中 Circom “表亲”的 约束不足信号扫描器。 ``` pip install o1js-scan # 或:pipx install o1js-scan # 或:npm install -D o1js-scan o1js-scan path/to/zkapp # o1js + Noir (auto) noir-scan path/to/circuits # same binary — Noir-friendly alias noir-scan . --lang noir --fail-on high --sarif noir.sarif ``` ### 示例 假设有一个金库,其 `withdraw` 金额是一个证明者控制的 witness, 且从未绑定到链上状态: ``` $ o1js-scan examples/vulnerable_vault.ts HIGH O1JS_UNCONSTRAINED_WITNESS vulnerable_vault.ts:23 fn=withdraw Unconstrained witness `amount` flows to send_amount in `withdraw` LOW O1JS_UNCONSTRAINED_RECIPIENT vulnerable_vault.ts:23 fn=withdraw Recipient `to` is prover-chosen in `withdraw` o1js-scan: 2 finding(s) [1 high, 1 low] in 1 file(s) — fails (--fail-on high) $ echo $? 1 ``` `HIGH` 发现结果是可被抽干的 bug;已修复的合约 (`examples/safe_vault.ts`)扫描通过并退出代码为 `0`。请参阅 [`examples/`](examples/) 以获取 o1js 和 Noir 的漏洞/修复配对示例。 ## 目录 - [安装](#install) - [用法](#usage) · [抑制发现结果](#suppressing-a-reviewed-finding) - [GitHub Action](#github-action) - [检测内容 — o1js](#what-it-detects-o1js) · [Noir](#what-it-detects-noir) - [已知限制](#known-limitations) · [工具的局限](#where-this-tool-stops) - [兼容性](#compatibility) · [工作原理](#how-it-works) - [贡献](#roadmap--contributing) ## 安装 ``` pip install o1js-scan ``` 要进行独立的全局 CLI 安装,请使用 `pipx`: ``` pipx install o1js-scan ``` 对于基于 Node/npm 的 Noir、Aztec 或 o1js 应用仓库,请安装 npm wrapper: ``` npm install -D o1js-scan npx noir-scan . --lang noir --fail-on high ``` 该 npm 包是同一个 Python 分析器的轻量级 wrapper,需要 在 `PATH` 中存在 Python 3.8+(`python3` 或 `python`)。设置 `O1JS_SCAN_PYTHON` 以选择 特定的解释器。 或从源码安装: ``` git clone https://github.com/auditinfra-io/o1js-scan cd o1js-scan pip install -e . ``` 无需第三方 Python 依赖。需要 Python 3.8+。`noir-scan` 控制台脚本 会随 `o1js-scan`(相同的入口点)一起安装,包括通过 npm wrapper 安装的情况。 ## 用法 ``` # 扫描目录(递归;跳过 node_modules、target/、.git 等) o1js-scan path/to/project # 仅 Noir / 仅 o1js noir-scan circuits --lang noir o1js-scan src --lang o1js # 扫描单个文件 o1js-scan src/MyContract.ts noir-scan src/main.nr # 用于 CI 的机器可读输出 o1js-scan src --json # 用于 GitHub code scanning 的 SARIF 2.1.0(默认写入 o1js-scan.sarif) o1js-scan src --sarif noir-scan . --lang noir --sarif noir.sarif # 选择哪种 severity 会导致 CI 失败(critical|high|medium|low|none;默认为 high) o1js-scan src --fail-on medium # 默认排除测试代码(两个 backend 均适用);可选择加入 o1js-scan src --include-tests # 示例代码默认降级为 LOW;保持原始 severity o1js-scan src --include-examples o1js-scan --version ``` 当存在达到或超过 `--fail-on` 级别(默认为 `high`)的发现结果时,退出代码为 `1`,否则为 `0` —— 因此你可以直接将其放入 CI 中。 在默认设置下,低/中级别的发现结果(包括下文的信息性接收者 规则)**不会**导致构建失败;使用 `--fail-on none` 仅作报告, 或使用 `--fail-on medium` 进行更严格的控制。缺少扫描路径会以退出代码 `2` 退出,并 在 stderr 上显示错误,因此拼写错误不会作为干净运行默默通过 CI。每次运行 都会在 stderr 上打印一行摘要(按严重程度统计和门禁判定)。 **测试代码默认被排除 —— 两个后端皆是如此。**测试会故意构建 无效的值和错误的交易,以证明断言会拒绝它们,因此 在那里的发现结果是测试的目的,而不是电路 bug。在以下情况下,文件被视为 测试代码: - 其名称匹配 `*.test.ts` / `*.spec.ts`(以及 `.js`/`.jsx`/`.tsx`/`.mjs` /`.cjs` 变体),或 `*_test.nr` / `test_*.nr`; - 它位于 `test/`、`tests/`、`__tests__/`、`spec/` 或 `__mocks__/` 目录下; - (仅限 Noir,基于内容)函数带有 `#[test]` / `#[test(...)]` attribute,或位于 `mod test { … }` / `mod tests { … }` 块内 —— 基于块作用域,因此生产文件底部的测试模块不会 掩盖文件其余部分的检查。 传入 `--include-tests` 以报告它们。 **示例代码会被降级,而不是丢弃。**在 `examples/` 或 `example/` 目录中的发现结果,或在名为 `*.eg.ts`(也适用于 `.nr` 以及其他 JS/TS 扩展名)的文件中的发现结果,会降级为 **LOW** 并附带说明 —— 仍然会报告,但不再 会导致构建失败。示例代码是刻意简化的,将 框架自身的示例标记为漏洞属于噪音;但它被 *复制到 生产环境* 的频率远高于测试代码,这就是为什么它被降级 而不是隐藏的原因。传入 `--include-examples` 以保留原始严重性。 只要适用了任一策略,运行都会向 **stderr** 打印一行相关日志 —— 例如 `6 file(s) skipped as test code, 1 finding(s) downgraded as examples` —— 这样静默扫描就不会无声无息地静默。这些计数也会出现在 SARIF 的 `invocation.properties` 下。请注意权衡:检测 **仅基于路径** (不解析 `describe(`/`it(`),因此存储在 `tests/` 下的生产电路 *将*被跳过 —— stderr 上的日志是你发现这一问题的方式。 **遍历目录树时跳过的目录:** `node_modules`、`target` (nargo)、 `.git`、`dist`、`build`、`__pycache__`、`.venv`、`venv`。 ### 抑制已审查的发现结果 在不放宽门禁的情况下,通过在被标记行上或上一行的内联 注释来屏蔽你已经排查过的发现结果: ``` this.send({ to, amount }); // o1js-scan-disable-line O1JS_UNCONSTRAINED_WITNESS // o1js-scan-disable-next-line this.send({ to, amount }); ``` ``` let inv = unsafe { hint(x) }; // o1js-scan-disable-line NOIR_UNCONSTRAINED_WITNESS ``` 列出一个或多个规则 id 以仅屏蔽它们;裸指令(无 id) 将屏蔽目标行上的所有规则。 作为库使用: ``` from o1js_scan import analyze_file, analyze_project for path, finding in analyze_project("src", lang="auto"): print(path, finding.rule_id, finding.severity.value, finding.title) ``` ## GitHub Action 只需几行代码即可将扫描器添加到 CI 中。发现结果将作为 PR diff 上的注释 以及仓库的 **Security → Code scanning** 选项卡中的警报显示。 ``` # .github/workflows/o1js-scan.yml name: o1js-scan on: [push, pull_request] permissions: contents: read security-events: write # required to upload SARIF to code scanning jobs: scan: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: auditinfra-io/o1js-scan@v0.10.0 with: path: src # optional, defaults to the repo root lang: auto # auto | o1js | noir # version: 0.10.0 # optional, pin the scanner version # fail-on-findings: true # optional, fail the job on any high/critical ``` ### 仅限 Noir 的 CI 配置 推荐用于需要代码扫描警报和高级别 门禁的 Noir 项目: ``` - uses: auditinfra-io/o1js-scan@v0.10.0 with: path: . lang: noir fail-on-findings: true ``` 或不使用 Action: ``` pip install o1js-scan noir-scan . --lang noir --fail-on high --sarif noir.sarif ``` ### pre-commit(可选) ``` # .pre-commit-config.yaml - repo: local hooks: - id: noir-scan name: noir-scan entry: noir-scan language: system pass_filenames: false args: [".", "--lang", "noir", "--fail-on", "high"] ``` 输入:`path`(默认为 `.`)、`lang`(`auto`|`o1js`|`noir`,默认为 `auto`)、 `version`(要安装的 PyPI 版本,默认为最新)、`upload-sarif`(默认为 `true`)、`fail-on-findings`(默认为 `false`)、`include-tests`(默认为 `false`)、`include-examples`(默认为 `false`)。输出:`sarif-file`。SARIF 上传需要 `security-events: write` 权限并启用代码扫描。 ## 检测内容 | 规则 | 严重性 | 含义 | |------|----------|---------------| | `O1JS_MISSING_STATE_PRECONDITION` | high | 读取 `this.x.get()` 时没有匹配的 `requireEquals(...)` / `getAndRequireEquals()`。裸 `get()` **不会**添加账户前置条件,因此证明不会将 `x` 绑定到其链上值 —— 证明者可以替换为任何值。 | | `O1JS_UNCONSTRAINED_WITNESS` | high / medium | `@method` 参数(证明者控制的私有 witness)流入 `this.send` 的 **amount** 或 state `.set(...)` 且**从未**被断言。这相当于约束不足的 Circom signal。当它涉及价值转移时为 High。 | | `O1JS_UNCONSTRAINED_PROVABLE_WITNESS` | high / medium / low | `Provable.witness(...)` 本地变量流入 send/state 效果但**没有**电路内断言。witness callback 在电路*外部*运行(它只是一个证明者提示),因此其结果是一个全新的证明者控制值 —— 这是 `@method` 参数之外的另一个 witness 来源。它必须被重新推导并断言(`x.assertEquals()`)或绑定到 state。在 send amount 上为 high,在 state 写入上为 medium,在接收者上为 low。 | | `O1JS_UNCONSTRAINED_RECIPIENT` | low | `@method` 参数**仅**用作 `this.send(...)` 的 `to:` 接收者。这通常是故意的(用户指定自己的提款目的地),属于信息性质 —— 只有在目的地本应是固定的金库或记录在 state 中的地址时才有影响。**不会**触发 CI 退出代码门禁。 | | `O1JS_WITNESS_NOT_BOUND_TO_STATE` | medium | 一个 witness 在产生效果之前仅受到*平凡的*约束(例如 `> 0`,或与常量比较) —— 从未与链上 state 绑定。确认链下编排是否使其安全,否则余额可能会被抽干至其现有价值。 | | `O1JS_STALE_MERKLE_ROOT` | high | 一个方法从证明者提供的 witness(`computeRootAndKey` / `calculateRoot`)重新计算 Merkle root,但**没有**将任何重新计算的 root 绑定到当前的链上 root。如果没有针对实时 root 的 `this.root.requireEquals(...)` / `assertEquals`,证明者就可以通过伪造或过期的树提供 witness —— 从而伪造成员身份或重放旧状态。绑定可能存在于未装饰的同级辅助方法(`this.verifyX(witness)`)中;一级 helper 传播涵盖此类情况。 | | `O1JS_UNVERIFIED_PROOF` | high | 类型为 `Proof<...>` / `SelfProof` / `DynamicProof` / `*Proof` 的 `@method` 参数从未被 `.verify()` / `.verifyIf()`。传递 Proof 并不会验证它 —— 如果没有显式验证,证明者可以提供任意的 proof 对象,并且对其 `publicOutput` 的任何使用都是不受约束的。 | | `O1JS_UNASSERTED_BOOL` | high / medium | o1js 断言(`equals` / `lessThanOrEqual` / …)返回一个 `Bool`,如果结果未被断言或使用,则**不**添加任何约束。当调用是单独的丢弃语句时为 HIGH;当赋值给一个此后再未被引用的本地变量时为 MEDIUM。 | | `O1JS_UNCONSTRAINED_SENDER` | high / medium | `this.sender.getUnconstrained()` 在未证明的情况下返回 tx sender。当该值(或从中派生的本地变量)流入断言 / state `.set` / `send`(空虚检查)时为 HIGH;否则为 MEDIUM。推荐使用 `this.sender.getAndRequireSignature()`,或展开的惯用语法 `AccountUpdate.createSigned(sender)`。**在以下情况保持静默** (1) 同一个 `@method` 在任何地方也调用了 `this.sender.getAndRequireSignature()`(签名要求是基于方法作用域的),或 (2) 见证的 sender 值是同一个密钥上 `AccountUpdate.createSigned(...)` / `AccountUpdate.create(...).requireSignature()` 的参数(要求参数身份一致 —— 不同密钥上的 `createSigned` 不会被抑制)。 | | `MissingRangeCheck` | high | 原始的 `Field`(非经过范围检查的 `UInt64`/`UInt32`)被用作转移金额。`Field` 是一个对 p 取模的元素,且没有范围限制。 | | `O1JS_WEAK_PERMISSIONS` | high / medium | `editState` / `send` 权限被设置为 `proofOrSignature()` 或 `none()`,允许 zkApp 账户密钥通过签名绕过电路。 | ### 误报防护 分析器设计为在正确的代码上保持静默: - **跳过由签名门控的方法。**调用 `this.requireSignature()`(或 `getAndRequireEquals`,`AccountUpdate.createSigned`, `Signature.verify`)的 `@method` 是所有者/管理员门控的 —— 其参数由密钥持有者 选择,而不是任意证明者 —— 因此其 witness 不会被标记。这是 o1js 中 `onlyOwner` 的等价物。 - **跳过绑定到 state 的 witness。**断言等于(或通过针对 `getAndRequireEquals()` 派生 值的排序比较来界定)的参数是安全的,不会被报告。这涵盖了直接形式 —— `amount.assertLessThanOrEqual(bal)` —— 以及链式形式 `.lessThanOrEqual(bal).assertTrue()`。存在于未装饰的同类辅助方法(`this.verifyX(arg)`)中的绑定也会被识别 (仅限深度 1)。 - **跳过已验证的 Proof。**调用了 `.verify()` / `.verifyIf()` 的 `Proof` / `SelfProof` / `DynamicProof` / `*Proof` 类型的参数受已验证电路的约束 —— 其上的 witness 发现结果(及其 `publicOutput` / `publicInput`)将被抑制。这同样适用于 规范的 OffchainState wrapper `this.offchainState.settle(proof)`(该 框架在 `settle` 内部进行验证)。手写的 `.settle(proof)` **不**被假定为会验证。反向情况(proof 类型的参数从未被验证 且未通过 OffchainState-settled 处理)会被报告为 `O1JS_UNVERIFIED_PROOF`。 - **跳过已断言 / 已使用的 Bool。**与 `.assertTrue()` / `.assertFalse()` 链接、嵌套在 `Provable.if(...)` 中或 赋值给此后被引用的本地变量的断言,不会被报告为 `O1JS_UNASSERTED_BOOL`。 - **跳过已验证的 sender。**当相同的 `@method` 也调用了 `this.sender.getAndRequireSignature()`,或者当该见证值被传递给 `AccountUpdate.createSigned(...)` / 通过在基于该值构建的 AccountUpdate 上调用 `.requireSignature()` 进行验证(要求参数身份一致)时,`this.sender.getUnconstrained()` 不会触发。 - 注释和字符串字面量在分析前会被剥离,因此字符串内的 `assert` 不会产生错误结果。 ## 检测内容 同样的可靠性理念 —— 约束不足的 witness —— 同样适用于 [Noir](https://noir-lang.org)(`.nr`)电路。将扫描器指向 `.nr` 文件(或使用 `--lang noir`),它将使用 Noir 规则集进行分析。 相同的基于词法、无依赖的方法。针对 aztec-nr oracle / `unsafe` 惯用法进行了校准 —— 参见 [`docs/noir_calibration.md`](docs/noir_calibration.md)。 | 规则 | 严重性 | 含义 | |------|----------|---------------| | `NOIR_UNCONSTRAINED_WITNESS` | high | 从 `unsafe { ... }` 块绑定的值 —— 即 `unconstrained fn`(oracle / Brillig 提示)的结果 —— 且从未被 `assert` / `assert_eq`(或确认性的 helper / merkle 检查)重新约束。该提示在电路**外部**运行。相当于 `O1JS_UNCONSTRAINED_PROVABLE_WITNESS`。 | | `NOIR_UNCONSTRAINED_INPUT` | medium | `fn main` 的私有 witness 输入,流入**任何** `assert` / `assert_eq` 且**不属于**公共输出的一部分。相当于 `O1JS_UNCONSTRAINED_WITNESS`。 | | `NOIR_UNCONSTRAINED_PUBLIC_INPUT` | medium | `fn main` 的**公共**输入,未达到任何约束且未产生输出 —— 电路从不读取它。这是私有 witness 规则的*对偶*:验证者提供该值并相信陈述与其相关,而电路忽略了它(例如,一个从未被检查的 `merkle_root: pub Field`,因此成员身份实际上从未被证明)。MEDIUM 是因为刻意不使用的公共输入也是一种将证明绑定到上下文(nonce / chain id / recipient)的合法惯用法,这在词法上是无法区分的 —— 因此在默认的 `--fail-on high` 下它不会阻断 CI。 | | `NOIR_UNCHECKED_CAST` | medium | 将证明者控制的值强制转换为狭窄的无符号类型(`as u8`/`u16`/`u32`)而**没有**范围断言。相当于 o1js 的 `MissingRangeCheck`。 | | `NOIR_UNASSERTED_BOOL` | high / medium | 比较产生的 `bool` 结果被**丢弃**。相当于 o1js 的 `O1JS_UNASSERTED_BOOL`。 | | `NOIR_CONDITIONAL_ASSERT` | medium | 在 `if { ... }` 内的 `assert`,其中 `` 是证明者控制的裸 `bool`。 | | `NOIR_CONDITIONAL_CONSTRAIN` | medium | 仅在证明者控制的 `if` 下进行 `constrain_*` / `confirm_*` / `verify_*` 调用,同时 `unsafe` 提示仍能到达输出。 | | `NOIR_UNUSED_CHECK_RESULT` | high / medium | `check_*` / `confirm_*` / `verify_*` / `constrain_*` 的结果被丢弃(裸调用)或被赋值后从未断言 —— 检查未绑定到电路。 | | `NOIR_VACUOUS_CONSTRAINT` | high / medium | 满足于构造本身的约束:自比较(`assert(x == x)`、`assert_eq(x, x)`、`x >= x`)或常量条件(`assert(true)`)。它不增加任何限制,但该行*看起来像*一个检查 —— 这使得它比缺少约束更危险,因为审查会在此止步。对于自比较为 HIGH(几乎总是真实检查的拼写错误:将 `assert(computed == expected)` 错误输入为 `assert(expected == expected)`);对于常量则为 MEDIUM,通常作为占位符。`x != x` **不**被标记 —— 因为它是不可满足的,属于活性 bug 而非静默的可靠性漏洞。 | | `NOIR_UNSAFE_MISSING_SAFETY` | low | 没有相邻 `// Safety:` 注释的 `unsafe { ... }` 块。仅供参考;在默认的 `--fail-on high` 下不会阻断 CI。 | ### 误报防护 - **Assert / let-hop / 同文件确认辅助方法** 绑定 `unsafe` 提示。 - **调用点名称** `constrain_*` / `confirm_*` / `verify_*` / `check_(non_)membership*` / `public_data_storage_read` 验证参数(带有针对丢弃检查的未使用结果检测)。 - **已记录的刻意无约束**(需要相邻的 `// Safety:`): `random()`、`avm::…`,以及 kernel/rollup/discovery 延迟措辞。 - **Tuple `let` + 断言标志** 绑定传递到成员检查中的 merkle witness。 示例: ``` noir-scan examples/noir_unconstrained.nr # HIGH NOIR_UNCONSTRAINED_WITNESS noir-scan examples/noir_constrained.nr # clean ``` ## 已知限制 该分析器是一个**基于词法、名称匹配**的检查过程,而不是数据流引擎。 在进行排查时请记住这些盲点 —— 对于这种无依赖的设计而言,它们是已知且刻意为之的,并非 bug: - **别名会破坏污点追踪。**Witness 追踪匹配的是参数*名称*, 因此通过本地变量复制 witness 会将其隐藏: const q = qty; this.send({ to: dest, amount: q }); // qty 未被标记 const slot = this.root; slot.get(); // 遗漏了缺失的前置条件 - **跨方法绑定的深度仅为 1。**作为 `this.verifyX(arg)` 调用的未装饰同类辅助方法 可以绑定调用者的参数(一级)。 更深的链(`@method` → helper A → helper B)以及自由/导入的函数 **不**被追踪。helper 参数的本地变量别名也是 已记录的限制。 - **未断言 Bool 的检测是基于语句形状的。**Tier A 仅标记裸 表达式语句,这些语句的最外层调用是一个后面没有链接任何内容的 Bool 断言。嵌套在 `Provable.if(...)` 中的断言,或被赋值并 随后使用的断言,不会被标记。如果名称从未被引用,复杂的控制流中使用 Bool 本地变量的情况 可能仍会被遗漏(失败模式:漏报, 而非误报)。 - **签名门控是基于方法级别和子字符串的。** `_method_is_signature_gated` 会将整个 `@method` 视为所有者门控,前提是它 包含签名惯用语法,并且仅当接收者名称字面上包含 `signature` 时才将其识别为验证器 —— 因此 `sig.verify(admin, msg)` **不**被识别为门控,而在大型方法中其他地方的不相关的签名检查 可能会过度抑制。它是基于每个方法的全有或全无机制。 - **Sender 验证是基于名称且仅限于同一方法内。** 当 `this.sender.getAndRequireSignature()` 或 `AccountUpdate.createSigned()` 出现在**同一个** `@method` 函数体中时,`O1JS_UNCONSTRAINED_SENDER` 将被抑制。仅存在于辅助方法中的签名要求 (`this.requireSenderSig()` → 内部调用 `getAndRequireSignature`)**不**被 追踪 —— 失败模式是在包装了该惯用正确代码上出现误报, 而不是遗漏了真正的 bug。 - **Noir 跨 crate 辅助方法**仅通过**名称约定**识别(不包含 `Nargo.toml` / 导入解析)。宁可选择漏报而非误报。 这些就是为什么发现结果是人工审查的起点,而不是 证明的原因。数据流感知的重写明确不在 词法分析器的作用范围之内。 ## 工具的局限 o1js-scan 刻意设计为**浅层的、单文件词法扫描** —— 没有解析器, 没有数据流,也没有求解器。这就是使其零依赖且在 CI 中瞬间完成的原因,同时也是它的硬性天花板。上述限制并不是待办事项; 它们是该设计的必然后果。 因此,明确该工具能告诉你什么以及不能告诉你什么是值得的: - **运行通过并不代表审计通过。**它只意味着没有该扫描器识别出的*形状*被 匹配到 —— 并不代表电路是可靠的。需要数据流、 路径敏感度或约束求解的 bug 类别超出了这种形状工具的能力范围, 这在任何语言中都是如此。 - **发现结果是一条线索,而不是定论。**这里的每一条规则都是具有 已记录误报类别的启发式方法。 这种权衡对于每次提交都运行的 linter 来说是正确的。如果你正在 处理那些差异至关重要的事情 —— 比如持有真正 价值的协议、或者绝不能出错的电路 —— 请将其视为第一步, 并留出预算进行真正的审查。 更深入的分析正是 [Proofplay Logic](https://github.com/auditinfra-io) 正在做的事情;这个扫描器是我们能够开源的那一部分。如果你希望 对某个电路进行妥善审查,请联系:`auditinfracorp@proton.me`。 ## 兼容性 适用于 **o1js 1.x 和 2.x**。o1js-scan 将 TypeScript 源码作为文本分析, 并且**不依赖 o1js 运行时** —— 没有任何版本锁定。它基于 现代 `require*` 前置条件 API(`getAndRequireEquals`、 `requireEquals`、`requireSignature`、`getAndRequireSignature`)、 `@method` / `@method.returns(...)` 装饰器、`@state`、`this.send({...})` 和 `Permissions.*` 进行匹配,所有这些在 1.x → 2.x 边界上都没有变化。 2.x 的所有者认证惯用语法 `this.sender.getAndRequireSignature()` 被识别 为签名门控。(旧版的 `assertEquals` 前置条件仍被接受, 因此旧代码也不会被破坏。) Noir 分析针对 Aztec / nargo 项目使用的 Noir 语法(`.nr`);它 不会调用 `nargo` 或编译电路。 ## 工作原理 它是一个词法分析器,而不是完整的 TypeScript 或 Noir 解析器 —— o1js 和 Noir 源码由大括号分隔且可通过正则表达式提取,其输出旨在 供人工排查。这使其保持零依赖,并能在 CI 中瞬间运行。 发现结果是审查的起点,而非证明。 ## 路线图 / 贡献 欢迎贡献 —— 新的规则系列、更多的 FP 防护以及真实的 校准原型都极具价值。请参阅 [`CONTRIBUTING.md`](CONTRIBUTING.md)。 运行测试和 linter: ``` pip install -e ".[dev]" pytest # 154 tests ruff check . # lint ``` ## 许可证 Apache-2.0。请参阅 [`LICENSE`](LICENSE)。
标签:云安全监控, 图数据库, 智能合约审计, 逆向工具, 零知识证明, 静态分析