# minichain —— 最小原型链（原生币 OKT）

用 Go 编写的原型区块链（支持多节点 P2P）：PoW 出块、ed25519 钱包、简单智能合约 VM、链上发币（**代币名称全局唯一**）。

## 架构

```
cmd/minichain/main.go   CLI 客户端
cmd/minichain/node.go   全节点：本地 HTTP API + mTLS P2P（gossip、节点交换、同步、打分封禁、限流）
cmd/attacker/           安全演示用恶意节点（女巫、握手洪泛、slowloris、伪造区块、垃圾交易、请求洪泛、谎报工作量）
wallet/                 钱包加密（scrypt + AES-256-GCM）
chain/perf.go           撤销日志、并行验签、签名缓存、tx_root
p2p/                    节点身份/TLS、身份 PoW、握手难题、签名握手、addrman、TLS 连接限制、LRU、限流、封禁（含测试）
chain/security.go       廉价区块头检查、链 ID / 共识参数哈希
cmd/minichain/node_test.go  多节点集成测试
cmd/minichain/web/      内置 Web 钱包 / 区块浏览器（第 8 阶段）；web.go 本地代理与钱包服务；explorer.go 浏览器 API
cmd/minichain/dandelion.go  Dandelion++；light.go 轻客户端；keyimageset.go 密钥镜像集合
chain/smt.go            稀疏 Merkle 状态树与证明；chain/feerule.go 共识手续费规则
internal/faultfs/       故障注入文件系统（FUSE）；internal/memlock 堆内存锁页
demo_web.sh             Web 界面演示与截图（screenshots/）
chain/types.go          交易、区块、地址、签名、出块奖励（减半）
chain/state.go          世界状态：OKT 余额、nonce、合约、代币；交易校验与执行
chain/vm.go             极简确定性栈式 VM（带 gas 上限）
chain/chain.go          区块树、最大工作量链选择与重组、难度调整、PoW
chain/store.go          bbolt 持久化：按块原子提交、打开时完整性检查、-reindex、旧 JSON 导入
chain/*fuzz_test.go     Go 模糊测试（交易/区块/区块头解码、合约 VM、撤销日志）；p2p/fuzz_test.go（握手）
chain/invariants_test.go, consensus_test.go, store_test.go  不变量 / 共识规则 / 存储与崩溃测试
THREAT_MODEL.md         威胁模型；AUDIT_PREP.md 审计准备（工具结果、模糊测试、覆盖率、自查发现）
audit/                  go vet / staticcheck / gosec / govulncheck / 覆盖率 原始输出
fuzz.sh                 运行全部模糊测试
chain/p2p_test.go       重组 / 同步 / 难度调整测试
chain/chain_test.go     单元测试
contracts/counter.asm   计数器合约示例
demo.sh                 单节点演示；输出见 demo_output.txt
demo_p2p.sh             多节点演示；输出见 demo_p2p_output.txt
demo_security.sh        网络安全演示；输出见 demo_security_output.txt
```

- **共识/出块**：PoW，`-difficulty` 为初始难度（区块哈希前导 0 的十六进制位数，每块工作量 = 16^难度）；`-blocktime` 为出块间隔（如 `5s`，设为 `0` 时只通过 `mine` 命令手动出块）。
- **难度调整**：每 `-retarget` 个块（默认 20）比较实际耗时与 `-target` 秒（默认 5）×块数：快于一半则难度 +1，慢于两倍则 −1（范围 1–5）。区块中声明的难度必须等于按其祖先计算出的值。`-target 0` 关闭调整。
- **持久化（bbolt，`<data>/chain.db`）**：区块（含侧链）、区块索引（累计工作量/发行/无效标记）、规范链（高度→哈希）、最近 2000 块的撤销日志、状态（余额、nonce、合约及其存储、代币头与余额——代币桶以规范化名称为键，即全局唯一名称索引）、链尖，**每个区块（或一次完整重组）在一个 bbolt 写事务中原子提交并 fsync**。重启时直接加载，不再验签/执行，只做完整性检查（哈希链、PoW、交易根、难度/奖励/发行/工作量重算、撤销日志存在、`Σ余额 = 已发行 ≤ 上限`、代币余额总和 = 供应量、名称索引一致）；检查失败则拒绝启动并提示 `-reindex`（从库中区块完整重放重建）。数据库记录共识参数哈希，参数不同拒绝打开。旧版 `chain.json` 首次启动时自动导入（完整校验），原文件保留为 `.legacy.json`。
- **内存池覆盖层**：待定状态是建立在链状态之上的写时复制覆盖层（只保存内存池造成的改动，读取未命中回落到链状态），每出一个块重建只需 O(内存池)，不再复制整个状态。
- **区块头承诺交易**：区块哈希只覆盖区块头，交易通过 `tx_root` 承诺，因此可以先下载并验证区块头（headers-first）。

## P2P 网络

节点间通过 **双向认证 TLS 1.3 上的 HTTP** 通信（`/p2p/*` 接口，监听 `-p2p`，默认 127.0.0.1:9545）；`-addr` 为仅供本地 CLI 使用的明文 API：

