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);