# OMNX Chain · 隐私账本任务清单

> 建立于 2026-09-24。**每一步代码改动前，先提交「改动范围 + 回滚方案」给项目方确认，确认后才动手。**
>
> ⚠️ **全局红线**：全部任务完成且回归通过之前，**不得导入任何真实业务数据**。
> ⚠️ 本次仍为**测试网**，主网闸门保持关闭；对外不得宣称 BFT 共识。

---

## 一、任务总览

| # | 任务 | 状态 | 前置 | 可否并行 |
|---|---|---|---|---|
| 1 | note 模型接入 `chain.py` / `rpc.py` | ✅ **已交付** | 无 | — |
| 2 | Merkle 树 / nullifier 集合持久化 | ✅ **已交付**（按项目方指示与 T1 合并为一次改动） | **T1**（状态容器先定形） | — |
| 3 | 透明账本 ↔ 隐私账本挂接打通 | ✅ **已交付**（T3，2026-09-25） | **T1** | 与 T2 并行 |
| 4 | 环签名 / zk-SNARK 方案，解决发送方关联 | 待开发 | 无（与 T1 有接口耦合） | 与 T2/T3 并行 |
| 5 | 第 4 期业务：0.1% 底座费 / 70-30 分账 / 质押折扣 / 回购销毁 | 待开发 | 无 | 与 T2/T3/T4 并行 |
| 6 | 范围证明体积与链上成本评估 + 压缩优化 | 待开发 | 无 | 与 T2~T5 并行 |
| 7 | 全量回归测试 | 最后 | T1~T6 | — |

### 已完成（本次之前）

| 期 | 内容 | 交付物 | 自检 |
|---|---|---|---|
| 0 | 密码学底座 | `node/privacy.py` | `tests/test_privacy.py` 39/39 |
| 2 | 金额范围证明（防通胀） | `node/rangeproof.py` | `tests/test_rangeproof.py` 59/59 |
| 1+3 | note 账本引擎（离线） | `node/notes.py` | `tests/test_notes.py` 101/101 |

> 注意：第 1+3 期只完成了**离屏引擎**，一行都还没接进链上。
> 上表 T1~T7 就是把它真正落到链上的过程。

---

## 二、逐项说明

### T1 · note 模型接入 `chain.py` / `rpc.py`　｜　✅ 已交付（2026-09-24）

**目标**：让隐私引擎成为链的一部分 —— 能通过标准交易提交，状态随链保存，RPC 可读写。

**当时明确不做**（留给 T3）：`shield` **不扣** `chain.balances`，`unshield` **不加**目标地址余额。
T1 结束后，隐私层内部的账是自洽的，但它与透明账本之间**还没有资金通道**。

> ⚠️ 上面这条是 T1 交付时的**历史口径**。T3（2026-09-25）已把资金通道打通：
> 现在 `shield` 会扣发起方透明余额、`unshield` 会给目标地址入账，
> 是否启用由创世 `privacy.bridge_enabled` 决定（关闭 = 退回本节口径）。
> 以 `docs/隐私资金通道设计.md` 为准。

**改动范围**

| 文件 | 改动 |
|---|---|
| `node/modules_privacy.py` | **新增**。照 `modules_rwadex.py` 的既有范式写：`PrivacyLayer.bootstrap(chain)` + `PrivacyRouter.route(chain, sender, tx)` |
| `node/chain.py` | **3 处插入，全部是增量，0 删除，共 +14 行**：`__init__` 末尾 bootstrap；`_finalize_block` 里每块 `flush()`（鸭子类型判断，避免每块走 import）；`_apply_tx()` 加 `elif mod == "privacy"`。**`snapshot()` / `_load()` 一行未动** —— 隐私状态不进 `chain.json`，见 T2 |
| `node/rpc.py` | 追加只读方法（`exx_privacyInfo` 等）与提交方法（`exx_shield` / `exx_shieldedTransfer` / `exx_unshield`），沿用现有 `submit()` 路径 |
| `tests/test_privacy_chain.py` | **新增**。链上接入专项自检 |

**设计约束（重要）**