- **连接与发现**：`-peers [节点ID@]host:port,...` 指定初始节点（带 ID 则钉扎，否则首次连接时记录 TOFU）；`-self` 为对外宣告地址。每 `-sync` 秒（默认 2s）节点会向每个 peer 发送 `hello`（对方把自己加入列表），并拉取对方的 peer 列表（节点交换 PEX），从而自动发现全网节点。
- **交易 gossip**：本地提交或收到的有效交易进入内存池，并转发给其他 peer；每个 peer 有独立的有序发送队列，保证同一账户的 nonce 顺序。
- **区块 gossip**：挖出或收到的有效区块立即转发。
- **去重**：按交易/区块哈希记录已见消息，重复消息直接丢弃，避免广播风暴。
- **同步/追块（headers-first）**：比较 peer 声明的累计工作量；若更大，先分页（≤500）下载区块头并验证链接、PoW、难度、奖励、时间戳，计算**可证明的**累计工作量——少于声明值即判定撒谎并扣分；验证通过后**从多个节点并行下载区块体**：除同步对象外，再选最多 `-bodypeers`−1 个（默认共 4 个）“拥有同一已验证头链”的节点（对方在最后高度处的区块头哈希必须与已验证的头相同——由于哈希链接，这证明它拥有整条前缀），按 `-bodychunk`（50）块一组分配，每节点 `-bodyworkers`（2）个并发请求；每个请求 `-bodytimeout`（4s）超时。区块体必须与已验证的头一致（`tx_root`、哈希），否则该节点被扣 50 分并停止分配；超时扣 5 分，连续两次超时停止分配；失败的块组重新分配给尚未尝试过它的其他节点。最后按序连接；执行失败的块按其来源节点扣分。
- **减少撒谎误判**：只有当对方的头链恰好结束于它声明的链尖、证明的工作量却少于声明时，才立即判定撒谎；若头链与声明的链尖不一致（对方在两次请求之间切换了链尖/重组），先用新的 status 重试一次，仍不一致才扣分（统计 `LieRetries`）。
- **分叉选择与重组**：节点保存区块索引（含侧链），规范链 = **累计工作量最大**的有效链。侧链区块先做只依赖区块头的检查后存储；当侧链更重时：用撤销日志回退到分叉点 → 逐块执行新分支；若新分支中某块无效，则标记该块及后代无效并恢复原链。代价与重组深度成正比；重组深度上限 2000（更早区块的撤销日志会被裁剪）。旧分支中独有的交易回到内存池。
- **全量校验**：收到的每笔交易（签名、nonce、余额、代币名唯一性、合约 gas）和每个区块（链接、PoW、难度、时间戳、奖励、供应上限、所有交易）都在本地重新校验。
- 同一网络的所有节点必须使用相同的 `-difficulty -halving -target -retarget` 参数（共识参数）。

## 网络安全设计

**身份与握手**
1. **节点身份 + 身份工作量证明（防女巫）**：节点 ID = ed25519 公钥；要求 `H("minichain-nodeid-v1|"+ID)` 至少有 `-idbits`（默认 16）个前导零比特，生成一个合格身份约需 2^16 次密钥生成。握手双方都验证；不合格 ID 直接拒绝。
2. **负载下握手难题**：每秒 hello 超过 `-loadhellos`（默认 20），或 TLS 未完成握手的连接超过上限一半时，`/p2p/ping` 下发一次性随机种子，客户端须求解 `H(seed|clientID|nonce)` 有 `-puzzlebits`（默认 18）个前导零比特才能握手。`-forcepuzzle` 可强制开启。
3. **mTLS 1.3 + 签名挑战握手**：证书公钥即节点密钥（钉扎到 ID）；hello 绑定双方 ID、链 ID、共识参数哈希、时间戳、随机挑战并签名；服务端签名应答。链/参数不一致、伪造身份、过期或转发重放均拒绝。

**信任、打分与封禁**
4. **新节点低信任**：握手后 `-newpeerperiod`（默认 10 分钟）内且未贡献 3 个有效区块的节点，普通请求带宽只有正常的 25%；同步类请求（headers/bodies）使用独立的较高额度，保证新节点也能完成初始同步；客户端遇到 429 会退避重试。
5. **打分随时间衰减**：分数每分钟恢复 `-decay`（默认 10）分，偶发错误的诚实节点不会被永久累积封禁。
6. **按 ID / IP / 子网封禁**：始终按节点 ID 封禁；非回环地址同时按 IP（阈值 100）和子网（IPv4 /24、IPv6 /48，阈值 300）封禁，被封 IP 在 TCP 接入时即断开。**回环/测试模式**（`-testmode auto|on|off`，P2P 监听回环时自动开启）下多个本地节点共享 127.0.0.1，因此回环 IP 不被封禁，其违规单独记在 `lo:<id>` 上统计。
7. **受信节点白名单**：`-trusted` 接受节点 ID、IP 或 CIDR；受信节点不扣分、不封禁、不限流、不受连接配额限制。封禁列表持久化于 `bans.json`。

**防日蚀**
8. **分桶地址簿（addrman）**：`new` 表 64 桶、`tried` 表 16 桶，每桶 16 个；new 桶 = H(秘密, 地址分组, 来源分组)，tried 桶 = H(秘密, 地址分组)。一个来源灌入的海量地址只能落入极少数桶。连接成功的地址移入 tried 表。地址簿持久化于 `peers.json`。
9. **分组**：IPv4 按 /16、IPv6 按 /32（回环单独一组）；出站连接每个分组最多 1 个（测试模式下回环除外）。
10. **锚点连接**：最早建立的 2 个出站连接保存在 `anchors.json`，重启后最先重连。
11. **出站轮换与 feeler**：每 `-rotate`（默认 10 分钟）断开一个非锚点、非受信的出站连接；每 `-feeler`（默认 2 分钟）试探一个 new 表地址，握手成功即移入 tried 表后断开。
12. **种子节点**：`-seeds` 指定，地址簿为空时使用。

**TLS 层与资源上限**
13. **连接上限（防 slowloris）**：TLS 握手前即检查：总连接 `-maxconns`（64）、每 IP `-maxconnsperip`（8）、正在握手的连接 `-maxpending`（16）；TLS 握手截止 `-handshaketimeout`（5s）；HTTP ReadHeader 5s / Read 15s / Write 30s / Idle 60s。只有完成握手的连接才交给 HTTP 层。
14. 原有措施：每 peer + 全局限流、消息大小上限（区块 2MB、交易 64KB）、有界已见缓存（LRU+TTL）、有界内存池（每账户 64、满时驱逐）、孤块池 64、廉价头部检查先行、区块头分页 ≤500、区块体每请求 ≤100。

