Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Validator ordering changes #172

Merged
merged 9 commits into from
Feb 5, 2024
2 changes: 1 addition & 1 deletion ferveo-python/examples/exception.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ def gen_eth_addr(i: int) -> str:

validator_keypairs = [Keypair.random() for _ in range(0, shares_num)]
validators = [
Validator(gen_eth_addr(i), keypair.public_key())
Validator(gen_eth_addr(i), keypair.public_key(), i)
for i, keypair in enumerate(validator_keypairs)
]

Expand Down
2 changes: 1 addition & 1 deletion ferveo-python/examples/server_api_precomputed.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def gen_eth_addr(i: int) -> str:

validator_keypairs = [Keypair.random() for _ in range(0, shares_num)]
validators = [
Validator(gen_eth_addr(i), keypair.public_key())
Validator(gen_eth_addr(i), keypair.public_key(), i)
for i, keypair in enumerate(validator_keypairs)
]

Expand Down
2 changes: 1 addition & 1 deletion ferveo-python/examples/server_api_simple.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def gen_eth_addr(i: int) -> str:
shares_num = 4
validator_keypairs = [Keypair.random() for _ in range(0, shares_num)]
validators = [
Validator(gen_eth_addr(i), keypair.public_key())
Validator(gen_eth_addr(i), keypair.public_key(), i)
for i, keypair in enumerate(validator_keypairs)
]