1. **状态容器先在 T1 定形**：所有隐私状态放在 `chain.privacy` 这个字典里，
   由 T2 决定它是落在 `chain.json` 还是独立文件。这样 T2 不需要再动 `chain.py`。
2. **交易信封预留 `"v": 1`**：T4（环签名/zk）与 T6（Bulletproofs）都可能改证明格式，
   有版本字段才能在不断链的情况下平滑升级。
3. **导入必须惰性**：`notes.py` 导入时会算 hash-to-curve 生成元，
   不能在链启动路径上无条件执行（避免拖慢节点启动）。
4. **零耦合原有模块**：不改 `modules.py` / `modules_dex.py` / `modules_rwa.py` /
   `modules_platform.py` / `modules_rwadex.py` / `evm_layer.py` 任何一行。

**回滚方案**

| 步骤 | 动作 |
|---|---|
| 改动前 | 备份 `node/chain.py`、`node/rpc.py`、`node/data/chain.json`，命名沿用现有约定 `.bak-YYYYMMDD-HHMMSS` |
| 回滚 | 还原两个 `.py` 备份 + 删除 `node/modules_privacy.py` 即可 |
| `chain.json` | **无需回滚**。新增的 `privacy` 键对旧代码是惰性的：旧 `snapshot()` 不会写它，旧 `_load()` 不会读它，旧节点照常启动 |
| 验证回滚 | 还原后跑 `run-local-gate.sh`，应回到改动前的通过数 |

**验收结果（实测）**

- [x] `tests/test_privacy_chain.py` 97/97 通过
- [x] 隐私交易与普通转账**同区块混合打包**通过（注意：同一发送方在同块内两笔会撞 nonce，测试用两个发送方）
- [x] 重启后状态不丢 —— 由 T2 的独立存储提供（`privacy.json` / `privacy.log`）
- [x] 现有 439 项离线自检**一项不回退**，合计 536/536
- [x] `chain.py` 增量插入 3 处、0 删除；`rpc.py` 0 删除、+104 行

---

### T2 · Merkle 树 / nullifier 集合持久化　｜　✅ 已交付（2026-09-24，与 T1 合并）

**目标**：解决重启数据丢失，并避免 `chain.json` 被 Merkle 叶子撑爆。

**为什么不能只靠 `chain.json`**：当时 `chain.json` 194 KB，其中 `blocks` 占 161 KB；
每条 note 叶子约 70 字节，1 万笔就是 700 KB，而且 `_save()` 每个区块重写整份文件。
量级一上来，保存延迟与损坏风险都会被放大。

**最终方案：快照 + 追加日志（不放进 `chain.json`）**

| 文件 | 作用 | 写时机 |
|---|---|---|
| `node/data/privacy.json` | 全量快照（原子替换写：`tmp + os.replace`） | 只在压实或关闭时写 |
| `node/data/privacy.log` | 追加日志，每含隐私交易的区块追加一条 | O(1) 追加，**无隐私交易时零写入** |

每条日志带 `seq` 与哈希链 `prev` / `hash`：

```
hash(n) = SHA3(canonical(记录去掉 hash 字段))
prev(n) = hash(n-1)
```

- 快照记录 `seq` 与 `journal_head`，装载时**跳过 `seq ≤ 快照 seq` 的记录** → 压实后不重复回放
- 日志起点由**快照 head** 决定（不是永远假定全零）→ 压实后继续追加不会误判断链
- 尾部半行（崩溃常见情形）按正常情况忽略，并**从磁盘截掉**，避免污染下一条记录
- 中间哈希链断裂 / 内容被篡改 → 判为损坏，**隐私子系统降级**：拒绝新的隐私交易，但公链照常出块

**实测体积基线**

| 记录 | 字节 |
|---|---|
| 单输出 `shield` 的 journal 记录 | 1176 |
| 1 入 2 出 `transfer` 的 journal 记录 | 2078 |
| 纯转账区块（无隐私交易） | 0（零写入） |

