钱包开发中 MPC 算法实践

  • Dapplink
  • 发布于 2025-04-17 20:41
  • 阅读 6

MPC(Multi-PartyComputation,多方安全计算)是一种由多方共同参与的密码协议,确保在不泄露任何一方私有信息的前提下,完成如密钥生成、交易签名等敏感操作。整个过程是去中心化、安全且隐私保护的,在Web3安全体系中扮演着越来越关键的角色。

一.MPC 概述

MPC(Multi-Party Computation,多方安全计算)是一种由多方共同参与的密码协议,确保在不泄露任何一方私有信息的前提下,完成如密钥生成、交易签名等敏感操作。整个过程是去中心化、安全且隐私保护的,在 Web3 安全体系中扮演着越来越关键的角色。

1.主要特点

  • 多方参与

    • Keygen 阶段:多方通过多轮共识协议共同生成一把“完整的密钥”(实际上是密钥的分片集合),各自只持有一部分密钥 share,没有人知道完整私钥。
    • Sign 阶段:多方协同完成签名运算,每一方都参与到签名计算过程中,最终生成一个合法的签名(例如标准的 ECDSA 签名)。
  • 门限签名(Threshold Signature)

    • 将私钥拆分为 K 份密钥 share,设置一个门限值 M,只要有任意 M 个参与方参与签名即可生成有效签名。
    • 例如:K = 10, M = 6,即 10 个节点中只需任意 6 个节点即可完成签名。
    • 这种机制兼具去中心化和高可用性。
  • 强隐私性

    • 每个 MPC 节点生成属于自己的密钥分片,节点之间无法相互推断彼此的私钥信息。
    • 在密钥生成和签名过程中,会使用同态加密(如 Paillier 加密)、密钥交换协议(如 Diffie-Hellman)和零知识证明等加密手段来确保协议安全和计算正确性。
  • 高容错性(一定意义上的高可用)

    • 只要参与节点数量不低于门限值,系统就能继续运作,不受个别节点宕机或掉线影响。

    • 例如,在 10 个 MPC 节点中,签名门限为 6,那么只要不超过 3 个节点不可用,系统即可持续正常服务。

2.应用场景

  • 隐私性要求极高的场合(如机密数据处理、隐私交易、隐私身份验证等)
  • MPC 共管钱包(多机构或多端协作管理资产)
  • MPC 托管钱包(如机构为用户提供安全的加密资产托管)
  • 需要多方协同参与验证的系统,例如 DAO、银行联盟链、分布式 KYC 等

3.主流算法(以 ZenGo 使用的 ECDSA MPC 库为例)

3.1.GG18(Gennaro-Goldfeder 2018)

  • Keygen(密钥生成)
    • 需要 5 轮交互完成密钥分片的生成
    • 输出 y_sum:完整的 ECDSA 公钥(所有分片对应的公钥加和)
  • Sign(签名)
    • 总共需要 10 轮交互完成一次 ECDSA 签名过程
    • 使用 Paillier 加密进行密文下乘法运算,结合零知识证明防作弊

3.2.GG20(Gennaro-Goldfeder 2020 优化版)

  • Keygen
    • 同样是 5 轮交互
    • 过程结构与 GG18 接近,但对协议进行了严谨的安全形式化
  • Sign
    • 仅需 8 轮交互,优化了 GG18 中某些冗余步骤,整体效率更高

在安全模型上扩展支持 t-of-n(而非 GG18 的 n-of-n)门限签名,更适用于实际部署场景

二. MPC 在项目里面怎么用

1. MPC 网络角色

1.1.tss-manager(中心协调服务)

  • 负责调度和协调 MPC 协议执行,控制整个签名流程
  • 核心职责:
    • 广播 Keygen 请求:启动密钥生成流程,每个节点生成自己的私钥分片,并共享同一个公钥
    • 广播签名请求:分发待签名消息,收集签名结果并返回给钱包系统
  • 控制入口、调度核心,是 MPC 网络的大脑

1.2.tss-node(共识计算节点)

  • 实际参与 Keygen 和 Sign 协议计算

  • 一般运行在 TEE(可信执行环境)中,确保密钥分片安全

  • 节点之间通过 P2P 通信协议完成 MPC 共识交互

  • 负责安全执行协议,是 MPC 网络的计算单元

