TSS-lib EdDSA 中的子群攻击分析

MPC

Author: Nicola Von

1.子群攻击问题的定义

在带有 cofactor 的椭圆曲线(例如 Ed25519)上,曲线点群不是一个大素数阶群,而是可以分解成:

  • 一个阶为大素数的 大子群,记为 (Q);
  • 一个阶很小的 小子群,记为 (T)。

如Ed25519曲线的结构是: $$ |G|=8\cdot \ell,\ell \text{是252bit的素数} $$ 子群攻击(small subgroup attack) 的典型模型:

  • 有一个需要保护的秘密标量 (x)(比如私钥或其线性组合);

  • 攻击者可以构造并发送任意曲线点 (P) 给(特别是包含小子群成分的点),例如: $$ P = q + t, \quad q \in Q,\ t \in T $$

  • 协议要求计算: $$ Q = x \cdot P $$ ,并把 (Q)(或其某种编码)返回给对方。

攻击者可以多次构造不同的小子群点,观察输出的 (xP) 在小子群上的投影,逐步恢复 (x) 的若干低位比特,甚至结合其他结构性弱点恢复出完整私钥。这就是 small subgroup attack 的核心:利用“拿秘密标量去乘他构造的坏点”。

关键点:

  • 危险对象是“对手给的点 P”,
  • 利用的是“用 自己的 secret x 去乘 P 并泄露结果”。

2.Ed25519 曲线的特征与群结构

Ed25519 使用的曲线(Edwards25519)上所有点构成一个有限 Abel 群 (G):

总阶: $$ |G| = 8 \cdot \ell $$ 其中( l ) 是 252 位大素数。

可以分解为:

  • 阶为 l 的大素数阶子群 Q,其中 B 是 Ed25519 的标准基点;
  • 阶为 8 的 小子群 (T)。

重要性质:

  • 任意点 ( P ∈ Q) 都可以唯一写成:P = q + t,q ∈ Q,t ∈ T。
  • 两个子群交集只包含单位元,不可能有非零点同时属于大子群和小子群。

这直接带来一个结论: $$ \text{对任意}, a \in \mathbb{Z}_\ell,\text{有 } A= aB \in Q,\text{ 且不含任何非零小子群成分, 即所有“标量×基点”的点都是“纯净的大子群点”。} $$ 换句话说,只要标量是模 ( l ) 的,且只乘标准基点 B(或 B 的线性组合),就天然工作在 prime-order 子群里面。

3.clamp 操作的作用与适用场景

根据 RFC 8032 定义的 Ed25519 的私钥生成流程,clamp 在这里起到的作用包括:

  • 将任意 SHA 输出的 32 字节映射为“合法标量”

    • 避免直接做昂贵的 ;

    • 确保范围、分布、位型满足 Ed25519 安全证明的要求。

  • 让标量成为 8 的倍数

    • 对任意阶 ( |8 ) 的小子群点 (t),有:at = 0

    • 这在那些需要 secret × 不可信点 的场景(例如 X25519/ECDH)中,能抵抗小子群攻击。

4.tss-lib EdDSA 中标量与点是怎么用的

4.1 标量的来源

在 DKG 的第一轮中,每个参与方本地生成私钥份额:

ui := common.GetRandomPositiveInt(round.Params().EC().Params().N)

这里

N = round.Params().EC().Params().N

在 EdDSA 场景下就是 ( l )(大子群的阶),因此 ui 是直接在大子群中随机采样的标量。

后续:

  • VSS 多项式系数、share 等全部在大子群上做加减乘;

  • 公钥相关点、VSS 承诺点等均形如 标量 × 基点 B 或其线性组合。

4.2 点的使用

协议中涉及两类点:

  • 本地生成的点(安全)

    • 形如 ui B、多项式系数承诺 a_k B、临时 nonce ri B 等;

    • 因为 B 阶为 ( l ),所有这类点必然在大子群 (Q) 内,不含小子群成分。

  • 从其他参与方接收的点(必须防御的接口)

    • DKG 中接收到的 VSS 承诺 decommit(多项式系数点);

    • 签名协议中接收到的各参与方的 nonce 点 (R_i);

    • 重分配协议中的各种承诺点。

    • 恶意参与方可以在这里构造点: P = q + t,t ∈ T,即带小子群成分的“坏点”。

tss-lib 在设计上通过:

  • 对关键点附带 离散对数证明(Schnorr PoK);

  • 在处理小子群问题后,引入 cofactor clearing / 子群检查;

来防止恶意参与方向协议中注入小子群点(参考 Baby Sharks 披露前后的修复方案)。

5.tss-lib 的设计

5.1 标量侧:已经在 prime 子群内采样,无 seed→标量问题

与 RFC 8032 的 Ed25519 不同:

RFC 从一个任意 32 字节 seed 出发,必须通过 hash + clamp 把它变成合法标量;

tss-lib 直接在大子群中采样:

ui := GetRandomPositiveInt(N) // N = ℓ

因此,一开始就已经生活在“大子群的标量空间”里,不存在 “hash(seed) 可能落在错误的 coset” 之类的问题。

5.2 子群攻击的真正入口在“点”,不是“标量 ui”

如第 1 节所述,小子群攻击的模型是:

  • 攻击者控制 点 P(可以故意让 P 带 torsion);

  • 用 secret x 去乘 P 并泄露结果。

在 tss-lib 的 EdDSA 协议中,需要特别防御的是“收到的点”:

  • 别的参与方发来的 decommit、nonce Rᵢ、部分公钥等;

  • 如果没有子群检查 / cofactor 清理,恶意方可以像 Baby Sharks 那样,把小子群点 T 注入联合公钥或临时点。

解决办法是:

  • 对收到的点进行 子群成员检查;或

  • 执行 cofactor clearing,去掉 torsion 成分;

  • 配合 Schnorr PoK,证明“P 确实等于某个标量乘基点”。

5.3 “tss-lib 不怕子群攻击”的精确定义

  • tss-lib 不需要靠“对 ui 做 clamp”来防小子群攻击,原因是:

    • 标量 ui 直接在 ( ) 中采样,已经是 prime-order 子群的合法标量;

    • 所有本地生成的点都是 标量×基点 B,天然落在大子群 (Q) 中;

    • 子群攻击真正的攻击面在“收到的点”,需要通过 cofactor 清理与子群检查来加固;

    • Baby Sharks 之后的修复方案,正是围绕“处理收到的点”而不是“clamp 标量”。

  • 如果实现忽略对子方点的子群检查 / cofactor 清理, 那么即使对 ui 做 clamp,也 不能 阻止恶意参与方向协议注入小子群点,仍然会受到类似攻击。

6.结论

tss-lib本身无需修改底层库代码来规避子群攻击问题。