Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

实验 06:综合项目验收

本实验不再引入新的公式,而是把前五个实验组合成一套边界清楚、可以重复演示的本地 2-of-3 FROST 学习系统。

综合项目通过不代表生产安全。最终产物仍应醒目标记为教育实现,不得承载真实资产或生产身份。

本项目只要求单进程事件队列和可注入崩溃的教学存储。真实网络/TLS、生产数据库、HSM、侧信道保证、云部署和真实资产全部排除在验收范围外。RFC 9591 字节互操作作为单独加分项,不与核心公式正确性混为一个结论。

最终目标

三个参与者 Alice、Bob、Carol 各自持有一份长期密钥份额。任意两人可以在不恢复完整私钥的情况下,对一条明确的消息生成普通 Schnorr 签名。

系统必须同时展示四类结果:

  1. 正确路径:三个合法 2-of-3 子集都能签名;
  2. 错误路径:篡改、重复 ID、非法编码和坏份额被准确拒绝;
  3. 攻击路径:测试专用代码复现 nonce 重用泄露;
  4. 恢复路径:在关键持久化边界崩溃后,nonce 不会跨 transcript 重用。

先用可信 Dealer 完成核心里程碑;完整毕业项目必须再把密钥来源替换为第 7 章的本地 DKG 模拟器。两者分开验收,避免同时调试密钥生成与签名公式。

两个完成级别

  • Level 1 — 核心里程碑:可信 Dealer → 三个 2-of-3 子集完成 FROST → 对抗性测试。
  • Level 2 — 完整毕业项目:本地同步 DKG → FROST → 持久 nonce 状态 → 崩溃/乱序/重放故障注入。

Level 1 已经是一套完整的门限签名学习成果;Level 2 才表示你把“无可信 Dealer”和协议工程也接入了同一条可验证主线。

前置闸门

开始综合项目前,确认:

  • 玩具有限域的全部穷举测试通过;
  • Scalar 版 Shamir 的所有 2-of-3 子集通过;
  • 普通 Schnorr 已拒绝单位元公钥和单位元承诺;
  • 普通 Schnorr nonce 重用实验能恢复测试私钥;
  • FROST binding factor 和 Schnorr challenge 使用不同的域分离函数;
  • SigningNonces / ReservedSigningNonces 不能被复制,且第二轮按值移动预留句柄,用持久化 compare-and-swap 完成消费;
  • 你能口头解释为什么这些条件仍不足以证明生产安全。

任一条件不满足,就先回到对应实验修复,不要在综合层用特殊分支掩盖底层错误。

建议的最终目录

src/
  lib.rs
  error.rs
  toy_field.rs             # 仅学习/测试,真实协议不依赖
  suite.rs                 # Ristretto255/SHA-512 与规范编码
  identifier.rs
  transcript.rs            # H1/H2/H3/H4/H5 或教学阶段的明确等价层
  shamir.rs
  schnorr.rs
  frost/
    mod.rs
    types.rs
    dealer.rs
    round1.rs
    round2.rs
    aggregate.rs
  state/
    mod.rs
    nonce_store.rs
    signer.rs
examples/
  two_of_three.rs
tests/
  shamir_properties.rs
  schnorr_negative.rs
  frost_subsets.rs
  frost_adversarial.rs
  crash_recovery.rs
  encoding.rs
  ui.rs
tests/ui/
  nonces_cannot_be_reused.rs

这只是职责划分,不要求第一天建立所有文件。一次只迁移一个已通过测试的模块。

核心 API 边界

最终实现至少应让下列对象不可混用:

ParticipantId
SigningShare              secret, long-lived
VerifyingShare            public
GroupPublicKey            public, validated, non-identity
KeyPackage                secret share + suite/group/epoch/roster/threshold
SigningNonces             secret, one-time, move-only
NonceId / ReservedSigningNonces
SigningCommitments        public, validated
CoefficientCommitment     public, protocol-specific identity rules
SigningPackage            message + ordered commitments
AuthenticatedSigningRequest / AuthenticatedSignatureShare
SignatureShare            public protocol response
Signature                 public, validated
SessionId / GroupId / Epoch

设计检查:

  • SigningShare 与普通 Scalar 不能随意互换;
  • KeyPackage 不可把秘密份额与 ciphersuite、group/epoch、有序 roster 或 threshold 分离使用;
  • SigningNonces 不实现 Clone、Copy、明文 Debug 或通用序列化;
  • 公开 Round 2 入口只接受 nonce store 生成的 ReservedSigningNonces 和通过消息认证的 request,不暴露只接受裸 package/nonce 的绕过路径;
  • GroupPublicKey 只能从验证过的非单位元点创建;
  • CoefficientCommitment 与 FROST 的 SigningCommitments 使用不同构造器;不要把签名层的“拒绝单位元”规则套给所有 VSS 系数;
  • Signature 的字段私有,解析器拒绝单位元 R 与非规范 z;
  • SigningPackage 构造时要求承诺已按 ID 严格递增,并拒绝乱序、重复、未知 ID 和未达门限的输入;只有协调者内部从 map 建包的 builder 可在编码前显式排序;
  • RFC 9591 兼容路径还检查 participant identifier 位于 [1, MAX_PARTICIPANTS];
  • 应用 envelope 的 session/epoch 不被擅自塞入 RFC 9591 的 H1/H2;
  • 若这些业务字段需要最终验证者确认,它们进入被签消息。

