From dee685d596e4a9c9bbff90893fa795d49a16385e Mon Sep 17 00:00:00 2001 From: natalie Date: Wed, 11 Dec 2024 08:57:49 +0000 Subject: [PATCH] Refresh Shares with DKG (#766) * Refresh shares with DKG (#663) * Add verification step for round2_packages for refreshing shares with DKG (#663) * Clean up clippy issues for correct indexing with refreshing shares with DKG (#663) * Import refresh tests for all ciphersuites (#663) * Fix formatting (#663) --- frost-core/src/keys/refresh.rs | 269 +++++++++++++++++- frost-core/src/tests/ciphersuite_generic.rs | 2 +- frost-core/src/tests/refresh.rs | 198 ++++++++++++- frost-ed25519/src/lib.rs | 1 + frost-ed25519/tests/integration_tests.rs | 7 + frost-ed448/src/lib.rs | 1 + frost-ed448/tests/integration_tests.rs | 7 + frost-p256/tests/integration_tests.rs | 7 + frost-ristretto255/tests/integration_tests.rs | 7 + frost-secp256k1/src/lib.rs | 1 + frost-secp256k1/tests/integration_tests.rs | 7 + 11 files changed, 498 insertions(+), 9 deletions(-) diff --git a/frost-core/src/keys/refresh.rs b/frost-core/src/keys/refresh.rs index 53a3cdd9..79f61185 100644 --- a/frost-core/src/keys/refresh.rs +++ b/frost-core/src/keys/refresh.rs @@ -8,14 +8,18 @@ use alloc::collections::BTreeMap; use alloc::vec::Vec; use crate::{ + keys::dkg::{compute_proof_of_knowledge, round1, round2}, keys::{ - generate_coefficients, generate_secret_shares, validate_num_of_signers, - CoefficientCommitment, PublicKeyPackage, SigningKey, SigningShare, VerifyingShare, + evaluate_polynomial, generate_coefficients, generate_secret_polynomial, + generate_secret_shares, validate_num_of_signers, CoefficientCommitment, PublicKeyPackage, + SigningKey, SigningShare, VerifyingShare, }, - Ciphersuite, CryptoRng, Error, Field, Group, Identifier, RngCore, + Ciphersuite, CryptoRng, Error, Field, Group, Header, Identifier, RngCore, }; -use super::{KeyPackage, SecretShare, VerifiableSecretSharingCommitment}; +use core::iter; + +use super::{dkg::round1::Package, KeyPackage, SecretShare, VerifiableSecretSharingCommitment}; /// Generates new zero key shares and a public key package using a trusted /// dealer Building a new public key package is done by taking the verifying @@ -114,3 +118,260 @@ pub fn refresh_share( Ok(new_key_package) } + +/// Part 1 of refresh share with DKG. A refreshing_key is generated and a new package and secret_package are generated. +/// The identity commitment is removed from the packages. +pub fn refresh_dkg_part_1( + identifier: Identifier, + max_signers: u16, + min_signers: u16, + mut rng: R, +) -> Result<(round1::SecretPackage, round1::Package), Error> { + validate_num_of_signers::(min_signers, max_signers)?; + + // Build refreshing shares + let refreshing_key = SigningKey { + scalar: <::Field>::zero(), + }; + + // Round 1, Step 1 + let coefficients = generate_coefficients::(min_signers as usize - 1, &mut rng); + + let (coefficients, commitment) = + generate_secret_polynomial(&refreshing_key, max_signers, min_signers, coefficients)?; + + // Remove identity element from coefficients + let mut coeff_comms = commitment.0; + coeff_comms.remove(0); + let commitment = VerifiableSecretSharingCommitment::new(coeff_comms.clone()); + + let proof_of_knowledge = + compute_proof_of_knowledge(identifier, &coefficients, &commitment, &mut rng)?; + + let secret_package = round1::SecretPackage { + identifier, + coefficients: coefficients.clone(), + commitment: commitment.clone(), + min_signers, + max_signers, + }; + let package = round1::Package { + header: Header::default(), + commitment, + proof_of_knowledge, + }; + + Ok((secret_package, package)) +} + +/// Part 2 of refresh share with DKG. The identity commitment needs to be added back into the secret package. +pub fn refresh_dkg_part2( + mut secret_package: round1::SecretPackage, + round1_packages: &BTreeMap, round1::Package>, +) -> Result< + ( + round2::SecretPackage, + BTreeMap, round2::Package>, + ), + Error, +> { + if round1_packages.len() != (secret_package.max_signers - 1) as usize { + return Err(Error::IncorrectNumberOfPackages); + } + + // The identity commitment needs to be added to the VSS commitment for secret package + let identity_commitment: Vec> = + vec![CoefficientCommitment::new(C::Group::identity())]; + + let refreshing_secret_share_commitments: Vec> = identity_commitment + .into_iter() + .chain(secret_package.commitment.0.clone()) + .collect(); + + secret_package.commitment = + VerifiableSecretSharingCommitment::::new(refreshing_secret_share_commitments); + + let mut round2_packages = BTreeMap::new(); + + for (sender_identifier, round1_package) in round1_packages { + // The identity commitment needs to be added to the VSS commitment for every round 1 package + let identity_commitment: Vec> = + vec![CoefficientCommitment::new(C::Group::identity())]; + + let refreshing_share_commitments: Vec> = identity_commitment + .into_iter() + .chain(round1_package.commitment.0.clone()) + .collect(); + + if refreshing_share_commitments.clone().len() != secret_package.min_signers as usize { + return Err(Error::IncorrectNumberOfCommitments); + } + + let ell = *sender_identifier; + + // Round 1, Step 5 + // We don't need to verify the proof of knowledge + + // Round 2, Step 1 + // + // > Each P_i securely sends to each other participant P_ℓ a secret share (ℓ, f_i(ℓ)), + // > deleting f_i and each share afterward except for (i, f_i(i)), + // > which they keep for themselves. + let signing_share = SigningShare::from_coefficients(&secret_package.coefficients, ell); + + round2_packages.insert( + ell, + round2::Package { + header: Header::default(), + signing_share, + }, + ); + } + let fii = evaluate_polynomial(secret_package.identifier, &secret_package.coefficients); + + Ok(( + round2::SecretPackage { + identifier: secret_package.identifier, + commitment: secret_package.commitment, + secret_share: fii, + min_signers: secret_package.min_signers, + max_signers: secret_package.max_signers, + }, + round2_packages, + )) +} + +/// This is the step that actually refreshes the shares. New public key packages +/// and key packages are created. +pub fn refresh_dkg_shares( + round2_secret_package: &round2::SecretPackage, + round1_packages: &BTreeMap, round1::Package>, + round2_packages: &BTreeMap, round2::Package>, + old_pub_key_package: PublicKeyPackage, + old_key_package: KeyPackage, +) -> Result<(KeyPackage, PublicKeyPackage), Error> { + // Add identity commitment back into round1_packages + let mut new_round_1_packages = BTreeMap::new(); + for (sender_identifier, round1_package) in round1_packages { + // The identity commitment needs to be added to the VSS commitment for every round 1 package + let identity_commitment: Vec> = + vec![CoefficientCommitment::new(C::Group::identity())]; + + let refreshing_share_commitments: Vec> = identity_commitment + .into_iter() + .chain(round1_package.commitment.0.clone()) + .collect(); + + let new_commitments = + VerifiableSecretSharingCommitment::::new(refreshing_share_commitments); + + let new_round_1_package = Package { + header: round1_package.header, + commitment: new_commitments, + proof_of_knowledge: round1_package.proof_of_knowledge, + }; + + new_round_1_packages.insert(*sender_identifier, new_round_1_package); + } + + if new_round_1_packages.len() != (round2_secret_package.max_signers - 1) as usize { + return Err(Error::IncorrectNumberOfPackages); + } + if new_round_1_packages.len() != round2_packages.len() { + return Err(Error::IncorrectNumberOfPackages); + } + if new_round_1_packages + .keys() + .any(|id| !round2_packages.contains_key(id)) + { + return Err(Error::IncorrectPackage); + } + + let mut signing_share = <::Field>::zero(); + + for (sender_identifier, round2_package) in round2_packages { + // Round 2, Step 2 + // + // > Each P_i verifies their shares by calculating: + // > g^{f_ℓ(i)} ≟ ∏^{t−1}_{k=0} φ^{i^k mod q}_{ℓk}, aborting if the + // > check fails. + let ell = *sender_identifier; + let f_ell_i = round2_package.signing_share; + + let commitment = &new_round_1_packages + .get(&ell) + .ok_or(Error::PackageNotFound)? + .commitment; + + // The verification is exactly the same as the regular SecretShare verification; + // however the required components are in different places. + // Build a temporary SecretShare so what we can call verify(). + let secret_share = SecretShare { + header: Header::default(), + identifier: round2_secret_package.identifier, + signing_share: f_ell_i, + commitment: commitment.clone(), + }; + + // Verify the share. We don't need the result. + let _ = secret_share.verify()?; + + // Round 2, Step 3 + // + // > Each P_i calculates their long-lived private signing share by computing + // > s_i = ∑^n_{ℓ=1} f_ℓ(i), stores s_i securely, and deletes each f_ℓ(i). + signing_share = signing_share + f_ell_i.to_scalar(); + } + + signing_share = signing_share + round2_secret_package.secret_share; + + // Build new signing share + let old_signing_share = old_key_package.signing_share.to_scalar(); + signing_share = signing_share + old_signing_share; + let signing_share = SigningShare::new(signing_share); + + // Round 2, Step 4 + // + // > Each P_i calculates their public verification share Y_i = g^{s_i}. + let verifying_share = signing_share.into(); + + let commitments: BTreeMap<_, _> = new_round_1_packages + .iter() + .map(|(id, package)| (*id, &package.commitment)) + .chain(iter::once(( + round2_secret_package.identifier, + &round2_secret_package.commitment, + ))) + .collect(); + + let zero_shares_public_key_package = PublicKeyPackage::from_dkg_commitments(&commitments)?; + + let mut new_verifying_shares = BTreeMap::new(); + + for (identifier, verifying_share) in zero_shares_public_key_package.verifying_shares { + let new_verifying_share = verifying_share.to_element() + + old_pub_key_package + .verifying_shares + .get(&identifier) + .ok_or(Error::UnknownIdentifier)? + .to_element(); + new_verifying_shares.insert(identifier, VerifyingShare::new(new_verifying_share)); + } + + let public_key_package = PublicKeyPackage { + header: old_pub_key_package.header, + verifying_shares: new_verifying_shares, + verifying_key: old_pub_key_package.verifying_key, + }; + + let key_package = KeyPackage { + header: Header::default(), + identifier: round2_secret_package.identifier, + signing_share, + verifying_share, + verifying_key: public_key_package.verifying_key, + min_signers: round2_secret_package.min_signers, + }; + + Ok((key_package, public_key_package)) +} diff --git a/frost-core/src/tests/ciphersuite_generic.rs b/frost-core/src/tests/ciphersuite_generic.rs index 3271f26a..2cf01831 100644 --- a/frost-core/src/tests/ciphersuite_generic.rs +++ b/frost-core/src/tests/ciphersuite_generic.rs @@ -566,7 +566,7 @@ fn check_part3_errors( } /// Check that calling dkg::part3() with distinct sets of participants fail. -fn check_part3_different_participants( +pub fn check_part3_different_participants( max_signers: u16, round2_secret_packages: BTreeMap, frost::keys::dkg::round2::SecretPackage>, received_round1_packages: BTreeMap< diff --git a/frost-core/src/tests/refresh.rs b/frost-core/src/tests/refresh.rs index 29330b90..b35ee0d4 100644 --- a/frost-core/src/tests/refresh.rs +++ b/frost-core/src/tests/refresh.rs @@ -1,17 +1,22 @@ //! Test for Refreshing shares -use std::collections::BTreeMap; - use rand_core::{CryptoRng, RngCore}; use crate::keys::generate_with_dealer; -use crate::keys::refresh::{compute_refreshing_shares, refresh_share}; +use crate::keys::refresh::{ + compute_refreshing_shares, refresh_dkg_part2, refresh_dkg_part_1, refresh_share, +}; use crate::{self as frost}; use crate::{ keys::{KeyPackage, PublicKeyPackage, SecretShare}, - Ciphersuite, Error, Identifier, + Ciphersuite, Error, Identifier, Signature, VerifyingKey, }; +use crate::tests::ciphersuite_generic::check_part3_different_participants; + +use alloc::collections::BTreeMap; +use alloc::vec::Vec; + use super::ciphersuite_generic::check_sign; /// We want to test that recover share matches the original share @@ -239,3 +244,188 @@ pub fn check_refresh_shares_with_dealer_serialisation( + mut rng: R, +) -> (Vec, Signature, VerifyingKey) +where + C::Group: core::cmp::PartialEq, +{ + //////////////////////////////////////////////////////////////////////////// + // Old Key generation + //////////////////////////////////////////////////////////////////////////// + + let old_max_signers = 5; + let min_signers = 3; + let (old_shares, pub_key_package) = generate_with_dealer( + old_max_signers, + min_signers, + frost::keys::IdentifierList::Default, + &mut rng, + ) + .unwrap(); + + let mut old_key_packages: BTreeMap, KeyPackage> = BTreeMap::new(); + + for (k, v) in old_shares { + let key_package = KeyPackage::try_from(v).unwrap(); + old_key_packages.insert(k, key_package); + } + + //////////////////////////////////////////////////////////////////////////// + // Key generation, Round 1 + //////////////////////////////////////////////////////////////////////////// + + let max_signers = 4; + let min_signers = 3; + + let remaining_ids = vec![ + Identifier::try_from(4).unwrap(), + Identifier::try_from(2).unwrap(), + Identifier::try_from(3).unwrap(), + Identifier::try_from(1).unwrap(), + ]; + + // Keep track of each participant's round 1 secret package. + // In practice each participant will keep its copy; no one + // will have all the participant's packages. + let mut round1_secret_packages: BTreeMap< + frost::Identifier, + frost::keys::dkg::round1::SecretPackage, + > = BTreeMap::new(); + + // Keep track of all round 1 packages sent to the given participant. + // This is used to simulate the broadcast; in practice the packages + // will be sent through some communication channel. + let mut received_round1_packages: BTreeMap< + frost::Identifier, + BTreeMap, frost::keys::dkg::round1::Package>, + > = BTreeMap::new(); + + // For each participant, perform the first part of the DKG protocol. + // In practice, each participant will perform this on their own environments. + for participant_identifier in remaining_ids.clone() { + let (round1_secret_package, round1_package) = + refresh_dkg_part_1(participant_identifier, max_signers, min_signers, &mut rng).unwrap(); + + // Store the participant's secret package for later use. + // In practice each participant will store it in their own environment. + round1_secret_packages.insert(participant_identifier, round1_secret_package); + + // "Send" the round 1 package to all other participants. In this + // test this is simulated using a BTreeMap; in practice this will be + // sent through some communication channel. + for receiver_participant_identifier in remaining_ids.clone() { + if receiver_participant_identifier == participant_identifier { + continue; + } + received_round1_packages + .entry(receiver_participant_identifier) + .or_default() + .insert(participant_identifier, round1_package.clone()); + } + } + + //////////////////////////////////////////////////////////////////////////// + // Key generation, Round 2 + //////////////////////////////////////////////////////////////////////////// + // Keep track of each participant's round 2 secret package. + // In practice each participant will keep its copy; no one + // will have all the participant's packages. + let mut round2_secret_packages = BTreeMap::new(); + + // Keep track of all round 2 packages sent to the given participant. + // This is used to simulate the broadcast; in practice the packages + // will be sent through some communication channel. + let mut received_round2_packages = BTreeMap::new(); + + // For each participant, perform the second part of the DKG protocol. + // In practice, each participant will perform this on their own environments. + for participant_identifier in remaining_ids.clone() { + let round1_secret_package = round1_secret_packages + .remove(&participant_identifier) + .unwrap(); + let round1_packages = &received_round1_packages[&participant_identifier]; + let (round2_secret_package, round2_packages) = + refresh_dkg_part2(round1_secret_package, round1_packages).expect("should work"); + + // Store the participant's secret package for later use. + // In practice each participant will store it in their own environment. + round2_secret_packages.insert(participant_identifier, round2_secret_package); + + // "Send" the round 2 package to all other participants. In this + // test this is simulated using a BTreeMap; in practice this will be + // sent through some communication channel. + // Note that, in contrast to the previous part, here each other participant + // gets its own specific package. + for (receiver_identifier, round2_package) in round2_packages { + received_round2_packages + .entry(receiver_identifier) + .or_insert_with(BTreeMap::new) + .insert(participant_identifier, round2_package); + } + } + + //////////////////////////////////////////////////////////////////////////// + // Key generation, final computation + //////////////////////////////////////////////////////////////////////////// + + // Keep track of each participant's long-lived key package. + // In practice each participant will keep its copy; no one + // will have all the participant's packages. + let mut key_packages = BTreeMap::new(); + + // Map of the verifying key of each participant. + // Used by the signing test that follows. + let mut verifying_keys = BTreeMap::new(); + // The group public key, used by the signing test that follows. + let mut verifying_key = None; + // For each participant, store the set of verifying keys they have computed. + // This is used to check if the set is correct (the same) for all participants. + // In practice, if there is a Coordinator, only they need to store the set. + // If there is not, then all candidates must store their own sets. + // The verifying keys are used to verify the signature shares produced + // for each signature before being aggregated. + let mut pubkey_packages_by_participant = BTreeMap::new(); + + check_part3_different_participants( + max_signers, + round2_secret_packages.clone(), + received_round1_packages.clone(), + received_round2_packages.clone(), + ); + + // For each participant, this is where they refresh thair shares + // In practice, each participant will perform this on their own environments. + for participant_identifier in remaining_ids.clone() { + let (key_package, pubkey_package_for_participant) = + frost::keys::refresh::refresh_dkg_shares( + &round2_secret_packages[&participant_identifier], + &received_round1_packages[&participant_identifier], + &received_round2_packages[&participant_identifier], + pub_key_package.clone(), + old_key_packages[&participant_identifier].clone(), + ) + .unwrap(); + verifying_keys.insert(participant_identifier, key_package.verifying_share); + // Test if all verifying_key are equal + if let Some(previous_verifying_key) = verifying_key { + assert_eq!(previous_verifying_key, key_package.verifying_key) + } + verifying_key = Some(key_package.verifying_key); + key_packages.insert(participant_identifier, key_package); + pubkey_packages_by_participant + .insert(participant_identifier, pubkey_package_for_participant); + } + + // Test if the set of verifying keys is correct for all participants. + for verifying_keys_for_participant in pubkey_packages_by_participant.values() { + assert!(verifying_keys_for_participant.verifying_shares == verifying_keys); + } + + let pubkeys = frost::keys::PublicKeyPackage::new(verifying_keys, verifying_key.unwrap()); + + // Proceed with the signing test. + check_sign(min_signers, key_packages, rng, pubkeys).unwrap() +} diff --git a/frost-ed25519/src/lib.rs b/frost-ed25519/src/lib.rs index 1e33b2d9..9c4bce8e 100644 --- a/frost-ed25519/src/lib.rs +++ b/frost-ed25519/src/lib.rs @@ -326,6 +326,7 @@ pub mod keys { pub type VerifiableSecretSharingCommitment = frost::keys::VerifiableSecretSharingCommitment; pub mod dkg; + pub mod refresh; pub mod repairable; } diff --git a/frost-ed25519/tests/integration_tests.rs b/frost-ed25519/tests/integration_tests.rs index 6c564788..c8c27fc2 100644 --- a/frost-ed25519/tests/integration_tests.rs +++ b/frost-ed25519/tests/integration_tests.rs @@ -180,6 +180,13 @@ fn check_refresh_shares_with_dealer_fails_with_invalid_identifier() { >(max_signers, min_signers, &identifiers, error, rng); } +#[test] +fn check_refresh_shares_with_dkg() { + let rng = thread_rng(); + + frost_core::tests::refresh::check_refresh_shares_with_dkg::(rng); +} + #[test] fn check_sign_with_dealer() { let rng = thread_rng(); diff --git a/frost-ed448/src/lib.rs b/frost-ed448/src/lib.rs index 4ceb707e..56523561 100644 --- a/frost-ed448/src/lib.rs +++ b/frost-ed448/src/lib.rs @@ -321,6 +321,7 @@ pub mod keys { pub type VerifiableSecretSharingCommitment = frost::keys::VerifiableSecretSharingCommitment; pub mod dkg; + pub mod refresh; pub mod repairable; } diff --git a/frost-ed448/tests/integration_tests.rs b/frost-ed448/tests/integration_tests.rs index 70061503..b96d8328 100644 --- a/frost-ed448/tests/integration_tests.rs +++ b/frost-ed448/tests/integration_tests.rs @@ -180,6 +180,13 @@ fn check_refresh_shares_with_dealer_fails_with_invalid_identifier() { >(max_signers, min_signers, &identifiers, error, rng); } +#[test] +fn check_refresh_shares_with_dkg() { + let rng = thread_rng(); + + frost_core::tests::refresh::check_refresh_shares_with_dkg::(rng); +} + #[test] fn check_sign_with_dealer() { let rng = thread_rng(); diff --git a/frost-p256/tests/integration_tests.rs b/frost-p256/tests/integration_tests.rs index 8d44312d..acd3f81e 100644 --- a/frost-p256/tests/integration_tests.rs +++ b/frost-p256/tests/integration_tests.rs @@ -180,6 +180,13 @@ fn check_refresh_shares_with_dealer_fails_with_invalid_identifier() { >(max_signers, min_signers, &identifiers, error, rng); } +#[test] +fn check_refresh_shares_with_dkg() { + let rng = thread_rng(); + + frost_core::tests::refresh::check_refresh_shares_with_dkg::(rng); +} + #[test] fn check_sign_with_dealer() { let rng = thread_rng(); diff --git a/frost-ristretto255/tests/integration_tests.rs b/frost-ristretto255/tests/integration_tests.rs index af536ac3..74362027 100644 --- a/frost-ristretto255/tests/integration_tests.rs +++ b/frost-ristretto255/tests/integration_tests.rs @@ -181,6 +181,13 @@ fn check_refresh_shares_with_dealer_fails_with_invalid_identifier() { >(max_signers, min_signers, &identifiers, error, rng); } +#[test] +fn check_refresh_shares_with_dkg() { + let rng = thread_rng(); + + frost_core::tests::refresh::check_refresh_shares_with_dkg::(rng); +} + #[test] fn check_sign_with_dealer() { let rng = thread_rng(); diff --git a/frost-secp256k1/src/lib.rs b/frost-secp256k1/src/lib.rs index 903ffda3..eec2c8ee 100644 --- a/frost-secp256k1/src/lib.rs +++ b/frost-secp256k1/src/lib.rs @@ -336,6 +336,7 @@ pub mod keys { pub type VerifiableSecretSharingCommitment = frost::keys::VerifiableSecretSharingCommitment; pub mod dkg; + pub mod refresh; pub mod repairable; } diff --git a/frost-secp256k1/tests/integration_tests.rs b/frost-secp256k1/tests/integration_tests.rs index 9581384b..d098f266 100644 --- a/frost-secp256k1/tests/integration_tests.rs +++ b/frost-secp256k1/tests/integration_tests.rs @@ -180,6 +180,13 @@ fn check_refresh_shares_with_dealer_fails_with_invalid_identifier() { >(max_signers, min_signers, &identifiers, error, rng); } +#[test] +fn check_refresh_shares_with_dkg() { + let rng = thread_rng(); + + frost_core::tests::refresh::check_refresh_shares_with_dkg::(rng); +} + #[test] fn check_sign_with_dealer() { let rng = thread_rng();