导语:把私钥拆开,却仍然能签出一笔合法交易
传统钱包的模型简单粗暴:一把私钥对应一个地址,谁拿到私钥谁就拥有一切。而 MPC(Multi-Party Computation,多方安全计算)钱包提出了一个反直觉的目标——私钥从头到尾没有被任何一方完整拥有过,但系统仍然能产出一笔链上完全合法的签名。
这不是把私钥切两半存起来再拼回去,而是一套真正的密码学协议:n 个参与者各自持有一份分片,任意 t 份协作就能完成签名,少于 t 份则连「私钥是什么」都无法计算。链上看到的就是一笔普通交易,看不出背后有几方在协作。
本文从门限签名的数学底座讲起,深入到 FROST 与 GG18/GG20 两大主流方案的工程细节,再落到钱包产品的密钥生命周期管理与安全边界。
1. 从单私钥到门限签名
1.1 单私钥模型的根本风险
单私钥模型的问题不在于「密码学不安全」,而在于「操作安全不可控」:
单私钥的失效模式:
1. 单点存储 —— 私钥落在哪台机器上,那台机器就是唯一防线
2. 单点使用 —— 签名瞬间私钥必须完整出现在内存中,可被 dump
3. 无法轮换 —— 换私钥就要换地址,历史资产与授权全部作废
4. 备份即复制 —— 备份越多泄露面越大,与"提高可用性"直接冲突
热钱包把私钥放在联网机器上,冷钱包把它放在离线设备里——两者都在「可用性」与「安全性」之间做二选一。门限签名试图同时拿到两端:没有单点,仍然可用。
1.2 门限签名的定义与多签对照
一个 (t, n) 门限签名方案由三个算法组成:
KeyGen → (pk, sk_1, sk_2, ..., sk_n)
输出一个公开的群公钥 pk,以及 n 份分片私钥,每方一份
Sign → (sk_{i1}, ..., sk_{it}, m) ⇒ σ
任意 t 份分片协作,对消息 m 产出签名 σ
Verify → (pk, m, σ) ⇒ {0, 1}
验证者只用群公钥 pk 验证,与普通单签名验证完全一致
关键性质是**「签名不可区分」**:σ 在分布上与用一把普通私钥签出的结果无法区分,验证算法就是标准的 ECDSA 或 Schnorr 验证。这是阈值签名与链上多签最本质的分野——前者对链而言是「隐形」的。
| 维度 | 链上多签 | 阈值签名(MPC) |
|---|---|---|
| 私钥是否存在 | 不存在单一私钥,每个参与者一把 | 存在数学意义上的群私钥,但从未被完整构造 |
| 链上形态 | 一个合约地址 + 多笔确认交易 | 一个普通地址 + 一笔普通交易 |
| 验证方式 | 合约代码逐一校验签名 | 标准 ECDSA/Schnorr 验证 |
一句话总结:多签是「把权限写进合约,让链来裁决」,阈值签名是「把权限藏进密码学,链上什么都看不见」——前者的信任在代码,后者的信任在数学。
2. 密码学底座:秘密共享与拉格朗日插值
2.1 Shamir 秘密共享
1979 年 Adi Shamir 提出的秘密共享方案是整个门限密码学的基石。核心思想出奇简单:用 t-1 次多项式隐藏秘密,任意 t 个点能唯一确定它,少于 t 个点则信息论上完全无法确定。
# Shamir 秘密共享(t-of-n):在有限域 GF(p) 上工作
import secrets
p = 2**256 - 2**32 - 977 # secp256k1 基域素数(示意)
def split(secret, t, n):
# 构造 t-1 次随机多项式 f(x) = secret + a1*x + a2*x^2 + ...
coeffs = [secret] + [secrets.randbelow(p) for _ in range(t - 1)]
def f(x):
acc = 0
for c in reversed(coeffs):
acc = (acc * x + c) % p # Horner 求值
return acc
# 第 i 个参与者的分片就是 f(i),x=0 保留给秘密本身
return {i: f(i) for i in range(1, n + 1)}
def reconstruct(shares):
# 拉格朗日插值在 x=0 处求值,即恢复 f(0) = secret
total = 0
for i, yi in shares.items():
num = den = 1
for j in shares:
if i != j:
num = num * (0 - j) % p
den = den * (i - j) % p
total = (total + yi * num * pow(den, -1, p)) % p
return total
这段代码在数学上完全正确,但它不能直接用来做钱包——原因见 2.3 节。
2.2 拉格朗日插值恢复
恢复过程本质是在 x=0 处做拉格朗日插值:
给定 t 个点 (x_1, y_1), ..., (x_t, y_t),求 f(0):
f(0) = Σ_{i=1..t} y_i · λ_i,其中 λ_i = Π_{j≠i} (0 - x_j) / (x_i - x_j)
λ_i 称为拉格朗日系数,只依赖参与者编号 x_i,与 y_i 无关。
这个「只依赖编号」的性质是门限签名的命门:任何关于分片的线性运算,都可以先算系数再线性组合。签名算法只要能表达成对私钥分片的线性组合,就能在分片层面完成。
2.3 从秘密共享到签名:为什么不能直接共享私钥
朴素想法是:把私钥 x 用 Shamir 拆成 x_1, x_2, x_3,签名时各自算份额再相加。问题在于:
障碍一:ECDSA 里 k^{-1} 无法从分片中直接得到
r = (k · G).x,需先算出 k 的公共值 r,但 k 本身必须保密
若 k 也做 Shamir 拆分,k^{-1} 与 x 的乘法是"两个秘密的乘积"——
无法用线性组合完成,需要专门的乘法协议
障碍二:Schnorr 更友好,但仍需处理 nonce
s = k + e·x ⇒ s_i = k_i + e·x_i —— 线性,直接可加
所以 Schnorr 门限的难点只在 nonce 生成,而非签名方程本身
这一条差异直接导致了两条技术路线:FROST(Schnorr)与 GG18/GG20(ECDSA)。前者因为签名方程线性,协议简洁优雅;后者必须引入复杂的乘法协议来绕过非线性项。
2.4 分布式密钥生成 DKG
Shamir 的经典用法需要一个「可信第三方」来生成多项式并分发分片——但这恰恰是我们要消灭的单点。DKG(Distributed Key Generation)用 n 方各自贡献随机性的方式取代它:
DKG 的核心流程(简化的 Gennaro 风格):
每个参与者 i 独立执行:
1. 随机生成自己的 t-1 次多项式 f_i(x),其中 f_i(0) = 自己的随机贡献
2. 计算并广播承诺 C_{i,k} = f_i(k)·G(对系数做 Pedersen 承诺)
3. 用加密信道把 f_i(j) 私发给每个参与者 j(j ≠ i)
参与者 j 收到所有 f_i(j) 后:
4. 用自己的分片做加总:x_j = Σ_i f_i(j)
代数上等价于"总多项式 F(x) = Σ_i f_i(x) 在 x=j 处的取值"
5. 群私钥就是 x = F(0) = Σ_i f_i(0),但没有任何一方知道它的值
6. 群公钥 X = x·G 可由所有承诺的常数项相加得到,人人可验证
关键校验:每个参与者可验证 f_i(j)·G 是否等于承诺的线性组合;
任何偏离者被公开指认并剔除(complaint 机制)
DKG 的产出与 Shamir 拆分在数学上同构,但没有任何时刻存在过完整私钥。这是 MPC 钱包「无私钥生成」的合法性来源。
一句话总结:Shamir 负责「拆分」,DKG 负责「无需可信第三方地拆分」,拉格朗日插值负责「重组」——三者合起来构成门限签名不可绕过的数学地基。
3. FROST:Schnorr 门限签名
3.1 FROST 的设计目标
FROST(Flexible Round-Optimized Schnorr Threshold signatures)由 Komlo 与 Goldberg 在 2020 年提出,目标非常明确:把 Schnorr 门限签名的通信轮数压到两轮,同时保持对恶意参与者的可问责性。它的友好之处来自 Schnorr 签名方程的线性结构:
Schnorr 签名(单方):
私钥 x,公钥 X = x·G
签名者选随机 nonce k,计算 R = k·G,r = R.x
计算挑战 e = H(r ‖ X ‖ m),签名值 s = k + e·x,输出 (R, s)
验证:s·G == R + e·X
把 x 换成 Shamir 分片 x_i,s 自然分解为 s_i = k_i + e·x_i,验证端只需 s = Σ λ_i·s_i。线性是 Schnorr 门限的全部秘密。
3.2 两轮签名流程
FROST 的两轮结构(对 t 个签名者):
Round 1(承诺阶段):
每个签名者 i 选两个 nonce d_i, e_i,计算承诺点 D_i = d_i·G, E_i = e_i·G
广播 (D_i, E_i) —— 注意:只广播点,不广播标量
聚合承诺:ρ_i = H(i ‖ m ‖ {D_j, E_j}_all),R = Σ_i ( D_i + ρ_i · E_i )
Round 2(签名份额阶段):
r = R.x,c = H(r ‖ X ‖ m)
每个签名者计算 z_i = d_i + ρ_i·e_i + λ_i·c·x_i 并广播
聚合:
z = Σ_i z_i,最终签名就是 (R, z),用群公钥 X 按标准 Schnorr 验证
注意 R = Σ (D_i + ρ_i·E_i) 这一步:每个签名者用两个 nonce 而不是一个,是为了让恶意者无法通过选择最后一个承诺点来偏置聚合 nonce(这是 Schnorr 多签中著名的 rogue nonce 攻击)。
3.3 与 Taproot 的天然契合
FROST 的签名是标准 Schnorr 签名,而比特币 Taproot(BIP-340/341)恰好把 Schnorr 签名带到了比特币主网。这带来了一个杀手级特性:
Taproot 下的 2-of-3 FROST 钱包:
链上表现:一笔普通的 key-path 花费(64 字节签名)
脚本路径:完全不被使用,不暴露任何脚本结构
隐私性:与单签名交易无法区分,observer 不知道存在"门限"
对比 Taproot 原生脚本多签(OP_CHECKSIGADD 聚合):
虽然也是单笔花费,但需要脚本路径,且参与者公钥进入 witness
FROST 连这些都不需要——签名在链下完成,链上只有一个签名值
这是阈值签名相对链上多签最直观的胜利:同样的安全模型,更小的链上足迹,更好的隐私。
3.4 FROST 的 Rust 参考实现
// 使用 frost-ed25519(ZcashFoundation/frost)演示 DKG + 两轮签名
use std::collections::HashMap;
use frost_ed25519 as frost;
use rand::thread_rng;
// ---- 阶段一:生成分片(生产环境应使用可信 DKG 协议而非 dealer)----
let mut rng = thread_rng();
let (shares, pubkey_package) = frost::keys::generate_with_dealer(
3, // max_signers = n
2, // min_signers = t
frost::keys::IdentifierList::Default,
&mut rng,
)?;
// 每个参与者本地保存 KeyPackage,签名份额绝不出本机
let mut key_packages = HashMap::new();
for (id, secret) in shares {
key_packages.insert(id, frost::keys::KeyPackage::try_from(secret)?);
}
let message = b"transfer 1.5 BTC to bc1q...";
// ---- 阶段二 Round 1:生成 nonce 并广播承诺 ----
let mut nonces = HashMap::new();
let mut commitments = HashMap::new();
for id in [id_a, id_b] { // 任取 t = 2 个参与者
let (nonce, commitment) = frost::round1::commit(
key_packages[&id].signing_share(), &mut rng);
nonces.insert(id, nonce); // nonce 必须严格本地保管
commitments.insert(id, commitment); // 承诺点可以安全广播
}
// ---- 阶段三 Round 2:广播签名份额 ----
let signing_package = frost::SigningPackage::new(commitments, message);
let mut signature_shares = HashMap::new();
for id in [id_a, id_b] {
let share = frost::round2::sign(&signing_package, &nonces[&id], &key_packages[&id])?;
signature_shares.insert(id, share);
}
// ---- 阶段四:聚合为一把标准 Ed25519 签名 ----
let group_signature = frost::aggregate(&signing_package, &signature_shares, &pubkey_package)?;
// 验证时只用群公钥,与普通 Ed25519 验证完全一致
pubkey_package.verifying_key().verify(message, &group_signature)?;
一句话总结:FROST 用「双 nonce 承诺 + 线性份额相加」把 Schnorr 门限压到两轮,并借助 Taproot 实现了链上完全隐形——它是目前比特币生态门限签名的事实标准。
4. GG18/GG20:ECDSA 门限签名
4.1 为什么 ECDSA 门限更难
以太坊用的是 ECDSA,它的签名方程里有一个致命的非线性项:
ECDSA 签名(单方):
私钥 x,公钥 X = x·G,选随机 nonce k,R = k·G,r = R.x
s = k^{-1} · ( H(m) + r · x ) ← k^{-1} 与 x 相乘,非线性
对比 Schnorr:
s = k + e·x ← 纯线性,可直接分摊
s = k^{-1}(H(m) + r·x) 中包含 k^{-1} · x 这个两个秘密的乘积。要把它分摊到多方,必须在不泄露各自秘密的前提下计算乘积——这就是 GG18/GG20 引入 MtA(Multiplicative-to-Additive) 协议的原因。
4.2 MtA 乘法三元组
MtA 解决的问题是:Alice 持有 a,Bob 持有 b,如何让双方分别得到 α 和 β,使得 α + β = a·b,且彼此不知道对方的原始输入。
MtA 基于 Paillier 同态加密的经典构造(简化):
Alice 持有 a 与自己的 Paillier 公钥 E_A;Bob 持有 b
1. Alice 计算 c_A = E_A(a),发送给 Bob
2. Bob 选随机掩码 b',计算 c_B = c_A^b · E_A(b') = E_A(a·b + b'),回传
3. Alice 用自己的私钥解密,得到 α = a·b + b';Bob 自己持有 β = -b'
4. 校验:α + β = a·b
安全性直觉:Bob 只知道密文,无法反推 a;Alice 只知道加了掩码的结果,
无法反推 b'——双方各自只拿到乘积的一个"加法碎片"
MtA 输出的加法碎片恰好能嫁接到 Shamir 分片上——因为分片的线性组合本来就是协议允许的操作。这是 GG18 能在分片层面完成 ECDSA 的关键桥梁。
4.3 预处理阶段与在线阶段
GG18/GG20 把签名拆成「与消息无关的预处理」和「与消息相关的在线阶段」,这是它工程上最重要的一招:
预处理阶段(Pre-signing,与消息 m 无关,可提前批量做):
- 生成并分发 nonce 分片 k_i,聚合 R = k·G 得到 r
- 通过 MtA 计算 k^{-1} 的分片与 x·r 的乘积碎片
- 产出"预签名"(pre-signature),缓存起来备用
在线阶段(Online,收到消息后立即完成):
- 输入消息 m,计算 e = H(m)
- 各参与者用预签名碎片做一次线性组合
- 聚合得到 s = k^{-1}(e + r·x),全程只需 1 轮广播
工程价值:预处理可以离线、批量地提前完成(比如夜间批处理);
用户点击"发送"时在线阶段可压到几十毫秒,
这就是 MPC 钱包能做到"体验接近热钱包"的根本原因
4.4 GG18 与 GG20 的差异
| 维度 | GG18 | GG20 |
|---|---|---|
| 论文 | Gennaro-Goldfeder 2018 | Gennaro-Goldfeder 2020 |
| 签名轮数 | 6 轮(含预处理) | 2 轮(预处理 + 在线) |
| 恶意安全 | 部分场景需要额外假设 | 完整的可识别中止 |
| 预处理 | 与签名强耦合 | 解耦,可批量预生成 |
| 乘性转加性 | 标准 MtA | MtA + 范围证明优化 |
| 实践地位 | 早期 MPC 钱包主力 | 当前机构级 MPC 钱包主流 |
一句话总结:ECDSA 门限的复杂度全部来自
k^{-1}·x这个非线性项,MtA 用同态加密把它拆成加法碎片,GG20 再用「预处理/在线」解耦把用户可感知的延迟压到单轮——这是工程对密码学的胜利。
5. 密钥分片与完整签名流程
5.1 keygen 阶段:分片如何诞生
生产级 MPC 钱包的 keygen 有两种路径:
路径一:Dealer 模式(中心化生成与分发)
由可信服务生成 Shamir 多项式并分发分片;实现简单、一次通信即可完成
缺点:Dealer 短暂接触过完整私钥,信任假设退化为"相信 Dealer"
路径二:DKG 模式(分布式生成)
所有参与方各自贡献随机性,通过承诺与 complaint 机制达成一致
优点:任何单方(含服务商)从未接触完整私钥,可审计、可验证
缺点:实现复杂,需要认证信道与可靠的广播层
机构级 MPC 钱包基本都要求 DKG,且往往要求:
- 参与方之间建立双向认证的 TLS/mTLS 信道
- 承诺与 complaint 记录写入不可篡改日志以备审计
- 支持参与者集合的动态变更(见 7.3 的 reshare)
5.2 presign 与 sign 阶段:把延迟前置
presign 是 GG20 风格方案的核心优化,它把「与消息无关」的全部重活提前做完:
presign 阶段各参与者本地完成:
1. 生成 nonce 分片 k_i,广播 K_i = k_i·G
2. 聚合得到 R = Σ K_i,取 r = R.x
3. 通过 MtA 计算两个乘积碎片:
χ_i + χ'_i = k_i · x(跨方的 k·x 乘积分解)
δ_i + δ'_i = k_i · r(k 与 r 的乘积分解)
4. 各方计算并保存自己的 pre-signature 片段
产出:一个 pre-signature,可缓存、可批量预生成、可丢弃重来
注意:pre-signature 一旦生成就必须严格保管——它与最终签名之间只差一个
消息哈希,泄露等同于泄露签名能力
在线签名(收到消息 m 后):
1. e = H(m),各参与者用本地 pre-signature 计算自己的份额
2. 单轮广播份额,聚合得到 s = k^{-1}(e + r·x)
3. 输出标准 ECDSA 签名 (r, s)
链上验证:用地址对应的公钥做一次普通 ecrecover;链上合约、区块浏览器、
任何第三方都无法分辨这是单人签名还是门限签名
5.3 通信轮数与延迟对照
| 方案 | 曲线 | keygen 轮数 | 签名轮数 | 在线延迟(同区域) | 在线延迟(跨洲) |
|---|---|---|---|---|---|
| FROST | Ed25519/Secp256k1 | DKG 3 轮 | 2 轮 | 约 30-80 ms | 约 300-600 ms |
| GG18 | Secp256k1 | 3-4 轮 | 6 轮 | 约 200-500 ms | 约 1-3 s |
| GG20 | Secp256k1 | 3-4 轮 | 2 轮(含预处理) | 约 20-60 ms | 约 200-500 ms |
| 2-of-3 链上多签 | 无(合约) | 无 | 2 笔链上交易 | 取决于出块时间 | 取决于出块时间 |
延迟的主要来源(按影响排序):
1. 网络往返(RTT)—— 跨洲部署时是绝对瓶颈
2. Paillier 加解密 —— 2048 位模幂运算,单次约 1-5 ms
3. 零知识范围证明 —— 防止恶意参与者提交越界值,开销可观
4. 分片数量 —— t 越大需要聚合的份额越多,但增长近似线性
优化手段:把预处理放到用户点击之前的空闲时间;参与方就近部署;
用 secp256k1 原生实现替代通用大数库
一句话总结:门限签名的用户体验几乎完全由「预处理是否做足」决定——把与消息无关的计算全部前置,在线阶段就只剩一轮广播,这是 MPC 钱包从"能用"到"好用"的分水岭。
6. 阈值签名与多签的全面对比
6.1 链上足迹与 gas
这是两者最容易被量化、也最容易被忽视的差异。看一段典型的多签合约:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
// 简化的 2-of-3 链上多签:每一笔转账都需要两笔链上交易
contract OnChainMultisig {
address[3] public owners;
uint256 public constant THRESHOLD = 2;
mapping(bytes32 => uint256) public confirmations;
mapping(bytes32 => mapping(address => bool)) public confirmed;
function isOwner(address who) internal view returns (bool) {
return who == owners[0] || who == owners[1] || who == owners[2];
}
// 第一步:发起并确认(第一笔链上交易,付一次 gas)
function submitAndConfirm(bytes32 txHash) external {
require(isOwner(msg.sender), "not owner");
require(!confirmed[txHash][msg.sender], "already confirmed");
confirmed[txHash][msg.sender] = true;
confirmations[txHash] += 1;
}
// 第二步:达到门限后执行(第二笔链上交易,再付一次 gas)
function execute(bytes32 txHash, address to, uint256 value) external {
require(confirmations[txHash] >= THRESHOLD, "not enough confirmations");
confirmations[txHash] = 0;
(bool ok, ) = to.call{value: value}("");
require(ok, "transfer failed");
}
}
对比阈值签名的链上视角:
阈值签名产生的交易:
from: 0xWallet(一个普通地址,没有合约代码)
to: 0xRecipient,value: 1 ether,data: 空
gas: 21000(基础转账,最便宜的形态)
链上一切正常:区块浏览器显示这是一笔普通的 EOA 转账;
没有任何合约被调用,没有事件日志暴露"多签"事实;
无法判断这个地址背后是单人、2-of-3 还是 5-of-9
6.2 隐私、可组合性与关键维度对比
多签的地址是合约,任何人都能读到 owners 列表与阈值,治理结构完全公开;阈值签名的地址是普通 EOA,链上零信息泄露——这是机构最看重的特性。在可组合性上,多签虽然任何 dApp 都能交互,但部分协议会显式拒绝合约地址;而阈值签名就是标准 ECDSA,对 dApp 而言是一个普通用户,可以参与任何 EOA 才能参与的活动,包括签名类登录(SIWE)。运维维度上,多签改 owner 集合需要一次公开可见的链上治理交易,而阈值签名的 reshare 完全在链下完成,地址不变、链上无感知。
| 维度 | 链上多签(如 Safe) | 阈值签名(MPC) |
| 维度 | 链上多签(如 Safe) | 阈值签名(MPC) |
|---|---|---|
| 链上地址类型 | 合约地址 | 普通 EOA 地址 |
| 每笔交易链上成本 | 高(合约调用 + 多笔确认) | 低(基础转账 21000 gas) |
| 签名者集合隐私 | 完全公开 | 完全隐藏 |
| 阈值隐私 | 公开 | 隐藏 |
| 验证方式 | 合约代码 | 标准 ECDSA/Schnorr |
| 对 dApp 的可见性 | 是合约,可能被限制 | 是普通用户 |
| 新增签名者 | 链上交易修改 owners | 链下 reshare,地址不变 |
| 实现复杂度 | 低(合约开源,直接部署) | 高(密码学协议 + 通信层) |
| 失败模式 | 合约漏洞、owner 密钥丢失 | 协议实现漏洞、通信中断 |
| 适用链 | 需要合约支持(EVM 友好) | 任何支持标准签名的链 |
| 信任假设 | 信任合约代码 | 信任密码学假设与实现 |
6.3 什么时候选哪个
选链上多签的场景:
- 需要链上可验证的治理结构(DAO 金库、协议多签)
- 参与者之间不信任彼此的软件实现,希望"规则写在链上"
- 需要时间锁、模块化扩展(Safe Guard、Zodiac 等生态)
选阈值签名的场景:
- 机构托管,需要隐藏签名者集合与治理结构
- 非 EVM 链(比特币 Taproot、Solana、Cosmos)上的统一密钥管理
- 需要频繁签名、gas 敏感(高频交易、批量归集)
- 需要链下密钥轮换而不改变地址
一句话总结:多签把安全模型放在链上让所有人监督,阈值签名把安全模型放在链下靠密码学保证——前者胜在透明可审计,后者胜在隐私、成本与跨链一致性。
7. 工程落地与安全边界
7.1 托管与非托管场景
MPC 在两类产品里的角色完全不同:
非托管钱包(用户自持分片):
- 典型配置:2-of-3,用户手机 1 份 + 用户云备份 1 份 + 服务商 1 份
- 服务商分片只"协助签名",无法单方面动用资产;用户丢手机可用备份恢复
- 关键设计:服务商必须实施策略引擎(限额、白名单、风控)后才参与签名
托管钱包(机构级密钥管理):
- 典型配置:3-of-5 或 5-of-9,分布在多个地理区域、多个云厂商
- 配合 HSM 存储分片、双人审批(dual control)、审计日志
- 关键设计:分片不能与"审批流"耦合过紧,否则可用性崩塌
共同点:分片存储与签名执行必须分离;签名请求必须过独立的策略层
7.2 移动端与服务端分片
移动端做 MPC 有几个绕不开的工程约束:
约束一:Paillier 运算在手机上很慢
GG18/GG20 的 MtA 需要 2048 位模幂,中端手机单次可达数十毫秒
优化:把 Paillier 运算放到服务端或改用更轻的方案(如 FROST)
约束二:移动网络不可靠,通信轮数直接决定失败率
跨洲 RTT 波动大,6 轮协议在弱网下失败率显著高于 2 轮
优化:优先选 FROST(2 轮),或把预处理提前到 Wi-Fi 环境下
约束三:手机是易失设备,分片必须可恢复
方案:分片加密后上传云;用社交恢复分散到信任联系人;或设备间复制
安全底线:分片绝不能明文落盘,必须用 Secure Enclave / Keystore 绑定;
分片解密应与生物识别绑定,防止设备被物理接触后静默签名
7.3 社交恢复与密钥刷新 reshare
社交恢复与 reshare 是 MPC 钱包相对多签的两个杀手级能力:
社交恢复(Social Recovery):
把 2-of-3 中的两份分片交给信任联系人(Guardians)保管
用户丢失设备时,联系其中 2 位即可恢复签名能力
与智能合约社交恢复(如 Argent)的区别:
合约方案:链上交易,公开可见,有 gas 成本
MPC 方案:链下协议,链上无痕,地址不变
密钥刷新(Proactive Reshare):
目标:在不改变群公钥与地址的前提下,更换分片集合或分片内容
用途:移除离职员工的分片、更换服务商或云厂商、定期刷新
协议要点:新多项式 F'(x) 满足 F'(0) = F(0),但对任意 x≠0,F'(x) ≠ F(x)
所有参与者用零知识证明"新旧分片对应同一私钥"
产出:新分片集合,旧分片全部作废,链上地址完全不变
reshare 的价值在于它把「密钥轮换」从一次昂贵的地址迁移,变成了一次链下协议交互。
7.4 攻击面与安全边界
MPC 钱包的攻击面比单签复杂得多,因为它引入了协议与通信两个新维度:
攻击面一:恶意参与者(Malicious Participant)
场景:t 个参与者中有 1 个作恶,试图在协作中提取他人分片
防御:协议须提供"可识别中止"(identifiable abort);广播值附带零知识范围证明
攻击面二:nonce 重用(Nonce Reuse)—— 最致命的实现 bug
若同一个 k 用于两条消息:s1 - s2 = k^{-1}(e1 - e2)
于是 k = (e1 - e2) / (s1 - s2) 泄露,进而 x = (s1·k - e1) / r 也泄露
防御:nonce 必须由 CSPRNG 生成且严格不复用;pre-signature 用后即焚
攻击面三:rogue key attack(针对多签与聚合签名)
场景:聚合公钥 X = X1 + X2,恶意者取 X2 = X2' - X1,使 X = X2',可单方伪造
防御:注册时要求"持有证明"(Proof of Possession);FROST/DKG 天然免疫
攻击面四:侧信道(Side Channel)
场景:通过时序、功耗、缓存命中分析反推 nonce 或分片
防御:常数时间实现;避免分支依赖秘密值;移动端分片放 Secure Enclave
攻击面五:通信信道认证(Channel Authentication)
场景:中间人篡改协议消息,替换承诺点或签名份额
防御:双向认证信道(mTLS 或签名握手);消息携带身份与序列号防重放
攻击面六:重放(Replay)
场景:录制一次合法的 pre-signature 或签名份额,在另一笔交易上重放
防御:签名请求绑定消息哈希与唯一会话 ID;维护已用会话拒绝列表
7.5 生产踩坑清单
1. 把 MPC 当"更贵的多签",忘了策略层
症状:分片齐全即签名成功,没有任何限额与风控
修复:签名前必须过独立的策略引擎(限额、白名单、时间锁)
2. 服务商分片与用户分片放在同一台设备上
症状:为了"备份方便",把两份分片都存进同一个云盘
修复:分片存储必须物理与逻辑双重隔离,否则 t-of-n 退化为 1-of-1
3. pre-signature 长期缓存且未绑定消息
症状:为降低延迟,预生成大量 pre-signature 循环复用
修复:pre-signature 用后即焚,且必须绑定唯一会话
4. 弱随机源生成 nonce
症状:移动端用 time() 或 Math.random() 派生 k
修复:强制 CSPRNG;有硬件随机源优先用硬件随机源
5. 忽略 DKG 的 complaint 机制
症状:某参与者分发的分片校验失败,但协议选择"跳过"
修复:严格实现 complaint 与剔除流程,任何不一致都要中止并追责
6. 跨洲部署却选了 6 轮协议,或把 reshare 实现成"重新 keygen + 迁移资产"
修复:就近部署参与方,改用 FROST / GG20 压低在线轮数;reshare 保持群公钥不变
一句话总结:MPC 钱包的安全边界不在密码学论文里,而在实现细节与运维流程中——nonce 管理、分片隔离、策略层与通信认证,任何一环失守都会让精妙的协议归零。
8. 总结
- 门限签名的本质:n 份分片协作产出与单签不可区分的签名,私钥从未被完整构造,链上完全隐形
- 数学底座:Shamir 秘密共享负责拆分,拉格朗日插值负责重组,DKG 负责在无可信第三方的前提下完成拆分
- 两条技术路线:Schnorr 的签名方程线性,所以 FROST 简洁优雅、两轮完成、与 Taproot 天然契合;ECDSA 含
k^{-1}·x非线性项,所以 GG18/GG20 必须引入 MtA 乘法协议 - GG20 的工程价值:把签名拆成"预处理 + 在线"两段,预处理可批量预生成,在线阶段只需单轮广播,让 MPC 钱包体验接近热钱包
- 与多签的分野:多签把规则写在链上,透明可审计但 gas 高、结构公开;阈值签名把规则藏进密码学,链上是一笔普通 EOA 转账,地址不变即可完成密钥轮换
- 落地关键:非托管场景重在分片隔离与社交恢复,托管场景重在 HSM、双人审批与地理分布;两者都必须有独立的策略层
- 安全边界:恶意参与者、nonce 重用、rogue key、侧信道、信道认证与重放是六大主战场,其中 nonce 重用是最容易犯也最致命的实现错误
- 选型建议:比特币生态与需要极致延迟的场景选 FROST;以太坊生态与需要机构级合规能力的场景选 GG20 系;需要链上可验证治理的场景仍应选多签
相关阅读
延伸阅读
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。