From 59aacbb2a71e9910daed2d8dabfd491f3e786ba0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=BA=C3=B1ez?= Date: Tue, 8 Aug 2023 07:58:51 +0200 Subject: [PATCH 01/17] Remove Pvss type alias --- ferveo/src/dkg.rs | 12 ++++++------ ferveo/src/pvss.rs | 11 ++++++----- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/ferveo/src/dkg.rs b/ferveo/src/dkg.rs index ed183b72..8e7e7449 100644 --- a/ferveo/src/dkg.rs +++ b/ferveo/src/dkg.rs @@ -10,7 +10,7 @@ use serde_with::serde_as; use crate::{ aggregate, utils::is_sorted, AggregatedPvss, Error, EthereumAddress, - PubliclyVerifiableParams, PubliclyVerifiableSS, Pvss, Result, Validator, + PubliclyVerifiableParams, PubliclyVerifiableSS, Result, Validator, }; #[derive(Copy, Clone, Debug, Serialize, Deserialize)] @@ -156,7 +156,7 @@ impl PubliclyVerifiableDkg { rng: &mut R, ) -> Result> { use ark_std::UniformRand; - Pvss::::new(&E::ScalarField::rand(rng), self, rng) + PubliclyVerifiableSS::::new(&E::ScalarField::rand(rng), self, rng) } /// Aggregate all received PVSS messages into a single message, prepared to post on-chain @@ -279,7 +279,7 @@ impl PubliclyVerifiableDkg { pub fn deal( &mut self, sender: &Validator, - pvss: &Pvss, + pvss: &PubliclyVerifiableSS, ) -> Result<()> { // Add the ephemeral public key and pvss transcript let (sender_address, _) = self @@ -306,11 +306,11 @@ pub struct Aggregation { #[derive(Serialize, Deserialize, Clone, Debug)] #[serde(bound( - serialize = "AggregatedPvss: Serialize, Pvss: Serialize", - deserialize = "AggregatedPvss: DeserializeOwned, Pvss: DeserializeOwned" + serialize = "AggregatedPvss: Serialize, PubliclyVerifiableSS: Serialize", + deserialize = "AggregatedPvss: DeserializeOwned, PubliclyVerifiableSS: DeserializeOwned" ))] pub enum Message { - Deal(Pvss), + Deal(PubliclyVerifiableSS), Aggregate(Aggregation), } diff --git a/ferveo/src/pvss.rs b/ferveo/src/pvss.rs index e4bf1c7c..410d6478 100644 --- a/ferveo/src/pvss.rs +++ b/ferveo/src/pvss.rs @@ -41,8 +41,8 @@ pub trait Aggregate {} /// Apply trait gate to Aggregated marker struct impl Aggregate for Aggregated {} -/// Type alias for non aggregated PVSS transcripts -pub type Pvss = PubliclyVerifiableSS; +// /// Type alias for non aggregated PVSS transcripts +// pub type Pvss = PubliclyVerifiableSS; /// Type alias for aggregated PVSS transcripts pub type AggregatedPvss = PubliclyVerifiableSS; @@ -504,8 +504,8 @@ mod test_pvss { let rng = &mut ark_std::test_rng(); let (dkg, _) = setup_dkg(0); let s = ScalarField::rand(rng); - let pvss = - Pvss::::new(&s, &dkg, rng).expect("Test failed"); + let pvss = PubliclyVerifiableSS::::new(&s, &dkg, rng) + .expect("Test failed"); // Check that the chosen secret coefficient is correct assert_eq!(pvss.coeffs[0], G1::generator().mul(s)); // Check that a polynomial of the correct degree was created @@ -548,7 +548,8 @@ mod test_pvss { let rng = &mut ark_std::test_rng(); let (dkg, _) = setup_dkg(0); let s = ScalarField::rand(rng); - let pvss = Pvss::::new(&s, &dkg, rng).unwrap(); + let pvss = + PubliclyVerifiableSS::::new(&s, &dkg, rng).unwrap(); // So far, everything works assert!(pvss.verify_optimistic()); From 372f5d7224e2abccbc2b1bbd344f3a761f987f3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=BA=C3=B1ez?= Date: Thu, 31 Aug 2023 08:37:07 +0200 Subject: [PATCH 02/17] Relocate refresh.rs module from tpke to ferveo crate --- ferveo/src/lib.rs | 17 +++++++++-------- ferveo/src/pvss.rs | 8 ++++---- {tpke => ferveo}/src/refresh.rs | 4 ++-- 3 files changed, 15 insertions(+), 14 deletions(-) rename {tpke => ferveo}/src/refresh.rs (97%) diff --git a/ferveo/src/lib.rs b/ferveo/src/lib.rs index 1ddd54e0..a6b1703d 100644 --- a/ferveo/src/lib.rs +++ b/ferveo/src/lib.rs @@ -17,6 +17,7 @@ pub mod api; pub mod dkg; pub mod primitives; pub mod pvss; +pub mod refresh; pub mod validator; mod utils; @@ -24,6 +25,7 @@ mod utils; pub use dkg::*; pub use primitives::*; pub use pvss::*; +pub use refresh::*; pub use validator::*; #[derive(Debug, thiserror::Error)] @@ -390,7 +392,7 @@ mod test_dkg_full { let share_updates = remaining_validators .keys() .map(|v_addr| { - let deltas_i = tpke::prepare_share_updates_for_recovery::( + let deltas_i = prepare_share_updates_for_recovery::( &domain_points, &dkg.pvss_params.h.into_affine(), &x_r, @@ -428,12 +430,11 @@ mod test_dkg_full { .collect(); // Now, we have to combine new share fragments into a new share - let new_private_key_share = - tpke::recover_share_from_updated_private_shares( - &x_r, - &domain_points, - &updated_shares, - ); + let new_private_key_share = recover_share_from_updated_private_shares( + &x_r, + &domain_points, + &updated_shares, + ); // Get decryption shares from remaining participants let mut decryption_shares: Vec> = @@ -498,7 +499,7 @@ mod test_dkg_full { // Now, we're going to refresh the shares and check that the shared secret is the same // Dealer computes a new random polynomial with constant term x_r = 0 - let polynomial = tpke::make_random_polynomial_at::( + let polynomial = make_random_polynomial_at::( dkg.dkg_params.security_threshold as usize, &Fr::zero(), rng, diff --git a/ferveo/src/pvss.rs b/ferveo/src/pvss.rs index 410d6478..66e8924a 100644 --- a/ferveo/src/pvss.rs +++ b/ferveo/src/pvss.rs @@ -13,15 +13,15 @@ use serde::{Deserialize, Serialize}; use serde_with::serde_as; use subproductdomain::fast_multiexp; use tpke::{ - prepare_combine_simple, refresh_private_key_share, - update_share_for_recovery, CiphertextHeader, DecryptionSharePrecomputed, + prepare_combine_simple, CiphertextHeader, DecryptionSharePrecomputed, DecryptionShareSimple, PrivateKeyShare, }; use zeroize::{self, Zeroize, ZeroizeOnDrop}; use crate::{ - batch_to_projective_g1, batch_to_projective_g2, utils::is_sorted, Error, - PVSSMap, PubliclyVerifiableDkg, Result, Validator, + batch_to_projective_g1, batch_to_projective_g2, refresh_private_key_share, + update_share_for_recovery, utils::is_sorted, Error, PVSSMap, + PubliclyVerifiableDkg, Result, Validator, }; /// These are the blinded evaluations of shares of a single random polynomial diff --git a/tpke/src/refresh.rs b/ferveo/src/refresh.rs similarity index 97% rename from tpke/src/refresh.rs rename to ferveo/src/refresh.rs index d7ebfa87..f17af7a4 100644 --- a/tpke/src/refresh.rs +++ b/ferveo/src/refresh.rs @@ -3,10 +3,10 @@ use std::{ops::Mul, usize}; use ark_ec::{pairing::Pairing, AffineRepr, CurveGroup}; use ark_ff::Zero; use ark_poly::{univariate::DensePolynomial, DenseUVPolynomial, Polynomial}; +use group_threshold_cryptography as tpke; use itertools::zip_eq; use rand_core::RngCore; - -use crate::{lagrange_basis_at, PrivateKeyShare}; +use tpke::{lagrange_basis_at, PrivateKeyShare}; /// From PSS paper, section 4.2.1, (https://link.springer.com/content/pdf/10.1007/3-540-44750-4_27.pdf) pub fn prepare_share_updates_for_recovery( From 5c141e189c5bf0d33190b9f359f1d7ce589703b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=BA=C3=B1ez?= Date: Thu, 31 Aug 2023 10:34:07 +0200 Subject: [PATCH 03/17] Adapting recovery & refresh tests --- ferveo/Cargo.toml | 2 +- ferveo/src/refresh.rs | 296 ++++++++++++++++++++++++++++++++++++++++++ tpke/src/lib.rs | 293 ++--------------------------------------- 3 files changed, 308 insertions(+), 283 deletions(-) diff --git a/ferveo/Cargo.toml b/ferveo/Cargo.toml index c00a423c..bf31f363 100644 --- a/ferveo/Cargo.toml +++ b/ferveo/Cargo.toml @@ -22,7 +22,7 @@ ark-serialize = "0.4" ark-std = "0.4" bincode = "1.3" ferveo-common = { package = "ferveo-common-pre-release", path = "../ferveo-common", version = "^0.1.1" } -group-threshold-cryptography = { package = "group-threshold-cryptography-pre-release", path = "../tpke", features = ["api"], version = "^0.2.0" } +group-threshold-cryptography = { package = "group-threshold-cryptography-pre-release", path = "../tpke", features = ["api", "test-common"], version = "^0.2.0" } hex = "0.4.3" itertools = "0.10.5" measure_time = "0.8" diff --git a/ferveo/src/refresh.rs b/ferveo/src/refresh.rs index f17af7a4..9493f8af 100644 --- a/ferveo/src/refresh.rs +++ b/ferveo/src/refresh.rs @@ -100,3 +100,299 @@ pub fn refresh_private_key_share( private_key_share: updated_share.into_affine(), } } + +#[cfg(test)] +mod tests_refresh { + + use std::{collections::HashMap, ops::Mul}; + + use ark_bls12_381::Fr; + use ark_ec::{pairing::Pairing, AffineRepr, CurveGroup}; + // use ark_ff::Zero; + use ark_std::{test_rng, UniformRand, Zero}; + // use ferveo_common::{FromBytes, ToBytes}; + use rand_core::RngCore; + + // use tpke::test_common::{make_shared_secret}; + + type E = ark_bls12_381::Bls12_381; + // type TargetField = ::TargetField; + type ScalarField = ::ScalarField; + + use crate::{ + make_random_polynomial_at, prepare_share_updates_for_recovery, + recover_share_from_updated_private_shares, refresh_private_key_share, + update_share_for_recovery, + }; + + use group_threshold_cryptography::{ + encrypt, + test_common::{make_shared_secret, setup_simple}, + CiphertextHeader, DecryptionShareSimple, + PrivateDecryptionContextSimple, PrivateKeyShare, SecretBox, + SharedSecret, + }; + + fn make_new_share_fragments( + rng: &mut R, + threshold: usize, + x_r: &Fr, + remaining_participants: &[PrivateDecryptionContextSimple], + ) -> Vec> { + // Each participant prepares an update for each other participant + let domain_points = remaining_participants[0] + .public_decryption_contexts + .iter() + .map(|c| c.domain) + .collect::>(); + let h = remaining_participants[0].public_decryption_contexts[0].h; + let share_updates = remaining_participants + .iter() + .map(|p| { + let deltas_i = prepare_share_updates_for_recovery::( + &domain_points, + &h, + x_r, + threshold, + rng, + ); + (p.index, deltas_i) + }) + .collect::>(); + + // Participants share updates and update their shares + let new_share_fragments: Vec<_> = remaining_participants + .iter() + .map(|p| { + // Current participant receives updates from other participants + let updates_for_participant: Vec<_> = share_updates + .values() + .map(|updates| *updates.get(p.index).unwrap()) + .collect(); + + // And updates their share + update_share_for_recovery::( + &p.private_key_share, + &updates_for_participant, + ) + }) + .collect(); + + new_share_fragments + } + + fn make_shared_secret_from_contexts( + contexts: &[PrivateDecryptionContextSimple], + ciphertext_header: &CiphertextHeader, + aad: &[u8], + ) -> SharedSecret { + let decryption_shares: Vec<_> = contexts + .iter() + .map(|c| c.create_share(ciphertext_header, aad).unwrap()) + .collect(); + make_shared_secret( + &contexts[0].public_decryption_contexts, + &decryption_shares, + ) + } + + /// Ñ parties (where t <= Ñ <= N) jointly execute a "share recovery" algorithm, and the output is 1 new share. + /// The new share is intended to restore a previously existing share, e.g., due to loss or corruption. + #[test] + fn tdec_simple_variant_share_recovery_at_selected_point() { + let rng = &mut test_rng(); + let shares_num = 16; + let threshold = shares_num * 2 / 3; + + let (_, _, mut contexts) = + setup_simple::(threshold, shares_num, rng); + + // Prepare participants + + // First, save the soon-to-be-removed participant + let selected_participant = contexts.pop().unwrap(); + let x_r = selected_participant + .public_decryption_contexts + .last() + .unwrap() + .domain; + let original_private_key_share = selected_participant.private_key_share; + + // Remove one participant from the contexts and all nested structures + let mut remaining_participants = contexts; + for p in &mut remaining_participants { + p.public_decryption_contexts.pop().unwrap(); + } + + // Each participant prepares an update for each other participant, and uses it to create a new share fragment + let new_share_fragments = make_new_share_fragments( + rng, + threshold, + &x_r, + &remaining_participants, + ); + + // Now, we have to combine new share fragments into a new share + let domain_points = &remaining_participants[0] + .public_decryption_contexts + .iter() + .map(|ctxt| ctxt.domain) + .collect::>(); + let new_private_key_share = recover_share_from_updated_private_shares( + &x_r, + domain_points, + &new_share_fragments, + ); + + assert_eq!(new_private_key_share, original_private_key_share); + } + + /// Ñ parties (where t <= Ñ <= N) jointly execute a "share recovery" algorithm, and the output is 1 new share. + /// The new share is independent from the previously existing shares. We can use this to on-board a new participant into an existing cohort. + #[test] + fn tdec_simple_variant_share_recovery_at_random_point() { + let rng = &mut test_rng(); + let shares_num = 16; + let threshold = shares_num * 2 / 3; + let msg = "my-msg".as_bytes().to_vec(); + let aad: &[u8] = "my-aad".as_bytes(); + + let (pubkey, _, contexts) = + setup_simple::(threshold, shares_num, rng); + let g_inv = &contexts[0].setup_params.g_inv; + let ciphertext = + encrypt::(SecretBox::new(msg), aad, &pubkey, rng).unwrap(); + + // Create an initial shared secret + let old_shared_secret = make_shared_secret_from_contexts( + &contexts, + &ciphertext.header().unwrap(), + aad, + ); + + // Now, we're going to recover a new share at a random point and check that the shared secret is still the same + + // Our random point + let x_r = ScalarField::rand(rng); + + // Remove one participant from the contexts and all nested structures + let mut remaining_participants = contexts.clone(); + remaining_participants.pop().unwrap(); + for p in &mut remaining_participants { + p.public_decryption_contexts.pop().unwrap(); + } + + let new_share_fragments = make_new_share_fragments( + rng, + threshold, + &x_r, + &remaining_participants, + ); + + // Now, we have to combine new share fragments into a new share + let domain_points = &remaining_participants[0] + .public_decryption_contexts + .iter() + .map(|ctxt| ctxt.domain) + .collect::>(); + let new_private_key_share = recover_share_from_updated_private_shares( + &x_r, + domain_points, + &new_share_fragments, + ); + + // Get decryption shares from remaining participants + let mut decryption_shares: Vec<_> = remaining_participants + .iter() + .map(|c| { + c.create_share(&ciphertext.header().unwrap(), aad).unwrap() + }) + .collect(); + + // Create a decryption share from a recovered private key share + let new_validator_decryption_key = ScalarField::rand(rng); + decryption_shares.push( + DecryptionShareSimple::create( + &new_validator_decryption_key, + &new_private_key_share, + &ciphertext.header().unwrap(), + aad, + g_inv, + ) + .unwrap(), + ); + + // Creating a shared secret from remaining shares and the recovered one + let new_shared_secret = make_shared_secret( + &remaining_participants[0].public_decryption_contexts, + &decryption_shares, + ); + + assert_eq!(old_shared_secret, new_shared_secret); + } + + /// Ñ parties (where t <= Ñ <= N) jointly execute a "share refresh" algorithm. + /// The output is M new shares (with M <= Ñ), with each of the M new shares substituting the + /// original share (i.e., the original share is deleted). + #[test] + fn tdec_simple_variant_share_refreshing() { + let rng = &mut test_rng(); + let shares_num = 16; + let threshold = shares_num * 2 / 3; + let msg = "my-msg".as_bytes().to_vec(); + let aad: &[u8] = "my-aad".as_bytes(); + + let (pubkey, _, contexts) = + setup_simple::(threshold, shares_num, rng); + let g_inv = &contexts[0].setup_params.g_inv; + let pub_contexts = contexts[0].public_decryption_contexts.clone(); + let ciphertext = + encrypt::(SecretBox::new(msg), aad, &pubkey, rng).unwrap(); + + // Create an initial shared secret + let old_shared_secret = make_shared_secret_from_contexts( + &contexts, + &ciphertext.header().unwrap(), + aad, + ); + + // Now, we're going to refresh the shares and check that the shared secret is the same + + // Dealer computes a new random polynomial with constant term x_r + let polynomial = make_random_polynomial_at::( + threshold, + &ScalarField::zero(), + rng, + ); + + // Dealer shares the polynomial with participants + + // Participants computes new decryption shares + let new_decryption_shares: Vec<_> = contexts + .iter() + .enumerate() + .map(|(i, p)| { + // Participant computes share updates and update their private key shares + let private_key_share = refresh_private_key_share::( + &p.setup_params.h.into_group(), + &p.public_decryption_contexts[i].domain, + &polynomial, + &p.private_key_share, + ); + DecryptionShareSimple::create( + &p.validator_private_key, + &private_key_share, + &ciphertext.header().unwrap(), + aad, + g_inv, + ) + .unwrap() + }) + .collect(); + + let new_shared_secret = + make_shared_secret(&pub_contexts, &new_decryption_shares); + + assert_eq!(old_shared_secret, new_shared_secret); + } +} diff --git a/tpke/src/lib.rs b/tpke/src/lib.rs index 46a78f24..a5c1b302 100644 --- a/tpke/src/lib.rs +++ b/tpke/src/lib.rs @@ -6,7 +6,6 @@ pub mod context; pub mod decryption; pub mod hash_to_curve; pub mod key_share; -pub mod refresh; pub mod secret_box; // TODO: Only show the public API, tpke::api @@ -24,7 +23,6 @@ pub use context::*; pub use decryption::*; pub use hash_to_curve::*; pub use key_share::*; -pub use refresh::*; pub use secret_box::*; #[cfg(feature = "api")] @@ -276,49 +274,31 @@ pub mod test_common { // In precomputed variant, the security threshold is equal to the number of shares setup_simple::(shares_num, shares_num, rng) } + + pub fn make_shared_secret( + pub_contexts: &[PublicDecryptionContextSimple], + decryption_shares: &[DecryptionShareSimple], + ) -> SharedSecret { + let domain = pub_contexts.iter().map(|c| c.domain).collect::>(); + let lagrange_coeffs = prepare_combine_simple::(&domain); + share_combine_simple::(decryption_shares, &lagrange_coeffs) + } } #[cfg(test)] mod tests { - use std::{collections::HashMap, ops::Mul}; + use std::ops::Mul; - use ark_bls12_381::Fr; use ark_ec::{pairing::Pairing, AffineRepr, CurveGroup}; - use ark_ff::Zero; use ark_std::{test_rng, UniformRand}; use ferveo_common::{FromBytes, ToBytes}; - use rand_core::RngCore; - use crate::{ - refresh::{ - make_random_polynomial_at, prepare_share_updates_for_recovery, - recover_share_from_updated_private_shares, - refresh_private_key_share, update_share_for_recovery, - }, - test_common::{setup_simple, *}, - }; + use crate::test_common::{make_shared_secret, setup_simple, *}; type E = ark_bls12_381::Bls12_381; type TargetField = ::TargetField; type ScalarField = ::ScalarField; - fn make_shared_secret_from_contexts( - contexts: &[PrivateDecryptionContextSimple], - ciphertext: &Ciphertext, - aad: &[u8], - ) -> SharedSecret { - let decryption_shares: Vec<_> = contexts - .iter() - .map(|c| { - c.create_share(&ciphertext.header().unwrap(), aad).unwrap() - }) - .collect(); - make_shared_secret( - &contexts[0].public_decryption_contexts, - &decryption_shares, - ) - } - #[test] fn ciphertext_serialization() { let rng = &mut test_rng(); @@ -374,63 +354,6 @@ mod tests { .is_err()); } - fn make_new_share_fragments( - rng: &mut R, - threshold: usize, - x_r: &Fr, - remaining_participants: &[PrivateDecryptionContextSimple], - ) -> Vec> { - // Each participant prepares an update for each other participant - let domain_points = remaining_participants[0] - .public_decryption_contexts - .iter() - .map(|c| c.domain) - .collect::>(); - let h = remaining_participants[0].public_decryption_contexts[0].h; - let share_updates = remaining_participants - .iter() - .map(|p| { - let deltas_i = prepare_share_updates_for_recovery::( - &domain_points, - &h, - x_r, - threshold, - rng, - ); - (p.index, deltas_i) - }) - .collect::>(); - - // Participants share updates and update their shares - let new_share_fragments: Vec<_> = remaining_participants - .iter() - .map(|p| { - // Current participant receives updates from other participants - let updates_for_participant: Vec<_> = share_updates - .values() - .map(|updates| *updates.get(p.index).unwrap()) - .collect(); - - // And updates their share - update_share_for_recovery::( - &p.private_key_share, - &updates_for_participant, - ) - }) - .collect(); - - new_share_fragments - } - - fn make_shared_secret( - pub_contexts: &[PublicDecryptionContextSimple], - decryption_shares: &[DecryptionShareSimple], - ) -> SharedSecret { - let domain = pub_contexts.iter().map(|c| c.domain).collect::>(); - let lagrange_coeffs = prepare_combine_simple::(&domain); - share_combine_simple::(decryption_shares, &lagrange_coeffs) - } - #[test] fn tdec_fast_variant_share_validation() { let rng = &mut test_rng(); @@ -679,198 +602,4 @@ mod tests { &ciphertext, )); } - - /// Ñ parties (where t <= Ñ <= N) jointly execute a "share recovery" algorithm, and the output is 1 new share. - /// The new share is intended to restore a previously existing share, e.g., due to loss or corruption. - #[test] - fn tdec_simple_variant_share_recovery_at_selected_point() { - let rng = &mut test_rng(); - let shares_num = 16; - let threshold = shares_num * 2 / 3; - - let (_, _, mut contexts) = - setup_simple::(threshold, shares_num, rng); - - // Prepare participants - - // First, save the soon-to-be-removed participant - let selected_participant = contexts.pop().unwrap(); - let x_r = selected_participant - .public_decryption_contexts - .last() - .unwrap() - .domain; - let original_private_key_share = selected_participant.private_key_share; - - // Remove one participant from the contexts and all nested structures - let mut remaining_participants = contexts; - for p in &mut remaining_participants { - p.public_decryption_contexts.pop().unwrap(); - } - - // Each participant prepares an update for each other participant, and uses it to create a new share fragment - let new_share_fragments = make_new_share_fragments( - rng, - threshold, - &x_r, - &remaining_participants, - ); - - // Now, we have to combine new share fragments into a new share - let domain_points = &remaining_participants[0] - .public_decryption_contexts - .iter() - .map(|ctxt| ctxt.domain) - .collect::>(); - let new_private_key_share = recover_share_from_updated_private_shares( - &x_r, - domain_points, - &new_share_fragments, - ); - - assert_eq!(new_private_key_share, original_private_key_share); - } - - /// Ñ parties (where t <= Ñ <= N) jointly execute a "share recovery" algorithm, and the output is 1 new share. - /// The new share is independent from the previously existing shares. We can use this to on-board a new participant into an existing cohort. - #[test] - fn tdec_simple_variant_share_recovery_at_random_point() { - let rng = &mut test_rng(); - let shares_num = 16; - let threshold = shares_num * 2 / 3; - let msg = "my-msg".as_bytes().to_vec(); - let aad: &[u8] = "my-aad".as_bytes(); - - let (pubkey, _, contexts) = - setup_simple::(threshold, shares_num, rng); - let g_inv = &contexts[0].setup_params.g_inv; - let ciphertext = - encrypt::(SecretBox::new(msg), aad, &pubkey, rng).unwrap(); - - // Create an initial shared secret - let old_shared_secret = - make_shared_secret_from_contexts(&contexts, &ciphertext, aad); - - // Now, we're going to recover a new share at a random point and check that the shared secret is still the same - - // Our random point - let x_r = ScalarField::rand(rng); - - // Remove one participant from the contexts and all nested structures - let mut remaining_participants = contexts.clone(); - remaining_participants.pop().unwrap(); - for p in &mut remaining_participants { - p.public_decryption_contexts.pop().unwrap(); - } - - let new_share_fragments = make_new_share_fragments( - rng, - threshold, - &x_r, - &remaining_participants, - ); - - // Now, we have to combine new share fragments into a new share - let domain_points = &remaining_participants[0] - .public_decryption_contexts - .iter() - .map(|ctxt| ctxt.domain) - .collect::>(); - let new_private_key_share = recover_share_from_updated_private_shares( - &x_r, - domain_points, - &new_share_fragments, - ); - - // Get decryption shares from remaining participants - let mut decryption_shares: Vec<_> = remaining_participants - .iter() - .map(|c| { - c.create_share(&ciphertext.header().unwrap(), aad).unwrap() - }) - .collect(); - - // Create a decryption share from a recovered private key share - let new_validator_decryption_key = ScalarField::rand(rng); - decryption_shares.push( - DecryptionShareSimple::create( - &new_validator_decryption_key, - &new_private_key_share, - &ciphertext.header().unwrap(), - aad, - g_inv, - ) - .unwrap(), - ); - - // Creating a shared secret from remaining shares and the recovered one - let new_shared_secret = make_shared_secret( - &remaining_participants[0].public_decryption_contexts, - &decryption_shares, - ); - - assert_eq!(old_shared_secret, new_shared_secret); - } - - /// Ñ parties (where t <= Ñ <= N) jointly execute a "share refresh" algorithm. - /// The output is M new shares (with M <= Ñ), with each of the M new shares substituting the - /// original share (i.e., the original share is deleted). - #[test] - fn tdec_simple_variant_share_refreshing() { - let rng = &mut test_rng(); - let shares_num = 16; - let threshold = shares_num * 2 / 3; - let msg = "my-msg".as_bytes().to_vec(); - let aad: &[u8] = "my-aad".as_bytes(); - - let (pubkey, _, contexts) = - setup_simple::(threshold, shares_num, rng); - let g_inv = &contexts[0].setup_params.g_inv; - let pub_contexts = contexts[0].public_decryption_contexts.clone(); - let ciphertext = - encrypt::(SecretBox::new(msg), aad, &pubkey, rng).unwrap(); - - // Create an initial shared secret - let old_shared_secret = - make_shared_secret_from_contexts(&contexts, &ciphertext, aad); - - // Now, we're going to refresh the shares and check that the shared secret is the same - - // Dealer computes a new random polynomial with constant term x_r - let polynomial = make_random_polynomial_at::( - threshold, - &ScalarField::zero(), - rng, - ); - - // Dealer shares the polynomial with participants - - // Participants computes new decryption shares - let new_decryption_shares: Vec<_> = contexts - .iter() - .enumerate() - .map(|(i, p)| { - // Participant computes share updates and update their private key shares - let private_key_share = refresh_private_key_share::( - &p.setup_params.h.into_group(), - &p.public_decryption_contexts[i].domain, - &polynomial, - &p.private_key_share, - ); - DecryptionShareSimple::create( - &p.validator_private_key, - &private_key_share, - &ciphertext.header().unwrap(), - aad, - g_inv, - ) - .unwrap() - }) - .collect(); - - let new_shared_secret = - make_shared_secret(&pub_contexts, &new_decryption_shares); - - assert_eq!(old_shared_secret, new_shared_secret); - } } From b6d1f416fc96fe637765126b7fe8dca253f94c23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=BA=C3=B1ez?= Date: Thu, 31 Aug 2023 19:00:27 +0200 Subject: [PATCH 04/17] Rename random polynomial function for refresh & recovery Technically, this helper function creates a random polynomial where the `root` parameter is a root of the polynomial, so `make_random_polynomial_with_root` is a better name. --- ferveo/src/lib.rs | 2 +- ferveo/src/refresh.rs | 7 ++++--- tpke/benches/arkworks.rs | 8 ++++---- tpke/benches/tpke.rs | 2 +- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/ferveo/src/lib.rs b/ferveo/src/lib.rs index a6b1703d..b5cbc990 100644 --- a/ferveo/src/lib.rs +++ b/ferveo/src/lib.rs @@ -499,7 +499,7 @@ mod test_dkg_full { // Now, we're going to refresh the shares and check that the shared secret is the same // Dealer computes a new random polynomial with constant term x_r = 0 - let polynomial = make_random_polynomial_at::( + let polynomial = make_random_polynomial_with_root::( dkg.dkg_params.security_threshold as usize, &Fr::zero(), rng, diff --git a/ferveo/src/refresh.rs b/ferveo/src/refresh.rs index 9493f8af..dc104e54 100644 --- a/ferveo/src/refresh.rs +++ b/ferveo/src/refresh.rs @@ -61,7 +61,7 @@ pub fn recover_share_from_updated_private_shares( } } -pub fn make_random_polynomial_at( +pub fn make_random_polynomial_with_root( threshold: usize, root: &E::ScalarField, rng: &mut impl RngCore, @@ -78,6 +78,7 @@ pub fn make_random_polynomial_at( let d_i_0 = E::ScalarField::zero() - threshold_poly.evaluate(root); threshold_poly[0] = d_i_0; + // Evaluating the polynomial at the root should result in 0 debug_assert!(threshold_poly.evaluate(root) == E::ScalarField::zero()); debug_assert!(threshold_poly.coeffs.len() == threshold); @@ -120,7 +121,7 @@ mod tests_refresh { type ScalarField = ::ScalarField; use crate::{ - make_random_polynomial_at, prepare_share_updates_for_recovery, + make_random_polynomial_with_root, prepare_share_updates_for_recovery, recover_share_from_updated_private_shares, refresh_private_key_share, update_share_for_recovery, }; @@ -359,7 +360,7 @@ mod tests_refresh { // Now, we're going to refresh the shares and check that the shared secret is the same // Dealer computes a new random polynomial with constant term x_r - let polynomial = make_random_polynomial_at::( + let polynomial = make_random_polynomial_with_root::( threshold, &ScalarField::zero(), rng, diff --git a/tpke/benches/arkworks.rs b/tpke/benches/arkworks.rs index a1f6a50f..957f5fc4 100644 --- a/tpke/benches/arkworks.rs +++ b/tpke/benches/arkworks.rs @@ -14,7 +14,7 @@ use ark_ff::{BigInteger256, Field, One, UniformRand, Zero}; use criterion::{ black_box, criterion_group, criterion_main, BenchmarkId, Criterion, }; -use group_threshold_cryptography_pre_release::make_random_polynomial_at; +use group_threshold_cryptography_pre_release::make_random_polynomial_with_root; use itertools::izip; use rand::prelude::StdRng; use rand_core::{RngCore, SeedableRng}; @@ -219,7 +219,7 @@ pub fn bench_random_poly(c: &mut Criterion) { result } - pub fn naive_make_random_polynomial_at( + pub fn naive_make_random_polynomial_with_root( threshold: usize, root: &Fr, rng: &mut impl RngCore, @@ -248,7 +248,7 @@ pub fn bench_random_poly(c: &mut Criterion) { let mut ark = { let mut rng = rng.clone(); move || { - black_box(make_random_polynomial_at::( + black_box(make_random_polynomial_with_root::( threshold, &Fr::zero(), &mut rng, @@ -258,7 +258,7 @@ pub fn bench_random_poly(c: &mut Criterion) { let mut naive = { let mut rng = rng.clone(); move || { - black_box(naive_make_random_polynomial_at::( + black_box(naive_make_random_polynomial_with_root::( threshold, &Fr::zero(), &mut rng, diff --git a/tpke/benches/tpke.rs b/tpke/benches/tpke.rs index c6ad85ae..69ddb848 100644 --- a/tpke/benches/tpke.rs +++ b/tpke/benches/tpke.rs @@ -552,7 +552,7 @@ pub fn bench_refresh_shares(c: &mut Criterion) { let setup = SetupSimple::new(shares_num, msg_size, rng); let threshold = setup.shared.threshold; let polynomial = - make_random_polynomial_at::(threshold, &Fr::zero(), rng); + make_random_polynomial_with_root::(threshold, &Fr::zero(), rng); let p = setup.contexts[0].clone(); group.bench_function( BenchmarkId::new("refresh_private_key_share", shares_num), From 39ec865c02466e727bca067fd59614d7123c5ae5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=BA=C3=B1ez?= Date: Thu, 31 Aug 2023 19:18:19 +0200 Subject: [PATCH 05/17] Helper functions to prepare share updates for both recovery & refresh There was no function for the refresh case, and since it's very similar to the recovery case, we define here a common method for both (`prepare_share_updates_with_root`) --- ferveo/src/refresh.rs | 56 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 45 insertions(+), 11 deletions(-) diff --git a/ferveo/src/refresh.rs b/ferveo/src/refresh.rs index dc104e54..b43cca16 100644 --- a/ferveo/src/refresh.rs +++ b/ferveo/src/refresh.rs @@ -8,6 +8,8 @@ use itertools::zip_eq; use rand_core::RngCore; use tpke::{lagrange_basis_at, PrivateKeyShare}; +// SHARE UPDATE FUNCTIONS: + /// From PSS paper, section 4.2.1, (https://link.springer.com/content/pdf/10.1007/3-540-44750-4_27.pdf) pub fn prepare_share_updates_for_recovery( domain_points: &[E::ScalarField], @@ -16,17 +18,8 @@ pub fn prepare_share_updates_for_recovery( threshold: usize, rng: &mut impl RngCore, ) -> Vec { - // Generate a new random polynomial with constant term x_r - let d_i = make_random_polynomial_at::(threshold, x_r, rng); - - // Now, we need to evaluate the polynomial at each of participants' indices - domain_points - .iter() - .map(|x_i| { - let eval = d_i.evaluate(x_i); - h.mul(eval) - }) - .collect() + // Update polynomial has root at x_r + prepare_share_updates_with_root::(domain_points, h, x_r, threshold, rng) } /// From PSS paper, section 4.2.3, (https://link.springer.com/content/pdf/10.1007/3-540-44750-4_27.pdf) @@ -61,6 +54,46 @@ pub fn recover_share_from_updated_private_shares( } } +// SHARE REFRESH FUNCTIONS: + +pub fn prepare_share_updates_for_refresh( + domain_points: &[E::ScalarField], + h: &E::G2Affine, + threshold: usize, + rng: &mut impl RngCore, +) -> Vec { + // Update polynomial has root at 0 + prepare_share_updates_with_root::( + domain_points, + h, + &E::ScalarField::zero(), + threshold, + rng, + ) +} + +// UTILS: + +fn prepare_share_updates_with_root( + domain_points: &[E::ScalarField], + h: &E::G2Affine, + root: &E::ScalarField, + threshold: usize, + rng: &mut impl RngCore, +) -> Vec { + // Generate a new random polynomial with defined root + let d_i = make_random_polynomial_with_root::(threshold, root, rng); + + // Now, we need to evaluate the polynomial at each of participants' indices + domain_points + .iter() + .map(|x_i| { + let eval = d_i.evaluate(x_i); + h.mul(eval) + }) + .collect() +} + pub fn make_random_polynomial_with_root( threshold: usize, root: &E::ScalarField, @@ -86,6 +119,7 @@ pub fn make_random_polynomial_with_root( } // TODO: Expose a method to create a proper decryption share after refreshing +// TODO: This is just updating a share locally, but not using contributions from others pub fn refresh_private_key_share( h: &E::G2, domain_point: &E::ScalarField, From 2d30abf5b4265ee7b890f07237d49f9d256daa20 Mon Sep 17 00:00:00 2001 From: Piotr Roslaniec Date: Tue, 5 Sep 2023 14:12:03 +0200 Subject: [PATCH 06/17] Cleaning up the DKG recovery test, in preparation for bug fixing --- ferveo/src/lib.rs | 37 +++++++++++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/ferveo/src/lib.rs b/ferveo/src/lib.rs index b5cbc990..fb113419 100644 --- a/ferveo/src/lib.rs +++ b/ferveo/src/lib.rs @@ -357,7 +357,10 @@ mod test_dkg_full { fn test_dkg_simple_tdec_share_recovery() { let rng = &mut test_rng(); - let (dkg, validator_keypairs) = setup_dealt_dkg_with_n_validators(3, 4); + let security_threshold = 3; + let shares_num = 4; + let (dkg, validator_keypairs) = + setup_dealt_dkg_with_n_validators(security_threshold, shares_num); let msg = "my-msg".as_bytes().to_vec(); let aad: &[u8] = "my-aad".as_bytes(); let public_key = &dkg.public_key(); @@ -375,19 +378,21 @@ mod test_dkg_full { // Now, we're going to recover a new share at a random point and check that the shared secret is still the same - // Our random point - let x_r = Fr::rand(rng); - // Remove one participant from the contexts and all nested structure let removed_validator_addr = dkg.validators.keys().last().unwrap().clone(); let mut remaining_validators = dkg.validators.clone(); remaining_validators.remove(&removed_validator_addr); + // dkg.vss.remove(&removed_validator_addr); // TODO: Test whether it makes any difference // Remember to remove one domain point too let mut domain_points = dkg.domain.elements().collect::>(); domain_points.pop().unwrap(); + // Our random point + let x_r = Fr::rand(rng); + // domain_points.push(x_r); + // Each participant prepares an update for each other participant let share_updates = remaining_validators .keys() @@ -404,6 +409,7 @@ mod test_dkg_full { .collect::>(); // Participants share updates and update their shares + // TODO: Consider moving into the loop let pvss_aggregated = aggregate(&dkg.vss); // Now, every participant separately: @@ -429,6 +435,8 @@ mod test_dkg_full { }) .collect(); + // TODO: Rename updated_private_shares to something that doesn't imply mutation + // Now, we have to combine new share fragments into a new share let new_private_key_share = recover_share_from_updated_private_shares( &x_r, @@ -437,8 +445,12 @@ mod test_dkg_full { ); // Get decryption shares from remaining participants + let mut remaining_validator_keypairs = validator_keypairs; + remaining_validator_keypairs + .pop() + .expect("Should have a keypair"); let mut decryption_shares: Vec> = - validator_keypairs + remaining_validator_keypairs .iter() .enumerate() .map(|(share_index, validator_keypair)| { @@ -467,15 +479,24 @@ mod test_dkg_full { .unwrap(), ); - let lagrange = tpke::prepare_combine_simple::(&domain_points); + domain_points.push(x_r); + assert_eq!(domain_points.len(), shares_num as usize); + assert_eq!(decryption_shares.len(), shares_num as usize); + + let domain_points = &domain_points[1..]; + let decryption_shares = &decryption_shares[1..]; + assert_eq!(domain_points.len(), security_threshold as usize); + assert_eq!(decryption_shares.len(), security_threshold as usize); + + let lagrange = tpke::prepare_combine_simple::(domain_points); let new_shared_secret = - tpke::share_combine_simple::(&decryption_shares, &lagrange); + tpke::share_combine_simple::(decryption_shares, &lagrange); assert_eq!(old_shared_secret, new_shared_secret); } #[test] - fn simple_tdec_share_refreshing() { + fn test_simple_tdec_share_refreshing() { let rng = &mut test_rng(); let (dkg, validator_keypairs) = setup_dealt_dkg_with_n_validators(3, 4); From 6115976bdf4d07a1a4ea28ad05e1669e2028eaac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=BA=C3=B1ez?= Date: Wed, 6 Sep 2023 18:54:17 +0200 Subject: [PATCH 07/17] Clarify how the degree in make_random_polynomial_with_root is defined The concept of "threshold" doesn't belong to the abstraction level of polynomials, which actually deal with degrees. For the record, it's always the case that `threshold == degree + 1`. --- ferveo/src/lib.rs | 6 ++++-- ferveo/src/refresh.rs | 28 +++++++++++++--------------- tpke/benches/arkworks.rs | 4 ++-- tpke/benches/tpke.rs | 7 +++++-- 4 files changed, 24 insertions(+), 21 deletions(-) diff --git a/ferveo/src/lib.rs b/ferveo/src/lib.rs index fb113419..e0499355 100644 --- a/ferveo/src/lib.rs +++ b/ferveo/src/lib.rs @@ -519,9 +519,11 @@ mod test_dkg_full { // Now, we're going to refresh the shares and check that the shared secret is the same - // Dealer computes a new random polynomial with constant term x_r = 0 + // Dealer computes a new random polynomial with constant term 0 + // TODO: Doesn't work if I move this to the loop below + let degree = dkg.dkg_params.security_threshold - 1; let polynomial = make_random_polynomial_with_root::( - dkg.dkg_params.security_threshold as usize, + degree as usize, &Fr::zero(), rng, ); diff --git a/ferveo/src/refresh.rs b/ferveo/src/refresh.rs index b43cca16..20d537d4 100644 --- a/ferveo/src/refresh.rs +++ b/ferveo/src/refresh.rs @@ -82,7 +82,7 @@ fn prepare_share_updates_with_root( rng: &mut impl RngCore, ) -> Vec { // Generate a new random polynomial with defined root - let d_i = make_random_polynomial_with_root::(threshold, root, rng); + let d_i = make_random_polynomial_with_root::(threshold - 1, root, rng); // Now, we need to evaluate the polynomial at each of participants' indices domain_points @@ -95,27 +95,25 @@ fn prepare_share_updates_with_root( } pub fn make_random_polynomial_with_root( - threshold: usize, + degree: usize, root: &E::ScalarField, rng: &mut impl RngCore, ) -> DensePolynomial { - // [][threshold-1] - let mut threshold_poly = - DensePolynomial::::rand(threshold - 1, rng); + // [c_0, c_1, ..., c_{degree}] (Random polynomial) + let mut poly = DensePolynomial::::rand(degree, rng); - // [0..][threshold] - threshold_poly[0] = E::ScalarField::zero(); + // [0, c_1, ... , c_{degree}] (We zeroize the free term) + poly[0] = E::ScalarField::zero(); - // Now, we calculate d_i_0 - // This is the term that will "zero out" the polynomial at x_r, d_i(x_r) = 0 - let d_i_0 = E::ScalarField::zero() - threshold_poly.evaluate(root); - threshold_poly[0] = d_i_0; + // Now, we calculate a new free term so that `poly(root) = 0` + let new_c_0 = E::ScalarField::zero() - poly.evaluate(root); + poly[0] = new_c_0; // Evaluating the polynomial at the root should result in 0 - debug_assert!(threshold_poly.evaluate(root) == E::ScalarField::zero()); - debug_assert!(threshold_poly.coeffs.len() == threshold); + debug_assert!(poly.evaluate(root) == E::ScalarField::zero()); + debug_assert!(poly.coeffs.len() == degree + 1); - threshold_poly + poly } // TODO: Expose a method to create a proper decryption share after refreshing @@ -395,7 +393,7 @@ mod tests_refresh { // Dealer computes a new random polynomial with constant term x_r let polynomial = make_random_polynomial_with_root::( - threshold, + threshold - 1, &ScalarField::zero(), rng, ); diff --git a/tpke/benches/arkworks.rs b/tpke/benches/arkworks.rs index 957f5fc4..dec8ee43 100644 --- a/tpke/benches/arkworks.rs +++ b/tpke/benches/arkworks.rs @@ -249,7 +249,7 @@ pub fn bench_random_poly(c: &mut Criterion) { let mut rng = rng.clone(); move || { black_box(make_random_polynomial_with_root::( - threshold, + threshold - 1, &Fr::zero(), &mut rng, )) @@ -259,7 +259,7 @@ pub fn bench_random_poly(c: &mut Criterion) { let mut rng = rng.clone(); move || { black_box(naive_make_random_polynomial_with_root::( - threshold, + threshold - 1, &Fr::zero(), &mut rng, )) diff --git a/tpke/benches/tpke.rs b/tpke/benches/tpke.rs index 69ddb848..9fd13a3c 100644 --- a/tpke/benches/tpke.rs +++ b/tpke/benches/tpke.rs @@ -551,8 +551,11 @@ pub fn bench_refresh_shares(c: &mut Criterion) { for &shares_num in NUM_SHARES_CASES.iter() { let setup = SetupSimple::new(shares_num, msg_size, rng); let threshold = setup.shared.threshold; - let polynomial = - make_random_polynomial_with_root::(threshold, &Fr::zero(), rng); + let polynomial = make_random_polynomial_with_root::( + threshold - 1, + &Fr::zero(), + rng, + ); let p = setup.contexts[0].clone(); group.bench_function( BenchmarkId::new("refresh_private_key_share", shares_num), From b65798f28cd62323adc886aae5b39f13202e23a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=BA=C3=B1ez?= Date: Thu, 7 Sep 2023 11:02:25 +0200 Subject: [PATCH 08/17] Make sure test for recovery at original point is correct --- ferveo/src/refresh.rs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/ferveo/src/refresh.rs b/ferveo/src/refresh.rs index 20d537d4..10abdbe3 100644 --- a/ferveo/src/refresh.rs +++ b/ferveo/src/refresh.rs @@ -137,10 +137,10 @@ pub fn refresh_private_key_share( #[cfg(test)] mod tests_refresh { - use std::{collections::HashMap, ops::Mul}; + use std::collections::HashMap; use ark_bls12_381::Fr; - use ark_ec::{pairing::Pairing, AffineRepr, CurveGroup}; + use ark_ec::{pairing::Pairing, AffineRepr}; // use ark_ff::Zero; use ark_std::{test_rng, UniformRand, Zero}; // use ferveo_common::{FromBytes, ToBytes}; @@ -273,11 +273,21 @@ mod tests_refresh { .collect::>(); let new_private_key_share = recover_share_from_updated_private_shares( &x_r, - domain_points, - &new_share_fragments, + &domain_points[..threshold], + &new_share_fragments[..threshold], ); assert_eq!(new_private_key_share, original_private_key_share); + + // If we don't have enough private share updates, the resulting private share will be incorrect + let incorrect_private_key_share = + recover_share_from_updated_private_shares( + &x_r, + &domain_points[..(threshold - 1)], + &new_share_fragments[..(threshold - 1)], + ); + + assert_ne!(incorrect_private_key_share, original_private_key_share); } /// Ñ parties (where t <= Ñ <= N) jointly execute a "share recovery" algorithm, and the output is 1 new share. From 7c264874d442d1ef5286499b2d250bb1a63d914e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=BA=C3=B1ez?= Date: Thu, 7 Sep 2023 13:41:25 +0200 Subject: [PATCH 09/17] Simplify simple recovery test Test using secret reconstruction directly, rather than via ciphertext decryption. --- ferveo/src/refresh.rs | 77 +++++++++++++++++-------------------------- 1 file changed, 30 insertions(+), 47 deletions(-) diff --git a/ferveo/src/refresh.rs b/ferveo/src/refresh.rs index 10abdbe3..02473e8a 100644 --- a/ferveo/src/refresh.rs +++ b/ferveo/src/refresh.rs @@ -166,7 +166,7 @@ mod tests_refresh { SharedSecret, }; - fn make_new_share_fragments( + fn make_new_share_fragments_for_recovery( rng: &mut R, threshold: usize, x_r: &Fr, @@ -258,7 +258,7 @@ mod tests_refresh { } // Each participant prepares an update for each other participant, and uses it to create a new share fragment - let new_share_fragments = make_new_share_fragments( + let new_share_fragments = make_new_share_fragments_for_recovery( rng, threshold, &x_r, @@ -297,35 +297,26 @@ mod tests_refresh { let rng = &mut test_rng(); let shares_num = 16; let threshold = shares_num * 2 / 3; - let msg = "my-msg".as_bytes().to_vec(); - let aad: &[u8] = "my-aad".as_bytes(); - let (pubkey, _, contexts) = + let (_, shared_private_key, mut contexts) = setup_simple::(threshold, shares_num, rng); - let g_inv = &contexts[0].setup_params.g_inv; - let ciphertext = - encrypt::(SecretBox::new(msg), aad, &pubkey, rng).unwrap(); - // Create an initial shared secret - let old_shared_secret = make_shared_secret_from_contexts( - &contexts, - &ciphertext.header().unwrap(), - aad, - ); - - // Now, we're going to recover a new share at a random point and check that the shared secret is still the same - - // Our random point - let x_r = ScalarField::rand(rng); + // Prepare participants // Remove one participant from the contexts and all nested structures + contexts.pop().unwrap(); let mut remaining_participants = contexts.clone(); - remaining_participants.pop().unwrap(); for p in &mut remaining_participants { p.public_decryption_contexts.pop().unwrap(); } - let new_share_fragments = make_new_share_fragments( + // Now, we're going to recover a new share at a random point and check that the shared secret is still the same + + // Our random point + let x_r = ScalarField::rand(rng); + + // Each participant prepares an update for each other participant, and uses it to create a new share fragment + let new_share_fragments = make_new_share_fragments_for_recovery( rng, threshold, &x_r, @@ -333,45 +324,37 @@ mod tests_refresh { ); // Now, we have to combine new share fragments into a new share - let domain_points = &remaining_participants[0] + let domain_points = &mut remaining_participants[0] .public_decryption_contexts .iter() .map(|ctxt| ctxt.domain) .collect::>(); let new_private_key_share = recover_share_from_updated_private_shares( &x_r, - domain_points, - &new_share_fragments, + &domain_points[..threshold], + &new_share_fragments[..threshold], ); - // Get decryption shares from remaining participants - let mut decryption_shares: Vec<_> = remaining_participants + let mut private_shares = contexts .iter() - .map(|c| { - c.create_share(&ciphertext.header().unwrap(), aad).unwrap() - }) - .collect(); + .cloned() + .map(|ctxt| ctxt.private_key_share) + .collect::>(); - // Create a decryption share from a recovered private key share - let new_validator_decryption_key = ScalarField::rand(rng); - decryption_shares.push( - DecryptionShareSimple::create( - &new_validator_decryption_key, - &new_private_key_share, - &ciphertext.header().unwrap(), - aad, - g_inv, - ) - .unwrap(), + // Finally, let's recreate the shared private key from some original shares and the recovered one + domain_points.push(x_r); + private_shares.push(new_private_key_share); + let start_from = shares_num - threshold; + let new_shared_private_key = recover_share_from_updated_private_shares( + &ScalarField::zero(), + &domain_points[start_from..], + &private_shares[start_from..], ); - // Creating a shared secret from remaining shares and the recovered one - let new_shared_secret = make_shared_secret( - &remaining_participants[0].public_decryption_contexts, - &decryption_shares, + assert_eq!( + shared_private_key, + new_shared_private_key.private_key_share ); - - assert_eq!(old_shared_secret, new_shared_secret); } /// Ñ parties (where t <= Ñ <= N) jointly execute a "share refresh" algorithm. From d7321966f7bb67fb3b2d1daa9c8545140e8df5f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=BA=C3=B1ez?= Date: Sat, 9 Sep 2023 09:22:21 +0200 Subject: [PATCH 10/17] Simplify share refresh test --- ferveo/src/pvss.rs | 8 +-- ferveo/src/refresh.rs | 111 ++++++++++++++++++------------------------ tpke/benches/tpke.rs | 2 +- 3 files changed, 53 insertions(+), 68 deletions(-) diff --git a/ferveo/src/pvss.rs b/ferveo/src/pvss.rs index 66e8924a..3e0cdca1 100644 --- a/ferveo/src/pvss.rs +++ b/ferveo/src/pvss.rs @@ -19,9 +19,9 @@ use tpke::{ use zeroize::{self, Zeroize, ZeroizeOnDrop}; use crate::{ - batch_to_projective_g1, batch_to_projective_g2, refresh_private_key_share, - update_share_for_recovery, utils::is_sorted, Error, PVSSMap, - PubliclyVerifiableDkg, Result, Validator, + apply_updates_to_private_share, batch_to_projective_g1, + batch_to_projective_g2, refresh_private_key_share, utils::is_sorted, Error, + PVSSMap, PubliclyVerifiableDkg, Result, Validator, }; /// These are the blinded evaluations of shares of a single random polynomial @@ -414,7 +414,7 @@ impl PubliclyVerifiableSS { .decrypt_private_key_share(validator_decryption_key, share_index); // And updates their share - update_share_for_recovery::(&private_key_share, share_updates) + apply_updates_to_private_share::(&private_key_share, share_updates) } } diff --git a/ferveo/src/refresh.rs b/ferveo/src/refresh.rs index 02473e8a..49ee68d6 100644 --- a/ferveo/src/refresh.rs +++ b/ferveo/src/refresh.rs @@ -23,7 +23,7 @@ pub fn prepare_share_updates_for_recovery( } /// From PSS paper, section 4.2.3, (https://link.springer.com/content/pdf/10.1007/3-540-44750-4_27.pdf) -pub fn update_share_for_recovery( +pub fn apply_updates_to_private_share( private_key_share: &PrivateKeyShare, share_updates: &[E::G2], ) -> PrivateKeyShare { @@ -153,9 +153,9 @@ mod tests_refresh { type ScalarField = ::ScalarField; use crate::{ - make_random_polynomial_with_root, prepare_share_updates_for_recovery, - recover_share_from_updated_private_shares, refresh_private_key_share, - update_share_for_recovery, + apply_updates_to_private_share, make_random_polynomial_with_root, + prepare_share_updates_for_recovery, prepare_share_updates_for_refresh, + recover_share_from_updated_private_shares, }; use group_threshold_cryptography::{ @@ -204,7 +204,7 @@ mod tests_refresh { .collect(); // And updates their share - update_share_for_recovery::( + apply_updates_to_private_share::( &p.private_key_share, &updates_for_participant, ) @@ -214,21 +214,6 @@ mod tests_refresh { new_share_fragments } - fn make_shared_secret_from_contexts( - contexts: &[PrivateDecryptionContextSimple], - ciphertext_header: &CiphertextHeader, - aad: &[u8], - ) -> SharedSecret { - let decryption_shares: Vec<_> = contexts - .iter() - .map(|c| c.create_share(ciphertext_header, aad).unwrap()) - .collect(); - make_shared_secret( - &contexts[0].public_decryption_contexts, - &decryption_shares, - ) - } - /// Ñ parties (where t <= Ñ <= N) jointly execute a "share recovery" algorithm, and the output is 1 new share. /// The new share is intended to restore a previously existing share, e.g., due to loss or corruption. #[test] @@ -365,60 +350,60 @@ mod tests_refresh { let rng = &mut test_rng(); let shares_num = 16; let threshold = shares_num * 2 / 3; - let msg = "my-msg".as_bytes().to_vec(); - let aad: &[u8] = "my-aad".as_bytes(); - let (pubkey, _, contexts) = + let (_, shared_private_key, contexts) = setup_simple::(threshold, shares_num, rng); - let g_inv = &contexts[0].setup_params.g_inv; - let pub_contexts = contexts[0].public_decryption_contexts.clone(); - let ciphertext = - encrypt::(SecretBox::new(msg), aad, &pubkey, rng).unwrap(); - - // Create an initial shared secret - let old_shared_secret = make_shared_secret_from_contexts( - &contexts, - &ciphertext.header().unwrap(), - aad, - ); - // Now, we're going to refresh the shares and check that the shared secret is the same - - // Dealer computes a new random polynomial with constant term x_r - let polynomial = make_random_polynomial_with_root::( - threshold - 1, - &ScalarField::zero(), - rng, - ); + let domain_points = &contexts[0] + .public_decryption_contexts + .iter() + .map(|ctxt| ctxt.domain) + .collect::>(); + let h = contexts[0].public_decryption_contexts[0].h; - // Dealer shares the polynomial with participants + // Each participant prepares an update for each other participant: + let share_updates = contexts + .iter() + .map(|p| { + let deltas_i = prepare_share_updates_for_refresh::( + &domain_points, + &h, + threshold, + rng, + ); + (p.index, deltas_i) + }) + .collect::>(); - // Participants computes new decryption shares - let new_decryption_shares: Vec<_> = contexts + // Participants "refresh" their shares with the updates from each other: + let refreshed_shares: Vec<_> = contexts .iter() - .enumerate() - .map(|(i, p)| { - // Participant computes share updates and update their private key shares - let private_key_share = refresh_private_key_share::( - &p.setup_params.h.into_group(), - &p.public_decryption_contexts[i].domain, - &polynomial, + .map(|p| { + // Current participant receives updates from other participants + let updates_for_participant: Vec<_> = share_updates + .values() + .map(|updates| *updates.get(p.index).unwrap()) + .collect(); + + // And updates their share + apply_updates_to_private_share::( &p.private_key_share, - ); - DecryptionShareSimple::create( - &p.validator_private_key, - &private_key_share, - &ciphertext.header().unwrap(), - aad, - g_inv, + &updates_for_participant, ) - .unwrap() }) .collect(); - let new_shared_secret = - make_shared_secret(&pub_contexts, &new_decryption_shares); + // Finally, let's recreate the shared private key from the refreshed shares + let new_shared_private_key = recover_share_from_updated_private_shares( + &ScalarField::zero(), + &domain_points[..threshold], + &refreshed_shares[..threshold], + ); + + assert_eq!( + shared_private_key, + new_shared_private_key.private_key_share + ); - assert_eq!(old_shared_secret, new_shared_secret); } } diff --git a/tpke/benches/tpke.rs b/tpke/benches/tpke.rs index 9fd13a3c..fad7741f 100644 --- a/tpke/benches/tpke.rs +++ b/tpke/benches/tpke.rs @@ -517,7 +517,7 @@ pub fn bench_recover_share_at_point(c: &mut Criterion) { .collect(); // And updates their share - update_share_for_recovery::( + apply_updates_to_private_share::( &p.private_key_share, &updates_for_participant, ) From ce675a61573fa2e43894229d551ea027fccc2e4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=BA=C3=B1ez?= Date: Sat, 9 Sep 2023 09:29:56 +0200 Subject: [PATCH 11/17] Remove incorrect share refresh utility functions These functions assumed access to an update polynomial, but we're actually using several distributed polynomials (one for each participant) and we only have access to their evaluated points. Note that the commits before did the preparation to remove this now. --- ferveo/src/pvss.rs | 33 ++------------------------------- ferveo/src/refresh.rs | 39 ++++++--------------------------------- 2 files changed, 8 insertions(+), 64 deletions(-) diff --git a/ferveo/src/pvss.rs b/ferveo/src/pvss.rs index 3e0cdca1..050e3dfd 100644 --- a/ferveo/src/pvss.rs +++ b/ferveo/src/pvss.rs @@ -20,8 +20,8 @@ use zeroize::{self, Zeroize, ZeroizeOnDrop}; use crate::{ apply_updates_to_private_share, batch_to_projective_g1, - batch_to_projective_g2, refresh_private_key_share, utils::is_sorted, Error, - PVSSMap, PubliclyVerifiableDkg, Result, Validator, + batch_to_projective_g2, utils::is_sorted, Error, PVSSMap, + PubliclyVerifiableDkg, Result, Validator, }; /// These are the blinded evaluations of shares of a single random polynomial @@ -374,35 +374,6 @@ impl PubliclyVerifiableSS { .map_err(|e| e.into()) } - pub fn refresh_decryption_share( - &self, - ciphertext_header: &CiphertextHeader, - aad: &[u8], - validator_decryption_key: &E::ScalarField, - share_index: usize, - polynomial: &DensePolynomial, - dkg: &PubliclyVerifiableDkg, - ) -> Result> { - let validator_private_key_share = self - .decrypt_private_key_share(validator_decryption_key, share_index); - let h = dkg.pvss_params.h; - let domain_point = dkg.domain.element(share_index); - let refreshed_private_key_share = refresh_private_key_share( - &h, - &domain_point, - polynomial, - &validator_private_key_share, - ); - DecryptionShareSimple::create( - validator_decryption_key, - &refreshed_private_key_share, - ciphertext_header, - aad, - &dkg.pvss_params.g_inv(), - ) - .map_err(|e| e.into()) - } - pub fn update_private_key_share_for_recovery( &self, validator_decryption_key: &E::ScalarField, diff --git a/ferveo/src/refresh.rs b/ferveo/src/refresh.rs index 49ee68d6..da6bd8e7 100644 --- a/ferveo/src/refresh.rs +++ b/ferveo/src/refresh.rs @@ -116,54 +116,28 @@ pub fn make_random_polynomial_with_root( poly } -// TODO: Expose a method to create a proper decryption share after refreshing -// TODO: This is just updating a share locally, but not using contributions from others -pub fn refresh_private_key_share( - h: &E::G2, - domain_point: &E::ScalarField, - polynomial: &DensePolynomial, - validator_private_key_share: &PrivateKeyShare, -) -> PrivateKeyShare { - let evaluated_polynomial = polynomial.evaluate(domain_point); - let share_update = h.mul(evaluated_polynomial); - let updated_share = - validator_private_key_share.private_key_share.into_group() - + share_update; - PrivateKeyShare { - private_key_share: updated_share.into_affine(), - } -} - #[cfg(test)] mod tests_refresh { use std::collections::HashMap; use ark_bls12_381::Fr; - use ark_ec::{pairing::Pairing, AffineRepr}; - // use ark_ff::Zero; + use ark_ec::pairing::Pairing; use ark_std::{test_rng, UniformRand, Zero}; - // use ferveo_common::{FromBytes, ToBytes}; use rand_core::RngCore; - // use tpke::test_common::{make_shared_secret}; - type E = ark_bls12_381::Bls12_381; - // type TargetField = ::TargetField; type ScalarField = ::ScalarField; use crate::{ - apply_updates_to_private_share, make_random_polynomial_with_root, - prepare_share_updates_for_recovery, prepare_share_updates_for_refresh, - recover_share_from_updated_private_shares, + apply_updates_to_private_share, prepare_share_updates_for_recovery, + prepare_share_updates_for_refresh, + recover_share_from_updated_private_shares, }; use group_threshold_cryptography::{ - encrypt, - test_common::{make_shared_secret, setup_simple}, - CiphertextHeader, DecryptionShareSimple, - PrivateDecryptionContextSimple, PrivateKeyShare, SecretBox, - SharedSecret, + test_common::setup_simple, PrivateDecryptionContextSimple, + PrivateKeyShare, }; fn make_new_share_fragments_for_recovery( @@ -404,6 +378,5 @@ mod tests_refresh { shared_private_key, new_shared_private_key.private_key_share ); - } } From 8ef9e7ff809af400808dbbc750d2afa25a7cde53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=BA=C3=B1ez?= Date: Mon, 11 Sep 2023 17:50:16 +0200 Subject: [PATCH 12/17] Almost fix test_dkg_simple_tdec_share_recovery Updates for shares were not applied correctly (see diff in L418-423). For the moment, we're recovering the same domain point since it facilitates debugging, but in the next commit I'll restore the test so there's a new validator, with a random domain point. --- ferveo/src/dkg.rs | 2 +- ferveo/src/lib.rs | 53 +++++++++++++++++++++++++++++------------------ 2 files changed, 34 insertions(+), 21 deletions(-) diff --git a/ferveo/src/dkg.rs b/ferveo/src/dkg.rs index 8e7e7449..dc22df78 100644 --- a/ferveo/src/dkg.rs +++ b/ferveo/src/dkg.rs @@ -355,7 +355,7 @@ pub(crate) mod test_common { my_index: usize, ) -> TestSetup { let keypairs = gen_keypairs(shares_num); - let mut validators = gen_validators(&keypairs); + let mut validators = gen_validators(&keypairs.as_slice()); validators.sort(); let me = validators[my_index].clone(); let dkg = PubliclyVerifiableDkg::new( diff --git a/ferveo/src/lib.rs b/ferveo/src/lib.rs index e0499355..6ae418ea 100644 --- a/ferveo/src/lib.rs +++ b/ferveo/src/lib.rs @@ -214,7 +214,7 @@ mod test_dkg_full { &dkg, aad, &ciphertext.header().unwrap(), - &validator_keypairs, + validator_keypairs.as_slice(), ); let plaintext = tpke::decrypt_with_shared_secret( @@ -310,7 +310,7 @@ mod test_dkg_full { &dkg, aad, &ciphertext.header().unwrap(), - &validator_keypairs, + validator_keypairs.as_slice(), ); izip!( @@ -373,25 +373,26 @@ mod test_dkg_full { &dkg, aad, &ciphertext.header().unwrap(), - &validator_keypairs, + validator_keypairs.as_slice(), ); - // Now, we're going to recover a new share at a random point and check that the shared secret is still the same - // Remove one participant from the contexts and all nested structure let removed_validator_addr = dkg.validators.keys().last().unwrap().clone(); let mut remaining_validators = dkg.validators.clone(); - remaining_validators.remove(&removed_validator_addr); + let removed_validator = remaining_validators.remove(&removed_validator_addr).unwrap(); // dkg.vss.remove(&removed_validator_addr); // TODO: Test whether it makes any difference // Remember to remove one domain point too let mut domain_points = dkg.domain.elements().collect::>(); - domain_points.pop().unwrap(); + let old = domain_points.pop().unwrap(); - // Our random point - let x_r = Fr::rand(rng); - // domain_points.push(x_r); + // Now, we're going to recover a new share at a random point, + // and check that the shared secret is still the same. + + // Our random point: + // FIXME: For the moment let's use the old point, but change later to random + let x_r = old; // Fr::rand(rng); // Each participant prepares an update for each other participant let share_updates = remaining_validators @@ -409,16 +410,17 @@ mod test_dkg_full { .collect::>(); // Participants share updates and update their shares - // TODO: Consider moving into the loop - let pvss_aggregated = aggregate(&dkg.vss); // Now, every participant separately: + // TODO: Move this logic outside tests let updated_shares: Vec<_> = remaining_validators .iter() - .map(|(validator_address, validator)| { - // Receives updates from other participants - let updates_for_participant = - share_updates.get(validator_address).unwrap(); + .map(|(_validator_address, validator)| { + // Current participant receives updates from other participants + let updates_for_participant: Vec<_> = share_updates + .values() + .map(|updates| *updates.get(validator.share_index).unwrap()) + .collect(); // Each validator uses their decryption key to update their share let decryption_key = validator_keypairs @@ -427,10 +429,12 @@ mod test_dkg_full { .decryption_key; // Creates updated private key shares + // TODO: Why not using dkg.aggregate()? + let pvss_aggregated = aggregate(&dkg.vss); pvss_aggregated.update_private_key_share_for_recovery( &decryption_key, validator.share_index, - updates_for_participant, + updates_for_participant.as_slice(), ) }) .collect(); @@ -444,6 +448,12 @@ mod test_dkg_full { &updated_shares, ); + // FIXME: Since for the moment we're using the old point, let's check that this recovers the same share + let old_dec_key = validator_keypairs.clone().pop().unwrap().decryption_key; + let pvss_aggregated = aggregate(&dkg.vss); + let original_private_key_share = pvss_aggregated.decrypt_private_key_share(&old_dec_key, removed_validator.share_index); + assert_eq!(new_private_key_share, original_private_key_share, "DIFFERENT SHARE"); + // Get decryption shares from remaining participants let mut remaining_validator_keypairs = validator_keypairs; remaining_validator_keypairs @@ -454,6 +464,8 @@ mod test_dkg_full { .iter() .enumerate() .map(|(share_index, validator_keypair)| { + // TODO: Why not using dkg.aggregate()? + let pvss_aggregated = aggregate(&dkg.vss); pvss_aggregated .make_decryption_share_simple( &ciphertext.header().unwrap(), @@ -483,16 +495,17 @@ mod test_dkg_full { assert_eq!(domain_points.len(), shares_num as usize); assert_eq!(decryption_shares.len(), shares_num as usize); + // Maybe parametrize this test with [1..] and [..threshold] let domain_points = &domain_points[1..]; let decryption_shares = &decryption_shares[1..]; assert_eq!(domain_points.len(), security_threshold as usize); assert_eq!(decryption_shares.len(), security_threshold as usize); - let lagrange = tpke::prepare_combine_simple::(domain_points); + let lagrange = tpke::prepare_combine_simple::(&domain_points); let new_shared_secret = - tpke::share_combine_simple::(decryption_shares, &lagrange); + tpke::share_combine_simple::(&decryption_shares, &lagrange); - assert_eq!(old_shared_secret, new_shared_secret); + assert_eq!(old_shared_secret, new_shared_secret, "Shared secret reconstruction failed"); } #[test] From ab5e58c44707a7b52d93ca911ec0d452e4a7a2d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=BA=C3=B1ez?= Date: Tue, 12 Sep 2023 12:56:20 +0200 Subject: [PATCH 13/17] Definitely fix test_dkg_simple_tdec_share_recovery --- ferveo/src/lib.rs | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/ferveo/src/lib.rs b/ferveo/src/lib.rs index 6ae418ea..3eed5ee1 100644 --- a/ferveo/src/lib.rs +++ b/ferveo/src/lib.rs @@ -380,19 +380,20 @@ mod test_dkg_full { let removed_validator_addr = dkg.validators.keys().last().unwrap().clone(); let mut remaining_validators = dkg.validators.clone(); - let removed_validator = remaining_validators.remove(&removed_validator_addr).unwrap(); + remaining_validators + .remove(&removed_validator_addr) + .unwrap(); // dkg.vss.remove(&removed_validator_addr); // TODO: Test whether it makes any difference // Remember to remove one domain point too let mut domain_points = dkg.domain.elements().collect::>(); - let old = domain_points.pop().unwrap(); + domain_points.pop().unwrap(); // Now, we're going to recover a new share at a random point, // and check that the shared secret is still the same. // Our random point: - // FIXME: For the moment let's use the old point, but change later to random - let x_r = old; // Fr::rand(rng); + let x_r = Fr::rand(rng); // Each participant prepares an update for each other participant let share_updates = remaining_validators @@ -448,12 +449,6 @@ mod test_dkg_full { &updated_shares, ); - // FIXME: Since for the moment we're using the old point, let's check that this recovers the same share - let old_dec_key = validator_keypairs.clone().pop().unwrap().decryption_key; - let pvss_aggregated = aggregate(&dkg.vss); - let original_private_key_share = pvss_aggregated.decrypt_private_key_share(&old_dec_key, removed_validator.share_index); - assert_eq!(new_private_key_share, original_private_key_share, "DIFFERENT SHARE"); - // Get decryption shares from remaining participants let mut remaining_validator_keypairs = validator_keypairs; remaining_validator_keypairs @@ -505,7 +500,10 @@ mod test_dkg_full { let new_shared_secret = tpke::share_combine_simple::(&decryption_shares, &lagrange); - assert_eq!(old_shared_secret, new_shared_secret, "Shared secret reconstruction failed"); + assert_eq!( + old_shared_secret, new_shared_secret, + "Shared secret reconstruction failed" + ); } #[test] From 8407f781e25761a17179002c59dbc00ea0326d8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=BA=C3=B1ez?= Date: Tue, 12 Sep 2023 13:04:42 +0200 Subject: [PATCH 14/17] Fix test_dkg_simple_tdec_share_refreshing() --- ferveo/src/lib.rs | 108 ++++++++++++++++++++++++++++++---------------- 1 file changed, 71 insertions(+), 37 deletions(-) diff --git a/ferveo/src/lib.rs b/ferveo/src/lib.rs index 3eed5ee1..5107a261 100644 --- a/ferveo/src/lib.rs +++ b/ferveo/src/lib.rs @@ -507,67 +507,101 @@ mod test_dkg_full { } #[test] - fn test_simple_tdec_share_refreshing() { + fn test_dkg_simple_tdec_share_refreshing() { let rng = &mut test_rng(); - let (dkg, validator_keypairs) = setup_dealt_dkg_with_n_validators(3, 4); + let security_threshold = 3; + let shares_num = 4; + let (dkg, validator_keypairs) = + setup_dealt_dkg_with_n_validators(security_threshold, shares_num); let msg = "my-msg".as_bytes().to_vec(); let aad: &[u8] = "my-aad".as_bytes(); - let public_key = dkg.public_key(); + let public_key = &dkg.public_key(); let ciphertext = - tpke::encrypt::(SecretBox::new(msg), aad, &public_key, rng) + tpke::encrypt::(SecretBox::new(msg), aad, public_key, rng) .unwrap(); - let pvss_aggregated = aggregate(&dkg.vss); - // Create an initial shared secret let (_, _, old_shared_secret) = make_shared_secret_simple_tdec( &dkg, aad, &ciphertext.header().unwrap(), - &validator_keypairs, + validator_keypairs.as_slice(), ); - // Now, we're going to refresh the shares and check that the shared secret is the same + let domain_points = dkg.domain.elements().collect::>(); - // Dealer computes a new random polynomial with constant term 0 - // TODO: Doesn't work if I move this to the loop below - let degree = dkg.dkg_params.security_threshold - 1; - let polynomial = make_random_polynomial_with_root::( - degree as usize, - &Fr::zero(), - rng, - ); + // Each participant prepares an update for each other participant + let share_updates = dkg + .validators + .keys() + .map(|v_addr| { + let deltas_i = prepare_share_updates_for_refresh::( + &domain_points, + &dkg.pvss_params.h.into_affine(), + dkg.dkg_params.security_threshold as usize, + rng, + ); + (v_addr.clone(), deltas_i) + }) + .collect::>(); + + // Participants share updates and update their shares + + // Now, every participant separately: + // TODO: Move this logic outside tests + let updated_shares: Vec<_> = dkg + .validators + .iter() + .map(|(_validator_address, validator)| { + // Current participant receives updates from other participants + let updates_for_participant: Vec<_> = share_updates + .values() + .map(|updates| *updates.get(validator.share_index).unwrap()) + .collect(); - // Dealer shares the polynomial with participants + // Each validator uses their decryption key to update their share + let decryption_key = validator_keypairs + .get(validator.share_index) + .unwrap() + .decryption_key; - // Participants computes new decryption shares - let new_decryption_shares: Vec> = + // Creates updated private key shares + // TODO: Why not using dkg.aggregate()? + let pvss_aggregated = aggregate(&dkg.vss); + pvss_aggregated.update_private_key_share_for_recovery( + &decryption_key, + validator.share_index, + updates_for_participant.as_slice(), + ) + }) + .collect(); + + // TODO: Rename updated_private_shares to something that doesn't imply mutation + + // Get decryption shares, now with refreshed private shares: + let decryption_shares: Vec> = validator_keypairs .iter() .enumerate() - .map(|(validator_address, validator_keypair)| { - pvss_aggregated - .refresh_decryption_share( - &ciphertext.header().unwrap(), - aad, - &validator_keypair.decryption_key, - validator_address, - &polynomial, - &dkg, - ) - .unwrap() + .map(|(share_index, validator_keypair)| { + DecryptionShareSimple::create( + &validator_keypair.decryption_key, + &updated_shares.get(share_index).unwrap(), + &ciphertext.header().unwrap(), + aad, + &dkg.pvss_params.g_inv(), + ) + .unwrap() }) .collect(); - // Create a new shared secret - let domain = &dkg.domain.elements().collect::>(); - // TODO: Combine `tpke::prepare_combine_simple` and `tpke::share_combine_simple` into - // one function and expose it in the tpke::api? - let lagrange_coeffs = tpke::prepare_combine_simple::(domain); + let lagrange = tpke::prepare_combine_simple::( + &domain_points[..security_threshold as usize], + ); let new_shared_secret = tpke::share_combine_simple::( - &new_decryption_shares, - &lagrange_coeffs, + &decryption_shares[..security_threshold as usize], + &lagrange, ); assert_eq!(old_shared_secret, new_shared_secret); From 3b3ff487555958255cdd078cd553f534f074b108 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=BA=C3=B1ez?= Date: Wed, 13 Sep 2023 11:39:08 +0200 Subject: [PATCH 15/17] TODOs and comments --- ferveo/src/lib.rs | 8 +++----- ferveo/src/pvss.rs | 1 + ferveo/src/refresh.rs | 2 ++ tpke/src/combine.rs | 2 ++ tpke/src/decryption.rs | 2 ++ 5 files changed, 10 insertions(+), 5 deletions(-) diff --git a/ferveo/src/lib.rs b/ferveo/src/lib.rs index 5107a261..fa5340f1 100644 --- a/ferveo/src/lib.rs +++ b/ferveo/src/lib.rs @@ -413,7 +413,7 @@ mod test_dkg_full { // Participants share updates and update their shares // Now, every participant separately: - // TODO: Move this logic outside tests + // TODO: Move this logic outside tests (see #162, #163) let updated_shares: Vec<_> = remaining_validators .iter() .map(|(_validator_address, validator)| { @@ -440,7 +440,7 @@ mod test_dkg_full { }) .collect(); - // TODO: Rename updated_private_shares to something that doesn't imply mutation + // TODO: Rename updated_private_shares to something that doesn't imply mutation (see #162, #163) // Now, we have to combine new share fragments into a new share let new_private_key_share = recover_share_from_updated_private_shares( @@ -549,7 +549,7 @@ mod test_dkg_full { // Participants share updates and update their shares // Now, every participant separately: - // TODO: Move this logic outside tests + // TODO: Move this logic outside tests (see #162, #163) let updated_shares: Vec<_> = dkg .validators .iter() @@ -577,8 +577,6 @@ mod test_dkg_full { }) .collect(); - // TODO: Rename updated_private_shares to something that doesn't imply mutation - // Get decryption shares, now with refreshed private shares: let decryption_shares: Vec> = validator_keypairs diff --git a/ferveo/src/pvss.rs b/ferveo/src/pvss.rs index 050e3dfd..91976ee9 100644 --- a/ferveo/src/pvss.rs +++ b/ferveo/src/pvss.rs @@ -374,6 +374,7 @@ impl PubliclyVerifiableSS { .map_err(|e| e.into()) } + // TODO: Consider relocate to different place, maybe PrivateKeyShare? (see #162, #163) pub fn update_private_key_share_for_recovery( &self, validator_decryption_key: &E::ScalarField, diff --git a/ferveo/src/refresh.rs b/ferveo/src/refresh.rs index da6bd8e7..bb11bb48 100644 --- a/ferveo/src/refresh.rs +++ b/ferveo/src/refresh.rs @@ -22,6 +22,7 @@ pub fn prepare_share_updates_for_recovery( prepare_share_updates_with_root::(domain_points, h, x_r, threshold, rng) } +// TODO: Consider relocating to PrivateKeyShare (see #162, #163) /// From PSS paper, section 4.2.3, (https://link.springer.com/content/pdf/10.1007/3-540-44750-4_27.pdf) pub fn apply_updates_to_private_share( private_key_share: &PrivateKeyShare, @@ -147,6 +148,7 @@ mod tests_refresh { remaining_participants: &[PrivateDecryptionContextSimple], ) -> Vec> { // Each participant prepares an update for each other participant + // TODO: Extract as parameter let domain_points = remaining_participants[0] .public_decryption_contexts .iter() diff --git a/tpke/src/combine.rs b/tpke/src/combine.rs index a46477fb..f9d8ddbb 100644 --- a/tpke/src/combine.rs +++ b/tpke/src/combine.rs @@ -56,6 +56,8 @@ pub fn prepare_combine_fast( .collect::>() } +// TODO: Combine `tpke::prepare_combine_simple` and `tpke::share_combine_simple` into +// one function and expose it in the tpke::api? pub fn prepare_combine_simple( domain: &[E::ScalarField], ) -> Vec { diff --git a/tpke/src/decryption.rs b/tpke/src/decryption.rs index 01ae5df7..0622e6a8 100644 --- a/tpke/src/decryption.rs +++ b/tpke/src/decryption.rs @@ -56,11 +56,13 @@ impl ValidatorShareChecksum { h: &E::G2, ciphertext: &Ciphertext, ) -> bool { + // See https://github.com/nucypher/ferveo/issues/42#issuecomment-1398953777 // D_i == e(C_i, Y_i) if *decryption_share != E::pairing(self.checksum, *share_aggregate).0 { return false; } + // TODO: use multipairing here (h_inv) // e(C_i, ek_i) == e(U, H) if E::pairing(self.checksum, *validator_public_key) != E::pairing(ciphertext.commitment, *h) From 29101b137f702564e7092ddfb98d75d4e26f4b59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=BA=C3=B1ez?= Date: Wed, 13 Sep 2023 12:39:32 +0200 Subject: [PATCH 16/17] Disable affected benchmarks * Removed benchmark for random polynomial with root (not much value as it's essentially benchmarking arkworks) * Commented recovery & refresh benchmarks for when #162 is solved --- tpke/benches/arkworks.rs | 73 -------------- tpke/benches/tpke.rs | 210 ++++++++++++++++++++------------------- 2 files changed, 106 insertions(+), 177 deletions(-) diff --git a/tpke/benches/arkworks.rs b/tpke/benches/arkworks.rs index dec8ee43..60459db0 100644 --- a/tpke/benches/arkworks.rs +++ b/tpke/benches/arkworks.rs @@ -14,7 +14,6 @@ use ark_ff::{BigInteger256, Field, One, UniformRand, Zero}; use criterion::{ black_box, criterion_group, criterion_main, BenchmarkId, Criterion, }; -use group_threshold_cryptography_pre_release::make_random_polynomial_with_root; use itertools::izip; use rand::prelude::StdRng; use rand_core::{RngCore, SeedableRng}; @@ -205,77 +204,6 @@ pub fn bench_product_of_pairings(c: &mut Criterion) { } } -pub fn bench_random_poly(c: &mut Criterion) { - let mut group = c.benchmark_group("random_polynomial_evaluation"); - group.sample_size(10); - - fn evaluate_polynomial(polynomial: &[Fr], x: &Fr) -> Fr { - let mut result = Fr::zero(); - let mut x_power = Fr::one(); - for coeff in polynomial { - result += *coeff * x_power; - x_power *= x; - } - result - } - - pub fn naive_make_random_polynomial_with_root( - threshold: usize, - root: &Fr, - rng: &mut impl RngCore, - ) -> Vec { - // [][threshold-1] - let mut d_i = (0..threshold - 1) - .map(|_| Fr::rand(rng)) - .collect::>(); - // [0..][threshold] - d_i.insert(0, Fr::zero()); - - // Now, we calculate d_i_0 - // This is the term that will "zero out" the polynomial at x_r, d_i(x_r) = 0 - let d_i_0 = Fr::zero() - evaluate_polynomial::(&d_i, root); - d_i[0] = d_i_0; - assert_eq!(evaluate_polynomial::(&d_i, root), Fr::zero()); - - debug_assert!(d_i.len() == threshold); - debug_assert!(evaluate_polynomial::(&d_i, root) == Fr::zero()); - d_i - } - - // Skipping t=1, because it results in a random polynomial with t-1=0 coefficients - for threshold in [2, 4, 8, 16, 32, 64] { - let rng = &mut StdRng::seed_from_u64(0); - let mut ark = { - let mut rng = rng.clone(); - move || { - black_box(make_random_polynomial_with_root::( - threshold - 1, - &Fr::zero(), - &mut rng, - )) - } - }; - let mut naive = { - let mut rng = rng.clone(); - move || { - black_box(naive_make_random_polynomial_with_root::( - threshold - 1, - &Fr::zero(), - &mut rng, - )) - } - }; - group.bench_function( - BenchmarkId::new("random_polynomial_ark", threshold), - |b| b.iter(|| ark()), - ); - group.bench_function( - BenchmarkId::new("random_polynomial_naive", threshold), - |b| b.iter(|| naive()), - ); - } -} - pub fn bench_dummy(_c: &mut Criterion) { // Does nothing on purpose, but is required to make criterion happy. } @@ -294,7 +222,6 @@ criterion_group!( // bench_final_exponentiation, // bench_pairing, // bench_product_of_pairings, - // bench_random_poly, ); criterion_main!(benches); diff --git a/tpke/benches/tpke.rs b/tpke/benches/tpke.rs index fad7741f..c122f7c9 100644 --- a/tpke/benches/tpke.rs +++ b/tpke/benches/tpke.rs @@ -470,108 +470,110 @@ pub fn bench_decryption_share_validity_checks(c: &mut Criterion) { } } -pub fn bench_recover_share_at_point(c: &mut Criterion) { - let mut group = c.benchmark_group("RECOVER SHARE"); - let rng = &mut StdRng::seed_from_u64(0); - let msg_size = MSG_SIZE_CASES[0]; - - for &shares_num in NUM_SHARES_CASES.iter() { - let mut setup = SetupSimple::new(shares_num, msg_size, rng); - let threshold = setup.shared.threshold; - let selected_participant = setup.contexts.pop().unwrap(); - let x_r = selected_participant - .public_decryption_contexts - .last() - .unwrap() - .domain; - let mut remaining_participants = setup.contexts; - for p in &mut remaining_participants { - p.public_decryption_contexts.pop(); - } - let domain_points = &remaining_participants[0] - .public_decryption_contexts - .iter() - .map(|ctxt| ctxt.domain) - .collect::>(); - let h = remaining_participants[0].public_decryption_contexts[0].h; - let share_updates = remaining_participants - .iter() - .map(|p| { - let deltas_i = prepare_share_updates_for_recovery::( - domain_points, - &h, - &x_r, - threshold, - rng, - ); - (p.index, deltas_i) - }) - .collect::>(); - let new_share_fragments: Vec<_> = remaining_participants - .iter() - .map(|p| { - // Current participant receives updates from other participants - let updates_for_participant: Vec<_> = share_updates - .values() - .map(|updates| *updates.get(p.index).unwrap()) - .collect(); - - // And updates their share - apply_updates_to_private_share::( - &p.private_key_share, - &updates_for_participant, - ) - }) - .collect(); - group.bench_function( - BenchmarkId::new( - "recover_share_from_updated_private_shares", - shares_num, - ), - |b| { - b.iter(|| { - let _ = black_box( - recover_share_from_updated_private_shares::( - &x_r, - domain_points, - &new_share_fragments, - ), - ); - }); - }, - ); - } -} - -pub fn bench_refresh_shares(c: &mut Criterion) { - let mut group = c.benchmark_group("REFRESH SHARES"); - let rng = &mut StdRng::seed_from_u64(0); - let msg_size = MSG_SIZE_CASES[0]; - - for &shares_num in NUM_SHARES_CASES.iter() { - let setup = SetupSimple::new(shares_num, msg_size, rng); - let threshold = setup.shared.threshold; - let polynomial = make_random_polynomial_with_root::( - threshold - 1, - &Fr::zero(), - rng, - ); - let p = setup.contexts[0].clone(); - group.bench_function( - BenchmarkId::new("refresh_private_key_share", shares_num), - |b| { - b.iter(|| { - black_box(refresh_private_key_share::( - &p.setup_params.h.into_group(), - &p.public_decryption_contexts[0].domain, - &polynomial, - &p.private_key_share, - )); - }); - }, - ); - } -} +// TODO: Relocate benchmark to ferveo/benches as part of #162, #163 +// pub fn bench_recover_share_at_point(c: &mut Criterion) { +// let mut group = c.benchmark_group("RECOVER SHARE"); +// let rng = &mut StdRng::seed_from_u64(0); +// let msg_size = MSG_SIZE_CASES[0]; + +// for &shares_num in NUM_SHARES_CASES.iter() { +// let mut setup = SetupSimple::new(shares_num, msg_size, rng); +// let threshold = setup.shared.threshold; +// let selected_participant = setup.contexts.pop().unwrap(); +// let x_r = selected_participant +// .public_decryption_contexts +// .last() +// .unwrap() +// .domain; +// let mut remaining_participants = setup.contexts; +// for p in &mut remaining_participants { +// p.public_decryption_contexts.pop(); +// } +// let domain_points = &remaining_participants[0] +// .public_decryption_contexts +// .iter() +// .map(|ctxt| ctxt.domain) +// .collect::>(); +// let h = remaining_participants[0].public_decryption_contexts[0].h; +// let share_updates = remaining_participants +// .iter() +// .map(|p| { +// let deltas_i = prepare_share_updates_for_recovery::( +// domain_points, +// &h, +// &x_r, +// threshold, +// rng, +// ); +// (p.index, deltas_i) +// }) +// .collect::>(); +// let new_share_fragments: Vec<_> = remaining_participants +// .iter() +// .map(|p| { +// // Current participant receives updates from other participants +// let updates_for_participant: Vec<_> = share_updates +// .values() +// .map(|updates| *updates.get(p.index).unwrap()) +// .collect(); + +// // And updates their share +// apply_updates_to_private_share::( +// &p.private_key_share, +// &updates_for_participant, +// ) +// }) +// .collect(); +// group.bench_function( +// BenchmarkId::new( +// "recover_share_from_updated_private_shares", +// shares_num, +// ), +// |b| { +// b.iter(|| { +// let _ = black_box( +// recover_share_from_updated_private_shares::( +// &x_r, +// domain_points, +// &new_share_fragments, +// ), +// ); +// }); +// }, +// ); +// } +// } + +// TODO: Relocate benchmark to ferveo/benches as part of #162, #163 +// pub fn bench_refresh_shares(c: &mut Criterion) { +// let mut group = c.benchmark_group("REFRESH SHARES"); +// let rng = &mut StdRng::seed_from_u64(0); +// let msg_size = MSG_SIZE_CASES[0]; + +// for &shares_num in NUM_SHARES_CASES.iter() { +// let setup = SetupSimple::new(shares_num, msg_size, rng); +// let threshold = setup.shared.threshold; +// let polynomial = make_random_polynomial_with_root::( +// threshold - 1, +// &Fr::zero(), +// rng, +// ); +// let p = setup.contexts[0].clone(); +// group.bench_function( +// BenchmarkId::new("refresh_private_key_share", shares_num), +// |b| { +// b.iter(|| { +// black_box(refresh_private_key_share::( +// &p.setup_params.h.into_group(), +// &p.public_decryption_contexts[0].domain, +// &polynomial, +// &p.private_key_share, +// )); +// }); +// }, +// ); +// } +// } criterion_group!( benches, @@ -581,8 +583,8 @@ criterion_group!( bench_share_encrypt_decrypt, bench_ciphertext_validity_checks, bench_decryption_share_validity_checks, - bench_recover_share_at_point, - bench_refresh_shares, + // bench_recover_share_at_point, + // bench_refresh_shares, ); criterion_main!(benches); From 1704f86c40de0a074878f358fe075a8086bf1a55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=BA=C3=B1ez?= Date: Thu, 14 Sep 2023 12:15:06 +0200 Subject: [PATCH 17/17] Linting and stuff --- ferveo/src/dkg.rs | 2 +- ferveo/src/lib.rs | 14 +++++++------- ferveo/src/refresh.rs | 13 +++++++------ tpke/benches/arkworks.rs | 4 ++-- tpke/benches/tpke.rs | 5 +---- 5 files changed, 18 insertions(+), 20 deletions(-) diff --git a/ferveo/src/dkg.rs b/ferveo/src/dkg.rs index dc22df78..9c95d96a 100644 --- a/ferveo/src/dkg.rs +++ b/ferveo/src/dkg.rs @@ -355,7 +355,7 @@ pub(crate) mod test_common { my_index: usize, ) -> TestSetup { let keypairs = gen_keypairs(shares_num); - let mut validators = gen_validators(&keypairs.as_slice()); + let mut validators = gen_validators(keypairs.as_slice()); validators.sort(); let me = validators[my_index].clone(); let dkg = PubliclyVerifiableDkg::new( diff --git a/ferveo/src/lib.rs b/ferveo/src/lib.rs index fa5340f1..41da3ba7 100644 --- a/ferveo/src/lib.rs +++ b/ferveo/src/lib.rs @@ -415,8 +415,8 @@ mod test_dkg_full { // Now, every participant separately: // TODO: Move this logic outside tests (see #162, #163) let updated_shares: Vec<_> = remaining_validators - .iter() - .map(|(_validator_address, validator)| { + .values() + .map(|validator| { // Current participant receives updates from other participants let updates_for_participant: Vec<_> = share_updates .values() @@ -496,9 +496,9 @@ mod test_dkg_full { assert_eq!(domain_points.len(), security_threshold as usize); assert_eq!(decryption_shares.len(), security_threshold as usize); - let lagrange = tpke::prepare_combine_simple::(&domain_points); + let lagrange = tpke::prepare_combine_simple::(domain_points); let new_shared_secret = - tpke::share_combine_simple::(&decryption_shares, &lagrange); + tpke::share_combine_simple::(decryption_shares, &lagrange); assert_eq!( old_shared_secret, new_shared_secret, @@ -552,8 +552,8 @@ mod test_dkg_full { // TODO: Move this logic outside tests (see #162, #163) let updated_shares: Vec<_> = dkg .validators - .iter() - .map(|(_validator_address, validator)| { + .values() + .map(|validator| { // Current participant receives updates from other participants let updates_for_participant: Vec<_> = share_updates .values() @@ -585,7 +585,7 @@ mod test_dkg_full { .map(|(share_index, validator_keypair)| { DecryptionShareSimple::create( &validator_keypair.decryption_key, - &updated_shares.get(share_index).unwrap(), + updated_shares.get(share_index).unwrap(), &ciphertext.header().unwrap(), aad, &dkg.pvss_params.g_inv(), diff --git a/ferveo/src/refresh.rs b/ferveo/src/refresh.rs index bb11bb48..ce87c81c 100644 --- a/ferveo/src/refresh.rs +++ b/ferveo/src/refresh.rs @@ -130,17 +130,17 @@ mod tests_refresh { type E = ark_bls12_381::Bls12_381; type ScalarField = ::ScalarField; + use group_threshold_cryptography::{ + test_common::setup_simple, PrivateDecryptionContextSimple, + PrivateKeyShare, + }; + use crate::{ apply_updates_to_private_share, prepare_share_updates_for_recovery, prepare_share_updates_for_refresh, recover_share_from_updated_private_shares, }; - use group_threshold_cryptography::{ - test_common::setup_simple, PrivateDecryptionContextSimple, - PrivateKeyShare, - }; - fn make_new_share_fragments_for_recovery( rng: &mut R, threshold: usize, @@ -241,6 +241,7 @@ mod tests_refresh { assert_eq!(new_private_key_share, original_private_key_share); // If we don't have enough private share updates, the resulting private share will be incorrect + assert_eq!(domain_points.len(), new_share_fragments.len()); let incorrect_private_key_share = recover_share_from_updated_private_shares( &x_r, @@ -342,7 +343,7 @@ mod tests_refresh { .iter() .map(|p| { let deltas_i = prepare_share_updates_for_refresh::( - &domain_points, + domain_points, &h, threshold, rng, diff --git a/tpke/benches/arkworks.rs b/tpke/benches/arkworks.rs index 60459db0..953f067c 100644 --- a/tpke/benches/arkworks.rs +++ b/tpke/benches/arkworks.rs @@ -10,13 +10,13 @@ use ark_ec::{ pairing::{prepare_g1, prepare_g2, Pairing}, AffineRepr, CurveGroup, }; -use ark_ff::{BigInteger256, Field, One, UniformRand, Zero}; +use ark_ff::{BigInteger256, Field, UniformRand}; use criterion::{ black_box, criterion_group, criterion_main, BenchmarkId, Criterion, }; use itertools::izip; use rand::prelude::StdRng; -use rand_core::{RngCore, SeedableRng}; +use rand_core::SeedableRng; type E = Bls12_381; type G1Prepared = ::G1Prepared; diff --git a/tpke/benches/tpke.rs b/tpke/benches/tpke.rs index c122f7c9..cb553c27 100644 --- a/tpke/benches/tpke.rs +++ b/tpke/benches/tpke.rs @@ -1,10 +1,7 @@ #![allow(clippy::redundant_closure)] -use std::collections::HashMap; - use ark_bls12_381::{Bls12_381, Fr, G1Affine as G1, G2Affine as G2}; -use ark_ec::{pairing::Pairing, AffineRepr}; -use ark_ff::Zero; +use ark_ec::pairing::Pairing; use criterion::{ black_box, criterion_group, criterion_main, BenchmarkId, Criterion, };