**回滚方案（原计划与实际）**：原计划做 `state_backend = "chain_json" | "dedicated"` 开关，
实测后**不需要** —— 因为隐私状态从来没写进 `chain.json`，旧代码读不到也写不出它，
回滚只需还原 `chain.py` / `rpc.py` 备份并删除 `modules_privacy.py`；
残留的 `privacy.json` / `privacy.log` 是惰性文件，留着不影响任何旧逻辑。

**验收结果（实测）**

- [x] 重启后 Merkle 根 / note 数 / nullifier 数 / journal head 全部一致
- [x] 压实（compact）后重新装载不重复回放，新记录从快照 head 续接
- [x] `chain.json` 里**不出现** `privacy` 顶层键、不出现 `_sk`、不出现 note 明文金额与盲因子
      （同时做了正向对照：`chain.json` 里确实有该 note 的 `cm`，证明交易真的进块了）
- [x] 快照损坏 / 日志篡改 → 降级且公链照常出块

---

---

### T3 · 透明账本 ↔ 隐私账本挂接打通　｜　✅ 已交付（2026-09-25）

**目标**：`shield` 真正从 `chain.balances` 扣减，`unshield` 真正给目标地址入账。

**实际口径**（详见 `docs/隐私资金通道设计.md`）

| 操作 | 发起方 | 托管 `0x…0010` | 手续费池 |
|---|---|---|---|
| `shield` | `-(V + gas)` | `+(V - F)` | `+F` |
| `transfer` | `-gas` | `-F` | `+F` |
| `unshield` | `-gas` | `-(V + F)` | `+F`；目标地址 `+V` |

- 不变量：`balances[托管地址] ≡ 未花费 note 面值之和` ⇒ `total_supply()` 仍恒为 10 亿
- 托管地址 `0x…0010`（`sys_addr(10)`），无私钥，与既有地址段全部错开
- 手续费 `fee_value` 与 Gas 同口径进 `fees_pool`

**改动范围**

| 文件 | 改动 |
|---|---|
| `node/modules_privacy.py` | **新增**通道常量 / `bridge_config` / 规划·动账·补偿 / `_pool_mark`·`_pool_undo`；`route()` 改为「规划 → 校验 → 动账 → 落账」；只读 `bridge_info()`；`compact` / `flush` / `_load` 记录通道口径 |
| `node/chain.py` | **仅 1 行**：`load_genesis_config` 白名单加 `privacy`（否则创世里那段会被过滤掉） |
| `node/rpc.py` | 新增只读 `exx_privacyEscrow`；`exx_shield` 加余额预检；`unshield` 地址归一与校验；提交回执加 `planned` / `bridge` |
| `node/notes.py` | **零改动**（保持「调用方负责扣透明账本」的零耦合设计） |
| `deploy/genesis.json` | 新增 `privacy` 段（`bridge_enabled` / `escrow_address` / `fee_sink`） |
| `tests/test_privacy_bridge.py` | **新增**，145 项 |
| `tests/test_privacy_chain.py` | **+2 行**：显式钉死 `bridge_enabled=false`（保住 T1/T2 语义） |
| `node/explorer.html` + 离线版浏览器 | 供应卡片追加「隐私池托管余额」只读一行（旧节点无此接口时静默跳过） |
| `deploy/aws/run-local-gate.sh` | 自检脚本 9 → **10 个**，基线 587 → **732 项** |

**关键要求（全部满足）**

- 扣减/入账与隐私交易在**同一个原子步骤**内完成（规划→校验→动账→落账；失败逐条冲回 +
  Merkle 增量按长度整体回退）—— 自检里用注入故障真实验证「零半执行状态」
- `unshield` 提现地址做格式校验（`0x` + 40 位 hex）并统一归一为小写
- 守恒不变量写成断言：托管余额 == 累计口径 == 日志 `pub` 汇总（三处独立交叉对账）
- 防凭空造币闸门：池内已有「通道开启前产出」的 note 时，开通道的第一笔交易被拒

**实测缺陷（自检抓到并已修）**：关闭态区块也会写全零 `pub` 记账，重放时被误判成
「已有通道交易」，从而绕过防凭空造币闸门 → 改为「只有真发生通道动账才写 `pub`」。

