# minichain 审计准备

> **本项目未经任何第三方安全审计。** 本文汇总自动化工具结果、测试与自查发现，供未来审计者使用；它不是审计报告，也不构成安全性保证。

## 1. 范围

| 模块 | 文件 | 关键性 |
|---|---|---|
| **隐私密码学（最高优先级）** | `privacy/*`（ec.go 哈希到点/子群检查、stealth.go、clsag.go、bulletproof.go、decoy.go、tx.go 平衡与消息编码）、`chain/privstate.go`、`chain/privwallet.go` | **最高（首要）** |
| 共识与状态 | `chain/chain.go`（区块树、重组、难度、奖励）、`chain/state.go`（交易执行、覆盖层）、`chain/perf.go`（撤销日志、签名缓存、交易根）、`chain/security.go`（区块头检查、链 ID）、`chain/types.go`（交易/区块编码、签名、减半）、`chain/vm.go`（合约 VM） | **最高** |
| 存储 | `chain/store.go`（bbolt 布局、原子提交、完整性检查、reindex） | 高 |
| 网络 | `p2p/*`（身份、mTLS、握手、addrman、连接限制、限流、封禁）、`cmd/minichain/node.go`（同步、多节点区块体下载、处理器） | 高 |
| 密钥 | `wallet/wallet.go`（scrypt + AES-GCM、密封文件）、`cmd/minichain/secrets.go` | 高 |
| CLI / 演示 | `cmd/minichain/main.go`、`cmd/attacker` | 低（攻击工具仅用于演示） |

## 2. 复现

```bash
go version                          # 需要 go1.26.x（测试使用 go1.26.9）
go build ./... && go vet ./...
go test -race -count=1 ./...        # 全部测试（含 kill -9 崩溃测试）
FUZZTIME=60s ./fuzz.sh              # 7 个模糊测试目标
go test -coverpkg=./chain,./p2p,./wallet -coverprofile=c.out ./chain ./p2p ./wallet ./cmd/minichain && go tool cover -func=c.out
staticcheck ./... ; gosec -exclude-dir=vendor ./... ; govulncheck ./...
./demo.sh ; ./demo_p2p.sh ; ./demo_security.sh
```

原始输出：`audit/go_vet.txt`、`audit/staticcheck.txt`、`audit/gosec.txt`、`audit/govulncheck.txt`、`audit/coverage_func.txt`、`fuzz_output.txt`、`test_output.txt`、`bench_phase5.txt`。

## 3. 静态分析结果

| 工具 | 版本 | 修复前 | 修复后 |
|---|---|---|---|
| go vet | go1.26.9 | 0 | 0 |
| staticcheck | 2026.2.2 | 3（2 个无用赋值、1 个错误字符串格式）+ 修复过程中出现的 2 个未使用代码 | **0** |
| gosec | v2（最新） | 54（旧版 v2.21.4）；升级后新增 G123、taint 类规则等发现 | **21，全部在 CLI 与攻击演示工具中**（见下） |
| govulncheck | v1.9.0 | go1.23.4：**43 个可达的标准库漏洞**；x/crypto v0.39 等依赖中的漏洞 | **代码受影响 0**；模块中仅剩 GO-2026-5932（`x/crypto/openpgp` 已弃用，未修复版本 N/A，本项目不导入该包） |

### gosec 处理说明

已修复：
- **G123** TLS 使用 `VerifyPeerCertificate` 而会话恢复仍开启 → 改用 `VerifyConnection`（会话恢复时也会调用）并设置 `SessionTicketsDisabled`。
- **G404** addrman 与出站轮换使用以时间播种的 `math/rand` → ChaCha8 CSPRNG（crypto/rand 播种）。
- **G306** `bans.json` 以 0644 写入 → 0600。
- **G402** CLI 的自定义 CA TLS 配置未设 MinVersion → TLS 1.2+。
- **G112** 攻击演示服务缺少 ReadHeaderTimeout → 已设置。
- **G104** 核心包中所有未处理错误 → 处理或显式忽略（关闭连接、尽力写缓存文件等）。

以 `#nosec` + 理由标注（共 22 处，全部人工核对）：
- **G115** 整数转换：int64 与 uint64 之间的补码存储往返（`be64/de64`）；`InitialReward >> n`（已保证 0 ≤ n < 63）；`blockWork` 的移位（难度已验证在 1–5）；桶号取模（桶数为正常量）；文件描述符转换。
- **G304** 读取数据目录内的文件（路径来自本地配置，不来自网络）。
- **G402** P2P 客户端 `InsecureSkipVerify`：节点证书是自签名的，WebPKI 链校验没有意义；身份由 `VerifyConnection` 钉扎到节点 ID。
- **G117** addrman 的分桶秘密写入 0600 文件（本地使用，属于设计）。