2.MPC 托管系统实施思路

a5c795bfe0201444335743db6d4e800b.png

  • 每个 tss-node 节点会生成一个密钥分片(share),所有分片共同组成一把完整私钥
  • 这些私钥分片满足门限签名特性,即:只需要任意 M 份分片即可恢复出完整私钥用于签名,无需全部参与
  • 每个节点根据自身密钥分片,计算出统一的公钥 y_sum,即完整私钥对应的公钥:
y_sum = x * G = (x1 + x2 + ... + xn) * G
  • Keygen 完成标志:

    • 所有节点将本地生成的 y_sum 上报给 tss-manager

    • 当 tss-manager 检查所有节点的 y_sum 一致,说明本轮 Keygen 成功,且所有节点均参与其中

    • y_sum 是 MPC 网络中用于签名验证的公钥,是系统是否完成密钥生成的共识依据

3.tss-manage 和 tss-node 常见的权限控制方式

  • 方案一:有过期时间的 JWT 验证
  • 方案二: tss-manage 维护一个密钥对,密钥定期更换, 用于签名验证,如下图

  • 方案三:TEE 环境,只有特定权限的人才能访问,只需要控制好 tss-manager 的入口

这里其实还有很多方案,这里就不一一赘述了。

三.MPC 在 C 端钱包的实践设计

MPC 技术的一个重要落地场景就是 C 端钱包 —— 即面向个人用户的加密资产钱包。在这个领域,MPC 提供了无需私钥暴露的签名能力,并且可以实现高度灵活、安全性与可用性兼顾的密钥管理方案。

不同的门限设定与参与方数量,会极大影响钱包的用户体验、安全边界与运营能力。以下是几种常见的模型设计:

1.两方计算(2-of-2 或 2-of-1)

这是最基础的 MPC 钱包形式,只有两个参与方:用户 + 钱包服务方。

1.1 [2-of-1 模型](其实是备份,不是真正 MPC)

  • 用户设备保留一份密钥 share,钱包服务方保留一份备份
  • 在本地密钥丢失时,钱包服务可以协助恢复

问题在于:服务方可以单方面作恶,理论上可以使用备份密钥参与签名,绕过用户操作

这种模型虽然看起来是“两方参与”,但安全性实质上接近中心化托管。适合新手用户或小额钱包场景。

  • 优点:易恢复

  • 缺点:服务方可作恶,非去中心化

1.2 [2-of-2 模型](真正的双方 MPC)

用户和钱包服务各持一个密钥分片,必须同时在线参与签名

如果任意一方密钥丢失或服务不可用,就无法完成签名

这种模型安全性强,但缺乏容错能力,用户体验也差。例如用户换手机或卸载应用就等于丢失密钥,无法找回。

  • 优点:安全性强,无单点控制
  • 缺点:无恢复机制,易丢资产

2 三方计算(3-of-2) + Trust Setup 模型(推荐)

这是目前业内相对最优雅的解决方案,兼顾安全性、可恢复性、抗作恶能力。

2.1.模型设计:

三方分别是:

  • 用户设备(如手机 App,本地运行 tss-node)
  • 钱包服务方(运行在 TEE 中的 MPC 节点)
  • 独立第三方机构(如审计公司、保险机构、银行等,也运行在 TEE 中)

采用门限配置:3 个节点中任意 2 个参与签名即可(3-of-2)

  • 优点详解:
    • 可恢复性:用户设备丢失时,钱包 + 第三方可以协助重建密钥
    • 防作恶能力:钱包服务 + 第三方无法联手作恶,因为他们无法伪造用户发起签名的操作意图
    • 隐私与安全兼顾:用户密钥不会上传或集中托管,第三方机构也看不到任何用户明文数据
    • 支持权限分层:可对不同类型交易(如转账 vs 授权)设定不同的签名门限或参与方组合

2.2.Trust Setup 的意义

所谓 “Trust Setup”,是指在密钥生成过程中,需要一个可信的初始化过程,让三方各自生成自己的密钥分片,并建立起初始共识。

这个过程通常只在首次创建钱包时进行,后续可以通过安全协议实现“门限重分片”,支持设备更换、恢复等操作

四. MPC 技术选型

1. 前置知识