**回滚方案**：创世里 `bridge_enabled` 改回 `false` 重启即退回 T1/T2（**不改代码**）；
代码级回滚用 `.bak-<时间戳>` 备份；状态级回滚用 `scripts/omnx-snapshot.py import`。
⚠️ 已铸出的 note 无法撤销；关闸后 note 仍在（可查、可私转），再 `unshield` 需重新开闸。

---

### T4 · 环签名 / zk-SNARK，解决发送方关联

**目标**：花费时不暴露 `owner_pk`，切断「同一发送方多笔交易可被关联」。

**现状与代价**：当前 `owner_pk` 在花费时公开（note 密钥与账户地址解耦能缓解，
但不等于匿名）。设计文档已把「用签名代替 zk 所有权证明」记为**阶段性妥协**。

**方案选项**（动手前需要先定方案）

| 方案 | 说明 | 取舍 |
|---|---|---|
| 环签名（如 MLSAG / CLSAG 思路） | 纯 Python 可实现，无需可信设置 | 体积随环大小线性增长 |
| zk-SNARK（Groth16） | 证明小、验证快 | 需要可信设置，本机无现成工具链，实现量大 |
| 二选一后置 | 先补环签名，zk 作为后续增强 | 稳妥 |

⚠️ 这一项工作量最大，且**可能需要引入新的第三方依赖**（本机无外网直连，
vendor 目录内现有 `pycryptodome` / `eth_keys` / `ckzg` / `bitarray` 是否够用需先评估）。

**回滚方案**：证明方案用 `v` 字段区分版本，旧交易格式继续可验；新方案作为可选升级。

---

### T5 · 第 4 期业务逻辑

**范围**：0.1% 底座费、70/30 手续费分账、质押折扣、回购销毁。

**前置结论（来自设计文档第四节）**：这几项在隐私版下的口径是

- 手续费：单笔不可见，**按区块聚合值**执行 70/30 分账
- 质押折扣：用范围证明表达「≥ 某档」，不暴露余额
- 回购销毁：总量与 txhash 公开，资金来源走聚合值

**改动范围**：需先确认是接入 `modules_platform.py` / `modules_rwadex.py`
还是新建 `modules_privacy_fee.py`。

⚠️ **注意**：这几个模块是**已交付并正在运行**的业务模块，
动它们与「只新增不修改」的原则冲突，**必须先单独确认**。

---

### T6 · 范围证明体积与链上成本评估 + 压缩

**现状**：64 位证明 **10,466 字节**，生成约 278 ms，校验约 411 ms。
对比 Bulletproofs 同强度约 674 字节。

**改动范围**：先出**成本评估报告**（带宽 / 存储 / 区块大小 / 手续费影响），
再决定是否上 Bulletproofs。评估报告属文档，不改运行代码。

**回滚方案**：压缩方案同样用 `v` 字段区分，新旧证明并存可验。

---

### T7 · 全量回归

- `run-local-gate.sh` 全绿（离线自检已从 439 项增至 **536 项**；链上回归保持 7/19/42/36）
- 新增 T1~T6 各项专项自检
- 输出验收报告，交由项目方核对

---

## 三、全局规则

1. **每步先确认再动手**：提交「改动范围 + 回滚方案」，等明确确认。
2. **不导入真实业务数据**：T1~T7 全部达标并通过回归之前，绝不上真实数据。
3. **不回退已有功能**：K 线拖动/缩放/全屏、指标设置、IP 自动语言、邀请码溯源、
   子链隔离、手续费分账、DAO 治理控制台等已交付功能，一律不动。
4. **只新增、不修改**：能用新文件解决就不动老文件；确实必须动老文件时，
   改动必须是增量插入，且可一键回滚。
5. **主网闸门关闭**：未收到明确文字「可以上主网」不得执行主网部署。
6. **对外口径**：HTTP Gossip + 单验证者确定性轮转，**不具备拜占庭容错**，
   严禁描述为 BFT 共识。
7. **诚实边界**：做不到的、没做的、有缺口的一律写明，不粉饰。

---

## 四、风险登记

