Skip to content

Commit

Permalink
feature: relax dkg ceremony constraints
Browse files Browse the repository at this point in the history
  • Loading branch information
piotr-roslaniec committed Jan 31, 2024
1 parent f98a417 commit 81bc1cb
Show file tree
Hide file tree
Showing 5 changed files with 141 additions and 99 deletions.
11 changes: 3 additions & 8 deletions ferveo/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,7 @@ impl AggregatedTranscript {
aad: &[u8],
validator_keypair: &Keypair,
) -> Result<DecryptionSharePrecomputed> {
// Prevent users from using the precomputed variant with improper DKG parameters
if dkg.0.dkg_params.shares_num()
!= dkg.0.dkg_params.security_threshold()
{
Expand All @@ -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(),
)
}
Expand All @@ -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,
Expand Down
177 changes: 99 additions & 78 deletions ferveo/src/bindings_python.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,30 +92,30 @@ impl From<FerveoPythonError> 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(),
}
Expand Down Expand Up @@ -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::*};

Expand All @@ -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::<Vec<_>>();
let validators: Vec<_> = validator_keypairs
Expand Down Expand Up @@ -800,33 +802,39 @@ 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

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
Expand All @@ -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);

Expand All @@ -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
Expand All @@ -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
Expand Down
37 changes: 32 additions & 5 deletions ferveo/src/dkg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,11 @@ pub struct PubliclyVerifiableDkg<E: Pairing> {
pub pvss_params: PubliclyVerifiableParams<E>,
pub validators: ValidatorsMap<E>,
pub vss: PVSSMap<E>,
// TODO: Remove pub?
// TODO: Consider replacing with domain_points entirely
pub domain: ark_poly::GeneralEvaluationDomain<E::ScalarField>,
pub me: Validator<E>,
// TODO: Remove pub?
pub state: DkgState<E>,
}

Expand Down Expand Up @@ -197,6 +200,21 @@ impl<E: Pairing> PubliclyVerifiableDkg<E> {
.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<E::ScalarField> {
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<E::ScalarField> {
self.domain.elements().take(self.validators.len()).collect()
}

/// `payload` is the content of the message
pub fn verify_message(
&self,
Expand Down Expand Up @@ -323,6 +341,8 @@ pub struct Aggregation<E: Pairing> {
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<E>: Serialize, PubliclyVerifiableSS<E>: Serialize",
Expand Down Expand Up @@ -593,19 +613,26 @@ 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(
security_threshold,
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 { .. }));
}

Expand Down
Loading

0 comments on commit 81bc1cb

Please sign in to comment.