我们都知道,主流的 MPC 算法有一下几种

  • GG18: Gennaro and Goldfeder, 2018
  • GG20: Gennaro and Goldfeder, 2020
  • DKLS: 分布式密钥生成
  • DKLS19: 改进版 DKLS
  • CGGMP: 分布式密钥生成
  • CCLST: 跨链轻量级协议
  • DMZ+21: 高效密钥生成和签名
  • Stinson and Strobl:鲁棒门限加密
  • FROST: Schnorr 门限签名, 主要使用在比特币网络,Web3 钱包

2.主流的开源库

  • Zengo
    • 支持 ECDSA 和 EDDSA
    • 支持 GG18 和 GG20
  • CoinBase kryptology
    • 支持 ECDSA 和 EDDSA
    • GG20 和 DKLS
  • Binance tss-lib
    • 仅仅支持 GG18
    • SwingBy Protocol fork tss-lib 支持 GG20
    • 支持 ECDSA 和 EDDSA
  • Fireblocks
    • 支持 CMP 和 CGGMP
    • 仅仅支持 ECDSA
  • AMIS
    • 支持 ECDSA 和 EDDSA
    • 支持 GG18, GG20, CCLST 和 CGGMP

3. MPC 开源代码库简介

下面是开源的门限签名(Threshold Signature Scheme)库,以及它们所支持的特性。

3.1 Threshold ECDSA

ECDSA 签名在区块链应用非常广泛,比如 Bitcoin 和 Ethereum 都使用它进行签名。下表中列出一些开源的 ECDSA 门限签名库,以及这些库所支持的一些特性。

a6601fc10b0ef332edf75a2e195c9781.png

3.2 Threshold Schnorr (Ed25519, Taproot)

348c6e3080220c3bda54442cb22867a7.png Schnorr 签名在区块链中也有广泛应用,Bitcoin 的 Taproot 就是一种 Schnorr 签名。此外,Schnorr 签名的变种 EdDSA(Ed25519)被 Solana/Cardano/Stellar/Near/Algorand/Tezos/Sui/Aptos 等采用。下表列出一些开源的 Schnorr 门限签名库,以及这些库所支持的一些特性。

3.3.底层代码库选型

从算法支持度,商用许可,代码库的安全性等多角度考量,最终我们建议选型如下

4. MPC 技术选型总结

在选择适用于自身业务场景的 MPC 技术或库时,需从多个维度综合评估。以下是几个关键考量因素:

4.1. 代码库的安全性

  • 是否经过专业第三方审计
  • 是否有公开披露和修复历史漏洞的记录
  • 是否有良好的安全响应机制和版本更新维护频率

代码安全是 MPC 技术选型的底线,优先选择通过审计、社区广泛验证的库。

4.2. 使用广泛度(社区与应用)

  • 是否被头部项目或厂商采纳(如 ZenGo、Fireblocks、Coinbase 等)
  • 是否有公开的 GitHub 仓库、社区活跃度、issue 处理响应速度
  • 是否有案例落地或商业化产品支撑

被大项目验证过的技术路线,风险更可控、生态更完善。

4.3. 算法支持与完整度

  • 是否支持主流算法:ECDSA、EdDSA(Schnorr)
  • 是否支持不同门限模型(如 n-of-n、t-of-n)
  • 是否可扩展支持其它曲线(如 secp256k1、ed25519)

支持 ECDSA 是基础,EdDSA 是未来趋势,完整度决定扩展能力。

4.4. 团队技术背景

  • 团队的核心成员是否有密码学/区块链/TEE 实战背景
  • 使用的语言和技术栈是否与自身项目兼容(如 Rust、Go、Node.js 等)
  • 是否有参与学术研究、开源社区、标准化制定

技术实力决定未来维护能力与响应速度。

4.5. 项目紧急程度

  • 如果项目上线时间紧、团队人手有限,建议优先使用已有成熟 SDK / SaaS 服务
  • 若有更长技术准备期,可考虑自研或定制集成门限协议(如 GG20、FROST)

紧急项目优先稳定、易用;长线项目可优先性能与灵活性。

五.如何设计一个 MPC 托管系统

1.架构概述

