ADR 005:带算法标识的持久化格式与原始字节身份
状态
2026-10-13 已采纳,按交付顺序实施。本 ADR 定义目标格式,不表示实现或验收已经完成;由 ADR 004 的内容算法标识扩展到所有持久化与认证结构。
背景
位置编码不能跳过新增字段,解码后重编码也不能保留未来字段的哈希和签名身份。已确认需求要求 protobuf、原始字节认证、显式算法号、仓库算法策略、成员能力通告和本机 schema。项目尚未发布:这次不读旧位置编码及旧 magic;从 protobuf format 1 开始保留旧格式读取能力,不复用字段号或改变已有字段含义。
决定
- schema 位于
crates/cipherfs-core/proto/cipherfs.proto,proto3 包cipherfs.v1。prost-build与纯 Rustprotox生成src/wire/generated.rs并入库,scripts/projects.sh proto在容器内生成;正常构建只编译已提交代码;生成入口使用官方 compile_fds,不调用 protoc。所有 proto 注释写在声明上一行,字段号废弃后 reserved。有序映射用按键排序的 repeated entry,重复键拒绝。 HashedBytes和SignedBytes携带写入方只编码一次的body。版本、退休、入会、恢复批准及权限记录保留原始认证 body;读取方在认证通过后解码,不以重编码计算身份。数据库、重开、快照与转发逐字保存 body,派生成员名字、角色和能力视图不修改入会原像。上传清单将 committed body 与授权证明分开,避免把证明清空后重新编码取哈希。配对 transcript 保持现有 keyed BLAKE3 MAC/SAS,MAC 输入改为传输的原始 body,不增加签名功能。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 身份不是哈希或公钥,不伪装成算法值。- 中英文算法注册表记录数值、文本标识、active/deprecated 与可选
deprecated_after;公钥和签名分开注册,为已有 FastCDC/Fixed 策略分配明确 chunking 注册号,既有 hash/AEAD 号不变。未知、未实现或已过期算法明确拒绝,不尝试默认算法。算法号也覆盖本机 AEAD 与 X25519 sealed box,不能只凭 magic、字段长度或调用位置推断。现有派生标签不变。 Genesis.algorithm_policy钉住 hash、signature、kdf 和 AEAD 集合。初始策略使用已实现算法,所有记录和封装均检查策略;首次 bootstrap 先核对独立 TrustAnchor 的 Genesis 原始段,再取得策略并检查同段所有数据。拒绝不能持久化候选密钥、成员、正文授权或 frontier。- 段使用
CFSSEG02,快照及沿用快照封装的上传清单使用CFSSNP03;magic 后为大端u32 format,当前只实现 1。头明确公钥、签名与 AEAD 算法,保留 author、epoch、seq/frontier 摘要和 authority 等原有认证绑定。magic、format、算法号、nonce 与 authority 前缀全部纳入外签名和 AEAD AAD,保存原始头字节;有界头解析不等于授权。现有 32MiB 段、快照和权限证明边界不放宽。恢复对象与配置另有 protobuf 类型与封装,不冒充快照。 - 入会及恢复 enrollment 原像绑定
max_format >= 1;MemberUpdate增加可选能力更新,只能由设备本人发布。撤销成员不参与协商。创建首段以本机 format 1 启动;正常写出取本机实际支持上限与全部活跃成员最新通告的最小值,不能因陈旧自通告写出未实现格式。高 format 在解密、候选钥处理及应用前明确拒绝,阻塞该设备之后的段;继续其他无因果依赖设备,返回明确错误,不推进被阻塞 frontier。依赖缺口的段仍 Deferred,快照不能静默越过这道门。 - metadata/CAS 本机数据库值和
VaultConfig改 protobuf;settings 明确存schema: u32。只有明确新建数据库才写初始 schema;已存在的库缺 schema、未知高 schema 或无法读取都明确失败,不静默重建。低 schema 在同一事务内逐级迁移,错误回滚。加密及未加密表的既有保密边界保持;本机加密封装带 AEAD 号,配置标注 KDF。 - 对象名用规范算法文本:
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 依赖,网络依赖改为已锁定同版本端点库,保留发现功能和协议。运行证据写入实现审计。