From e903c1ba6f84c9656aa5777b62a0885362c6fa08 Mon Sep 17 00:00:00 2001
From: Piotr Roslaniec
Date: Tue, 30 Jan 2024 09:29:28 +0100
Subject: [PATCH 1/8] fix: add missing exception definition
---
ferveo/src/bindings_python.rs | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/ferveo/src/bindings_python.rs b/ferveo/src/bindings_python.rs
index fab67561..72407a16 100644
--- a/ferveo/src/bindings_python.rs
+++ b/ferveo/src/bindings_python.rs
@@ -109,7 +109,7 @@ impl From for PyErr {
))
},
Error::DuplicatedShareIndex(index) => {
- InvalidShareIndex::new_err(format!(
+ DuplicatedShareIndex::new_err(format!(
"{index}"
))
},
@@ -152,6 +152,7 @@ create_exception!(exceptions, InvalidByteLength, PyValueError);
create_exception!(exceptions, InvalidVariant, PyValueError);
create_exception!(exceptions, InvalidDkgParameters, PyValueError);
create_exception!(exceptions, InvalidShareIndex, PyValueError);
+create_exception!(exceptions, DuplicatedShareIndex, PyValueError);
create_exception!(exceptions, NoTranscriptsToAggregate, PyValueError);
fn from_py_bytes(bytes: &[u8]) -> PyResult {
From f98a417091644cf7b99b7c4702fadb0629a9d0cf Mon Sep 17 00:00:00 2001
From: Piotr Roslaniec
Date: Mon, 29 Jan 2024 12:15:18 +0100
Subject: [PATCH 2/8] chore(test): relax dkg ceremony constraints
---
ferveo/src/dkg.rs | 34 +++++++++++++++------------
ferveo/src/lib.rs | 48 ++++++++++++++++++++++++++-------------
ferveo/src/pvss.rs | 32 ++++++++++++++++++--------
ferveo/src/refresh.rs | 2 --
ferveo/src/test_common.rs | 28 +++++++++++++++++++----
5 files changed, 99 insertions(+), 45 deletions(-)
diff --git a/ferveo/src/dkg.rs b/ferveo/src/dkg.rs
index 3bba9b47..cbd90971 100644
--- a/ferveo/src/dkg.rs
+++ b/ferveo/src/dkg.rs
@@ -112,7 +112,7 @@ impl PubliclyVerifiableDkg {
me: &Validator,
) -> Result {
let domain = ark_poly::GeneralEvaluationDomain::::new(
- dkg_params.shares_num as usize,
+ validators.len(),
)
.expect("unable to construct domain");
@@ -432,11 +432,13 @@ mod test_dealing {
}
));
let pvss = dkg.share(rng).unwrap();
- let unknown_validator_i = dkg.dkg_params.shares_num + 1;
+ // Need to make sure this falls outside of the validator set:
+ let unknown_validator_index =
+ dkg.dkg_params.shares_num + VALIDATORS_NUM + 1;
let sender = Validator:: {
- address: gen_address(unknown_validator_i as usize),
+ address: gen_address(unknown_validator_index as usize),
public_key: ferveo_common::Keypair::::new(rng).public_key(),
- share_index: dkg.dkg_params.shares_num + 5, // Not in the validator set
+ share_index: unknown_validator_index,
};
// check that verification fails
assert!(dkg.verify_message(&sender, &pvss).is_err());
@@ -586,14 +588,20 @@ mod test_dealing {
#[cfg(test)]
mod test_aggregation {
use ark_ec::AffineRepr;
+ use test_case::test_case;
use crate::{dkg::*, test_common::*, DkgState, Message};
- /// Test that if the security threshold is
- /// met, we can create a final key
- #[test]
- fn test_aggregate() {
- let (mut dkg, _) = setup_dealt_dkg();
+ /// Test that if the security threshold is met, we can create a final key
+ #[test_case(4,4; "number of validators is equal to the number of shares")]
+ #[test_case(4,6; "number of validators is greater than the number of shares")]
+ fn test_aggregate(shares_num: u32, validators_num: u32) {
+ let security_threshold = shares_num - 1;
+ let (mut dkg, _) = setup_dealt_dkg_with_n_validators(
+ security_threshold,
+ shares_num,
+ validators_num,
+ );
let aggregate = dkg.aggregate().unwrap();
let sender = dkg.me.clone();
assert!(dkg.verify_message(&sender, &aggregate).is_ok());
@@ -601,8 +609,7 @@ mod test_aggregation {
assert!(matches!(dkg.state, DkgState::Success { .. }));
}
- /// Test that aggregate only succeeds if we are in
- /// the state [`DkgState::Dealt]
+ /// Test that aggregate only succeeds if we are in the state [`DkgState::Dealt]
#[test]
fn test_aggregate_state_guards() {
let (mut dkg, _) = setup_dealt_dkg();
@@ -617,9 +624,8 @@ mod test_aggregation {
assert!(dkg.aggregate().is_err());
}
- /// Test that aggregate message fail to be verified
- /// or applied unless dkg.state is
- /// [`DkgState::Dealt`]
+ /// Test that aggregate message fail to be verified or applied unless
+ /// dkg.state is [`DkgState::Dealt`]
#[test]
fn test_aggregate_message_state_guards() {
let (mut dkg, _) = setup_dealt_dkg();
diff --git a/ferveo/src/lib.rs b/ferveo/src/lib.rs
index a7e5e7a4..2931defe 100644
--- a/ferveo/src/lib.rs
+++ b/ferveo/src/lib.rs
@@ -195,14 +195,18 @@ mod test_dkg_full {
(pvss_aggregated, decryption_shares, shared_secret)
}
- #[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) {
+ #[test_case(4, 4; "number of shares (validators) is a power of 2")]
+ #[test_case(7, 7; "number of shares (validators) is not a power of 2")]
+ #[test_case(4, 6; "number of validators is greater than the number of shares")]
+ fn test_dkg_simple_tdec(shares_num: u32, validators_num: u32) {
let rng = &mut test_rng();
- let threshold = shares_num / 2 + 1;
- let (dkg, validator_keypairs) =
- setup_dealt_dkg_with(threshold, shares_num);
+ let security_threshold = shares_num / 2 + 1;
+ let (dkg, validator_keypairs) = setup_dealt_dkg_with_n_validators(
+ security_threshold,
+ shares_num,
+ validators_num,
+ );
let public_key = dkg.public_key();
let ciphertext = ferveo_tdec::encrypt::(
@@ -230,15 +234,19 @@ mod test_dkg_full {
assert_eq!(plaintext, MSG);
}
- #[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) {
+ #[test_case(4, 4; "number of shares (validators) is a power of 2")]
+ #[test_case(7, 7; "number of shares (validators) is not a power of 2")]
+ #[test_case(4, 6; "number of validators is greater than the number of shares")]
+ fn test_dkg_simple_tdec_precomputed(shares_num: u32, validators_num: u32) {
let rng = &mut test_rng();
// In precomputed variant, threshold must be equal to shares_num
- let threshold = shares_num;
- let (dkg, validator_keypairs) =
- setup_dealt_dkg_with(threshold, shares_num);
+ let security_threshold = shares_num;
+ let (dkg, validator_keypairs) = setup_dealt_dkg_with_n_validators(
+ security_threshold,
+ shares_num,
+ validators_num,
+ );
let public_key = dkg.public_key();
let ciphertext = ferveo_tdec::encrypt::(
SecretBox::new(MSG.to_vec()),
@@ -293,12 +301,20 @@ mod test_dkg_full {
assert_eq!(plaintext, MSG);
}
- #[test]
- fn test_dkg_simple_tdec_share_verification() {
+ #[test_case(4, 4; "number of validators is equal to the number of shares")]
+ #[test_case(4, 6; "number of validators is greater than the number of shares")]
+ fn test_dkg_simple_tdec_share_verification(
+ shares_num: u32,
+ validators_num: u32,
+ ) {
let rng = &mut test_rng();
+ let security_threshold = shares_num / 2 + 1;
- let (dkg, validator_keypairs) =
- setup_dealt_dkg_with(SECURITY_THRESHOLD, SHARES_NUM);
+ let (dkg, validator_keypairs) = setup_dealt_dkg_with_n_validators(
+ security_threshold,
+ shares_num,
+ validators_num,
+ );
let public_key = dkg.public_key();
let ciphertext = ferveo_tdec::encrypt::(
SecretBox::new(MSG.to_vec()),
diff --git a/ferveo/src/pvss.rs b/ferveo/src/pvss.rs
index 07328620..edbd8578 100644
--- a/ferveo/src/pvss.rs
+++ b/ferveo/src/pvss.rs
@@ -422,16 +422,24 @@ mod test_pvss {
use ark_bls12_381::Bls12_381 as EllipticCurve;
use ark_ec::AffineRepr;
use ark_ff::UniformRand;
+ use test_case::test_case;
use super::*;
use crate::{test_common::*, DkgParams};
- /// Test the happy flow that a pvss with the correct form is created
+ /// Test the happy flow such that the PVSS with the correct form is created
/// and that appropriate validations pass
- #[test]
- fn test_new_pvss() {
+ #[test_case(4,4; "number of validators is equal to the number of shares")]
+ #[test_case(4,6; "number of validators is greater than the number of shares")]
+ fn test_new_pvss(shares_num: u32, validators_num: u32) {
let rng = &mut ark_std::test_rng();
- let (dkg, _) = setup_dkg(0);
+ let security_threshold = shares_num - 1;
+
+ let (dkg, _) = setup_dealt_dkg_with_n_validators(
+ security_threshold,
+ shares_num,
+ validators_num,
+ );
let s = ScalarField::rand(rng);
let pvss = PubliclyVerifiableSS::::new(&s, &dkg, rng)
.expect("Test failed");
@@ -453,7 +461,7 @@ mod test_pvss {
}
/// Check that if the proof of knowledge is wrong,
- /// the optimistic verification of PVSS fails
+ /// then the optimistic verification of the PVSS fails
#[test]
fn test_verify_pvss_wrong_proof_of_knowledge() {
let rng = &mut ark_std::test_rng();
@@ -523,10 +531,16 @@ mod test_pvss {
}
/// Check that happy flow of aggregating PVSS transcripts
- /// Should have the correct form and validations pass
- #[test]
- fn test_aggregate_pvss() {
- let (dkg, _) = setup_dealt_dkg();
+ /// has the correct form and it's validations passes
+ #[test_case(4,4; "number of validators is equal to the number of shares")]
+ #[test_case(4,6; "number of validators is greater than the number of shares")]
+ fn test_aggregate_pvss(shares_num: u32, validators_num: u32) {
+ let security_threshold = shares_num - 1;
+ let (dkg, _) = setup_dealt_dkg_with_n_validators(
+ security_threshold,
+ shares_num,
+ validators_num,
+ );
let pvss_list = dkg.vss.values().cloned().collect::>();
let aggregate = aggregate(&pvss_list).unwrap();
// Check that a polynomial of the correct degree was created
diff --git a/ferveo/src/refresh.rs b/ferveo/src/refresh.rs
index 524e6569..b02eba3b 100644
--- a/ferveo/src/refresh.rs
+++ b/ferveo/src/refresh.rs
@@ -118,7 +118,6 @@ pub fn make_random_polynomial_with_root(
#[cfg(test)]
mod tests_refresh {
-
use std::collections::HashMap;
use ark_bls12_381::Fr;
@@ -316,7 +315,6 @@ mod tests_refresh {
/// 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_matrix([4, 7, 11, 16])]
-
fn tdec_simple_variant_share_refreshing(shares_num: usize) {
let rng = &mut test_rng();
let threshold = shares_num * 2 / 3;
diff --git a/ferveo/src/test_common.rs b/ferveo/src/test_common.rs
index 4faae733..dce10e5d 100644
--- a/ferveo/src/test_common.rs
+++ b/ferveo/src/test_common.rs
@@ -18,6 +18,7 @@ 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 const VALIDATORS_NUM: u32 = SHARES_NUM + 2;
pub fn gen_keypairs(n: u32) -> Vec> {
let rng = &mut ark_std::test_rng();
@@ -46,8 +47,9 @@ pub fn setup_dkg_for_n_validators(
security_threshold: u32,
shares_num: u32,
my_validator_index: usize,
+ validators_num: u32,
) -> TestSetup {
- let keypairs = gen_keypairs(shares_num);
+ let keypairs = gen_keypairs(validators_num);
let validators = gen_validators(keypairs.as_slice());
let me = validators[my_validator_index].clone();
let dkg = PubliclyVerifiableDkg::new(
@@ -67,6 +69,7 @@ pub fn setup_dkg(my_validator_index: usize) -> TestSetup {
SECURITY_THRESHOLD,
SHARES_NUM,
my_validator_index,
+ VALIDATORS_NUM,
)
}
@@ -80,16 +83,29 @@ pub fn setup_dealt_dkg() -> TestSetup {
pub fn setup_dealt_dkg_with(
security_threshold: u32,
shares_num: u32,
+) -> TestSetup {
+ setup_dealt_dkg_with_n_validators(
+ security_threshold,
+ shares_num,
+ shares_num,
+ )
+}
+
+pub fn setup_dealt_dkg_with_n_validators(
+ security_threshold: u32,
+ shares_num: u32,
+ validators_num: u32,
) -> TestSetup {
let rng = &mut ark_std::test_rng();
// Gather everyone's transcripts
- let mut messages: Vec<_> = (0..shares_num)
+ let mut messages: Vec<_> = (0..validators_num)
.map(|my_index| {
let (mut dkg, _) = setup_dkg_for_n_validators(
security_threshold,
shares_num,
my_index as usize,
+ validators_num,
);
let me = dkg.me.clone();
let message = dkg.share(rng).unwrap();
@@ -98,8 +114,12 @@ pub fn setup_dealt_dkg_with(
.collect();
// Create a test DKG instance
- let (mut dkg, keypairs) =
- setup_dkg_for_n_validators(security_threshold, shares_num, 0);
+ let (mut dkg, keypairs) = setup_dkg_for_n_validators(
+ security_threshold,
+ shares_num,
+ 0,
+ validators_num,
+ );
// The ordering of messages should not matter
messages.shuffle(rng);
From 81bc1cbb7c51db655db859684f29f86150bba072 Mon Sep 17 00:00:00 2001
From: Piotr Roslaniec
Date: Tue, 30 Jan 2024 09:59:49 +0100
Subject: [PATCH 3/8] feature: relax dkg ceremony constraints
---
ferveo/src/api.rs | 11 +--
ferveo/src/bindings_python.rs | 177 +++++++++++++++++++---------------
ferveo/src/dkg.rs | 37 ++++++-
ferveo/src/lib.rs | 8 +-
ferveo/src/pvss.rs | 7 +-
5 files changed, 141 insertions(+), 99 deletions(-)
diff --git a/ferveo/src/api.rs b/ferveo/src/api.rs
index 4c6defd2..80ab58d6 100644
--- a/ferveo/src/api.rs
+++ b/ferveo/src/api.rs
@@ -324,6 +324,7 @@ impl AggregatedTranscript {
aad: &[u8],
validator_keypair: &Keypair,
) -> Result {
+ // Prevent users from using the precomputed variant with improper DKG parameters
if dkg.0.dkg_params.shares_num()
!= dkg.0.dkg_params.security_threshold()
{
@@ -332,18 +333,12 @@ impl AggregatedTranscript {
dkg.0.dkg_params.security_threshold(),
));
}
- let domain_points: Vec<_> = dkg
- .0
- .domain
- .elements()
- .take(dkg.0.dkg_params.shares_num() as usize)
- .collect();
self.0.make_decryption_share_simple_precomputed(
&ciphertext_header.0,
aad,
&validator_keypair.decryption_key,
dkg.0.me.share_index as usize,
- &domain_points,
+ &dkg.0.domain_points(),
&dkg.0.pvss_params.g_inv(),
)
}
@@ -362,7 +357,7 @@ impl AggregatedTranscript {
dkg.0.me.share_index as usize,
&dkg.0.pvss_params.g_inv(),
)?;
- let domain_point = dkg.0.domain.element(dkg.0.me.share_index as usize);
+ let domain_point = dkg.0.get_domain_point(dkg.0.me.share_index)?;
Ok(DecryptionShareSimple {
share,
domain_point,
diff --git a/ferveo/src/bindings_python.rs b/ferveo/src/bindings_python.rs
index 72407a16..bd29dc0d 100644
--- a/ferveo/src/bindings_python.rs
+++ b/ferveo/src/bindings_python.rs
@@ -92,30 +92,30 @@ impl From for PyErr {
}
Error::InvalidVariant(variant) => {
InvalidVariant::new_err(variant.to_string())
- },
- Error::InvalidDkgParameters(num_shares, security_threshold) => {
+ }
+ Error::InvalidDkgParameters(shares_num, security_threshold) => {
InvalidDkgParameters::new_err(format!(
- "num_shares: {num_shares}, security_threshold: {security_threshold}"
+ "shares_num: {shares_num}, security_threshold: {security_threshold}"
))
- },
+ }
Error::InvalidShareIndex(index) => {
InvalidShareIndex::new_err(format!(
"{index}"
))
- },
- Error::InvalidDkgParametersForPrecomputedVariant(num_shares, security_threshold) => {
+ }
+ Error::InvalidDkgParametersForPrecomputedVariant(shares_num, security_threshold) => {
InvalidDkgParameters::new_err(format!(
- "num_shares: {num_shares}, security_threshold: {security_threshold}"
+ "shares_num: {shares_num}, security_threshold: {security_threshold}"
))
- },
+ }
Error::DuplicatedShareIndex(index) => {
DuplicatedShareIndex::new_err(format!(
"{index}"
))
- },
+ }
Error::NoTranscriptsToAggregate => {
NoTranscriptsToAggregate::new_err("")
- },
+ }
},
_ => default(),
}
@@ -751,6 +751,7 @@ pub fn make_ferveo_py_module(py: Python<'_>, m: &PyModule) -> PyResult<()> {
#[cfg(test)]
mod test_ferveo_python {
use itertools::izip;
+ use test_case::test_case;
use crate::{bindings_python::*, test_common::*};
@@ -760,8 +761,9 @@ mod test_ferveo_python {
tau: u32,
security_threshold: u32,
shares_num: u32,
+ validators_num: u32,
) -> TestInputs {
- let validator_keypairs = (0..shares_num)
+ let validator_keypairs = (0..validators_num)
.map(|_| Keypair::random())
.collect::>();
let validators: Vec<_> = validator_keypairs
@@ -800,13 +802,18 @@ mod test_ferveo_python {
(messages, validators, validator_keypairs)
}
- #[test]
- fn test_server_api_tdec_precomputed() {
+ #[test_case(4, 4; "number of validators equal to the number of shares")]
+ #[test_case(4, 6; "number of validators greater than the number of shares")]
+ fn test_server_api_tdec_precomputed(shares_num: u32, validators_num: u32) {
// 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);
+ let (messages, validators, validator_keypairs) = make_test_inputs(
+ TAU,
+ security_threshold,
+ shares_num,
+ validators_num,
+ );
// Now that every validator holds a dkg instance and a transcript for every other validator,
// every validator can aggregate the transcripts
@@ -814,19 +821,20 @@ mod test_ferveo_python {
let me = validators[0].clone();
let mut dkg = Dkg::new(
TAU,
- SHARES_NUM,
+ shares_num,
security_threshold,
validators.clone(),
&me,
)
.unwrap();
- // Lets say that we've only receives `security_threshold` transcripts
+ // Lets say that we've only received `security_threshold` transcripts
let messages = messages[..security_threshold as usize].to_vec();
let pvss_aggregated =
dkg.aggregate_transcripts(messages.clone()).unwrap();
+ // TODO: Redo how verification API works;
assert!(pvss_aggregated
- .verify(SHARES_NUM, messages.clone())
+ .verify(validators_num, messages.clone())
.unwrap());
// At this point, any given validator should be able to provide a DKG public key
@@ -836,36 +844,38 @@ mod test_ferveo_python {
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)
- .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.clone(),
- validator,
- )
- .unwrap();
- let aggregate =
- dkg.aggregate_transcripts(messages.clone()).unwrap();
- assert!(pvss_aggregated
- .verify(SHARES_NUM, messages.clone())
- .is_ok());
- aggregate
- .create_decryption_share_precomputed(
- &dkg,
- &ciphertext.header().unwrap(),
- AAD,
- validator_keypair,
+ let decryption_shares: Vec<_> =
+ izip!(validators.clone(), &validator_keypairs)
+ .map(|(validator, validator_keypair)| {
+ // Each validator holds their own instance of DKG and creates their own aggregate
+ let mut validator_dkg = Dkg::new(
+ TAU,
+ shares_num,
+ security_threshold,
+ validators.clone(),
+ &validator,
)
- .unwrap()
- })
- .collect();
+ .unwrap();
+ let aggregate = validator_dkg
+ .aggregate_transcripts(messages.clone())
+ .unwrap();
+ // TODO: Redo how verification API works;
+ assert!(pvss_aggregated
+ .verify(validators_num, messages.clone())
+ .is_ok());
+ aggregate
+ .create_decryption_share_precomputed(
+ &validator_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 =
combine_decryption_shares_precomputed(decryption_shares);
@@ -875,29 +885,36 @@ mod test_ferveo_python {
assert_eq!(plaintext, MSG);
}
- #[test]
- fn test_server_api_tdec_simple() {
- let (messages, validators, validator_keypairs) =
- make_test_inputs(TAU, SECURITY_THRESHOLD, SHARES_NUM);
+ #[test_case(4, 4; "number of validators equal to the number of shares")]
+ #[test_case(4, 6; "number of validators greater than the number of shares")]
+ fn test_server_api_tdec_simple(shares_num: u32, validators_num: u32) {
+ let security_threshold = shares_num - 1;
+ let (messages, validators, validator_keypairs) = make_test_inputs(
+ TAU,
+ security_threshold,
+ shares_num,
+ validators_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,
+ 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();
+ // TODO: Redo how verification API works;
assert!(pvss_aggregated
- .verify(SHARES_NUM, messages.clone())
+ .verify(validators_num, messages.clone())
.unwrap());
// At this point, any given validator should be able to provide a DKG public key
@@ -907,32 +924,36 @@ mod test_ferveo_python {
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)
- .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.clone(),
- validator,
- )
- .unwrap();
- let aggregate =
- dkg.aggregate_transcripts(messages.clone()).unwrap();
- assert!(aggregate
- .verify(SHARES_NUM, messages.clone())
- .unwrap());
- aggregate
- .create_decryption_share_simple(
- &dkg,
- &ciphertext.header().unwrap(),
- AAD,
- validator_keypair,
+ let decryption_shares: Vec<_> =
+ izip!(validators.clone(), &validator_keypairs)
+ .map(|(validator, validator_keypair)| {
+ // Each validator holds their own instance of DKG and creates their own aggregate
+ let mut validator_dkg = Dkg::new(
+ TAU,
+ shares_num,
+ security_threshold,
+ validators.clone(),
+ &validator,
)
- .unwrap()
- })
- .collect();
+ .unwrap();
+ let aggregate = validator_dkg
+ .aggregate_transcripts(messages.clone())
+ .unwrap();
+
+ // TODO: Redo how verification API works;
+ assert!(aggregate
+ .verify(validators_num, messages.clone())
+ .unwrap());
+ aggregate
+ .create_decryption_share_simple(
+ &validator_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
diff --git a/ferveo/src/dkg.rs b/ferveo/src/dkg.rs
index cbd90971..be5f6cec 100644
--- a/ferveo/src/dkg.rs
+++ b/ferveo/src/dkg.rs
@@ -94,8 +94,11 @@ pub struct PubliclyVerifiableDkg {
pub pvss_params: PubliclyVerifiableParams,
pub validators: ValidatorsMap,
pub vss: PVSSMap,
+ // TODO: Remove pub?
+ // TODO: Consider replacing with domain_points entirely
pub domain: ark_poly::GeneralEvaluationDomain,
pub me: Validator,
+ // TODO: Remove pub?
pub state: DkgState,
}
@@ -197,6 +200,21 @@ impl PubliclyVerifiableDkg {
.into_affine()
}
+ // TODO: Use instead of domain.element
+ /// Return a domain point for the share_index
+ pub fn get_domain_point(&self, share_index: u32) -> Result {
+ let domain_points = self.domain_points();
+ domain_points
+ .get(share_index as usize)
+ .ok_or_else(|| Error::InvalidShareIndex(share_index))
+ .copied()
+ }
+
+ /// Return an appropriate amount of domain points for the DKG
+ pub fn domain_points(&self) -> Vec {
+ self.domain.elements().take(self.validators.len()).collect()
+ }
+
/// `payload` is the content of the message
pub fn verify_message(
&self,
@@ -323,6 +341,8 @@ pub struct Aggregation {
public_key: E::G1Affine,
}
+// TODO: Remove these?
+// TODO: These messages are not actually used anywhere, we use our own ValidatorMessage for Deal, and Aggregate for Message.Aggregate
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(bound(
serialize = "AggregatedPvss: Serialize, PubliclyVerifiableSS: Serialize",
@@ -593,8 +613,8 @@ mod test_aggregation {
use crate::{dkg::*, test_common::*, DkgState, Message};
/// Test that if the security threshold is met, we can create a final key
- #[test_case(4,4; "number of validators is equal to the number of shares")]
- #[test_case(4,6; "number of validators is greater than the number of shares")]
+ #[test_case(4,4; "number of validators equal to the number of shares")]
+ #[test_case(4,6; "number of validators greater than the number of shares")]
fn test_aggregate(shares_num: u32, validators_num: u32) {
let security_threshold = shares_num - 1;
let (mut dkg, _) = setup_dealt_dkg_with_n_validators(
@@ -602,10 +622,17 @@ mod test_aggregation {
shares_num,
validators_num,
);
- let aggregate = dkg.aggregate().unwrap();
+ let aggregate_msg = dkg.aggregate().unwrap();
+ if let Message::Aggregate(Aggregation { public_key, .. }) =
+ &aggregate_msg
+ {
+ assert_eq!(public_key, &dkg.public_key());
+ } else {
+ panic!("Expected aggregate message")
+ }
let sender = dkg.me.clone();
- assert!(dkg.verify_message(&sender, &aggregate).is_ok());
- assert!(dkg.apply_message(&sender, &aggregate).is_ok());
+ assert!(dkg.verify_message(&sender, &aggregate_msg).is_ok());
+ assert!(dkg.apply_message(&sender, &aggregate_msg).is_ok());
assert!(matches!(dkg.state, DkgState::Success { .. }));
}
diff --git a/ferveo/src/lib.rs b/ferveo/src/lib.rs
index 2931defe..bba06458 100644
--- a/ferveo/src/lib.rs
+++ b/ferveo/src/lib.rs
@@ -197,7 +197,7 @@ mod test_dkg_full {
#[test_case(4, 4; "number of shares (validators) is a power of 2")]
#[test_case(7, 7; "number of shares (validators) is not a power of 2")]
- #[test_case(4, 6; "number of validators is greater than the number of shares")]
+ #[test_case(4, 6; "number of validators greater than the number of shares")]
fn test_dkg_simple_tdec(shares_num: u32, validators_num: u32) {
let rng = &mut test_rng();
@@ -236,7 +236,7 @@ mod test_dkg_full {
#[test_case(4, 4; "number of shares (validators) is a power of 2")]
#[test_case(7, 7; "number of shares (validators) is not a power of 2")]
- #[test_case(4, 6; "number of validators is greater than the number of shares")]
+ #[test_case(4, 6; "number of validators greater than the number of shares")]
fn test_dkg_simple_tdec_precomputed(shares_num: u32, validators_num: u32) {
let rng = &mut test_rng();
@@ -301,8 +301,8 @@ mod test_dkg_full {
assert_eq!(plaintext, MSG);
}
- #[test_case(4, 4; "number of validators is equal to the number of shares")]
- #[test_case(4, 6; "number of validators is greater than the number of shares")]
+ #[test_case(4, 4; "number of validators equal to the number of shares")]
+ #[test_case(4, 6; "number of validators greater than the number of shares")]
fn test_dkg_simple_tdec_share_verification(
shares_num: u32,
validators_num: u32,
diff --git a/ferveo/src/pvss.rs b/ferveo/src/pvss.rs
index edbd8578..6bc074c2 100644
--- a/ferveo/src/pvss.rs
+++ b/ferveo/src/pvss.rs
@@ -262,7 +262,7 @@ pub fn do_verify_aggregation(
// Now, we verify that the aggregated PVSS transcript is a valid aggregation
let mut y = E::G1::zero();
- for (_, pvss) in vss.iter() {
+ for pvss in vss.values() {
y += pvss.coeffs[0].into_group();
}
if y.into_affine() == pvss_agg_coefficients[0] {
@@ -276,7 +276,7 @@ pub fn do_verify_aggregation(
impl PubliclyVerifiableSS {
/// Verify that this PVSS instance is a valid aggregation of
/// the PVSS instances, produced by [`aggregate`],
- /// and received by the DKG context `dkg`
+ /// and received by the DKG context `dkg`.
/// Returns the total nr of shares in the aggregated PVSS
pub fn verify_aggregation(
&self,
@@ -395,7 +395,7 @@ pub(crate) fn aggregate(
// So now we're iterating over the PVSS instances, and adding their coefficients and shares, and their sigma
// sigma is the sum of all the sigma_i, which is the proof of knowledge of the secret polynomial
- // Aggregating is just adding the corresponding values in pvss instances, so pvss = pvss + pvss_j
+ // Aggregating is just adding the corresponding values in PVSS instances, so PVSS_i = PVSS_(i-1) PVSS_i
for next_pvss in pvss_iter {
sigma = (sigma + next_pvss.sigma).into();
coeffs
@@ -502,7 +502,6 @@ mod test_pvss {
assert!(!bad_pvss.verify_full(&dkg));
}
- // TODO: Move this code to dkg.rs
/// Check that the canonical share indices of validators are expected and enforced
/// by the DKG methods.
#[test]
From 3d987585e28c5543107af5cb3705af28fae88461 Mon Sep 17 00:00:00 2001
From: Piotr Roslaniec
Date: Wed, 31 Jan 2024 11:53:26 +0100
Subject: [PATCH 4/8] refactor(test): move a test to a dkg test module
---
ferveo/src/dkg.rs | 32 +++++++++++++++++++++++++++++++-
ferveo/src/pvss.rs | 29 +----------------------------
2 files changed, 32 insertions(+), 29 deletions(-)
diff --git a/ferveo/src/dkg.rs b/ferveo/src/dkg.rs
index be5f6cec..b6560691 100644
--- a/ferveo/src/dkg.rs
+++ b/ferveo/src/dkg.rs
@@ -390,7 +390,37 @@ mod test_dkg_init {
mod test_dealing {
use ark_ec::AffineRepr;
- use crate::{test_common::*, DkgState, DkgState::Dealt, Validator};
+ use crate::{
+ test_common::*, DkgParams, DkgState, DkgState::Dealt, Error,
+ PubliclyVerifiableDkg, Validator,
+ };
+
+ /// Check that the canonical share indices of validators are expected and enforced
+ /// by the DKG methods.
+ #[test]
+ fn test_canonical_share_indices_are_enforced() {
+ let shares_num = 4;
+ let security_threshold = shares_num - 1;
+ let keypairs = gen_keypairs(shares_num);
+ let mut validators = gen_validators(&keypairs);
+ let me = validators[0].clone();
+
+ // Validators (share indices) are not unique
+ let duplicated_index = 0;
+ validators.insert(duplicated_index, me.clone());
+
+ // And because of that the DKG should fail
+ let result = PubliclyVerifiableDkg::new(
+ &validators,
+ &DkgParams::new(0, security_threshold, shares_num).unwrap(),
+ &me,
+ );
+ assert!(result.is_err());
+ assert_eq!(
+ result.unwrap_err().to_string(),
+ Error::DuplicatedShareIndex(duplicated_index as u32).to_string()
+ );
+ }
/// Test that dealing correct PVSS transcripts
/// pass verification an application and that
diff --git a/ferveo/src/pvss.rs b/ferveo/src/pvss.rs
index 6bc074c2..7be4fac2 100644
--- a/ferveo/src/pvss.rs
+++ b/ferveo/src/pvss.rs
@@ -425,7 +425,7 @@ mod test_pvss {
use test_case::test_case;
use super::*;
- use crate::{test_common::*, DkgParams};
+ use crate::test_common::*;
/// Test the happy flow such that the PVSS with the correct form is created
/// and that appropriate validations pass
@@ -502,33 +502,6 @@ mod test_pvss {
assert!(!bad_pvss.verify_full(&dkg));
}
- /// Check that the canonical share indices of validators are expected and enforced
- /// by the DKG methods.
- #[test]
- fn test_canonical_share_indices_are_enforced() {
- let shares_num = 4;
- let security_threshold = shares_num - 1;
- let keypairs = gen_keypairs(shares_num);
- let mut validators = gen_validators(&keypairs);
- let me = validators[0].clone();
-
- // Validators (share indices) are not unique
- let duplicated_index = 0;
- validators.insert(duplicated_index, me.clone());
-
- // And because of that the DKG should fail
- let result = PubliclyVerifiableDkg::new(
- &validators,
- &DkgParams::new(0, security_threshold, shares_num).unwrap(),
- &me,
- );
- assert!(result.is_err());
- assert_eq!(
- result.unwrap_err().to_string(),
- Error::DuplicatedShareIndex(duplicated_index as u32).to_string()
- );
- }
-
/// Check that happy flow of aggregating PVSS transcripts
/// has the correct form and it's validations passes
#[test_case(4,4; "number of validators is equal to the number of shares")]
From 514221ebb052f6757c49c0c7ed2ff097fb878b34 Mon Sep 17 00:00:00 2001
From: Piotr Roslaniec
Date: Wed, 31 Jan 2024 12:18:45 +0100
Subject: [PATCH 5/8] refactor(dkg): hide dkg fields in the internal api
---
ferveo/src/dkg.rs | 11 ++---------
ferveo/src/lib.rs | 4 ++--
ferveo/src/pvss.rs | 12 +++++++++---
3 files changed, 13 insertions(+), 14 deletions(-)
diff --git a/ferveo/src/dkg.rs b/ferveo/src/dkg.rs
index b6560691..e8afbe30 100644
--- a/ferveo/src/dkg.rs
+++ b/ferveo/src/dkg.rs
@@ -94,12 +94,9 @@ pub struct PubliclyVerifiableDkg {
pub pvss_params: PubliclyVerifiableParams,
pub validators: ValidatorsMap,
pub vss: PVSSMap,
- // TODO: Remove pub?
- // TODO: Consider replacing with domain_points entirely
pub domain: ark_poly::GeneralEvaluationDomain,
pub me: Validator,
- // TODO: Remove pub?
- pub state: DkgState,
+ state: DkgState,
}
impl PubliclyVerifiableDkg {
@@ -123,10 +120,7 @@ impl PubliclyVerifiableDkg {
let validators: ValidatorsMap = validators
.iter()
- .enumerate()
- .map(|(_validator_index, validator)| {
- (validator.address.clone(), validator.clone())
- })
+ .map(|validator| (validator.address.clone(), validator.clone()))
.collect();
// Make sure that `me` is a known validator
@@ -200,7 +194,6 @@ impl PubliclyVerifiableDkg {
.into_affine()
}
- // TODO: Use instead of domain.element
/// Return a domain point for the share_index
pub fn get_domain_point(&self, share_index: u32) -> Result {
let domain_points = self.domain_points();
diff --git a/ferveo/src/lib.rs b/ferveo/src/lib.rs
index bba06458..d5f1fb11 100644
--- a/ferveo/src/lib.rs
+++ b/ferveo/src/lib.rs
@@ -405,7 +405,7 @@ mod test_dkg_full {
// dkg.vss.remove(&removed_validator_addr); // TODO: Test whether it makes any difference
// Remember to remove one domain point too
- let mut domain_points = dkg.domain.elements().collect::>();
+ let mut domain_points = dkg.domain_points();
domain_points.pop().unwrap();
// Now, we're going to recover a new share at a random point,
@@ -557,7 +557,7 @@ mod test_dkg_full {
validator_keypairs.as_slice(),
);
- let domain_points = dkg.domain.elements().collect::>();
+ let domain_points = dkg.domain_points();
// Each participant prepares an update for each other participant
let share_updates = dkg
diff --git a/ferveo/src/pvss.rs b/ferveo/src/pvss.rs
index 7be4fac2..34695bd3 100644
--- a/ferveo/src/pvss.rs
+++ b/ferveo/src/pvss.rs
@@ -4,7 +4,7 @@ use ark_ec::{pairing::Pairing, AffineRepr, CurveGroup, Group};
use ark_ff::{Field, Zero};
use ark_poly::{
polynomial::univariate::DensePolynomial, DenseUVPolynomial,
- EvaluationDomain,
+ EvaluationDomain, Polynomial,
};
use ferveo_tdec::{
prepare_combine_simple, CiphertextHeader, DecryptionSharePrecomputed,
@@ -140,7 +140,13 @@ impl PubliclyVerifiableSS {
);
// Evaluations of the polynomial over the domain
- let evals = phi.0.evaluate_over_domain_by_ref(dkg.domain);
+ let evals = dkg
+ .domain_points()
+ .iter()
+ .map(|x| phi.0.evaluate(x))
+ .collect::>();
+ debug_assert_eq!(evals.len(), dkg.validators.len());
+
// commitment to coeffs, F_i
let coeffs = fast_multiexp(&phi.0.coeffs, dkg.pvss_params.g);
let shares = dkg
@@ -150,7 +156,7 @@ impl PubliclyVerifiableSS {
// ek_{i}^{eval_i}, i = validator index
fast_multiexp(
// &evals.evals[i..i] = &evals.evals[i]
- &[evals.evals[validator.share_index as usize]], // one share per validator
+ &[evals[validator.share_index as usize]], // one share per validator
validator.public_key.encryption_key.into_group(),
)[0]
})
From 6fd65bd506a0502da39ea4fa292e5fc1669abc27 Mon Sep 17 00:00:00 2001
From: Piotr Roslaniec
Date: Wed, 31 Jan 2024 13:08:56 +0100
Subject: [PATCH 6/8] test: update tests for dkgs with relaxed constraints
---
.../examples/server_api_precomputed.py | 12 +-
ferveo-python/examples/server_api_simple.py | 9 +-
ferveo-python/ferveo/__init__.py | 7 +-
ferveo-python/ferveo/__init__.pyi | 32 ++-
ferveo-python/test/test_ferveo.py | 94 ++++++--
ferveo-wasm/examples/node/src/main.test.ts | 173 +++++++-------
ferveo-wasm/tests/node.rs | 217 ++++++++++--------
ferveo/src/api.rs | 156 +++++++++----
ferveo/src/bindings_python.rs | 53 ++++-
ferveo/src/bindings_wasm.rs | 8 +-
ferveo/src/lib.rs | 10 +-
11 files changed, 474 insertions(+), 297 deletions(-)
diff --git a/ferveo-python/examples/server_api_precomputed.py b/ferveo-python/examples/server_api_precomputed.py
index 72263405..a9b98001 100644
--- a/ferveo-python/examples/server_api_precomputed.py
+++ b/ferveo-python/examples/server_api_precomputed.py
@@ -16,18 +16,16 @@ def gen_eth_addr(i: int) -> str:
tau = 1
shares_num = 4
+validators_num = shares_num + 2
# In precomputed variant, security threshold must be equal to shares_num
security_threshold = shares_num
-validator_keypairs = [Keypair.random() for _ in range(0, shares_num)]
+validator_keypairs = [Keypair.random() for _ in range(0, validators_num)]
validators = [
Validator(gen_eth_addr(i), keypair.public_key(), i)
for i, keypair in enumerate(validator_keypairs)
]
-# Validators must be sorted by their public key
-validators.sort(key=lambda v: v.address)
-
# Each validator holds their own DKG instance and generates a transcript every
# validator, including themselves
messages = []
@@ -52,11 +50,11 @@ def gen_eth_addr(i: int) -> str:
# Server can aggregate the transcripts
server_aggregate = dkg.aggregate_transcripts(messages)
-assert server_aggregate.verify(shares_num, messages)
+assert server_aggregate.verify(validators_num, messages)
# And the client can also aggregate and verify the transcripts
client_aggregate = AggregatedTranscript(messages)
-assert client_aggregate.verify(shares_num, messages)
+assert client_aggregate.verify(validators_num, messages)
# In the meantime, the client creates a ciphertext and decryption request
msg = "abc".encode()
@@ -76,7 +74,7 @@ def gen_eth_addr(i: int) -> str:
# We can also obtain the aggregated transcript from the side-channel (deserialize)
aggregate = AggregatedTranscript(messages)
- assert aggregate.verify(shares_num, messages)
+ assert aggregate.verify(validators_num, messages)
# The ciphertext is obtained from the client
diff --git a/ferveo-python/examples/server_api_simple.py b/ferveo-python/examples/server_api_simple.py
index 5fd2c8e5..44fb69c4 100644
--- a/ferveo-python/examples/server_api_simple.py
+++ b/ferveo-python/examples/server_api_simple.py
@@ -17,7 +17,8 @@ def gen_eth_addr(i: int) -> str:
tau = 1
security_threshold = 3
shares_num = 4
-validator_keypairs = [Keypair.random() for _ in range(0, shares_num)]
+validators_num = shares_num + 2
+validator_keypairs = [Keypair.random() for _ in range(0, validators_num)]
validators = [
Validator(gen_eth_addr(i), keypair.public_key(), i)
for i, keypair in enumerate(validator_keypairs)
@@ -52,11 +53,11 @@ def gen_eth_addr(i: int) -> str:
# Server can aggregate the transcripts
server_aggregate = dkg.aggregate_transcripts(messages)
-assert server_aggregate.verify(shares_num, messages)
+assert server_aggregate.verify(validators_num, messages)
# And the client can also aggregate and verify the transcripts
client_aggregate = AggregatedTranscript(messages)
-assert client_aggregate.verify(shares_num, messages)
+assert client_aggregate.verify(validators_num, messages)
# In the meantime, the client creates a ciphertext and decryption request
msg = "abc".encode()
@@ -79,7 +80,7 @@ def gen_eth_addr(i: int) -> str:
# We can also obtain the aggregated transcript from the side-channel (deserialize)
aggregate = AggregatedTranscript(messages)
- assert aggregate.verify(shares_num, messages)
+ assert aggregate.verify(validators_num, messages)
# The ciphertext is obtained from the client
diff --git a/ferveo-python/ferveo/__init__.py b/ferveo-python/ferveo/__init__.py
index 478628b1..fd906e54 100644
--- a/ferveo-python/ferveo/__init__.py
+++ b/ferveo-python/ferveo/__init__.py
@@ -30,8 +30,13 @@
InvalidDkgPublicKey,
InsufficientValidators,
InvalidTranscriptAggregate,
- ValidatorsNotSorted,
ValidatorPublicKeyMismatch,
SerializationError,
InvalidVariant,
+ InvalidDkgParameters,
+ InvalidDkgParametersForPrecomputedVariant,
+ InvalidShareIndex,
+ DuplicatedShareIndex,
+ NoTranscriptsToAggregate,
+ InvalidAggregateVerificationParameters,
)
diff --git a/ferveo-python/ferveo/__init__.pyi b/ferveo-python/ferveo/__init__.pyi
index a7c5abb6..ba7e7403 100644
--- a/ferveo-python/ferveo/__init__.pyi
+++ b/ferveo-python/ferveo/__init__.pyi
@@ -25,12 +25,14 @@ class FerveoPublicKey:
@final
class Validator:
- def __init__(self, address: str, public_key: FerveoPublicKey): ...
+ def __init__(self, address: str, public_key: FerveoPublicKey, share_index: int): ...
address: str
public_key: FerveoPublicKey
+ share_index: int
+
@final
class Transcript:
@staticmethod
@@ -104,7 +106,9 @@ class DecryptionSharePrecomputed:
@final
class AggregatedTranscript:
def __init__(self, messages: Sequence[ValidatorMessage]): ...
- def verify(self, shares_num: int, messages: Sequence[ValidatorMessage]) -> bool: ...
+ def verify(
+ self, validators_num: int, messages: Sequence[ValidatorMessage]
+ ) -> bool: ...
def create_decryption_share_simple(
self,
dkg: Dkg,
@@ -189,11 +193,29 @@ class InsufficientValidators(Exception):
class InvalidTranscriptAggregate(Exception):
pass
-class ValidatorsNotSorted(Exception):
- pass
-
class ValidatorPublicKeyMismatch(Exception):
pass
class SerializationError(Exception):
pass
+
+class InvalidVariant(Exception):
+ pass
+
+class InvalidDkgParameters(Exception):
+ pass
+
+class InvalidDkgParametersForPrecomputedVariant(Exception):
+ pass
+
+class InvalidShareIndex(Exception):
+ pass
+
+class DuplicatedShareIndex(Exception):
+ pass
+
+class NoTranscriptsToAggregate(Exception):
+ pass
+
+class InvalidAggregateVerificationParameters(Exception):
+ pass
diff --git a/ferveo-python/test/test_ferveo.py b/ferveo-python/test/test_ferveo.py
index b3496d3e..51af3867 100644
--- a/ferveo-python/test/test_ferveo.py
+++ b/ferveo-python/test/test_ferveo.py
@@ -5,6 +5,7 @@
combine_decryption_shares_simple,
combine_decryption_shares_precomputed,
decrypt_with_shared_secret,
+ AggregatedTranscript,
Keypair,
Validator,
ValidatorMessage,
@@ -37,18 +38,29 @@ def combine_shares_for_variant(v: FerveoVariant, decryption_shares):
raise ValueError("Unknown variant")
-def scenario_for_variant(variant: FerveoVariant, shares_num, threshold, shares_to_use):
+def scenario_for_variant(
+ variant: FerveoVariant, shares_num, validators_num, threshold, shares_to_use
+):
if variant not in [FerveoVariant.Simple, FerveoVariant.Precomputed]:
raise ValueError("Unknown variant: " + variant)
+ if validators_num < shares_num:
+ raise ValueError("validators_num must be >= shares_num")
+
+ if variant == FerveoVariant.Precomputed and shares_to_use != validators_num:
+ raise ValueError(
+ "In precomputed variant, shares_to_use must be equal to validators_num"
+ )
+
tau = 1
- validator_keypairs = [Keypair.random() for _ in range(0, shares_num)]
+ validator_keypairs = [Keypair.random() for _ in range(0, validators_num)]
validators = [
Validator(gen_eth_addr(i), keypair.public_key(), i)
for i, keypair in enumerate(validator_keypairs)
]
- validators.sort(key=lambda v: v.address)
+ # Each validator holds their own DKG instance and generates a transcript every
+ # validator, including themselves
messages = []
for sender in validators:
dkg = Dkg(
@@ -60,6 +72,7 @@ def scenario_for_variant(variant: FerveoVariant, shares_num, threshold, shares_t
)
messages.append(ValidatorMessage(sender, dkg.generate_transcript()))
+ # Both client and server should be able to verify the aggregated transcript
dkg = Dkg(
tau=tau,
shares_num=shares_num,
@@ -67,18 +80,23 @@ def scenario_for_variant(variant: FerveoVariant, shares_num, threshold, shares_t
validators=validators,
me=validators[0],
)
- pvss_aggregated = dkg.aggregate_transcripts(messages)
- assert pvss_aggregated.verify(shares_num, messages)
+ server_aggregate = dkg.aggregate_transcripts(messages)
+ assert server_aggregate.verify(validators_num, messages)
- dkg_pk_bytes = bytes(dkg.public_key)
- dkg_pk = DkgPublicKey.from_bytes(dkg_pk_bytes)
+ client_aggregate = AggregatedTranscript(messages)
+ assert client_aggregate.verify(validators_num, messages)
+ # Client creates a ciphertext and requests decryption shares from validators
msg = "abc".encode()
aad = "my-aad".encode()
- ciphertext = encrypt(msg, aad, dkg_pk)
+ ciphertext = encrypt(msg, aad, dkg.public_key)
+ # Having aggregated the transcripts, the validators can now create decryption shares
decryption_shares = []
for validator, validator_keypair in zip(validators, validator_keypairs):
+ assert validator.public_key == validator_keypair.public_key()
+ print("validator: ", validator.share_index)
+
dkg = Dkg(
tau=tau,
shares_num=shares_num,
@@ -87,15 +105,17 @@ def scenario_for_variant(variant: FerveoVariant, shares_num, threshold, shares_t
me=validator,
)
pvss_aggregated = dkg.aggregate_transcripts(messages)
- assert pvss_aggregated.verify(shares_num, messages)
+ assert pvss_aggregated.verify(validators_num, messages)
decryption_share = decryption_share_for_variant(variant, pvss_aggregated)(
dkg, ciphertext.header, aad, validator_keypair
)
decryption_shares.append(decryption_share)
- decryption_shares = decryption_shares[:shares_to_use]
+ # We are limiting the number of decryption shares to use for testing purposes
+ # decryption_shares = decryption_shares[:shares_to_use]
+ # Client combines the decryption shares and decrypts the ciphertext
shared_secret = combine_shares_for_variant(variant, decryption_shares)
if variant == FerveoVariant.Simple and len(decryption_shares) < threshold:
@@ -103,7 +123,7 @@ def scenario_for_variant(variant: FerveoVariant, shares_num, threshold, shares_t
decrypt_with_shared_secret(ciphertext, aad, shared_secret)
return
- if variant == FerveoVariant.Precomputed and len(decryption_shares) < shares_num:
+ if variant == FerveoVariant.Precomputed and len(decryption_shares) < threshold:
with pytest.raises(ThresholdEncryptionError):
decrypt_with_shared_secret(ciphertext, aad, shared_secret)
return
@@ -113,27 +133,55 @@ def scenario_for_variant(variant: FerveoVariant, shares_num, threshold, shares_t
def test_simple_tdec_has_enough_messages():
- scenario_for_variant(
- FerveoVariant.Simple, shares_num=4, threshold=3, shares_to_use=3
- )
+ shares_num = 4
+ threshold = shares_num - 1
+ for validators_num in [shares_num, shares_num + 2]:
+ scenario_for_variant(
+ FerveoVariant.Simple,
+ shares_num=shares_num,
+ validators_num=validators_num,
+ threshold=threshold,
+ shares_to_use=threshold,
+ )
def test_simple_tdec_doesnt_have_enough_messages():
- scenario_for_variant(
- FerveoVariant.Simple, shares_num=4, threshold=3, shares_to_use=2
- )
+ shares_num = 4
+ threshold = shares_num - 1
+ for validators_num in [shares_num, shares_num + 2]:
+ scenario_for_variant(
+ FerveoVariant.Simple,
+ shares_num=shares_num,
+ validators_num=validators_num,
+ threshold=threshold,
+ shares_to_use=validators_num - 1,
+ )
def test_precomputed_tdec_has_enough_messages():
- scenario_for_variant(
- FerveoVariant.Precomputed, shares_num=4, threshold=4, shares_to_use=4
- )
+ shares_num = 4
+ threshold = shares_num # in precomputed variant, we need all shares
+ for validators_num in [shares_num, shares_num + 2]:
+ scenario_for_variant(
+ FerveoVariant.Precomputed,
+ shares_num=shares_num,
+ validators_num=validators_num,
+ threshold=threshold,
+ shares_to_use=validators_num,
+ )
def test_precomputed_tdec_doesnt_have_enough_messages():
- scenario_for_variant(
- FerveoVariant.Precomputed, shares_num=4, threshold=4, shares_to_use=3
- )
+ shares_num = 4
+ threshold = shares_num # in precomputed variant, we need all shares
+ for validators_num in [shares_num, shares_num + 2]:
+ scenario_for_variant(
+ FerveoVariant.Simple,
+ shares_num=shares_num,
+ validators_num=validators_num,
+ threshold=threshold,
+ shares_to_use=threshold - 1,
+ )
PARAMS = [
diff --git a/ferveo-wasm/examples/node/src/main.test.ts b/ferveo-wasm/examples/node/src/main.test.ts
index 5814a53d..00da665b 100644
--- a/ferveo-wasm/examples/node/src/main.test.ts
+++ b/ferveo-wasm/examples/node/src/main.test.ts
@@ -22,11 +22,16 @@ const genEthAddr = (i: number) => {
return EthereumAddress.fromString(ethAddr);
};
-const tau = 1;
-function setupTest(sharesNum :number, threshold: number) {
+const TAU = 1;
+
+function setupTest(
+ sharesNum: number,
+ validatorsNum: number,
+ threshold: number
+) {
const validatorKeypairs: Keypair[] = [];
const validators: Validator[] = [];
- for (let i = 0; i < sharesNum; i++) {
+ for (let i = 0; i < validatorsNum; i++) {
const keypair = Keypair.random();
validatorKeypairs.push(keypair);
const validator = new Validator(genEthAddr(i), keypair.publicKey, i);
@@ -37,7 +42,7 @@ function setupTest(sharesNum :number, threshold: number) {
// validator, including themselves
const messages: ValidatorMessage[] = [];
validators.forEach((sender) => {
- const dkg = new Dkg(tau, sharesNum, threshold, validators, sender);
+ const dkg = new Dkg(TAU, sharesNum, threshold, validators, sender);
const transcript = dkg.generateTranscript();
const message = new ValidatorMessage(sender, transcript);
messages.push(message);
@@ -45,16 +50,16 @@ function setupTest(sharesNum :number, threshold: number) {
// Now that every validator holds a dkg instance and a transcript for every other validator,
// every validator can aggregate the transcripts
- const dkg = new Dkg(tau, sharesNum, threshold, validators, validators[0]);
+ const dkg = new Dkg(TAU, sharesNum, threshold, validators, validators[0]);
const serverAggregate = dkg.aggregateTranscript(messages);
- expect(serverAggregate.verify(sharesNum, messages)).toBe(true);
+ expect(serverAggregate.verify(validatorsNum, messages)).toBe(true);
// Client can also aggregate the transcripts and verify them
const clientAggregate = new AggregatedTranscript(messages);
- expect(clientAggregate.verify(sharesNum, messages)).toBe(true);
+ expect(clientAggregate.verify(validatorsNum, messages)).toBe(true);
- // In the meantime, the client creates a ciphertext and decryption request
+ // Client creates a ciphertext and requests decryption shares from validators
const msg = Buffer.from("my-msg");
const aad = Buffer.from("my-aad");
const ciphertext = ferveoEncrypt(msg, aad, dkg.publicKey());
@@ -73,94 +78,78 @@ function setupTest(sharesNum :number, threshold: number) {
// 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 {
- validatorKeypairs,
- validators,
- messages,
- msg,
- aad,
- ciphertext,
- } = setupTest(sharesNum, threshold);
-
- // Having aggregated the transcripts, the validators can now create decryption shares
- const decryptionShares: DecryptionShareSimple[] = [];
- zip(validators, validatorKeypairs).forEach(([validator, keypair]) => {
- expect(validator.publicKey.equals(keypair.publicKey)).toBe(true);
-
- const dkg = new Dkg(tau, sharesNum, threshold, validators, validator);
- const aggregate = dkg.aggregateTranscript(messages);
- const isValid = aggregate.verify(sharesNum, messages);
- expect(isValid).toBe(true);
-
- const decryptionShare = aggregate.createDecryptionShareSimple(
- dkg,
- ciphertext.header,
- aad,
- keypair
- );
- decryptionShares.push(decryptionShare);
+ const sharesNum = 4;
+ const threshold = sharesNum - 1;
+ [sharesNum, sharesNum + 2].forEach((validatorsNum) => {
+ const { validatorKeypairs, validators, messages, msg, aad, ciphertext } =
+ setupTest(sharesNum, validatorsNum, threshold);
+
+ // Having aggregated the transcripts, the validators can now create decryption shares
+ const decryptionShares: DecryptionShareSimple[] = [];
+ zip(validators, validatorKeypairs).forEach(([validator, keypair]) => {
+ expect(validator.publicKey.equals(keypair.publicKey)).toBe(true);
+
+ const dkg = new Dkg(TAU, sharesNum, threshold, validators, validator);
+ const aggregate = dkg.aggregateTranscript(messages);
+ const isValid = aggregate.verify(validatorsNum, messages);
+ expect(isValid).toBe(true);
+
+ const decryptionShare = aggregate.createDecryptionShareSimple(
+ dkg,
+ ciphertext.header,
+ aad,
+ keypair
+ );
+ decryptionShares.push(decryptionShare);
+ });
+
+ // Now, the decryption share can be used to decrypt the ciphertext
+ // This part is in the client API
+
+ const sharedSecret = combineDecryptionSharesSimple(decryptionShares);
+
+ // The client should have access to the public parameters of the DKG
+
+ const plaintext = decryptWithSharedSecret(ciphertext, aad, sharedSecret);
+ expect(Buffer.from(plaintext)).toEqual(msg);
});
-
- // Now, the decryption share can be used to decrypt the ciphertext
- // This part is in the client API
-
- const sharedSecret = combineDecryptionSharesSimple(
- decryptionShares,
- );
-
- // The client should have access to the public parameters of the DKG
-
- const plaintext = decryptWithSharedSecret(
- ciphertext,
- aad,
- sharedSecret,
- );
- expect(Buffer.from(plaintext)).toEqual(msg);
});
it("precomputed tdec variant", () => {
- const sharesNum = 4;
- const threshold = sharesNum; // threshold is equal to sharesNum in precomputed variant
- const {
- validatorKeypairs,
- validators,
- messages,
- msg,
- aad,
- ciphertext,
- } = setupTest(sharesNum, threshold);
-
- // Having aggregated the transcripts, the validators can now create decryption shares
- const decryptionShares: DecryptionSharePrecomputed[] = [];
- zip(validators, validatorKeypairs).forEach(([validator, keypair]) => {
- const dkg = new Dkg(tau, sharesNum, threshold, validators, validator);
- const aggregate = dkg.aggregateTranscript(messages);
- const isValid = aggregate.verify(sharesNum, messages);
- expect(isValid).toBe(true);
-
- const decryptionShare = aggregate.createDecryptionSharePrecomputed(
- dkg,
- ciphertext.header,
- aad,
- keypair
- );
- decryptionShares.push(decryptionShare);
+ const sharesNum = 4;
+ const threshold = sharesNum; // threshold is equal to sharesNum in precomputed variant
+ [sharesNum, sharesNum + 2].forEach((validatorsNum) => {
+ const { validatorKeypairs, validators, messages, msg, aad, ciphertext } =
+ setupTest(sharesNum, validatorsNum, threshold);
+
+ // Having aggregated the transcripts, the validators can now create decryption shares
+ const decryptionShares: DecryptionSharePrecomputed[] = [];
+ zip(validators, validatorKeypairs).forEach(([validator, keypair]) => {
+ expect(validator.publicKey.equals(keypair.publicKey)).toBe(true);
+
+ const dkg = new Dkg(TAU, sharesNum, threshold, validators, validator);
+ const aggregate = dkg.aggregateTranscript(messages);
+ const isValid = aggregate.verify(validatorsNum, messages);
+ expect(isValid).toBe(true);
+
+ const decryptionShare = aggregate.createDecryptionSharePrecomputed(
+ dkg,
+ ciphertext.header,
+ aad,
+ keypair
+ );
+ decryptionShares.push(decryptionShare);
+ });
+
+ // Now, the decryption share can be used to decrypt the ciphertext
+ // This part is in the client API
+
+ const sharedSecret = combineDecryptionSharesPrecomputed(decryptionShares);
+
+ // The client should have access to the public parameters of the DKG
+
+ const plaintext = decryptWithSharedSecret(ciphertext, aad, sharedSecret);
+ expect(Buffer.from(plaintext)).toEqual(msg);
});
-
- // Now, the decryption share can be used to decrypt the ciphertext
- // This part is in the client API
-
- const sharedSecret = combineDecryptionSharesPrecomputed(decryptionShares);
-
- // The client should have access to the public parameters of the DKG
-
- const plaintext = decryptWithSharedSecret(
- ciphertext,
- aad,
- sharedSecret,
- );
- expect(Buffer.from(plaintext)).toEqual(msg);
});
});
diff --git a/ferveo-wasm/tests/node.rs b/ferveo-wasm/tests/node.rs
index bae5750b..7b35efa5 100644
--- a/ferveo-wasm/tests/node.rs
+++ b/ferveo-wasm/tests/node.rs
@@ -18,8 +18,12 @@ type TestSetup = (
const TAU: u32 = 0;
-fn setup_dkg(shares_num: u32, security_threshold: u32) -> TestSetup {
- let validator_keypairs = (0..shares_num as usize)
+fn setup_dkg(
+ shares_num: u32,
+ validators_num: u32,
+ security_threshold: u32,
+) -> TestSetup {
+ let validator_keypairs = (0..validators_num as usize)
.map(gen_keypair)
.collect::>();
let validators = validator_keypairs
@@ -32,7 +36,7 @@ fn setup_dkg(shares_num: u32, security_threshold: u32) -> TestSetup {
// Each validator holds their own DKG instance and generates a transcript every
// validator, including themselves
let messages = validators.iter().map(|sender| {
- let mut dkg = Dkg::new(
+ let mut validator_dkg = Dkg::new(
TAU,
shares_num,
security_threshold,
@@ -40,7 +44,7 @@ fn setup_dkg(shares_num: u32, security_threshold: u32) -> TestSetup {
sender,
)
.unwrap();
- let transcript = dkg.generate_transcript().unwrap();
+ let transcript = validator_dkg.generate_transcript().unwrap();
ValidatorMessage::new(sender, &transcript).unwrap()
});
@@ -61,12 +65,16 @@ fn setup_dkg(shares_num: u32, security_threshold: u32) -> TestSetup {
// Server can aggregate the transcripts and verify them
let server_aggregate = dkg.aggregate_transcripts(&messages_js).unwrap();
- let is_valid = server_aggregate.verify(shares_num, &messages_js).unwrap();
+ let is_valid = server_aggregate
+ .verify(validators_num, &messages_js)
+ .unwrap();
assert!(is_valid);
// Client can also aggregate the transcripts and verify them
let client_aggregate = AggregatedTranscript::new(&messages_js).unwrap();
- let is_valid = client_aggregate.verify(shares_num, &messages_js).unwrap();
+ let is_valid = client_aggregate
+ .verify(validators_num, &messages_js)
+ .unwrap();
assert!(is_valid);
// In the meantime, the client creates a ciphertext and decryption request
@@ -88,105 +96,116 @@ fn setup_dkg(shares_num: u32, security_threshold: u32) -> TestSetup {
#[wasm_bindgen_test]
fn tdec_simple() {
let shares_num = 16;
- let security_threshold = 10;
- let (
- validator_keypairs,
- validators,
- validators_js,
- messages_js,
- msg,
- aad,
- ciphertext,
- ) = 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,
- shares_num,
- security_threshold,
- &validators_js,
- &validator,
- )
- .unwrap();
- let aggregate = dkg.aggregate_transcripts(&messages_js).unwrap();
- let is_valid = aggregate.verify(shares_num, &messages_js).unwrap();
- assert!(is_valid);
-
- aggregate
- .create_decryption_share_simple(
- &dkg,
- &ciphertext.header().unwrap(),
- &aad,
- &keypair,
+ let security_threshold = shares_num / 2;
+ for validators_num in [shares_num, shares_num + 2] {
+ let (
+ validator_keypairs,
+ validators,
+ validators_js,
+ messages_js,
+ msg,
+ aad,
+ ciphertext,
+ ) = setup_dkg(shares_num, validators_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,
+ shares_num,
+ security_threshold,
+ &validators_js,
+ &validator,
)
- .unwrap()
- })
- .collect::>();
- let decryption_shares_js = into_js_array(decryption_shares);
-
- // Now, the decryption share can be used to decrypt the ciphertext
- // This part is in the client API
-
- let shared_secret =
- combine_decryption_shares_simple(&decryption_shares_js).unwrap();
-
- // The client should have access to the public parameters of the DKG
- let plaintext =
- decrypt_with_shared_secret(&ciphertext, &aad, &shared_secret).unwrap();
- assert_eq!(msg, plaintext);
+ .unwrap();
+ let aggregate =
+ dkg.aggregate_transcripts(&messages_js).unwrap();
+ let is_valid =
+ aggregate.verify(validators_num, &messages_js).unwrap();
+ assert!(is_valid);
+
+ aggregate
+ .create_decryption_share_simple(
+ &dkg,
+ &ciphertext.header().unwrap(),
+ &aad,
+ &keypair,
+ )
+ .unwrap()
+ })
+ .collect::>();
+ let decryption_shares_js = into_js_array(decryption_shares);
+
+ // Now, the decryption share can be used to decrypt the ciphertext
+ // This part is in the client API
+
+ let shared_secret =
+ combine_decryption_shares_simple(&decryption_shares_js).unwrap();
+
+ // The client should have access to the public parameters of the DKG
+ let plaintext =
+ decrypt_with_shared_secret(&ciphertext, &aad, &shared_secret)
+ .unwrap();
+ assert_eq!(msg, plaintext);
+ }
}
#[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 (
- validator_keypairs,
- validators,
- validators_js,
- messages_js,
- msg,
- aad,
- ciphertext,
- ) = 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,
- shares_num,
- security_threshold,
- &validators_js,
- &validator,
- )
- .unwrap();
- let aggregate = dkg.aggregate_transcripts(&messages_js).unwrap();
- let is_valid = aggregate.verify(shares_num, &messages_js).unwrap();
- assert!(is_valid);
-
- aggregate
- .create_decryption_share_precomputed(
- &dkg,
- &ciphertext.header().unwrap(),
- &aad,
- &keypair,
+ for validators_num in [shares_num, shares_num + 2] {
+ let (
+ validator_keypairs,
+ validators,
+ validators_js,
+ messages_js,
+ msg,
+ aad,
+ ciphertext,
+ ) = setup_dkg(shares_num, validators_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,
+ shares_num,
+ security_threshold,
+ &validators_js,
+ &validator,
)
- .unwrap()
- })
- .collect::>();
- let decryption_shares_js = into_js_array(decryption_shares);
-
- // Now, the decryption share can be used to decrypt the ciphertext
- // This part is in the client API
-
- let shared_secret =
- combine_decryption_shares_precomputed(&decryption_shares_js).unwrap();
-
- // The client should have access to the public parameters of the DKG
- let plaintext =
- decrypt_with_shared_secret(&ciphertext, &aad, &shared_secret).unwrap();
- assert_eq!(msg, plaintext);
+ .unwrap();
+ let aggregate =
+ dkg.aggregate_transcripts(&messages_js).unwrap();
+ let is_valid =
+ aggregate.verify(validators_num, &messages_js).unwrap();
+ assert!(is_valid);
+
+ aggregate
+ .create_decryption_share_precomputed(
+ &dkg,
+ &ciphertext.header().unwrap(),
+ &aad,
+ &keypair,
+ )
+ .unwrap()
+ })
+ .collect::>();
+ let decryption_shares_js = into_js_array(decryption_shares);
+
+ // Now, the decryption share can be used to decrypt the ciphertext
+ // This part is in the client API
+
+ let shared_secret =
+ combine_decryption_shares_precomputed(&decryption_shares_js)
+ .unwrap();
+
+ // The client should have access to the public parameters of the DKG
+ let plaintext =
+ decrypt_with_shared_secret(&ciphertext, &aad, &shared_secret)
+ .unwrap();
+ assert_eq!(msg, plaintext);
+ }
}
diff --git a/ferveo/src/api.rs b/ferveo/src/api.rs
index 80ab58d6..caa0d9b4 100644
--- a/ferveo/src/api.rs
+++ b/ferveo/src/api.rs
@@ -286,12 +286,20 @@ impl AggregatedTranscript {
pub fn verify(
&self,
- shares_num: u32,
+ validators_num: u32,
messages: &[ValidatorMessage],
) -> Result {
+ if validators_num < messages.len() as u32 {
+ return Err(Error::InvalidAggregateVerificationParameters(
+ validators_num,
+ messages.len() as u32,
+ ));
+ }
+
let pvss_params = PubliclyVerifiableParams::::default();
- let domain = GeneralEvaluationDomain::::new(shares_num as usize)
- .expect("Unable to construct an evaluation domain");
+ let domain =
+ GeneralEvaluationDomain::::new(validators_num as usize)
+ .expect("Unable to construct an evaluation domain");
let is_valid_optimistic = self.0.verify_optimistic();
if !is_valid_optimistic {
@@ -428,8 +436,9 @@ mod test_ferveo_api {
tau: u32,
security_threshold: u32,
shares_num: u32,
+ validators_num: u32,
) -> TestInputs {
- let validator_keypairs = gen_keypairs(shares_num);
+ let validator_keypairs = gen_keypairs(validators_num);
let validators = validator_keypairs
.iter()
.enumerate()
@@ -469,16 +478,22 @@ mod test_ferveo_api {
assert_eq!(dkg_pk, deserialized);
}
- #[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) {
+ #[test_case(4, 4; "number of shares (validators) is a power of 2")]
+ #[test_case(7, 7; "number of shares (validators) is not a power of 2")]
+ #[test_case(4, 6; "number of validators greater than the number of shares")]
+ fn test_server_api_tdec_precomputed(shares_num: u32, validators_num: u32) {
let rng = &mut StdRng::seed_from_u64(0);
// In precomputed variant, the security threshold is equal to the number of shares
let security_threshold = shares_num;
- let (messages, validators, validator_keypairs) =
- make_test_inputs(rng, TAU, security_threshold, shares_num);
+ let (messages, validators, validator_keypairs) = make_test_inputs(
+ rng,
+ TAU,
+ security_threshold,
+ shares_num,
+ validators_num,
+ );
// Now that every validator holds a dkg instance and a transcript for every other validator,
// every validator can aggregate the transcripts
@@ -488,7 +503,7 @@ mod test_ferveo_api {
.unwrap();
let pvss_aggregated = dkg.aggregate_transcripts(&messages).unwrap();
- assert!(pvss_aggregated.verify(shares_num, &messages).unwrap());
+ assert!(pvss_aggregated.verify(validators_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();
@@ -511,7 +526,9 @@ mod test_ferveo_api {
)
.unwrap();
let aggregate = dkg.aggregate_transcripts(&messages).unwrap();
- assert!(pvss_aggregated.verify(shares_num, &messages).unwrap());
+ assert!(pvss_aggregated
+ .verify(validators_num, &messages)
+ .unwrap());
// And then each validator creates their own decryption share
aggregate
@@ -551,15 +568,21 @@ mod test_ferveo_api {
assert!(result.is_err());
}
- #[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) {
+ #[test_case(4, 4; "number of shares (validators) is a power of 2")]
+ #[test_case(7, 7; "number of shares (validators) is not a power of 2")]
+ #[test_case(4, 6; "number of validators greater than the number of shares")]
+ fn test_server_api_tdec_simple(shares_num: u32, validators_num: u32) {
let rng = &mut StdRng::seed_from_u64(0);
let security_threshold = shares_num / 2 + 1;
- let (messages, validators, validator_keypairs) =
- make_test_inputs(rng, TAU, security_threshold, shares_num);
+ let (messages, validators, validator_keypairs) = make_test_inputs(
+ rng,
+ TAU,
+ security_threshold,
+ shares_num,
+ validators_num,
+ );
// Now that every validator holds a dkg instance and a transcript for every other validator,
// every validator can aggregate the transcripts
@@ -573,7 +596,7 @@ mod test_ferveo_api {
.unwrap();
let pvss_aggregated = dkg.aggregate_transcripts(&messages).unwrap();
- assert!(pvss_aggregated.verify(shares_num, &messages).unwrap());
+ assert!(pvss_aggregated.verify(validators_num, &messages).unwrap());
// At this point, any given validator should be able to provide a DKG public key
let public_key = dkg.public_key();
@@ -595,7 +618,7 @@ mod test_ferveo_api {
)
.unwrap();
let aggregate = dkg.aggregate_transcripts(&messages).unwrap();
- assert!(aggregate.verify(shares_num, &messages).unwrap());
+ assert!(aggregate.verify(validators_num, &messages).unwrap());
aggregate
.create_decryption_share_simple(
&dkg,
@@ -635,104 +658,137 @@ mod test_ferveo_api {
// implementation for aggregation and aggregate verification.
// Here, we focus on testing user-facing APIs for server and client users.
- #[test]
- fn server_side_local_verification() {
+ #[test_case(4, 4; "number of shares (validators) is a power of 2")]
+ #[test_case(7, 7; "number of shares (validators) is not a power of 2")]
+ #[test_case(4, 6; "number of validators greater than the number of shares")]
+ fn server_side_local_verification(shares_num: u32, validators_num: u32) {
let rng = &mut StdRng::seed_from_u64(0);
+ let security_threshold = shares_num / 2 + 1;
- let (messages, validators, _) =
- make_test_inputs(rng, TAU, SECURITY_THRESHOLD, SHARES_NUM);
+ let (messages, validators, _) = make_test_inputs(
+ rng,
+ TAU,
+ security_threshold,
+ shares_num,
+ validators_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();
- assert!(local_aggregate
- .verify(dkg.0.dkg_params.shares_num(), &messages)
- .is_ok());
+ let good_aggregate = dkg.aggregate_transcripts(&messages).unwrap();
+ assert!(good_aggregate.verify(validators_num, &messages).is_ok());
// Test negative cases
// Notice that the dkg instance is mutable, so we need to get a fresh one
// for every test case
+ // Should fail if the number of validators is less than the number of messages
+ assert!(good_aggregate
+ .verify(messages.len() as u32 - 1, &messages)
+ .is_err());
+
// Should fail if no transcripts are provided
let mut dkg =
- Dkg::new(TAU, SHARES_NUM, SECURITY_THRESHOLD, &validators, &me)
+ Dkg::new(TAU, shares_num, security_threshold, &validators, &me)
.unwrap();
let result = dkg.aggregate_transcripts(&[]);
assert!(result.is_err());
// Not enough transcripts
let mut dkg =
- Dkg::new(TAU, SHARES_NUM, SECURITY_THRESHOLD, &validators, &me)
+ Dkg::new(TAU, shares_num, security_threshold, &validators, &me)
.unwrap();
- let not_enough_messages = &messages[..SECURITY_THRESHOLD as usize - 1];
- 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 =
dkg.aggregate_transcripts(not_enough_messages).unwrap();
- let result = insufficient_aggregate.verify(SHARES_NUM, &messages);
+ let result = insufficient_aggregate.verify(validators_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 mut dkg =
- Dkg::new(TAU, SHARES_NUM, SECURITY_THRESHOLD, &validators, &me)
+ Dkg::new(TAU, shares_num, security_threshold, &validators, &me)
.unwrap();
- let (bad_messages, _, _) =
- make_test_inputs(rng, TAU, SECURITY_THRESHOLD, SHARES_NUM);
+ let (bad_messages, _, _) = make_test_inputs(
+ rng,
+ TAU,
+ security_threshold,
+ shares_num,
+ validators_num,
+ );
let mixed_messages = [&messages[..2], &bad_messages[..1]].concat();
let bad_aggregate = dkg.aggregate_transcripts(&mixed_messages).unwrap();
- let result = bad_aggregate.verify(SHARES_NUM, &messages);
+ let result = bad_aggregate.verify(validators_num, &messages);
assert!(result.is_err());
}
- #[test]
- fn client_side_local_verification() {
+ #[test_case(4, 4; "number of shares (validators) is a power of 2")]
+ #[test_case(7, 7; "number of shares (validators) is not a power of 2")]
+ #[test_case(4, 6; "number of validators greater than the number of shares")]
+ fn client_side_local_verification(shares_num: u32, validators_num: u32) {
let rng = &mut StdRng::seed_from_u64(0);
+ let security_threshold = shares_num / 2 + 1;
- let (messages, _, _) =
- make_test_inputs(rng, TAU, SECURITY_THRESHOLD, SHARES_NUM);
+ let (messages, _, _) = make_test_inputs(
+ rng,
+ TAU,
+ security_threshold,
+ shares_num,
+ validators_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).unwrap();
+ let good_aggregate = AggregatedTranscript::new(messages).unwrap();
// We are separating the verification from the aggregation since the client may fetch
// 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 = good_aggregate.verify(validators_num, messages);
assert!(result.is_ok());
assert!(result.unwrap());
// Test negative cases
+ // Should fail if the number of validators is less than the number of messages
+ assert!(good_aggregate
+ .verify(messages.len() as u32 - 1, messages)
+ .is_err());
+
// Should fail if no transcripts are provided
let result = AggregatedTranscript::new(&[]);
assert!(result.is_err());
// Not enough transcripts
- let not_enough_messages = &messages[..SECURITY_THRESHOLD as usize - 1];
- 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).unwrap();
- let result = insufficient_aggregate.verify(SHARES_NUM, messages);
+ let result = insufficient_aggregate.verify(validators_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);
+ let (bad_messages, _, _) = make_test_inputs(
+ rng,
+ TAU,
+ security_threshold,
+ shares_num,
+ validators_num,
+ );
let mixed_messages = [&messages[..2], &bad_messages[..1]].concat();
let bad_aggregate = AggregatedTranscript::new(&mixed_messages).unwrap();
- let result = bad_aggregate.verify(SHARES_NUM, messages);
+ let result = bad_aggregate.verify(validators_num, messages);
assert!(result.is_err());
}
}
diff --git a/ferveo/src/bindings_python.rs b/ferveo/src/bindings_python.rs
index bd29dc0d..fe534af8 100644
--- a/ferveo/src/bindings_python.rs
+++ b/ferveo/src/bindings_python.rs
@@ -104,7 +104,7 @@ impl From for PyErr {
))
}
Error::InvalidDkgParametersForPrecomputedVariant(shares_num, security_threshold) => {
- InvalidDkgParameters::new_err(format!(
+ InvalidDkgParametersForPrecomputedVariant::new_err(format!(
"shares_num: {shares_num}, security_threshold: {security_threshold}"
))
}
@@ -116,6 +116,11 @@ impl From for PyErr {
Error::NoTranscriptsToAggregate => {
NoTranscriptsToAggregate::new_err("")
}
+ Error::InvalidAggregateVerificationParameters(validators_num, messages_num) => {
+ InvalidAggregateVerificationParameters::new_err(format!(
+ "validators_num: {validators_num}, messages_num: {messages_num}"
+ ))
+ }
},
_ => default(),
}
@@ -145,15 +150,24 @@ create_exception!(exceptions, InsufficientTranscriptsForAggregate, PyException);
create_exception!(exceptions, InvalidDkgPublicKey, PyValueError);
create_exception!(exceptions, InsufficientValidators, PyValueError);
create_exception!(exceptions, InvalidTranscriptAggregate, PyValueError);
-create_exception!(exceptions, ValidatorsNotSorted, PyValueError);
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,
+ InvalidDkgParametersForPrecomputedVariant,
+ PyValueError
+);
create_exception!(exceptions, InvalidShareIndex, PyValueError);
create_exception!(exceptions, DuplicatedShareIndex, PyValueError);
create_exception!(exceptions, NoTranscriptsToAggregate, PyValueError);
+create_exception!(
+ exceptions,
+ InvalidAggregateVerificationParameters,
+ PyValueError
+);
fn from_py_bytes(bytes: &[u8]) -> PyResult {
T::from_bytes(bytes)
@@ -421,6 +435,11 @@ impl Validator {
pub fn public_key(&self) -> FerveoPublicKey {
FerveoPublicKey(self.0.public_key)
}
+
+ #[getter]
+ pub fn share_index(&self) -> u32 {
+ self.0.share_index
+ }
}
#[pyclass(module = "ferveo")]
@@ -595,14 +614,14 @@ impl AggregatedTranscript {
pub fn verify(
&self,
- shares_num: u32,
+ validators_num: u32,
messages: Vec,
) -> PyResult {
let messages: Vec<_> =
messages.into_iter().map(|vm| vm.to_inner()).collect();
let is_valid = self
.0
- .verify(shares_num, &messages)
+ .verify(validators_num, &messages)
.map_err(FerveoPythonError::FerveoError)?;
Ok(is_valid)
}
@@ -736,13 +755,33 @@ pub fn make_ferveo_py_module(py: Python<'_>, m: &PyModule) -> PyResult<()> {
"InvalidTranscriptAggregate",
py.get_type::(),
)?;
- m.add("ValidatorsNotSorted", py.get_type::())?;
m.add(
"ValidatorPublicKeyMismatch",
py.get_type::(),
)?;
m.add("SerializationError", py.get_type::())?;
m.add("InvalidVariant", py.get_type::())?;
+ m.add(
+ "InvalidDkgParameters",
+ py.get_type::(),
+ )?;
+ m.add(
+ "InvalidDkgParametersForPrecomputedVariant",
+ py.get_type::(),
+ )?;
+ m.add("InvalidShareIndex", py.get_type::())?;
+ m.add(
+ "DuplicatedShareIndex",
+ py.get_type::(),
+ )?;
+ m.add(
+ "NoTranscriptsToAggregate",
+ py.get_type::(),
+ )?;
+ m.add(
+ "InvalidAggregateVerificationParameters",
+ py.get_type::(),
+ )?;
Ok(())
}
@@ -832,7 +871,6 @@ mod test_ferveo_python {
let messages = messages[..security_threshold as usize].to_vec();
let pvss_aggregated =
dkg.aggregate_transcripts(messages.clone()).unwrap();
- // TODO: Redo how verification API works;
assert!(pvss_aggregated
.verify(validators_num, messages.clone())
.unwrap());
@@ -859,7 +897,6 @@ mod test_ferveo_python {
let aggregate = validator_dkg
.aggregate_transcripts(messages.clone())
.unwrap();
- // TODO: Redo how verification API works;
assert!(pvss_aggregated
.verify(validators_num, messages.clone())
.is_ok());
@@ -912,7 +949,6 @@ mod test_ferveo_python {
let messages = messages[..security_threshold as usize].to_vec();
let pvss_aggregated =
dkg.aggregate_transcripts(messages.clone()).unwrap();
- // TODO: Redo how verification API works;
assert!(pvss_aggregated
.verify(validators_num, messages.clone())
.unwrap());
@@ -940,7 +976,6 @@ mod test_ferveo_python {
.aggregate_transcripts(messages.clone())
.unwrap();
- // TODO: Redo how verification API works;
assert!(aggregate
.verify(validators_num, messages.clone())
.unwrap());
diff --git a/ferveo/src/bindings_wasm.rs b/ferveo/src/bindings_wasm.rs
index 1396de13..07e22e3f 100644
--- a/ferveo/src/bindings_wasm.rs
+++ b/ferveo/src/bindings_wasm.rs
@@ -515,13 +515,15 @@ impl AggregatedTranscript {
#[wasm_bindgen]
pub fn verify(
&self,
- shares_num: u32,
+ validators_num: u32,
messages: &ValidatorMessageArray,
) -> JsResult {
set_panic_hook();
let messages = unwrap_messages_js(messages)?;
- let is_valid =
- self.0.verify(shares_num, &messages).map_err(map_js_err)?;
+ let is_valid = self
+ .0
+ .verify(validators_num, &messages)
+ .map_err(map_js_err)?;
Ok(is_valid)
}
diff --git a/ferveo/src/lib.rs b/ferveo/src/lib.rs
index d5f1fb11..f9d6c1a5 100644
--- a/ferveo/src/lib.rs
+++ b/ferveo/src/lib.rs
@@ -117,6 +117,10 @@ pub enum Error {
/// Creating a transcript aggregate requires at least one transcript
#[error("No transcripts to aggregate")]
NoTranscriptsToAggregate,
+
+ /// The number of messages may not be greater than the number of validators
+ #[error("Invalid aggregate verification parameters: number of validators {0}, number of messages: {1}")]
+ InvalidAggregateVerificationParameters(u32, u32),
}
pub type Result = std::result::Result;
@@ -557,15 +561,13 @@ mod test_dkg_full {
validator_keypairs.as_slice(),
);
- let domain_points = dkg.domain_points();
-
// Each participant prepares an update for each other participant
let share_updates = dkg
.validators
.keys()
.map(|v_addr| {
let deltas_i = prepare_share_updates_for_refresh::(
- &domain_points,
+ &dkg.domain_points(),
&dkg.pvss_params.h.into_affine(),
dkg.dkg_params.security_threshold() as usize,
rng,
@@ -628,7 +630,7 @@ mod test_dkg_full {
.collect();
let lagrange = ferveo_tdec::prepare_combine_simple::(
- &domain_points[..SECURITY_THRESHOLD as usize],
+ &dkg.domain_points()[..SECURITY_THRESHOLD as usize],
);
let new_shared_secret = ferveo_tdec::share_combine_simple::(
&decryption_shares[..SECURITY_THRESHOLD as usize],
From 61d6e641ecaf81c3bbc2febec0aa5d73cccfd8b7 Mon Sep 17 00:00:00 2001
From: Piotr Roslaniec
Date: Wed, 21 Feb 2024 15:21:08 +0100
Subject: [PATCH 7/8] apply pr suggestions
---
ferveo/src/api.rs | 55 ++++++++++++++++++++++++++++++----------------
ferveo/src/pvss.rs | 5 +++--
2 files changed, 39 insertions(+), 21 deletions(-)
diff --git a/ferveo/src/api.rs b/ferveo/src/api.rs
index caa0d9b4..7328515e 100644
--- a/ferveo/src/api.rs
+++ b/ferveo/src/api.rs
@@ -272,7 +272,9 @@ fn make_pvss_map(messages: &[ValidatorMessage]) -> PVSSMap {
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
-pub struct AggregatedTranscript(PubliclyVerifiableSS);
+pub struct AggregatedTranscript(
+ pub(crate) PubliclyVerifiableSS,
+);
impl AggregatedTranscript {
pub fn new(messages: &[ValidatorMessage]) -> Result {
@@ -689,16 +691,19 @@ mod test_ferveo_api {
// for every test case
// Should fail if the number of validators is less than the number of messages
- assert!(good_aggregate
- .verify(messages.len() as u32 - 1, &messages)
- .is_err());
+ assert!(matches!(
+ good_aggregate.verify(messages.len() as u32 - 1, &messages),
+ Err(Error::InvalidAggregateVerificationParameters(_, _))
+ ));
// Should fail if no transcripts are provided
let mut dkg =
Dkg::new(TAU, shares_num, security_threshold, &validators, &me)
.unwrap();
- let result = dkg.aggregate_transcripts(&[]);
- assert!(result.is_err());
+ assert!(matches!(
+ dkg.aggregate_transcripts(&[]),
+ Err(Error::NoTranscriptsToAggregate)
+ ));
// Not enough transcripts
let mut dkg =
@@ -708,8 +713,10 @@ mod test_ferveo_api {
assert!(not_enough_messages.len() < security_threshold as usize);
let insufficient_aggregate =
dkg.aggregate_transcripts(not_enough_messages).unwrap();
- let result = insufficient_aggregate.verify(validators_num, &messages);
- assert!(result.is_err());
+ assert!(matches!(
+ insufficient_aggregate.verify(validators_num, &messages),
+ Err(Error::InvalidTranscriptAggregate)
+ ));
// Unexpected transcripts in the aggregate or transcripts from a different ritual
// Using same DKG parameters, but different DKG instances and validators
@@ -725,8 +732,10 @@ mod test_ferveo_api {
);
let mixed_messages = [&messages[..2], &bad_messages[..1]].concat();
let bad_aggregate = dkg.aggregate_transcripts(&mixed_messages).unwrap();
- let result = bad_aggregate.verify(validators_num, &messages);
- assert!(result.is_err());
+ assert!(matches!(
+ bad_aggregate.verify(validators_num, &messages),
+ Err(Error::InvalidTranscriptAggregate)
+ ));
}
#[test_case(4, 4; "number of shares (validators) is a power of 2")]
@@ -761,21 +770,27 @@ mod test_ferveo_api {
// Test negative cases
// Should fail if the number of validators is less than the number of messages
- assert!(good_aggregate
- .verify(messages.len() as u32 - 1, messages)
- .is_err());
+ assert!(matches!(
+ good_aggregate.verify(messages.len() as u32 - 1, messages),
+ Err(Error::InvalidAggregateVerificationParameters(_, _))
+ ));
// Should fail if no transcripts are provided
- let result = AggregatedTranscript::new(&[]);
- assert!(result.is_err());
+ assert!(matches!(
+ AggregatedTranscript::new(&[]),
+ Err(Error::NoTranscriptsToAggregate)
+ ));
// Not enough transcripts
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).unwrap();
- let result = insufficient_aggregate.verify(validators_num, messages);
- assert!(result.is_err());
+ let _result = insufficient_aggregate.verify(validators_num, messages);
+ assert!(matches!(
+ insufficient_aggregate.verify(validators_num, messages),
+ Err(Error::InvalidTranscriptAggregate)
+ ));
// Unexpected transcripts in the aggregate or transcripts from a different ritual
// Using same DKG parameters, but different DKG instances and validators
@@ -788,7 +803,9 @@ mod test_ferveo_api {
);
let mixed_messages = [&messages[..2], &bad_messages[..1]].concat();
let bad_aggregate = AggregatedTranscript::new(&mixed_messages).unwrap();
- let result = bad_aggregate.verify(validators_num, messages);
- assert!(result.is_err());
+ assert!(matches!(
+ bad_aggregate.verify(validators_num, messages),
+ Err(Error::InvalidTranscriptAggregate)
+ ));
}
}
diff --git a/ferveo/src/pvss.rs b/ferveo/src/pvss.rs
index 34695bd3..5108f6b9 100644
--- a/ferveo/src/pvss.rs
+++ b/ferveo/src/pvss.rs
@@ -154,6 +154,7 @@ impl PubliclyVerifiableSS {
.values()
.map(|validator| {
// ek_{i}^{eval_i}, i = validator index
+ // TODO: Replace with regular, single-element exponentiation
fast_multiexp(
// &evals.evals[i..i] = &evals.evals[i]
&[evals[validator.share_index as usize]], // one share per validator
@@ -399,9 +400,9 @@ pub(crate) fn aggregate(
let mut shares = batch_to_projective_g2::(&first_pvss.shares);
- // So now we're iterating over the PVSS instances, and adding their coefficients and shares, and their sigma
+ // So now we're iterating over the PVSS instances, and adding their coefficients and shares, and their
// sigma is the sum of all the sigma_i, which is the proof of knowledge of the secret polynomial
- // Aggregating is just adding the corresponding values in PVSS instances, so PVSS_i = PVSS_(i-1) PVSS_i
+ // Aggregating is just adding the corresponding values in PVSS instances, so PVSS = PVSS + PVSS_i
for next_pvss in pvss_iter {
sigma = (sigma + next_pvss.sigma).into();
coeffs
From 5e9d46ea8d9ff6b4761498000e90a7b45bef6d71 Mon Sep 17 00:00:00 2001
From: Piotr Roslaniec
Date: Thu, 22 Feb 2024 10:09:07 +0100
Subject: [PATCH 8/8] chore: update cargo-machete on ci job
---
.github/workflows/workspace.yml | 1 +
1 file changed, 1 insertion(+)
diff --git a/.github/workflows/workspace.yml b/.github/workflows/workspace.yml
index 270a512e..eb462911 100644
--- a/.github/workflows/workspace.yml
+++ b/.github/workflows/workspace.yml
@@ -42,6 +42,7 @@ jobs:
uses: baptiste0928/cargo-install@v1
with:
crate: cargo-machete
+ version: "=0.6.0"
- run: cargo machete
- name: Install cargo-sort
uses: baptiste0928/cargo-install@v1