diff --git a/tpke/benches/benchmarks.rs b/tpke/benches/benchmarks.rs index 01ac8420..45380d2c 100644 --- a/tpke/benches/benchmarks.rs +++ b/tpke/benches/benchmarks.rs @@ -1,6 +1,12 @@ -use criterion::{black_box, criterion_group, criterion_main, Criterion}; +use ark_std::Zero; +use criterion::{ + black_box, criterion_group, criterion_main, BenchmarkId, Criterion, +}; use group_threshold_cryptography::*; +type Fr = ::Fr; +type E = ark_bls12_381::Bls12_381; + pub fn bench_decryption(c: &mut Criterion) { use rand::SeedableRng; use rand_core::RngCore; @@ -143,5 +149,49 @@ pub fn bench_decryption(c: &mut Criterion) { } } -criterion_group!(benches, bench_decryption); +pub fn bench_random_poly(c: &mut Criterion) { + use rand::SeedableRng; + let mut group = c.benchmark_group("RandomPoly"); + group.sample_size(10); + + for threshold in [1, 2, 4, 8, 16, 32, 64] { + let rng = &mut rand::rngs::StdRng::seed_from_u64(0); + let mut ark = { + let mut rng = rng.clone(); + move || { + black_box(make_random_ark_polynomial_at::( + threshold, + &Fr::zero(), + &mut rng, + )) + } + }; + let mut vec = { + let mut rng = rng.clone(); + move || { + black_box(make_random_polynomial_at::( + threshold, + &Fr::zero(), + &mut rng, + )) + } + }; + group.bench_function( + BenchmarkId::new("random_polynomial_ark", threshold), + |b| { + #[allow(clippy::redundant_closure)] + b.iter(|| ark()) + }, + ); + group.bench_function( + BenchmarkId::new("random_polynomial_vec", threshold), + |b| { + #[allow(clippy::redundant_closure)] + b.iter(|| vec()) + }, + ); + } +} + +criterion_group!(benches, bench_decryption, bench_random_poly); criterion_main!(benches); diff --git a/tpke/src/combine.rs b/tpke/src/combine.rs index 4e1f1ed8..2f82df90 100644 --- a/tpke/src/combine.rs +++ b/tpke/src/combine.rs @@ -3,7 +3,6 @@ use crate::*; use ark_ec::ProjectiveCurve; -use itertools::zip_eq; pub fn prepare_combine_fast( public_decryption_contexts: &[PublicDecryptionContextFast], @@ -52,26 +51,28 @@ pub fn prepare_combine_simple( .map(|ctxt| ctxt.domain) .collect::>(); + // Calculate lagrange coefficients using optimized formula, see https://en.wikipedia.org/wiki/Lagrange_polynomial#Optimal_algorithm // In this formula x_i = 0, hence numerator is x_m - lagrange_coeffs_at::(&shares_x, &E::Fr::zero()) + lagrange_basis_at::(&shares_x, &E::Fr::zero()) } -fn lagrange_coeffs_at( - shares_x: &Vec, +/// Calculates Lagrange coefficients for a given x_i +pub fn lagrange_basis_at( + shares_x: &[E::Fr], x_i: &E::Fr, ) -> Vec { - // Calculate lagrange coefficients using optimized formula, see https://en.wikipedia.org/wiki/Lagrange_polynomial#Optimal_algorithm - let mut lagrange_coeffs = vec![]; - for x_j in shares_x { - let mut prod = E::Fr::one(); - for x_m in shares_x { - if x_j != x_m { - prod *= (*x_m - x_i) / (*x_m - *x_j); - } - } - lagrange_coeffs.push(prod); - } - lagrange_coeffs + shares_x + .iter() + .map(|x_j| { + let mut prod = E::Fr::one(); + shares_x.iter().for_each(|x_m| { + if x_j != x_m { + prod *= (*x_m - x_i) / (*x_m - *x_j); + } + }); + prod + }) + .collect() } pub fn share_combine_fast( @@ -101,7 +102,7 @@ pub fn share_combine_simple( let mut product_of_shares = E::Fqk::one(); // Sum of C_i^{L_i}z - for (c_i, alpha_i) in zip_eq(shares.iter(), lagrange_coeffs.iter()) { + for (c_i, alpha_i) in izip!(shares, lagrange_coeffs) { // Exponentiation by alpha_i let ss = c_i.pow(alpha_i.into_repr()); product_of_shares *= ss; diff --git a/tpke/src/context.rs b/tpke/src/context.rs index 0cbd140d..635f19e1 100644 --- a/tpke/src/context.rs +++ b/tpke/src/context.rs @@ -23,6 +23,7 @@ pub struct SetupParams { pub g: E::G1Affine, pub g_inv: E::G1Prepared, pub h_inv: E::G2Prepared, + pub h: E::G2Affine, } #[derive(Clone, Debug)] diff --git a/tpke/src/lib.rs b/tpke/src/lib.rs index c1f86602..baa5d3cc 100644 --- a/tpke/src/lib.rs +++ b/tpke/src/lib.rs @@ -2,12 +2,16 @@ #![allow(dead_code)] use crate::hash_to_curve::htp_bls12381_g2; +use crate::SetupParams; + use ark_ec::{msm::FixedBaseMSM, AffineCurve, PairingEngine}; use ark_ff::{Field, One, PrimeField, ToBytes, UniformRand, Zero}; -use ark_poly::{univariate::DensePolynomial, UVPolynomial}; -use ark_poly::{EvaluationDomain, Polynomial}; +use ark_poly::{ + univariate::DensePolynomial, EvaluationDomain, Polynomial, UVPolynomial, +}; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; use itertools::izip; + use subproductdomain::SubproductDomain; use rand_core::RngCore; @@ -15,26 +19,19 @@ use std::usize; use thiserror::Error; mod ciphertext; +mod combine; +mod context; +mod decryption; mod hash_to_curve; - -pub use ciphertext::*; - mod key_share; +mod refresh; -pub use key_share::*; - -mod decryption; - -pub use decryption::*; - -mod combine; - +pub use ciphertext::*; pub use combine::*; - -mod context; - -use crate::SetupParams; pub use context::*; +pub use decryption::*; +pub use key_share::*; +pub use refresh::*; // TODO: Turn into a crate features pub mod api; @@ -180,6 +177,7 @@ pub fn setup_fast( g, g_inv: E::G1Prepared::from(-g), h_inv: E::G2Prepared::from(-h), + h, }, private_key_share, public_decryption_contexts: vec![], @@ -275,6 +273,7 @@ pub fn setup_simple( g, g_inv: E::G1Prepared::from(-g), h_inv: E::G2Prepared::from(-h), + h, }, private_key_share, public_decryption_contexts: vec![], @@ -291,6 +290,11 @@ pub fn setup_simple( private.public_decryption_contexts = public_contexts.clone(); } + // TODO: Should we also be returning some sort of signed transcript? + // "Post the signed message \(\tau, (F_0, \ldots, F_t), \hat{u}2, (\hat{Y}{i,\omega_j})\) to the blockchain" + // \tau - unique session identifier + // See: https://nikkolasg.github.io/ferveo/pvss.html#dealers-role + (pubkey.into(), privkey.into(), private_contexts) } @@ -301,27 +305,38 @@ pub fn generate_random( (0..n).map(|_| E::Fr::rand(rng)).collect::>() } +fn make_decryption_share( + private_share: &PrivateKeyShare, + ciphertext: &Ciphertext, +) -> E::Fqk { + let z_i = private_share; + let u = ciphertext.commitment; + let z_i = z_i.private_key_shares[0]; + E::pairing(u, z_i) +} + #[cfg(test)] mod tests { use crate::*; + use ark_bls12_381::Fr; + use ark_ec::ProjectiveCurve; use ark_std::test_rng; + use rand::prelude::StdRng; + use std::panic; type E = ark_bls12_381::Bls12_381; #[test] fn ciphertext_serialization() { - let mut rng = test_rng(); - let threshold = 3; - let shares_num = 8; + let rng = &mut test_rng(); + let shares_num = 16; + let threshold = shares_num * 2 / 3; let msg: &[u8] = "abc".as_bytes(); - let aad: &[u8] = "aad".as_bytes(); + let aad: &[u8] = "my-aad".as_bytes(); - let (pubkey, _privkey, _) = - setup_fast::(threshold, shares_num, &mut rng); + let (pubkey, _, _) = setup_fast::(threshold, shares_num, rng); - let ciphertext = encrypt::( - msg, aad, &pubkey, &mut rng, - ); + let ciphertext = encrypt::(msg, aad, &pubkey, rng); let serialized = ciphertext.to_bytes(); let deserialized: Ciphertext = Ciphertext::from_bytes(&serialized); @@ -345,47 +360,29 @@ mod tests { #[test] fn symmetric_encryption() { - let mut rng = test_rng(); - let threshold = 3; - let shares_num = 8; + let rng = &mut test_rng(); + let shares_num = 16; + let threshold = shares_num * 2 / 3; let msg: &[u8] = "abc".as_bytes(); let aad: &[u8] = "my-aad".as_bytes(); - let (pubkey, privkey, _) = - setup_fast::(threshold, shares_num, &mut rng); + let (pubkey, privkey, _) = setup_fast::(threshold, shares_num, rng); - let ciphertext = encrypt::( - msg, aad, &pubkey, &mut rng, - ); + let ciphertext = encrypt::(msg, aad, &pubkey, rng); let plaintext = checked_decrypt(&ciphertext, aad, privkey); assert_eq!(msg, plaintext) } - // Source: https://stackoverflow.com/questions/26469715/how-do-i-write-a-rust-unit-test-that-ensures-that-a-panic-has-occurred - // TODO: Remove after adding proper error handling to the library - use std::panic; - - fn catch_unwind_silent R + panic::UnwindSafe, R>( - f: F, - ) -> std::thread::Result { - let prev_hook = panic::take_hook(); - panic::set_hook(Box::new(|_| {})); - let result = panic::catch_unwind(f); - panic::set_hook(prev_hook); - result - } - #[test] fn threshold_encryption() { - let mut rng = &mut test_rng(); - let threshold = 16 * 2 / 3; + let rng = &mut test_rng(); let shares_num = 16; + let threshold = shares_num * 2 / 3; let msg: &[u8] = "abc".as_bytes(); let aad: &[u8] = "my-aad".as_bytes(); - let (pubkey, _privkey, contexts) = - setup_fast::(threshold, shares_num, &mut rng); + let (pubkey, _, contexts) = setup_fast::(threshold, shares_num, rng); let mut ciphertext = encrypt::<_, E>(msg, aad, &pubkey, rng); let mut shares: Vec> = vec![]; @@ -415,14 +412,14 @@ mod tests { // Malformed the ciphertext ciphertext.ciphertext[0] += 1; - let result = std::panic::catch_unwind(|| { + let result = panic::catch_unwind(|| { checked_decrypt_with_shared_secret(&ciphertext, aad, &shared_secret) }); assert!(result.is_err()); // Malformed the AAD let aad = "bad aad".as_bytes(); - let result = std::panic::catch_unwind(|| { + let result = panic::catch_unwind(|| { checked_decrypt_with_shared_secret(&ciphertext, aad, &shared_secret) }); assert!(result.is_err()); @@ -430,17 +427,14 @@ mod tests { #[test] fn ciphertext_validity_check() { - let mut rng = test_rng(); - let threshold = 3; - let shares_num = 8; + let rng = &mut test_rng(); + let shares_num = 16; + let threshold = shares_num * 2 / 3; let msg: &[u8] = "abc".as_bytes(); let aad: &[u8] = "my-aad".as_bytes(); - let (pubkey, _privkey, _) = - setup_fast::(threshold, shares_num, &mut rng); - let mut ciphertext = encrypt::( - msg, aad, &pubkey, &mut rng, - ); + let (pubkey, _, _) = setup_fast::(threshold, shares_num, rng); + let mut ciphertext = encrypt::(msg, aad, &pubkey, rng); // So far, the ciphertext is valid assert!(check_ciphertext_validity(&ciphertext, aad)); @@ -456,38 +450,26 @@ mod tests { #[test] fn simple_threshold_decryption() { - let mut rng = &mut test_rng(); - let threshold = 16 * 2 / 3; + let rng = &mut test_rng(); let shares_num = 16; + let threshold = shares_num * 2 / 3; let msg: &[u8] = "abc".as_bytes(); let aad: &[u8] = "my-aad".as_bytes(); - // To be updated - let (pubkey, _privkey, private_decryption_contexts) = - setup_simple::(threshold, shares_num, &mut rng); + let (pubkey, _, contexts) = + setup_simple::(threshold, shares_num, rng); - // Stays the same // Ciphertext.commitment is already computed to match U let mut ciphertext = encrypt::<_, E>(msg, aad, &pubkey, rng); - // Creating decryption shares - let decryption_shares = private_decryption_contexts + // Create decryption shares + let decryption_shares: Vec<_> = contexts .iter() - .map(|context| { - let u = ciphertext.commitment; - let z_i = context.private_key_share.clone(); - // Simplifying to just one key share per node - assert_eq!(z_i.private_key_shares.len(), 1); - let z_i = z_i.private_key_shares[0]; - // Really want to call E::pairing here to avoid heavy computations on client side - // C_i = e(U, Z_i) - // TODO: Check whether blinded key share fits here - E::pairing(u, z_i) + .map(|ctxt| { + make_decryption_share(&ctxt.private_key_share, &ciphertext) }) - .collect::>(); - - let pub_contexts = - &private_decryption_contexts[0].public_decryption_contexts; + .collect(); + let pub_contexts = &contexts[0].public_decryption_contexts; let lagrange = prepare_combine_simple::(pub_contexts); let shared_secret = @@ -503,16 +485,179 @@ mod tests { // Malformed the ciphertext ciphertext.ciphertext[0] += 1; - let result = std::panic::catch_unwind(|| { + let result = panic::catch_unwind(|| { checked_decrypt_with_shared_secret(&ciphertext, aad, &shared_secret) }); assert!(result.is_err()); // Malformed the AAD let aad = "bad aad".as_bytes(); - let result = std::panic::catch_unwind(|| { + let result = panic::catch_unwind(|| { checked_decrypt_with_shared_secret(&ciphertext, aad, &shared_secret) }); assert!(result.is_err()); } + + #[test] + /// Ñ 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. + fn simple_threshold_decryption_with_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_y_r = + selected_participant.private_key_share.private_key_shares[0]; + + // Now, we have to remove the participant from the contexts and all nested structures + let mut remaining_participants = contexts; + for p in &mut remaining_participants { + p.public_decryption_contexts.pop(); + } + + // Recover the share + let y_r = recover_share_at_point( + &remaining_participants, + threshold, + &x_r, + rng, + ); + assert_eq!(y_r.into_affine(), original_y_r); + } + + fn make_shared_secret_from_contexts( + contexts: &[PrivateDecryptionContextSimple], + ciphertext: &Ciphertext, + ) -> E::Fqk { + let decryption_shares: Vec<_> = contexts + .iter() + .map(|ctxt| { + make_decryption_share(&ctxt.private_key_share, ciphertext) + }) + .collect(); + make_shared_secret( + &contexts[0].public_decryption_contexts, + &decryption_shares, + ) + } + + fn make_shared_secret( + pub_contexts: &[PublicDecryptionContextSimple], + decryption_shares: &[E::Fqk], + ) -> E::Fqk { + let lagrange = prepare_combine_simple::(pub_contexts); + share_combine_simple::(decryption_shares, &lagrange) + } + + #[test] + /// Ñ 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. + fn simple_threshold_decryption_with_share_recovery_at_random_point() { + let rng = &mut test_rng(); + let shares_num = 16; + let threshold = shares_num * 2 / 3; + let msg: &[u8] = "abc".as_bytes(); + let aad: &[u8] = "my-aad".as_bytes(); + + let (pubkey, _, contexts) = + setup_simple::(threshold, shares_num, rng); + let ciphertext = encrypt::<_, E>(msg, aad, &pubkey, rng); + + // Create an initial shared secret + let old_shared_secret = + make_shared_secret_from_contexts(&contexts, &ciphertext); + + // 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 structures + let mut remaining_participants = contexts; + remaining_participants.pop().unwrap(); + for p in &mut remaining_participants { + p.public_decryption_contexts.pop().unwrap(); + } + + // Recover the share + let x_r = Fr::rand(rng); + let y_r = recover_share_at_point( + &remaining_participants, + threshold, + &x_r, + rng, + ); + let recovered_key_share = PrivateKeyShare { + private_key_shares: vec![y_r.into_affine()], + }; + + // Creating decryption shares + let mut decryption_shares: Vec<_> = remaining_participants + .iter() + .map(|ctxt| { + make_decryption_share(&ctxt.private_key_share, &ciphertext) + }) + .collect(); + decryption_shares + .push(make_decryption_share(&recovered_key_share, &ciphertext)); + + // 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 simple_threshold_decryption_with_share_refreshing() { + let rng = &mut test_rng(); + let shares_num = 16; + let threshold = shares_num * 2 / 3; + let msg: &[u8] = "abc".as_bytes(); + let aad: &[u8] = "my-aad".as_bytes(); + + let (pubkey, _, contexts) = + setup_simple::(threshold, shares_num, rng); + let pub_contexts = contexts[0].public_decryption_contexts.clone(); + let ciphertext = encrypt::<_, E>(msg, aad, &pubkey, rng); + + // Create an initial shared secret + let old_shared_secret = + make_shared_secret_from_contexts(&contexts, &ciphertext); + + // Now, we're going to refresh the shares and check that the shared secret is the same + + // Refresh shares + let new_shares = refresh_shares::(&contexts, threshold, rng); + + // Creating new decryption shares + let new_decryption_shares: Vec<_> = new_shares + .iter() + .map(|private_share| { + let private_share = PrivateKeyShare { + private_key_shares: vec![private_share.into_affine()], + }; + make_decryption_share(&private_share, &ciphertext) + }) + .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/refresh.rs b/tpke/src/refresh.rs new file mode 100644 index 00000000..e330829e --- /dev/null +++ b/tpke/src/refresh.rs @@ -0,0 +1,171 @@ +use crate::{lagrange_basis_at, PrivateDecryptionContextSimple}; +use ark_ec::{PairingEngine, ProjectiveCurve}; +use ark_ff::{PrimeField, Zero}; +use ark_poly::{univariate::DensePolynomial, Polynomial, UVPolynomial}; +use ark_std::UniformRand; +use itertools::zip_eq; +use rand::prelude::StdRng; +use rand_core::RngCore; +use std::collections::HashMap; +use std::usize; + +pub fn recover_share_at_point( + other_participants: &[PrivateDecryptionContextSimple], + threshold: usize, + x_r: &E::Fr, + rng: &mut StdRng, +) -> E::G2Projective { + let share_updates = prepare_share_updates_for_recovery::( + other_participants, + x_r, + threshold, + rng, + ); + + let new_shares_y = + update_shares_for_recovery::(other_participants, &share_updates); + + // From the PSS paper, section 4.2.4, (https://link.springer.com/content/pdf/10.1007/3-540-44750-4_27.pdf) + // Interpolate new shares to recover y_r + let shares_x = &other_participants[0] + .public_decryption_contexts + .iter() + .map(|ctxt| ctxt.domain) + .collect::>(); + + // Recover y_r + let lagrange = lagrange_basis_at::(shares_x, x_r); + let prods = + zip_eq(new_shares_y, lagrange).map(|(y_j, l)| y_j.mul(l.into_repr())); + prods.fold(E::G2Projective::zero(), |acc, y_j| acc + y_j) +} + +/// From PSS paper, section 4.2.1, (https://link.springer.com/content/pdf/10.1007/3-540-44750-4_27.pdf) +fn prepare_share_updates_for_recovery( + participants: &[PrivateDecryptionContextSimple], + x_r: &E::Fr, + threshold: usize, + rng: &mut impl RngCore, +) -> HashMap> { + // TODO: Refactor this function so that each participant performs it individually + // Each participant prepares an update for each other participant + participants + .iter() + .map(|p1| { + let i = p1.index; + // 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 + let deltas_i: HashMap<_, _> = + compute_polynomial_deltas::(participants, &d_i); + (i, deltas_i) + }) + .collect::>() +} + +/// From PSS paper, section 4.2.3, (https://link.springer.com/content/pdf/10.1007/3-540-44750-4_27.pdf) +fn update_shares_for_recovery( + participants: &[PrivateDecryptionContextSimple], + deltas: &HashMap>, +) -> Vec { + // TODO: Refactor this function so that each participant performs it individually + participants + .iter() + .map(|p| { + let i = p.index; + let mut new_y = E::G2Projective::from( + p.private_key_share.private_key_shares[0], // y_i + ); + for j in deltas.keys() { + new_y += deltas[j][&i]; + } + new_y + }) + .collect() +} + +pub fn make_random_polynomial_at( + threshold: usize, + root: &E::Fr, + rng: &mut impl RngCore, +) -> DensePolynomial { + // [][threshold-1] + let mut d_i = (0..threshold - 1) + .map(|_| E::Fr::rand(rng)) + .collect::>(); + // [0..][threshold] + d_i.insert(0, E::Fr::zero()); + let mut d_i = DensePolynomial::from_coefficients_vec(d_i); + + // 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::Fr::zero() - d_i.evaluate(root); + d_i[0] = d_i_0; + + debug_assert!(d_i.evaluate(root) == E::Fr::zero()); + debug_assert!(d_i.len() == threshold); + + d_i +} + +pub fn make_random_ark_polynomial_at( + threshold: usize, + root: &E::Fr, + rng: &mut impl RngCore, +) -> Vec { + let mut threshold_poly = DensePolynomial::::rand(threshold - 1, rng); + threshold_poly[0] = E::Fr::zero(); + let d_i_0 = E::Fr::zero() - threshold_poly.evaluate(root); + threshold_poly[0] = d_i_0; + debug_assert!(threshold_poly.evaluate(root) == E::Fr::zero()); + debug_assert!(threshold_poly.coeffs.len() == threshold); + threshold_poly.coeffs +} + +fn prepare_share_updates_for_refreshing( + participants: &[PrivateDecryptionContextSimple], + threshold: usize, + rng: &mut impl RngCore, +) -> HashMap { + let polynomial = + make_random_polynomial_at::(threshold, &E::Fr::zero(), rng); + compute_polynomial_deltas(participants, &polynomial) +} + +fn compute_polynomial_deltas( + participants: &[PrivateDecryptionContextSimple], + polynomial: &DensePolynomial, +) -> HashMap { + let h_g2 = E::G2Projective::from(participants[0].setup_params.h); + participants + .iter() + .map(|p| { + let i = p.index; + let x_i = p.public_decryption_contexts[i].domain; + let eval = polynomial.evaluate(&x_i); + let eval_g2 = h_g2.mul(eval.into_repr()); + (i, eval_g2) + }) + .collect::>() +} + +pub fn refresh_shares( + participants: &[PrivateDecryptionContextSimple], + threshold: usize, + rng: &mut impl RngCore, +) -> Vec { + let share_updates = + prepare_share_updates_for_refreshing::(participants, threshold, rng); + participants + .iter() + .map(|p| { + let i = p.index; + let mut new_y = E::G2Projective::from( + p.private_key_share.private_key_shares[0], // y_i + ); + new_y += share_updates[&i]; + new_y + }) + .collect() +}