**本地 API 与钱包**
15. **API 令牌**：首次启动生成 32 字节随机令牌，写入 `<data>/api_token`（权限 0600）；所有 API 请求必须带 `Authorization: Bearer <token>`（常量时间比较）。CLI 从 `MINICHAIN_TOKEN` / `MINICHAIN_TOKEN_FILE`（默认 `data/api_token`）读取。
16. **绑定限制**：默认 127.0.0.1；绑定非回环地址时必须提供 `-apicert/-apikey`（HTTPS），否则拒绝启动。
17. **钱包加密**：私钥用 scrypt（N=2^15, r=8, p=1）派生 256 位密钥，AES-256-GCM 加密，地址作为附加认证数据；文件权限 0600。口令来自 `MINICHAIN_PASSPHRASE` 或终端输入（≥8 字符，**终端输入不回显**，x/term）。解密后由种子重新派生密钥。旧版明文钱包仍可读取但会警告，可用 `wallet-encrypt` 升级。
18. **节点私钥 / API 令牌静态加密（可选）**：`-encryptkeys` 或设置 `MINICHAIN_KEY_PASSPHRASE` 时，节点私钥保存为 `node_key.enc`、令牌保存为 `api_token.enc`（同样 scrypt + AES-GCM，用途字符串作为 AAD，防止文件互换）；已有明文文件会被加密迁移后删除。CLI 读取 `api_token.enc` 时使用同一口令（或直接设置 `MINICHAIN_TOKEN`）。

**交易与共识加固（本阶段自查修复）**
19. **防跨链重放**：交易新增 `chain_id` 字段并被签名覆盖；链状态只接受本链 ID（`/info` 返回，CLI 自动填写）。不同创世或共识参数的链无法重放彼此的交易。
20. **规范编码（防可塑性）**：签名、公钥、地址只接受小写 hex；否则同一签名可产生不同的交易哈希，绕过去重。握手签名同理，且握手字段禁止包含分隔符 `|`。
21. **交易/区块字段上限**：代码 ≤16 KB、参数 ≤16、函数名 ≤64、名称/符号长度受限；转账接收方必须是 40 位小写 hex 地址；矿工字段 1–64 字节，奖励与时间戳不能为负。
22. **VM**：加减乘检查溢出（溢出 → 交易失败，而不是静默回绕）；栈深 ≤1024；部署时检查 `ARG` 下标范围；合约存储 ≤1024 个键。
23. **无效区块记忆**：头合法但执行失败的区块按哈希标记无效，重复提交不会被重复执行（防 CPU 型 DoS）；不持久化无效区块体。
24. **TLS**：用 `VerifyConnection`（对会话恢复同样生效）替代 `VerifyPeerCertificate`，并禁用会话票据；addrman 选择与出站轮换改用 ChaCha8 CSPRNG（crypto/rand 播种），不可预测。
25. **依赖与工具链**：升级到 Go 1.26.9（旧的 1.23.4 被 govulncheck 报告 43 个可达标准库漏洞）、`x/crypto` v0.58、`bbolt` v1.5、`x/term` v0.47。

## 性能

- **撤销日志**：状态修改经过日志记录，每个规范链区块保存撤销记录；重组只回退/前进分叉点之后的区块，不再从创世块重放。
- **索引化状态**：余额/nonce/合约/代币均为哈希表；区块索引按哈希、规范链按高度 O(1) 访问；内存池按账户计数索引；状态拷贝改为手写深拷贝（不再 JSON 往返）。
- **并行签名校验**：区块内交易签名用工作池（GOMAXPROCS 个 goroutine）并行验证。
- **签名缓存**：已验证签名按 `H(签名内容‖签名)` 缓存（上限 20 万），内存池里验过的交易打包时不再重验；内容一变键就变，篡改无法命中缓存。
- **headers-first + 并行区块体下载**（见上）。

基准（`go test -bench`，8 核 Xeon；历史数据见 `bench_before.txt` / `bench_after.txt`，第 5 阶段见 `bench_phase5.txt`，第 6 阶段见 `bench_phase6.txt`）：

| 基准 | 第 3 阶段 | 第 4 阶段 | 本阶段 |
|---|---|---|---|
| 校验 200 笔转账的区块（冷签名缓存） | 11.2 ms | 4.5 ms | 5.1 ms（新增规范编码/链 ID 检查） |
| 同上（交易已在内存池验过签名） | 11.2 ms | 1.8 ms | 2.9 ms |
| 10k 块链上 100 深度重组 | 199 ms | 0.27 ms | 0.34 ms |
| 从零同步 1000 块（链层，内存） | 449 ms | 3.6 ms | 3.8 ms |
| **重启：10k 块（每块 5 笔混合交易）** | 全量重放 | 全量重放 3.12 s | **bbolt 加载 + 完整性检查 0.44 s**（跳过检查 0.32 s） |
| **内存池重建（10 万账户、50 个代币 × 1000 持有人，执行 100 笔交易）** | — | 整份拷贝 13.9 ms | **覆盖层 0.77 ms** |
| 每块持久化提交（20 笔交易，含 fsync） | 整链 JSON 重写 O(链长) | 同左 | 1.07 ms |
| 端到端 mTLS 同步 1000 块（单源节点，本机） | — | 24 ms | 36 ms（BodyWorkers 4→2，多了节点探测） |
| **区块体下载 1000 块，模拟每响应 25 ms 网络延迟：1 个节点** | — | — | 289 ms |
| **同上：4 个节点并行** | — | — | **101 ms（2.9×）** |
| 同上，本机回环（无延迟）：1 个 / 4 个节点 | — | — | 34.6 ms / 23.3 ms |

第 6 阶段（`bench_phase6.txt`，同机）：校验 200 笔转账 11.1 ms（冷）/ 7.7 ms（热签名缓存；变慢主要来自手续费检查与状态根更新）；100 深度重组 4.1 ms（重组需从磁盘/LRU 取裁剪区块体并更新累加器）；同步 1000 块 28.9 ms；每块持久化提交 2.8 ms；内存池覆盖层重建 0.58 ms；重启 10k 块：加载 + 完整性检查（含状态根重算）0.72 s、跳过检查 0.49 s、旧式全量重放 5.7 s；`-verifystate` 2000 块 0.72 s；单个状态根元素 5.2 µs；10k 账户状态根重算 56 ms；代币名键 12.6 µs。

