From b01d5b5092b6e550e2d4d06bcf9f6614b70697ca Mon Sep 17 00:00:00 2001
From: Piotr Roslaniec
Date: Tue, 30 Jan 2024 09:59:49 +0100
Subject: [PATCH] feature: relax dkg ceremony constraints
---
ferveo/src/api.rs | 11 +--
ferveo/src/bindings_python.rs | 176 +++++++++++++++++++---------------
ferveo/src/dkg.rs | 40 +++++++-
ferveo/src/lib.rs | 8 +-
ferveo/src/pvss.rs | 7 +-
5 files changed, 143 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..ef03033e 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,35 @@ 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 dkg = Dkg::new(
+ TAU,
+ shares_num,
+ security_threshold,
+ validators.clone(),
+ &validator,
)
- .unwrap()
- })
- .collect();
+ .unwrap();
+ let aggregate =
+ 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(
+ &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..80e32905 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,25 @@ 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()
+ // self.domain
+ // .elements()
+ // .take(self.dkg_params.shares_num as usize)
+ // .collect()
+ }
+
/// `payload` is the content of the message
pub fn verify_message(
&self,
@@ -323,6 +345,7 @@ pub struct Aggregation {
public_key: E::G1Affine,
}
+// 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 +616,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 +625,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]