From 06ae244941d8eb93aff63a4ff1e5088c3deccd1b Mon Sep 17 00:00:00 2001
From: Piotr Roslaniec
Date: Thu, 18 Jan 2024 15:40:57 +0100
Subject: [PATCH 1/7] refactor(dkg): refactor dkg params into a seperate struct
---
ferveo-wasm/tests/node.rs | 27 ++++----
ferveo/benches/benchmarks/validity_checks.rs | 6 +-
ferveo/examples/bench_primitives_size.rs | 6 +-
ferveo/src/api.rs | 15 +++--
ferveo/src/bindings_python.rs | 14 +++-
ferveo/src/bindings_wasm.rs | 8 +--
ferveo/src/dkg.rs | 71 +++++++++++++++++++-
ferveo/src/lib.rs | 34 ++++++----
ferveo/src/pvss.rs | 34 +++++-----
ferveo/src/validator.rs | 1 +
10 files changed, 146 insertions(+), 70 deletions(-)
diff --git a/ferveo-wasm/tests/node.rs b/ferveo-wasm/tests/node.rs
index b4234d07..4ac71429 100644
--- a/ferveo-wasm/tests/node.rs
+++ b/ferveo-wasm/tests/node.rs
@@ -8,8 +8,8 @@ use wasm_bindgen_test::*;
type TestSetup = (
u32,
- usize,
- usize,
+ u32,
+ u32,
Vec,
Vec,
ValidatorArray,
@@ -21,11 +21,12 @@ type TestSetup = (
fn setup_dkg() -> TestSetup {
let tau = 1;
- let shares_num = 16;
+ let shares_num: u32 = 16;
let security_threshold = shares_num * 2 / 3;
- let validator_keypairs =
- (0..shares_num).map(gen_keypair).collect::>();
+ let validator_keypairs = (0..shares_num as usize)
+ .map(gen_keypair)
+ .collect::>();
let validators = validator_keypairs
.iter()
.enumerate()
@@ -38,8 +39,8 @@ fn setup_dkg() -> TestSetup {
let messages = validators.iter().map(|sender| {
let dkg = Dkg::new(
tau,
- shares_num as u32,
- security_threshold as u32,
+ shares_num,
+ security_threshold,
&validators_js,
sender,
)
@@ -54,8 +55,8 @@ fn setup_dkg() -> TestSetup {
let mut dkg = Dkg::new(
tau,
- shares_num as u32,
- security_threshold as u32,
+ shares_num,
+ security_threshold,
&validators_js,
&validators[0],
)
@@ -112,8 +113,8 @@ fn tdec_simple() {
.map(|(validator, keypair)| {
let mut dkg = Dkg::new(
tau,
- shares_num as u32,
- security_threshold as u32,
+ shares_num,
+ security_threshold,
&validators_js,
&validator,
)
@@ -166,8 +167,8 @@ fn tdec_precomputed() {
.map(|(validator, keypair)| {
let mut dkg = Dkg::new(
tau,
- shares_num as u32,
- security_threshold as u32,
+ shares_num,
+ security_threshold,
&validators_js,
&validator,
)
diff --git a/ferveo/benches/benchmarks/validity_checks.rs b/ferveo/benches/benchmarks/validity_checks.rs
index a6dd9f48..cc7266f7 100644
--- a/ferveo/benches/benchmarks/validity_checks.rs
+++ b/ferveo/benches/benchmarks/validity_checks.rs
@@ -45,11 +45,7 @@ fn setup_dkg(
let me = validators[validator].clone();
PubliclyVerifiableDkg::new(
&validators,
- &DkgParams {
- tau: 0,
- security_threshold: shares_num / 3,
- shares_num,
- },
+ &DkgParams::new(0, shares_num / 3, shares_num).unwrap(),
&me,
)
.expect("Setup failed")
diff --git a/ferveo/examples/bench_primitives_size.rs b/ferveo/examples/bench_primitives_size.rs
index 18adf673..79afb8a4 100644
--- a/ferveo/examples/bench_primitives_size.rs
+++ b/ferveo/examples/bench_primitives_size.rs
@@ -80,11 +80,7 @@ fn setup_dkg(
let me = validators[validator].clone();
PubliclyVerifiableDkg::new(
&validators,
- &DkgParams {
- tau: 0,
- security_threshold,
- shares_num,
- },
+ &DkgParams::new(0, security_threshold, shares_num).unwrap(),
&me,
)
.expect("Setup failed")
diff --git a/ferveo/src/api.rs b/ferveo/src/api.rs
index af3edcd4..be8f5b1f 100644
--- a/ferveo/src/api.rs
+++ b/ferveo/src/api.rs
@@ -18,6 +18,9 @@ pub type PublicKey = ferveo_common::PublicKey;
pub type Keypair = ferveo_common::Keypair;
pub type Validator = crate::Validator;
pub type Transcript = PubliclyVerifiableSS;
+
+// pub type ShareIndex = u32;
+// pub type ValidatorMessage = (ShareIndex, Validator, Transcript);
pub type ValidatorMessage = (Validator, Transcript);
#[cfg(feature = "bindings-python")]
@@ -203,11 +206,8 @@ impl Dkg {
validators: &[Validator],
me: &Validator,
) -> Result {
- let dkg_params = crate::DkgParams {
- tau,
- security_threshold,
- shares_num,
- };
+ let dkg_params =
+ crate::DkgParams::new(tau, security_threshold, shares_num)?;
let dkg = crate::PubliclyVerifiableDkg::::new(
validators,
&dkg_params,
@@ -312,7 +312,7 @@ impl AggregatedTranscript {
.0
.domain
.elements()
- .take(dkg.0.dkg_params.shares_num as usize)
+ .take(dkg.0.dkg_params.shares_num() as usize)
.collect();
self.0.make_decryption_share_simple_precomputed(
&ciphertext_header.0,
@@ -434,6 +434,7 @@ mod test_ferveo_api {
(sender.clone(), dkg.generate_transcript(rng).unwrap())
})
.collect();
+
(messages, validators, validator_keypairs)
}
@@ -647,7 +648,7 @@ mod test_ferveo_api {
let local_aggregate = dkg.aggregate_transcripts(&messages).unwrap();
assert!(local_aggregate
- .verify(dkg.0.dkg_params.shares_num, &messages)
+ .verify(dkg.0.dkg_params.shares_num(), &messages)
.is_ok());
}
diff --git a/ferveo/src/bindings_python.rs b/ferveo/src/bindings_python.rs
index ed965f3e..00c455a3 100644
--- a/ferveo/src/bindings_python.rs
+++ b/ferveo/src/bindings_python.rs
@@ -93,7 +93,17 @@ impl From for PyErr {
}
Error::InvalidVariant(variant) => {
InvalidVariant::new_err(variant.to_string())
- }
+ },
+ Error::InvalidDkgParameters(num_shares, security_threshold) => {
+ InvalidDkgParameters::new_err(format!(
+ "num_shares: {num_shares}, security_threshold: {security_threshold}"
+ ))
+ },
+ Error::InvalidShareIndex(index) => {
+ InvalidShareIndex::new_err(format!(
+ "{index}"
+ ))
+ },
},
_ => default(),
}
@@ -128,6 +138,8 @@ create_exception!(exceptions, ValidatorPublicKeyMismatch, PyValueError);
create_exception!(exceptions, SerializationError, PyValueError);
create_exception!(exceptions, InvalidByteLength, PyValueError);
create_exception!(exceptions, InvalidVariant, PyValueError);
+create_exception!(exceptions, InvalidDkgParameters, PyValueError);
+create_exception!(exceptions, InvalidShareIndex, PyValueError);
fn from_py_bytes(bytes: &[u8]) -> PyResult {
T::from_bytes(bytes)
diff --git a/ferveo/src/bindings_wasm.rs b/ferveo/src/bindings_wasm.rs
index e412b6e0..5b03f188 100644
--- a/ferveo/src/bindings_wasm.rs
+++ b/ferveo/src/bindings_wasm.rs
@@ -510,15 +510,13 @@ impl AggregatedTranscript {
#[wasm_bindgen]
pub fn verify(
&self,
- shares_num: usize,
+ shares_num: u32,
messages: &ValidatorMessageArray,
) -> JsResult {
set_panic_hook();
let messages = unwrap_messages_js(messages)?;
- let is_valid = self
- .0
- .verify(shares_num as u32, &messages)
- .map_err(map_js_err)?;
+ let is_valid =
+ self.0.verify(shares_num, &messages).map_err(map_js_err)?;
Ok(is_valid)
}
diff --git a/ferveo/src/dkg.rs b/ferveo/src/dkg.rs
index 3f7fc09d..808afd40 100644
--- a/ferveo/src/dkg.rs
+++ b/ferveo/src/dkg.rs
@@ -15,9 +15,50 @@ use crate::{
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
pub struct DkgParams {
- pub tau: u32,
- pub security_threshold: u32,
- pub shares_num: u32,
+ tau: u32,
+ security_threshold: u32,
+ shares_num: u32,
+}
+
+impl DkgParams {
+ /// Create new DKG parameters
+ /// `tau` is a unique identifier for the DKG (ritual id)
+ /// `security_threshold` is the minimum number of shares required to reconstruct the key
+ /// `shares_num` is the total number of shares to be generated
+ /// Returns an error if the parameters are invalid
+ /// Parameters must hold: `shares_num` >= `security_threshold`
+ pub fn new(
+ tau: u32,
+ security_threshold: u32,
+ shares_num: u32,
+ ) -> Result {
+ if shares_num < security_threshold
+ || shares_num == 0
+ || security_threshold == 0
+ {
+ return Err(Error::InvalidDkgParameters(
+ shares_num,
+ security_threshold,
+ ));
+ }
+ Ok(Self {
+ tau,
+ security_threshold,
+ shares_num,
+ })
+ }
+
+ pub fn tau(&self) -> u32 {
+ self.tau
+ }
+
+ pub fn security_threshold(&self) -> u32 {
+ self.security_threshold
+ }
+
+ pub fn shares_num(&self) -> u32 {
+ self.shares_num
+ }
}
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
@@ -123,6 +164,7 @@ impl PubliclyVerifiableDkg {
validators,
state: DkgState::Sharing {
accumulated_shares: 0,
+ // TODO: Do we need to keep track of the block number?
block: 0,
},
})
@@ -151,6 +193,7 @@ impl PubliclyVerifiableDkg {
}
}
+ // TODO: Make private, use `share` instead. Currently used only in bindings
pub fn create_share(
&self,
rng: &mut R,
@@ -248,6 +291,10 @@ impl PubliclyVerifiableDkg {
return Err(Error::UnknownDealer(sender.clone().address));
}
+ // TODO: Throw error instead of silently accepting excess shares?
+ // if self.vss.len() < self.dkg_params.shares_num as usize {
+ // self.vss.insert(sender.address.clone(), pvss.clone());
+ // }
self.vss.insert(sender.address.clone(), pvss.clone());
// we keep track of the amount of shares seen until the security
@@ -751,3 +798,21 @@ mod test_aggregation {
assert!(dkg.verify_message(&sender, &aggregate).is_err());
}
}
+
+/// Test DKG parameters
+#[cfg(test)]
+mod test_dkg_params {
+ const TAU: u32 = 0;
+
+ #[test]
+ fn test_shares_num_less_than_security_threshold() {
+ let dkg_params = super::DkgParams::new(TAU, 4, 3);
+ assert!(dkg_params.is_err());
+ }
+
+ #[test]
+ fn test_valid_dkg_params() {
+ let dkg_params = super::DkgParams::new(TAU, 2, 3);
+ assert!(dkg_params.is_ok());
+ }
+}
diff --git a/ferveo/src/lib.rs b/ferveo/src/lib.rs
index 59a44024..4a29f099 100644
--- a/ferveo/src/lib.rs
+++ b/ferveo/src/lib.rs
@@ -101,6 +101,12 @@ pub enum Error {
#[error("Invalid variant: {0}")]
InvalidVariant(String),
+
+ #[error("Invalid DKG parameters: number of shares {0}, threshold {1}")]
+ InvalidDkgParameters(u32, u32),
+
+ #[error("Invalid share index: {0}")]
+ InvalidShareIndex(u32),
}
pub type Result = std::result::Result;
@@ -410,7 +416,7 @@ mod test_dkg_full {
&domain_points,
&dkg.pvss_params.h.into_affine(),
&x_r,
- dkg.dkg_params.security_threshold as usize,
+ dkg.dkg_params.security_threshold() as usize,
rng,
);
(v_addr.clone(), deltas_i)
@@ -439,11 +445,13 @@ mod test_dkg_full {
// Creates updated private key shares
// TODO: Why not using dkg.aggregate()?
let pvss_aggregated = aggregate(&dkg.vss);
- pvss_aggregated.update_private_key_share_for_recovery(
- &decryption_key,
- validator.share_index,
- updates_for_participant.as_slice(),
- )
+ pvss_aggregated
+ .update_private_key_share_for_recovery(
+ &decryption_key,
+ validator.share_index,
+ updates_for_participant.as_slice(),
+ )
+ .unwrap()
})
.collect();
@@ -552,7 +560,7 @@ mod test_dkg_full {
let deltas_i = prepare_share_updates_for_refresh::(
&domain_points,
&dkg.pvss_params.h.into_affine(),
- dkg.dkg_params.security_threshold as usize,
+ dkg.dkg_params.security_threshold() as usize,
rng,
);
(v_addr.clone(), deltas_i)
@@ -582,11 +590,13 @@ mod test_dkg_full {
// Creates updated private key shares
// TODO: Why not using dkg.aggregate()?
let pvss_aggregated = aggregate(&dkg.vss);
- pvss_aggregated.update_private_key_share_for_recovery(
- &decryption_key,
- validator.share_index,
- updates_for_participant.as_slice(),
- )
+ pvss_aggregated
+ .update_private_key_share_for_recovery(
+ &decryption_key,
+ validator.share_index,
+ updates_for_participant.as_slice(),
+ )
+ .unwrap()
})
.collect();
diff --git a/ferveo/src/pvss.rs b/ferveo/src/pvss.rs
index 4f63da82..495018bf 100644
--- a/ferveo/src/pvss.rs
+++ b/ferveo/src/pvss.rs
@@ -40,9 +40,6 @@ pub trait Aggregate {}
/// Apply trait gate to Aggregated marker struct
impl Aggregate for Aggregated {}
-// /// Type alias for non aggregated PVSS transcripts
-// pub type Pvss = PubliclyVerifiableSS;
-
/// Type alias for aggregated PVSS transcripts
pub type AggregatedPvss = PubliclyVerifiableSS;
@@ -138,7 +135,7 @@ impl PubliclyVerifiableSS {
) -> Result {
let phi = SecretPolynomial::::new(
s,
- (dkg.dkg_params.security_threshold - 1) as usize,
+ (dkg.dkg_params.security_threshold() - 1) as usize,
rng,
);
@@ -311,19 +308,19 @@ impl PubliclyVerifiableSS {
&self,
validator_decryption_key: &E::ScalarField,
share_index: usize,
- ) -> PrivateKeyShare {
+ ) -> Result> {
// Decrypt private key shares https://nikkolasg.github.io/ferveo/pvss.html#validator-decryption-of-private-key-shares
let private_key_share = self
.shares
.get(share_index)
- .unwrap()
+ .ok_or(Error::InvalidShareIndex(share_index as u32))?
.mul(
validator_decryption_key
.inverse()
.expect("Validator decryption key must have an inverse"),
)
.into_affine();
- PrivateKeyShare { private_key_share }
+ Ok(PrivateKeyShare { private_key_share })
}
pub fn make_decryption_share_simple(
@@ -335,7 +332,7 @@ impl PubliclyVerifiableSS {
g_inv: &E::G1Prepared,
) -> Result> {
let private_key_share = self
- .decrypt_private_key_share(validator_decryption_key, share_index);
+ .decrypt_private_key_share(validator_decryption_key, share_index)?;
DecryptionShareSimple::create(
validator_decryption_key,
&private_key_share,
@@ -356,7 +353,7 @@ impl PubliclyVerifiableSS {
g_inv: &E::G1Prepared,
) -> Result> {
let private_key_share = self
- .decrypt_private_key_share(validator_decryption_key, share_index);
+ .decrypt_private_key_share(validator_decryption_key, share_index)?;
// We use the `prepare_combine_simple` function to precompute the lagrange coefficients
let lagrange_coeffs = prepare_combine_simple::(domain_points);
@@ -379,13 +376,16 @@ impl PubliclyVerifiableSS {
validator_decryption_key: &E::ScalarField,
share_index: usize,
share_updates: &[E::G2],
- ) -> PrivateKeyShare {
+ ) -> Result> {
// Retrieves their private key share
let private_key_share = self
- .decrypt_private_key_share(validator_decryption_key, share_index);
+ .decrypt_private_key_share(validator_decryption_key, share_index)?;
// And updates their share
- apply_updates_to_private_share::(&private_key_share, share_updates)
+ Ok(apply_updates_to_private_share::(
+ &private_key_share,
+ share_updates,
+ ))
}
}
@@ -482,7 +482,7 @@ mod test_pvss {
// Check that a polynomial of the correct degree was created
assert_eq!(
pvss.coeffs.len(),
- dkg.dkg_params.security_threshold as usize
+ dkg.dkg_params.security_threshold() as usize
);
// Check that the correct number of shares were created
assert_eq!(pvss.shares.len(), dkg.validators.len());
@@ -555,11 +555,7 @@ mod test_pvss {
// And because of that the DKG should fail
let result = PubliclyVerifiableDkg::new(
&validators,
- &DkgParams {
- tau: 0,
- security_threshold,
- shares_num,
- },
+ &DkgParams::new(0, security_threshold, shares_num).unwrap(),
&me,
);
assert!(result.is_err());
@@ -578,7 +574,7 @@ mod test_pvss {
// Check that a polynomial of the correct degree was created
assert_eq!(
aggregate.coeffs.len(),
- dkg.dkg_params.security_threshold as usize
+ dkg.dkg_params.security_threshold() as usize
);
// Check that the correct number of shares were created
assert_eq!(aggregate.shares.len(), dkg.validators.len());
diff --git a/ferveo/src/validator.rs b/ferveo/src/validator.rs
index 7b014266..d931ca06 100644
--- a/ferveo/src/validator.rs
+++ b/ferveo/src/validator.rs
@@ -54,6 +54,7 @@ impl PartialOrd for Validator {
}
impl Ord for Validator {
+ // Validators are ordered by their address only
fn cmp(&self, other: &Self) -> Ordering {
self.address.cmp(&other.address)
}
From 66d25aecb5a3e29784f6d2ef1a7977ce4a2d406a Mon Sep 17 00:00:00 2001
From: Piotr Roslaniec
Date: Thu, 18 Jan 2024 19:05:50 +0100
Subject: [PATCH 2/7] refactor(test): use test_case crate to deduplicate tests
---
Cargo.lock | 34 +++++
ferveo/Cargo.toml | 1 +
ferveo/src/api.rs | 346 ++++++++++++++++++++++------------------------
ferveo/src/dkg.rs | 10 +-
ferveo/src/lib.rs | 177 ++++++++++++------------
5 files changed, 293 insertions(+), 275 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index 7aa7e272..685f931f 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -786,6 +786,7 @@ dependencies = [
"serde",
"serde_with",
"subproductdomain-pre-release",
+ "test-case",
"thiserror",
"wasm-bindgen",
"wasm-bindgen-derive",
@@ -1881,6 +1882,39 @@ dependencies = [
"winapi-util",
]
+[[package]]
+name = "test-case"
+version = "3.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eb2550dd13afcd286853192af8601920d959b14c401fcece38071d53bf0768a8"
+dependencies = [
+ "test-case-macros",
+]
+
+[[package]]
+name = "test-case-core"
+version = "3.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "adcb7fd841cd518e279be3d5a3eb0636409487998a4aff22f3de87b81e88384f"
+dependencies = [
+ "cfg-if",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.15",
+]
+
+[[package]]
+name = "test-case-macros"
+version = "3.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.15",
+ "test-case-core",
+]
+
[[package]]
name = "textwrap"
version = "0.11.0"
diff --git a/ferveo/Cargo.toml b/ferveo/Cargo.toml
index c063c502..d2a0d0eb 100644
--- a/ferveo/Cargo.toml
+++ b/ferveo/Cargo.toml
@@ -51,6 +51,7 @@ wasm-bindgen-derive = { version = "0.2.1", optional = true }
criterion = "0.3" # supports pprof, # TODO: Figure out if/how we can update to 0.4
digest = { version = "0.10.0", features = ["alloc"] }
pprof = { version = "0.6", features = ["flamegraph", "criterion"] }
+test-case = "3.3.1"
# WASM bindings
console_error_panic_hook = "0.1.7"
diff --git a/ferveo/src/api.rs b/ferveo/src/api.rs
index be8f5b1f..0e610658 100644
--- a/ferveo/src/api.rs
+++ b/ferveo/src/api.rs
@@ -397,11 +397,14 @@ mod test_ferveo_api {
use ferveo_tdec::SecretBox;
use itertools::izip;
use rand::{prelude::StdRng, SeedableRng};
+ use test_case::test_case;
use crate::{api::*, dkg::test_common::*};
type TestInputs = (Vec, Vec, Vec);
+ const TAU: u32 = 1;
+
fn make_test_inputs(
rng: &mut StdRng,
tau: u32,
@@ -446,204 +449,188 @@ mod test_ferveo_api {
assert_eq!(dkg_pk, deserialized);
}
- #[test]
- fn test_server_api_tdec_precomputed() {
+ #[test_case(4; "number of shares (validators) is a power of 2")]
+ #[test_case(7; "number of shares (validators) is not a power of 2")]
+ fn test_server_api_tdec_precomputed(shares_num: u32) {
let rng = &mut StdRng::seed_from_u64(0);
- // Works for both power of 2 and non-power of 2
- for shares_num in [4, 7] {
- let tau = 1;
- // In precomputed variant, the security threshold is equal to the number of shares
- // TODO: Refactor DKG constructor to not require security threshold or this case.
- // Or figure out a different way to simplify the precomputed variant API.
- let security_threshold = shares_num;
-
- let (messages, validators, validator_keypairs) =
- make_test_inputs(rng, tau, security_threshold, shares_num);
-
- // Now that every validator holds a dkg instance and a transcript for every other validator,
- // every validator can aggregate the transcripts
- let me = validators[0].clone();
- let mut dkg =
- Dkg::new(tau, shares_num, security_threshold, &validators, &me)
- .unwrap();
-
- let pvss_aggregated = dkg.aggregate_transcripts(&messages).unwrap();
- assert!(pvss_aggregated.verify(shares_num, &messages).unwrap());
-
- // At this point, any given validator should be able to provide a DKG public key
- let dkg_public_key = dkg.public_key();
-
- // In the meantime, the client creates a ciphertext and decryption request
- let msg = "my-msg".as_bytes().to_vec();
- let aad: &[u8] = "my-aad".as_bytes();
- let ciphertext =
- encrypt(SecretBox::new(msg.clone()), aad, &dkg_public_key)
- .unwrap();
-
- // Having aggregated the transcripts, the validators can now create decryption shares
- let decryption_shares: Vec<_> =
- izip!(&validators, &validator_keypairs)
- .map(|(validator, validator_keypair)| {
- // Each validator holds their own instance of DKG and creates their own aggregate
- let mut dkg = Dkg::new(
- tau,
- shares_num,
- security_threshold,
- &validators,
- validator,
- )
- .unwrap();
- let aggregate =
- dkg.aggregate_transcripts(&messages).unwrap();
- assert!(pvss_aggregated
- .verify(shares_num, &messages)
- .unwrap());
-
- // And then each validator creates their own decryption share
- aggregate
- .create_decryption_share_precomputed(
- &dkg,
- &ciphertext.header().unwrap(),
- aad,
- validator_keypair,
- )
- .unwrap()
- })
- .collect();
-
- // Now, the decryption share can be used to decrypt the ciphertext
- // This part is part of the client API
-
- let shared_secret = share_combine_precomputed(&decryption_shares);
- let plaintext = decrypt_with_shared_secret(
- &ciphertext,
- aad,
- &SharedSecret(shared_secret),
- )
- .unwrap();
- assert_eq!(plaintext, msg);
-
- // Since we're using a precomputed variant, we need all the shares to be able to decrypt
- // So if we remove one share, we should not be able to decrypt
- let decryption_shares =
- decryption_shares[..shares_num as usize - 1].to_vec();
-
- let shared_secret = share_combine_precomputed(&decryption_shares);
- let result = decrypt_with_shared_secret(
- &ciphertext,
- aad,
- &SharedSecret(shared_secret),
- );
- assert!(result.is_err());
- }
+ // In precomputed variant, the security threshold is equal to the number of shares
+ // TODO: Refactor DKG constructor to not require security threshold or this case.
+ // Or figure out a different way to simplify the precomputed variant API.
+ let security_threshold = shares_num;
+
+ let (messages, validators, validator_keypairs) =
+ make_test_inputs(rng, TAU, security_threshold, shares_num);
+
+ // Now that every validator holds a dkg instance and a transcript for every other validator,
+ // every validator can aggregate the transcripts
+ let me = validators[0].clone();
+ let mut dkg =
+ Dkg::new(TAU, shares_num, security_threshold, &validators, &me)
+ .unwrap();
+
+ let pvss_aggregated = dkg.aggregate_transcripts(&messages).unwrap();
+ assert!(pvss_aggregated.verify(shares_num, &messages).unwrap());
+
+ // At this point, any given validator should be able to provide a DKG public key
+ let dkg_public_key = dkg.public_key();
+
+ // In the meantime, the client creates a ciphertext and decryption request
+ let msg = "my-msg".as_bytes().to_vec();
+ let aad: &[u8] = "my-aad".as_bytes();
+ let ciphertext =
+ encrypt(SecretBox::new(msg.clone()), aad, &dkg_public_key).unwrap();
+
+ // Having aggregated the transcripts, the validators can now create decryption shares
+ let decryption_shares: Vec<_> = izip!(&validators, &validator_keypairs)
+ .map(|(validator, validator_keypair)| {
+ // Each validator holds their own instance of DKG and creates their own aggregate
+ let mut dkg = Dkg::new(
+ TAU,
+ shares_num,
+ security_threshold,
+ &validators,
+ validator,
+ )
+ .unwrap();
+ let aggregate = dkg.aggregate_transcripts(&messages).unwrap();
+ assert!(pvss_aggregated.verify(shares_num, &messages).unwrap());
+
+ // And then each validator creates their own decryption share
+ aggregate
+ .create_decryption_share_precomputed(
+ &dkg,
+ &ciphertext.header().unwrap(),
+ aad,
+ validator_keypair,
+ )
+ .unwrap()
+ })
+ .collect();
+
+ // Now, the decryption share can be used to decrypt the ciphertext
+ // This part is part of the client API
+
+ let shared_secret = share_combine_precomputed(&decryption_shares);
+ let plaintext = decrypt_with_shared_secret(
+ &ciphertext,
+ aad,
+ &SharedSecret(shared_secret),
+ )
+ .unwrap();
+ assert_eq!(plaintext, msg);
+
+ // Since we're using a precomputed variant, we need all the shares to be able to decrypt
+ // So if we remove one share, we should not be able to decrypt
+ let decryption_shares =
+ decryption_shares[..shares_num as usize - 1].to_vec();
+
+ let shared_secret = share_combine_precomputed(&decryption_shares);
+ let result = decrypt_with_shared_secret(
+ &ciphertext,
+ aad,
+ &SharedSecret(shared_secret),
+ );
+ assert!(result.is_err());
}
- #[test]
- fn test_server_api_tdec_simple() {
+ #[test_case(4; "number of shares (validators) is a power of 2")]
+ #[test_case(7; "number of shares (validators) is not a power of 2")]
+ fn test_server_api_tdec_simple(shares_num: u32) {
let rng = &mut StdRng::seed_from_u64(0);
- // Works for both power of 2 and non-power of 2
- for shares_num in [4, 7] {
- let tau = 1;
- let security_threshold = shares_num / 2 + 1;
-
- let (messages, validators, validator_keypairs) =
- make_test_inputs(rng, tau, security_threshold, shares_num);
-
- // Now that every validator holds a dkg instance and a transcript for every other validator,
- // every validator can aggregate the transcripts
- let mut dkg = Dkg::new(
- tau,
- shares_num,
- security_threshold,
- &validators,
- &validators[0],
- )
- .unwrap();
-
- let pvss_aggregated = dkg.aggregate_transcripts(&messages).unwrap();
- assert!(pvss_aggregated.verify(shares_num, &messages).unwrap());
-
- // At this point, any given validator should be able to provide a DKG public key
- let public_key = dkg.public_key();
-
- // In the meantime, the client creates a ciphertext and decryption request
- let msg = "my-msg".as_bytes().to_vec();
- let aad: &[u8] = "my-aad".as_bytes();
- let ciphertext =
- encrypt(SecretBox::new(msg.clone()), aad, &public_key).unwrap();
-
- // Having aggregated the transcripts, the validators can now create decryption shares
- let decryption_shares: Vec<_> =
- izip!(&validators, &validator_keypairs)
- .map(|(validator, validator_keypair)| {
- // Each validator holds their own instance of DKG and creates their own aggregate
- let mut dkg = Dkg::new(
- tau,
- shares_num,
- security_threshold,
- &validators,
- validator,
- )
- .unwrap();
- let aggregate =
- dkg.aggregate_transcripts(&messages).unwrap();
- assert!(aggregate
- .verify(shares_num, &messages)
- .unwrap());
- aggregate
- .create_decryption_share_simple(
- &dkg,
- &ciphertext.header().unwrap(),
- aad,
- validator_keypair,
- )
- .unwrap()
- })
- .collect();
-
- // Now, the decryption share can be used to decrypt the ciphertext
- // This part is part of the client API
-
- // In simple variant, we only need `security_threshold` shares to be able to decrypt
- let decryption_shares =
- decryption_shares[..security_threshold as usize].to_vec();
-
- let shared_secret = combine_shares_simple(&decryption_shares);
- let plaintext =
- decrypt_with_shared_secret(&ciphertext, aad, &shared_secret)
- .unwrap();
- assert_eq!(plaintext, msg);
-
- // Let's say that we've only received `security_threshold - 1` shares
- // In this case, we should not be able to decrypt
- let decryption_shares =
- decryption_shares[..security_threshold as usize - 1].to_vec();
-
- let shared_secret = combine_shares_simple(&decryption_shares);
- let result =
- decrypt_with_shared_secret(&ciphertext, aad, &shared_secret);
- assert!(result.is_err());
- }
+ let security_threshold = shares_num / 2 + 1;
+
+ let (messages, validators, validator_keypairs) =
+ make_test_inputs(rng, TAU, security_threshold, shares_num);
+
+ // Now that every validator holds a dkg instance and a transcript for every other validator,
+ // every validator can aggregate the transcripts
+ let mut dkg = Dkg::new(
+ TAU,
+ shares_num,
+ security_threshold,
+ &validators,
+ &validators[0],
+ )
+ .unwrap();
+
+ let pvss_aggregated = dkg.aggregate_transcripts(&messages).unwrap();
+ assert!(pvss_aggregated.verify(shares_num, &messages).unwrap());
+
+ // At this point, any given validator should be able to provide a DKG public key
+ let public_key = dkg.public_key();
+
+ // In the meantime, the client creates a ciphertext and decryption request
+ let msg = "my-msg".as_bytes().to_vec();
+ let aad: &[u8] = "my-aad".as_bytes();
+ let ciphertext =
+ encrypt(SecretBox::new(msg.clone()), aad, &public_key).unwrap();
+
+ // Having aggregated the transcripts, the validators can now create decryption shares
+ let decryption_shares: Vec<_> = izip!(&validators, &validator_keypairs)
+ .map(|(validator, validator_keypair)| {
+ // Each validator holds their own instance of DKG and creates their own aggregate
+ let mut dkg = Dkg::new(
+ TAU,
+ shares_num,
+ security_threshold,
+ &validators,
+ validator,
+ )
+ .unwrap();
+ let aggregate = dkg.aggregate_transcripts(&messages).unwrap();
+ assert!(aggregate.verify(shares_num, &messages).unwrap());
+ aggregate
+ .create_decryption_share_simple(
+ &dkg,
+ &ciphertext.header().unwrap(),
+ aad,
+ validator_keypair,
+ )
+ .unwrap()
+ })
+ .collect();
+
+ // Now, the decryption share can be used to decrypt the ciphertext
+ // This part is part of the client API
+
+ // In simple variant, we only need `security_threshold` shares to be able to decrypt
+ let decryption_shares =
+ decryption_shares[..security_threshold as usize].to_vec();
+
+ let shared_secret = combine_shares_simple(&decryption_shares);
+ let plaintext =
+ decrypt_with_shared_secret(&ciphertext, aad, &shared_secret)
+ .unwrap();
+ assert_eq!(plaintext, msg);
+
+ // Let's say that we've only received `security_threshold - 1` shares
+ // In this case, we should not be able to decrypt
+ let decryption_shares =
+ decryption_shares[..security_threshold as usize - 1].to_vec();
+
+ let shared_secret = combine_shares_simple(&decryption_shares);
+ let result =
+ decrypt_with_shared_secret(&ciphertext, aad, &shared_secret);
+ assert!(result.is_err());
}
#[test]
fn server_side_local_verification() {
let rng = &mut StdRng::seed_from_u64(0);
- let tau = 1;
let security_threshold = 3;
let shares_num = 4;
let (messages, validators, _) =
- make_test_inputs(rng, tau, security_threshold, shares_num);
+ make_test_inputs(rng, TAU, security_threshold, shares_num);
// Now that every validator holds a dkg instance and a transcript for every other validator,
// every validator can aggregate the transcripts
let me = validators[0].clone();
let mut dkg =
- Dkg::new(tau, shares_num, security_threshold, &validators, &me)
+ Dkg::new(TAU, shares_num, security_threshold, &validators, &me)
.unwrap();
let local_aggregate = dkg.aggregate_transcripts(&messages).unwrap();
@@ -656,12 +643,11 @@ mod test_ferveo_api {
fn client_side_local_verification() {
let rng = &mut StdRng::seed_from_u64(0);
- let tau = 1;
let security_threshold = 3;
let shares_num = 4;
let (messages, _, _) =
- make_test_inputs(rng, tau, security_threshold, shares_num);
+ make_test_inputs(rng, TAU, security_threshold, shares_num);
// We only need `security_threshold` transcripts to aggregate
let messages = &messages[..security_threshold as usize];
@@ -690,7 +676,7 @@ mod test_ferveo_api {
// Unexpected transcripts in the aggregate or transcripts from a different ritual
// Using same DKG parameters, but different DKG instances and validators
let (bad_messages, _, _) =
- make_test_inputs(rng, tau, security_threshold, shares_num);
+ make_test_inputs(rng, TAU, security_threshold, shares_num);
let mixed_messages = [&messages[..2], &bad_messages[..1]].concat();
let bad_aggregate = AggregatedTranscript::new(&mixed_messages);
let result = bad_aggregate.verify(shares_num, messages);
diff --git a/ferveo/src/dkg.rs b/ferveo/src/dkg.rs
index 808afd40..dbab8e03 100644
--- a/ferveo/src/dkg.rs
+++ b/ferveo/src/dkg.rs
@@ -508,18 +508,18 @@ mod test_dealing {
fn test_pvss_dealing() {
let rng = &mut ark_std::test_rng();
+ // Create a test DKG instance
+ let (mut dkg, _) = setup_dkg(0);
+
// Gather everyone's transcripts
let mut messages = vec![];
- for i in 0..4 {
- let (mut dkg, _) = setup_dkg(i);
+ for i in 0..dkg.dkg_params.shares_num() {
+ let (mut dkg, _) = setup_dkg(i as usize);
let message = dkg.share(rng).unwrap();
let sender = dkg.me.validator.clone();
messages.push((sender, message));
}
- // Create a test DKG instance
- let (mut dkg, _) = setup_dkg(0);
-
let mut expected = 0u32;
for (sender, pvss) in messages.iter() {
// Check the verification passes
diff --git a/ferveo/src/lib.rs b/ferveo/src/lib.rs
index 4a29f099..605dc0d7 100644
--- a/ferveo/src/lib.rs
+++ b/ferveo/src/lib.rs
@@ -137,6 +137,7 @@ mod test_dkg_full {
SharedSecret,
};
use itertools::izip;
+ use test_case::test_case;
use super::*;
use crate::dkg::test_common::*;
@@ -195,107 +196,103 @@ mod test_dkg_full {
(pvss_aggregated, decryption_shares, shared_secret)
}
- #[test]
- fn test_dkg_simple_tdec() {
+ #[test_case(4; "number of shares (validators) is a power of 2")]
+ #[test_case(7; "number of shares (validators) is not a power of 2")]
+ fn test_dkg_simple_tdec(shares_num: u32) {
let rng = &mut test_rng();
- // Works for both power of 2 and non-power of 2
- for shares_num in [4, 7] {
- let threshold = shares_num / 2 + 1;
- let (dkg, validator_keypairs) =
- setup_dealt_dkg_with_n_validators(threshold, shares_num);
- let msg = "my-msg".as_bytes().to_vec();
- let aad: &[u8] = "my-aad".as_bytes();
- let public_key = dkg.public_key();
- let ciphertext = ferveo_tdec::encrypt::(
- SecretBox::new(msg.clone()),
- aad,
- &public_key,
- rng,
- )
- .unwrap();
+ let threshold = shares_num / 2 + 1;
+ let (dkg, validator_keypairs) =
+ setup_dealt_dkg_with_n_validators(threshold, shares_num);
+ let msg = "my-msg".as_bytes().to_vec();
+ let aad: &[u8] = "my-aad".as_bytes();
+ let public_key = dkg.public_key();
+ let ciphertext = ferveo_tdec::encrypt::(
+ SecretBox::new(msg.clone()),
+ aad,
+ &public_key,
+ rng,
+ )
+ .unwrap();
- let (_, _, shared_secret) = make_shared_secret_simple_tdec(
- &dkg,
- aad,
- &ciphertext.header().unwrap(),
- validator_keypairs.as_slice(),
- );
+ let (_, _, shared_secret) = make_shared_secret_simple_tdec(
+ &dkg,
+ aad,
+ &ciphertext.header().unwrap(),
+ validator_keypairs.as_slice(),
+ );
- let plaintext = ferveo_tdec::decrypt_with_shared_secret(
- &ciphertext,
- aad,
- &shared_secret,
- &dkg.pvss_params.g_inv(),
- )
- .unwrap();
- assert_eq!(plaintext, msg);
- }
+ let plaintext = ferveo_tdec::decrypt_with_shared_secret(
+ &ciphertext,
+ aad,
+ &shared_secret,
+ &dkg.pvss_params.g_inv(),
+ )
+ .unwrap();
+ assert_eq!(plaintext, msg);
}
- #[test]
- fn test_dkg_simple_tdec_precomputed() {
+ #[test_case(4; "number of shares (validators) is a power of 2")]
+ #[test_case(7; "number of shares (validators) is not a power of 2")]
+ fn test_dkg_simple_tdec_precomputed(shares_num: u32) {
let rng = &mut test_rng();
- // Works for both power of 2 and non-power of 2
- for shares_num in [4, 7] {
- // In precomputed variant, threshold must be equal to shares_num
- let threshold = shares_num;
- let (dkg, validator_keypairs) =
- setup_dealt_dkg_with_n_validators(threshold, shares_num);
- let msg = "my-msg".as_bytes().to_vec();
- let aad: &[u8] = "my-aad".as_bytes();
- let public_key = dkg.public_key();
- let ciphertext = ferveo_tdec::encrypt::(
- SecretBox::new(msg.clone()),
- aad,
- &public_key,
- rng,
- )
- .unwrap();
+ // In precomputed variant, threshold must be equal to shares_num
+ let threshold = shares_num;
+ let (dkg, validator_keypairs) =
+ setup_dealt_dkg_with_n_validators(threshold, shares_num);
+ let msg = "my-msg".as_bytes().to_vec();
+ let aad: &[u8] = "my-aad".as_bytes();
+ let public_key = dkg.public_key();
+ let ciphertext = ferveo_tdec::encrypt::(
+ SecretBox::new(msg.clone()),
+ aad,
+ &public_key,
+ rng,
+ )
+ .unwrap();
- let pvss_aggregated = aggregate(&dkg.vss);
- pvss_aggregated.verify_aggregation(&dkg).unwrap();
- let domain_points = dkg
- .domain
- .elements()
- .take(validator_keypairs.len())
- .collect::>();
-
- let decryption_shares: Vec> =
- validator_keypairs
- .iter()
- .map(|validator_keypair| {
- let validator = dkg
- .get_validator(&validator_keypair.public_key())
- .unwrap();
- pvss_aggregated
- .make_decryption_share_simple_precomputed(
- &ciphertext.header().unwrap(),
- aad,
- &validator_keypair.decryption_key,
- validator.share_index,
- &domain_points,
- &dkg.pvss_params.g_inv(),
- )
- .unwrap()
- })
- .collect();
- assert_eq!(domain_points.len(), decryption_shares.len());
+ let pvss_aggregated = aggregate(&dkg.vss);
+ pvss_aggregated.verify_aggregation(&dkg).unwrap();
+ let domain_points = dkg
+ .domain
+ .elements()
+ .take(validator_keypairs.len())
+ .collect::>();
+
+ let decryption_shares: Vec> =
+ validator_keypairs
+ .iter()
+ .map(|validator_keypair| {
+ let validator = dkg
+ .get_validator(&validator_keypair.public_key())
+ .unwrap();
+ pvss_aggregated
+ .make_decryption_share_simple_precomputed(
+ &ciphertext.header().unwrap(),
+ aad,
+ &validator_keypair.decryption_key,
+ validator.share_index,
+ &domain_points,
+ &dkg.pvss_params.g_inv(),
+ )
+ .unwrap()
+ })
+ .collect();
+ assert_eq!(domain_points.len(), decryption_shares.len());
- let shared_secret =
- ferveo_tdec::share_combine_precomputed::(&decryption_shares);
+ let shared_secret =
+ ferveo_tdec::share_combine_precomputed::(&decryption_shares);
- // Combination works, let's decrypt
- let plaintext = ferveo_tdec::decrypt_with_shared_secret(
- &ciphertext,
- aad,
- &shared_secret,
- &dkg.pvss_params.g_inv(),
- )
- .unwrap();
- assert_eq!(plaintext, msg);
- }
+ // Combination works, let's decrypt
+ let plaintext = ferveo_tdec::decrypt_with_shared_secret(
+ &ciphertext,
+ aad,
+ &shared_secret,
+ &dkg.pvss_params.g_inv(),
+ )
+ .unwrap();
+ assert_eq!(plaintext, msg);
}
#[test]
From 52f441cae1ac8ed6c88743082bd2434f3cad9012 Mon Sep 17 00:00:00 2001
From: Piotr Roslaniec
Date: Fri, 19 Jan 2024 12:54:29 +0100
Subject: [PATCH 3/7] refactor(test): deduplicate test utils
---
ferveo/src/api.rs | 65 ++++++++----------
ferveo/src/bindings_python.rs | 64 ++++++++----------
ferveo/src/bindings_wasm.rs | 1 -
ferveo/src/dkg.rs | 124 ++++------------------------------
ferveo/src/lib.rs | 99 +++++++++++++--------------
ferveo/src/pvss.rs | 6 +-
ferveo/src/refresh.rs | 40 +++++------
ferveo/src/test_common.rs | 110 ++++++++++++++++++++++++++++++
8 files changed, 242 insertions(+), 267 deletions(-)
create mode 100644 ferveo/src/test_common.rs
diff --git a/ferveo/src/api.rs b/ferveo/src/api.rs
index 0e610658..0a8bf2aa 100644
--- a/ferveo/src/api.rs
+++ b/ferveo/src/api.rs
@@ -9,7 +9,10 @@ pub use ferveo_tdec::api::{
prepare_combine_simple, share_combine_precomputed, share_combine_simple,
Fr, G1Affine, G1Prepared, G2Affine, SecretBox, E,
};
-use generic_array::{typenum::U48, GenericArray};
+use generic_array::{
+ typenum::{Unsigned, U48},
+ GenericArray,
+};
use rand::RngCore;
use serde::{Deserialize, Serialize};
use serde_with::serde_as;
@@ -19,8 +22,6 @@ pub type Keypair = ferveo_common::Keypair;
pub type Validator = crate::Validator;
pub type Transcript = PubliclyVerifiableSS;
-// pub type ShareIndex = u32;
-// pub type ValidatorMessage = (ShareIndex, Validator, Transcript);
pub type ValidatorMessage = (Validator, Transcript);
#[cfg(feature = "bindings-python")]
@@ -167,7 +168,7 @@ impl DkgPublicKey {
}
pub fn serialized_size() -> usize {
- 48
+ U48::to_usize()
}
/// Generate a random DKG public key.
@@ -399,12 +400,10 @@ mod test_ferveo_api {
use rand::{prelude::StdRng, SeedableRng};
use test_case::test_case;
- use crate::{api::*, dkg::test_common::*};
+ use crate::{api::*, test_common::*};
type TestInputs = (Vec, Vec, Vec);
- const TAU: u32 = 1;
-
fn make_test_inputs(
rng: &mut StdRng,
tau: u32,
@@ -446,6 +445,7 @@ mod test_ferveo_api {
let dkg_pk = DkgPublicKey::random();
let serialized = dkg_pk.to_bytes().unwrap();
let deserialized = DkgPublicKey::from_bytes(&serialized).unwrap();
+ assert_eq!(serialized.len(), 48_usize);
assert_eq!(dkg_pk, deserialized);
}
@@ -476,10 +476,9 @@ mod test_ferveo_api {
let dkg_public_key = dkg.public_key();
// In the meantime, the client creates a ciphertext and decryption request
- let msg = "my-msg".as_bytes().to_vec();
- let aad: &[u8] = "my-aad".as_bytes();
let ciphertext =
- encrypt(SecretBox::new(msg.clone()), aad, &dkg_public_key).unwrap();
+ encrypt(SecretBox::new(MSG.to_vec()), AAD, &dkg_public_key)
+ .unwrap();
// Having aggregated the transcripts, the validators can now create decryption shares
let decryption_shares: Vec<_> = izip!(&validators, &validator_keypairs)
@@ -501,7 +500,7 @@ mod test_ferveo_api {
.create_decryption_share_precomputed(
&dkg,
&ciphertext.header().unwrap(),
- aad,
+ AAD,
validator_keypair,
)
.unwrap()
@@ -514,11 +513,11 @@ mod test_ferveo_api {
let shared_secret = share_combine_precomputed(&decryption_shares);
let plaintext = decrypt_with_shared_secret(
&ciphertext,
- aad,
+ AAD,
&SharedSecret(shared_secret),
)
.unwrap();
- assert_eq!(plaintext, msg);
+ assert_eq!(plaintext, MSG);
// Since we're using a precomputed variant, we need all the shares to be able to decrypt
// So if we remove one share, we should not be able to decrypt
@@ -528,7 +527,7 @@ mod test_ferveo_api {
let shared_secret = share_combine_precomputed(&decryption_shares);
let result = decrypt_with_shared_secret(
&ciphertext,
- aad,
+ AAD,
&SharedSecret(shared_secret),
);
assert!(result.is_err());
@@ -562,10 +561,8 @@ mod test_ferveo_api {
let public_key = dkg.public_key();
// In the meantime, the client creates a ciphertext and decryption request
- let msg = "my-msg".as_bytes().to_vec();
- let aad: &[u8] = "my-aad".as_bytes();
let ciphertext =
- encrypt(SecretBox::new(msg.clone()), aad, &public_key).unwrap();
+ encrypt(SecretBox::new(MSG.to_vec()), AAD, &public_key).unwrap();
// Having aggregated the transcripts, the validators can now create decryption shares
let decryption_shares: Vec<_> = izip!(&validators, &validator_keypairs)
@@ -585,7 +582,7 @@ mod test_ferveo_api {
.create_decryption_share_simple(
&dkg,
&ciphertext.header().unwrap(),
- aad,
+ AAD,
validator_keypair,
)
.unwrap()
@@ -601,9 +598,9 @@ mod test_ferveo_api {
let shared_secret = combine_shares_simple(&decryption_shares);
let plaintext =
- decrypt_with_shared_secret(&ciphertext, aad, &shared_secret)
+ decrypt_with_shared_secret(&ciphertext, AAD, &shared_secret)
.unwrap();
- assert_eq!(plaintext, msg);
+ assert_eq!(plaintext, MSG);
// Let's say that we've only received `security_threshold - 1` shares
// In this case, we should not be able to decrypt
@@ -612,7 +609,7 @@ mod test_ferveo_api {
let shared_secret = combine_shares_simple(&decryption_shares);
let result =
- decrypt_with_shared_secret(&ciphertext, aad, &shared_secret);
+ decrypt_with_shared_secret(&ciphertext, AAD, &shared_secret);
assert!(result.is_err());
}
@@ -620,17 +617,14 @@ mod test_ferveo_api {
fn server_side_local_verification() {
let rng = &mut StdRng::seed_from_u64(0);
- let security_threshold = 3;
- let shares_num = 4;
-
let (messages, validators, _) =
- make_test_inputs(rng, TAU, security_threshold, shares_num);
+ make_test_inputs(rng, TAU, SECURITY_THRESHOLD, SHARES_NUM);
// Now that every validator holds a dkg instance and a transcript for every other validator,
// every validator can aggregate the transcripts
let me = validators[0].clone();
let mut dkg =
- Dkg::new(TAU, shares_num, security_threshold, &validators, &me)
+ Dkg::new(TAU, SHARES_NUM, SECURITY_THRESHOLD, &validators, &me)
.unwrap();
let local_aggregate = dkg.aggregate_transcripts(&messages).unwrap();
@@ -643,14 +637,11 @@ mod test_ferveo_api {
fn client_side_local_verification() {
let rng = &mut StdRng::seed_from_u64(0);
- let security_threshold = 3;
- let shares_num = 4;
-
let (messages, _, _) =
- make_test_inputs(rng, TAU, security_threshold, shares_num);
+ make_test_inputs(rng, TAU, SECURITY_THRESHOLD, SHARES_NUM);
// We only need `security_threshold` transcripts to aggregate
- let messages = &messages[..security_threshold as usize];
+ let messages = &messages[..SECURITY_THRESHOLD as usize];
// Create an aggregated transcript on the client side
let aggregated_transcript = AggregatedTranscript::new(messages);
@@ -659,27 +650,27 @@ mod test_ferveo_api {
// the aggregate from a side-channel or decide to persist it and verify it later
// Now, the client can verify the aggregated transcript
- let result = aggregated_transcript.verify(shares_num, messages);
+ let result = aggregated_transcript.verify(SHARES_NUM, messages);
assert!(result.is_ok());
assert!(result.unwrap());
// Test negative cases
// Not enough transcripts
- let not_enough_messages = &messages[..2];
- assert!(not_enough_messages.len() < security_threshold as usize);
+ let not_enough_messages = &messages[..SECURITY_THRESHOLD as usize - 1];
+ assert!(not_enough_messages.len() < SECURITY_THRESHOLD as usize);
let insufficient_aggregate =
AggregatedTranscript::new(not_enough_messages);
- let result = insufficient_aggregate.verify(shares_num, messages);
+ let result = insufficient_aggregate.verify(SHARES_NUM, messages);
assert!(result.is_err());
// Unexpected transcripts in the aggregate or transcripts from a different ritual
// Using same DKG parameters, but different DKG instances and validators
let (bad_messages, _, _) =
- make_test_inputs(rng, TAU, security_threshold, shares_num);
+ make_test_inputs(rng, TAU, SECURITY_THRESHOLD, SHARES_NUM);
let mixed_messages = [&messages[..2], &bad_messages[..1]].concat();
let bad_aggregate = AggregatedTranscript::new(&mixed_messages);
- let result = bad_aggregate.verify(shares_num, messages);
+ let result = bad_aggregate.verify(SHARES_NUM, messages);
assert!(result.is_err());
}
}
diff --git a/ferveo/src/bindings_python.rs b/ferveo/src/bindings_python.rs
index 00c455a3..9b472d34 100644
--- a/ferveo/src/bindings_python.rs
+++ b/ferveo/src/bindings_python.rs
@@ -739,7 +739,7 @@ pub fn make_ferveo_py_module(py: Python<'_>, m: &PyModule) -> PyResult<()> {
mod test_ferveo_python {
use itertools::izip;
- use crate::bindings_python::*;
+ use crate::{bindings_python::*, test_common::*};
type TestInputs = (Vec, Vec, Vec);
@@ -785,21 +785,19 @@ mod test_ferveo_python {
#[test]
fn test_server_api_tdec_precomputed() {
- let tau = 1;
- let shares_num = 4;
// In precomputed variant, the security threshold is equal to the number of shares
- let security_threshold = shares_num;
+ let security_threshold = SHARES_NUM;
let (messages, validators, validator_keypairs) =
- make_test_inputs(tau, security_threshold, shares_num);
+ make_test_inputs(TAU, security_threshold, SHARES_NUM);
// Now that every validator holds a dkg instance and a transcript for every other validator,
// every validator can aggregate the transcripts
let me = validators[0].clone();
let mut dkg = Dkg::new(
- tau,
- shares_num,
+ TAU,
+ SHARES_NUM,
security_threshold,
validators.clone(),
&me,
@@ -811,24 +809,22 @@ mod test_ferveo_python {
let pvss_aggregated =
dkg.aggregate_transcripts(messages.clone()).unwrap();
assert!(pvss_aggregated
- .verify(shares_num, messages.clone())
+ .verify(SHARES_NUM, messages.clone())
.unwrap());
// At this point, any given validator should be able to provide a DKG public key
let dkg_public_key = dkg.public_key();
// In the meantime, the client creates a ciphertext and decryption request
- let msg: &[u8] = "my-msg".as_bytes();
- let aad: &[u8] = "my-aad".as_bytes();
- let ciphertext = encrypt(msg, aad, &dkg_public_key).unwrap();
+ let ciphertext = encrypt(MSG, AAD, &dkg_public_key).unwrap();
// Having aggregated the transcripts, the validators can now create decryption shares
let decryption_shares: Vec<_> = izip!(&validators, &validator_keypairs)
.map(|(validator, validator_keypair)| {
// Each validator holds their own instance of DKG and creates their own aggregate
let mut dkg = Dkg::new(
- tau,
- shares_num,
+ TAU,
+ SHARES_NUM,
security_threshold,
validators.clone(),
validator,
@@ -837,13 +833,13 @@ mod test_ferveo_python {
let aggregate =
dkg.aggregate_transcripts(messages.clone()).unwrap();
assert!(pvss_aggregated
- .verify(shares_num, messages.clone())
+ .verify(SHARES_NUM, messages.clone())
.is_ok());
aggregate
.create_decryption_share_precomputed(
&dkg,
&ciphertext.header().unwrap(),
- aad,
+ AAD,
validator_keypair,
)
.unwrap()
@@ -857,56 +853,50 @@ mod test_ferveo_python {
combine_decryption_shares_precomputed(decryption_shares);
let plaintext =
- decrypt_with_shared_secret(&ciphertext, aad, &shared_secret)
+ decrypt_with_shared_secret(&ciphertext, AAD, &shared_secret)
.unwrap();
- assert_eq!(plaintext, msg);
+ assert_eq!(plaintext, MSG);
}
#[test]
fn test_server_api_tdec_simple() {
- let tau = 1;
- let shares_num = 4;
- let security_threshold = 3;
-
let (messages, validators, validator_keypairs) =
- make_test_inputs(tau, security_threshold, shares_num);
+ make_test_inputs(TAU, SECURITY_THRESHOLD, SHARES_NUM);
// Now that every validator holds a dkg instance and a transcript for every other validator,
// every validator can aggregate the transcripts
let me = validators[0].clone();
let mut dkg = Dkg::new(
- tau,
- shares_num,
- security_threshold,
+ TAU,
+ SHARES_NUM,
+ SECURITY_THRESHOLD,
validators.clone(),
&me,
)
.unwrap();
// Lets say that we've only receives `security_threshold` transcripts
- let messages = messages[..security_threshold as usize].to_vec();
+ let messages = messages[..SECURITY_THRESHOLD as usize].to_vec();
let pvss_aggregated =
dkg.aggregate_transcripts(messages.clone()).unwrap();
assert!(pvss_aggregated
- .verify(shares_num, messages.clone())
+ .verify(SHARES_NUM, messages.clone())
.unwrap());
// At this point, any given validator should be able to provide a DKG public key
let dkg_public_key = dkg.public_key();
// In the meantime, the client creates a ciphertext and decryption request
- let msg: &[u8] = "my-msg".as_bytes();
- let aad: &[u8] = "my-aad".as_bytes();
- let ciphertext = encrypt(msg, aad, &dkg_public_key).unwrap();
+ let ciphertext = encrypt(MSG, AAD, &dkg_public_key).unwrap();
// Having aggregated the transcripts, the validators can now create decryption shares
let decryption_shares: Vec<_> = izip!(&validators, &validator_keypairs)
.map(|(validator, validator_keypair)| {
// Each validator holds their own instance of DKG and creates their own aggregate
let mut dkg = Dkg::new(
- tau,
- shares_num,
- security_threshold,
+ TAU,
+ SHARES_NUM,
+ SECURITY_THRESHOLD,
validators.clone(),
validator,
)
@@ -914,13 +904,13 @@ mod test_ferveo_python {
let aggregate =
dkg.aggregate_transcripts(messages.clone()).unwrap();
assert!(aggregate
- .verify(shares_num, messages.clone())
+ .verify(SHARES_NUM, messages.clone())
.unwrap());
aggregate
.create_decryption_share_simple(
&dkg,
&ciphertext.header().unwrap(),
- aad,
+ AAD,
validator_keypair,
)
.unwrap()
@@ -933,8 +923,8 @@ mod test_ferveo_python {
let shared_secret = combine_decryption_shares_simple(decryption_shares);
let plaintext =
- decrypt_with_shared_secret(&ciphertext, aad, &shared_secret)
+ decrypt_with_shared_secret(&ciphertext, AAD, &shared_secret)
.unwrap();
- assert_eq!(plaintext, msg);
+ assert_eq!(plaintext, MSG);
}
}
diff --git a/ferveo/src/bindings_wasm.rs b/ferveo/src/bindings_wasm.rs
index 5b03f188..a1310277 100644
--- a/ferveo/src/bindings_wasm.rs
+++ b/ferveo/src/bindings_wasm.rs
@@ -595,7 +595,6 @@ impl Keypair {
}
}
-/// Factory functions for testing
pub mod test_common {
use crate::bindings_wasm::*;
diff --git a/ferveo/src/dkg.rs b/ferveo/src/dkg.rs
index dbab8e03..e13c4894 100644
--- a/ferveo/src/dkg.rs
+++ b/ferveo/src/dkg.rs
@@ -361,110 +361,14 @@ pub enum Message {
Aggregate(Aggregation),
}
-/// Factory functions for testing
-#[cfg(test)]
-pub(crate) mod test_common {
- use std::str::FromStr;
-
- pub use ark_bls12_381::Bls12_381 as E;
- use ferveo_common::Keypair;
-
- pub use super::*;
-
- pub type G1 = ::G1Affine;
-
- pub fn gen_keypairs(n: u32) -> Vec> {
- let rng = &mut ark_std::test_rng();
- (0..n).map(|_| Keypair::::new(rng)).collect()
- }
-
- pub fn gen_address(i: usize) -> EthereumAddress {
- EthereumAddress::from_str(&format!("0x{i:040}")).unwrap()
- }
-
- pub fn gen_validators(keypairs: &[Keypair]) -> Vec> {
- keypairs
- .iter()
- .enumerate()
- .map(|(i, keypair)| Validator {
- address: gen_address(i),
- public_key: keypair.public_key(),
- })
- .collect()
- }
-
- pub type TestSetup = (PubliclyVerifiableDkg, Vec>);
-
- pub fn setup_dkg_for_n_validators(
- security_threshold: u32,
- shares_num: u32,
- my_index: usize,
- ) -> TestSetup {
- let keypairs = gen_keypairs(shares_num);
- let mut validators = gen_validators(keypairs.as_slice());
- validators.sort();
- let me = validators[my_index].clone();
- let dkg = PubliclyVerifiableDkg::new(
- &validators,
- &DkgParams {
- tau: 0,
- security_threshold,
- shares_num,
- },
- &me,
- )
- .expect("Setup failed");
- (dkg, keypairs)
- }
-
- /// Create a test dkg
- ///
- /// The [`test_dkg_init`] module checks correctness of this setup
- pub fn setup_dkg(validator: usize) -> TestSetup {
- setup_dkg_for_n_validators(2, 4, validator)
- }
-
- /// Set up a dkg with enough pvss transcripts to meet the threshold
- ///
- /// The correctness of this function is tested in the module [`test_dealing`]
- pub fn setup_dealt_dkg() -> TestSetup {
- setup_dealt_dkg_with_n_validators(2, 4)
- }
-
- pub fn setup_dealt_dkg_with_n_validators(
- security_threshold: u32,
- shares_num: u32,
- ) -> TestSetup {
- let rng = &mut ark_std::test_rng();
-
- // Gather everyone's transcripts
- let messages: Vec<_> = (0..shares_num)
- .map(|my_index| {
- let (mut dkg, _) = setup_dkg_for_n_validators(
- security_threshold,
- shares_num,
- my_index as usize,
- );
- let me = dkg.me.validator.clone();
- let message = dkg.share(rng).unwrap();
- (me, message)
- })
- .collect();
-
- // Create a test DKG instance
- let (mut dkg, keypairs) =
- setup_dkg_for_n_validators(security_threshold, shares_num, 0);
- messages.iter().for_each(|(sender, message)| {
- dkg.apply_message(sender, message).expect("Setup failed");
- });
- (dkg, keypairs)
- }
-}
-
/// Test initializing DKG
#[cfg(test)]
mod test_dkg_init {
- use super::test_common::*;
+ use crate::{
+ dkg::{PubliclyVerifiableDkg, Validator},
+ test_common::*,
+ DkgParams,
+ };
/// Test that dkg fails to start if the `me` input
/// is not in the validator set
@@ -480,11 +384,7 @@ mod test_dkg_init {
};
let err = PubliclyVerifiableDkg::::new(
&gen_validators(&known_keypairs),
- &DkgParams {
- tau: 0,
- security_threshold: shares_num / 2,
- shares_num,
- },
+ &DkgParams::new(TAU, SECURITY_THRESHOLD, SHARES_NUM).unwrap(),
&unknown_validator,
)
.unwrap_err();
@@ -498,8 +398,7 @@ mod test_dkg_init {
mod test_dealing {
use ark_ec::AffineRepr;
- use super::test_common::*;
- use crate::DkgState::Dealt;
+ use crate::{test_common::*, DkgState, DkgState::Dealt, Validator};
/// Test that dealing correct PVSS transcripts
/// pass verification an application and that
@@ -716,7 +615,7 @@ mod test_dealing {
mod test_aggregation {
use ark_ec::AffineRepr;
- use super::test_common::*;
+ use crate::{dkg::*, test_common::*, DkgState, Message};
/// Test that if the security threshold is
/// met, we can create a final key
@@ -802,17 +701,18 @@ mod test_aggregation {
/// Test DKG parameters
#[cfg(test)]
mod test_dkg_params {
- const TAU: u32 = 0;
+ use crate::test_common::*;
#[test]
fn test_shares_num_less_than_security_threshold() {
- let dkg_params = super::DkgParams::new(TAU, 4, 3);
+ let dkg_params = super::DkgParams::new(TAU, SHARES_NUM + 1, SHARES_NUM);
assert!(dkg_params.is_err());
}
#[test]
fn test_valid_dkg_params() {
- let dkg_params = super::DkgParams::new(TAU, 2, 3);
+ let dkg_params =
+ super::DkgParams::new(TAU, SECURITY_THRESHOLD, SHARES_NUM);
assert!(dkg_params.is_ok());
}
}
diff --git a/ferveo/src/lib.rs b/ferveo/src/lib.rs
index 605dc0d7..394afb1a 100644
--- a/ferveo/src/lib.rs
+++ b/ferveo/src/lib.rs
@@ -21,6 +21,9 @@ pub mod validator;
mod utils;
+#[cfg(test)]
+mod test_common;
+
pub use dkg::*;
pub use primitives::*;
pub use pvss::*;
@@ -96,15 +99,19 @@ pub enum Error {
#[error(transparent)]
ArkSerializeError(#[from] ark_serialize::SerializationError),
+ /// Invalid byte length
#[error("Invalid byte length. Expected {0}, got {1}")]
InvalidByteLength(usize, usize),
+ /// Invalid variant
#[error("Invalid variant: {0}")]
InvalidVariant(String),
+ /// DKG parameters validaiton failed
#[error("Invalid DKG parameters: number of shares {0}, threshold {1}")]
InvalidDkgParameters(u32, u32),
+ /// Failed to access a share for a given share index
#[error("Invalid share index: {0}")]
InvalidShareIndex(u32),
}
@@ -127,7 +134,7 @@ mod test_dkg_full {
use std::collections::HashMap;
use ark_bls12_381::{Bls12_381 as E, Fr, G1Affine};
- use ark_ec::{pairing::Pairing, AffineRepr, CurveGroup};
+ use ark_ec::{AffineRepr, CurveGroup};
use ark_ff::{UniformRand, Zero};
use ark_poly::EvaluationDomain;
use ark_std::test_rng;
@@ -137,12 +144,11 @@ mod test_dkg_full {
SharedSecret,
};
use itertools::izip;
+ use rand::seq::SliceRandom;
use test_case::test_case;
use super::*;
- use crate::dkg::test_common::*;
-
- type TargetField = ::TargetField;
+ use crate::test_common::*;
fn make_shared_secret_simple_tdec(
dkg: &PubliclyVerifiableDkg,
@@ -203,13 +209,12 @@ mod test_dkg_full {
let threshold = shares_num / 2 + 1;
let (dkg, validator_keypairs) =
- setup_dealt_dkg_with_n_validators(threshold, shares_num);
- let msg = "my-msg".as_bytes().to_vec();
- let aad: &[u8] = "my-aad".as_bytes();
+ setup_dealt_dkg_with(threshold, shares_num);
+
let public_key = dkg.public_key();
let ciphertext = ferveo_tdec::encrypt::(
- SecretBox::new(msg.clone()),
- aad,
+ SecretBox::new(MSG.to_vec()),
+ AAD,
&public_key,
rng,
)
@@ -217,19 +222,19 @@ mod test_dkg_full {
let (_, _, shared_secret) = make_shared_secret_simple_tdec(
&dkg,
- aad,
+ AAD,
&ciphertext.header().unwrap(),
validator_keypairs.as_slice(),
);
let plaintext = ferveo_tdec::decrypt_with_shared_secret(
&ciphertext,
- aad,
+ AAD,
&shared_secret,
&dkg.pvss_params.g_inv(),
)
.unwrap();
- assert_eq!(plaintext, msg);
+ assert_eq!(plaintext, MSG);
}
#[test_case(4; "number of shares (validators) is a power of 2")]
@@ -240,13 +245,11 @@ mod test_dkg_full {
// In precomputed variant, threshold must be equal to shares_num
let threshold = shares_num;
let (dkg, validator_keypairs) =
- setup_dealt_dkg_with_n_validators(threshold, shares_num);
- let msg = "my-msg".as_bytes().to_vec();
- let aad: &[u8] = "my-aad".as_bytes();
+ setup_dealt_dkg_with(threshold, shares_num);
let public_key = dkg.public_key();
let ciphertext = ferveo_tdec::encrypt::(
- SecretBox::new(msg.clone()),
- aad,
+ SecretBox::new(MSG.to_vec()),
+ AAD,
&public_key,
rng,
)
@@ -260,7 +263,7 @@ mod test_dkg_full {
.take(validator_keypairs.len())
.collect::>();
- let decryption_shares: Vec> =
+ let mut decryption_shares: Vec> =
validator_keypairs
.iter()
.map(|validator_keypair| {
@@ -270,7 +273,7 @@ mod test_dkg_full {
pvss_aggregated
.make_decryption_share_simple_precomputed(
&ciphertext.header().unwrap(),
- aad,
+ AAD,
&validator_keypair.decryption_key,
validator.share_index,
&domain_points,
@@ -279,6 +282,7 @@ mod test_dkg_full {
.unwrap()
})
.collect();
+ decryption_shares.shuffle(rng);
assert_eq!(domain_points.len(), decryption_shares.len());
let shared_secret =
@@ -287,25 +291,24 @@ mod test_dkg_full {
// Combination works, let's decrypt
let plaintext = ferveo_tdec::decrypt_with_shared_secret(
&ciphertext,
- aad,
+ AAD,
&shared_secret,
&dkg.pvss_params.g_inv(),
)
.unwrap();
- assert_eq!(plaintext, msg);
+ assert_eq!(plaintext, MSG);
}
#[test]
fn test_dkg_simple_tdec_share_verification() {
let rng = &mut test_rng();
- let (dkg, validator_keypairs) = setup_dealt_dkg_with_n_validators(3, 4);
- let msg = "my-msg".as_bytes().to_vec();
- let aad: &[u8] = "my-aad".as_bytes();
+ let (dkg, validator_keypairs) =
+ setup_dealt_dkg_with(SECURITY_THRESHOLD, SHARES_NUM);
let public_key = dkg.public_key();
let ciphertext = ferveo_tdec::encrypt::(
- SecretBox::new(msg),
- aad,
+ SecretBox::new(MSG.to_vec()),
+ AAD,
&public_key,
rng,
)
@@ -314,7 +317,7 @@ mod test_dkg_full {
let (pvss_aggregated, decryption_shares, _) =
make_shared_secret_simple_tdec(
&dkg,
- aad,
+ AAD,
&ciphertext.header().unwrap(),
validator_keypairs.as_slice(),
);
@@ -363,16 +366,12 @@ mod test_dkg_full {
fn test_dkg_simple_tdec_share_recovery() {
let rng = &mut test_rng();
- let security_threshold = 3;
- let shares_num = 4;
let (dkg, validator_keypairs) =
- setup_dealt_dkg_with_n_validators(security_threshold, shares_num);
- let msg = "my-msg".as_bytes().to_vec();
- let aad: &[u8] = "my-aad".as_bytes();
+ setup_dealt_dkg_with(SECURITY_THRESHOLD, SHARES_NUM);
let public_key = &dkg.public_key();
let ciphertext = ferveo_tdec::encrypt::(
- SecretBox::new(msg),
- aad,
+ SecretBox::new(MSG.to_vec()),
+ AAD,
public_key,
rng,
)
@@ -381,7 +380,7 @@ mod test_dkg_full {
// Create an initial shared secret
let (_, _, old_shared_secret) = make_shared_secret_simple_tdec(
&dkg,
- aad,
+ AAD,
&ciphertext.header().unwrap(),
validator_keypairs.as_slice(),
);
@@ -476,7 +475,7 @@ mod test_dkg_full {
pvss_aggregated
.make_decryption_share_simple(
&ciphertext.header().unwrap(),
- aad,
+ AAD,
&validator_keypair.decryption_key,
share_index,
&dkg.pvss_params.g_inv(),
@@ -492,21 +491,21 @@ mod test_dkg_full {
&new_validator_decryption_key,
&new_private_key_share,
&ciphertext.header().unwrap(),
- aad,
+ AAD,
&dkg.pvss_params.g_inv(),
)
.unwrap(),
);
domain_points.push(x_r);
- assert_eq!(domain_points.len(), shares_num as usize);
- assert_eq!(decryption_shares.len(), shares_num as usize);
+ assert_eq!(domain_points.len(), SHARES_NUM as usize);
+ assert_eq!(decryption_shares.len(), SHARES_NUM as usize);
// Maybe parametrize this test with [1..] and [..threshold]
let domain_points = &domain_points[1..];
let decryption_shares = &decryption_shares[1..];
- assert_eq!(domain_points.len(), security_threshold as usize);
- assert_eq!(decryption_shares.len(), security_threshold as usize);
+ assert_eq!(domain_points.len(), SECURITY_THRESHOLD as usize);
+ assert_eq!(decryption_shares.len(), SECURITY_THRESHOLD as usize);
let lagrange = ferveo_tdec::prepare_combine_simple::(domain_points);
let new_shared_secret = ferveo_tdec::share_combine_simple::(
@@ -524,16 +523,12 @@ mod test_dkg_full {
fn test_dkg_simple_tdec_share_refreshing() {
let rng = &mut test_rng();
- let security_threshold = 3;
- let shares_num = 4;
let (dkg, validator_keypairs) =
- setup_dealt_dkg_with_n_validators(security_threshold, shares_num);
- let msg = "my-msg".as_bytes().to_vec();
- let aad: &[u8] = "my-aad".as_bytes();
+ setup_dealt_dkg_with(SECURITY_THRESHOLD, SHARES_NUM);
let public_key = &dkg.public_key();
let ciphertext = ferveo_tdec::encrypt::(
- SecretBox::new(msg),
- aad,
+ SecretBox::new(MSG.to_vec()),
+ AAD,
public_key,
rng,
)
@@ -542,7 +537,7 @@ mod test_dkg_full {
// Create an initial shared secret
let (_, _, old_shared_secret) = make_shared_secret_simple_tdec(
&dkg,
- aad,
+ AAD,
&ciphertext.header().unwrap(),
validator_keypairs.as_slice(),
);
@@ -607,7 +602,7 @@ mod test_dkg_full {
&validator_keypair.decryption_key,
updated_shares.get(share_index).unwrap(),
&ciphertext.header().unwrap(),
- aad,
+ AAD,
&dkg.pvss_params.g_inv(),
)
.unwrap()
@@ -615,10 +610,10 @@ mod test_dkg_full {
.collect();
let lagrange = ferveo_tdec::prepare_combine_simple::(
- &domain_points[..security_threshold as usize],
+ &domain_points[..SECURITY_THRESHOLD as usize],
);
let new_shared_secret = ferveo_tdec::share_combine_simple::(
- &decryption_shares[..security_threshold as usize],
+ &decryption_shares[..SECURITY_THRESHOLD as usize],
&lagrange,
);
diff --git a/ferveo/src/pvss.rs b/ferveo/src/pvss.rs
index 495018bf..c8498bb7 100644
--- a/ferveo/src/pvss.rs
+++ b/ferveo/src/pvss.rs
@@ -462,11 +462,7 @@ mod test_pvss {
use rand::seq::SliceRandom;
use super::*;
- use crate::{dkg::test_common::*, utils::is_sorted};
-
- type ScalarField = ::ScalarField;
- type G1 = ::G1Affine;
- type G2 = ::G2Affine;
+ use crate::{test_common::*, utils::is_sorted, DkgParams};
/// Test the happy flow that a pvss with the correct form is created
/// and that appropriate validations pass
diff --git a/ferveo/src/refresh.rs b/ferveo/src/refresh.rs
index c9e692d4..524e6569 100644
--- a/ferveo/src/refresh.rs
+++ b/ferveo/src/refresh.rs
@@ -122,22 +122,18 @@ mod tests_refresh {
use std::collections::HashMap;
use ark_bls12_381::Fr;
- use ark_ec::pairing::Pairing;
use ark_std::{test_rng, UniformRand, Zero};
- use rand_core::RngCore;
-
- type E = ark_bls12_381::Bls12_381;
- type ScalarField = ::ScalarField;
-
use ferveo_tdec::{
test_common::setup_simple, PrivateDecryptionContextSimple,
PrivateKeyShare,
};
+ use rand_core::RngCore;
+ use test_case::test_matrix;
use crate::{
apply_updates_to_private_share, prepare_share_updates_for_recovery,
prepare_share_updates_for_refresh,
- recover_share_from_updated_private_shares,
+ recover_share_from_updated_private_shares, test_common::*,
};
fn make_new_share_fragments_for_recovery(
@@ -191,14 +187,13 @@ mod tests_refresh {
/// Ñ parties (where t <= Ñ <= N) jointly execute a "share recovery" algorithm, and the output is 1 new share.
/// The new share is intended to restore a previously existing share, e.g., due to loss or corruption.
- #[test]
- fn tdec_simple_variant_share_recovery_at_selected_point() {
+ #[test_matrix([4, 7, 11, 16])]
+ fn tdec_simple_variant_share_recovery_at_selected_point(shares_num: usize) {
let rng = &mut test_rng();
- let shares_num = 16;
- let threshold = shares_num * 2 / 3;
+ let security_threshold = shares_num * 2 / 3;
let (_, _, mut contexts) =
- setup_simple::(threshold, shares_num, rng);
+ setup_simple::(security_threshold, shares_num, rng);
// Prepare participants
@@ -220,7 +215,7 @@ mod tests_refresh {
// Each participant prepares an update for each other participant, and uses it to create a new share fragment
let new_share_fragments = make_new_share_fragments_for_recovery(
rng,
- threshold,
+ security_threshold,
&x_r,
&remaining_participants,
);
@@ -233,8 +228,8 @@ mod tests_refresh {
.collect::>();
let new_private_key_share = recover_share_from_updated_private_shares(
&x_r,
- &domain_points[..threshold],
- &new_share_fragments[..threshold],
+ &domain_points[..security_threshold],
+ &new_share_fragments[..security_threshold],
);
assert_eq!(new_private_key_share, original_private_key_share);
@@ -244,8 +239,8 @@ mod tests_refresh {
let incorrect_private_key_share =
recover_share_from_updated_private_shares(
&x_r,
- &domain_points[..(threshold - 1)],
- &new_share_fragments[..(threshold - 1)],
+ &domain_points[..(security_threshold - 1)],
+ &new_share_fragments[..(security_threshold - 1)],
);
assert_ne!(incorrect_private_key_share, original_private_key_share);
@@ -253,10 +248,9 @@ mod tests_refresh {
/// Ñ parties (where t <= Ñ <= N) jointly execute a "share recovery" algorithm, and the output is 1 new share.
/// The new share is independent from the previously existing shares. We can use this to on-board a new participant into an existing cohort.
- #[test]
- fn tdec_simple_variant_share_recovery_at_random_point() {
+ #[test_matrix([4, 7, 11, 16])]
+ fn tdec_simple_variant_share_recovery_at_random_point(shares_num: usize) {
let rng = &mut test_rng();
- let shares_num = 16;
let threshold = shares_num * 2 / 3;
let (_, shared_private_key, mut contexts) =
@@ -321,10 +315,10 @@ mod tests_refresh {
/// Ñ parties (where t <= Ñ <= N) jointly execute a "share refresh" algorithm.
/// The output is M new shares (with M <= Ñ), with each of the M new shares substituting the
/// original share (i.e., the original share is deleted).
- #[test]
- fn tdec_simple_variant_share_refreshing() {
+ #[test_matrix([4, 7, 11, 16])]
+
+ fn tdec_simple_variant_share_refreshing(shares_num: usize) {
let rng = &mut test_rng();
- let shares_num = 16;
let threshold = shares_num * 2 / 3;
let (_, shared_private_key, contexts) =
diff --git a/ferveo/src/test_common.rs b/ferveo/src/test_common.rs
new file mode 100644
index 00000000..22d072a2
--- /dev/null
+++ b/ferveo/src/test_common.rs
@@ -0,0 +1,110 @@
+/// Factory functions and variables for testing
+use std::str::FromStr;
+
+pub use ark_bls12_381::Bls12_381 as E;
+use ark_ec::pairing::Pairing;
+use ferveo_common::Keypair;
+use rand::seq::SliceRandom;
+
+use crate::{DkgParams, EthereumAddress, PubliclyVerifiableDkg, Validator};
+
+pub type ScalarField = ::ScalarField;
+pub type G1 = ::G1Affine;
+pub type G2 = ::G2Affine;
+pub type TargetField = ::TargetField;
+
+pub const TAU: u32 = 0;
+pub const MSG: &[u8] = b"my-msg";
+pub const AAD: &[u8] = b"my-aad";
+pub const SECURITY_THRESHOLD: u32 = 3;
+pub const SHARES_NUM: u32 = 4;
+
+pub fn gen_keypairs(n: u32) -> Vec> {
+ let rng = &mut ark_std::test_rng();
+ (0..n).map(|_| Keypair::::new(rng)).collect()
+}
+
+pub fn gen_address(i: usize) -> EthereumAddress {
+ EthereumAddress::from_str(&format!("0x{i:040}")).unwrap()
+}
+
+pub fn gen_validators(keypairs: &[Keypair]) -> Vec> {
+ keypairs
+ .iter()
+ .enumerate()
+ .map(|(i, keypair)| Validator {
+ address: gen_address(i),
+ public_key: keypair.public_key(),
+ })
+ .collect()
+}
+
+pub type TestSetup = (PubliclyVerifiableDkg, Vec>);
+
+pub fn setup_dkg_for_n_validators(
+ security_threshold: u32,
+ shares_num: u32,
+ my_validator_index: usize,
+) -> TestSetup {
+ let keypairs = gen_keypairs(shares_num);
+ let mut validators = gen_validators(keypairs.as_slice());
+ validators.sort();
+ let me = validators[my_validator_index].clone();
+ let dkg = PubliclyVerifiableDkg::new(
+ &validators,
+ &DkgParams::new(TAU, security_threshold, shares_num).unwrap(),
+ &me,
+ )
+ .expect("Setup failed");
+ (dkg, keypairs)
+}
+
+/// Create a test dkg
+///
+/// The [`crate::dkg::test_dkg_init`] module checks correctness of this setup
+pub fn setup_dkg(my_validator_index: usize) -> TestSetup {
+ setup_dkg_for_n_validators(
+ SECURITY_THRESHOLD,
+ SHARES_NUM,
+ my_validator_index,
+ )
+}
+
+/// Set up a dkg with enough pvss transcripts to meet the threshold
+///
+/// The correctness of this function is tested in the module [`crate::dkg::test_dealing`]
+pub fn setup_dealt_dkg() -> TestSetup {
+ setup_dealt_dkg_with(SECURITY_THRESHOLD, SHARES_NUM)
+}
+
+pub fn setup_dealt_dkg_with(
+ security_threshold: u32,
+ shares_num: u32,
+) -> TestSetup {
+ let rng = &mut ark_std::test_rng();
+
+ // Gather everyone's transcripts
+ let mut messages: Vec<_> = (0..shares_num)
+ .map(|my_index| {
+ let (mut dkg, _) = setup_dkg_for_n_validators(
+ security_threshold,
+ shares_num,
+ my_index as usize,
+ );
+ let me = dkg.me.validator.clone();
+ let message = dkg.share(rng).unwrap();
+ (me, message)
+ })
+ .collect();
+
+ // Create a test DKG instance
+ let (mut dkg, keypairs) =
+ setup_dkg_for_n_validators(security_threshold, shares_num, 0);
+
+ // The ordering of messages should not matter
+ messages.shuffle(rng);
+ messages.iter().for_each(|(sender, message)| {
+ dkg.apply_message(sender, message).expect("Setup failed");
+ });
+ (dkg, keypairs)
+}
From b8fd959943c604eb0152e6715a13095501b906bb Mon Sep 17 00:00:00 2001
From: Piotr Roslaniec
Date: Fri, 19 Jan 2024 14:39:36 +0100
Subject: [PATCH 4/7] fix: allow double allocation when using SecretBox
---
ferveo/src/bindings_python.rs | 16 ++++++----------
1 file changed, 6 insertions(+), 10 deletions(-)
diff --git a/ferveo/src/bindings_python.rs b/ferveo/src/bindings_python.rs
index 9b472d34..411b42c5 100644
--- a/ferveo/src/bindings_python.rs
+++ b/ferveo/src/bindings_python.rs
@@ -231,17 +231,13 @@ macro_rules! generate_boxed_bytes_serialization {
#[pyfunction]
pub fn encrypt(
- message: &[u8],
+ message: Vec,
aad: &[u8],
dkg_public_key: &DkgPublicKey,
) -> PyResult {
- let ciphertext = api::encrypt(
- // TODO: Avoid double-allocation here. `SecretBox` already allocates for its contents.
- api::SecretBox::new(message.to_vec()),
- aad,
- &dkg_public_key.0,
- )
- .map_err(FerveoPythonError::FerveoError)?;
+ let ciphertext =
+ api::encrypt(api::SecretBox::new(message), aad, &dkg_public_key.0)
+ .map_err(FerveoPythonError::FerveoError)?;
Ok(Ciphertext(ciphertext))
}
@@ -816,7 +812,7 @@ mod test_ferveo_python {
let dkg_public_key = dkg.public_key();
// In the meantime, the client creates a ciphertext and decryption request
- let ciphertext = encrypt(MSG, AAD, &dkg_public_key).unwrap();
+ let ciphertext = encrypt(MSG.to_vec(), AAD, &dkg_public_key).unwrap();
// Having aggregated the transcripts, the validators can now create decryption shares
let decryption_shares: Vec<_> = izip!(&validators, &validator_keypairs)
@@ -887,7 +883,7 @@ mod test_ferveo_python {
let dkg_public_key = dkg.public_key();
// In the meantime, the client creates a ciphertext and decryption request
- let ciphertext = encrypt(MSG, AAD, &dkg_public_key).unwrap();
+ let ciphertext = encrypt(MSG.to_vec(), AAD, &dkg_public_key).unwrap();
// Having aggregated the transcripts, the validators can now create decryption shares
let decryption_shares: Vec<_> = izip!(&validators, &validator_keypairs)
From e79b4e5b05f83d16e23388edb04f2fed4674a355 Mon Sep 17 00:00:00 2001
From: Piotr Roslaniec
Date: Fri, 19 Jan 2024 15:13:10 +0100
Subject: [PATCH 5/7] chore: document benchmark todos
---
ferveo/benches/benchmarks/block_proposer.rs | 2 ++
ferveo/benches/benchmarks/mod.rs | 3 ++-
2 files changed, 4 insertions(+), 1 deletion(-)
diff --git a/ferveo/benches/benchmarks/block_proposer.rs b/ferveo/benches/benchmarks/block_proposer.rs
index 1f18a4ee..5043450f 100644
--- a/ferveo/benches/benchmarks/block_proposer.rs
+++ b/ferveo/benches/benchmarks/block_proposer.rs
@@ -1,5 +1,7 @@
#![allow(non_snake_case)]
+// TODO: Currently not maintained - see mod.rs
+
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use ark_bls12_381::*;
diff --git a/ferveo/benches/benchmarks/mod.rs b/ferveo/benches/benchmarks/mod.rs
index 7e19ab37..46d8d434 100644
--- a/ferveo/benches/benchmarks/mod.rs
+++ b/ferveo/benches/benchmarks/mod.rs
@@ -1,4 +1,5 @@
-//pub mod block_proposer;
+// We disabled the following benchmarks because their outcomes were not relevant to us at the time.
+// pub mod block_proposer;
// pub mod pairing;
pub mod eval_domain;
pub mod validity_checks;
From aebaab39e18a1c114e2aaae62ec9061d49f7a78a Mon Sep 17 00:00:00 2001
From: Piotr Roslaniec
Date: Fri, 19 Jan 2024 15:19:44 +0100
Subject: [PATCH 6/7] fix: prevent precomputed shares from being created with
inapprioriate variant
---
ferveo-wasm/examples/node/src/main.test.ts | 24 +++++----------
ferveo-wasm/tests/node.rs | 34 ++++++++--------------
ferveo/src/api.rs | 10 +++++--
ferveo/src/bindings_python.rs | 5 ++++
ferveo/src/lib.rs | 4 +++
5 files changed, 37 insertions(+), 40 deletions(-)
diff --git a/ferveo-wasm/examples/node/src/main.test.ts b/ferveo-wasm/examples/node/src/main.test.ts
index 28144861..047beeb5 100644
--- a/ferveo-wasm/examples/node/src/main.test.ts
+++ b/ferveo-wasm/examples/node/src/main.test.ts
@@ -22,11 +22,8 @@ const genEthAddr = (i: number) => {
return EthereumAddress.fromString(ethAddr);
};
-function setupTest() {
- const tau = 1;
- const sharesNum = 4;
- const threshold = Math.floor((sharesNum * 2) / 3);
-
+const tau = 1;
+function setupTest(sharesNum :number, threshold: number) {
const validatorKeypairs: Keypair[] = [];
const validators: Validator[] = [];
for (let i = 0; i < sharesNum; i++) {
@@ -63,9 +60,6 @@ function setupTest() {
const ciphertext = ferveoEncrypt(msg, aad, dkg.publicKey());
return {
- tau,
- sharesNum,
- threshold,
validatorKeypairs,
validators,
dkg,
@@ -79,17 +73,16 @@ function setupTest() {
// This test suite replicates tests from ferveo-wasm/tests/node.rs
describe("ferveo-wasm", () => {
it("simple tdec variant", () => {
+ const sharesNum = 4;
+ const threshold = 3;
const {
- tau,
- sharesNum,
- threshold,
validatorKeypairs,
validators,
messages,
msg,
aad,
ciphertext,
- } = setupTest();
+ } = setupTest(sharesNum, threshold);
// Having aggregated the transcripts, the validators can now create decryption shares
const decryptionShares: DecryptionShareSimple[] = [];
@@ -128,17 +121,16 @@ describe("ferveo-wasm", () => {
});
it("precomputed tdec variant", () => {
+ const sharesNum = 4;
+ const threshold = sharesNum; // threshold is equal to sharesNum in precomputed variant
const {
- tau,
- sharesNum,
- threshold,
validatorKeypairs,
validators,
messages,
msg,
aad,
ciphertext,
- } = setupTest();
+ } = setupTest(sharesNum, threshold);
// Having aggregated the transcripts, the validators can now create decryption shares
const decryptionShares: DecryptionSharePrecomputed[] = [];
diff --git a/ferveo-wasm/tests/node.rs b/ferveo-wasm/tests/node.rs
index 4ac71429..68e5f641 100644
--- a/ferveo-wasm/tests/node.rs
+++ b/ferveo-wasm/tests/node.rs
@@ -7,9 +7,6 @@ use itertools::zip_eq;
use wasm_bindgen_test::*;
type TestSetup = (
- u32,
- u32,
- u32,
Vec,
Vec,
ValidatorArray,
@@ -19,11 +16,9 @@ type TestSetup = (
Ciphertext,
);
-fn setup_dkg() -> TestSetup {
- let tau = 1;
- let shares_num: u32 = 16;
- let security_threshold = shares_num * 2 / 3;
+const TAU: u32 = 0;
+fn setup_dkg(shares_num: u32, security_threshold: u32) -> TestSetup {
let validator_keypairs = (0..shares_num as usize)
.map(gen_keypair)
.collect::>();
@@ -38,7 +33,7 @@ fn setup_dkg() -> TestSetup {
// validator, including themselves
let messages = validators.iter().map(|sender| {
let dkg = Dkg::new(
- tau,
+ TAU,
shares_num,
security_threshold,
&validators_js,
@@ -54,7 +49,7 @@ fn setup_dkg() -> TestSetup {
// every validator can aggregate the transcripts
let mut dkg = Dkg::new(
- tau,
+ TAU,
shares_num,
security_threshold,
&validators_js,
@@ -80,9 +75,6 @@ fn setup_dkg() -> TestSetup {
let ciphertext = ferveo_encrypt(&msg, &aad, &dkg.public_key()).unwrap();
(
- tau,
- shares_num,
- security_threshold,
validator_keypairs,
validators,
validators_js,
@@ -95,10 +87,9 @@ fn setup_dkg() -> TestSetup {
#[wasm_bindgen_test]
fn tdec_simple() {
+ let shares_num = 16;
+ let security_threshold = 10;
let (
- tau,
- shares_num,
- security_threshold,
validator_keypairs,
validators,
validators_js,
@@ -106,13 +97,13 @@ fn tdec_simple() {
msg,
aad,
ciphertext,
- ) = setup_dkg();
+ ) = setup_dkg(shares_num, security_threshold);
// Having aggregated the transcripts, the validators can now create decryption shares
let decryption_shares = zip_eq(validators, validator_keypairs)
.map(|(validator, keypair)| {
let mut dkg = Dkg::new(
- tau,
+ TAU,
shares_num,
security_threshold,
&validators_js,
@@ -149,10 +140,9 @@ fn tdec_simple() {
#[wasm_bindgen_test]
fn tdec_precomputed() {
+ let shares_num = 16;
+ let security_threshold = shares_num; // Must be equal to shares_num in precomputed variant
let (
- tau,
- shares_num,
- security_threshold,
validator_keypairs,
validators,
validators_js,
@@ -160,13 +150,13 @@ fn tdec_precomputed() {
msg,
aad,
ciphertext,
- ) = setup_dkg();
+ ) = setup_dkg(shares_num, security_threshold);
// Having aggregated the transcripts, the validators can now create decryption shares
let decryption_shares = zip_eq(validators, validator_keypairs)
.map(|(validator, keypair)| {
let mut dkg = Dkg::new(
- tau,
+ TAU,
shares_num,
security_threshold,
&validators_js,
diff --git a/ferveo/src/api.rs b/ferveo/src/api.rs
index 0a8bf2aa..3c2295d1 100644
--- a/ferveo/src/api.rs
+++ b/ferveo/src/api.rs
@@ -309,6 +309,14 @@ impl AggregatedTranscript {
aad: &[u8],
validator_keypair: &Keypair,
) -> Result {
+ if dkg.0.dkg_params.shares_num()
+ != dkg.0.dkg_params.security_threshold()
+ {
+ return Err(Error::InvalidDkgParametersForPrecomputedVariant(
+ dkg.0.dkg_params.shares_num(),
+ dkg.0.dkg_params.security_threshold(),
+ ));
+ }
let domain_points: Vec<_> = dkg
.0
.domain
@@ -455,8 +463,6 @@ mod test_ferveo_api {
let rng = &mut StdRng::seed_from_u64(0);
// In precomputed variant, the security threshold is equal to the number of shares
- // TODO: Refactor DKG constructor to not require security threshold or this case.
- // Or figure out a different way to simplify the precomputed variant API.
let security_threshold = shares_num;
let (messages, validators, validator_keypairs) =
diff --git a/ferveo/src/bindings_python.rs b/ferveo/src/bindings_python.rs
index 411b42c5..f897c8f6 100644
--- a/ferveo/src/bindings_python.rs
+++ b/ferveo/src/bindings_python.rs
@@ -104,6 +104,11 @@ impl From for PyErr {
"{index}"
))
},
+ Error::InvalidDkgParametersForPrecomputedVariant(num_shares, security_threshold) => {
+ InvalidDkgParameters::new_err(format!(
+ "num_shares: {num_shares}, security_threshold: {security_threshold}"
+ ))
+ },
},
_ => default(),
}
diff --git a/ferveo/src/lib.rs b/ferveo/src/lib.rs
index 394afb1a..c316c815 100644
--- a/ferveo/src/lib.rs
+++ b/ferveo/src/lib.rs
@@ -114,6 +114,10 @@ pub enum Error {
/// Failed to access a share for a given share index
#[error("Invalid share index: {0}")]
InvalidShareIndex(u32),
+
+ /// Failed to produce a precomputed variant decryption share
+ #[error("Invalid DKG parameters for precomputed variant: number of shares {0}, threshold {1}")]
+ InvalidDkgParametersForPrecomputedVariant(u32, u32),
}
pub type Result = std::result::Result;
From 802e7121d5eb5a31617bf88c4e14fe79d45e68e3 Mon Sep 17 00:00:00 2001
From: Piotr Roslaniec
Date: Fri, 19 Jan 2024 17:04:39 +0100
Subject: [PATCH 7/7] chore: remove duplicated field
---
ferveo-tdec/benches/tpke.rs | 2 +-
ferveo-tdec/src/context.rs | 8 +++-----
ferveo-tdec/src/lib.rs | 1 -
3 files changed, 4 insertions(+), 7 deletions(-)
diff --git a/ferveo-tdec/benches/tpke.rs b/ferveo-tdec/benches/tpke.rs
index e47d6b51..420bf869 100644
--- a/ferveo-tdec/benches/tpke.rs
+++ b/ferveo-tdec/benches/tpke.rs
@@ -189,7 +189,7 @@ pub fn bench_create_decryption_share(c: &mut Criterion) {
.map(|ctx| {
// Using create_unchecked here to avoid the cost of verifying the ciphertext
DecryptionShareSimple::create_unchecked(
- &ctx.validator_private_key,
+ &ctx.setup_params.b,
&ctx.private_key_share,
&setup.shared.ciphertext.header().unwrap(),
)
diff --git a/ferveo-tdec/src/context.rs b/ferveo-tdec/src/context.rs
index 32fa91fb..238db71c 100644
--- a/ferveo-tdec/src/context.rs
+++ b/ferveo-tdec/src/context.rs
@@ -29,7 +29,7 @@ pub struct PublicDecryptionContextSimple {
#[derive(Clone, Debug)]
pub struct SetupParams {
- pub b: E::ScalarField,
+ pub b: E::ScalarField, // Validator private key
pub b_inv: E::ScalarField,
pub g: E::G1Affine,
pub g_inv: E::G1Prepared,
@@ -71,8 +71,6 @@ pub struct PrivateDecryptionContextSimple {
pub setup_params: SetupParams,
pub private_key_share: PrivateKeyShare,
pub public_decryption_contexts: Vec>,
- // TODO: Remove/replace with `setup_params.b` after refactoring
- pub validator_private_key: E::ScalarField,
}
impl PrivateDecryptionContextSimple {
@@ -82,7 +80,7 @@ impl PrivateDecryptionContextSimple {
aad: &[u8],
) -> Result> {
DecryptionShareSimple::create(
- &self.validator_private_key,
+ &self.setup_params.b,
&self.private_key_share,
ciphertext_header,
aad,
@@ -104,7 +102,7 @@ impl PrivateDecryptionContextSimple {
DecryptionSharePrecomputed::new(
self.index,
- &self.validator_private_key,
+ &self.setup_params.b,
&self.private_key_share,
ciphertext_header,
aad,
diff --git a/ferveo-tdec/src/lib.rs b/ferveo-tdec/src/lib.rs
index a5c1b302..297b066c 100644
--- a/ferveo-tdec/src/lib.rs
+++ b/ferveo-tdec/src/lib.rs
@@ -243,7 +243,6 @@ pub mod test_common {
h,
},
private_key_share,
- validator_private_key: b,
public_decryption_contexts: vec![],
});
public_contexts.push(PublicDecryptionContextSimple:: {