From 3d0c13b78fa89e3cf221e48c68f9ce7f97dbce17 Mon Sep 17 00:00:00 2001
From: Piotr Roslaniec
Date: Thu, 24 Nov 2022 13:43:51 +0100
Subject: [PATCH 01/14] initial work on simple threshold decryption
---
tpke/src/lib.rs | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/tpke/src/lib.rs b/tpke/src/lib.rs
index c1f86602..35121c4a 100644
--- a/tpke/src/lib.rs
+++ b/tpke/src/lib.rs
@@ -291,6 +291,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)
}
From 81870afb4381a7acf7fb773c88b4508bd1d507dc Mon Sep 17 00:00:00 2001
From: Piotr Roslaniec
Date: Fri, 23 Dec 2022 13:30:04 +0100
Subject: [PATCH 02/14] wip
---
tpke/src/lib.rs | 135 ++++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 132 insertions(+), 3 deletions(-)
diff --git a/tpke/src/lib.rs b/tpke/src/lib.rs
index 35121c4a..3caa2b11 100644
--- a/tpke/src/lib.rs
+++ b/tpke/src/lib.rs
@@ -145,7 +145,7 @@ pub fn setup_fast(
// F_0 - The commitment to the constant term, and is the public key output Y from PVDKG
// TODO: It seems like the rest of the F_i are not computed?
let pubkey = g.mul(x);
- let privkey = h.mul(x);
+ let privkey = h.mul(x); // ek_i in PVSS?
let mut private_contexts = vec![];
let mut public_contexts = vec![];
@@ -309,6 +309,7 @@ pub fn generate_random(
#[cfg(test)]
mod tests {
use crate::*;
+ use ark_bls12_381::Fr;
use ark_std::test_rng;
type E = ark_bls12_381::Bls12_381;
@@ -369,8 +370,7 @@ mod tests {
// 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;
-
+ use std::{collections::HashMap, panic};
fn catch_unwind_silent R + panic::UnwindSafe, R>(
f: F,
) -> std::thread::Result {
@@ -520,4 +520,133 @@ mod tests {
});
assert!(result.is_err());
}
+
+ fn make_decryption_shares(
+ private_decryption_contexts: &Vec>,
+ ciphertext: &Ciphertext,
+ ) -> Vec> {
+ private_decryption_contexts
+ .iter()
+ .map(|context| {
+ let u = ciphertext.commitment;
+ let i = context.index;
+ 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)
+ let c_i = E::pairing(u, z_i); // TODO: Check whether blinded key share fits here
+ DecryptionShareSimple {
+ decrypter_index: i,
+ decryption_share: c_i,
+ }
+ })
+ .collect::>()
+ }
+
+ #[test]
+ fn simple_threshold_decryption_with_share_recovery() {
+ let mut rng = &mut test_rng();
+ let threshold = 16 * 2 / 3;
+ let shares_num = 16;
+ let msg: &[u8] = "abc".as_bytes();
+ let aad: &[u8] = "my-aad".as_bytes();
+
+ let (pubkey, _privkey, private_decryption_contexts, dealer_lagrange) =
+ setup_simple::(threshold, shares_num, &mut rng);
+
+ let ciphertext = encrypt::<_, E>(msg, aad, &pubkey, rng);
+
+ let shares =
+ make_decryption_shares(&private_decryption_contexts, &ciphertext);
+ let lagrange = prepare_combine_simple(
+ &private_decryption_contexts[0].public_decryption_contexts,
+ );
+ let s = share_combine_simple::(&shares, &lagrange);
+
+ let plaintext =
+ checked_decrypt_with_shared_secret(&ciphertext, aad, &s);
+ assert_eq!(plaintext, msg);
+
+ // Refresh the shares
+
+ // `private_decryption_contexts` represents set A of participants
+ // B set of participants contains one participant
+ // D = A\B
+ let x_r = Fr::one();
+ let original_y_r = &private_decryption_contexts[0].private_key_share;
+
+ let remaining_participants = private_decryption_contexts[1..].to_vec();
+ let new_shares = refresh_decryption_shares::(
+ &remaining_participants,
+ &x_r,
+ threshold,
+ rng,
+ );
+ }
+
+ fn refresh_decryption_shares(
+ participants: &[PrivateDecryptionContextSimple],
+ x_r: &E::Fr,
+ threshold: usize,
+ rng: &mut impl RngCore,
+ ) -> Vec> {
+ let mut deltas: HashMap> = HashMap::new();
+ for p1 in participants {
+ let i = p1.index;
+ let d_i = make_random_polynomial(threshold, x_r, rng);
+ for p2 in participants {
+ let j = p2.index;
+ let x_j = p2.public_decryption_contexts[j].domain;
+ if !deltas.contains_key(&i) {
+ deltas = HashMap::new();
+ }
+ deltas[&i][&j] = evaluate_polynomial(&d_i, x_j);
+ }
+ }
+
+ let mut new_shares: Vec> = Vec::new();
+ for p1 in participants {
+ let i = p1.index;
+ let mut y_prime_i = p1.private_key_share.private_key_shares[0];
+ for j in deltas.keys() {
+ y_prime_i = y_prime_i + deltas[j][&i];
+ }
+ new_shares.push(DecryptionShareSimple {
+ decrypter_index: i,
+ decryption_share: y_prime_i,
+ });
+ }
+ new_shares
+ }
+
+ fn make_random_polynomial(
+ threshold: usize,
+ x_r: E::Fr,
+ rng: &mut impl RngCore,
+ ) -> Vec {
+ let d_i = (0..threshold).map(|_| E::Fr::rand(rng)).collect::>();
+ d_i.insert(0, E::Fr::zero());
+
+ let d_i_at_x_r: E::Fr = evaluate_polynomial::(&d_i, x_r);
+ assert_eq!(d_i_at_x_r, E::Fr::zero());
+
+ let d_i_0 = E::Fr::zero() - d_i_at_x_r;
+ d_i[0] = d_i_0;
+ d_i
+ }
+
+ fn evaluate_polynomial(
+ polynomial: &[E::Fr],
+ x: E::Fr,
+ ) -> E::Fr {
+ let mut result = E::Fr::zero();
+ let mut x_power = E::Fr::one();
+ for coeff in polynomial {
+ result += *coeff * x_power;
+ x_power *= x;
+ }
+ result
+ }
}
From 2575edd70e5d312e83bbc011c54c666bc7312d42 Mon Sep 17 00:00:00 2001
From: Piotr Roslaniec
Date: Wed, 28 Dec 2022 20:19:25 +0100
Subject: [PATCH 03/14] failing to create a proper polynomial for recovery
---
tpke/src/lib.rs | 55 ++++++++++++++++++++++++++++++++++++++-----------
1 file changed, 43 insertions(+), 12 deletions(-)
diff --git a/tpke/src/lib.rs b/tpke/src/lib.rs
index 3caa2b11..6210bf02 100644
--- a/tpke/src/lib.rs
+++ b/tpke/src/lib.rs
@@ -310,6 +310,7 @@ pub fn generate_random(
mod tests {
use crate::*;
use ark_bls12_381::Fr;
+ use ark_ec::ProjectiveCurve;
use ark_std::test_rng;
type E = ark_bls12_381::Bls12_381;
@@ -370,7 +371,11 @@ mod tests {
// 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::{collections::HashMap, panic};
+ use std::{
+ collections::HashMap,
+ ops::{Add, AddAssign},
+ panic,
+ };
fn catch_unwind_silent R + panic::UnwindSafe, R>(
f: F,
) -> std::thread::Result {
@@ -584,6 +589,20 @@ mod tests {
threshold,
rng,
);
+
+ // TODO: Refresh lagrange coefficeints here?
+ // let lagrange = prepare_combine_simple(
+ // &private_decryption_contexts[0].public_decryption_contexts,
+ // );
+
+ let s = share_combine_simple::(&new_shares, &lagrange);
+
+ // So far, the ciphertext is valid
+ let plaintext =
+ checked_decrypt_with_shared_secret(&ciphertext, aad, &s);
+ assert_eq!(plaintext, msg);
+
+
}
fn refresh_decryption_shares(
@@ -595,27 +614,39 @@ mod tests {
let mut deltas: HashMap> = HashMap::new();
for p1 in participants {
let i = p1.index;
- let d_i = make_random_polynomial(threshold, x_r, rng);
+ let d_i = make_random_polynomial::(threshold, x_r, rng);
for p2 in participants {
let j = p2.index;
let x_j = p2.public_decryption_contexts[j].domain;
if !deltas.contains_key(&i) {
- deltas = HashMap::new();
+ deltas.insert(i, HashMap::new());
}
- deltas[&i][&j] = evaluate_polynomial(&d_i, x_j);
+ let eval = evaluate_polynomial::(&d_i, &x_j);
+ // TODO: Find a more efficient way keep track of those values
+ let mut d = deltas.get(&i).unwrap().clone();
+ d.insert(j, eval);
+ deltas.insert(i, d);
}
}
let mut new_shares: Vec> = Vec::new();
- for p1 in participants {
- let i = p1.index;
- let mut y_prime_i = p1.private_key_share.private_key_shares[0];
+ for p in participants {
+ let h_g2 = E::G2Projective::from(p.h);
+
+ assert_eq!(p.private_key_share.private_key_shares.len(), 1);
+ let i = p.index;
+ let mut y_prime_i = E::G2Projective::from(
+ p.private_key_share.private_key_shares[0],
+ );
for j in deltas.keys() {
- y_prime_i = y_prime_i + deltas[j][&i];
+ let delta_g2 = h_g2.mul(deltas[j][&i].into_repr());
+ y_prime_i += delta_g2;
}
+
new_shares.push(DecryptionShareSimple {
decrypter_index: i,
- decryption_share: y_prime_i,
+ // TODO: Is this a correct method to convert new y_prime_i to E::Fr?
+ decryption_share: E::pairing(p.g, y_prime_i),
});
}
new_shares
@@ -623,10 +654,10 @@ mod tests {
fn make_random_polynomial(
threshold: usize,
- x_r: E::Fr,
+ x_r: &E::Fr,
rng: &mut impl RngCore,
) -> Vec {
- let d_i = (0..threshold).map(|_| E::Fr::rand(rng)).collect::>();
+ let mut d_i = (0..threshold).map(|_| E::Fr::rand(rng)).collect::>();
d_i.insert(0, E::Fr::zero());
let d_i_at_x_r: E::Fr = evaluate_polynomial::(&d_i, x_r);
@@ -639,7 +670,7 @@ mod tests {
fn evaluate_polynomial(
polynomial: &[E::Fr],
- x: E::Fr,
+ x: &E::Fr,
) -> E::Fr {
let mut result = E::Fr::zero();
let mut x_power = E::Fr::one();
From c0df26e23e31107e24cfcad0319ff38cc17e5d19 Mon Sep 17 00:00:00 2001
From: Piotr Roslaniec
Date: Fri, 30 Dec 2022 19:01:57 +0100
Subject: [PATCH 04/14] fix after rebase
---
tpke/src/lib.rs | 86 ++++++++++++++++++++++---------------------------
1 file changed, 39 insertions(+), 47 deletions(-)
diff --git a/tpke/src/lib.rs b/tpke/src/lib.rs
index 6210bf02..1de585f0 100644
--- a/tpke/src/lib.rs
+++ b/tpke/src/lib.rs
@@ -376,6 +376,7 @@ mod tests {
ops::{Add, AddAssign},
panic,
};
+
fn catch_unwind_silent R + panic::UnwindSafe, R>(
f: F,
) -> std::thread::Result {
@@ -481,23 +482,10 @@ mod tests {
let mut ciphertext = encrypt::<_, E>(msg, aad, &pubkey, rng);
// Creating decryption shares
- let decryption_shares = private_decryption_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)
- })
- .collect::>();
+ let decryption_shares = make_decryption_shares(&contexts, &ciphertext);
let pub_contexts =
- &private_decryption_contexts[0].public_decryption_contexts;
+ &contexts[0].public_decryption_contexts;
let lagrange = prepare_combine_simple::(pub_contexts);
let shared_secret =
@@ -527,10 +515,10 @@ mod tests {
}
fn make_decryption_shares(
- private_decryption_contexts: &Vec>,
+ contexts: &Vec>,
ciphertext: &Ciphertext,
- ) -> Vec> {
- private_decryption_contexts
+ ) -> Vec {
+ contexts
.iter()
.map(|context| {
let u = ciphertext.commitment;
@@ -541,11 +529,7 @@ mod tests {
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)
- let c_i = E::pairing(u, z_i); // TODO: Check whether blinded key share fits here
- DecryptionShareSimple {
- decrypter_index: i,
- decryption_share: c_i,
- }
+ E::pairing(u, z_i)
})
.collect::>()
}
@@ -558,31 +542,44 @@ mod tests {
let msg: &[u8] = "abc".as_bytes();
let aad: &[u8] = "my-aad".as_bytes();
- let (pubkey, _privkey, private_decryption_contexts, dealer_lagrange) =
+ // To be updated
+ let (pubkey, _privkey, contexts) =
setup_simple::(threshold, shares_num, &mut rng);
- let ciphertext = encrypt::<_, E>(msg, aad, &pubkey, 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 = make_decryption_shares(&contexts, &ciphertext);
- let shares =
- make_decryption_shares(&private_decryption_contexts, &ciphertext);
- let lagrange = prepare_combine_simple(
- &private_decryption_contexts[0].public_decryption_contexts,
- );
- let s = share_combine_simple::(&shares, &lagrange);
+ let shares_x = &contexts[0]
+ .public_decryption_contexts
+ .iter()
+ .map(|ctxt| ctxt.domain)
+ .collect::>();
+ let lagrange = prepare_combine_simple::(shares_x);
- let plaintext =
- checked_decrypt_with_shared_secret(&ciphertext, aad, &s);
+ let shared_secret =
+ share_combine_simple::(&decryption_shares, &lagrange);
+
+ // So far, the ciphertext is valid
+ let plaintext = checked_decrypt_with_shared_secret(
+ &ciphertext,
+ aad,
+ &shared_secret,
+ );
assert_eq!(plaintext, msg);
// Refresh the shares
- // `private_decryption_contexts` represents set A of participants
+ // `contexts` represents set A of participants
// B set of participants contains one participant
// D = A\B
let x_r = Fr::one();
- let original_y_r = &private_decryption_contexts[0].private_key_share;
+ let original_y_r = &contexts[0].private_key_share;
- let remaining_participants = private_decryption_contexts[1..].to_vec();
+ let remaining_participants = contexts[1..].to_vec();
let new_shares = refresh_decryption_shares::(
&remaining_participants,
&x_r,
@@ -592,7 +589,7 @@ mod tests {
// TODO: Refresh lagrange coefficeints here?
// let lagrange = prepare_combine_simple(
- // &private_decryption_contexts[0].public_decryption_contexts,
+ // &contexts[0].public_decryption_contexts,
// );
let s = share_combine_simple::(&new_shares, &lagrange);
@@ -601,8 +598,6 @@ mod tests {
let plaintext =
checked_decrypt_with_shared_secret(&ciphertext, aad, &s);
assert_eq!(plaintext, msg);
-
-
}
fn refresh_decryption_shares(
@@ -610,7 +605,7 @@ mod tests {
x_r: &E::Fr,
threshold: usize,
rng: &mut impl RngCore,
- ) -> Vec> {
+ ) -> Vec {
let mut deltas: HashMap> = HashMap::new();
for p1 in participants {
let i = p1.index;
@@ -629,7 +624,7 @@ mod tests {
}
}
- let mut new_shares: Vec> = Vec::new();
+ let mut new_shares = Vec::new();
for p in participants {
let h_g2 = E::G2Projective::from(p.h);
@@ -643,11 +638,7 @@ mod tests {
y_prime_i += delta_g2;
}
- new_shares.push(DecryptionShareSimple {
- decrypter_index: i,
- // TODO: Is this a correct method to convert new y_prime_i to E::Fr?
- decryption_share: E::pairing(p.g, y_prime_i),
- });
+ new_shares.push(E::pairing(p.g, y_prime_i));
}
new_shares
}
@@ -657,7 +648,8 @@ mod tests {
x_r: &E::Fr,
rng: &mut impl RngCore,
) -> Vec {
- let mut d_i = (0..threshold).map(|_| E::Fr::rand(rng)).collect::>();
+ let mut d_i =
+ (0..threshold).map(|_| E::Fr::rand(rng)).collect::>();
d_i.insert(0, E::Fr::zero());
let d_i_at_x_r: E::Fr = evaluate_polynomial::(&d_i, x_r);
From 1697924d35d2c0e689ccd20f4f784be2d03c70b6 Mon Sep 17 00:00:00 2001
From: Piotr Roslaniec
Date: Mon, 2 Jan 2023 17:54:51 +0100
Subject: [PATCH 05/14] refreshing initial pass
---
tpke/src/combine.rs | 35 +++--
tpke/src/lib.rs | 340 +++++++++++++++++++++-----------------------
tpke/src/refresh.rs | 125 ++++++++++++++++
3 files changed, 309 insertions(+), 191 deletions(-)
create mode 100644 tpke/src/refresh.rs
diff --git a/tpke/src/combine.rs b/tpke/src/combine.rs
index 4e1f1ed8..96d0d18f 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],
@@ -61,17 +60,27 @@ fn lagrange_coeffs_at(
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
+ // In this formula x_i = 0, hence numerator is x_m
+ lagrange_basis_at::(shares_x, &E::Fr::zero())
+}
+
+/// Calculates Lagrange coefficients for a given x_i
+pub fn lagrange_basis_at(
+ shares_x: &[E::Fr],
+ x_i: &E::Fr,
+) -> Vec {
+ 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 +110,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/lib.rs b/tpke/src/lib.rs
index 1de585f0..c939d7ca 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 ark_ec::{msm::FixedBaseMSM, AffineCurve, PairingEngine};
+use crate::SetupParams;
+
+use ark_ec::{msm::FixedBaseMSM, AffineCurve, PairingEngine, ProjectiveCurve};
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;
@@ -306,12 +303,23 @@ 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;
@@ -323,12 +331,10 @@ mod tests {
let msg: &[u8] = "abc".as_bytes();
let aad: &[u8] = "aad".as_bytes();
- let (pubkey, _privkey, _) =
+ let (pubkey, _, _) =
setup_fast::(threshold, shares_num, &mut rng);
- let ciphertext = encrypt::(
- msg, aad, &pubkey, &mut rng,
- );
+ let ciphertext = encrypt::(msg, aad, &pubkey, &mut rng);
let serialized = ciphertext.to_bytes();
let deserialized: Ciphertext = Ciphertext::from_bytes(&serialized);
@@ -361,32 +367,12 @@ mod tests {
let (pubkey, privkey, _) =
setup_fast::(threshold, shares_num, &mut rng);
- let ciphertext = encrypt::(
- msg, aad, &pubkey, &mut rng,
- );
+ let ciphertext = encrypt::(msg, aad, &pubkey, &mut 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::{
- collections::HashMap,
- ops::{Add, AddAssign},
- 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();
@@ -395,8 +381,7 @@ mod tests {
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, &mut rng);
let mut ciphertext = encrypt::<_, E>(msg, aad, &pubkey, rng);
let mut shares: Vec> = vec![];
@@ -426,14 +411,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());
@@ -447,11 +432,10 @@ mod tests {
let msg: &[u8] = "abc".as_bytes();
let aad: &[u8] = "my-aad".as_bytes();
- let (pubkey, _privkey, _) =
+ let (pubkey, _, _) =
setup_fast::(threshold, shares_num, &mut rng);
- let mut ciphertext = encrypt::(
- msg, aad, &pubkey, &mut rng,
- );
+ let mut ciphertext =
+ encrypt::(msg, aad, &pubkey, &mut rng);
// So far, the ciphertext is valid
assert!(check_ciphertext_validity(&ciphertext, aad));
@@ -473,17 +457,19 @@ mod tests {
let msg: &[u8] = "abc".as_bytes();
let aad: &[u8] = "my-aad".as_bytes();
- // To be updated
- let (pubkey, _privkey, private_decryption_contexts) =
+ let (pubkey, _, private_decryption_contexts) =
setup_simple::(threshold, shares_num, &mut 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 = make_decryption_shares(&contexts, &ciphertext);
-
+ // Create decryption shares
+ let decryption_shares: Vec<_> = contexts
+ .iter()
+ .map(|ctxt| {
+ make_decryption_share(&ctxt.private_key_share, &ciphertext)
+ })
+ .collect();
let pub_contexts =
&contexts[0].public_decryption_contexts;
let lagrange = prepare_combine_simple::(pub_contexts);
@@ -501,59 +487,99 @@ 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());
}
- fn make_decryption_shares(
- contexts: &Vec>,
- ciphertext: &Ciphertext,
- ) -> Vec {
- contexts
- .iter()
- .map(|context| {
- let u = ciphertext.commitment;
- let i = context.index;
- 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)
- E::pairing(u, z_i)
- })
- .collect::>()
+ #[test]
+ fn simple_threshold_decryption_with_share_refreshing_at_point() {
+ let mut rng = &mut test_rng();
+ let shares_num = 16;
+ let threshold = shares_num * 2 / 3;
+
+ let (_, _, mut contexts) =
+ setup_simple::(threshold, shares_num, &mut 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();
+ }
+
+ // Refresh the share
+ let y_r = recover_share_at_point(
+ &remaining_participants,
+ threshold,
+ &x_r,
+ rng,
+ );
+ assert_eq!(y_r.into_affine(), original_y_r);
}
#[test]
- fn simple_threshold_decryption_with_share_recovery() {
+ fn simple_threshold_decryption_with_share_recovery_at_point() {
let mut rng = &mut test_rng();
- let threshold = 16 * 2 / 3;
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, contexts) =
+ let (pubkey, _, contexts) =
setup_simple::(threshold, shares_num, &mut rng);
+ let ciphertext = encrypt::<_, E>(msg, aad, &pubkey, rng);
- // Stays the same
- // Ciphertext.commitment is already computed to match U
- let mut ciphertext = encrypt::<_, E>(msg, aad, &pubkey, rng);
+ // Remove one participant from the contexts and all nested structures
+ let mut remaining_participants = contexts;
+ remaining_participants.pop();
+ for p in &mut remaining_participants {
+ p.public_decryption_contexts.pop();
+ }
+
+ // Refresh 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 decryption_shares = make_decryption_shares(&contexts, &ciphertext);
+ 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));
- let shares_x = &contexts[0]
+ // Creating a shared secret from remaining shares and the recovered one
+ let shares_x = &remaining_participants[0]
.public_decryption_contexts
.iter()
.map(|ctxt| ctxt.domain)
@@ -563,113 +589,71 @@ mod tests {
let shared_secret =
share_combine_simple::(&decryption_shares, &lagrange);
- // So far, the ciphertext is valid
let plaintext = checked_decrypt_with_shared_secret(
&ciphertext,
aad,
&shared_secret,
);
assert_eq!(plaintext, msg);
+ }
- // Refresh the shares
+ #[test]
+ fn simple_threshold_decryption_with_share_refresh() {
+ let mut 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();
- // `contexts` represents set A of participants
- // B set of participants contains one participant
- // D = A\B
- let x_r = Fr::one();
- let original_y_r = &contexts[0].private_key_share;
+ let (pubkey, _, contexts) =
+ setup_simple::(threshold, shares_num, &mut rng);
+ let ciphertext = encrypt::<_, E>(msg, aad, &pubkey, rng);
- let remaining_participants = contexts[1..].to_vec();
- let new_shares = refresh_decryption_shares::(
+ // Remove one participant from the contexts and all nested structures
+ let mut remaining_participants = contexts;
+ remaining_participants.pop();
+ for p in &mut remaining_participants {
+ p.public_decryption_contexts.pop();
+ }
+
+ // Refresh the share
+ let x_r = Fr::rand(rng);
+ let y_r = recover_share_at_point(
&remaining_participants,
- &x_r,
threshold,
+ &x_r,
rng,
);
+ let recovered_key_share = PrivateKeyShare {
+ private_key_shares: vec![y_r.into_affine()],
+ };
- // TODO: Refresh lagrange coefficeints here?
- // let lagrange = prepare_combine_simple(
- // &contexts[0].public_decryption_contexts,
- // );
-
- let s = share_combine_simple::(&new_shares, &lagrange);
-
- // So far, the ciphertext is valid
- let plaintext =
- checked_decrypt_with_shared_secret(&ciphertext, aad, &s);
- assert_eq!(plaintext, msg);
- }
-
- fn refresh_decryption_shares(
- participants: &[PrivateDecryptionContextSimple],
- x_r: &E::Fr,
- threshold: usize,
- rng: &mut impl RngCore,
- ) -> Vec {
- let mut deltas: HashMap> = HashMap::new();
- for p1 in participants {
- let i = p1.index;
- let d_i = make_random_polynomial::(threshold, x_r, rng);
- for p2 in participants {
- let j = p2.index;
- let x_j = p2.public_decryption_contexts[j].domain;
- if !deltas.contains_key(&i) {
- deltas.insert(i, HashMap::new());
- }
- let eval = evaluate_polynomial::(&d_i, &x_j);
- // TODO: Find a more efficient way keep track of those values
- let mut d = deltas.get(&i).unwrap().clone();
- d.insert(j, eval);
- deltas.insert(i, d);
- }
- }
+ // 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));
- let mut new_shares = Vec::new();
- for p in participants {
- let h_g2 = E::G2Projective::from(p.h);
-
- assert_eq!(p.private_key_share.private_key_shares.len(), 1);
- let i = p.index;
- let mut y_prime_i = E::G2Projective::from(
- p.private_key_share.private_key_shares[0],
- );
- for j in deltas.keys() {
- let delta_g2 = h_g2.mul(deltas[j][&i].into_repr());
- y_prime_i += delta_g2;
- }
-
- new_shares.push(E::pairing(p.g, y_prime_i));
- }
- new_shares
- }
+ // Creating a shared secret from remaining shares and the recovered one
+ let shares_x = &remaining_participants[0]
+ .public_decryption_contexts
+ .iter()
+ .map(|ctxt| ctxt.domain)
+ .collect::>();
+ let lagrange = prepare_combine_simple::(shares_x);
- fn make_random_polynomial(
- threshold: usize,
- x_r: &E::Fr,
- rng: &mut impl RngCore,
- ) -> Vec {
- let mut d_i =
- (0..threshold).map(|_| E::Fr::rand(rng)).collect::>();
- d_i.insert(0, E::Fr::zero());
-
- let d_i_at_x_r: E::Fr = evaluate_polynomial::(&d_i, x_r);
- assert_eq!(d_i_at_x_r, E::Fr::zero());
-
- let d_i_0 = E::Fr::zero() - d_i_at_x_r;
- d_i[0] = d_i_0;
- d_i
- }
+ let shared_secret =
+ share_combine_simple::(&decryption_shares, &lagrange);
- fn evaluate_polynomial(
- polynomial: &[E::Fr],
- x: &E::Fr,
- ) -> E::Fr {
- let mut result = E::Fr::zero();
- let mut x_power = E::Fr::one();
- for coeff in polynomial {
- result += *coeff * x_power;
- x_power *= x;
- }
- result
+ let plaintext = checked_decrypt_with_shared_secret(
+ &ciphertext,
+ aad,
+ &shared_secret,
+ );
+ assert_eq!(plaintext, msg);
}
}
diff --git a/tpke/src/refresh.rs b/tpke/src/refresh.rs
new file mode 100644
index 00000000..e79ef8f6
--- /dev/null
+++ b/tpke/src/refresh.rs
@@ -0,0 +1,125 @@
+use crate::{lagrange_basis_at, PrivateDecryptionContextSimple};
+use ark_ec::{PairingEngine, ProjectiveCurve};
+use ark_ff::{One, PrimeField, Zero};
+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::(other_participants, x_r, threshold, rng);
+
+ let new_shares_y =
+ update_decryption_shares::(other_participants, &share_updates);
+
+ // 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)
+}
+
+fn prepare_share_updates(
+ 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 0
+ let d_i = make_random_polynomial::(threshold, x_r, rng);
+
+ // Now, we need to evaluate the polynomial at each of participants' indices
+ let deltas_i: HashMap<_, _> = participants
+ .iter()
+ .map(|p2| {
+ let j = p2.index;
+ let x_j = p2.public_decryption_contexts[j].domain;
+ // Compute the evaluation of the polynomial at the domain element x_j
+ // d_i(x_j)
+ let eval = evaluate_polynomial::(&d_i, &x_j);
+ let h_g2 = E::G2Projective::from(p2.h);
+ let eval_g2 = h_g2.mul(eval.into_repr());
+ (j, eval_g2)
+ })
+ .collect();
+ (i, deltas_i)
+ })
+ .collect::>()
+}
+
+fn update_decryption_shares(
+ 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()
+}
+
+fn make_random_polynomial(
+ threshold: usize,
+ x_r: &E::Fr,
+ rng: &mut impl RngCore,
+) -> Vec {
+ // [][threshold-1]
+ let mut d_i = (0..threshold - 1)
+ .map(|_| E::Fr::rand(rng))
+ .collect::>();
+ // [0..][threshold]
+ d_i.insert(0, E::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 = E::Fr::zero() - evaluate_polynomial::(&d_i, x_r);
+ d_i[0] = d_i_0;
+ assert_eq!(evaluate_polynomial::(&d_i, x_r), E::Fr::zero());
+
+ assert_eq!(d_i.len(), threshold);
+
+ d_i
+}
+
+fn evaluate_polynomial(
+ polynomial: &[E::Fr],
+ x: &E::Fr,
+) -> E::Fr {
+ let mut result = E::Fr::zero();
+ let mut x_power = E::Fr::one();
+ for coeff in polynomial {
+ result += *coeff * x_power;
+ x_power *= x;
+ }
+ result
+}
From e4e59c8ce60c440c308748097db1423763a358f7 Mon Sep 17 00:00:00 2001
From: Piotr Roslaniec
Date: Tue, 3 Jan 2023 09:37:43 +0100
Subject: [PATCH 06/14] share refreshing
---
tpke/src/lib.rs | 93 +++++++++++++++++++--------------------------
tpke/src/refresh.rs | 75 +++++++++++++++++++++++++++---------
2 files changed, 96 insertions(+), 72 deletions(-)
diff --git a/tpke/src/lib.rs b/tpke/src/lib.rs
index c939d7ca..f8e10f70 100644
--- a/tpke/src/lib.rs
+++ b/tpke/src/lib.rs
@@ -4,7 +4,7 @@
use crate::hash_to_curve::htp_bls12381_g2;
use crate::SetupParams;
-use ark_ec::{msm::FixedBaseMSM, AffineCurve, PairingEngine, ProjectiveCurve};
+use ark_ec::{msm::FixedBaseMSM, AffineCurve, PairingEngine};
use ark_ff::{Field, One, PrimeField, ToBytes, UniformRand, Zero};
use ark_poly::{
univariate::DensePolynomial, EvaluationDomain, Polynomial, UVPolynomial,
@@ -317,6 +317,7 @@ fn make_decryption_share(
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;
@@ -325,16 +326,16 @@ mod tests {
#[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, _, _) =
- setup_fast::(threshold, shares_num, &mut rng);
+ 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);
@@ -358,16 +359,16 @@ 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);
+ 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)
@@ -375,13 +376,13 @@ mod tests {
#[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, _, 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![];
@@ -426,16 +427,15 @@ 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, _, _) =
- setup_fast::(threshold, shares_num, &mut rng);
- let mut ciphertext =
- encrypt::(msg, aad, &pubkey, &mut rng);
+ 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));
@@ -451,14 +451,14 @@ 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();
let (pubkey, _, private_decryption_contexts) =
- setup_simple::(threshold, shares_num, &mut rng);
+ setup_simple::(threshold, shares_num, rng);
// Ciphertext.commitment is already computed to match U
let mut ciphertext = encrypt::<_, E>(msg, aad, &pubkey, rng);
@@ -502,12 +502,12 @@ mod tests {
#[test]
fn simple_threshold_decryption_with_share_refreshing_at_point() {
- let mut rng = &mut test_rng();
+ let rng = &mut test_rng();
let shares_num = 16;
let threshold = shares_num * 2 / 3;
let (_, _, mut contexts) =
- setup_simple::(threshold, shares_num, &mut rng);
+ setup_simple::(threshold, shares_num, rng);
// Prepare participants
@@ -539,14 +539,14 @@ mod tests {
#[test]
fn simple_threshold_decryption_with_share_recovery_at_point() {
- let mut rng = &mut test_rng();
+ 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, &mut rng);
+ setup_simple::(threshold, shares_num, rng);
let ciphertext = encrypt::<_, E>(msg, aad, &pubkey, rng);
// Remove one participant from the contexts and all nested structures
@@ -599,47 +599,32 @@ mod tests {
#[test]
fn simple_threshold_decryption_with_share_refresh() {
- let mut rng = &mut test_rng();
+ 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, &mut rng);
+ setup_simple::(threshold, shares_num, rng);
let ciphertext = encrypt::<_, E>(msg, aad, &pubkey, rng);
- // Remove one participant from the contexts and all nested structures
- let mut remaining_participants = contexts;
- remaining_participants.pop();
- for p in &mut remaining_participants {
- p.public_decryption_contexts.pop();
- }
-
- // Refresh 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()],
- };
+ // Refresh shares
+ let fresh_shares = refresh_shares::(&contexts, threshold, rng);
// Creating decryption shares
- let mut decryption_shares: Vec<_> = remaining_participants
+ let decryption_shares: Vec<_> = fresh_shares
.iter()
- .map(|ctxt| {
- make_decryption_share(&ctxt.private_key_share, &ciphertext)
+ .map(|private_share| {
+ let private_share = PrivateKeyShare {
+ private_key_shares: vec![private_share.into_affine()],
+ };
+ make_decryption_share(&private_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 shares_x = &remaining_participants[0]
+ let shares_x = &contexts[0]
.public_decryption_contexts
.iter()
.map(|ctxt| ctxt.domain)
diff --git a/tpke/src/refresh.rs b/tpke/src/refresh.rs
index e79ef8f6..4b47e81b 100644
--- a/tpke/src/refresh.rs
+++ b/tpke/src/refresh.rs
@@ -14,11 +14,15 @@ pub fn recover_share_at_point(
x_r: &E::Fr,
rng: &mut StdRng,
) -> E::G2Projective {
- let share_updates =
- prepare_share_updates::(other_participants, x_r, threshold, rng);
+ let share_updates = prepare_share_updates_for_recovery::(
+ other_participants,
+ x_r,
+ threshold,
+ rng,
+ );
let new_shares_y =
- update_decryption_shares::(other_participants, &share_updates);
+ update_shares_for_recovery::(other_participants, &share_updates);
// Interpolate new shares to recover y_r
let shares_x = &other_participants[0]
@@ -34,7 +38,7 @@ pub fn recover_share_at_point(
prods.fold(E::G2Projective::zero(), |acc, y_j| acc + y_j)
}
-fn prepare_share_updates(
+fn prepare_share_updates_for_recovery(
participants: &[PrivateDecryptionContextSimple],
x_r: &E::Fr,
threshold: usize,
@@ -50,25 +54,14 @@ fn prepare_share_updates(
let d_i = make_random_polynomial::(threshold, x_r, rng);
// Now, we need to evaluate the polynomial at each of participants' indices
- let deltas_i: HashMap<_, _> = participants
- .iter()
- .map(|p2| {
- let j = p2.index;
- let x_j = p2.public_decryption_contexts[j].domain;
- // Compute the evaluation of the polynomial at the domain element x_j
- // d_i(x_j)
- let eval = evaluate_polynomial::(&d_i, &x_j);
- let h_g2 = E::G2Projective::from(p2.h);
- let eval_g2 = h_g2.mul(eval.into_repr());
- (j, eval_g2)
- })
- .collect();
+ let deltas_i: HashMap<_, _> =
+ compute_polynomial_deltas::(participants, &d_i);
(i, deltas_i)
})
.collect::>()
}
-fn update_decryption_shares(
+fn update_shares_for_recovery(
participants: &[PrivateDecryptionContextSimple],
deltas: &HashMap>,
) -> Vec {
@@ -123,3 +116,49 @@ fn evaluate_polynomial(
}
result
}
+
+fn prepare_share_updates_for_refreshing(
+ participants: &[PrivateDecryptionContextSimple],
+ threshold: usize,
+ rng: &mut impl RngCore,
+) -> HashMap {
+ let coeffs = make_random_polynomial::(threshold, &E::Fr::zero(), rng);
+ compute_polynomial_deltas(participants, &coeffs)
+}
+
+fn compute_polynomial_deltas(
+ participants: &[PrivateDecryptionContextSimple],
+ coeffs: &Vec,
+) -> HashMap {
+ participants
+ .iter()
+ .map(|p| {
+ let i = p.index;
+ let x_i = p.public_decryption_contexts[i].domain;
+ let eval = evaluate_polynomial::(coeffs, &x_i);
+ let h_g2 = E::G2Projective::from(p.h);
+ 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()
+}
From 232737832b34658df95a500b61fe856d7bd767f1 Mon Sep 17 00:00:00 2001
From: Piotr Roslaniec
Date: Tue, 3 Jan 2023 10:22:56 +0100
Subject: [PATCH 07/14] fix clippy warnings
---
tpke/src/refresh.rs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tpke/src/refresh.rs b/tpke/src/refresh.rs
index 4b47e81b..a4256a69 100644
--- a/tpke/src/refresh.rs
+++ b/tpke/src/refresh.rs
@@ -128,7 +128,7 @@ fn prepare_share_updates_for_refreshing(
fn compute_polynomial_deltas(
participants: &[PrivateDecryptionContextSimple],
- coeffs: &Vec,
+ coeffs: &[E::Fr],
) -> HashMap {
participants
.iter()
From 7d5ecd9a54873719f0a1f1ec42957eb94ff97945 Mon Sep 17 00:00:00 2001
From: Piotr Roslaniec
Date: Wed, 4 Jan 2023 11:01:24 +0100
Subject: [PATCH 08/14] fix after rebase
---
tpke/src/lib.rs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tpke/src/lib.rs b/tpke/src/lib.rs
index f8e10f70..cc48e50a 100644
--- a/tpke/src/lib.rs
+++ b/tpke/src/lib.rs
@@ -457,7 +457,7 @@ mod tests {
let msg: &[u8] = "abc".as_bytes();
let aad: &[u8] = "my-aad".as_bytes();
- let (pubkey, _, private_decryption_contexts) =
+ let (pubkey, _, contexts) =
setup_simple::(threshold, shares_num, rng);
// Ciphertext.commitment is already computed to match U
From 5456c422a9f9b2a3964c2d3dd8de5700f0dccdd3 Mon Sep 17 00:00:00 2001
From: Piotr Roslaniec
Date: Fri, 6 Jan 2023 16:15:41 +0100
Subject: [PATCH 09/14] add comments after initial review
---
tpke/src/refresh.rs | 18 +++++++++++-------
1 file changed, 11 insertions(+), 7 deletions(-)
diff --git a/tpke/src/refresh.rs b/tpke/src/refresh.rs
index a4256a69..6d84bb36 100644
--- a/tpke/src/refresh.rs
+++ b/tpke/src/refresh.rs
@@ -24,6 +24,7 @@ pub fn recover_share_at_point(
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
@@ -44,14 +45,16 @@ fn prepare_share_updates_for_recovery(
threshold: usize,
rng: &mut impl RngCore,
) -> HashMap> {
+ // From PSS paper, section 4.2.1, (https://link.springer.com/content/pdf/10.1007/3-540-44750-4_27.pdf)
+
// 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 0
- let d_i = make_random_polynomial::(threshold, x_r, rng);
+ // 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<_, _> =
@@ -65,6 +68,7 @@ fn update_shares_for_recovery(
participants: &[PrivateDecryptionContextSimple],
deltas: &HashMap>,
) -> Vec {
+ // From PSS paper, section 4.2.3, (https://link.springer.com/content/pdf/10.1007/3-540-44750-4_27.pdf)
// TODO: Refactor this function so that each participant performs it individually
participants
.iter()
@@ -81,9 +85,9 @@ fn update_shares_for_recovery(
.collect()
}
-fn make_random_polynomial(
+fn make_random_polynomial_at(
threshold: usize,
- x_r: &E::Fr,
+ root: &E::Fr,
rng: &mut impl RngCore,
) -> Vec {
// [][threshold-1]
@@ -95,9 +99,9 @@ fn make_random_polynomial(
// 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() - evaluate_polynomial::(&d_i, x_r);
+ let d_i_0 = E::Fr::zero() - evaluate_polynomial::(&d_i, root);
d_i[0] = d_i_0;
- assert_eq!(evaluate_polynomial::(&d_i, x_r), E::Fr::zero());
+ assert_eq!(evaluate_polynomial::(&d_i, root), E::Fr::zero());
assert_eq!(d_i.len(), threshold);
@@ -122,7 +126,7 @@ fn prepare_share_updates_for_refreshing(
threshold: usize,
rng: &mut impl RngCore,
) -> HashMap {
- let coeffs = make_random_polynomial::(threshold, &E::Fr::zero(), rng);
+ let coeffs = make_random_polynomial_at::(threshold, &E::Fr::zero(), rng);
compute_polynomial_deltas(participants, &coeffs)
}
From 48732e7d6e221ff985bde4fca35a0137f2ce123a Mon Sep 17 00:00:00 2001
From: Piotr Roslaniec
Date: Wed, 11 Jan 2023 14:34:07 +0100
Subject: [PATCH 10/14] apply pr suggestions
---
tpke/src/lib.rs | 109 +++++++++++++++++++++++++++-----------------
tpke/src/refresh.rs | 42 +++++++----------
2 files changed, 83 insertions(+), 68 deletions(-)
diff --git a/tpke/src/lib.rs b/tpke/src/lib.rs
index cc48e50a..e822f099 100644
--- a/tpke/src/lib.rs
+++ b/tpke/src/lib.rs
@@ -142,7 +142,7 @@ pub fn setup_fast(
// F_0 - The commitment to the constant term, and is the public key output Y from PVDKG
// TODO: It seems like the rest of the F_i are not computed?
let pubkey = g.mul(x);
- let privkey = h.mul(x); // ek_i in PVSS?
+ let privkey = h.mul(x);
let mut private_contexts = vec![];
let mut public_contexts = vec![];
@@ -501,7 +501,9 @@ mod tests {
}
#[test]
- fn simple_threshold_decryption_with_share_refreshing_at_point() {
+ /// Ñ 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;
@@ -527,7 +529,7 @@ mod tests {
p.public_decryption_contexts.pop();
}
- // Refresh the share
+ // Recover the share
let y_r = recover_share_at_point(
&remaining_participants,
threshold,
@@ -537,8 +539,38 @@ mod tests {
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 shares_x = pub_contexts
+ .iter()
+ .map(|context| context.domain)
+ .collect::>();
+ let lagrange = prepare_combine_simple::(&shares_x);
+ share_combine_simple::(decryption_shares, &lagrange)
+ }
+
#[test]
- fn simple_threshold_decryption_with_share_recovery_at_point() {
+ /// Ñ 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;
@@ -549,14 +581,20 @@ mod tests {
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();
+ remaining_participants.pop().unwrap();
for p in &mut remaining_participants {
- p.public_decryption_contexts.pop();
+ p.public_decryption_contexts.pop().unwrap();
}
- // Refresh the share
+ // Recover the share
let x_r = Fr::rand(rng);
let y_r = recover_share_at_point(
&remaining_participants,
@@ -579,26 +617,19 @@ mod tests {
.push(make_decryption_share(&recovered_key_share, &ciphertext));
// Creating a shared secret from remaining shares and the recovered one
- let shares_x = &remaining_participants[0]
- .public_decryption_contexts
- .iter()
- .map(|ctxt| ctxt.domain)
- .collect::>();
- let lagrange = prepare_combine_simple::