说明：本机回环几乎没有网络延迟，多节点收益主要体现在有延迟/带宽限制的网络上（用 `TestBodyDelay` 模拟）。

多节点示例：

```bash
./bin/minichain node -data d1 -addr 127.0.0.1:9101 -p2p 127.0.0.1:9201 -miner <地址>
./bin/minichain node -data d2 -addr 127.0.0.1:9102 -p2p 127.0.0.1:9202 -peers 127.0.0.1:9201
./bin/minichain node -data d3 -addr 127.0.0.1:9103 -p2p 127.0.0.1:9203 -peers 127.0.0.1:9202   # 自动发现 9201
MINICHAIN_NODE=http://127.0.0.1:9103 ./bin/minichain peers
./demo_p2p.sh       # 3 个节点 + 1 个后加入节点的完整演示
./demo_security.sh  # 恶意节点攻击 → 被拒绝/扣分/封禁，诚实节点持续收敛
```

- **钱包**：ed25519 密钥对；地址 = sha256(公钥) 前 20 字节（hex）。每笔交易带 nonce 并签名，防重放、防篡改。
- **原生币 OKT**：1 OKT = 10^8 最小单位。
- **供应上限 21,000,000 OKT**：区块奖励初始 50 OKT，每 `-halving` 个块（默认 210000）减半，类似比特币；理论总量约 20,999,999.9769 OKT。`BlockReward` 在接近上限时会截断奖励，区块校验阶段要求奖励严格等于计划值，且 `已发行 + 奖励 ≤ 上限`，否则拒绝该区块。
- **智能合约**：文本汇编，每行一条指令，`name:` 定义函数入口。指令：`PUSH n, ARG i, LOAD key, STORE key, ADD, SUB, MUL, DUP, POP, JMP label, JZ label, RET, STOP`。普通指令 1 gas，`LOAD` 5，`STORE` 20；超出 gas 上限则执行失败、存储回滚、交易被拒。合约存储为 `key → int64`。
- **代币**：`token_create`（name, symbol, supply）与 `token_transfer`。**名称去首尾空白、不区分大小写后必须全局唯一**，重复名称在交易校验时（进入内存池时以及区块校验时）被拒。`OKT` 作为名称或符号被保留，用户代币不能使用。

## 审计准备（未经审计）

本项目**没有经过任何第三方安全审计**。为审计所做的准备见 `AUDIT_PREP.md` 与 `THREAT_MODEL.md`，要点：

- `go vet`、`staticcheck`：0 个问题。`gosec`：核心包（chain / p2p / wallet / 节点）0 个未处理问题（有意的位转换、本地文件路径等以 `#nosec` 加理由标注）；剩余 21 条全部位于 CLI 与攻击演示工具（taint 型误报、演示代码未处理错误），逐条说明见 AUDIT_PREP.md。`govulncheck`：代码受影响的漏洞 0 个。
- 模糊测试 7 个目标（`./fuzz.sh`），每个 60 秒，未发现崩溃。
- 不变量测试：供应上限、无负余额、OKT 守恒、代币名唯一、重组后状态 = 从创世重放；kill -9 崩溃测试。
- 测试：77 个（70 个测试函数 + 7 个模糊目标的种子语料），`-race` 通过。
- 覆盖率（合并统计）：`chain` 81.4% → 90.9%，`p2p` 90.9%。

## 运行

需要 Go 1.26+（推荐 1.26.9；`go.mod` 要求 1.26，因为旧工具链存在已知标准库漏洞）。依赖已 vendor，离线可编译。

```bash
go test ./... -v            # 单元测试
./demo.sh                   # 完整演示
go build -o bin/minichain ./cmd/minichain
./bin/minichain node -data ./data -miner <地址> -blocktime 5s -difficulty 3
./bin/minichain node -data ./data -encryptkeys            # 节点私钥/令牌静态加密（或设置 MINICHAIN_KEY_PASSPHRASE）
./bin/minichain node -data ./data -reindex                # 完整性检查失败时：从库中区块完整重建
./fuzz.sh                                                 # 模糊测试（FUZZTIME=60s）
./bin/minichain wallet-new -out alice.json
./bin/minichain balance -addr <地址>
./bin/minichain transfer -wallet alice.json -to <地址> -amount 12.5
./bin/minichain deploy -wallet alice.json -code contracts/counter.asm
./bin/minichain call -wallet alice.json -contract <合约地址> -func incr -gas 1000
./bin/minichain contract -addr <合约地址>
./bin/minichain token-create -wallet alice.json -name AzaCoin -symbol AZA -supply 1000000
./bin/minichain token-transfer -wallet alice.json -name AzaCoin -to <地址> -amount 250
./bin/minichain token -name azacoin
./bin/minichain info
./bin/minichain node -network testnet -web 127.0.0.1:8650   # 内置 Web 钱包 / 浏览器（第 8 阶段）
./bin/minichain node -network testnet -proxy 127.0.0.1:9050 # 出站 P2P 经 Tor/SOCKS5
./bin/minichain light-verify -network devnet -kind balance -id <地址>  # 轻客户端证明验证
./demo_web.sh                                              # Web 演示 + 截图
```

CLI 环境变量：`MINICHAIN_NODE`（默认 http://127.0.0.1:8545）、`MINICHAIN_TOKEN` 或 `MINICHAIN_TOKEN_FILE`（默认 `data/api_token`）、`MINICHAIN_PASSPHRASE`（钱包口令；未设置时终端隐藏输入）、`MINICHAIN_KEY_PASSPHRASE`（节点密钥/令牌加密口令）、`MINICHAIN_CA_FILE`（API 使用自签名 HTTPS 时）。

