CVE-2025-58359
Description
ZF FROST is a Rust implementation of FROST (Flexible Round-Optimised Schnorr Threshold signatures). In versions 2.0.0 through 2.1.0, refresh shares with smaller min_signers will reduce security of group. The inability to change min_signers (i.e. the threshold) with the refresh share functionality (frost_core::keys::refresh module) was not made clear to users. Using a smaller value would not decrease the threshold, and attempts to sign using a smaller threshold would fail. Additionally, after refreshing the shares with a smaller threshold, it would still be possible to sign with the original threshold, potentially causing a security loss to the participant's shares. This issue is fixed in version 2.2.0.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
frost-corecrates.io | >= 2.0.0, < 2.2.0 | 2.2.0 |
Affected products
1- Range: 0.2.0, 0.2.1, 0.3.0, …
Patches
1379ef689c733refresh: validate min_signers (#908)
24 files changed · +734 −67
frost-core/Cargo.toml+3 −1 modified@@ -45,7 +45,9 @@ rand_chacha.workspace = true serde_json.workspace = true [features] -default = ["serialization", "cheater-detection"] +default = ["serialization", "cheater-detection", "std"] +# No longer needed. Kept for retrocompatibility until 3.0.0 +std = [] #! ## Features ## Expose internal types, which do not have SemVer guarantees. This is an advanced ## feature which can be useful if you need to build a modified version of FROST.
frost-core/CHANGELOG.md+3 −0 modified@@ -6,6 +6,9 @@ Entries are listed in reverse chronological order. * MSRV has been bumped to Rust 1.81, making all crates no-std (except `frost-ed448`). The `std` and `nightly` features were removed from all crates +* Added validation for the `min_signers` parameter in the + `frost_core::keys::refresh` functions. +* Added DKG refresh functions to the crate-specific `refresh` modules. ## 2.1.0
frost-core/src/keys/dkg.rs+2 −2 modified@@ -484,7 +484,7 @@ pub(crate) fn verify_proof_of_knowledge<C: Ciphersuite>( /// `round1_packages` maps the identifier of each other participant to the /// [`round1::Package`] they sent to the current participant (the owner of /// `secret_package`). These identifiers must come from whatever mapping the -/// coordinator has between communication channels and participants, i.e. they +/// participant has between communication channels and participants, i.e. they /// must have assurance that the [`round1::Package`] came from the participant /// with that identifier. /// @@ -561,7 +561,7 @@ pub fn part2<C: Ciphersuite>( /// `round2_packages` maps the identifier of each other participant to the /// [`round2::Package`] they sent to the current participant (the owner of /// `secret_package`). These identifiers must come from whatever mapping the -/// coordinator has between communication channels and participants, i.e. they +/// participant has between communication channels and participants, i.e. they /// must have assurance that the [`round2::Package`] came from the participant /// with that identifier. ///
frost-core/src/keys/refresh.rs+100 −15 modified@@ -1,8 +1,27 @@ //! 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. +//! Refreshing shares has two purposes: +//! +//! - Mitigate against share compromise. +//! - Remove participants from a group. +//! +//! Refer to the [FROST +//! book](https://frost.zfnd.org/frost.html#refreshing-shares) for important +//! details. +//! +//! This modules supports refreshing shares using a Trusted Dealer or DKG. You +//! probably want to use the same approach as the original share generation. +//! +//! For the Trusted Dealer approach, the trusted dealer should call +//! [`compute_refreshing_shares()`] and send the returned refreshing shares to +//! the participants. Each participant should then call [`refresh_share()`]. +//! +//! For the DKG approach, the flow is very similar to [DKG +//! itself](`https://frost.zfnd.org/tutorial/dkg.html`). Each participant calls +//! [`refresh_dkg_part_1()`], keeps the returned secret package and sends the +//! returned package to other participants. Then each participants calls +//! [`refresh_dkg_part2()`] and sends the returned packages to the other +//! participants. Finally each participant calls [`refresh_dkg_shares()`]. use alloc::collections::BTreeMap; use alloc::vec::Vec; @@ -21,10 +40,20 @@ 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 -/// shares from the new public key package and adding them to the original -/// verifying shares +/// Compute refreshing shares for the Trusted Dealer refresh procedure. +/// +/// - `pub_key_package`: the current public key package. +/// - `max_signers`: the number of participants that are refreshing their +/// shares. It can be smaller than the original value, but still equal to or +/// greater than `min_signers`. +/// - `min_signers`: the threshold needed to sign. It must be equal to the +/// original value for the group (i.e. the refresh process can't reduce +/// the threshold). +/// - `identifiers`: The identifiers of all participants that want to refresh +/// their shares. Must be the same length as `max_signers`. +/// +/// It returns a vectors of [`SecretShare`] that must be sent to the participants +/// in the same order as `identifiers`, and the refreshed [`PublicKeyPackage`]. pub fn compute_refreshing_shares<C: Ciphersuite, R: RngCore + CryptoRng>( pub_key_package: PublicKeyPackage<C>, max_signers: u16, @@ -86,9 +115,11 @@ pub fn compute_refreshing_shares<C: Ciphersuite, R: RngCore + CryptoRng>( 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 +/// Refresh a share in the Trusted Dealer refresh procedure. +/// +/// Must be called by each participant refreshing the shares, with the +/// `refreshing_share` received from the trusted dealer and the +/// `current_key_package` of the participant. pub fn refresh_share<C: Ciphersuite>( mut refreshing_share: SecretShare<C>, current_key_package: &KeyPackage<C>, @@ -108,6 +139,10 @@ pub fn refresh_share<C: Ciphersuite>( // Verify refreshing_share secret share let refreshed_share_package = KeyPackage::<C>::try_from(refreshing_share)?; + if refreshed_share_package.min_signers() != current_key_package.min_signers() { + return Err(Error::InvalidMinSigners); + } + let signing_share: SigningShare<C> = SigningShare::new( refreshed_share_package.signing_share.to_scalar() + current_key_package.signing_share.to_scalar(), @@ -119,8 +154,20 @@ pub fn refresh_share<C: Ciphersuite>( 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. +/// Part 1 of refresh share with DKG. +/// +/// - `identifier`: The identifier of the participant that wants to refresh +/// their share. +/// - `max_signers`: the number of participants that are refreshing their +/// shares. It can be smaller than the original value, but still equal to or +/// greater than `min_signers`. +/// - `min_signers`: the threshold needed to sign. It must be equal to the +/// original value for the group (i.e. the refresh process can't reduce +/// the threshold). +/// +/// It returns the [`round1::SecretPackage`] that must be kept in memory +/// by the participant for the other steps, and the [`round1::Package`] that +/// must be sent to each other participant in the refresh run. pub fn refresh_dkg_part_1<C: Ciphersuite, R: RngCore + CryptoRng>( identifier: Identifier<C>, max_signers: u16, @@ -164,7 +211,21 @@ pub fn refresh_dkg_part_1<C: Ciphersuite, R: RngCore + CryptoRng>( Ok((secret_package, package)) } -/// Part 2 of refresh share with DKG. The identity commitment needs to be added back into the secret package. +/// Performs the second part of the refresh procedure for the +/// participant holding the given [`round1::SecretPackage`], given the received +/// [`round1::Package`]s received from the other participants. +/// +/// `round1_packages` maps the identifier of each other participant to the +/// [`round1::Package`] they sent to the current participant (the owner of +/// `secret_package`). These identifiers must come from whatever mapping the +/// participant has between communication channels and participants, i.e. they +/// must have assurance that the [`round1::Package`] came from the participant +/// with that identifier. +/// +/// It returns the [`round2::SecretPackage`] that must be kept in memory by the +/// participant for the final step, and the map of [`round2::Package`]s that +/// must be sent to each other participant who has the given identifier in the +/// map key. pub fn refresh_dkg_part2<C: Ciphersuite>( mut secret_package: round1::SecretPackage<C>, round1_packages: &BTreeMap<Identifier<C>, round1::Package<C>>, @@ -241,15 +302,39 @@ pub fn refresh_dkg_part2<C: Ciphersuite>( )) } -/// This is the step that actually refreshes the shares. New public key packages -/// and key packages are created. +/// Performs the third and final part of the refresh procedure for the +/// participant holding the given [`round2::SecretPackage`], given the received +/// [`round1::Package`]s and [`round2::Package`]s received from the other +/// participants. +/// +/// `round1_packages` must be the same used in [`refresh_dkg_part2()`]. +/// +/// `round2_packages` maps the identifier of each other participant to the +/// [`round2::Package`] they sent to the current participant (the owner of +/// `secret_package`). These identifiers must come from whatever mapping the +/// participant has between communication channels and participants, i.e. they +/// must have assurance that the [`round2::Package`] came from the participant +/// with that identifier. +/// +/// `old_pub_key_package` and `old_key_package` are the old values from the +/// participant, which are being refreshed. +/// +/// It returns the refreshed [`KeyPackage`] that has the long-lived key share +/// for the participant, and the refreshed [`PublicKeyPackage`]s that has public +/// information about all participants; both of which are required to compute +/// FROST signatures. Note that while the verifying (group) key of the +/// [`PublicKeyPackage`] will stay the same, the verifying shares will change. pub fn refresh_dkg_shares<C: Ciphersuite>( round2_secret_package: &round2::SecretPackage<C>, round1_packages: &BTreeMap<Identifier<C>, round1::Package<C>>, round2_packages: &BTreeMap<Identifier<C>, round2::Package<C>>, old_pub_key_package: PublicKeyPackage<C>, old_key_package: KeyPackage<C>, ) -> Result<(KeyPackage<C>, PublicKeyPackage<C>), Error<C>> { + if round2_secret_package.min_signers() != old_key_package.min_signers() { + return Err(Error::InvalidMinSigners); + } + // Add identity commitment back into round1_packages let mut new_round_1_packages = BTreeMap::new(); for (sender_identifier, round1_package) in round1_packages {
frost-core/src/tests/refresh.rs+225 −0 modified@@ -245,6 +245,83 @@ pub fn check_refresh_shares_with_dealer_serialisation<C: Ciphersuite, R: RngCore assert!(key_package.is_ok()); } +/// We want to test that using a different min_signers than original fails. +pub fn check_refresh_shares_with_dealer_fails_with_different_min_signers< + C: Ciphersuite, + R: RngCore + CryptoRng, +>( + 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<frost::Identifier<C>, KeyPackage<C>> = 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; + const NEW_MIN_SIGNERS: u16 = 2; + + // 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, + NEW_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); + } + + assert!( + new_shares + .iter() + .all(|(_, v)| v.is_err() && matches!(v, Err(Error::InvalidMinSigners))), + "{:?}", + new_shares + ); +} + /// Test FROST signing with DKG with a Ciphersuite. pub fn check_refresh_shares_with_dkg<C: Ciphersuite + PartialEq, R: RngCore + CryptoRng>( mut rng: R, @@ -429,3 +506,151 @@ where // Proceed with the signing test. check_sign(min_signers, key_packages, rng, pubkeys).unwrap() } + +/// Test FROST signing with DKG with a Ciphersuite, using a smaller +/// threshold than the original one. +pub fn check_refresh_shares_with_dkg_smaller_threshold< + C: Ciphersuite + PartialEq, + R: RngCore + CryptoRng, +>( + mut rng: R, +) 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<frost::Identifier<C>, KeyPackage<C>> = 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; + // Use a smaller threshold than the original + let min_signers = 2; + + 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<C>, + frost::keys::dkg::round1::SecretPackage<C>, + > = 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<C>, + BTreeMap<frost::Identifier<C>, frost::keys::dkg::round1::Package<C>>, + > = 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 + //////////////////////////////////////////////////////////////////////////// + + // For each participant, this is where they refresh their shares + // In practice, each participant will perform this on their own environments. + let mut results = Vec::new(); + for participant_identifier in remaining_ids.clone() { + results.push(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(), + )); + } + + assert!(results + .iter() + .all(|r| matches!(r, Err(Error::InvalidMinSigners)))); +}
frost-ed25519/Cargo.toml+3 −1 modified@@ -37,7 +37,9 @@ rand_chacha.workspace = true serde_json.workspace = true [features] -default = ["serialization", "cheater-detection"] +default = ["serialization", "cheater-detection", "std"] +# No longer needed. Kept for retrocompatibility until 3.0.0 +std = [] #! ## Features ## Enable `serde` support for types that need to be communicated. You ## can use `serde` to serialize structs with any encoder that supports
frost-ed25519/src/keys/refresh.rs+44 −7 modified@@ -1,15 +1,17 @@ //! 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) +//! Refer to [`frost_core::keys::refresh`] for more details. -use crate::{frost, Ciphersuite, CryptoRng, Error, Identifier, RngCore}; -use alloc::vec::Vec; +use crate::{ + frost, + keys::dkg::{round1, round2}, + Ciphersuite, CryptoRng, Error, Identifier, RngCore, +}; +use alloc::{collections::btree_map::BTreeMap, vec::Vec}; use super::{KeyPackage, PublicKeyPackage, SecretShare}; -/// Refreshes shares using a trusted dealer +/// Refer to [`frost_core::keys::refresh::compute_refreshing_shares`]. pub fn compute_refreshing_shares<C: Ciphersuite, R: RngCore + CryptoRng>( old_pub_key_package: PublicKeyPackage, max_signers: u16, @@ -26,10 +28,45 @@ pub fn compute_refreshing_shares<C: Ciphersuite, R: RngCore + CryptoRng>( ) } -/// Each participant refreshed their shares +/// Refer to [`frost_core::keys::refresh::refresh_share`]. pub fn refresh_share<C: Ciphersuite>( zero_share: SecretShare, current_share: &KeyPackage, ) -> Result<KeyPackage, Error> { frost::keys::refresh::refresh_share(zero_share, current_share) } + +/// Refer to [`frost_core::keys::refresh::refresh_dkg_part_1`]. +pub fn refresh_dkg_part1<R: RngCore + CryptoRng>( + identifier: Identifier, + max_signers: u16, + min_signers: u16, + mut rng: R, +) -> Result<(round1::SecretPackage, round1::Package), Error> { + frost::keys::refresh::refresh_dkg_part_1(identifier, max_signers, min_signers, &mut rng) +} + +/// Refer to [`frost_core::keys::refresh::refresh_dkg_part2`]. +pub fn refresh_dkg_part2( + secret_package: round1::SecretPackage, + round1_packages: &BTreeMap<Identifier, round1::Package>, +) -> Result<(round2::SecretPackage, BTreeMap<Identifier, round2::Package>), Error> { + frost::keys::refresh::refresh_dkg_part2(secret_package, round1_packages) +} + +/// Refer to [`frost_core::keys::refresh::refresh_dkg_shares`]. +pub fn refresh_dkg_shares( + round2_secret_package: &round2::SecretPackage, + round1_packages: &BTreeMap<Identifier, round1::Package>, + round2_packages: &BTreeMap<Identifier, round2::Package>, + old_pub_key_package: PublicKeyPackage, + old_key_package: KeyPackage, +) -> Result<(KeyPackage, PublicKeyPackage), Error> { + frost::keys::refresh::refresh_dkg_shares( + round2_secret_package, + round1_packages, + round2_packages, + old_pub_key_package, + old_key_package, + ) +}
frost-ed25519/tests/integration_tests.rs+19 −0 modified@@ -179,13 +179,32 @@ fn check_refresh_shares_with_dealer_fails_with_invalid_identifier() { >(max_signers, min_signers, &identifiers, error, rng); } +#[test] +fn check_refresh_shares_with_dealer_fails_with_different_min_signers() { + let rng = rand::rngs::OsRng; + + frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_different_min_signers::< + Ed25519Sha512, + _, + >(rng); +} + #[test] fn check_refresh_shares_with_dkg() { let rng = rand::rngs::OsRng; frost_core::tests::refresh::check_refresh_shares_with_dkg::<Ed25519Sha512, _>(rng); } +#[test] +fn check_refresh_shares_with_dkg_smaller_threshold() { + let rng = rand::rngs::OsRng; + + frost_core::tests::refresh::check_refresh_shares_with_dkg_smaller_threshold::<Ed25519Sha512, _>( + rng, + ); +} + #[test] fn check_sign_with_dealer() { let rng = rand::rngs::OsRng;
frost-ed448/Cargo.toml+3 −1 modified@@ -36,7 +36,9 @@ rand_chacha.workspace = true serde_json.workspace = true [features] -default = ["serialization", "cheater-detection"] +default = ["serialization", "cheater-detection", "std"] +# No longer needed. Kept for retrocompatibility until 3.0.0 +std = [] #! ## Features ## Enable `serde` support for types that need to be communicated. You ## can use `serde` to serialize structs with any encoder that supports
frost-ed448/src/keys/refresh.rs+44 −7 modified@@ -1,15 +1,17 @@ //! 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) +//! Refer to [`frost_core::keys::refresh`] for more details. -use crate::{frost, Ciphersuite, CryptoRng, Error, Identifier, RngCore}; -use alloc::vec::Vec; +use crate::{ + frost, + keys::dkg::{round1, round2}, + Ciphersuite, CryptoRng, Error, Identifier, RngCore, +}; +use alloc::{collections::btree_map::BTreeMap, vec::Vec}; use super::{KeyPackage, PublicKeyPackage, SecretShare}; -/// Refreshes shares using a trusted dealer +/// Refer to [`frost_core::keys::refresh::compute_refreshing_shares`]. pub fn compute_refreshing_shares<C: Ciphersuite, R: RngCore + CryptoRng>( old_pub_key_package: PublicKeyPackage, max_signers: u16, @@ -26,10 +28,45 @@ pub fn compute_refreshing_shares<C: Ciphersuite, R: RngCore + CryptoRng>( ) } -/// Each participant refreshed their shares +/// Refer to [`frost_core::keys::refresh::refresh_share`]. pub fn refresh_share<C: Ciphersuite>( zero_share: SecretShare, current_share: &KeyPackage, ) -> Result<KeyPackage, Error> { frost::keys::refresh::refresh_share(zero_share, current_share) } + +/// Refer to [`frost_core::keys::refresh::refresh_dkg_part_1`]. +pub fn refresh_dkg_part1<R: RngCore + CryptoRng>( + identifier: Identifier, + max_signers: u16, + min_signers: u16, + mut rng: R, +) -> Result<(round1::SecretPackage, round1::Package), Error> { + frost::keys::refresh::refresh_dkg_part_1(identifier, max_signers, min_signers, &mut rng) +} + +/// Refer to [`frost_core::keys::refresh::refresh_dkg_part2`]. +pub fn refresh_dkg_part2( + secret_package: round1::SecretPackage, + round1_packages: &BTreeMap<Identifier, round1::Package>, +) -> Result<(round2::SecretPackage, BTreeMap<Identifier, round2::Package>), Error> { + frost::keys::refresh::refresh_dkg_part2(secret_package, round1_packages) +} + +/// Refer to [`frost_core::keys::refresh::refresh_dkg_shares`]. +pub fn refresh_dkg_shares( + round2_secret_package: &round2::SecretPackage, + round1_packages: &BTreeMap<Identifier, round1::Package>, + round2_packages: &BTreeMap<Identifier, round2::Package>, + old_pub_key_package: PublicKeyPackage, + old_key_package: KeyPackage, +) -> Result<(KeyPackage, PublicKeyPackage), Error> { + frost::keys::refresh::refresh_dkg_shares( + round2_secret_package, + round1_packages, + round2_packages, + old_pub_key_package, + old_key_package, + ) +}
frost-ed448/tests/integration_tests.rs+19 −0 modified@@ -179,13 +179,32 @@ fn check_refresh_shares_with_dealer_fails_with_invalid_identifier() { >(max_signers, min_signers, &identifiers, error, rng); } +#[test] +fn check_refresh_shares_with_dealer_fails_with_different_min_signers() { + let rng = rand::rngs::OsRng; + + frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_different_min_signers::< + Ed448Shake256, + _, + >(rng); +} + #[test] fn check_refresh_shares_with_dkg() { let rng = rand::rngs::OsRng; frost_core::tests::refresh::check_refresh_shares_with_dkg::<Ed448Shake256, _>(rng); } +#[test] +fn check_refresh_shares_with_dkg_smaller_threshold() { + let rng = rand::rngs::OsRng; + + frost_core::tests::refresh::check_refresh_shares_with_dkg_smaller_threshold::<Ed448Shake256, _>( + rng, + ); +} + #[test] fn check_sign_with_dealer() { let rng = rand::rngs::OsRng;
frost-p256/Cargo.toml+3 −1 modified@@ -36,7 +36,9 @@ rand_chacha.workspace = true serde_json.workspace = true [features] -default = ["serialization", "cheater-detection"] +default = ["serialization", "cheater-detection", "std"] +# No longer needed. Kept for retrocompatibility until 3.0.0 +std = [] #! ## Features ## Enable `serde` support for types that need to be communicated. You ## can use `serde` to serialize structs with any encoder that supports
frost-p256/src/keys/refresh.rs+44 −7 modified@@ -1,15 +1,17 @@ //! 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) +//! Refer to [`frost_core::keys::refresh`] for more details. -use crate::{frost, Ciphersuite, CryptoRng, Error, Identifier, RngCore}; -use alloc::vec::Vec; +use crate::{ + frost, + keys::dkg::{round1, round2}, + Ciphersuite, CryptoRng, Error, Identifier, RngCore, +}; +use alloc::{collections::btree_map::BTreeMap, vec::Vec}; use super::{KeyPackage, PublicKeyPackage, SecretShare}; -/// Refreshes shares using a trusted dealer +/// Refer to [`frost_core::keys::refresh::compute_refreshing_shares`]. pub fn compute_refreshing_shares<C: Ciphersuite, R: RngCore + CryptoRng>( old_pub_key_package: PublicKeyPackage, max_signers: u16, @@ -26,10 +28,45 @@ pub fn compute_refreshing_shares<C: Ciphersuite, R: RngCore + CryptoRng>( ) } -/// Each participant refreshed their shares +/// Refer to [`frost_core::keys::refresh::refresh_share`]. pub fn refresh_share<C: Ciphersuite>( zero_share: SecretShare, current_share: &KeyPackage, ) -> Result<KeyPackage, Error> { frost::keys::refresh::refresh_share(zero_share, current_share) } + +/// Refer to [`frost_core::keys::refresh::refresh_dkg_part_1`]. +pub fn refresh_dkg_part1<R: RngCore + CryptoRng>( + identifier: Identifier, + max_signers: u16, + min_signers: u16, + mut rng: R, +) -> Result<(round1::SecretPackage, round1::Package), Error> { + frost::keys::refresh::refresh_dkg_part_1(identifier, max_signers, min_signers, &mut rng) +} + +/// Refer to [`frost_core::keys::refresh::refresh_dkg_part2`]. +pub fn refresh_dkg_part2( + secret_package: round1::SecretPackage, + round1_packages: &BTreeMap<Identifier, round1::Package>, +) -> Result<(round2::SecretPackage, BTreeMap<Identifier, round2::Package>), Error> { + frost::keys::refresh::refresh_dkg_part2(secret_package, round1_packages) +} + +/// Refer to [`frost_core::keys::refresh::refresh_dkg_shares`]. +pub fn refresh_dkg_shares( + round2_secret_package: &round2::SecretPackage, + round1_packages: &BTreeMap<Identifier, round1::Package>, + round2_packages: &BTreeMap<Identifier, round2::Package>, + old_pub_key_package: PublicKeyPackage, + old_key_package: KeyPackage, +) -> Result<(KeyPackage, PublicKeyPackage), Error> { + frost::keys::refresh::refresh_dkg_shares( + round2_secret_package, + round1_packages, + round2_packages, + old_pub_key_package, + old_key_package, + ) +}
frost-p256/tests/integration_tests.rs+19 −0 modified@@ -179,13 +179,32 @@ fn check_refresh_shares_with_dealer_fails_with_invalid_identifier() { >(max_signers, min_signers, &identifiers, error, rng); } +#[test] +fn check_refresh_shares_with_dealer_fails_with_different_min_signers() { + let rng = rand::rngs::OsRng; + + frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_different_min_signers::< + P256Sha256, + _, + >(rng); +} + #[test] fn check_refresh_shares_with_dkg() { let rng = rand::rngs::OsRng; frost_core::tests::refresh::check_refresh_shares_with_dkg::<P256Sha256, _>(rng); } +#[test] +fn check_refresh_shares_with_dkg_smaller_threshold() { + let rng = rand::rngs::OsRng; + + frost_core::tests::refresh::check_refresh_shares_with_dkg_smaller_threshold::<P256Sha256, _>( + rng, + ); +} + #[test] fn check_sign_with_dealer() { let rng = rand::rngs::OsRng;
frost-rerandomized/Cargo.toml+3 −1 modified@@ -25,7 +25,9 @@ rand_core.workspace = true [dev-dependencies] [features] -default = ["serialization", "cheater-detection"] +default = ["serialization", "cheater-detection", "std"] +# No longer needed. Kept for retrocompatibility until 3.0.0 +std = [] #! ## Features ## Enable `serde` support for types that need to be communicated. You ## can use `serde` to serialize structs with any encoder that supports
frost-ristretto255/Cargo.toml+3 −1 modified@@ -37,7 +37,9 @@ rand_chacha.workspace = true serde_json.workspace = true [features] -default = ["serialization", "cheater-detection"] +default = ["serialization", "cheater-detection", "std"] +# No longer needed. Kept for retrocompatibility until 3.0.0 +std = [] #! ## Features ## Enable `serde` support for types that need to be communicated. You ## can use `serde` to serialize structs with any encoder that supports
frost-ristretto255/src/keys/refresh.rs+44 −7 modified@@ -1,15 +1,17 @@ //! 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) +//! Refer to [`frost_core::keys::refresh`] for more details. -use crate::{frost, Ciphersuite, CryptoRng, Error, Identifier, RngCore}; -use alloc::vec::Vec; +use crate::{ + frost, + keys::dkg::{round1, round2}, + Ciphersuite, CryptoRng, Error, Identifier, RngCore, +}; +use alloc::{collections::btree_map::BTreeMap, vec::Vec}; use super::{KeyPackage, PublicKeyPackage, SecretShare}; -/// Refreshes shares using a trusted dealer +/// Refer to [`frost_core::keys::refresh::compute_refreshing_shares`]. pub fn compute_refreshing_shares<C: Ciphersuite, R: RngCore + CryptoRng>( old_pub_key_package: PublicKeyPackage, max_signers: u16, @@ -26,10 +28,45 @@ pub fn compute_refreshing_shares<C: Ciphersuite, R: RngCore + CryptoRng>( ) } -/// Each participant refreshed their shares +/// Refer to [`frost_core::keys::refresh::refresh_share`]. pub fn refresh_share<C: Ciphersuite>( zero_share: SecretShare, current_share: &KeyPackage, ) -> Result<KeyPackage, Error> { frost::keys::refresh::refresh_share(zero_share, current_share) } + +/// Refer to [`frost_core::keys::refresh::refresh_dkg_part_1`]. +pub fn refresh_dkg_part1<R: RngCore + CryptoRng>( + identifier: Identifier, + max_signers: u16, + min_signers: u16, + mut rng: R, +) -> Result<(round1::SecretPackage, round1::Package), Error> { + frost::keys::refresh::refresh_dkg_part_1(identifier, max_signers, min_signers, &mut rng) +} + +/// Refer to [`frost_core::keys::refresh::refresh_dkg_part2`]. +pub fn refresh_dkg_part2( + secret_package: round1::SecretPackage, + round1_packages: &BTreeMap<Identifier, round1::Package>, +) -> Result<(round2::SecretPackage, BTreeMap<Identifier, round2::Package>), Error> { + frost::keys::refresh::refresh_dkg_part2(secret_package, round1_packages) +} + +/// Refer to [`frost_core::keys::refresh::refresh_dkg_shares`]. +pub fn refresh_dkg_shares( + round2_secret_package: &round2::SecretPackage, + round1_packages: &BTreeMap<Identifier, round1::Package>, + round2_packages: &BTreeMap<Identifier, round2::Package>, + old_pub_key_package: PublicKeyPackage, + old_key_package: KeyPackage, +) -> Result<(KeyPackage, PublicKeyPackage), Error> { + frost::keys::refresh::refresh_dkg_shares( + round2_secret_package, + round1_packages, + round2_packages, + old_pub_key_package, + old_key_package, + ) +}
frost-ristretto255/tests/integration_tests.rs+20 −0 modified@@ -180,13 +180,33 @@ fn check_refresh_shares_with_dealer_fails_with_invalid_identifier() { >(max_signers, min_signers, &identifiers, error, rng); } +#[test] +fn check_refresh_shares_with_dealer_fails_with_different_min_signers() { + let rng = rand::rngs::OsRng; + + frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_different_min_signers::< + Ristretto255Sha512, + _, + >(rng); +} + #[test] fn check_refresh_shares_with_dkg() { let rng = rand::rngs::OsRng; frost_core::tests::refresh::check_refresh_shares_with_dkg::<Ristretto255Sha512, _>(rng); } +#[test] +fn check_refresh_shares_with_dkg_smaller_threshold() { + let rng = rand::rngs::OsRng; + + frost_core::tests::refresh::check_refresh_shares_with_dkg_smaller_threshold::< + Ristretto255Sha512, + _, + >(rng); +} + #[test] fn check_sign_with_dealer() { let rng = rand::rngs::OsRng;
frost-secp256k1/Cargo.toml+3 −1 modified@@ -36,7 +36,9 @@ rand_chacha.workspace = true serde_json.workspace = true [features] -default = ["serialization", "cheater-detection"] +default = ["serialization", "cheater-detection", "std"] +# No longer needed. Kept for retrocompatibility until 3.0.0 +std = [] #! ## Features ## Enable `serde` support for types that need to be communicated. You ## can use `serde` to serialize structs with any encoder that supports
frost-secp256k1/src/keys/refresh.rs+44 −7 modified@@ -1,15 +1,17 @@ //! 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) +//! Refer to [`frost_core::keys::refresh`] for more details. -use crate::{frost, Ciphersuite, CryptoRng, Error, Identifier, RngCore}; -use alloc::vec::Vec; +use crate::{ + frost, + keys::dkg::{round1, round2}, + Ciphersuite, CryptoRng, Error, Identifier, RngCore, +}; +use alloc::{collections::btree_map::BTreeMap, vec::Vec}; use super::{KeyPackage, PublicKeyPackage, SecretShare}; -/// Refreshes shares using a trusted dealer +/// Refer to [`frost_core::keys::refresh::compute_refreshing_shares`]. pub fn compute_refreshing_shares<C: Ciphersuite, R: RngCore + CryptoRng>( old_pub_key_package: PublicKeyPackage, max_signers: u16, @@ -26,10 +28,45 @@ pub fn compute_refreshing_shares<C: Ciphersuite, R: RngCore + CryptoRng>( ) } -/// Each participant refreshed their shares +/// Refer to [`frost_core::keys::refresh::refresh_share`]. pub fn refresh_share<C: Ciphersuite>( zero_share: SecretShare, current_share: &KeyPackage, ) -> Result<KeyPackage, Error> { frost::keys::refresh::refresh_share(zero_share, current_share) } + +/// Refer to [`frost_core::keys::refresh::refresh_dkg_part_1`]. +pub fn refresh_dkg_part1<R: RngCore + CryptoRng>( + identifier: Identifier, + max_signers: u16, + min_signers: u16, + mut rng: R, +) -> Result<(round1::SecretPackage, round1::Package), Error> { + frost::keys::refresh::refresh_dkg_part_1(identifier, max_signers, min_signers, &mut rng) +} + +/// Refer to [`frost_core::keys::refresh::refresh_dkg_part2`]. +pub fn refresh_dkg_part2( + secret_package: round1::SecretPackage, + round1_packages: &BTreeMap<Identifier, round1::Package>, +) -> Result<(round2::SecretPackage, BTreeMap<Identifier, round2::Package>), Error> { + frost::keys::refresh::refresh_dkg_part2(secret_package, round1_packages) +} + +/// Refer to [`frost_core::keys::refresh::refresh_dkg_shares`]. +pub fn refresh_dkg_shares( + round2_secret_package: &round2::SecretPackage, + round1_packages: &BTreeMap<Identifier, round1::Package>, + round2_packages: &BTreeMap<Identifier, round2::Package>, + old_pub_key_package: PublicKeyPackage, + old_key_package: KeyPackage, +) -> Result<(KeyPackage, PublicKeyPackage), Error> { + frost::keys::refresh::refresh_dkg_shares( + round2_secret_package, + round1_packages, + round2_packages, + old_pub_key_package, + old_key_package, + ) +}
frost-secp256k1/tests/integration_tests.rs+19 −0 modified@@ -179,13 +179,32 @@ fn check_refresh_shares_with_dealer_fails_with_invalid_identifier() { >(max_signers, min_signers, &identifiers, error, rng); } +#[test] +fn check_refresh_shares_with_dealer_fails_with_different_min_signers() { + let rng = rand::rngs::OsRng; + + frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_different_min_signers::< + Secp256K1Sha256, + _, + >(rng); +} + #[test] fn check_refresh_shares_with_dkg() { let rng = rand::rngs::OsRng; frost_core::tests::refresh::check_refresh_shares_with_dkg::<Secp256K1Sha256, _>(rng); } +#[test] +fn check_refresh_shares_with_dkg_smaller_threshold() { + let rng = rand::rngs::OsRng; + + frost_core::tests::refresh::check_refresh_shares_with_dkg_smaller_threshold::<Secp256K1Sha256, _>( + rng, + ); +} + #[test] fn check_sign_with_dealer() { let rng = rand::rngs::OsRng;
frost-secp256k1-tr/Cargo.toml+3 −1 modified@@ -37,7 +37,9 @@ secp256k1 = "0.31.0" serde_json.workspace = true [features] -default = ["serialization", "cheater-detection"] +default = ["serialization", "cheater-detection", "std"] +# No longer needed. Kept for retrocompatibility until 3.0.0 +std = [] #! ## Features ## Enable `serde` support for types that need to be communicated. You ## can use `serde` to serialize structs with any encoder that supports
frost-secp256k1-tr/src/keys/refresh.rs+44 −7 modified@@ -1,15 +1,17 @@ //! 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) +//! Refer to [`frost_core::keys::refresh`] for more details. -use crate::{frost, Ciphersuite, CryptoRng, Error, Identifier, RngCore}; -use alloc::vec::Vec; +use crate::{ + frost, + keys::dkg::{round1, round2}, + Ciphersuite, CryptoRng, Error, Identifier, RngCore, +}; +use alloc::{collections::btree_map::BTreeMap, vec::Vec}; use super::{KeyPackage, PublicKeyPackage, SecretShare}; -/// Refreshes shares using a trusted dealer +/// Refer to [`frost_core::keys::refresh::compute_refreshing_shares`]. pub fn compute_refreshing_shares<C: Ciphersuite, R: RngCore + CryptoRng>( old_pub_key_package: PublicKeyPackage, max_signers: u16, @@ -26,10 +28,45 @@ pub fn compute_refreshing_shares<C: Ciphersuite, R: RngCore + CryptoRng>( ) } -/// Each participant refreshed their shares +/// Refer to [`frost_core::keys::refresh::refresh_share`]. pub fn refresh_share<C: Ciphersuite>( zero_share: SecretShare, current_share: &KeyPackage, ) -> Result<KeyPackage, Error> { frost::keys::refresh::refresh_share(zero_share, current_share) } + +/// Refer to [`frost_core::keys::refresh::refresh_dkg_part_1`]. +pub fn refresh_dkg_part1<R: RngCore + CryptoRng>( + identifier: Identifier, + max_signers: u16, + min_signers: u16, + mut rng: R, +) -> Result<(round1::SecretPackage, round1::Package), Error> { + frost::keys::refresh::refresh_dkg_part_1(identifier, max_signers, min_signers, &mut rng) +} + +/// Refer to [`frost_core::keys::refresh::refresh_dkg_part2`]. +pub fn refresh_dkg_part2( + secret_package: round1::SecretPackage, + round1_packages: &BTreeMap<Identifier, round1::Package>, +) -> Result<(round2::SecretPackage, BTreeMap<Identifier, round2::Package>), Error> { + frost::keys::refresh::refresh_dkg_part2(secret_package, round1_packages) +} + +/// Refer to [`frost_core::keys::refresh::refresh_dkg_shares`]. +pub fn refresh_dkg_shares( + round2_secret_package: &round2::SecretPackage, + round1_packages: &BTreeMap<Identifier, round1::Package>, + round2_packages: &BTreeMap<Identifier, round2::Package>, + old_pub_key_package: PublicKeyPackage, + old_key_package: KeyPackage, +) -> Result<(KeyPackage, PublicKeyPackage), Error> { + frost::keys::refresh::refresh_dkg_shares( + round2_secret_package, + round1_packages, + round2_packages, + old_pub_key_package, + old_key_package, + ) +}
frost-secp256k1-tr/tests/integration_tests.rs+20 −0 modified@@ -180,13 +180,33 @@ fn check_refresh_shares_with_dealer_fails_with_invalid_identifier() { >(max_signers, min_signers, &identifiers, error, rng); } +#[test] +fn check_refresh_shares_with_dealer_fails_with_different_min_signers() { + let rng = rand::rngs::OsRng; + + frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_different_min_signers::< + Secp256K1Sha256TR, + _, + >(rng); +} + #[test] fn check_refresh_shares_with_dkg() { let rng = rand::rngs::OsRng; frost_core::tests::refresh::check_refresh_shares_with_dkg::<Secp256K1Sha256TR, _>(rng); } +#[test] +fn check_refresh_shares_with_dkg_smaller_threshold() { + let rng = rand::rngs::OsRng; + + frost_core::tests::refresh::check_refresh_shares_with_dkg_smaller_threshold::< + Secp256K1Sha256TR, + _, + >(rng); +} + #[test] fn check_sign_with_dealer() { let rng = rand::rngs::OsRng;
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- github.com/advisories/GHSA-wgq8-vr6r-mqxmghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2025-58359ghsaADVISORY
- github.com/ZcashFoundation/frost/commit/379ef689c733b3d9c80fd409071d4f3af4dafed2nvdWEB
- github.com/ZcashFoundation/frost/releases/tag/frost-core%2Fv2.2.0nvdWEB
- github.com/ZcashFoundation/frost/security/advisories/GHSA-wgq8-vr6r-mqxmnvdWEB
News mentions
0No linked articles in our index yet.