Zengo 的安全多方计算 ECDSA GG18 和 GG20 代码详解

  • Dapplink
  • 发布于 2024-11-30 10:35
  • 阅读 35

Zengo的安全多方计算ECDSAGG18和GG20代码详解

Zengo的安全多方计算

ECDSA GG18和GG20代码详解

一. 关于 Zengo 的安全多方计算库 ECDSA    

ZenGo-X 的 multi-party-ecdsa 是一个用 Rust 实现的 {t,n}-门限 ECDSA(椭圆曲线数字签名算法)的库。该库旨在实现分布式密钥生成和签名协议,使得在 n 个参与方中,至少 t+1 个参与方共同合作才能生成有效的签名。

1. 主要功能密钥生成(Key Generation):

各参与方共同生成密钥,每个参与方持有密钥份额,确保私钥从未在单一地点完整存在签名(Signing):至少 t+1 个持有密钥份额的参与方共同生成 ECDSA 签名

2. 支持的协议该库实现了多种门限 ECDSA 协议,

每种协议在参数、安全假设和效率方面各有权衡Lindell 17:适用于两方协议,签名速度快Gennaro、Goldfeder 19:支持任意阈值 t 和参与方数量 nCastagnos 等人 19:目前作为该库的一个特性,可通过启用 cclst 特性使用Gennaro、Goldfeder 20:支持识别恶意参与方的完整阈值协议

3. 项目结构该项目遵循标准的 Rust 项目布局,

主要目录和文件包括:src/:包含核心源代码examples/:提供示例代码,展示库的使用方法docs/:包含项目文档benches/:包含性能测试代码Cargo.toml:Rust 项目的主要配置文件,声明依赖项和元数据README.md:项目简介和使用说明

4. 该代码库的使用方法

4.1 构建项目要使用该库,首先需要在系统中安装 Rust 和 GMP 库。然后,可以通过以下步骤进行构建和运行示例:构建项目

cargo build --release --examples
  • 如果未安装 GMP 库,可使用以下命令构建,但效率可能较低
cargo build --release --examples --no-default-features --features curv-kzen/num-bigint

4.2 运行项目运行 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);

4.3 交易签名构建交易,可以构建任意类型的交易,构建 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);

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

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
}

返回值,返回值为交易 Hash

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

二、GG18代码详解

1. 核心代码库

1.1核心依赖

  • curv:提供椭圆曲线、哈希承诺、秘密共享和其他加密原语。

  • paillier 和 zk_paillier:实现Paillier加密和相关零知识证明,用于秘密共享的安全加密和验证

  • serde:用于序列化和反序列化消息,方便多方通信

  • centipede:提供多方计算相关的分段工具和证明

1.2核心结构和方法1.2.1 参数结构

#[derive(Debug)]
pub struct Parameters {
    pub threshold: u16,   // 门限值 t
    pub share_count: u16, // 总参与方数 n
}
  • threshold:签名的最低参与方数量

  • share_count:总的参与方数量

1.2.2 密钥生成(keys)结构定义

#[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,
    }
}
  • 功能: 为参与方生成随机的ECDSA密钥份额 (u_i, y_i) 和Paillier加密密钥 (ek, dk)。

1.2.3 第一阶段:广播与解承诺生成广播和解承诺消息

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生成秘密共享方案和密钥份额
  • 使用Feldman VSS生成秘密共享方案和密钥份额

1.2.4 第二阶段:共享密钥生成

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的正确性,确保秘密共享的完整性

生成共享密钥

  • 聚合所有参与方的公钥份额和私钥份额 - 生成共享密钥对和离散对数证明

  • 聚合所有参与方的公钥份额和私钥份额

  • 生成共享密钥对和离散对数证明

1.2.5 签名阶段

生成本地签名

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)
    }
}
  • 聚合所有参与方的签名份额 (s_vec),生成完整的ECDSA签名

2. Keygen

2.1 核心功能解析

该代码的主要目的是完成 GG18 协议的密钥生成阶段,包括以下几个部分:

  • 注册阶段 :通过 signup 获取参与方编号和会话唯一标识符 (UUID)

  • 密钥生成阶段 :包括多个通信轮次,各参与方协作生成共享密钥

  • 验证阶段 :验证每一轮通信数据的完整性和正确性

  • 结果保存 :最终保存生成的共享密钥对和其他元数据