本地 API（需令牌）：`POST /tx`、`GET /status`、`GET /info`、`GET /netinfo`、`GET /bans`、`GET /mine`、`GET /account?addr=`、`GET /contract?addr=`、`GET /token?name=`；第 8 阶段：`GET /fee?type=&amount=`、`GET /headers`、`GET /proof?kind=&id=`、`GET /priv/outputs`、`GET /priv/keyimageset?from=`、`GET /blocks`、`GET /block?h=|hash=`、`GET /txinfo?hash=`。

P2P（mTLS）：`GET /p2p/ping`、`POST /p2p/hello`、`GET /p2p/status`、`GET /p2p/peers`、`GET /p2p/headers?from=&limit=`、`POST /p2p/bodies`、`POST /p2p/tx`、`POST /p2p/stemtx`（Dandelion++ 茎）、`POST /p2p/block`。

基准：`go test ./chain -bench . -benchtime 10x`、`go test ./cmd/minichain -run x -bench .`。依赖 `golang.org/x/crypto`（scrypt）、`go.etcd.io/bbolt`、`golang.org/x/term`，已 vendor 到 `vendor/`。

## 第 6 阶段新增

- **代币名防伪**：NFKC → 大写 → UTS #39 同形骨架（Unicode 18.0 confusables 表，`tools/genconfusables.py` 生成）→ 去组合符 → 大小写折叠，结果作为唯一键；拒绝混合文字（Latin/Greek/Cyrillic 等不可混用）、零宽/不可见/控制/双向控制字符，长度 1–32 字符（≤128 字节）。符号仅限 ASCII `A-Z0-9`（1–10）。保留名 `OKT` 及其全部同形变体（西里尔 ОКТ、全角 ＯＫＴ、带零宽字符等）。
- **时间戳**：区块时间必须 > 前 11 块中位时间（MTP），且不超前本地时钟 2 小时；重定向用 MTP 计算窗口时长，抗时间扭曲。
- **难度**：比特币式紧凑目标 `bits`（规范编码、≤ PowLimit），每块按窗口（`-retarget` 块）平均出块时间重定向，单次调整限 4×；默认初始难度 16 比特（约 6.5 万次哈希/块），`-difficulty` 为前导零比特数。
- **手续费**：交易带签名的 `fee`；合约调用另付 `gas_price × gas_used`；共识最低费 `-minfee` / `-mingasprice`；手续费全部归矿工，不增发，供应上限不变；撤销日志、重组、持久化、不变量与模糊测试均覆盖。
- **内存池**：按费率（fee/字节，128 位交叉乘法比较）优先打包；满时驱逐最低费率交易。（第 8 阶段：手续费由共识唯一确定，RBF 已移除，`-minrelayfee` 默认 0，见下文。）
- **状态根**：区块头含 `state_root`，执行后校验；打开时重算比对；`-verifystate` 在内存中重放全链比对每块状态根与最终状态。（第 8 阶段起为稀疏 Merkle 树，见下文；第 6–7 阶段为加法累加器。）
- **检查点与最终性**：`-checkpoints h:hash,...`；`-finality N`（默认 2000）以下的分叉块/重组一律拒绝。
- **区块体裁剪**：内存仅保留最近 `-keepbodies` 块的完整区块体，更旧的从 bbolt 按需加载（LRU 256）。
- **多节点头链**：`SyncBest` 向所有节点询问声明工作量，从最多工作量者同步，并交叉检查其余节点（一致/落后/分歧计数，分歧多时告警）。
- **提交失败恢复**：存储提交失败后下次加块自动 `Recover()` 从磁盘重新加载，无需重启。
- **对等多样性**：`-asmap` 文件（`CIDR ASN`）启用按 ASN 分组，未提供时回退到 /16（IPv6 /32）。
- **减少 TOFU**：`-strictpin` 只连接已钉扎 ID 的节点（种子写成 `id@host:port`）。
- **秘密**：口令以 `[]byte` 处理并在使用后清零（派生密钥、明文私钥同样）；支持 `MINICHAIN_PASSPHRASE_FD` / `_FILE`，环境变量方式已弃用并告警。

## 第 7 阶段新增

### Part B：Monero 式隐私（**未经审计，最高优先级审计范围**）

基于 `filippo.io/edwards25519`（已 vendor），本项目**没有自写曲线运算**，但协议层（哈希到点、CLSAG、Bulletproofs、交易平衡）是自行实现的，**未经审计，不得用于真实资产**。

- **隐形地址**：查看密钥 (a, A)、花费密钥 (b, B)，地址 `oktx…`（A‖B + 4 字节校验）。每个输出一次性公钥 P = Hs(8·r·A, i)·G + B，带 1 字节 view tag 加速扫描；金额用共享秘密派生的掩码异或加密。
- **Pedersen 承诺**：C = x·G + a·H，H 由哈希到点生成（无已知离散对数）。
- **CLSAG 环签名 + 密钥镜像**：I = x·Hp(P)；环大小由共识参数决定（mainnet 16，devnet 4，上限 128）；I 与 D 做素数阶子群检查（防小阶点混淆）；已用密钥镜像集合是共识状态（拒绝双花，重组可撤销）。
- **Bulletproofs 范围证明**：64 位、聚合（最多 16 个输出），单次多标量乘法验证，区块内批量验证（随机权重）。（第 8 阶段已替换为 Bulletproofs+，见下文。）
- **诱饵选择**：Monero 式 gamma 分布（形状 19.28、尺度 1/1.61，作用于输出年龄的对数秒），ChaCha8（由 crypto/rand 播种）。
- **交易类型**：`shield`（透明 OKT → 隐私输出，金额公开，接收方隐藏）、`private`（环签名输入 → 隐私输出 + 可选 unshield 到透明地址，手续费公开）。新输出锁定 10 块才可作为真实输入或诱饵。
- **总量守恒**：Σ输入伪承诺 = Σ输出承诺 + (fee + unshield)·H；屏蔽池 Pool = Σshield − Σunshield − Σ隐私手续费，必须 ≥ 0；每块检查 Σ透明余额 + Pool = 已发行 ≤ 2100 万。
- **钱包**：`priv-new / priv-address / priv-balance / shield / priv-send / unshield`；用查看密钥在本地扫描（第 7 阶段钱包逐个查询密钥镜像会泄露给节点；第 8 阶段改为下载完整集合本地比对）。
- 演示：`./demo_privacy.sh`。

