yoozzeek/zk-scribble
GitHub: yoozzeek/zk-scribble
面向 Hekate ZK 程序的 trace 变异模糊测试工具,通过篡改有效 trace 来检测 AIR 约束是否存在完备性漏洞。
Stars: 0 | Forks: 0
# zk-scribble
[](https://crates.io/crates/zk-scribble)
[](https://docs.rs/zk-scribble)
[](https://github.com/yoozzeek/zk-scribble/actions/workflows/ci.yml)
[](./LICENSE)
[Hekate](https://github.com/yoozzeek/zk-scribble) ZK 程序与 chiplet 的 trace 变异 fuzzer。
它会篡改你有效的 trace,运行 preflight 检查,
如果篡改未被检测到则会触发 panic。如果 scribble
触发了 panic,说明你的约束存在漏洞。
## 它的功能
- 翻转比特位、交换行、切换 selector、注入越界值
- 使用 preflight 进行验证(逐行约束求值 + bus multiset 检查)
- 集成 Proptest,将失败案例收缩为逃逸你 AIR 的最小变异
- 在 debug 模式下数秒内运行完毕,而非数分钟
## 它不能做什么
- **不生成证明。** Scribble 永远不会调用 prover 或 verifier。它检查的是具体 trace 上的约束,而
不是通过 ZK pipeline 进行。这就是它速度快的原因。
- **不进行协议级测试。** Transcript 绑定、Fiat-Shamir、求值参数、Brakedown,这些都需要真正的
prover/verifier e2e 测试。
- **不提供完备性保证。** 通过 scribble 检查意味着每一个随机变异都被捕获了。这并不能证明你的
约束是完备的,只能说明你写下的约束已经被正确接入了。
## 两个层级
**第 1 层:随机覆盖。** 使用浅层变异(BitFlip、FlipSelector、SwapRows、
OutOfBounds)执行 `assert_all_caught`。用于发现无约束列、缺失的布尔值、selector 隔离漏洞。Proptest 会将任何逃逸的变异
收缩为最小的复现案例。在 chiplet 开发期间运行。
**第 2 层:确定性模拟。** 使用手动构造的结构化或复合变异
(SwapColumns、CopyColumns、Compound)执行 `check_single_mutation`。用 15 行的调用
取代每个 e2e 利用测试中 80 行的样板代码。相同的攻击逻辑,速度快 100 倍(preflight 对比 prove+verify)。作为回归测试套件运行。
## 快速开始
```
use zk_scribble::{ScribbleConfig, assert_all_caught};
#[test]
fn my_chiplet_survives_chaos() {
let (air, instance, witness) = setup_my_chiplet();
assert_all_caught(&air, &instance, &witness, ScribbleConfig::default());
}
```
## 针对特定 chiplet
```
use zk_scribble::{ScribbleConfig, Target, assert_all_caught};
#[test]
fn mlkem_ntt_chiplet_survives_chaos() {
let (program, instance, witness) = setup_mlkem_fixture();
let config = ScribbleConfig::default()
.target(Target::Chiplet(1))
.cases(512);
assert_all_caught(&program, &instance, &witness, config);
}
```
## 限制变异类型
```
use zk_scribble::{MutationKind, ScribbleConfig, assert_all_caught};
#[test]
fn ram_selector_fuzzing() {
let (air, instance, witness) = setup_ram_fixture();
let config = ScribbleConfig::default()
.mutations([MutationKind::FlipSelector, MutationKind::SwapRows])
.cases(1024);
assert_all_caught(&air, &instance, &witness, config);
}
```
## Dispatch swap(第 2 层)
交换两行之间的部分列。Selector 和 RAM 列保持不变,
用于模拟在保持 dispatch 结构的同时重新排列数据的攻击者。
```
use zk_scribble::{Mutation, Target, check_single_mutation};
#[test]
fn ntt_dispatch_swap_caught() {
let (air, instance, witness) = setup_ntt_fixture();
let ntt_data_cols = vec![
NTT_A, NTT_B, NTT_A_OUT, NTT_B_OUT,
NTT_LAYER, NTT_BFLY, NTT_INSTANCE,
];
let mutation = Mutation::SwapColumns {
target: Target::Chiplet(0),
cols: ntt_data_cols,
row_a: 5,
row_b: 12,
};
let result = check_single_mutation(&air, &instance, &witness, &mutation);
assert!(result.is_ok(), "dispatch swap must be caught by RAM binding");
}
```
## 协调的跨 trace 攻击
`Compound` 会原子性地应用多个变异。同步修改 chiplet 和 main trace
以绕过单表检查,此时跨表 bus 是唯一的防线。
```
use zk_scribble::{Mutation, Target, check_single_mutation};
#[test]
fn out_of_range_cross_trace_caught() {
let (program, instance, witness) = setup_ntt_with_cpu_fixture();
let mutation = Mutation::Compound(vec![
Mutation::OutOfBounds {
target: Target::Chiplet(0),
col: BUS_B_OUT_PHY,
row: 0,
value: Q as u128,
},
Mutation::OutOfBounds {
target: Target::Main,
col: CPU_B_OUT,
row: 0,
value: Q as u128,
},
]);
let result = check_single_mutation(&program, &instance, &witness, &mutation);
assert!(result.is_ok(), "out-of-range b_out must be caught");
}
```
## 阅读 preflight 报告
当 preflight 捕获到变异时,报告会指出失败的不变量、表和行:
```
PREFLIGHT: 1 constraint violations, 0 boundary violations, 0 bus issues
[Chiplet 0] Constraint 32 "bits_01_equal" failed at row 0
```
边界违规会显示具体的偏差:
```
PREFLIGHT: 0 constraint violations, 2 boundary violations, 0 bus issues
Boundary #0: col=0 row=0 actual=Flat(Block128(0)) expected=Flat(Block128(22067681354706156661646625971774519825))
Boundary #1: col=1 row=0 actual=Flat(Block128(1)) expected=Flat(Block128(265498766201044366875656389800751278795))
```
Bus 诊断会列出每一个 endpoint 及其行数、活跃行,
以及 multiset 乘积,这样你就可以看出 bus 的哪一边出现了分歧:
```
PREFLIGHT: 0 constraint violations, 0 boundary violations, 1 bus issues
Bus "test_bus" (2 endpoints):
Main: 8 rows, 4 active, product=Flat(Block128(16200159481073039905153729824056248498))
Chiplet 0: 8 rows, 4 active, product=Flat(Block128(179105452667142969769264969604701840821))
```
如果某个变异逃逸了(报告是干净的),scribble 就会带着经 proptest 收缩的 `Mutation` 触发 panic。该篡改
就是完备性漏洞,请添加一个能捕获它的约束。
## 环境要求
依赖于 `hekate-math`、`hekate-sdk`、`hekate-core`、`hekate-program`。
## 许可证
MIT
标签:Rust, SOC Prime, ZK, 代码质量审计, 可视化界面, 开发工具, 网络流量审计, 通知系统, 零知识证明