diff --git a/synedrion/src/cggmp21/entities.rs b/synedrion/src/cggmp21/entities.rs index 8034152..cb247d1 100644 --- a/synedrion/src/cggmp21/entities.rs +++ b/synedrion/src/cggmp21/entities.rs @@ -1,8 +1,10 @@ use alloc::{ collections::{BTreeMap, BTreeSet}, + format, vec::Vec, }; use core::{fmt::Debug, marker::PhantomData}; +use manul::session::LocalError; use k256::ecdsa::VerifyingKey; use rand_core::CryptoRngCore; @@ -125,23 +127,43 @@ pub(crate) struct PresigningValues { impl KeyShare { /// Updates a key share with a change obtained from KeyRefresh protocol. - pub fn update(self, change: KeyShareChange) -> Self { - // TODO (#68): check that party_idx is the same for both, and the number of parties is the same - assert_eq!(self.owner, change.owner); + pub fn update(self, change: KeyShareChange) -> Result { + if self.owner != change.owner { + return Err(LocalError::new(format!( + "Owning party mismatch. self.owner={:?}, change.owner={:?}", + self.owner, change.owner + ))); + } + if self.public_shares.len() != change.public_share_changes.len() { + return Err(LocalError::new(format!( + "Inconsistent number of public key shares in updated share set (expected {}, was {})", + self.public_shares.len(), + change.public_share_changes.len() + ))); + } let secret_share = self.secret_share + change.secret_share_change; let public_shares = self .public_shares .iter() - .map(|(id, public_share)| (id.clone(), *public_share + change.public_share_changes[id])) - .collect(); + .map(|(id, public_share)| { + Ok(( + id.clone(), + *public_share + + *change + .public_share_changes + .get(id) + .ok_or_else(|| LocalError::new("id={id:?} is missing in public_share_changes"))?, + )) + }) + .collect::>()?; - Self { + Ok(Self { owner: self.owner, secret_share, public_shares, phantom: PhantomData, - } + }) } /// Creates a set of random self-consistent key shares diff --git a/synedrion/src/cggmp21/interactive_signing.rs b/synedrion/src/cggmp21/interactive_signing.rs index f84fc8f..7ab056d 100644 --- a/synedrion/src/cggmp21/interactive_signing.rs +++ b/synedrion/src/cggmp21/interactive_signing.rs @@ -18,7 +18,7 @@ use rand_core::CryptoRngCore; use serde::{Deserialize, Serialize}; use super::{ - entities::{AuxInfo, AuxInfoPrecomputed, KeyShare, PresigningData, PresigningValues}, + entities::{AuxInfo, AuxInfoPrecomputed, KeyShare, PresigningData, PresigningValues, PublicAuxInfoPrecomputed}, params::{ bounded_from_scalar, secret_bounded_from_scalar, secret_scalar_from_signed, secret_signed_from_scalar, secret_uint_from_scalar, signed_from_scalar, SchemeParams, @@ -200,6 +200,26 @@ struct Context { nu: Randomizer, } +impl Context +where + P: SchemeParams, + I: Ord + Debug, +{ + pub fn public_share(&self, i: &I) -> Result<&Point, LocalError> { + self.key_share + .public_shares + .get(i) + .ok_or_else(|| LocalError::new("Missing public_share for party Id {i:?}")) + } + + pub fn public_aux(&self, i: &I) -> Result<&PublicAuxInfoPrecomputed

, LocalError> { + self.aux_info + .public_aux + .get(i) + .ok_or_else(|| LocalError::new(format!("Missing public_aux for party Id {i:?}"))) + } +} + #[derive(Debug)] struct Round1 { context: Context, @@ -207,6 +227,16 @@ struct Round1 { cap_g: Ciphertext, } +impl Round1 { + fn public_aux(&self, i: &I) -> Result<&PublicAuxInfoPrecomputed

, LocalError> { + self.context + .aux_info + .public_aux + .get(i) + .ok_or_else(|| LocalError::new(format!("Missing public_aux for party Id {i:?}"))) + } +} + #[derive(Clone, Serialize, Deserialize)] #[serde(bound(serialize = "CiphertextWire: Serialize"))] #[serde(bound(deserialize = "CiphertextWire: for<'x> Deserialize<'x>"))] @@ -273,7 +303,7 @@ impl Round for Round1 { &self.context.rho, self.context.aux_info.secret_aux.paillier_sk.public_key(), &self.cap_k, - &self.context.aux_info.public_aux[destination].rp_params, + &self.public_aux(destination)?.rp_params, &aux, ); @@ -296,9 +326,9 @@ impl Round for Round1 { let aux = (&self.context.ssid_hash, &self.context.my_id); - let public_aux = &self.context.aux_info.public_aux[&self.context.my_id]; + let public_aux = self.public_aux(&self.context.my_id)?; - let from_pk = &self.context.aux_info.public_aux[from].paillier_pk; + let from_pk = &self.public_aux(from)?.paillier_pk; if !direct_message.psi0.verify( from_pk, @@ -335,19 +365,21 @@ impl Round for Round1 { let mut all_cap_k = others_cap_k .into_iter() .map(|(id, ciphertext)| { - let ciphertext_mod = ciphertext.to_precomputed(&self.context.aux_info.public_aux[&id].paillier_pk); - (id, ciphertext_mod) + let paux = self.public_aux(&id)?; + Ok((id, ciphertext.to_precomputed(&paux.paillier_pk))) }) - .collect::>(); - all_cap_k.insert(my_id.clone(), self.cap_k); + .collect::, _>>()?; let mut all_cap_g = others_cap_g .into_iter() .map(|(id, ciphertext)| { - let ciphertext_mod = ciphertext.to_precomputed(&self.context.aux_info.public_aux[&id].paillier_pk); - (id, ciphertext_mod) + let paux = self.public_aux(&id)?; + let ciphertext_mod = ciphertext.to_precomputed(&paux.paillier_pk); + Ok((id, ciphertext_mod)) }) - .collect::>(); + .collect::, _>>()?; + + all_cap_k.insert(my_id.clone(), self.cap_k); all_cap_g.insert(my_id, self.cap_g); Ok(FinalizeOutcome::AnotherRound(BoxedRound::new_dynamic(Round2 { @@ -439,7 +471,7 @@ impl Round for Round2 { let cap_gamma = self.context.gamma.mul_by_generator(); let pk = self.context.aux_info.secret_aux.paillier_sk.public_key(); - let target_pk = &self.context.aux_info.public_aux[destination].paillier_pk; + let target_pk = &self.context.public_aux(destination)?.paillier_pk; let beta = Secret::init_with(|| Signed::random_bounded_bits(rng, P::LP_BOUND)); let hat_beta = Secret::init_with(|| Signed::random_bounded_bits(rng, P::LP_BOUND)); @@ -452,16 +484,22 @@ impl Round for Round2 { let x = secret_signed_from_scalar::

(&self.context.key_share.secret_share); let cap_f = Ciphertext::new_with_randomizer_signed(pk, &beta, &r); - let cap_d = - &self.all_cap_k[destination] * &gamma + Ciphertext::new_with_randomizer_signed(target_pk, &-&beta, &s); + let cap_d = self + .all_cap_k + .get(destination) + .ok_or(LocalError::new("Missing destination={destination:?} in all_cap_k"))? + * &gamma + + Ciphertext::new_with_randomizer_signed(target_pk, &-&beta, &s); let hat_cap_f = Ciphertext::new_with_randomizer_signed(pk, &hat_beta, &hat_r); - let hat_cap_d = &self.all_cap_k[destination] + let hat_cap_d = self + .all_cap_k + .get(destination) + .ok_or(LocalError::new("Missing destination={destination:?} in all_cap_k"))? * secret_signed_from_scalar::