2.2 依赖库

  • 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 数据

2.3 代码详细解析

2.3.1初始化参数

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:最低签名方数

2.3.2 注册参与方

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 用于标识不同的密钥生成会话

2.3.3 第一轮广播:发送承诺

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 将承诺消息发送给其他所有参与方

2.3.4 第二轮广播:发送解承诺消息

解承诺消息发送和验证

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 给其他所有参与方

  • 每个参与方接收解承诺消息,验证承诺是否正确

2.3.5 计算共享公钥

let (head, tail) = point_vec.split_at(1);
let y_sum = tail.iter().fold(head[0].clone(), |acc, x| acc + x);
  • 收集所有的临时公钥 (point_vec),逐个相加生成共享公钥 y_sum

2.3.6 第三轮:加密并分发密钥份额

生成秘密共享并分发

let (vss_scheme, secret_shares, _index) = party_keys .phase1_verify_com_phase3_verify_correct_key_phase2_distribute( &params, &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 加密密钥份额,并通过点对点消息发送给其他参与方

2.3.7 第四轮广播:发送 VSS 承诺

assert!(broadcast(
    &client,
    party_num_int,
    "round4",
    serde_json::to_string(&vss_scheme).unwrap(),
    uuid.clone()
)
.is_ok());
  • 每个参与方发送自己的 VSS 承诺 (vss_scheme)

2.3.8 第五轮广播:发送 DLog 证明

let (shared_keys, dlog_proof) = party_keys
    .phase2_verify_vss_construct_keypair_phase3_pok_dlog(
        &params,
        &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());
  • 每个参与方生成离散对数证明 (DLog Proof),并广播

2.3.9 验证 DLog 证明

Keys::verify_dlog_proofs(&params, &dlog_proof_vec, &point_vec).expect("bad dlog proof");
  • 验证所有参与方的 DLog 证明,确保共享密钥的正确性

2.3.10 保存密钥

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 !");
  • 保存密钥对 (shared_keys)、VSS 承诺 (vss_scheme_vec) 和共享公钥 (y_sum)

3. Sign 过程

3.1 主要功能

  • 初始化和注册 :读取之前生成的密钥,并进行参与方注册,确定当前签名参与方

  • 多轮通信

  • 每一轮通过广播或点对点 (P2P) 消息交换,完成必要的数据计算 - 包括零知识证明 (ZKP) 的生成和验证

  • 每一轮通过广播或点对点 (P2P) 消息交换,完成必要的数据计算

  • 包括零知识证明 (ZKP) 的生成和验证

  • ECDSA 签名生成

  • 每个参与方计算部分签名 - 通过聚合所有签名份额生成最终签名

  • 每个参与方计算部分签名

  • 通过聚合所有签名份额生成最终签名

  • 签名验证和保存 :验证生成的签名是否有效,并将结果保存到本地文件

3.2 代码详解

3.2.1 初始化

读取密钥文件

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();
  • 从 params.json 文件读取门限值 (THRESHOLD) 和参与方数量

3.2.2 注册参与方

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)

3.2.3 第一轮:收集签名者 ID

广播签名者 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;
    }
}
  • 将签名者的 ID 存储在 signers_vec 中

3.2.4 第二轮:广播承诺消息

生成并广播承诺

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(),
);
  • 调用 poll_for_broadcasts 收集其他参与方的广播数据

3.2.5 第三轮:点对点消息交换

生成并发送点对点消息

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(),
);
  • 调用 poll_for_p2p 接收点对点消息

3.2.6 签名计算

计算部分签名

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);
  • 聚合所有签名份额 (delta_vec 和 sigma_vec),生成完整的签名

3.2.7 验证和保存签名

生成最终签名

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 !");
  • 将签名保存到文件 signature 中

3.2.8 总结

  • 代码功能 :实现了 GG18 协议的多方 ECDSA 签名阶段,支持多个签名者协作生成有效的 ECDSA 签名。

  • 主要步骤

  • 初始化并注册参与方 - 多轮通信,包括广播和点对点消息交换

    • 计算并聚合部分签名,生成最终签名

    • 验证签名并保存结果。

  • 初始化并注册参与方

  • 多轮通信,包括广播和点对点消息交换

  • 计算并聚合部分签名,生成最终签名

  • 验证签名并保存结果。

  • 适用场景 :高安全性多方签名,例如区块链钱包和托管服务。