保留（已评估，均不在核心包）：
- `cmd/minichain/main.go`：G703/G304（路径遍历：钱包、合约、令牌文件路径由操作者在命令行给出）、G704（SSRF：节点 URL 来自 `MINICHAIN_NODE`）、G706（日志注入：把操作者自己的输入写入错误信息）——操作者即信任主体，属于误报。
- `cmd/attacker`：11 个 G104、G704、G705——攻击演示工具，有意忽略错误、向用户给定目标发请求。

## 4. 模糊测试

Go 原生模糊测试（`go test -fuzz`），8 核，每个目标 60 秒（另有一轮每个 20 秒的预跑）：

| 目标 | 包 | 检查的性质 | 执行次数 | 新语料 | 结果 |
|---|---|---|---|---|---|
| FuzzTxDecode | chain | 任意 JSON 交易：解码/哈希/执行不崩溃；被拒绝的交易不改变状态 | 11.4 M | 354 | 通过 |
| FuzzBlockTxs | chain | 模糊字段构造任意交易并真实出块（满足 PoW）：覆盖层上接受的交易出块后必须被接受；执行后 `CheckInvariants` 成立；撤销后状态完全复原 | 1.04 M | 478 | 通过 |
| FuzzBlockDecode | chain | 任意区块 / 区块头列表 JSON：`Add`、`VerifyHeaders` 不崩溃，之后 `CheckIntegrity` 仍成立 | 2.10 M | 201 | 通过 |
| FuzzVM | chain | 任意代码/函数/参数/gas：不崩溃；结果确定（两次运行完全一致）；gas 不超上限 | 10.5 M | 394 | 通过 |
| FuzzUndoDecode | chain | 任意撤销日志字节：解码与回滚不崩溃 | 12.9 M | 358 | 通过 |
| FuzzHandshake | p2p | 任意 hello/ack：验证不崩溃；**合法 hello 的任一字段被篡改一个字节必须被拒绝** | 3.66 M | 279 | 通过 |
| FuzzNetgroup | p2p | 任意地址字符串：分组/子网/白名单解析不崩溃 | 6.56 M | 378 | 通过 |

模糊测试本身**没有发现崩溃**。下面的问题都是人工审查和不变量测试发现的（其中握手 hex 可塑性如果不先修复，会被 FuzzHandshake 的“篡改必须被拒绝”性质捕获）。

## 5. 不变量与属性测试

- `TestInvariantsRandomWorkloadWithReorgs`：6 个随机种子，混合负载（转账、代币创建含故意重名、代币转账、部署、调用），随机深度 1–6 的重组；每一步都做 `CheckIntegrity`（OKT 守恒、无负余额、发行 ≤ 上限、代币余额总和 = 供应量、名称索引一致），定期检查**撤销日志得到的状态 = 从创世完整重放**。
- `TestSupplyCapNeverExceeded`：按默认减半解析计算总量（≤ 2100 万且误差 < 1 OKT）；多个小减半周期逐块模拟；接近上限的截断；极大高度。
- `TestTokenNamesGloballyUniqueAnyCase`：200 个随机大小写/空白变体全部被拒绝。
- `TestCrossChainReplayRejected`、`TestTxEncodingMalleabilityRejected`、`TestVMOverflowStackAndLimits`、`TestBlockFieldValidation`。
- 存储：持久化/重启后重组、篡改余额/区块被检测、`-reindex` 恢复、参数不匹配拒绝、注入提交失败时原子回滚、**kill -9 崩溃测试**（子进程持续出块并周期性重组，随机时刻 SIGKILL，共 4 轮：重新打开 → 完整性通过 → 状态 = 完整重放）、旧 JSON 导入、覆盖层不修改底层状态。

## 6. 覆盖率

合并统计（`-coverpkg=./chain,./p2p,./wallet`，测试来自这些包和 `cmd/minichain`）：

| 包 / 文件 | 上阶段（各包单独统计） | 本阶段 |
|---|---|---|
| **chain（共识）** | 81.4% | **90.9%** |
| chain/security.go | — | 100% |
| chain/state.go | — | 98.0% |
| chain/perf.go、vm.go | — | 96.3% |
| chain/chain.go | — | 94.4% |
| chain/types.go | — | 93.3% |
| chain/store.go | 新 | 81.7%（未覆盖的主要是磁盘 I/O 错误分支） |
| p2p | 72.7% | 90.9%（合并）/ 76.1%（单独） |
| wallet | 81.0% | 80.4% |
| cmd/minichain | 62.0% | 64.0% |

