Zengo的安全多方计算ECDSAGG18和GG20代码详解
Zengo的安全多方计算
ZenGo-X 的 multi-party-ecdsa 是一个用 Rust 实现的 {t,n}-门限 ECDSA(椭圆曲线数字签名算法)的库。该库旨在实现分布式密钥生成和签名协议,使得在 n 个参与方中,至少 t+1 个参与方共同合作才能生成有效的签名。
各参与方共同生成密钥,每个参与方持有密钥份额,确保私钥从未在单一地点完整存在签名(Signing):至少 t+1 个持有密钥份额的参与方共同生成 ECDSA 签名
每种协议在参数、安全假设和效率方面各有权衡Lindell 17:适用于两方协议,签名速度快Gennaro、Goldfeder 19:支持任意阈值 t 和参与方数量 nCastagnos 等人 19:目前作为该库的一个特性,可通过启用 cclst 特性使用Gennaro、Goldfeder 20:支持识别恶意参与方的完整阈值协议
主要目录和文件包括:src/:包含核心源代码examples/:提供示例代码,展示库的使用方法docs/:包含项目文档benches/:包含性能测试代码Cargo.toml:Rust 项目的主要配置文件,声明依赖项和元数据README.md:项目简介和使用说明
cargo build --release --examples
cargo build --release --examples --no-default-features --features curv-kzen/num-bigint
./gg20_sm_manager
./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
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);
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()
};
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);
./gg20_signing -p 1,2 -d "sighash" -l local-share1.json
./gg20_signing -p 1,2 -d "sighash" -l local-share2.json
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"
}
}
curv:提供椭圆曲线、哈希承诺、秘密共享和其他加密原语。
paillier 和 zk_paillier:实现Paillier加密和相关零知识证明,用于秘密共享的安全加密和验证
serde:用于序列化和反序列化消息,方便多方通信
centipede:提供多方计算相关的分段工具和证明
#[derive(Debug)]
pub struct Parameters {
pub threshold: u16, // 门限值 t
pub share_count: u16, // 总参与方数 n
}
threshold:签名的最低参与方数量
share_count:总的参与方数量
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Keys<E: Curve = Secp256k1> {
pub u_i: Scalar<E>, // 私钥份额
pub y_i: Point<E>, // 对应的公钥份额
pub dk: DecryptionKey, // Paillier解密密钥
pub ek: EncryptionKey, // Paillier加密密钥
pub party_index: u16, // 参与方索引
}
pub fn create(index: u16) -> Self {
let u = Scalar::<Secp256k1>::random(); // 生成随机私钥
let y = Point::generator() * &u; // 计算公钥
let (ek, dk) = Paillier::keypair().keys(); // 生成Paillier密钥对
Self {
u_i: u,
y_i: y,
dk,
ek,
party_index: index,
}
}
pub fn phase1_broadcast_phase3_proof_of_correct_key(
&self,
) -> (KeyGenBroadcastMessage1, KeyGenDecommitMessage1) {
let blind_factor = BigInt::sample(SECURITY); // 随机生成盲因子
let correct_key_proof = NiCorrectKeyProof::proof(&self.dk, None); // 生成Paillier密钥的正确性证明
let com = HashCommitment::<Sha256>::create_commitment_with_user_defined_randomness(
&BigInt::from_bytes(self.y_i.to_bytes(true).as_ref()),
&blind_factor,
);
let bcm1 = KeyGenBroadcastMessage1 {
e: self.ek.clone(),
com,
correct_key_proof,
};
let decom1 = KeyGenDecommitMessage1 {
blind_factor,
y_i: self.y_i.clone(),
};
(bcm1, decom1)
}
广播消息
com:使用公钥份额 (y_i) 和盲因子生成的哈希承诺 - correct_key_proof:Paillier密钥正确性的零知识证明
com:使用公钥份额 (y_i) 和盲因子生成的哈希承诺
correct_key_proof:Paillier密钥正确性的零知识证明
解承诺消息
包括公钥份额 (y_i) 和盲因子
包括公钥份额 (y_i) 和盲因子
验证广播消息
pub fn phase1_verify_com_phase3_verify_correct_key_phase2_distribute(
&self,
params: &Parameters,
decom_vec: &[KeyGenDecommitMessage1],
bc1_vec: &[KeyGenBroadcastMessage1],
) -> Result<(VerifiableSS<Secp256k1>, Vec<Scalar<Secp256k1>>, u16), Error> {
let correct_key_correct_decom_all = (0..bc1_vec.len()).all(|i| {
HashCommitment::<Sha256>::create_commitment_with_user_defined_randomness(
&BigInt::from_bytes(decom_vec[i].y_i.to_bytes(true).as_ref()),
&decom_vec[i].blind_factor,
) == bc1_vec[i].com
&& bc1_vec[i]
.correct_key_proof
.verify(&bc1_vec[i].e, zk_paillier::zkproofs::SALT_STRING)
.is_ok()
});
let (vss_scheme, secret_shares) =
VerifiableSS::share(params.threshold, params.share_count, &self.u_i);
if correct_key_correct_decom_all {
Ok((vss_scheme, secret_shares.to_vec(), self.party_index))
} else {
Err(InvalidKey)
}
}
验证承诺
检查广播的哈希承诺与解承诺消息是否匹配 - 验证Paillier密钥的正确性证明
检查广播的哈希承诺与解承诺消息是否匹配
验证Paillier密钥的正确性证明
分发秘密共享
使用Feldman VSS生成秘密共享方案和密钥份额
pub fn phase2_verify_vss_construct_keypair_phase3_pok_dlog(
&self,
params: &Parameters,
y_vec: &[Point<Secp256k1>],
secret_shares_vec: &[Scalar<Secp256k1>],
vss_scheme_vec: &[VerifiableSS<Secp256k1>],
index: u16,
) -> Result<(SharedKeys, DLogProof<Secp256k1, Sha256>), Error> {
let correct_ss_verify = (0..y_vec.len()).all(|i| {
vss_scheme_vec[i]
.validate_share(&secret_shares_vec[i], index)
.is_ok()
&& vss_scheme_vec[i].commitments[0] == y_vec[i]
});
if correct_ss_verify {
let y: Point<Secp256k1> = y_vec.iter().sum(); // 聚合所有公钥份额
let x_i: Scalar<Secp256k1> = secret_shares_vec.iter().sum(); // 聚合所有私钥份额
let dlog_proof = DLogProof::prove(&x_i); // 生成离散对数证明
Ok((SharedKeys { y, x_i }, dlog_proof))
} else {
Err(InvalidSS)
}
}
验证 :检查所有VSS的正确性,确保秘密共享的完整性
生成共享密钥
聚合所有参与方的公钥份额和私钥份额 - 生成共享密钥对和离散对数证明
聚合所有参与方的公钥份额和私钥份额
生成共享密钥对和离散对数证明
生成本地签名
pub fn phase5_local_sig(
k_i: &Scalar<Secp256k1>,
message: &BigInt,
R: &Point<Secp256k1>,
sigma_i: &Scalar<Secp256k1>,
pubkey: &Point<Secp256k1>,
) -> Self {
let m_fe = Scalar::<Secp256k1>::from(message);
let r = Scalar::<Secp256k1>::from(
&R.x_coord()
.unwrap()
.mod_floor(Scalar::<Secp256k1>::group_order()),
);
let s_i = m_fe * k_i + r * sigma_i;
Self {
l_i: Scalar::<Secp256k1>::random(),
rho_i: Scalar::<Secp256k1>::random(),
R: R.clone(),
s_i,
m: message.clone(),
y: pubkey.clone(),
}
}
根据本地私钥份额和消息计算ECDSA部分签名 (s_i)
生成最终签名
pub fn output_signature(&self, s_vec: &[Scalar<Secp256k1>]) -> Result<SignatureRecid, Error> {
let s = &self.s_i + s_vec.iter().sum::<Scalar<Secp256k1>>(); // 聚合所有签名份额
let sig = SignatureRecid { r, s, recid };
let ver = verify(&sig, &self.y, &self.m).is_ok();
if ver {
Ok(sig)
} else {
Err(InvalidSig)
}
}
该代码的主要目的是完成 GG18 协议的密钥生成阶段,包括以下几个部分:
注册阶段 :通过 signup 获取参与方编号和会话唯一标识符 (UUID)
密钥生成阶段 :包括多个通信轮次,各参与方协作生成共享密钥
验证阶段 :验证每一轮通信数据的完整性和正确性
结果保存 :最终保存生成的共享密钥对和其他元数据
curv
secp256_k1:使用 secp256k1 曲线 - feldman_vss:实现 Feldman 的可验证秘密共享 (VSS)
提供椭圆曲线、秘密共享和加密原语。
核心模块
secp256_k1:使用 secp256k1 曲线
feldman_vss:实现 Feldman 的可验证秘密共享 (VSS)
提供椭圆曲线、秘密共享和加密原语。
核心模块
multi_party_ecdsa,包含 GG18 协议的实现
密钥生成 (KeyGen)。 - 签名 (Sign)
密钥生成 (KeyGen)。
签名 (Sign)
paillier:Paillier 加密库,用于加密秘密共享的密钥份额
reqwest:HTTP 客户端,用于多方之间的通信
serde: 用于序列化和反序列化 JSON 数据
let data = fs::read_to_string("params.json")
.expect("Unable to read params, make sure config file is present in the same folder ");
let params: Params = serde_json::from_str(&data).unwrap();
let PARTIES: u16 = params.parties.parse::<u16>().unwrap();
let THRESHOLD: u16 = params.threshold.parse::<u16>().unwrap();
从 params.json 文件中读取参与方数量 (PARTIES) 和阈值 (THRESHOLD)
PARTIES:总参与方数
THRESHOLD:最低签名方数
let (party_num_int, uuid) = match signup(&client).unwrap() {
PartySignup { number, uuid } => (number, uuid),
};
println!("number: {:?}, uuid: {:?}", party_num_int, uuid);
调用 signup 方法向服务器注册,获取当前参与方编号 (party_num_int) 和会话标识符 (uuid)
uuid 用于标识不同的密钥生成会话
let party_keys = Keys::create(party_num_int);
let (bc_i, decom_i) = party_keys.phase1_broadcast_phase3_proof_of_correct_key();
assert!(broadcast(
&client,
party_num_int,
"round1",
serde_json::to_string(&bc_i).unwrap(),
uuid.clone()
)
.is_ok());
调用 Keys::create 生成本地的密钥对
bc_i:广播消息,包含承诺
decom_i:解承诺消息,包含临时公钥
参与方通过 broadcast 将承诺消息发送给其他所有参与方
解承诺消息发送和验证
assert!(broadcast(
&client,
party_num_int,
"round2",
serde_json::to_string(&decom_i).unwrap(),
uuid.clone()
)
.is_ok());
let round2_ans_vec = poll_for_broadcasts(
&client,
party_num_int,
PARTIES,
delay,
"round2",
uuid.clone(),
);
参与方发送 decom_i 给其他所有参与方
每个参与方接收解承诺消息,验证承诺是否正确
let (head, tail) = point_vec.split_at(1);
let y_sum = tail.iter().fold(head[0].clone(), |acc, x| acc + x);
生成秘密共享并分发
let (vss_scheme, secret_shares, _index) = party_keys .phase1_verify_com_phase3_verify_correct_key_phase2_distribute( ¶ms, &decom_vec, &bc1_vec, ) .expect("invalid key");
调用 phase1_verify_com_phase3_verify_correct_key_phase2_distribute
验证承诺 - 生成 Feldman VSS 密钥份额
验证承诺
生成 Feldman VSS 密钥份额
加密并发送密钥份额
for (k, i) in (1..=PARTIES).enumerate() {
if i != party_num_int {
let key_i = &enc_keys[j];
let plaintext = BigInt::to_bytes(&secret_shares[k].to_bigint());
let aead_pack_i = aes_encrypt(key_i, &plaintext);
assert!(sendp2p(
&client,
party_num_int,
i,
"round3",
serde_json::to_string(&aead_pack_i).unwrap(),
uuid.clone()
)
.is_ok());
j += 1;
}
}
使用 AES 加密密钥份额,并通过点对点消息发送给其他参与方
assert!(broadcast(
&client,
party_num_int,
"round4",
serde_json::to_string(&vss_scheme).unwrap(),
uuid.clone()
)
.is_ok());
let (shared_keys, dlog_proof) = party_keys
.phase2_verify_vss_construct_keypair_phase3_pok_dlog(
¶ms,
&point_vec,
&party_shares,
&vss_scheme_vec,
party_num_int,
)
.expect("invalid vss");
assert!(broadcast(
&client,
party_num_int,
"round5",
serde_json::to_string(&dlog_proof).unwrap(),
uuid.clone()
)
.is_ok());
Keys::verify_dlog_proofs(¶ms, &dlog_proof_vec, &point_vec).expect("bad dlog proof");
let keygen_json = serde_json::to_string(&(
party_keys,
shared_keys,
party_num_int,
vss_scheme_vec,
paillier_key_vec,
y_sum,
))
.unwrap();
fs::write(env::args().nth(2).unwrap(), keygen_json).expect("Unable to save !");
初始化和注册 :读取之前生成的密钥,并进行参与方注册,确定当前签名参与方
多轮通信
每一轮通过广播或点对点 (P2P) 消息交换,完成必要的数据计算 - 包括零知识证明 (ZKP) 的生成和验证
每一轮通过广播或点对点 (P2P) 消息交换,完成必要的数据计算
包括零知识证明 (ZKP) 的生成和验证
ECDSA 签名生成
每个参与方计算部分签名 - 通过聚合所有签名份额生成最终签名
每个参与方计算部分签名
通过聚合所有签名份额生成最终签名
签名验证和保存 :验证生成的签名是否有效,并将结果保存到本地文件
读取密钥文件
let data = fs::read_to_string(env::args().nth(2).unwrap())
.expect("Unable to load keys, did you run keygen first? ");
let (party_keys, shared_keys, party_id, vss_scheme_vec, paillier_key_vector, y_sum): (
Keys,
SharedKeys,
u16,
Vec<VerifiableSS<Secp256k1>>,
Vec<EncryptionKey>,
Point<Secp256k1>,
) = serde_json::from_str(&data).unwrap();
从密钥文件加载以下数据
party_keys:当前参与方的密钥 - shared_keys:共享的公钥
vss_scheme_vec:可验证秘密共享方案
paillier_key_vector:Paillier 公钥列表,用于加密私钥份额
y_sum:共享公钥(所有公钥份额的和)
party_keys:当前参与方的密钥
shared_keys:共享的公钥
vss_scheme_vec:可验证秘密共享方案
paillier_key_vector:Paillier 公钥列表,用于加密私钥份额
y_sum:共享公钥(所有公钥份额的和)
读取参数文件
let data = fs::read_to_string("params.json")
.expect("Unable to read params, make sure config file is present in the same folder ");
let params: Params = serde_json::from_str(&data).unwrap();
let THRESHOLD = params.threshold.parse::<u16>().unwrap();
let (party_num_int, uuid) = match signup(&client).unwrap() {
PartySignup { number, uuid } => (number, uuid),
};
println!("number: {:?}, uuid: {:?}", party_num_int, uuid);
广播签名者 ID
assert!(broadcast(
&client,
party_num_int,
"round0",
serde_json::to_string(&party_id).unwrap(),
uuid.clone()
)
.is_ok());
let round0_ans_vec = poll_for_broadcasts(
&client,
party_num_int,
THRESHOLD + 1,
delay,
"round0",
uuid.clone(),
);
当前参与方广播自己的 ID (party_id)
调用 poll_for_broadcasts 收集所有签名者的 ID
存储签名者 ID
let mut signers_vec: Vec<u16> = Vec::new();
for i in 1..=THRESHOLD + 1 {
if i == party_num_int {
signers_vec.push(party_id - 1);
} else {
let signer_j: u16 = serde_json::from_str(&round0_ans_vec[j]).unwrap();
signers_vec.push(signer_j - 1);
j += 1;
}
}
生成并广播承诺
let (com, decommit) = sign_keys.phase1_broadcast();
let (m_a_k, _) = MessageA::a(&sign_keys.k_i, &party_keys.ek, &[]);
assert!(broadcast(
&client,
party_num_int,
"round1",
serde_json::to_string(&(com.clone(), m_a_k)).unwrap(),
uuid.clone()
)
.is_ok());
每个参与方生成
承诺消息 ( com ) :用于零知识证明 - 部分加密消息 ( m_a_k ) :用于多方安全加密协议
承诺消息 ( com ) :用于零知识证明
部分加密消息 ( m_a_k ) :用于多方安全加密协议
将这些数据广播给其他所有参与方
接收广播消息
let round1_ans_vec = poll_for_broadcasts(
&client,
party_num_int,
THRESHOLD + 1,
delay,
"round1",
uuid.clone(),
);
生成并发送点对点消息
for i in 1..THRESHOLD + 2 {
if i != party_num_int {
let (m_b_gamma, beta_gamma, _, _) = MessageB::b(
&sign_keys.gamma_i,
&paillier_key_vector[usize::from(signers_vec[usize::from(i - 1)])],
m_a_vec[j].clone(),
&[],
)
.unwrap();
let (m_b_w, beta_wi, _, _) = MessageB::b(
&sign_keys.w_i,
&paillier_key_vector[usize::from(signers_vec[usize::from(i - 1)])],
m_a_vec[j].clone(),
&[],
)
.unwrap();
m_b_gamma_send_vec.push(m_b_gamma);
m_b_w_send_vec.push(m_b_w);
beta_vec.push(beta_gamma);
ni_vec.push(beta_wi);
j += 1;
}
}
每个参与方生成两个消息:
m_b_gamma 和 m_b_w:基于安全加密协议生成,用于验证零知识证明
m_b_gamma 和 m_b_w:基于安全加密协议生成,用于验证零知识证明
使用 sendp2p 将这些消息发送给其他签名者
接收点对点消息
let round2_ans_vec = poll_for_p2p(
&client,
party_num_int,
THRESHOLD + 1,
delay,
"round2",
uuid.clone(),
);
计算部分签名
let delta_i = sign_keys.phase2_delta_i(&alpha_vec, &beta_vec);
let sigma = sign_keys.phase2_sigma_i(&miu_vec, &ni_vec);
每个参与方计算自己的部分签名:delta_i 和 sigma:通过加密数据和零知识证明生成
广播部分签名
assert!(broadcast(
&client,
party_num_int,
"round3",
serde_json::to_string(&delta_i).unwrap(),
uuid.clone()
)
.is_ok());
将部分签名广播给其他参与方
聚合部分签名
let delta_inv = SignKeys::phase3_reconstruct_delta(&delta_vec);
生成最终签名
let sig = local_sig
.output_signature(&s_i_vec)
.expect("verification failed");
println!("R: {:?}", sig.r);
println!("s: {:?} \n", sig.s);
调用 output_signature 方法生成完整的 ECDSA 签名
验证签名
check_sig(&sig.r, &sig.s, &message_bn, &y_sum);
验证生成的签名是否有效
保存签名
fs::write("signature", sign_json).expect("Unable to save !");
代码功能 :实现了 GG18 协议的多方 ECDSA 签名阶段,支持多个签名者协作生成有效的 ECDSA 签名。
主要步骤
初始化并注册参与方 - 多轮通信,包括广播和点对点消息交换
计算并聚合部分签名,生成最终签名
验证签名并保存结果。
初始化并注册参与方
多轮通信,包括广播和点对点消息交换
计算并聚合部分签名,生成最终签名
验证签名并保存结果。
适用场景 :高安全性多方签名,例如区块链钱包和托管服务。
协议由 Round 0 到 Round 4 五个阶段构成,每一轮完成特定的任务,并通过消息交互实现阶段性的结果传递。
Round0 :密钥初始化
每个参与方生成自己的密钥对和相关的承诺信息 - 广播承诺信息 (bc1)。
每个参与方生成自己的密钥对和相关的承诺信息
广播承诺信息 (bc1)。
Round1 :承诺验证
接收所有参与方的承诺信息 - 广播解承诺信息 (decom1),以证明承诺的正确性。
接收所有参与方的承诺信息
广播解承诺信息 (decom1),以证明承诺的正确性。
Round2 :验证承诺和分发共享
验证接收到的承诺和解承诺 - 分发秘密共享值 (VSS),用于门限加密
验证接收到的承诺和解承诺
分发秘密共享值 (VSS),用于门限加密
Round3 :计算共享密钥
验证分发的共享值 - 使用共享值生成Paillier加密密钥和零知识证明 (dlog_proof)
验证分发的共享值
使用共享值生成Paillier加密密钥和零知识证明 (dlog_proof)
广播零知识证明
Round4 :生成全局公钥
验证零知识证明 - 计算并存储全局公钥和每个参与方的本地密钥
验证零知识证明
计算并存储全局公钥和每个参与方的本地密钥
LocalKey
pub struct LocalKey<E: Curve> {
pub paillier_dk: paillier::DecryptionKey, // 本地Paillier解密密钥
pub pk_vec: Vec<Point<E>>, // 所有参与方的公钥向量
pub keys_linear: gg_2020::party_i::SharedKeys, // 共享的ECDSA密钥
pub paillier_key_vec: Vec<EncryptionKey>, // Paillier加密密钥向量
pub y_sum_s: Point<E>, // 全局共享公钥
pub h1_h2_n_tilde_vec: Vec<DLogStatement>, // 零知识证明的辅助数据
pub vss_scheme: VerifiableSS<E>, // 门限加密的共享验证数据
pub i: u16, // 当前参与方索引
pub t: u16, // 门限值
pub n: u16, // 总参与方数量
}
含义 :密钥生成协议结束后,每个参与方生成的本地密钥。
核心字段
paillier_dk 和 keys_linear:本地密钥数据,供后续分布式签名使用 - y_sum_s:全局公钥,用于签名验证
paillier_dk 和 keys_linear:本地密钥数据,供后续分布式签名使用
y_sum_s:全局公钥,用于签名验证
Msg:
Msg {
sender: u16, // 消息发送方
receiver: Option<u16>, // 消息接收方(None表示广播)
body: T, // 消息内容
}
含义 :用于多方之间的通信
功能 :支持广播和点对点消息
ProceedError
#[derive(Debug, Error)]
pub enum ProceedError {
#[error("round 2: verify commitments: {0:?}")]
Round2VerifyCommitments(ErrorType),
#[error("round 3: verify vss construction: {0:?}")]
Round3VerifyVssConstruct(ErrorType),
#[error("round 4: verify dlog proof: {0:?}")]
Round4VerifyDLogProof(ErrorType),
}
let party_keys = Keys::create(self.party_i as usize);
let (bc1, decom1) = party_keys.phase1_broadcast_phase3_proof_of_correct_key_proof_of_correct_h1h2();
output.push(Msg {
sender: self.party_i,
receiver: None,
body: bc1.clone(),
});
每个参与方生成本地密钥对,并计算承诺(bc1)和解承诺(decom1)
广播 bc1,为下一轮做准备
output.push(Msg {
sender: self.party_i,
receiver: None,
body: self.decom1.clone(),
});
广播解承诺信息 decom1
验证所有参与方的承诺正确性
let vss_result = self
.keys
.phase1_verify_com_phase3_verify_correct_key_verify_dlog_phase2_distribute(
¶ms,
&received_decom,
&self.received_comm,
)
.map_err(ProceedError::Round2VerifyCommitments)?;
验证每个参与方的承诺和解承诺
分发秘密共享值 VSS,为下一轮的密钥生成做准备
let (shared_keys, dlog_proof) = self
.keys
.phase2_verify_vss_construct_keypair_phase3_pok_dlog(
¶ms,
&self.y_vec,
&party_shares,
&vss_schemes,
self.party_i.into(),
)
.map_err(ProceedError::Round3VerifyVssConstruct)?;
output.push(Msg {
sender: self.party_i,
receiver: None,
body: dlog_proof.clone(),
});
验证并构建共享密钥
广播零知识证明 dlog_proof,证明密钥生成的正确性
Keys::verify_dlog_proofs_check_against_vss(
¶ms,
&dlog_proofs,
&self.y_vec,
&self.vss_vec,
)
.map_err(ProceedError::Round4VerifyDLogProof)?;
验证零知识证明
计算全局共享公钥 y_sum,并生成本地密钥 LocalKey
背景 :ECDSA签名是区块链、密码学中常用的签名算法。多方签名的目标是将签名操作分散到多个参与方之间,以避免单点故障并增强安全性
特点
分布式协作 :签名过程分成多个阶段,每个参与方在各自的本地完成部分操作,通过交换消息完成协作 - 门限属性 :仅需要 t 个参与方即可完成签名,而无需全部参与
分布式协作 :签名过程分成多个阶段,每个参与方在各自的本地完成部分操作,通过交换消息完成协作
门限属性 :仅需要 t 个参与方即可完成签名,而无需全部参与
零知识证明 :协议中涉及大量的零知识证明(如 PDLwSlackProof、HomoELGamalProof)以确保隐私性和安全性
pub struct LocalKey<E: Curve> {
pub paillier_dk: paillier::DecryptionKey, // Paillier解密密钥
pub pk_vec: Vec<Point<E>>, // 公钥向量(所有参与方)
pub keys_linear: gg_2020::party_i::SharedKeys, // 共享ECDSA密钥
pub y_sum_s: Point<E>, // 全局公钥
pub h1_h2_n_tilde_vec: Vec<DLogStatement>, // 零知识证明辅助数据
pub vss_scheme: VerifiableSS<E>, // Verifiable Secret Sharing (VSS)
pub i: u16, // 当前参与方索引
pub t: u16, // 门限值
pub n: u16, // 总参与方数量
}
功能 :存储多方ECDSA密钥生成过程中计算的本地密钥数据
用途 :提供签名阶段所需的本地密钥材料,如 y_sum_s(全局公钥)和 paillier_dk(用于门限加密的私钥)
消息类型
pub struct Msg<T> {
sender: u16, // 消息发送方
receiver: Option<u16>, // 消息接收方(None 表示广播)
body: T, // 消息内容
}
用于多方之间的通信,支持广播和点对点消息
协议中常用消息 :
MessageA 和 MessageB:参与方之间用于密钥协作和加密的消息 - GammaI 和 WI:多方生成和交换的中间签名值
DeltaI 和 TI:用于计算全局签名参数的消息
SI 和 HEGProof:最终签名和证明数据
MessageA 和 MessageB:参与方之间用于密钥协作和加密的消息
GammaI 和 WI:多方生成和交换的中间签名值
DeltaI 和 TI:用于计算全局签名参数的消息
SI 和 HEGProof:最终签名和证明数据
签名协议分为以下阶段:
功能
初始化本地签名密钥 - 生成并广播初始承诺信息 (MessageA 和 SignBroadcastPhase1)
初始化本地签名密钥
生成并广播初始承诺信息 (MessageA 和 SignBroadcastPhase1)
关键代码
let (bc1, decom1) = sign_keys.phase1_broadcast();
let party_ek = self.local_key.paillier_key_vec[usize::from(self.local_key.i - 1)].clone();
let m_a = MessageA::a(&sign_keys.k_i, &party_ek, &self.local_key.h1_h2_n_tilde_vec);
output.push(Msg {
sender: self.i,
receiver: None,
body: (m_a.0.clone(), bc1.clone()),
});
结果
广播初始承诺值 bc1 - 准备下一轮的解承诺值 decom1
广播初始承诺值 bc1
准备下一轮的解承诺值 decom1
功能
接收所有参与方的 MessageA 和 SignBroadcastPhase1 - 生成 GammaI 和 WI 消息并发送给其他参与方
接收所有参与方的 MessageA 和 SignBroadcastPhase1
生成 GammaI 和 WI 消息并发送给其他参与方
关键代码
for ((j, gamma_i), w_i) in party_indices.zip(m_b_gamma_vec).zip(m_b_w_vec) {
output.push(Msg {
sender: self.i,
receiver: Some(j),
body: (GammaI(gamma_i.clone()), WI(w_i.clone())),
});
}
功能
计算中间签名值 DeltaI 和 TI - 广播签名值和证明信息
计算中间签名值 DeltaI 和 TI
广播签名值和证明信息
关键代码
let delta_i = self.sign_keys.phase2_delta_i(&alpha_vec, &self.beta_vec);
let (t_i, l_i, t_i_proof) = SignKeys::phase3_compute_t_i(&sigma_i);
output.push(Msg {
sender: self.i,
receiver: None,
body: (
DeltaI(delta_i.clone()),
TI(t_i.clone()),
TIProof(t_i_proof.clone()),
),
});
结果
每个参与方计算自己的中间签名参数 DeltaI 和 TI - 准备下一阶段的 RDash 和 PDLwSlackProof
每个参与方计算自己的中间签名参数 DeltaI 和 TI
准备下一阶段的 RDash 和 PDLwSlackProof
功能
验证 DeltaI 和 TI - 使用 PDLwSlackProof 生成签名验证证明
验证 DeltaI 和 TI
使用 PDLwSlackProof 生成签名验证证明
关键代码
let R_dash = &R * &self.sign_keys.k_i;
output.push(Msg {
sender: self.i,
receiver: None,
body: (RDash(R_dash.clone()), phase5_proofs_vec.clone()),
});
结果
验证阶段签名参数
准备全局签名验证信息
功能
验证 SI 和 HEGProof - 计算ECDSA签名
验证 SI 和 HEGProof
计算ECDSA签名
关键代码
LocalSignature::phase6_verify_proof(
&S_i_vec,
&hegp_vec,
&R_vec,
&self.protocol_output.t_vec,
)
.map_err(Error::Round6VerifyProof)?;
LocalSignature::phase6_check_S_i_sum(&self.protocol_output.local_key.y_sum_s, &S_i_vec)
.map_err(Error::Round6CheckSig)?;
功能
生成完整的ECDSA签名 - 确保签名正确性。
生成完整的ECDSA签名
确保签名正确性。
关键代码
let local_signature = LocalSignature::phase7_local_sig(
&completed_offline_stage.sign_keys.k_i,
message,
&completed_offline_stage.R,
&completed_offline_stage.sigma_i,
&completed_offline_stage.local_key.y_sum_s,
);
let partial = PartialSignature(local_signature.s_i.clone());
特点
分步设计 :每个阶段实现独立功能,逻辑清晰
零知识证明 :保护隐私,验证参与方的正确性
门限安全 :仅需满足 t 门限即可完成签名
分步设计 :每个阶段实现独立功能,逻辑清晰
零知识证明 :保护隐私,验证参与方的正确性
门限安全 :仅需满足 t 门限即可完成签名
应用场景
多签钱包 :如区块链的去中心化交易所
分布式身份认证 :增强安全性
跨链协议 :实现多链协作签名
本文围绕 ZenGo-X 的 multi-party-ecdsa 库 ,深入解析了其在多方安全计算(MPC)领域的实现与应用,涵盖了 GG18 和 GG20 两种协议的核心流程。
ECDSA 是区块链与密码学中广泛应用的签名算法。通过多方计算技术,可以将签名操作分散到多个参与方之间,具有以下优势:
增强安全性 :私钥从未在单一地点完整存在,避免单点故障
支持门限签名 :仅需 t 个参与方即可完成签名,无需所有参与方参与
隐私保护 :零知识证明确保协议的隐私性和安全性
密钥生成(Key Generation)
通过多轮通信,参与方共同生成共享密钥对 - 支持 Paillier 加密和可验证秘密共享(VSS)技术
通过多轮通信,参与方共同生成共享密钥对
支持 Paillier 加密和可验证秘密共享(VSS)技术
签名(Signing)
至少 t+1 个参与方合作生成有效的ECDSA签名 - 使用零知识证明验证参与方操作的正确性
至少 t+1 个参与方合作生成有效的ECDSA签名
使用零知识证明验证参与方操作的正确性
GG18 协议
适用于多方门限签名 - 通过五轮通信完成密钥生成,支持零知识证明和 VSS
适用于多方门限签名
通过五轮通信完成密钥生成,支持零知识证明和 VSS
签名阶段包括多轮广播和点对点通信。
GG20 协议
相较 GG18,增强了对恶意参与方的防御能力 - 通过五轮通信完成密钥生成,支持 Paillier 加密和更严格的验证
签名阶段进一步优化了消息交互流程
相较 GG18,增强了对恶意参与方的防御能力
通过五轮通信完成密钥生成,支持 Paillier 加密和更严格的验证
签名阶段进一步优化了消息交互流程
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!