三.GG20代码详解

1. Keygen 详细代码解析

1.1 Round 流程

协议由 Round 0 到 Round 4 五个阶段构成,每一轮完成特定的任务,并通过消息交互实现阶段性的结果传递。

  • Round0 :密钥初始化

  • 每个参与方生成自己的密钥对和相关的承诺信息 - 广播承诺信息 (bc1)。

  • 每个参与方生成自己的密钥对和相关的承诺信息

  • 广播承诺信息 (bc1)。

  • Round1 :承诺验证

  • 接收所有参与方的承诺信息 - 广播解承诺信息 (decom1),以证明承诺的正确性。

  • 接收所有参与方的承诺信息

  • 广播解承诺信息 (decom1),以证明承诺的正确性。

  • Round2 :验证承诺和分发共享

  • 验证接收到的承诺和解承诺 - 分发秘密共享值 (VSS),用于门限加密

  • 验证接收到的承诺和解承诺

  • 分发秘密共享值 (VSS),用于门限加密

  • Round3 :计算共享密钥

  • 验证分发的共享值 - 使用共享值生成Paillier加密密钥和零知识证明 (dlog_proof)

    • 广播零知识证明
  • 验证分发的共享值

  • 使用共享值生成Paillier加密密钥和零知识证明 (dlog_proof)

  • 广播零知识证明

  • Round4 :生成全局公钥

  • 验证零知识证明 - 计算并存储全局公钥和每个参与方的本地密钥

  • 验证零知识证明

  • 计算并存储全局公钥和每个参与方的本地密钥

1.2 关键数据结构

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,             // 消息内容
}
  • 含义 :用于多方之间的通信

  • 功能 :支持广播和点对点消息

1.3 错误处理

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),
}
  • 定义了协议执行过程中可能出现的错误类型,包括承诺验证失败、VSS验证失败和零知识证明验证失败

1.4 协议流程详解

1.4.1 Round 0

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,为下一轮做准备

1.4.2 Round 1

output.push(Msg {
    sender: self.party_i,
    receiver: None,
    body: self.decom1.clone(),
});
  • 广播解承诺信息 decom1

  • 验证所有参与方的承诺正确性

1.4.3 Round 2

let vss_result = self
    .keys
    .phase1_verify_com_phase3_verify_correct_key_verify_dlog_phase2_distribute(
        &params,
        &received_decom,
        &self.received_comm,
    )
    .map_err(ProceedError::Round2VerifyCommitments)?;
  • 验证每个参与方的承诺和解承诺

  • 分发秘密共享值 VSS,为下一轮的密钥生成做准备

1.4.4 Round 3

let (shared_keys, dlog_proof) = self
    .keys
    .phase2_verify_vss_construct_keypair_phase3_pok_dlog(
        &params,
        &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,证明密钥生成的正确性

1.4.5 Round 4

Keys::verify_dlog_proofs_check_against_vss(
    &params,
    &dlog_proofs,
    &self.y_vec,
    &self.vss_vec,
)
.map_err(ProceedError::Round4VerifyDLogProof)?;
  • 验证零知识证明

  • 计算全局共享公钥 y_sum,并生成本地密钥 LocalKey

2. Sign 签名代码详细流程

2.1 核心功能概述

  • 背景 :ECDSA签名是区块链、密码学中常用的签名算法。多方签名的目标是将签名操作分散到多个参与方之间,以避免单点故障并增强安全性

  • 特点

  • 分布式协作 :签名过程分成多个阶段,每个参与方在各自的本地完成部分操作,通过交换消息完成协作 - 门限属性 :仅需要 t 个参与方即可完成签名,而无需全部参与

    • 零知识证明 :协议中涉及大量的零知识证明(如 PDLwSlackProof、HomoELGamalProof)以确保隐私性和安全性
  • 分布式协作 :签名过程分成多个阶段,每个参与方在各自的本地完成部分操作,通过交换消息完成协作

  • 门限属性 :仅需要 t 个参与方即可完成签名,而无需全部参与

  • 零知识证明 :协议中涉及大量的零知识证明(如 PDLwSlackProof、HomoELGamalProof)以确保隐私性和安全性

2.2 核心数据结构

2.2.1 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 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(用于门限加密的私钥)

2.2.2 消息定义

消息类型

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:最终签名和证明数据

2.3 签名阶段详解

签名协议分为以下阶段:

2.3.1 Round 0:初始化签名

  • 功能

  • 初始化本地签名密钥 - 生成并广播初始承诺信息 (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

2.3.2 Round 1:交换加密和解密消息

  • 功能

  • 接收所有参与方的 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())),
    });
}
  • 结果: 完成所有参与方之间的密钥交换。