阶段 A:密钥材料

任务

用可信 Dealer 生成:

  • 非零组私钥 x,但只允许存在于 keygen 的受限作用域;
  • 三份 2-of-3 Shamir signing share;
  • 三份 verification share;
  • 非单位元 group public key;
  • roster、threshold、suite 和 key package 标识。

Dealer 返回以后,主签名 API 不得再提供恢复 x 的入口。

验收

  • 任意两份在测试夹具中可验证对应同一个 group public key;
  • 一份不能调用恢复 API;
  • 重复/零 participant ID 被拒绝;
  • 组公钥为单位元时 keygen 失败;
  • key package 的 group、epoch、roster hash 完全一致。

阶段 A+:用本地 DKG 替换 Dealer

完成 Level 1 后,保持 FROST 签名 API 不变,只替换 KeyPackage 的来源。使用单进程同步事件队列模拟三个独立节点:

  1. 每个节点各自生成次数至多为 1 的随机多项式;
  2. 广播 Feldman 系数承诺;
  3. 向另外两名参与者发送私密分量份额;
  4. 每个接收者逐 Dealer 验证份额;
  5. 冻结投诉、响应和共同资格集合 Q;
  6. 各自聚合自己的 signing share 与 verification share;
  7. 从常数项承诺计算共同、非单位元的 group public key;
  8. 删除临时 Dealer 多项式和分量份额;
  9. 把三个 KeyPackage 原样交给阶段 B。

核心 DKG 验收:

  • 三方得到相同的 group public key、roster hash、epoch 和资格集合;
  • Q 满足所选教学 DKG 的最小合格集合条件;不能在只剩一个 Dealer、使其知道完整组私钥时仍宣称“分布式生成成功”;
  • 三方各自的 verification share 与 signing share 对应;
  • 正常路径从不计算完整组私钥;
  • 篡改一个私密份额会被指定接收者检测;
  • 对不同接收者分叉承诺时,所有诚实节点按同一规则共同中止;
  • 虚假投诉、Dealer 沉默或最终 Y 为单位元时,共同中止而不是各自定稿;
  • DKG 输出继续通过阶段 B 的三个签名子集测试。

本阶段仍不实现真实网络。乱序、重放和崩溃由后面的事件队列故障注入统一验收。

阶段 B:两轮签名

Round 1

每位候选签名者:

  1. 按选定套件生成 hiding/binding nonce;
  2. 计算两个公开承诺;
  3. 在本地 nonce store 中原子预留给一个 session;
  4. 发布承诺;
  5. 从此不允许把该 nonce 对退回可用库存。

Round 2

每位最终签名者:

  1. 验证应用 envelope、自己的承诺和完整 signing package;
  2. 独立重算 binding factors、group commitment、challenge 和拉格朗日系数;
  3. 若任一输入点或聚合 R 为单位元,失败并烧毁 nonce;
  4. 先耐久记录消费状态与响应,再释放 signature share;
  5. 协调者验证每一份,再聚合;
  6. 用普通 Schnorr 验证器检查最终签名。

验收

对 {Alice,Bob}、{Alice,Carol}、{Bob,Carol} 分别运行:

  • Round 1 使用全新 nonce;
  • 每个 signature share 单独验证;
  • 聚合签名由同一 group public key 验证;
  • 最终签名字节不包含 roster、门限或签名者身份;
  • 三组签名都不能访问完整私钥。

阶段 C:失败矩阵

为每一行建立独立测试。不要只断言 is_err(),还要断言错误发生在预期边界。

场景最早拒绝位置nonce 结果
少于门限signing package 构造未发布者可保留;已发布者烧毁
participant ID 为 0/重复解码或 package 构造未使用
自己的承诺被替换签名者接受 package 前烧毁
消息与批准内容不同应用策略检查烧毁
commitment list 乱序规范解析烧毁或按 API 约定拒绝前不发布
输入点为单位元点解析/验证烧毁
聚合 R 为单位元challenge 计算前全部烧毁
signature share 被修改聚合前份额验证已消费,不能重试
nonce 为 A 预留,却收到 session B 的签名请求签名者 envelope / nonce store 前置检查A 的记录烧毁,不改绑 B
share envelope 来自另一 session协调者 envelope / session store 前置检查不触碰签名者 nonce store
最终签名验证失败发布签名前已消费,创建新会话

