auditinfra-io/o1js-scan
GitHub: auditinfra-io/o1js-scan
一款零依赖的静态分析器,专门检测 o1js/Mina zkApps 和 Noir 零知识电路中的约束不足漏洞。
Stars: 1 | Forks: 0
# o1js-scan
[](https://github.com/auditinfra-io/o1js-scan/actions/workflows/ci.yml)

[](LICENSE)
[](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)。
标签:云安全监控, 图数据库, 智能合约审计, 逆向工具, 零知识证明, 静态分析