diff --git a/.github/workflows/workspace.yml b/.github/workflows/workspace.yml index 70db57d2..1f4bb821 100644 --- a/.github/workflows/workspace.yml +++ b/.github/workflows/workspace.yml @@ -104,12 +104,12 @@ jobs: key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} restore-keys: ${{ runner.os }}-cargo- - - name: Run benchmarks - uses: boa-dev/criterion-compare-action@v3 - if: github.event_name == 'pull_request' - with: - cwd: ${{ matrix.component }} - branchName: ${{ github.base_ref }} +# - name: Run benchmarks +# uses: boa-dev/criterion-compare-action@v3 +# if: github.event_name == 'pull_request' +# with: +# cwd: ${{ matrix.component }} +# branchName: ${{ github.base_ref }} # The next steps have been adapted from https://raw.githubusercontent.com/unicode-org/icu4x/main/.github/workflows/build-test.yml diff --git a/ferveo-common/src/lib.rs b/ferveo-common/src/lib.rs index b4c32651..7868a5d6 100644 --- a/ferveo-common/src/lib.rs +++ b/ferveo-common/src/lib.rs @@ -6,97 +6,20 @@ use ark_serialize::{ pub mod keypair; pub use keypair::*; -use std::cmp::Ordering; -#[derive(Clone, Debug, CanonicalSerialize, CanonicalDeserialize)] -/// Represents a tendermint validator -pub struct TendermintValidator { - /// Total voting power in tendermint consensus - pub power: u64, +#[derive(Clone, Debug, CanonicalSerialize, CanonicalDeserialize, PartialEq)] +/// Represents an external validator +pub struct ExternalValidator { /// The established address of the validator pub address: String, /// The Public key pub public_key: PublicKey, } -impl PartialEq for TendermintValidator { - fn eq(&self, other: &Self) -> bool { - (self.power, &self.address) == (other.power, &other.address) - } -} - -impl Eq for TendermintValidator {} - -impl PartialOrd for TendermintValidator { - fn partial_cmp(&self, other: &Self) -> Option { - Some((self.power, &self.address).cmp(&(other.power, &other.address))) - } -} - -impl Ord for TendermintValidator { - fn cmp(&self, other: &Self) -> Ordering { - (self.power, &self.address).cmp(&(other.power, &other.address)) - } -} - -#[derive(Clone, Debug, CanonicalSerialize, CanonicalDeserialize)] -/// The set of tendermint validators for a dkg instance -pub struct ValidatorSet { - pub validators: Vec>, -} - -impl ValidatorSet { - /// Sorts the validators from highest to lowest. This ordering - /// first considers staking weight and breaks ties on established - /// address - pub fn new(mut validators: Vec>) -> Self { - // reverse the ordering here - validators.sort_by(|a, b| b.cmp(a)); - Self { validators } - } - - /// Get the total voting power of the validator set - pub fn total_voting_power(&self) -> u64 { - self.validators.iter().map(|v| v.power).sum() - } -} - #[derive(Clone, Debug, CanonicalSerialize, CanonicalDeserialize)] pub struct Validator { - pub validator: TendermintValidator, - pub weight: u32, - pub share_start: usize, - pub share_end: usize, -} - -impl PartialEq for Validator { - fn eq(&self, other: &Self) -> bool { - ( - &self.validator, - self.weight, - self.share_start, - self.share_end, - ) == ( - &other.validator, - other.weight, - other.share_start, - other.share_end, - ) - } -} - -impl Eq for Validator {} - -impl PartialOrd for Validator { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.validator.cmp(&other.validator)) - } -} - -impl Ord for Validator { - fn cmp(&self, other: &Self) -> Ordering { - self.validator.cmp(&other.validator) - } + pub validator: ExternalValidator, + pub share_index: usize, } impl Rng for ark_std::rand::prelude::StdRng {} @@ -115,7 +38,7 @@ pub mod ark_serde { { use serde::ser::Error; let mut bytes = vec![]; - data.serialize(&mut bytes).map_err(S::Error::custom)?; + data.serialize(&mut bytes).map_err(Error::custom)?; serde_bytes::Bytes::new(&bytes).serialize(serializer) } /// Deserialize an ark type with serde @@ -126,7 +49,7 @@ pub mod ark_serde { { use serde::de::Error; let bytes = ::deserialize(deserializer)?; - T::deserialize(bytes.as_slice()).map_err(D::Error::custom) + T::deserialize(bytes.as_slice()).map_err(Error::custom) } } diff --git a/ferveo/benches/benchmarks/pvdkg.rs b/ferveo/benches/benchmarks/pvdkg.rs index 02df0341..82600589 100644 --- a/ferveo/benches/benchmarks/pvdkg.rs +++ b/ferveo/benches/benchmarks/pvdkg.rs @@ -1,6 +1,6 @@ pub use ark_bls12_381::Bls12_381 as EllipticCurve; use criterion::{criterion_group, criterion_main, Criterion}; -use ferveo_common::{TendermintValidator, ValidatorSet}; +use ferveo_common::ExternalValidator; use pprof::criterion::{Output, PProfProfiler}; use ferveo::*; @@ -47,16 +47,13 @@ pub fn gen_keypairs(num: u64) -> Vec> { /// Generate a few validators pub fn gen_validators( keypairs: &[ferveo_common::Keypair], -) -> ValidatorSet { - ValidatorSet::new( - (0..keypairs.len()) - .map(|i| TendermintValidator { - power: i as u64, - address: format!("validator_{}", i), - public_key: keypairs[i].public(), - }) - .collect(), - ) +) -> Vec> { + (0..keypairs.len()) + .map(|i| ExternalValidator { + address: format!("validator_{}", i), + public_key: keypairs[i].public(), + }) + .collect() } /// Create a test dkg in state [`DkgState::Init`] @@ -66,16 +63,17 @@ pub fn setup_dkg( ) -> PubliclyVerifiableDkg { let keypairs = gen_keypairs(num); let validators = gen_validators(&keypairs); - let me = validators.validators[validator].clone(); + let me = validators[validator].clone(); + let shares_num = 300; PubliclyVerifiableDkg::new( validators, Params { tau: 0, - security_threshold: 300 / 3, - total_weight: 300, + security_threshold: shares_num / 3, + shares_num, retry_after: 2, }, - me, + &me, keypairs[validator], ) .expect("Setup failed") diff --git a/ferveo/examples/pvdkg.rs b/ferveo/examples/pvdkg.rs index c1dcf071..82968d1c 100644 --- a/ferveo/examples/pvdkg.rs +++ b/ferveo/examples/pvdkg.rs @@ -1,6 +1,6 @@ pub use ark_bls12_381::Bls12_381 as EllipticCurve; use ferveo::*; -use ferveo_common::{TendermintValidator, ValidatorSet}; +use ferveo_common::ExternalValidator; use measure_time::print_time; pub fn main() { @@ -21,36 +21,33 @@ pub fn gen_keypairs(num: u64) -> Vec> { /// Generate a few validators pub fn gen_validators( keypairs: &[ferveo_common::Keypair], -) -> ValidatorSet { - ValidatorSet::new( - (0..keypairs.len()) - .map(|i| TendermintValidator { - power: i as u64, - address: format!("validator_{}", i), - public_key: keypairs[i].public(), - }) - .collect(), - ) +) -> Vec> { + (0..keypairs.len()) + .map(|i| ExternalValidator { + address: format!("validator_{}", i), + public_key: keypairs[i].public(), + }) + .collect() } /// Create a test dkg in state [`DkgState::Init`] pub fn setup_dkg( validator: usize, num: u64, - shares: u32, + shares_num: u32, ) -> PubliclyVerifiableDkg { let keypairs = gen_keypairs(num); let validators = gen_validators(&keypairs); - let me = validators.validators[validator].clone(); + let me = validators[validator].clone(); PubliclyVerifiableDkg::new( validators, Params { tau: 0, - security_threshold: shares / 3, - total_weight: shares, + security_threshold: shares_num / 3, + shares_num, retry_after: 1, }, - me, + &me, keypairs[validator], ) .expect("Setup failed") @@ -71,7 +68,7 @@ pub fn setup_dealt_dkg(num: u64, shares: u32) { for (sender, pvss) in transcripts.into_iter().rev().enumerate() { if let Message::Deal(ss) = pvss.clone() { print_time!("PVSS verify pvdkg"); - ss.verify_full(&dkg, rng); + ss.verify_full(&dkg); } dkg.apply_message( dkg.validators[num as usize - 1 - sender].validator.clone(), diff --git a/ferveo/src/dkg.rs b/ferveo/src/dkg.rs index daba9172..93aebf7f 100644 --- a/ferveo/src/dkg.rs +++ b/ferveo/src/dkg.rs @@ -12,10 +12,12 @@ use ark_poly::{ EvaluationDomain, Polynomial, }; use ark_serialize::*; +use bincode::Options; use ed25519_dalek as ed25519; pub mod common; pub mod pv; + pub use common::*; pub use pv::*; @@ -23,9 +25,9 @@ pub use pv::*; #[derive(Copy, Clone, Debug, CanonicalSerialize, CanonicalDeserialize)] pub struct Params { pub tau: u64, - pub security_threshold: u32, // threshold - pub total_weight: u32, // total weight - pub retry_after: u32, + pub security_threshold: u32, + pub shares_num: u32, + pub retry_after: u32, // TODO: Remove. Not relevant in our scheme. } #[derive(Clone, Debug, Eq, PartialEq)] @@ -36,7 +38,7 @@ pub enum PvssScheduler { #[derive(Debug, Clone)] pub enum DkgState { - Sharing { accumulated_weight: u32, block: u32 }, + Sharing { accumulated_shares: u32, block: u32 }, Dealt, Success { final_key: E::G1Affine }, Invalid, @@ -50,12 +52,12 @@ impl CanonicalSerialize for DkgState { ) -> Result<(), SerializationError> { match self { Self::Sharing { - accumulated_weight, + accumulated_shares, block, } => { CanonicalSerialize::serialize(&0u8, &mut writer)?; CanonicalSerialize::serialize( - &(*accumulated_weight, *block), + &(*accumulated_shares, *block), &mut writer, ) } @@ -72,11 +74,11 @@ impl CanonicalSerialize for DkgState { fn serialized_size(&self) -> usize { match self { Self::Sharing { - accumulated_weight, + accumulated_shares, block, } => { 0u8.serialized_size() - + (*accumulated_weight, *block).serialized_size() + + (*accumulated_shares, *block).serialized_size() } Self::Dealt => 1u8.serialized_size(), Self::Success { final_key } => { @@ -93,12 +95,12 @@ impl CanonicalDeserialize for DkgState { let variant = ::deserialize(&mut reader)?; match variant { 0 => { - let (accumulated_weight, block) = + let (accumulated_shares, block) = <(u32, u32) as CanonicalDeserialize>::deserialize( &mut reader, )?; Ok(Self::Sharing { - accumulated_weight, + accumulated_shares, block, }) } diff --git a/ferveo/src/dkg/common.rs b/ferveo/src/dkg/common.rs index b69c314f..c519db1b 100644 --- a/ferveo/src/dkg/common.rs +++ b/ferveo/src/dkg/common.rs @@ -1,59 +1,16 @@ use crate::*; -use ferveo_common::ValidatorSet; +use ferveo_common::ExternalValidator; use itertools::izip; -/// partition_domain takes as input a vector of validators from -/// participants in the DKG, containing their total stake amounts -/// and public address (as Bech32m string) -/// -/// The validators are *assumed to be* stable-sorted by staking weight -/// (so highest weight participants come first), then by address -/// and the DKG share domain is partitioned into continuous segments roughly -/// the same relative size as the staked weight. -/// -/// partition_domain returns a vector of DKG participants -pub fn partition_domain( - params: &Params, - mut validator_set: ValidatorSet, -) -> Result>> { - // Sort participants from greatest to least stake - - // Compute the total amount staked - let total_voting_power = - params.total_weight as f64 / validator_set.total_voting_power() as f64; - - // Compute the weight of each participant rounded down - let mut weights = validator_set - .validators +pub fn make_validators( + validators: Vec>, +) -> Vec> { + validators .iter() - .map(|p| (p.power as f64 * total_voting_power).floor() as u32) - .collect::>(); - - // Add any excess weight to the largest weight participants - let adjust_weight = params - .total_weight - .checked_sub(weights.iter().sum()) - .ok_or_else(|| anyhow!("adjusted weight negative"))? - as usize; - for i in &mut weights[0..adjust_weight] { - *i += 1; - } - - let mut allocated_weight = 0usize; - let mut participants = vec![]; - // note that the order of `participants` corresponds to the same - // order as `validator_set` - for (ix, validator) in validator_set.validators.drain(0..).enumerate() { - participants.push(ferveo_common::Validator:: { - validator, - weight: weights[ix], - share_start: allocated_weight, - share_end: allocated_weight + weights[ix] as usize, - }); - allocated_weight = - allocated_weight - .checked_add(weights[ix] as usize) - .ok_or_else(|| anyhow!("allocated weight overflow"))?; - } - Ok(participants) + .enumerate() + .map(|(index, validator)| ferveo_common::Validator:: { + validator: validator.clone(), + share_index: index, + }) + .collect() } diff --git a/ferveo/src/dkg/pv.rs b/ferveo/src/dkg/pv.rs index 34ed7565..ec90673a 100644 --- a/ferveo/src/dkg/pv.rs +++ b/ferveo/src/dkg/pv.rs @@ -1,10 +1,11 @@ use crate::*; +use anyhow::Context; use ark_ec::bn::TwistType::D; use ark_ec::PairingEngine; use ark_ff::Field; use ark_serialize::*; use ark_std::{end_timer, start_timer}; -use ferveo_common::{PublicKey, TendermintValidator, ValidatorSet}; +use ferveo_common::{ExternalValidator, PublicKey}; use std::collections::BTreeMap; /// The DKG context that holds all of the local state for participating in the DKG @@ -24,33 +25,30 @@ pub struct PubliclyVerifiableDkg { impl PubliclyVerifiableDkg { /// Create a new DKG context to participate in the DKG /// Every identity in the DKG is linked to an ed25519 public key; - /// `validator_set`: The set of validators and their respective voting powers - /// *IMPORTANT: this set should be reverse sorted* + /// `validatorst`: List of validators /// `params` contains the parameters of the DKG such as number of shares /// `me` the validator creating this instance /// `session_keypair` the keypair for `me` pub fn new( - validator_set: ValidatorSet, + validators: Vec>, params: Params, - me: TendermintValidator, + me: &ExternalValidator, session_keypair: ferveo_common::Keypair, ) -> Result { use ark_std::UniformRand; let domain = ark_poly::Radix2EvaluationDomain::::new( - params.total_weight as usize, + params.shares_num as usize, ) .ok_or_else(|| anyhow!("unable to construct domain"))?; // keep track of the owner of this instance in the validator set - let me = validator_set - .validators - .binary_search_by(|probe| me.cmp(probe)) - .map_err(|_| anyhow!("could not find this validator in the provided validator set"))?; - - // partition out weight shares of validators based on their voting power - let validators = partition_domain(¶ms, validator_set)?; - // we further partition out valdiators into partitions to submit pvss transcripts - // so as to minimize network load and enable retrying + let me = validators.iter().position(|probe| me == probe).context( + "could not find this validator in the provided validator set", + )?; + + let validators = make_validators(validators); + + // TODO: Remove my_partition let my_partition = params.retry_after * (2 * me as u32 / params.retry_after); Ok(Self { @@ -63,11 +61,12 @@ impl PubliclyVerifiableDkg { vss: BTreeMap::new(), domain, state: DkgState::Sharing { - accumulated_weight: 0, + accumulated_shares: 0, block: 0, }, me, validators, + // TODO: Remove window window: (my_partition, my_partition + params.retry_after), }) } @@ -144,20 +143,19 @@ impl PubliclyVerifiableDkg { /// Verify a DKG related message in a block proposal /// `sender` is the validator of the sender of the message /// `payload` is the content of the message - pub fn verify_message( + pub fn verify_message( &self, - sender: &TendermintValidator, + sender: &ExternalValidator, payload: &Message, - rng: &mut R, ) -> Result<()> { match payload { Message::Deal(pvss) if matches!(self.state, DkgState::Sharing{..} | DkgState::Dealt) => { // TODO: If this is two slow, we can convert self.validators to - // an address keyed hashmap after partitioning the weight shares + // an address keyed hashmap after partitioning the shares shares // in the [`new`] method let sender = self.validators - .binary_search_by(|probe| sender.cmp(&probe.validator)) - .map_err(|_| anyhow!("dkg received unknown dealer"))?; + .iter().position(|probe| sender == &probe.validator) + .context("dkg received unknown dealer")?; if self.vss.contains_key(&(sender as u32)) { Err(anyhow!("Repeat dealer {}", sender)) } else if !pvss.verify_optimistic() { @@ -166,14 +164,14 @@ impl PubliclyVerifiableDkg { Ok(()) } } - Message::Aggregate(Aggregation{vss, final_key}) if matches!(self.state, DkgState::Dealt) => { - let minimum_weight = self.params.total_weight + Message::Aggregate(Aggregation { vss, final_key }) if matches!(self.state, DkgState::Dealt) => { + let minimum_shares = self.params.shares_num - self.params.security_threshold; - let verified_weight = vss.verify_aggregation(self, rng)?; + let verified_shares = vss.verify_aggregation(self)?; // we reject aggregations that fail to meet the security threshold - if verified_weight < minimum_weight { + if verified_shares < minimum_shares { Err( - anyhow!("Aggregation failed because the verified weight was insufficient") + anyhow!("Aggregation failed because the verified shares was insufficient") ) } else if &self.final_key() == final_key { Ok(()) @@ -192,31 +190,30 @@ impl PubliclyVerifiableDkg { /// to the state machine pub fn apply_message( &mut self, - sender: TendermintValidator, + sender: ExternalValidator, payload: Message, ) -> Result<()> { match payload { Message::Deal(pvss) if matches!(self.state, DkgState::Sharing{..} | DkgState::Dealt) => { // Add the ephemeral public key and pvss transcript let sender = self.validators - .binary_search_by(|probe| sender.cmp(&probe.validator)) - .map_err(|_| anyhow!("dkg received unknown dealer"))?; + .iter().position(|probe| sender.address == probe.validator.address) + .context("dkg received unknown dealer")?; self.vss.insert(sender as u32, pvss); - // we keep track of the amount of weight seen until the security + // we keep track of the amount of shares seen until the security // threshold is met. Then we may change the state of the DKG - if let DkgState::Sharing{ref mut accumulated_weight, ..} = &mut self.state { - *accumulated_weight += self.validators[sender].weight; - if *accumulated_weight - >= self.params.total_weight - self.params.security_threshold { - self.state = DkgState::Dealt; + if let DkgState::Sharing { ref mut accumulated_shares, .. } = &mut self.state { + *accumulated_shares += 1; + if *accumulated_shares >= self.params.shares_num - self.params.security_threshold { + self.state = DkgState::Dealt; } } Ok(()) } Message::Aggregate(_) if matches!(self.state, DkgState::Dealt) => { // change state and cache the final key - self.state = DkgState::Success {final_key: self.final_key()}; + self.state = DkgState::Success { final_key: self.final_key() }; Ok(()) } _ => Err(anyhow!("DKG state machine is not in correct state to apply this message")) @@ -255,73 +252,105 @@ pub(crate) mod test_common { pub use super::*; pub use ark_bls12_381::Bls12_381 as EllipticCurve; pub use ark_ff::UniformRand; + pub type G1 = ::G1Affine; - /// Generate a set of keypairs for each validator - pub fn gen_keypairs() -> Vec> { + pub fn gen_n_keypairs( + n: u32, + ) -> Vec> { let rng = &mut ark_std::test_rng(); - (0..4) + (0..n) .map(|_| ferveo_common::Keypair::::new(rng)) .collect() } + /// Generate a set of keypairs for each validator + pub fn gen_keypairs() -> Vec> { + gen_n_keypairs(4) + } + + pub fn gen_n_validators( + keypairs: &[ferveo_common::Keypair], + n: u32, + ) -> Vec> { + (0..n) + .map(|i| ExternalValidator { + address: format!("validator_{}", i), + public_key: keypairs[i as usize].public(), + }) + .collect() + } + /// Generate a few validators pub fn gen_validators( keypairs: &[ferveo_common::Keypair], - ) -> ValidatorSet { - ValidatorSet::new( - (0..4) - .map(|i| TendermintValidator { - power: i, - address: format!("validator_{}", i), - public_key: keypairs[i as usize].public(), - }) - .collect(), - ) + ) -> Vec> { + gen_n_validators(keypairs, 4) } - /// Create a test dkg - /// - /// The [`test_dkg_init`] module checks correctness of this setup - pub fn setup_dkg(validator: usize) -> PubliclyVerifiableDkg { - let keypairs = gen_keypairs(); - let validators = gen_validators(&keypairs); - let me = validators.validators[validator].clone(); + pub fn setup_dkg_for_n_validators( + security_threshold: u32, + shares_num: u32, + my_index: usize, + ) -> PubliclyVerifiableDkg { + let keypairs = gen_n_keypairs(shares_num); + let validators = gen_n_validators(&keypairs, shares_num); + let me = validators[my_index].clone(); PubliclyVerifiableDkg::new( validators, Params { tau: 0, - security_threshold: 2, - total_weight: 6, + security_threshold, + shares_num, retry_after: 2, }, - me, - keypairs[validator], + &me, + keypairs[my_index], ) .expect("Setup failed") } + /// Create a test dkg + /// + /// The [`test_dkg_init`] module checks correctness of this setup + pub fn setup_dkg(validator: usize) -> PubliclyVerifiableDkg { + setup_dkg_for_n_validators(2, 4, validator) + } + /// Set up a dkg with enough pvss transcripts to meet the threshold /// /// The correctness of this function is tested in the module [`test_dealing`] pub fn setup_dealt_dkg() -> PubliclyVerifiableDkg { + setup_dealt_dkg_with_n_validators(2, 4) + } + + pub fn setup_dealt_dkg_with_n_validators( + security_threshold: u32, + shares_num: u32, + ) -> PubliclyVerifiableDkg { + // Make sure that the number of shares is a power of 2 for the FFT to work (Radix-2 FFT domain is being used) + let is_power_of_2 = |n: u32| n != 0 && (n & (n - 1)) == 0; + assert!(is_power_of_2(shares_num)); + let rng = &mut ark_std::test_rng(); - // gather everyone's transcripts - let mut transcripts = vec![]; - for i in 0..4 { - let mut dkg = setup_dkg(i); - transcripts.push(dkg.share(rng).expect("Test failed")); - } - // our test dkg - let mut dkg = setup_dkg(0); - // iterate over transcripts from lowest weight to highest - for (sender, pvss) in transcripts.into_iter().rev().enumerate() { - dkg.apply_message( - dkg.validators[3 - sender].validator.clone(), - pvss, - ) - .expect("Setup failed"); - } + + // Gather everyone's transcripts + let transcripts = (0..shares_num).map(|i| { + let mut dkg = setup_dkg_for_n_validators( + security_threshold, + shares_num, + i as usize, + ); + dkg.share(rng).expect("Test failed") + }); + + // Our test dkg + let mut dkg = + setup_dkg_for_n_validators(security_threshold, shares_num, 0); + transcripts.enumerate().for_each(|(sender, pvss)| { + dkg.apply_message(dkg.validators[sender].validator.clone(), pvss) + .expect("Setup failed"); + }); dkg } } @@ -331,59 +360,6 @@ pub(crate) mod test_common { mod test_dkg_init { use super::test_common::*; - /// Test that validators are correctly sorted - #[test] - fn test_validator_set() { - let rng = &mut ark_std::test_rng(); - let validators = vec![ - TendermintValidator:: { - power: 0, - address: "validator_0".into(), - public_key: ferveo_common::Keypair::::new(rng) - .public(), - }, - TendermintValidator:: { - power: 2, - address: "validator_1".into(), - public_key: ferveo_common::Keypair::::new(rng) - .public(), - }, - TendermintValidator:: { - power: 2, - address: "validator_2".into(), - public_key: ferveo_common::Keypair::::new(rng) - .public(), - }, - TendermintValidator:: { - power: 1, - address: "validator_3".into(), - public_key: ferveo_common::Keypair::::new(rng) - .public(), - }, - ]; - let expected = vec![ - validators[2].clone(), - validators[1].clone(), - validators[3].clone(), - validators[0].clone(), - ]; - let validator_set = ValidatorSet::new(validators); - assert_eq!(validator_set.validators, expected); - let params = Params { - tau: 0, - security_threshold: 2, - total_weight: 6, - retry_after: 2, - }; - let validator_set: Vec> = - partition_domain(¶ms, validator_set) - .expect("Test failed") - .iter() - .map(|v| v.validator.clone()) - .collect(); - assert_eq!(validator_set, expected); - } - /// Test that dkg fails to start if the `me` input /// is not in the validator set #[test] @@ -396,12 +372,11 @@ mod test_dkg_init { Params { tau: 0, security_threshold: 4, - total_weight: 6, + shares_num: 8, retry_after: 2, }, - TendermintValidator:: { - power: 9001, - address: "Goku".into(), + &ExternalValidator:: { + address: "non-existant-validator".into(), public_key: keypair.public(), }, keypair, @@ -444,37 +419,34 @@ mod test_dealing { } // our test dkg let mut dkg = setup_dkg(0); - // iterate over transcripts from lowest weight to highest + let mut expected = 0u32; - for (sender, pvss) in transcripts.into_iter().rev().enumerate() { + for (sender, pvss) in transcripts.iter().enumerate() { // check the verification passes assert!(dkg - .verify_message( - &dkg.validators[3 - sender].validator, - &pvss, - rng - ) + .verify_message(&dkg.validators[sender].validator, pvss) .is_ok()); // check that application passes assert!(dkg .apply_message( - dkg.validators[3 - sender].validator.clone(), - pvss + dkg.validators[sender].validator.clone(), + pvss.clone(), ) .is_ok()); - expected += dkg.validators[3 - sender].validator.power as u32; - if sender < 3 { - // check that weight accumulates correctly + + expected += 1; + if sender < (dkg.params.security_threshold - 1) as usize { + // check that shares accumulates correctly match dkg.state { DkgState::Sharing { - accumulated_weight, .. + accumulated_shares, .. } => { - assert_eq!(accumulated_weight, expected) + assert_eq!(accumulated_shares, expected) } _ => panic!("Test failed"), } } else { - // check that when enough weight is accumulated, we transition state + // check that when enough shares is accumulated, we transition state assert!(matches!(dkg.state, DkgState::Dealt)); } } @@ -490,26 +462,25 @@ mod test_dealing { assert!(matches!( dkg.state, DkgState::Sharing { - accumulated_weight: 0, + accumulated_shares: 0, block: 0 } )); let pvss = dkg.share(rng).expect("Test failed"); - let sender = TendermintValidator:: { - power: 9001, - address: "Goku".into(), + let sender = ExternalValidator:: { + address: "fake-address".into(), public_key: ferveo_common::Keypair::::new(rng) .public(), }; // check that verification fails - assert!(dkg.verify_message(&sender, &pvss, rng).is_err()); + assert!(dkg.verify_message(&sender, &pvss).is_err()); // check that application fails assert!(dkg.apply_message(sender, pvss).is_err()); // check that state has not changed assert!(matches!( dkg.state, DkgState::Sharing { - accumulated_weight: 0, + accumulated_shares: 0, block: 0, } )); @@ -521,29 +492,31 @@ mod test_dealing { fn test_pvss_sent_twice_rejected() { let rng = &mut ark_std::test_rng(); let mut dkg = setup_dkg(0); + // We start with an empty state assert!(matches!( dkg.state, DkgState::Sharing { - accumulated_weight: 0, + accumulated_shares: 0, block: 0, } )); + let pvss = dkg.share(rng).expect("Test failed"); let sender = dkg.validators[3].validator.clone(); - // check that verification fails - assert!(dkg.verify_message(&sender, &pvss, rng).is_ok()); - // check that application fails + + // First PVSS is accepted + assert!(dkg.verify_message(&sender, &pvss).is_ok()); assert!(dkg.apply_message(sender.clone(), pvss.clone()).is_ok()); - // check that state has appropriately changed assert!(matches!( dkg.state, DkgState::Sharing { - accumulated_weight: 0, + accumulated_shares: 1, block: 0, } )); - // check that sending another pvss from same sender fails - assert!(dkg.verify_message(&sender, &pvss, rng).is_err()); + + // Second PVSS is rejected + assert!(dkg.verify_message(&sender, &pvss).is_err()); } /// Test that if a validators tries to verify it's own @@ -552,31 +525,35 @@ mod test_dealing { fn test_own_pvss() { let rng = &mut ark_std::test_rng(); let mut dkg = setup_dkg(0); + // We start with an empty state assert!(matches!( dkg.state, DkgState::Sharing { - accumulated_weight: 0, + accumulated_shares: 0, block: 0, } )); - // create share message and check state update + + // Sender creates a PVSS transcript let pvss = dkg.share(rng).expect("Test failed"); + // Note that state of DKG has not changed assert!(matches!( dkg.state, DkgState::Sharing { - accumulated_weight: 0, + accumulated_shares: 0, block: 0, } )); + let sender = dkg.validators[0].validator.clone(); - // check that verification fails - assert!(dkg.verify_message(&sender, &pvss, rng).is_ok()); + + // Sender verifies it's own PVSS transcript + assert!(dkg.verify_message(&sender, &pvss).is_ok()); assert!(dkg.apply_message(sender, pvss).is_ok()); - // check that state did not change assert!(matches!( dkg.state, DkgState::Sharing { - accumulated_weight: 3, + accumulated_shares: 1, block: 0, } )); @@ -591,7 +568,7 @@ mod test_dealing { assert!(matches!( dkg.state, DkgState::Sharing { - accumulated_weight: 0, + accumulated_shares: 0, block: 0, } )); @@ -602,7 +579,7 @@ mod test_dealing { assert!(dkg.share(rng).is_err()); // check that even if security threshold is met, we can still share - dkg.state = DkgState::Dealt; + dkg.state = Dealt; assert!(dkg.share(rng).is_ok()); } @@ -617,7 +594,7 @@ mod test_dealing { assert!(matches!( dkg.state, DkgState::Sharing { - accumulated_weight: 0, + accumulated_shares: 0, block: 0, } )); @@ -625,12 +602,12 @@ mod test_dealing { dkg.state = DkgState::Success { final_key: G1::zero(), }; - assert!(dkg.verify_message(&sender, &pvss, rng).is_err()); + assert!(dkg.verify_message(&sender, &pvss).is_err()); assert!(dkg.apply_message(sender.clone(), pvss.clone()).is_err()); // check that we can still accept pvss transcripts after meeting threshold - dkg.state = DkgState::Dealt; - assert!(dkg.verify_message(&sender, &pvss, rng).is_ok()); + dkg.state = Dealt; + assert!(dkg.verify_message(&sender, &pvss).is_ok()); assert!(dkg.apply_message(sender, pvss).is_ok()); assert!(matches!(dkg.state, DkgState::Dealt)) } @@ -654,7 +631,7 @@ mod test_dealing { fn test_pvss_wait_if_not_in_sharing_state() { let mut dkg = setup_dkg(0); for state in vec![ - DkgState::Dealt, + Dealt, DkgState::Success { final_key: G1::zero(), }, @@ -674,7 +651,7 @@ mod test_dealing { let pvss = dkg.share(rng).expect("Test failed"); let sender = dkg.validators[0].validator.clone(); // check that verification fails - assert!(dkg.verify_message(&sender, &pvss, rng).is_ok()); + assert!(dkg.verify_message(&sender, &pvss).is_ok()); assert!(dkg.apply_message(sender, pvss).is_ok()); assert_eq!(dkg.increase_block(), PvssScheduler::Wait); } @@ -685,7 +662,7 @@ mod test_dealing { fn test_pvss_reissue() { let mut dkg = setup_dkg(0); dkg.state = DkgState::Sharing { - accumulated_weight: 0, + accumulated_shares: 0, block: 2, }; assert_eq!(dkg.increase_block(), PvssScheduler::Issue); @@ -716,11 +693,10 @@ mod test_aggregation { /// met, we can create a final key #[test] fn test_aggregate() { - let rng = &mut ark_std::test_rng(); let mut dkg = setup_dealt_dkg(); let aggregate = dkg.aggregate().expect("Test failed"); let sender = dkg.validators[dkg.me].validator.clone(); - assert!(dkg.verify_message(&sender, &aggregate, rng).is_ok()); + assert!(dkg.verify_message(&sender, &aggregate).is_ok()); assert!(dkg.apply_message(sender, aggregate).is_ok()); assert!(matches!(dkg.state, DkgState::Success { .. })); } @@ -731,7 +707,7 @@ mod test_aggregation { fn test_aggregate_state_guards() { let mut dkg = setup_dealt_dkg(); dkg.state = DkgState::Sharing { - accumulated_weight: 0, + accumulated_shares: 0, block: 0, }; assert!(dkg.aggregate().is_err()); @@ -746,22 +722,21 @@ mod test_aggregation { /// [`DkgState::Dealt`] #[test] fn test_aggregate_message_state_guards() { - let rng = &mut ark_std::test_rng(); let mut dkg = setup_dealt_dkg(); let aggregate = dkg.aggregate().expect("Test failed"); let sender = dkg.validators[dkg.me].validator.clone(); dkg.state = DkgState::Sharing { - accumulated_weight: 0, + accumulated_shares: 0, block: 0, }; - assert!(dkg.verify_message(&sender, &aggregate, rng).is_err()); + assert!(dkg.verify_message(&sender, &aggregate).is_err()); assert!(dkg .apply_message(sender.clone(), aggregate.clone()) .is_err()); dkg.state = DkgState::Success { final_key: G1::zero(), }; - assert!(dkg.verify_message(&sender, &aggregate, rng).is_err()); + assert!(dkg.verify_message(&sender, &aggregate).is_err()); assert!(dkg.apply_message(sender, aggregate).is_err()) } @@ -769,19 +744,17 @@ mod test_aggregation { /// security threshold is not met #[test] fn test_aggregate_wont_verify_if_under_threshold() { - let rng = &mut ark_std::test_rng(); let mut dkg = setup_dealt_dkg(); - dkg.params.total_weight = 10; + dkg.params.shares_num = 10; let aggregate = dkg.aggregate().expect("Test failed"); let sender = dkg.validators[dkg.me].validator.clone(); - assert!(dkg.verify_message(&sender, &aggregate, rng).is_err()); + assert!(dkg.verify_message(&sender, &aggregate).is_err()); } /// If the aggregated pvss passes, check that the announced /// key is correct. Verification should fail if it is not #[test] fn test_aggregate_wont_verify_if_wrong_key() { - let rng = &mut ark_std::test_rng(); let mut dkg = setup_dealt_dkg(); let mut aggregate = dkg.aggregate().expect("Test failed"); while dkg.final_key() == G1::zero() { @@ -793,6 +766,6 @@ mod test_aggregation { *final_key = G1::zero(); } let sender = dkg.validators[dkg.me].validator.clone(); - assert!(dkg.verify_message(&sender, &aggregate, rng).is_err()); + assert!(dkg.verify_message(&sender, &aggregate).is_err()); } } diff --git a/ferveo/src/lib.rs b/ferveo/src/lib.rs index 6b9f509f..cb125135 100644 --- a/ferveo/src/lib.rs +++ b/ferveo/src/lib.rs @@ -1,9 +1,12 @@ #![allow(unused_imports)] + pub mod dkg; pub mod msg; pub mod vss; pub mod primitives; + +use itertools::{izip, zip_eq}; pub use primitives::*; use ferveo_common::Rng; @@ -30,3 +33,106 @@ use ark_ec::PairingEngine; use ark_ff::PrimeField; use measure_time::print_time; + +#[cfg(test)] +mod test_dkg_full { + use super::*; + + use crate::dkg::pv::test_common::*; + use ark_bls12_381::{Bls12_381 as EllipticCurve, Bls12_381, G2Projective}; + use ark_ec::bls12::G2Affine; + use ark_ff::{Fp12, UniformRand}; + use ferveo_common::{ExternalValidator, Keypair}; + use group_threshold_cryptography as tpke; + use group_threshold_cryptography::Ciphertext; + use itertools::{zip_eq, Itertools}; + + type E = Bls12_381; + + #[test] + fn test_dkg_simple_decryption_variant_single_validator() { + let rng = &mut ark_std::test_rng(); + let dkg = setup_dealt_dkg_with_n_validators(1, 1); + + let msg: &[u8] = "abc".as_bytes(); + let aad: &[u8] = "my-aad".as_bytes(); + let public_key = dkg.final_key(); + + let ciphertext = tpke::encrypt::<_, E>(msg, aad, &public_key, rng); + + let aggregate = aggregate_for_decryption(&dkg); + // Aggregate contains only one set of shares + assert_eq!(aggregate, dkg.vss.get(&0).unwrap().shares); + + let validator_keypairs = gen_n_keypairs(1); + let decryption_shares = + make_decryption_shares(&ciphertext, validator_keypairs, aggregate); + + let shares_x = &dkg + .domain + .elements() + .take(decryption_shares.len()) + .collect::>(); + let lagrange_coeffs = tpke::prepare_combine_simple::(shares_x); + + let shared_secret = tpke::share_combine_simple::( + &decryption_shares, + &lagrange_coeffs, + ); + + let plaintext = tpke::checked_decrypt_with_shared_secret( + &ciphertext, + aad, + &shared_secret, + ) + .unwrap(); + assert_eq!(plaintext, msg); + } + + #[test] + fn test_dkg_simple_decryption_variant() { + let rng = &mut ark_std::test_rng(); + let dkg = setup_dealt_dkg_with_n_validators(3, 4); + + let msg: &[u8] = "abc".as_bytes(); + let aad: &[u8] = "my-aad".as_bytes(); + let public_key = dkg.final_key(); + let ciphertext = tpke::encrypt::<_, E>(msg, aad, &public_key, rng); + + let aggregate = aggregate_for_decryption(&dkg); + + // TODO: Before creating decryption shares, check ciphertext validity + // See: https://nikkolasg.github.io/ferveo/tpke.html#to-validate-ciphertext-for-ind-cca2-security + + let validator_keypairs = gen_n_keypairs(4); + // Make sure validators are in the same order dkg is by comparing their public keys + dkg.validators + .iter() + .zip_eq(validator_keypairs.iter()) + .for_each(|(v, k)| { + assert_eq!(v.validator.public_key, k.public()); + }); + let decryption_shares = + make_decryption_shares(&ciphertext, validator_keypairs, aggregate); + + let shares_x = &dkg + .domain + .elements() + .take(decryption_shares.len()) + .collect::>(); + let lagrange_coeffs = tpke::prepare_combine_simple::(shares_x); + + let shared_secret = tpke::share_combine_simple::( + &decryption_shares, + &lagrange_coeffs, + ); + + let plaintext = tpke::checked_decrypt_with_shared_secret( + &ciphertext, + aad, + &shared_secret, + ) + .unwrap(); + assert_eq!(plaintext, msg); + } +} diff --git a/ferveo/src/main.rs b/ferveo/src/main.rs deleted file mode 100644 index e7a11a96..00000000 --- a/ferveo/src/main.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - println!("Hello, world!"); -} diff --git a/ferveo/src/vss/pvss.rs b/ferveo/src/vss/pvss.rs index 451caae3..719a0ab0 100644 --- a/ferveo/src/vss/pvss.rs +++ b/ferveo/src/vss/pvss.rs @@ -7,21 +7,25 @@ use ark_ec::bn::G2Affine; use ark_ec::PairingEngine; use ark_ff::UniformRand; use ark_serialize::*; -use ferveo_common::PublicKey; -use itertools::Itertools; +use ferveo_common::{Keypair, PublicKey}; +use group_threshold_cryptography::{Ciphertext, DecryptionShareSimple}; +use itertools::{zip_eq, Itertools}; use subproductdomain::fast_multiexp; /// These are the blinded evaluations of weight shares of a single random polynomial -pub type ShareEncryptions = Vec<::G2Affine>; +pub type ShareEncryptions = ::G2Affine; + /// Marker struct for unaggregated PVSS transcripts #[derive(CanonicalSerialize, CanonicalDeserialize, Clone, Debug)] pub struct Unaggregated; + /// Marker struct for aggregated PVSS transcripts #[derive(CanonicalSerialize, CanonicalDeserialize, Clone, Debug)] pub struct Aggregated; /// Trait gate used to add extra methods to aggregated PVSS transcripts pub trait Aggregate {} + /// Apply trait gate to Aggregated marker struct impl Aggregate for Aggregated {} @@ -42,7 +46,7 @@ pub struct PubliclyVerifiableParams { /// 2/3 the total), this will be aggregated into a final key #[derive(CanonicalSerialize, CanonicalDeserialize, Clone, Debug)] pub struct PubliclyVerifiableSS { - /// Feldman commitment to the VSS polynomial, F = g^{\phi} + /// Used in Feldman commitment to the VSS polynomial, F = g^{\phi} pub coeffs: Vec, /// The shares to be dealt to each validator @@ -66,11 +70,14 @@ impl PubliclyVerifiableSS { dkg: &PubliclyVerifiableDkg, rng: &mut R, ) -> Result { + // Our random polynomial, \phi(x) = s + \sum_{i=1}^{t-1} a_i x^i let mut phi = DensePolynomial::::rand( - (dkg.params.total_weight - dkg.params.security_threshold) as usize, + (dkg.params.shares_num - dkg.params.security_threshold) as usize, rng, ); - phi.coeffs[0] = *s; + phi.coeffs[0] = *s; // setting the first coefficient to secret value + + // Evaluations of the polynomial over the domain let evals = phi.evaluate_over_domain_by_ref(dkg.domain); // commitment to coeffs, F_i let coeffs = fast_multiexp(&phi.coeffs, dkg.pvss_params.g); @@ -81,9 +88,9 @@ impl PubliclyVerifiableSS { // ek_{i}^{eval_i}, i = validator index fast_multiexp( // &evals.evals[i..i] = &evals.evals[i] - &evals.evals[val.share_start..val.share_end], + &[evals.evals[val.share_index]], // one share per validator val.validator.public_key.encryption_key.into_projective(), - ) + )[0] }) .collect::>>(); if shares.len() != dkg.validators.len() { @@ -91,7 +98,9 @@ impl PubliclyVerifiableSS { "Not all validator session keys have been announced" )); } - //phi.zeroize(); // TODO zeroize? + // phi.zeroize(); // TODO zeroize? + // TODO: Cross check proof of knowledge check with the whitepaper; this check proves that there is a relationship between the secret and the pvss transcript + // Sigma is a proof of knowledge of the secret, sigma = h^s let sigma = E::G2Affine::prime_subgroup_generator().mul(*s).into(); //todo hash to curve let vss = Self { coeffs, @@ -106,10 +115,15 @@ impl PubliclyVerifiableSS { /// i.e. we optimistically do not check the commitment. This is deferred /// until the aggregation step pub fn verify_optimistic(&self) -> bool { + // We're only checking the proof of knowledge here, sigma ?= h^s + // "Does the first coefficient of the secret polynomial match the proof of knowledge?" E::pairing( - self.coeffs[0].into_projective(), - E::G2Affine::prime_subgroup_generator(), - ) == E::pairing(E::G1Affine::prime_subgroup_generator(), self.sigma) + self.coeffs[0].into_projective(), // F_0 = g^s + E::G2Affine::prime_subgroup_generator(), // h + ) == E::pairing( + E::G1Affine::prime_subgroup_generator(), // g + self.sigma, // h^s + ) } /// Part of checking the validity of an aggregated PVSS transcript @@ -117,37 +131,29 @@ impl PubliclyVerifiableSS { /// If aggregation fails, a validator needs to know that their pvss /// transcript was at fault so that the can issue a new one. This /// function may also be used for that purpose. - pub fn verify_full( - &self, - dkg: &PubliclyVerifiableDkg, - rng: &mut R, - ) -> bool { + pub fn verify_full(&self, dkg: &PubliclyVerifiableDkg) -> bool { // compute the commitment let mut commitment = batch_to_projective(&self.coeffs); print_time!("commitment fft"); dkg.domain.fft_in_place(&mut commitment); + // Each validator checks that their share is correct dkg.validators.iter().zip(self.shares.iter()).all( - |(validator, shares)| { + |(validator, share)| { + // ek is the public key of the validator + // TODO: Is that the ek = [dk]H key? let ek = validator .validator .public_key .encryption_key .into_projective(); - let alpha = E::Fr::rand(rng); - let mut powers_of_alpha = alpha; - let mut y = E::G2Projective::zero(); - let mut a = E::G1Projective::zero(); - for (y_i, a_i) in shares.iter().zip_eq( - commitment[validator.share_start..validator.share_end] - .iter(), - ) { - y += y_i.mul(powers_of_alpha.into_repr()); - a += a_i.mul(powers_of_alpha.into_repr()); - powers_of_alpha *= alpha; - } - // Y = \sum_i y_i \alpha^i - // A = \sum_i a_i \alpha^i + // Validator checks checks aggregated shares against commitment + // TODO: Check #3 is missing + // See #3 in 4.2.3 section of https://eprint.iacr.org/2022/898.pdf + let y = *share; + let a = commitment[validator.share_index]; + // We verify that e(G, Y_j) = e(A_j, ek_j) for all j + // See #4 in 4.2.3 section of https://eprint.iacr.org/2022/898.pdf // e(G,Y) = e(A, ek) E::pairing(dkg.pvss_params.g, y) == E::pairing(a, ek) }, @@ -161,21 +167,23 @@ impl PubliclyVerifiableSS { /// the PVSS instances, produced by [`aggregate`], /// and received by the DKG context `dkg` /// Returns the total valid weight of the aggregated PVSS - pub fn verify_aggregation( + pub fn verify_aggregation( &self, dkg: &PubliclyVerifiableDkg, - rng: &mut R, ) -> Result { print_time!("PVSS verify_aggregation"); - self.verify_full(dkg, rng); + self.verify_full(dkg); + // Now, we verify that the aggregated PVSS transcript is a valid aggregation + // If it is, we return the total weights of the PVSS transcripts let mut y = E::G1Projective::zero(); - let mut weight = 0u32; - for (dealer, pvss) in dkg.vss.iter() { + // TODO: If we don't deal with share weights anymore, do we even need to call `verify_aggregation`? + let mut shares_total = 0u32; + for (_, pvss) in dkg.vss.iter() { y += pvss.coeffs[0].into_projective(); - weight += dkg.validators[*dealer as usize].weight; + shares_total += 1 } if y.into_affine() == self.coeffs[0] { - Ok(weight) + Ok(shares_total) } else { Err(anyhow!( "aggregation does not match received PVSS instances" @@ -195,11 +203,11 @@ pub fn aggregate( let mut coeffs = batch_to_projective(&first_pvss.coeffs); let mut sigma = first_pvss.sigma; - let mut shares = first_pvss - .shares - .iter() - .map(|a| batch_to_projective(a)) - .collect::>(); + let mut shares = batch_to_projective(&first_pvss.shares); + + // 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 for (_, next) in pvss_iter { sigma = sigma.add(next.sigma); coeffs @@ -209,16 +217,9 @@ pub fn aggregate( shares .iter_mut() .zip_eq(next.shares.iter()) - .for_each(|(a, b)| { - a.iter_mut() - .zip_eq(b.iter()) - .for_each(|(c, d)| *c += d.into_projective()) - }); + .for_each(|(a, b)| *a += b.into_projective()); } - let shares = shares - .iter() - .map(|a| E::G2Projective::batch_normalization_into_affine(a)) - .collect::>(); + let shares = E::G2Projective::batch_normalization_into_affine(&shares); PubliclyVerifiableSS { coeffs: E::G1Projective::batch_normalization_into_affine(&coeffs), @@ -228,6 +229,54 @@ pub fn aggregate( } } +pub fn aggregate_for_decryption( + dkg: &PubliclyVerifiableDkg, +) -> Vec> { + // From docs: https://nikkolasg.github.io/ferveo/pvss.html?highlight=aggregate#aggregation + // "Two PVSS instances may be aggregated into a single PVSS instance by adding elementwise each of the corresponding group elements." + let shares = dkg + .vss + .values() + .map(|pvss| pvss.shares.clone()) + .collect::>(); + let first_share = shares.first().unwrap().to_vec(); + shares + .into_iter() + .skip(1) + // We're assuming that in every PVSS instance, the shares are in the same order + .fold(first_share, |acc, shares| { + acc.into_iter() + .zip_eq(shares.into_iter()) + .map(|(a, b)| a + b) + .collect() + }) +} + +pub fn make_decryption_shares( + ciphertext: &Ciphertext, + validator_keypairs: Vec>, + aggregate: Vec, +) -> Vec> { + aggregate + .iter() + .zip_eq(validator_keypairs.iter()) + .map(|(encrypted_share, keypair)| { + // Decrypt private key shares https://nikkolasg.github.io/ferveo/pvss.html#validator-decryption-of-private-key-shares + let z_i = encrypted_share + .mul(keypair.decryption_key.inverse().unwrap().into_repr()); + let u = ciphertext.commitment; + E::pairing(u, z_i) + }) + .enumerate() + .map( + |(decrypter_index, decryption_share)| DecryptionShareSimple { + decrypter_index, + decryption_share, + }, + ) + .collect::>() +} + #[cfg(test)] mod test_pvss { use super::*; @@ -235,7 +284,8 @@ mod test_pvss { use crate::dkg::pv::test_common::*; use ark_bls12_381::Bls12_381 as EllipticCurve; use ark_ff::UniformRand; - use ferveo_common::{TendermintValidator, ValidatorSet}; + use ferveo_common::ExternalValidator; + type Fr = ::Fr; type G1 = ::G1Affine; type G2 = ::G2Affine; @@ -252,15 +302,18 @@ mod test_pvss { // check that the chosen secret coefficient is correct assert_eq!(pvss.coeffs[0], G1::prime_subgroup_generator().mul(s)); //check that a polynomial of the correct degree was created - assert_eq!(pvss.coeffs.len(), 5); + assert_eq!( + pvss.coeffs.len(), + dkg.params.security_threshold as usize + 1 + ); // check that the correct number of shares were created - assert_eq!(pvss.shares.len(), 4); + assert_eq!(pvss.shares.len(), dkg.validators.len()); // check that the prove of knowledge is correct assert_eq!(pvss.sigma, G2::prime_subgroup_generator().mul(s)); // check that the optimistic verify returns true assert!(pvss.verify_optimistic()); // check that the full verify returns true - assert!(pvss.verify_full(&dkg, rng)); + assert!(pvss.verify_full(&dkg)); } /// Check that if the proof of knowledge is wrong, @@ -286,23 +339,23 @@ mod test_pvss { /// Should have the correct form and validations pass #[test] fn test_aggregate_pvss() { - let rng = &mut ark_std::test_rng(); let dkg = setup_dealt_dkg(); let aggregate = aggregate(&dkg); //check that a polynomial of the correct degree was created - assert_eq!(aggregate.coeffs.len(), 5); + assert_eq!( + aggregate.coeffs.len(), + dkg.params.security_threshold as usize + 1 + ); // check that the correct number of shares were created - assert_eq!(aggregate.shares.len(), 4); + assert_eq!(aggregate.shares.len(), dkg.validators.len()); // check that the optimistic verify returns true assert!(aggregate.verify_optimistic()); // check that the full verify returns true - assert!(aggregate.verify_full(&dkg, rng)); + assert!(aggregate.verify_full(&dkg)); // check that the verification of aggregation passes assert_eq!( - aggregate - .verify_aggregation(&dkg, rng) - .expect("Test failed"), - 6 + aggregate.verify_aggregation(&dkg).expect("Test failed"), + dkg.validators.len() as u32 ); } @@ -311,7 +364,6 @@ mod test_pvss { #[test] fn test_verify_aggregation_fails_if_constant_term_wrong() { use std::ops::Neg; - let rng = &mut ark_std::test_rng(); let dkg = setup_dealt_dkg(); let mut aggregated = aggregate(&dkg); while aggregated.coeffs[0] == G1::zero() { @@ -321,7 +373,7 @@ mod test_pvss { aggregated.coeffs[0] = G1::zero(); assert_eq!( aggregated - .verify_aggregation(&dkg, rng) + .verify_aggregation(&dkg) .expect_err("Test failed") .to_string(), "aggregation does not match received PVSS instances" diff --git a/subproductdomain/src/lib.rs b/subproductdomain/src/lib.rs index e7136a3c..e2d329ad 100644 --- a/subproductdomain/src/lib.rs +++ b/subproductdomain/src/lib.rs @@ -303,7 +303,7 @@ impl SubproductTree { pub fn derivative(f: &Poly) -> Poly { let mut coeffs = Vec::with_capacity(f.coeffs().len() - 1); for (i, c) in f.coeffs.iter().enumerate().skip(1) { - coeffs.push(F::from(i as u64) * c); + coeffs.push(F::from(i as u128) * c); } Poly:: { coeffs } } @@ -374,7 +374,7 @@ pub fn toeplitz_mul( Ok(( tmp[..toeplitz_size].to_vec(), - E::Fr::from(domain.size() as u64).inverse().unwrap(), + E::Fr::from(domain.size() as u128).inverse().unwrap(), )) } diff --git a/tpke/benches/tpke.rs b/tpke/benches/tpke.rs index 965b004b..1ab7d2a8 100644 --- a/tpke/benches/tpke.rs +++ b/tpke/benches/tpke.rs @@ -110,7 +110,8 @@ impl SetupSimple { .collect(); let pub_contexts = contexts[0].clone().public_decryption_contexts; - let lagrange = prepare_combine_simple::(&pub_contexts); + let domain: Vec = pub_contexts.iter().map(|c| c.domain).collect(); + let lagrange = prepare_combine_simple::(&domain); let shared_secret = share_combine_simple::(&decryption_shares, &lagrange); @@ -203,7 +204,9 @@ pub fn bench_share_prepare(c: &mut Criterion) { }; let simple = { let setup = SetupSimple::new(shares_num, msg_size, rng); - move || black_box(prepare_combine_simple(&setup.pub_contexts)) + let domain: Vec = + setup.pub_contexts.iter().map(|c| c.domain).collect(); + move || black_box(prepare_combine_simple::(&domain)) }; group.bench_function( diff --git a/tpke/src/api.rs b/tpke/src/api.rs index 492862c7..ee769e9a 100644 --- a/tpke/src/api.rs +++ b/tpke/src/api.rs @@ -1,5 +1,7 @@ //! Contains the public API of the library. +#![allow(dead_code)] + // TODO: Refactor this module to deduplicate shared code from tpke-wasm and tpke-wasm. use std::convert::TryInto; diff --git a/tpke/src/ciphertext.rs b/tpke/src/ciphertext.rs index 76b2ee8a..68389d53 100644 --- a/tpke/src/ciphertext.rs +++ b/tpke/src/ciphertext.rs @@ -56,7 +56,6 @@ impl Ciphertext { ); let auth_tag = E::G2Affine::read(&auth_tag_bytes[..]).unwrap(); - const CIPHERTEXT_LEN: usize = 33; let ciphertext = bytes[COMMITMENT_LEN + AUTH_TAG_LEN..].to_vec(); Self { @@ -118,17 +117,6 @@ pub fn check_ciphertext_validity( ]) == E::Fqk::one() } -fn decrypt( - ciphertext: &Ciphertext, - privkey: E::G2Affine, -) -> Vec { - let s = E::product_of_pairings(&[( - E::G1Prepared::from(ciphertext.commitment), - E::G2Prepared::from(privkey), - )]); - decrypt_with_shared_secret(ciphertext, &s) -} - pub fn checked_decrypt( ciphertext: &Ciphertext, aad: &[u8], diff --git a/tpke/src/combine.rs b/tpke/src/combine.rs index bc0b655c..a1b54870 100644 --- a/tpke/src/combine.rs +++ b/tpke/src/combine.rs @@ -11,30 +11,19 @@ pub fn prepare_combine_fast( let mut domain = vec![]; // omega_i, vector of domain points let mut n_0 = E::Fr::one(); for d_i in shares.iter() { - // There's just one domain point per participant, TODO: Refactor underlying data structures - assert_eq!( - public_decryption_contexts[d_i.decrypter_index].domain.len(), - 1 - ); - domain.push(public_decryption_contexts[d_i.decrypter_index].domain[0]); + domain.push(public_decryption_contexts[d_i.decrypter_index].domain); n_0 *= public_decryption_contexts[d_i.decrypter_index].lagrange_n_0; // n_0_i = 1 * t^1 * t^2 ... } let s = SubproductDomain::::new(domain); let mut lagrange = s.inverse_lagrange_coefficients(); // 1/L_i - // TODO: If this is really 1/L_i can I just return here and use it directly? Or is 1/L_i somehow different from L_i(0)? // Given a vector of field elements {v_i}, compute the vector {coeff * v_i^(-1)} ark_ff::batch_inversion_and_mul(&mut lagrange, &n_0); // n_0 * L_i // L_i * [b]Z_i izip!(shares.iter(), lagrange.iter()) .map(|(d_i, lambda)| { let decrypter = &public_decryption_contexts[d_i.decrypter_index]; - // There's just one share per participant, TODO: Refactor underlying data structures - assert_eq!( - decrypter.blinded_key_shares.blinded_key_shares.len(), - 1 - ); let blinded_key_share = - decrypter.blinded_key_shares.blinded_key_shares[0]; + decrypter.blinded_key_share.blinded_key_share; E::G2Prepared::from( // [b]Z_i * L_i blinded_key_share.mul(*lambda).into_affine(), @@ -44,12 +33,11 @@ pub fn prepare_combine_fast( } pub fn prepare_combine_simple( - pub_contexts: &[PublicDecryptionContextSimple], + domain: &[E::Fr], ) -> Vec { - let shares_x: Vec<_> = pub_contexts.iter().map(|c| c.domain).collect(); // See https://en.wikipedia.org/wiki/Lagrange_polynomial#Optimal_algorithm // In this formula x_i = 0, hence numerator is x_m - lagrange_basis_at::(&shares_x, &E::Fr::zero()) + lagrange_basis_at::(domain, &E::Fr::zero()) } /// Calculate lagrange coefficients using optimized formula @@ -78,7 +66,7 @@ pub fn share_combine_fast( let mut pairing_product: Vec<(E::G1Prepared, E::G2Prepared)> = vec![]; for (d_i, prepared_key_share) in izip!(shares, prepared_key_shares.iter()) { - // e(D_i, [b*omega_i^-1] Z_{i,omega_i}), TODO: Is this formula correct? + // e(D_i, [b*omega_i^-1] Z_{i,omega_i}) pairing_product.push(( // D_i E::G1Prepared::from(d_i.decryption_share), diff --git a/tpke/src/context.rs b/tpke/src/context.rs index ddff1a25..89e21e6a 100644 --- a/tpke/src/context.rs +++ b/tpke/src/context.rs @@ -3,9 +3,9 @@ use ark_ec::ProjectiveCurve; #[derive(Clone, Debug)] pub struct PublicDecryptionContextFast { - pub domain: Vec, - pub public_key_shares: PublicKeyShares, - pub blinded_key_shares: BlindedKeyShares, + pub domain: E::Fr, + pub public_key_share: PublicKeyShare, + pub blinded_key_share: BlindedKeyShare, // This decrypter's contribution to N(0), namely (-1)^|domain| * \prod_i omega_i pub lagrange_n_0: E::Fr, } @@ -13,8 +13,8 @@ pub struct PublicDecryptionContextFast { #[derive(Clone, Debug)] pub struct PublicDecryptionContextSimple { pub domain: E::Fr, - pub public_key_shares: PublicKeyShares, - pub blinded_key_shares: BlindedKeyShares, + pub public_key_share: PublicKeyShare, + pub blinded_key_share: BlindedKeyShare, } #[derive(Clone, Debug)] @@ -34,7 +34,6 @@ pub struct PrivateDecryptionContextFast { pub private_key_share: PrivateKeyShare, pub public_decryption_contexts: Vec>, pub scalar_bits: usize, - pub window_size: usize, } impl PrivateDecryptionContextFast { @@ -42,9 +41,10 @@ impl PrivateDecryptionContextFast { &self, ciphertext: &Ciphertext, ) -> DecryptionShareFast { - // let decryption_share = - // ciphertext.commitment.mul(self.b_inv).into_affine(); - let decryption_share = ciphertext.commitment; + let decryption_share = ciphertext + .commitment + .mul(self.setup_params.b_inv) + .into_affine(); DecryptionShareFast { decrypter_index: self.index, @@ -66,7 +66,7 @@ impl PrivateDecryptionContextFast { .iter() .map(|d| { self.public_decryption_contexts[d.decrypter_index] - .blinded_key_shares + .blinded_key_share .blinding_key_prepared .clone() }) @@ -132,7 +132,7 @@ impl PrivateDecryptionContextSimple { ) -> DecryptionShareSimple { let u = ciphertext.commitment; let z_i = self.private_key_share.clone(); - let z_i = z_i.private_key_shares[0]; + let z_i = z_i.private_key_share; // C_i = e(U, Z_i) let c_i = E::pairing(u, z_i); DecryptionShareSimple { diff --git a/tpke/src/decryption.rs b/tpke/src/decryption.rs index b00f7379..c09a9628 100644 --- a/tpke/src/decryption.rs +++ b/tpke/src/decryption.rs @@ -41,3 +41,24 @@ pub struct DecryptionShareSimple { pub decrypter_index: usize, pub decryption_share: E::Fqk, } + +#[cfg(test)] +mod tests { + use crate::*; + + type E = ark_bls12_381::Bls12_381; + + #[test] + fn decryption_share_serialization() { + let decryption_share = DecryptionShareFast:: { + decrypter_index: 1, + decryption_share: ark_bls12_381::G1Affine::prime_subgroup_generator( + ), + }; + + let serialized = decryption_share.to_bytes(); + let deserialized: DecryptionShareFast = + DecryptionShareFast::from_bytes(&serialized); + assert_eq!(serialized, deserialized.to_bytes()) + } +} diff --git a/tpke/src/key_share.rs b/tpke/src/key_share.rs index 9719a745..241963f3 100644 --- a/tpke/src/key_share.rs +++ b/tpke/src/key_share.rs @@ -3,112 +3,63 @@ use crate::*; use ark_ec::ProjectiveCurve; -use itertools::Itertools; #[derive(Debug, Clone)] -pub struct PublicKeyShares { - pub public_key_shares: Vec, // A_{i, \omega_i} +pub struct PublicKeyShare { + pub public_key_share: E::G1Affine, // A_{i, \omega_i} } #[derive(Debug, Clone)] -pub struct BlindedKeyShares { - pub blinding_key: E::G2Affine, - pub blinding_key_prepared: E::G2Prepared, // [b] H - pub blinded_key_shares: Vec, // [b] Z_{i, \omega_i} - pub window_tables: Vec>, // [b*omega_i^-1] Z_{i, \omega_i} +pub struct BlindedKeyShare { + pub blinding_key: E::G2Affine, // [b] H + pub blinded_key_share: E::G2Affine, // [b] Z_{i, \omega_i} + pub blinding_key_prepared: E::G2Prepared, } -impl BlindedKeyShares { +impl BlindedKeyShare { pub fn verify_blinding( &self, - public_key_shares: &PublicKeyShares, + public_key_share: &PublicKeyShare, rng: &mut R, ) -> bool { let g = E::G1Affine::prime_subgroup_generator(); - let _alpha = E::Fr::rand(rng); - let alpha_i = generate_random::<_, E>( - public_key_shares.public_key_shares.len(), - rng, - ); + let alpha = E::Fr::rand(rng); - let alpha_a_i = E::G1Prepared::from( - g + public_key_shares - .public_key_shares - .iter() - .zip_eq(alpha_i.iter()) - .map(|(key, alpha)| key.mul(*alpha)) - .sum::() - .into_affine(), + let alpha_a = E::G1Prepared::from( + g + public_key_share.public_key_share.mul(alpha).into_affine(), ); // Sum of Yi - let alpha_z_i = E::G2Prepared::from( - self.blinding_key - + self - .blinded_key_shares - .iter() - .zip_eq(alpha_i.iter()) - .map(|(key, alpha)| key.mul(*alpha)) - .sum::() - .into_affine(), + let alpha_z = E::G2Prepared::from( + self.blinding_key + self.blinded_key_share.mul(alpha).into_affine(), ); - // e(g, sum(Yi)) == e(sum(Ai), [b] H) + // e(g, Yi) == e(Ai, [b] H) E::product_of_pairings(&[ - (E::G1Prepared::from(-g), alpha_z_i), - (alpha_a_i, E::G2Prepared::from(self.blinding_key)), + (E::G1Prepared::from(-g), alpha_z), + (alpha_a, E::G2Prepared::from(self.blinding_key)), ]) == E::Fqk::one() } - pub fn get_window_table( - &self, - window_size: usize, - scalar_bits: usize, - domain_inv: &[E::Fr], - ) -> Vec> { - izip!(self.blinded_key_shares.iter(), domain_inv.iter()) - .map(|(key, omega_inv)| BlindedKeyShareWindowTable:: { - window_table: FixedBaseMSM::get_window_table( - scalar_bits, - window_size, - key.mul(-*omega_inv), - ), - }) - .collect::>() - } - - // key shares = [a, b, c] - // domain_inv = [1, 2, 3] - // output = [a * 1, b * 2, c * 3] - pub fn multiply_by_omega_inv(&mut self, domain_inv: &[E::Fr]) { - izip!(self.blinded_key_shares.iter_mut(), domain_inv.iter()).for_each( - |(key, omega_inv)| *key = key.mul(-*omega_inv).into_affine(), - ) + pub fn multiply_by_omega_inv(&mut self, omega_inv: &E::Fr) { + self.blinded_key_share = + self.blinded_key_share.mul(-*omega_inv).into_affine(); } } -#[derive(Debug, Clone)] -pub struct BlindedKeyShareWindowTable { - pub window_table: Vec>, -} #[derive(Debug, Clone)] pub struct PrivateKeyShare { - pub private_key_shares: Vec, + pub private_key_share: E::G2Affine, } impl PrivateKeyShare { - pub fn blind(&self, b: E::Fr) -> BlindedKeyShares { + pub fn blind(&self, b: E::Fr) -> BlindedKeyShare { let blinding_key = E::G2Affine::prime_subgroup_generator().mul(b).into_affine(); - BlindedKeyShares:: { + BlindedKeyShare:: { blinding_key, blinding_key_prepared: E::G2Prepared::from(blinding_key), - blinded_key_shares: self - .private_key_shares - .iter() - .map(|z| z.mul(b).into_affine()) - .collect::>(), - window_tables: vec![], + blinded_key_share: self.private_key_share.mul(b).into_affine(), } } } diff --git a/tpke/src/lib.rs b/tpke/src/lib.rs index 5a3f16ff..b2be3ff6 100644 --- a/tpke/src/lib.rs +++ b/tpke/src/lib.rs @@ -1,10 +1,7 @@ -#![allow(non_snake_case)] -#![allow(dead_code)] - use crate::hash_to_curve::htp_bls12381_g2; use crate::SetupParams; -use ark_ec::{msm::FixedBaseMSM, AffineCurve, PairingEngine}; +use ark_ec::{AffineCurve, PairingEngine}; use ark_ff::{Field, One, PrimeField, ToBytes, UniformRand, Zero}; use ark_poly::{ univariate::DensePolynomial, EvaluationDomain, Polynomial, UVPolynomial, @@ -109,40 +106,29 @@ pub fn setup_fast( let mut domain_points_inv = Vec::with_capacity(shares_num); let mut point_inv = E::Fr::one(); - // domain_points are the powers of the generator g - // domain_points_inv are the powers of the inverse of the generator g - // It seems like domain points are being used in "share partitioning" - // https://nikkolasg.github.io/ferveo/dkginit.html#share-partitioning - // There's also a mention of this operation here: - // "DKG.PartitionDomain({ek_i, s_i}) -> {(ek_i, Omega_i)}" - // https://nikkolasg.github.io/ferveo/tpke-concrete.html for _ in 0..shares_num { - // domain_points is the share domain of the i-th participant (?) domain_points.push(point); // 1, t, t^2, t^3, ...; where t is a scalar generator fft_domain.group_gen point *= fft_domain.group_gen; domain_points_inv.push(point_inv); point_inv *= fft_domain.group_gen_inv; } - let window_size = FixedBaseMSM::get_mul_window_size(100); let scalar_bits = E::Fr::size_in_bits(); // A - public key shares of participants let pubkey_shares = subproductdomain::fast_multiexp(&evals.evals, g.into_projective()); let pubkey_share = g.mul(evals.evals[0]); - assert!(pubkey_shares[0] == E::G1Affine::from(pubkey_share)); + debug_assert!(pubkey_shares[0] == E::G1Affine::from(pubkey_share)); // Y, but only when b = 1 - private key shares of participants let privkey_shares = subproductdomain::fast_multiexp(&evals.evals, h.into_projective()); - // h^{f(omega)} // a_0 let x = threshold_poly.coeffs[0]; // F_0 - The commitment to the constant term, and is the public key output Y from PVDKG - // TODO: It seems like the rest of the F_i are not computed? let pubkey = g.mul(x); let privkey = h.mul(x); @@ -151,26 +137,19 @@ pub fn setup_fast( // (domain, domain_inv, A, Y) for (index, (domain, domain_inv, public, private)) in izip!( - // Since we're assigning only one key share to one entity we can use chunks(1) - // This is a quick workaround to avoid refactoring all related entities that assume there are multiple key shares - // TODO: Refactor this code and all related code - domain_points.chunks(1), - domain_points_inv.chunks(1), - pubkey_shares.chunks(1), - privkey_shares.chunks(1) + domain_points.iter(), + domain_points_inv.iter(), + pubkey_shares.iter(), + privkey_shares.iter() ) .enumerate() { let private_key_share = PrivateKeyShare:: { - private_key_shares: private.to_vec(), + private_key_share: *private, }; - let b = E::Fr::one(); // TODO: Not blinding for now + let b = E::Fr::rand(rng); let mut blinded_key_shares = private_key_share.blind(b); blinded_key_shares.multiply_by_omega_inv(domain_inv); - // TODO: Is `blinded_key_shares` equal to [b]Z_{i,omega_i})? - // Z_{i,omega_i}) = [dk_{i}^{-1}]*\hat{Y}_{i_omega_j}] - /*blinded_key_shares.window_tables = - blinded_key_shares.get_window_table(window_size, scalar_bits, domain_inv);*/ private_contexts.push(PrivateDecryptionContextFast:: { index, setup_params: SetupParams { @@ -184,15 +163,14 @@ pub fn setup_fast( private_key_share, public_decryption_contexts: vec![], scalar_bits, - window_size, }); public_contexts.push(PublicDecryptionContextFast:: { - domain: domain.to_vec(), - public_key_shares: PublicKeyShares:: { - public_key_shares: public.to_vec(), + domain: *domain, + public_key_share: PublicKeyShare:: { + public_key_share: *public, }, - blinded_key_shares, - lagrange_n_0: domain.iter().product::(), + blinded_key_share: blinded_key_shares, + lagrange_n_0: *domain, }); } for private in private_contexts.iter_mut() { @@ -216,7 +194,7 @@ pub fn setup_simple( let g = E::G1Affine::prime_subgroup_generator(); let h = E::G2Affine::prime_subgroup_generator(); - // The delear chooses a uniformly random polynomial f of degree t-1 + // The dealer chooses a uniformly random polynomial f of degree t-1 let threshold_poly = DensePolynomial::::rand(threshold - 1, rng); // Domain, or omega Ω let fft_domain = @@ -233,14 +211,12 @@ pub fn setup_simple( assert!(pubkey_shares[0] == E::G1Affine::from(pubkey_share)); // Y, but only when b = 1 - private key shares of participants - // Z_i = h^{f(omega)} ? let privkey_shares = subproductdomain::fast_multiexp(&evals.evals, h.into_projective()); // a_0 let x = threshold_poly.coeffs[0]; // F_0 - // TODO: It seems like the rest of the F_i are not computed? let pubkey = g.mul(x); let privkey = h.mul(x); @@ -251,21 +227,14 @@ pub fn setup_simple( let mut public_contexts = vec![]; // (domain, A, Y) - for (index, (domain, public, private)) in izip!( - // Since we're assigning only one key share to one entity we can use chunks(1) - // This is a quick workaround to avoid refactoring all related entities that assume there are multiple key shares - // TODO: Refactor this code and all related code - shares_x.chunks(1), - pubkey_shares.chunks(1), - privkey_shares.chunks(1) - ) - .enumerate() + for (index, (domain, public, private)) in + izip!(shares_x.iter(), pubkey_shares.iter(), privkey_shares.iter()) + .enumerate() { let private_key_share = PrivateKeyShare:: { - private_key_shares: private.to_vec(), + private_key_share: *private, }; - // let b = E::Fr::rand(rng); - let b = E::Fr::one(); // TODO: Not blinding for now + let b = E::Fr::rand(rng); let blinded_key_shares = private_key_share.blind(b); private_contexts.push(PrivateDecryptionContextSimple:: { index, @@ -281,22 +250,17 @@ pub fn setup_simple( public_decryption_contexts: vec![], }); public_contexts.push(PublicDecryptionContextSimple:: { - domain: domain[0], - public_key_shares: PublicKeyShares:: { - public_key_shares: public.to_vec(), + domain: *domain, + public_key_share: PublicKeyShare:: { + public_key_share: *public, }, - blinded_key_shares, + blinded_key_share: blinded_key_shares, }); } for private in private_contexts.iter_mut() { private.public_decryption_contexts = public_contexts.clone(); } - // TODO: Should we also be returning some sort of signed transcript? - // "Post the signed message \(\tau, (F_0, \ldots, F_t), \hat{u}2, (\hat{Y}{i,\omega_j})\) to the blockchain" - // \tau - unique session identifier - // See: https://nikkolasg.github.io/ferveo/pvss.html#dealers-role - (pubkey.into(), privkey.into(), private_contexts) } @@ -307,16 +271,6 @@ pub fn generate_random( (0..n).map(|_| E::Fr::rand(rng)).collect::>() } -fn make_decryption_share( - private_share: &PrivateKeyShare, - ciphertext: &Ciphertext, -) -> E::Fqk { - let z_i = private_share; - let u = ciphertext.commitment; - let z_i = z_i.private_key_shares[0]; - E::pairing(u, z_i) -} - #[cfg(test)] mod tests { use crate::*; @@ -345,20 +299,6 @@ mod tests { assert_eq!(serialized, deserialized.to_bytes()) } - #[test] - fn decryption_share_serialization() { - let decryption_share = DecryptionShareFast:: { - decrypter_index: 1, - decryption_share: ark_bls12_381::G1Affine::prime_subgroup_generator( - ), - }; - - let serialized = decryption_share.to_bytes(); - let deserialized: DecryptionShareFast = - DecryptionShareFast::from_bytes(&serialized); - assert_eq!(serialized, deserialized.to_bytes()) - } - #[test] fn symmetric_encryption() { let rng = &mut test_rng(); @@ -434,8 +374,8 @@ mod tests { #[test] fn fast_threshold_encryption() { let mut rng = &mut test_rng(); - let threshold = 16 * 2 / 3; let shares_num = 16; + let threshold = shares_num * 2 / 3; let msg: &[u8] = "abc".as_bytes(); let aad: &[u8] = "my-aad".as_bytes(); @@ -466,8 +406,8 @@ mod tests { #[test] fn simple_threshold_decryption() { let mut rng = &mut test_rng(); - let threshold = 16 * 2 / 3; let shares_num = 16; + let threshold = shares_num * 2 / 3; let msg: &[u8] = "abc".as_bytes(); let aad: &[u8] = "my-aad".as_bytes(); @@ -482,9 +422,12 @@ mod tests { .iter() .map(|c| c.create_share(&ciphertext)) .collect(); - let lagrange = prepare_combine_simple::( - &contexts[0].public_decryption_contexts, - ); + let domain = contexts[0] + .public_decryption_contexts + .iter() + .map(|c| c.domain) + .collect::>(); + let lagrange = prepare_combine_simple::(&domain); let shared_secret = share_combine_simple::(&decryption_shares, &lagrange); @@ -513,7 +456,7 @@ mod tests { .unwrap() .domain; let original_y_r = - selected_participant.private_key_share.private_key_shares[0]; + selected_participant.private_key_share.private_key_share; // Now, we have to remove the participant from the contexts and all nested structures let mut remaining_participants = contexts; @@ -549,10 +492,21 @@ mod tests { pub_contexts: &[PublicDecryptionContextSimple], decryption_shares: &[DecryptionShareSimple], ) -> E::Fqk { - let lagrange = prepare_combine_simple::(pub_contexts); + let domain = pub_contexts.iter().map(|c| c.domain).collect::>(); + let lagrange = prepare_combine_simple::(&domain); share_combine_simple::(decryption_shares, &lagrange) } + fn make_decryption_share( + private_share: &PrivateKeyShare, + ciphertext: &Ciphertext, + ) -> E::Fqk { + let z_i = private_share; + let u = ciphertext.commitment; + let z_i = z_i.private_key_share; + E::pairing(u, z_i) + } + #[test] /// Ñ parties (where t <= Ñ <= N) jointly execute a "share recovery" algorithm, and the output is 1 new share. /// The new share is independent from the previously existing shares. We can use this to on-board a new participant into an existing cohort. @@ -589,7 +543,7 @@ mod tests { rng, ); let recovered_key_share = PrivateKeyShare { - private_key_shares: vec![y_r.into_affine()], + private_key_share: y_r.into_affine(), }; // Creating decryption shares @@ -645,7 +599,7 @@ mod tests { .enumerate() .map(|(decrypter_index, private_share)| { let private_share = PrivateKeyShare { - private_key_shares: vec![private_share.into_affine()], + private_key_share: private_share.into_affine(), }; let decryption_share = make_decryption_share(&private_share, &ciphertext); diff --git a/tpke/src/refresh.rs b/tpke/src/refresh.rs index b233ed10..66821ea4 100644 --- a/tpke/src/refresh.rs +++ b/tpke/src/refresh.rs @@ -74,7 +74,7 @@ fn update_shares_for_recovery( .map(|p| { let i = p.index; let mut new_y = E::G2Projective::from( - p.private_key_share.private_key_shares[0], // y_i + p.private_key_share.private_key_share, // y_i ); for j in deltas.keys() { new_y += deltas[j][&i]; @@ -145,7 +145,7 @@ pub fn refresh_shares( .map(|p| { let i = p.index; let mut new_y = E::G2Projective::from( - p.private_key_share.private_key_shares[0], // y_i + p.private_key_share.private_key_share, // y_i ); new_y += share_updates[&i]; new_y