该系统融合了以下三个关键要素:目标是构建一个由社区驱动、技术安全、激励兼容的去中心化签名 MPC 钱包托管平台。

  • MPC/TSS 网络:用于安全签名操作(如钱包转账、质押管理)
  • 治理机制:通过治理合约 & 治理投票选出可信的 TSS 运维节点(Operators)
  • 经济激励模型:通过质押与奖励/惩罚机制引导 Operator 行为

2.业务流程拆解

2.1.治理与角色选择

  • 社区用户通过 治理投票合约 在以太坊上投票,选出 Operator 候选人
  • Stakers 通过 staking Token + delegateTo 将投票权授权给某个运营者
  • 运营者(Operators)通过调用链上 registerAsOperator 接口注册成为 TSS 网络中的节点运营商
  • 投票人获得奖励或承受惩罚(reward/slashing)取决于所选 Operator 的行为表现

2.2.TSS 网络构建与启动

  • 每个 Operator 启动自己的 tss-node(运行在 TEE 等安全环境)
  • 所有 tss-node 节点共同组成 TSS 网络,进行密钥管理与签名共识
  • 节点通过 P2P 协议进行通信与共识协作

2.3. 密钥生成(Keygen 阶段)

  • tss-manager 发起 keygen 请求,广播给所有 TSS 节点
  • 各节点协同执行 Keygen 协议,生成密钥分片并计算出统一公钥 y_sum
  • 成功后将 y_sum 上报给 tss-manager
  • 一旦所有节点 y_sum 一致,说明本轮密钥生成成功

2.4. 签名过程(Sign 阶段)

  • 用户钱包发起签名请求,通过 tss-manager 分发
  • TSS 网络根据动态委员会机制(可支持轮换)选出 N 个节点参与签名(满足门限值)
  • 多节点共同计算签名结果,返回给 tss-manager,再返回给钱包使用
  • 签名过程是链下 MPC 协作,结果在链上可被验证(兼容 ECDSA / EdDSA)

2.5. 奖励与惩罚机制

  • 若节点在 TSS 网络中作恶(如签名失败、故意拒绝服务等),通过链上的 acceptSlash 和经济激励合约实施惩罚
  • 表现良好的节点将获得 staking 奖励

六.基于 Zengo 运行签名测试

1.Zengo 的代码库

2.运行测试

我们将按照上面的流程用 ZenGo-X 的 multi-party-ecdsa 实现一个 Ethereum MPC 托管钱包 MPC,这里由于是 MVP, 我们将不给大家展示完整的源码,只讲一个简单的实现流程和代码片段,如果您想获得 MPC 托管钱包的实战代码,请联系 The Web3 社区获取,代码目前这个阶段并不开源,可能需要付一定费用才能拿到实战的源码。

下面我们基于 multi-party-ecdsa 的 example,并结合以太坊签名交易特征完成项目的 MVP 练习。

2.1. 生成账户地址

将 multi-party-ecdsa 的代码库编译构建之后,启动 gg20 manager, 命令如下:

./gg20_sm_manager

模拟三个节点 keygen, 命令如下:

./gg20_keygen -t 1 -n 3 -i 1 --output local-share1.json
./gg20_keygen -t 1 -n 3 -i 2 --output local-share2.json
./gg20_keygen -t 1 -n 3 -i 3 --output local-share3.json

生成的 keygen 文件里面有一个叫 y_sum_s 的字段,这里面的 point 对应的就是三个节点掌管的私钥对应的公钥,通过下面代码可以生成地址,代码如下:

const y_sum_s = [
    3, 51, 74, 205, 71, 16, 34, 12, 190, 49, 191, 131,
    245, 158, 114, 173, 238, 162, 120, 125, 221, 191,
    128, 106, 146, 177, 243, 86, 18, 254, 233, 50, 118
];
const address = ethers.utils.computeAddress("0x" + Buffer.from(y_sum_s).toString("hex"))
console.log("wallet address:", address);

2.2. 交易签名

构建交易,可以构建任意类型的交易,构建 Legacy 类型

let tx = TransactionRequest {
    chain_id: Option::from(U64::from(10)),
    to: Some(NameOrAddress::from(wallet.address())),
    value: Some(U256::from(100000000000u64)), // 1 ETH
    gas: Some(U256::from(21000)),
    gas_price: Some(U256::from(10000000000u64)), // 20 Gwei
    nonce: Some(U256::from(0)),
    ..Default::default()
};

