# 任务：持久化格式与算法升级改造

2026-10-13 立项。目标是满足[已确认需求](requirements.zh.md#格式与算法升级)"格式与算法升级"一节。项目未发布，不需要兼容现有数据；现有测试全部通过是回归基线，新增格式验收不得放宽旧断言。改造期间不加功能；具体边界见 [ADR 005](adr/005-wire-format.zh.md)。

## 现状

- 15 个生产 `src` 文件引用 bincode 1.3（含错误转换和测试模块），位置编码没有字段标签，不能跳过未知字段。文件：`cipherfs-core` 的 `error.rs`、`index/{membership,records,retirement,segment,snapshot}.rs`、`storage/metadata_store.rs`、`vault.rs`、`vault/{authority_keys,devices,maintenance,recovery,snapshot,sync}.rs`；`cipherfs-sync` 的 `pairing.rs`。另有集成测试 `core/tests/pages.rs`、`sync/tests/retirement_gc.rs`；锁文件还通过未使用的 `async-session` 和 Iroh umbrella 的 RPC 依赖闭包引入它。
- `VersionId`、`RetirementId`、快照 frontier 和上传清单 commitment 使用带现有派生标签的 BLAKE3，原像为 bincode；版本验证会解码后重编码。普通成员/退休记录由段外签名认证；设备请求、恢复 enrollment 与 RecoveryEnvelope 有独立元组签名。配对 transcript 用 ticket-secret keyed BLAKE3 MAC/SAS，不是独立 Ed25519 签名。上传清单当前清空授权证明后重编码取 commitment。
- 容器头是手写的：段 `CFSSEG01` 固定头 104 bytes；快照 `CFSSNP01`/`CFSSNP02` 固定头 112 bytes，载荷为 AEAD 密封的 bincode，末尾 Ed25519 签名。02 还有未加密但受签名/AAD 认证的 authority 长度和原始段向量；快照写 02，上传清单复用 01。恢复对象是 bincode RecoveryEnvelope，keys 单独非对称密封。
- `metadata.redb` 有 31 张表：18 张使用 bincode 后本机密封；config 整体未密封，含各自加密的 wraps；还有明文 bincode、原始加密签名段、密封密钥、BE 整数/位置和空标记。settings 没有 schema 号。CAS 另有 3 张表，含整数和手工位置编码，同属本机数据库改造范围。
- 对象名 `i/<device>/<seq>-<hash>-<len>`、`p/<hh>/<hash>-<len>`、`n/<frontier_hash>-<hash>-<len>`、`u/<hash>-<len>` 中哈希和设备 ID 是裸 hex，隐含 BLAKE3 和 Ed25519；`v/`、`s/` 是随机 16-byte ID，`r/` 的 epoch 是派生身份，不能都视为公钥。
- 内容层已有算法号：`ContentBody.suite` 是 `(u8, u8)`，分别编码 EncryptionAlgorithm/HashAlgorithm，XChaCha20Poly1305=1、Aes256Gcm=2、ChaCha20Poly1305=3，Blake3=1、Sha256=2、Sha512=3。内容当前只接受 Blake3；注册号不等于已启用。保留全部既有数值。
- 密钥派生标签已带版本（`cipherfs index v6`、`cipherfs version id v1` 等），不改。

## 设计决定

1. **编码用 protobuf**，proto3，包名 `cipherfs.v1`，文件 `crates/cipherfs-core/proto/cipherfs.proto`。生成的 Rust 代码提交进仓库（`crates/cipherfs-core/src/wire/generated.rs`），由 `scripts/projects.sh proto` 重新生成；生成器是工作区内一个小 crate，用 prost-build 加 protox（纯 Rust，不需要 protoc 二进制）。`build.rs` 不得在构建时调用任何外部工具。不使用宿主 protoc；proto 注释必须单独写在声明上一行，废弃字段号 reserved。
2. **身份即字节**。定义两个通用封装：`HashedBytes { bytes body }`（身份由 body 派生）和 `SignedBytes { bytes body; Signature signature }`。各被哈希或签名的记录按原有载体携带封装；段中的版本、成员、退休等保留原始 record body，配对消息与恢复对象保留各自原始 body；写入方编码一次，哈希和签名只算在 `body` 上，读取方先验再解码 `body`，数据库、重开、快照与转发保留原始字节及未知字段。普通记录仍由段签名认证，不新增独立记录签名；配对保持原有 MAC。`VersionId` 的派生标签保持 `cipherfs version id v1`，原像改为 body 字节。删除所有"解码后重编码再比对"的代码。
3. **算法标识**。新增 `Digest { HashAlgorithm algorithm; bytes value }`、`Signature { SignatureAlgorithm algorithm; bytes value }`、`PublicKey { PublicKeyAlgorithm algorithm; bytes value }`，签名 Ed25519=1、公钥 Ed25519=1/X25519=2，以及 `KdfAlgorithm`（Argon2id=1）。Rust 侧对应类型实现 `Display`/`FromStr`，文本形式 `b3.<base58>`、`sha256.<base58>`、`sha512.<base58>`、`ed25519.<base58>`、`x25519.<base58>`，base58 用比特币字母表，前导零字节编码为前导 `1`。解析时校验长度：b3/sha256 为 32 字节、sha512 为 64、ed25519 公钥 32/签名 64、x25519 公钥 32。CLI、Web、日志、FFI 的 JSON 一律用文本形式。
4. **算法注册表**写在 `docs/algorithms.zh.md`（加英文版），hash、signature/public-key（两套枚举）、kdf、aead、chunking；每条有数值 ID、文本标识、状态 active/deprecated、deprecated_after。实现遇到未知或过期算法返回明确错误，不可降级。
5. **仓库钉算法**。`Genesis` 记录增加 `AlgorithmPolicy { hash, signature, kdf, repeated aead }`；应用任何记录前检查其算法在策略内，不符合拒绝；bootstrap 先核对 TrustAnchor 绑定的 Genesis，再检查同段算法，拒绝不落库候选钥或 frontier。运行中切换策略的记录类型只在 ADR 里定义，不实现。
6. **格式号与能力通告**。段和快照头的 magic 升为 `CFSSEG02`、`CFSSNP03`，去掉对 01/02 的解码；头后紧跟大端 `format: u32`（当前 1）；头中的公钥、签名、AEAD 号及 format 都被签名/AAD 认证。`MemberRecord` 增加 `max_format: u32`；入会原像绑定该值，已有本人 MemberUpdate 路径支持可选能力更新，值至少为 1。写入方取本机实际上限与全部未撤销成员最新通告的最小值；空成员集初始写 1。读到高 format 明确报错并停止应用该设备后续段，其他无依赖设备继续；未知 record 类型同样拒绝，不得过滤后推进 frontier。
7. **本机数据库**。metadata 与 CAS 数据库值改 protobuf；settings 存 `schema: u32`，只有明确新建库可初始化。打开时低 schema 逐级事务迁移、高 schema 报错，已有库缺失视为损坏；保持各表既有加密边界。`VaultConfig` 改 protobuf 并加 `kdf` 算法字段。
8. **对象名**。哈希和设备 ID 改为文本形式：`i/ed25519.<b58>/<seq:016x>-b3.<b58>-<len>`、`p/<b58 前两字符>/b3.<b58>-<len>`、`n/ed25519.<b58>-b3.<b58>-<len>`、`u/b3.<b58>-<len>`，`v/<b58>`、`s/<b58>` 保留 namespace 内随机 16-byte ID，`r/b3.<epoch-b58>-b3.<b58>-<len>` 标注派生身份。n/ 改为设备前缀时，frontier 原始 bytes/digest 继续在认证载荷内承诺和验证。解析时未知算法前缀的对象忽略并记日志，不可作为权限或 GC 删除依据。
9. **测试向量**。`crates/cipherfs-core/tests/vectors/` 放固定输入与期望字节、ID、签名的黄金文件，覆盖版本记录、段、快照、配置、每种文本形式。测试比对字节，不仅比对结构；包含未知字段的认证 body 经重开和快照后须逐字保留。

## 交付顺序

每步结束时 `scripts/projects.sh test` 工作区完整验证全部通过后单独提交，再进下一步；定向测试不能代替整套门槛。

1. ADR 005（中英文）：决策、备选方案、不实现的切换机制设计；ADR 004 标注"由 005 扩展到所有结构"。
2. 算法注册表文档和 `Digest`/`Signature`/`PublicKey` 类型及文本形式，先不改线上格式。
3. proto 定义、生成器 crate、`scripts/projects.sh proto`，提交生成代码。
4. 被哈希/签名记录改为封装形式，`VersionId` 和各签名改为算 body 字节。
5. 段与快照换 protobuf 载荷，新 magic 与 format 号，成员能力通告，写出格式取最小值。
6. metadata/CAS redb 值、`VaultConfig`、本机加密封装算法号、schema 与迁移框架。
7. 对象名改造，目录存放点与 S3 存放点的解析、列举、GC 同步更新。
8. 删除直接依赖及测试调用；移除未使用 async-session，将仅使用 net API 的 Iroh umbrella 换为已锁定 `iroh-net 0.28.2`，保留发现特征与协议，重新解析 lock，使间接依赖也不再出现 bincode。
9. 测试向量；更新 `requirements.zh.md`（如有偏差）、`IMPLEMENTATION_AUDIT.md`（新增一节，附运行日志）、`architecture`、`rust-api`、各 crate README；`TODO.md` 删除本项。

## 约束

- 宿主只跑 `scripts/projects.sh check|clippy|format`；完整测试用 `scripts/projects.sh test`（容器）。不在宿主安装任何第三方工具、包或工具链。
- 私钥、库密钥、凭据和用户明文不进入日志、测试向量和文档；向量只含公开标识/签名/密文及合成元数据。
- 文档保持简短：README 只写当前行为，证据写进审计。文档中不出现其他项目名称。
- 每一步单独提交，提交信息说明改了哪一层。

## 验收

- `scripts/projects.sh test` 全部通过；`scripts/projects.sh check` 和 `clippy` 无新警告。
- `rg -n bincode crates/ Cargo.toml Cargo.lock` 无结果，含生成代码、集成测试和间接依赖。
- 测试向量存在且通过；手动改任一向量的一个字节测试失败。
- 两台设备（容器内现有 P2P 测试夹具）一台把 `max_format` 通告为 1、另一台支持 2 时，写出格式为 1；伪造 format 为 3 的段及该设备后续段不推进 frontier，其他无因果依赖设备的合法段继续应用；拒绝的 bootstrap 不落库候选钥。
- 审计新增小节列出上述运行证据。
