diff --git a/Cargo.toml b/Cargo.toml index 9943958..592d627 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,17 +22,22 @@ curve25519-dalek = { version = "4.1.0", default-features = false, features = [ "zeroize", "precomputed-tables", "legacy_compatibility", + "rand_core", + "serde", ] } subtle = { version = "2.4.1", default-features = false } merlin = { version = "3.0.0", default-features = false } getrandom_or_panic = { version = "0.0.3", default-features = false } rand_core = { version = "0.6.2", default-features = false } -serde_crate = { version = "1.0.130", package = "serde", default-features = false, optional = true } +serde = { version = "1.0.130", default-features = false, optional = true } serde_bytes = { version = "0.11.5", default-features = false, optional = true } cfg-if = { version = "1.0.0", optional = true } sha2 = { version = "0.10.7", default-features = false } failure = { version = "0.1.8", default-features = false, optional = true } zeroize = { version = "1.6", default-features = false, features = ["zeroize_derive"] } +derive-getters = "0.3.0" +chacha20poly1305 = { version = "0.10.1", default-features = false } +hex = { version = "0.4", default-features = true, optional = true } [dev-dependencies] rand = "0.8.5" @@ -47,17 +52,38 @@ serde_json = "1.0.68" name = "schnorr_benchmarks" harness = false +[[bench]] +name = "olaf_benchmarks" +required-features = ["alloc", "aead"] + [features] default = ["std", "getrandom"] preaudit_deprecated = [] nightly = [] -alloc = ["curve25519-dalek/alloc", "rand_core/alloc", "getrandom_or_panic/alloc", "serde_bytes/alloc"] -std = ["alloc", "getrandom", "serde_bytes/std", "rand_core/std", "getrandom_or_panic/std"] +alloc = [ + "curve25519-dalek/alloc", + "rand_core/alloc", + "getrandom_or_panic/alloc", + "serde_bytes?/alloc", +] +std = [ + "alloc", + "getrandom", + "serde_bytes?/std", + "rand_core/std", + "getrandom_or_panic/std", + "chacha20poly1305/std", + "hex/std", +] asm = ["sha2/asm"] -serde = ["serde_crate", "serde_bytes", "cfg-if"] +serde = ["dep:serde", "serde_bytes", "cfg-if"] # We cannot make getrandom a direct dependency because rand_core makes # getrandom a feature name, which requires forwarding. -getrandom = ["rand_core/getrandom", "getrandom_or_panic/getrandom", "aead?/getrandom"] +getrandom = [ + "rand_core/getrandom", + "getrandom_or_panic/getrandom", + "aead?/getrandom", +] # We thus cannot forward the wasm-bindgen feature of getrandom, # but our consumers could depend upon getrandom and activate its # wasm-bindgen feature themselve, which works due to cargo features @@ -66,3 +92,4 @@ getrandom = ["rand_core/getrandom", "getrandom_or_panic/getrandom", "aead?/getra # See https://github.com/rust-lang/cargo/issues/9210 # and https://github.com/w3f/schnorrkel/issues/65#issuecomment-786923588 aead = ["dep:aead"] +cheater-detection = [] diff --git a/benches/olaf_benchmarks.rs b/benches/olaf_benchmarks.rs new file mode 100644 index 0000000..fd1032f --- /dev/null +++ b/benches/olaf_benchmarks.rs @@ -0,0 +1,293 @@ +use criterion::{criterion_group, criterion_main, Criterion}; + +mod olaf_benches { + use super::*; + use criterion::BenchmarkId; + use merlin::Transcript; + use rand_core::OsRng; + use schnorrkel::olaf::{ + errors::DKGResult, + identifier::Identifier, + keys::{GroupPublicKey, GroupPublicKeyShare}, + simplpedpop::{ + round1::{self, PrivateData, PublicData, PublicMessage}, + round2, + round2::Messages, + round3, Identifiers, Parameters, + }, + }; + use std::collections::{BTreeMap, BTreeSet}; + + fn generate_parameters(max_signers: u16, min_signers: u16) -> 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 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..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..participants as usize { + let own_message = PublicMessage::new(&participants_round1_public_data[i]); + + 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>, + ) -> ( + 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 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) + } + + 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 = olaf_benches; + config = Criterion::default(); + targets = + benchmark_simplpedpop, + } +} + +criterion_main!(olaf_benches::olaf_benches); diff --git a/src/aead.rs b/src/aead.rs index d47cf6f..6484028 100644 --- a/src/aead.rs +++ b/src/aead.rs @@ -41,7 +41,7 @@ use crate::context::SigningTranscript; use crate::cert::AdaptorCertPublic; -fn make_aead(mut t: T) -> AEAD +pub(crate) fn make_aead(mut t: T) -> AEAD where T: SigningTranscript, AEAD: KeyInit, diff --git a/src/errors.rs b/src/errors.rs index 3344b86..761e42d 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -150,7 +150,7 @@ impl failure::Fail for SignatureError {} #[cfg(feature = "serde")] pub fn serde_error_from_signature_error(err: SignatureError) -> E where - E: serde_crate::de::Error, + E: serde::de::Error, { E::custom(err) } diff --git a/src/lib.rs b/src/lib.rs index a7e373b..f3ba529 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -248,11 +248,14 @@ pub mod derive; pub mod cert; pub mod errors; +#[cfg(feature = "alloc")] +mod batch; + #[cfg(all(feature = "aead", feature = "getrandom"))] pub mod aead; -#[cfg(feature = "alloc")] -mod batch; +#[cfg(all(feature = "alloc", feature = "aead"))] +pub mod olaf; // Not safe because need randomness diff --git a/src/olaf/errors.rs b/src/olaf/errors.rs new file mode 100644 index 0000000..301be35 --- /dev/null +++ b/src/olaf/errors.rs @@ -0,0 +1,80 @@ +//! Errors of the Olaf protocol. + +use super::identifier::Identifier; +use crate::SignatureError; + +/// 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 1 public messages. + UnknownIdentifierRound1PublicMessages(Identifier), + /// Unknown identifier in round 2 public messages. + UnknownIdentifierRound2PublicMessages(Identifier), + /// Unknown identifier in round 2 private messages. + UnknownIdentifierRound2PrivateMessages, + /// 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), + /// Encryption error when encrypting the secret share. + EncryptionError(chacha20poly1305::Error), + /// Incorrect number of coefficient commitments. + InvalidSecretPolynomialCommitment { + /// The expected value. + expected: usize, + /// The actual value. + actual: usize, + }, +} diff --git a/src/olaf/identifier.rs b/src/olaf/identifier.rs new file mode 100644 index 0000000..4f3d5bb --- /dev/null +++ b/src/olaf/identifier.rs @@ -0,0 +1,176 @@ +//! The identifier of a participant in the Olaf protocol. + +use super::errors::DKGError; +use core::cmp::Ordering; +#[cfg(feature = "serde")] +use core::fmt; +use curve25519_dalek::Scalar; +#[cfg(feature = "serde")] +use serde::de::{self, Visitor}; +#[cfg(feature = "serde")] +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + +/// The identifier is represented by a Scalar. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct Identifier(pub(crate) Scalar); + +#[cfg(feature = "serde")] +impl Serialize for Identifier { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let scalar_bytes = self.0.to_bytes(); + let scalar_hex = hex::encode(scalar_bytes); + serializer.serialize_str(&scalar_hex) + } +} + +#[cfg(feature = "serde")] +struct IdentifierVisitor; + +#[cfg(feature = "serde")] +impl<'de> Visitor<'de> for IdentifierVisitor { + type Value = Identifier; + + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str("a hexadecimal string representing a Scalar") + } + + fn visit_str(self, value: &str) -> Result + where + E: de::Error, + { + let bytes = match hex::decode(value) { + Ok(b) => b, + Err(_) => return Err(E::custom("Invalid hexadecimal string")), + }; + if bytes.len() != 32 { + return Err(E::custom( + "Hexadecimal string must be exactly 32 bytes long", + )); + } + let mut bytes_array = [0u8; 32]; + bytes_array.copy_from_slice(&bytes); + + let scalar = Scalar::from_canonical_bytes(bytes_array); + if scalar.is_some().unwrap_u8() == 1 { + Ok(Identifier(scalar.unwrap())) + } else { + Err(E::custom("Invalid bytes for a Scalar")) + } + } +} + +#[cfg(feature = "serde")] +impl<'de> Deserialize<'de> for Identifier { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_str(IdentifierVisitor) + } +} + +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)) + } +} + +#[cfg(test)] +#[cfg(feature = "serde")] +mod tests { + use super::*; + use rand_core::OsRng; + + #[test] + fn test_serialize_deserialize_random_identifier() { + // Create a random Identifier + let random_scalar = Scalar::random(&mut OsRng); + let identifier = Identifier(random_scalar); + + // Serialize the Identifier + let serialized = serde_json::to_string(&identifier).expect("Failed to serialize"); + + // Deserialize the serialized string back into an Identifier + let deserialized: Result = serde_json::from_str(&serialized); + + // Check if the deserialized Identifier matches the original + assert!(deserialized.is_ok()); + assert_eq!(identifier, deserialized.unwrap()); + } + + #[test] + fn test_deserialize_invalid_hex_identifier() { + // Hexadecimal string with invalid characters (not valid hex) + let invalid_hex_scalar = + "\"g1c4c8a8ff4d21243af23e5ef23fea223b7cdde1baf31e56af77f872a8cc8402\""; + let result: Result = serde_json::from_str(invalid_hex_scalar); + + // Assert that the deserialization fails + assert!( + result.is_err(), + "Deserialization should fail for invalid hex characters" + ); + } + + #[test] + fn test_deserialize_invalid_length_identifier() { + // Incorrect length hexadecimal string (not 64 characters long) + let invalid_length_hex = "\"1c4c8a8ff4d21243af23e5ef23fea223b7cd\""; + let result: Result = serde_json::from_str(invalid_length_hex); + + // Assert that the deserialization fails due to length mismatch + assert!( + result.is_err(), + "Deserialization should fail for incorrect hex length" + ); + } +} diff --git a/src/olaf/keys.rs b/src/olaf/keys.rs new file mode 100644 index 0000000..f00ccd9 --- /dev/null +++ b/src/olaf/keys.rs @@ -0,0 +1,9 @@ +//! Olaf keys and key shares. + +use super::polynomial::CoefficientCommitment; +use crate::PublicKey; + +/// 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; diff --git a/src/olaf/mod.rs b/src/olaf/mod.rs new file mode 100644 index 0000000..d915964 --- /dev/null +++ b/src/olaf/mod.rs @@ -0,0 +1,9 @@ +//! Implementation of the Olaf protocol (), which is composed of the Distributed +//! Key Generation (DKG) protocol SimplPedPoP and the Threshold Signing protocol FROST. + +pub mod errors; +pub mod identifier; +pub mod keys; +mod polynomial; +pub mod simplpedpop; +mod tests; diff --git a/src/olaf/polynomial.rs b/src/olaf/polynomial.rs new file mode 100644 index 0000000..6b78fa3 --- /dev/null +++ b/src/olaf/polynomial.rs @@ -0,0 +1,212 @@ +//! Implementation of a polynomial and related operations. + +use crate::olaf::simplpedpop::GENERATOR; +use alloc::vec; +use alloc::vec::Vec; +use curve25519_dalek::{traits::Identity, RistrettoPoint, Scalar}; +use rand_core::{CryptoRng, RngCore}; +use zeroize::ZeroizeOnDrop; + +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)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +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::{ + olaf::polynomial::{Coefficient, Polynomial, PolynomialCommitment}, + olaf::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/olaf/simplpedpop.rs b/src/olaf/simplpedpop.rs new file mode 100644 index 0000000..9cc4e1f --- /dev/null +++ b/src/olaf/simplpedpop.rs @@ -0,0 +1,976 @@ +//! 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 crate::{aead::make_aead, context::SigningTranscript, 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; + +use super::{ + errors::{DKGError, DKGResult}, + identifier::Identifier, + polynomial::{Coefficient, CoefficientCommitment, Polynomial, PolynomialCommitment}, +}; + +pub(crate) const GENERATOR: RistrettoPoint = RISTRETTO_BASEPOINT_POINT; + +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)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +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)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +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[..]) + .expect("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)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct SecretShare(pub(crate) Scalar); + +impl SecretShare { + pub(crate) fn encrypt( + &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 ciphertext: Vec = cipher + .encrypt(nonce, &self.0.as_bytes()[..]) + .map_err(DKGError::EncryptionError)?; + + Ok(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::{ + olaf::errors::DKGResult, + olaf::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)] + #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] + 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)] + #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] + 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() + .expect("This never fails because the minimum threshold of the protocol is 2") + .compress() + .0 + .cmp( + &other + .secret_polynomial_commitment + .coefficients_commitments + .first() + .expect( + "This never fails because the minimum threshold of the protocol is 2", + ) + .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() + .expect("This never fails because the minimum threshold of the protocol is 2") + != &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() + .expect("This never fails because the minimum threshold of the protocol is 2"), + rng, + ); + + let public_key = PublicKey::from_point( + *secret_polynomial_commitment + .coefficients_commitments + .first() + .expect("This never fails because the minimum threshold of the protocol is 2"), + ); + + 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, + olaf::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)] + #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] + pub struct PublicData { + pub(crate) identifiers: Identifiers, + pub(crate) round1_public_messages: BTreeMap, + pub(crate) transcript: Scalar, + 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], + ) -> DKGResult { + let encrypted_secret_share = secret_share.encrypt(&deckey, &enckey, context)?; + + Ok(PrivateMessage { + encrypted_secret_share, + }) + } + } + + /// The messages to be sent by the participant in round 2. + #[derive(Debug, Clone, Getters)] + #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] + 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() + .expect("This never fails because the minimum threshold of the protocol is 2"); + + 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() + .expect("This never fails because the minimum threshold of the protocol is 2") + .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() + .expect("This never fails because the minimum threshold of the protocol is 2") + .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: scalar, + 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() + .expect("This never fails because the minimum threshold of the protocol is 2") + .compress() + .0 + }) + .collect(); + + let own_secret_commitment = round1_public_data + .secret_polynomial_commitment + .coefficients_commitments + .first() + .expect("This never fails because the minimum threshold of the protocol is 2"); + + 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) + .expect("This never fails because the index < len"); + 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() + .expect("This never fails because the minimum threshold of the protocol is 2"), + ); + 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, + ) -> DKGResult { + 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() + .expect("This never fails because the minimum threshold of the protocol is 2") + }) + .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 mut transcript = Transcript::new(b"certificate"); + transcript.append_message(b"scalar", round2_public_data.transcript.as_bytes()); + + let certificate = secret_key.sign(transcript, &public_key); + + let public_message = PublicMessage { certificate }; + + Ok(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, Identifier, Identifiers, Parameters, SecretPolynomial, + SecretShare, TotalSecretShare, GENERATOR, + }; + use crate::{ + context::SigningTranscript, + olaf::{ + errors::{DKGError, DKGResult}, + keys::{GroupPublicKey, GroupPublicKeyShare}, + polynomial::PolynomialCommitment, + }, + verify_batch, + }; + use alloc::{collections::BTreeMap, vec, vec::Vec}; + use curve25519_dalek::Scalar; + use derive_getters::Getters; + use merlin::Transcript; + use zeroize::ZeroizeOnDrop; + + /// The private data of round 3. + #[derive(Debug, Clone, ZeroizeOnDrop, Getters)] + #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] + 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, + )?; + + let mut transcript = Transcript::new(b"certificate"); + transcript.append_message(b"scalar", round2_public_data.transcript.as_bytes()); + + verify_round2_public_messages(round2_public_messages, round2_public_data, transcript)?; + + 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, &private_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, + transcript: T, + ) -> DKGResult<()> { + verify_batch( + vec![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() { + 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) + .ok_or(DKGError::UnknownIdentifierRound1PublicMessages(*identifier))? + .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 { + total_secret_share += secret_shares + .get(id) + .ok_or(DKGError::UnknownIdentifierRound2PrivateMessages)? + .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, + round2_private_data: &PrivateData, + ) -> 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 = round2_private_data.total_secret_share.0 * GENERATOR; + + 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() + .expect("This never fails because the minimum threshold of the protocol is 2"), + ); + + Ok((shared_public_key, group_public_key_shares)) + } +} diff --git a/src/olaf/spp.rs b/src/olaf/spp.rs new file mode 100644 index 0000000..a4c4844 --- /dev/null +++ b/src/olaf/spp.rs @@ -0,0 +1,128 @@ + +// It's irrelevant if we're in the recipiants or not ??? + + +type SPPResult = Result; + +pub struct Parameters { + pub threshold: u16, + pub participants: u16, +} + +impl Parameters { + pub fn check(&self) -> SPPResult<()> { .. } + + pub fn commit(&self, t: &mut T) { + t.commit_bytes(b"threshold", &self.threshold.to_le_bytes()); + t.commit_bytes(b"participants", &self.participants.to_le_bytes()); + } +} + +/// Compute identifier aka evaluation position scalar from recipiants_hash +/// and recipiant index. +/// +/// We'd ideally hash the recipiant's PublicKey here, instead, except +pub fn identifier(recipiants_hash: &[u8; 16], index: u16) -> Scalar { + let mut pos = merlin::Transcript::new(b"Identifier"); + pos.commit_bytes(b"RecipiantsHash", &recipiants_hash); + pos.commit_bytes(b"i", index.to_le_bytes()); + // let i = usize::from(i); + // e.commit_point(b"recipiant", recipiants[i].as_compressed()); + pos.challenge_scalar(b"evaluation position"); +} + +impl Keypair { + pub fn simplpedpop_contribute_all( + &self, + threshold: u16, + mut recipiants: Vec, + ) -> SPPResult + { + let parameters = Parameters { threshold, participants: recipiants.len() }; + parameters.check() ?; + + // We do not recipiants.sort() because the protocol is simpler + // if we require that all contributions provide the list in + // exactly the same order. + // + // Instead we create a kind of session id by hashing the list + // provided, but we provide only hash to recipiants, not the + // full recipiants list. + let mut t = merlin::Transcript::new(b"RecipiantsHash"); + parameters.commit(&mut t); + for r in recipiants.iter() { + t.commit_point(b"recipiant", r.as_compressed()); + } + let mut recipiants_hash = [0u8; 16]; + t.challenge_bytes(b"finalize", &mut recipiants_hash); + + // uses identifier(recipiants_hash, i) + let point_polynomial = ... + let scalar_evaluations = ... + + // All this custom encrhyption mess saves 32 bytes per recipiant + // over chacha20poly1305, so maybe not worth the trouble. + + let mut enc0 = merlin::Transcript::new(b"Encryption"); + parameters.commit(&mut enc0); + enc0.commit_point(b"contributor", self.public.as_compressed()); + + encrypton_nonce = [0u8; 16]; + super::getrandom_or_panic().fill_bytes(&mut encrypton_nonce); + enc0.commit_bytes(b"nonce", &encrypton_nonce); + + let mut ciphertexts = scalar_evaluations; + for i in [0..parameters.participants] { + let mut e = enc0.clone(); + // We tweak by i too since encrypton_nonce is not truly a nonce. + e.commit_bytes(b"i", &i.to_le_bytes()); + let i = usize::from(i); + + e.commit_point(b"recipiant", recipiants[i].as_compressed()); + keypair.secret.commit_raw_key_exchange(&mut e, b"kex", r); + + // Afaik redundant for merlin, but attacks get better. + e.commit_bytes(b"nonce", &encrypton_nonce); + + // As this is encryption, we require similar security properties + // as from witness_bytes here, but without randomness, and + // challenge_scalar is imeplemented close enough. + ciphertexts[i] += e.challenge_scalar(b"encryption scalar"); + } + + let sender = self.public.to_bytesd(); + Ok(Message { sender, encrypton_nonce, parameters, recipiants_hash, point_polynomial, ciphertexts, }) + } +} + +/// AllMessage packs together messages for all participants. +/// +/// We'd save bandwidth by having seperate messages for each +/// participant, but typical thresholds lie between 1/2 and 2/3, +/// so this doubles or tripples bandwidth usage. +pub struct AllMessage { + sender: PublicKey, + encrypton_nonce: [u8; 16], + parameters: Parameters, + recipiants_hash: [u8; 16], + point_polynomial: Vec, + ciphertexts: Vec, + signature: Signature, +} + +impl AllMessage { + pub fn to_bytes(self) -> Vec { ... } + pub fn from_bytes(&[u8]) -> SPPResult { ... } +} + +impl Keypair { + pub fn simplpedpop_recipiant_all( + &self, + index: u16, + messages: &[AllMessage], + ) -> >??? + { + ; + } +) + diff --git a/src/olaf/tests.rs b/src/olaf/tests.rs new file mode 100644 index 0000000..a3e5d30 --- /dev/null +++ b/src/olaf/tests.rs @@ -0,0 +1,976 @@ +#[cfg(test)] +mod tests { + use crate::olaf::{ + errors::DKGResult, + identifier::Identifier, + keys::{GroupPublicKey, GroupPublicKeyShare}, + simplpedpop::{ + self, + round1::{self, PrivateData, PublicData, PublicMessage}, + round2::{self, Messages}, + round3, Identifiers, Parameters, + }, + }; + use alloc::{ + collections::{BTreeMap, BTreeSet}, + vec::Vec, + }; + use merlin::Transcript; + use rand::{rngs::OsRng, Rng}; + + 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 = simplpedpop::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) + } + + mod simplpedpop_tests { + use super::*; + use crate::{ + olaf::{ + errors::DKGError, + polynomial::{Polynomial, PolynomialCommitment}, + simplpedpop::{EncryptedSecretShare, SecretShare}, + }, + PublicKey, SecretKey, SignatureError, + }; + use curve25519_dalek::{RistrettoPoint, Scalar}; + use simplpedpop_tests::simplpedpop::GENERATOR; + + #[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.unwrap(); + + 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 = Scalar::random(&mut OsRng); + + 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 ( + 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(); + + 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); + } + + let unknown_identifier = Identifier(Scalar::ONE); + + let private_message = round2_private_messages[0].pop_first().unwrap().1; + round2_private_messages[0].insert(unknown_identifier, private_message); + + let public_message = participants_round2_public_data[0] + .round1_public_messages + .pop_first() + .unwrap() + .1; + + participants_round2_public_data[0] + .round1_public_messages + .insert(unknown_identifier, public_message); + + 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, + "Expected DKGError::UnknownIdentifierRound2PrivateMessages." + ), + } + } + + #[test] + fn test_invalid_threshold() { + let parameters = Parameters::new(3, 1); + let result = round1::run(parameters, &mut OsRng); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => assert_eq!( + e, + DKGError::InsufficientThreshold, + "Expected DKGError::InsufficientThreshold." + ), + } + } + + #[test] + fn test_invalid_participants() { + let parameters = Parameters::new(1, 2); + let result = round1::run(parameters, &mut OsRng); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => assert_eq!( + e, + DKGError::InvalidNumberOfParticipants, + "Expected DKGError::InvalidNumberOfParticipants." + ), + } + } + + #[test] + fn test_threshold_greater_than_participants() { + let parameters = Parameters::new(2, 3); + let result = round1::run(parameters, &mut OsRng); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => assert_eq!( + e, + DKGError::ExcessiveThreshold, + "Expected 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.unwrap().decrypt(&deckey, &enckey, context); + + assert_eq!( + original_share.0, + decrypted_share.unwrap().0, + "Decryption must return the original share!" + ); + } + } +} diff --git a/src/serdey.rs b/src/serdey.rs index 7b31ed3..8a8e2f5 100644 --- a/src/serdey.rs +++ b/src/serdey.rs @@ -13,15 +13,15 @@ #[cfg(feature = "serde")] #[rustfmt::skip] macro_rules! serde_boilerplate { ($t:ty) => { -impl serde_crate::Serialize for $t { - fn serialize(&self, serializer: S) -> Result where S: serde_crate::Serializer { +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) } } -impl<'d> serde_crate::Deserialize<'d> for $t { - fn deserialize(deserializer: D) -> Result where D: serde_crate::Deserializer<'d> { +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)?;