From 835a3f0cdc4b55b003b2f7763d3148dee8f3219b Mon Sep 17 00:00:00 2001 From: natalie Date: Thu, 18 Jul 2024 23:33:10 +0100 Subject: [PATCH] Add refresh shares with dealer functionality (#665) * Add refresh shares with dealer functionality (#245) * Change refresh share API (#245) Split refresh_shares_with_dealer into calculate_zero_key and refresh_share * Fix serialisation error with refresh share (#245) Add serialisation test * Fix serialisation errors after updates (#245) Fixed some typos * Update refresh_share to accept and return a KeyPackage instead of SecretShare (#245) * Tidy up refresh share functionality (#245) * Add refresh share functionality to Book (#245) Diagram is still to be added * Update book for rereshing shares with trusted dealer (#245) * Add new verifying shares calculation for refresh shares (#245) Add tests for invalid identifiers when refreshing shares * Rename calculate_zero_key to compute_refreshing_shares (#245) * Import Vec from the alloc crate (#245) This is to be compatible with the no_std attribute * Use alloc crate instead of std for refresh shares (#245) * Fix fmt error (#245) * Refactoring refresh shares functionality (#245) * cleanups during review * Update book/src/tutorial/refreshing-shares.md * update docs * always return error in detect_cheater * add changelog entry --------- Co-authored-by: Conrado Gouvea Co-authored-by: Conrado Gouvea --- README.md | 1 + book/src/SUMMARY.md | 1 + book/src/frost.md | 33 +++ book/src/tutorial/refreshing-shares.md | 34 +++ book/src/tutorial/refreshing.png | 0 frost-core/CHANGELOG.md | 3 + frost-core/src/keys.rs | 1 + frost-core/src/keys/refresh.rs | 116 +++++++++ frost-core/src/lib.rs | 102 +++++--- frost-core/src/serialization.rs | 4 +- frost-core/src/tests.rs | 1 + frost-core/src/tests/ciphersuite_generic.rs | 52 +++- frost-core/src/tests/refresh.rs | 241 ++++++++++++++++++ frost-ed25519/src/keys/refresh.rs | 35 +++ frost-ed25519/tests/integration_tests.rs | 116 +++++++++ frost-ed448/src/keys/refresh.rs | 35 +++ frost-ed448/tests/integration_tests.rs | 116 +++++++++ frost-p256/src/keys/refresh.rs | 35 +++ frost-p256/src/lib.rs | 1 + frost-p256/tests/integration_tests.rs | 116 +++++++++ frost-ristretto255/src/keys/refresh.rs | 35 +++ frost-ristretto255/src/lib.rs | 1 + frost-ristretto255/tests/integration_tests.rs | 117 +++++++++ frost-secp256k1/src/keys/refresh.rs | 35 +++ frost-secp256k1/tests/integration_tests.rs | 116 +++++++++ gencode/src/main.rs | 1 + 26 files changed, 1293 insertions(+), 55 deletions(-) create mode 100644 book/src/tutorial/refreshing-shares.md create mode 100644 book/src/tutorial/refreshing.png create mode 100644 frost-core/src/keys/refresh.rs create mode 100644 frost-core/src/tests/refresh.rs create mode 100644 frost-ed25519/src/keys/refresh.rs create mode 100644 frost-ed448/src/keys/refresh.rs create mode 100644 frost-p256/src/keys/refresh.rs create mode 100644 frost-ristretto255/src/keys/refresh.rs create mode 100644 frost-secp256k1/src/keys/refresh.rs diff --git a/README.md b/README.md index 1774125d..8f22461a 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,7 @@ Besides FROST itself, this repository also provides: - Distributed key generation as specified in the original paper [FROST20](https://eprint.iacr.org/2020/852.pdf); - Repairable Threshold Scheme (RTS) from ['A Survey and Refinement of Repairable Threshold Schemes'](https://eprint.iacr.org/2017/1155) which allows a participant to recover a lost share with the help of a threshold of other participants; - Rerandomized FROST (paper under review). +- Refresh Share functionality using a Trusted Dealer. This can be used to refresh the shares of the participants or to remove a participant. ## Getting Started diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index 4cf983c7..3970f8e0 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -7,6 +7,7 @@ - [Trusted Dealer Key Generation](tutorial/trusted-dealer.md) - [Signing](tutorial/signing.md) - [Distributed Key Generation](tutorial/dkg.md) + - [Refreshing Shares](tutorial/refreshing-shares.md) - [User Documentation](user.md) - [Serialization Format](user/serialization.md) - [FROST with Zcash](zcash.md) diff --git a/book/src/frost.md b/book/src/frost.md index dd7591ee..1daa881f 100644 --- a/book/src/frost.md +++ b/book/src/frost.md @@ -83,6 +83,39 @@ is still free to start the process with only 2 participants if they wish. Signature verification is carried out as normal with single-party signatures, along with the signed message and the group verifying key as inputs. +## Repairing + +Repairing shares allow participants to help another participant recover their +share if they have lost it, or also issue a new share to a new participant +(while keeping the same threshold). + +The repair share functionality requires a threshold of participants to work. +For example, in a 2-of-3 scenario, two participants can help the third recover +their share, or they could issue a new share to move to a 2-of-4 group. + +The functionality works in such a way that each participant running the repair +share function is not able to obtain the share that is being recovered or +issued. + +## Refreshing + +Refreshing shares allow participants (or a subset of them) to update their +shares in a way that maintains the same group public key. Some applications are: + +- Make it harder for attackers to compromise the shares. For example, in a + 2-of-3 threshold scenario, if an attacker steals one participant's device and + all participants refresh their shares, the attacker will need to start over + and steal two shares instead of just one more. +- Remove a participant from the group. For example, in a 2-of-3 threshold + scenario, if two participants decide to remove the third they can both refresh + their shares and the third participant would no longer be able to participate + in signing sessions with the others. (They can also then use the repair share + functionality to issue a new share and move from 2-of-2 back to 2-of-3.) + +```admonish note +This is also possible via Distributed Key Generation but this has not yet been +implemented. +``` ## Ciphersuites diff --git a/book/src/tutorial/refreshing-shares.md b/book/src/tutorial/refreshing-shares.md new file mode 100644 index 00000000..cf75b3da --- /dev/null +++ b/book/src/tutorial/refreshing-shares.md @@ -0,0 +1,34 @@ +# Refreshing Shares using a Trusted Dealer + +The diagram below shows the refresh share process. Dashed lines +represent data being sent through an [authenticated and confidential communication +channel](https://frost.zfnd.org/terminology.html#peer-to-peer-channel). + + + +The Trusted Dealer needs to first run `compute_refreshing_shares()` which +returns SecretShares (the "refreshing shares") and a PublicKeyPackage. Each +`SecretShare` must then be sent along with the `PublicKeyPackage` via an +[**authenticated** and **confidential** channel +](https://frost.zfnd.org/terminology.html#peer-to-peer-channel) for each +participant. + +Each Participant then runs `refresh_share()` to generate a new `KeyPackage` +which will replace their old `KeyPackage`; they must also replace their old +`PublicKeyPackage` with the one sent by the Trusted Dealer. + +```admonish danger +The refreshed `KeyPackage` contents must be stored securely and the original +`KeyPackage` should be deleted. For example: + +- Make sure other users in the system can't read it; +- If possible, use the OS secure storage such that the package + contents can only be opened with the user's password or biometrics. +``` + +```admonish danger +Applications should first ensure that all participants who refreshed their +`KeyPackages` were actually able to do so successfully, before deleting their old +`KeyPackages`. How this is done is up to the application; it might require +sucessfully generating a signature with all of those participants. +``` diff --git a/book/src/tutorial/refreshing.png b/book/src/tutorial/refreshing.png new file mode 100644 index 00000000..e69de29b diff --git a/frost-core/CHANGELOG.md b/frost-core/CHANGELOG.md index abd9af08..515941b5 100644 --- a/frost-core/CHANGELOG.md +++ b/frost-core/CHANGELOG.md @@ -4,6 +4,9 @@ Entries are listed in reverse chronological order. ## Unreleased +* Added refresh share functionality for trusted dealer: + `frost_core::keys::refresh::{compute_refreshing_shares, refresh_share}` + ## 2.0.0-rc.0 * Changed the `deserialize()` function of Elements and structs containing diff --git a/frost-core/src/keys.rs b/frost-core/src/keys.rs index ba24b13b..5a884e42 100644 --- a/frost-core/src/keys.rs +++ b/frost-core/src/keys.rs @@ -29,6 +29,7 @@ use crate::serialization::{Deserialize, Serialize}; use super::compute_lagrange_coefficient; pub mod dkg; +pub mod refresh; pub mod repairable; /// Sum the commitments from all participants in a distributed key generation diff --git a/frost-core/src/keys/refresh.rs b/frost-core/src/keys/refresh.rs new file mode 100644 index 00000000..53a3cdd9 --- /dev/null +++ b/frost-core/src/keys/refresh.rs @@ -0,0 +1,116 @@ +//! Refresh Shares +//! +//! Implements the functionality to refresh a share. This requires the participation +//! of all the remaining signers. This can be done using a Trusted Dealer or +//! DKG (not yet implemented) + +use alloc::collections::BTreeMap; +use alloc::vec::Vec; + +use crate::{ + keys::{ + generate_coefficients, generate_secret_shares, validate_num_of_signers, + CoefficientCommitment, PublicKeyPackage, SigningKey, SigningShare, VerifyingShare, + }, + Ciphersuite, CryptoRng, Error, Field, Group, Identifier, RngCore, +}; + +use super::{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 +/// shares from the new public key package and adding them to the original +/// verifying shares +pub fn compute_refreshing_shares( + pub_key_package: PublicKeyPackage, + max_signers: u16, + min_signers: u16, + identifiers: &[Identifier], + rng: &mut R, +) -> Result<(Vec>, PublicKeyPackage), Error> { + // Validate inputs + if identifiers.len() != max_signers as usize { + return Err(Error::IncorrectNumberOfIdentifiers); + } + validate_num_of_signers(min_signers, max_signers)?; + + // Build refreshing shares + let refreshing_key = SigningKey { + scalar: <::Field>::zero(), + }; + + let coefficients = generate_coefficients::(min_signers as usize - 1, rng); + let refreshing_shares = generate_secret_shares( + &refreshing_key, + max_signers, + min_signers, + coefficients, + identifiers, + )?; + + let mut refreshed_verifying_shares: BTreeMap, VerifyingShare> = + BTreeMap::new(); + let mut refreshing_shares_minus_identity: Vec> = Vec::new(); + + for mut share in refreshing_shares { + let refreshing_verifying_share: VerifyingShare = SigningShare::into(share.signing_share); + + let verifying_share = pub_key_package.verifying_shares.get(&share.identifier); + + match verifying_share { + Some(verifying_share) => { + let refreshed_verifying_share = + refreshing_verifying_share.to_element() + verifying_share.to_element(); + refreshed_verifying_shares.insert( + share.identifier, + VerifyingShare::new(refreshed_verifying_share), + ); + } + None => return Err(Error::UnknownIdentifier), + }; + + share.commitment.0.remove(0); + refreshing_shares_minus_identity.push(share); + } + + let refreshed_pub_key_package = PublicKeyPackage:: { + header: pub_key_package.header, + verifying_shares: refreshed_verifying_shares, + verifying_key: pub_key_package.verifying_key, + }; + + Ok((refreshing_shares_minus_identity, refreshed_pub_key_package)) +} + +/// Each participant refreshes their shares This is done by taking the +/// `refreshing_share` received from the trusted dealer and adding it to the +/// original share +pub fn refresh_share( + mut refreshing_share: SecretShare, + current_key_package: &KeyPackage, +) -> Result, Error> { + // The identity commitment needs to be added to the VSS commitment + let identity_commitment: Vec> = + vec![CoefficientCommitment::new(C::Group::identity())]; + + let refreshing_share_commitments: Vec> = identity_commitment + .into_iter() + .chain(refreshing_share.commitment.0.clone()) + .collect(); + + refreshing_share.commitment = + VerifiableSecretSharingCommitment::::new(refreshing_share_commitments); + + // Verify refreshing_share secret share + let refreshed_share_package = KeyPackage::::try_from(refreshing_share)?; + + let signing_share: SigningShare = SigningShare::new( + refreshed_share_package.signing_share.to_scalar() + + current_key_package.signing_share.to_scalar(), + ); + + let mut new_key_package = current_key_package.clone(); + new_key_package.signing_share = signing_share; + + Ok(new_key_package) +} diff --git a/frost-core/src/lib.rs b/frost-core/src/lib.rs index b93195ec..fbecfe07 100644 --- a/frost-core/src/lib.rs +++ b/frost-core/src/lib.rs @@ -573,6 +573,7 @@ where if signing_package.signing_commitments().len() != signature_shares.len() { return Err(Error::UnknownIdentifier); } + if !signing_package.signing_commitments().keys().all(|id| { #[cfg(feature = "cheater-detection")] return signature_shares.contains_key(id) && pubkeys.verifying_shares().contains_key(id); @@ -586,7 +587,6 @@ where // binding factor. let binding_factor_list: BindingFactorList = compute_binding_factor_list(signing_package, &pubkeys.verifying_key, &[])?; - // Compute the group commitment from signing commitments produced in round one. let group_commitment = compute_group_commitment(signing_package, &binding_factor_list)?; @@ -616,48 +616,14 @@ where // This approach is more efficient since we don't need to verify all shares // if the aggregate signature is valid (which should be the common case). #[cfg(feature = "cheater-detection")] - if let Err(err) = verification_result { - // Compute the per-message challenge. - let challenge = crate::challenge::( - &group_commitment.0, - &pubkeys.verifying_key, - signing_package.message().as_slice(), + if verification_result.is_err() { + detect_cheater( + group_commitment, + pubkeys, + signing_package, + signature_shares, + &binding_factor_list, )?; - - // Verify the signature shares. - for (signature_share_identifier, signature_share) in signature_shares { - // Look up the public key for this signer, where `signer_pubkey` = _G.ScalarBaseMult(s[i])_, - // and where s[i] is a secret share of the constant term of _f_, the secret polynomial. - let signer_pubkey = pubkeys - .verifying_shares - .get(signature_share_identifier) - .ok_or(Error::UnknownIdentifier)?; - - // Compute Lagrange coefficient. - let lambda_i = derive_interpolating_value(signature_share_identifier, signing_package)?; - - let binding_factor = binding_factor_list - .get(signature_share_identifier) - .ok_or(Error::UnknownIdentifier)?; - - // Compute the commitment share. - let R_share = signing_package - .signing_commitment(signature_share_identifier) - .ok_or(Error::UnknownIdentifier)? - .to_group_commitment_share(binding_factor); - - // Compute relation values to verify this signature share. - signature_share.verify( - *signature_share_identifier, - &R_share, - signer_pubkey, - lambda_i, - &challenge, - )?; - } - - // We should never reach here; but we return the verification error to be safe. - return Err(err); } #[cfg(not(feature = "cheater-detection"))] @@ -665,3 +631,55 @@ where Ok(signature) } + +/// Optional cheater detection feature +/// Each share is verified to find the cheater +fn detect_cheater( + group_commitment: GroupCommitment, + pubkeys: &keys::PublicKeyPackage, + signing_package: &SigningPackage, + signature_shares: &BTreeMap, round2::SignatureShare>, + binding_factor_list: &BindingFactorList, +) -> Result<(), Error> { + // Compute the per-message challenge. + let challenge = crate::challenge::( + &group_commitment.0, + &pubkeys.verifying_key, + signing_package.message().as_slice(), + )?; + + // Verify the signature shares. + for (signature_share_identifier, signature_share) in signature_shares { + // Look up the public key for this signer, where `signer_pubkey` = _G.ScalarBaseMult(s[i])_, + // and where s[i] is a secret share of the constant term of _f_, the secret polynomial. + let signer_pubkey = pubkeys + .verifying_shares + .get(signature_share_identifier) + .ok_or(Error::UnknownIdentifier)?; + + // Compute Lagrange coefficient. + let lambda_i = derive_interpolating_value(signature_share_identifier, signing_package)?; + + let binding_factor = binding_factor_list + .get(signature_share_identifier) + .ok_or(Error::UnknownIdentifier)?; + + // Compute the commitment share. + let R_share = signing_package + .signing_commitment(signature_share_identifier) + .ok_or(Error::UnknownIdentifier)? + .to_group_commitment_share(binding_factor); + + // Compute relation values to verify this signature share. + signature_share.verify( + *signature_share_identifier, + &R_share, + signer_pubkey, + lambda_i, + &challenge, + )?; + } + + // We should never reach here; but we return an error to be safe. + Err(Error::InvalidSignature) +} diff --git a/frost-core/src/serialization.rs b/frost-core/src/serialization.rs index a442e58b..fe1df7b8 100644 --- a/frost-core/src/serialization.rs +++ b/frost-core/src/serialization.rs @@ -83,12 +83,12 @@ impl SerializableElement where C: Ciphersuite, { - /// Serialize an Element. Returns and error if it's the identity. + /// Serialize an Element. Returns an error if it's the identity. pub fn serialize(&self) -> Result, Error> { Ok(::serialize(&self.0)?.as_ref().to_vec()) } - /// Deserialized an Element. Returns an error if it's malformed or is the + /// Deserialize an Element. Returns an error if it's malformed or is the /// identity. pub fn deserialize(bytes: &[u8]) -> Result> { let serialized: ::Serialization = bytes diff --git a/frost-core/src/tests.rs b/frost-core/src/tests.rs index d2b1b712..e4855c90 100644 --- a/frost-core/src/tests.rs +++ b/frost-core/src/tests.rs @@ -7,6 +7,7 @@ pub mod ciphersuite_generic; pub mod coefficient_commitment; pub mod helpers; pub mod proptests; +pub mod refresh; pub mod repairable; pub mod vectors; pub mod vectors_dkg; diff --git a/frost-core/src/tests/ciphersuite_generic.rs b/frost-core/src/tests/ciphersuite_generic.rs index a6219340..4528e9b7 100644 --- a/frost-core/src/tests/ciphersuite_generic.rs +++ b/frost-core/src/tests/ciphersuite_generic.rs @@ -4,8 +4,10 @@ use alloc::collections::BTreeMap; use crate as frost; +use crate::round2::SignatureShare; use crate::{ - keys::PublicKeyPackage, Error, Field, Group, Identifier, Signature, SigningKey, VerifyingKey, + keys::PublicKeyPackage, Error, Field, Group, Identifier, Signature, SigningKey, SigningPackage, + VerifyingKey, }; use alloc::borrow::ToOwned; use alloc::vec::Vec; @@ -222,7 +224,7 @@ pub fn check_sign( // - take one (unused) commitment per signing participant let mut signature_shares = BTreeMap::new(); let message = "message to sign".as_bytes(); - let signing_package = frost::SigningPackage::new(commitments_map, message); + let signing_package = SigningPackage::new(commitments_map, message); //////////////////////////////////////////////////////////////////////////// // Round 2: each participant generates their signature share @@ -249,19 +251,18 @@ pub fn check_sign( // generates the final signature. //////////////////////////////////////////////////////////////////////////// - #[cfg(not(feature = "cheater-detection"))] - let pubkey_package = PublicKeyPackage { - header: pubkey_package.header, - verifying_shares: BTreeMap::new(), - verifying_key: pubkey_package.verifying_key, - }; - check_aggregate_errors( signing_package.clone(), signature_shares.clone(), pubkey_package.clone(), ); + check_verifying_shares( + pubkey_package.clone(), + signing_package.clone(), + signature_shares.clone(), + ); + // Aggregate (also verifies the signature shares) let group_signature = frost::aggregate(&signing_package, &signature_shares, &pubkey_package)?; @@ -313,6 +314,13 @@ fn check_aggregate_errors( signature_shares: BTreeMap, frost::round2::SignatureShare>, pubkey_package: frost::keys::PublicKeyPackage, ) { + #[cfg(not(feature = "cheater-detection"))] + let pubkey_package = PublicKeyPackage { + header: pubkey_package.header, + verifying_shares: BTreeMap::new(), + verifying_key: pubkey_package.verifying_key, + }; + #[cfg(feature = "cheater-detection")] check_aggregate_corrupted_share( signing_package.clone(), @@ -745,7 +753,7 @@ pub fn check_sign_with_missing_identifier( + pubkeys: PublicKeyPackage, + signing_package: SigningPackage, + mut signature_shares: BTreeMap, SignatureShare>, +) { + let one = <::Group as Group>::Field::one(); + + // Corrupt last share + let id = *signature_shares.keys().last().unwrap(); + *signature_shares.get_mut(&id).unwrap() = + SignatureShare::new(signature_shares[&id].to_scalar() + one); + + let e = frost::aggregate(&signing_package, &signature_shares, &pubkeys).unwrap_err(); + assert_eq!(e.culprit(), Some(id)); + assert_eq!(e, Error::InvalidSignatureShare { culprit: id }); +} diff --git a/frost-core/src/tests/refresh.rs b/frost-core/src/tests/refresh.rs new file mode 100644 index 00000000..29330b90 --- /dev/null +++ b/frost-core/src/tests/refresh.rs @@ -0,0 +1,241 @@ +//! 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::{self as frost}; +use crate::{ + keys::{KeyPackage, PublicKeyPackage, SecretShare}, + Ciphersuite, Error, Identifier, +}; + +use super::ciphersuite_generic::check_sign; + +/// We want to test that recover share matches the original share +pub fn check_refresh_shares_with_dealer(mut rng: R) { + // Compute shares + + //////////////////////////////////////////////////////////////////////////// + // Old Key generation + //////////////////////////////////////////////////////////////////////////// + + const MAX_SIGNERS: u16 = 5; + const MIN_SIGNERS: u16 = 3; + let (old_shares, pub_key_package) = generate_with_dealer( + 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); + } + + //////////////////////////////////////////////////////////////////////////// + // New Key generation + //////////////////////////////////////////////////////////////////////////// + + // Signer 2 will be removed and Signers 1, 3, 4 & 5 will remain + + let remaining_ids = vec![ + Identifier::try_from(1).unwrap(), + Identifier::try_from(3).unwrap(), + Identifier::try_from(4).unwrap(), + Identifier::try_from(5).unwrap(), + ]; + + const NEW_MAX_SIGNERS: u16 = 4; + + // Trusted Dealer generates zero keys and new public key package + + let (zero_shares, new_pub_key_package) = compute_refreshing_shares( + pub_key_package, + NEW_MAX_SIGNERS, + MIN_SIGNERS, + &remaining_ids, + &mut rng, + ) + .unwrap(); + + // Each participant refreshes their share + + let mut new_shares = BTreeMap::new(); + + for i in 0..remaining_ids.len() { + let identifier = remaining_ids[i]; + let current_share = &old_key_packages[&identifier]; + let new_share = refresh_share(zero_shares[i].clone(), current_share); + new_shares.insert(identifier, new_share); + } + + let mut key_packages: BTreeMap, KeyPackage> = BTreeMap::new(); + + for (k, v) in new_shares { + key_packages.insert(k, v.unwrap()); + } + check_sign(MIN_SIGNERS, key_packages, rng, new_pub_key_package).unwrap(); +} + +/// We want to check that shares are refreshed with valid signers +pub fn check_refresh_shares_with_dealer_fails_with_invalid_signers< + C: Ciphersuite, + R: RngCore + CryptoRng, +>( + new_max_signers: u16, + min_signers: u16, + identifiers: &[Identifier], + error: Error, + mut rng: R, +) { + let (_old_shares, pub_key_package) = + generate_with_dealer::(5, 2, frost::keys::IdentifierList::Default, &mut rng).unwrap(); + let out = compute_refreshing_shares( + pub_key_package, + new_max_signers, + min_signers, + identifiers, + &mut rng, + ); + + assert!(out.is_err()); + assert!(out == Err(error)) +} + +/// We want to test that refresh share fails if the identifiers don't match the +/// identifiers in the public key package +pub fn check_refresh_shares_with_dealer_fails_with_invalid_public_key_package< + C: Ciphersuite, + R: RngCore + CryptoRng, +>( + mut rng: R, +) { + // Compute shares + + //////////////////////////////////////////////////////////////////////////// + // Old Key generation + //////////////////////////////////////////////////////////////////////////// + + const MAX_SIGNERS: u16 = 3; + const MIN_SIGNERS: u16 = 2; + let (old_shares, incorrect_pub_key_package) = generate_with_dealer( + 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); + } + + //////////////////////////////////////////////////////////////////////////// + // New Key generation + //////////////////////////////////////////////////////////////////////////// + + // Signer 2 will be removed and Signers 1, 3, 4 & 5 will remain + + let remaining_ids = vec![ + Identifier::try_from(1).unwrap(), + Identifier::try_from(3).unwrap(), + Identifier::try_from(4).unwrap(), + Identifier::try_from(5).unwrap(), + ]; + + const NEW_MAX_SIGNERS: u16 = 4; + + // Trusted Dealer generates zero keys and new public key package + + let e = compute_refreshing_shares( + incorrect_pub_key_package, + NEW_MAX_SIGNERS, + MIN_SIGNERS, + &remaining_ids, + &mut rng, + ) + .unwrap_err(); + + assert_eq!(e, Error::UnknownIdentifier) +} + +/// Check serialisation +pub fn check_refresh_shares_with_dealer_serialisation( + mut rng: R, +) { + //////////////////////////////////////////////////////////////////////////// + // Old Key generation + //////////////////////////////////////////////////////////////////////////// + + const MAX_SIGNERS: u16 = 5; + const MIN_SIGNERS: u16 = 3; + let (_old_shares, pub_key_package) = generate_with_dealer( + MAX_SIGNERS, + MIN_SIGNERS, + frost::keys::IdentifierList::Default, + &mut rng, + ) + .unwrap(); + + //////////////////////////////////////////////////////////////////////////// + // New Key generation + // + // Zero key is calculated by trusted dealer + // Participant 2 will be removed and Participants 1, 3, 4 & 5 will remain + //////////////////////////////////////////////////////////////////////////// + + let remaining_ids = vec![ + Identifier::try_from(1).unwrap(), + Identifier::try_from(3).unwrap(), + Identifier::try_from(4).unwrap(), + Identifier::try_from(5).unwrap(), + ]; + + const NEW_MAX_SIGNERS: u16 = 4; + + let (zero_shares, new_pub_key_package) = compute_refreshing_shares( + pub_key_package, + NEW_MAX_SIGNERS, + MIN_SIGNERS, + &remaining_ids, + &mut rng, + ) + .unwrap(); + + // Trusted dealer serialises zero shares and key package + + let zero_shares_serialised = SecretShare::::serialize(&zero_shares[0]); + + assert!(zero_shares_serialised.is_ok()); + + let new_pub_key_package_serialised = PublicKeyPackage::::serialize(&new_pub_key_package); + + assert!(new_pub_key_package_serialised.is_ok()); + + // Participant 1 deserialises zero share and key package + + let zero_share = SecretShare::::deserialize(&zero_shares_serialised.unwrap()); + + assert!(zero_share.is_ok()); + + let new_pub_key_package = + PublicKeyPackage::::deserialize(&new_pub_key_package_serialised.unwrap()); + + assert!(new_pub_key_package.is_ok()); + + // Participant 1 checks Key Package can be created from Secret Share + + let key_package = KeyPackage::::try_from(zero_share.unwrap()); + + assert!(key_package.is_ok()); +} diff --git a/frost-ed25519/src/keys/refresh.rs b/frost-ed25519/src/keys/refresh.rs new file mode 100644 index 00000000..c270fc20 --- /dev/null +++ b/frost-ed25519/src/keys/refresh.rs @@ -0,0 +1,35 @@ +//! Refresh Shares +//! +//! Implements the functionality to refresh a share. This requires the participation +//! of all the remaining signers. This can be done using a Trusted Dealer or +//! DKG (not yet implemented) + +use crate::{frost, Ciphersuite, CryptoRng, Error, Identifier, RngCore}; +use alloc::vec::Vec; + +use super::{KeyPackage, PublicKeyPackage, SecretShare}; + +/// Refreshes shares using a trusted dealer +pub fn compute_refreshing_shares( + old_pub_key_package: PublicKeyPackage, + max_signers: u16, + min_signers: u16, + identifiers: &[Identifier], + mut rng: &mut R, +) -> Result<(Vec, PublicKeyPackage), Error> { + frost::keys::refresh::compute_refreshing_shares( + old_pub_key_package, + max_signers, + min_signers, + identifiers, + &mut rng, + ) +} + +/// Each participant refreshed their shares +pub fn refresh_share( + zero_share: SecretShare, + current_share: &KeyPackage, +) -> Result { + frost::keys::refresh::refresh_share(zero_share, current_share) +} diff --git a/frost-ed25519/tests/integration_tests.rs b/frost-ed25519/tests/integration_tests.rs index 1421079a..6c564788 100644 --- a/frost-ed25519/tests/integration_tests.rs +++ b/frost-ed25519/tests/integration_tests.rs @@ -64,6 +64,122 @@ fn check_rts() { frost_core::tests::repairable::check_rts::(rng); } +#[test] +fn check_refresh_shares_with_dealer() { + let rng = thread_rng(); + + frost_core::tests::refresh::check_refresh_shares_with_dealer::(rng); +} + +#[test] +fn check_refresh_shares_with_dealer_serialisation() { + let rng = thread_rng(); + + frost_core::tests::refresh::check_refresh_shares_with_dealer_serialisation::( + rng, + ); +} + +#[test] +fn check_refresh_shares_with_dealer_fails_with_invalid_public_key_package() { + let rng = thread_rng(); + + frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_public_key_package::< + Ed25519Sha512, + _, + >(rng); +} + +#[test] +fn check_refresh_shares_with_dealer_fails_with_invalid_min_signers() { + let rng = thread_rng(); + let identifiers = vec![ + Identifier::try_from(1).unwrap(), + Identifier::try_from(3).unwrap(), + Identifier::try_from(4).unwrap(), + Identifier::try_from(5).unwrap(), + ]; + let min_signers = 1; + let max_signers = 4; + let error = Error::InvalidMinSigners; + + frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< + Ed25519Sha512, + _, + >(max_signers, min_signers, &identifiers, error, rng); +} + +#[test] +fn check_refresh_shares_with_dealer_fails_with_unequal_num_identifiers_and_max_signers() { + let rng = thread_rng(); + let identifiers = vec![ + Identifier::try_from(1).unwrap(), + Identifier::try_from(3).unwrap(), + Identifier::try_from(4).unwrap(), + Identifier::try_from(5).unwrap(), + ]; + let min_signers = 3; + let max_signers = 3; + let error: frost_core::Error = Error::IncorrectNumberOfIdentifiers; + + frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< + Ed25519Sha512, + _, + >(max_signers, min_signers, &identifiers, error, rng); +} + +#[test] +fn check_refresh_shares_with_dealer_fails_with_min_signers_greater_than_max() { + let rng = thread_rng(); + let identifiers = vec![ + Identifier::try_from(1).unwrap(), + Identifier::try_from(3).unwrap(), + Identifier::try_from(4).unwrap(), + Identifier::try_from(5).unwrap(), + ]; + let min_signers = 6; + let max_signers = 4; + let error: frost_core::Error = Error::InvalidMinSigners; + + frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< + Ed25519Sha512, + _, + >(max_signers, min_signers, &identifiers, error, rng); +} + +#[test] +fn check_refresh_shares_with_dealer_fails_with_invalid_max_signers() { + let rng = thread_rng(); + let identifiers = vec![Identifier::try_from(1).unwrap()]; + let min_signers = 3; + let max_signers = 1; + let error = Error::InvalidMaxSigners; + + frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< + Ed25519Sha512, + _, + >(max_signers, min_signers, &identifiers, error, rng); +} + +#[test] +fn check_refresh_shares_with_dealer_fails_with_invalid_identifier() { + let rng = thread_rng(); + let identifiers = vec![ + Identifier::try_from(8).unwrap(), + Identifier::try_from(3).unwrap(), + Identifier::try_from(4).unwrap(), + Identifier::try_from(6).unwrap(), + ]; + let min_signers = 2; + let max_signers = 4; + let error = Error::UnknownIdentifier; + + frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< + Ed25519Sha512, + _, + >(max_signers, min_signers, &identifiers, error, rng); +} + #[test] fn check_sign_with_dealer() { let rng = thread_rng(); diff --git a/frost-ed448/src/keys/refresh.rs b/frost-ed448/src/keys/refresh.rs new file mode 100644 index 00000000..c270fc20 --- /dev/null +++ b/frost-ed448/src/keys/refresh.rs @@ -0,0 +1,35 @@ +//! Refresh Shares +//! +//! Implements the functionality to refresh a share. This requires the participation +//! of all the remaining signers. This can be done using a Trusted Dealer or +//! DKG (not yet implemented) + +use crate::{frost, Ciphersuite, CryptoRng, Error, Identifier, RngCore}; +use alloc::vec::Vec; + +use super::{KeyPackage, PublicKeyPackage, SecretShare}; + +/// Refreshes shares using a trusted dealer +pub fn compute_refreshing_shares( + old_pub_key_package: PublicKeyPackage, + max_signers: u16, + min_signers: u16, + identifiers: &[Identifier], + mut rng: &mut R, +) -> Result<(Vec, PublicKeyPackage), Error> { + frost::keys::refresh::compute_refreshing_shares( + old_pub_key_package, + max_signers, + min_signers, + identifiers, + &mut rng, + ) +} + +/// Each participant refreshed their shares +pub fn refresh_share( + zero_share: SecretShare, + current_share: &KeyPackage, +) -> Result { + frost::keys::refresh::refresh_share(zero_share, current_share) +} diff --git a/frost-ed448/tests/integration_tests.rs b/frost-ed448/tests/integration_tests.rs index 3409a7e0..70061503 100644 --- a/frost-ed448/tests/integration_tests.rs +++ b/frost-ed448/tests/integration_tests.rs @@ -64,6 +64,122 @@ fn check_rts() { frost_core::tests::repairable::check_rts::(rng); } +#[test] +fn check_refresh_shares_with_dealer() { + let rng = thread_rng(); + + frost_core::tests::refresh::check_refresh_shares_with_dealer::(rng); +} + +#[test] +fn check_refresh_shares_with_dealer_serialisation() { + let rng = thread_rng(); + + frost_core::tests::refresh::check_refresh_shares_with_dealer_serialisation::( + rng, + ); +} + +#[test] +fn check_refresh_shares_with_dealer_fails_with_invalid_public_key_package() { + let rng = thread_rng(); + + frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_public_key_package::< + Ed448Shake256, + _, + >(rng); +} + +#[test] +fn check_refresh_shares_with_dealer_fails_with_invalid_min_signers() { + let rng = thread_rng(); + let identifiers = vec![ + Identifier::try_from(1).unwrap(), + Identifier::try_from(3).unwrap(), + Identifier::try_from(4).unwrap(), + Identifier::try_from(5).unwrap(), + ]; + let min_signers = 1; + let max_signers = 4; + let error = Error::InvalidMinSigners; + + frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< + Ed448Shake256, + _, + >(max_signers, min_signers, &identifiers, error, rng); +} + +#[test] +fn check_refresh_shares_with_dealer_fails_with_unequal_num_identifiers_and_max_signers() { + let rng = thread_rng(); + let identifiers = vec![ + Identifier::try_from(1).unwrap(), + Identifier::try_from(3).unwrap(), + Identifier::try_from(4).unwrap(), + Identifier::try_from(5).unwrap(), + ]; + let min_signers = 3; + let max_signers = 3; + let error: frost_core::Error = Error::IncorrectNumberOfIdentifiers; + + frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< + Ed448Shake256, + _, + >(max_signers, min_signers, &identifiers, error, rng); +} + +#[test] +fn check_refresh_shares_with_dealer_fails_with_min_signers_greater_than_max() { + let rng = thread_rng(); + let identifiers = vec![ + Identifier::try_from(1).unwrap(), + Identifier::try_from(3).unwrap(), + Identifier::try_from(4).unwrap(), + Identifier::try_from(5).unwrap(), + ]; + let min_signers = 6; + let max_signers = 4; + let error: frost_core::Error = Error::InvalidMinSigners; + + frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< + Ed448Shake256, + _, + >(max_signers, min_signers, &identifiers, error, rng); +} + +#[test] +fn check_refresh_shares_with_dealer_fails_with_invalid_max_signers() { + let rng = thread_rng(); + let identifiers = vec![Identifier::try_from(1).unwrap()]; + let min_signers = 3; + let max_signers = 1; + let error = Error::InvalidMaxSigners; + + frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< + Ed448Shake256, + _, + >(max_signers, min_signers, &identifiers, error, rng); +} + +#[test] +fn check_refresh_shares_with_dealer_fails_with_invalid_identifier() { + let rng = thread_rng(); + let identifiers = vec![ + Identifier::try_from(8).unwrap(), + Identifier::try_from(3).unwrap(), + Identifier::try_from(4).unwrap(), + Identifier::try_from(6).unwrap(), + ]; + let min_signers = 2; + let max_signers = 4; + let error = Error::UnknownIdentifier; + + frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< + Ed448Shake256, + _, + >(max_signers, min_signers, &identifiers, error, rng); +} + #[test] fn check_sign_with_dealer() { let rng = thread_rng(); diff --git a/frost-p256/src/keys/refresh.rs b/frost-p256/src/keys/refresh.rs new file mode 100644 index 00000000..c270fc20 --- /dev/null +++ b/frost-p256/src/keys/refresh.rs @@ -0,0 +1,35 @@ +//! Refresh Shares +//! +//! Implements the functionality to refresh a share. This requires the participation +//! of all the remaining signers. This can be done using a Trusted Dealer or +//! DKG (not yet implemented) + +use crate::{frost, Ciphersuite, CryptoRng, Error, Identifier, RngCore}; +use alloc::vec::Vec; + +use super::{KeyPackage, PublicKeyPackage, SecretShare}; + +/// Refreshes shares using a trusted dealer +pub fn compute_refreshing_shares( + old_pub_key_package: PublicKeyPackage, + max_signers: u16, + min_signers: u16, + identifiers: &[Identifier], + mut rng: &mut R, +) -> Result<(Vec, PublicKeyPackage), Error> { + frost::keys::refresh::compute_refreshing_shares( + old_pub_key_package, + max_signers, + min_signers, + identifiers, + &mut rng, + ) +} + +/// Each participant refreshed their shares +pub fn refresh_share( + zero_share: SecretShare, + current_share: &KeyPackage, +) -> Result { + frost::keys::refresh::refresh_share(zero_share, current_share) +} diff --git a/frost-p256/src/lib.rs b/frost-p256/src/lib.rs index 7491d3d6..3e798f4b 100644 --- a/frost-p256/src/lib.rs +++ b/frost-p256/src/lib.rs @@ -343,6 +343,7 @@ pub mod keys { pub type VerifiableSecretSharingCommitment = frost::keys::VerifiableSecretSharingCommitment

; pub mod dkg; + pub mod refresh; pub mod repairable; } diff --git a/frost-p256/tests/integration_tests.rs b/frost-p256/tests/integration_tests.rs index f2573353..8d44312d 100644 --- a/frost-p256/tests/integration_tests.rs +++ b/frost-p256/tests/integration_tests.rs @@ -64,6 +64,122 @@ fn check_rts() { frost_core::tests::repairable::check_rts::(rng); } +#[test] +fn check_refresh_shares_with_dealer() { + let rng = thread_rng(); + + frost_core::tests::refresh::check_refresh_shares_with_dealer::(rng); +} + +#[test] +fn check_refresh_shares_with_dealer_serialisation() { + let rng = thread_rng(); + + frost_core::tests::refresh::check_refresh_shares_with_dealer_serialisation::( + rng, + ); +} + +#[test] +fn check_refresh_shares_with_dealer_fails_with_invalid_public_key_package() { + let rng = thread_rng(); + + frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_public_key_package::< + P256Sha256, + _, + >(rng); +} + +#[test] +fn check_refresh_shares_with_dealer_fails_with_invalid_min_signers() { + let rng = thread_rng(); + let identifiers = vec![ + Identifier::try_from(1).unwrap(), + Identifier::try_from(3).unwrap(), + Identifier::try_from(4).unwrap(), + Identifier::try_from(5).unwrap(), + ]; + let min_signers = 1; + let max_signers = 4; + let error = Error::InvalidMinSigners; + + frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< + P256Sha256, + _, + >(max_signers, min_signers, &identifiers, error, rng); +} + +#[test] +fn check_refresh_shares_with_dealer_fails_with_unequal_num_identifiers_and_max_signers() { + let rng = thread_rng(); + let identifiers = vec![ + Identifier::try_from(1).unwrap(), + Identifier::try_from(3).unwrap(), + Identifier::try_from(4).unwrap(), + Identifier::try_from(5).unwrap(), + ]; + let min_signers = 3; + let max_signers = 3; + let error: frost_core::Error = Error::IncorrectNumberOfIdentifiers; + + frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< + P256Sha256, + _, + >(max_signers, min_signers, &identifiers, error, rng); +} + +#[test] +fn check_refresh_shares_with_dealer_fails_with_min_signers_greater_than_max() { + let rng = thread_rng(); + let identifiers = vec![ + Identifier::try_from(1).unwrap(), + Identifier::try_from(3).unwrap(), + Identifier::try_from(4).unwrap(), + Identifier::try_from(5).unwrap(), + ]; + let min_signers = 6; + let max_signers = 4; + let error: frost_core::Error = Error::InvalidMinSigners; + + frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< + P256Sha256, + _, + >(max_signers, min_signers, &identifiers, error, rng); +} + +#[test] +fn check_refresh_shares_with_dealer_fails_with_invalid_max_signers() { + let rng = thread_rng(); + let identifiers = vec![Identifier::try_from(1).unwrap()]; + let min_signers = 3; + let max_signers = 1; + let error = Error::InvalidMaxSigners; + + frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< + P256Sha256, + _, + >(max_signers, min_signers, &identifiers, error, rng); +} + +#[test] +fn check_refresh_shares_with_dealer_fails_with_invalid_identifier() { + let rng = thread_rng(); + let identifiers = vec![ + Identifier::try_from(8).unwrap(), + Identifier::try_from(3).unwrap(), + Identifier::try_from(4).unwrap(), + Identifier::try_from(6).unwrap(), + ]; + let min_signers = 2; + let max_signers = 4; + let error = Error::UnknownIdentifier; + + frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< + P256Sha256, + _, + >(max_signers, min_signers, &identifiers, error, rng); +} + #[test] fn check_sign_with_dealer() { let rng = thread_rng(); diff --git a/frost-ristretto255/src/keys/refresh.rs b/frost-ristretto255/src/keys/refresh.rs new file mode 100644 index 00000000..c270fc20 --- /dev/null +++ b/frost-ristretto255/src/keys/refresh.rs @@ -0,0 +1,35 @@ +//! Refresh Shares +//! +//! Implements the functionality to refresh a share. This requires the participation +//! of all the remaining signers. This can be done using a Trusted Dealer or +//! DKG (not yet implemented) + +use crate::{frost, Ciphersuite, CryptoRng, Error, Identifier, RngCore}; +use alloc::vec::Vec; + +use super::{KeyPackage, PublicKeyPackage, SecretShare}; + +/// Refreshes shares using a trusted dealer +pub fn compute_refreshing_shares( + old_pub_key_package: PublicKeyPackage, + max_signers: u16, + min_signers: u16, + identifiers: &[Identifier], + mut rng: &mut R, +) -> Result<(Vec, PublicKeyPackage), Error> { + frost::keys::refresh::compute_refreshing_shares( + old_pub_key_package, + max_signers, + min_signers, + identifiers, + &mut rng, + ) +} + +/// Each participant refreshed their shares +pub fn refresh_share( + zero_share: SecretShare, + current_share: &KeyPackage, +) -> Result { + frost::keys::refresh::refresh_share(zero_share, current_share) +} diff --git a/frost-ristretto255/src/lib.rs b/frost-ristretto255/src/lib.rs index d82d5405..0929c228 100644 --- a/frost-ristretto255/src/lib.rs +++ b/frost-ristretto255/src/lib.rs @@ -312,6 +312,7 @@ pub mod keys { pub type VerifiableSecretSharingCommitment = frost::keys::VerifiableSecretSharingCommitment; pub mod dkg; + pub mod refresh; pub mod repairable; } diff --git a/frost-ristretto255/tests/integration_tests.rs b/frost-ristretto255/tests/integration_tests.rs index bacc9fb4..af536ac3 100644 --- a/frost-ristretto255/tests/integration_tests.rs +++ b/frost-ristretto255/tests/integration_tests.rs @@ -64,6 +64,123 @@ fn check_rts() { frost_core::tests::repairable::check_rts::(rng); } +#[test] +fn check_refresh_shares_with_dealer() { + let rng = thread_rng(); + + frost_core::tests::refresh::check_refresh_shares_with_dealer::(rng); +} + +#[test] +fn check_refresh_shares_with_dealer_serialisation() { + let rng = thread_rng(); + + frost_core::tests::refresh::check_refresh_shares_with_dealer_serialisation::< + Ristretto255Sha512, + _, + >(rng); +} + +#[test] +fn check_refresh_shares_with_dealer_fails_with_invalid_public_key_package() { + let rng = thread_rng(); + + frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_public_key_package::< + Ristretto255Sha512, + _, + >(rng); +} + +#[test] +fn check_refresh_shares_with_dealer_fails_with_invalid_min_signers() { + let rng = thread_rng(); + let identifiers = vec![ + Identifier::try_from(1).unwrap(), + Identifier::try_from(3).unwrap(), + Identifier::try_from(4).unwrap(), + Identifier::try_from(5).unwrap(), + ]; + let min_signers = 1; + let max_signers = 4; + let error = Error::InvalidMinSigners; + + frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< + Ristretto255Sha512, + _, + >(max_signers, min_signers, &identifiers, error, rng); +} + +#[test] +fn check_refresh_shares_with_dealer_fails_with_unequal_num_identifiers_and_max_signers() { + let rng = thread_rng(); + let identifiers = vec![ + Identifier::try_from(1).unwrap(), + Identifier::try_from(3).unwrap(), + Identifier::try_from(4).unwrap(), + Identifier::try_from(5).unwrap(), + ]; + let min_signers = 3; + let max_signers = 3; + let error: frost_core::Error = Error::IncorrectNumberOfIdentifiers; + + frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< + Ristretto255Sha512, + _, + >(max_signers, min_signers, &identifiers, error, rng); +} + +#[test] +fn check_refresh_shares_with_dealer_fails_with_min_signers_greater_than_max() { + let rng = thread_rng(); + let identifiers = vec![ + Identifier::try_from(1).unwrap(), + Identifier::try_from(3).unwrap(), + Identifier::try_from(4).unwrap(), + Identifier::try_from(5).unwrap(), + ]; + let min_signers = 6; + let max_signers = 4; + let error: frost_core::Error = Error::InvalidMinSigners; + + frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< + Ristretto255Sha512, + _, + >(max_signers, min_signers, &identifiers, error, rng); +} + +#[test] +fn check_refresh_shares_with_dealer_fails_with_invalid_max_signers() { + let rng = thread_rng(); + let identifiers = vec![Identifier::try_from(1).unwrap()]; + let min_signers = 3; + let max_signers = 1; + let error = Error::InvalidMaxSigners; + + frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< + Ristretto255Sha512, + _, + >(max_signers, min_signers, &identifiers, error, rng); +} + +#[test] +fn check_refresh_shares_with_dealer_fails_with_invalid_identifier() { + let rng = thread_rng(); + let identifiers = vec![ + Identifier::try_from(8).unwrap(), + Identifier::try_from(3).unwrap(), + Identifier::try_from(4).unwrap(), + Identifier::try_from(6).unwrap(), + ]; + let min_signers = 2; + let max_signers = 4; + let error = Error::UnknownIdentifier; + + frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< + Ristretto255Sha512, + _, + >(max_signers, min_signers, &identifiers, error, rng); +} + #[test] fn check_sign_with_dealer() { let rng = thread_rng(); diff --git a/frost-secp256k1/src/keys/refresh.rs b/frost-secp256k1/src/keys/refresh.rs new file mode 100644 index 00000000..c270fc20 --- /dev/null +++ b/frost-secp256k1/src/keys/refresh.rs @@ -0,0 +1,35 @@ +//! Refresh Shares +//! +//! Implements the functionality to refresh a share. This requires the participation +//! of all the remaining signers. This can be done using a Trusted Dealer or +//! DKG (not yet implemented) + +use crate::{frost, Ciphersuite, CryptoRng, Error, Identifier, RngCore}; +use alloc::vec::Vec; + +use super::{KeyPackage, PublicKeyPackage, SecretShare}; + +/// Refreshes shares using a trusted dealer +pub fn compute_refreshing_shares( + old_pub_key_package: PublicKeyPackage, + max_signers: u16, + min_signers: u16, + identifiers: &[Identifier], + mut rng: &mut R, +) -> Result<(Vec, PublicKeyPackage), Error> { + frost::keys::refresh::compute_refreshing_shares( + old_pub_key_package, + max_signers, + min_signers, + identifiers, + &mut rng, + ) +} + +/// Each participant refreshed their shares +pub fn refresh_share( + zero_share: SecretShare, + current_share: &KeyPackage, +) -> Result { + frost::keys::refresh::refresh_share(zero_share, current_share) +} diff --git a/frost-secp256k1/tests/integration_tests.rs b/frost-secp256k1/tests/integration_tests.rs index 58ba3e08..9581384b 100644 --- a/frost-secp256k1/tests/integration_tests.rs +++ b/frost-secp256k1/tests/integration_tests.rs @@ -64,6 +64,122 @@ fn check_rts() { frost_core::tests::repairable::check_rts::(rng); } +#[test] +fn check_refresh_shares_with_dealer() { + let rng = thread_rng(); + + frost_core::tests::refresh::check_refresh_shares_with_dealer::(rng); +} + +#[test] +fn check_refresh_shares_with_dealer_serialisation() { + let rng = thread_rng(); + + frost_core::tests::refresh::check_refresh_shares_with_dealer_serialisation::( + rng, + ); +} + +#[test] +fn check_refresh_shares_with_dealer_fails_with_invalid_public_key_package() { + let rng = thread_rng(); + + frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_public_key_package::< + Secp256K1Sha256, + _, + >(rng); +} + +#[test] +fn check_refresh_shares_with_dealer_fails_with_invalid_min_signers() { + let rng = thread_rng(); + let identifiers = vec![ + Identifier::try_from(1).unwrap(), + Identifier::try_from(3).unwrap(), + Identifier::try_from(4).unwrap(), + Identifier::try_from(5).unwrap(), + ]; + let min_signers = 1; + let max_signers = 4; + let error = Error::InvalidMinSigners; + + frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< + Secp256K1Sha256, + _, + >(max_signers, min_signers, &identifiers, error, rng); +} + +#[test] +fn check_refresh_shares_with_dealer_fails_with_unequal_num_identifiers_and_max_signers() { + let rng = thread_rng(); + let identifiers = vec![ + Identifier::try_from(1).unwrap(), + Identifier::try_from(3).unwrap(), + Identifier::try_from(4).unwrap(), + Identifier::try_from(5).unwrap(), + ]; + let min_signers = 3; + let max_signers = 3; + let error: frost_core::Error = Error::IncorrectNumberOfIdentifiers; + + frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< + Secp256K1Sha256, + _, + >(max_signers, min_signers, &identifiers, error, rng); +} + +#[test] +fn check_refresh_shares_with_dealer_fails_with_min_signers_greater_than_max() { + let rng = thread_rng(); + let identifiers = vec![ + Identifier::try_from(1).unwrap(), + Identifier::try_from(3).unwrap(), + Identifier::try_from(4).unwrap(), + Identifier::try_from(5).unwrap(), + ]; + let min_signers = 6; + let max_signers = 4; + let error: frost_core::Error = Error::InvalidMinSigners; + + frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< + Secp256K1Sha256, + _, + >(max_signers, min_signers, &identifiers, error, rng); +} + +#[test] +fn check_refresh_shares_with_dealer_fails_with_invalid_max_signers() { + let rng = thread_rng(); + let identifiers = vec![Identifier::try_from(1).unwrap()]; + let min_signers = 3; + let max_signers = 1; + let error = Error::InvalidMaxSigners; + + frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< + Secp256K1Sha256, + _, + >(max_signers, min_signers, &identifiers, error, rng); +} + +#[test] +fn check_refresh_shares_with_dealer_fails_with_invalid_identifier() { + let rng = thread_rng(); + let identifiers = vec![ + Identifier::try_from(8).unwrap(), + Identifier::try_from(3).unwrap(), + Identifier::try_from(4).unwrap(), + Identifier::try_from(6).unwrap(), + ]; + let min_signers = 2; + let max_signers = 4; + let error = Error::UnknownIdentifier; + + frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< + Secp256K1Sha256, + _, + >(max_signers, min_signers, &identifiers, error, rng); +} + #[test] fn check_sign_with_dealer() { let rng = thread_rng(); diff --git a/gencode/src/main.rs b/gencode/src/main.rs index 0806901e..eda52d4e 100644 --- a/gencode/src/main.rs +++ b/gencode/src/main.rs @@ -321,6 +321,7 @@ fn main() -> ExitCode { "README.md", "dkg.md", "src/keys/dkg.rs", + "src/keys/refresh.rs", "src/keys/repairable.rs", "src/tests/batch.rs", "src/tests/coefficient_commitment.rs",