### Part A：遗留局限的缓解

- **性能**：pprof 定位后，累加器更新改为延迟、去重、并行批处理（`accbatch.go`），元素展开由 ChaCha20 改为 AES-CTR，撤销快照不再逐次 hex 编码。验证 200 笔交易 11.4→6.4 ms（热缓存 8.1→3.4），同步 1000 块 30.3→19.8 ms，重组 4.1→2.6 ms，挖 200 笔 7.5→2.9 ms，-verifystate 2000 块 722→423 ms（同机 `bench_phase6.txt` / `bench_phase7.txt`）（仍慢于第 5 阶段，见局限）。
- **网络配置**：内置 `-network mainnet`（默认；初始难度 30 比特、目标 120 s、窗口 720、环 16、严格钉扎开启）与 `-network devnet`（16 比特、5 s、环 4）。显式命令行参数覆盖配置。
- **检查点进入共识参数**：配置文件中的检查点写入 `Config`，参与 ParamsHash / 链 ID；本地 `-checkpoints` 与之合并，冲突则拒绝。
- **ASN 示例表**：`config/asmap_sample.txt`（示意性前缀，**未与实时 BGP 表核对**）及加载说明。
- **严格钉扎默认开启**（mainnet），种子必须写成 `id@host:port`。
- **锁定内存**：钱包私钥放入 mmap + mlock + MADV_DONTDUMP + 保护页的内存，用后清零并 munmap；签名时仅临时在堆上重建（见下“发现的问题”）。
- **断电测试**：按 4 KiB 页构造断电镜像（旧/新/垃圾数据页、撕裂 meta、违反写入顺序），验证打开结果为提交前或提交后状态；新增 `<db>.tip` 回滚标记，检测“自洽但更旧”的数据库（`-acceptrollback` 可显式接受）。
- **带宽 DoS**：每节点字节令牌桶（4 MB/s，突发 16 MB）、每节点并发请求上限 8（背压，超出返回 busy）、紧凑区块中继（8 字节短 ID，缺失交易回退到整块）。

### 第 7 阶段发现并修复的问题

- `writeDirty` 去重键漏了操作类型，导致同块多个隐私输出只落盘一个（重启后状态不一致）。
- bbolt 在 freelist 损坏时 panic → `openDBSafe` 转为 `ErrCorrupt`。
- 违反写入顺序时可能静默回滚到更旧状态甚至创世 → 回滚标记；随后断电测试又发现 meta 丢失时库被当作“全新库”而绕过标记检查，已修复。
- 锁定内存直接作为 `ed25519.PrivateKey` 使用会让 Go 运行时崩溃（`crypto/ed25519` 对密钥切片建立弱引用，非 Go 堆内存非法）→ 签名时临时复制到堆上并立即清零。
- 握手：对方在 ping 与 hello 之间进入负载状态时，诚实节点连接失败（race 测试中偶发）→ 重新 ping 一次。
- 测试缺陷：`TestSecretSources` 的 `os.File` 终结器重复关闭被复用的 fd，导致其他测试偶发 “bad file descriptor”。

## 第 8 阶段新增（**仍未经审计**）

### A. 完成上阶段的部分项

- **Bulletproofs+**（`privacy/bulletproofplus.go`，替换 Bulletproofs）：加权内积论证（WIP），证明 = A, A1, B, r1, s1, d1, L[], R[]，大小 32·(6 + 2·log2(64m)) 字节（m=2：736 → 640 B）。单次多标量乘法验证，区块内批量验证（随机权重）；转录域分隔 `okt-bpp-*`；所有挑战做非零检查。篡改测试覆盖每个字段、L/R 交换、截断、承诺重排/子集、批内交换证明。**未与 Monero 测试向量交叉验证**（格式不同），这是审计重点。
- **稀疏 Merkle 状态树**（`chain/smt.go`、`accumulator.go`）：压缩 SMT（单叶子子树折叠，形状规范）；叶 = sha256(0x00‖key‖sha256(value))，内部节点 = sha256(0x01‖l‖r)，空 = 32 字节零；key = sha256("okt-smt-key-v1"‖类型‖标识)。节点不可变（路径复制），批量有序更新，顶层并行。撤销记录只存旧根哈希，撤销后标记为过期并在下次刷新时重算。每个键支持**包含证明与不存在证明**（`ProveEntry` / `VerifySMTProof`）。
- **轻客户端**：API `/headers`、`/proof?kind=balance|nonce|token_balance|contract_slot|key_image|minted|pool&id=…`；`minichain light-verify -network devnet -kind balance -id <地址>` 下载全部区块头，用该网络的共识参数验证 PoW/难度/链接，再用对应区块头的状态根验证证明——不需要信任节点给出的值。测试：篡改值、篡改区块头、错误网络均被拒绝。
- **故障注入文件系统**（`internal/faultfs`，FUSE，独立进程 `faultfsd`，Linux）：区分已持久化/未持久化写；在第 N 次写入时“断电”；未持久化写随机丢弃/全部保留/按 512 B 扇区撕裂；`lie` 模式（fsync 谎报成功）、`drop` 模式（丢弃 fsync）。测试 `TestFaultFSPowerLoss` 对 bbolt 链库多次随机断电：诚实模式 10/10 一致；drop-fsync 8–9 一致 + 1–2 被检测到（不同运行随机不同）；lie-fsync 10/10 被检测到（打开时拒绝或回滚标记报警）；**0 次静默回滚到更旧状态**。简化：元数据操作与文件大小变化立即持久。
- **锁定内存扩展到所有密钥**：隐私查看/花费私钥标量所在堆页 mlock + MADV_DONTDUMP（`internal/memlock`，Go GC 不移动堆对象），解密缓冲用后清零；节点身份/TLS 私钥（同一把 ed25519）同样锁页，密钥文件缓冲清零（改用可清零的 `json.RawMessage`），SIGINT/SIGTERM 时关闭数据库并清零；API TLS 私钥 PEM 缓冲解析后清零。局限见下。
- **主网配置骨架**：`profiles/{devnet,testnet,mainnet}.json` + `profiles/README.md`：种子格式 `nodeid@host:port`（`-seedfile` 每行一个），检查点进入共识参数。**真实种子需要真实、独立运营的基础设施，本仓库没有。** 主网的基金会/市场地址为空，节点拒绝以空地址或 TEST-ONLY 地址启动主网。

