MPC(Multi-PartyComputation,多方安全计算)是一种由多方共同参与的密码协议,确保在不泄露任何一方私有信息的前提下,完成如密钥生成、交易签名等敏感操作。整个过程是去中心化、安全且隐私保护的,在Web3安全体系中扮演着越来越关键的角色。
MPC(Multi-Party Computation,多方安全计算)是一种由多方共同参与的密码协议,确保在不泄露任何一方私有信息的前提下,完成如密钥生成、交易签名等敏感操作。整个过程是去中心化、安全且隐私保护的,在 Web3 安全体系中扮演着越来越关键的角色。
多方参与
门限签名(Threshold Signature)
强隐私性
高容错性(一定意义上的高可用)
只要参与节点数量不低于门限值,系统就能继续运作,不受个别节点宕机或掉线影响。
例如,在 10 个 MPC 节点中,签名门限为 6,那么只要不超过 3 个节点不可用,系统即可持续正常服务。
在安全模型上扩展支持 t-of-n(而非 GG18 的 n-of-n)门限签名,更适用于实际部署场景
实际参与 Keygen 和 Sign 协议计算
一般运行在 TEE(可信执行环境)中,确保密钥分片安全
节点之间通过 P2P 通信协议完成 MPC 共识交互
负责安全执行协议,是 MPC 网络的计算单元
y_sum = x * G = (x1 + x2 + ... + xn) * G
Keygen 完成标志:
所有节点将本地生成的 y_sum 上报给 tss-manager
当 tss-manager 检查所有节点的 y_sum 一致,说明本轮 Keygen 成功,且所有节点均参与其中
y_sum 是 MPC 网络中用于签名验证的公钥,是系统是否完成密钥生成的共识依据
这里其实还有很多方案,这里就不一一赘述了。
MPC 技术的一个重要落地场景就是 C 端钱包 —— 即面向个人用户的加密资产钱包。在这个领域,MPC 提供了无需私钥暴露的签名能力,并且可以实现高度灵活、安全性与可用性兼顾的密钥管理方案。
不同的门限设定与参与方数量,会极大影响钱包的用户体验、安全边界与运营能力。以下是几种常见的模型设计:
这是最基础的 MPC 钱包形式,只有两个参与方:用户 + 钱包服务方。
问题在于:服务方可以单方面作恶,理论上可以使用备份密钥参与签名,绕过用户操作
这种模型虽然看起来是“两方参与”,但安全性实质上接近中心化托管。适合新手用户或小额钱包场景。
优点:易恢复
缺点:服务方可作恶,非去中心化
用户和钱包服务各持一个密钥分片,必须同时在线参与签名
如果任意一方密钥丢失或服务不可用,就无法完成签名
这种模型安全性强,但缺乏容错能力,用户体验也差。例如用户换手机或卸载应用就等于丢失密钥,无法找回。
这是目前业内相对最优雅的解决方案,兼顾安全性、可恢复性、抗作恶能力。
三方分别是:
采用门限配置:3 个节点中任意 2 个参与签名即可(3-of-2)
所谓 “Trust Setup”,是指在密钥生成过程中,需要一个可信的初始化过程,让三方各自生成自己的密钥分片,并建立起初始共识。
这个过程通常只在首次创建钱包时进行,后续可以通过安全协议实现“门限重分片”,支持设备更换、恢复等操作
我们都知道,主流的 MPC 算法有一下几种
下面是开源的门限签名(Threshold Signature Scheme)库,以及它们所支持的特性。
ECDSA 签名在区块链应用非常广泛,比如 Bitcoin 和 Ethereum 都使用它进行签名。下表中列出一些开源的 ECDSA 门限签名库,以及这些库所支持的一些特性。
Schnorr 签名在区块链中也有广泛应用,Bitcoin 的 Taproot 就是一种 Schnorr 签名。此外,Schnorr 签名的变种 EdDSA(Ed25519)被 Solana/Cardano/Stellar/Near/Algorand/Tezos/Sui/Aptos 等采用。下表列出一些开源的 Schnorr 门限签名库,以及这些库所支持的一些特性。
从算法支持度,商用许可,代码库的安全性等多角度考量,最终我们建议选型如下
在选择适用于自身业务场景的 MPC 技术或库时,需从多个维度综合评估。以下是几个关键考量因素:
代码安全是 MPC 技术选型的底线,优先选择通过审计、社区广泛验证的库。
被大项目验证过的技术路线,风险更可控、生态更完善。
支持 ECDSA 是基础,EdDSA 是未来趋势,完整度决定扩展能力。
技术实力决定未来维护能力与响应速度。
紧急项目优先稳定、易用;长线项目可优先性能与灵活性。
该系统融合了以下三个关键要素:目标是构建一个由社区驱动、技术安全、激励兼容的去中心化签名 MPC 钱包托管平台。
我们将按照上面的流程用 ZenGo-X 的 multi-party-ecdsa 实现一个 Ethereum MPC 托管钱包 MPC,这里由于是 MVP, 我们将不给大家展示完整的源码,只讲一个简单的实现流程和代码片段,如果您想获得 MPC 托管钱包的实战代码,请联系 The Web3 社区获取,代码目前这个阶段并不开源,可能需要付一定费用才能拿到实战的源码。
下面我们基于 multi-party-ecdsa 的 example,并结合以太坊签名交易特征完成项目的 MVP 练习。
将 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);
构建交易,可以构建任意类型的交易,构建 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);
将上面签名得到的消息发送到区块链网络 请求示范
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
forge script script/TssGroupManager.s.sol:TssGroupManagerScript --rpc-url $URL--private-key $PrivateKey --broadcast
./tss parse-peer-id --pri-key $privataKey
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
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"
cast send --private-key a482a7dd30e44b8d18ded0ffe6ea0336d46cfad74c22da6a29686b8bdf344bd1 --rpc-url https://eth-holesky.g.alchemy.com/v2/BvSZ5ZfdIwB-5SDXMz8PfGcbICYQqwrl 0x0e852786C175885B8d9e4f07FCe285F84843E225 "setTssGroupMember(uint256,bytes[])" 2 '[27f6a4f53f5c7c09273542deeca916ca4c36e93910f7d09f706092579d7d5ad57256919102741c4a326793d7167efa4c30c03a1b2a66261e00c9f4697ec6b281,e8bab6f64f4d6f99c8abeb019466e8e09edc779e20ed4de43a1662fe1b6261a26b487350a35c42d73123ad5214ebb0567abfb83ffb492bb5683af403657971b9,fa3af3ad7a5c97e3b6bd8bc6dd5751c4b8ced139a2b3a62716f70560cf4a211e16cc977ebaf7f1a8f3bf81f8f0c9cdd717225311ecf7b7007f6458d7f56317cd]'
./tss manager -c manager.toml
./tss node -c node.toml
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="}
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!