## 7. 自查发现的问题（已修复）

| # | 严重度（自评） | 问题 | 修复 |
|---|---|---|---|
| 1 | 高 | **交易签名不含链 ID**：任何参数不同、创世相同（或任意测试网）的链上签名的交易都可以被重放 | `Tx.ChainID` 被签名覆盖，链状态强制匹配；CLI 自动从 `/info` 获取 |
| 2 | 高 | **交易哈希可塑性**：`hex.DecodeString` 接受大写，同一签名可得到不同交易哈希，绕过内存池/已见去重（nonce 仍防双花） | 签名/公钥只接受规范小写 hex；地址同样 |
| 3 | 中 | VM 整数溢出静默回绕（ADD/SUB/MUL，包括 MinInt64 × −1） | 检查溢出，溢出 → 执行失败 |
| 4 | 中 | VM 栈无上限（gas 1e6 内可压入约 100 万个元素 ≈ 8 MB/交易） | 栈 ≤ 1024 |
| 5 | 中 | 合约存储无上限，且每次调用都复制整份存储（O(存储) 的 CPU/内存 DoS） | ≤ 1024 键 |
| 6 | 中 | 交易字段无大小限制（代码、参数、名称等只受 HTTP 消息上限限制）；部署时 `ARG` 下标不检查范围，且部署/执行用不同的整数解析函数 | `CheckStatic` 上限；`ARG` 在 [0,16) |
| 7 | 中 | 头合法但执行失败的区块没有被记住：重复发送同一个（低难度即可满足 PoW 的）无效块会被反复完整执行（CPU DoS） | 按哈希标记无效 |
| 8 | 中 | 矿工字段不校验（任意长度字符串写入状态 → 内存/磁盘 DoS）；奖励/时间可为负 | 1–64 字节；非负 |
| 9 | 中 | TLS 会话恢复可能跳过自定义证书校验（gosec G123） | `VerifyConnection` + 禁用会话票据 |
| 10 | 中 | addrman 选择与出站轮换使用以时间播种的 `math/rand`：可预测，有利于日蚀攻击 | ChaCha8 CSPRNG |
| 11 | 中 | 工具链 go1.23.4：43 个可达的标准库漏洞（crypto/x509、net/http、crypto/tls 等） | go1.26.9；依赖升级 |
| 12 | 低 | 握手签名消息用 `|` 拼接，字段内允许 `|`（字段边界歧义）；握手签名接受大写 hex | 禁止 `|` 并限制长度；规范 hex |
| 13 | 低 | 转账接收方只检查长度（非 hex/大写地址会产生“看起来相同”的不同账户） | 必须是 40 位小写 hex |
| 14 | 低 | 同步统计计数器在多个并发同步中无锁更新（潜在数据竞争） | 在节点锁下更新 |
| 15 | 低 | 对端 status 中的高度/工作量未限制范围（`Height+1` 可溢出） | 高度 ≤ 2^40，工作量 ≤ 4096 比特 |
| 16 | 低 | 钱包解密后直接使用 64 字节私钥（种子与公钥两半可能不一致） | 由种子重新派生 |
| 17 | 低 | `bans.json` 权限 0644、写入错误被忽略 | 0600，原子替换 |
| 18 | 低 | 难度重定向在祖先缺失时会解引用空指针（目前所有调用路径都保证祖先存在，属防御性修复） | 判空 |
| 19 | 可用性 | 撒谎检测误判：对端在两次请求之间切换链尖会被扣 50 分 | 头链结束于声明链尖才立即判定，否则重试一次 |
| 20 | 性能/安全 | 每出一块整链 JSON 重写（O(链长)）；重启需全量重放；内存池每块复制整份状态 | bbolt 按块原子提交；覆盖层 |

## 8. 建议审计重点

1. `reorgTo` 的失败回滚路径与撤销日志的正确性（尤其是覆盖层/写时复制与指针共享）。
2. `flush` / `writeDirty`：脏键集合是否完整覆盖所有状态变化（目前由“撤销日志 ⊇ 所有写入”保证；测试通过 重启状态 = 重放 验证）。
3. 打开时完整性检查的边界（不重放交易，见 THREAT_MODEL §5）。
4. headers-first 中的工作量比较、分页与 `maxHeaders` 处理；多节点区块体调度在恶意节点组合下的活性。
5. 握手与 TOFU；限流/封禁在回环测试模式下的行为。
6. VM 的 gas 计价是否与实际 CPU/内存成本相称。

