MPC 与阈值签名钱包:FROST、GG18 与门限签名的工程实践

从 Shamir 秘密共享与拉格朗日插值出发,系统讲解门限签名(t-of-n)的数学原理与分布式密钥生成 DKG,深入对比 Schnorr 门限方案 FROST 的两轮签名与 Taproot 契合度,以及 GG18/GG20 中基于 MtA 乘法三元组的 ECDSA 门限构造。文章给出 keygen、presign、sign 各阶段的通信轮数与延迟对照,横向比较多签与阈值签名在链上足迹、gas、隐私与可组合性上的差异,并覆盖托管与非托管钱包、社交恢复、密钥刷新 reshare 的落地细节,最后梳理恶意参与者、nonce 重用、rogue key、侧信道与信道认证等安全边界与生产踩坑。

导语:把私钥拆开,却仍然能签出一笔合法交易

传统钱包的模型简单粗暴:一把私钥对应一个地址,谁拿到私钥谁就拥有一切。而 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 的差异

维度GG18GG20
论文Gennaro-Goldfeder 2018Gennaro-Goldfeder 2020
签名轮数6 轮(含预处理)2 轮(预处理 + 在线)
恶意安全部分场景需要额外假设完整的可识别中止
预处理与签名强耦合解耦,可批量预生成
乘性转加性标准 MtAMtA + 范围证明优化
实践地位早期 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 轮数签名轮数在线延迟(同区域)在线延迟(跨洲)
FROSTEd25519/Secp256k1DKG 3 轮2 轮约 30-80 ms约 300-600 ms
GG18Secp256k13-4 轮6 轮约 200-500 ms约 1-3 s
GG20Secp256k13-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. 总结

  1. 门限签名的本质:n 份分片协作产出与单签不可区分的签名,私钥从未被完整构造,链上完全隐形
  2. 数学底座:Shamir 秘密共享负责拆分,拉格朗日插值负责重组,DKG 负责在无可信第三方的前提下完成拆分
  3. 两条技术路线:Schnorr 的签名方程线性,所以 FROST 简洁优雅、两轮完成、与 Taproot 天然契合;ECDSA 含 k^{-1}·x 非线性项,所以 GG18/GG20 必须引入 MtA 乘法协议
  4. GG20 的工程价值:把签名拆成"预处理 + 在线"两段,预处理可批量预生成,在线阶段只需单轮广播,让 MPC 钱包体验接近热钱包
  5. 与多签的分野:多签把规则写在链上,透明可审计但 gas 高、结构公开;阈值签名把规则藏进密码学,链上是一笔普通 EOA 转账,地址不变即可完成密钥轮换
  6. 落地关键:非托管场景重在分片隔离与社交恢复,托管场景重在 HSM、双人审批与地理分布;两者都必须有独立的策略层
  7. 安全边界:恶意参与者、nonce 重用、rogue key、侧信道、信道认证与重放是六大主战场,其中 nonce 重用是最容易犯也最致命的实现错误
  8. 选型建议:比特币生态与需要极致延迟的场景选 FROST;以太坊生态与需要机构级合规能力的场景选 GG20 系;需要链上可验证治理的场景仍应选多签

相关阅读


延伸阅读

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「blockchain」更多文章

  1. Rollup 序列器、L3 与应用链
  2. 账户抽象:ERC-4337、智能合约钱包与 Paymaster
  3. 零知识证明:zk-SNARKs、zk-STARKs、电路与隐私应用