2.3.3 Round 2: 计算签名参数

  • 功能

  • 计算中间签名值 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

2.3.4 Round 3: 计算签名和验证

  • 功能

  • 验证 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()),
});
  • 结果

    • 验证阶段签名参数 - 准备全局签名验证信息
  • 验证阶段签名参数

  • 准备全局签名验证信息

2.3.5 Round 4:验证最终签名

  • 功能

  • 验证 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)?;
  • 结果: 完成签名验证,生成最终签名

2.3.6 Round 5 & 6:签名输出

  • 功能

  • 生成完整的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());
  • 结果: 输出完整ECDSA签名结果

3. 总结

  • 特点

  • 分步设计 :每个阶段实现独立功能,逻辑清晰

  • 零知识证明 :保护隐私,验证参与方的正确性

  • 门限安全 :仅需满足 t 门限即可完成签名

  • 分步设计 :每个阶段实现独立功能,逻辑清晰

  • 零知识证明 :保护隐私,验证参与方的正确性

  • 门限安全 :仅需满足 t 门限即可完成签名

  • 应用场景

    • 多签钱包 :如区块链的去中心化交易所

    • 分布式身份认证 :增强安全性

    • 跨链协议 :实现多链协作签名

四.总结

本文围绕 ZenGo-X 的 multi-party-ecdsa 库 ,深入解析了其在多方安全计算(MPC)领域的实现与应用,涵盖了 GG18GG20 两种协议的核心流程。

1. ECDSA 协议在多方计算中的价值

ECDSA 是区块链与密码学中广泛应用的签名算法。通过多方计算技术,可以将签名操作分散到多个参与方之间,具有以下优势:

  • 增强安全性 :私钥从未在单一地点完整存在,避免单点故障

  • 支持门限签名 :仅需 t 个参与方即可完成签名,无需所有参与方参与

  • 隐私保护 :零知识证明确保协议的隐私性和安全性

2. ZenGo-X multi-party-ecdsa 库的主要功能

  • 密钥生成(Key Generation)

  • 通过多轮通信,参与方共同生成共享密钥对 - 支持 Paillier 加密和可验证秘密共享(VSS)技术

  • 通过多轮通信,参与方共同生成共享密钥对

  • 支持 Paillier 加密和可验证秘密共享(VSS)技术

  • 签名(Signing)

  • 至少 t+1 个参与方合作生成有效的ECDSA签名 - 使用零知识证明验证参与方操作的正确性

  • 至少 t+1 个参与方合作生成有效的ECDSA签名

  • 使用零知识证明验证参与方操作的正确性

3. GG18 和 GG20 的对比

  • GG18 协议

  • 适用于多方门限签名 - 通过五轮通信完成密钥生成,支持零知识证明和 VSS

    • 签名阶段包括多轮广播和点对点通信。
  • 适用于多方门限签名

  • 通过五轮通信完成密钥生成,支持零知识证明和 VSS

  • 签名阶段包括多轮广播和点对点通信。

  • GG20 协议

    • 相较 GG18,增强了对恶意参与方的防御能力 - 通过五轮通信完成密钥生成,支持 Paillier 加密和更严格的验证

    • 签名阶段进一步优化了消息交互流程

  • 相较 GG18,增强了对恶意参与方的防御能力

  • 通过五轮通信完成密钥生成,支持 Paillier 加密和更严格的验证

  • 签名阶段进一步优化了消息交互流程

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

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