Expand Down
2 changes: 1 addition & 1 deletion ferveo-python/test/test_ferveo.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ def scenario_for_variant(variant: FerveoVariant, shares_num, threshold, shares_t
tau = 1
validator_keypairs = [Keypair.random() for _ in range(0, shares_num)]
validators = [
Validator(gen_eth_addr(i), keypair.public_key())
Validator(gen_eth_addr(i), keypair.public_key(), i)
for i, keypair in enumerate(validator_keypairs)
]
validators.sort(key=lambda v: v.address)
Expand Down
2 changes: 1 addition & 1 deletion ferveo-python/test/test_serialization.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def gen_eth_addr(i: int) -> str:
shares_num = 4
validator_keypairs = [Keypair.random() for _ in range(shares_num)]
validators = [
Validator(gen_eth_addr(i), keypair.public_key())
Validator(gen_eth_addr(i), keypair.public_key(), i)
for i, keypair in enumerate(validator_keypairs)
]
validators.sort(key=lambda v: v.address)
Expand Down
2 changes: 1 addition & 1 deletion ferveo-wasm/examples/node/src/main.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ function setupTest(sharesNum :number, threshold: number) {
for (let i = 0; i < sharesNum; i++) {
const keypair = Keypair.random();
validatorKeypairs.push(keypair);
const validator = new Validator(genEthAddr(i), keypair.publicKey);
const validator = new Validator(genEthAddr(i), keypair.publicKey, i);
validators.push(validator);
}

Expand Down
2 changes: 1 addition & 1 deletion ferveo-wasm/tests/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,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 dkg = Dkg::new(
let mut dkg = Dkg::new(
TAU,
shares_num,
security_threshold,
Expand Down
1 change: 1 addition & 0 deletions ferveo/benches/benchmarks/validity_checks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ fn gen_validators(
.map(|i| Validator {
address: gen_address(i),
public_key: keypairs[i].public_key(),
share_index: i as u32,
})
.collect()
}
Expand Down
6 changes: 3 additions & 3 deletions ferveo/examples/bench_primitives_size.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ fn gen_validators(
.map(|i| Validator {
address: gen_address(i),
public_key: keypairs[i].public_key(),
share_index: i as u32,
})
.collect()
}
Expand Down Expand Up @@ -95,14 +96,13 @@ fn setup(
for i in 0..shares_num {
let mut dkg = setup_dkg(i as usize, shares_num, security_threshold);
let message = dkg.share(rng).expect("Test failed");
let sender = dkg.get_validator(&dkg.me.validator.public_key).unwrap();
let sender = dkg.get_validator(&dkg.me.public_key).unwrap();
transcripts.push((sender.clone(), message.clone()));
}

let mut dkg = setup_dkg(0, shares_num, security_threshold);
for (sender, pvss) in transcripts.into_iter() {
dkg.apply_message(&sender.validator, &pvss)
.expect("Setup failed");
dkg.apply_message(&sender, &pvss).expect("Setup failed");
}
dkg
}
Expand Down
89 changes: 75 additions & 14 deletions ferveo/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ use crate::bindings_python;
use crate::bindings_wasm;
pub use crate::EthereumAddress;
use crate::{
do_verify_aggregation, Error, PVSSMap, PubliclyVerifiableParams,
do_verify_aggregation, Error, Message, PVSSMap, PubliclyVerifiableParams,
PubliclyVerifiableSS, Result,
};

Expand Down Expand Up @@ -222,15 +222,21 @@ impl Dkg {
}

pub fn generate_transcript<R: RngCore>(
&self,
&mut self,
rng: &mut R,
// TODO: Replace with Message::Deal?
) -> Result<Transcript> {
self.0.create_share(rng)
match self.0.share(rng) {
Ok(Message::Deal(transcript)) => Ok(transcript),
Err(e) => Err(e),
_ => Err(Error::InvalidDkgStateToDeal),
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does this block code do?

Copy link
Author

@piotr-roslaniec piotr-roslaniec Feb 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a pattern-matching expression with different cases on each line:

  • The first line describes a case where we get the intended result - We just need to unwrap transcript from a Message struct, and specifically from Message::Deal variant. I'm considering deprecating Message as we only use it internally.
  • The second line handles the error result
  • The third one handles other Message variants. There are two currently, Message::Deal and Message:Aggregate. We treat results other than Message::Deal as errors, as we don't expect the DKG to be ready to aggregate at this point.

}

pub fn aggregate_transcripts(
&mut self,
messages: &[ValidatorMessage],
// TODO: Replace with Message::Aggregate?
) -> Result<AggregatedTranscript> {
// We must use `deal` here instead of to produce AggregatedTranscript instead of simply
// creating an AggregatedTranscript from the messages, because `deal` also updates the
Expand All @@ -242,7 +248,12 @@ impl Dkg {
for (validator, transcript) in messages {
self.0.deal(validator, transcript)?;
}
Ok(AggregatedTranscript(crate::pvss::aggregate(&self.0.vss)))
let pvss = messages
.iter()
.map(|(_, t)| t)
.cloned()
.collect::<Vec<PubliclyVerifiableSS<E>>>();
Ok(AggregatedTranscript(crate::pvss::aggregate(&pvss)?))
}

pub fn public_params(&self) -> DkgPublicParameters {
Expand All @@ -264,9 +275,13 @@ fn make_pvss_map(messages: &[ValidatorMessage]) -> PVSSMap<E> {
pub struct AggregatedTranscript(PubliclyVerifiableSS<E, crate::Aggregated>);

impl AggregatedTranscript {
pub fn new(messages: &[ValidatorMessage]) -> Self {
let pvss_map = make_pvss_map(messages);
AggregatedTranscript(crate::pvss::aggregate(&pvss_map))
pub fn new(messages: &[ValidatorMessage]) -> Result<Self> {
let pvss_list = messages
.iter()
.map(|(_, t)| t)
.cloned()
.collect::<Vec<PubliclyVerifiableSS<E>>>();
Ok(AggregatedTranscript(crate::pvss::aggregate(&pvss_list)?))
}

pub fn verify(
Expand Down Expand Up @@ -327,7 +342,7 @@ impl AggregatedTranscript {
&ciphertext_header.0,
aad,
&validator_keypair.decryption_key,
dkg.0.me.share_index,
dkg.0.me.share_index as usize,
&domain_points,
&dkg.0.pvss_params.g_inv(),
)
Expand All @@ -344,12 +359,13 @@ impl AggregatedTranscript {
&ciphertext_header.0,
aad,
&validator_keypair.decryption_key,
dkg.0.me.share_index,
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);
Ok(DecryptionShareSimple {
share,
domain_point: dkg.0.domain.element(dkg.0.me.share_index),
domain_point,
})
}
}
Expand Down Expand Up @@ -425,6 +441,7 @@ mod test_ferveo_api {
.map(|(i, keypair)| Validator {
address: gen_address(i),
public_key: keypair.public_key(),
share_index: i as u32,
})
.collect::<Vec<_>>();

Expand All @@ -433,7 +450,7 @@ mod test_ferveo_api {
let messages: Vec<_> = validators
.iter()
.map(|sender| {
let dkg = Dkg::new(
let mut dkg = Dkg::new(
tau,
shares_num,
security_threshold,
Expand Down Expand Up @@ -619,6 +636,10 @@ mod test_ferveo_api {
assert!(result.is_err());
}

// Note that the server and client code are using the same underlying
// 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() {
let rng = &mut StdRng::seed_from_u64(0);
Expand All @@ -637,6 +658,41 @@ mod test_ferveo_api {
assert!(local_aggregate
.verify(dkg.0.dkg_params.shares_num(), &messages)
.is_ok());

// Test negative cases
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These test cases were adapted from the server variant of tests above


// Notice that the dkg instance is mutable, so we need to get a fresh one
// for every test case

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

// Not enough transcripts
let mut dkg =
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 insufficient_aggregate =
dkg.aggregate_transcripts(not_enough_messages).unwrap();
let result = insufficient_aggregate.verify(SHARES_NUM, &messages);
assert!(result.is_err());

// Unexpected transcripts in the aggregate or transcripts from a different ritual
// Using same DKG parameters, but different DKG instances and validators
let mut dkg =
Dkg::new(TAU, SHARES_NUM, SECURITY_THRESHOLD, &validators, &me)
.unwrap();
let (bad_messages, _, _) =
make_test_inputs(rng, TAU, SECURITY_THRESHOLD, SHARES_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);
assert!(result.is_err());
}

#[test]
Expand All @@ -650,7 +706,8 @@ mod test_ferveo_api {
let messages = &messages[..SECURITY_THRESHOLD as usize];

// Create an aggregated transcript on the client side
let aggregated_transcript = AggregatedTranscript::new(messages);
let aggregated_transcript =
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
Expand All @@ -662,11 +719,15 @@ mod test_ferveo_api {

// Test negative cases

// 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 insufficient_aggregate =
AggregatedTranscript::new(not_enough_messages);
AggregatedTranscript::new(not_enough_messages).unwrap();
let result = insufficient_aggregate.verify(SHARES_NUM, messages);
assert!(result.is_err());

Expand All @@ -675,7 +736,7 @@ mod test_ferveo_api {
let (bad_messages, _, _) =
make_test_inputs(rng, TAU, SECURITY_THRESHOLD, SHARES_NUM);
let mixed_messages = [&messages[..2], &bad_messages[..1]].concat();
let bad_aggregate = AggregatedTranscript::new(&mixed_messages);
let bad_aggregate = AggregatedTranscript::new(&mixed_messages).unwrap();
let result = bad_aggregate.verify(SHARES_NUM, messages);
assert!(result.is_err());
}
Expand Down
31 changes: 23 additions & 8 deletions ferveo/src/bindings_python.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,6 @@ impl From<FerveoPythonError> for PyErr {
Error::InvalidTranscriptAggregate => {
InvalidTranscriptAggregate::new_err("")
}
Error::ValidatorsNotSorted => ValidatorsNotSorted::new_err(""),
Error::ValidatorPublicKeyMismatch => {
ValidatorPublicKeyMismatch::new_err("")
}
Expand Down Expand Up @@ -109,6 +108,14 @@ impl From<FerveoPythonError> for PyErr {
"num_shares: {num_shares}, security_threshold: {security_threshold}"
))
},
Error::DuplicatedShareIndex(index) => {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New exceptions

InvalidShareIndex::new_err(format!(
"{index}"
))
},
Error::NoTranscriptsToAggregate => {
NoTranscriptsToAggregate::new_err("")
},
},
_ => default(),
}
Expand Down Expand Up @@ -145,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, NoTranscriptsToAggregate, PyValueError);

fn from_py_bytes<T: FromBytes>(bytes: &[u8]) -> PyResult<T> {
T::from_bytes(bytes)
Expand Down Expand Up @@ -396,8 +404,9 @@ impl Validator {
pub fn new(
address: String,
public_key: &FerveoPublicKey,
share_index: u32,
) -> PyResult<Self> {
let validator = api::Validator::new(address, public_key.0)
let validator = api::Validator::new(address, public_key.0, share_index)
.map_err(|err| FerveoPythonError::Other(err.to_string()))?;
Ok(Self(validator))
}
Expand Down Expand Up @@ -486,7 +495,7 @@ impl Dkg {
DkgPublicKey(self.0.public_key())
}

pub fn generate_transcript(&self) -> PyResult<Transcript> {
pub fn generate_transcript(&mut self) -> PyResult<Transcript> {
let rng = &mut thread_rng();
let transcript = self
.0
Expand Down Expand Up @@ -575,10 +584,12 @@ generate_bytes_serialization!(AggregatedTranscript);
#[pymethods]
impl AggregatedTranscript {
#[new]
pub fn new(messages: Vec<ValidatorMessage>) -> Self {
pub fn new(messages: Vec<ValidatorMessage>) -> PyResult<Self> {
let messages: Vec<_> =
messages.into_iter().map(|vm| vm.to_inner()).collect();
Self(api::AggregatedTranscript::new(&messages))
let inner = api::AggregatedTranscript::new(&messages)
.map_err(FerveoPythonError::FerveoError)?;
Ok(Self(inner))
}

pub fn verify(
Expand Down Expand Up @@ -756,8 +767,12 @@ mod test_ferveo_python {
.iter()
.enumerate()
.map(|(i, keypair)| {
Validator::new(format!("0x{i:040}"), &keypair.public_key())
.unwrap()
Validator::new(
format!("0x{i:040}"),
&keypair.public_key(),
i as u32,
)
.unwrap()
})
.collect();

Expand All @@ -767,7 +782,7 @@ mod test_ferveo_python {
.iter()
.cloned()
.map(|sender| {
let dkg = Dkg::new(
let mut dkg = Dkg::new(
tau,
shares_num,
security_threshold,
Expand Down
Loading
Loading