### 共识手续费规则（替换第 6 阶段的“最低费 + 费率市场”）

全部为基本单位整数运算，`pct(x) = ⌈x/100⌉`（x>0 时至少 1），`base = min_fee`（各配置为 1000 = 0.00001 OKT）。交易的 `fee` 字段必须**恰好等于**规定值（因此不再有 RBF；内存池仍按费率排序、满时驱逐，保留反垃圾与最低费检查）。

| 交易类型 | 手续费 |
|---|---|
| transfer、shield | max(base, pct(金额)) |
| token_transfer | OKT 手续费 base；另收 pct(数量) 个**该代币**（在金额之外扣除，每块按同样比例分配） |
| token_create、deploy | base |
| call | base + gas_price × 实际 gas（合约不能转移 OKT，无 1% 部分） |
| private（隐私→隐私） | private_flat_fee（默认 100000 = 0.001 OKT） |
| private + unshield | private_flat_fee + pct(unshield 金额) |

- 隐私→隐私为固定费：金额被承诺隐藏，范围证明无法强制 1%（发送方可以把任意输出标为“找零”）。1% 只在透明边界（shield / unshield）生效——这是有意的取舍。
- **每块分配**：区块全部手续费 F → 基金会 ⌊F·30/100⌋，市场 ⌊F·20/100⌋，矿工得其余（含取整余数）。地址写在共识参数 `foundation_addr` / `marketing_addr`（参与链 ID）；空地址时份额归矿工（仅测试配置）。代币手续费按代币分别分配（按名称排序）。
- **不增发**：手续费只在地址间转移；每块检查 Σ余额 + 屏蔽池 = 已发行 ≤ 2100 万；撤销/重组/持久化/不变量/模糊测试均覆盖分配。
- **测试钱包**（TEST ONLY，`testnet_keys/`）：基金会 `c9e8b6a028170c1a32cb266abbd1f30197d63496`，市场 `47359592485a3e856d51e6fd56a05856c2fa9b41`，已写入 devnet/testnet 配置。主网替换方法见 `testnet_keys/README.md` 与 `profiles/README.md`。
- 预览：API `/fee?type=…&amount=…`；CLI `send` 打印手续费与分配；Web 钱包实时预览。

### B. 隐私与网络缓解

- **shield/unshield 关联**：共识参数 `priv_denoms`（devnet/testnet/mainnet：0.1、1、10、100、1000、10000 OKT）——shield 金额与 unshield 金额必须是某个面额；CLI 与 Web 钱包自动把 shield 拆成多笔面额交易；unshield 每次一个面额，并打印/显示关联风险警告；隐私→隐私仍是默认且不受面额限制。单元测试配置不启用面额。**局限**：面额只削弱金额关联，不能隐藏时间关联；拆分出的多笔交易本身可能被聚类。
- **私密的密钥镜像检查**：移除逐个查询接口 `/priv/keyimages`；钱包下载 `/priv/keyimageset?from=H`（已花费集合按链顺序 + 内存池/本节点茎池中的待定集合）并在本地比对，节点无法得知钱包关心哪些镜像。代价：带宽 O(全部隐私花费)（测试网可接受；可改为按高度增量缓存）。
- **Dandelion++**（`cmd/minichain/dandelion.go`）：本地交易先进入茎阶段（不进入公开内存池），沿每个纪元（10 分钟）固定选择的出站茎中继转发；每纪元节点以 10% 概率成为“扩散者”；茎上收到的交易若中继是来源则扩散；禁运计时器 15–30 s 后若交易仍未公开则本节点自行扩散；本节点茎池参与 `pending_nonce` 计算（连续发送不冲突）。新端点 `/p2p/stemtx`。测试：链式拓扑茎→扩散、黑洞中继触发禁运、连续 nonce。**局限**：只有一个出边（未实现论文中的 4-regular 伪随机图）；第一个茎中继知道来源 IP；对控制大量节点的攻击者保护有限。
- **Tor / SOCKS5**：`-proxy 127.0.0.1:9050`：所有出站 P2P 连接经 SOCKS5（RFC 1928，无认证 CONNECT），目标始终以域名（ATYP 3）发送、本地不做 DNS 解析（防 DNS 泄露）。入站连接不会被匿名化。
- **按 IP / 子网的带宽预算**：在每节点 ID 预算之上，增加每 IP（8 MB/s）与每 /24、/48 子网（16 MB/s）的字节令牌桶（突发 4×），使同一来源的多个女巫身份共享预算。
- **连接 PoW 随负载提高**：握手难题位数 = 基础 + 最多 8 位，随近期 hello 速率与待握手连接数上升；每个难题记录发出时的位数。

### C. 节点内置 Web 钱包与区块浏览器（中文）

`minichain node … -web 127.0.0.1:8650` 后打开 `http://127.0.0.1:8650/`。静态文件编译进二进制（`cmd/minichain/web/`，原生 JS，无外部依赖）。

