Caloogy 文章
English

MPC 钱包与门限签名的工程坑

日期
2025 年 11 月 16 日
系列
钱包
阅读
4 分钟
主题
WalletMPCThreshold signatureTss

一句话版本

MPC(多方安全计算)钱包把私钥拆成多个份额,分给不同的设备或服务器,签名时各方协同计算出一个普通签名,完整私钥从头到尾都不在任何一个地方出现。它比单把私钥更抗单点失守,链上看起来又和普通地址一样。 但门限 ECDSA 的协议非常复杂,理论上安全,不等于实现上安全。2023 年披露的 BitForge 和 TSSHOCK 等漏洞表明:参数没校验、零知识证明被省略,就可能让一个恶意参与方从别人的份额里算出完整私钥。

全文分三层:

  1. 通俗理解:从"拆钥匙"到"永远不拼钥匙"
  2. 实际用处:机构托管、交易所、个人 MPC 钱包,以及真实漏洞
  3. 原理与工程要点:门限 Schnorr 与门限 ECDSA 的难度差异、份额刷新、实现检查清单

第一层:通俗理解

1.1 从"拆钥匙"到"永远不拼钥匙"

方案 做法 弱点
Shamir 分片 私钥拆成几份分开存,用的时候拼回来 拼回来的那一刻,完整私钥出现在某台机器上
多签(链上) 链上合约要求多个独立私钥签名 规则公开、手续费高、每条链要单独部署
MPC / 门限签名 各方各持一份,直接协同算出签名,从不拼回私钥 协议复杂,实现容易出错

1.2 一个比喻

三个人各知道一个数字的一部分,他们要一起算出"这个数字乘以某个公开的数"的结果。通过一套精心设计的交换信息的规则,三人最终都得到了正确结果,但谁也没有得知那个完整的数字。门限签名就是在做类似的事,只是"乘法"换成了签名运算。


第二层:实际用处

真实漏洞:

披露 问题
BitForge(Fireblocks 研究团队,2023) 多个流行的门限 ECDSA 实现(基于 GG18 / GG20 等协议)省略或简化了对 Paillier 密钥参数的零知识证明。恶意参与方可以构造特殊参数,通过几次签名交互就算出其他人的份额
TSSHOCK(Verichains,2023) 一些实现中,零知识证明的哈希编码方式有歧义、或者挑战值空间太小,攻击者可以伪造证明

这些都不是协议论文里的数学错误,而是实现时为了性能或省事跳过了检查。


第三层:原理与工程要点

3.1 门限 Schnorr 容易,门限 ECDSA 难

Schnorr签名的签名值 s=k+c xs = k + c\,x 对私钥 xx 和随机数 kk 都是线性的。如果 xx 和 kk 都用Shamir秘密分享拆开,各方算出自己的 sis_i,再用拉格朗日系数加起来就是完整签名。FROST 协议就是这样做的。

ECDSA 的签名值是

s=k−1(h+r x) mod n,s = k^{-1}(h + r\,x) \bmod n,

里面有一个 k−1k^{-1},还有 k−1k^{-1} 和 xx 的乘积。分片之后的乘法和求逆都不能简单地"各算各的再相加",需要额外的密码学工具:常见做法是用 Paillier 同态加密(见《同态加密》)或不经意传输(OT)来做"秘密分享的乘法",并配合大量零知识证明保证每一方都诚实。协议步骤多、参数多,出错的地方也多。

3.2 份额刷新(proactive refresh)

如果攻击者能慢慢地一份一份偷,总有一天会凑够门限。对策是定期刷新:各方协同把份额换成一组新的,对应的私钥不变,旧份额作废。这样攻击者必须在同一个刷新周期内偷够门限份额。

刷新协议本身也要小心:刷新中途失败、或者有一方离线,不能留下"一半新一半旧"的混乱状态。

3.3 实现检查清单

检查 说明
所有零知识证明都必须验证,不能为了性能跳过 BitForge 的根源
对其他参与方提供的参数做完整校验 例如 Paillier 模数确实是两个大素数之积、没有小因子(参见《素性检验》)
Fiat–Shamir 哈希要编码所有上下文,且无歧义 TSSHOCK 的根源(见《Schnorr签名》 里的 Fiat–Shamir)
挑战值空间足够大 太小会让伪造证明可行
签名随机数不能重复,也不能被单方控制 否则回到椭圆曲线里 kk 重复的灾难
恶意参与方被发现时能识别并排除 "可识别中止"(identifiable abort)
使用经过审计、仍在维护的库,跟进安全公告 不要自己实现

3.4 MPC 不能解决的问题


总结

层次 要点
思想 份额协同算签名,完整私钥永不出现
难点 ECDSA 的 k−1k^{-1} 和乘积让门限化很复杂;Schnorr 简单得多
教训 BitForge、TSSHOCK:协议没错,实现跳过了检查
局限 策略在链下;挡不住前端篡改;要定期刷新份额