From 21b5c50b58c20e0b680fe882e59b5566d30762cf Mon Sep 17 00:00:00 2001 From: iquerejeta Date: Tue, 1 Jun 2021 14:51:05 +0100 Subject: [PATCH 01/10] Added docs and references --- chain-vote/src/committee.rs | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/chain-vote/src/committee.rs b/chain-vote/src/committee.rs index 7dcdc73a8..9c1d6cf77 100644 --- a/chain-vote/src/committee.rs +++ b/chain-vote/src/committee.rs @@ -1,3 +1,11 @@ +//! Implementation of the distributed key generation (DKG) +//! procedure presented by Gennaro, Jarecki, Krawczyk and Rabin in +//! ["Secure distributed key generation for discrete-log based cryptosystems."](https://link.springer.com/article/10.1007/s00145-006-0347-3). +//! The distinction with the original protocol lies in the use of hybrid +//! encryption. We use the description and notation presented in the technical +//! [spec](https://github.com/input-output-hk/treasury-crypto/blob/master/docs/voting_protocol_spec/Treasury_voting_protocol_spec.pdf), +//! written by Dmytro Kaidalov. + use crate::encryption::{HybridCiphertext, PublicKey, SecretKey}; use crate::gang::{GroupElement, Scalar}; use crate::math::Polynomial; @@ -12,6 +20,7 @@ pub struct MemberSecretKey(pub(crate) SecretKey); #[derive(Debug, Clone, Eq, PartialEq)] pub struct MemberPublicKey(pub(crate) PublicKey); +/// Committee member communication private key #[derive(Clone)] pub struct MemberCommunicationKey(SecretKey); @@ -41,15 +50,18 @@ pub struct MemberState { } impl MemberState { - /// Generate a new member state from random, where the number + /// Generate a new member state from random. This is round 1 of the protocol. Receives as + /// input the threshold `t`, the expected number of participants, `n`, common reference string + /// `crs`, `committee_pks`, and the party's index `my`. Initiates a Pedersen-VSS as a dealer. pub fn new( rng: &mut R, t: usize, - h: &Crs, // TODO: document + n: usize, + crs: &Crs, // TODO: document committee_pks: &[MemberCommunicationPublicKey], my: usize, ) -> MemberState { - let n = committee_pks.len(); + assert_eq!(committee_pks.len(), n); assert!(t > 0); assert!(t <= n); assert!(my < n); @@ -62,7 +74,7 @@ impl MemberState { for (ai, bi) in pshek.get_coefficients().zip(pcomm.get_coefficients()) { let apub = GroupElement::generator() * ai; - let e = &apub + h * bi; + let e = &apub + crs * bi; apubs.push(apub); es.push(e); } @@ -171,6 +183,10 @@ impl MemberCommunicationPublicKey { pub fn to_bytes(&self) -> Vec { self.0.to_bytes() } + + pub fn from_bytes(bytes: &[u8]) -> Option { + PublicKey::from_bytes(bytes).map(Self) + } } impl ElectionPublicKey { From f226584726904ab6fb8fd1d6f64e76554e06de55 Mon Sep 17 00:00:00 2001 From: iquerejeta Date: Mon, 7 Jun 2021 18:22:47 +0100 Subject: [PATCH 02/10] new errors --- chain-vote/src/errors.rs | 9 +++++++++ chain-vote/src/lib.rs | 26 ++++++++++++++++---------- 2 files changed, 25 insertions(+), 10 deletions(-) create mode 100644 chain-vote/src/errors.rs diff --git a/chain-vote/src/errors.rs b/chain-vote/src/errors.rs new file mode 100644 index 000000000..bbe7a223e --- /dev/null +++ b/chain-vote/src/errors.rs @@ -0,0 +1,9 @@ +/// Represents an error in Distributed Key Generation protocol. +#[derive(Clone, Debug, Eq, PartialEq)] +#[cfg_attr(feature = "std", derive(Error))] +pub enum DkgError { + /// This error occurs when a scalar parsing failed, due to the + /// byte-array representing a scalar out of bounds. + #[cfg_attr(feature = "std", error("Scalar out of bounds."))] + ScalarOutOfBounds, +} diff --git a/chain-vote/src/lib.rs b/chain-vote/src/lib.rs index 6e17ea0a6..9d09f8474 100644 --- a/chain-vote/src/lib.rs +++ b/chain-vote/src/lib.rs @@ -8,6 +8,7 @@ mod encrypted; pub mod encryption; mod gang; mod math; +mod errors; pub mod private_voting; mod unit_vector; @@ -23,7 +24,7 @@ pub mod debug { } pub use committee::{ - MemberCommunicationKey, MemberCommunicationPublicKey, MemberPublicKey, MemberState, + MemberCommunicationKey, MemberCommunicationPublicKey, MemberPublicKey, DistributedKeyGeneration, }; pub use encrypted::EncryptingVote; pub use encryption::Ciphertext; @@ -306,8 +307,9 @@ mod tests { let mc = [mc1.to_public()]; let threshold = 1; + let nr_members = 1; - let m1 = MemberState::new(&mut rng, threshold, &h, &mc, 0); + let m1 = DistributedKeyGeneration::init(&mut rng, threshold, nr_members, &h, &mc, 0); let participants = vec![m1.public_key()]; let ek = EncryptingVoteKey::from_participants(&participants); @@ -359,10 +361,11 @@ mod tests { let mc = [mc1.to_public(), mc2.to_public(), mc3.to_public()]; let threshold = 3; + let nr_members = 3; - let m1 = MemberState::new(&mut rng, threshold, &h, &mc, 0); - let m2 = MemberState::new(&mut rng, threshold, &h, &mc, 1); - let m3 = MemberState::new(&mut rng, threshold, &h, &mc, 2); + let m1 = DistributedKeyGeneration::init(&mut rng, threshold, nr_members, &h, &mc, 0); + let m2 = DistributedKeyGeneration::init(&mut rng, threshold, nr_members, &h, &mc, 1); + let m3 = DistributedKeyGeneration::init(&mut rng, threshold, nr_members, &h, &mc, 2); let participants = vec![m1.public_key(), m2.public_key(), m3.public_key()]; let ek = EncryptingVoteKey::from_participants(&participants); @@ -415,8 +418,9 @@ mod tests { let mc = [mc1.to_public()]; let threshold = 1; + let nr_members = 1; - let m1 = MemberState::new(&mut rng, threshold, &h, &mc, 0); + let m1 = DistributedKeyGeneration::init(&mut rng, threshold, nr_members, &h, &mc, 0); let participants = vec![m1.public_key()]; let ek = EncryptingVoteKey::from_participants(&participants); @@ -462,8 +466,9 @@ mod tests { let mc = [mc1.to_public()]; let threshold = 1; + let nr_members = 1; - let m1 = MemberState::new(&mut rng, threshold, &h, &mc, 0); + let m1 = DistributedKeyGeneration::init(&mut rng, threshold, nr_members, &h, &mc, 0); let vote_options = 2; @@ -503,10 +508,11 @@ mod tests { let mc = [mc1.to_public(), mc2.to_public(), mc3.to_public()]; let threshold = 3; + let nr_members = 3; - let m1 = MemberState::new(&mut rng, threshold, &h, &mc, 0); - let m2 = MemberState::new(&mut rng, threshold, &h, &mc, 1); - let m3 = MemberState::new(&mut rng, threshold, &h, &mc, 2); + let m1 = DistributedKeyGeneration::init(&mut rng, threshold, nr_members, &h, &mc, 0); + let m2 = DistributedKeyGeneration::init(&mut rng, threshold, nr_members, &h, &mc, 1); + let m3 = DistributedKeyGeneration::init(&mut rng, threshold, nr_members, &h, &mc, 2); let participants = vec![m1.public_key(), m2.public_key(), m3.public_key()]; let ek = EncryptingVoteKey::from_participants(&participants); From e0fbed357e68c214ae615cd9a694c07e8f04d689 Mon Sep 17 00:00:00 2001 From: iquerejeta Date: Mon, 7 Jun 2021 18:23:00 +0100 Subject: [PATCH 03/10] starting misbehaviour handling --- chain-vote/src/committee.rs | 92 +++++++++++++++---- chain-vote/src/gang/ristretto255.rs | 4 +- .../src/private_voting/unit_vector_zkp.rs | 4 +- 3 files changed, 77 insertions(+), 23 deletions(-) diff --git a/chain-vote/src/committee.rs b/chain-vote/src/committee.rs index 9c1d6cf77..35063943d 100644 --- a/chain-vote/src/committee.rs +++ b/chain-vote/src/committee.rs @@ -10,6 +10,7 @@ use crate::encryption::{HybridCiphertext, PublicKey, SecretKey}; use crate::gang::{GroupElement, Scalar}; use crate::math::Polynomial; use crate::Crs; +use crate::errors::DkgError; use rand_core::{CryptoRng, RngCore}; /// Committee member election secret key @@ -32,45 +33,53 @@ pub struct MemberCommunicationPublicKey(PublicKey); #[derive(Clone)] pub struct ElectionPublicKey(pub(crate) PublicKey); -impl ElectionPublicKey { - #[doc(hidden)] - pub fn as_raw(&self) -> &PublicKey { - &self.0 - } -} +pub type DistributedKeyGeneration = MemberState1; -/// Initial state generated by a Member, which include keys for this election +/// Initial state generated by a Member, corresponding to round 1. #[derive(Clone)] -pub struct MemberState { +pub struct MemberState1 { sk: MemberSecretKey, + threshold: usize, + nr_members: usize, owner_index: usize, + crs: Crs, apubs: Vec, es: Vec, encrypted: Vec<(HybridCiphertext, HybridCiphertext)>, } -impl MemberState { +/// State of the member corresponding to round 2. +#[derive(Clone)] +pub struct MemberState2 { + misbehaving_parties: Vec, +} + +type MembersFetchedState1 = (usize, HybridCiphertext, HybridCiphertext); +// todo: third element should be a proof of misbehaviour. +type MisbehavingPartiesState1 = (usize, DkgError, HybridCiphertext); + +impl MemberState1 { /// Generate a new member state from random. This is round 1 of the protocol. Receives as /// input the threshold `t`, the expected number of participants, `n`, common reference string /// `crs`, `committee_pks`, and the party's index `my`. Initiates a Pedersen-VSS as a dealer. - pub fn new( + pub fn init( rng: &mut R, t: usize, n: usize, crs: &Crs, // TODO: document committee_pks: &[MemberCommunicationPublicKey], my: usize, - ) -> MemberState { + ) -> MemberState1 { assert_eq!(committee_pks.len(), n); assert!(t > 0); assert!(t <= n); - assert!(my < n); + assert!(my <= n); let pcomm = Polynomial::random(rng, t); let pshek = Polynomial::random(rng, t); - let mut apubs = Vec::new(); - let mut es = Vec::new(); + let mut apubs = Vec::with_capacity(t); + let mut es = Vec::with_capacity(t); for (ai, bi) in pshek.get_coefficients().zip(pcomm.get_coefficients()) { let apub = GroupElement::generator() * ai; @@ -79,7 +88,7 @@ impl MemberState { es.push(e); } - let mut encrypted = Vec::new(); + let mut encrypted = Vec::with_capacity(n - 1); #[allow(clippy::needless_range_loop)] for i in 0..n { // don't generate share for self @@ -99,14 +108,13 @@ impl MemberState { } } - assert_eq!(apubs.len(), t + 1); - assert_eq!(es.len(), t + 1); - assert_eq!(encrypted.len(), n - 1); - - MemberState { + MemberState1 { sk: MemberSecretKey(SecretKey { sk: pshek.at_zero(), }), + crs: crs.clone(), + threshold: t, + nr_members: n, owner_index: my + 1, // committee member are 1-indexed apubs, es, @@ -114,6 +122,45 @@ impl MemberState { } } + pub fn to_phase_2( + &self, + members_state: Vec, + + coeff_comms: Vec, + ) -> Result { + let mut misbehaving_parties: Vec = Vec::new(); + for fetched_data in members_state { + let comm_scalar = Scalar::from_bytes(&self.sk.0.hybrid_decrypt(&fetched_data.1)); + let shek_scalar = Scalar::from_bytes(&self.sk.0.hybrid_decrypt(&fetched_data.2)); + + if comm_scalar.is_none() { + // todo: generate the proofs + misbehaving_parties.push((fetched_data.0, DkgError::ScalarOutOfBounds, fetched_data.1)); + continue; + } + + if shek_scalar.is_none() { + // todo: generate the proofs + misbehaving_parties.push((fetched_data.0, DkgError::ScalarOutOfBounds, fetched_data.2)); + continue; + } + + let check_element = GroupElement::generator() * dec_comms[i] + self.crs * dec_sheks[i]; + assert_eq!(GroupElement::generator() * comm_scalar + self.crs * shek_scalar, coeff_comms[i]); + } + for i in 0..self.nr_members { + if i == self.owner_index - 1 { + continue; + } else { + let check_element = GroupElement::generator() * dec_comms[i] + self.crs * dec_sheks[i]; + assert_eq!(GroupElement::generator() * dec_comms[i] + self.crs * dec_sheks[i], coeff_comms[i]); + } + } + + Ok(MemberState2{misbehaving_parties}) + + } + pub fn secret_key(&self) -> &MemberSecretKey { &self.sk } @@ -206,4 +253,9 @@ impl ElectionPublicKey { pub fn from_bytes(buf: &[u8]) -> Option { PublicKey::from_bytes(buf).map(ElectionPublicKey) } + + #[doc(hidden)] + pub fn as_raw(&self) -> &PublicKey { + &self.0 + } } diff --git a/chain-vote/src/gang/ristretto255.rs b/chain-vote/src/gang/ristretto255.rs index 9a1716d6f..112c79fbd 100644 --- a/chain-vote/src/gang/ristretto255.rs +++ b/chain-vote/src/gang/ristretto255.rs @@ -5,6 +5,8 @@ use curve25519_dalek_ng::{ traits::Identity, }; +use crate::errors::EcErrors; + use cryptoxide::blake2b::Blake2b; use cryptoxide::digest::Digest; @@ -81,7 +83,7 @@ impl GroupElement { } sum } - pub fn multiscalar_multiplication(scalars: I, points: J) -> Self + pub fn vartime_multiscalar_multiplication(scalars: I, points: J) -> Self where I: IntoIterator, J: IntoIterator, diff --git a/chain-vote/src/private_voting/unit_vector_zkp.rs b/chain-vote/src/private_voting/unit_vector_zkp.rs index 5d70d25e6..fa7b3fef2 100644 --- a/chain-vote/src/private_voting/unit_vector_zkp.rs +++ b/chain-vote/src/private_voting/unit_vector_zkp.rs @@ -192,7 +192,7 @@ impl Proof { let batch_challenge = Scalar::random(&mut thread_rng()); for (zwv, iba) in self.zwvs.iter().zip(self.ibas.iter()) { - if GroupElement::multiscalar_multiplication( + if GroupElement::vartime_multiscalar_multiplication( iter::once(zwv.z) .chain(iter::once(zwv.w + batch_challenge * zwv.v)) .chain(iter::once( @@ -211,7 +211,7 @@ impl Proof { } } - let mega_check = GroupElement::multiscalar_multiplication( + let mega_check = GroupElement::vartime_multiscalar_multiplication( powers_cy .take(length) .map(|s| s * cx_pow) From f90af13d95823a000c0fd184be0274552e97f20a Mon Sep 17 00:00:00 2001 From: iquerejeta Date: Mon, 7 Jun 2021 18:59:52 +0100 Subject: [PATCH 04/10] added some tests (failing) --- chain-vote/src/committee.rs | 80 ++++++++++++++++++++++++++----------- chain-vote/src/errors.rs | 3 ++ 2 files changed, 59 insertions(+), 24 deletions(-) diff --git a/chain-vote/src/committee.rs b/chain-vote/src/committee.rs index 35063943d..2fc887964 100644 --- a/chain-vote/src/committee.rs +++ b/chain-vote/src/committee.rs @@ -56,7 +56,7 @@ pub struct MemberState2 { type MembersFetchedState1 = (usize, HybridCiphertext, HybridCiphertext); // todo: third element should be a proof of misbehaviour. -type MisbehavingPartiesState1 = (usize, DkgError, HybridCiphertext); +type MisbehavingPartiesState1 = (usize, DkgError, usize); impl MemberState1 { /// Generate a new member state from random. This is round 1 of the protocol. Receives as @@ -122,39 +122,33 @@ impl MemberState1 { } } + // todo: is vectors a correct way to handle the indexed members (e.g.: coeff_comms and pks in + // a vec will certainly enduce errors)? pub fn to_phase_2( &self, - members_state: Vec, - - coeff_comms: Vec, + // todo: maybe we can simply have one type for the state--- or a struct + members_state: &Vec, + coeff_comms: &Vec, ) -> Result { let mut misbehaving_parties: Vec = Vec::new(); - for fetched_data in members_state { + for (index, fetched_data) in members_state.iter().enumerate() { let comm_scalar = Scalar::from_bytes(&self.sk.0.hybrid_decrypt(&fetched_data.1)); let shek_scalar = Scalar::from_bytes(&self.sk.0.hybrid_decrypt(&fetched_data.2)); - if comm_scalar.is_none() { - // todo: generate the proofs - misbehaving_parties.push((fetched_data.0, DkgError::ScalarOutOfBounds, fetched_data.1)); - continue; + if let (Some(comm), Some(shek)) = (comm_scalar, shek_scalar) { + let check_element = GroupElement::generator() * comm + &self.crs * shek; + if check_element != coeff_comms[index] { + misbehaving_parties.push((fetched_data.0.clone(), DkgError::MisbehaviourHigherThreshold, 0)); + } } - - if shek_scalar.is_none() { - // todo: generate the proofs - misbehaving_parties.push((fetched_data.0, DkgError::ScalarOutOfBounds, fetched_data.2)); - continue; + else { + // todo: handle the proofs. Might not be the most optimal way of handling these two + misbehaving_parties.push((fetched_data.0.clone(), DkgError::ScalarOutOfBounds, 0)); } - - let check_element = GroupElement::generator() * dec_comms[i] + self.crs * dec_sheks[i]; - assert_eq!(GroupElement::generator() * comm_scalar + self.crs * shek_scalar, coeff_comms[i]); } - for i in 0..self.nr_members { - if i == self.owner_index - 1 { - continue; - } else { - let check_element = GroupElement::generator() * dec_comms[i] + self.crs * dec_sheks[i]; - assert_eq!(GroupElement::generator() * dec_comms[i] + self.crs * dec_sheks[i], coeff_comms[i]); - } + + if misbehaving_parties.len() == self.threshold { + return Err(DkgError::MisbehaviourHigherThreshold); } Ok(MemberState2{misbehaving_parties}) @@ -259,3 +253,41 @@ impl ElectionPublicKey { &self.0 } } + +#[cfg(test)] +mod tests { + use super::*; + use rand_chacha::ChaCha20Rng; + use rand_core::SeedableRng; + + #[test] + fn test_misbehaviour() { + let mut rng = ChaCha20Rng::from_seed([0u8; 32]); + + let mut shared_string = + b"Example of a shared string. This should be VotePlan.to_id()".to_owned(); + let h = Crs::from_hash(&mut shared_string); + + let mc1 = MemberCommunicationKey::new(&mut rng); + let mc2 = MemberCommunicationKey::new(&mut rng); + let mc = [mc1.to_public(), mc2.to_public()]; + + let threshold = 1; + let nr_members = 2; + + let m1 = DistributedKeyGeneration::init(&mut rng, threshold, nr_members, &h, &mc, 0); + let m2 = DistributedKeyGeneration::init(&mut rng, threshold, nr_members, &h, &mc, 1); + + // Now, party one fetches the state of the other parties, mainly party two + let fetched_state = vec![(1, m2.encrypted[0].0.clone(), m2.encrypted[0].1.clone())]; + let coeff_comms = vec![m2.apubs[0].clone()]; + + let phase_2 = m1.to_phase_2(&fetched_state, &coeff_comms); + + assert!(phase_2.is_ok()); + + // let phase_2_faked = m1.to_phase_2(&fetched_state, &vec![GroupElement::zero()]); + // // todo: we probably want to check for a particular error here + // assert!(phase_2_faked.is_err()); + } +} diff --git a/chain-vote/src/errors.rs b/chain-vote/src/errors.rs index bbe7a223e..2eca6da72 100644 --- a/chain-vote/src/errors.rs +++ b/chain-vote/src/errors.rs @@ -6,4 +6,7 @@ pub enum DkgError { /// byte-array representing a scalar out of bounds. #[cfg_attr(feature = "std", error("Scalar out of bounds."))] ScalarOutOfBounds, + /// This error occurs when too many members misbehaved. + #[cfg_attr(feature = "std", error("Misbehaviours higher than threshold."))] + MisbehaviourHigherThreshold, } From 8e1805a559b1e92faf6416e3a819619936ed9d00 Mon Sep 17 00:00:00 2001 From: iquerejeta Date: Tue, 8 Jun 2021 12:31:24 +0100 Subject: [PATCH 05/10] added multiscalar mult interface for p256k1 --- chain-vote/src/gang/p256k1.rs | 12 ++++++++++++ chain-vote/src/gang/ristretto255.rs | 4 ++++ 2 files changed, 16 insertions(+) diff --git a/chain-vote/src/gang/p256k1.rs b/chain-vote/src/gang/p256k1.rs index 336050590..e218a721a 100644 --- a/chain-vote/src/gang/p256k1.rs +++ b/chain-vote/src/gang/p256k1.rs @@ -147,6 +147,18 @@ impl GroupElement { } sum } + + pub fn vartime_multiscalar_multiplication(scalars: I, points: J) -> Self + where + I: IntoIterator, + J: IntoIterator, + { + let mut sum = GroupElement::zero(); + for (scalar, point) in scalars.into_iter().zip(points.into_iter()) { + sum = sum + scalar * point; + } + sum + } } impl Scalar { diff --git a/chain-vote/src/gang/ristretto255.rs b/chain-vote/src/gang/ristretto255.rs index 112c79fbd..211c75bfb 100644 --- a/chain-vote/src/gang/ristretto255.rs +++ b/chain-vote/src/gang/ristretto255.rs @@ -83,6 +83,10 @@ impl GroupElement { } sum } + + /// Variable time multiscalar multiplication. Takes as input an iterator of scalar, and an + /// iterator over group elements, and computes a variable time multiscalar operation. Should + /// only be used when the scalar are not secret. pub fn vartime_multiscalar_multiplication(scalars: I, points: J) -> Self where I: IntoIterator, From 29779f5f280041af524d6f3b875e70c0ba20e16b Mon Sep 17 00:00:00 2001 From: iquerejeta Date: Tue, 8 Jun 2021 15:20:33 +0100 Subject: [PATCH 06/10] passing error --- chain-vote/src/committee.rs | 91 ++++++++++++++++++++--------- chain-vote/src/encryption.rs | 7 +++ chain-vote/src/errors.rs | 4 ++ chain-vote/src/gang/p256k1.rs | 5 +- chain-vote/src/gang/ristretto255.rs | 2 - 5 files changed, 79 insertions(+), 30 deletions(-) diff --git a/chain-vote/src/committee.rs b/chain-vote/src/committee.rs index 2fc887964..5f046f4df 100644 --- a/chain-vote/src/committee.rs +++ b/chain-vote/src/committee.rs @@ -45,7 +45,7 @@ pub struct MemberState1 { crs: Crs, apubs: Vec, es: Vec, - encrypted: Vec<(HybridCiphertext, HybridCiphertext)>, + encrypted: Vec, } /// State of the member corresponding to round 2. @@ -54,7 +54,9 @@ pub struct MemberState2 { misbehaving_parties: Vec, } -type MembersFetchedState1 = (usize, HybridCiphertext, HybridCiphertext); +// state of members, with the index, the hybrid ctxts and the commitment to +type MembersFetchedState1 = (IndexedEncryptedShares, Vec); +type IndexedEncryptedShares = (usize, HybridCiphertext, HybridCiphertext); // todo: third element should be a proof of misbehaviour. type MisbehavingPartiesState1 = (usize, DkgError, usize); @@ -73,22 +75,23 @@ impl MemberState1 { assert_eq!(committee_pks.len(), n); assert!(t > 0); assert!(t <= n); - assert!(my <= n); + assert!(t > n / 2); + assert!(my < n); let pcomm = Polynomial::random(rng, t); let pshek = Polynomial::random(rng, t); - let mut apubs = Vec::with_capacity(t); + let mut coeff_comm = Vec::with_capacity(t); let mut es = Vec::with_capacity(t); for (ai, bi) in pshek.get_coefficients().zip(pcomm.get_coefficients()) { let apub = GroupElement::generator() * ai; let e = &apub + crs * bi; - apubs.push(apub); + coeff_comm.push(apub); es.push(e); } - let mut encrypted = Vec::with_capacity(n - 1); + let mut encrypted_shares: Vec = Vec::with_capacity(n - 1); #[allow(clippy::needless_range_loop)] for i in 0..n { // don't generate share for self @@ -104,7 +107,7 @@ impl MemberState1 { let ecomm = pk.0.hybrid_encrypt(&share_comm.to_bytes(), rng); let eshek = pk.0.hybrid_encrypt(&share_shek.to_bytes(), rng); - encrypted.push((ecomm, eshek)); + encrypted_shares.push((i, ecomm, eshek)); } } @@ -116,9 +119,9 @@ impl MemberState1 { threshold: t, nr_members: n, owner_index: my + 1, // committee member are 1-indexed - apubs, + apubs: coeff_comm, es, - encrypted, + encrypted: encrypted_shares, } } @@ -126,24 +129,34 @@ impl MemberState1 { // a vec will certainly enduce errors)? pub fn to_phase_2( &self, - // todo: maybe we can simply have one type for the state--- or a struct + secret_key: &MemberCommunicationKey, members_state: &Vec, - coeff_comms: &Vec, ) -> Result { let mut misbehaving_parties: Vec = Vec::new(); - for (index, fetched_data) in members_state.iter().enumerate() { - let comm_scalar = Scalar::from_bytes(&self.sk.0.hybrid_decrypt(&fetched_data.1)); - let shek_scalar = Scalar::from_bytes(&self.sk.0.hybrid_decrypt(&fetched_data.2)); + for fetched_data in members_state { + let comm_scalar = Scalar::from_bytes( + &secret_key.0.hybrid_decrypt(&fetched_data.0.1)); + let shek_scalar = Scalar::from_bytes( + &secret_key.0.hybrid_decrypt(&fetched_data.0.2)); if let (Some(comm), Some(shek)) = (comm_scalar, shek_scalar) { - let check_element = GroupElement::generator() * comm + &self.crs * shek; - if check_element != coeff_comms[index] { - misbehaving_parties.push((fetched_data.0.clone(), DkgError::MisbehaviourHigherThreshold, 0)); + let index_pow = Scalar::from_u64(self.owner_index as u64) + .exp_iter() + .take(self.threshold + 1); + + let check_element = GroupElement::generator() * shek + &self.crs * comm; + #[cfg(feature = "ristretto255")] + let multi_scalar = GroupElement::vartime_multiscalar_multiplication(index_pow, fetched_data.1.clone()); + #[cfg(not(feature = "ristretto255"))] + let multi_scalar = GroupElement::multiscalar_multiplication(index_pow, fetched_data.1.clone()); + + if check_element != multi_scalar { + misbehaving_parties.push((fetched_data.0.0.clone(), DkgError::ShareValidityFailed, 0)); } } else { // todo: handle the proofs. Might not be the most optimal way of handling these two - misbehaving_parties.push((fetched_data.0.clone(), DkgError::ScalarOutOfBounds, 0)); + misbehaving_parties.push((fetched_data.0.0.clone(), DkgError::ScalarOutOfBounds, 0)); } } @@ -261,7 +274,7 @@ mod tests { use rand_core::SeedableRng; #[test] - fn test_misbehaviour() { + fn valid_phase_2() { let mut rng = ChaCha20Rng::from_seed([0u8; 32]); let mut shared_string = @@ -272,22 +285,46 @@ mod tests { let mc2 = MemberCommunicationKey::new(&mut rng); let mc = [mc1.to_public(), mc2.to_public()]; - let threshold = 1; + let threshold = 2; let nr_members = 2; let m1 = DistributedKeyGeneration::init(&mut rng, threshold, nr_members, &h, &mc, 0); let m2 = DistributedKeyGeneration::init(&mut rng, threshold, nr_members, &h, &mc, 1); - // Now, party one fetches the state of the other parties, mainly party two - let fetched_state = vec![(1, m2.encrypted[0].0.clone(), m2.encrypted[0].1.clone())]; - let coeff_comms = vec![m2.apubs[0].clone()]; + // Now, party one fetches the state of the other parties, mainly party two and three + let fetched_state = vec![(m2.encrypted[0].clone(), m2.es.clone())]; - let phase_2 = m1.to_phase_2(&fetched_state, &coeff_comms); + let phase_2 = m1.to_phase_2(&mc1, &fetched_state); assert!(phase_2.is_ok()); + } + #[test] + fn invalid_phase_2() { + let mut rng = ChaCha20Rng::from_seed([0u8; 32]); + + let mut shared_string = + b"Example of a shared string. This should be VotePlan.to_id()".to_owned(); + let h = Crs::from_hash(&mut shared_string); + + let mc1 = MemberCommunicationKey::new(&mut rng); + let mc2 = MemberCommunicationKey::new(&mut rng); + let mc3 = MemberCommunicationKey::new(&mut rng); + let mc = [mc1.to_public(), mc2.to_public(), mc3.to_public()]; + + let threshold = 2; + let nr_members = 3; + + let m1 = DistributedKeyGeneration::init(&mut rng, threshold, nr_members, &h, &mc, 0); + let m2 = DistributedKeyGeneration::init(&mut rng, threshold, nr_members, &h, &mc, 1); + let m3 = DistributedKeyGeneration::init(&mut rng, threshold, nr_members, &h, &mc, 2); + + // Now, party one fetches invalid state of the other parties, mainly party two and three + let fetched_state = vec![ + (m2.encrypted[0].clone(), vec![GroupElement::zero(); 3]), + (m3.encrypted[0].clone(), vec![GroupElement::zero(); 3])]; - // let phase_2_faked = m1.to_phase_2(&fetched_state, &vec![GroupElement::zero()]); - // // todo: we probably want to check for a particular error here - // assert!(phase_2_faked.is_err()); + let phase_2_faked = m1.to_phase_2(&mc1, &fetched_state); + // todo: we probably want to check for a particular error here + assert!(phase_2_faked.is_err()); } } diff --git a/chain-vote/src/encryption.rs b/chain-vote/src/encryption.rs index d58a40760..f61b97bf2 100644 --- a/chain-vote/src/encryption.rs +++ b/chain-vote/src/encryption.rs @@ -11,6 +11,7 @@ use std::ops::{Add, Mul, Sub}; use cryptoxide::blake2b::Blake2b; use cryptoxide::chacha20::ChaCha20; use cryptoxide::digest::Digest; +use std::fmt; #[derive(Debug, Clone, Eq, PartialEq)] /// ElGamal public key. pk = sk * G, where sk is the `SecretKey` and G is the group @@ -48,6 +49,12 @@ pub struct HybridCiphertext { e2: Box<[u8]>, } +impl fmt::Debug for HybridCiphertext { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Point: {:?}\nBox: {:?}", self.e1.to_bytes(), self.e2) + } +} + /// The hybrid encryption scheme uses a group element as a /// representation of the symmetric key. This facilitates /// its exchange using ElGamal encryption. diff --git a/chain-vote/src/errors.rs b/chain-vote/src/errors.rs index 2eca6da72..26724ae73 100644 --- a/chain-vote/src/errors.rs +++ b/chain-vote/src/errors.rs @@ -6,6 +6,10 @@ pub enum DkgError { /// byte-array representing a scalar out of bounds. #[cfg_attr(feature = "std", error("Scalar out of bounds."))] ScalarOutOfBounds, + /// This error occurs when the check of validity of the shares + /// fails. + #[cfg_attr(feature = "std", error("Share validity check failed."))] + ShareValidityFailed, /// This error occurs when too many members misbehaved. #[cfg_attr(feature = "std", error("Misbehaviours higher than threshold."))] MisbehaviourHigherThreshold, diff --git a/chain-vote/src/gang/p256k1.rs b/chain-vote/src/gang/p256k1.rs index e218a721a..f58915c0e 100644 --- a/chain-vote/src/gang/p256k1.rs +++ b/chain-vote/src/gang/p256k1.rs @@ -148,11 +148,14 @@ impl GroupElement { sum } - pub fn vartime_multiscalar_multiplication(scalars: I, points: J) -> Self + /// Non-optimised multiscalar multiplication. If we use the sec2 backend, this function could + /// be optimised. + pub fn multiscalar_multiplication(scalars: I, points: J) -> Self where I: IntoIterator, J: IntoIterator, { + assert_eq!(scalars.len(), points.len()); let mut sum = GroupElement::zero(); for (scalar, point) in scalars.into_iter().zip(points.into_iter()) { sum = sum + scalar * point; diff --git a/chain-vote/src/gang/ristretto255.rs b/chain-vote/src/gang/ristretto255.rs index 211c75bfb..065e47fc5 100644 --- a/chain-vote/src/gang/ristretto255.rs +++ b/chain-vote/src/gang/ristretto255.rs @@ -5,8 +5,6 @@ use curve25519_dalek_ng::{ traits::Identity, }; -use crate::errors::EcErrors; - use cryptoxide::blake2b::Blake2b; use cryptoxide::digest::Digest; From ea26cbd97e00b94bc88e9d8c0953a85ce477c267 Mon Sep 17 00:00:00 2001 From: iquerejeta Date: Tue, 8 Jun 2021 17:36:23 +0100 Subject: [PATCH 07/10] mockchain modifs --- chain-impl-mockchain/src/certificate/test.rs | 3 +- chain-impl-mockchain/src/testing/data/vote.rs | 7 +- chain-impl-mockchain/src/vote/payload.rs | 5 +- chain-vote/src/{ => dkg}/committee.rs | 216 ++++++------------ chain-vote/src/dkg/mod.rs | 2 + chain-vote/src/dkg/procedure_keys.rs | 132 +++++++++++ chain-vote/src/gang/p256k1.rs | 1 - chain-vote/src/lib.rs | 11 +- 8 files changed, 220 insertions(+), 157 deletions(-) rename chain-vote/src/{ => dkg}/committee.rs (56%) create mode 100644 chain-vote/src/dkg/mod.rs create mode 100644 chain-vote/src/dkg/procedure_keys.rs diff --git a/chain-impl-mockchain/src/certificate/test.rs b/chain-impl-mockchain/src/certificate/test.rs index a14092da9..57cd1dc7d 100644 --- a/chain-impl-mockchain/src/certificate/test.rs +++ b/chain-impl-mockchain/src/certificate/test.rs @@ -189,7 +189,8 @@ impl Arbitrary for VotePlan { for _i in 0..keys_n { let mc = chain_vote::MemberCommunicationKey::new(&mut rng); let threshold = 1; - let m1 = chain_vote::MemberState::new(&mut rng, threshold, &h, &[mc.to_public()], 0); + let nr_members = 1; + let m1 = chain_vote::DistributedKeyGeneration::init(&mut rng, threshold, nr_members, &h, &[mc.to_public()], 0); keys.push(m1.public_key()); } diff --git a/chain-impl-mockchain/src/testing/data/vote.rs b/chain-impl-mockchain/src/testing/data/vote.rs index f79d496c8..ab8ef3e22 100644 --- a/chain-impl-mockchain/src/testing/data/vote.rs +++ b/chain-impl-mockchain/src/testing/data/vote.rs @@ -1,7 +1,8 @@ use crate::vote::VotePlanStatus; use chain_vote::{ - committee::MemberSecretKey, Crs, MemberCommunicationKey, MemberPublicKey, MemberState, + MemberSecretKey, Crs, MemberCommunicationKey, MemberPublicKey, TallyDecryptShare, + DistributedKeyGeneration, }; use rand_core::CryptoRng; use rand_core::RngCore; @@ -11,7 +12,7 @@ pub struct CommitteeMembersManager { } pub struct CommitteeMember { - state: MemberState, + state: DistributedKeyGeneration, } impl CommitteeMembersManager { @@ -32,7 +33,7 @@ impl CommitteeMembersManager { let mut members = Vec::new(); for i in 0..members_no { - let state = MemberState::new(rng, threshold, &crs, &public_keys, i); + let state = DistributedKeyGeneration::init(rng, threshold, members_no, &crs, &public_keys, i); members.push(CommitteeMember { state }) } diff --git a/chain-impl-mockchain/src/vote/payload.rs b/chain-impl-mockchain/src/vote/payload.rs index 78fff5c35..46567c71f 100644 --- a/chain-impl-mockchain/src/vote/payload.rs +++ b/chain-impl-mockchain/src/vote/payload.rs @@ -205,7 +205,7 @@ mod tests { impl Arbitrary for Payload { fn arbitrary(g: &mut G) -> Self { use chain_vote::{ - encrypt_vote, Crs, EncryptingVoteKey, MemberCommunicationKey, MemberState, Vote, + encrypt_vote, Crs, EncryptingVoteKey, MemberCommunicationKey, DistributedKeyGeneration, Vote, }; use rand_core::SeedableRng; @@ -217,8 +217,9 @@ mod tests { let mut gen = rand_chacha::ChaCha20Rng::from_seed(seed); let mc = MemberCommunicationKey::new(&mut gen); let threshold = 1; + let nr_members = 1; let h = Crs::from_hash(&mut seed); - let m = MemberState::new(&mut gen, threshold, &h, &[mc.to_public()], 0); + let m = DistributedKeyGeneration::init(&mut gen, threshold, nr_members, &h, &[mc.to_public()], 0); let participants = vec![m.public_key()]; let ek = EncryptingVoteKey::from_participants(&participants); let vote_options = 3; diff --git a/chain-vote/src/committee.rs b/chain-vote/src/dkg/committee.rs similarity index 56% rename from chain-vote/src/committee.rs rename to chain-vote/src/dkg/committee.rs index 5f046f4df..409ace487 100644 --- a/chain-vote/src/committee.rs +++ b/chain-vote/src/dkg/committee.rs @@ -12,58 +12,61 @@ use crate::math::Polynomial; use crate::Crs; use crate::errors::DkgError; use rand_core::{CryptoRng, RngCore}; - -/// Committee member election secret key -#[derive(Clone)] -pub struct MemberSecretKey(pub(crate) SecretKey); - -/// Committee member election public key -#[derive(Debug, Clone, Eq, PartialEq)] -pub struct MemberPublicKey(pub(crate) PublicKey); - -/// Committee member communication private key -#[derive(Clone)] -pub struct MemberCommunicationKey(SecretKey); - -/// Committee Member communication public key (with other committee members) -#[derive(Clone)] -pub struct MemberCommunicationPublicKey(PublicKey); - -/// The overall committee public key used for everyone to encrypt their vote to. -#[derive(Clone)] -pub struct ElectionPublicKey(pub(crate) PublicKey); +use super::procedure_keys::{MemberCommunicationKey, MemberPublicKey, MemberSecretKey, MemberCommunicationPublicKey}; pub type DistributedKeyGeneration = MemberState1; /// Initial state generated by a Member, corresponding to round 1. #[derive(Clone)] pub struct MemberState1 { - sk: MemberSecretKey, + sk_share: MemberSecretKey, threshold: usize, nr_members: usize, owner_index: usize, crs: Crs, apubs: Vec, - es: Vec, - encrypted: Vec, + coeff_comms: Vec, + encrypted_shares: Vec, } /// State of the member corresponding to round 2. #[derive(Clone)] pub struct MemberState2 { + threshold: usize, misbehaving_parties: Vec, } -// state of members, with the index, the hybrid ctxts and the commitment to -type MembersFetchedState1 = (IndexedEncryptedShares, Vec); -type IndexedEncryptedShares = (usize, HybridCiphertext, HybridCiphertext); -// todo: third element should be a proof of misbehaviour. +/// Type that contains the index of the receiver, and its two encrypted +/// shares. +pub(crate) type IndexedEncryptedShares = (usize, HybridCiphertext, HybridCiphertext); + +// todo: third element should be a proof of misbehaviour. waiting for PR542 to resolve +/// Type that contains misbehaving parties detected in round 1. These +/// consist of the misbehaving member's index, the error which failed, +/// and a proof of correctness of the misbehaviour claim. type MisbehavingPartiesState1 = (usize, DkgError, usize); +/// State of the members after round 1. This structure contains the indexed encrypted +/// shares of every other participant, `indexed_shares`, and the committed coefficients +/// of the generated polynomials, `committed_coeffs`. +#[derive(Clone)] +pub struct MembersFetchedState1 { + indexed_shares: IndexedEncryptedShares, + committed_coeffs: Vec +} + +impl MembersFetchedState1 { + fn get_index(&self) -> usize { + self.indexed_shares.0 + } +} + impl MemberState1 { /// Generate a new member state from random. This is round 1 of the protocol. Receives as /// input the threshold `t`, the expected number of participants, `n`, common reference string - /// `crs`, `committee_pks`, and the party's index `my`. Initiates a Pedersen-VSS as a dealer. + /// `crs`, `committee_pks`, and the party's index `my`. Initiates a Pedersen-VSS as a dealer, + /// and returns the committed coefficients of its polynomials, together with encryption of the + /// shares of the other different members. pub fn init( rng: &mut R, t: usize, @@ -81,14 +84,14 @@ impl MemberState1 { let pcomm = Polynomial::random(rng, t); let pshek = Polynomial::random(rng, t); - let mut coeff_comm = Vec::with_capacity(t); - let mut es = Vec::with_capacity(t); + let mut apubs = Vec::with_capacity(t); + let mut coeff_comms = Vec::with_capacity(t); for (ai, bi) in pshek.get_coefficients().zip(pcomm.get_coefficients()) { let apub = GroupElement::generator() * ai; - let e = &apub + crs * bi; - coeff_comm.push(apub); - es.push(e); + let coeff_comm = &apub + crs * bi; + apubs.push(apub); + coeff_comms.push(coeff_comm); } let mut encrypted_shares: Vec = Vec::with_capacity(n - 1); @@ -104,72 +107,65 @@ impl MemberState1 { let pk = &committee_pks[i]; - let ecomm = pk.0.hybrid_encrypt(&share_comm.to_bytes(), rng); - let eshek = pk.0.hybrid_encrypt(&share_shek.to_bytes(), rng); + let ecomm = pk.hybrid_encrypt(&share_comm.to_bytes(), rng); + let eshek = pk.hybrid_encrypt(&share_shek.to_bytes(), rng); encrypted_shares.push((i, ecomm, eshek)); } } MemberState1 { - sk: MemberSecretKey(SecretKey { + sk_share: MemberSecretKey(SecretKey { sk: pshek.at_zero(), }), crs: crs.clone(), threshold: t, nr_members: n, owner_index: my + 1, // committee member are 1-indexed - apubs: coeff_comm, - es, - encrypted: encrypted_shares, + apubs, + coeff_comms, + encrypted_shares, } } - // todo: is vectors a correct way to handle the indexed members (e.g.: coeff_comms and pks in - // a vec will certainly enduce errors)? + /// Function to proceed to phase 2. It checks and keeps track of misbehaving parties. If this + /// step does not validate, the member is not allowed to proceed to phase 3. pub fn to_phase_2( &self, secret_key: &MemberCommunicationKey, members_state: &Vec, - ) -> Result { + ) -> MemberState2 { let mut misbehaving_parties: Vec = Vec::new(); for fetched_data in members_state { - let comm_scalar = Scalar::from_bytes( - &secret_key.0.hybrid_decrypt(&fetched_data.0.1)); - let shek_scalar = Scalar::from_bytes( - &secret_key.0.hybrid_decrypt(&fetched_data.0.2)); - if let (Some(comm), Some(shek)) = (comm_scalar, shek_scalar) { + if let (Some(comm), Some(shek)) = secret_key.decrypt_shares(fetched_data.indexed_shares.clone()) { let index_pow = Scalar::from_u64(self.owner_index as u64) .exp_iter() .take(self.threshold + 1); let check_element = GroupElement::generator() * shek + &self.crs * comm; #[cfg(feature = "ristretto255")] - let multi_scalar = GroupElement::vartime_multiscalar_multiplication(index_pow, fetched_data.1.clone()); + let multi_scalar = GroupElement::vartime_multiscalar_multiplication(index_pow, fetched_data.committed_coeffs.clone()); #[cfg(not(feature = "ristretto255"))] - let multi_scalar = GroupElement::multiscalar_multiplication(index_pow, fetched_data.1.clone()); + let multi_scalar = GroupElement::multiscalar_multiplication(index_pow, fetched_data.committed_coeffs.clone()); if check_element != multi_scalar { - misbehaving_parties.push((fetched_data.0.0.clone(), DkgError::ShareValidityFailed, 0)); + // todo: should we instead store the sender's index? + misbehaving_parties.push((fetched_data.get_index().clone(), DkgError::ShareValidityFailed, 0)); } } else { // todo: handle the proofs. Might not be the most optimal way of handling these two - misbehaving_parties.push((fetched_data.0.0.clone(), DkgError::ScalarOutOfBounds, 0)); + misbehaving_parties.push((fetched_data.get_index().clone(), DkgError::ScalarOutOfBounds, 0)); } } - if misbehaving_parties.len() == self.threshold { - return Err(DkgError::MisbehaviourHigherThreshold); - } - - Ok(MemberState2{misbehaving_parties}) + MemberState2{misbehaving_parties, threshold: self.threshold} } pub fn secret_key(&self) -> &MemberSecretKey { - &self.sk + &self.sk_share } pub fn public_key(&self) -> MemberPublicKey { @@ -179,91 +175,13 @@ impl MemberState1 { } } -impl MemberSecretKey { - pub fn to_bytes(&self) -> [u8; 32] { - self.0.sk.to_bytes() - } - - pub fn from_bytes(bytes: &[u8]) -> Option { - let sk = Scalar::from_bytes(bytes)?; - Some(Self(SecretKey { sk })) - } -} - -impl MemberPublicKey { - pub const BYTES_LEN: usize = PublicKey::BYTES_LEN; - - pub fn to_bytes(&self) -> Vec { - self.0.to_bytes() - } - - pub fn from_bytes(buf: &[u8]) -> Option { - Some(Self(PublicKey::from_bytes(buf)?)) - } -} - -impl From for MemberPublicKey { - fn from(pk: PublicKey) -> MemberPublicKey { - MemberPublicKey(pk) - } -} - -impl MemberCommunicationKey { - pub fn new(rng: &mut R) -> Self { - let sk = SecretKey::generate(rng); - MemberCommunicationKey(sk) - } - - pub fn to_public(&self) -> MemberCommunicationPublicKey { - MemberCommunicationPublicKey(PublicKey { - pk: &GroupElement::generator() * &self.0.sk, - }) - } - - pub fn from_bytes(bytes: &[u8]) -> Option { - SecretKey::from_bytes(bytes).map(MemberCommunicationKey) - } - - pub fn to_bytes(&self) -> [u8; 32] { - self.0.sk.to_bytes() - } -} - -impl MemberCommunicationPublicKey { - pub fn from_public_key(pk: PublicKey) -> Self { - Self(pk) - } - - pub fn to_bytes(&self) -> Vec { - self.0.to_bytes() - } - - pub fn from_bytes(bytes: &[u8]) -> Option { - PublicKey::from_bytes(bytes).map(Self) - } -} - -impl ElectionPublicKey { - /// Create an election public key from all the participants of this committee - pub fn from_participants(pks: &[MemberPublicKey]) -> Self { - let mut k = pks[0].0.pk.clone(); - for pk in &pks[1..] { - k = k + &pk.0.pk; +impl MemberState2 { + pub fn validate(&self) -> Result { + if self.misbehaving_parties.len() == self.threshold { + return Err(DkgError::MisbehaviourHigherThreshold); } - ElectionPublicKey(PublicKey { pk: k }) - } - - pub fn to_bytes(&self) -> Vec { - self.0.to_bytes() - } - pub fn from_bytes(buf: &[u8]) -> Option { - PublicKey::from_bytes(buf).map(ElectionPublicKey) - } - - #[doc(hidden)] - pub fn as_raw(&self) -> &PublicKey { - &self.0 + Ok(self.clone()) } } @@ -292,11 +210,14 @@ mod tests { let m2 = DistributedKeyGeneration::init(&mut rng, threshold, nr_members, &h, &mc, 1); // Now, party one fetches the state of the other parties, mainly party two and three - let fetched_state = vec![(m2.encrypted[0].clone(), m2.es.clone())]; + let fetched_state = vec![ + MembersFetchedState1{ + indexed_shares: m2.encrypted_shares[0].clone(), + committed_coeffs: m2.coeff_comms.clone()}]; let phase_2 = m1.to_phase_2(&mc1, &fetched_state); - assert!(phase_2.is_ok()); + assert!(phase_2.validate().is_ok()); } #[test] fn invalid_phase_2() { @@ -320,11 +241,16 @@ mod tests { // Now, party one fetches invalid state of the other parties, mainly party two and three let fetched_state = vec![ - (m2.encrypted[0].clone(), vec![GroupElement::zero(); 3]), - (m3.encrypted[0].clone(), vec![GroupElement::zero(); 3])]; + MembersFetchedState1{ + indexed_shares: m2.encrypted_shares[0].clone(), + committed_coeffs: vec![GroupElement::zero(); 3]}, + MembersFetchedState1{ + indexed_shares: m3.encrypted_shares[0].clone(), + committed_coeffs: vec![GroupElement::zero(); 3]}, + ]; let phase_2_faked = m1.to_phase_2(&mc1, &fetched_state); // todo: we probably want to check for a particular error here - assert!(phase_2_faked.is_err()); + assert!(phase_2_faked.validate().is_err()); } } diff --git a/chain-vote/src/dkg/mod.rs b/chain-vote/src/dkg/mod.rs new file mode 100644 index 000000000..2de98d888 --- /dev/null +++ b/chain-vote/src/dkg/mod.rs @@ -0,0 +1,2 @@ +pub mod committee; +pub mod procedure_keys; \ No newline at end of file diff --git a/chain-vote/src/dkg/procedure_keys.rs b/chain-vote/src/dkg/procedure_keys.rs new file mode 100644 index 000000000..b855994fc --- /dev/null +++ b/chain-vote/src/dkg/procedure_keys.rs @@ -0,0 +1,132 @@ +use crate::encryption::{PublicKey, SecretKey, HybridCiphertext}; +use crate::gang::{GroupElement, Scalar}; +use rand_core::{CryptoRng, RngCore}; +use super::committee::IndexedEncryptedShares; + +/// Committee member election secret key +#[derive(Clone)] +pub struct MemberSecretKey(pub(crate) SecretKey); + +/// Committee member election public key +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct MemberPublicKey(pub(crate) PublicKey); + +/// Committee member communication private key +#[derive(Clone)] +pub struct MemberCommunicationKey(SecretKey); + +/// Committee Member communication public key (with other committee members) +#[derive(Clone)] +pub struct MemberCommunicationPublicKey(PublicKey); + +/// The overall committee public key used for everyone to encrypt their vote to. +#[derive(Clone)] +pub struct ElectionPublicKey(pub(crate) PublicKey); + +impl MemberSecretKey { + pub fn to_bytes(&self) -> [u8; 32] { + self.0.sk.to_bytes() + } + + pub fn from_bytes(bytes: &[u8]) -> Option { + let sk = Scalar::from_bytes(bytes)?; + Some(Self(SecretKey { sk })) + } +} + +impl MemberPublicKey { + pub const BYTES_LEN: usize = PublicKey::BYTES_LEN; + + pub fn to_bytes(&self) -> Vec { + self.0.to_bytes() + } + + pub fn from_bytes(buf: &[u8]) -> Option { + Some(Self(PublicKey::from_bytes(buf)?)) + } +} + +impl From for MemberPublicKey { + fn from(pk: PublicKey) -> MemberPublicKey { + MemberPublicKey(pk) + } +} + +impl MemberCommunicationKey { + pub fn new(rng: &mut R) -> Self { + let sk = SecretKey::generate(rng); + MemberCommunicationKey(sk) + } + + pub fn to_public(&self) -> MemberCommunicationPublicKey { + MemberCommunicationPublicKey(PublicKey { + pk: &GroupElement::generator() * &self.0.sk, + }) + } + + pub fn from_bytes(bytes: &[u8]) -> Option { + SecretKey::from_bytes(bytes).map(MemberCommunicationKey) + } + + pub fn to_bytes(&self) -> [u8; 32] { + self.0.sk.to_bytes() + } + + pub fn hybrid_decrypt(&self, ciphertext: &HybridCiphertext) -> Vec { + self.0.hybrid_decrypt(ciphertext) + } + + pub(crate) fn decrypt_shares(&self, shares: IndexedEncryptedShares) -> (Option, Option) { + let comm_scalar = Scalar::from_bytes( + &self.hybrid_decrypt(&shares.1)); + let shek_scalar = Scalar::from_bytes( + &self.hybrid_decrypt(&shares.2)); + + (comm_scalar, shek_scalar) + } +} + +impl MemberCommunicationPublicKey { + pub fn from_public_key(pk: PublicKey) -> Self { + Self(pk) + } + + pub fn to_bytes(&self) -> Vec { + self.0.to_bytes() + } + + pub fn from_bytes(bytes: &[u8]) -> Option { + PublicKey::from_bytes(bytes).map(Self) + } + + pub fn hybrid_encrypt(&self, message: &[u8], rng: &mut R) -> HybridCiphertext + where + R: RngCore + CryptoRng, + { + self.0.hybrid_encrypt(message, rng) + } +} + +impl ElectionPublicKey { + /// Create an election public key from all the participants of this committee + pub fn from_participants(pks: &[MemberPublicKey]) -> Self { + let mut k = pks[0].0.pk.clone(); + for pk in &pks[1..] { + k = k + &pk.0.pk; + } + ElectionPublicKey(PublicKey { pk: k }) + } + + pub fn to_bytes(&self) -> Vec { + self.0.to_bytes() + } + + pub fn from_bytes(buf: &[u8]) -> Option { + PublicKey::from_bytes(buf).map(ElectionPublicKey) + } + + #[doc(hidden)] + pub fn as_raw(&self) -> &PublicKey { + &self.0 + } +} diff --git a/chain-vote/src/gang/p256k1.rs b/chain-vote/src/gang/p256k1.rs index f58915c0e..59e4a4716 100644 --- a/chain-vote/src/gang/p256k1.rs +++ b/chain-vote/src/gang/p256k1.rs @@ -155,7 +155,6 @@ impl GroupElement { I: IntoIterator, J: IntoIterator, { - assert_eq!(scalars.len(), points.len()); let mut sum = GroupElement::zero(); for (scalar, point) in scalars.into_iter().zip(points.into_iter()) { sum = sum + scalar * point; diff --git a/chain-vote/src/lib.rs b/chain-vote/src/lib.rs index 9d09f8474..e390f8a98 100644 --- a/chain-vote/src/lib.rs +++ b/chain-vote/src/lib.rs @@ -3,7 +3,6 @@ #[macro_use] mod macros; mod commitment; -pub mod committee; mod encrypted; pub mod encryption; mod gang; @@ -11,6 +10,7 @@ mod math; mod errors; pub mod private_voting; mod unit_vector; +mod dkg; // re-export under a debug module #[doc(hidden)] @@ -23,9 +23,10 @@ pub mod debug { } } -pub use committee::{ - MemberCommunicationKey, MemberCommunicationPublicKey, MemberPublicKey, DistributedKeyGeneration, +pub use dkg::procedure_keys::{ + MemberCommunicationKey, MemberCommunicationPublicKey, MemberPublicKey, MemberSecretKey }; +pub use dkg::committee::DistributedKeyGeneration; pub use encrypted::EncryptingVote; pub use encryption::Ciphertext; use gang::GroupElement; @@ -34,10 +35,10 @@ use rand_core::{CryptoRng, RngCore}; pub use unit_vector::UnitVector; /// Secret key for opening vote -pub type OpeningVoteKey = committee::MemberSecretKey; +pub type OpeningVoteKey = dkg::procedure_keys::MemberSecretKey; /// Public Key for the vote -pub type EncryptingVoteKey = committee::ElectionPublicKey; +pub type EncryptingVoteKey = dkg::procedure_keys::ElectionPublicKey; /// A vote is represented by a standard basis unit vector of a N dimension space /// From 779e97d4da3bcd08ab05f2a71446ac960fedc892 Mon Sep 17 00:00:00 2001 From: iquerejeta Date: Tue, 8 Jun 2021 17:41:35 +0100 Subject: [PATCH 08/10] fmt --- chain-impl-mockchain/src/certificate/test.rs | 9 ++- chain-impl-mockchain/src/testing/data/vote.rs | 6 +- chain-impl-mockchain/src/vote/payload.rs | 12 +++- chain-vote/src/dkg/committee.rs | 62 ++++++++++++------- chain-vote/src/dkg/mod.rs | 2 +- chain-vote/src/dkg/procedure_keys.rs | 19 +++--- chain-vote/src/gang/p256k1.rs | 6 +- chain-vote/src/lib.rs | 8 +-- 8 files changed, 80 insertions(+), 44 deletions(-) diff --git a/chain-impl-mockchain/src/certificate/test.rs b/chain-impl-mockchain/src/certificate/test.rs index 57cd1dc7d..dbb397b22 100644 --- a/chain-impl-mockchain/src/certificate/test.rs +++ b/chain-impl-mockchain/src/certificate/test.rs @@ -190,7 +190,14 @@ impl Arbitrary for VotePlan { let mc = chain_vote::MemberCommunicationKey::new(&mut rng); let threshold = 1; let nr_members = 1; - let m1 = chain_vote::DistributedKeyGeneration::init(&mut rng, threshold, nr_members, &h, &[mc.to_public()], 0); + let m1 = chain_vote::DistributedKeyGeneration::init( + &mut rng, + threshold, + nr_members, + &h, + &[mc.to_public()], + 0, + ); keys.push(m1.public_key()); } diff --git a/chain-impl-mockchain/src/testing/data/vote.rs b/chain-impl-mockchain/src/testing/data/vote.rs index ab8ef3e22..b4375465e 100644 --- a/chain-impl-mockchain/src/testing/data/vote.rs +++ b/chain-impl-mockchain/src/testing/data/vote.rs @@ -1,8 +1,7 @@ use crate::vote::VotePlanStatus; use chain_vote::{ - MemberSecretKey, Crs, MemberCommunicationKey, MemberPublicKey, + Crs, DistributedKeyGeneration, MemberCommunicationKey, MemberPublicKey, MemberSecretKey, TallyDecryptShare, - DistributedKeyGeneration, }; use rand_core::CryptoRng; use rand_core::RngCore; @@ -33,7 +32,8 @@ impl CommitteeMembersManager { let mut members = Vec::new(); for i in 0..members_no { - let state = DistributedKeyGeneration::init(rng, threshold, members_no, &crs, &public_keys, i); + let state = + DistributedKeyGeneration::init(rng, threshold, members_no, &crs, &public_keys, i); members.push(CommitteeMember { state }) } diff --git a/chain-impl-mockchain/src/vote/payload.rs b/chain-impl-mockchain/src/vote/payload.rs index 46567c71f..eb738fdd5 100644 --- a/chain-impl-mockchain/src/vote/payload.rs +++ b/chain-impl-mockchain/src/vote/payload.rs @@ -205,7 +205,8 @@ mod tests { impl Arbitrary for Payload { fn arbitrary(g: &mut G) -> Self { use chain_vote::{ - encrypt_vote, Crs, EncryptingVoteKey, MemberCommunicationKey, DistributedKeyGeneration, Vote, + encrypt_vote, Crs, DistributedKeyGeneration, EncryptingVoteKey, + MemberCommunicationKey, Vote, }; use rand_core::SeedableRng; @@ -219,7 +220,14 @@ mod tests { let threshold = 1; let nr_members = 1; let h = Crs::from_hash(&mut seed); - let m = DistributedKeyGeneration::init(&mut gen, threshold, nr_members, &h, &[mc.to_public()], 0); + let m = DistributedKeyGeneration::init( + &mut gen, + threshold, + nr_members, + &h, + &[mc.to_public()], + 0, + ); let participants = vec![m.public_key()]; let ek = EncryptingVoteKey::from_participants(&participants); let vote_options = 3; diff --git a/chain-vote/src/dkg/committee.rs b/chain-vote/src/dkg/committee.rs index 409ace487..209b00560 100644 --- a/chain-vote/src/dkg/committee.rs +++ b/chain-vote/src/dkg/committee.rs @@ -6,13 +6,15 @@ //! [spec](https://github.com/input-output-hk/treasury-crypto/blob/master/docs/voting_protocol_spec/Treasury_voting_protocol_spec.pdf), //! written by Dmytro Kaidalov. +use super::procedure_keys::{ + MemberCommunicationKey, MemberCommunicationPublicKey, MemberPublicKey, MemberSecretKey, +}; use crate::encryption::{HybridCiphertext, PublicKey, SecretKey}; +use crate::errors::DkgError; use crate::gang::{GroupElement, Scalar}; use crate::math::Polynomial; use crate::Crs; -use crate::errors::DkgError; use rand_core::{CryptoRng, RngCore}; -use super::procedure_keys::{MemberCommunicationKey, MemberPublicKey, MemberSecretKey, MemberCommunicationPublicKey}; pub type DistributedKeyGeneration = MemberState1; @@ -52,7 +54,7 @@ type MisbehavingPartiesState1 = (usize, DkgError, usize); #[derive(Clone)] pub struct MembersFetchedState1 { indexed_shares: IndexedEncryptedShares, - committed_coeffs: Vec + committed_coeffs: Vec, } impl MembersFetchedState1 { @@ -137,31 +139,47 @@ impl MemberState1 { ) -> MemberState2 { let mut misbehaving_parties: Vec = Vec::new(); for fetched_data in members_state { - - if let (Some(comm), Some(shek)) = secret_key.decrypt_shares(fetched_data.indexed_shares.clone()) { + if let (Some(comm), Some(shek)) = + secret_key.decrypt_shares(fetched_data.indexed_shares.clone()) + { let index_pow = Scalar::from_u64(self.owner_index as u64) .exp_iter() .take(self.threshold + 1); let check_element = GroupElement::generator() * shek + &self.crs * comm; #[cfg(feature = "ristretto255")] - let multi_scalar = GroupElement::vartime_multiscalar_multiplication(index_pow, fetched_data.committed_coeffs.clone()); + let multi_scalar = GroupElement::vartime_multiscalar_multiplication( + index_pow, + fetched_data.committed_coeffs.clone(), + ); #[cfg(not(feature = "ristretto255"))] - let multi_scalar = GroupElement::multiscalar_multiplication(index_pow, fetched_data.committed_coeffs.clone()); + let multi_scalar = GroupElement::multiscalar_multiplication( + index_pow, + fetched_data.committed_coeffs.clone(), + ); if check_element != multi_scalar { // todo: should we instead store the sender's index? - misbehaving_parties.push((fetched_data.get_index().clone(), DkgError::ShareValidityFailed, 0)); + misbehaving_parties.push(( + fetched_data.get_index().clone(), + DkgError::ShareValidityFailed, + 0, + )); } - } - else { + } else { // todo: handle the proofs. Might not be the most optimal way of handling these two - misbehaving_parties.push((fetched_data.get_index().clone(), DkgError::ScalarOutOfBounds, 0)); + misbehaving_parties.push(( + fetched_data.get_index().clone(), + DkgError::ScalarOutOfBounds, + 0, + )); } } - MemberState2{misbehaving_parties, threshold: self.threshold} - + MemberState2 { + misbehaving_parties, + threshold: self.threshold, + } } pub fn secret_key(&self) -> &MemberSecretKey { @@ -210,10 +228,10 @@ mod tests { let m2 = DistributedKeyGeneration::init(&mut rng, threshold, nr_members, &h, &mc, 1); // Now, party one fetches the state of the other parties, mainly party two and three - let fetched_state = vec![ - MembersFetchedState1{ - indexed_shares: m2.encrypted_shares[0].clone(), - committed_coeffs: m2.coeff_comms.clone()}]; + let fetched_state = vec![MembersFetchedState1 { + indexed_shares: m2.encrypted_shares[0].clone(), + committed_coeffs: m2.coeff_comms.clone(), + }]; let phase_2 = m1.to_phase_2(&mc1, &fetched_state); @@ -241,12 +259,14 @@ mod tests { // Now, party one fetches invalid state of the other parties, mainly party two and three let fetched_state = vec![ - MembersFetchedState1{ + MembersFetchedState1 { indexed_shares: m2.encrypted_shares[0].clone(), - committed_coeffs: vec![GroupElement::zero(); 3]}, - MembersFetchedState1{ + committed_coeffs: vec![GroupElement::zero(); 3], + }, + MembersFetchedState1 { indexed_shares: m3.encrypted_shares[0].clone(), - committed_coeffs: vec![GroupElement::zero(); 3]}, + committed_coeffs: vec![GroupElement::zero(); 3], + }, ]; let phase_2_faked = m1.to_phase_2(&mc1, &fetched_state); diff --git a/chain-vote/src/dkg/mod.rs b/chain-vote/src/dkg/mod.rs index 2de98d888..c7d25336f 100644 --- a/chain-vote/src/dkg/mod.rs +++ b/chain-vote/src/dkg/mod.rs @@ -1,2 +1,2 @@ pub mod committee; -pub mod procedure_keys; \ No newline at end of file +pub mod procedure_keys; diff --git a/chain-vote/src/dkg/procedure_keys.rs b/chain-vote/src/dkg/procedure_keys.rs index b855994fc..164173059 100644 --- a/chain-vote/src/dkg/procedure_keys.rs +++ b/chain-vote/src/dkg/procedure_keys.rs @@ -1,7 +1,7 @@ -use crate::encryption::{PublicKey, SecretKey, HybridCiphertext}; +use super::committee::IndexedEncryptedShares; +use crate::encryption::{HybridCiphertext, PublicKey, SecretKey}; use crate::gang::{GroupElement, Scalar}; use rand_core::{CryptoRng, RngCore}; -use super::committee::IndexedEncryptedShares; /// Committee member election secret key #[derive(Clone)] @@ -76,11 +76,12 @@ impl MemberCommunicationKey { self.0.hybrid_decrypt(ciphertext) } - pub(crate) fn decrypt_shares(&self, shares: IndexedEncryptedShares) -> (Option, Option) { - let comm_scalar = Scalar::from_bytes( - &self.hybrid_decrypt(&shares.1)); - let shek_scalar = Scalar::from_bytes( - &self.hybrid_decrypt(&shares.2)); + pub(crate) fn decrypt_shares( + &self, + shares: IndexedEncryptedShares, + ) -> (Option, Option) { + let comm_scalar = Scalar::from_bytes(&self.hybrid_decrypt(&shares.1)); + let shek_scalar = Scalar::from_bytes(&self.hybrid_decrypt(&shares.2)); (comm_scalar, shek_scalar) } @@ -100,8 +101,8 @@ impl MemberCommunicationPublicKey { } pub fn hybrid_encrypt(&self, message: &[u8], rng: &mut R) -> HybridCiphertext - where - R: RngCore + CryptoRng, + where + R: RngCore + CryptoRng, { self.0.hybrid_encrypt(message, rng) } diff --git a/chain-vote/src/gang/p256k1.rs b/chain-vote/src/gang/p256k1.rs index 59e4a4716..1aaa2e08e 100644 --- a/chain-vote/src/gang/p256k1.rs +++ b/chain-vote/src/gang/p256k1.rs @@ -151,9 +151,9 @@ impl GroupElement { /// Non-optimised multiscalar multiplication. If we use the sec2 backend, this function could /// be optimised. pub fn multiscalar_multiplication(scalars: I, points: J) -> Self - where - I: IntoIterator, - J: IntoIterator, + where + I: IntoIterator, + J: IntoIterator, { let mut sum = GroupElement::zero(); for (scalar, point) in scalars.into_iter().zip(points.into_iter()) { diff --git a/chain-vote/src/lib.rs b/chain-vote/src/lib.rs index e390f8a98..73a097c97 100644 --- a/chain-vote/src/lib.rs +++ b/chain-vote/src/lib.rs @@ -3,14 +3,14 @@ #[macro_use] mod macros; mod commitment; +mod dkg; mod encrypted; pub mod encryption; +mod errors; mod gang; mod math; -mod errors; pub mod private_voting; mod unit_vector; -mod dkg; // re-export under a debug module #[doc(hidden)] @@ -23,10 +23,10 @@ pub mod debug { } } +pub use dkg::committee::DistributedKeyGeneration; pub use dkg::procedure_keys::{ - MemberCommunicationKey, MemberCommunicationPublicKey, MemberPublicKey, MemberSecretKey + MemberCommunicationKey, MemberCommunicationPublicKey, MemberPublicKey, MemberSecretKey, }; -pub use dkg::committee::DistributedKeyGeneration; pub use encrypted::EncryptingVote; pub use encryption::Ciphertext; use gang::GroupElement; From 4cac766fbaa0afa5fd75a93085627b50f9bf43a2 Mon Sep 17 00:00:00 2001 From: iquerejeta Date: Tue, 8 Jun 2021 17:42:55 +0100 Subject: [PATCH 09/10] lints --- chain-vote/src/dkg/committee.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/chain-vote/src/dkg/committee.rs b/chain-vote/src/dkg/committee.rs index 209b00560..f1e3b764a 100644 --- a/chain-vote/src/dkg/committee.rs +++ b/chain-vote/src/dkg/committee.rs @@ -135,7 +135,7 @@ impl MemberState1 { pub fn to_phase_2( &self, secret_key: &MemberCommunicationKey, - members_state: &Vec, + members_state: &[MembersFetchedState1], ) -> MemberState2 { let mut misbehaving_parties: Vec = Vec::new(); for fetched_data in members_state { @@ -161,7 +161,7 @@ impl MemberState1 { if check_element != multi_scalar { // todo: should we instead store the sender's index? misbehaving_parties.push(( - fetched_data.get_index().clone(), + fetched_data.get_index(), DkgError::ShareValidityFailed, 0, )); @@ -169,7 +169,7 @@ impl MemberState1 { } else { // todo: handle the proofs. Might not be the most optimal way of handling these two misbehaving_parties.push(( - fetched_data.get_index().clone(), + fetched_data.get_index(), DkgError::ScalarOutOfBounds, 0, )); From a35f16e01ef72575da1e404d478339f7c12dd1b0 Mon Sep 17 00:00:00 2001 From: iquerejeta Date: Tue, 8 Jun 2021 17:48:50 +0100 Subject: [PATCH 10/10] forgotten code of benches --- chain-vote/benches/shvzk.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/chain-vote/benches/shvzk.rs b/chain-vote/benches/shvzk.rs index 2c83fc8cf..237359086 100644 --- a/chain-vote/benches/shvzk.rs +++ b/chain-vote/benches/shvzk.rs @@ -10,8 +10,9 @@ fn common(rng: &mut ChaCha20Rng) -> (EncryptingVoteKey, EncryptingVote) { let mc = [mc1.to_public()]; let threshold = 1; + let nr_members = 1; - let m1 = MemberState::new(rng, threshold, &h, &mc, 0); + let m1 = DistributedKeyGeneration::init(rng, threshold, nr_members, &h, &mc, 0); let participants = vec![m1.public_key()]; let ek = EncryptingVoteKey::from_participants(&participants);