如果想构建 EIP1559 交易,使用下面代码

let tx1559 = Eip1559TransactionRequest {
    chain_id: Option::from(U64::from(10)),
    to: Some(NameOrAddress::from(wallet.address())),
    value: Some(U256::from(100000000000u64)), // 1 ETH
    gas: Some(U256::from(21000)),
    max_fee_per_gas: Some(U256::from(10000000000u64)), // 20 Gwei
    max_priority_fee_per_gas: Some(U256::from(10000000000u64)), // 20 Gwei
    nonce: Some(U256::from(0)),
    ..Default::default()
};

构建待签名的消息摘要, 代码如下:

let chain_id = tx.chain_id().map(|id| id.as_u64()).unwrap_or(self.chain_id);
let mut tx = tx.clone();
tx.set_chain_id(chain_id);
let sighash = tx.sighash();
println!("Tx Sign Hash: {:?}", sighash);

将 sign hash 拿去给 MPC 网络签名

./gg20_signing -p 1,2 -d "sighash" -l local-share1.json
./gg20_signing -p 1,2 -d "sighash" -l local-share2.json

使用 r, s 构建完整的交易

let signed_tx = Transaction {
    hash: Default::default(),
    nonce: U256::from(0),
    block_hash: None,
    block_number: None,
    transaction_index: None,
    gas_price: Option::from(U256::from(10000000000u64)),
    gas: U256::from(21000),
    to: Option::from(wallet.address()),
    value: U256::from(100000000000u64),
    v: U64::from(signature.v),
    r: signature.r,
    s: signature.s,
    transaction_type: None,
    access_list: None,
    max_priority_fee_per_gas: None,
    max_fee_per_gas: None,
    chain_id: None,
    from: Default::default(),
    input: Default::default(),
    other: Default::default(),
};
println!("{:?}", signed_tx.rlp());

从签名信息里面恢复地址代码如下:

const convertHashToHex = (value) => {
    return value.map(v => v.toString(16).padStart(2, '0')).join('');
}
const r = "0x" + convertHashToHex([38,197,206,18,56,101,23,224,63,131,52,210,1,129,193,227,250,26,43,168,54,154,241,18,51,125,21,186,218,126,144,249])
const s = "0x" + convertHashToHex([33,26,215,163,100,30,208,235,66,20,231,175,68,154,183,100,230,211,218,117,115,71,118,238,183,162,169,117,76,61,103,179])
const recid = 0
const expandedSig = {
    r: r,
    s: s,
    v: recid
}
const recoveredAddress = ethers.utils.recoverAddress(Buffer.from("f87fde81fd3fa0e152252a45467b32faeac8377b4e6580c9ac06cc6ee82240bb"), expandedSig)
console.log(recoveredAddress);

2.3. 发送交易到区块链网络

将上面签名得到的消息发送到区块链网络 请求示范

curl --location 'https://eth-mainnet.g.alchemy.com/v2/XZw9s8EsSyUtwDOjtVvzwL8N0T96Zxt0' \
--header 'Content-Type: application/json' \
--header 'Cookie: _cfuvid=A7vae8DmfdNdKLQ37u_mDw17rqRDDuKqXFrJuWD1ccA-1716725090138-0.0.1.1-604800000' \
--data '{
    "jsonrpc":"2.0",
    "method":"eth_sendRawTransaction",
    "params":["0x"],
    "id":1
}'

返回值

{
    "jsonrpc": "2.0",
    "id": 1,
    "error": {
        "code": -32000,
        "message": "typed transaction too short"
    }
}

返回值为交易 Hash

六.运行 DappLink 基础开源版本的 MPC 签名系统

1.代码库

2.运行流程

2.1.部署 tss 合约

forge script script/TssGroupManager.s.sol:TssGroupManagerScript --rpc-url $URL--private-key $PrivateKey --broadcast

2.2. Bootnode

./tss parse-peer-id --pri-key $privataKey

2.3. 环境变量配置

  • tss-node 配置