| 风险 | 影响 | 应对 |
|---|---|---|
| T1 锁死交易格式，T4/T6 被迫破坏性升级 | 断链、历史交易无法验证 | 交易信封预留 `v` 字段；证明本身也已带 `v` |
| T2 存储量与 `chain.json` 耦合 | 保存延迟上升、损坏风险放大 | 独立存储 + 追加式写入 + 启动自检 |
| T3 挂接破坏原子性 | 扣了透明账却没铸出 note，或反之 | 同一步内完成 + 守恒不变量断言 |
| T5 改动正在运行的业务模块 | 影响已交付功能 | 动手前单独确认；优先新建模块而非改老模块 |
| T4 需要新依赖但本机无外网 | 方案不可行 | 动手前先评估 vendor 现有库是否够用 |
| 隐私层上了但透明层仍明文 | 误以为「已经隐私了」 | 文档与对外口径始终写清「链上仍是明文」 |

---

## 五、当前链上事实（每次汇报都要如实带上）

- 链上交易**仍是完全明文**（`from` / `to` / `value`）
- 隐私账本已独立持久化到 `node/data/privacy.json` + `privacy.log`；但**没有任何隐私交易进入现行链上生产路径**，真实业务数据仍未导入
- **发送方关联性未解决**
- 手续费分账、质押折扣、回购销毁**尚未接入**隐私版口径
- 因此**金额可被反推**这个事实，在链上仍然成立

---

## 六、T1+T2 交付证据（2026-09-24）

### 改动文件与哈希

| 文件 | md5 | 说明 |
|---|---|---|
| `node/modules_privacy.py` | `bc15ba499d6745760bac48a0a57384ef` | 新增 |
| `node/notes.py` | `6c9eb011c029676df3e5284f3c7513c1` | 增量：持久化接口 |
| `node/chain.py` | `9f629575aee4bcfe51cd869d87094da9` | 3 处插入，0 删除，+14 行 |
| `node/rpc.py` | `93f5b7bf1d375b06d43e691b5f1ffcca` | 0 删除，+104 行 |
| `tests/test_privacy_chain.py` | `d1e03e60cd840d75f2078eccba1e267f` | 新增，97 项 |

回滚备份：`node/chain.py.bak-20260924-210847`（`63f6080366aa2f32c2a642aea87106ef`）、
`node/rpc.py.bak-20260924-210847`（`06bbe696d67725ea5e3ebbfb2254e37f`）。

### 上链跑通后发现并修掉的 3 个真实缺陷

改动的第一版**在纸面上是通的**，是专项自检把它们逼出来的 —— 三个都属于「不修就会静默丢数据/永久降级」的级别：

| # | 缺陷 | 不修的后果 | 修法 |
|---|---|---|---|
| A | `_load()` 没有把 `store.problems` 冒泡出来 | 快照文件损坏时 `ok` 仍是 `True`，账本被静默重置成空，**旧 note 全部丢失且无人察觉** | 快照读不出来即降级 |
| B | 尾部半行只在内存里忽略，没从磁盘截掉 | 崩溃后**下一笔隐私交易**会与半行拼成一行，该记录再也读不回来，且此后每次启动都因这一行解析失败而**永久降级** | 检测到半行即把文件截断到干净长度 |
| C | `read_journal()` 固定以全零作为哈希链起点 | 压实（compact）后日志从快照 head 续写，下次启动误判「哈希链断裂」→ **永久降级** | 起点改由快照 `head` 决定 |

三个缺陷都已写成回归用例，落在 `tests/test_privacy_chain.py` 第 7、8、9 节。

### 全局自检

`tests/` 8 个脚本 **536/536** 全绿：
`test_omnimx_dex` 88 + `test_notes` 101 + `test_platform` 36 + `test_privacy` 39 +
**`test_privacy_chain` 97** + `test_rangeproof` 59 + `test_testnet` 68 + `test_ws_subscribe` 48。

另外，把 `omnx-aws-deploy-new.tar.gz` 解包后按 `install.sh` 的目录布局暂存（把 `genesis.json` /
`platform_discount.json` 放进 `deploy/`），**包内代码同样 97/97 通过** —— 交付包自身是自洽的。

---

## 七、T3 交付证据（2026-09-25）

### 改动文件与 md5

