From 3c335551104f2306f9233d8dc18aae40c3b9cd67 Mon Sep 17 00:00:00 2001 From: Fiono11 Date: Mon, 25 Mar 2024 16:10:48 +0000 Subject: [PATCH] Implementation of SimplPedPoP This reverts commit 7bf09b57873da606f1ccb02bb3afa217dc7e1e99. --- .DS_Store | Bin 0 -> 6148 bytes Cargo.toml | 39 +- benches/simplpedpop_benchmarks.rs | 221 ++++ src/aead.rs | 105 +- src/errors.rs | 123 +- src/identifier.rs | 62 + src/lib.rs | 30 +- src/polynomial.rs | 212 ++++ src/serdey.rs | 102 +- src/simplpedpop.rs | 1883 +++++++++++++++++++++++++++++ 10 files changed, 2650 insertions(+), 127 deletions(-) create mode 100644 .DS_Store create mode 100644 benches/simplpedpop_benchmarks.rs create mode 100644 src/identifier.rs create mode 100644 src/polynomial.rs create mode 100644 src/simplpedpop.rs diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..1f487643c31855e8a0f311ffce8c5cef9e99ed9d GIT binary patch literal 6148 zcmeHKJ8HvF5S)z-F}QJ=@?9Y}5XLz{E?_JNQiup5(yPk3aD}rERu}TBU##kOE%{`1hgF9lOFgF+Lq! zVgw+r84lwe m(T<7Hj(PBQd>2WX*L=_CUE!P Vec { + (1..=max_signers) + .map(|_| Parameters::new(max_signers, min_signers)) + .collect() + } + + fn round1( + participants: u16, + threshold: u16, + ) -> ( + Vec, + Vec, + Vec, + Vec>, + ) { + let parameters_list = generate_parameters(participants, threshold); + + let mut participants_round1_private_data = Vec::new(); + let mut participants_round1_public_data = Vec::new(); + let mut all_public_messages_vec = Vec::new(); + + for parameters in ¶meters_list { + let (private_data, public_message, public_data) = + round1::run(parameters.clone(), OsRng).unwrap(); + participants_round1_private_data.push(private_data); + participants_round1_public_data.push(public_data); + all_public_messages_vec.push(public_message); + } + + let mut received_round1_public_messages: Vec> = Vec::new(); + + for (i, own_message) in all_public_messages_vec.iter().enumerate() { + let mut messages_for_participant = all_public_messages_vec + .iter() + .enumerate() + .filter(|&(j, _)| i != j) + .map(|(_, message)| message.clone()) + .collect::>(); + + received_round1_public_messages.push(messages_for_participant); + } + + ( + parameters_list, + participants_round1_private_data, + participants_round1_public_data, + received_round1_public_messages, + ) + } + + fn round2( + parameters_list: &Vec, + participants_round1_private_data: Vec, + participants_round1_public_data: &Vec, + participants_round1_public_messages: &Vec>, + ) -> ( + Vec>, + Vec, + Vec, + Vec, + ) { + let mut participants_round2_public_data = Vec::new(); + let mut participants_round2_public_messages = Vec::new(); + let mut participants_set_of_participants = Vec::new(); + let mut identifiers_vec = Vec::new(); + + for i in 0..*parameters_list[0].participants() { + let result = round2::run( + participants_round1_private_data[i as usize].clone(), + &participants_round1_public_data[i as usize].clone(), + participants_round1_public_messages[i as usize].clone(), + Transcript::new(b"simplpedpop"), + ) + .expect("Round 2 should complete without errors!"); + + participants_round2_public_data.push(result.0.clone()); + participants_round2_public_messages.push(result.1); + participants_set_of_participants.push(result.0.identifiers().clone()); + identifiers_vec.push(*result.0.identifiers().own_identifier()); + } + + ( + participants_round2_public_data, + participants_round2_public_messages, + participants_set_of_participants, + identifiers_vec, + ) + } + + fn benchmark_simplpedpop(c: &mut Criterion) { + let mut group = c.benchmark_group("SimplPedPoP"); + + group + .sample_size(10) + .warm_up_time(std::time::Duration::from_secs(2)) + .measurement_time(std::time::Duration::from_secs(30)); + + for &n in [3, 10, 100].iter() { + let participants = n; + let threshold = (n * 2 + 2) / 3; + let parameters_list = generate_parameters(participants, threshold); + + group.bench_function(BenchmarkId::new("round1", participants), |b| { + b.iter(|| { + round1::run(parameters_list[0].clone(), OsRng).unwrap(); + }) + }); + + let ( + parameters_list, + participants_round1_private_data, + participants_round1_public_data, + participants_round1_public_messages, + ) = round1(participants, threshold); + + group.bench_function(BenchmarkId::new("round2", participants), |b| { + b.iter(|| { + round2::run( + participants_round1_private_data[0].clone(), + &participants_round1_public_data[0], + participants_round1_public_messages[0].clone(), + Transcript::new(b"simplpedpop"), + ) + .unwrap(); + }) + }); + + let ( + participants_round2_public_data, + participants_round2_messages, + participants_sets_of_participants, + identifiers_vec, + ) = round2( + ¶meters_list, + participants_round1_private_data.clone(), + &participants_round1_public_data, + &participants_round1_public_messages, + ); + + let participants_round2_public_messages: Vec = + participants_round2_messages + .iter() + .map(|msg| msg.public_message().clone()) + .collect(); + + let participants_round2_private_messages: Vec< + BTreeMap, + > = participants_round2_messages + .iter() + .map(|msg| msg.private_messages().clone()) + .collect(); + + let received_round2_public_messages = participants_round2_public_messages + .iter() + .enumerate() + .filter(|(index, _msg)| { + identifiers_vec[*index] + != *participants_sets_of_participants[0].own_identifier() + }) + .map(|(index, msg)| (identifiers_vec[index], msg.clone())) + .collect::>(); + + let mut round2_private_messages: Vec> = + Vec::new(); + + for participants in participants_sets_of_participants.iter() { + let mut messages_for_participant = BTreeMap::new(); + + for (i, round_messages) in participants_round2_private_messages.iter().enumerate() { + if let Some(message) = round_messages.get(&participants.own_identifier()) { + messages_for_participant.insert(identifiers_vec[i], message.clone()); + } + } + + round2_private_messages.push(messages_for_participant); + } + + group.bench_function(BenchmarkId::new("round3", participants), |b| { + b.iter(|| { + round3::run( + &received_round2_public_messages, + &participants_round2_public_data[0], + &participants_round1_public_data[0], + participants_round1_private_data[0].clone(), + &round2_private_messages[0], + ) + .unwrap(); + }) + }); + } + + group.finish(); + } + + criterion_group! { + name = simplpedpop_benches; + config = Criterion::default(); + targets = + benchmark_simplpedpop, + } +} + +criterion_main!(simplpedpop_benches::simplpedpop_benches); diff --git a/src/aead.rs b/src/aead.rs index 6cb1b67..29688ca 100644 --- a/src/aead.rs +++ b/src/aead.rs @@ -25,25 +25,26 @@ regarded as a pointer, not a recommendation. // use rand_core::{RngCore,CryptoRng}; -use aead::{KeyInit, KeySizeUser, generic_array::{GenericArray}}; +use aead::{generic_array::GenericArray, KeyInit, KeySizeUser}; -use curve25519_dalek::digest::generic_array::typenum::{U32}; +use curve25519_dalek::digest::generic_array::typenum::U32; use curve25519_dalek::{ - ristretto::{CompressedRistretto}, // RistrettoPoint + ristretto::CompressedRistretto, // RistrettoPoint }; -use super::{SecretKey,PublicKey,Keypair,SignatureResult}; +use super::{Keypair, PublicKey, SecretKey, SignatureResult}; use crate::context::SigningTranscript; use crate::cert::AdaptorCertPublic; - -fn make_aead(mut t: T) -> AEAD -where T: SigningTranscript,AEAD: KeyInit +pub(crate) fn make_aead(mut t: T) -> AEAD +where + T: SigningTranscript, + AEAD: KeyInit, { let mut key: GenericArray::KeySize> = Default::default(); - t.challenge_bytes(b"",key.as_mut_slice()); + t.challenge_bytes(b"", key.as_mut_slice()); AEAD::new(&key) } @@ -56,7 +57,8 @@ impl SecretKey { /// Commit the results of a raw key exchange into a transcript pub fn commit_raw_key_exchange(&self, t: &mut T, ctx: &'static [u8], public: &PublicKey) - where T: SigningTranscript + where + T: SigningTranscript, { let p = self.raw_key_exchange(public); t.commit_point(ctx, &p); @@ -66,10 +68,11 @@ impl SecretKey { /// /// Requires the AEAD have a 32 byte public key and does not support a context. pub fn aead32_unauthenticated(&self, public: &PublicKey) -> AEAD - where AEAD: KeyInit + where + AEAD: KeyInit, { let mut key: GenericArray::KeySize> = Default::default(); - key.clone_from_slice( self.raw_key_exchange(public).as_bytes() ); + key.clone_from_slice(self.raw_key_exchange(public).as_bytes()); AEAD::new(&key) } } @@ -78,10 +81,12 @@ impl PublicKey { /// Initialize an AEAD to the public key `self` using an ephemeral key exchange. /// /// Returns the ephemeral public key and AEAD. - pub fn init_aead_unauthenticated(&self, ctx: &[u8]) -> (CompressedRistretto,AEAD) - { + pub fn init_aead_unauthenticated( + &self, + ctx: &[u8], + ) -> (CompressedRistretto, AEAD) { let ephemeral = Keypair::generate(); - let aead = ephemeral.aead_unauthenticated(ctx,self); + let aead = ephemeral.aead_unauthenticated(ctx, self); (ephemeral.public.into_compressed(), aead) } @@ -89,8 +94,9 @@ impl PublicKey { /// /// Returns the ephemeral public key and AEAD. /// Requires the AEAD have a 32 byte public key and does not support a context. - pub fn init_aead32_unauthenticated(&self) -> (CompressedRistretto,AEAD) - where AEAD: KeyInit + pub fn init_aead32_unauthenticated(&self) -> (CompressedRistretto, AEAD) + where + AEAD: KeyInit, { let secret = SecretKey::generate(); let aead = secret.aead32_unauthenticated(self); @@ -102,48 +108,55 @@ impl Keypair { /// Commit the results of a key exchange into a transcript /// including the public keys in sorted order. pub fn commit_key_exchange(&self, t: &mut T, ctx: &'static [u8], public: &PublicKey) - where T: SigningTranscript + where + T: SigningTranscript, { let mut pks = [self.public.as_compressed(), public.as_compressed()]; - pks.sort_unstable_by_key( |pk| pk.as_bytes() ); - for pk in &pks { t.commit_point(b"pk",pk); } - self.secret.commit_raw_key_exchange(t,ctx,public); + pks.sort_unstable_by_key(|pk| pk.as_bytes()); + for pk in &pks { + t.commit_point(b"pk", pk); + } + self.secret.commit_raw_key_exchange(t, ctx, public); } /// An AEAD from a key exchange with the specified public key. pub fn aead_unauthenticated(&self, ctx: &[u8], public: &PublicKey) -> AEAD { let mut t = merlin::Transcript::new(b"KEX"); - t.append_message(b"ctx",ctx); - self.commit_key_exchange(&mut t,b"kex",public); + t.append_message(b"ctx", ctx); + self.commit_key_exchange(&mut t, b"kex", public); make_aead(t) } /// Reciever's 2DH AEAD - pub fn reciever_aead( + pub fn reciever_aead( &self, mut t: T, ephemeral_pk: &PublicKey, static_pk: &PublicKey, ) -> AEAD - where T: SigningTranscript, AEAD: KeyInit + where + T: SigningTranscript, + AEAD: KeyInit, { - self.commit_key_exchange(&mut t,b"epk",ephemeral_pk); - self.commit_key_exchange(&mut t,b"epk",static_pk); + self.commit_key_exchange(&mut t, b"epk", ephemeral_pk); + self.commit_key_exchange(&mut t, b"epk", static_pk); make_aead(t) } /// Sender's 2DH AEAD - pub fn sender_aead( - &self, - mut t: T, - public: &PublicKey, - ) -> (CompressedRistretto,AEAD) - where T: SigningTranscript, AEAD: KeyInit + pub fn sender_aead(&self, mut t: T, public: &PublicKey) -> (CompressedRistretto, AEAD) + where + T: SigningTranscript, + AEAD: KeyInit, { let key = t.witness_scalar(b"make_esk", &[&self.secret.nonce]); - let ekey = SecretKey { key, nonce: self.secret.nonce }.to_keypair(); - ekey.commit_key_exchange(&mut t,b"epk",public); - self.commit_key_exchange(&mut t,b"epk",public); + let ekey = SecretKey { + key, + nonce: self.secret.nonce, + } + .to_keypair(); + ekey.commit_key_exchange(&mut t, b"epk", public); + self.commit_key_exchange(&mut t, b"epk", public); (ekey.public.into_compressed(), make_aead(t)) } @@ -152,26 +165,34 @@ impl Keypair { /// Returns the AEAD constructed from an ephemeral key exchange /// with the public key computed form the sender's public key /// and their implicit Adaptor certificate. - pub fn reciever_aead_with_adaptor_cert( + pub fn reciever_aead_with_adaptor_cert( &self, t: T, cert_public: &AdaptorCertPublic, public: &PublicKey, ) -> SignatureResult - where T: SigningTranscript, AEAD: KeyInit + where + T: SigningTranscript, + AEAD: KeyInit, { - let epk = public.open_adaptor_cert(t,cert_public) ?; - Ok(self.aead_unauthenticated(b"",&epk)) + let epk = public.open_adaptor_cert(t, cert_public)?; + Ok(self.aead_unauthenticated(b"", &epk)) } /// Sender's AEAD with Adaptor certificate. /// /// Along with the AEAD, we return the implicit Adaptor certificate /// from which the receiver recreates the ephemeral public key. - pub fn sender_aead_with_adaptor_cert(&self, t: T, public: &PublicKey) -> (AdaptorCertPublic,AEAD) - where T: SigningTranscript+Clone, AEAD: KeyInit + pub fn sender_aead_with_adaptor_cert( + &self, + t: T, + public: &PublicKey, + ) -> (AdaptorCertPublic, AEAD) + where + T: SigningTranscript + Clone, + AEAD: KeyInit, { - let (cert,secret) = self.issue_self_adaptor_cert(t); + let (cert, secret) = self.issue_self_adaptor_cert(t); let aead = secret.to_keypair().aead_unauthenticated(b"", public); (cert, aead) } diff --git a/src/errors.rs b/src/errors.rs index e8c73c0..657c2aa 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -14,10 +14,10 @@ // Display) should be snake cased, for some reason. #![allow(non_snake_case)] +use crate::identifier::Identifier; use core::fmt; use core::fmt::Display; - /// `Result` specialized to this crate for convenience. pub type SignatureResult = Result; @@ -88,7 +88,7 @@ pub enum SignatureError { /// Describes the type returning the error description: &'static str, /// Length expected by the constructor in bytes - length: usize + length: usize, }, /// Signature not marked as schnorrkel, maybe try ed25519 instead. NotMarkedSchnorrkel, @@ -116,26 +116,32 @@ impl Display for SignatureError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { use self::SignatureError::*; match *self { - EquationFalse => - write!(f, "Verification equation failed"), - PointDecompressionError => - write!(f, "Cannot decompress Ristretto point"), - ScalarFormatError => - write!(f, "Cannot use scalar with high-bit set"), - InvalidKey => - write!(f, "The provided key is not valid"), - BytesLengthError { name, length, .. } => - write!(f, "{name} must be {length} bytes in length"), - NotMarkedSchnorrkel => - write!(f, "Signature bytes not marked as a schnorrkel signature"), - MuSigAbsent { musig_stage, } => - write!(f, "Absent {musig_stage} violated multi-signature protocol"), - MuSigInconsistent { musig_stage, duplicate, } => + EquationFalse => write!(f, "Verification equation failed"), + PointDecompressionError => write!(f, "Cannot decompress Ristretto point"), + ScalarFormatError => write!(f, "Cannot use scalar with high-bit set"), + InvalidKey => write!(f, "The provided key is not valid"), + BytesLengthError { name, length, .. } => { + write!(f, "{name} must be {length} bytes in length") + } + NotMarkedSchnorrkel => { + write!(f, "Signature bytes not marked as a schnorrkel signature") + } + MuSigAbsent { musig_stage } => { + write!(f, "Absent {musig_stage} violated multi-signature protocol") + } + MuSigInconsistent { + musig_stage, + duplicate, + } => { if duplicate { write!(f, "Inconsistent duplicate {musig_stage} in multi-signature") } else { - write!(f, "Inconsistent {musig_stage} violated multi-signature protocol") - }, + write!( + f, + "Inconsistent {musig_stage} violated multi-signature protocol" + ) + } + } } } } @@ -149,7 +155,84 @@ impl failure::Fail for SignatureError {} /// `impl From for E where E: serde::de::Error`. #[cfg(feature = "serde")] pub fn serde_error_from_signature_error(err: SignatureError) -> E -where E: serde_crate::de::Error +where + E: serde::de::Error, { E::custom(err) } + +/// A result for the SimplPedPoP protocol. +pub type DKGResult = Result; + +/// An error ocurred during the execution of the SimplPedPoP protocol. +#[derive(Debug, Clone, Eq, PartialEq)] +pub enum DKGError { + /// Invalid Proof of Possession. + InvalidProofOfPossession(SignatureError), + /// Invalid certificate. + InvalidCertificate(SignatureError), + /// Threshold cannot be greater than the number of participants. + ExcessiveThreshold, + /// Threshold must be at least 2. + InsufficientThreshold, + /// Number of participants is invalid. + InvalidNumberOfParticipants, + /// Secret share verification failed. + InvalidSecretShare(Identifier), + /// Invalid secret. + InvalidSecret, + /// Unknown identifier in round 2 public messages. + UnknownIdentifierRound2PublicMessages(Identifier), + /// Unknown identifier in round 2 private messages. + UnknownIdentifierRound2PrivateMessages(Identifier), + /// Unknown identifier. + UnknownIdentifier, + /// Shared public key mismatch. + SharedPublicKeyMismatch, + /// Identifier cannot be a zero scalar. + InvalidIdentifier, + /// Incorrect number of identifiers. + IncorrectNumberOfIdentifiers { + /// The expected value. + expected: usize, + /// The actual value. + actual: usize, + }, + /// Incorrect number of private messages. + IncorrectNumberOfPrivateMessages { + /// The expected value. + expected: usize, + /// The actual value. + actual: usize, + }, + /// Incorrect number of round 1 public messages. + IncorrectNumberOfRound1PublicMessages { + /// The expected value. + expected: usize, + /// The actual value. + actual: usize, + }, + /// Incorrect number of round 2 public messages. + IncorrectNumberOfRound2PublicMessages { + /// The expected value. + expected: usize, + /// The actual value. + actual: usize, + }, + /// Incorrect number of round 2 private messages. + IncorrectNumberOfRound2PrivateMessages { + /// The expected value. + expected: usize, + /// The actual value. + actual: usize, + }, + /// Decryption error when decrypting an encrypted secret share. + DecryptionError(chacha20poly1305::Error), + /// Incorrect number of coefficient commitments. + InvalidSecretPolynomialCommitment { + /// The expected value. + expected: usize, + /// The actual value. + actual: usize, + }, +} diff --git a/src/identifier.rs b/src/identifier.rs new file mode 100644 index 0000000..ed1ed78 --- /dev/null +++ b/src/identifier.rs @@ -0,0 +1,62 @@ +//! The identifier of a participant in a multiparty protocol. + +use core::cmp::Ordering; + +use crate::errors::DKGError; +use curve25519_dalek::Scalar; + +/// The identifier is represented by a scalar. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct Identifier(pub(crate) Scalar); + +impl PartialOrd for Identifier { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for Identifier { + fn cmp(&self, other: &Self) -> Ordering { + let serialized_self = self.0.as_bytes(); + let serialized_other = other.0.as_bytes(); + + // The default cmp uses lexicographic order; so we need the elements in big endian + serialized_self + .as_ref() + .iter() + .rev() + .cmp(serialized_other.as_ref().iter().rev()) + } +} + +impl TryFrom for Identifier { + type Error = DKGError; + + fn try_from(n: u16) -> Result { + if n == 0 { + Err(DKGError::InvalidIdentifier) + } else { + // Classic left-to-right double-and-add algorithm that skips the first bit 1 (since + // identifiers are never zero, there is always a bit 1), thus `sum` starts with 1 too. + let one = Scalar::ONE; + let mut sum = Scalar::ONE; + + let bits = (n.to_be_bytes().len() as u32) * 8; + for i in (0..(bits - n.leading_zeros() - 1)).rev() { + sum = sum + sum; + if n & (1 << i) != 0 { + sum += one; + } + } + Ok(Self(sum)) + } + } +} + +impl TryFrom for Identifier { + type Error = DKGError; + + fn try_from(value: Scalar) -> Result { + Ok(Self(value)) + } +} diff --git a/src/lib.rs b/src/lib.rs index 323bdaa..eeebe87 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -231,22 +231,29 @@ extern crate std; #[cfg(feature = "alloc")] extern crate alloc; -use getrandom_or_panic::{RngCore,CryptoRng,getrandom_or_panic}; use curve25519_dalek::scalar::Scalar; +use getrandom_or_panic::{getrandom_or_panic, CryptoRng, RngCore}; #[macro_use] mod serdey; +pub mod keys; pub mod points; mod scalars; -pub mod keys; +pub mod cert; pub mod context; -pub mod sign; -pub mod vrf; pub mod derive; -pub mod cert; pub mod errors; +pub mod identifier; +pub mod sign; +pub mod vrf; + +#[cfg(all(feature = "alloc", feature = "aead"))] +pub mod polynomial; + +#[cfg(all(feature = "alloc", feature = "aead"))] +pub mod simplpedpop; #[cfg(all(feature = "aead", feature = "getrandom"))] pub mod aead; @@ -256,17 +263,20 @@ mod batch; // Not safe because need randomness -#[cfg_attr(not(test), deprecated(since = "0.11.0", note = "This module will be replaced in the future"))] +#[cfg_attr( + not(test), + deprecated(since = "0.11.0", note = "This module will be replaced in the future") +)] #[cfg(feature = "std")] pub mod musig; +pub use crate::context::signing_context; // SigningContext,SigningTranscript +pub use crate::errors::{SignatureError, SignatureResult}; pub use crate::keys::*; // {MiniSecretKey,SecretKey,PublicKey,Keypair,ExpansionMode}; + *_LENGTH -pub use crate::context::{signing_context}; // SigningContext,SigningTranscript -pub use crate::sign::{Signature,SIGNATURE_LENGTH}; -pub use crate::errors::{SignatureError,SignatureResult}; +pub use crate::sign::{Signature, SIGNATURE_LENGTH}; #[cfg(feature = "alloc")] -pub use crate::batch::{verify_batch,verify_batch_rng,verify_batch_deterministic,PreparedBatch}; +pub use crate::batch::{verify_batch, verify_batch_deterministic, verify_batch_rng, PreparedBatch}; pub(crate) fn scalar_from_canonical_bytes(bytes: [u8; 32]) -> Option { let key = Scalar::from_canonical_bytes(bytes); diff --git a/src/polynomial.rs b/src/polynomial.rs new file mode 100644 index 0000000..a21150e --- /dev/null +++ b/src/polynomial.rs @@ -0,0 +1,212 @@ +//! Implementation of a polynomial and related operations. + +use alloc::vec; +use alloc::vec::Vec; +use curve25519_dalek::{traits::Identity, RistrettoPoint, Scalar}; +use rand_core::{CryptoRng, RngCore}; +use zeroize::ZeroizeOnDrop; + +use crate::simplpedpop::GENERATOR; + +pub(crate) type Coefficient = Scalar; +pub(crate) type Value = Scalar; +pub(crate) type ValueCommitment = RistrettoPoint; +pub(crate) type CoefficientCommitment = RistrettoPoint; + +/// A polynomial. +#[derive(Debug, Clone, ZeroizeOnDrop)] +pub struct Polynomial { + pub(crate) coefficients: Vec, +} + +impl Polynomial { + pub(crate) fn generate(rng: &mut R, degree: u16) -> Self { + let mut coefficients = Vec::new(); + + for _ in 0..(degree as usize + 1) { + coefficients.push(Scalar::random(rng)); + } + + Self { coefficients } + } + + pub(crate) fn evaluate(&self, x: &Value) -> Value { + let mut value = *self + .coefficients + .last() + .expect("coefficients must have at least one element"); + + // Process all coefficients except the last one, using Horner's method + for coeff in self.coefficients.iter().rev().skip(1) { + value = value * x + coeff; + } + + value + } +} + +/// A polynomial commitment. +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct PolynomialCommitment { + pub(crate) coefficients_commitments: Vec, +} + +impl PolynomialCommitment { + pub(crate) fn commit(polynomial: &Polynomial) -> Self { + let coefficients_commitments = polynomial + .coefficients + .iter() + .map(|coefficient| GENERATOR * coefficient) + .collect(); + + Self { + coefficients_commitments, + } + } + + pub(crate) fn evaluate(&self, identifier: &Value) -> ValueCommitment { + let i = identifier; + + let (_, result) = self.coefficients_commitments.iter().fold( + (Scalar::ONE, RistrettoPoint::identity()), + |(i_to_the_k, sum_so_far), comm_k| (i * i_to_the_k, sum_so_far + comm_k * i_to_the_k), + ); + result + } + + pub(crate) fn sum_polynomial_commitments( + polynomials_commitments: &[&PolynomialCommitment], + ) -> PolynomialCommitment { + let max_length = polynomials_commitments + .iter() + .map(|c| c.coefficients_commitments.len()) + .max() + .unwrap_or(0); + + let mut total_commitment = vec![RistrettoPoint::identity(); max_length]; + + for polynomial_commitment in polynomials_commitments { + for (i, coeff_commitment) in polynomial_commitment + .coefficients_commitments + .iter() + .enumerate() + { + total_commitment[i] += coeff_commitment; + } + } + + PolynomialCommitment { + coefficients_commitments: total_commitment, + } + } +} + +#[cfg(test)] +mod tests { + use crate::{ + polynomial::{Coefficient, Polynomial, PolynomialCommitment}, + simplpedpop::GENERATOR, + }; + + use alloc::vec::Vec; + use curve25519_dalek::Scalar; + use rand::rngs::OsRng; + + #[test] + fn test_generate_polynomial_commitment_valid() { + let degree = 3; + + let polynomial = Polynomial::generate(&mut OsRng, degree); + + let polynomial_commitment = PolynomialCommitment::commit(&polynomial); + + assert_eq!(polynomial.coefficients.len(), degree as usize + 1); + + assert_eq!( + polynomial_commitment.coefficients_commitments.len(), + degree as usize + 1 + ); + } + + #[test] + fn test_evaluate_polynomial() { + let coefficients: Vec = + vec![Scalar::from(3u64), Scalar::from(2u64), Scalar::from(1u64)]; // Polynomial x^2 + 2x + 3 + + let polynomial = Polynomial { coefficients }; + + let value = Scalar::from(5u64); // x = 5 + + let result = polynomial.evaluate(&value); + + assert_eq!(result, Scalar::from(38u64)); // 5^2 + 2*5 + 3 + } + + #[test] + fn test_sum_secret_polynomial_commitments() { + let polynomial_commitment1 = PolynomialCommitment { + coefficients_commitments: vec![ + GENERATOR * Scalar::from(1u64), // Constant + GENERATOR * Scalar::from(2u64), // Linear + GENERATOR * Scalar::from(3u64), // Quadratic + ], + }; + + let polynomial_commitment2 = PolynomialCommitment { + coefficients_commitments: vec![ + GENERATOR * Scalar::from(4u64), // Constant + GENERATOR * Scalar::from(5u64), // Linear + GENERATOR * Scalar::from(6u64), // Quadratic + ], + }; + + let summed_polynomial_commitments = PolynomialCommitment::sum_polynomial_commitments(&[ + &polynomial_commitment1, + &polynomial_commitment2, + ]); + + let expected_coefficients_commitments = vec![ + GENERATOR * Scalar::from(5u64), // 1 + 4 = 5 + GENERATOR * Scalar::from(7u64), // 2 + 5 = 7 + GENERATOR * Scalar::from(9u64), // 3 + 6 = 9 + ]; + + assert_eq!( + summed_polynomial_commitments.coefficients_commitments, + expected_coefficients_commitments, + "Coefficient commitments do not match" + ); + } + + #[test] + fn test_evaluate_polynomial_commitment() { + // f(x) = 3 + 2x + x^2 + let constant_coefficient_commitment = Scalar::from(3u64) * GENERATOR; + let linear_commitment = Scalar::from(2u64) * GENERATOR; + let quadratic_commitment = Scalar::from(1u64) * GENERATOR; + + // Note the order and inclusion of the constant term + let coefficients_commitments = vec![ + constant_coefficient_commitment, + linear_commitment, + quadratic_commitment, + ]; + + let polynomial_commitment = PolynomialCommitment { + coefficients_commitments, + }; + + let value = Scalar::from(2u64); + + // f(2) = 11 + let expected = Scalar::from(11u64) * GENERATOR; + + let result = polynomial_commitment.evaluate(&value); + + assert_eq!( + result, expected, + "The evaluated commitment does not match the expected result" + ); + } +} diff --git a/src/serdey.rs b/src/serdey.rs index 64c5e26..f152ef5 100644 --- a/src/serdey.rs +++ b/src/serdey.rs @@ -11,51 +11,60 @@ //! ### Various and tooling related to serde #[cfg(feature = "serde")] -macro_rules! serde_boilerplate { ($t:ty) => { -impl serde_crate::Serialize for $t { - fn serialize(&self, serializer: S) -> Result where S: serde_crate::Serializer { - let bytes = &self.to_bytes()[..]; - serde_bytes::Bytes::new(bytes).serialize(serializer) - } -} - -impl<'d> serde_crate::Deserialize<'d> for $t { - fn deserialize(deserializer: D) -> Result where D: serde_crate::Deserializer<'d> { - cfg_if::cfg_if!{ - if #[cfg(feature = "std")] { - let bytes = >::deserialize(deserializer)?; - } else if #[cfg(feature = "alloc")] { - let bytes = >::deserialize(deserializer)?; - } else { - let bytes = <&::serde_bytes::Bytes>::deserialize(deserializer)?; +macro_rules! serde_boilerplate { + ($t:ty) => { + impl serde::Serialize for $t { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let bytes = &self.to_bytes()[..]; + serde_bytes::Bytes::new(bytes).serialize(serializer) } } - Self::from_bytes(bytes.as_ref()) - .map_err(crate::errors::serde_error_from_signature_error) - } -} -} } // macro_rules! serde_boilerplate + impl<'d> serde::Deserialize<'d> for $t { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'d>, + { + cfg_if::cfg_if! { + if #[cfg(feature = "std")] { + let bytes = >::deserialize(deserializer)?; + } else if #[cfg(feature = "alloc")] { + let bytes = >::deserialize(deserializer)?; + } else { + let bytes = <&::serde_bytes::Bytes>::deserialize(deserializer)?; + } + } + + Self::from_bytes(bytes.as_ref()) + .map_err(crate::errors::serde_error_from_signature_error) + } + } + }; +} // macro_rules! serde_boilerplate #[cfg(not(feature = "serde"))] -macro_rules! serde_boilerplate { ($t:ty) => { } } +macro_rules! serde_boilerplate { + ($t:ty) => {}; +} #[cfg(all(test, feature = "serde"))] mod test { use std::vec::Vec; - use bincode::{serialize, serialized_size, deserialize}; - use serde_json::{to_value, from_value, to_string, from_str}; + use bincode::{deserialize, serialize, serialized_size}; + use serde_json::{from_str, from_value, to_string, to_value}; - use curve25519_dalek::ristretto::{CompressedRistretto}; + use curve25519_dalek::ristretto::CompressedRistretto; use crate::*; - static COMPRESSED_PUBLIC_KEY : CompressedRistretto = CompressedRistretto([ - 208, 120, 140, 129, 177, 179, 237, 159, - 252, 160, 028, 013, 206, 005, 211, 241, - 192, 218, 001, 097, 130, 241, 020, 169, - 119, 046, 246, 029, 079, 080, 077, 084]); + static COMPRESSED_PUBLIC_KEY: CompressedRistretto = CompressedRistretto([ + 208, 120, 140, 129, 177, 179, 237, 159, 252, 160, 028, 013, 206, 005, 211, 241, 192, 218, + 001, 097, 130, 241, 020, 169, 119, 046, 246, 029, 079, 080, 077, 084, + ]); /* static ED25519_PUBLIC_KEY: CompressedEdwardsY = CompressedEdwardsY([ @@ -66,22 +75,17 @@ mod test { */ static ED25519_SECRET_KEY: MiniSecretKey = MiniSecretKey([ - 062, 070, 027, 163, 092, 182, 011, 003, - 077, 234, 098, 004, 011, 127, 079, 228, - 243, 187, 150, 073, 201, 137, 076, 022, - 085, 251, 152, 002, 241, 042, 072, 054, ]); + 062, 070, 027, 163, 092, 182, 011, 003, 077, 234, 098, 004, 011, 127, 079, 228, 243, 187, + 150, 073, 201, 137, 076, 022, 085, 251, 152, 002, 241, 042, 072, 054, + ]); /// Ed25519 signature with the above keypair of a blank message. static SIGNATURE_BYTES: [u8; SIGNATURE_LENGTH] = [ - 010, 126, 151, 143, 157, 064, 047, 001, - 196, 140, 179, 058, 226, 152, 018, 102, - 160, 123, 080, 016, 210, 086, 196, 028, - 053, 231, 012, 157, 169, 019, 158, 063, - 045, 154, 238, 007, 053, 185, 227, 229, - 079, 108, 213, 080, 124, 252, 084, 167, - 216, 085, 134, 144, 129, 149, 041, 081, - 063, 120, 126, 100, 092, 059, 050, 138, ]; - + 010, 126, 151, 143, 157, 064, 047, 001, 196, 140, 179, 058, 226, 152, 018, 102, 160, 123, + 080, 016, 210, 086, 196, 028, 053, 231, 012, 157, 169, 019, 158, 063, 045, 154, 238, 007, + 053, 185, 227, 229, 079, 108, 213, 080, 124, 252, 084, 167, 216, 085, 134, 144, 129, 149, + 041, 081, 063, 120, 126, 100, 092, 059, 050, 138, + ]; #[test] fn serialize_deserialize_signature() { @@ -172,21 +176,21 @@ mod test { #[test] fn serialize_public_key_size() { let public_key = PublicKey::from_compressed(COMPRESSED_PUBLIC_KEY).unwrap(); - assert_eq!(serialized_size(&public_key).unwrap(), 32+8); // Size specific to bincode==1.0.1 + assert_eq!(serialized_size(&public_key).unwrap(), 32 + 8); // Size specific to bincode==1.0.1 } #[test] fn serialize_signature_size() { let signature: Signature = Signature::from_bytes(&SIGNATURE_BYTES).unwrap(); - assert_eq!(serialized_size(&signature).unwrap(), 64+8); // Size specific to bincode==1.0.1 + assert_eq!(serialized_size(&signature).unwrap(), 64 + 8); // Size specific to bincode==1.0.1 } #[test] fn serialize_secret_key_size() { - assert_eq!(serialized_size(&ED25519_SECRET_KEY).unwrap(), 32+8); + assert_eq!(serialized_size(&ED25519_SECRET_KEY).unwrap(), 32 + 8); let secret_key = ED25519_SECRET_KEY.expand(ExpansionMode::Ed25519); - assert_eq!(serialized_size(&secret_key).unwrap(), 64+8); // Sizes specific to bincode==1.0.1 + assert_eq!(serialized_size(&secret_key).unwrap(), 64 + 8); // Sizes specific to bincode==1.0.1 let secret_key = ED25519_SECRET_KEY.expand(ExpansionMode::Uniform); - assert_eq!(serialized_size(&secret_key).unwrap(), 64+8); // Sizes specific to bincode==1.0.1 + assert_eq!(serialized_size(&secret_key).unwrap(), 64 + 8); // Sizes specific to bincode==1.0.1 } } diff --git a/src/simplpedpop.rs b/src/simplpedpop.rs new file mode 100644 index 0000000..f48ed9a --- /dev/null +++ b/src/simplpedpop.rs @@ -0,0 +1,1883 @@ +//! Implementation of a modified version of SimplPedPoP (), a DKG based on PedPoP, which in turn is based +//! on Pedersen's DKG. All of them have as the fundamental building block the Shamir's Secret Sharing scheme. +//! +//! The modification consists of each participant sending the secret shares of the other participants only in round 2 +//! instead of in round 1. The reason for this is we use the secret commitments (the evaluations of the secret polynomial +//! commitments at zero) of round 1 to assign the identifiers of all the participants in round 2, which will then be +//! used to compute the corresponding secret shares. Finally, we encrypt and authenticate the secret shares with +//! Chacha20Poly1305, meaning they can be distributed to the participants by an untrusted coordinator instead of sending +//! them directly. +//! +//! The protocol is divided into three rounds. In each round some data and some messages are produced and some messages +//! are verified (if received from a previous round). Data is divided into public and private because in a given round we +//! want to pass a reference to the public data (performance reasons) and the private data itself so that it is zeroized +//! after getting out of scope (security reasons). Public messages are destined to all the other participants, while private +//! messages are destined to a single participant. + +use super::{ + errors::{DKGError, DKGResult}, + identifier::Identifier, + polynomial::{Coefficient, CoefficientCommitment, Polynomial, PolynomialCommitment}, +}; +use crate::{aead::make_aead, context::SigningTranscript, PublicKey, SecretKey, Signature}; +use alloc::{collections::BTreeSet, vec::Vec}; +use chacha20poly1305::{aead::Aead, ChaCha20Poly1305, Nonce}; +use curve25519_dalek::{constants::RISTRETTO_BASEPOINT_POINT, RistrettoPoint, Scalar}; +use derive_getters::Getters; +use merlin::Transcript; +use rand_core::{CryptoRng, RngCore}; +use zeroize::ZeroizeOnDrop; + +pub(crate) const GENERATOR: RistrettoPoint = RISTRETTO_BASEPOINT_POINT; + +/// The group public key generated by the SimplPedPoP protocol. +pub type GroupPublicKey = PublicKey; +/// The group public key share of each participant in the SimplPedPoP protocol. +pub type GroupPublicKeyShare = CoefficientCommitment; +pub(crate) type SecretPolynomialCommitment = PolynomialCommitment; +pub(crate) type SecretPolynomial = Polynomial; +pub(crate) type TotalSecretShare = SecretShare; +pub(crate) type SecretCommitment = CoefficientCommitment; +pub(crate) type Certificate = Signature; +pub(crate) type ProofOfPossession = Signature; +pub(crate) type Secret = Coefficient; + +/// The parameters of a given execution of the SimplPedPoP protocol. +#[derive(Debug, Clone, Getters)] +pub struct Parameters { + pub(crate) participants: u16, + pub(crate) threshold: u16, +} + +impl Parameters { + /// Create new parameters. + pub fn new(participants: u16, threshold: u16) -> Parameters { + Parameters { + participants, + threshold, + } + } + + pub(crate) fn validate(&self) -> Result<(), DKGError> { + if self.threshold < 2 { + return Err(DKGError::InsufficientThreshold); + } + + if self.participants < 2 { + return Err(DKGError::InvalidNumberOfParticipants); + } + + if self.threshold > self.participants { + return Err(DKGError::ExcessiveThreshold); + } + + Ok(()) + } +} + +/// The participants of a given execution of the SimplPedPoP protocol. +#[derive(Debug, Clone, Getters)] +pub struct Identifiers { + pub(crate) own_identifier: Identifier, + pub(crate) others_identifiers: BTreeSet, +} + +impl Identifiers { + /// Create new participants. + pub fn new( + own_identifier: Identifier, + others_identifiers: BTreeSet, + ) -> Identifiers { + Identifiers { + own_identifier, + others_identifiers, + } + } + + pub(crate) fn validate(&self, participants: u16) -> Result<(), DKGError> { + if self.own_identifier.0 == Scalar::ZERO { + return Err(DKGError::InvalidIdentifier); + } + + for other_identifier in &self.others_identifiers { + if other_identifier.0 == Scalar::ZERO { + return Err(DKGError::InvalidIdentifier); + } + } + + if self.others_identifiers.len() != participants as usize - 1 { + return Err(DKGError::IncorrectNumberOfIdentifiers { + expected: participants as usize, + actual: self.others_identifiers.len() + 1, + }); + } + + Ok(()) + } +} + +fn derive_secret_key_from_secret(secret: &Secret, mut rng: R) -> SecretKey { + let mut bytes = [0u8; 64]; + let mut nonce: [u8; 32] = [0u8; 32]; + + rng.fill_bytes(&mut nonce); + let secret_bytes = secret.to_bytes(); + + bytes[..32].copy_from_slice(&secret_bytes[..]); + bytes[32..].copy_from_slice(&nonce[..]); + + SecretKey::from_bytes(&bytes[..]).unwrap() // This never fails because bytes has length 64 and the key is a scalar +} + +/// A secret share, which corresponds to an evaluation of a value that identifies a participant in a secret polynomial. +#[derive(Debug, Clone, ZeroizeOnDrop)] +pub struct SecretShare(pub(crate) Scalar); + +impl SecretShare { + pub(crate) fn encrypt( + &self, + decryption_key: &Scalar, + encryption_key: &RistrettoPoint, + context: &[u8], + ) -> EncryptedSecretShare { + let shared_secret = decryption_key * encryption_key; + + let mut transcript = Transcript::new(b"encryption"); + transcript.commit_point(b"shared secret", &shared_secret.compress()); + transcript.append_message(b"context", context); + + let mut bytes = [0; 12]; + transcript.challenge_bytes(b"nonce", &mut bytes); + + let cipher: ChaCha20Poly1305 = make_aead::(transcript); + let nonce = Nonce::from_slice(&bytes[..]); + + let ciphertext: Vec = cipher.encrypt(nonce, &self.0.as_bytes()[..]).unwrap(); + + EncryptedSecretShare(ciphertext) + } +} + +/// An encrypted secret share, which can be sent directly to the intended participant or through an untrusted coordinator. +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct EncryptedSecretShare(pub(crate) Vec); + +impl EncryptedSecretShare { + pub(crate) fn decrypt( + &self, + decryption_key: &Scalar, + encryption_key: &RistrettoPoint, + context: &[u8], + ) -> DKGResult { + let shared_secret = decryption_key * encryption_key; + + let mut transcript = Transcript::new(b"encryption"); + transcript.commit_point(b"shared secret", &shared_secret.compress()); + transcript.append_message(b"context", context); + + let mut bytes = [0; 12]; + transcript.challenge_bytes(b"nonce", &mut bytes); + + let cipher: ChaCha20Poly1305 = make_aead::(transcript); + let nonce = Nonce::from_slice(&bytes[..]); + + let plaintext = cipher + .decrypt(nonce, &self.0[..]) + .map_err(DKGError::DecryptionError)?; + + let mut bytes = [0; 32]; + bytes.copy_from_slice(&plaintext); + + Ok(SecretShare(Scalar::from_bytes_mod_order(bytes))) + } +} + +/// SimplPedPoP round 1. +/// +/// The participant commits to a secret polynomial f(x) of degree t-1, where t is the threshold of the DKG, by commiting +/// to each one of the t coefficients of the secret polynomial. +/// +/// It derives a secret key from the secret of the polynomial f(0) and uses it to generate a Proof of Possession of that +/// secret by signing a message with the secret key. +pub mod round1 { + use super::{ + derive_secret_key_from_secret, Parameters, ProofOfPossession, SecretPolynomial, + SecretPolynomialCommitment, + }; + use crate::{ + errors::DKGResult, + polynomial::{Polynomial, PolynomialCommitment}, + PublicKey, SecretKey, + }; + use core::cmp::Ordering; + use curve25519_dalek::Scalar; + use merlin::Transcript; + use rand_core::{CryptoRng, RngCore}; + + /// The private data generated by the participant in round 1. + #[derive(Debug, Clone)] + pub struct PrivateData { + pub(crate) secret_key: SecretKey, + pub(crate) secret_polynomial: SecretPolynomial, + } + + /// The public data generated by the participant in round 1. + #[derive(Debug, Clone)] + pub struct PublicData { + pub(crate) parameters: Parameters, + pub(crate) secret_polynomial_commitment: SecretPolynomialCommitment, + pub(crate) proof_of_possession: ProofOfPossession, + } + + /// Public message to be sent by the participant to all the other participants or to the coordinator in round 1. + #[derive(Debug, Clone, PartialEq, Eq)] + #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] + pub struct PublicMessage { + pub(crate) secret_polynomial_commitment: SecretPolynomialCommitment, + pub(crate) proof_of_possession: ProofOfPossession, + } + + impl PublicMessage { + /// Creates a new public message. + pub fn new(public_data: &PublicData) -> PublicMessage { + PublicMessage { + secret_polynomial_commitment: public_data.secret_polynomial_commitment.clone(), + proof_of_possession: public_data.proof_of_possession, + } + } + } + + impl PartialOrd for PublicMessage { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } + } + + impl Ord for PublicMessage { + fn cmp(&self, other: &Self) -> Ordering { + self.secret_polynomial_commitment + .coefficients_commitments + .first() + .unwrap() + .compress() + .0 + .cmp( + &other + .secret_polynomial_commitment + .coefficients_commitments + .first() + .unwrap() + .compress() + .0, + ) + } + } + + /// Runs the round 1 of the SimplPedPoP protocol. + pub fn run( + parameters: Parameters, + mut rng: R, + ) -> DKGResult<(PrivateData, PublicMessage, PublicData)> { + parameters.validate()?; + + let (private_data, public_data) = generate_data(parameters, &mut rng); + + let public_message = PublicMessage::new(&public_data); + + Ok((private_data, public_message, public_data)) + } + + fn generate_data( + parameters: Parameters, + mut rng: R, + ) -> (PrivateData, PublicData) { + let secret_polynomial = loop { + let temp_polynomial = Polynomial::generate(&mut rng, *parameters.threshold() - 1); + // There must be a secret, which is the constant coefficient of the secret polynomial + if temp_polynomial.coefficients.first().unwrap() != &Scalar::ZERO { + break temp_polynomial; + } + }; + + let secret_polynomial_commitment = PolynomialCommitment::commit(&secret_polynomial); + + // This secret key will be used to sign the proof of possession and the certificate + let secret_key = + derive_secret_key_from_secret(secret_polynomial.coefficients.first().unwrap(), rng); + + let public_key = PublicKey::from_point( + *secret_polynomial_commitment + .coefficients_commitments + .first() + .unwrap(), + ); + + let proof_of_possession = + secret_key.sign(Transcript::new(b"Proof of Possession"), &public_key); + + ( + PrivateData { + secret_key, + secret_polynomial, + }, + PublicData { + parameters, + secret_polynomial_commitment, + proof_of_possession, + }, + ) + } +} + +/// SimplPedPoP round 2. +/// +/// The participant verifies the received messages of the other participants from round 1, the secret polynomial commitments +/// and the Proofs of Possession. +/// +/// It orders the secret commitments and uses that ordering to assing random identifiers to all the participants in the +/// protocol, including its own. +/// +/// It computes the secret shares of each participant based on their identifiers, encrypts and authenticates them using +/// Chacha20Poly1305 with a shared secret. +/// +/// It signs a transcript of the protocol execution (certificate) with its secret key, which contains the PoPs and the +/// polynomial commitments from all the participants (including its own). +pub mod round2 { + use super::{ + round1, Certificate, EncryptedSecretShare, Identifier, Identifiers, Parameters, + SecretCommitment, SecretPolynomial, SecretShare, + }; + use crate::{ + context::SigningTranscript, + errors::{DKGError, DKGResult}, + verify_batch, PublicKey, SecretKey, + }; + use alloc::{ + collections::{BTreeMap, BTreeSet}, + string::ToString, + vec, + vec::Vec, + }; + use curve25519_dalek::{RistrettoPoint, Scalar}; + use derive_getters::Getters; + use merlin::Transcript; + use sha2::{digest::Update, Digest, Sha512}; + + /// The public data of round 2. + #[derive(Debug, Clone, Getters)] + pub struct PublicData { + pub(crate) identifiers: Identifiers, + pub(crate) round1_public_messages: BTreeMap, + pub(crate) transcript: T, + pub(crate) public_keys: Vec, + } + + /// The public message of round 2. + #[derive(Debug, Clone)] + #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] + pub struct PublicMessage { + pub(crate) certificate: Certificate, + } + + /// Private message to sent by a participant to another participant or to the coordinator in encrypted form in round 1. + #[derive(Debug, Clone)] + #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] + pub struct PrivateMessage { + pub(crate) encrypted_secret_share: EncryptedSecretShare, + } + + impl PrivateMessage { + /// Creates a new private message. + pub fn new( + secret_share: SecretShare, + deckey: Scalar, + enckey: RistrettoPoint, + context: &[u8], + ) -> PrivateMessage { + let encrypted_secret_share = secret_share.encrypt(&deckey, &enckey, context); + + PrivateMessage { + encrypted_secret_share, + } + } + } + + /// The messages to be sent by the participant in round 2. + #[derive(Debug, Clone, Getters)] + pub struct Messages { + // The identifier is the intended recipient of the private message. + private_messages: BTreeMap, + public_message: PublicMessage, + } + + fn validate_messages( + parameters: &Parameters, + round1_public_messages: &BTreeSet, + ) -> DKGResult<()> { + if round1_public_messages.len() != *parameters.participants() as usize - 1 { + return Err(DKGError::IncorrectNumberOfRound1PublicMessages { + expected: *parameters.participants() as usize - 1, + actual: round1_public_messages.len(), + }); + } + + for round1_public_message in round1_public_messages { + if round1_public_message + .secret_polynomial_commitment + .coefficients_commitments + .len() + != *parameters.threshold() as usize + { + return Err(DKGError::InvalidSecretPolynomialCommitment { + expected: *parameters.threshold() as usize, + actual: round1_public_message + .secret_polynomial_commitment + .coefficients_commitments + .len(), + }); + } + } + + Ok(()) + } + + /// Runs the round 2 of a SimplPedPoP protocol. + pub fn run( + round1_private_data: round1::PrivateData, + round1_public_data: &round1::PublicData, + round1_public_messages: BTreeSet, + transcript: T, + ) -> DKGResult<(PublicData, Messages)> { + round1_public_data.parameters.validate()?; + + validate_messages(&round1_public_data.parameters, &round1_public_messages)?; + + let public_keys = verify_round1_messages(&round1_public_messages)?; + + let public_data = generate_public_data( + round1_public_messages, + round1_public_data, + transcript, + public_keys, + ); + + let secret_commitment = round1_public_data + .secret_polynomial_commitment + .coefficients_commitments + .first() + .unwrap(); + + let messages = generate_messages( + &public_data, + &round1_private_data.secret_polynomial, + round1_private_data.secret_key, + secret_commitment, + ); + + Ok((public_data, messages)) + } + + fn generate_public_data( + round1_public_messages: BTreeSet, + round1_public_data: &round1::PublicData, + mut transcript: T, + public_keys: Vec, + ) -> PublicData { + let mut own_inserted = false; + + let own_first_coefficient_compressed = round1_public_data + .secret_polynomial_commitment + .coefficients_commitments + .first() + .unwrap() + .compress(); + + // Writes the data of all the participants in the transcript ordered by their identifiers + for message in &round1_public_messages { + let message_first_coefficient_compressed = message + .secret_polynomial_commitment + .coefficients_commitments + .first() + .unwrap() + .compress(); + + if own_first_coefficient_compressed.0 < message_first_coefficient_compressed.0 + && !own_inserted + { + // Writes own data in the transcript + transcript.commit_point(b"SecretCommitment", &own_first_coefficient_compressed); + + for coefficient_commitment in &round1_public_data + .secret_polynomial_commitment + .coefficients_commitments + { + transcript + .commit_point(b"CoefficientCommitment", &coefficient_commitment.compress()); + } + + transcript.commit_point( + b"ProofOfPossessionR", + &round1_public_data.proof_of_possession.R, + ); + + own_inserted = true; + } + // Writes the data of the other participants in the transcript + transcript.commit_point(b"SecretCommitment", &message_first_coefficient_compressed); + + for coefficient_commitment in &message + .secret_polynomial_commitment + .coefficients_commitments + { + transcript + .commit_point(b"CoefficientCommitment", &coefficient_commitment.compress()); + } + + transcript.commit_point(b"ProofOfPossessionR", &message.proof_of_possession.R); + } + + // Writes own data in the transcript if own identifier is the last one + if !own_inserted { + transcript.commit_point(b"SecretCommitment", &own_first_coefficient_compressed); + + for coefficient_commitment in &round1_public_data + .secret_polynomial_commitment + .coefficients_commitments + { + transcript + .commit_point(b"CoefficientCommitment", &coefficient_commitment.compress()); + } + + transcript.commit_point( + b"ProofOfPossessionR", + &round1_public_data.proof_of_possession.R, + ); + } + + // Scalar generated from transcript used to generate random identifiers to the participants + let scalar = transcript.challenge_scalar(b"participants"); + + let (identifiers, round1_public_messages) = + generate_identifiers(round1_public_data, round1_public_messages, &scalar); + + PublicData { + identifiers, + round1_public_messages, + transcript, + public_keys, + } + } + + fn generate_identifiers( + round1_public_data: &round1::PublicData, + round1_public_messages_set: BTreeSet, + scalar: &Scalar, + ) -> (Identifiers, BTreeMap) { + let mut others_identifiers: BTreeSet = BTreeSet::new(); + let mut round1_public_messages: BTreeMap = + BTreeMap::new(); + + let mut secret_commitments: BTreeSet<[u8; 32]> = round1_public_messages_set + .iter() + .map(|msg| { + msg.secret_polynomial_commitment + .coefficients_commitments + .first() + .unwrap() + .compress() + .0 + }) + .collect(); + + let own_secret_commitment = round1_public_data + .secret_polynomial_commitment + .coefficients_commitments + .first() + .unwrap(); + + secret_commitments.insert(own_secret_commitment.compress().0); + + let mut index = 0; + for message in &secret_commitments { + if message == &own_secret_commitment.compress().0 { + break; + } + index += 1; + } + + for i in 0..secret_commitments.len() { + let input = Sha512::new().chain(scalar.as_bytes()).chain(i.to_string()); + let random_scalar = Scalar::from_hash(input); + others_identifiers.insert(Identifier(random_scalar)); + } + + let own_identifier = *others_identifiers.iter().nth(index).unwrap(); + others_identifiers.remove(&own_identifier); + + for (id, message) in others_identifiers.iter().zip(round1_public_messages_set) { + round1_public_messages.insert(*id, message); + } + + let identifiers = Identifiers::new(own_identifier, others_identifiers); + + (identifiers, round1_public_messages) + } + + fn verify_round1_messages( + round1_public_messages: &BTreeSet, + ) -> DKGResult> { + let len = round1_public_messages.len(); + let mut public_keys = Vec::with_capacity(len); + let mut proofs_of_possession = Vec::with_capacity(len); + + // The public keys are the secret commitments of the participants + for round1_public_message in round1_public_messages { + let public_key = PublicKey::from_point( + *round1_public_message + .secret_polynomial_commitment + .coefficients_commitments + .first() + .unwrap(), + ); + public_keys.push(public_key); + proofs_of_possession.push(round1_public_message.proof_of_possession); + } + + verify_batch( + vec![Transcript::new(b"Proof of Possession"); len], + &proofs_of_possession[..], + &public_keys[..], + false, + ) + .map_err(DKGError::InvalidProofOfPossession)?; + + Ok(public_keys) + } + + fn generate_messages( + round2_public_data: &PublicData, + secret_polynomial: &SecretPolynomial, + secret_key: SecretKey, + secret_commitment: &SecretCommitment, + ) -> Messages { + let mut private_messages = BTreeMap::new(); + let enc_keys: Vec = round2_public_data + .round1_public_messages + .values() + .map(|msg| { + *msg.secret_polynomial_commitment + .coefficients_commitments + .first() + .unwrap() + }) + .collect(); + + for (i, identifier) in round2_public_data + .identifiers + .others_identifiers + .iter() + .enumerate() + { + let secret_share = secret_polynomial.evaluate(&identifier.0); + private_messages.insert( + *identifier, + PrivateMessage::new( + SecretShare(secret_share), + secret_key.key, + enc_keys[i], + identifier.0.as_bytes(), + ), + ); + } + + let public_key = PublicKey::from_point(*secret_commitment); + + let certificate = secret_key.sign(round2_public_data.transcript.clone(), &public_key); + + let public_message = PublicMessage { certificate }; + + Messages { + private_messages, + public_message, + } + } +} + +/// SimplPedPoP round 3. +/// +/// The participant decrypts and verifies the secret shares received from the other participants from round 2, computes +/// its own secret share and its own total secret share, which corresponds to its share of the group public key. +/// +/// It verifies the certificates from all the other participants and generates the shared public +/// key and the total secret shares commitments of the other partipants. +pub mod round3 { + use super::{ + round1, round2, Certificate, GroupPublicKey, GroupPublicKeyShare, Identifier, Identifiers, + Parameters, SecretPolynomial, SecretShare, TotalSecretShare, GENERATOR, + }; + use crate::{ + context::SigningTranscript, + errors::{DKGError, DKGResult}, + polynomial::PolynomialCommitment, + verify_batch, + }; + use alloc::{collections::BTreeMap, vec, vec::Vec}; + use curve25519_dalek::Scalar; + use derive_getters::Getters; + use zeroize::ZeroizeOnDrop; + + /// The private data of round 3. + #[derive(Debug, Clone, ZeroizeOnDrop, Getters)] + pub struct PrivateData { + pub(crate) total_secret_share: TotalSecretShare, + } + + fn validate_messages( + parameters: &Parameters, + round2_public_messages: &BTreeMap, + round1_public_messages: &BTreeMap, + round2_private_messages: &BTreeMap, + ) -> DKGResult<()> { + if round2_public_messages.len() != *parameters.participants() as usize - 1 { + return Err(DKGError::IncorrectNumberOfRound2PublicMessages { + expected: *parameters.participants() as usize - 1, + actual: round2_public_messages.len(), + }); + } + + if round1_public_messages.len() != *parameters.participants() as usize - 1 { + return Err(DKGError::IncorrectNumberOfRound1PublicMessages { + expected: *parameters.participants() as usize - 1, + actual: round1_public_messages.len(), + }); + } + + if round2_private_messages.len() != *parameters.participants() as usize - 1 { + return Err(DKGError::IncorrectNumberOfRound2PrivateMessages { + expected: *parameters.participants() as usize - 1, + actual: round2_private_messages.len(), + }); + } + + Ok(()) + } + + /// Runs the round 3 of the SimplPedPoP protocol. + pub fn run( + round2_public_messages: &BTreeMap, + round2_public_data: &round2::PublicData, + round1_public_data: &round1::PublicData, + round1_private_data: round1::PrivateData, + round2_private_messages: &BTreeMap, + ) -> DKGResult<( + GroupPublicKey, + BTreeMap, + PrivateData, + )> { + round1_public_data.parameters.validate()?; + + round2_public_data + .identifiers + .validate(round1_public_data.parameters.participants)?; + + validate_messages( + &round1_public_data.parameters, + round2_public_messages, + &round2_public_data.round1_public_messages, + round2_private_messages, + )?; + + verify_round2_public_messages(round2_public_messages, round2_public_data)?; + + let secret_shares = verify_round2_private_messages( + round2_public_data, + round2_private_messages, + &round1_private_data.secret_key.key, + )?; + + let private_data = generate_private_data( + &round2_public_data.identifiers, + &secret_shares, + &round1_private_data.secret_polynomial, + )?; + + let (group_public_key, group_public_key_shares) = + generate_public_data(round2_public_data, round1_public_data)?; + + Ok((group_public_key, group_public_key_shares, private_data)) + } + + fn verify_round2_public_messages( + round2_public_messages: &BTreeMap, + round2_public_data: &round2::PublicData, + ) -> DKGResult<()> { + verify_batch( + vec![ + round2_public_data.transcript.clone(); + round2_public_data.identifiers.others_identifiers.len() + ], + &round2_public_messages + .iter() + .map(|(id, msg)| { + if !round2_public_data + .identifiers + .others_identifiers() + .contains(id) + { + Err(DKGError::UnknownIdentifierRound2PublicMessages(*id)) + } else { + Ok(msg.certificate) + } + }) + .collect::, DKGError>>()?, + &round2_public_data.public_keys[..], + false, + ) + .map_err(DKGError::InvalidCertificate) + } + + fn verify_round2_private_messages( + round2_public_data: &round2::PublicData, + round2_private_messages: &BTreeMap, + secret: &Scalar, + ) -> DKGResult> { + let mut secret_shares = BTreeMap::new(); + + for (i, (identifier, private_message)) in round2_private_messages.iter().enumerate() { + if !round2_public_data + .identifiers + .others_identifiers + .contains(identifier) + { + return Err(DKGError::UnknownIdentifierRound2PrivateMessages( + *identifier, + )); + } + + let secret_share = private_message.encrypted_secret_share.decrypt( + secret, + &round2_public_data.public_keys[i].into_point(), + round2_public_data.identifiers.own_identifier.0.as_bytes(), + )?; + + let expected_evaluation = GENERATOR * secret_share.0; + + secret_shares.insert(*identifier, secret_share); + + let evaluation = round2_public_data + .round1_public_messages + .get(identifier) + .unwrap() + .secret_polynomial_commitment + .evaluate(&round2_public_data.identifiers.own_identifier.0); + + if !(evaluation == expected_evaluation) { + return Err(DKGError::InvalidSecretShare(*identifier)); + } + } + + Ok(secret_shares) + } + + fn generate_private_data( + identifiers: &Identifiers, + secret_shares: &BTreeMap, + secret_polynomial: &SecretPolynomial, + ) -> DKGResult { + let own_secret_share = secret_polynomial.evaluate(&identifiers.own_identifier.0); + + let mut total_secret_share = Scalar::ZERO; + + for id in &identifiers.others_identifiers { + // This never fails because we previously checked + total_secret_share += secret_shares.get(id).unwrap().0; + } + + total_secret_share += own_secret_share; + + let private_data = PrivateData { + total_secret_share: SecretShare(total_secret_share), + }; + + Ok(private_data) + } + + fn generate_public_data( + round2_public_data: &round2::PublicData, + round1_public_data: &round1::PublicData, + ) -> DKGResult<(GroupPublicKey, BTreeMap)> { + // Sum of the secret polynomial commitments of the other participants + let others_secret_polynomial_commitment = PolynomialCommitment::sum_polynomial_commitments( + &round2_public_data + .round1_public_messages + .values() + .map(|msg| &msg.secret_polynomial_commitment) + .collect::>(), + ); + + // The total secret polynomial commitment, which includes the secret polynomial commitment of the participant + let total_secret_polynomial_commitment = + PolynomialCommitment::sum_polynomial_commitments(&[ + &others_secret_polynomial_commitment, + &round1_public_data.secret_polynomial_commitment, + ]); + + // The group public key shares of all the participants, which correspond to the total secret shares commitments + let mut group_public_key_shares = BTreeMap::new(); + + for identifier in &round2_public_data.identifiers.others_identifiers { + let group_public_key_share = total_secret_polynomial_commitment.evaluate(&identifier.0); + + group_public_key_shares.insert(*identifier, group_public_key_share); + } + + let own_group_public_key_share = total_secret_polynomial_commitment + .evaluate(&round2_public_data.identifiers.own_identifier.0); + + group_public_key_shares.insert( + round2_public_data.identifiers.own_identifier, + own_group_public_key_share, + ); + + let shared_public_key = GroupPublicKey::from_point( + *total_secret_polynomial_commitment + .coefficients_commitments + .first() + .unwrap(), + ); + + // The shared public key corresponds to the secret commitment of the total secret polynomial commitment + if shared_public_key.as_point() + != &total_secret_polynomial_commitment.evaluate(&Scalar::ZERO) + { + return Err(DKGError::SharedPublicKeyMismatch); + } + + Ok((shared_public_key, group_public_key_shares)) + } +} + +#[cfg(test)] +mod tests { + use self::{ + round1::{PrivateData, PublicData}, + round2::Messages, + }; + use super::*; + use crate::{errors::DKGResult, SignatureError}; + #[cfg(feature = "alloc")] + use alloc::{ + collections::{BTreeMap, BTreeSet}, + vec::Vec, + }; + use merlin::Transcript; + use rand::{rngs::OsRng, Rng}; + use tests::round1::PublicMessage; + + const MAXIMUM_PARTICIPANTS: u16 = 10; + const MINIMUM_PARTICIPANTS: u16 = 3; + const MININUM_THRESHOLD: u16 = 2; + const PROTOCOL_RUNS: usize = 1; + + fn generate_parameters() -> Vec { + let mut rng = rand::thread_rng(); + let max_signers = rng.gen_range(MINIMUM_PARTICIPANTS..=MAXIMUM_PARTICIPANTS); + let min_signers = rng.gen_range(MININUM_THRESHOLD..=max_signers); + + (1..=max_signers) + .map(|_| Parameters::new(max_signers, min_signers)) + .collect() + } + + fn round1() -> ( + Vec, + Vec, + Vec, + Vec>, + ) { + let parameters_list = generate_parameters(); + + let mut all_public_messages_vec = Vec::new(); + let mut participants_round1_private_data = Vec::new(); + let mut participants_round1_public_data = Vec::new(); + + for i in 0..parameters_list.len() { + let (private_data, public_message, public_data) = + round1::run(parameters_list[i as usize].clone(), OsRng) + .expect("Round 1 should complete without errors!"); + + all_public_messages_vec.push(public_message.clone()); + participants_round1_public_data.push(public_data); + participants_round1_private_data.push(private_data); + } + + let mut received_round1_public_messages: Vec> = Vec::new(); + + let mut all_public_messages = BTreeSet::new(); + + for i in 0..parameters_list[0].participants { + all_public_messages.insert(all_public_messages_vec[i as usize].clone()); + } + + // Iterate through each participant to create a set of messages excluding their own. + for i in 0..parameters_list[0].participants { + let own_message = PublicMessage::new(&participants_round1_public_data[i as usize]); + + let mut messages_for_participant = BTreeSet::new(); + + for message in &all_public_messages { + if &own_message != message { + // Exclude the participant's own message. + messages_for_participant.insert(message.clone()); + } + } + + received_round1_public_messages.push(messages_for_participant); + } + + ( + parameters_list, + participants_round1_private_data, + participants_round1_public_data, + received_round1_public_messages, + ) + } + + fn round2( + parameters_list: &Vec, + participants_round1_private_data: Vec, + participants_round1_public_data: &Vec, + participants_round1_public_messages: &Vec>, + ) -> DKGResult<( + Vec>, + Vec, + Vec, + Vec, + )> { + let mut participants_round2_public_data = Vec::new(); + let mut participants_round2_public_messages = Vec::new(); + let mut participants_set_of_participants = Vec::new(); + let mut identifiers_vec = Vec::new(); + + for i in 0..parameters_list[0].participants { + let result = round2::run( + participants_round1_private_data[i as usize].clone(), + &participants_round1_public_data[i as usize].clone(), + participants_round1_public_messages[i as usize].clone(), + Transcript::new(b"simplpedpop"), + )?; + + participants_round2_public_data.push(result.0.clone()); + participants_round2_public_messages.push(result.1); + participants_set_of_participants.push(result.0.identifiers.clone()); + identifiers_vec.push(result.0.identifiers.own_identifier); + } + + Ok(( + participants_round2_public_data, + participants_round2_public_messages, + participants_set_of_participants, + identifiers_vec, + )) + } + + fn round3( + participants_sets_of_participants: &Vec, + participants_round2_public_messages: &Vec, + participants_round2_public_data: &Vec>, + participants_round1_public_data: &Vec, + participants_round1_private_data: Vec, + participants_round2_private_messages: Vec>, + identifiers_vec: &Vec, + ) -> DKGResult< + Vec<( + GroupPublicKey, + BTreeMap, + round3::PrivateData, + )>, + > { + let mut participant_data_round3 = Vec::new(); + + for i in 0..participants_sets_of_participants.len() { + let received_round2_public_messages = participants_round2_public_messages + .iter() + .enumerate() + .filter(|(index, _msg)| { + identifiers_vec[*index] + != participants_sets_of_participants[i as usize].own_identifier + }) + .map(|(index, msg)| (identifiers_vec[index], msg.clone())) + .collect::>(); + + let mut round2_private_messages: Vec> = + Vec::new(); + + for participants in participants_sets_of_participants.iter() { + let mut messages_for_participant = BTreeMap::new(); + + for (i, round_messages) in participants_round2_private_messages.iter().enumerate() { + if let Some(message) = round_messages.get(&participants.own_identifier) { + messages_for_participant.insert(identifiers_vec[i], message.clone()); + } + } + + round2_private_messages.push(messages_for_participant); + } + + let result = round3::run( + &received_round2_public_messages, + &participants_round2_public_data[i as usize], + &participants_round1_public_data[i as usize], + participants_round1_private_data[i as usize].clone(), + &round2_private_messages[i as usize], + )?; + + participant_data_round3.push(result); + } + + Ok(participant_data_round3) + } + + #[test] + pub fn test_successful_simplpedpop() { + for _ in 0..PROTOCOL_RUNS { + let ( + parameters_list, + participants_round1_private_data, + participants_round1_public_data, + participants_round1_public_messages, + ) = round1(); + + let ( + participants_round2_public_data, + participants_round2_messages, + participants_sets_of_participants, + identifiers_vec, + ) = round2( + ¶meters_list, + participants_round1_private_data.clone(), + &participants_round1_public_data, + &participants_round1_public_messages, + ) + .unwrap(); + + let participants_data_round3 = round3( + &participants_sets_of_participants, + &participants_round2_messages + .iter() + .map(|msg| msg.public_message().clone()) + .collect(), + &participants_round2_public_data, + &participants_round1_public_data, + participants_round1_private_data, + participants_round2_messages + .iter() + .map(|msg| msg.private_messages().clone()) + .collect(), + &identifiers_vec, + ) + .unwrap(); + + let shared_public_keys: Vec = participants_data_round3 + .iter() + .map(|state| state.0) + .collect(); + + assert!( + shared_public_keys.windows(2).all(|w| w[0] == w[1]), + "All participants must have the same group public key!" + ); + + for i in 0..parameters_list[0].participants { + assert_eq!( + participants_data_round3[i as usize] + .1 + .get(&participants_sets_of_participants[i as usize].own_identifier) + .unwrap() + .compress(), + (participants_data_round3[i as usize].2.total_secret_share.0 * GENERATOR) + .compress(), + "Verification of total secret shares failed!" + ); + } + } + } + + #[test] + fn test_incorrect_number_of_round1_public_messages_in_round2() { + let ( + parameters_list, + participants_round1_private_data, + participants_round1_public_data, + mut participants_round1_public_messages, + ) = round1(); + + participants_round1_public_messages[0].pop_last(); + + let result = round2( + ¶meters_list, + participants_round1_private_data.clone(), + &participants_round1_public_data, + &participants_round1_public_messages, + ); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => assert_eq!( + e, + DKGError::IncorrectNumberOfRound1PublicMessages { + expected: parameters_list[0].participants as usize - 1, + actual: parameters_list[0].participants as usize - 2, + }, + "Expected DKGError::IncorrectNumberOfRound1PublicMessages." + ), + } + } + + #[test] + fn test_invalid_secret_polynomial_commitment_in_round2() { + let ( + parameters_list, + participants_round1_private_data, + participants_round1_public_data, + mut participants_round1_public_messages, + ) = round1(); + + let mut new_message = participants_round1_public_messages[0] + .first() + .unwrap() + .clone(); + + new_message + .secret_polynomial_commitment + .coefficients_commitments + .pop(); + + participants_round1_public_messages[0].pop_first(); + participants_round1_public_messages[0].insert(new_message); + + let result = round2( + ¶meters_list, + participants_round1_private_data.clone(), + &participants_round1_public_data, + &participants_round1_public_messages, + ); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => assert_eq!( + e, + DKGError::InvalidSecretPolynomialCommitment { + expected: *parameters_list[0].threshold() as usize, + actual: *parameters_list[0].threshold() as usize - 1, + }, + "Expected DKGError::IncorrectNumberOfRound1PublicMessages." + ), + } + } + + #[test] + fn test_invalid_secret_share_error_in_round3() { + let ( + parameters_list, + participants_round1_private_data, + participants_round1_public_data, + participants_round1_public_messages, + ) = round1(); + + let ( + participants_round2_public_data, + participants_round2_messages, + participants_sets_of_participants, + identifiers_vec, + ) = round2( + ¶meters_list, + participants_round1_private_data.clone(), + &participants_round1_public_data, + &participants_round1_public_messages, + ) + .unwrap(); + + let mut participants_round2_private_messages: Vec< + BTreeMap, + > = participants_round2_messages + .iter() + .map(|msg| msg.private_messages().clone()) + .collect(); + + let enc_keys: Vec = participants_round1_public_messages[1] + .iter() + .map(|msg| { + *msg.secret_polynomial_commitment + .coefficients_commitments + .first() + .unwrap() + }) + .collect(); + + let secret_share = SecretShare(Scalar::random(&mut OsRng)); + + let identifiers: BTreeSet = participants_sets_of_participants[1] + .others_identifiers + .clone(); + + let index = identifiers + .iter() + .position(|x| x == &participants_sets_of_participants[0].own_identifier) + .unwrap(); + + let enc_share = secret_share.encrypt( + &participants_round1_private_data[1].secret_key.key, + &enc_keys[index], + participants_sets_of_participants[0] + .own_identifier + .0 + .as_bytes(), + ); + + let private_message = participants_round2_private_messages[1] + .get_mut(&participants_sets_of_participants[0].own_identifier) + .unwrap(); + + private_message.encrypted_secret_share = enc_share; + + let result = round3( + &participants_sets_of_participants, + &participants_round2_messages + .iter() + .map(|msg| msg.public_message().clone()) + .collect(), + &participants_round2_public_data, + &participants_round1_public_data, + participants_round1_private_data, + participants_round2_private_messages, + &identifiers_vec, + ); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => assert_eq!( + e, + DKGError::InvalidSecretShare(participants_sets_of_participants[1].own_identifier), + "Expected DKGError::InvalidSecretShare." + ), + } + } + + #[test] + fn test_decryption_error_in_round3() { + let ( + parameters_list, + participants_round1_private_data, + participants_round1_public_data, + participants_round1_public_messages, + ) = round1(); + + let ( + participants_round2_public_data, + participants_round2_messages, + participants_sets_of_participants, + identifiers_vec, + ) = round2( + ¶meters_list, + participants_round1_private_data.clone(), + &participants_round1_public_data, + &participants_round1_public_messages, + ) + .unwrap(); + + let mut participants_round2_private_messages: Vec< + BTreeMap, + > = participants_round2_messages + .iter() + .map(|msg| msg.private_messages().clone()) + .collect(); + + let private_message = participants_round2_private_messages[1] + .get_mut(&participants_sets_of_participants[0].own_identifier) + .unwrap(); + + private_message.encrypted_secret_share = EncryptedSecretShare(vec![1]); + + let result = round3( + &participants_sets_of_participants, + &participants_round2_messages + .iter() + .map(|msg| msg.public_message().clone()) + .collect(), + &participants_round2_public_data, + &participants_round1_public_data, + participants_round1_private_data, + participants_round2_private_messages, + &identifiers_vec, + ); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => assert_eq!( + e, + DKGError::DecryptionError(chacha20poly1305::Error), + "Expected DKGError::DecryptionError." + ), + } + } + + #[test] + fn test_invalid_proof_of_possession_in_round2() { + let ( + parameters_list, + participants_round1_private_data, + participants_round1_public_data, + mut participants_round1_public_messages, + ) = round1(); + + let sk = SecretKey::generate(); + let proof_of_possession = sk.sign( + Transcript::new(b"invalid proof of possession"), + &PublicKey::from(sk.clone()), + ); + let msg = PublicMessage { + secret_polynomial_commitment: PolynomialCommitment::commit(&Polynomial::generate( + &mut OsRng, + parameters_list[0].threshold - 1, + )), + proof_of_possession, + }; + participants_round1_public_messages[0].pop_last(); + participants_round1_public_messages[0].insert(msg); + + let result = round2( + ¶meters_list, + participants_round1_private_data.clone(), + &participants_round1_public_data, + &participants_round1_public_messages, + ); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => assert_eq!( + e, + DKGError::InvalidProofOfPossession(SignatureError::EquationFalse), + "Expected DKGError::InvalidProofOfPossession." + ), + } + } + + #[test] + pub fn test_invalid_certificate_in_round3() { + let ( + parameters_list, + participants_round1_private_data, + participants_round1_public_data, + participants_round1_public_messages, + ) = round1(); + + let ( + mut participants_round2_public_data, + participants_round2_messages, + participants_sets_of_participants, + identifiers_vec, + ) = round2( + ¶meters_list, + participants_round1_private_data.clone(), + &participants_round1_public_data, + &participants_round1_public_messages, + ) + .unwrap(); + + participants_round2_public_data[0].transcript = Transcript::new(b"invalid transcript"); + + let participants_round2_private_messages: Vec< + BTreeMap, + > = participants_round2_messages + .iter() + .map(|msg| msg.private_messages().clone()) + .collect(); + + let result = round3( + &participants_sets_of_participants, + &participants_round2_messages + .iter() + .map(|msg| msg.public_message().clone()) + .collect(), + &participants_round2_public_data, + &participants_round1_public_data, + participants_round1_private_data, + participants_round2_private_messages, + &identifiers_vec, + ); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => assert_eq!( + e, + DKGError::InvalidCertificate(SignatureError::EquationFalse), + "Expected DKGError::InvalidCertificate." + ), + } + } + + #[test] + pub fn test_incorrect_number_of_round2_public_messages_in_round3() { + let ( + parameters_list, + participants_round1_private_data, + participants_round1_public_data, + participants_round1_public_messages, + ) = round1(); + + let ( + participants_round2_public_data, + participants_round2_messages, + participants_sets_of_participants, + identifiers_vec, + ) = round2( + ¶meters_list, + participants_round1_private_data.clone(), + &participants_round1_public_data, + &participants_round1_public_messages, + ) + .unwrap(); + + let mut participants_round2_public_messages: Vec = + participants_round2_messages + .iter() + .map(|msg| msg.public_message().clone()) + .collect(); + + participants_round2_public_messages.pop(); + + let participants_round2_private_messages: Vec< + BTreeMap, + > = participants_round2_messages + .iter() + .map(|msg| msg.private_messages().clone()) + .collect(); + + let result = round3( + &participants_sets_of_participants, + &participants_round2_public_messages, + &participants_round2_public_data, + &participants_round1_public_data, + participants_round1_private_data, + participants_round2_private_messages, + &identifiers_vec, + ); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => assert_eq!( + e, + DKGError::IncorrectNumberOfRound2PublicMessages { + expected: *parameters_list[0].participants() as usize - 1, + actual: *parameters_list[0].participants() as usize - 2 + }, + "Expected DKGError::IncorrectNumberOfRound2PublicMessages." + ), + } + } + + #[test] + pub fn test_incorrect_number_of_round1_public_messages_in_round3() { + let ( + parameters_list, + participants_round1_private_data, + participants_round1_public_data, + participants_round1_public_messages, + ) = round1(); + + let ( + mut participants_round2_public_data, + participants_round2_messages, + participants_sets_of_participants, + identifiers_vec, + ) = round2( + ¶meters_list, + participants_round1_private_data.clone(), + &participants_round1_public_data, + &participants_round1_public_messages, + ) + .unwrap(); + + let participants_round2_public_messages: Vec = + participants_round2_messages + .iter() + .map(|msg| msg.public_message().clone()) + .collect(); + + participants_round2_public_data[0] + .round1_public_messages + .pop_first(); + + let participants_round2_private_messages: Vec< + BTreeMap, + > = participants_round2_messages + .iter() + .map(|msg| msg.private_messages().clone()) + .collect(); + + let result = round3( + &participants_sets_of_participants, + &participants_round2_public_messages, + &participants_round2_public_data, + &participants_round1_public_data, + participants_round1_private_data, + participants_round2_private_messages, + &identifiers_vec, + ); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => assert_eq!( + e, + DKGError::IncorrectNumberOfRound1PublicMessages { + expected: *parameters_list[0].participants() as usize - 1, + actual: *parameters_list[0].participants() as usize - 2 + }, + "Expected DKGError::IncorrectNumberOfRound1PublicMessages." + ), + } + } + + #[test] + pub fn test_incorrect_number_of_round2_private_messages_in_round3() { + let ( + parameters_list, + participants_round1_private_data, + participants_round1_public_data, + participants_round1_public_messages, + ) = round1(); + + let ( + participants_round2_public_data, + participants_round2_messages, + participants_sets_of_participants, + identifiers_vec, + ) = round2( + ¶meters_list, + participants_round1_private_data.clone(), + &participants_round1_public_data, + &participants_round1_public_messages, + ) + .unwrap(); + + let participants_round2_public_messages: Vec = + participants_round2_messages + .iter() + .map(|msg| msg.public_message().clone()) + .collect(); + + let mut participants_round2_private_messages: Vec< + BTreeMap, + > = participants_round2_messages + .iter() + .map(|msg| msg.private_messages().clone()) + .collect(); + + participants_round2_private_messages[1].pop_last(); + + let result = round3( + &participants_sets_of_participants, + &participants_round2_public_messages, + &participants_round2_public_data, + &participants_round1_public_data, + participants_round1_private_data, + participants_round2_private_messages, + &identifiers_vec, + ); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => assert_eq!( + e, + DKGError::IncorrectNumberOfRound2PrivateMessages { + expected: *parameters_list[0].participants() as usize - 1, + actual: *parameters_list[0].participants() as usize - 2 + }, + "Expected DKGError::IncorrectNumberOfRound2PrivateMessages." + ), + } + } + + #[test] + pub fn test_unknown_identifier_from_round2_public_messages_in_round3() { + let ( + parameters_list, + participants_round1_private_data, + participants_round1_public_data, + participants_round1_public_messages, + ) = round1(); + + let ( + participants_round2_public_data, + participants_round2_messages, + participants_sets_of_participants, + mut identifiers_vec, + ) = round2( + ¶meters_list, + participants_round1_private_data.clone(), + &participants_round1_public_data, + &participants_round1_public_messages, + ) + .unwrap(); + + let participants_round2_public_messages: Vec = + participants_round2_messages + .iter() + .map(|msg| msg.public_message().clone()) + .collect(); + + identifiers_vec.pop(); + let unknown_identifier = Identifier(Scalar::random(&mut OsRng)); + identifiers_vec.push(unknown_identifier); + + let participants_round2_private_messages: Vec< + BTreeMap, + > = participants_round2_messages + .iter() + .map(|msg| msg.private_messages().clone()) + .collect(); + + let result = round3( + &participants_sets_of_participants, + &participants_round2_public_messages, + &participants_round2_public_data, + &participants_round1_public_data, + participants_round1_private_data, + participants_round2_private_messages, + &identifiers_vec, + ); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => assert_eq!( + e, + DKGError::UnknownIdentifierRound2PublicMessages(unknown_identifier), + "Expected DKGError::UnknownIdentifierRound2PublicMessages." + ), + } + } + + #[test] + fn test_unknown_identifier_from_round2_private_messages_in_round3() { + let ( + parameters_list, + participants_round1_private_data, + participants_round1_public_data, + participants_round1_public_messages, + ) = round1(); + + let ( + participants_round2_public_data, + participants_round2_messages, + participants_sets_of_participants, + identifiers_vec, + ) = round2( + ¶meters_list, + participants_round1_private_data.clone(), + &participants_round1_public_data, + &participants_round1_public_messages, + ) + .unwrap(); + + let participants_round2_public_messages: Vec = + participants_round2_messages + .iter() + .map(|msg| msg.public_message().clone()) + .collect(); + + let mut identifiers_vec2 = identifiers_vec.clone(); + identifiers_vec2.pop(); + let unknown_identifier = Identifier(Scalar::random(&mut OsRng)); + identifiers_vec2.push(unknown_identifier); + + let participants_round2_private_messages: Vec< + BTreeMap, + > = participants_round2_messages + .iter() + .map(|msg| msg.private_messages().clone()) + .collect(); + + let received_round2_public_messages = participants_round2_public_messages + .iter() + .enumerate() + .filter(|(index, _msg)| { + identifiers_vec[*index] != participants_sets_of_participants[0].own_identifier + }) + .map(|(index, msg)| (identifiers_vec[index], msg.clone())) + .collect::>(); + + let mut round2_private_messages: Vec> = + Vec::new(); + + for participants in participants_sets_of_participants.iter() { + let mut messages_for_participant = BTreeMap::new(); + + for (i, round_messages) in participants_round2_private_messages.iter().enumerate() { + if let Some(message) = round_messages.get(&participants.own_identifier) { + messages_for_participant.insert(identifiers_vec2[i], message.clone()); + } + } + + round2_private_messages.push(messages_for_participant); + } + + let result = round3::run( + &received_round2_public_messages, + &participants_round2_public_data[0], + &participants_round1_public_data[0], + participants_round1_private_data[0].clone(), + &round2_private_messages[0], + ); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => assert_eq!( + e, + DKGError::UnknownIdentifierRound2PrivateMessages(unknown_identifier), + "Expected DKGError::UnknownIdentifierRound2PrivateMessages." + ), + } + } + + #[test] + fn test_invalid_threshold() { + let parameters = Parameters::new(3, 1); + assert_eq!(parameters.validate(), Err(DKGError::InsufficientThreshold)); + } + + #[test] + fn test_invalid_participants() { + let parameters = Parameters::new(1, 2); + assert_eq!( + parameters.validate(), + Err(DKGError::InvalidNumberOfParticipants) + ); + } + + #[test] + fn test_threshold_greater_than_participants() { + let parameters = Parameters::new(2, 3); + assert_eq!(parameters.validate(), Err(DKGError::ExcessiveThreshold)); + } + + #[test] + fn test_encryption_decryption() { + let mut rng = OsRng; + let deckey = Scalar::random(&mut rng); + let enckey = RistrettoPoint::random(&mut rng); + let context = b"context"; + + let original_share = SecretShare(Scalar::random(&mut rng)); + + let encrypted_share = original_share.encrypt(&deckey, &enckey, context); + let decrypted_share = encrypted_share.decrypt(&deckey, &enckey, context); + + assert_eq!( + original_share.0, + decrypted_share.unwrap().0, + "Decryption must return the original share!" + ); + } +}