## 9. 已知未解决问题

第 6 阶段已解决：MTP、紧凑难度与逐块重定向、手续费与内存池策略、Unicode 名称防伪、状态根与 `-verifystate`、检查点/最终性、区块体裁剪、多节点头链交叉检查、提交失败在线恢复、ASN 分组、严格钉扎、秘密清零与 FD/文件口令。

仍未解决：见 `README.md` “局限”与 `THREAT_MODEL.md` §5–§7（低默认难度、Go 内存清零不保证、隐私代码未审计）。（第 8 阶段已解决：累加器 → 稀疏 Merkle 树 + 成员/不存在证明。）本项目**未经审计**。

### 第 6 阶段建议审计重点

1. `accumulator.go`：每个状态写入是否都更新累加器（含零值删除、合约存储差分、代币整体更新），撤销路径 `uAcc` 是否完整。
2. `names.go`：规范化顺序与 confusables 表的完整性；混合文字判定。
3. `pow.go`：`CompactToBig`/`BigToCompact` 边界、MTP 与重定向夹逼。
4. `mempool.go`：RBF 与驱逐在跨账户依赖下的正确性；费率比较溢出。
5. `finality.go`：`Recover()` 与并发加块的交互；检查点与重组的交互。

### 第 6 阶段覆盖率（各包单独统计）

chain 91.5%、p2p 77.9%、wallet 80.6%、cmd/minichain 65.3%。

## 第 7 阶段

> 仍然**未经任何审计**。隐私代码使用经审查的曲线库（filippo.io/edwards25519），但协议实现是本项目自写的，是**最高优先级审计范围**。

### 建议审计重点（按优先级）

1. `privacy/bulletproof.go`：Fiat–Shamir 转录是否绑定全部公开输入（V、A、S、T1、T2、L/R）；聚合填充；批量验证的随机权重；`VerifyRange` 对 m、向量长度的边界检查。
2. `privacy/clsag.go`：μP/μC 域分离、挑战链、D 的存储与 8 倍处理、环成员顺序与消息绑定。
3. `privacy/ec.go`：HashToPoint（试探递增 + 清余因子）是否均匀、与常数时间无关的泄露；`InPrimeSubgroup`。
4. `privacy/tx.go` + `chain/privstate.go`：平衡等式、屏蔽池记账、手续费与 unshield 计入、密钥镜像集合与重组撤销、环成员必须已存在且已过锁定期、签名缓存键是否包含环成员内容。
5. `privacy/stealth.go`：共享秘密 8rA、view tag、金额加密的密钥派生与重用。
6. `chain/accbatch.go`：延迟累加器更新的完整性（每次写入都被 flush）。
7. `chain/rollback.go`、`chain/store.go` 打开路径。
8. `cmd/minichain/bandwidth.go`：紧凑区块重建（短 ID 碰撞时回退整块）。

### 工具结果

- `go vet`、`staticcheck`：0。`govulncheck`：代码受影响 0（所需模块中 1 条，代码未调用）。
- `gosec`：privacy / chain / p2p / wallet 0 未处理（`decoy.go` ChaCha8 由 crypto/rand 播种，已 `#nosec` 注明）；其余全部在 CLI 与 `cmd/attacker`（文件路径、未检查错误、taint 型误报）。
- 模糊：16 个目标各 60 s（新增 FuzzPointDecode、FuzzPrivTxDecode、FuzzRangeProofDecode、FuzzAddress、FuzzPrivTxApply），结果见 `fuzz_phase7.txt`。
- 属性测试：正确性往返、密钥镜像双花拒绝、无效范围证明拒绝、承诺平衡伪造拒绝、环成员替换/篡改拒绝、随机隐私交易序列下 Σ余额 + 池 = 已发行、重组与重启后隐私状态一致。
- 断电模型测试（`chain/powerloss_test.go`）。
- `-race` × 3 通过。

### 覆盖率（各包单独统计）

chain 90.2%、privacy 78.1%、p2p 77.9%、wallet 81.0%、cmd/minichain 60.0%。

## 第 8 阶段

> 仍然**未经任何审计**。下面是给审计方的准备材料，不是审计结论。隐私密码学仍是**最高优先级审计范围**。

### 建议审计重点（按优先级）

