From e7ecab0e1b9b310490e7f7ccf6deb73d08c866b4 Mon Sep 17 00:00:00 2001 From: Piotr Roslaniec Date: Thu, 24 Nov 2022 13:43:51 +0100 Subject: [PATCH 1/6] implement simple threshold decryption variant --- Cargo.lock | 1 + ferveo/Cargo.toml | 1 + ferveo/benches/benchmarks/pairing.rs | 5 +- ferveo/benches/benchmarks/pvdkg.rs | 2 +- ferveo/examples/pvdkg.rs | 2 +- ferveo/src/dkg/pv.rs | 4 +- tpke-wasm/benches/benchmarks.rs | 30 ++- tpke-wasm/src/lib.rs | 8 +- tpke-wasm/tests/node.rs | 9 +- tpke/benches/benchmarks.rs | 9 +- tpke/src/combine.rs | 99 ++++++---- tpke/src/context.rs | 19 ++ tpke/src/decryption.rs | 12 +- tpke/src/key_share.rs | 5 + tpke/src/lib.rs | 285 +++++++++++++++++++++++---- 15 files changed, 378 insertions(+), 113 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6d6161a3..66c3df3e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -898,6 +898,7 @@ dependencies = [ "miracl_core", "num", "pprof", + "rand 0.7.3", "rand 0.8.5", "serde", "serde_bytes", diff --git a/ferveo/Cargo.toml b/ferveo/Cargo.toml index 840d9c41..007ac763 100644 --- a/ferveo/Cargo.toml +++ b/ferveo/Cargo.toml @@ -18,6 +18,7 @@ ark-ff = "0.3" ark-serialize = "0.3" ark-poly = "0.3" rand = "0.8" +rand_old = { package = "rand", version = "0.7" } # used by benchmarks/pairing.rs either = "1.6.1" hex = "0.4.2" miracl_core = "2.3.0" diff --git a/ferveo/benches/benchmarks/pairing.rs b/ferveo/benches/benchmarks/pairing.rs index 24807cf8..318bdd30 100644 --- a/ferveo/benches/benchmarks/pairing.rs +++ b/ferveo/benches/benchmarks/pairing.rs @@ -367,7 +367,10 @@ pub fn bench_batch_inverse(c: &mut Criterion) { criterion::BenchmarkId::new("BLS12-381 Batch inverse", n), &a, |b, a| { - b.iter(|| black_box(ark_ff::batch_inversion(&mut a.clone()))); + b.iter(|| { + ark_ff::batch_inversion(&mut a.clone()); + black_box(()) + }); }, ); } diff --git a/ferveo/benches/benchmarks/pvdkg.rs b/ferveo/benches/benchmarks/pvdkg.rs index 2741d636..1f0e9edb 100644 --- a/ferveo/benches/benchmarks/pvdkg.rs +++ b/ferveo/benches/benchmarks/pvdkg.rs @@ -54,7 +54,7 @@ pub fn gen_validators( .map(|i| TendermintValidator { power: i as u64, address: format!("validator_{}", i), - public_key: keypairs[i as usize].public(), + public_key: keypairs[i].public(), }) .collect(), ) diff --git a/ferveo/examples/pvdkg.rs b/ferveo/examples/pvdkg.rs index f74f53be..c1dcf071 100644 --- a/ferveo/examples/pvdkg.rs +++ b/ferveo/examples/pvdkg.rs @@ -27,7 +27,7 @@ pub fn gen_validators( .map(|i| TendermintValidator { power: i as u64, address: format!("validator_{}", i), - public_key: keypairs[i as usize].public(), + public_key: keypairs[i].public(), }) .collect(), ) diff --git a/ferveo/src/dkg/pv.rs b/ferveo/src/dkg/pv.rs index a605d933..34ed7565 100644 --- a/ferveo/src/dkg/pv.rs +++ b/ferveo/src/dkg/pv.rs @@ -135,8 +135,8 @@ impl PubliclyVerifiableDkg { /// Returns the public key generated by the DKG pub fn final_key(&self) -> E::G1Affine { self.vss - .iter() - .map(|(_, vss)| vss.coeffs[0].into_projective()) + .values() + .map(|vss| vss.coeffs[0].into_projective()) .sum::() .into_affine() } diff --git a/tpke-wasm/benches/benchmarks.rs b/tpke-wasm/benches/benchmarks.rs index 3b2ec960..55e01f4f 100644 --- a/tpke-wasm/benches/benchmarks.rs +++ b/tpke-wasm/benches/benchmarks.rs @@ -3,14 +3,10 @@ use criterion::{black_box, criterion_group, criterion_main, Criterion}; pub fn bench_encrypt_combine(c: &mut Criterion) { use tpke_wasm::*; - fn bench_encrypt( - num_shares: usize, - num_entities: usize, - threshold: usize, - ) -> impl Fn() { + fn bench_encrypt(num_shares: usize, threshold: usize) -> impl Fn() { let message = "my-secret-message".as_bytes().to_vec(); let aad = "my-aad".as_bytes().to_vec(); - let setup = Setup::new(threshold, num_shares, num_entities); + let setup = Setup::new(threshold, num_shares); move || { let message = message.clone(); let aad = aad.clone(); @@ -18,14 +14,10 @@ pub fn bench_encrypt_combine(c: &mut Criterion) { } } - fn bench_combine( - num_shares: usize, - num_entities: usize, - threshold: usize, - ) -> impl Fn() { + fn bench_combine(num_shares: usize, threshold: usize) -> impl Fn() { let message = "my-secret-message".as_bytes().to_vec(); let aad = "my-aad".as_bytes().to_vec(); - let setup = Setup::new(threshold, num_shares, num_entities); + let setup = Setup::new(threshold, num_shares); let ciphertext = encrypt(&message.to_vec(), &aad, &setup.public_key); let participant_payloads: Vec = setup .decrypter_indexes() @@ -59,17 +51,19 @@ pub fn bench_encrypt_combine(c: &mut Criterion) { group.sample_size(10); for num_shares in [8, 16, 32, 64, 128].iter() { - let encrypt_fn = bench_encrypt(*num_shares, *num_shares, *num_shares); + let encrypt_fn = bench_encrypt(*num_shares, *num_shares); group.measurement_time(core::time::Duration::new(30, 0)); group.bench_function(format!("tpke-wasm::encrypt - num_shares={}, num_entities={}, threshold={}", num_shares, num_shares, num_shares), |b| { - b.iter(|| encrypt_fn()) - }); + #[allow(clippy::redundant_closure)] + b.iter(|| encrypt_fn()) + }); - let combine_fn = bench_combine(*num_shares, *num_shares, *num_shares); + let combine_fn = bench_combine(*num_shares, *num_shares); group.measurement_time(core::time::Duration::new(30, 0)); group.bench_function(format!("tpke-wasm::combine - num_shares={}, num_entities={}, threshold={}", num_shares, num_shares, num_shares), |b| { - b.iter(|| combine_fn()) - }); + #[allow(clippy::redundant_closure)] + b.iter(|| combine_fn()) + }); } } diff --git a/tpke-wasm/src/lib.rs b/tpke-wasm/src/lib.rs index 3b3705fb..68595015 100644 --- a/tpke-wasm/src/lib.rs +++ b/tpke-wasm/src/lib.rs @@ -163,16 +163,12 @@ pub struct Setup { #[wasm_bindgen] impl Setup { #[wasm_bindgen(constructor)] - pub fn new( - threshold: usize, - shares_num: usize, - num_entities: usize, - ) -> Self { + pub fn new(threshold: usize, shares_num: usize) -> Self { set_panic_hook(); let mut rng = rand::thread_rng(); let (public_key, private_key, contexts) = - tpke::setup::(threshold, shares_num, num_entities, &mut rng); + tpke::setup::(threshold, shares_num, &mut rng); let private_contexts = contexts .clone() .into_iter() diff --git a/tpke-wasm/tests/node.rs b/tpke-wasm/tests/node.rs index ad1f66da..708d6aec 100644 --- a/tpke-wasm/tests/node.rs +++ b/tpke-wasm/tests/node.rs @@ -14,10 +14,9 @@ pub fn participant_payload_serialization() { // TODO: Build a ciphertext from scratch let threshold = 3; let shares_num = 5; - let num_entities = 5; let message = "my-secret-message".as_bytes().to_vec(); let aad = "my-aad".as_bytes().to_vec(); - let setup = Setup::new(threshold, shares_num, num_entities); + let setup = Setup::new(threshold, shares_num); let ciphertext = encrypt(&message, &aad, &setup.public_key); let participant_payload = @@ -34,11 +33,10 @@ pub fn participant_payload_serialization() { fn encrypts_and_decrypts() { let threshold = 3; let shares_num = 5; - let num_entities = 5; let message = "my-secret-message".as_bytes().to_vec(); let aad = "my-aad".as_bytes().to_vec(); - let setup = Setup::new(threshold, shares_num, num_entities); + let setup = Setup::new(threshold, shares_num); let ciphertext = encrypt(&message, &aad, &setup.public_key); let plaintext = decrypt(&ciphertext, &setup.private_key); @@ -52,7 +50,6 @@ fn encrypts_and_decrypts() { fn threshold_encryption() { let threshold = 16 * 2 / 3; let shares_num = 16; - let num_entities = 5; let message = "my-secret-message".as_bytes().to_vec(); let aad = "my-aad".as_bytes().to_vec(); @@ -61,7 +58,7 @@ fn threshold_encryption() { // // Initialize the DKG setup - let setup = Setup::new(threshold, shares_num, num_entities); + let setup = Setup::new(threshold, shares_num); // Encrypt the message let ciphertext = encrypt(&message, &aad, &setup.public_key); diff --git a/tpke/benches/benchmarks.rs b/tpke/benches/benchmarks.rs index a440a8fc..c4f0d6fa 100644 --- a/tpke/benches/benchmarks.rs +++ b/tpke/benches/benchmarks.rs @@ -20,8 +20,7 @@ pub fn bench_decryption(c: &mut Criterion) { type E = ark_bls12_381::Bls12_381; let threshold = num_shares * 2 / 3; - let (pubkey, _, contexts) = - setup::(threshold, num_shares, num_entities, &mut rng); + let (pubkey, _, contexts) = setup::(threshold, num_shares, &mut rng); // let mut messages: Vec<[u8; NUM_OF_TX]> = vec![]; let mut messages: Vec> = vec![]; @@ -70,8 +69,7 @@ pub fn bench_decryption(c: &mut Criterion) { type E = ark_bls12_381::Bls12_381; let threshold = num_shares * 2 / 3; - let (pubkey, _, contexts) = - setup::(threshold, num_shares, num_entities, &mut rng); + let (pubkey, _, contexts) = setup::(threshold, num_shares, &mut rng); // let mut messages: Vec<[u8; NUM_OF_TX]> = vec![]; let mut messages: Vec> = vec![]; @@ -128,7 +126,8 @@ pub fn bench_decryption(c: &mut Criterion) { ); group.measurement_time(core::time::Duration::new(30, 0)); group.bench_function(format!("share_combine: {} validators threshold {}*2/3 - #msg {} - msg-size = {} bytes", num_validators, num_shares, msg_num, msg_size), |b| { - b.iter(|| a()) + #[allow(clippy::redundant_closure)] + b.iter(|| a()) }); /* let a = block_propose_bench(msg_num, num_shares, 150, msg_size); diff --git a/tpke/src/combine.rs b/tpke/src/combine.rs index 1391f281..fd14f8ef 100644 --- a/tpke/src/combine.rs +++ b/tpke/src/combine.rs @@ -2,54 +2,63 @@ #![allow(dead_code)] use crate::*; use ark_ec::ProjectiveCurve; +use itertools::zip_eq; pub fn prepare_combine( public_decryption_contexts: &[PublicDecryptionContext], shares: &[DecryptionShare], ) -> Vec { - let mut domain = vec![]; + let mut domain = vec![]; // omega_i, vector of domain points let mut n_0 = E::Fr::one(); for d_i in shares.iter() { - domain.extend( - public_decryption_contexts[d_i.decrypter_index] - .domain - .iter(), + // There's just one domain point per participant, TODO: Refactor underlying data structures + assert_eq!( + public_decryption_contexts[d_i.decrypter_index].domain.len(), + 1 ); - n_0 *= public_decryption_contexts[d_i.decrypter_index].lagrange_n_0; + domain.push(public_decryption_contexts[d_i.decrypter_index].domain[0]); + n_0 *= public_decryption_contexts[d_i.decrypter_index].lagrange_n_0; // n_0_i = 1 * t^1 * t^2 ... } let s = SubproductDomain::::new(domain); - let mut lagrange = s.inverse_lagrange_coefficients(); - ark_ff::batch_inversion_and_mul(&mut lagrange, &n_0); - let mut start = 0usize; - shares - .iter() - .map(|d_i| { + let mut lagrange = s.inverse_lagrange_coefficients(); // 1/L_i + // TODO: If this is really 1/L_i can I just return here and use it directly? Or is 1/L_i somehow different from L_i(0)? + // Given a vector of field elements {v_i}, compute the vector {coeff * v_i^(-1)} + ark_ff::batch_inversion_and_mul(&mut lagrange, &n_0); // n_0 * L_i + // L_i * [b]Z_i + izip!(shares.iter(), lagrange.iter()) + .map(|(d_i, lambda)| { let decrypter = &public_decryption_contexts[d_i.decrypter_index]; - let end = start + decrypter.domain.len(); - let lagrange_slice = &lagrange[start..end]; - start = end; + // There's just one share per participant, TODO: Refactor underlying data structures + assert_eq!( + decrypter.blinded_key_shares.blinded_key_shares.len(), + 1 + ); + let blinded_key_share = + decrypter.blinded_key_shares.blinded_key_shares[0]; E::G2Prepared::from( - izip!( - lagrange_slice.iter(), - decrypter.blinded_key_shares.blinded_key_shares.iter() //decrypter.blinded_key_shares.window_tables.iter() - ) - .map(|(lambda, blinded_key_share)| { - blinded_key_share.mul(*lambda) - }) - /*.map(|(lambda, base_table)| { - FixedBaseMSM::multi_scalar_mul::( - scalar_bits, - window_size, - &base_table.window_table, - &[*lambda], - )[0] - })*/ - .sum::() - .into_affine(), + // [b]Z_i * L_i + blinded_key_share.mul(*lambda).into_affine(), ) }) .collect::>() } +pub fn prepare_combine_simple( + shares_x: &[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 { + // In this formula x_i = 0, hence numerator is x_m + prod *= (*x_m) / (*x_m - *x_j); + } + } + lagrange_coeffs.push(prod); + } + lagrange_coeffs +} pub fn share_combine( shares: &[DecryptionShare], @@ -57,16 +66,36 @@ pub fn share_combine( ) -> E::Fqk { let mut pairing_product: Vec<(E::G1Prepared, E::G2Prepared)> = vec![]; - for (d_i, blinded_key_share) in izip!(shares, prepared_key_shares.iter()) { - // e(D_i, [b*omega_i^-1] Z_{i,omega_i}) + for (d_i, prepared_key_share) in izip!(shares, prepared_key_shares.iter()) { + // e(D_i, [b*omega_i^-1] Z_{i,omega_i}), TODO: Is this formula correct? pairing_product.push(( + // D_i E::G1Prepared::from(d_i.decryption_share), - blinded_key_share.clone(), + // Z_{i,omega_i}) = [dk_{i}^{-1}]*\hat{Y}_{i_omega_j}] + // Reference: https://nikkolasg.github.io/ferveo/pvss.html#validator-decryption-of-private-key-shares + // Prepared key share is a sum of L_i * [b]Z_i + prepared_key_share.clone(), )); } E::product_of_pairings(&pairing_product) } +pub fn share_combine_simple( + shares: &[E::Fqk], + lagrange_coeffs: &[E::Fr], +) -> E::Fqk { + 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()) { + // Exponentiation by alpha_i + let ss = c_i.pow(alpha_i.into_repr()); + product_of_shares *= ss; + } + + product_of_shares +} + #[cfg(test)] mod tests { diff --git a/tpke/src/context.rs b/tpke/src/context.rs index 38e3dd66..d6305c7c 100644 --- a/tpke/src/context.rs +++ b/tpke/src/context.rs @@ -9,6 +9,13 @@ pub struct PublicDecryptionContext { pub lagrange_n_0: E::Fr, } +#[derive(Clone, Debug)] +pub struct PublicDecryptionContextSimple { + pub domain: E::Fr, + pub public_key_shares: PublicKeyShares, + pub blinded_key_shares: BlindedKeyShares, +} + #[derive(Clone, Debug)] pub struct PrivateDecryptionContext { pub index: usize, @@ -22,3 +29,15 @@ pub struct PrivateDecryptionContext { pub scalar_bits: usize, pub window_size: usize, } + +#[derive(Clone, Debug)] +pub struct PrivateDecryptionContextSimple { + pub index: usize, + pub b: E::Fr, + pub b_inv: E::Fr, + pub private_key_share: PrivateKeyShare, + pub public_decryption_contexts: Vec>, + pub g: E::G1Affine, + pub g_inv: E::G1Prepared, + pub h_inv: E::G2Prepared, +} diff --git a/tpke/src/decryption.rs b/tpke/src/decryption.rs index 028d5225..78d01388 100644 --- a/tpke/src/decryption.rs +++ b/tpke/src/decryption.rs @@ -2,6 +2,7 @@ #![allow(dead_code)] use crate::*; + use ark_ec::ProjectiveCurve; #[derive(Debug, Clone)] @@ -42,8 +43,9 @@ impl PrivateDecryptionContext { &self, ciphertext: &Ciphertext, ) -> DecryptionShare { - let decryption_share = - ciphertext.commitment.mul(self.b_inv).into_affine(); + // let decryption_share = + // ciphertext.commitment.mul(self.b_inv).into_affine(); + let decryption_share = ciphertext.commitment; DecryptionShare { decrypter_index: self.index, @@ -115,3 +117,9 @@ impl PrivateDecryptionContext { E::product_of_pairings(&pairings) == E::Fqk::one() } } + +#[derive(Debug, Clone)] +pub struct DecryptionShareSimple { + pub decrypter_index: usize, + pub decryption_share: E::Fqk, +} diff --git a/tpke/src/key_share.rs b/tpke/src/key_share.rs index 99212fa7..9719a745 100644 --- a/tpke/src/key_share.rs +++ b/tpke/src/key_share.rs @@ -41,6 +41,7 @@ impl BlindedKeyShares { .into_affine(), ); + // Sum of Yi let alpha_z_i = E::G2Prepared::from( self.blinding_key + self @@ -52,6 +53,7 @@ impl BlindedKeyShares { .into_affine(), ); + // e(g, sum(Yi)) == e(sum(Ai), [b] H) E::product_of_pairings(&[ (E::G1Prepared::from(-g), alpha_z_i), (alpha_a_i, E::G2Prepared::from(self.blinding_key)), @@ -75,6 +77,9 @@ impl BlindedKeyShares { .collect::>() } + // key shares = [a, b, c] + // domain_inv = [1, 2, 3] + // output = [a * 1, b * 2, c * 3] pub fn multiply_by_omega_inv(&mut self, domain_inv: &[E::Fr]) { izip!(self.blinded_key_shares.iter_mut(), domain_inv.iter()).for_each( |(key, omega_inv)| *key = key.mul(-*omega_inv).into_affine(), diff --git a/tpke/src/lib.rs b/tpke/src/lib.rs index d1a1277d..b66c3522 100644 --- a/tpke/src/lib.rs +++ b/tpke/src/lib.rs @@ -1,10 +1,11 @@ #![allow(non_snake_case)] #![allow(dead_code)] + use crate::hash_to_curve::htp_bls12381_g2; use ark_ec::{msm::FixedBaseMSM, AffineCurve, PairingEngine}; use ark_ff::{Field, One, PrimeField, ToBytes, UniformRand, Zero}; -use ark_poly::EvaluationDomain; use ark_poly::{univariate::DensePolynomial, UVPolynomial}; +use ark_poly::{EvaluationDomain, Polynomial}; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; use itertools::izip; use subproductdomain::SubproductDomain; @@ -15,14 +16,23 @@ use thiserror::Error; mod ciphertext; mod hash_to_curve; + pub use ciphertext::*; + mod key_share; + pub use key_share::*; + mod decryption; + pub use decryption::*; + mod combine; + pub use combine::*; + mod context; + pub use context::*; // TODO: Turn into a crate features @@ -32,6 +42,7 @@ pub mod serialization; pub trait ThresholdEncryptionParameters { type E: PairingEngine; } + #[derive(Debug, Error)] pub enum ThresholdEncryptionError { /// Error @@ -73,18 +84,20 @@ fn construct_tag_hash( pub fn setup( threshold: usize, shares_num: usize, - num_entities: usize, rng: &mut impl RngCore, ) -> (E::G1Affine, E::G2Affine, Vec>) { + assert!(shares_num >= threshold); + + // Generators G∈G1, H∈G2 let g = E::G1Affine::prime_subgroup_generator(); let h = E::G2Affine::prime_subgroup_generator(); - let _g_inv = E::G1Prepared::from(-g); - let _h_inv = E::G2Prepared::from(-h); - assert!(shares_num >= threshold); + // The dealer chooses a uniformly random polynomial f of degree t-1 let threshold_poly = DensePolynomial::::rand(threshold - 1, rng); + // Domain, or omega Ω let fft_domain = ark_poly::Radix2EvaluationDomain::::new(shares_num).unwrap(); + // `evals` are evaluations of the polynomial f over the domain, omega: f(ω_j) for ω_j in Ω let evals = threshold_poly.evaluate_over_domain_by_ref(fft_domain); let mut domain_points = Vec::with_capacity(shares_num); @@ -92,8 +105,16 @@ pub fn setup( let mut domain_points_inv = Vec::with_capacity(shares_num); let mut point_inv = E::Fr::one(); + // domain_points are the powers of the generator g + // domain_points_inv are the powers of the inverse of the generator g + // It seems like domain points are being used in "share partitioning" + // https://nikkolasg.github.io/ferveo/dkginit.html#share-partitioning + // There's also a mention of this operation here: + // "DKG.PartitionDomain({ek_i, s_i}) -> {(ek_i, Omega_i)}" + // https://nikkolasg.github.io/ferveo/tpke-concrete.html for _ in 0..shares_num { - domain_points.push(point); + // domain_points is the share domain of the i-th participant (?) + domain_points.push(point); // 1, t, t^2, t^3, ...; where t is a scalar generator fft_domain.group_gen point *= fft_domain.group_gen; domain_points_inv.push(point_inv); point_inv *= fft_domain.group_gen_inv; @@ -102,32 +123,58 @@ pub fn setup( let window_size = FixedBaseMSM::get_mul_window_size(100); let scalar_bits = E::Fr::size_in_bits(); + // A - public key shares of participants let pubkey_shares = subproductdomain::fast_multiexp(&evals.evals, g.into_projective()); + let pubkey_share = g.mul(evals.evals[0]); + assert!(pubkey_shares[0] == E::G1Affine::from(pubkey_share)); + + // Y, but only when b = 1 - private key shares of participants let privkey_shares = subproductdomain::fast_multiexp(&evals.evals, h.into_projective()); + // h^{f(omega)} + // a_0 let x = threshold_poly.coeffs[0]; + + // 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 mut private_contexts = vec![]; let mut public_contexts = vec![]; + // TODO: There are some missing variables: \hat{u_1}, \hat{u_2} + // See: https://nikkolasg.github.io/ferveo/pvss.html#dealers-role + // \hat{u_1} is defined as \hat{u}_1 \in \mathbb{G}_2 + // See: https://nikkolasg.github.io/ferveo/dkg.html#parameters + + // We're putting together a PVSS transcript + // It seems like there is some deviation from the docs. The output is supposed to be: + // "PVSS = ((F0,sigma),(F1,ldots,Ft),Zi,ωj)" + // https://nikkolasg.github.io/ferveo/tpke-concrete.html#dkggeneratepvsstau-total_weight-ek_i-omega_i---pvss + // + // (domain, domain_inv, A, Y) for (index, (domain, domain_inv, public, private)) in izip!( - domain_points.chunks(shares_num / num_entities), - domain_points_inv.chunks(shares_num / num_entities), - pubkey_shares.chunks(shares_num / num_entities), - privkey_shares.chunks(shares_num / num_entities) + // Since we're assigning only one key share to one entity we can use chunks(1) + // This is a quick workaround to avoid refactoring all related entities that assume there are multiple key shares + // TODO: Refactor this code and all related code + domain_points.chunks(1), + domain_points_inv.chunks(1), + pubkey_shares.chunks(1), + privkey_shares.chunks(1) ) .enumerate() { let private_key_share = PrivateKeyShare:: { private_key_shares: private.to_vec(), }; - let b = E::Fr::rand(rng); + let b = E::Fr::one(); // TODO: Not blinding for now let mut blinded_key_shares = private_key_share.blind(b); blinded_key_shares.multiply_by_omega_inv(domain_inv); + // TODO: Is `blinded_key_shares` equal to [b]Z_{i,omega_i})? + // Z_{i,omega_i}) = [dk_{i}^{-1}]*\hat{Y}_{i_omega_j}] /*blinded_key_shares.window_tables = blinded_key_shares.get_window_table(window_size, scalar_bits, domain_inv);*/ private_contexts.push(PrivateDecryptionContext:: { @@ -142,23 +189,126 @@ pub fn setup( scalar_bits, window_size, }); - let mut lagrange_n_0 = domain.iter().product::(); - if domain.len() % 2 == 1 { - lagrange_n_0 = -lagrange_n_0; - } public_contexts.push(PublicDecryptionContext:: { domain: domain.to_vec(), public_key_shares: PublicKeyShares:: { public_key_shares: public.to_vec(), }, blinded_key_shares, - lagrange_n_0, + lagrange_n_0: domain.iter().product::(), + }); + } + for private in private_contexts.iter_mut() { + 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) +} + +pub fn setup_simple( + threshold: usize, + shares_num: usize, + rng: &mut impl RngCore, +) -> ( + E::G1Affine, + E::G2Affine, + Vec>, +) { + assert!(shares_num >= threshold); + + let g = E::G1Affine::prime_subgroup_generator(); + let h = E::G2Affine::prime_subgroup_generator(); + + // The delear chooses a uniformly random polynomial f of degree t-1 + let threshold_poly = DensePolynomial::::rand(threshold - 1, rng); + // Domain, or omega Ω + let fft_domain = + ark_poly::Radix2EvaluationDomain::::new(shares_num).unwrap(); + // `evals` are evaluations of the polynomial f over the domain, omega: f(ω_j) for ω_j in Ω + let evals = threshold_poly.evaluate_over_domain_by_ref(fft_domain); + + let shares_x = fft_domain.elements().collect::>(); + + // A - public key shares of participants + let pubkey_shares = + subproductdomain::fast_multiexp(&evals.evals, g.into_projective()); + let pubkey_share = g.mul(evals.evals[0]); + assert!(pubkey_shares[0] == E::G1Affine::from(pubkey_share)); + + // Y, but only when b = 1 - private key shares of participants + // Z_i = h^{f(omega)} ? + let privkey_shares = + subproductdomain::fast_multiexp(&evals.evals, h.into_projective()); + + // a_0 + let x = threshold_poly.coeffs[0]; + // F_0 + // TODO: It seems like the rest of the F_i are not computed? + let pubkey = g.mul(x); + let privkey = h.mul(x); + + let secret = threshold_poly.evaluate(&E::Fr::zero()); + assert_eq!(secret, x); + + let mut private_contexts = vec![]; + let mut public_contexts = vec![]; + + // TODO: There are some missing variables: \hat{u_1}, \hat{u_2} + // See: https://nikkolasg.github.io/ferveo/pvss.html#dealers-role + + // We're putting together a PVSS transcript + // It seems like there is some deviation from the docs. The output is supposed to be: + // "PVSS = ((F0,sigma),(F1,ldots,Ft),Zi,ωj)" + // https://nikkolasg.github.io/ferveo/tpke-concrete.html#dkggeneratepvsstau-total_weight-ek_i-omega_i---pvss + // + // (domain, A, Y) + for (index, (domain, public, private)) in izip!( + // Since we're assigning only one key share to one entity we can use chunks(1) + // This is a quick workaround to avoid refactoring all related entities that assume there are multiple key shares + // TODO: Refactor this code and all related code + shares_x.chunks(1), + pubkey_shares.chunks(1), + privkey_shares.chunks(1) + ) + .enumerate() + { + let private_key_share = PrivateKeyShare:: { + private_key_shares: private.to_vec(), + }; + // let b = E::Fr::rand(rng); + let b = E::Fr::one(); // TODO: Not blinding for now + let blinded_key_shares = private_key_share.blind(b); + private_contexts.push(PrivateDecryptionContextSimple:: { + index, + b, + b_inv: b.inverse().unwrap(), + private_key_share, + public_decryption_contexts: vec![], + g, + g_inv: E::G1Prepared::from(-g), + h_inv: E::G2Prepared::from(-h), + }); + public_contexts.push(PublicDecryptionContextSimple:: { + domain: domain[0], + public_key_shares: PublicKeyShares:: { + public_key_shares: public.to_vec(), + }, + blinded_key_shares, }); } for private in private_contexts.iter_mut() { 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" + // See: https://nikkolasg.github.io/ferveo/pvss.html#dealers-role + (pubkey.into(), privkey.into(), private_contexts) } @@ -180,13 +330,11 @@ mod tests { fn ciphertext_serialization() { let mut rng = test_rng(); let threshold = 3; - let shares_num = 5; - let num_entities = 5; + let shares_num = 8; let msg: &[u8] = "abc".as_bytes(); let aad: &[u8] = "aad".as_bytes(); - let (pubkey, _privkey, _) = - setup::(threshold, shares_num, num_entities, &mut rng); + let (pubkey, _privkey, _) = setup::(threshold, shares_num, &mut rng); let ciphertext = encrypt::( msg, aad, &pubkey, &mut rng, @@ -195,7 +343,7 @@ mod tests { let serialized = ciphertext.to_bytes(); let deserialized: Ciphertext = Ciphertext::from_bytes(&serialized); - assert!(serialized == deserialized.to_bytes()) + assert_eq!(serialized, deserialized.to_bytes()) } #[test] @@ -216,13 +364,11 @@ mod tests { fn symmetric_encryption() { let mut rng = test_rng(); let threshold = 3; - let shares_num = 5; - let num_entities = 5; + let shares_num = 8; let msg: &[u8] = "abc".as_bytes(); let aad: &[u8] = "my-aad".as_bytes(); - let (pubkey, privkey, _) = - setup::(threshold, shares_num, num_entities, &mut rng); + let (pubkey, privkey, _) = setup::(threshold, shares_num, &mut rng); let ciphertext = encrypt::( msg, aad, &pubkey, &mut rng, @@ -235,6 +381,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; + fn catch_unwind_silent R + panic::UnwindSafe, R>( f: F, ) -> std::thread::Result { @@ -250,12 +397,11 @@ mod tests { let mut rng = &mut test_rng(); let threshold = 16 * 2 / 3; let shares_num = 16; - let num_entities = 5; let msg: &[u8] = "abc".as_bytes(); let aad: &[u8] = "my-aad".as_bytes(); let (pubkey, _privkey, contexts) = - setup::(threshold, shares_num, num_entities, &mut rng); + setup::(threshold, shares_num, &mut rng); let mut ciphertext = encrypt::<_, E>(msg, aad, &pubkey, rng); let mut shares: Vec> = vec![]; @@ -270,24 +416,28 @@ mod tests { }*/ let prepared_blinded_key_shares = prepare_combine(&contexts[0].public_decryption_contexts, &shares); - let s = share_combine(&shares, &prepared_blinded_key_shares); + let shared_secret = + share_combine(&shares, &prepared_blinded_key_shares); // So far, the ciphertext is valid - let plaintext = - checked_decrypt_with_shared_secret(&ciphertext, aad, &s); + let plaintext = checked_decrypt_with_shared_secret( + &ciphertext, + aad, + &shared_secret, + ); assert_eq!(plaintext, msg); // Malformed the ciphertext ciphertext.ciphertext[0] += 1; let result = std::panic::catch_unwind(|| { - checked_decrypt_with_shared_secret(&ciphertext, aad, &s) + 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(|| { - checked_decrypt_with_shared_secret(&ciphertext, aad, &s) + checked_decrypt_with_shared_secret(&ciphertext, aad, &shared_secret) }); assert!(result.is_err()); } @@ -296,13 +446,11 @@ mod tests { fn ciphertext_validity_check() { let mut rng = test_rng(); let threshold = 3; - let shares_num = 5; - let num_entities = 5; + let shares_num = 8; let msg: &[u8] = "abc".as_bytes(); let aad: &[u8] = "my-aad".as_bytes(); - let (pubkey, _privkey, _) = - setup::(threshold, shares_num, num_entities, &mut rng); + let (pubkey, _privkey, _) = setup::(threshold, shares_num, &mut rng); let mut ciphertext = encrypt::( msg, aad, &pubkey, &mut rng, ); @@ -318,4 +466,69 @@ mod tests { let aad = "bad aad".as_bytes(); assert!(!check_ciphertext_validity(&ciphertext, aad)); } + + #[test] + fn simple_threshold_decryption() { + 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(); + + // To be updated + let (pubkey, _privkey, 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 = 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 shares_x = &private_decryption_contexts[0] + .public_decryption_contexts + .iter() + .map(|ctxt| ctxt.domain) + .collect::>(); + let lagrange = prepare_combine_simple::(shares_x); + + 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); + + // Malformed the ciphertext + ciphertext.ciphertext[0] += 1; + let result = std::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(|| { + checked_decrypt_with_shared_secret(&ciphertext, aad, &shared_secret) + }); + assert!(result.is_err()); + } } From e7694dffa785c2429658fdaa237a8963aa3f1f3c Mon Sep 17 00:00:00 2001 From: Piotr Roslaniec Date: Tue, 3 Jan 2023 10:17:15 +0100 Subject: [PATCH 2/6] run tests on CI --- .github/workflows/ferveo.yml | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 .github/workflows/ferveo.yml diff --git a/.github/workflows/ferveo.yml b/.github/workflows/ferveo.yml new file mode 100644 index 00000000..8f65c1d8 --- /dev/null +++ b/.github/workflows/ferveo.yml @@ -0,0 +1,35 @@ +name: ferveo + +on: + pull_request: + push: + branches: + - main + tags: + - v* + +env: + CARGO_INCREMENTAL: 0 + RUSTFLAGS: "-Dwarnings" + +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + include: + - target: x86_64-unknown-linux-gnu + rust: 1.66 # MSRV + - target: x86_64-unknown-linux-gnu + rust: stable + steps: + - uses: actions/checkout@v1 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: ${{ matrix.rust }} + target: ${{ matrix.target }} + override: true + - run: ${{ matrix.deps }} + - run: cargo check --all-features + - run: cargo test --release --all-features From c416197a1debc93900f9201c4fbb5e8a922f2103 Mon Sep 17 00:00:00 2001 From: Piotr Roslaniec Date: Tue, 3 Jan 2023 10:25:34 +0100 Subject: [PATCH 3/6] update msrv --- .github/workflows/ferveo.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ferveo.yml b/.github/workflows/ferveo.yml index 8f65c1d8..7cf3ede0 100644 --- a/.github/workflows/ferveo.yml +++ b/.github/workflows/ferveo.yml @@ -19,7 +19,7 @@ jobs: matrix: include: - target: x86_64-unknown-linux-gnu - rust: 1.66 # MSRV + rust: 1.63 # MSRV, `cargo msrv` - target: x86_64-unknown-linux-gnu rust: stable steps: From 116097195929ffd85e1a979b47d8783cd02285d6 Mon Sep 17 00:00:00 2001 From: Piotr Roslaniec Date: Tue, 3 Jan 2023 10:46:35 +0100 Subject: [PATCH 4/6] silence clippy warnings --- ferveo/benches/benchmarks/pairing.rs | 6 ++---- tpke-wasm/benches/benchmarks.rs | 1 + 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/ferveo/benches/benchmarks/pairing.rs b/ferveo/benches/benchmarks/pairing.rs index 318bdd30..a5a9aebb 100644 --- a/ferveo/benches/benchmarks/pairing.rs +++ b/ferveo/benches/benchmarks/pairing.rs @@ -367,10 +367,8 @@ pub fn bench_batch_inverse(c: &mut Criterion) { criterion::BenchmarkId::new("BLS12-381 Batch inverse", n), &a, |b, a| { - b.iter(|| { - ark_ff::batch_inversion(&mut a.clone()); - black_box(()) - }); + #[allow(clippy::unit_arg)] + b.iter(|| black_box(ark_ff::batch_inversion(&mut a.clone()))); }, ); } diff --git a/tpke-wasm/benches/benchmarks.rs b/tpke-wasm/benches/benchmarks.rs index 55e01f4f..69c64c23 100644 --- a/tpke-wasm/benches/benchmarks.rs +++ b/tpke-wasm/benches/benchmarks.rs @@ -37,6 +37,7 @@ pub fn bench_encrypt_combine(c: &mut Criterion) { move || { let setup = setup.clone(); let decryption_shares = decryption_shares.clone(); + #[allow(clippy::unit_arg)] black_box({ let mut ss_builder = SharedSecretBuilder::new(&setup); for share in decryption_shares { From 526d19887686d94b09aa3b389b58b1f065938352 Mon Sep 17 00:00:00 2001 From: Piotr Roslaniec Date: Tue, 3 Jan 2023 10:53:03 +0100 Subject: [PATCH 5/6] remove some comments --- tpke/src/lib.rs | 27 --------------------------- 1 file changed, 27 deletions(-) diff --git a/tpke/src/lib.rs b/tpke/src/lib.rs index b66c3522..18f94c35 100644 --- a/tpke/src/lib.rs +++ b/tpke/src/lib.rs @@ -145,16 +145,6 @@ pub fn setup( let mut private_contexts = vec![]; let mut public_contexts = vec![]; - // TODO: There are some missing variables: \hat{u_1}, \hat{u_2} - // See: https://nikkolasg.github.io/ferveo/pvss.html#dealers-role - // \hat{u_1} is defined as \hat{u}_1 \in \mathbb{G}_2 - // See: https://nikkolasg.github.io/ferveo/dkg.html#parameters - - // We're putting together a PVSS transcript - // It seems like there is some deviation from the docs. The output is supposed to be: - // "PVSS = ((F0,sigma),(F1,ldots,Ft),Zi,ωj)" - // https://nikkolasg.github.io/ferveo/tpke-concrete.html#dkggeneratepvsstau-total_weight-ek_i-omega_i---pvss - // // (domain, domain_inv, A, Y) for (index, (domain, domain_inv, public, private)) in izip!( // Since we're assigning only one key share to one entity we can use chunks(1) @@ -202,11 +192,6 @@ pub fn setup( 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) } @@ -258,14 +243,6 @@ pub fn setup_simple( let mut private_contexts = vec![]; let mut public_contexts = vec![]; - // TODO: There are some missing variables: \hat{u_1}, \hat{u_2} - // See: https://nikkolasg.github.io/ferveo/pvss.html#dealers-role - - // We're putting together a PVSS transcript - // It seems like there is some deviation from the docs. The output is supposed to be: - // "PVSS = ((F0,sigma),(F1,ldots,Ft),Zi,ωj)" - // https://nikkolasg.github.io/ferveo/tpke-concrete.html#dkggeneratepvsstau-total_weight-ek_i-omega_i---pvss - // // (domain, A, Y) for (index, (domain, public, private)) in izip!( // Since we're assigning only one key share to one entity we can use chunks(1) @@ -305,10 +282,6 @@ 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" - // See: https://nikkolasg.github.io/ferveo/pvss.html#dealers-role - (pubkey.into(), privkey.into(), private_contexts) } From 6dc71731e880fdb8c7bd27da7e48649d18fff80f Mon Sep 17 00:00:00 2001 From: Piotr Roslaniec Date: Mon, 9 Jan 2023 19:45:10 +0100 Subject: [PATCH 6/6] apply pr suggestions --- tpke-wasm/src/lib.rs | 19 ++++++---- tpke/benches/benchmarks.rs | 22 ++++++------ tpke/src/api.rs | 4 +-- tpke/src/combine.rs | 31 +++++++++++----- tpke/src/context.rs | 21 +++++------ tpke/src/decryption.rs | 16 ++++----- tpke/src/lib.rs | 73 ++++++++++++++++++++++---------------- 7 files changed, 109 insertions(+), 77 deletions(-) diff --git a/tpke-wasm/src/lib.rs b/tpke-wasm/src/lib.rs index 68595015..2fdf1aa4 100644 --- a/tpke-wasm/src/lib.rs +++ b/tpke-wasm/src/lib.rs @@ -14,8 +14,8 @@ pub type E = ark_bls12_381::Bls12_381; pub type TpkePublicKey = ark_bls12_381::G1Affine; pub type TpkePrivateKey = ark_bls12_381::G2Affine; pub type TpkeCiphertext = tpke::Ciphertext; -pub type TpkeDecryptionShare = tpke::DecryptionShare; -pub type TpkePublicDecryptionContext = tpke::PublicDecryptionContext; +pub type TpkeDecryptionShare = tpke::DecryptionShareFast; +pub type TpkePublicDecryptionContext = tpke::PublicDecryptionContextFast; pub type TpkeSharedSecret = ::Fqk; @@ -168,13 +168,16 @@ impl Setup { let mut rng = rand::thread_rng(); let (public_key, private_key, contexts) = - tpke::setup::(threshold, shares_num, &mut rng); + tpke::setup_fast::(threshold, shares_num, &mut rng); let private_contexts = contexts .clone() .into_iter() .map(|x| { PrivateDecryptionContext( - tpke::api::PrivateDecryptionContext::new(&x.b_inv, x.index), + tpke::api::PrivateDecryptionContext::new( + &x.setup_params.b_inv, + x.index, + ), ) }) .collect(); @@ -282,9 +285,11 @@ impl SharedSecretBuilder { } let prepared_blinded_key_shares = - tpke::prepare_combine(&self.contexts, &self.shares); - let shared_secret = - tpke::share_combine(&self.shares, &prepared_blinded_key_shares); + tpke::prepare_combine_fast(&self.contexts, &self.shares); + let shared_secret = tpke::share_combine_fast( + &self.shares, + &prepared_blinded_key_shares, + ); SharedSecret(shared_secret) } } diff --git a/tpke/benches/benchmarks.rs b/tpke/benches/benchmarks.rs index c4f0d6fa..01ac8420 100644 --- a/tpke/benches/benchmarks.rs +++ b/tpke/benches/benchmarks.rs @@ -20,12 +20,13 @@ pub fn bench_decryption(c: &mut Criterion) { type E = ark_bls12_381::Bls12_381; let threshold = num_shares * 2 / 3; - let (pubkey, _, contexts) = setup::(threshold, num_shares, &mut rng); + let (pubkey, _, contexts) = + setup_fast::(threshold, num_shares, &mut rng); // let mut messages: Vec<[u8; NUM_OF_TX]> = vec![]; let mut messages: Vec> = vec![]; let mut ciphertexts: Vec> = vec![]; - let mut dec_shares: Vec>> = + let mut dec_shares: Vec>> = Vec::with_capacity(ciphertexts.len()); for j in 0..num_msg { // let mut msg: [u8; NUM_OF_TX] = [0u8; NUM_OF_TX]; @@ -40,16 +41,16 @@ pub fn bench_decryption(c: &mut Criterion) { dec_shares[j].push(ctx.create_share(&ciphertexts[j])); } } - let prepared_blinded_key_shares = prepare_combine( + let prepared_blinded_key_shares = prepare_combine_fast( &contexts[0].public_decryption_contexts, &dec_shares[0], ); move || { - let shares: Vec>> = dec_shares.clone(); + let shares: Vec>> = dec_shares.clone(); for i in 0..ciphertexts.len() { - black_box(share_combine( + black_box(share_combine_fast( &shares[i], &prepared_blinded_key_shares, )); @@ -69,12 +70,13 @@ pub fn bench_decryption(c: &mut Criterion) { type E = ark_bls12_381::Bls12_381; let threshold = num_shares * 2 / 3; - let (pubkey, _, contexts) = setup::(threshold, num_shares, &mut rng); + let (pubkey, _, contexts) = + setup_fast::(threshold, num_shares, &mut rng); // let mut messages: Vec<[u8; NUM_OF_TX]> = vec![]; let mut messages: Vec> = vec![]; let mut ciphertexts: Vec> = vec![]; - let mut dec_shares: Vec>> = + let mut dec_shares: Vec>> = Vec::with_capacity(ciphertexts.len()); for j in 0..num_msg { // let mut msg: [u8; NUM_OF_TX] = [0u8; NUM_OF_TX]; @@ -93,16 +95,16 @@ pub fn bench_decryption(c: &mut Criterion) { move || { let rng = &mut ark_std::test_rng(); let c: Vec> = ciphertexts.clone(); - let shares: Vec>> = dec_shares.clone(); + let shares: Vec>> = dec_shares.clone(); contexts[0].batch_verify_decryption_shares(&c, &shares, rng); - let prepared_blinded_key_shares = prepare_combine( + let prepared_blinded_key_shares = prepare_combine_fast( &contexts[0].public_decryption_contexts, &dec_shares[0], ); for i in 0..ciphertexts.len() { - black_box(share_combine( + black_box(share_combine_fast( &shares[i], &prepared_blinded_key_shares, )); diff --git a/tpke/src/api.rs b/tpke/src/api.rs index 6ac420f3..492862c7 100644 --- a/tpke/src/api.rs +++ b/tpke/src/api.rs @@ -13,8 +13,8 @@ type E = ark_bls12_381::Bls12_381; type TpkePublicKey = ark_bls12_381::G1Affine; type TpkePrivateKey = ark_bls12_381::G2Affine; type TpkeCiphertext = crate::Ciphertext; -type TpkeDecryptionShare = crate::DecryptionShare; -type TpkePublicDecryptionContext = crate::PublicDecryptionContext; +type TpkeDecryptionShare = crate::DecryptionShareFast; +type TpkePublicDecryptionContext = crate::PublicDecryptionContextFast; type TpkeSharedSecret = ::Fqk; diff --git a/tpke/src/combine.rs b/tpke/src/combine.rs index fd14f8ef..4e1f1ed8 100644 --- a/tpke/src/combine.rs +++ b/tpke/src/combine.rs @@ -1,12 +1,13 @@ #![allow(non_snake_case)] #![allow(dead_code)] + use crate::*; use ark_ec::ProjectiveCurve; use itertools::zip_eq; -pub fn prepare_combine( - public_decryption_contexts: &[PublicDecryptionContext], - shares: &[DecryptionShare], +pub fn prepare_combine_fast( + public_decryption_contexts: &[PublicDecryptionContextFast], + shares: &[DecryptionShareFast], ) -> Vec { let mut domain = vec![]; // omega_i, vector of domain points let mut n_0 = E::Fr::one(); @@ -42,8 +43,22 @@ pub fn prepare_combine( }) .collect::>() } + pub fn prepare_combine_simple( - shares_x: &[E::Fr], + pub_contexts: &[PublicDecryptionContextSimple], +) -> Vec { + let shares_x = pub_contexts + .iter() + .map(|ctxt| ctxt.domain) + .collect::>(); + + // In this formula x_i = 0, hence numerator is x_m + lagrange_coeffs_at::(&shares_x, &E::Fr::zero()) +} + +fn lagrange_coeffs_at( + shares_x: &Vec, + 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![]; @@ -51,8 +66,7 @@ pub fn prepare_combine_simple( let mut prod = E::Fr::one(); for x_m in shares_x { if x_j != x_m { - // In this formula x_i = 0, hence numerator is x_m - prod *= (*x_m) / (*x_m - *x_j); + prod *= (*x_m - x_i) / (*x_m - *x_j); } } lagrange_coeffs.push(prod); @@ -60,8 +74,8 @@ pub fn prepare_combine_simple( lagrange_coeffs } -pub fn share_combine( - shares: &[DecryptionShare], +pub fn share_combine_fast( + shares: &[DecryptionShareFast], prepared_key_shares: &[E::G2Prepared], ) -> E::Fqk { let mut pairing_product: Vec<(E::G1Prepared, E::G2Prepared)> = vec![]; @@ -98,7 +112,6 @@ pub fn share_combine_simple( #[cfg(test)] mod tests { - type Fr = ::Fr; #[test] diff --git a/tpke/src/context.rs b/tpke/src/context.rs index d6305c7c..0cbd140d 100644 --- a/tpke/src/context.rs +++ b/tpke/src/context.rs @@ -1,7 +1,7 @@ use crate::*; #[derive(Clone, Debug)] -pub struct PublicDecryptionContext { +pub struct PublicDecryptionContextFast { pub domain: Vec, pub public_key_shares: PublicKeyShares, pub blinded_key_shares: BlindedKeyShares, @@ -17,15 +17,20 @@ pub struct PublicDecryptionContextSimple { } #[derive(Clone, Debug)] -pub struct PrivateDecryptionContext { - pub index: usize, +pub struct SetupParams { pub b: E::Fr, pub b_inv: E::Fr, - pub private_key_share: PrivateKeyShare, - pub public_decryption_contexts: Vec>, pub g: E::G1Affine, pub g_inv: E::G1Prepared, pub h_inv: E::G2Prepared, +} + +#[derive(Clone, Debug)] +pub struct PrivateDecryptionContextFast { + pub index: usize, + pub setup_params: SetupParams, + pub private_key_share: PrivateKeyShare, + pub public_decryption_contexts: Vec>, pub scalar_bits: usize, pub window_size: usize, } @@ -33,11 +38,7 @@ pub struct PrivateDecryptionContext { #[derive(Clone, Debug)] pub struct PrivateDecryptionContextSimple { pub index: usize, - pub b: E::Fr, - pub b_inv: E::Fr, + pub setup_params: SetupParams, pub private_key_share: PrivateKeyShare, pub public_decryption_contexts: Vec>, - pub g: E::G1Affine, - pub g_inv: E::G1Prepared, - pub h_inv: E::G2Prepared, } diff --git a/tpke/src/decryption.rs b/tpke/src/decryption.rs index 78d01388..f5bee24d 100644 --- a/tpke/src/decryption.rs +++ b/tpke/src/decryption.rs @@ -6,12 +6,12 @@ use crate::*; use ark_ec::ProjectiveCurve; #[derive(Debug, Clone)] -pub struct DecryptionShare { +pub struct DecryptionShareFast { pub decrypter_index: usize, pub decryption_share: E::G1Affine, } -impl DecryptionShare { +impl DecryptionShareFast { pub fn to_bytes(&self) -> Vec { let mut bytes = Vec::new(); let decrypter_index = @@ -31,23 +31,23 @@ impl DecryptionShare { CanonicalDeserialize::deserialize(&bytes[INDEX_BYTE_LEN..]) .unwrap(); - DecryptionShare { + DecryptionShareFast { decrypter_index, decryption_share, } } } -impl PrivateDecryptionContext { +impl PrivateDecryptionContextFast { pub fn create_share( &self, ciphertext: &Ciphertext, - ) -> DecryptionShare { + ) -> DecryptionShareFast { // let decryption_share = // ciphertext.commitment.mul(self.b_inv).into_affine(); let decryption_share = ciphertext.commitment; - DecryptionShare { + DecryptionShareFast { decrypter_index: self.index, decryption_share, } @@ -55,7 +55,7 @@ impl PrivateDecryptionContext { pub fn batch_verify_decryption_shares( &self, ciphertexts: &[Ciphertext], - shares: &[Vec>], + shares: &[Vec>], //ciphertexts_and_shares: &[(Ciphertext, Vec>)], rng: &mut R, ) -> bool { @@ -95,7 +95,7 @@ impl PrivateDecryptionContext { ); // e(\sum_j [ \sum_i \alpha_{i,j} ] U_j, -H) - pairings.push((sum_u_j, self.h_inv.clone())); + pairings.push((sum_u_j, self.setup_params.h_inv.clone())); let mut sum_d_j = vec![E::G1Projective::zero(); num_shares]; diff --git a/tpke/src/lib.rs b/tpke/src/lib.rs index 18f94c35..c1f86602 100644 --- a/tpke/src/lib.rs +++ b/tpke/src/lib.rs @@ -33,6 +33,7 @@ pub use combine::*; mod context; +use crate::SetupParams; pub use context::*; // TODO: Turn into a crate features @@ -81,11 +82,15 @@ fn construct_tag_hash( hash_to_g2(&hash_input) } -pub fn setup( +pub fn setup_fast( threshold: usize, shares_num: usize, rng: &mut impl RngCore, -) -> (E::G1Affine, E::G2Affine, Vec>) { +) -> ( + E::G1Affine, + E::G2Affine, + Vec>, +) { assert!(shares_num >= threshold); // Generators G∈G1, H∈G2 @@ -167,19 +172,21 @@ pub fn setup( // Z_{i,omega_i}) = [dk_{i}^{-1}]*\hat{Y}_{i_omega_j}] /*blinded_key_shares.window_tables = blinded_key_shares.get_window_table(window_size, scalar_bits, domain_inv);*/ - private_contexts.push(PrivateDecryptionContext:: { + private_contexts.push(PrivateDecryptionContextFast:: { index, - b, - b_inv: b.inverse().unwrap(), + setup_params: SetupParams { + b, + b_inv: b.inverse().unwrap(), + g, + g_inv: E::G1Prepared::from(-g), + h_inv: E::G2Prepared::from(-h), + }, private_key_share, public_decryption_contexts: vec![], - g, - g_inv: E::G1Prepared::from(-g), - h_inv: E::G2Prepared::from(-h), scalar_bits, window_size, }); - public_contexts.push(PublicDecryptionContext:: { + public_contexts.push(PublicDecryptionContextFast:: { domain: domain.to_vec(), public_key_shares: PublicKeyShares:: { public_key_shares: public.to_vec(), @@ -262,13 +269,15 @@ pub fn setup_simple( let blinded_key_shares = private_key_share.blind(b); private_contexts.push(PrivateDecryptionContextSimple:: { index, - b, - b_inv: b.inverse().unwrap(), + setup_params: SetupParams { + b, + b_inv: b.inverse().unwrap(), + g, + g_inv: E::G1Prepared::from(-g), + h_inv: E::G2Prepared::from(-h), + }, private_key_share, public_decryption_contexts: vec![], - g, - g_inv: E::G1Prepared::from(-g), - h_inv: E::G2Prepared::from(-h), }); public_contexts.push(PublicDecryptionContextSimple:: { domain: domain[0], @@ -307,7 +316,8 @@ mod tests { let msg: &[u8] = "abc".as_bytes(); let aad: &[u8] = "aad".as_bytes(); - let (pubkey, _privkey, _) = setup::(threshold, shares_num, &mut rng); + let (pubkey, _privkey, _) = + setup_fast::(threshold, shares_num, &mut rng); let ciphertext = encrypt::( msg, aad, &pubkey, &mut rng, @@ -321,15 +331,15 @@ mod tests { #[test] fn decryption_share_serialization() { - let decryption_share = DecryptionShare:: { + let decryption_share = DecryptionShareFast:: { decrypter_index: 1, decryption_share: ark_bls12_381::G1Affine::prime_subgroup_generator( ), }; let serialized = decryption_share.to_bytes(); - let deserialized: DecryptionShare = - DecryptionShare::from_bytes(&serialized); + let deserialized: DecryptionShareFast = + DecryptionShareFast::from_bytes(&serialized); assert_eq!(serialized, deserialized.to_bytes()) } @@ -341,7 +351,8 @@ mod tests { let msg: &[u8] = "abc".as_bytes(); let aad: &[u8] = "my-aad".as_bytes(); - let (pubkey, privkey, _) = setup::(threshold, shares_num, &mut rng); + let (pubkey, privkey, _) = + setup_fast::(threshold, shares_num, &mut rng); let ciphertext = encrypt::( msg, aad, &pubkey, &mut rng, @@ -374,10 +385,10 @@ mod tests { let aad: &[u8] = "my-aad".as_bytes(); let (pubkey, _privkey, contexts) = - setup::(threshold, shares_num, &mut rng); + setup_fast::(threshold, shares_num, &mut rng); let mut ciphertext = encrypt::<_, E>(msg, aad, &pubkey, rng); - let mut shares: Vec> = vec![]; + let mut shares: Vec> = vec![]; for context in contexts.iter() { shares.push(context.create_share(&ciphertext)); } @@ -387,10 +398,12 @@ mod tests { .blinded_key_shares .verify_blinding(&pub_context.public_key_shares, rng)); }*/ - let prepared_blinded_key_shares = - prepare_combine(&contexts[0].public_decryption_contexts, &shares); + let prepared_blinded_key_shares = prepare_combine_fast( + &contexts[0].public_decryption_contexts, + &shares, + ); let shared_secret = - share_combine(&shares, &prepared_blinded_key_shares); + share_combine_fast(&shares, &prepared_blinded_key_shares); // So far, the ciphertext is valid let plaintext = checked_decrypt_with_shared_secret( @@ -423,7 +436,8 @@ mod tests { let msg: &[u8] = "abc".as_bytes(); let aad: &[u8] = "my-aad".as_bytes(); - let (pubkey, _privkey, _) = setup::(threshold, shares_num, &mut rng); + let (pubkey, _privkey, _) = + setup_fast::(threshold, shares_num, &mut rng); let mut ciphertext = encrypt::( msg, aad, &pubkey, &mut rng, ); @@ -472,12 +486,9 @@ mod tests { }) .collect::>(); - let shares_x = &private_decryption_contexts[0] - .public_decryption_contexts - .iter() - .map(|ctxt| ctxt.domain) - .collect::>(); - let lagrange = prepare_combine_simple::(shares_x); + let pub_contexts = + &private_decryption_contexts[0].public_decryption_contexts; + let lagrange = prepare_combine_simple::(pub_contexts); let shared_secret = share_combine_simple::(&decryption_shares, &lagrange);