- **本地代理**：浏览器访问 `/api/*`，Web 服务在进程内转发到带令牌认证的 API 并注入令牌（令牌不进入浏览器）；只暴露只读白名单 + 测试模式下的手动挖矿。
- **钱包服务**：钱包文件（透明 + 隐私）保存在 `<data>/webwallets/`（scrypt + AES-GCM，与 CLI 钱包格式相同）；解锁后私钥在 mlock 内存，签名在节点进程内完成；会话 cookie HttpOnly + SameSite=Strict，空闲 15 分钟自动锁定。
- **安全**：只能绑定回环；校验 Host（防 DNS 重绑定）；非 GET 请求必须为 `application/json` 且同源（防 CSRF）；严格 CSP（禁止内联脚本/样式）；所有动态内容转义。
- **功能**：概览（高度、节点数、难度、发行量对比 2100 万上限、手续费规则）；区块/交易/地址浏览与搜索；钱包创建/解锁、透明与隐私余额、隐私输出列表、转账、隐私转账、屏蔽/解除屏蔽（面额拆分 + 风险提示）、实时手续费预览与 50/30/20 分配；代币发行（名称唯一性实时检查，含待定交易）与代币转账；合约部署与调用、查看存储。
- 演示与截图：`./demo_web.sh`（无头 Chromium，1280×800，输出到 `screenshots/`，脚本 `tools/shot.js`）。

### 第 8 阶段性能（同机，`bench_phase7.txt` → `bench_phase8.txt`）

| 基准 | 第 7 阶段 | 第 8 阶段 |
|---|---|---|
| 验证 200 笔转账（冷 / 热签名缓存） | 6.37 / 3.35 ms | 5.92 / 3.11 ms |
| 100 深度重组（10k 块） | 2.65 ms | 1.92 ms |
| 同步 1000 块 | 19.8 ms | 15.5 ms |
| 重启 10k 块（含完整性检查） | 706 ms | 576 ms |
| 10k 账户状态根重算 | 28.4 ms | 17.5 ms |
| 单个状态写入 + 刷新 | 2.8 µs | 5.7 µs（SMT 路径哈希，慢于累加器） |
| `-verifystate` 2000 块 | 423 ms | 350 ms |
| 挖 200 笔 | 2.85 ms | 3.00 ms |
| 范围证明 m=2：生成 / 验证 / 大小 | 28.5 / 4.22 ms / 736 B | 19.8 / 4.26 ms / 640 B |
| 16 个证明：逐个 / 批量 | 68.7 / 24.5 ms | 66.0 / 23.9 ms |
| 隐私交易 2 入 2 出：构造 / 验证 | 50.4 / 12.2 ms | 45.0 / 11.4 ms |
| 内存池排序 2000 笔 | 16.4 ms | 22.1 ms（手续费由规则决定后费率相同更常见，确定性比较需预计算哈希） |
| headers-first 同步 1000 | 72.7 ms | 53.6 ms |

### 第 8 阶段发现并修复的问题

- FUSE 文件系统与 bbolt 在同一进程时死锁（mmap 缺页的线程阻塞 GC 的 stop-the-world）→ faultfs 改为独立进程。
- 模拟断电后读返回 EIO 导致 bbolt mmap SIGBUS → 断电后允许读。
- 内核 dentry 缓存跨试验保留旧文件 → 每次试验使用唯一数据库名。
- CLI `send` 打印 “OKT OKT”。
- 内存池排序：费率相同时堆比较反复计算 `Tx.Hash()`（JSON 编码），2000 笔 100 ms → 预计算哈希后 22 ms。
- `/tx` 改走 Dandelion 后丢失了部署交易的 `contract` 字段，`demo.sh` 的合约调用因此失败 → 已恢复。
- 基准与 `demo.sh` 仍使用旧的自定费/RBF 流程 → 改为共识规定手续费。
- Dandelion：扩散时把来源排除在广播之外，导致茎上游节点永远收不到公开交易（只能等禁运计时器）→ 扩散时向所有邻居广播。
- 面额拆分的 shield 在第一笔后私钥已被销毁（`send` 返回时销毁受保护内存）→ 每笔重新加载钱包。
- 已知未清理：faultfs 测试残留的 FUSE 挂载点（容器中缺 `/etc/mtab`，`fusermount3 -u` 失败，已通过 `/sys/fs/fuse/connections/*/abort` 中止，无害）。
- 首次运行时 `FuzzSMTProof` 的“失败”实为运行期间修改源码导致的 build failed，并非模糊测试发现；`chain -race` 偶发 panic 已定位并修复（bbolt 打开断电损坏镜像时越界读 → 致命 fault；见 audit/flaky_investigation.md）。

## 局限（原型阶段）

- **隐私密码学未经审计**（协议层自行实现，含 Bulletproofs+）；无多签/视图密钥分级；交易图分析（时间关联、面额聚类）仍可能去匿名化。
- 密钥锁页是尽力而为：只保护“当前这份”副本；编译器/库产生的临时副本（栈、寄存器、`crypto/tls` 握手状态、解析后的 API TLS 私钥对象）无法控制；mlock 受 RLIMIT_MEMLOCK 限制；kill -9 / 崩溃时无法清零。
- 轻客户端需要下载全部区块头（无 FlyClient/NiPoPoW 之类的压缩）。
- mainnet 配置没有真实种子节点，唯一检查点是创世块；默认难度 30 比特在无真实算力时仍不安全。
- 同步/重组性能仍慢于第 5 阶段（状态根的代价）。
- 故障注入文件系统是 FUSE 模型：元数据与文件大小变化立即持久，不模拟设备级重排序之外的介质错误。
- **未经审计**；共识与密码学代码未做形式化验证。
- 同形骨架基于 UTS #39 表，覆盖不到所有视觉相似（如字体相关的相似）；允许的单文字内部仍可能存在相似字符组合。
- 检查点与最终性是本地策略，不同配置的节点可能分裂；最终性深度内仍可重组。
- 内置 ASN 表仅为示意；生产需自行从 BGP 数据生成。devnet 关闭严格钉扎，首次连接为 TOFU。
- Go 无法保证内存清零（GC 可能已复制；字符串形式的 hex 中间值、TLS 库内部的私钥不可清零）；运行中节点私钥仍在内存中。
- 带宽限制按 ID + IP + 子网计；拥有大量不同子网地址（如僵尸网络、云厂商）的攻击者仍可消耗带宽。
- 合约语言非常简单（仅 int64、无合约间调用、无事件，合约不能持有 OKT）。