l1_url = "https://eth-holesky.g.alchemy.com/v2/BvSZ5ZfdIwB-5SDXMz8PfGcbICYQqwrl"
time_task_interval = "10s"
l1_receipt_confirm_timeout = "30s"
l1_confirm_blocks = "1"
tss_group_contract_address = "0xcd3fc9E7d3454aEAcfaaf6cbe802E86BEd87563a"
timed_task_interval = "10s"
l1_start_block_number = 2808260
miss_signed_number = 5

[node]
db_dir = "/Users/guoshijiang/.tssnode0/db"
base_dir = "/Users/guoshijiang/.tssnode0"
ws_addr = "tcp://127.0.0.1:8081"
http_addr = ":8082"
l2_eth_rpc = "http://bitnetwork-l2geth"
pre_param_file = ""
p2p_port = "8001"
bootstrap_peers="/ip4/127.0.0.1/tcp/8001/p2p/16Uiu2HAmFLzPFGjw46Ljr1KqVdDmRNGYQE2o3MqZATpKJMbg9Kg8,/ip4/127.0.0.1/tcp/8001/p2p/16Uiu2HAmUKU4tn3uVZa9rhjxfQ3eCq7iXGPGFFg42iHCwTTC6apZ,/ip4/127.0.0.1/tcp/8003/p2p/16Uiu2HAmVVnQsZKtzeyyoPPdYqKQK5YQ2G83iyPC4JC2pPVFTYry"
external_ip = "127.0.0.1"
join_party_timeout = "60s"
key_gen_timeout = "60s"
key_sign_timeout = "60s"
pre_param_timeout = "5m0s"
private_key = ""
gas_limit_scaler = 15
  • tss-manager 配置
l1_url = "https://eth-holesky.g.alchemy.com/v2/BvSZ5ZfdIwB-5SDXMz8PfGcbICYQqwrl"
time_task_interval = "10s"
l1_receipt_confirm_timeout = "30s"
l1_confirm_blocks = "1"
tss_group_contract_address = "0xcd3fc9E7d3454aEAcfaaf6cbe802E86BEd87563a"
timed_task_interval = "10s"
l1_start_block_number = 2808260
miss_signed_number = 5

[manager]
private_key = ""
ws_addr = "tcp://127.0.0.1:8081"
http_addr = "127.0.0.1:8080"
db_dir = "/Users/guoshijiang/.tssmanager/db"
keygen_timeout = "120s"
cpk_confirm_timeout = "2h"
ask_timeout = "60s"
sign_timeout = "60s"

2.4. 手动注册 tss 节点个数,签名节点阀值

cast send --private-key a482a7dd30e44b8d18ded0ffe6ea0336d46cfad74c22da6a29686b8bdf344bd1 --rpc-url https://eth-holesky.g.alchemy.com/v2/BvSZ5ZfdIwB-5SDXMz8PfGcbICYQqwrl 0x0e852786C175885B8d9e4f07FCe285F84843E225 "setTssGroupMember(uint256,bytes[])" 2 '[27f6a4f53f5c7c09273542deeca916ca4c36e93910f7d09f706092579d7d5ad57256919102741c4a326793d7167efa4c30c03a1b2a66261e00c9f4697ec6b281,e8bab6f64f4d6f99c8abeb019466e8e09edc779e20ed4de43a1662fe1b6261a26b487350a35c42d73123ad5214ebb0567abfb83ffb492bb5683af403657971b9,fa3af3ad7a5c97e3b6bd8bc6dd5751c4b8ced139a2b3a62716f70560cf4a211e16cc977ebaf7f1a8f3bf81f8f0c9cdd717225311ecf7b7007f6458d7f56317cd]'

2.5. 启动网络

./tss manager -c manager.toml
./tss node  -c node.toml

2.6. 调度签名

  • 请求示范
curl --location '127.0.0.1:8080/api/v1/sign/state' \
--header 'Content-Type: application/json' \
--data '{
    "message_hash": "747ffbb4181879990f0aebe5ca43aab69af63911f2efd30e91ef61274e5dbdb4",
    "election_id": 1
}'
  • 返回值
{"signature":"wmH+bg9HuukRnV7QkUCdZE/xOz+G6uYHBt0efa4J9Qddw5BBS8njfW6q2VHO7pPze2BPFljt+Ao7Q5jjkoUFKQA="}
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
Dapplink
Dapplink
0xBdcb...f214
首个模块化、可组合的Layer3协议。