1. `privacy/bulletproofplus.go`：转录绑定（V、A、每轮 L/R、A1/B）；加权内积最后一轮 r1/s1/d1 的验证等式；y 的幂次与 d 向量；批量验证的随机权重与单次 MSM 合并；isZero 检查；聚合 m 的填充与上限。**无外部测试向量**。
2. `privacy/clsag.go`、`privacy/ec.go`、`privacy/stealth.go`（同第 7 阶段）。
3. `chain/feerule.go` + `state.go` + `privstate.go` + `perf.go`：每种交易类型的手续费必须精确相等；代币手续费在数量之外扣除；`distributeFees` 在区块执行末尾、撤销记录覆盖分配；空地址时份额归矿工；整数溢出（金额 ≤ MaxSupply）。
4. `chain/smt.go` + `accumulator.go` + `accbatch.go`：叶/内部节点域分离（0x00/0x01）、压缩路径的规范形状、不存在证明的两种情形（空子树 / 其他叶子占位）、撤销后过期标记与重算。
5. `cmd/minichain/web.go`：CSRF/Host/Origin 检查、会话生命周期、私钥清零、代理白名单。
6. `cmd/minichain/dandelion.go`：茎池上限、禁运计时器、`stemView` 叠加顺序、茎交易不进入公开内存池。
7. `internal/faultfs`：故障模型是否覆盖目标文件系统语义（元数据立即持久是简化）。

### 共识手续费规则

见 `README.md`“共识手续费规则”。性质测试：`TestFeesSplitNoMint`、`TestFeeSplitRounding`、重组测试中的分配撤销、`FuzzBlockTxs`（一半输入使用规定手续费并检查总量与分配）、新增 `FuzzFeeRule`。

### 发现的问题

见 `README.md`“第 8 阶段发现并修复的问题”。首次运行 `FuzzSMTProof` 的失败经查为 build failed（运行中修改了 faultfs_test.go），非真实发现。详见 `audit/flaky_investigation.md`。

### 工具结果

见 `audit/phase8_*.txt`（vet、staticcheck、gosec、govulncheck、覆盖率）、`fuzz_phase8.txt`、`bench_phase8.txt`。

结果摘要：`go vet` 0、`staticcheck` 0（本阶段修复：已弃用的 `runtime.GOROOT`、2 个未使用符号、1 个只执行一次的循环）、`govulncheck` 代码受影响 0（所需模块中 1 条未调用）、`gosec` 24 条全部位于 CLI（`cmd/minichain/main.go`、`privcli.go`、`profile.go`：用户指定文件路径、未检查的 Close）与攻击演示工具 `cmd/attacker`；chain / privacy / p2p / wallet / internal / web.go 0 未处理（新增 `#nosec` 均附理由）。
`-race`：完整 `./...` 3 轮中第 3 轮的 `chain` 包出现一次 panic（当时与模糊测试并发运行，输出被过滤，未保存堆栈）；**已定位并修复**：TestFaultFSPowerLoss（drop-fsync）产生的损坏镜像使 bbolt 在 Open 时越界读 mmap，Go 报致命 `unexpected fault address`（不可 recover，导致 faultfsd/FUSE 挂载泄漏）；时序依赖解释了其偶发性。修复：openDBSafe 期间 `debug.SetPanicOnFault(true)`，openDB 在 panic 路径关闭句柄释放文件锁（第二个潜在缺陷）；回归测试 TestOpenCorruptLeafOffsetsNoFault。修复后 207 次 -race 循环 0 次 fault，`-race -count=10 -cpu 1,2,4,8` 通过。限制：成功打开后的运行期读取仍不受保护（bbolt 无页校验）。
模糊测试：20 个目标各 45 s（新增 FuzzSMTProof、FuzzFeeRule、FuzzStemTx、FuzzWebAmount）。`FuzzRangeProofDecode` 首轮报告 FAIL，但没有保存失败输入，输出被截断（已把 fuzz.sh 改为保留更多输出），90 s 重跑通过；后续 10 min×8 worker、6×45 s（负载下）、406 条缓存语料回放全部通过，代码审查未发现问题。**仍未复现**：最可能为截止时刻 worker 被终止/挂起（无失败输入写入 testdata/fuzz），但未证实，审计时仍应关注。FuzzSMTProof 早期“失败”为 build failed。
覆盖率：chain 91.3%、privacy 78.2%、p2p 79.1%、wallet 81.0%、cmd/minichain 61.3%、internal/memlock 100%（internal/faultfs 由 chain 的 `TestFaultFSPowerLoss` 覆盖）。