(&self.context.key_share.secret_share) + Ciphertext::new_with_randomizer_signed(target_pk, &-&hat_beta, &hat_s); - let public_aux = &self.context.aux_info.public_aux[destination]; - let rp = &public_aux.rp_params; + let rp = &self.context.public_aux(destination)?.rp_params; let psi = AffGProof::new( rng, @@ -471,7 +509,9 @@ impl Round for Round2 { &r, target_pk, pk, - &self.all_cap_k[destination], + self.all_cap_k + .get(destination) + .ok_or(LocalError::new("destination={destination:?} is missing in all_cap_k"))?, &cap_d, &cap_f, &cap_gamma, @@ -487,10 +527,12 @@ impl Round for Round2 { &hat_r, target_pk, pk, - &self.all_cap_k[destination], + self.all_cap_k + .get(destination) + .ok_or(LocalError::new("destination={destination:?} is missing in all_cap_k"))?, &hat_cap_d, &hat_cap_f, - &self.context.key_share.public_shares[&self.context.my_id], + self.context.public_share(&self.context.my_id)?, rp, &aux, ); @@ -500,7 +542,10 @@ impl Round for Round2 { &gamma, &self.context.nu, pk, - &self.all_cap_g[&self.context.my_id], + self.all_cap_g.get(&self.context.my_id).ok_or(LocalError::new(format!( + "my_id={:?} is missing in all_cap_g", + &self.context.my_id + )))?, &Point::GENERATOR, &cap_gamma, rp, @@ -550,14 +595,13 @@ impl Round for Round2 { normal_broadcast.assert_is_none()?; let direct_message = direct_message.deserialize::>(deserializer)?; - let aux = (&self.context.ssid_hash, &from); - let pk = &self.context.aux_info.secret_aux.paillier_sk.public_key(); - let from_pk = &self.context.aux_info.public_aux[from].paillier_pk; + let aux = (&self.context.ssid_hash, from); + let pk = self.context.aux_info.secret_aux.paillier_sk.public_key(); + let from_pk = &self.context.public_aux(from)?.paillier_pk; - let cap_x = self.context.key_share.public_shares[from]; + let cap_x = self.context.public_share(from)?; - let public_aux = &self.context.aux_info.public_aux[&self.context.my_id]; - let rp = &public_aux.rp_params; + let rp = &self.context.public_aux(&self.context.my_id)?.rp_params; let cap_d = direct_message.cap_d.to_precomputed(pk); let hat_cap_d = direct_message.hat_cap_d.to_precomputed(pk); @@ -565,7 +609,9 @@ impl Round for Round2 { if !direct_message.psi.verify( pk, from_pk, - &self.all_cap_k[&self.context.my_id], + self.all_cap_k + .get(&self.context.my_id) + .ok_or(LocalError::new("my_id={my_id:?} is missing in all_cap_k"))?, &cap_d, &direct_message.cap_f.to_precomputed(from_pk), &direct_message.cap_gamma, @@ -580,10 +626,12 @@ impl Round for Round2 { if !direct_message.hat_psi.verify( pk, from_pk, - &self.all_cap_k[&self.context.my_id], + self.all_cap_k + .get(&self.context.my_id) + .ok_or(LocalError::new("my_id={my_id:?} is missing in all_cap_k"))?, &hat_cap_d, &direct_message.hat_cap_f.to_precomputed(from_pk), - &cap_x, + cap_x, rp, &aux, ) { @@ -594,7 +642,9 @@ impl Round for Round2 { if !direct_message.hat_psi_prime.verify( from_pk, - &self.all_cap_g[from], + self.all_cap_g + .get(from) + .ok_or(LocalError::new("from={from:?} is missing in all_cap_g"))?, &Point::GENERATOR, &direct_message.cap_gamma, rp, @@ -695,6 +745,16 @@ struct Round3 { round2_artifacts: BTreeMap>, } +impl Round3 { + fn public_aux(&self, i: &I) -> Result<&PublicAuxInfoPrecomputed

, LocalError> { + self.context + .aux_info + .public_aux + .get(i) + .ok_or_else(|| LocalError::new(format!("Missing public_aux for party Id {i:?}"))) + } +} + #[derive(Clone, Serialize, Deserialize)] #[serde(bound(serialize = "LogStarProof

: Serialize"))] #[serde(bound(deserialize = "LogStarProof

: for<'x> Deserialize<'x>"))] @@ -737,15 +797,17 @@ impl Round for Round3 { let aux = (&self.context.ssid_hash, &self.context.my_id); let pk = &self.context.aux_info.secret_aux.paillier_sk.public_key(); - let public_aux = &self.context.aux_info.public_aux[destination]; - let rp = &public_aux.rp_params; + let rp = &self.public_aux(destination)?.rp_params; let psi_pprime = LogStarProof::new( rng, &secret_signed_from_scalar::

(&self.context.k), &self.context.rho, pk, - &self.all_cap_k[&self.context.my_id], + self.all_cap_k.get(&self.context.my_id).ok_or(LocalError::new(format!( + "my_id={:?} is missing in all_cap_k", + &self.context.my_id + )))?, &self.cap_gamma, &self.cap_delta, rp, @@ -778,14 +840,15 @@ impl Round for Round3 { let direct_message = direct_message.deserialize::>(deserializer)?; let aux = (&self.context.ssid_hash, &from); - let from_pk = &self.context.aux_info.public_aux[from].paillier_pk; + let from_pk = &self.public_aux(from)?.paillier_pk; - let public_aux = &self.context.aux_info.public_aux[&self.context.my_id]; - let rp = &public_aux.rp_params; + let rp = &self.public_aux(&self.context.my_id)?.rp_params; if !direct_message.psi_pprime.verify( from_pk, - &self.all_cap_k[from], + self.all_cap_k + .get(from) + .ok_or(LocalError::new("from={from:?} is missing in all_cap_k"))?, &self.cap_gamma, &direct_message.cap_delta, rp, @@ -833,25 +896,39 @@ impl Round for Round3 { .round2_artifacts .into_iter() .map(|(id, artifact)| { + let cap_k = self + .all_cap_k + .get(&id) + .ok_or_else(|| LocalError::new("id={id:?} is missing in all_cap_k"))? + .clone(); + let hat_cap_d_received = self + .hat_cap_ds + .get(&id) + .ok_or_else(|| LocalError::new("id={id:?} is missing in hat_cap_ds"))? + .clone(); let values = PresigningValues { hat_beta: artifact.hat_beta, hat_r: artifact.hat_r, hat_s: artifact.hat_s, - cap_k: self.all_cap_k[&id].clone(), - hat_cap_d_received: self.hat_cap_ds[&id].clone(), + cap_k, + hat_cap_d_received, hat_cap_d: artifact.hat_cap_d, hat_cap_f: artifact.hat_cap_f, }; - (id, values) + Ok((id, values)) }) - .collect(); + .collect::>()?; let presigning_data = PresigningData { nonce, ephemeral_scalar_share: self.context.k.clone(), product_share: secret_scalar_from_signed::

(&self.chi), product_share_nonreduced: self.chi, - cap_k: self.all_cap_k[&my_id].clone(), + cap_k: self + .all_cap_k + .get(&my_id) + .ok_or_else(|| LocalError::new("m_id={my_id:?} is missing in all_cap_k"))? + .clone(), values, }; @@ -874,16 +951,19 @@ impl Round for Round3 { let cap_gamma = self.context.gamma.mul_by_generator(); - for id_j in self.context.other_ids.iter() { - let r2_artefacts = &self.round2_artifacts[id_j]; - - for id_l in self.context.other_ids.iter().filter(|id| id != &id_j) { - let target_pk = &self.context.aux_info.public_aux[id_j].paillier_pk; - let rp = &self.context.aux_info.public_aux[id_l].rp_params; + for (id_j, (_, r2_artifacts)) in self.context.other_ids.iter().zip(self.round2_artifacts.iter()) { + let cap_c = self + .all_cap_k + .get(id_j) + .ok_or_else(|| LocalError::new("id_j={id_j:?} is missing in all_cap_k"))?; + for id_l in self.context.other_ids.iter().filter(|id| *id != id_j) { + let paux = self.public_aux(id_j)?; + let target_pk = &paux.paillier_pk; + let rp = &paux.rp_params; - let beta = &self.round2_artifacts[id_j].beta; - let r = &self.round2_artifacts[id_j].r; - let s = &self.round2_artifacts[id_j].s; + let beta = &r2_artifacts.beta; + let r = &r2_artifacts.r; + let s = &r2_artifacts.s; let p_aff_g = AffGProof::

::new( rng, @@ -893,9 +973,9 @@ impl Round for Round3 { r, target_pk, pk, - &self.all_cap_k[id_j], - &r2_artefacts.cap_d, - &r2_artefacts.cap_f, + cap_c, + &r2_artifacts.cap_d, + &r2_artifacts.cap_f, &cap_gamma, rp, &aux, @@ -904,9 +984,9 @@ impl Round for Round3 { assert!(p_aff_g.verify( target_pk, pk, - &self.all_cap_k[id_j], - &r2_artefacts.cap_d, - &r2_artefacts.cap_f, + cap_c, + &r2_artifacts.cap_d, + &r2_artifacts.cap_f, &cap_gamma, rp, &aux, @@ -917,10 +997,17 @@ impl Round for Round3 { } // Mul proof - + let my_id = &self.context.my_id; let rho = Randomizer::random(rng, pk); - let cap_h = (&self.all_cap_g[&self.context.my_id] * secret_bounded_from_scalar::

(&self.context.k)) - .mul_randomizer(&rho); + let cap_x = self + .all_cap_k + .get(my_id) + .ok_or_else(|| LocalError::new("my_id={my_id:?} is missing in all_cap_k"))?; + let cap_y = self + .all_cap_g + .get(my_id) + .ok_or_else(|| LocalError::new("my_id={my_id:?} is missing in all_cap_g"))?; + let cap_h = (cap_y * secret_bounded_from_scalar::

(&self.context.k)).mul_randomizer(&rho); let p_mul = MulProof::

::new( rng, @@ -928,18 +1015,12 @@ impl Round for Round3 { &self.context.rho, &rho, pk, - &self.all_cap_k[&self.context.my_id], - &self.all_cap_g[&self.context.my_id], + cap_x, + cap_y, &cap_h, &aux, ); - assert!(p_mul.verify( - pk, - &self.all_cap_k[&self.context.my_id], - &self.all_cap_g[&self.context.my_id], - &cap_h, - &aux - )); + assert!(p_mul.verify(pk, cap_x, cap_y, &cap_h, &aux)); // Dec proof @@ -968,14 +1049,14 @@ impl Round for Round3 { pk, scalar_delta.expose_secret(), &ciphertext, - &self.context.aux_info.public_aux[id_j].rp_params, + &self.public_aux(id_j)?.rp_params, &aux, ); assert!(p_dec.verify( pk, scalar_delta.expose_secret(), &ciphertext, - &self.context.aux_info.public_aux[id_j].rp_params, + &self.public_aux(id_j)?.rp_params, &aux )); dec_proofs.push((id_j.clone(), p_dec)); @@ -1101,15 +1182,15 @@ impl Round for Round4 { let mut aff_g_proofs = Vec::new(); for id_j in self.context.other_ids.iter() { - for id_l in self.context.other_ids.iter().filter(|id| id != &id_j) { - let target_pk = &self.context.aux_info.public_aux[id_j].paillier_pk; - let rp = &self.context.aux_info.public_aux[id_l].rp_params; + for id_l in self.context.other_ids.iter().filter(|id| *id != id_j) { + let target_pk = &self.context.public_aux(id_j)?.paillier_pk; + let rp = &self.context.public_aux(id_l)?.rp_params; let values = self .presigning .values .get(id_j) - .ok_or_else(|| LocalError::new(format!("Missing presigning values for {id_j:?}")))?; + .ok_or_else(|| LocalError::new("Missing presigning values for {id_j:?}"))?; let p_aff_g = AffGProof::

::new( rng, @@ -1122,7 +1203,7 @@ impl Round for Round4 { &values.cap_k, &values.hat_cap_d, &values.hat_cap_f, - &self.context.key_share.public_shares[&my_id], + self.context.public_share(&my_id)?, rp, &aux, ); @@ -1133,7 +1214,7 @@ impl Round for Round4 { &values.cap_k, &values.hat_cap_d, &values.hat_cap_f, - &self.context.key_share.public_shares[&my_id], + self.context.public_share(&my_id)?, rp, &aux, )); @@ -1145,7 +1226,7 @@ impl Round for Round4 { // mul* proofs let x = &self.context.key_share.secret_share; - let cap_x = self.context.key_share.public_shares[&my_id]; + let cap_x = self.context.public_share(&my_id)?; let rho = Randomizer::random(rng, pk); let hat_cap_h = (&self.presigning.cap_k * secret_bounded_from_scalar::

(x)).mul_randomizer(&rho); @@ -1155,6 +1236,7 @@ impl Round for Round4 { let mut mul_star_proofs = Vec::new(); for id_l in self.context.other_ids.iter() { + let paux = self.context.public_aux(id_l)?; let p_mul = MulStarProof::

::new( rng, &secret_signed_from_scalar::

(x), @@ -1162,19 +1244,12 @@ impl Round for Round4 { pk, &self.presigning.cap_k, &hat_cap_h, - &cap_x, - &self.context.aux_info.public_aux[id_l].rp_params, + cap_x, + &paux.rp_params, &aux, ); - assert!(p_mul.verify( - pk, - &self.presigning.cap_k, - &hat_cap_h, - &cap_x, - &self.context.aux_info.public_aux[id_l].rp_params, - &aux, - )); + assert!(p_mul.verify(pk, &self.presigning.cap_k, &hat_cap_h, cap_x, &paux.rp_params, &aux,)); mul_star_proofs.push((id_l.clone(), p_mul)); } @@ -1205,6 +1280,7 @@ impl Round for Round4 { let mut dec_proofs = Vec::new(); for id_l in self.context.other_ids.iter() { + let paux = self.context.public_aux(id_l)?; let p_dec = DecProof::

::new( rng, &s_part_nonreduced, @@ -1212,16 +1288,10 @@ impl Round for Round4 { pk, &self.sigma, &ciphertext, - &self.context.aux_info.public_aux[id_l].rp_params, + &paux.rp_params, &aux, ); - assert!(p_dec.verify( - pk, - &self.sigma, - &ciphertext, - &self.context.aux_info.public_aux[id_l].rp_params, - &aux, - )); + assert!(p_dec.verify(pk, &self.sigma, &ciphertext, &paux.rp_params, &aux,)); dec_proofs.push((id_l.clone(), p_dec)); } diff --git a/synedrion/src/cggmp21/key_refresh.rs b/synedrion/src/cggmp21/key_refresh.rs index 808550a..3442a5b 100644 --- a/synedrion/src/cggmp21/key_refresh.rs +++ b/synedrion/src/cggmp21/key_refresh.rs @@ -601,19 +601,39 @@ impl Round for Round3 { let phi = FacProof::new(rng, &self.context.paillier_sk, &data.rp_params, &aux); - let destination_idx = self.context.ids_ordering[destination]; + let destination_idx = *self + .context + .ids_ordering + .get(destination) + .ok_or_else(|| LocalError::new("destination={destination:?} is missing in ids_ordering"))?; - let x_secret = &self.context.x_to_send[destination]; - let x_public = self.context.data_precomp.data.cap_x_to_send[destination_idx]; + let x_secret = self + .context + .x_to_send + .get(destination) + .ok_or_else(|| LocalError::new("destination={destination} is missing in x_to_send"))?; + let x_public = self + .context + .data_precomp + .data + .cap_x_to_send + .get(destination_idx) + .ok_or_else(|| LocalError::new("destination_idx={destination_idx} is missing in cap_x_to_send"))?; let ciphertext = Ciphertext::new(rng, &data.paillier_pk, &secret_uint_from_scalar::

(x_secret)); + let proof_secret = self + .context + .tau_x + .get(destination) + .ok_or_else(|| LocalError::new("destination_idx={destination_idx} is missing in tau_x"))?; + let commitment = self + .context + .data_precomp + .data + .cap_a_to_send + .get(destination_idx) + .ok_or_else(|| LocalError::new("destination_idx={destination_idx} is missing in cap_a_to_send"))?; - let psi_sch = SchProof::new( - &self.context.tau_x[destination], - x_secret, - &self.context.data_precomp.data.cap_a_to_send[destination_idx], - &x_public, - &aux, - ); + let psi_sch = SchProof::new(proof_secret, x_secret, commitment, x_public, &aux); let data2 = PublicData2 { psi_mod: self.psi_mod.clone(), @@ -652,9 +672,19 @@ impl Round for Round3 { let x = secret_scalar_from_uint::

(&enc_x.decrypt(&self.context.paillier_sk)); - let my_idx = self.context.ids_ordering[&self.context.my_id]; - - if x.mul_by_generator() != sender_data.data.cap_x_to_send[my_idx] { + let my_idx = *self + .context + .ids_ordering + .get(&self.context.my_id) + .ok_or_else(|| LocalError::new(format!("my_id={:?} is missing in ids_ordering", self.context.my_id)))?; + + if x.mul_by_generator() + != *sender_data + .data + .cap_x_to_send + .get(my_idx) + .ok_or_else(|| LocalError::new("my_idx={my_idx} is missing in cap_x_to_send"))? + { let mu = enc_x.derive_randomizer(&self.context.paillier_sk); return Err(ReceiveError::protocol(KeyRefreshError( KeyRefreshErrorEnum::Round3MismatchedSecret { @@ -694,8 +724,16 @@ impl Round for Round3 { } if !direct_message.data2.psi_sch.verify( - &sender_data.data.cap_a_to_send[my_idx], - &sender_data.data.cap_x_to_send[my_idx], + sender_data + .data + .cap_a_to_send + .get(my_idx) + .ok_or_else(|| LocalError::new("my_idx={my_idx} is missing in cap_a_to_send"))?, + sender_data + .data + .cap_x_to_send + .get(my_idx) + .ok_or_else(|| LocalError::new("my_idx={my_idx} is missing in cap_a_to_send"))?, &aux, ) { return Err(ReceiveError::protocol(KeyRefreshError(KeyRefreshErrorEnum::Round3( @@ -719,7 +757,11 @@ impl Round for Round3 { .collect::>(); // The combined secret share change - let x_star = others_x.into_values().sum::>() + &self.context.x_to_send[&self.context.my_id]; + let x_star = + others_x.into_values().sum::>() + + self.context.x_to_send.get(&self.context.my_id).ok_or_else(|| { + LocalError::new(format!("my_id={:?} is missing in x_to_send", self.context.my_id)) + })?; let my_id = self.context.my_id.clone(); let mut all_ids = self.context.other_ids; @@ -733,12 +775,16 @@ impl Round for Round3 { .iter() .enumerate() .map(|(idx, id)| { - ( + Ok(( id.clone(), - all_data.values().map(|data| data.data.cap_x_to_send[idx]).sum(), - ) + all_data + .values() + .map(|data| data.data.cap_x_to_send.get(idx)) + .sum::>() + .ok_or_else(|| LocalError::new("idx={idx} is missing in cap_x_to_send"))?, + )) }) - .collect(); + .collect::>()?; let public_aux = all_data .into_iter() diff --git a/synedrion/src/cggmp21/params.rs b/synedrion/src/cggmp21/params.rs index 18f5cf9..44be41a 100644 --- a/synedrion/src/cggmp21/params.rs +++ b/synedrion/src/cggmp21/params.rs @@ -23,12 +23,14 @@ use crate::{ #[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct PaillierTest; +#[allow(clippy::indexing_slicing)] const fn upcast_uint(value: K256Uint) -> K256Uint { assert!(N2 >= N1, "Upcast target must be bigger than the upcast candidate"); let mut result_words = [0; N2]; let mut i = 0; + let words = value.as_words(); while i < N1 { - result_words[i] = value.as_words()[i]; + result_words[i] = words[i]; i += 1; } K256Uint::from_words(result_words) @@ -136,8 +138,10 @@ pub(crate) fn uint_from_scalar(value: &Scalar) -> = scalar_len); - repr.as_mut()[uint_len - scalar_len..].copy_from_slice(&scalar_bytes); + repr.as_mut() + .get_mut(uint_len - scalar_len..) + .expect("PaillierParams::Uint is expected to be bigger than a Scalar") + .copy_from_slice(&scalar_bytes); ::Uint::from_be_bytes(repr) } @@ -166,8 +170,12 @@ pub(crate) fn scalar_from_uint(value: &(value: &( // Can unwrap here since the value is within the Scalar range Secret::init_with(|| { - Scalar::try_from_be_bytes(&repr.expose_secret().as_ref()[uint_len - scalar_len..]) - .expect("the value was reduced modulo `CURVE_ORDER`, so it's a valid curve scalar") + Scalar::try_from_be_bytes( + repr.expose_secret() + .as_ref() + .get(uint_len - scalar_len..) + .expect("Uint is assumed to be bigger than Scalar"), + ) + .expect("the value was reduced modulo `CURVE_ORDER`, so it's a valid curve scalar") }) } @@ -223,7 +240,11 @@ pub(crate) fn secret_uint_from_scalar( let scalar_len = scalar_bytes.expose_secret().len(); debug_assert!(uint_len >= scalar_len); - repr.expose_secret_mut().as_mut()[uint_len - scalar_len..].copy_from_slice(scalar_bytes.expose_secret()); + repr.expose_secret_mut() + .as_mut() + .get_mut(uint_len - scalar_len..) + .expect("::Uint is assumed to be configured to be bigger than Scalar") + .copy_from_slice(scalar_bytes.expose_secret()); Secret::init_with(|| ::Uint::from_be_bytes(*repr.expose_secret())) } diff --git a/synedrion/src/cggmp21/sigma/mod_.rs b/synedrion/src/cggmp21/sigma/mod_.rs index 051fc55..a378866 100644 --- a/synedrion/src/cggmp21/sigma/mod_.rs +++ b/synedrion/src/cggmp21/sigma/mod_.rs @@ -79,14 +79,15 @@ impl ModProof

{ let (omega_mod_p, omega_mod_q) = sk.rns_split(&commitment.0); - let proof = (0..challenge.0.len()) - .map(|i| { + let proof = challenge + .0 + .iter() + .map(|y| { let mut y_sqrt = None; let mut found_a = false; let mut found_b = false; for (a, b) in [(false, false), (false, true), (true, false), (true, true)].iter() { - let y = challenge.0[i]; - let (mut y_mod_p, mut y_mod_q) = sk.rns_split(&y); + let (mut y_mod_p, mut y_mod_q) = sk.rns_split(y); if *a { y_mod_p = -y_mod_p; y_mod_q = -y_mod_q; @@ -113,7 +114,7 @@ impl ModProof

{ let y_4th = sk.rns_join(&y_4th_parts); - let y = challenge.0[i].to_montgomery(pk.monty_params_mod_n()); + let y = y.to_montgomery(pk.monty_params_mod_n()); let sk_inv_modulus = sk.inv_modulus(); let z = y.pow_bounded(sk_inv_modulus.expose_secret()); diff --git a/synedrion/src/cggmp21/sigma/prm.rs b/synedrion/src/cggmp21/sigma/prm.rs index 2687d67..35846f9 100644 --- a/synedrion/src/cggmp21/sigma/prm.rs +++ b/synedrion/src/cggmp21/sigma/prm.rs @@ -121,12 +121,10 @@ impl PrmProof

{ return false; } - for i in 0..challenge.0.len() { - let z = self.proof[i]; - let e = challenge.0[i]; - let a = self.commitment.0[i].to_montgomery(monty_params); - let pwr = setup.base_randomizer().pow_bounded(&z); - let test = if e { pwr == a * setup.base_value() } else { pwr == a }; + for ((e, z), a) in challenge.0.iter().zip(self.proof.iter()).zip(self.commitment.0.iter()) { + let a = a.to_montgomery(monty_params); + let pwr = setup.base_randomizer().pow_bounded(z); + let test = if *e { pwr == a * setup.base_value() } else { pwr == a }; if !test { return false; } diff --git a/synedrion/src/lib.rs b/synedrion/src/lib.rs index e45c245..827c43b 100644 --- a/synedrion/src/lib.rs +++ b/synedrion/src/lib.rs @@ -11,7 +11,7 @@ trivial_numeric_casts, unused_qualifications )] -#![cfg_attr(not(test), warn(clippy::unwrap_used))] +#![cfg_attr(not(test), warn(clippy::unwrap_used, clippy::indexing_slicing))] extern crate alloc; diff --git a/synedrion/src/paillier/rsa.rs b/synedrion/src/paillier/rsa.rs index c6265e0..89c1183 100644 --- a/synedrion/src/paillier/rsa.rs +++ b/synedrion/src/paillier/rsa.rs @@ -12,7 +12,7 @@ use crate::{ fn random_paillier_blum_prime(rng: &mut impl CryptoRngCore) -> P::HalfUint { loop { let prime = P::HalfUint::generate_prime_with_rng(rng, P::PRIME_BITS); - if prime.as_ref()[0].0 & 3 == 3 { + if prime.as_ref().first().expect("First Limb exists").0 & 3 == 3 { return prime; } } @@ -32,8 +32,14 @@ pub(crate) struct SecretPrimesWire { impl SecretPrimesWire

{ /// A single constructor to check the invariants fn new(p: Secret, q: Secret) -> Self { - debug_assert!(p.expose_secret().as_ref()[0].0 & 3 == 3, "p must be 3 mod 4"); - debug_assert!(q.expose_secret().as_ref()[0].0 & 3 == 3, "q must be 3 mod 4"); + debug_assert!( + p.expose_secret().as_ref().first().expect("First Limb exists").0 & 3 == 3, + "p must be 3 mod 4" + ); + debug_assert!( + q.expose_secret().as_ref().first().expect("First Limb exists").0 & 3 == 3, + "q must be 3 mod 4" + ); Self { p, q } } diff --git a/synedrion/src/tools/bitvec.rs b/synedrion/src/tools/bitvec.rs index cb5b02d..f5717be 100644 --- a/synedrion/src/tools/bitvec.rs +++ b/synedrion/src/tools/bitvec.rs @@ -20,8 +20,8 @@ impl BitVec { impl BitXorAssign<&BitVec> for BitVec { fn bitxor_assign(&mut self, rhs: &BitVec) { assert!(self.0.len() == rhs.0.len()); - for i in 0..self.0.len() { - self.0[i] ^= rhs.0[i]; + for (lhs, rhs) in self.0.iter_mut().zip(rhs.0.iter()) { + *lhs ^= rhs } } } diff --git a/synedrion/src/tools/hashing.rs b/synedrion/src/tools/hashing.rs index 3cc674a..f245280 100644 --- a/synedrion/src/tools/hashing.rs +++ b/synedrion/src/tools/hashing.rs @@ -147,7 +147,8 @@ impl Hashable for T { } } -/// Build a `T` integer from an extendable Reader function +/// Build a `T` integer from an extendable Reader function. The resulting `T` is guaranteed to be +/// smaller than the modulus (uses rejection sampling). pub(crate) fn uint_from_xof(reader: &mut impl XofReader, modulus: &NonZero) -> T where T: Integer + Encoding, @@ -155,11 +156,10 @@ where let backend_modulus = modulus.as_ref(); let n_bits = backend_modulus.bits_vartime(); - let n_bytes = (n_bits + 7) / 8; // ceiling division by 8 + let n_bytes = n_bits.div_ceil(8) as usize; - // If the number of bits is not a multiple of 8, - // use a mask to zeroize the high bits in the gererated random bytestring, - // so that we don't have to reject too much. + // If the number of bits is not a multiple of 8, use a mask to zeroize the high bits in the + // gererated random bytestring, so that we don't have to reject too much. let mask = if n_bits & 7 != 0 { (1 << (n_bits & 7)) - 1 } else { @@ -168,8 +168,15 @@ where let mut bytes = T::zero().to_le_bytes(); loop { - reader.read(&mut (bytes.as_mut()[0..n_bytes as usize])); - bytes.as_mut()[n_bytes as usize - 1] &= mask; + let buf = bytes + .as_mut() + .get_mut(0..n_bytes) + .expect("The modulus is a T and has at least n_bytes that can be read."); + reader.read(buf); + bytes.as_mut().last_mut().map(|byte| { + *byte &= mask; + Some(byte) + }); let n = T::from_le_bytes(bytes); if n.ct_lt(backend_modulus).into() { diff --git a/synedrion/src/tools/sss.rs b/synedrion/src/tools/sss.rs index 9f2bd61..4961839 100644 --- a/synedrion/src/tools/sss.rs +++ b/synedrion/src/tools/sss.rs @@ -3,6 +3,7 @@ use alloc::{ vec::Vec, }; use core::ops::{Add, Mul}; +use manul::session::LocalError; use rand_core::CryptoRngCore; use serde::{Deserialize, Serialize}; @@ -33,23 +34,25 @@ fn evaluate_polynomial(coeffs: &[T], x: &Scalar) -> T where T: Copy + Add + for<'a> Mul<&'a Scalar, Output = T>, { + assert!(coeffs.len() > 1, "Expected coefficients to be non-empty"); // Evaluate in reverse to save on multiplications. // Basically: a0 + a1 x + a2 x^2 + a3 x^3 == (((a3 x) + a2) x + a1) x + a0 - let mut res = coeffs[coeffs.len() - 1]; - for i in (0..(coeffs.len() - 1)).rev() { - res = res * x + coeffs[i]; - } - res + + let (acc, coeffs) = coeffs.split_last().expect("Coefficients is not empty"); + coeffs.iter().rev().fold(*acc, |mut acc, coeff| { + acc = acc * x + *coeff; + acc + }) } fn evaluate_polynomial_secret(coeffs: &[Secret], x: &Scalar) -> Secret { // Evaluate in reverse to save on multiplications. // Basically: a0 + a1 x + a2 x^2 + a3 x^3 == (((a3 x) + a2) x + a1) x + a0 - let mut res = coeffs[coeffs.len() - 1].clone(); - for i in (0..(coeffs.len() - 1)).rev() { - res = res * x + coeffs[i].expose_secret(); - } - res + let (acc, coeffs) = coeffs.split_last().expect("Coefficients is not empty"); + coeffs.iter().rev().fold(acc.clone(), |mut acc, coeff| { + acc = acc * x + coeff.expose_secret(); + acc + }) } #[derive(Debug)] @@ -87,8 +90,10 @@ impl PublicPolynomial { evaluate_polynomial(&self.0, &x.0) } - pub fn coeff0(&self) -> Point { - self.0[0] + pub fn coeff0(&self) -> Result<&Point, LocalError> { + self.0 + .first() + .ok_or_else(|| LocalError::new("Invalid PublicPolynomial")) } } diff --git a/synedrion/src/uint/bounded.rs b/synedrion/src/uint/bounded.rs index ad0632c..cbd0e14 100644 --- a/synedrion/src/uint/bounded.rs +++ b/synedrion/src/uint/bounded.rs @@ -26,7 +26,10 @@ where fn from(val: Bounded) -> Self { let repr = val.as_ref().to_be_bytes(); let bound_bytes = (val.bound() + 7) / 8; - let slice = &repr.as_ref()[(repr.as_ref().len() - bound_bytes as usize)..]; + let slice = repr + .as_ref() + .get((repr.as_ref().len() - bound_bytes as usize)..) + .expect("val has a valid bound that was checked when it was created"); Self { bound: val.bound(), bytes: slice.into(), @@ -51,7 +54,10 @@ where )); } - repr.as_mut()[(repr_len - bytes_len)..].copy_from_slice(&val.bytes); + repr.as_mut() + .get_mut((repr_len - bytes_len)..) + .expect("Just checked that val's data all fit in a T") + .copy_from_slice(&val.bytes); let abs_value = T::from_be_bytes(repr); Self::new(abs_value, val.bound).ok_or_else(|| "Invalid values for the signed integer".into()) diff --git a/synedrion/src/uint/traits.rs b/synedrion/src/uint/traits.rs index f05afca..c2fc62f 100644 --- a/synedrion/src/uint/traits.rs +++ b/synedrion/src/uint/traits.rs @@ -1,8 +1,7 @@ use crypto_bigint::{ modular::MontyForm, - nlimbs, subtle::{ConditionallySelectable, CtOption}, - Encoding, Integer, Invert, PowBoundedExp, RandomMod, Square, Uint, Zero, U1024, U2048, U4096, U512, U8192, + Encoding, Integer, Invert, Limb, PowBoundedExp, RandomMod, Square, Uint, Zero, U1024, U2048, U4096, U512, U8192, }; use crate::uint::{Bounded, Signed}; @@ -239,10 +238,11 @@ impl HasWide for U4096 { } } -pub type U512Mod = MontyForm<{ nlimbs!(512) }>; -pub type U1024Mod = MontyForm<{ nlimbs!(1024) }>; -pub type U2048Mod = MontyForm<{ nlimbs!(2048) }>; -pub type U4096Mod = MontyForm<{ nlimbs!(4096) }>; +// TODO(dp): Suggest crypto-bigint update nlimbs! macro. +pub type U512Mod = MontyForm<{ 512u32.div_ceil(Limb::BITS) as usize }>; +pub type U1024Mod = MontyForm<{ 1024u32.div_ceil(Limb::BITS) as usize }>; +pub type U2048Mod = MontyForm<{ 2048u32.div_ceil(Limb::BITS) as usize }>; +pub type U4096Mod = MontyForm<{ 4096u32.div_ceil(Limb::BITS) as usize }>; impl ToMontgomery for U512 {} impl ToMontgomery for U1024 {} diff --git a/synedrion/src/www02/entities.rs b/synedrion/src/www02/entities.rs index 49d3a80..80e0acd 100644 --- a/synedrion/src/www02/entities.rs +++ b/synedrion/src/www02/entities.rs @@ -1,8 +1,10 @@ use alloc::{ collections::{BTreeMap, BTreeSet}, + format, vec::Vec, }; use core::{fmt::Debug, marker::PhantomData}; +use manul::session::LocalError; use bip32::{DerivationPath, PrivateKey, PrivateKeyBytes, PublicKey}; use k256::ecdsa::{SigningKey, VerifyingKey}; @@ -36,8 +38,11 @@ pub struct ThresholdKeyShare { impl ThresholdKeyShare { /// Threshold share ID. - pub fn share_id(&self) -> ShareId { - self.share_ids[&self.owner] + pub fn share_id(&self) -> Result<&ShareId, LocalError> { + self.share_ids.get(&self.owner).ok_or(LocalError::new(format!( + "owner={:?} is missing in the share_ids", + self.owner + ))) } /// The threshold. @@ -51,8 +56,13 @@ impl ThresholdKeyShare, threshold: usize, signing_key: Option<&SigningKey>, - ) -> BTreeMap { - debug_assert!(threshold <= ids.len()); // TODO (#68): make the method fallible + ) -> Result, LocalError> { + if threshold > ids.len() { + return Err(LocalError::new(format!( + "Invalid threshold ({threshold}). Must be smaller than {}", + ids.len() + ))); + } let secret = Secret::init_with(|| match signing_key { None => Scalar::random(rng), @@ -65,75 +75,110 @@ impl ThresholdKeyShare>(); + .map(|(id, share_id)| { + let secret_share = secret_shares + .get(share_id) + .ok_or_else(|| LocalError::new("share_id={share_id:?} is missing in the secret shares"))?; + Ok((id.clone(), secret_share.mul_by_generator())) + }) + .collect::, LocalError>>()?; ids.iter() .map(|id| { - ( + let share_id = share_ids + .get(id) + .ok_or_else(|| LocalError::new("id={id:?} is missing in the share_ids"))?; + let secret_share = secret_shares + .get(share_id) + .ok_or_else(|| LocalError::new("share_id={share_id:?} is missing in the secret shares"))? + .clone(); + Ok(( id.clone(), Self { owner: id.clone(), threshold: threshold as u32, - secret_share: secret_shares[&share_ids[id]].clone(), + secret_share, share_ids: share_ids.clone(), public_shares: public_shares.clone(), phantom: PhantomData, }, - ) + )) }) .collect() } - pub(crate) fn verifying_key_as_point(&self) -> Point { - shamir_join_points( + pub(crate) fn verifying_key_as_point(&self) -> Result { + Ok(shamir_join_points( &self .share_ids .iter() - .map(|(party_idx, share_id)| (*share_id, self.public_shares[party_idx])) + .map(|(party_idx, share_id)| { + let public_share = self.public_shares.get(party_idx).ok_or(LocalError::new( + "party_idx={party_idx:?} is missing in the public shares", + ))?; + Ok((*share_id, *public_share)) + }) .take(self.threshold as usize) - .collect(), - ) + .collect::>()?, + )) } /// Return the verifying key to which this set of shares corresponds. - pub fn verifying_key(&self) -> VerifyingKey { - self.verifying_key_as_point() + pub fn verifying_key(&self) -> Result { + self.verifying_key_as_point()? .to_verifying_key() - .expect("the combined verrifying key is not an identity") + .ok_or_else(|| LocalError::new("The combined verifying key is an identity")) } /// Converts a t-of-n key share into a t-of-t key share /// (for the `t` share indices supplied as `share_ids`) /// that can be used in the presigning/signing protocols. - pub fn to_key_share(&self, ids: &BTreeSet) -> KeyShare { + pub fn to_key_share(&self, ids: &BTreeSet) -> Result, LocalError> { debug_assert!(ids.len() == self.threshold as usize); debug_assert!(ids.iter().any(|id| id == &self.owner)); - let share_id = self.share_ids[&self.owner]; + let owner_share_id = self + .share_ids + .get(&self.owner) + .ok_or_else(|| LocalError::new("id={id:?} is missing in the share_ids"))?; + let share_ids = ids .iter() - .map(|id| (id.clone(), self.share_ids[id])) - .collect::>(); + .map(|id| { + let share_id = self + .share_ids + .get(id) + .ok_or_else(|| LocalError::new("id={id:?} is missing in the share_ids"))?; + Ok((id.clone(), *share_id)) + }) + .collect::, LocalError>>()?; let share_ids_set = share_ids.values().cloned().collect(); - let secret_share = self.secret_share.clone() * interpolation_coeff(&share_ids_set, &share_id); + let secret_share = self.secret_share.clone() * interpolation_coeff(&share_ids_set, owner_share_id); let public_shares = ids .iter() .map(|id| { - ( + let public_share = self + .public_shares + .get(id) + .ok_or_else(|| LocalError::new("id={id:?} is missing in the public shares"))?; + let this_share_id = self + .share_ids + .get(id) + .ok_or_else(|| LocalError::new("id={id:?} is missing in the share_ids"))?; + Ok(( id.clone(), - self.public_shares[id] * interpolation_coeff(&share_ids_set, &self.share_ids[id]), - ) + public_share * &interpolation_coeff(&share_ids_set, this_share_id), + )) }) - .collect(); + .collect::>()?; - KeyShare { + Ok(KeyShare { owner: self.owner.clone(), secret_share, public_shares, phantom: PhantomData, - } + }) } /// Creates a t-of-t threshold keyshare that can be used in KeyResharing protocol. @@ -147,16 +192,23 @@ impl ThresholdKeyShare>(); let share_ids_set = share_ids.values().cloned().collect(); + let owner_share_id = share_ids + .get(key_share.owner()) + .expect("Just created a ShareId for all parties"); + let secret_share = key_share.secret_share.clone() - * interpolation_coeff(&share_ids_set, &share_ids[key_share.owner()]) + * interpolation_coeff(&share_ids_set, owner_share_id) .invert() .expect("the interpolation coefficient is a non-zero scalar"); let public_shares = ids .iter() .map(|id| { - let share_id = share_ids[id]; - let public_share = key_share.public_shares[id] - * interpolation_coeff(&share_ids_set, &share_id) + let share_id = share_ids.get(id).expect("share_ids and ids have identical lengths"); + let public_share = key_share + .public_shares + .get(id) + .expect("There is one public share (Point) for each party") + * &interpolation_coeff(&share_ids_set, share_id) .invert() .expect("the interpolation coefficient is a non-zero scalar"); (id.clone(), public_share) @@ -175,7 +227,8 @@ impl ThresholdKeyShare Result { - let tweaks = derive_tweaks(self.verifying_key(), derivation_path)?; + let pk = self.verifying_key().map_err(|_| bip32::Error::Crypto)?; + let tweaks = derive_tweaks(pk, derivation_path)?; // Will fail here if secret share is zero let secret_share = self.secret_share.clone().to_signing_key().ok_or(bip32::Error::Crypto)?; @@ -212,7 +265,7 @@ pub trait DeriveChildKey { impl DeriveChildKey for ThresholdKeyShare { fn derive_verifying_key_bip32(&self, derivation_path: &DerivationPath) -> Result { - let public_key = self.verifying_key(); + let public_key = self.verifying_key().map_err(|_| bip32::Error::Crypto)?; let tweaks = derive_tweaks(public_key, derivation_path)?; apply_tweaks_public(public_key, &tweaks) } @@ -286,23 +339,25 @@ mod tests { let ids = signers.iter().map(|signer| signer.verifying_key()).collect::>(); let ids_set = ids.iter().cloned().collect::>(); - let shares = ThresholdKeyShare::::new_centralized(&mut OsRng, &ids_set, 2, Some(&sk)); + let shares = + ThresholdKeyShare::::new_centralized(&mut OsRng, &ids_set, 2, Some(&sk)).unwrap(); - assert_eq!(&shares[&ids[0]].verifying_key(), sk.verifying_key()); - assert_eq!(&shares[&ids[1]].verifying_key(), sk.verifying_key()); - assert_eq!(&shares[&ids[2]].verifying_key(), sk.verifying_key()); + let sk_verifying_key = sk.verifying_key(); + assert_eq!(&shares[&ids[0]].verifying_key().unwrap(), sk_verifying_key); + assert_eq!(&shares[&ids[1]].verifying_key().unwrap(), sk_verifying_key); + assert_eq!(&shares[&ids[2]].verifying_key().unwrap(), sk_verifying_key); - assert_eq!(&shares[&ids[0]].verifying_key(), sk.verifying_key()); + assert_eq!(&shares[&ids[0]].verifying_key().unwrap(), sk_verifying_key); let ids_subset = BTreeSet::from([ids[2], ids[0]]); - let nt_share0 = shares[&ids[0]].to_key_share(&ids_subset); - let nt_share1 = shares[&ids[2]].to_key_share(&ids_subset); + let nt_share0 = shares[&ids[0]].to_key_share(&ids_subset).unwrap(); + let nt_share1 = shares[&ids[2]].to_key_share(&ids_subset).unwrap(); assert_eq!( nt_share0.secret_share.expose_secret() + nt_share1.secret_share.expose_secret(), Scalar::from(sk.as_nonzero_scalar()) ); - assert_eq!(&nt_share0.verifying_key().unwrap(), sk.verifying_key()); - assert_eq!(&nt_share1.verifying_key().unwrap(), sk.verifying_key()); + assert_eq!(&nt_share0.verifying_key().unwrap(), sk_verifying_key); + assert_eq!(&nt_share1.verifying_key().unwrap(), sk_verifying_key); } } diff --git a/synedrion/src/www02/key_resharing.rs b/synedrion/src/www02/key_resharing.rs index 55d3e67..916be77 100644 --- a/synedrion/src/www02/key_resharing.rs +++ b/synedrion/src/www02/key_resharing.rs @@ -7,6 +7,7 @@ use alloc::{ collections::{BTreeMap, BTreeSet}, + format, string::String, vec::Vec, }; @@ -175,16 +176,19 @@ impl EntryPoint for KeyResharing { EchoRoundParticipation::Default }; - let old_holder = self.old_holder.map(|old_holder| { - let polynomial = Polynomial::random(rng, old_holder.key_share.secret_share.clone(), self.new_threshold); - let public_polynomial = polynomial.public(); - - OldHolderData { - polynomial, - share_id: old_holder.key_share.share_id(), - public_polynomial, - } - }); + let old_holder = self + .old_holder + .map(|old_holder| { + let polynomial = Polynomial::random(rng, old_holder.key_share.secret_share.clone(), self.new_threshold); + let public_polynomial = polynomial.public(); + + Ok(OldHolderData { + polynomial, + share_id: *old_holder.key_share.share_id()?, + public_polynomial, + }) + }) + .transpose()?; let new_holder = self.new_holder.map(|new_holder| NewHolderData { inputs: new_holder }); @@ -292,7 +296,12 @@ impl Round for Round1 { destination: &I, ) -> Result<(DirectMessage, Option), LocalError> { if let Some(old_holder) = self.old_holder.as_ref() { - let subshare = old_holder.polynomial.evaluate(&self.new_share_ids[destination]); + let their_share_id = self.new_share_ids.get(destination).ok_or(LocalError::new(format!( + "destination={:?} is missing from the new_share_ids", + destination + )))?; + + let subshare = old_holder.polynomial.evaluate(their_share_id); let dm = DirectMessage::new(serializer, Round1DirectMessage { subshare })?; Ok((dm, None)) } else { @@ -315,9 +324,11 @@ impl Round for Round1 { if let Some(new_holder) = self.new_holder.as_ref() { if new_holder.inputs.old_holders.contains(from) { - let public_subshare_from_poly = echo_broadcast - .public_polynomial - .evaluate(&self.new_share_ids[&self.my_id]); + let my_share_id = self.new_share_ids.get(&self.my_id).ok_or(LocalError::new(format!( + "my_id={:?} is missing from the new_share_ids", + &self.my_id + )))?; + let public_subshare_from_poly = echo_broadcast.public_polynomial.evaluate(my_share_id); let public_subshare_from_private = direct_message.subshare.mul_by_generator(); // Check that the public polynomial sent in the broadcast corresponds to the secret share @@ -350,13 +361,16 @@ impl Round for Round1 { let mut payloads = payloads.downcast_all::()?; - let share_id = self.new_share_ids[&self.my_id]; + let share_id = self + .new_share_ids + .get(&self.my_id) + .ok_or_else(|| LocalError::new(format!("my_id={:?} is missing from new_share_ids", &self.my_id)))?; // If this node is both an old and a new holder, // add a simulated payload to the mapping, as if it sent a message to itself. if let Some(old_holder) = self.old_holder.as_ref() { if self.new_holder.as_ref().is_some() { - let subshare = old_holder.polynomial.evaluate(&share_id); + let subshare = old_holder.polynomial.evaluate(share_id); let my_payload = Round1Payload { subshare, public_polynomial: old_holder.public_polynomial.clone(), @@ -375,9 +389,12 @@ impl Round for Round1 { let vkey = payloads .values() .map(|payload| { - payload.public_polynomial.coeff0() * interpolation_coeff(&old_share_ids, &payload.old_share_id) + payload + .public_polynomial + .coeff0() + .map(|coeff0| coeff0 * &interpolation_coeff(&old_share_ids, &payload.old_share_id)) }) - .sum(); + .sum::>()?; if Point::from_verifying_key(&new_holder.inputs.verifying_key) != vkey { // TODO (#113): this is unattributable. // Should we add an enum variant to `FinalizeError`? @@ -390,19 +407,23 @@ impl Round for Round1 { .inputs .old_holders .iter() - .map(|id| (payloads[id].old_share_id, payloads[id].subshare.clone())) - .collect::>(); + .map(|id| { + let payload = payloads + .get(id) + .ok_or_else(|| LocalError::new("id={id:?} is missing from the payloads"))?; + Ok((payload.old_share_id, payload.subshare.clone())) + }) + .collect::, _>>()?; let secret_share = shamir_join_scalars(subshares); // Generate the public shares of all the new holders. let public_shares = self .new_share_ids - .keys() - .map(|id| { - let share_id = self.new_share_ids[id]; + .iter() + .map(|(id, share_id)| { let public_subshares = payloads .values() - .map(|p| (p.old_share_id, p.public_polynomial.evaluate(&share_id))) + .map(|p| (p.old_share_id, p.public_polynomial.evaluate(share_id))) .collect::>(); let public_share = shamir_join_points(&public_subshares); (id.clone(), public_share) @@ -442,8 +463,8 @@ mod tests { let new_holders = BTreeSet::from([ids[1], ids[2], ids[3]]); let old_key_shares = - ThresholdKeyShare::::new_centralized(&mut OsRng, &old_holders, 2, None); - let old_vkey = old_key_shares[&ids[0]].verifying_key(); + ThresholdKeyShare::::new_centralized(&mut OsRng, &old_holders, 2, None).unwrap(); + let old_vkey = old_key_shares[&ids[0]].verifying_key().unwrap(); let new_threshold = 2; let party0 = KeyResharing::new( diff --git a/synedrion/tests/threshold.rs b/synedrion/tests/threshold.rs index f44ebfc..0ec63c6 100644 --- a/synedrion/tests/threshold.rs +++ b/synedrion/tests/threshold.rs @@ -58,13 +58,13 @@ fn full_sequence() { // The full verifying key can be obtained both from the original key shares and child key shares let child_vkey = t_key_shares[&verifiers[0]].derive_verifying_key_bip32(&path).unwrap(); - assert_eq!(child_vkey, child_key_shares[&verifiers[0]].verifying_key()); + assert_eq!(child_vkey, child_key_shares[&verifiers[0]].verifying_key().unwrap()); // Reshare to `n` nodes // This will need to be published so that new holders can see it and verify the received data let new_holder = NewHolder { - verifying_key: t_key_shares[&verifiers[0]].verifying_key(), + verifying_key: t_key_shares[&verifiers[0]].verifying_key().unwrap(), old_threshold: t_key_shares[&verifiers[0]].threshold(), old_holders, }; @@ -108,8 +108,8 @@ fn full_sequence() { .collect::>(); assert_eq!( - new_t_key_shares[&verifiers[0]].verifying_key(), - t_key_shares[&verifiers[0]].verifying_key() + new_t_key_shares[&verifiers[0]].verifying_key().unwrap(), + t_key_shares[&verifiers[0]].verifying_key().unwrap() ); // Check that resharing did not change the derived child key @@ -143,15 +143,18 @@ fn full_sequence() { new_t_key_shares[&verifiers[0]] .derive_bip32(&path) .unwrap() - .to_key_share(&selected_parties), + .to_key_share(&selected_parties) + .unwrap(), new_t_key_shares[&verifiers[2]] .derive_bip32(&path) .unwrap() - .to_key_share(&selected_parties), + .to_key_share(&selected_parties) + .unwrap(), new_t_key_shares[&verifiers[4]] .derive_bip32(&path) .unwrap() - .to_key_share(&selected_parties), + .to_key_share(&selected_parties) + .unwrap(), ]; let selected_aux_infos = [ aux_infos[&verifiers[0]].clone(),