From ead5a27c966cd168da18ba73054904fecc8837d2 Mon Sep 17 00:00:00 2001 From: Robin Salen Date: Tue, 12 Mar 2024 14:37:41 +0900 Subject: [PATCH 1/5] Add test case for resharing from outside of FROST --- src/keys.rs | 10 +++++- tests/ice_frost.rs | 88 +++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 96 insertions(+), 2 deletions(-) diff --git a/src/keys.rs b/src/keys.rs index 35467a6..7fe52c8 100644 --- a/src/keys.rs +++ b/src/keys.rs @@ -200,7 +200,15 @@ impl Drop for IndividualSigningKey { } impl IndividualSigningKey { - /// Derive the corresponding public key for this secret key. + /// Outputs an [`IndividualSigningKey`] from an isolated secret key. + /// This can be useful for single parties owning a public key for + /// Schnorr signatures outside of an ICE-FROST context and who would + /// like to reshare its corresponding secret key to a set of participants. + pub fn from_single_key(key: ::ScalarField) -> Self { + Self { index: 1, key } + } + + /// Derives the corresponding public key for this secret key. pub fn to_public(&self) -> IndividualVerifyingKey { let share = C::G::generator() * self.key; diff --git a/tests/ice_frost.rs b/tests/ice_frost.rs index 7024092..2da48ad 100644 --- a/tests/ice_frost.rs +++ b/tests/ice_frost.rs @@ -1,10 +1,13 @@ //! Integration tests for ICE-FROST. +use ark_ec::Group; +use ark_ff::UniformRand; +use ice_frost::keys::{DiffieHellmanPrivateKey, GroupVerifyingKey, IndividualSigningKey}; use rand::rngs::OsRng; use ice_frost::CipherSuite; -use ice_frost::dkg::{DistributedKeyGeneration, Participant}; +use ice_frost::dkg::{DistributedKeyGeneration, EncryptedSecretShare, Participant, RoundOne}; use ice_frost::parameters::ThresholdParameters; use ice_frost::sign::generate_commitment_share_lists; @@ -272,3 +275,86 @@ fn signing_and_verification_3_out_of_5() { assert!(verification_result1.is_ok()); assert!(verification_result2.is_ok()); } + +#[test] +fn resharing_from_non_frost_key() { + type SchnorrSecretKey = <::G as Group>::ScalarField; + type SchnorrPublicKey = ::G; + + // A single party outside of any ICE-FROST scenario, who owns a keypair to perform + // Schnorr signatures. + let mut rng = OsRng; + let single_party_sk: SchnorrSecretKey = SchnorrSecretKey::rand(&mut rng); + let single_party_pk: SchnorrPublicKey = + <::G>::generator() * single_party_sk; + + // Converts this party's keys into ICE-FROST format, simulating a 1-out-of-1 setup. + let simulated_parameters = ThresholdParameters::new(1, 1); + let frost_sk = IndividualSigningKey::from_single_key(single_party_sk); + let frost_pk = GroupVerifyingKey::new(single_party_pk); + + // Start a resharing phase from this single party to a set of new participants. + const N_PARAM: u32 = 5; + const T_PARAM: u32 = 3; + + let threshold_parameters = ThresholdParameters::new(N_PARAM, T_PARAM); + + let mut signers = Vec::>::new(); + let mut signers_dh_secret_keys = Vec::>::new(); + + for i in 1..=N_PARAM { + let (p, dh_sk) = + Participant::::new_signer(threshold_parameters, i, rng).unwrap(); + + signers.push(p); + signers_dh_secret_keys.push(dh_sk); + } + + let mut signers_encrypted_secret_shares: Vec>> = + (0..N_PARAM).map(|_| Vec::new()).collect(); + + let mut signers_states_1 = Vec::>::new(); + let mut signers_states_2 = Vec::>::new(); + + let (single_dealer, dealer_encrypted_shares_for_signers, _participant_lists) = + Participant::reshare(threshold_parameters, &frost_sk, &signers, rng).unwrap(); + + for i in 0..N_PARAM as usize { + let (signer_state, _participant_lists) = + DistributedKeyGeneration::::new( + simulated_parameters, + &signers_dh_secret_keys[i], + signers[i].index, + &[single_dealer.clone()], + rng, + ) + .unwrap(); + signers_states_1.push(signer_state); + } + + for (i, shares) in signers_encrypted_secret_shares.iter_mut().enumerate() { + let share_for_signer = dealer_encrypted_shares_for_signers + .get(&(i as u32 + 1)) + .unwrap() + .clone(); + *shares = vec![share_for_signer]; + } + + for i in 0..N_PARAM as usize { + let (si_state, complaints) = signers_states_1[i] + .clone() + .to_round_two(&signers_encrypted_secret_shares[i], rng) + .unwrap(); + assert!(complaints.is_empty()); + + signers_states_2.push(si_state); + } + + for signers_state in &signers_states_2 { + let (si_group_key, _si_sk) = signers_state.clone().finish().unwrap(); + + // Assert that each signer's individual group key matches the converted + // single's party public key. + assert!(si_group_key == frost_pk); + } +} From a0b4b8e66f578cbdbc9eb323f8a29c94b24b6422 Mon Sep 17 00:00:00 2001 From: Robin Salen Date: Tue, 12 Mar 2024 15:29:21 +0900 Subject: [PATCH 2/5] Add signing check --- tests/ice_frost.rs | 57 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 55 insertions(+), 2 deletions(-) diff --git a/tests/ice_frost.rs b/tests/ice_frost.rs index 2da48ad..1221a34 100644 --- a/tests/ice_frost.rs +++ b/tests/ice_frost.rs @@ -9,7 +9,9 @@ use ice_frost::CipherSuite; use ice_frost::dkg::{DistributedKeyGeneration, EncryptedSecretShare, Participant, RoundOne}; use ice_frost::parameters::ThresholdParameters; -use ice_frost::sign::generate_commitment_share_lists; +use ice_frost::sign::{ + generate_commitment_share_lists, PublicCommitmentShareList, SecretCommitmentShareList, +}; use ice_frost::sign::SignatureAggregator; @@ -18,6 +20,9 @@ use ice_frost::testing::Secp256k1Sha256; type ParticipantDKG = Participant; type Dkg = DistributedKeyGeneration; +type PublicCommShareList = PublicCommitmentShareList; +type SecretCommShareList = SecretCommitmentShareList; + #[test] fn signing_and_verification_3_out_of_5() { let params = ThresholdParameters::new(5, 3); @@ -350,11 +355,59 @@ fn resharing_from_non_frost_key() { signers_states_2.push(si_state); } + let mut signers_secret_keys = Vec::>::new(); + for signers_state in &signers_states_2 { - let (si_group_key, _si_sk) = signers_state.clone().finish().unwrap(); + let (si_group_key, si_sk) = signers_state.clone().finish().unwrap(); + signers_secret_keys.push(si_sk); // Assert that each signer's individual group key matches the converted // single's party public key. assert!(si_group_key == frost_pk); } + + let message = b"This is a test of the tsunami alert system. This is only a test."; + + let mut signers_public_comshares = Vec::::with_capacity(N_PARAM as usize); + let mut signers_secret_comshares = Vec::::with_capacity(N_PARAM as usize); + + for i in 0..T_PARAM { + let (pi_public_comshares, pi_secret_comshares) = + generate_commitment_share_lists(&mut OsRng, &signers_secret_keys[i as usize], 1) + .unwrap(); + signers_public_comshares.push(pi_public_comshares); + signers_secret_comshares.push(pi_secret_comshares); + } + + let mut aggregator = SignatureAggregator::new(threshold_parameters, frost_pk, &message[..]); + + for i in 0..T_PARAM { + aggregator.include_signer( + signers[i as usize].index, + signers_public_comshares[i as usize].commitments[0], + &signers_secret_keys[i as usize].to_public(), + ); + } + + let participating_signers = aggregator.get_signers().clone(); + let message_hash = Secp256k1Sha256::h4(&message[..]); + + for i in 0..T_PARAM { + let pi_partial_signature = signers_secret_keys[i as usize] + .sign( + &message_hash, + &frost_pk, + &mut signers_secret_comshares[i as usize], + 0, + &participating_signers, + ) + .unwrap(); + aggregator.include_partial_signature(&pi_partial_signature); + } + + let aggregator = aggregator.finalize().unwrap(); + + let threshold_signature = aggregator.aggregate().unwrap(); + + assert!(threshold_signature.verify(&frost_pk, &message_hash).is_ok()); } From 4ffd620b15dd576151d8eebf78ae06b7bcf61177 Mon Sep 17 00:00:00 2001 From: Robin Salen <30937548+Nashtare@users.noreply.github.com> Date: Tue, 12 Mar 2024 18:54:01 +0900 Subject: [PATCH 3/5] Fix clippy warning and remove a clone on the signers set during `finalize()` (#37) * Fix clippy warning and remove a clone * Update comment --- src/sign/signature.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/sign/signature.rs b/src/sign/signature.rs index d7513cc..40ad150 100644 --- a/src/sign/signature.rs +++ b/src/sign/signature.rs @@ -476,6 +476,8 @@ impl SignatureAggregator> { } /// Get the list of partipating signers. + /// It will internally sort the signers by indices, remove any duplicate, + /// and then return a reference to the updated internal list. /// /// # Returns /// @@ -556,7 +558,7 @@ impl SignatureAggregator> { } // Ensure that our new state is ordered and deduplicated. - self.state.signers = self.get_signers().clone(); + let _ = self.get_signers(); for signer in &self.state.signers { if self From 9f782335c9c3974f9b3bfd136b1842fa4a977212 Mon Sep 17 00:00:00 2001 From: Robin Salen Date: Wed, 3 Apr 2024 09:27:31 +0900 Subject: [PATCH 4/5] Fix doc --- src/dkg/key_generation.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/dkg/key_generation.rs b/src/dkg/key_generation.rs index d46238a..b5850b0 100644 --- a/src/dkg/key_generation.rs +++ b/src/dkg/key_generation.rs @@ -666,8 +666,9 @@ impl DistributedKeyGeneration { /// /// # Inputs /// - /// * The protocol new instance [`ThresholdParameters`]. These parameters can - /// be different from the previous ICE-FROST session using the same group key. + /// * The protocol old instance [`ThresholdParameters`]. These parameters can + /// be different from the current ICE-FROST session which will be having the + /// same group key. /// * This participant's [`DiffieHellmanPrivateKey`]. /// * This participant's `index`. /// * The list of `dealers`. These are the participants of the previous ICE-FROST From df832a4eb28484c512fa253ed2f57bdddc05b72f Mon Sep 17 00:00:00 2001 From: Robin Salen Date: Wed, 3 Apr 2024 09:29:20 +0900 Subject: [PATCH 5/5] Rename constants --- tests/ice_frost.rs | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/tests/ice_frost.rs b/tests/ice_frost.rs index 1221a34..29b1436 100644 --- a/tests/ice_frost.rs +++ b/tests/ice_frost.rs @@ -299,15 +299,16 @@ fn resharing_from_non_frost_key() { let frost_pk = GroupVerifyingKey::new(single_party_pk); // Start a resharing phase from this single party to a set of new participants. - const N_PARAM: u32 = 5; - const T_PARAM: u32 = 3; + const NUMBER_OF_PARTICIPANTS: u32 = 5; + const THRESHOLD_OF_PARTICIPANTS: u32 = 3; - let threshold_parameters = ThresholdParameters::new(N_PARAM, T_PARAM); + let threshold_parameters = + ThresholdParameters::new(NUMBER_OF_PARTICIPANTS, THRESHOLD_OF_PARTICIPANTS); let mut signers = Vec::>::new(); let mut signers_dh_secret_keys = Vec::>::new(); - for i in 1..=N_PARAM { + for i in 1..=NUMBER_OF_PARTICIPANTS { let (p, dh_sk) = Participant::::new_signer(threshold_parameters, i, rng).unwrap(); @@ -316,7 +317,7 @@ fn resharing_from_non_frost_key() { } let mut signers_encrypted_secret_shares: Vec>> = - (0..N_PARAM).map(|_| Vec::new()).collect(); + (0..NUMBER_OF_PARTICIPANTS).map(|_| Vec::new()).collect(); let mut signers_states_1 = Vec::>::new(); let mut signers_states_2 = Vec::>::new(); @@ -324,7 +325,7 @@ fn resharing_from_non_frost_key() { let (single_dealer, dealer_encrypted_shares_for_signers, _participant_lists) = Participant::reshare(threshold_parameters, &frost_sk, &signers, rng).unwrap(); - for i in 0..N_PARAM as usize { + for i in 0..NUMBER_OF_PARTICIPANTS as usize { let (signer_state, _participant_lists) = DistributedKeyGeneration::::new( simulated_parameters, @@ -345,7 +346,7 @@ fn resharing_from_non_frost_key() { *shares = vec![share_for_signer]; } - for i in 0..N_PARAM as usize { + for i in 0..NUMBER_OF_PARTICIPANTS as usize { let (si_state, complaints) = signers_states_1[i] .clone() .to_round_two(&signers_encrypted_secret_shares[i], rng) @@ -368,10 +369,12 @@ fn resharing_from_non_frost_key() { let message = b"This is a test of the tsunami alert system. This is only a test."; - let mut signers_public_comshares = Vec::::with_capacity(N_PARAM as usize); - let mut signers_secret_comshares = Vec::::with_capacity(N_PARAM as usize); + let mut signers_public_comshares = + Vec::::with_capacity(NUMBER_OF_PARTICIPANTS as usize); + let mut signers_secret_comshares = + Vec::::with_capacity(NUMBER_OF_PARTICIPANTS as usize); - for i in 0..T_PARAM { + for i in 0..THRESHOLD_OF_PARTICIPANTS { let (pi_public_comshares, pi_secret_comshares) = generate_commitment_share_lists(&mut OsRng, &signers_secret_keys[i as usize], 1) .unwrap(); @@ -381,7 +384,7 @@ fn resharing_from_non_frost_key() { let mut aggregator = SignatureAggregator::new(threshold_parameters, frost_pk, &message[..]); - for i in 0..T_PARAM { + for i in 0..THRESHOLD_OF_PARTICIPANTS { aggregator.include_signer( signers[i as usize].index, signers_public_comshares[i as usize].commitments[0], @@ -392,7 +395,7 @@ fn resharing_from_non_frost_key() { let participating_signers = aggregator.get_signers().clone(); let message_hash = Secp256k1Sha256::h4(&message[..]); - for i in 0..T_PARAM { + for i in 0..THRESHOLD_OF_PARTICIPANTS { let pi_partial_signature = signers_secret_keys[i as usize] .sign( &message_hash,