| 文件 | 改动性质 |
|---|---|
| `node/modules_privacy.py` | 通道常量 / `bridge_config` / `bridge_plan`·`bridge_apply`·`bridge_undo`·`bridge_commit` / `_pool_mark`·`_pool_undo` / `route()` 重写为「规划→校验→动账→落账」/ `bridge_info()` / `flush`·`compact`·`_load` 记通道口径 |
| `node/chain.py` | **仅 1 行**：`load_genesis_config` 白名单加 `privacy` |
| `node/rpc.py` | `exx_privacyEscrow`（新）；`exx_shield` 余额预检；`unshield` 地址校验/归一；提交回执加 `planned` / `bridge` |
| `node/notes.py` | **零改动** |
| `deploy/genesis.json` | 新增 `privacy` 段（只加键，既有参数一个未改） |
| `node/explorer.html`、离线版浏览器 HTML | 只读追加「隐私池托管余额」一行 |
| `tests/test_privacy_bridge.py` | **新增**，145 项 |
| `tests/test_privacy_chain.py` | **+2 行**：钉死 `bridge_enabled=false` |
| `docs/隐私资金通道设计.md` | **新增**（口径 / 不变量 / 原子性 / 回滚 / 已知边界） |
| `deploy/aws/run-local-gate.sh` | 自检脚本 9 → 10 个，基线 587 → 732 项 |

### 全局自检

`tests/` 10 个脚本 **732/732** 全绿（本地工程与「解包后按 `install.sh` 布局暂存」的包内代码各跑一遍）：

`test_omnimx_dex` 88 + `test_notes` 101 + `test_platform` 36 + `test_privacy` 39 +
`test_privacy_bridge` **145** + `test_privacy_chain` 97 + `test_rangeproof` 59 +
`test_testnet` 68 + `test_upgrade_compat` 51 + `test_ws_subscribe` 48 = **732**

### 上链跑通后发现并修掉的缺陷

| # | 缺陷 | 不修的后果 | 修法 |
|---|---|---|---|
| D | 关闭态区块也写全零 `pub` 通道记账 | 重放时把「从未开通道」误判成「已开通道」，**防凭空造币闸门失效**（旧 note 可被凭空兑现） | 只有真发生通道动账才写 `pub`；重放时也只在非零时置 `history` |

该缺陷已写成回归用例，落在 `tests/test_privacy_bridge.py` 第 8 节「未背书 note 保护」。

### 已知边界（对外必须如实说明）

链上交易仍是明文（`from` / `to` / `value` 公开）；发送方地址关联性未解决（属 T4）；
隐私版 0.1% 底座费与 70/30 分账未接入（属 T5）；节点升级仍需停机重启。

---

## 补丁 · T3 落盘顺序修复（2026-09-25）

> 主网闸门保持关闭；仅测试网。

| # | 文件 | 改动 |
|---|---|---|
| 1 | `node/chain.py` | `_finalize_block` 落盘顺序改为「先出块并落盘透明账本 → 再追加隐私日志」，隐私日志成为唯一提交点 |
| 2 | `node/modules_privacy.py` | `flush(chain, height=None)` 支持显式传本块高度（日志 `h` 不产生 off-by-one）；`bridge_info()` 新增 `backed_exact` / `escrow_surplus` / `escrow_deficit` |
| 3 | `tests/test_privacy_durability_order.py` | **新增**，21 项回归用例 |
| 4 | `deploy/aws/run-local-gate.sh` | 自检脚本 10 → 11 个，基线 732 → 753 项（合计 858 项） |

**缺陷**：旧顺序下若在「写日志」与「写透明账本」之间被 SIGKILL，重启后未花费 note 之和大于
托管余额，即赤字、`backed=false`。

**修复后**：断电只可能留下盈余（托管 > note 之和，无人可认领），不可能留下赤字；
`backed` 表示「无赤字」，`backed_exact` 表示严格对账。
回归用例第 4 组**反向**要求「旧顺序产物必须报 `backed=false`」，防止检查被放水。

**全量回归**：`bash deploy/aws/run-local-gate.sh` → 858/858 全绿。
