# ADR 005：带算法标识的持久化格式与原始字节身份

## 状态

2026-10-13 已采纳，按[交付顺序](../TODO-wire-format.zh.md#交付顺序)实施。本 ADR 定义目标格式，不表示实现或验收已经完成；由 ADR 004 的内容算法标识扩展到所有持久化与认证结构。

## 背景

位置编码不能跳过新增字段，解码后重编码也不能保留未来字段的哈希和签名身份。[已确认需求](../requirements.zh.md#格式与算法升级)要求 protobuf、原始字节认证、显式算法号、仓库算法策略、成员能力通告和本机 schema。项目尚未发布：这次不读旧位置编码及旧 magic；从 protobuf format 1 开始保留旧格式读取能力，不复用字段号或改变已有字段含义。

## 决定

1. schema 位于 `crates/cipherfs-core/proto/cipherfs.proto`，proto3 包 `cipherfs.v1`。`prost-build` 与纯 Rust `protox` 生成 `src/wire/generated.rs` 并入库，`scripts/projects.sh proto` 在容器内生成；正常构建只编译已提交代码；生成入口使用官方 [compile_fds](https://docs.rs/prost-build/0.14.4/prost_build/struct.Config.html#method.compile_fds)，不调用 protoc。所有 proto 注释写在声明上一行，字段号废弃后 reserved。有序映射用按键排序的 repeated entry，重复键拒绝。
2. `HashedBytes` 和 `SignedBytes` 携带写入方只编码一次的 `body`。版本、退休、入会、恢复批准及权限记录保留原始认证 body；读取方在认证通过后解码，不以重编码计算身份。数据库、重开、快照与转发逐字保存 body，派生成员名字、角色和能力视图不修改入会原像。上传清单将 committed body 与授权证明分开，避免把证明清空后重新编码取哈希。配对 transcript 保持现有 keyed BLAKE3 MAC/SAS，MAC 输入改为传输的原始 body，不增加签名功能。
3. `Digest`、`Signature` 和 `PublicKey` 明确算法及值。保持哈希 Blake3=1、Sha256=2、Sha512=3，AEAD XChaCha20Poly1305=1、Aes256Gcm=2、ChaCha20Poly1305=3。签名 Ed25519=1；公钥枚举另有 Ed25519=1、X25519=2；KDF Argon2id=1。文本用 Bitcoin base58，前导零保留，长度与规范形式严格校验：`b3`/`sha256` 为 32 bytes、`sha512` 为 64、`ed25519` 公钥 32/签名 64、`x25519` 公钥 32。随机 16-byte 身份不是哈希或公钥，不伪装成算法值。
4. 中英文算法注册表记录数值、文本标识、active/deprecated 与可选 `deprecated_after`；公钥和签名分开注册，为已有 FastCDC/Fixed 策略分配明确 chunking 注册号，既有 hash/AEAD 号不变。未知、未实现或已过期算法明确拒绝，不尝试默认算法。算法号也覆盖本机 AEAD 与 X25519 sealed box，不能只凭 magic、字段长度或调用位置推断。现有派生标签不变。
5. `Genesis.algorithm_policy` 钉住 hash、signature、kdf 和 AEAD 集合。初始策略使用已实现算法，所有记录和封装均检查策略；首次 bootstrap 先核对独立 TrustAnchor 的 Genesis 原始段，再取得策略并检查同段所有数据。拒绝不能持久化候选密钥、成员、正文授权或 frontier。
6. 段使用 `CFSSEG02`，快照及沿用快照封装的上传清单使用 `CFSSNP03`；magic 后为大端 `u32 format`，当前只实现 1。头明确公钥、签名与 AEAD 算法，保留 author、epoch、seq/frontier 摘要和 authority 等原有认证绑定。magic、format、算法号、nonce 与 authority 前缀全部纳入外签名和 AEAD AAD，保存原始头字节；有界头解析不等于授权。现有 32MiB 段、快照和权限证明边界不放宽。恢复对象与配置另有 protobuf 类型与封装，不冒充快照。
7. 入会及恢复 enrollment 原像绑定 `max_format >= 1`；`MemberUpdate` 增加可选能力更新，只能由设备本人发布。撤销成员不参与协商。创建首段以本机 format 1 启动；正常写出取本机实际支持上限与全部活跃成员最新通告的最小值，不能因陈旧自通告写出未实现格式。高 format 在解密、候选钥处理及应用前明确拒绝，阻塞该设备之后的段；继续其他无因果依赖设备，返回明确错误，不推进被阻塞 frontier。依赖缺口的段仍 Deferred，快照不能静默越过这道门。
8. metadata/CAS 本机数据库值和 `VaultConfig` 改 protobuf；settings 明确存 `schema: u32`。只有明确新建数据库才写初始 schema；已存在的库缺 schema、未知高 schema 或无法读取都明确失败，不静默重建。低 schema 在同一事务内逐级迁移，错误回滚。加密及未加密表的既有保密边界保持；本机加密封装带 AEAD 号，配置标注 KDF。
9. 对象名用规范算法文本：`i/ed25519.<b58>/<seq:016x>-b3.<b58>-<len>`、`p/<digest值前两字符>/b3.<b58>-<len>`、`n/ed25519.<b58>-b3.<b58>-<len>`、`u/b3.<b58>-<len>`、`r/b3.<epoch-b58>-b3.<b58>-<len>`。`v/<b58>`、`s/<b58>` 为 namespace 内精确 16-byte 随机 ID。新快照名按发布设备区分，frontier 的原始字节及 digest 继续在认证载荷内承诺和验证。未知算法对象记诊断并忽略，不可作为授权或 GC 删除依据。

## 尚不实现的策略切换

未来策略记录必须由现有策略授权，包含前一策略身份、新集合、激活 frontier 和所需 format；所有活跃成员先通告能力，激活前写旧策略、激活后写新策略。旧记录永久使用其原策略认证，保留原算法号和原始 bytes；不重写旧正文或身份。此轮不添加切换记录、自动重加密、后台迁移或新算法实现。

## 备选方案

- 继续位置编码并为每次加字段换 magic：不能满足字段演进，也无法保留未知字段的身份。
- 对解码结构做确定性重编码：仍会丢弃读取方不认识的字段，不能代替认证原始 bytes。
- 在构建时调用 protoc：引入宿主工具依赖与不可复验的生成差异，改为纯 Rust 容器生成并提交结果。

## 后果与验收

封装保留一份原始 body，换取可验证的未知字段转发；派生状态与认证记录须分离。schema 和新增算法只向前扩展，不自动保证旧程序能应用新的语义。

每步通过 `scripts/projects.sh test` 后单独提交，再进入下一步；宿主仅用 `check`、`clippy`、`format`。黄金向量位于 `crates/cipherfs-core/tests/vectors/`，含公开标识、签名、密文和合成元数据，不写私钥、库密钥、凭据或用户明文。验证逐字 bytes、ID 和签名，任意单字节改动必须失败；含未知字段的 body 经导入、重开和快照保持逐字相等。

真实设备夹具通告 1/2 时实际写出 format 1；高 format 3 被拒绝、该设备后续 frontier 不推进且其他无依赖设备仍应用。拒绝的 bootstrap 不落库候选钥。最终直接及间接依赖均不含旧位置编码库：删除未使用 session 依赖，网络依赖改为已锁定同版本端点库，保留发现功能和协议。运行证据写入实现审计。