同一种错误在不同状态发生时,nonce 处理可能不同;测试名称应把状态写出来。

阶段 D:崩溃恢复

为签名者建立最小状态:

Fresh -> Reserved -> CommitmentPublished
      -> ConsumedAndResponsePersisted -> Completed
      \-> Burned

ConsumedAndResponsePersisted 是一次原子事务,不是先保存 response、后标记 consumed 的两个可崩溃步骤。Completed 只表示传输/确认完成,nonce 在前一状态就已不可再用。

在下面每个边界注入崩溃:

  1. nonce 生成后、预留前;
  2. 预留提交后、承诺发送前;
  3. 承诺发送后、package 到达前;
  4. response 计算后、消费事务提交前;
  5. 消费事务提交后、response 发送前;
  6. response 发送后、确认前;
  7. 从旧数据库快照启动;
  8. 两个进程同时争抢同一个 nonce。

每个恢复测试必须证明:

  • 同一 nonce ID 不会对应两个不同 transcript;
  • 已发布或状态不确定的 nonce 不会回到库存;
  • 允许幂等重发时,只重发已持久化的完全相同响应;
  • 无法证明安全时中止,而不是重新计算。

阶段 E:标准对照

把“协议逻辑正确”和“RFC 9591 字节兼容”分成两个独立标签。

edu

  • 公式、类型和失败测试正确;
  • 可能使用自定义域标签或简化线格式;
  • 不声称与其他实现互操作。

rfc9591-conformant-study

  • 锁定 RFC 9591 的 Ristretto255/SHA-512 ciphersuite;
  • 实现规定的 H1 至 H5、nonce_generate、序列化和输入验证;
  • participant identifier 是 [1, MAX_PARTICIPANTS] 内的 NonZeroScalar,且互不重复;
  • 官方 ristretto255 测试向量逐项一致;
  • 与成熟实现做差分测试;
  • 仍然保留“未经审计、禁止生产使用”的声明。

不要为了让最终签名“碰巧验证”而跳过中间值对照。至少比较:

  • nonce commitments;
  • binding factors;
  • group commitment;
  • challenge;
  • 每个 signature share;
  • 最终签名字节。

核心验收矩阵

能力必须提供的证据
数学正确性手算例与性质测试得到相同结果
DKG 正常流程三个节点得到同一非单位元公钥,正常路径从不重构完整私钥
DKG 恶意输入错误份额、承诺分叉、虚假投诉导致共同剔除或共同中止
三个 2-of-3 子集三组独立成功测试
普通验证兼容聚合签名由单方 Schnorr verifier 接受
不重构私钥正常签名路径无 combine/recover 调用
份额可归责修改每个 z_i 都定位到对应 ID
identity 防护X=O、输入 commitment =O、聚合 R=O 均有负例
nonce 一次性编译期 move 测试 + 持久化/崩溃测试
transcript 唯一黄金字节向量、乱序/重复/长度歧义负例
会话隔离跨 session、group、epoch 消息全部拒绝
解析健壮性任意畸形字节返回错误且不 panic
安全边界README 和实验报告明确未覆盖项

RFC 9591 对照不在上表的核心毕业门槛中。只有选择 rfc9591-conformant-study 加分路线时,才额外要求官方向量逐项一致并与成熟实现完成差分测试。

演示程序

examples/two_of_three.rs 只演示公开流程,不打印秘密:

1. 显示 group public key 指纹和 roster
2. 选择 Alice + Carol
3. 显示两轮中的公开 commitment
4. 显示每个 signature share 的验证结果
5. 显示最终签名与普通验证结果
6. 篡改消息并显示验证失败

禁止输出 signing share、nonce、Dealer 多项式或完整私钥。若需要攻击演示,使用单独的 #[cfg(test)] 夹具和固定玩具参数。

项目报告

综合项目完成时,提交一份短报告,至少回答:

  1. 你的实现采用哪个确切密码套件和版本?
  2. 哪些模块是自己实现的,哪些基础原语来自库?
  3. 正常路径中是否任何时刻出现完整私钥?
  4. nonce 在发布承诺、崩溃和重试后如何处理?
  5. 哪些网络和身份假设尚未实现?
  6. 哪三个测试最可能阻止真实事故?为什么?
  7. 与 RFC 9591 测试向量还有哪些差异?
  8. 如果旧 epoch 的完整份额集合泄露,为什么 refresh 不能撤销它?
  9. 当前项目最不确定的安全结论是什么?

完成定义

只有当以下三句话都真实,综合项目才算完成:

  1. “我能从公式解释每个公开量和秘密量。”
  2. “我能用测试展示错误输入在何处、为什么被拒绝。”
  3. “我能清楚说明这套实现为何仍不能用于生产。”

如果只能生成一枚有效签名,却不能回答这三点,项目仍处于演示阶段。