From b93d5ea4ecf401a2f021618722547ca07cd22a67 Mon Sep 17 00:00:00 2001 From: Fiono11 Date: Mon, 6 May 2024 11:33:35 +0100 Subject: [PATCH 01/60] Implementation of SimplPedPoP --- Cargo.toml | 33 +++- benches/olaf_benchmarks.rs | 57 ++++++ benches/schnorr_benchmarks.rs | 2 +- src/lib.rs | 3 + src/musig.rs | 2 +- src/olaf/data_structures.rs | 339 ++++++++++++++++++++++++++++++++++ src/olaf/errors.rs | 55 ++++++ src/olaf/mod.rs | 13 ++ src/olaf/simplpedpop.rs | 285 ++++++++++++++++++++++++++++ src/olaf/tests.rs | 222 ++++++++++++++++++++++ src/olaf/utils.rs | 163 ++++++++++++++++ 11 files changed, 1168 insertions(+), 6 deletions(-) create mode 100644 benches/olaf_benchmarks.rs create mode 100644 src/olaf/data_structures.rs create mode 100644 src/olaf/errors.rs create mode 100644 src/olaf/mod.rs create mode 100644 src/olaf/simplpedpop.rs create mode 100644 src/olaf/tests.rs create mode 100644 src/olaf/utils.rs diff --git a/Cargo.toml b/Cargo.toml index 9943958..7797028 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ curve25519-dalek = { version = "4.1.0", default-features = false, features = [ "zeroize", "precomputed-tables", "legacy_compatibility", + "rand_core", ] } subtle = { version = "2.4.1", default-features = false } merlin = { version = "3.0.0", default-features = false } @@ -32,7 +33,10 @@ 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"] } +zeroize = { version = "1.6", default-features = false, features = [ + "zeroize_derive", +] } +chacha20poly1305 = { version = "0.10.1", default-features = false } [dev-dependencies] rand = "0.8.5" @@ -47,17 +51,38 @@ serde_json = "1.0.68" name = "schnorr_benchmarks" harness = false +[[bench]] +name = "olaf_benchmarks" +required-features = ["alloc", "aead"] +harness = false + [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", +] asm = ["sha2/asm"] serde = ["serde_crate", "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 diff --git a/benches/olaf_benchmarks.rs b/benches/olaf_benchmarks.rs new file mode 100644 index 0000000..66b29bd --- /dev/null +++ b/benches/olaf_benchmarks.rs @@ -0,0 +1,57 @@ +use criterion::criterion_main; + +mod olaf_benches { + use criterion::{criterion_group, BenchmarkId, Criterion}; + use schnorrkel::{olaf::data_structures::AllMessage, Keypair, PublicKey}; + + 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(300)); + + for &n in [1000].iter() { + let participants = n; + let threshold = 100; //(n * 2 + 2) / 3; + + let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public).collect(); + + // Each participant creates an AllMessage + let mut all_messages = Vec::new(); + for i in 0..participants { + let message: AllMessage = keypairs[i] + .simplpedpop_contribute_all(threshold as u16, public_keys.clone()) + .unwrap(); + all_messages.push(message); + } + + group.bench_function(BenchmarkId::new("round1", participants), |b| { + b.iter(|| { + keypairs[0] + .simplpedpop_contribute_all(threshold as u16, public_keys.clone()) + .unwrap(); + }) + }); + + group.bench_function(BenchmarkId::new("round2", participants), |b| { + b.iter(|| { + keypairs[0].simplpedpop_recipient_all(&all_messages).unwrap(); + }) + }); + } + + group.finish(); + } + + criterion_group! { + name = olaf_benches; + config = Criterion::default(); + targets = + benchmark_simplpedpop, + } +} + +criterion_main!(olaf_benches::olaf_benches); diff --git a/benches/schnorr_benchmarks.rs b/benches/schnorr_benchmarks.rs index ef0d829..724ba3d 100644 --- a/benches/schnorr_benchmarks.rs +++ b/benches/schnorr_benchmarks.rs @@ -79,4 +79,4 @@ mod schnorr_benches { criterion_main!( schnorr_benches::schnorr_benches, -); +); \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index a7e373b..5208751 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -248,6 +248,9 @@ pub mod derive; pub mod cert; pub mod errors; +#[cfg(all(feature = "alloc", feature = "aead"))] +pub mod olaf; + #[cfg(all(feature = "aead", feature = "getrandom"))] pub mod aead; diff --git a/src/musig.rs b/src/musig.rs index 8d89127..63cd31a 100644 --- a/src/musig.rs +++ b/src/musig.rs @@ -822,4 +822,4 @@ mod tests { assert_eq!(signature, cosigns[i].sign().unwrap()); } } -} +} \ No newline at end of file diff --git a/src/olaf/data_structures.rs b/src/olaf/data_structures.rs new file mode 100644 index 0000000..bedf584 --- /dev/null +++ b/src/olaf/data_structures.rs @@ -0,0 +1,339 @@ +//! SimplPedPoP data structures. + +#![allow(clippy::too_many_arguments)] + +use alloc::vec::Vec; +use curve25519_dalek::{ristretto::CompressedRistretto, RistrettoPoint}; +use crate::{context::SigningTranscript, PublicKey, Signature, PUBLIC_KEY_LENGTH, SIGNATURE_LENGTH}; +use super::{errors::DKGError, MINIMUM_THRESHOLD}; + +pub(crate) const COMPRESSED_RISTRETTO_LENGTH: usize = 32; +pub(crate) const U16_LENGTH: usize = 2; +pub(crate) const ENCRYPTION_NONCE_LENGTH: usize = 12; +pub(crate) const RECIPIENTS_HASH_LENGTH: usize = 16; +pub(crate) const CHACHA20POLY1305_LENGTH: usize = 64; + +/// The parameters of a given execution of the SimplPedPoP protocol. +#[derive(Clone, PartialEq, Eq)] +pub struct Parameters { + pub(crate) participants: u16, + pub(crate) threshold: u16, +} + +impl Parameters { + /// Create new parameters. + pub fn generate(participants: u16, threshold: u16) -> Parameters { + Parameters { participants, threshold } + } + + pub(crate) fn validate(&self) -> Result<(), DKGError> { + if self.threshold < MINIMUM_THRESHOLD { + return Err(DKGError::InsufficientThreshold); + } + + if self.participants < MINIMUM_THRESHOLD { + return Err(DKGError::InvalidNumberOfParticipants); + } + + if self.threshold > self.participants { + return Err(DKGError::ExcessiveThreshold); + } + + Ok(()) + } + + pub(crate) 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()); + } +} + +/// AllMessage packs together messages for all participants. +/// +/// We'd save bandwidth by having separate messages for each +/// participant, but typical thresholds lie between 1/2 and 2/3, +/// so this doubles or tripples bandwidth usage. +pub struct AllMessage { + pub(crate) content: MessageContent, + pub(crate) signature: Signature, +} + +impl AllMessage { + /// Creates a new message. + pub fn new(content: MessageContent, signature: Signature) -> Self { + Self { content, signature } + } + /// Serialize AllMessage + pub fn to_bytes(&self) -> Vec { + let mut bytes = Vec::new(); + + bytes.extend(self.content.to_bytes()); + bytes.extend(self.signature.to_bytes()); + + bytes + } + + /// Deserialize AllMessage from bytes + pub fn from_bytes(bytes: &[u8]) -> Result { + let mut cursor = 0; + + let content = MessageContent::from_bytes(&bytes[cursor..])?; + cursor += content.to_bytes().len(); + + let signature = Signature::from_bytes(&bytes[cursor..cursor + SIGNATURE_LENGTH]) + .map_err(DKGError::InvalidSignature)?; + + Ok(AllMessage { content, signature }) + } +} + +/// The contents of the message destined to all participants. +pub struct MessageContent { + pub(crate) sender: PublicKey, + pub(crate) encryption_nonce: [u8; ENCRYPTION_NONCE_LENGTH], + pub(crate) parameters: Parameters, + pub(crate) recipients_hash: [u8; RECIPIENTS_HASH_LENGTH], + pub(crate) point_polynomial: Vec, + pub(crate) ciphertexts: Vec>, + pub(crate) ephemeral_key: PublicKey, + pub(crate) proof_of_possession: Signature, +} + +impl MessageContent { + /// Creates the content of the message. + pub fn new( + sender: PublicKey, + encryption_nonce: [u8; ENCRYPTION_NONCE_LENGTH], + parameters: Parameters, + recipients_hash: [u8; RECIPIENTS_HASH_LENGTH], + point_polynomial: Vec, + ciphertexts: Vec>, + ephemeral_key: PublicKey, + proof_of_possession: Signature, + ) -> Self { + Self { + sender, + encryption_nonce, + parameters, + recipients_hash, + point_polynomial, + ciphertexts, + ephemeral_key, + proof_of_possession, + } + } + /// Serialize MessageContent + pub fn to_bytes(&self) -> Vec { + let mut bytes = Vec::new(); + + bytes.extend(self.sender.to_bytes()); + bytes.extend(&self.encryption_nonce); + bytes.extend(self.parameters.participants.to_le_bytes()); + bytes.extend(self.parameters.threshold.to_le_bytes()); + bytes.extend(&self.recipients_hash); + + for point in &self.point_polynomial { + bytes.extend(point.compress().to_bytes()); + } + + for ciphertext in &self.ciphertexts { + bytes.extend(ciphertext); + } + + bytes.extend(&self.ephemeral_key.to_bytes()); + bytes.extend(&self.proof_of_possession.to_bytes()); + + bytes + } + + /// Deserialize MessageContent from bytes + pub fn from_bytes(bytes: &[u8]) -> Result { + let mut cursor = 0; + + // Deserialize PublicKey + let sender = PublicKey::from_bytes(&bytes[cursor..cursor + PUBLIC_KEY_LENGTH]) + .map_err(DKGError::InvalidPublicKey)?; + cursor += PUBLIC_KEY_LENGTH; + + // Deserialize encryption_nonce + let encryption_nonce: [u8; ENCRYPTION_NONCE_LENGTH] = bytes + [cursor..cursor + ENCRYPTION_NONCE_LENGTH] + .try_into() + .map_err(DKGError::DeserializationError)?; + cursor += ENCRYPTION_NONCE_LENGTH; + + // Deserialize Parameters + let participants = u16::from_le_bytes( + bytes[cursor..cursor + U16_LENGTH] + .try_into() + .map_err(DKGError::DeserializationError)?, + ); + cursor += U16_LENGTH; + let threshold = u16::from_le_bytes( + bytes[cursor..cursor + U16_LENGTH] + .try_into() + .map_err(DKGError::DeserializationError)?, + ); + cursor += U16_LENGTH; + + let recipients_hash: [u8; RECIPIENTS_HASH_LENGTH] = bytes + [cursor..cursor + RECIPIENTS_HASH_LENGTH] + .try_into() + .map_err(DKGError::DeserializationError)?; + cursor += RECIPIENTS_HASH_LENGTH; + + let mut point_polynomial = Vec::with_capacity(participants as usize); + for _ in 0..participants { + let point = CompressedRistretto::from_slice( + &bytes[cursor..cursor + COMPRESSED_RISTRETTO_LENGTH], + ) + .map_err(DKGError::DeserializationError)?; + point_polynomial.push(point.decompress().ok_or(DKGError::InvalidRistrettoPoint)?); + cursor += COMPRESSED_RISTRETTO_LENGTH; + } + + let mut ciphertexts = Vec::new(); + for _ in 0..participants { + let ciphertext = bytes[cursor..cursor + CHACHA20POLY1305_LENGTH].to_vec(); + ciphertexts.push(ciphertext); + cursor += CHACHA20POLY1305_LENGTH; + } + + let ephemeral_key = PublicKey::from_bytes(&bytes[cursor..cursor + PUBLIC_KEY_LENGTH]) + .map_err(DKGError::InvalidPublicKey)?; + cursor += PUBLIC_KEY_LENGTH; + + let proof_of_possession = Signature::from_bytes(&bytes[cursor..cursor + SIGNATURE_LENGTH]) + .map_err(DKGError::InvalidSignature)?; + + Ok(MessageContent { + sender, + encryption_nonce, + parameters: Parameters { participants, threshold }, + recipients_hash, + point_polynomial, + ciphertexts, + ephemeral_key, + proof_of_possession, + }) + } +} + +/// The signed output of the SimplPedPoP protocol. +pub struct DKGOutput { + pub(crate) sender: PublicKey, + pub(crate) content: DKGOutputContent, + pub(crate) signature: Signature, +} + +impl DKGOutput { + /// Creates a signed SimplPedPoP output. + pub fn new(sender: PublicKey, content: DKGOutputContent, signature: Signature) -> Self { + Self { sender, content, signature } + } + + /// Serializes the DKGOutput into bytes. + pub fn to_bytes(&self) -> Vec { + let mut bytes = Vec::new(); + + let pk_bytes = self.sender.to_bytes(); + bytes.extend(pk_bytes); + + let content_bytes = self.content.to_bytes(); + bytes.extend(content_bytes); + + let signature_bytes = self.signature.to_bytes(); + bytes.extend(signature_bytes); + + bytes + } + + /// Deserializes the DKGOutput from bytes. + pub fn from_bytes(bytes: &[u8]) -> Result { + let mut cursor = 0; + + let pk_bytes = &bytes[..PUBLIC_KEY_LENGTH]; + let sender = PublicKey::from_bytes(pk_bytes).map_err(DKGError::InvalidPublicKey)?; + cursor += PUBLIC_KEY_LENGTH; + + let content_bytes = &bytes[cursor..bytes.len() - SIGNATURE_LENGTH]; + let content = DKGOutputContent::from_bytes(content_bytes)?; + + cursor = bytes.len() - SIGNATURE_LENGTH; + let signature = Signature::from_bytes(&bytes[cursor..cursor + SIGNATURE_LENGTH]) + .map_err(DKGError::InvalidSignature)?; + + Ok(DKGOutput { sender, content, signature }) + } +} + +/// The content of the signed output of the SimplPedPoP protocol. +#[derive(Debug)] +pub struct DKGOutputContent { + pub(crate) group_public_key: PublicKey, + pub(crate) verifying_keys: Vec, +} + +impl DKGOutputContent { + /// Creates the content of the SimplPedPoP output. + pub fn new(group_public_key: PublicKey, verifying_keys: Vec) -> Self { + Self { group_public_key, verifying_keys } + } + /// Serializes the DKGOutputContent into bytes. + pub fn to_bytes(&self) -> Vec { + let mut bytes = Vec::new(); + + // Serialize the group public key + let compressed_public_key = self.group_public_key.as_compressed(); // Assuming PublicKey can be compressed directly + bytes.extend(compressed_public_key.to_bytes().iter()); + + // Serialize the number of verifying keys + let key_count = self.verifying_keys.len() as u16; + bytes.extend(key_count.to_le_bytes()); + + // Serialize each verifying key + for key in &self.verifying_keys { + let compressed_key = key.compress(); + bytes.extend(compressed_key.to_bytes()); + } + + bytes + } +} + +impl DKGOutputContent { + /// Deserializes the DKGOutputContent from bytes. + pub fn from_bytes(bytes: &[u8]) -> Result { + let mut cursor = 0; + + // Deserialize the group public key + let public_key_bytes = &bytes[cursor..cursor + PUBLIC_KEY_LENGTH]; // Ristretto points are 32 bytes when compressed + cursor += PUBLIC_KEY_LENGTH; + let compressed_public_key = CompressedRistretto::from_slice(public_key_bytes) + .map_err(DKGError::DeserializationError)?; + let group_public_key = + compressed_public_key.decompress().ok_or(DKGError::InvalidRistrettoPoint)?; + + // Deserialize the number of verifying keys + let key_count_bytes = &bytes[cursor..cursor + U16_LENGTH]; + cursor += U16_LENGTH; + let key_count = + u16::from_le_bytes(key_count_bytes.try_into().map_err(DKGError::DeserializationError)?); + + // Deserialize each verifying key + let mut verifying_keys = Vec::with_capacity(key_count as usize); + for _ in 0..key_count { + let key_bytes = &bytes[cursor..cursor + COMPRESSED_RISTRETTO_LENGTH]; + cursor += COMPRESSED_RISTRETTO_LENGTH; + let compressed_key = CompressedRistretto::from_slice(key_bytes) + .map_err(DKGError::DeserializationError)?; + let key = compressed_key.decompress().ok_or(DKGError::InvalidRistrettoPoint)?; + verifying_keys.push(key); + } + + Ok(DKGOutputContent { + group_public_key: PublicKey::from_point(group_public_key), + verifying_keys, + }) + } +} diff --git a/src/olaf/errors.rs b/src/olaf/errors.rs new file mode 100644 index 0000000..50ccf5e --- /dev/null +++ b/src/olaf/errors.rs @@ -0,0 +1,55 @@ +//! Errors of the Olaf protocol. + +use core::array::TryFromSliceError; +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)] +pub enum DKGError { + /// Invalid Proof of Possession. + InvalidProofOfPossession(SignatureError), + /// Threshold cannot be greater than the number of participants. + ExcessiveThreshold, + /// Threshold must be at least 2. + InsufficientThreshold, + /// Number of participants is invalid. + InvalidNumberOfParticipants, + /// Invalid PublicKey. + InvalidPublicKey(SignatureError), + /// Invalid Signature. + InvalidSignature(SignatureError), + /// Invalid Scalar. + InvalidScalar, + /// Invalid Ristretto Point. + InvalidRistrettoPoint, + /// Deserialization Error. + DeserializationError(TryFromSliceError), + /// Incorrect number secret shares. + IncorrectNumberOfValidSecretShares { + /// The expected value. + expected: usize, + /// The actual value. + actual: usize, + }, + /// The parameters of all messages should be equal. + DifferentParameters, + /// The recipients hash of all messages should be equal. + DifferentRecipientsHash, + /// The number of messages should be 2 at least, which the minimum number of participants. + InvalidNumberOfMessages, + /// The number of messages should be equal to the number of participants. + IncorrectNumberOfMessages, + /// The number of commitments per message should be equal to the number of participants - 1. + IncorrectNumberOfCommitments, + /// The number of encrypted shares per message should be equal to the number of participants. + IncorrectNumberOfEncryptedShares, + /// The verifying key is invalid. + InvalidVerifyingKey, + /// Decryption error when decrypting an encrypted secret share. + DecryptionError(chacha20poly1305::Error), + /// Encryption error when encrypting the secret share. + EncryptionError(chacha20poly1305::Error), +} diff --git a/src/olaf/mod.rs b/src/olaf/mod.rs new file mode 100644 index 0000000..a03e299 --- /dev/null +++ b/src/olaf/mod.rs @@ -0,0 +1,13 @@ +//! Implementation of the Olaf protocol (), which is composed of the Distributed +//! Key Generation (DKG) protocol SimplPedPoP and the Threshold Signing protocol FROST. + +use curve25519_dalek::{constants::RISTRETTO_BASEPOINT_POINT, RistrettoPoint}; + +pub mod errors; +pub mod simplpedpop; +mod tests; +pub mod data_structures; +mod utils; + +const MINIMUM_THRESHOLD: u16 = 2; +const GENERATOR: RistrettoPoint = RISTRETTO_BASEPOINT_POINT; diff --git a/src/olaf/simplpedpop.rs b/src/olaf/simplpedpop.rs new file mode 100644 index 0000000..11a7590 --- /dev/null +++ b/src/olaf/simplpedpop.rs @@ -0,0 +1,285 @@ +//! Implementation of the SimplPedPoP protocol (), 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. + +use alloc::vec::Vec; +use curve25519_dalek::{traits::Identity, RistrettoPoint, Scalar}; +use merlin::Transcript; +use rand_core::RngCore; +use crate::{context::SigningTranscript, verify_batch, Keypair, PublicKey}; +use super::{ + data_structures::{ + AllMessage, DKGOutput, DKGOutputContent, MessageContent, Parameters, + ENCRYPTION_NONCE_LENGTH, RECIPIENTS_HASH_LENGTH, + }, + errors::{DKGError, DKGResult}, + utils::{ + decrypt, derive_secret_key_from_secret, encrypt, evaluate_polynomial, + evaluate_polynomial_commitment, generate_coefficients, generate_identifier, + sum_commitments, + }, + GENERATOR, MINIMUM_THRESHOLD, +}; + +impl Keypair { + /// First round of the SimplPedPoP protocol. + pub fn simplpedpop_contribute_all( + &self, + threshold: u16, + recipients: Vec, + ) -> DKGResult { + let parameters = Parameters::generate(recipients.len() as u16, threshold); + parameters.validate()?; + + let mut rng = crate::getrandom_or_panic(); + + // We do not recipients.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"RecipientsHash"); + parameters.commit(&mut t); + for r in recipients.iter() { + t.commit_point(b"recipient", r.as_compressed()); + } + let mut recipients_hash = [0u8; RECIPIENTS_HASH_LENGTH]; + t.challenge_bytes(b"finalize", &mut recipients_hash); + + let coefficients = generate_coefficients(parameters.threshold as usize - 1, &mut rng); + let mut scalar_evaluations = Vec::new(); + + for i in 0..parameters.participants { + let identifier = generate_identifier(&recipients_hash, i); + let scalar_evaluation = evaluate_polynomial(&identifier, &coefficients); + scalar_evaluations.push(scalar_evaluation); + } + + // Create the vector of commitments + let point_polynomial: Vec = + coefficients.iter().map(|c| GENERATOR * *c).collect(); + + let mut enc0 = merlin::Transcript::new(b"Encryption"); + parameters.commit(&mut enc0); + enc0.commit_point(b"contributor", self.public.as_compressed()); + + let mut encryption_nonce = [0u8; ENCRYPTION_NONCE_LENGTH]; + rng.fill_bytes(&mut encryption_nonce); + enc0.append_message(b"nonce", &encryption_nonce); + + let ephemeral_key = Keypair::generate(); + + let mut ciphertexts = Vec::new(); + + for i in 0..parameters.participants { + let ciphertext = encrypt( + &scalar_evaluations[i as usize], + &ephemeral_key.secret.key, + enc0.clone(), + &recipients[i as usize], + &encryption_nonce, + i as usize, + )?; + + ciphertexts.push(ciphertext); + } + + let pk = &PublicKey::from_point( + *point_polynomial + .first() + .expect("This never fails because the minimum threshold is 2"), + ); + + let secret = coefficients + .first() + .expect("This never fails because the minimum threshold is 2"); + + let secret_key = derive_secret_key_from_secret(secret, &mut rng); + + let secret_commitment = point_polynomial + .first() + .expect("This never fails because the minimum threshold is 2"); + + let mut t_pop = Transcript::new(b"pop"); + t_pop.append_message(b"secret commitment", secret_commitment.compress().as_bytes()); + let proof_of_possession = secret_key.sign(t_pop, pk); + + let message_content = MessageContent::new( + self.public, + encryption_nonce, + parameters, + recipients_hash, + point_polynomial, + ciphertexts, + ephemeral_key.public, + proof_of_possession, + ); + + let mut t_sig = Transcript::new(b"signature"); + t_sig.append_message(b"message", &message_content.to_bytes()); + let signature = self.sign(t_sig); + + Ok(AllMessage::new(message_content, signature)) + } + + /// Second round of the SimplPedPoP protocol. + pub fn simplpedpop_recipient_all( + &self, + messages: &[AllMessage], + ) -> DKGResult<(DKGOutput, Scalar)> { + if messages.len() < MINIMUM_THRESHOLD as usize { + return Err(DKGError::InvalidNumberOfMessages); + } + + let participants = messages[0].content.parameters.participants as usize; + let threshold = messages[0].content.parameters.threshold as usize; + + if messages.len() != participants { + return Err(DKGError::IncorrectNumberOfMessages); + } + + messages[0].content.parameters.validate()?; + + let first_params = &messages[0].content.parameters; + let recipients_hash = &messages[0].content.recipients_hash; + + let mut secret_shares = Vec::new(); + + let mut verifying_keys = Vec::new(); + + let mut public_keys = Vec::with_capacity(participants); + let mut proofs_of_possession = Vec::with_capacity(participants); + + let mut senders = Vec::with_capacity(participants); + let mut signatures = Vec::with_capacity(participants); + + let mut t_sigs = Vec::with_capacity(participants); + let mut t_pops = Vec::with_capacity(participants); + + let mut group_point = RistrettoPoint::identity(); + let mut total_secret_share = Scalar::ZERO; + let mut total_polynomial_commitment: Vec = Vec::new(); + + for (j, message) in messages.iter().enumerate() { + if &message.content.parameters != first_params { + return Err(DKGError::DifferentParameters); + } + if &message.content.recipients_hash != recipients_hash { + return Err(DKGError::DifferentRecipientsHash); + } + // The public keys are the secret commitments of the participants + let public_key = + PublicKey::from_point( + *message.content.point_polynomial.first().expect( + "This never fails because the minimum threshold of the protocol is 2", + ), + ); + + public_keys.push(public_key); + proofs_of_possession.push(message.content.proof_of_possession); + + senders.push(message.content.sender); + signatures.push(message.signature); + + // Recreate the encryption environment + let mut enc = merlin::Transcript::new(b"Encryption"); + message.content.parameters.commit(&mut enc); + enc.commit_point(b"contributor", message.content.sender.as_compressed()); + + let point_polynomial = &message.content.point_polynomial; + let ciphertexts = &message.content.ciphertexts; + + if point_polynomial.len() != threshold - 1 { + return Err(DKGError::IncorrectNumberOfCommitments); + } + + if ciphertexts.len() != participants { + return Err(DKGError::IncorrectNumberOfEncryptedShares); + } + + let encryption_nonce = message.content.encryption_nonce; + enc.append_message(b"nonce", &encryption_nonce); + + let message_bytes = &message.content.to_bytes(); + + let mut t_sig = Transcript::new(b"signature"); + t_sig.append_message(b"message", message_bytes); + + let mut t_pop = Transcript::new(b"pop"); + let secret_commitment = point_polynomial + .first() + .expect("This never fails because the minimum threshold is 2"); + t_pop.append_message(b"secret commitment", secret_commitment.compress().as_bytes()); + + t_sigs.push(t_sig); + t_pops.push(t_pop); + + if total_polynomial_commitment.is_empty() { + total_polynomial_commitment = point_polynomial.clone(); + } else { + total_polynomial_commitment = + sum_commitments(&[&total_polynomial_commitment, point_polynomial])?; + } + + let ephemeral_key = message.content.ephemeral_key; + let key_exchange = self.secret.key * message.content.ephemeral_key.into_point(); + + for (i, ciphertext) in ciphertexts.iter().enumerate() { + let identifier = generate_identifier(recipients_hash, i as u16); + + if let Ok(secret_share) = decrypt( + enc.clone(), + &ephemeral_key, + &self.public, + &key_exchange, + ciphertext, + &encryption_nonce, + i, + ) { + if secret_share * GENERATOR + == evaluate_polynomial_commitment(&identifier, point_polynomial) + { + secret_shares.push(secret_share); + break; + } + } + } + + total_secret_share += secret_shares[j]; + + group_point += secret_commitment; + } + + for i in 0..participants { + let identifier = generate_identifier(recipients_hash, i as u16); + verifying_keys + .push(evaluate_polynomial_commitment(&identifier, &total_polynomial_commitment)); + } + + if secret_shares.len() != messages[0].content.parameters.participants as usize { + return Err(DKGError::IncorrectNumberOfValidSecretShares { + expected: messages[0].content.parameters.participants as usize, + actual: secret_shares.len(), + }); + } + + verify_batch(t_pops, &proofs_of_possession[..], &public_keys[..], false) + .map_err(DKGError::InvalidProofOfPossession)?; + + verify_batch(t_sigs, &signatures[..], &senders[..], false) + .map_err(DKGError::InvalidSignature)?; + + let dkg_output_content = + DKGOutputContent::new(PublicKey::from_point(group_point), verifying_keys); + + let mut transcript = Transcript::new(b"dkg output"); + transcript.append_message(b"content", &dkg_output_content.to_bytes()); + + let signature = self.sign(transcript); + + let dkg_output = DKGOutput::new(self.public, dkg_output_content, signature); + + Ok((dkg_output, total_secret_share)) + } +} diff --git a/src/olaf/tests.rs b/src/olaf/tests.rs new file mode 100644 index 0000000..2d9a365 --- /dev/null +++ b/src/olaf/tests.rs @@ -0,0 +1,222 @@ +#[cfg(test)] +mod tests { + use crate::olaf::data_structures::{ + AllMessage, DKGOutput, DKGOutputContent, MessageContent, Parameters, + CHACHA20POLY1305_LENGTH, ENCRYPTION_NONCE_LENGTH, RECIPIENTS_HASH_LENGTH, + }; + use crate::olaf::utils::{decrypt, encrypt}; + use crate::olaf::GENERATOR; + use crate::{Keypair, PublicKey}; + use alloc::vec::Vec; + use curve25519_dalek::ristretto::RistrettoPoint; + use curve25519_dalek::scalar::Scalar; + use merlin::Transcript; + use rand::rngs::OsRng; + + #[test] + fn test_simplpedpop_protocol() { + // Create participants + let threshold = 2; + let participants = 2; + let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public).collect(); + + // Each participant creates an AllMessage + let mut all_messages = Vec::new(); + for i in 0..participants { + let message: AllMessage = + keypairs[i].simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap(); + all_messages.push(message); + } + + let mut dkg_outputs = Vec::new(); + + for kp in keypairs.iter() { + let dkg_output = kp.simplpedpop_recipient_all(&all_messages).unwrap(); + dkg_outputs.push(dkg_output); + } + + // Verify that all DKG outputs are equal for group_public_key and verifying_keys + assert!( + dkg_outputs.windows(2).all(|w| w[0].0.content.group_public_key + == w[1].0.content.group_public_key + && w[0].0.content.verifying_keys.len() == w[1].0.content.verifying_keys.len() + && w[0] + .0 + .content + .verifying_keys + .iter() + .zip(w[1].0.content.verifying_keys.iter()) + .all(|(a, b)| a == b)), + "All DKG outputs should have identical group public keys and verifying keys." + ); + + // Verify that all verifying_keys are valid + for i in 0..participants { + for j in 0..participants { + assert_eq!( + dkg_outputs[i].0.content.verifying_keys[j].compress(), + (dkg_outputs[j].1 * GENERATOR).compress(), + "Verification of total secret shares failed!" + ); + } + } + } + + #[test] + fn test_serialize_deserialize_all_message() { + let sender = Keypair::generate(); + let encryption_nonce = [1u8; ENCRYPTION_NONCE_LENGTH]; + let parameters = Parameters { participants: 2, threshold: 1 }; + let recipients_hash = [2u8; RECIPIENTS_HASH_LENGTH]; + let point_polynomial = + vec![RistrettoPoint::random(&mut OsRng), RistrettoPoint::random(&mut OsRng)]; + let ciphertexts = vec![vec![1; CHACHA20POLY1305_LENGTH], vec![1; CHACHA20POLY1305_LENGTH]]; + let proof_of_possession = sender.sign(Transcript::new(b"pop")); + let signature = sender.sign(Transcript::new(b"sig")); + let ephemeral_key = PublicKey::from_point(RistrettoPoint::random(&mut OsRng)); + + let message_content = MessageContent::new( + sender.public, + encryption_nonce, + parameters, + recipients_hash, + point_polynomial, + ciphertexts, + ephemeral_key, + proof_of_possession, + ); + + let message = AllMessage::new(message_content, signature); + + let bytes = message.to_bytes(); + + let deserialized_message = AllMessage::from_bytes(&bytes).expect("Failed to deserialize"); + + assert_eq!(message.content.sender, deserialized_message.content.sender); + + assert_eq!(message.content.encryption_nonce, deserialized_message.content.encryption_nonce); + + assert_eq!( + message.content.parameters.participants, + deserialized_message.content.parameters.participants + ); + + assert_eq!( + message.content.parameters.threshold, + deserialized_message.content.parameters.threshold + ); + + assert_eq!(message.content.recipients_hash, deserialized_message.content.recipients_hash); + + assert!(message + .content + .point_polynomial + .iter() + .zip(deserialized_message.content.point_polynomial.iter()) + .all(|(a, b)| a.compress() == b.compress())); + + assert!(message + .content + .ciphertexts + .iter() + .zip(deserialized_message.content.ciphertexts.iter()) + .all(|(a, b)| a == b)); + + assert_eq!( + message.content.proof_of_possession, + deserialized_message.content.proof_of_possession + ); + + assert_eq!(message.signature, deserialized_message.signature); + } + + #[test] + fn test_dkg_output_serialization() { + let mut rng = OsRng; + let group_public_key = RistrettoPoint::random(&mut rng); + let verifying_keys = vec![ + RistrettoPoint::random(&mut rng), + RistrettoPoint::random(&mut rng), + RistrettoPoint::random(&mut rng), + ]; + + let dkg_output_content = DKGOutputContent { + group_public_key: PublicKey::from_point(group_public_key), + verifying_keys, + }; + + let keypair = Keypair::generate(); + let signature = keypair.sign(Transcript::new(b"test")); + + let dkg_output = + DKGOutput { sender: keypair.public, content: dkg_output_content, signature }; + + // Serialize the DKGOutput + let bytes = dkg_output.to_bytes(); + + // Deserialize the DKGOutput + let deserialized_dkg_output = + DKGOutput::from_bytes(&bytes).expect("Deserialization failed"); + + // Check if the deserialized content matches the original + assert_eq!( + deserialized_dkg_output.content.group_public_key.as_compressed(), + dkg_output.content.group_public_key.as_compressed(), + "Group public keys do not match" + ); + + assert_eq!( + deserialized_dkg_output.content.verifying_keys.len(), + dkg_output.content.verifying_keys.len(), + "Verifying keys counts do not match" + ); + + assert!( + deserialized_dkg_output + .content + .verifying_keys + .iter() + .zip(dkg_output.content.verifying_keys.iter()) + .all(|(a, b)| a == b), + "Verifying keys do not match" + ); + + assert_eq!( + deserialized_dkg_output.signature.s, dkg_output.signature.s, + "Signatures do not match" + ); + } + + #[test] + fn test_encryption_decryption() { + let mut rng = OsRng; + let ephemeral_key = Keypair::generate(); + let recipient = Keypair::generate(); + let encryption_nonce = [1; ENCRYPTION_NONCE_LENGTH]; + let t = Transcript::new(b"label"); + let key_exchange = ephemeral_key.secret.key * recipient.public.as_point(); + let plaintext = Scalar::random(&mut rng); + + let encrypted_share = encrypt( + &plaintext, + &ephemeral_key.secret.key, + t.clone(), + &recipient.public, + &encryption_nonce, + 0, + ) + .unwrap(); + + decrypt( + t, + &ephemeral_key.public, + &recipient.public, + &key_exchange, + &encrypted_share, + &encryption_nonce, + 0, + ) + .unwrap(); + } +} diff --git a/src/olaf/utils.rs b/src/olaf/utils.rs new file mode 100644 index 0000000..89896df --- /dev/null +++ b/src/olaf/utils.rs @@ -0,0 +1,163 @@ +use core::iter; +use alloc::vec::Vec; +use aead::{generic_array::GenericArray, KeyInit, KeySizeUser}; +use chacha20poly1305::{aead::Aead, ChaCha20Poly1305, Nonce}; +use curve25519_dalek::{traits::Identity, RistrettoPoint, Scalar}; +use rand_core::{CryptoRng, RngCore}; +use crate::{context::SigningTranscript, PublicKey, SecretKey}; +use super::{ + data_structures::ENCRYPTION_NONCE_LENGTH, + errors::{DKGError, DKGResult}, + GENERATOR, +}; + +pub(crate) fn generate_identifier(recipients_hash: &[u8; 16], index: u16) -> Scalar { + let mut pos = merlin::Transcript::new(b"Identifier"); + pos.append_message(b"RecipientsHash", recipients_hash); + pos.append_message(b"i", &index.to_le_bytes()[..]); + pos.challenge_scalar(b"evaluation position") +} + +/// Evaluate the polynomial with the given coefficients (constant term first) +/// at the point x=identifier using Horner's method. +pub(crate) fn evaluate_polynomial(identifier: &Scalar, coefficients: &[Scalar]) -> Scalar { + let mut value = Scalar::ZERO; + + let ell_scalar = identifier; + for coeff in coefficients.iter().skip(1).rev() { + value += *coeff; + value *= ell_scalar; + } + value += *coefficients.first().expect("coefficients must have at least one element"); + value +} + +/// Return a vector of randomly generated polynomial coefficients ([`Scalar`]s). +pub(crate) fn generate_coefficients( + size: usize, + rng: &mut R, +) -> Vec { + let mut coefficients = Vec::with_capacity(size); + + // Ensure the first coefficient is not zero + let mut first = Scalar::random(rng); + while first == Scalar::ZERO { + first = Scalar::random(rng); + } + coefficients.push(first); + + // Generate the remaining coefficients + coefficients.extend(iter::repeat_with(|| Scalar::random(rng)).take(size - 1)); + + coefficients +} + +pub(crate) fn derive_secret_key_from_secret( + secret: &Scalar, + 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") +} + +pub(crate) fn evaluate_polynomial_commitment( + identifier: &Scalar, + commitment: &[RistrettoPoint], +) -> RistrettoPoint { + let i = identifier; + + let (_, result) = commitment + .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_commitments( + commitments: &[&Vec], +) -> Result, DKGError> { + let mut group_commitment = + vec![ + RistrettoPoint::identity(); + commitments.first().ok_or(DKGError::IncorrectNumberOfCommitments)?.len() + ]; + for commitment in commitments { + for (i, c) in group_commitment.iter_mut().enumerate() { + *c += commitment.get(i).ok_or(DKGError::IncorrectNumberOfCommitments)?; + } + } + Ok(group_commitment) +} + +pub(crate) fn encrypt( + scalar_evaluation: &Scalar, + ephemeral_key: &Scalar, + mut transcript: T, + recipient: &PublicKey, + nonce: &[u8; ENCRYPTION_NONCE_LENGTH], + i: usize, +) -> DKGResult> { + transcript.commit_bytes(b"i", &i.to_le_bytes()); + transcript.commit_point(b"contributor", &(ephemeral_key * GENERATOR).compress()); + transcript.commit_point(b"recipient", recipient.as_compressed()); + + transcript.commit_bytes(b"nonce", nonce); + transcript.commit_point(b"key exchange", &(ephemeral_key * recipient.as_point()).compress()); + + let mut key: GenericArray::KeySize> = + Default::default(); + + transcript.challenge_bytes(b"", key.as_mut_slice()); + + let cipher = ChaCha20Poly1305::new(&key); + + let nonce = Nonce::from_slice(&nonce[..]); + + let ciphertext: Vec = cipher + .encrypt(nonce, &scalar_evaluation.to_bytes()[..]) + .map_err(DKGError::EncryptionError)?; + + Ok(ciphertext) +} + +pub(crate) fn decrypt( + mut transcript: T, + contributor: &PublicKey, + recipient: &PublicKey, + key_exchange: &RistrettoPoint, + encrypted_scalar: &[u8], + nonce: &[u8; ENCRYPTION_NONCE_LENGTH], + i: usize, +) -> DKGResult { + transcript.commit_bytes(b"i", &i.to_le_bytes()); + transcript.commit_point(b"contributor", contributor.as_compressed()); + transcript.commit_point(b"recipient", recipient.as_compressed()); + transcript.commit_bytes(b"nonce", nonce); + transcript.commit_point(b"key exchange", &key_exchange.compress()); + + let mut key: GenericArray::KeySize> = + Default::default(); + + transcript.challenge_bytes(b"", key.as_mut_slice()); + + let cipher = ChaCha20Poly1305::new(&key); + + let nonce = Nonce::from_slice(&nonce[..]); + + let plaintext = cipher.decrypt(nonce, encrypted_scalar).map_err(DKGError::DecryptionError)?; + + let mut bytes = [0; 32]; + bytes.copy_from_slice(&plaintext); + + Ok(Scalar::from_bytes_mod_order(bytes)) +} From 02290350e5366f4fcb57caacdc5aca12a0650697 Mon Sep 17 00:00:00 2001 From: Fiono11 Date: Mon, 6 May 2024 12:50:08 +0100 Subject: [PATCH 02/60] Improvements --- Cargo.toml | 28 +---- benches/olaf_benchmarks.rs | 4 +- benches/schnorr_benchmarks.rs | 2 +- src/musig.rs | 2 +- src/olaf/errors.rs | 15 +-- src/olaf/simplpedpop.rs | 210 +++++++++++++++------------------- src/olaf/tests.rs | 12 +- src/olaf/utils.rs | 7 -- 8 files changed, 105 insertions(+), 175 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 7797028..a5a71a9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,7 @@ curve25519-dalek = { version = "4.1.0", default-features = false, features = [ "zeroize", "precomputed-tables", "legacy_compatibility", - "rand_core", + "rand_core" ] } subtle = { version = "2.4.1", default-features = false } merlin = { version = "3.0.0", default-features = false } @@ -33,9 +33,7 @@ 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", -] } +zeroize = { version = "1.6", default-features = false, features = ["zeroize_derive"] } chacha20poly1305 = { version = "0.10.1", default-features = false } [dev-dependencies] @@ -60,29 +58,13 @@ harness = false 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", - "chacha20poly1305/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"] asm = ["sha2/asm"] serde = ["serde_crate", "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 diff --git a/benches/olaf_benchmarks.rs b/benches/olaf_benchmarks.rs index 66b29bd..755a5c5 100644 --- a/benches/olaf_benchmarks.rs +++ b/benches/olaf_benchmarks.rs @@ -12,9 +12,9 @@ mod olaf_benches { .warm_up_time(std::time::Duration::from_secs(2)) .measurement_time(std::time::Duration::from_secs(300)); - for &n in [1000].iter() { + for &n in [3, 10, 100, 1000].iter() { let participants = n; - let threshold = 100; //(n * 2 + 2) / 3; + let threshold = (n * 2 + 2) / 3; let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); let public_keys: Vec = keypairs.iter().map(|kp| kp.public).collect(); diff --git a/benches/schnorr_benchmarks.rs b/benches/schnorr_benchmarks.rs index 724ba3d..ef0d829 100644 --- a/benches/schnorr_benchmarks.rs +++ b/benches/schnorr_benchmarks.rs @@ -79,4 +79,4 @@ mod schnorr_benches { criterion_main!( schnorr_benches::schnorr_benches, -); \ No newline at end of file +); diff --git a/src/musig.rs b/src/musig.rs index 63cd31a..8d89127 100644 --- a/src/musig.rs +++ b/src/musig.rs @@ -822,4 +822,4 @@ mod tests { assert_eq!(signature, cosigns[i].sign().unwrap()); } } -} \ No newline at end of file +} diff --git a/src/olaf/errors.rs b/src/olaf/errors.rs index 50ccf5e..9b8f17e 100644 --- a/src/olaf/errors.rs +++ b/src/olaf/errors.rs @@ -21,33 +21,22 @@ pub enum DKGError { InvalidPublicKey(SignatureError), /// Invalid Signature. InvalidSignature(SignatureError), - /// Invalid Scalar. - InvalidScalar, /// Invalid Ristretto Point. InvalidRistrettoPoint, + /// Invalid secret share. + InvalidSecretShare, /// Deserialization Error. DeserializationError(TryFromSliceError), - /// Incorrect number secret shares. - IncorrectNumberOfValidSecretShares { - /// The expected value. - expected: usize, - /// The actual value. - actual: usize, - }, /// The parameters of all messages should be equal. DifferentParameters, /// The recipients hash of all messages should be equal. DifferentRecipientsHash, /// The number of messages should be 2 at least, which the minimum number of participants. InvalidNumberOfMessages, - /// The number of messages should be equal to the number of participants. - IncorrectNumberOfMessages, /// The number of commitments per message should be equal to the number of participants - 1. IncorrectNumberOfCommitments, /// The number of encrypted shares per message should be equal to the number of participants. IncorrectNumberOfEncryptedShares, - /// The verifying key is invalid. - InvalidVerifyingKey, /// Decryption error when decrypting an encrypted secret share. DecryptionError(chacha20poly1305::Error), /// Encryption error when encrypting the secret share. diff --git a/src/olaf/simplpedpop.rs b/src/olaf/simplpedpop.rs index 11a7590..fd479aa 100644 --- a/src/olaf/simplpedpop.rs +++ b/src/olaf/simplpedpop.rs @@ -37,53 +37,50 @@ impl Keypair { // 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"RecipientsHash"); - parameters.commit(&mut t); + // provided, but we provide only hash to recipients, not the + // full recipients list. + let mut recipients_transcript = merlin::Transcript::new(b"RecipientsHash"); + parameters.commit(&mut recipients_transcript); for r in recipients.iter() { - t.commit_point(b"recipient", r.as_compressed()); + recipients_transcript.commit_point(b"recipient", r.as_compressed()); } let mut recipients_hash = [0u8; RECIPIENTS_HASH_LENGTH]; - t.challenge_bytes(b"finalize", &mut recipients_hash); + recipients_transcript.challenge_bytes(b"finalize", &mut recipients_hash); let coefficients = generate_coefficients(parameters.threshold as usize - 1, &mut rng); - let mut scalar_evaluations = Vec::new(); - for i in 0..parameters.participants { - let identifier = generate_identifier(&recipients_hash, i); - let scalar_evaluation = evaluate_polynomial(&identifier, &coefficients); - scalar_evaluations.push(scalar_evaluation); - } + let scalar_evaluations: Vec = (0..parameters.participants) + .map(|i| { + let identifier = generate_identifier(&recipients_hash, i); + evaluate_polynomial(&identifier, &coefficients) + }) + .collect(); - // Create the vector of commitments let point_polynomial: Vec = coefficients.iter().map(|c| GENERATOR * *c).collect(); - let mut enc0 = merlin::Transcript::new(b"Encryption"); - parameters.commit(&mut enc0); - enc0.commit_point(b"contributor", self.public.as_compressed()); + let mut encryption_transcript = merlin::Transcript::new(b"Encryption"); + parameters.commit(&mut encryption_transcript); + encryption_transcript.commit_point(b"contributor", self.public.as_compressed()); let mut encryption_nonce = [0u8; ENCRYPTION_NONCE_LENGTH]; rng.fill_bytes(&mut encryption_nonce); - enc0.append_message(b"nonce", &encryption_nonce); + encryption_transcript.append_message(b"nonce", &encryption_nonce); let ephemeral_key = Keypair::generate(); - let mut ciphertexts = Vec::new(); - - for i in 0..parameters.participants { - let ciphertext = encrypt( - &scalar_evaluations[i as usize], - &ephemeral_key.secret.key, - enc0.clone(), - &recipients[i as usize], - &encryption_nonce, - i as usize, - )?; - - ciphertexts.push(ciphertext); - } + let ciphertexts: Vec> = (0..parameters.participants) + .map(|i| { + encrypt( + &scalar_evaluations[i as usize], + &ephemeral_key.secret.key, + encryption_transcript.clone(), + &recipients[i as usize], + &encryption_nonce, + i as usize, + ) + }) + .collect::>()?; let pk = &PublicKey::from_point( *point_polynomial @@ -101,9 +98,10 @@ impl Keypair { .first() .expect("This never fails because the minimum threshold is 2"); - let mut t_pop = Transcript::new(b"pop"); - t_pop.append_message(b"secret commitment", secret_commitment.compress().as_bytes()); - let proof_of_possession = secret_key.sign(t_pop, pk); + let mut pop_transcript = Transcript::new(b"pop"); + pop_transcript + .append_message(b"secret commitment", secret_commitment.compress().as_bytes()); + let proof_of_possession = secret_key.sign(pop_transcript, pk); let message_content = MessageContent::new( self.public, @@ -116,9 +114,9 @@ impl Keypair { proof_of_possession, ); - let mut t_sig = Transcript::new(b"signature"); - t_sig.append_message(b"message", &message_content.to_bytes()); - let signature = self.sign(t_sig); + let mut signature_transcript = Transcript::new(b"signature"); + signature_transcript.append_message(b"message", &message_content.to_bytes()); + let signature = self.sign(signature_transcript); Ok(AllMessage::new(message_content, signature)) } @@ -132,88 +130,72 @@ impl Keypair { return Err(DKGError::InvalidNumberOfMessages); } - let participants = messages[0].content.parameters.participants as usize; - let threshold = messages[0].content.parameters.threshold as usize; - - if messages.len() != participants { - return Err(DKGError::IncorrectNumberOfMessages); - } - - messages[0].content.parameters.validate()?; - - let first_params = &messages[0].content.parameters; - let recipients_hash = &messages[0].content.recipients_hash; + let first_message = &messages[0]; + let parameters = &first_message.content.parameters; + let threshold = parameters.threshold as usize; + let participants = parameters.participants as usize; - let mut secret_shares = Vec::new(); - - let mut verifying_keys = Vec::new(); + first_message.content.parameters.validate()?; + let mut secret_shares = Vec::with_capacity(participants); + let mut verifying_keys = Vec::with_capacity(participants); let mut public_keys = Vec::with_capacity(participants); let mut proofs_of_possession = Vec::with_capacity(participants); - let mut senders = Vec::with_capacity(participants); let mut signatures = Vec::with_capacity(participants); - - let mut t_sigs = Vec::with_capacity(participants); - let mut t_pops = Vec::with_capacity(participants); - + let mut signatures_transcripts = Vec::with_capacity(participants); + let mut pops_transcripts = Vec::with_capacity(participants); let mut group_point = RistrettoPoint::identity(); let mut total_secret_share = Scalar::ZERO; - let mut total_polynomial_commitment: Vec = Vec::new(); + let mut total_polynomial_commitment = Vec::new(); + let mut identifiers = Vec::new(); for (j, message) in messages.iter().enumerate() { - if &message.content.parameters != first_params { + if &message.content.parameters != parameters { return Err(DKGError::DifferentParameters); } - if &message.content.recipients_hash != recipients_hash { + if &message.content.recipients_hash != &first_message.content.recipients_hash { return Err(DKGError::DifferentRecipientsHash); } - // The public keys are the secret commitments of the participants - let public_key = - PublicKey::from_point( - *message.content.point_polynomial.first().expect( - "This never fails because the minimum threshold of the protocol is 2", - ), - ); + let content = &message.content; + let point_polynomial = &content.point_polynomial; + let ciphertexts = &content.ciphertexts; + + let public_key = PublicKey::from_point( + *point_polynomial + .first() + .expect("This never fails because the minimum threshold is 2"), + ); public_keys.push(public_key); - proofs_of_possession.push(message.content.proof_of_possession); + proofs_of_possession.push(content.proof_of_possession); - senders.push(message.content.sender); + senders.push(content.sender); signatures.push(message.signature); - // Recreate the encryption environment - let mut enc = merlin::Transcript::new(b"Encryption"); - message.content.parameters.commit(&mut enc); - enc.commit_point(b"contributor", message.content.sender.as_compressed()); - - let point_polynomial = &message.content.point_polynomial; - let ciphertexts = &message.content.ciphertexts; + let mut encryption_transcript = merlin::Transcript::new(b"Encryption"); + parameters.commit(&mut encryption_transcript); + encryption_transcript.commit_point(b"contributor", content.sender.as_compressed()); + encryption_transcript.append_message(b"nonce", &content.encryption_nonce); if point_polynomial.len() != threshold - 1 { return Err(DKGError::IncorrectNumberOfCommitments); } - if ciphertexts.len() != participants { return Err(DKGError::IncorrectNumberOfEncryptedShares); } - let encryption_nonce = message.content.encryption_nonce; - enc.append_message(b"nonce", &encryption_nonce); - - let message_bytes = &message.content.to_bytes(); + let mut signature_transcript = Transcript::new(b"signature"); + signature_transcript.append_message(b"message", &content.to_bytes()); + signatures_transcripts.push(signature_transcript); - let mut t_sig = Transcript::new(b"signature"); - t_sig.append_message(b"message", message_bytes); - - let mut t_pop = Transcript::new(b"pop"); + let mut pop_transcript = Transcript::new(b"pop"); let secret_commitment = point_polynomial .first() .expect("This never fails because the minimum threshold is 2"); - t_pop.append_message(b"secret commitment", secret_commitment.compress().as_bytes()); - - t_sigs.push(t_sig); - t_pops.push(t_pop); + pop_transcript + .append_message(b"secret commitment", secret_commitment.compress().as_bytes()); + pops_transcripts.push(pop_transcript); if total_polynomial_commitment.is_empty() { total_polynomial_commitment = point_polynomial.clone(); @@ -222,23 +204,24 @@ impl Keypair { sum_commitments(&[&total_polynomial_commitment, point_polynomial])?; } - let ephemeral_key = message.content.ephemeral_key; - let key_exchange = self.secret.key * message.content.ephemeral_key.into_point(); - + let key_exchange = self.secret.key * content.ephemeral_key.into_point(); for (i, ciphertext) in ciphertexts.iter().enumerate() { - let identifier = generate_identifier(recipients_hash, i as u16); + if identifiers.len() != participants { + let identifier = + generate_identifier(&first_message.content.recipients_hash, i as u16); + identifiers.push(identifier); + } if let Ok(secret_share) = decrypt( - enc.clone(), - &ephemeral_key, + encryption_transcript.clone(), &self.public, &key_exchange, ciphertext, - &encryption_nonce, + &content.encryption_nonce, i, ) { if secret_share * GENERATOR - == evaluate_polynomial_commitment(&identifier, point_polynomial) + == evaluate_polynomial_commitment(&identifiers[i], point_polynomial) { secret_shares.push(secret_share); break; @@ -246,38 +229,29 @@ impl Keypair { } } - total_secret_share += secret_shares[j]; - + total_secret_share += secret_shares.get(j).ok_or(DKGError::InvalidSecretShare)?; group_point += secret_commitment; } - for i in 0..participants { - let identifier = generate_identifier(recipients_hash, i as u16); - verifying_keys - .push(evaluate_polynomial_commitment(&identifier, &total_polynomial_commitment)); - } - - if secret_shares.len() != messages[0].content.parameters.participants as usize { - return Err(DKGError::IncorrectNumberOfValidSecretShares { - expected: messages[0].content.parameters.participants as usize, - actual: secret_shares.len(), - }); - } - - verify_batch(t_pops, &proofs_of_possession[..], &public_keys[..], false) + verify_batch(&mut pops_transcripts, &proofs_of_possession, &public_keys, false) .map_err(DKGError::InvalidProofOfPossession)?; - verify_batch(t_sigs, &signatures[..], &senders[..], false) + verify_batch(&mut signatures_transcripts, &signatures, &senders, false) .map_err(DKGError::InvalidSignature)?; + for i in 0..participants { + verifying_keys.push(evaluate_polynomial_commitment( + &identifiers[i], + &total_polynomial_commitment, + )); + } + let dkg_output_content = DKGOutputContent::new(PublicKey::from_point(group_point), verifying_keys); + let mut dkg_output_transcript = Transcript::new(b"dkg output"); + dkg_output_transcript.append_message(b"content", &dkg_output_content.to_bytes()); - let mut transcript = Transcript::new(b"dkg output"); - transcript.append_message(b"content", &dkg_output_content.to_bytes()); - - let signature = self.sign(transcript); - + let signature = self.sign(dkg_output_transcript); let dkg_output = DKGOutput::new(self.public, dkg_output_content, signature); Ok((dkg_output, total_secret_share)) diff --git a/src/olaf/tests.rs b/src/olaf/tests.rs index 2d9a365..61e73f6 100644 --- a/src/olaf/tests.rs +++ b/src/olaf/tests.rs @@ -208,15 +208,7 @@ mod tests { ) .unwrap(); - decrypt( - t, - &ephemeral_key.public, - &recipient.public, - &key_exchange, - &encrypted_share, - &encryption_nonce, - 0, - ) - .unwrap(); + decrypt(t, &recipient.public, &key_exchange, &encrypted_share, &encryption_nonce, 0) + .unwrap(); } } diff --git a/src/olaf/utils.rs b/src/olaf/utils.rs index 89896df..eec840d 100644 --- a/src/olaf/utils.rs +++ b/src/olaf/utils.rs @@ -8,7 +8,6 @@ use crate::{context::SigningTranscript, PublicKey, SecretKey}; use super::{ data_structures::ENCRYPTION_NONCE_LENGTH, errors::{DKGError, DKGResult}, - GENERATOR, }; pub(crate) fn generate_identifier(recipients_hash: &[u8; 16], index: u16) -> Scalar { @@ -108,10 +107,7 @@ pub(crate) fn encrypt( i: usize, ) -> DKGResult> { transcript.commit_bytes(b"i", &i.to_le_bytes()); - transcript.commit_point(b"contributor", &(ephemeral_key * GENERATOR).compress()); transcript.commit_point(b"recipient", recipient.as_compressed()); - - transcript.commit_bytes(b"nonce", nonce); transcript.commit_point(b"key exchange", &(ephemeral_key * recipient.as_point()).compress()); let mut key: GenericArray::KeySize> = @@ -132,7 +128,6 @@ pub(crate) fn encrypt( pub(crate) fn decrypt( mut transcript: T, - contributor: &PublicKey, recipient: &PublicKey, key_exchange: &RistrettoPoint, encrypted_scalar: &[u8], @@ -140,9 +135,7 @@ pub(crate) fn decrypt( i: usize, ) -> DKGResult { transcript.commit_bytes(b"i", &i.to_le_bytes()); - transcript.commit_point(b"contributor", contributor.as_compressed()); transcript.commit_point(b"recipient", recipient.as_compressed()); - transcript.commit_bytes(b"nonce", nonce); transcript.commit_point(b"key exchange", &key_exchange.compress()); let mut key: GenericArray::KeySize> = From 059b601d57c6f95a173fcf12765df2ce1356667f Mon Sep 17 00:00:00 2001 From: Fiono11 Date: Mon, 6 May 2024 15:11:11 +0100 Subject: [PATCH 03/60] Add tests --- Cargo.toml | 28 +- src/olaf/data_structures.rs | 142 +++++++++- src/olaf/simplpedpop.rs | 2 +- src/olaf/tests.rs | 519 +++++++++++++++++++++++------------- src/olaf/utils.rs | 35 ++- 5 files changed, 516 insertions(+), 210 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a5a71a9..7797028 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,7 @@ curve25519-dalek = { version = "4.1.0", default-features = false, features = [ "zeroize", "precomputed-tables", "legacy_compatibility", - "rand_core" + "rand_core", ] } subtle = { version = "2.4.1", default-features = false } merlin = { version = "3.0.0", default-features = false } @@ -33,7 +33,9 @@ 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"] } +zeroize = { version = "1.6", default-features = false, features = [ + "zeroize_derive", +] } chacha20poly1305 = { version = "0.10.1", default-features = false } [dev-dependencies] @@ -58,13 +60,29 @@ harness = false 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", "chacha20poly1305/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", +] asm = ["sha2/asm"] serde = ["serde_crate", "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 diff --git a/src/olaf/data_structures.rs b/src/olaf/data_structures.rs index bedf584..7a58f33 100644 --- a/src/olaf/data_structures.rs +++ b/src/olaf/data_structures.rs @@ -150,19 +150,16 @@ impl MessageContent { pub fn from_bytes(bytes: &[u8]) -> Result { let mut cursor = 0; - // Deserialize PublicKey let sender = PublicKey::from_bytes(&bytes[cursor..cursor + PUBLIC_KEY_LENGTH]) .map_err(DKGError::InvalidPublicKey)?; cursor += PUBLIC_KEY_LENGTH; - // Deserialize encryption_nonce let encryption_nonce: [u8; ENCRYPTION_NONCE_LENGTH] = bytes [cursor..cursor + ENCRYPTION_NONCE_LENGTH] .try_into() .map_err(DKGError::DeserializationError)?; cursor += ENCRYPTION_NONCE_LENGTH; - // Deserialize Parameters let participants = u16::from_le_bytes( bytes[cursor..cursor + U16_LENGTH] .try_into() @@ -283,15 +280,12 @@ impl DKGOutputContent { pub fn to_bytes(&self) -> Vec { let mut bytes = Vec::new(); - // Serialize the group public key let compressed_public_key = self.group_public_key.as_compressed(); // Assuming PublicKey can be compressed directly bytes.extend(compressed_public_key.to_bytes().iter()); - // Serialize the number of verifying keys let key_count = self.verifying_keys.len() as u16; bytes.extend(key_count.to_le_bytes()); - // Serialize each verifying key for key in &self.verifying_keys { let compressed_key = key.compress(); bytes.extend(compressed_key.to_bytes()); @@ -306,7 +300,6 @@ impl DKGOutputContent { pub fn from_bytes(bytes: &[u8]) -> Result { let mut cursor = 0; - // Deserialize the group public key let public_key_bytes = &bytes[cursor..cursor + PUBLIC_KEY_LENGTH]; // Ristretto points are 32 bytes when compressed cursor += PUBLIC_KEY_LENGTH; let compressed_public_key = CompressedRistretto::from_slice(public_key_bytes) @@ -314,13 +307,11 @@ impl DKGOutputContent { let group_public_key = compressed_public_key.decompress().ok_or(DKGError::InvalidRistrettoPoint)?; - // Deserialize the number of verifying keys let key_count_bytes = &bytes[cursor..cursor + U16_LENGTH]; cursor += U16_LENGTH; let key_count = u16::from_le_bytes(key_count_bytes.try_into().map_err(DKGError::DeserializationError)?); - // Deserialize each verifying key let mut verifying_keys = Vec::with_capacity(key_count as usize); for _ in 0..key_count { let key_bytes = &bytes[cursor..cursor + COMPRESSED_RISTRETTO_LENGTH]; @@ -337,3 +328,136 @@ impl DKGOutputContent { }) } } + +#[cfg(test)] +mod tests { + use merlin::Transcript; + use rand_core::OsRng; + use crate::Keypair; + use super::*; + + #[test] + fn test_serialize_deserialize_all_message() { + let sender = Keypair::generate(); + let encryption_nonce = [1u8; ENCRYPTION_NONCE_LENGTH]; + let parameters = Parameters { participants: 2, threshold: 1 }; + let recipients_hash = [2u8; RECIPIENTS_HASH_LENGTH]; + let point_polynomial = + vec![RistrettoPoint::random(&mut OsRng), RistrettoPoint::random(&mut OsRng)]; + let ciphertexts = vec![vec![1; CHACHA20POLY1305_LENGTH], vec![1; CHACHA20POLY1305_LENGTH]]; + let proof_of_possession = sender.sign(Transcript::new(b"pop")); + let signature = sender.sign(Transcript::new(b"sig")); + let ephemeral_key = PublicKey::from_point(RistrettoPoint::random(&mut OsRng)); + + let message_content = MessageContent::new( + sender.public, + encryption_nonce, + parameters, + recipients_hash, + point_polynomial, + ciphertexts, + ephemeral_key, + proof_of_possession, + ); + + let message = AllMessage::new(message_content, signature); + + let bytes = message.to_bytes(); + + let deserialized_message = AllMessage::from_bytes(&bytes).expect("Failed to deserialize"); + + assert_eq!(message.content.sender, deserialized_message.content.sender); + + assert_eq!(message.content.encryption_nonce, deserialized_message.content.encryption_nonce); + + assert_eq!( + message.content.parameters.participants, + deserialized_message.content.parameters.participants + ); + + assert_eq!( + message.content.parameters.threshold, + deserialized_message.content.parameters.threshold + ); + + assert_eq!(message.content.recipients_hash, deserialized_message.content.recipients_hash); + + assert!(message + .content + .point_polynomial + .iter() + .zip(deserialized_message.content.point_polynomial.iter()) + .all(|(a, b)| a.compress() == b.compress())); + + assert!(message + .content + .ciphertexts + .iter() + .zip(deserialized_message.content.ciphertexts.iter()) + .all(|(a, b)| a == b)); + + assert_eq!( + message.content.proof_of_possession, + deserialized_message.content.proof_of_possession + ); + + assert_eq!(message.signature, deserialized_message.signature); + } + + #[test] + fn test_dkg_output_serialization() { + let mut rng = OsRng; + let group_public_key = RistrettoPoint::random(&mut rng); + let verifying_keys = vec![ + RistrettoPoint::random(&mut rng), + RistrettoPoint::random(&mut rng), + RistrettoPoint::random(&mut rng), + ]; + + let dkg_output_content = DKGOutputContent { + group_public_key: PublicKey::from_point(group_public_key), + verifying_keys, + }; + + let keypair = Keypair::generate(); + let signature = keypair.sign(Transcript::new(b"test")); + + let dkg_output = + DKGOutput { sender: keypair.public, content: dkg_output_content, signature }; + + // Serialize the DKGOutput + let bytes = dkg_output.to_bytes(); + + // Deserialize the DKGOutput + let deserialized_dkg_output = + DKGOutput::from_bytes(&bytes).expect("Deserialization failed"); + + // Check if the deserialized content matches the original + assert_eq!( + deserialized_dkg_output.content.group_public_key.as_compressed(), + dkg_output.content.group_public_key.as_compressed(), + "Group public keys do not match" + ); + + assert_eq!( + deserialized_dkg_output.content.verifying_keys.len(), + dkg_output.content.verifying_keys.len(), + "Verifying keys counts do not match" + ); + + assert!( + deserialized_dkg_output + .content + .verifying_keys + .iter() + .zip(dkg_output.content.verifying_keys.iter()) + .all(|(a, b)| a == b), + "Verifying keys do not match" + ); + + assert_eq!( + deserialized_dkg_output.signature.s, dkg_output.signature.s, + "Signatures do not match" + ); + } +} diff --git a/src/olaf/simplpedpop.rs b/src/olaf/simplpedpop.rs index fd479aa..e67cac6 100644 --- a/src/olaf/simplpedpop.rs +++ b/src/olaf/simplpedpop.rs @@ -80,7 +80,7 @@ impl Keypair { i as usize, ) }) - .collect::>()?; + .collect::>>>()?; let pk = &PublicKey::from_point( *point_polynomial diff --git a/src/olaf/tests.rs b/src/olaf/tests.rs index 61e73f6..f9ec2da 100644 --- a/src/olaf/tests.rs +++ b/src/olaf/tests.rs @@ -1,214 +1,349 @@ #[cfg(test)] mod tests { - use crate::olaf::data_structures::{ - AllMessage, DKGOutput, DKGOutputContent, MessageContent, Parameters, - CHACHA20POLY1305_LENGTH, ENCRYPTION_NONCE_LENGTH, RECIPIENTS_HASH_LENGTH, - }; - use crate::olaf::utils::{decrypt, encrypt}; - use crate::olaf::GENERATOR; - use crate::{Keypair, PublicKey}; - use alloc::vec::Vec; - use curve25519_dalek::ristretto::RistrettoPoint; - use curve25519_dalek::scalar::Scalar; - use merlin::Transcript; - use rand::rngs::OsRng; - - #[test] - fn test_simplpedpop_protocol() { - // Create participants - let threshold = 2; - let participants = 2; - let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); - let public_keys: Vec = keypairs.iter().map(|kp| kp.public).collect(); - - // Each participant creates an AllMessage - let mut all_messages = Vec::new(); - for i in 0..participants { - let message: AllMessage = - keypairs[i].simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap(); - all_messages.push(message); + mod simplpedpop { + use crate::olaf::data_structures::{ + AllMessage, Parameters, CHACHA20POLY1305_LENGTH, RECIPIENTS_HASH_LENGTH, + }; + use crate::olaf::errors::DKGError; + use crate::olaf::{GENERATOR, MINIMUM_THRESHOLD}; + use crate::{Keypair, PublicKey}; + use alloc::vec::Vec; + use curve25519_dalek::ristretto::RistrettoPoint; + use curve25519_dalek::traits::Identity; + use merlin::Transcript; + + #[test] + fn test_simplpedpop_protocol() { + let threshold = 2; + let participants = 2; + let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public).collect(); + + let mut all_messages = Vec::new(); + for i in 0..participants { + let message: AllMessage = + keypairs[i].simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap(); + all_messages.push(message); + } + + let mut dkg_outputs = Vec::new(); + + for kp in keypairs.iter() { + let dkg_output = kp.simplpedpop_recipient_all(&all_messages).unwrap(); + dkg_outputs.push(dkg_output); + } + + // Verify that all DKG outputs are equal for group_public_key and verifying_keys + assert!( + dkg_outputs.windows(2).all(|w| w[0].0.content.group_public_key + == w[1].0.content.group_public_key + && w[0].0.content.verifying_keys.len() == w[1].0.content.verifying_keys.len() + && w[0] + .0 + .content + .verifying_keys + .iter() + .zip(w[1].0.content.verifying_keys.iter()) + .all(|(a, b)| a == b)), + "All DKG outputs should have identical group public keys and verifying keys." + ); + + // Verify that all verifying_keys are valid + for i in 0..participants { + for j in 0..participants { + assert_eq!( + dkg_outputs[i].0.content.verifying_keys[j].compress(), + (dkg_outputs[j].1 * GENERATOR).compress(), + "Verification of total secret shares failed!" + ); + } + } } - let mut dkg_outputs = Vec::new(); + #[test] + fn test_insufficient_messages_below_threshold() { + let threshold = 3; + let participants = 5; - for kp in keypairs.iter() { - let dkg_output = kp.simplpedpop_recipient_all(&all_messages).unwrap(); - dkg_outputs.push(dkg_output); + let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); + + let messages: Vec = keypairs + .iter() + .map(|kp| kp.simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap()) + .take(MINIMUM_THRESHOLD as usize - 1) + .collect(); + + let result = keypairs[0].simplpedpop_recipient_all(&messages); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + DKGError::InvalidNumberOfMessages => assert!(true), + _ => { + panic!("Expected DKGError::DifferentRecipientsHash, but got {:?}", e) + }, + }, + } } - // Verify that all DKG outputs are equal for group_public_key and verifying_keys - assert!( - dkg_outputs.windows(2).all(|w| w[0].0.content.group_public_key - == w[1].0.content.group_public_key - && w[0].0.content.verifying_keys.len() == w[1].0.content.verifying_keys.len() - && w[0] - .0 - .content - .verifying_keys - .iter() - .zip(w[1].0.content.verifying_keys.iter()) - .all(|(a, b)| a == b)), - "All DKG outputs should have identical group public keys and verifying keys." - ); - - // Verify that all verifying_keys are valid - for i in 0..participants { - for j in 0..participants { - assert_eq!( - dkg_outputs[i].0.content.verifying_keys[j].compress(), - (dkg_outputs[j].1 * GENERATOR).compress(), - "Verification of total secret shares failed!" - ); + #[test] + fn test_different_parameters() { + // Define threshold and participants + let threshold = 3; + let participants = 5; + + // Generate keypairs for participants + let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); + + // Each participant creates an AllMessage with different parameters + let mut messages: Vec = Vec::new(); + for i in 0..participants { + let mut parameters = Parameters::generate(participants as u16, threshold); + // Modify parameters for the first participant + if i == 0 { + parameters.threshold += 1; // Modify threshold + } + let message = keypairs[i] + .simplpedpop_contribute_all(parameters.threshold, public_keys.clone()) + .unwrap(); + messages.push(message); + } + + // Call simplpedpop_recipient_all + let result = keypairs[0].simplpedpop_recipient_all(&messages); + + // Check if the result is an error + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + DKGError::DifferentParameters => assert!(true), + _ => panic!("Expected DKGError::DifferentRecipientsHash, but got {:?}", e), + }, } } - } - #[test] - fn test_serialize_deserialize_all_message() { - let sender = Keypair::generate(); - let encryption_nonce = [1u8; ENCRYPTION_NONCE_LENGTH]; - let parameters = Parameters { participants: 2, threshold: 1 }; - let recipients_hash = [2u8; RECIPIENTS_HASH_LENGTH]; - let point_polynomial = - vec![RistrettoPoint::random(&mut OsRng), RistrettoPoint::random(&mut OsRng)]; - let ciphertexts = vec![vec![1; CHACHA20POLY1305_LENGTH], vec![1; CHACHA20POLY1305_LENGTH]]; - let proof_of_possession = sender.sign(Transcript::new(b"pop")); - let signature = sender.sign(Transcript::new(b"sig")); - let ephemeral_key = PublicKey::from_point(RistrettoPoint::random(&mut OsRng)); - - let message_content = MessageContent::new( - sender.public, - encryption_nonce, - parameters, - recipients_hash, - point_polynomial, - ciphertexts, - ephemeral_key, - proof_of_possession, - ); - - let message = AllMessage::new(message_content, signature); - - let bytes = message.to_bytes(); - - let deserialized_message = AllMessage::from_bytes(&bytes).expect("Failed to deserialize"); - - assert_eq!(message.content.sender, deserialized_message.content.sender); - - assert_eq!(message.content.encryption_nonce, deserialized_message.content.encryption_nonce); - - assert_eq!( - message.content.parameters.participants, - deserialized_message.content.parameters.participants - ); - - assert_eq!( - message.content.parameters.threshold, - deserialized_message.content.parameters.threshold - ); - - assert_eq!(message.content.recipients_hash, deserialized_message.content.recipients_hash); - - assert!(message - .content - .point_polynomial - .iter() - .zip(deserialized_message.content.point_polynomial.iter()) - .all(|(a, b)| a.compress() == b.compress())); - - assert!(message - .content - .ciphertexts - .iter() - .zip(deserialized_message.content.ciphertexts.iter()) - .all(|(a, b)| a == b)); - - assert_eq!( - message.content.proof_of_possession, - deserialized_message.content.proof_of_possession - ); - - assert_eq!(message.signature, deserialized_message.signature); - } + #[test] + fn test_different_recipients_hash() { + let threshold = 3; + let participants = 5; - #[test] - fn test_dkg_output_serialization() { - let mut rng = OsRng; - let group_public_key = RistrettoPoint::random(&mut rng); - let verifying_keys = vec![ - RistrettoPoint::random(&mut rng), - RistrettoPoint::random(&mut rng), - RistrettoPoint::random(&mut rng), - ]; - - let dkg_output_content = DKGOutputContent { - group_public_key: PublicKey::from_point(group_public_key), - verifying_keys, - }; + let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); - let keypair = Keypair::generate(); - let signature = keypair.sign(Transcript::new(b"test")); + let mut messages: Vec = keypairs + .iter() + .map(|kp| kp.simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap()) + .collect(); - let dkg_output = - DKGOutput { sender: keypair.public, content: dkg_output_content, signature }; + messages[1].content.recipients_hash = [1; RECIPIENTS_HASH_LENGTH]; - // Serialize the DKGOutput - let bytes = dkg_output.to_bytes(); + let result = keypairs[0].simplpedpop_recipient_all(&messages); - // Deserialize the DKGOutput - let deserialized_dkg_output = - DKGOutput::from_bytes(&bytes).expect("Deserialization failed"); + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + DKGError::DifferentRecipientsHash => assert!(true), + _ => { + panic!("Expected DKGError::DifferentRecipientsHash, but got {:?}", e) + }, + }, + } + } - // Check if the deserialized content matches the original - assert_eq!( - deserialized_dkg_output.content.group_public_key.as_compressed(), - dkg_output.content.group_public_key.as_compressed(), - "Group public keys do not match" - ); + #[test] + fn test_incorrect_number_of_commitments() { + let threshold = 3; + let participants = 5; - assert_eq!( - deserialized_dkg_output.content.verifying_keys.len(), - dkg_output.content.verifying_keys.len(), - "Verifying keys counts do not match" - ); + let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); - assert!( - deserialized_dkg_output - .content - .verifying_keys + let mut messages: Vec = keypairs .iter() - .zip(dkg_output.content.verifying_keys.iter()) - .all(|(a, b)| a == b), - "Verifying keys do not match" - ); - - assert_eq!( - deserialized_dkg_output.signature.s, dkg_output.signature.s, - "Signatures do not match" - ); - } + .map(|kp| kp.simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap()) + .collect(); + + messages[1].content.point_polynomial.pop(); + + let result = keypairs[0].simplpedpop_recipient_all(&messages); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + DKGError::IncorrectNumberOfCommitments => assert!(true), + _ => panic!("Expected DKGError::IncorrectNumberOfCommitments, but got {:?}", e), + }, + } + } + + #[test] + fn test_incorrect_number_of_encrypted_shares() { + let threshold = 3; + let participants = 5; + + let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); + + let mut messages: Vec = keypairs + .iter() + .map(|kp| kp.simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap()) + .collect(); + + messages[1].content.ciphertexts.pop(); + + let result = keypairs[0].simplpedpop_recipient_all(&messages); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + DKGError::IncorrectNumberOfEncryptedShares => assert!(true), + _ => panic!( + "Expected DKGError::IncorrectNumberOfEncryptedShares, but got {:?}", + e + ), + }, + } + } + + #[test] + fn test_invalid_secret_share() { + let threshold = 3; + let participants = 5; + + let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); - #[test] - fn test_encryption_decryption() { - let mut rng = OsRng; - let ephemeral_key = Keypair::generate(); - let recipient = Keypair::generate(); - let encryption_nonce = [1; ENCRYPTION_NONCE_LENGTH]; - let t = Transcript::new(b"label"); - let key_exchange = ephemeral_key.secret.key * recipient.public.as_point(); - let plaintext = Scalar::random(&mut rng); - - let encrypted_share = encrypt( - &plaintext, - &ephemeral_key.secret.key, - t.clone(), - &recipient.public, - &encryption_nonce, - 0, - ) - .unwrap(); - - decrypt(t, &recipient.public, &key_exchange, &encrypted_share, &encryption_nonce, 0) - .unwrap(); + let mut messages: Vec = keypairs + .iter() + .map(|kp| kp.simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap()) + .collect(); + + messages[1].content.ciphertexts[0] = vec![1; CHACHA20POLY1305_LENGTH]; + + let result = keypairs[0].simplpedpop_recipient_all(&messages[0..participants - 1]); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + DKGError::InvalidSecretShare => assert!(true), + _ => panic!("Expected DKGError::InvalidSecretShare, but got {:?}", e), + }, + } + } + + #[test] + fn test_invalid_proof_of_possession() { + let threshold = 3; + let participants = 5; + + let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); + + let mut messages: Vec = keypairs + .iter() + .map(|kp| kp.simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap()) + .collect(); + + messages[1].content.proof_of_possession = + keypairs[1].secret.sign(Transcript::new(b"invalid"), &keypairs[1].public); + + let result = keypairs[0].simplpedpop_recipient_all(&messages[0..participants - 1]); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + DKGError::InvalidProofOfPossession(_) => assert!(true), + _ => panic!("Expected DKGError::InvalidProofOfPossession, but got {:?}", e), + }, + } + } + + #[test] + fn test_invalid_signature() { + let threshold = 3; + let participants = 5; + + let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); + + let mut messages: Vec = keypairs + .iter() + .map(|kp| kp.simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap()) + .collect(); + + messages[1].signature = + keypairs[1].secret.sign(Transcript::new(b"invalid"), &keypairs[1].public); + + let result = keypairs[0].simplpedpop_recipient_all(&messages[0..participants - 1]); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + DKGError::InvalidSignature(_) => assert!(true), + _ => panic!("Expected DKGError::InvalidSignature, but got {:?}", e), + }, + } + } + + #[test] + fn test_invalid_threshold() { + let keypair = Keypair::generate(); + let result = keypair.simplpedpop_contribute_all( + 1, + vec![ + PublicKey::from_point(RistrettoPoint::identity()), + PublicKey::from_point(RistrettoPoint::identity()), + ], + ); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + DKGError::InsufficientThreshold => assert!(true), + _ => panic!("Expected DKGError::DifferentRecipientsHash, but got {:?}", e), + }, + } + } + + #[test] + fn test_invalid_participants() { + let keypair = Keypair::generate(); + let result = keypair.simplpedpop_contribute_all( + 2, + vec![PublicKey::from_point(RistrettoPoint::identity())], + ); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + DKGError::InvalidNumberOfParticipants => assert!(true), + _ => { + panic!("Expected DKGError::DifferentRecipientsHash, but got {:?}", e) + }, + }, + } + } + + #[test] + fn test_threshold_greater_than_participants() { + let keypair = Keypair::generate(); + let result = keypair.simplpedpop_contribute_all( + 3, + vec![ + PublicKey::from_point(RistrettoPoint::identity()), + PublicKey::from_point(RistrettoPoint::identity()), + ], + ); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + DKGError::ExcessiveThreshold => assert!(true), + _ => panic!("Expected DKGError::DifferentRecipientsHash, but got {:?}", e), + }, + } + } } } diff --git a/src/olaf/utils.rs b/src/olaf/utils.rs index eec840d..a69f6d9 100644 --- a/src/olaf/utils.rs +++ b/src/olaf/utils.rs @@ -38,14 +38,11 @@ pub(crate) fn generate_coefficients( ) -> Vec { let mut coefficients = Vec::with_capacity(size); - // Ensure the first coefficient is not zero let mut first = Scalar::random(rng); while first == Scalar::ZERO { first = Scalar::random(rng); } coefficients.push(first); - - // Generate the remaining coefficients coefficients.extend(iter::repeat_with(|| Scalar::random(rng)).take(size - 1)); coefficients @@ -154,3 +151,35 @@ pub(crate) fn decrypt( Ok(Scalar::from_bytes_mod_order(bytes)) } + +#[cfg(test)] +mod tests { + use merlin::Transcript; + use crate::Keypair; + use rand_core::OsRng; + use super::*; + + #[test] + fn test_encryption_decryption() { + let mut rng = OsRng; + let ephemeral_key = Keypair::generate(); + let recipient = Keypair::generate(); + let encryption_nonce = [1; ENCRYPTION_NONCE_LENGTH]; + let t = Transcript::new(b"label"); + let key_exchange = ephemeral_key.secret.key * recipient.public.as_point(); + let plaintext = Scalar::random(&mut rng); + + let encrypted_share = encrypt( + &plaintext, + &ephemeral_key.secret.key, + t.clone(), + &recipient.public, + &encryption_nonce, + 0, + ) + .unwrap(); + + decrypt(t, &recipient.public, &key_exchange, &encrypted_share, &encryption_nonce, 0) + .unwrap(); + } +} From bbb6b741969ebcf631a55dd338437114216f8cab Mon Sep 17 00:00:00 2001 From: Fiono11 Date: Mon, 6 May 2024 15:20:35 +0100 Subject: [PATCH 04/60] Fix tests --- src/olaf/simplpedpop.rs | 10 +++++----- src/olaf/tests.rs | 17 +++++++++-------- src/olaf/utils.rs | 1 + 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/olaf/simplpedpop.rs b/src/olaf/simplpedpop.rs index e67cac6..7ee4c1c 100644 --- a/src/olaf/simplpedpop.rs +++ b/src/olaf/simplpedpop.rs @@ -17,7 +17,7 @@ use super::{ evaluate_polynomial_commitment, generate_coefficients, generate_identifier, sum_commitments, }, - GENERATOR, MINIMUM_THRESHOLD, + GENERATOR, }; impl Keypair { @@ -126,10 +126,6 @@ impl Keypair { &self, messages: &[AllMessage], ) -> DKGResult<(DKGOutput, Scalar)> { - if messages.len() < MINIMUM_THRESHOLD as usize { - return Err(DKGError::InvalidNumberOfMessages); - } - let first_message = &messages[0]; let parameters = &first_message.content.parameters; let threshold = parameters.threshold as usize; @@ -137,6 +133,10 @@ impl Keypair { first_message.content.parameters.validate()?; + if messages.len() < participants { + return Err(DKGError::InvalidNumberOfMessages); + } + let mut secret_shares = Vec::with_capacity(participants); let mut verifying_keys = Vec::with_capacity(participants); let mut public_keys = Vec::with_capacity(participants); diff --git a/src/olaf/tests.rs b/src/olaf/tests.rs index f9ec2da..91157bf 100644 --- a/src/olaf/tests.rs +++ b/src/olaf/tests.rs @@ -5,7 +5,7 @@ mod tests { AllMessage, Parameters, CHACHA20POLY1305_LENGTH, RECIPIENTS_HASH_LENGTH, }; use crate::olaf::errors::DKGError; - use crate::olaf::{GENERATOR, MINIMUM_THRESHOLD}; + use crate::olaf::GENERATOR; use crate::{Keypair, PublicKey}; use alloc::vec::Vec; use curve25519_dalek::ristretto::RistrettoPoint; @@ -61,19 +61,20 @@ mod tests { } #[test] - fn test_insufficient_messages_below_threshold() { + fn test_invalid_number_of_messages() { let threshold = 3; let participants = 5; let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); - let messages: Vec = keypairs + let mut messages: Vec = keypairs .iter() .map(|kp| kp.simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap()) - .take(MINIMUM_THRESHOLD as usize - 1) .collect(); + messages.pop(); + let result = keypairs[0].simplpedpop_recipient_all(&messages); match result { @@ -81,7 +82,7 @@ mod tests { Err(e) => match e { DKGError::InvalidNumberOfMessages => assert!(true), _ => { - panic!("Expected DKGError::DifferentRecipientsHash, but got {:?}", e) + panic!("Expected DKGError::InvalidNumberOfMessages, but got {:?}", e) }, }, } @@ -222,7 +223,7 @@ mod tests { messages[1].content.ciphertexts[0] = vec![1; CHACHA20POLY1305_LENGTH]; - let result = keypairs[0].simplpedpop_recipient_all(&messages[0..participants - 1]); + let result = keypairs[0].simplpedpop_recipient_all(&messages); match result { Ok(_) => panic!("Expected an error, but got Ok."), @@ -249,7 +250,7 @@ mod tests { messages[1].content.proof_of_possession = keypairs[1].secret.sign(Transcript::new(b"invalid"), &keypairs[1].public); - let result = keypairs[0].simplpedpop_recipient_all(&messages[0..participants - 1]); + let result = keypairs[0].simplpedpop_recipient_all(&messages); match result { Ok(_) => panic!("Expected an error, but got Ok."), @@ -276,7 +277,7 @@ mod tests { messages[1].signature = keypairs[1].secret.sign(Transcript::new(b"invalid"), &keypairs[1].public); - let result = keypairs[0].simplpedpop_recipient_all(&messages[0..participants - 1]); + let result = keypairs[0].simplpedpop_recipient_all(&messages); match result { Ok(_) => panic!("Expected an error, but got Ok."), diff --git a/src/olaf/utils.rs b/src/olaf/utils.rs index a69f6d9..0abdd41 100644 --- a/src/olaf/utils.rs +++ b/src/olaf/utils.rs @@ -42,6 +42,7 @@ pub(crate) fn generate_coefficients( while first == Scalar::ZERO { first = Scalar::random(rng); } + coefficients.push(first); coefficients.extend(iter::repeat_with(|| Scalar::random(rng)).take(size - 1)); From 9f645440cc4ce8d746cb8e153805ea26925a442a Mon Sep 17 00:00:00 2001 From: Fiono11 Date: Mon, 6 May 2024 16:03:01 +0100 Subject: [PATCH 05/60] WIP --- src/lib.rs | 2 +- src/olaf/frost/data_structures.rs | 141 ++++++++++++++++++ src/olaf/frost/errors.rs | 25 ++++ src/olaf/frost/mod.rs | 41 +++++ src/olaf/mod.rs | 5 +- src/olaf/{ => simplpedpop}/data_structures.rs | 7 +- src/olaf/{ => simplpedpop}/errors.rs | 2 +- .../{simplpedpop.rs => simplpedpop/mod.rs} | 36 ++--- src/olaf/{ => simplpedpop}/tests.rs | 6 +- src/olaf/{ => simplpedpop}/utils.rs | 0 10 files changed, 236 insertions(+), 29 deletions(-) create mode 100644 src/olaf/frost/data_structures.rs create mode 100644 src/olaf/frost/errors.rs create mode 100644 src/olaf/frost/mod.rs rename src/olaf/{ => simplpedpop}/data_structures.rs (98%) rename src/olaf/{ => simplpedpop}/errors.rs (97%) rename src/olaf/{simplpedpop.rs => simplpedpop/mod.rs} (93%) rename src/olaf/{ => simplpedpop}/tests.rs (98%) rename src/olaf/{ => simplpedpop}/utils.rs (100%) diff --git a/src/lib.rs b/src/lib.rs index 5208751..43eef8d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -248,7 +248,7 @@ pub mod derive; pub mod cert; pub mod errors; -#[cfg(all(feature = "alloc", feature = "aead"))] +//#[cfg(all(feature = "alloc", feature = "aead"))] pub mod olaf; #[cfg(all(feature = "aead", feature = "getrandom"))] diff --git a/src/olaf/frost/data_structures.rs b/src/olaf/frost/data_structures.rs new file mode 100644 index 0000000..15d4aad --- /dev/null +++ b/src/olaf/frost/data_structures.rs @@ -0,0 +1,141 @@ +use curve25519_dalek::{RistrettoPoint, Scalar}; +use merlin::Transcript; +use getrandom_or_panic::RngCore; +use zeroize::ZeroizeOnDrop; + +use crate::{context::SigningTranscript, olaf::GENERATOR}; + +/// A scalar that is a signing nonce. +#[derive(Debug, Clone, PartialEq, Eq, ZeroizeOnDrop)] +pub struct Nonce(pub(super) Scalar); + +impl Nonce { + /// Generates a new uniformly random signing nonce by sourcing fresh randomness and combining + /// with the secret signing share, to hedge against a bad RNG. + /// + /// Each participant generates signing nonces before performing a signing + /// operation. + /// + /// An implementation of `nonce_generate(secret)` from the [spec]. + /// + /// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#name-nonce-generation + pub fn new(secret: &Scalar) -> Self { + let mut rng = crate::getrandom_or_panic(); + + let mut random_bytes = [0; 32]; + rng.fill_bytes(&mut random_bytes[..]); + + Self::nonce_generate_from_random_bytes(secret, &random_bytes[..]) + } + + /// Generates a nonce from the given random bytes. + /// This function allows testing and MUST NOT be made public. + pub(crate) fn nonce_generate_from_random_bytes(secret: &Scalar, random_bytes: &[u8]) -> Self { + let mut transcript = Transcript::new(b"nonce_generate_from_random_bytes"); + + transcript.append_message(b"random bytes", random_bytes); + transcript.append_message(b"secret", secret.as_bytes()); + + Self(transcript.challenge_scalar(b"nonce")) + } +} + +/// A group element that is a commitment to a signing nonce share. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct NonceCommitment(pub(super) RistrettoPoint); + +impl From for NonceCommitment { + fn from(nonce: Nonce) -> Self { + From::from(&nonce) + } +} + +impl From<&Nonce> for NonceCommitment { + fn from(nonce: &Nonce) -> Self { + Self(GENERATOR * nonce.0) + } +} + +/// Comprised of hiding and binding nonces. +/// +/// Note that [`SigningNonces`] must be used *only once* for a signing +/// operation; re-using nonces will result in leakage of a signer's long-lived +/// signing key. +#[derive(Debug, Clone, PartialEq, Eq, ZeroizeOnDrop)] +pub struct SigningNonces { + /// The hiding [`Nonce`]. + pub(crate) hiding: Nonce, + /// The binding [`Nonce`]. + pub(crate) binding: Nonce, + /// The commitments to the nonces. This is precomputed to improve + /// sign() performance, since it needs to check if the commitments + /// to the participant's nonces are included in the commitments sent + /// by the Coordinator, and this prevents having to recompute them. + #[zeroize(skip)] + pub(crate) commitments: SigningCommitments, +} + +impl SigningNonces { + /// Generates a new signing nonce. + /// + /// Each participant generates signing nonces before performing a signing + /// operation. + pub fn new(secret: &Scalar) -> Self { + let hiding = Nonce::new(secret); + let binding = Nonce::new(secret); + + Self::from_nonces(hiding, binding) + } + + /// Generates a new [`SigningNonces`] from a pair of [`Nonce`]. + /// + /// # Security + /// + /// SigningNonces MUST NOT be repeated in different FROST signings. + /// Thus, if you're using this method (because e.g. you're writing it + /// to disk between rounds), be careful so that does not happen. + pub fn from_nonces(hiding: Nonce, binding: Nonce) -> Self { + let hiding_commitment = (&hiding).into(); + let binding_commitment = (&binding).into(); + let commitments = SigningCommitments::new(hiding_commitment, binding_commitment); + + Self { hiding, binding, commitments } + } +} + +/// Published by each participant in the first round of the signing protocol. +/// +/// This step can be batched if desired by the implementation. Each +/// SigningCommitment can be used for exactly *one* signature. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub struct SigningCommitments { + /// Commitment to the hiding [`Nonce`]. + pub(crate) hiding: NonceCommitment, + /// Commitment to the binding [`Nonce`]. + pub(crate) binding: NonceCommitment, +} + +impl SigningCommitments { + /// Create new SigningCommitments + pub fn new(hiding: NonceCommitment, binding: NonceCommitment) -> Self { + Self { hiding, binding } + } + + /// Computes the [signature commitment share] from these round one signing commitments. + /// + /// [signature commitment share]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#name-signature-share-verificatio + #[cfg(feature = "cheater-detection")] + pub(super) fn to_group_commitment_share( + self, + binding_factor: &BindingFactor, + ) -> GroupCommitmentShare { + GroupCommitmentShare(self.hiding.0 + (self.binding.0 * binding_factor.0)) + } +} + +impl From<&SigningNonces> for SigningCommitments { + fn from(nonces: &SigningNonces) -> Self { + nonces.commitments + } +} diff --git a/src/olaf/frost/errors.rs b/src/olaf/frost/errors.rs new file mode 100644 index 0000000..4013d25 --- /dev/null +++ b/src/olaf/frost/errors.rs @@ -0,0 +1,25 @@ +//! Errors of the FROST protocol. + +/// A result for the FROST protocol. +pub type FROSTResult = Result; + +/// An error ocurred during the execution of the FROST protocol +#[derive(Debug, Clone, Eq, PartialEq)] +pub enum FROSTError { + /// Incorrect number of signing commitments. + IncorrectNumberOfSigningCommitments, + /// The participant's signing commitment is missing from the Signing Package + MissingSigningCommitment, + /// The participant's signing commitment is incorrect + IncorrectSigningCommitment, + /// This identifier does not belong to a participant in the signing process. + UnknownIdentifier, + /// Commitment equals the identity + IdentitySigningCommitment, + /// Incorrect number of identifiers. + IncorrectNumberOfIdentifiers, + /// Signature verification failed. + InvalidSignature, + /// This identifier is duplicated. + DuplicatedIdentifier, +} diff --git a/src/olaf/frost/mod.rs b/src/olaf/frost/mod.rs new file mode 100644 index 0000000..ddb2177 --- /dev/null +++ b/src/olaf/frost/mod.rs @@ -0,0 +1,41 @@ +//! Implementation of the FROST protocol (). + +pub mod errors; +mod data_structures; + +use alloc::vec::Vec; +use curve25519_dalek::Scalar; +use crate::{Keypair, PublicKey}; +use self::{ + data_structures::{SigningCommitments, SigningNonces}, + errors::FROSTResult, +}; + +impl Keypair { + /// Done once by each participant, to generate _their_ nonces and commitments + /// that are then used during signing. + /// + /// This is only needed if pre-processing is needed (for 1-round FROST). For + /// regular 2-round FROST, use [`commit`]. + /// + /// When performing signing using two rounds, num_nonces would equal 1, to + /// perform the first round. Batching entails generating more than one + /// nonce/commitment pair at a time. Nonces should be stored in secret storage + /// for later use, whereas the commitments are published. + pub fn preprocess( + num_nonces: u8, + secret: &Scalar, + ) -> (Vec, Vec) { + let mut signing_nonces: Vec = Vec::with_capacity(num_nonces as usize); + let mut signing_commitments: Vec = + Vec::with_capacity(num_nonces as usize); + + for _ in 0..num_nonces { + let nonces = SigningNonces::new(secret); + signing_commitments.push(SigningCommitments::from(&nonces)); + signing_nonces.push(nonces); + } + + (signing_nonces, signing_commitments) + } +} diff --git a/src/olaf/mod.rs b/src/olaf/mod.rs index a03e299..916ea93 100644 --- a/src/olaf/mod.rs +++ b/src/olaf/mod.rs @@ -3,11 +3,8 @@ use curve25519_dalek::{constants::RISTRETTO_BASEPOINT_POINT, RistrettoPoint}; -pub mod errors; pub mod simplpedpop; -mod tests; -pub mod data_structures; -mod utils; +pub mod frost; const MINIMUM_THRESHOLD: u16 = 2; const GENERATOR: RistrettoPoint = RISTRETTO_BASEPOINT_POINT; diff --git a/src/olaf/data_structures.rs b/src/olaf/simplpedpop/data_structures.rs similarity index 98% rename from src/olaf/data_structures.rs rename to src/olaf/simplpedpop/data_structures.rs index 7a58f33..581af66 100644 --- a/src/olaf/data_structures.rs +++ b/src/olaf/simplpedpop/data_structures.rs @@ -4,8 +4,11 @@ use alloc::vec::Vec; use curve25519_dalek::{ristretto::CompressedRistretto, RistrettoPoint}; -use crate::{context::SigningTranscript, PublicKey, Signature, PUBLIC_KEY_LENGTH, SIGNATURE_LENGTH}; -use super::{errors::DKGError, MINIMUM_THRESHOLD}; +use crate::{ + context::SigningTranscript, olaf::MINIMUM_THRESHOLD, PublicKey, Signature, PUBLIC_KEY_LENGTH, + SIGNATURE_LENGTH, +}; +use super::{errors::DKGError}; pub(crate) const COMPRESSED_RISTRETTO_LENGTH: usize = 32; pub(crate) const U16_LENGTH: usize = 2; diff --git a/src/olaf/errors.rs b/src/olaf/simplpedpop/errors.rs similarity index 97% rename from src/olaf/errors.rs rename to src/olaf/simplpedpop/errors.rs index 9b8f17e..af00d8c 100644 --- a/src/olaf/errors.rs +++ b/src/olaf/simplpedpop/errors.rs @@ -1,4 +1,4 @@ -//! Errors of the Olaf protocol. +//! Errors of the SimplPedPoP protocol. use core::array::TryFromSliceError; use crate::SignatureError; diff --git a/src/olaf/simplpedpop.rs b/src/olaf/simplpedpop/mod.rs similarity index 93% rename from src/olaf/simplpedpop.rs rename to src/olaf/simplpedpop/mod.rs index 7ee4c1c..23b5558 100644 --- a/src/olaf/simplpedpop.rs +++ b/src/olaf/simplpedpop/mod.rs @@ -1,25 +1,28 @@ //! Implementation of the SimplPedPoP protocol (), 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. +pub mod errors; +mod tests; +pub mod data_structures; +mod utils; + use alloc::vec::Vec; use curve25519_dalek::{traits::Identity, RistrettoPoint, Scalar}; use merlin::Transcript; use rand_core::RngCore; use crate::{context::SigningTranscript, verify_batch, Keypair, PublicKey}; -use super::{ - data_structures::{ - AllMessage, DKGOutput, DKGOutputContent, MessageContent, Parameters, - ENCRYPTION_NONCE_LENGTH, RECIPIENTS_HASH_LENGTH, - }, - errors::{DKGError, DKGResult}, - utils::{ - decrypt, derive_secret_key_from_secret, encrypt, evaluate_polynomial, - evaluate_polynomial_commitment, generate_coefficients, generate_identifier, - sum_commitments, - }, - GENERATOR, +use data_structures::{ + AllMessage, DKGOutput, DKGOutputContent, MessageContent, Parameters, ENCRYPTION_NONCE_LENGTH, + RECIPIENTS_HASH_LENGTH, +}; +use errors::{DKGError, DKGResult}; +use utils::{ + decrypt, derive_secret_key_from_secret, encrypt, evaluate_polynomial, + evaluate_polynomial_commitment, generate_coefficients, generate_identifier, sum_commitments, }; +use super::GENERATOR; + impl Keypair { /// First round of the SimplPedPoP protocol. pub fn simplpedpop_contribute_all( @@ -154,7 +157,7 @@ impl Keypair { if &message.content.parameters != parameters { return Err(DKGError::DifferentParameters); } - if &message.content.recipients_hash != &first_message.content.recipients_hash { + if message.content.recipients_hash != first_message.content.recipients_hash { return Err(DKGError::DifferentRecipientsHash); } @@ -239,11 +242,8 @@ impl Keypair { verify_batch(&mut signatures_transcripts, &signatures, &senders, false) .map_err(DKGError::InvalidSignature)?; - for i in 0..participants { - verifying_keys.push(evaluate_polynomial_commitment( - &identifiers[i], - &total_polynomial_commitment, - )); + for id in identifiers.iter() { + verifying_keys.push(evaluate_polynomial_commitment(id, &total_polynomial_commitment)); } let dkg_output_content = diff --git a/src/olaf/tests.rs b/src/olaf/simplpedpop/tests.rs similarity index 98% rename from src/olaf/tests.rs rename to src/olaf/simplpedpop/tests.rs index 91157bf..317b611 100644 --- a/src/olaf/tests.rs +++ b/src/olaf/simplpedpop/tests.rs @@ -1,11 +1,11 @@ #[cfg(test)] mod tests { mod simplpedpop { - use crate::olaf::data_structures::{ + use crate::olaf::simplpedpop::data_structures::{ AllMessage, Parameters, CHACHA20POLY1305_LENGTH, RECIPIENTS_HASH_LENGTH, }; - use crate::olaf::errors::DKGError; - use crate::olaf::GENERATOR; + use crate::olaf::simplpedpop::errors::DKGError; + use crate::olaf::simplpedpop::GENERATOR; use crate::{Keypair, PublicKey}; use alloc::vec::Vec; use curve25519_dalek::ristretto::RistrettoPoint; diff --git a/src/olaf/utils.rs b/src/olaf/simplpedpop/utils.rs similarity index 100% rename from src/olaf/utils.rs rename to src/olaf/simplpedpop/utils.rs From 2e557c9395aceca570746a7411cda263ed7c4eae Mon Sep 17 00:00:00 2001 From: Fiono11 Date: Mon, 6 May 2024 16:18:00 +0100 Subject: [PATCH 06/60] Return keys from simplpedpop_recipient_all instead of points and scalars --- src/olaf/data_structures.rs | 21 +++++++++------------ src/olaf/simplpedpop.rs | 23 ++++++++++++----------- src/olaf/tests.rs | 31 +++++++++++-------------------- src/olaf/utils.rs | 6 +++--- 4 files changed, 35 insertions(+), 46 deletions(-) diff --git a/src/olaf/data_structures.rs b/src/olaf/data_structures.rs index 7a58f33..ba1b42e 100644 --- a/src/olaf/data_structures.rs +++ b/src/olaf/data_structures.rs @@ -268,12 +268,12 @@ impl DKGOutput { #[derive(Debug)] pub struct DKGOutputContent { pub(crate) group_public_key: PublicKey, - pub(crate) verifying_keys: Vec, + pub(crate) verifying_keys: Vec, } impl DKGOutputContent { /// Creates the content of the SimplPedPoP output. - pub fn new(group_public_key: PublicKey, verifying_keys: Vec) -> Self { + pub fn new(group_public_key: PublicKey, verifying_keys: Vec) -> Self { Self { group_public_key, verifying_keys } } /// Serializes the DKGOutputContent into bytes. @@ -287,8 +287,7 @@ impl DKGOutputContent { bytes.extend(key_count.to_le_bytes()); for key in &self.verifying_keys { - let compressed_key = key.compress(); - bytes.extend(compressed_key.to_bytes()); + bytes.extend(key.to_bytes()); } bytes @@ -314,11 +313,9 @@ impl DKGOutputContent { let mut verifying_keys = Vec::with_capacity(key_count as usize); for _ in 0..key_count { - let key_bytes = &bytes[cursor..cursor + COMPRESSED_RISTRETTO_LENGTH]; - cursor += COMPRESSED_RISTRETTO_LENGTH; - let compressed_key = CompressedRistretto::from_slice(key_bytes) - .map_err(DKGError::DeserializationError)?; - let key = compressed_key.decompress().ok_or(DKGError::InvalidRistrettoPoint)?; + let key_bytes = &bytes[cursor..cursor + PUBLIC_KEY_LENGTH]; + cursor += PUBLIC_KEY_LENGTH; + let key = PublicKey::from_bytes(key_bytes).map_err(DKGError::InvalidPublicKey)?; verifying_keys.push(key); } @@ -409,9 +406,9 @@ mod tests { let mut rng = OsRng; let group_public_key = RistrettoPoint::random(&mut rng); let verifying_keys = vec![ - RistrettoPoint::random(&mut rng), - RistrettoPoint::random(&mut rng), - RistrettoPoint::random(&mut rng), + PublicKey::from_point(RistrettoPoint::random(&mut rng)), + PublicKey::from_point(RistrettoPoint::random(&mut rng)), + PublicKey::from_point(RistrettoPoint::random(&mut rng)), ]; let dkg_output_content = DKGOutputContent { diff --git a/src/olaf/simplpedpop.rs b/src/olaf/simplpedpop.rs index 7ee4c1c..c94541c 100644 --- a/src/olaf/simplpedpop.rs +++ b/src/olaf/simplpedpop.rs @@ -5,7 +5,7 @@ use alloc::vec::Vec; use curve25519_dalek::{traits::Identity, RistrettoPoint, Scalar}; use merlin::Transcript; use rand_core::RngCore; -use crate::{context::SigningTranscript, verify_batch, Keypair, PublicKey}; +use crate::{context::SigningTranscript, verify_batch, Keypair, PublicKey, SecretKey}; use super::{ data_structures::{ AllMessage, DKGOutput, DKGOutputContent, MessageContent, Parameters, @@ -13,7 +13,7 @@ use super::{ }, errors::{DKGError, DKGResult}, utils::{ - decrypt, derive_secret_key_from_secret, encrypt, evaluate_polynomial, + decrypt, derive_secret_key_from_scalar, encrypt, evaluate_polynomial, evaluate_polynomial_commitment, generate_coefficients, generate_identifier, sum_commitments, }, @@ -92,7 +92,7 @@ impl Keypair { .first() .expect("This never fails because the minimum threshold is 2"); - let secret_key = derive_secret_key_from_secret(secret, &mut rng); + let secret_key = derive_secret_key_from_scalar(secret, &mut rng); let secret_commitment = point_polynomial .first() @@ -125,7 +125,7 @@ impl Keypair { pub fn simplpedpop_recipient_all( &self, messages: &[AllMessage], - ) -> DKGResult<(DKGOutput, Scalar)> { + ) -> DKGResult<(DKGOutput, SecretKey)> { let first_message = &messages[0]; let parameters = &first_message.content.parameters; let threshold = parameters.threshold as usize; @@ -154,7 +154,7 @@ impl Keypair { if &message.content.parameters != parameters { return Err(DKGError::DifferentParameters); } - if &message.content.recipients_hash != &first_message.content.recipients_hash { + if message.content.recipients_hash != first_message.content.recipients_hash { return Err(DKGError::DifferentRecipientsHash); } @@ -239,11 +239,9 @@ impl Keypair { verify_batch(&mut signatures_transcripts, &signatures, &senders, false) .map_err(DKGError::InvalidSignature)?; - for i in 0..participants { - verifying_keys.push(evaluate_polynomial_commitment( - &identifiers[i], - &total_polynomial_commitment, - )); + for id in &identifiers { + let evaluation = evaluate_polynomial_commitment(id, &total_polynomial_commitment); + verifying_keys.push(PublicKey::from_point(evaluation)); } let dkg_output_content = @@ -254,6 +252,9 @@ impl Keypair { let signature = self.sign(dkg_output_transcript); let dkg_output = DKGOutput::new(self.public, dkg_output_content, signature); - Ok((dkg_output, total_secret_share)) + let secret_key = + derive_secret_key_from_scalar(&total_secret_share, &mut crate::getrandom_or_panic()); + + Ok((dkg_output, secret_key)) } } diff --git a/src/olaf/tests.rs b/src/olaf/tests.rs index 91157bf..00cda0c 100644 --- a/src/olaf/tests.rs +++ b/src/olaf/tests.rs @@ -2,10 +2,9 @@ mod tests { mod simplpedpop { use crate::olaf::data_structures::{ - AllMessage, Parameters, CHACHA20POLY1305_LENGTH, RECIPIENTS_HASH_LENGTH, + AllMessage, CHACHA20POLY1305_LENGTH, RECIPIENTS_HASH_LENGTH, }; use crate::olaf::errors::DKGError; - use crate::olaf::GENERATOR; use crate::{Keypair, PublicKey}; use alloc::vec::Vec; use curve25519_dalek::ristretto::RistrettoPoint; @@ -52,8 +51,8 @@ mod tests { for i in 0..participants { for j in 0..participants { assert_eq!( - dkg_outputs[i].0.content.verifying_keys[j].compress(), - (dkg_outputs[j].1 * GENERATOR).compress(), + dkg_outputs[i].0.content.verifying_keys[j], + (dkg_outputs[j].1.to_public()), "Verification of total secret shares failed!" ); } @@ -90,29 +89,21 @@ mod tests { #[test] fn test_different_parameters() { - // Define threshold and participants let threshold = 3; let participants = 5; - // Generate keypairs for participants let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); - // Each participant creates an AllMessage with different parameters let mut messages: Vec = Vec::new(); for i in 0..participants { - let mut parameters = Parameters::generate(participants as u16, threshold); - // Modify parameters for the first participant - if i == 0 { - parameters.threshold += 1; // Modify threshold - } - let message = keypairs[i] - .simplpedpop_contribute_all(parameters.threshold, public_keys.clone()) - .unwrap(); + let message = + keypairs[i].simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap(); messages.push(message); } - // Call simplpedpop_recipient_all + messages[1].content.parameters.threshold += 1; + let result = keypairs[0].simplpedpop_recipient_all(&messages); // Check if the result is an error @@ -120,7 +111,7 @@ mod tests { Ok(_) => panic!("Expected an error, but got Ok."), Err(e) => match e { DKGError::DifferentParameters => assert!(true), - _ => panic!("Expected DKGError::DifferentRecipientsHash, but got {:?}", e), + _ => panic!("Expected DKGError::DifferentParameters, but got {:?}", e), }, } } @@ -303,7 +294,7 @@ mod tests { Ok(_) => panic!("Expected an error, but got Ok."), Err(e) => match e { DKGError::InsufficientThreshold => assert!(true), - _ => panic!("Expected DKGError::DifferentRecipientsHash, but got {:?}", e), + _ => panic!("Expected DKGError::InsufficientThreshold, but got {:?}", e), }, } } @@ -321,7 +312,7 @@ mod tests { Err(e) => match e { DKGError::InvalidNumberOfParticipants => assert!(true), _ => { - panic!("Expected DKGError::DifferentRecipientsHash, but got {:?}", e) + panic!("Expected DKGError::InvalidNumberOfParticipants, but got {:?}", e) }, }, } @@ -342,7 +333,7 @@ mod tests { Ok(_) => panic!("Expected an error, but got Ok."), Err(e) => match e { DKGError::ExcessiveThreshold => assert!(true), - _ => panic!("Expected DKGError::DifferentRecipientsHash, but got {:?}", e), + _ => panic!("Expected DKGError::ExcessiveThreshold, but got {:?}", e), }, } } diff --git a/src/olaf/utils.rs b/src/olaf/utils.rs index 0abdd41..4ee6497 100644 --- a/src/olaf/utils.rs +++ b/src/olaf/utils.rs @@ -49,15 +49,15 @@ pub(crate) fn generate_coefficients( coefficients } -pub(crate) fn derive_secret_key_from_secret( - secret: &Scalar, +pub(crate) fn derive_secret_key_from_scalar( + scalar: &Scalar, 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(); + let secret_bytes = scalar.to_bytes(); bytes[..32].copy_from_slice(&secret_bytes[..]); bytes[32..].copy_from_slice(&nonce[..]); From 999f299d8d9693410da324b164ee8120f1e7db98 Mon Sep 17 00:00:00 2001 From: Fiono11 Date: Mon, 6 May 2024 16:32:31 +0100 Subject: [PATCH 07/60] WIP --- src/olaf/frost/data_structures.rs | 46 ++++++++++++++----------------- src/olaf/frost/mod.rs | 10 +++---- 2 files changed, 26 insertions(+), 30 deletions(-) diff --git a/src/olaf/frost/data_structures.rs b/src/olaf/frost/data_structures.rs index 15d4aad..169e01f 100644 --- a/src/olaf/frost/data_structures.rs +++ b/src/olaf/frost/data_structures.rs @@ -1,13 +1,12 @@ use curve25519_dalek::{RistrettoPoint, Scalar}; use merlin::Transcript; -use getrandom_or_panic::RngCore; +use getrandom_or_panic::{CryptoRng, RngCore}; use zeroize::ZeroizeOnDrop; - use crate::{context::SigningTranscript, olaf::GENERATOR}; /// A scalar that is a signing nonce. -#[derive(Debug, Clone, PartialEq, Eq, ZeroizeOnDrop)] -pub struct Nonce(pub(super) Scalar); +#[derive(ZeroizeOnDrop)] +pub(super) struct Nonce(pub(super) Scalar); impl Nonce { /// Generates a new uniformly random signing nonce by sourcing fresh randomness and combining @@ -19,9 +18,7 @@ impl Nonce { /// An implementation of `nonce_generate(secret)` from the [spec]. /// /// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#name-nonce-generation - pub fn new(secret: &Scalar) -> Self { - let mut rng = crate::getrandom_or_panic(); - + pub fn new(secret: &Scalar, rng: &mut R) -> Self { let mut random_bytes = [0; 32]; rng.fill_bytes(&mut random_bytes[..]); @@ -30,7 +27,7 @@ impl Nonce { /// Generates a nonce from the given random bytes. /// This function allows testing and MUST NOT be made public. - pub(crate) fn nonce_generate_from_random_bytes(secret: &Scalar, random_bytes: &[u8]) -> Self { + pub(super) fn nonce_generate_from_random_bytes(secret: &Scalar, random_bytes: &[u8]) -> Self { let mut transcript = Transcript::new(b"nonce_generate_from_random_bytes"); transcript.append_message(b"random bytes", random_bytes); @@ -41,9 +38,8 @@ impl Nonce { } /// A group element that is a commitment to a signing nonce share. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct NonceCommitment(pub(super) RistrettoPoint); +#[derive(Copy, Clone)] +pub(super) struct NonceCommitment(pub(super) RistrettoPoint); impl From for NonceCommitment { fn from(nonce: Nonce) -> Self { @@ -62,18 +58,18 @@ impl From<&Nonce> for NonceCommitment { /// Note that [`SigningNonces`] must be used *only once* for a signing /// operation; re-using nonces will result in leakage of a signer's long-lived /// signing key. -#[derive(Debug, Clone, PartialEq, Eq, ZeroizeOnDrop)] -pub struct SigningNonces { +#[derive(ZeroizeOnDrop)] +pub(super) struct SigningNonces { /// The hiding [`Nonce`]. - pub(crate) hiding: Nonce, + pub(super) hiding: Nonce, /// The binding [`Nonce`]. - pub(crate) binding: Nonce, + pub(super) binding: Nonce, /// The commitments to the nonces. This is precomputed to improve /// sign() performance, since it needs to check if the commitments /// to the participant's nonces are included in the commitments sent /// by the Coordinator, and this prevents having to recompute them. #[zeroize(skip)] - pub(crate) commitments: SigningCommitments, + pub(super) commitments: SigningCommitments, } impl SigningNonces { @@ -81,9 +77,9 @@ impl SigningNonces { /// /// Each participant generates signing nonces before performing a signing /// operation. - pub fn new(secret: &Scalar) -> Self { - let hiding = Nonce::new(secret); - let binding = Nonce::new(secret); + pub(super) fn new(secret: &Scalar, rng: &mut R) -> Self { + let hiding = Nonce::new(secret, rng); + let binding = Nonce::new(secret, rng); Self::from_nonces(hiding, binding) } @@ -95,7 +91,7 @@ impl SigningNonces { /// SigningNonces MUST NOT be repeated in different FROST signings. /// Thus, if you're using this method (because e.g. you're writing it /// to disk between rounds), be careful so that does not happen. - pub fn from_nonces(hiding: Nonce, binding: Nonce) -> Self { + pub(super) fn from_nonces(hiding: Nonce, binding: Nonce) -> Self { let hiding_commitment = (&hiding).into(); let binding_commitment = (&binding).into(); let commitments = SigningCommitments::new(hiding_commitment, binding_commitment); @@ -108,17 +104,17 @@ impl SigningNonces { /// /// This step can be batched if desired by the implementation. Each /// SigningCommitment can be used for exactly *one* signature. -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub struct SigningCommitments { +#[derive(Copy, Clone)] +pub(super) struct SigningCommitments { /// Commitment to the hiding [`Nonce`]. - pub(crate) hiding: NonceCommitment, + pub(super) hiding: NonceCommitment, /// Commitment to the binding [`Nonce`]. - pub(crate) binding: NonceCommitment, + pub(super) binding: NonceCommitment, } impl SigningCommitments { /// Create new SigningCommitments - pub fn new(hiding: NonceCommitment, binding: NonceCommitment) -> Self { + pub(super) fn new(hiding: NonceCommitment, binding: NonceCommitment) -> Self { Self { hiding, binding } } diff --git a/src/olaf/frost/mod.rs b/src/olaf/frost/mod.rs index ddb2177..b81207d 100644 --- a/src/olaf/frost/mod.rs +++ b/src/olaf/frost/mod.rs @@ -22,16 +22,16 @@ impl Keypair { /// perform the first round. Batching entails generating more than one /// nonce/commitment pair at a time. Nonces should be stored in secret storage /// for later use, whereas the commitments are published. - pub fn preprocess( - num_nonces: u8, - secret: &Scalar, - ) -> (Vec, Vec) { + pub fn preprocess(&self, num_nonces: u8) -> (Vec, Vec) { + let mut rng = crate::getrandom_or_panic(); + let mut signing_nonces: Vec = Vec::with_capacity(num_nonces as usize); + let mut signing_commitments: Vec = Vec::with_capacity(num_nonces as usize); for _ in 0..num_nonces { - let nonces = SigningNonces::new(secret); + let nonces = SigningNonces::new(&self.secret.key, &mut rng); signing_commitments.push(SigningCommitments::from(&nonces)); signing_nonces.push(nonces); } From 5fc364ae57698546a37ba5c529156d9d8abff9cb Mon Sep 17 00:00:00 2001 From: Fiono11 Date: Mon, 6 May 2024 16:34:07 +0100 Subject: [PATCH 08/60] Replace pub(crate) with pub(super) --- src/olaf/data_structures.rs | 48 ++++++++++++++++++------------------- src/olaf/utils.rs | 16 ++++++------- 2 files changed, 32 insertions(+), 32 deletions(-) diff --git a/src/olaf/data_structures.rs b/src/olaf/data_structures.rs index ba1b42e..721b52c 100644 --- a/src/olaf/data_structures.rs +++ b/src/olaf/data_structures.rs @@ -7,17 +7,17 @@ use curve25519_dalek::{ristretto::CompressedRistretto, RistrettoPoint}; use crate::{context::SigningTranscript, PublicKey, Signature, PUBLIC_KEY_LENGTH, SIGNATURE_LENGTH}; use super::{errors::DKGError, MINIMUM_THRESHOLD}; -pub(crate) const COMPRESSED_RISTRETTO_LENGTH: usize = 32; -pub(crate) const U16_LENGTH: usize = 2; -pub(crate) const ENCRYPTION_NONCE_LENGTH: usize = 12; -pub(crate) const RECIPIENTS_HASH_LENGTH: usize = 16; -pub(crate) const CHACHA20POLY1305_LENGTH: usize = 64; +pub(super) const COMPRESSED_RISTRETTO_LENGTH: usize = 32; +pub(super) const U16_LENGTH: usize = 2; +pub(super) const ENCRYPTION_NONCE_LENGTH: usize = 12; +pub(super) const RECIPIENTS_HASH_LENGTH: usize = 16; +pub(super) const CHACHA20POLY1305_LENGTH: usize = 64; /// The parameters of a given execution of the SimplPedPoP protocol. #[derive(Clone, PartialEq, Eq)] pub struct Parameters { - pub(crate) participants: u16, - pub(crate) threshold: u16, + pub(super) participants: u16, + pub(super) threshold: u16, } impl Parameters { @@ -26,7 +26,7 @@ impl Parameters { Parameters { participants, threshold } } - pub(crate) fn validate(&self) -> Result<(), DKGError> { + pub(super) fn validate(&self) -> Result<(), DKGError> { if self.threshold < MINIMUM_THRESHOLD { return Err(DKGError::InsufficientThreshold); } @@ -42,7 +42,7 @@ impl Parameters { Ok(()) } - pub(crate) fn commit(&self, t: &mut T) { + pub(super) 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()); } @@ -54,8 +54,8 @@ impl Parameters { /// participant, but typical thresholds lie between 1/2 and 2/3, /// so this doubles or tripples bandwidth usage. pub struct AllMessage { - pub(crate) content: MessageContent, - pub(crate) signature: Signature, + pub(super) content: MessageContent, + pub(super) signature: Signature, } impl AllMessage { @@ -89,14 +89,14 @@ impl AllMessage { /// The contents of the message destined to all participants. pub struct MessageContent { - pub(crate) sender: PublicKey, - pub(crate) encryption_nonce: [u8; ENCRYPTION_NONCE_LENGTH], - pub(crate) parameters: Parameters, - pub(crate) recipients_hash: [u8; RECIPIENTS_HASH_LENGTH], - pub(crate) point_polynomial: Vec, - pub(crate) ciphertexts: Vec>, - pub(crate) ephemeral_key: PublicKey, - pub(crate) proof_of_possession: Signature, + pub(super) sender: PublicKey, + pub(super) encryption_nonce: [u8; ENCRYPTION_NONCE_LENGTH], + pub(super) parameters: Parameters, + pub(super) recipients_hash: [u8; RECIPIENTS_HASH_LENGTH], + pub(super) point_polynomial: Vec, + pub(super) ciphertexts: Vec>, + pub(super) ephemeral_key: PublicKey, + pub(super) proof_of_possession: Signature, } impl MessageContent { @@ -218,9 +218,9 @@ impl MessageContent { /// The signed output of the SimplPedPoP protocol. pub struct DKGOutput { - pub(crate) sender: PublicKey, - pub(crate) content: DKGOutputContent, - pub(crate) signature: Signature, + pub(super) sender: PublicKey, + pub(super) content: DKGOutputContent, + pub(super) signature: Signature, } impl DKGOutput { @@ -267,8 +267,8 @@ impl DKGOutput { /// The content of the signed output of the SimplPedPoP protocol. #[derive(Debug)] pub struct DKGOutputContent { - pub(crate) group_public_key: PublicKey, - pub(crate) verifying_keys: Vec, + pub(super) group_public_key: PublicKey, + pub(super) verifying_keys: Vec, } impl DKGOutputContent { diff --git a/src/olaf/utils.rs b/src/olaf/utils.rs index 4ee6497..8b21dd8 100644 --- a/src/olaf/utils.rs +++ b/src/olaf/utils.rs @@ -10,7 +10,7 @@ use super::{ errors::{DKGError, DKGResult}, }; -pub(crate) fn generate_identifier(recipients_hash: &[u8; 16], index: u16) -> Scalar { +pub(super) fn generate_identifier(recipients_hash: &[u8; 16], index: u16) -> Scalar { let mut pos = merlin::Transcript::new(b"Identifier"); pos.append_message(b"RecipientsHash", recipients_hash); pos.append_message(b"i", &index.to_le_bytes()[..]); @@ -19,7 +19,7 @@ pub(crate) fn generate_identifier(recipients_hash: &[u8; 16], index: u16) -> Sca /// Evaluate the polynomial with the given coefficients (constant term first) /// at the point x=identifier using Horner's method. -pub(crate) fn evaluate_polynomial(identifier: &Scalar, coefficients: &[Scalar]) -> Scalar { +pub(super) fn evaluate_polynomial(identifier: &Scalar, coefficients: &[Scalar]) -> Scalar { let mut value = Scalar::ZERO; let ell_scalar = identifier; @@ -32,7 +32,7 @@ pub(crate) fn evaluate_polynomial(identifier: &Scalar, coefficients: &[Scalar]) } /// Return a vector of randomly generated polynomial coefficients ([`Scalar`]s). -pub(crate) fn generate_coefficients( +pub(super) fn generate_coefficients( size: usize, rng: &mut R, ) -> Vec { @@ -49,7 +49,7 @@ pub(crate) fn generate_coefficients( coefficients } -pub(crate) fn derive_secret_key_from_scalar( +pub(super) fn derive_secret_key_from_scalar( scalar: &Scalar, mut rng: R, ) -> SecretKey { @@ -66,7 +66,7 @@ pub(crate) fn derive_secret_key_from_scalar( .expect("This never fails because bytes has length 64 and the key is a scalar") } -pub(crate) fn evaluate_polynomial_commitment( +pub(super) fn evaluate_polynomial_commitment( identifier: &Scalar, commitment: &[RistrettoPoint], ) -> RistrettoPoint { @@ -80,7 +80,7 @@ pub(crate) fn evaluate_polynomial_commitment( result } -pub(crate) fn sum_commitments( +pub(super) fn sum_commitments( commitments: &[&Vec], ) -> Result, DKGError> { let mut group_commitment = @@ -96,7 +96,7 @@ pub(crate) fn sum_commitments( Ok(group_commitment) } -pub(crate) fn encrypt( +pub(super) fn encrypt( scalar_evaluation: &Scalar, ephemeral_key: &Scalar, mut transcript: T, @@ -124,7 +124,7 @@ pub(crate) fn encrypt( Ok(ciphertext) } -pub(crate) fn decrypt( +pub(super) fn decrypt( mut transcript: T, recipient: &PublicKey, key_exchange: &RistrettoPoint, From 1a2b54d024d8639dffe546b0d53e6fd0df1065ca Mon Sep 17 00:00:00 2001 From: Fiono11 Date: Mon, 6 May 2024 17:17:30 +0100 Subject: [PATCH 09/60] WIP --- src/olaf/frost/data_structures.rs | 176 +++++++++++++++++++++++++++++- src/olaf/frost/mod.rs | 111 ++++++++++++++++++- src/olaf/frost/utils.rs | 162 +++++++++++++++++++++++++++ 3 files changed, 443 insertions(+), 6 deletions(-) create mode 100644 src/olaf/frost/utils.rs diff --git a/src/olaf/frost/data_structures.rs b/src/olaf/frost/data_structures.rs index 169e01f..85e3d99 100644 --- a/src/olaf/frost/data_structures.rs +++ b/src/olaf/frost/data_structures.rs @@ -1,8 +1,11 @@ +use alloc::{collections::BTreeMap, vec::Vec}; use curve25519_dalek::{RistrettoPoint, Scalar}; use merlin::Transcript; use getrandom_or_panic::{CryptoRng, RngCore}; use zeroize::ZeroizeOnDrop; -use crate::{context::SigningTranscript, olaf::GENERATOR}; +use crate::{context::SigningTranscript, olaf::GENERATOR, PublicKey, SecretKey}; + +use super::{Identifier, VerifyingKey}; /// A scalar that is a signing nonce. #[derive(ZeroizeOnDrop)] @@ -38,7 +41,7 @@ impl Nonce { } /// A group element that is a commitment to a signing nonce share. -#[derive(Copy, Clone)] +#[derive(Copy, Clone, PartialEq, Eq)] pub(super) struct NonceCommitment(pub(super) RistrettoPoint); impl From for NonceCommitment { @@ -104,7 +107,7 @@ impl SigningNonces { /// /// This step can be batched if desired by the implementation. Each /// SigningCommitment can be used for exactly *one* signature. -#[derive(Copy, Clone)] +#[derive(Copy, Clone, PartialEq, Eq)] pub(super) struct SigningCommitments { /// Commitment to the hiding [`Nonce`]. pub(super) hiding: NonceCommitment, @@ -135,3 +138,170 @@ impl From<&SigningNonces> for SigningCommitments { nonces.commitments } } + +/// Generated by the coordinator of the signing operation and distributed to +/// each signing party. +pub(super) struct SigningPackage { + /// The set of commitments participants published in the first round of the + /// protocol. + pub(super) signing_commitments: BTreeMap, + /// Message which each participant will sign. + /// + /// Each signer should perform protocol-specific verification on the + /// message. + pub(super) message: Vec, +} + +impl SigningPackage { + /// Create a new `SigningPackage`. + /// + /// The `signing_commitments` are sorted by participant `identifier`. + pub(super) fn new( + signing_commitments: BTreeMap, + message: &[u8], + ) -> SigningPackage { + SigningPackage { signing_commitments, message: message.to_vec() } + } + + /// Get a signing commitment by its participant identifier, or None if not found. + pub(super) fn signing_commitment(&self, identifier: &Identifier) -> Option { + self.signing_commitments.get(identifier).copied() + } + + /// Compute the transcripts to compute the per-signer binding factors. + pub(super) fn binding_factor_transcripts( + &self, + verifying_key: &VerifyingKey, + ) -> Vec<(Identifier, Transcript)> { + let mut transcript = Transcript::new(b"binding_factor"); + + transcript.commit_point(b"verifying_key", verifying_key.as_compressed()); + + transcript.append_message(b"message", &self.message); + + transcript.append_message( + b"group_commitment", + encode_group_commitments(&self.signing_commitments) + .challenge_scalar(b"encode_group_commitments") + .as_bytes(), + ); + + self.signing_commitments + .keys() + .map(|identifier| { + transcript.append_message(b"identifier", &identifier.to_be_bytes()); + (*identifier, transcript.clone()) + }) + .collect() + } +} + +/// A participant's signature share, which the coordinator will aggregate with all other signer's +/// shares into the joint signature. +pub(super) struct SignatureShare { + /// This participant's signature over the message. + pub(super) share: Scalar, +} + +/// A FROST keypair, which is generated by the SimplPedPoP protocol. +#[derive(ZeroizeOnDrop)] +pub(super) struct KeyPackage { + /// Denotes the participant identifier each secret share key package is owned by. + #[zeroize(skip)] + pub(super) identifier: u16, + /// This participant's signing share. This is secret. + pub(super) signing_share: SecretKey, + /// This participant's public key. + #[zeroize(skip)] + pub(super) verifying_share: PublicKey, + /// The public key that represents the entire group. + #[zeroize(skip)] + pub(super) verifying_key: PublicKey, + pub(super) min_signers: u16, +} + +impl KeyPackage { + /// Create a new [`KeyPackage`] instance. + pub(super) fn new( + identifier: Identifier, + signing_share: SecretKey, + verifying_share: VerifyingKey, + verifying_key: PublicKey, + min_signers: u16, + ) -> Self { + Self { identifier, signing_share, verifying_share, verifying_key, min_signers } + } +} + +/// A list of binding factors and their associated identifiers. +#[derive(Clone)] +pub(super) struct BindingFactorList(BTreeMap); + +impl BindingFactorList { + /// Create a new [`BindingFactorList`] from a map of identifiers to binding factors. + pub fn new(binding_factors: BTreeMap) -> Self { + Self(binding_factors) + } + + /// Get the [`BindingFactor`] for the given identifier, or None if not found. + pub fn get(&self, key: &Identifier) -> Option<&BindingFactor> { + self.0.get(key) + } + + /// [`compute_binding_factors`] in the spec + /// + /// [`compute_binding_factors`]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-4.4. + pub(super) fn compute_binding_factor_list( + signing_package: &SigningPackage, + verifying_key: &VerifyingKey, + ) -> BindingFactorList { + let mut transcripts = signing_package.binding_factor_transcripts(verifying_key); + + BindingFactorList::new( + transcripts + .iter_mut() + .map(|(identifier, transcript)| { + let binding_factor = transcript.challenge_scalar(b"binding factor"); + + (*identifier, BindingFactor(binding_factor)) + }) + .collect(), + ) + } +} + +/// The binding factor, also known as _rho_ (ρ), ensures each signature share is strongly bound to a signing set, specific set +/// of commitments, and a specific message. +#[derive(Clone, PartialEq, Eq)] +pub(super) struct BindingFactor(pub(super) Scalar); + +/// One signer's share of the group commitment, derived from their individual signing commitments +/// and the binding factor _rho_. +#[derive(Clone, Copy, PartialEq)] +pub struct GroupCommitmentShare(pub(super) RistrettoPoint); + +/// Encode the list of group signing commitments. +/// +/// Implements [`encode_group_commitment_list()`] from the spec. +/// +/// `signing_commitments` must contain the sorted map of participants +/// identifiers to the signing commitments they issued. +/// +/// Returns a byte string containing the serialized representation of the +/// commitment list. +/// +/// [`encode_group_commitment_list()`]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#name-list-operations +pub(super) fn encode_group_commitments( + signing_commitments: &BTreeMap, +) -> Transcript { + //let mut bytes = vec![]; + let mut transcript = Transcript::new(b"encode_group_commitments"); + + for (item_identifier, item) in signing_commitments { + transcript.append_message(b"identifier", &item_identifier.to_be_bytes()); + transcript.commit_point(b"hiding", &item.hiding.0.compress()); + transcript.commit_point(b"binding", &item.binding.0.compress()); + } + + transcript +} diff --git a/src/olaf/frost/mod.rs b/src/olaf/frost/mod.rs index b81207d..1492aba 100644 --- a/src/olaf/frost/mod.rs +++ b/src/olaf/frost/mod.rs @@ -2,15 +2,24 @@ pub mod errors; mod data_structures; +mod utils; use alloc::vec::Vec; -use curve25519_dalek::Scalar; use crate::{Keypair, PublicKey}; use self::{ - data_structures::{SigningCommitments, SigningNonces}, - errors::FROSTResult, + data_structures::{ + BindingFactor, BindingFactorList, KeyPackage, SignatureShare, SigningCommitments, + SigningNonces, SigningPackage, + }, + errors::FROSTError, + utils::{ + challenge, compute_group_commitment, compute_signature_share, derive_interpolating_value, + }, }; +pub(super) type VerifyingKey = PublicKey; +pub(super) type Identifier = u16; + impl Keypair { /// Done once by each participant, to generate _their_ nonces and commitments /// that are then used during signing. @@ -38,4 +47,100 @@ impl Keypair { (signing_nonces, signing_commitments) } + + /// Performed once by each participant selected for the signing operation. + /// + /// Implements [`commit`] from the spec. + /// + /// Generates the signing nonces and commitments to be used in the signing + /// operation. + /// + /// [`commit`]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#name-round-one-commitment. + pub fn commit(&self) -> (SigningNonces, SigningCommitments) { + let (mut vec_signing_nonces, mut vec_signing_commitments) = self.preprocess(1); + ( + vec_signing_nonces.pop().expect("must have 1 element"), + vec_signing_commitments.pop().expect("must have 1 element"), + ) + } + + /// Performed once by each participant selected for the signing operation. + /// + /// Implements [`sign`] from the spec. + /// + /// Receives the message to be signed and a set of signing commitments and a set + /// of randomizing commitments to be used in that signing operation, including + /// that for this participant. + /// + /// Assumes the participant has already determined which nonce corresponds with + /// the commitment that was assigned by the coordinator in the SigningPackage. + /// + /// [`sign`]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#name-round-two-signature-share-g + pub fn sign_frost( + &self, + signing_package: &SigningPackage, + signer_nonces: &SigningNonces, + verifying_key: VerifyingKey, + identifier: Identifier, + min_signers: u16, + ) -> Result { + let key_package = KeyPackage::new( + identifier, + self.secret.clone(), + self.public, + verifying_key, + min_signers, + ); + + if signing_package.signing_commitments.len() < key_package.min_signers as usize { + return Err(FROSTError::IncorrectNumberOfSigningCommitments); + } + + // Validate the signer's commitment is present in the signing package + let commitment = signing_package + .signing_commitments + .get(&key_package.identifier) + .ok_or(FROSTError::MissingSigningCommitment)?; + + // Validate if the signer's commitment exists + if &signer_nonces.commitments != commitment { + return Err(FROSTError::IncorrectSigningCommitment); + } + + // Encodes the signing commitment list produced in round one as part of generating [`BindingFactor`], the + // binding factor. + let binding_factor_list: BindingFactorList = BindingFactorList::compute_binding_factor_list( + signing_package, + &key_package.verifying_key, + ); + + let binding_factor: BindingFactor = binding_factor_list + .get(&key_package.identifier) + .ok_or(FROSTError::UnknownIdentifier)? + .clone(); + + // Compute the group commitment from signing commitments produced in round one. + let group_commitment = compute_group_commitment(signing_package, &binding_factor_list)?; + + // Compute Lagrange coefficient. + let lambda_i = derive_interpolating_value(&key_package.identifier, signing_package)?; + + // Compute the per-message challenge. + let challenge = challenge( + &group_commitment.0, + &key_package.verifying_key, + signing_package.message.as_slice(), + ); + + // Compute the Schnorr signature share. + let signature_share = compute_signature_share( + signer_nonces, + binding_factor, + lambda_i, + &key_package, + challenge, + ); + + Ok(signature_share) + } } diff --git a/src/olaf/frost/utils.rs b/src/olaf/frost/utils.rs new file mode 100644 index 0000000..f573007 --- /dev/null +++ b/src/olaf/frost/utils.rs @@ -0,0 +1,162 @@ +use alloc::{collections::BTreeSet, vec::Vec}; +use curve25519_dalek::{ + traits::{Identity, VartimeMultiscalarMul}, + RistrettoPoint, Scalar, +}; +use merlin::Transcript; + +use crate::context::SigningTranscript; + +use super::{ + data_structures::{ + BindingFactor, BindingFactorList, KeyPackage, SignatureShare, SigningNonces, SigningPackage, + }, + errors::FROSTError, + Identifier, VerifyingKey, +}; + +/// Compute the signature share for a signing operation. +pub(super) fn compute_signature_share( + signer_nonces: &SigningNonces, + binding_factor: BindingFactor, + lambda_i: Scalar, + key_package: &KeyPackage, + challenge: Scalar, +) -> SignatureShare { + let z_share: Scalar = signer_nonces.hiding.0 + + (signer_nonces.binding.0 * binding_factor.0) + + (lambda_i * key_package.signing_share.key * challenge); + + SignatureShare { share: z_share } +} + +/// Generates the group commitment which is published as part of the joint +/// Schnorr signature. +/// +/// Implements [`compute_group_commitment`] from the spec. +/// +/// [`compute_group_commitment`]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-4.5. +pub(crate) fn compute_group_commitment( + signing_package: &SigningPackage, + binding_factor_list: &BindingFactorList, +) -> Result { + let identity = RistrettoPoint::identity(); + + let mut group_commitment = RistrettoPoint::identity(); + + // Number of signing participants we are iterating over. + let signers = signing_package.signing_commitments.len(); + + let mut binding_scalars = Vec::with_capacity(signers); + + let mut binding_elements = Vec::with_capacity(signers); + + for (commitment_identifier, commitment) in &signing_package.signing_commitments { + // The following check prevents a party from accidentally revealing their share. + // Note that the '&&' operator would be sufficient. + if identity == commitment.binding.0 || identity == commitment.hiding.0 { + return Err(FROSTError::IdentitySigningCommitment); + } + + let binding_factor = binding_factor_list + .get(&commitment_identifier) + .ok_or(FROSTError::UnknownIdentifier)?; + + // Collect the binding commitments and their binding factors for one big + // multiscalar multiplication at the end. + binding_elements.push(commitment.binding.0); + binding_scalars.push(binding_factor.0); + + group_commitment += commitment.hiding.0; + } + + let accumulated_binding_commitment: RistrettoPoint = + RistrettoPoint::vartime_multiscalar_mul(binding_scalars, binding_elements); + + group_commitment += accumulated_binding_commitment; + + Ok(GroupCommitment(group_commitment)) +} + +/// The product of all signers' individual commitments, published as part of the +/// final signature. +#[derive(Clone, PartialEq, Eq)] +pub(crate) struct GroupCommitment(pub(crate) RistrettoPoint); + +/// Generates the lagrange coefficient for the i'th participant (for `signer_id`). +/// +/// Implements [`derive_interpolating_value()`] from the spec. +/// +/// [`derive_interpolating_value()`]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#name-polynomials +pub(super) fn derive_interpolating_value( + signer_id: &Identifier, + signing_package: &SigningPackage, +) -> Result { + compute_lagrange_coefficient( + &signing_package.signing_commitments.keys().cloned().collect(), + None, + *signer_id, + ) +} + +/// Generates a lagrange coefficient. +/// +/// The Lagrange polynomial for a set of points (x_j, y_j) for 0 <= j <= k +/// is ∑_{i=0}^k y_i.ℓ_i(x), where ℓ_i(x) is the Lagrange basis polynomial: +/// +/// ℓ_i(x) = ∏_{0≤j≤k; j≠i} (x - x_j) / (x_i - x_j). +/// +/// This computes ℓ_j(x) for the set of points `xs` and for the j corresponding +/// to the given xj. +/// +/// If `x` is None, it uses 0 for it (since Identifiers can't be 0). +pub(super) fn compute_lagrange_coefficient( + x_set: &BTreeSet, + x: Option, + x_i: Identifier, +) -> Result { + if x_set.is_empty() { + return Err(FROSTError::IncorrectNumberOfIdentifiers); + } + let mut num = Scalar::ONE; + let mut den = Scalar::ONE; + + let mut x_i_found = false; + + for x_j in x_set.iter() { + if x_i == *x_j { + x_i_found = true; + continue; + } + + if let Some(x) = x { + num *= Scalar::from(x) - Scalar::from(*x_j); + den *= Scalar::from(x_i) - Scalar::from(*x_j); + } else { + // Both signs inverted just to avoid requiring Neg (-*xj) + num *= Scalar::from(*x_j); + den *= Scalar::from(*x_j) - Scalar::from(x_i); + } + } + if !x_i_found { + return Err(FROSTError::UnknownIdentifier); + } + + let inverse = num * den.invert(); + + if inverse == Scalar::ZERO { + Err(FROSTError::DuplicatedIdentifier) + } else { + Ok(inverse) + } +} + +/// Generates the challenge as is required for Schnorr signatures. +pub(super) fn challenge(R: &RistrettoPoint, verifying_key: &VerifyingKey, msg: &[u8]) -> Scalar { + let mut transcript = Transcript::new(b"challenge"); + + transcript.commit_point(b"R", &R.compress()); + transcript.commit_point(b"verifying_key", verifying_key.as_compressed()); + transcript.append_message(b"message", msg); + transcript.challenge_scalar(b"challenge") +} From 9b775b93690efeacd572329a4551e5c19c4d9d07 Mon Sep 17 00:00:00 2001 From: Fiono11 Date: Mon, 6 May 2024 19:55:00 +0100 Subject: [PATCH 10/60] WIP --- src/olaf/frost/data_structures.rs | 104 ++++- src/olaf/frost/mod.rs | 153 ++++++- src/olaf/frost/tests.rs | 330 ++++++++++++++ src/olaf/mod.rs | 28 +- src/olaf/polynomials.rs | 193 +++++++++ src/olaf/simplpedpop/data_structures.rs | 50 +-- src/olaf/simplpedpop/mod.rs | 4 +- src/olaf/simplpedpop/tests.rs | 549 ++++++++++++------------ src/olaf/simplpedpop/utils.rs | 30 +- 9 files changed, 1102 insertions(+), 339 deletions(-) create mode 100644 src/olaf/frost/tests.rs create mode 100644 src/olaf/polynomials.rs diff --git a/src/olaf/frost/data_structures.rs b/src/olaf/frost/data_structures.rs index 85e3d99..49aaf0a 100644 --- a/src/olaf/frost/data_structures.rs +++ b/src/olaf/frost/data_structures.rs @@ -1,14 +1,21 @@ -use alloc::{collections::BTreeMap, vec::Vec}; +use alloc::{ + collections::{BTreeMap, BTreeSet}, + vec::Vec, +}; use curve25519_dalek::{RistrettoPoint, Scalar}; use merlin::Transcript; use getrandom_or_panic::{CryptoRng, RngCore}; use zeroize::ZeroizeOnDrop; -use crate::{context::SigningTranscript, olaf::GENERATOR, PublicKey, SecretKey}; +use crate::{ + context::SigningTranscript, + olaf::{polynomials::PolynomialCommitment, sum_commitments, GENERATOR}, + PublicKey, SecretKey, +}; -use super::{Identifier, VerifyingKey}; +use super::{errors::FROSTError, Identifier, VerifyingKey, VerifyingShare}; /// A scalar that is a signing nonce. -#[derive(ZeroizeOnDrop)] +#[derive(Clone, ZeroizeOnDrop)] pub(super) struct Nonce(pub(super) Scalar); impl Nonce { @@ -61,7 +68,7 @@ impl From<&Nonce> for NonceCommitment { /// Note that [`SigningNonces`] must be used *only once* for a signing /// operation; re-using nonces will result in leakage of a signer's long-lived /// signing key. -#[derive(ZeroizeOnDrop)] +#[derive(Clone, ZeroizeOnDrop)] pub(super) struct SigningNonces { /// The hiding [`Nonce`]. pub(super) hiding: Nonce, @@ -141,6 +148,7 @@ impl From<&SigningNonces> for SigningCommitments { /// Generated by the coordinator of the signing operation and distributed to /// each signing party. +#[derive(Clone)] pub(super) struct SigningPackage { /// The set of commitments participants published in the first round of the /// protocol. @@ -198,13 +206,40 @@ impl SigningPackage { /// A participant's signature share, which the coordinator will aggregate with all other signer's /// shares into the joint signature. +#[derive(Clone, Debug, PartialEq)] pub(super) struct SignatureShare { /// This participant's signature over the message. pub(super) share: Scalar, } +impl SignatureShare { + /// Tests if a signature share issued by a participant is valid before + /// aggregating it into a final joint signature to publish. + /// + /// This is the final step of [`verify_signature_share`] from the spec. + /// + /// [`verify_signature_share`]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#name-signature-share-verificatio + #[cfg(feature = "cheater-detection")] + pub(crate) fn verify( + &self, + identifier: Identifier, + group_commitment_share: &round1::GroupCommitmentShare, + verifying_share: &VerifyingShare, + lambda_i: Scalar, + challenge: &Challenge, + ) -> Result<(), FROSTError> { + if (GENERATOR * self.share) + != (group_commitment_share.0 + (verifying_share * challenge * lambda_i)) + { + return Err(FROSTError::InvalidSignatureShare { culprit: identifier }); + } + + Ok(()) + } +} + /// A FROST keypair, which is generated by the SimplPedPoP protocol. -#[derive(ZeroizeOnDrop)] +#[derive(Clone, ZeroizeOnDrop)] pub(super) struct KeyPackage { /// Denotes the participant identifier each secret share key package is owned by. #[zeroize(skip)] @@ -305,3 +340,60 @@ pub(super) fn encode_group_commitments( transcript } + +/// Public data that contains all the signers' verifying shares as well as the +/// group verifying key. +/// +/// Used for verification purposes before publishing a signature. +#[derive(Clone, Debug)] +pub struct PublicKeyPackage { + /// The verifying shares for all participants. Used to validate signature + /// shares they generate. + pub(super) verifying_shares: BTreeMap, + /// The joint public key for the entire group. + pub(super) verifying_key: VerifyingKey, +} + +impl PublicKeyPackage { + /// Create a new [`PublicKeyPackage`] instance. + pub(super) fn new( + verifying_shares: BTreeMap, + verifying_key: VerifyingKey, + ) -> Self { + Self { verifying_shares, verifying_key } + } + + /// Computes the public key package given a list of participant identifiers + /// and a [`VerifiableSecretSharingCommitment`]. This is useful in scenarios + /// where the commitments are published somewhere and it's desirable to + /// recreate the public key package from them. + pub(super) fn from_commitment( + identifiers: &BTreeSet, + commitment: &mut PolynomialCommitment, + ) -> Result { + let verifying_keys: BTreeMap<_, _> = identifiers + .iter() + .map(|id| (*id, PublicKey::from_point(commitment.evaluate(&Scalar::from(*id))))) + .collect(); + + Ok(PublicKeyPackage::new( + verifying_keys, + VerifyingKey::from_point(*commitment.coefficients_commitments.first().unwrap()), + )) + } + + /// Computes the public key package given a map of participant identifiers + /// and their [`VerifiableSecretSharingCommitment`] from a distributed key + /// generation process. This is useful in scenarios where the commitments + /// are published somewhere and it's desirable to recreate the public key + /// package from them. + pub(super) fn from_dkg_commitments( + commitments: &BTreeMap, + ) -> Result { + let identifiers: BTreeSet<_> = commitments.keys().copied().collect(); + let commitments: Vec<&PolynomialCommitment> = commitments.values().collect(); + let mut group_commitment = + PolynomialCommitment::sum_polynomial_commitments(&commitments[..]); + Self::from_commitment(&identifiers, &mut group_commitment) + } +} diff --git a/src/olaf/frost/mod.rs b/src/olaf/frost/mod.rs index 1492aba..c01c1ce 100644 --- a/src/olaf/frost/mod.rs +++ b/src/olaf/frost/mod.rs @@ -3,13 +3,15 @@ pub mod errors; mod data_structures; mod utils; +mod tests; -use alloc::vec::Vec; -use crate::{Keypair, PublicKey}; +use alloc::{collections::BTreeMap, vec::Vec}; +use curve25519_dalek::{traits::Identity, RistrettoPoint, Scalar}; +use crate::{Keypair, PublicKey, Signature}; use self::{ data_structures::{ - BindingFactor, BindingFactorList, KeyPackage, SignatureShare, SigningCommitments, - SigningNonces, SigningPackage, + BindingFactor, BindingFactorList, KeyPackage, PublicKeyPackage, SignatureShare, + SigningCommitments, SigningNonces, SigningPackage, }, errors::FROSTError, utils::{ @@ -17,6 +19,9 @@ use self::{ }, }; +use super::GENERATOR; + +pub(super) type VerifyingShare = PublicKey; pub(super) type VerifyingKey = PublicKey; pub(super) type Identifier = u16; @@ -144,3 +149,143 @@ impl Keypair { Ok(signature_share) } } + +/// Aggregates the signature shares to produce a final signature that +/// can be verified with the group public key. +/// +/// `signature_shares` maps the identifier of each participant to the +/// [`round2::SignatureShare`] they sent. These identifiers must come from whatever mapping +/// the coordinator has between communication channels and participants, i.e. +/// they must have assurance that the [`round2::SignatureShare`] came from +/// the participant with that identifier. +/// +/// This operation is performed by a coordinator that can communicate with all +/// the signing participants before publishing the final signature. The +/// coordinator can be one of the participants or a semi-trusted third party +/// (who is trusted to not perform denial of service attacks, but does not learn +/// any secret information). Note that because the coordinator is trusted to +/// report misbehaving parties in order to avoid publishing an invalid +/// signature, if the coordinator themselves is a signer and misbehaves, they +/// can avoid that step. However, at worst, this results in a denial of +/// service attack due to publishing an invalid signature. +pub fn aggregate( + signing_package: &SigningPackage, + signature_shares: &BTreeMap, + pubkeys: &PublicKeyPackage, +) -> Result { + // Check if signing_package.signing_commitments and signature_shares have + // the same set of identifiers, and if they are all in pubkeys.verifying_shares. + if signing_package.signing_commitments.len() != signature_shares.len() { + return Err(FROSTError::UnknownIdentifier); + } + + if !signing_package.signing_commitments.keys().all(|id| { + #[cfg(feature = "cheater-detection")] + return signature_shares.contains_key(id) && pubkeys.verifying_shares().contains_key(id); + #[cfg(not(feature = "cheater-detection"))] + return signature_shares.contains_key(id); + }) { + return Err(FROSTError::UnknownIdentifier); + } + + // Encodes the signing commitment list produced in round one as part of generating [`BindingFactor`], the + // binding factor. + let binding_factor_list: BindingFactorList = + BindingFactorList::compute_binding_factor_list(signing_package, &pubkeys.verifying_key); + + // Compute the group commitment from signing commitments produced in round one. + let group_commitment = compute_group_commitment(signing_package, &binding_factor_list)?; + + // The aggregation of the signature shares by summing them up, resulting in + // a plain Schnorr signature. + // + // Implements [`aggregate`] from the spec. + // + // [`aggregate`]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-5.3 + let mut s = Scalar::ZERO; + + for signature_share in signature_shares.values() { + s += signature_share.share; + } + + let signature = Signature { R: group_commitment.0.compress(), s }; + + // Verify the aggregate signature + let verification_result = + verify_signature(&signing_package.message, &signature, &pubkeys.verifying_key); + + // Only if the verification of the aggregate signature failed; verify each share to find the cheater. + // This approach is more efficient since we don't need to verify all shares + // if the aggregate signature is valid (which should be the common case). + #[cfg(feature = "cheater-detection")] + if let Err(err) = verification_result { + // Compute the per-message challenge. + let challenge = challenge( + &group_commitment.0, + pubkeys.verifying_key(), + signing_package.message().as_slice(), + ); + + // Verify the signature shares. + for (signature_share_identifier, signature_share) in signature_shares { + // Look up the public key for this signer, where `signer_pubkey` = _G.ScalarBaseMult(s[i])_, + // and where s[i] is a secret share of the constant term of _f_, the secret polynomial. + let signer_pubkey = pubkeys + .verifying_shares + .get(signature_share_identifier) + .ok_or(FROSTError::UnknownIdentifier)?; + + // Compute Lagrange coefficient. + let lambda_i = derive_interpolating_value(signature_share_identifier, signing_package)?; + + let binding_factor = binding_factor_list + .get(signature_share_identifier) + .ok_or(FROSTError::UnknownIdentifier)?; + + // Compute the commitment share. + let R_share = signing_package + .signing_commitment(signature_share_identifier) + .ok_or(FROSTError::UnknownIdentifier)? + .to_group_commitment_share(binding_factor); + + // Compute relation values to verify this signature share. + signature_share.verify( + *signature_share_identifier, + &R_share, + signer_pubkey, + lambda_i, + &challenge, + )?; + } + + // We should never reach here; but we return the verification error to be safe. + return Err(err); + } + + #[cfg(not(feature = "cheater-detection"))] + verification_result?; + + Ok(signature) +} + +// TODO: Integrate this into Keypair +/// Verify a purported `signature` with a pre-hashed [`Challenge`] made by the group public key. +pub(super) fn verify_signature( + msg: &[u8], + signature: &Signature, + public_key: &VerifyingKey, +) -> Result<(), FROSTError> { + let challenge = challenge(&signature.R.decompress().unwrap(), public_key, msg); + + // Verify check is h * ( - z * B + R + c * A) == 0 + // h * ( z * B - c * A - R) == 0 + let zB = GENERATOR * signature.s; + let cA = public_key.as_point() * challenge; + let check = zB - cA - signature.R.decompress().unwrap(); + + if check == RistrettoPoint::identity() { + Ok(()) + } else { + Err(FROSTError::InvalidSignature) + } +} diff --git a/src/olaf/frost/tests.rs b/src/olaf/frost/tests.rs new file mode 100644 index 0000000..771d9eb --- /dev/null +++ b/src/olaf/frost/tests.rs @@ -0,0 +1,330 @@ +#[cfg(test)] +mod tests { + use crate::{ + olaf::{ + frost::{ + aggregate, + data_structures::{ + KeyPackage, PublicKeyPackage, SignatureShare, SigningCommitments, + SigningNonces, SigningPackage, + }, + errors::FROSTError, + verify_signature, Identifier, + }, + GroupPublicKey, + }, + Keypair, Signature, + }; + use alloc::{collections::BTreeMap, vec::Vec}; + use rand_core::{CryptoRng, RngCore}; + + /// Test FROST signing with the given shares. + fn check_sign( + min_signers: u16, + key_packages: BTreeMap, + mut rng: R, + pubkey_package: PublicKeyPackage, + ) -> Result<(Vec, Signature, GroupPublicKey), FROSTError> { + let mut nonces_map: BTreeMap = BTreeMap::new(); + let mut commitments_map: BTreeMap = BTreeMap::new(); + + //////////////////////////////////////////////////////////////////////////// + // Round 1: generating nonces and signing commitments for each participant + //////////////////////////////////////////////////////////////////////////// + + for participant_identifier in key_packages.keys().take(min_signers as usize).cloned() { + // Generate one (1) nonce and one SigningCommitments instance for each + // participant, up to _min_signers_. + let sk = key_packages.get(&participant_identifier).unwrap().signing_share.clone(); + let keypair = Keypair::from(sk); + + let (nonces, commitments) = keypair.commit(); + nonces_map.insert(participant_identifier, nonces); + commitments_map.insert(participant_identifier, commitments); + } + + // This is what the signature aggregator / coordinator needs to do: + // - decide what message to sign + // - take one (unused) commitment per signing participant + let mut signature_shares = BTreeMap::new(); + let message = b"message to sign"; + let signing_package = SigningPackage::new(commitments_map, message); + + //////////////////////////////////////////////////////////////////////////// + // Round 2: each participant generates their signature share + //////////////////////////////////////////////////////////////////////////// + + for participant_identifier in nonces_map.keys() { + let key_package = key_packages.get(participant_identifier).unwrap(); + + let nonces_to_use = nonces_map.get(participant_identifier).unwrap(); + + check_sign_errors(signing_package.clone(), nonces_to_use.clone(), key_package.clone()); + + let sk = key_package.signing_share.clone(); + let keypair = Keypair::from(sk); + + // Each participant generates their signature share. + let signature_share = keypair.sign_frost( + &signing_package, + nonces_to_use, + key_package.verifying_key, + key_package.identifier, + key_package.min_signers, + )?; + signature_shares.insert(*participant_identifier, signature_share); + } + + //////////////////////////////////////////////////////////////////////////// + // Aggregation: collects the signing shares from all participants, + // generates the final signature. + //////////////////////////////////////////////////////////////////////////// + + #[cfg(not(feature = "cheater-detection"))] + let pubkey_package = PublicKeyPackage { + verifying_shares: BTreeMap::new(), + verifying_key: pubkey_package.verifying_key, + }; + + check_aggregate_errors( + signing_package.clone(), + signature_shares.clone(), + pubkey_package.clone(), + ); + + // Aggregate (also verifies the signature shares) + let group_signature = aggregate(&signing_package, &signature_shares, &pubkey_package)?; + + // Check that the threshold signature can be verified by the group public + // key (the verification key). + verify_signature(b"message to sign", &group_signature, &pubkey_package.verifying_key)?; + + // Check that the threshold signature can be verified by the group public + // key (the verification key) from KeyPackage.group_public_key + for (participant_identifier, _) in nonces_map.clone() { + let key_package = key_packages.get(&participant_identifier).unwrap(); + + verify_signature(b"message to sign", &group_signature, &key_package.verifying_key)?; + } + + Ok((message.to_vec(), group_signature, pubkey_package.verifying_key)) + } + + /// Test FROST signing with the given shares. + fn check_sign_preprocessing( + min_signers: u16, + key_packages: BTreeMap, + mut rng: R, + pubkey_package: PublicKeyPackage, + num_nonces: u8, + ) -> Result<(Vec>, Vec, GroupPublicKey), FROSTError> { + let mut nonces_map_vec: Vec> = Vec::new(); + let mut commitments_map_vec: Vec> = Vec::new(); + + //////////////////////////////////////////////////////////////////////////// + // Round 1: Generating nonces and signing commitments for each participant + //////////////////////////////////////////////////////////////////////////// + + // First, iterate to gather all nonces and commitments + let mut all_nonces_map: BTreeMap> = BTreeMap::new(); + let mut all_commitments_map: BTreeMap> = + BTreeMap::new(); + + for participant_identifier in key_packages.keys().take(min_signers as usize) { + let signing_share = + key_packages.get(&participant_identifier).unwrap().signing_share.clone(); + let keypair = Keypair::from(signing_share); + let (nonces, commitments) = keypair.preprocess(num_nonces); + + all_nonces_map.insert(participant_identifier.clone(), nonces); + all_commitments_map.insert(*participant_identifier, commitments); + } + + // Now distribute these nonces and commitments to individual participant maps + let mut nonces_map: BTreeMap = BTreeMap::new(); + let mut commitments_map: BTreeMap = BTreeMap::new(); + + for (id, nonces) in &all_nonces_map { + for nonce in nonces { + nonces_map.insert(id.clone(), nonce.clone()); + } + } + + for (id, commitments) in &all_commitments_map { + for commitment in commitments { + commitments_map.insert(id.clone(), commitment.clone()); + } + } + + nonces_map_vec.push(nonces_map); + commitments_map_vec.push(commitments_map); + + // This is what the signature aggregator / coordinator needs to do: + // - decide what message to sign + // - take one (unused) commitment per signing participant + let mut signature_shares = BTreeMap::new(); + + let mut messages = Vec::new(); + + for i in 0..num_nonces { + let mut message = b"message to sign".to_vec(); + message.extend_from_slice(&i.to_be_bytes()); + messages.push(message); + } + + //////////////////////////////////////////////////////////////////////////// + // Round 2: each participant generates their signature share + //////////////////////////////////////////////////////////////////////////// + + let mut signing_packages = Vec::new(); + let mut group_signatures = Vec::new(); + + for i in 0..commitments_map_vec.len() { + let message = &messages[i as usize]; + + let signing_package = + SigningPackage::new(commitments_map_vec[i as usize].clone(), message); + + signing_packages.push(signing_package.clone()); + + for participant_identifier in nonces_map_vec[i as usize].keys() { + let key_package = key_packages.get(participant_identifier).unwrap(); + + let nonces_to_use = nonces_map_vec[i as usize].get(participant_identifier).unwrap(); + + check_sign_errors( + signing_package.clone(), + nonces_to_use.clone(), + key_package.clone(), + ); + + let sk = key_package.signing_share.clone(); + let keypair = Keypair::from(sk); + + // Each participant generates their signature share. + let signature_share = keypair.sign_frost( + &signing_package, + nonces_to_use, + key_package.verifying_key, + key_package.identifier, + key_package.min_signers, + )?; + signature_shares.insert(*participant_identifier, signature_share); + } + + //////////////////////////////////////////////////////////////////////////// + // Aggregation: collects the signing shares from all participants, + // generates the final signature. + //////////////////////////////////////////////////////////////////////////// + + #[cfg(not(feature = "cheater-detection"))] + let pubkey_package = PublicKeyPackage { + verifying_shares: BTreeMap::new(), + verifying_key: pubkey_package.verifying_key, + }; + + check_aggregate_errors( + signing_package.clone(), + signature_shares.clone(), + pubkey_package.clone(), + ); + + // Aggregate (also verifies the signature shares) + let group_signature = aggregate(&signing_package, &signature_shares, &pubkey_package)?; + + group_signatures.push(group_signature); + + // Check that the threshold signature can be verified by the group public key. + verify_signature(message, &group_signature, &pubkey_package.verifying_key)?; + + // Check that the threshold signature can be verified by the group public + // key from KeyPackage.group_public_key + for (participant_identifier, _) in nonces_map_vec[i as usize].clone() { + let key_package = key_packages.get(&participant_identifier).unwrap(); + + verify_signature(message, &group_signature, &key_package.verifying_key)?; + } + } + + Ok((messages, group_signatures, pubkey_package.verifying_key)) + } + + fn check_sign_errors( + signing_package: SigningPackage, + signing_nonces: SigningNonces, + key_package: KeyPackage, + ) { + // Check if passing not enough commitments causes an error + + let mut commitments = signing_package.signing_commitments.clone(); + // Remove one commitment that's not from the key_package owner + let id = *commitments.keys().find(|&&id| id != key_package.identifier).unwrap(); + commitments.remove(&id); + let signing_package = SigningPackage::new(commitments, &signing_package.message); + + let sk = key_package.signing_share.clone(); + let keypair = Keypair::from(sk); + + let r = keypair.sign_frost( + &signing_package, + &signing_nonces, + key_package.verifying_key, + key_package.identifier, + key_package.min_signers, + ); + + assert_eq!(r, Err(FROSTError::IncorrectNumberOfSigningCommitments)); + } + + fn check_aggregate_errors( + signing_package: SigningPackage, + signature_shares: BTreeMap, + pubkey_package: PublicKeyPackage, + ) { + #[cfg(feature = "cheater-detection")] + check_aggregate_corrupted_share( + signing_package.clone(), + signature_shares.clone(), + pubkey_package.clone(), + ); + + check_aggregate_invalid_share_identifier_for_verifying_shares( + signing_package, + signature_shares, + pubkey_package, + ); + } + + #[cfg(feature = "cheater-detection")] + fn check_aggregate_corrupted_share( + signing_package: SigningPackage, + mut signature_shares: BTreeMap, + pubkey_package: PublicKeyPackage, + ) { + let one = Scalar::ONE; + // Corrupt a share + let id = *signature_shares.keys().next().unwrap(); + signature_shares.get_mut(&id).unwrap().share = signature_shares[&id].share + one; + let e = aggregate(&signing_package, &signature_shares, &pubkey_package).unwrap_err(); + assert_eq!(e, FROSTError::InvalidSignatureShare { culprit: id }); + } + + /// Test NCC-E008263-4VP audit finding (PublicKeyPackage). + /// Note that the SigningPackage part of the finding is not currently reachable + /// since it's caught by `compute_lagrange_coefficient()`, and the Binding Factor + /// part can't either since it's caught before by the PublicKeyPackage part. + fn check_aggregate_invalid_share_identifier_for_verifying_shares( + signing_package: SigningPackage, + mut signature_shares: BTreeMap, + pubkey_package: PublicKeyPackage, + ) { + // Insert a new share (copied from other existing share) with an invalid identifier + signature_shares.insert(0, signature_shares.values().next().unwrap().clone()); + // Should error, but not panic + aggregate(&signing_package, &signature_shares, &pubkey_package) + .expect_err("should not work"); + } + + #[test] + fn test_n_of_n_frost_with_simplpedpop() {} +} diff --git a/src/olaf/mod.rs b/src/olaf/mod.rs index 916ea93..75fe2f1 100644 --- a/src/olaf/mod.rs +++ b/src/olaf/mod.rs @@ -1,10 +1,36 @@ //! Implementation of the Olaf protocol (), which is composed of the Distributed //! Key Generation (DKG) protocol SimplPedPoP and the Threshold Signing protocol FROST. -use curve25519_dalek::{constants::RISTRETTO_BASEPOINT_POINT, RistrettoPoint}; +use alloc::vec::Vec; +use curve25519_dalek::{constants::RISTRETTO_BASEPOINT_POINT, traits::Identity, RistrettoPoint}; +use crate::PublicKey; + +use self::{polynomials::CoefficientCommitment, simplpedpop::errors::DKGError}; pub mod simplpedpop; pub mod frost; +mod polynomials; const MINIMUM_THRESHOLD: u16 = 2; const GENERATOR: RistrettoPoint = RISTRETTO_BASEPOINT_POINT; + +/// The group public key generated by a trusted dealer or a DKG protocol like SimplPedPoP. +pub type GroupPublicKey = PublicKey; +/// The share of the group public key, which corresponds to the total secret share commitment of the secret sharing scheme. +pub type GroupPublicKeyShare = CoefficientCommitment; + +pub(super) fn sum_commitments( + commitments: &[&Vec], +) -> Result, DKGError> { + let mut group_commitment = + vec![ + RistrettoPoint::identity(); + commitments.first().ok_or(DKGError::IncorrectNumberOfCommitments)?.len() + ]; + for commitment in commitments { + for (i, c) in group_commitment.iter_mut().enumerate() { + *c += commitment.get(i).ok_or(DKGError::IncorrectNumberOfCommitments)?; + } + } + Ok(group_commitment) +} diff --git a/src/olaf/polynomials.rs b/src/olaf/polynomials.rs new file mode 100644 index 0000000..e4d8039 --- /dev/null +++ b/src/olaf/polynomials.rs @@ -0,0 +1,193 @@ +//! 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 super::GENERATOR; + +pub(super) type Coefficient = Scalar; +pub(super) type Value = Scalar; +pub(super) type ValueCommitment = RistrettoPoint; +pub(super) type CoefficientCommitment = RistrettoPoint; + +/// A polynomial. +#[derive(Debug, Clone, ZeroizeOnDrop)] +pub struct Polynomial { + pub(super) coefficients: Vec, +} + +impl Polynomial { + pub(super) 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(super) 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(super) coefficients_commitments: Vec, +} + +impl PolynomialCommitment { + pub(super) fn commit(polynomial: &Polynomial) -> Self { + let coefficients_commitments = polynomial + .coefficients + .iter() + .map(|coefficient| GENERATOR * coefficient) + .collect(); + + Self { coefficients_commitments } + } + + pub(super) 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(super) 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::GENERATOR; + + use super::*; + 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/data_structures.rs b/src/olaf/simplpedpop/data_structures.rs index 581af66..3931b0a 100644 --- a/src/olaf/simplpedpop/data_structures.rs +++ b/src/olaf/simplpedpop/data_structures.rs @@ -8,19 +8,19 @@ use crate::{ context::SigningTranscript, olaf::MINIMUM_THRESHOLD, PublicKey, Signature, PUBLIC_KEY_LENGTH, SIGNATURE_LENGTH, }; -use super::{errors::DKGError}; +use super::errors::DKGError; -pub(crate) const COMPRESSED_RISTRETTO_LENGTH: usize = 32; -pub(crate) const U16_LENGTH: usize = 2; -pub(crate) const ENCRYPTION_NONCE_LENGTH: usize = 12; -pub(crate) const RECIPIENTS_HASH_LENGTH: usize = 16; -pub(crate) const CHACHA20POLY1305_LENGTH: usize = 64; +pub(super) const COMPRESSED_RISTRETTO_LENGTH: usize = 32; +pub(super) const U16_LENGTH: usize = 2; +pub(super) const ENCRYPTION_NONCE_LENGTH: usize = 12; +pub(super) const RECIPIENTS_HASH_LENGTH: usize = 16; +pub(super) const CHACHA20POLY1305_LENGTH: usize = 64; /// The parameters of a given execution of the SimplPedPoP protocol. #[derive(Clone, PartialEq, Eq)] pub struct Parameters { - pub(crate) participants: u16, - pub(crate) threshold: u16, + pub(super) participants: u16, + pub(super) threshold: u16, } impl Parameters { @@ -29,7 +29,7 @@ impl Parameters { Parameters { participants, threshold } } - pub(crate) fn validate(&self) -> Result<(), DKGError> { + pub(super) fn validate(&self) -> Result<(), DKGError> { if self.threshold < MINIMUM_THRESHOLD { return Err(DKGError::InsufficientThreshold); } @@ -45,7 +45,7 @@ impl Parameters { Ok(()) } - pub(crate) fn commit(&self, t: &mut T) { + pub(super) 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()); } @@ -57,8 +57,8 @@ impl Parameters { /// participant, but typical thresholds lie between 1/2 and 2/3, /// so this doubles or tripples bandwidth usage. pub struct AllMessage { - pub(crate) content: MessageContent, - pub(crate) signature: Signature, + pub(super) content: MessageContent, + pub(super) signature: Signature, } impl AllMessage { @@ -92,14 +92,14 @@ impl AllMessage { /// The contents of the message destined to all participants. pub struct MessageContent { - pub(crate) sender: PublicKey, - pub(crate) encryption_nonce: [u8; ENCRYPTION_NONCE_LENGTH], - pub(crate) parameters: Parameters, - pub(crate) recipients_hash: [u8; RECIPIENTS_HASH_LENGTH], - pub(crate) point_polynomial: Vec, - pub(crate) ciphertexts: Vec>, - pub(crate) ephemeral_key: PublicKey, - pub(crate) proof_of_possession: Signature, + pub(super) sender: PublicKey, + pub(super) encryption_nonce: [u8; ENCRYPTION_NONCE_LENGTH], + pub(super) parameters: Parameters, + pub(super) recipients_hash: [u8; RECIPIENTS_HASH_LENGTH], + pub(super) point_polynomial: Vec, + pub(super) ciphertexts: Vec>, + pub(super) ephemeral_key: PublicKey, + pub(super) proof_of_possession: Signature, } impl MessageContent { @@ -221,9 +221,9 @@ impl MessageContent { /// The signed output of the SimplPedPoP protocol. pub struct DKGOutput { - pub(crate) sender: PublicKey, - pub(crate) content: DKGOutputContent, - pub(crate) signature: Signature, + pub(super) sender: PublicKey, + pub(super) content: DKGOutputContent, + pub(super) signature: Signature, } impl DKGOutput { @@ -270,8 +270,8 @@ impl DKGOutput { /// The content of the signed output of the SimplPedPoP protocol. #[derive(Debug)] pub struct DKGOutputContent { - pub(crate) group_public_key: PublicKey, - pub(crate) verifying_keys: Vec, + pub(super) group_public_key: PublicKey, + pub(super) verifying_keys: Vec, } impl DKGOutputContent { diff --git a/src/olaf/simplpedpop/mod.rs b/src/olaf/simplpedpop/mod.rs index 23b5558..c844116 100644 --- a/src/olaf/simplpedpop/mod.rs +++ b/src/olaf/simplpedpop/mod.rs @@ -18,10 +18,10 @@ use data_structures::{ use errors::{DKGError, DKGResult}; use utils::{ decrypt, derive_secret_key_from_secret, encrypt, evaluate_polynomial, - evaluate_polynomial_commitment, generate_coefficients, generate_identifier, sum_commitments, + evaluate_polynomial_commitment, generate_coefficients, generate_identifier, }; -use super::GENERATOR; +use super::{sum_commitments, GENERATOR}; impl Keypair { /// First round of the SimplPedPoP protocol. diff --git a/src/olaf/simplpedpop/tests.rs b/src/olaf/simplpedpop/tests.rs index 317b611..b46e798 100644 --- a/src/olaf/simplpedpop/tests.rs +++ b/src/olaf/simplpedpop/tests.rs @@ -1,350 +1,343 @@ #[cfg(test)] mod tests { - mod simplpedpop { - use crate::olaf::simplpedpop::data_structures::{ - AllMessage, Parameters, CHACHA20POLY1305_LENGTH, RECIPIENTS_HASH_LENGTH, - }; - use crate::olaf::simplpedpop::errors::DKGError; - use crate::olaf::simplpedpop::GENERATOR; - use crate::{Keypair, PublicKey}; - use alloc::vec::Vec; - use curve25519_dalek::ristretto::RistrettoPoint; - use curve25519_dalek::traits::Identity; - use merlin::Transcript; - - #[test] - fn test_simplpedpop_protocol() { - let threshold = 2; - let participants = 2; - let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); - let public_keys: Vec = keypairs.iter().map(|kp| kp.public).collect(); - - let mut all_messages = Vec::new(); - for i in 0..participants { - let message: AllMessage = - keypairs[i].simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap(); - all_messages.push(message); - } + use crate::olaf::simplpedpop::data_structures::{ + AllMessage, Parameters, CHACHA20POLY1305_LENGTH, RECIPIENTS_HASH_LENGTH, + }; + use crate::olaf::simplpedpop::errors::DKGError; + use crate::olaf::simplpedpop::GENERATOR; + use crate::{Keypair, PublicKey}; + use alloc::vec::Vec; + use curve25519_dalek::ristretto::RistrettoPoint; + use curve25519_dalek::traits::Identity; + use merlin::Transcript; + + #[test] + fn test_simplpedpop_protocol() { + let threshold = 2; + let participants = 2; + let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public).collect(); + + let mut all_messages = Vec::new(); + for i in 0..participants { + let message: AllMessage = + keypairs[i].simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap(); + all_messages.push(message); + } - let mut dkg_outputs = Vec::new(); + let mut dkg_outputs = Vec::new(); - for kp in keypairs.iter() { - let dkg_output = kp.simplpedpop_recipient_all(&all_messages).unwrap(); - dkg_outputs.push(dkg_output); - } + for kp in keypairs.iter() { + let dkg_output = kp.simplpedpop_recipient_all(&all_messages).unwrap(); + dkg_outputs.push(dkg_output); + } - // Verify that all DKG outputs are equal for group_public_key and verifying_keys - assert!( - dkg_outputs.windows(2).all(|w| w[0].0.content.group_public_key - == w[1].0.content.group_public_key - && w[0].0.content.verifying_keys.len() == w[1].0.content.verifying_keys.len() - && w[0] - .0 - .content - .verifying_keys - .iter() - .zip(w[1].0.content.verifying_keys.iter()) - .all(|(a, b)| a == b)), - "All DKG outputs should have identical group public keys and verifying keys." - ); - - // Verify that all verifying_keys are valid - for i in 0..participants { - for j in 0..participants { - assert_eq!( - dkg_outputs[i].0.content.verifying_keys[j].compress(), - (dkg_outputs[j].1 * GENERATOR).compress(), - "Verification of total secret shares failed!" - ); - } + // Verify that all DKG outputs are equal for group_public_key and verifying_keys + assert!( + dkg_outputs.windows(2).all(|w| w[0].0.content.group_public_key + == w[1].0.content.group_public_key + && w[0].0.content.verifying_keys.len() == w[1].0.content.verifying_keys.len() + && w[0] + .0 + .content + .verifying_keys + .iter() + .zip(w[1].0.content.verifying_keys.iter()) + .all(|(a, b)| a == b)), + "All DKG outputs should have identical group public keys and verifying keys." + ); + + // Verify that all verifying_keys are valid + for i in 0..participants { + for j in 0..participants { + assert_eq!( + dkg_outputs[i].0.content.verifying_keys[j].compress(), + (dkg_outputs[j].1 * GENERATOR).compress(), + "Verification of total secret shares failed!" + ); } } + } - #[test] - fn test_invalid_number_of_messages() { - let threshold = 3; - let participants = 5; + #[test] + fn test_invalid_number_of_messages() { + let threshold = 3; + let participants = 5; - let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); - let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); + let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); - let mut messages: Vec = keypairs - .iter() - .map(|kp| kp.simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap()) - .collect(); + let mut messages: Vec = keypairs + .iter() + .map(|kp| kp.simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap()) + .collect(); - messages.pop(); + messages.pop(); - let result = keypairs[0].simplpedpop_recipient_all(&messages); + let result = keypairs[0].simplpedpop_recipient_all(&messages); - match result { - Ok(_) => panic!("Expected an error, but got Ok."), - Err(e) => match e { - DKGError::InvalidNumberOfMessages => assert!(true), - _ => { - panic!("Expected DKGError::InvalidNumberOfMessages, but got {:?}", e) - }, + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + DKGError::InvalidNumberOfMessages => assert!(true), + _ => { + panic!("Expected DKGError::InvalidNumberOfMessages, but got {:?}", e) }, - } + }, } + } - #[test] - fn test_different_parameters() { - // Define threshold and participants - let threshold = 3; - let participants = 5; - - // Generate keypairs for participants - let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); - let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); - - // Each participant creates an AllMessage with different parameters - let mut messages: Vec = Vec::new(); - for i in 0..participants { - let mut parameters = Parameters::generate(participants as u16, threshold); - // Modify parameters for the first participant - if i == 0 { - parameters.threshold += 1; // Modify threshold - } - let message = keypairs[i] - .simplpedpop_contribute_all(parameters.threshold, public_keys.clone()) - .unwrap(); - messages.push(message); + #[test] + fn test_different_parameters() { + // Define threshold and participants + let threshold = 3; + let participants = 5; + + // Generate keypairs for participants + let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); + + // Each participant creates an AllMessage with different parameters + let mut messages: Vec = Vec::new(); + for i in 0..participants { + let mut parameters = Parameters::generate(participants as u16, threshold); + // Modify parameters for the first participant + if i == 0 { + parameters.threshold += 1; // Modify threshold } + let message = keypairs[i] + .simplpedpop_contribute_all(parameters.threshold, public_keys.clone()) + .unwrap(); + messages.push(message); + } - // Call simplpedpop_recipient_all - let result = keypairs[0].simplpedpop_recipient_all(&messages); + // Call simplpedpop_recipient_all + let result = keypairs[0].simplpedpop_recipient_all(&messages); - // Check if the result is an error - match result { - Ok(_) => panic!("Expected an error, but got Ok."), - Err(e) => match e { - DKGError::DifferentParameters => assert!(true), - _ => panic!("Expected DKGError::DifferentRecipientsHash, but got {:?}", e), - }, - } + // Check if the result is an error + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + DKGError::DifferentParameters => assert!(true), + _ => panic!("Expected DKGError::DifferentRecipientsHash, but got {:?}", e), + }, } + } - #[test] - fn test_different_recipients_hash() { - let threshold = 3; - let participants = 5; + #[test] + fn test_different_recipients_hash() { + let threshold = 3; + let participants = 5; - let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); - let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); + let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); - let mut messages: Vec = keypairs - .iter() - .map(|kp| kp.simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap()) - .collect(); + let mut messages: Vec = keypairs + .iter() + .map(|kp| kp.simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap()) + .collect(); - messages[1].content.recipients_hash = [1; RECIPIENTS_HASH_LENGTH]; + messages[1].content.recipients_hash = [1; RECIPIENTS_HASH_LENGTH]; - let result = keypairs[0].simplpedpop_recipient_all(&messages); + let result = keypairs[0].simplpedpop_recipient_all(&messages); - match result { - Ok(_) => panic!("Expected an error, but got Ok."), - Err(e) => match e { - DKGError::DifferentRecipientsHash => assert!(true), - _ => { - panic!("Expected DKGError::DifferentRecipientsHash, but got {:?}", e) - }, + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + DKGError::DifferentRecipientsHash => assert!(true), + _ => { + panic!("Expected DKGError::DifferentRecipientsHash, but got {:?}", e) }, - } + }, } + } - #[test] - fn test_incorrect_number_of_commitments() { - let threshold = 3; - let participants = 5; + #[test] + fn test_incorrect_number_of_commitments() { + let threshold = 3; + let participants = 5; - let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); - let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); + let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); - let mut messages: Vec = keypairs - .iter() - .map(|kp| kp.simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap()) - .collect(); + let mut messages: Vec = keypairs + .iter() + .map(|kp| kp.simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap()) + .collect(); - messages[1].content.point_polynomial.pop(); + messages[1].content.point_polynomial.pop(); - let result = keypairs[0].simplpedpop_recipient_all(&messages); + let result = keypairs[0].simplpedpop_recipient_all(&messages); - match result { - Ok(_) => panic!("Expected an error, but got Ok."), - Err(e) => match e { - DKGError::IncorrectNumberOfCommitments => assert!(true), - _ => panic!("Expected DKGError::IncorrectNumberOfCommitments, but got {:?}", e), - }, - } + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + DKGError::IncorrectNumberOfCommitments => assert!(true), + _ => panic!("Expected DKGError::IncorrectNumberOfCommitments, but got {:?}", e), + }, } + } - #[test] - fn test_incorrect_number_of_encrypted_shares() { - let threshold = 3; - let participants = 5; + #[test] + fn test_incorrect_number_of_encrypted_shares() { + let threshold = 3; + let participants = 5; - let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); - let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); + let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); - let mut messages: Vec = keypairs - .iter() - .map(|kp| kp.simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap()) - .collect(); + let mut messages: Vec = keypairs + .iter() + .map(|kp| kp.simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap()) + .collect(); - messages[1].content.ciphertexts.pop(); + messages[1].content.ciphertexts.pop(); - let result = keypairs[0].simplpedpop_recipient_all(&messages); + let result = keypairs[0].simplpedpop_recipient_all(&messages); - match result { - Ok(_) => panic!("Expected an error, but got Ok."), - Err(e) => match e { - DKGError::IncorrectNumberOfEncryptedShares => assert!(true), - _ => panic!( - "Expected DKGError::IncorrectNumberOfEncryptedShares, but got {:?}", - e - ), - }, - } + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + DKGError::IncorrectNumberOfEncryptedShares => assert!(true), + _ => panic!("Expected DKGError::IncorrectNumberOfEncryptedShares, but got {:?}", e), + }, } + } - #[test] - fn test_invalid_secret_share() { - let threshold = 3; - let participants = 5; + #[test] + fn test_invalid_secret_share() { + let threshold = 3; + let participants = 5; - let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); - let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); + let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); - let mut messages: Vec = keypairs - .iter() - .map(|kp| kp.simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap()) - .collect(); + let mut messages: Vec = keypairs + .iter() + .map(|kp| kp.simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap()) + .collect(); - messages[1].content.ciphertexts[0] = vec![1; CHACHA20POLY1305_LENGTH]; + messages[1].content.ciphertexts[0] = vec![1; CHACHA20POLY1305_LENGTH]; - let result = keypairs[0].simplpedpop_recipient_all(&messages); + let result = keypairs[0].simplpedpop_recipient_all(&messages); - match result { - Ok(_) => panic!("Expected an error, but got Ok."), - Err(e) => match e { - DKGError::InvalidSecretShare => assert!(true), - _ => panic!("Expected DKGError::InvalidSecretShare, but got {:?}", e), - }, - } + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + DKGError::InvalidSecretShare => assert!(true), + _ => panic!("Expected DKGError::InvalidSecretShare, but got {:?}", e), + }, } + } - #[test] - fn test_invalid_proof_of_possession() { - let threshold = 3; - let participants = 5; + #[test] + fn test_invalid_proof_of_possession() { + let threshold = 3; + let participants = 5; - let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); - let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); + let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); - let mut messages: Vec = keypairs - .iter() - .map(|kp| kp.simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap()) - .collect(); + let mut messages: Vec = keypairs + .iter() + .map(|kp| kp.simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap()) + .collect(); - messages[1].content.proof_of_possession = - keypairs[1].secret.sign(Transcript::new(b"invalid"), &keypairs[1].public); + messages[1].content.proof_of_possession = + keypairs[1].secret.sign(Transcript::new(b"invalid"), &keypairs[1].public); - let result = keypairs[0].simplpedpop_recipient_all(&messages); + let result = keypairs[0].simplpedpop_recipient_all(&messages); - match result { - Ok(_) => panic!("Expected an error, but got Ok."), - Err(e) => match e { - DKGError::InvalidProofOfPossession(_) => assert!(true), - _ => panic!("Expected DKGError::InvalidProofOfPossession, but got {:?}", e), - }, - } + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + DKGError::InvalidProofOfPossession(_) => assert!(true), + _ => panic!("Expected DKGError::InvalidProofOfPossession, but got {:?}", e), + }, } + } - #[test] - fn test_invalid_signature() { - let threshold = 3; - let participants = 5; + #[test] + fn test_invalid_signature() { + let threshold = 3; + let participants = 5; - let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); - let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); + let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); - let mut messages: Vec = keypairs - .iter() - .map(|kp| kp.simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap()) - .collect(); + let mut messages: Vec = keypairs + .iter() + .map(|kp| kp.simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap()) + .collect(); - messages[1].signature = - keypairs[1].secret.sign(Transcript::new(b"invalid"), &keypairs[1].public); + messages[1].signature = + keypairs[1].secret.sign(Transcript::new(b"invalid"), &keypairs[1].public); - let result = keypairs[0].simplpedpop_recipient_all(&messages); + let result = keypairs[0].simplpedpop_recipient_all(&messages); - match result { - Ok(_) => panic!("Expected an error, but got Ok."), - Err(e) => match e { - DKGError::InvalidSignature(_) => assert!(true), - _ => panic!("Expected DKGError::InvalidSignature, but got {:?}", e), - }, - } + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + DKGError::InvalidSignature(_) => assert!(true), + _ => panic!("Expected DKGError::InvalidSignature, but got {:?}", e), + }, } + } - #[test] - fn test_invalid_threshold() { - let keypair = Keypair::generate(); - let result = keypair.simplpedpop_contribute_all( - 1, - vec![ - PublicKey::from_point(RistrettoPoint::identity()), - PublicKey::from_point(RistrettoPoint::identity()), - ], - ); - - match result { - Ok(_) => panic!("Expected an error, but got Ok."), - Err(e) => match e { - DKGError::InsufficientThreshold => assert!(true), - _ => panic!("Expected DKGError::DifferentRecipientsHash, but got {:?}", e), - }, - } + #[test] + fn test_invalid_threshold() { + let keypair = Keypair::generate(); + let result = keypair.simplpedpop_contribute_all( + 1, + vec![ + PublicKey::from_point(RistrettoPoint::identity()), + PublicKey::from_point(RistrettoPoint::identity()), + ], + ); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + DKGError::InsufficientThreshold => assert!(true), + _ => panic!("Expected DKGError::DifferentRecipientsHash, but got {:?}", e), + }, } + } - #[test] - fn test_invalid_participants() { - let keypair = Keypair::generate(); - let result = keypair.simplpedpop_contribute_all( - 2, - vec![PublicKey::from_point(RistrettoPoint::identity())], - ); - - match result { - Ok(_) => panic!("Expected an error, but got Ok."), - Err(e) => match e { - DKGError::InvalidNumberOfParticipants => assert!(true), - _ => { - panic!("Expected DKGError::DifferentRecipientsHash, but got {:?}", e) - }, + #[test] + fn test_invalid_participants() { + let keypair = Keypair::generate(); + let result = keypair + .simplpedpop_contribute_all(2, vec![PublicKey::from_point(RistrettoPoint::identity())]); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + DKGError::InvalidNumberOfParticipants => assert!(true), + _ => { + panic!("Expected DKGError::DifferentRecipientsHash, but got {:?}", e) }, - } + }, } + } - #[test] - fn test_threshold_greater_than_participants() { - let keypair = Keypair::generate(); - let result = keypair.simplpedpop_contribute_all( - 3, - vec![ - PublicKey::from_point(RistrettoPoint::identity()), - PublicKey::from_point(RistrettoPoint::identity()), - ], - ); - - match result { - Ok(_) => panic!("Expected an error, but got Ok."), - Err(e) => match e { - DKGError::ExcessiveThreshold => assert!(true), - _ => panic!("Expected DKGError::DifferentRecipientsHash, but got {:?}", e), - }, - } + #[test] + fn test_threshold_greater_than_participants() { + let keypair = Keypair::generate(); + let result = keypair.simplpedpop_contribute_all( + 3, + vec![ + PublicKey::from_point(RistrettoPoint::identity()), + PublicKey::from_point(RistrettoPoint::identity()), + ], + ); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + DKGError::ExcessiveThreshold => assert!(true), + _ => panic!("Expected DKGError::DifferentRecipientsHash, but got {:?}", e), + }, } } } diff --git a/src/olaf/simplpedpop/utils.rs b/src/olaf/simplpedpop/utils.rs index 0abdd41..f73ae97 100644 --- a/src/olaf/simplpedpop/utils.rs +++ b/src/olaf/simplpedpop/utils.rs @@ -10,7 +10,7 @@ use super::{ errors::{DKGError, DKGResult}, }; -pub(crate) fn generate_identifier(recipients_hash: &[u8; 16], index: u16) -> Scalar { +pub(super) fn generate_identifier(recipients_hash: &[u8; 16], index: u16) -> Scalar { let mut pos = merlin::Transcript::new(b"Identifier"); pos.append_message(b"RecipientsHash", recipients_hash); pos.append_message(b"i", &index.to_le_bytes()[..]); @@ -19,7 +19,7 @@ pub(crate) fn generate_identifier(recipients_hash: &[u8; 16], index: u16) -> Sca /// Evaluate the polynomial with the given coefficients (constant term first) /// at the point x=identifier using Horner's method. -pub(crate) fn evaluate_polynomial(identifier: &Scalar, coefficients: &[Scalar]) -> Scalar { +pub(super) fn evaluate_polynomial(identifier: &Scalar, coefficients: &[Scalar]) -> Scalar { let mut value = Scalar::ZERO; let ell_scalar = identifier; @@ -32,7 +32,7 @@ pub(crate) fn evaluate_polynomial(identifier: &Scalar, coefficients: &[Scalar]) } /// Return a vector of randomly generated polynomial coefficients ([`Scalar`]s). -pub(crate) fn generate_coefficients( +pub(super) fn generate_coefficients( size: usize, rng: &mut R, ) -> Vec { @@ -49,7 +49,7 @@ pub(crate) fn generate_coefficients( coefficients } -pub(crate) fn derive_secret_key_from_secret( +pub(super) fn derive_secret_key_from_secret( secret: &Scalar, mut rng: R, ) -> SecretKey { @@ -66,7 +66,7 @@ pub(crate) fn derive_secret_key_from_secret( .expect("This never fails because bytes has length 64 and the key is a scalar") } -pub(crate) fn evaluate_polynomial_commitment( +pub(super) fn evaluate_polynomial_commitment( identifier: &Scalar, commitment: &[RistrettoPoint], ) -> RistrettoPoint { @@ -80,23 +80,7 @@ pub(crate) fn evaluate_polynomial_commitment( result } -pub(crate) fn sum_commitments( - commitments: &[&Vec], -) -> Result, DKGError> { - let mut group_commitment = - vec![ - RistrettoPoint::identity(); - commitments.first().ok_or(DKGError::IncorrectNumberOfCommitments)?.len() - ]; - for commitment in commitments { - for (i, c) in group_commitment.iter_mut().enumerate() { - *c += commitment.get(i).ok_or(DKGError::IncorrectNumberOfCommitments)?; - } - } - Ok(group_commitment) -} - -pub(crate) fn encrypt( +pub(super) fn encrypt( scalar_evaluation: &Scalar, ephemeral_key: &Scalar, mut transcript: T, @@ -124,7 +108,7 @@ pub(crate) fn encrypt( Ok(ciphertext) } -pub(crate) fn decrypt( +pub(super) fn decrypt( mut transcript: T, recipient: &PublicKey, key_exchange: &RistrettoPoint, From 75dd78b385713d20663cd6e50e4c8d127d45a948 Mon Sep 17 00:00:00 2001 From: Fiono11 Date: Mon, 6 May 2024 21:27:47 +0100 Subject: [PATCH 11/60] WIP --- src/olaf/frost/data_structures.rs | 10 ++--- src/olaf/frost/mod.rs | 3 +- src/olaf/frost/tests.rs | 5 ++- src/olaf/frost/utils.rs | 8 ++-- src/olaf/mod.rs | 65 ++++++++++++++++++++++++++++++- 5 files changed, 78 insertions(+), 13 deletions(-) diff --git a/src/olaf/frost/data_structures.rs b/src/olaf/frost/data_structures.rs index 49aaf0a..504f7e3 100644 --- a/src/olaf/frost/data_structures.rs +++ b/src/olaf/frost/data_structures.rs @@ -152,7 +152,7 @@ impl From<&SigningNonces> for SigningCommitments { pub(super) struct SigningPackage { /// The set of commitments participants published in the first round of the /// protocol. - pub(super) signing_commitments: BTreeMap, + pub(super) signing_commitments: BTreeMap, /// Message which each participant will sign. /// /// Each signer should perform protocol-specific verification on the @@ -197,7 +197,7 @@ impl SigningPackage { self.signing_commitments .keys() .map(|identifier| { - transcript.append_message(b"identifier", &identifier.to_be_bytes()); + transcript.append_message(b"identifier", identifier.0.as_bytes()); (*identifier, transcript.clone()) }) .collect() @@ -243,7 +243,7 @@ impl SignatureShare { pub(super) struct KeyPackage { /// Denotes the participant identifier each secret share key package is owned by. #[zeroize(skip)] - pub(super) identifier: u16, + pub(super) identifier: Identifier, /// This participant's signing share. This is secret. pub(super) signing_share: SecretKey, /// This participant's public key. @@ -333,7 +333,7 @@ pub(super) fn encode_group_commitments( let mut transcript = Transcript::new(b"encode_group_commitments"); for (item_identifier, item) in signing_commitments { - transcript.append_message(b"identifier", &item_identifier.to_be_bytes()); + transcript.append_message(b"identifier", item_identifier.0.as_bytes()); transcript.commit_point(b"hiding", &item.hiding.0.compress()); transcript.commit_point(b"binding", &item.binding.0.compress()); } @@ -373,7 +373,7 @@ impl PublicKeyPackage { ) -> Result { let verifying_keys: BTreeMap<_, _> = identifiers .iter() - .map(|id| (*id, PublicKey::from_point(commitment.evaluate(&Scalar::from(*id))))) + .map(|id| (*id, PublicKey::from_point(commitment.evaluate(&id.0)))) .collect(); Ok(PublicKeyPackage::new( diff --git a/src/olaf/frost/mod.rs b/src/olaf/frost/mod.rs index c01c1ce..50cf51b 100644 --- a/src/olaf/frost/mod.rs +++ b/src/olaf/frost/mod.rs @@ -19,11 +19,10 @@ use self::{ }, }; -use super::GENERATOR; +use super::{Identifier, GENERATOR}; pub(super) type VerifyingShare = PublicKey; pub(super) type VerifyingKey = PublicKey; -pub(super) type Identifier = u16; impl Keypair { /// Done once by each participant, to generate _their_ nonces and commitments diff --git a/src/olaf/frost/tests.rs b/src/olaf/frost/tests.rs index 771d9eb..211b29b 100644 --- a/src/olaf/frost/tests.rs +++ b/src/olaf/frost/tests.rs @@ -16,6 +16,7 @@ mod tests { Keypair, Signature, }; use alloc::{collections::BTreeMap, vec::Vec}; + use curve25519_dalek::Scalar; use rand_core::{CryptoRng, RngCore}; /// Test FROST signing with the given shares. @@ -318,8 +319,10 @@ mod tests { mut signature_shares: BTreeMap, pubkey_package: PublicKeyPackage, ) { + let invalid_identifier = Identifier(Scalar::ZERO); // Insert a new share (copied from other existing share) with an invalid identifier - signature_shares.insert(0, signature_shares.values().next().unwrap().clone()); + signature_shares + .insert(invalid_identifier, signature_shares.values().next().unwrap().clone()); // Should error, but not panic aggregate(&signing_package, &signature_shares, &pubkey_package) .expect_err("should not work"); diff --git a/src/olaf/frost/utils.rs b/src/olaf/frost/utils.rs index f573007..12534e1 100644 --- a/src/olaf/frost/utils.rs +++ b/src/olaf/frost/utils.rs @@ -130,12 +130,12 @@ pub(super) fn compute_lagrange_coefficient( } if let Some(x) = x { - num *= Scalar::from(x) - Scalar::from(*x_j); - den *= Scalar::from(x_i) - Scalar::from(*x_j); + num *= x.0 - x_j.0; + den *= x_i.0 - x_j.0; } else { // Both signs inverted just to avoid requiring Neg (-*xj) - num *= Scalar::from(*x_j); - den *= Scalar::from(*x_j) - Scalar::from(x_i); + num *= x_j.0; + den *= x_j.0 - x_i.0; } } if !x_i_found { diff --git a/src/olaf/mod.rs b/src/olaf/mod.rs index 75fe2f1..af00cc1 100644 --- a/src/olaf/mod.rs +++ b/src/olaf/mod.rs @@ -1,8 +1,10 @@ //! Implementation of the Olaf protocol (), which is composed of the Distributed //! Key Generation (DKG) protocol SimplPedPoP and the Threshold Signing protocol FROST. +use core::cmp::Ordering; + use alloc::vec::Vec; -use curve25519_dalek::{constants::RISTRETTO_BASEPOINT_POINT, traits::Identity, RistrettoPoint}; +use curve25519_dalek::{constants::RISTRETTO_BASEPOINT_POINT, traits::Identity, RistrettoPoint, Scalar}; use crate::PublicKey; use self::{polynomials::CoefficientCommitment, simplpedpop::errors::DKGError}; @@ -19,6 +21,62 @@ pub type GroupPublicKey = PublicKey; /// The share of the group public key, which corresponds to the total secret share commitment of the secret sharing scheme. pub type GroupPublicKeyShare = CoefficientCommitment; +/// The identifier of a participant in the Olaf protocol. +#[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 = OlafError; + + fn try_from(n: u16) -> Result { + if n == 0 { + Err(OlafError::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)) + } +} + pub(super) fn sum_commitments( commitments: &[&Vec], ) -> Result, DKGError> { @@ -34,3 +92,8 @@ pub(super) fn sum_commitments( } Ok(group_commitment) } + +pub enum OlafError { + /// Identifier cannot be a zero scalar. + InvalidIdentifier, +} From 1b387d221dfaaa23b12b492d9f5ccbd902d74108 Mon Sep 17 00:00:00 2001 From: Fiono11 Date: Tue, 7 May 2024 14:17:46 +0100 Subject: [PATCH 12/60] Fix in ciphertexts loop --- src/olaf/simplpedpop.rs | 30 ++++++------ src/olaf/tests.rs | 100 ++++++++++++++++++++++++---------------- 2 files changed, 78 insertions(+), 52 deletions(-) diff --git a/src/olaf/simplpedpop.rs b/src/olaf/simplpedpop.rs index c94541c..1ade541 100644 --- a/src/olaf/simplpedpop.rs +++ b/src/olaf/simplpedpop.rs @@ -206,25 +206,29 @@ impl Keypair { let key_exchange = self.secret.key * content.ephemeral_key.into_point(); for (i, ciphertext) in ciphertexts.iter().enumerate() { + let mut secret_share_found = false; + if identifiers.len() != participants { let identifier = generate_identifier(&first_message.content.recipients_hash, i as u16); identifiers.push(identifier); } - if let Ok(secret_share) = decrypt( - encryption_transcript.clone(), - &self.public, - &key_exchange, - ciphertext, - &content.encryption_nonce, - i, - ) { - if secret_share * GENERATOR - == evaluate_polynomial_commitment(&identifiers[i], point_polynomial) - { - secret_shares.push(secret_share); - break; + if !secret_share_found { + if let Ok(secret_share) = decrypt( + encryption_transcript.clone(), + &self.public, + &key_exchange, + ciphertext, + &content.encryption_nonce, + i, + ) { + if secret_share * GENERATOR + == evaluate_polynomial_commitment(&identifiers[i], point_polynomial) + { + secret_shares.push(secret_share); + secret_share_found = true; + } } } } diff --git a/src/olaf/tests.rs b/src/olaf/tests.rs index 00cda0c..2e3c461 100644 --- a/src/olaf/tests.rs +++ b/src/olaf/tests.rs @@ -10,51 +10,73 @@ mod tests { use curve25519_dalek::ristretto::RistrettoPoint; use curve25519_dalek::traits::Identity; use merlin::Transcript; + use rand::Rng; + use crate::olaf::data_structures::Parameters; + use crate::olaf::MINIMUM_THRESHOLD; - #[test] - fn test_simplpedpop_protocol() { - let threshold = 2; - let participants = 2; - let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); - let public_keys: Vec = keypairs.iter().map(|kp| kp.public).collect(); + const MAXIMUM_PARTICIPANTS: u16 = 10; + const MINIMUM_PARTICIPANTS: u16 = 2; + const PROTOCOL_RUNS: usize = 1; - let mut all_messages = Vec::new(); - for i in 0..participants { - let message: AllMessage = - keypairs[i].simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap(); - all_messages.push(message); - } + fn generate_parameters() -> Parameters { + let mut rng = rand::thread_rng(); + let participants = rng.gen_range(MINIMUM_PARTICIPANTS..=MAXIMUM_PARTICIPANTS); + let threshold = rng.gen_range(MINIMUM_THRESHOLD..=participants); - let mut dkg_outputs = Vec::new(); + Parameters { participants, threshold } + } - for kp in keypairs.iter() { - let dkg_output = kp.simplpedpop_recipient_all(&all_messages).unwrap(); - dkg_outputs.push(dkg_output); - } + #[test] + fn test_simplpedpop_protocol() { + for _ in 0..PROTOCOL_RUNS { + let parameters = generate_parameters(); + let participants = parameters.participants as usize; + let threshold = parameters.threshold as usize; + + let keypairs: Vec = + (0..participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public).collect(); + + let mut all_messages = Vec::new(); + for i in 0..participants { + let message: AllMessage = keypairs[i] + .simplpedpop_contribute_all(threshold as u16, public_keys.clone()) + .unwrap(); + all_messages.push(message); + } - // Verify that all DKG outputs are equal for group_public_key and verifying_keys - assert!( - dkg_outputs.windows(2).all(|w| w[0].0.content.group_public_key - == w[1].0.content.group_public_key - && w[0].0.content.verifying_keys.len() == w[1].0.content.verifying_keys.len() - && w[0] - .0 - .content - .verifying_keys - .iter() - .zip(w[1].0.content.verifying_keys.iter()) - .all(|(a, b)| a == b)), - "All DKG outputs should have identical group public keys and verifying keys." - ); + let mut dkg_outputs = Vec::new(); - // Verify that all verifying_keys are valid - for i in 0..participants { - for j in 0..participants { - assert_eq!( - dkg_outputs[i].0.content.verifying_keys[j], - (dkg_outputs[j].1.to_public()), - "Verification of total secret shares failed!" - ); + for kp in keypairs.iter() { + let dkg_output = kp.simplpedpop_recipient_all(&all_messages).unwrap(); + dkg_outputs.push(dkg_output); + } + + // Verify that all DKG outputs are equal for group_public_key and verifying_keys + assert!( + dkg_outputs.windows(2).all(|w| w[0].0.content.group_public_key + == w[1].0.content.group_public_key + && w[0].0.content.verifying_keys.len() + == w[1].0.content.verifying_keys.len() + && w[0] + .0 + .content + .verifying_keys + .iter() + .zip(w[1].0.content.verifying_keys.iter()) + .all(|(a, b)| a == b)), + "All DKG outputs should have identical group public keys and verifying keys." + ); + + // Verify that all verifying_keys are valid + for i in 0..participants { + for j in 0..participants { + assert_eq!( + dkg_outputs[i].0.content.verifying_keys[j], + (dkg_outputs[j].1.to_public()), + "Verification of total secret shares failed!" + ); + } } } } From cf308ba68dc17963d7808c53cb02902d10b6404a Mon Sep 17 00:00:00 2001 From: Fiono11 Date: Tue, 7 May 2024 17:01:17 +0100 Subject: [PATCH 13/60] Add wrapper types --- benches/olaf_benchmarks.rs | 4 ++-- src/olaf/data_structures.rs | 31 +++++++++++++++---------------- src/olaf/mod.rs | 9 +++++++++ src/olaf/simplpedpop.rs | 10 ++++++---- src/olaf/tests.rs | 8 ++++---- 5 files changed, 36 insertions(+), 26 deletions(-) diff --git a/benches/olaf_benchmarks.rs b/benches/olaf_benchmarks.rs index 755a5c5..66b29bd 100644 --- a/benches/olaf_benchmarks.rs +++ b/benches/olaf_benchmarks.rs @@ -12,9 +12,9 @@ mod olaf_benches { .warm_up_time(std::time::Duration::from_secs(2)) .measurement_time(std::time::Duration::from_secs(300)); - for &n in [3, 10, 100, 1000].iter() { + for &n in [1000].iter() { let participants = n; - let threshold = (n * 2 + 2) / 3; + let threshold = 100; //(n * 2 + 2) / 3; let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); let public_keys: Vec = keypairs.iter().map(|kp| kp.public).collect(); diff --git a/src/olaf/data_structures.rs b/src/olaf/data_structures.rs index 721b52c..507536c 100644 --- a/src/olaf/data_structures.rs +++ b/src/olaf/data_structures.rs @@ -5,7 +5,7 @@ use alloc::vec::Vec; use curve25519_dalek::{ristretto::CompressedRistretto, RistrettoPoint}; use crate::{context::SigningTranscript, PublicKey, Signature, PUBLIC_KEY_LENGTH, SIGNATURE_LENGTH}; -use super::{errors::DKGError, MINIMUM_THRESHOLD}; +use super::{errors::DKGError, GroupPublicKey, VerifyingKey, MINIMUM_THRESHOLD}; pub(super) const COMPRESSED_RISTRETTO_LENGTH: usize = 32; pub(super) const U16_LENGTH: usize = 2; @@ -265,29 +265,28 @@ impl DKGOutput { } /// The content of the signed output of the SimplPedPoP protocol. -#[derive(Debug)] pub struct DKGOutputContent { - pub(super) group_public_key: PublicKey, - pub(super) verifying_keys: Vec, + pub(super) group_public_key: GroupPublicKey, + pub(super) verifying_keys: Vec, } impl DKGOutputContent { /// Creates the content of the SimplPedPoP output. - pub fn new(group_public_key: PublicKey, verifying_keys: Vec) -> Self { + pub fn new(group_public_key: GroupPublicKey, verifying_keys: Vec) -> Self { Self { group_public_key, verifying_keys } } /// Serializes the DKGOutputContent into bytes. pub fn to_bytes(&self) -> Vec { let mut bytes = Vec::new(); - let compressed_public_key = self.group_public_key.as_compressed(); // Assuming PublicKey can be compressed directly + let compressed_public_key = self.group_public_key.0.as_compressed(); // Assuming PublicKey can be compressed directly bytes.extend(compressed_public_key.to_bytes().iter()); let key_count = self.verifying_keys.len() as u16; bytes.extend(key_count.to_le_bytes()); for key in &self.verifying_keys { - bytes.extend(key.to_bytes()); + bytes.extend(key.0.to_bytes()); } bytes @@ -316,11 +315,11 @@ impl DKGOutputContent { let key_bytes = &bytes[cursor..cursor + PUBLIC_KEY_LENGTH]; cursor += PUBLIC_KEY_LENGTH; let key = PublicKey::from_bytes(key_bytes).map_err(DKGError::InvalidPublicKey)?; - verifying_keys.push(key); + verifying_keys.push(VerifyingKey(key)); } Ok(DKGOutputContent { - group_public_key: PublicKey::from_point(group_public_key), + group_public_key: GroupPublicKey(PublicKey::from_point(group_public_key)), verifying_keys, }) } @@ -406,13 +405,13 @@ mod tests { let mut rng = OsRng; let group_public_key = RistrettoPoint::random(&mut rng); let verifying_keys = vec![ - PublicKey::from_point(RistrettoPoint::random(&mut rng)), - PublicKey::from_point(RistrettoPoint::random(&mut rng)), - PublicKey::from_point(RistrettoPoint::random(&mut rng)), + VerifyingKey(PublicKey::from_point(RistrettoPoint::random(&mut rng))), + VerifyingKey(PublicKey::from_point(RistrettoPoint::random(&mut rng))), + VerifyingKey(PublicKey::from_point(RistrettoPoint::random(&mut rng))), ]; let dkg_output_content = DKGOutputContent { - group_public_key: PublicKey::from_point(group_public_key), + group_public_key: GroupPublicKey(PublicKey::from_point(group_public_key)), verifying_keys, }; @@ -431,8 +430,8 @@ mod tests { // Check if the deserialized content matches the original assert_eq!( - deserialized_dkg_output.content.group_public_key.as_compressed(), - dkg_output.content.group_public_key.as_compressed(), + deserialized_dkg_output.content.group_public_key.0.as_compressed(), + dkg_output.content.group_public_key.0.as_compressed(), "Group public keys do not match" ); @@ -448,7 +447,7 @@ mod tests { .verifying_keys .iter() .zip(dkg_output.content.verifying_keys.iter()) - .all(|(a, b)| a == b), + .all(|(a, b)| a.0 == b.0), "Verifying keys do not match" ); diff --git a/src/olaf/mod.rs b/src/olaf/mod.rs index a03e299..124f83e 100644 --- a/src/olaf/mod.rs +++ b/src/olaf/mod.rs @@ -3,6 +3,8 @@ use curve25519_dalek::{constants::RISTRETTO_BASEPOINT_POINT, RistrettoPoint}; +use crate::{PublicKey, SecretKey}; + pub mod errors; pub mod simplpedpop; mod tests; @@ -11,3 +13,10 @@ mod utils; const MINIMUM_THRESHOLD: u16 = 2; const GENERATOR: RistrettoPoint = RISTRETTO_BASEPOINT_POINT; + +/// The group public key generated by the SimplPedPoP protocol. +pub struct GroupPublicKey(PublicKey); +/// The verifying key of a participant in the SimplPedPoP protocol, used to verify its signature share. +pub struct VerifyingKey(PublicKey); +/// The signing share of a participant in the SimplPedPoP protocol, used to produce its signature share. +pub struct SigningShare(SecretKey); diff --git a/src/olaf/simplpedpop.rs b/src/olaf/simplpedpop.rs index 1ade541..1c4ffc5 100644 --- a/src/olaf/simplpedpop.rs +++ b/src/olaf/simplpedpop.rs @@ -17,7 +17,7 @@ use super::{ evaluate_polynomial_commitment, generate_coefficients, generate_identifier, sum_commitments, }, - GENERATOR, + GroupPublicKey, VerifyingKey, GENERATOR, }; impl Keypair { @@ -245,11 +245,13 @@ impl Keypair { for id in &identifiers { let evaluation = evaluate_polynomial_commitment(id, &total_polynomial_commitment); - verifying_keys.push(PublicKey::from_point(evaluation)); + verifying_keys.push(VerifyingKey(PublicKey::from_point(evaluation))); } - let dkg_output_content = - DKGOutputContent::new(PublicKey::from_point(group_point), verifying_keys); + let dkg_output_content = DKGOutputContent::new( + GroupPublicKey(PublicKey::from_point(group_point)), + verifying_keys, + ); let mut dkg_output_transcript = Transcript::new(b"dkg output"); dkg_output_transcript.append_message(b"content", &dkg_output_content.to_bytes()); diff --git a/src/olaf/tests.rs b/src/olaf/tests.rs index 2e3c461..63df45c 100644 --- a/src/olaf/tests.rs +++ b/src/olaf/tests.rs @@ -54,8 +54,8 @@ mod tests { // Verify that all DKG outputs are equal for group_public_key and verifying_keys assert!( - dkg_outputs.windows(2).all(|w| w[0].0.content.group_public_key - == w[1].0.content.group_public_key + dkg_outputs.windows(2).all(|w| w[0].0.content.group_public_key.0 + == w[1].0.content.group_public_key.0 && w[0].0.content.verifying_keys.len() == w[1].0.content.verifying_keys.len() && w[0] @@ -64,7 +64,7 @@ mod tests { .verifying_keys .iter() .zip(w[1].0.content.verifying_keys.iter()) - .all(|(a, b)| a == b)), + .all(|(a, b)| a.0 == b.0)), "All DKG outputs should have identical group public keys and verifying keys." ); @@ -72,7 +72,7 @@ mod tests { for i in 0..participants { for j in 0..participants { assert_eq!( - dkg_outputs[i].0.content.verifying_keys[j], + dkg_outputs[i].0.content.verifying_keys[j].0, (dkg_outputs[j].1.to_public()), "Verification of total secret shares failed!" ); From ff8d132ddee249207de84262fc35e8115e83bab7 Mon Sep 17 00:00:00 2001 From: Fiono11 Date: Wed, 8 May 2024 10:43:39 +0100 Subject: [PATCH 14/60] Implement SecretShare and EncryptedSecretShare types --- src/olaf/data_structures.rs | 131 +++++++++++++++++++++++++++++++----- src/olaf/simplpedpop.rs | 34 +++++----- src/olaf/tests.rs | 7 +- src/olaf/utils.rs | 98 +-------------------------- 4 files changed, 138 insertions(+), 132 deletions(-) diff --git a/src/olaf/data_structures.rs b/src/olaf/data_structures.rs index 507536c..66f2997 100644 --- a/src/olaf/data_structures.rs +++ b/src/olaf/data_structures.rs @@ -3,9 +3,15 @@ #![allow(clippy::too_many_arguments)] use alloc::vec::Vec; -use curve25519_dalek::{ristretto::CompressedRistretto, RistrettoPoint}; +use curve25519_dalek::{ristretto::CompressedRistretto, RistrettoPoint, Scalar}; +use zeroize::ZeroizeOnDrop; use crate::{context::SigningTranscript, PublicKey, Signature, PUBLIC_KEY_LENGTH, SIGNATURE_LENGTH}; -use super::{errors::DKGError, GroupPublicKey, VerifyingKey, MINIMUM_THRESHOLD}; +use super::{ + errors::{DKGError, DKGResult}, + GroupPublicKey, VerifyingKey, MINIMUM_THRESHOLD, +}; +use aead::{generic_array::GenericArray, KeyInit, KeySizeUser}; +use chacha20poly1305::{aead::Aead, ChaCha20Poly1305, Nonce}; pub(super) const COMPRESSED_RISTRETTO_LENGTH: usize = 32; pub(super) const U16_LENGTH: usize = 2; @@ -48,6 +54,78 @@ impl Parameters { } } +#[derive(ZeroizeOnDrop)] +pub(super) struct SecretShare(pub(super) Scalar); + +impl SecretShare { + pub(super) fn encrypt( + &self, + ephemeral_key: &Scalar, + mut transcript: T, + recipient: &PublicKey, + nonce: &[u8; ENCRYPTION_NONCE_LENGTH], + i: usize, + ) -> DKGResult { + transcript.commit_bytes(b"i", &i.to_le_bytes()); + transcript.commit_point(b"recipient", recipient.as_compressed()); + transcript + .commit_point(b"key exchange", &(ephemeral_key * recipient.as_point()).compress()); + + let mut key: GenericArray< + u8, + ::KeySize, + > = Default::default(); + + transcript.challenge_bytes(b"", key.as_mut_slice()); + + let cipher = ChaCha20Poly1305::new(&key); + + let nonce = Nonce::from_slice(&nonce[..]); + + let ciphertext: Vec = cipher + .encrypt(nonce, &self.0.to_bytes()[..]) + .map_err(DKGError::EncryptionError)?; + + Ok(EncryptedSecretShare(ciphertext)) + } +} + +#[derive(Clone)] +pub struct EncryptedSecretShare(pub(super) Vec); + +impl EncryptedSecretShare { + pub(super) fn decrypt( + &self, + mut transcript: T, + recipient: &PublicKey, + key_exchange: &RistrettoPoint, + nonce: &[u8; ENCRYPTION_NONCE_LENGTH], + i: usize, + ) -> DKGResult { + transcript.commit_bytes(b"i", &i.to_le_bytes()); + transcript.commit_point(b"recipient", recipient.as_compressed()); + transcript.commit_point(b"key exchange", &key_exchange.compress()); + + let mut key: GenericArray< + u8, + ::KeySize, + > = Default::default(); + + transcript.challenge_bytes(b"", key.as_mut_slice()); + + let cipher = ChaCha20Poly1305::new(&key); + + let nonce = Nonce::from_slice(&nonce[..]); + + 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))) + } +} + /// AllMessage packs together messages for all participants. /// /// We'd save bandwidth by having separate messages for each @@ -94,7 +172,7 @@ pub struct MessageContent { pub(super) parameters: Parameters, pub(super) recipients_hash: [u8; RECIPIENTS_HASH_LENGTH], pub(super) point_polynomial: Vec, - pub(super) ciphertexts: Vec>, + pub(super) encrypted_secret_shares: Vec, pub(super) ephemeral_key: PublicKey, pub(super) proof_of_possession: Signature, } @@ -107,7 +185,7 @@ impl MessageContent { parameters: Parameters, recipients_hash: [u8; RECIPIENTS_HASH_LENGTH], point_polynomial: Vec, - ciphertexts: Vec>, + ciphertexts: Vec, ephemeral_key: PublicKey, proof_of_possession: Signature, ) -> Self { @@ -117,7 +195,7 @@ impl MessageContent { parameters, recipients_hash, point_polynomial, - ciphertexts, + encrypted_secret_shares: ciphertexts, ephemeral_key, proof_of_possession, } @@ -136,8 +214,8 @@ impl MessageContent { bytes.extend(point.compress().to_bytes()); } - for ciphertext in &self.ciphertexts { - bytes.extend(ciphertext); + for ciphertext in &self.encrypted_secret_shares { + bytes.extend(ciphertext.0.clone()); } bytes.extend(&self.ephemeral_key.to_bytes()); @@ -189,10 +267,11 @@ impl MessageContent { cursor += COMPRESSED_RISTRETTO_LENGTH; } - let mut ciphertexts = Vec::new(); + let mut encrypted_secret_shares = Vec::new(); + for _ in 0..participants { let ciphertext = bytes[cursor..cursor + CHACHA20POLY1305_LENGTH].to_vec(); - ciphertexts.push(ciphertext); + encrypted_secret_shares.push(EncryptedSecretShare(ciphertext)); cursor += CHACHA20POLY1305_LENGTH; } @@ -209,7 +288,7 @@ impl MessageContent { parameters: Parameters { participants, threshold }, recipients_hash, point_polynomial, - ciphertexts, + encrypted_secret_shares, ephemeral_key, proof_of_possession, }) @@ -340,7 +419,10 @@ mod tests { let recipients_hash = [2u8; RECIPIENTS_HASH_LENGTH]; let point_polynomial = vec![RistrettoPoint::random(&mut OsRng), RistrettoPoint::random(&mut OsRng)]; - let ciphertexts = vec![vec![1; CHACHA20POLY1305_LENGTH], vec![1; CHACHA20POLY1305_LENGTH]]; + let encrypted_secret_shares = vec![ + EncryptedSecretShare(vec![1; CHACHA20POLY1305_LENGTH]), + EncryptedSecretShare(vec![1; CHACHA20POLY1305_LENGTH]), + ]; let proof_of_possession = sender.sign(Transcript::new(b"pop")); let signature = sender.sign(Transcript::new(b"sig")); let ephemeral_key = PublicKey::from_point(RistrettoPoint::random(&mut OsRng)); @@ -351,7 +433,7 @@ mod tests { parameters, recipients_hash, point_polynomial, - ciphertexts, + encrypted_secret_shares, ephemeral_key, proof_of_possession, ); @@ -387,10 +469,10 @@ mod tests { assert!(message .content - .ciphertexts + .encrypted_secret_shares .iter() - .zip(deserialized_message.content.ciphertexts.iter()) - .all(|(a, b)| a == b)); + .zip(deserialized_message.content.encrypted_secret_shares.iter()) + .all(|(a, b)| a.0 == b.0)); assert_eq!( message.content.proof_of_possession, @@ -456,4 +538,23 @@ mod tests { "Signatures do not match" ); } + + #[test] + fn test_encryption_decryption() { + let mut rng = OsRng; + let ephemeral_key = Keypair::generate(); + let recipient = Keypair::generate(); + let encryption_nonce = [1; ENCRYPTION_NONCE_LENGTH]; + let t = Transcript::new(b"label"); + let key_exchange = ephemeral_key.secret.key * recipient.public.as_point(); + let secret_share = SecretShare(Scalar::random(&mut rng)); + + let encrypted_share = secret_share + .encrypt(&ephemeral_key.secret.key, t.clone(), &recipient.public, &encryption_nonce, 0) + .unwrap(); + + encrypted_share + .decrypt(t, &recipient.public, &key_exchange, &encryption_nonce, 0) + .unwrap(); + } } diff --git a/src/olaf/simplpedpop.rs b/src/olaf/simplpedpop.rs index 1c4ffc5..b948f77 100644 --- a/src/olaf/simplpedpop.rs +++ b/src/olaf/simplpedpop.rs @@ -8,14 +8,13 @@ use rand_core::RngCore; use crate::{context::SigningTranscript, verify_batch, Keypair, PublicKey, SecretKey}; use super::{ data_structures::{ - AllMessage, DKGOutput, DKGOutputContent, MessageContent, Parameters, - ENCRYPTION_NONCE_LENGTH, RECIPIENTS_HASH_LENGTH, + AllMessage, DKGOutput, DKGOutputContent, EncryptedSecretShare, MessageContent, Parameters, + SecretShare, ENCRYPTION_NONCE_LENGTH, RECIPIENTS_HASH_LENGTH, }, errors::{DKGError, DKGResult}, utils::{ - decrypt, derive_secret_key_from_scalar, encrypt, evaluate_polynomial, - evaluate_polynomial_commitment, generate_coefficients, generate_identifier, - sum_commitments, + derive_secret_key_from_scalar, evaluate_polynomial, evaluate_polynomial_commitment, + generate_coefficients, generate_identifier, sum_commitments, }, GroupPublicKey, VerifyingKey, GENERATOR, }; @@ -49,10 +48,10 @@ impl Keypair { let coefficients = generate_coefficients(parameters.threshold as usize - 1, &mut rng); - let scalar_evaluations: Vec = (0..parameters.participants) + let secret_shares: Vec = (0..parameters.participants) .map(|i| { let identifier = generate_identifier(&recipients_hash, i); - evaluate_polynomial(&identifier, &coefficients) + SecretShare(evaluate_polynomial(&identifier, &coefficients)) }) .collect(); @@ -69,10 +68,9 @@ impl Keypair { let ephemeral_key = Keypair::generate(); - let ciphertexts: Vec> = (0..parameters.participants) + let ciphertexts: Vec = (0..parameters.participants) .map(|i| { - encrypt( - &scalar_evaluations[i as usize], + secret_shares[i as usize].encrypt( &ephemeral_key.secret.key, encryption_transcript.clone(), &recipients[i as usize], @@ -80,7 +78,7 @@ impl Keypair { i as usize, ) }) - .collect::>>>()?; + .collect::>>()?; let pk = &PublicKey::from_point( *point_polynomial @@ -160,7 +158,7 @@ impl Keypair { let content = &message.content; let point_polynomial = &content.point_polynomial; - let ciphertexts = &content.ciphertexts; + let encrypted_secret_shares = &content.encrypted_secret_shares; let public_key = PublicKey::from_point( *point_polynomial @@ -181,7 +179,7 @@ impl Keypair { if point_polynomial.len() != threshold - 1 { return Err(DKGError::IncorrectNumberOfCommitments); } - if ciphertexts.len() != participants { + if encrypted_secret_shares.len() != participants { return Err(DKGError::IncorrectNumberOfEncryptedShares); } @@ -205,7 +203,8 @@ impl Keypair { } let key_exchange = self.secret.key * content.ephemeral_key.into_point(); - for (i, ciphertext) in ciphertexts.iter().enumerate() { + + for (i, encrypted_secret_share) in encrypted_secret_shares.iter().enumerate() { let mut secret_share_found = false; if identifiers.len() != participants { @@ -215,15 +214,14 @@ impl Keypair { } if !secret_share_found { - if let Ok(secret_share) = decrypt( + if let Ok(secret_share) = encrypted_secret_share.decrypt( encryption_transcript.clone(), &self.public, &key_exchange, - ciphertext, &content.encryption_nonce, i, ) { - if secret_share * GENERATOR + if secret_share.0 * GENERATOR == evaluate_polynomial_commitment(&identifiers[i], point_polynomial) { secret_shares.push(secret_share); @@ -233,7 +231,7 @@ impl Keypair { } } - total_secret_share += secret_shares.get(j).ok_or(DKGError::InvalidSecretShare)?; + total_secret_share += secret_shares.get(j).ok_or(DKGError::InvalidSecretShare)?.0; group_point += secret_commitment; } diff --git a/src/olaf/tests.rs b/src/olaf/tests.rs index 63df45c..c9044d7 100644 --- a/src/olaf/tests.rs +++ b/src/olaf/tests.rs @@ -2,7 +2,7 @@ mod tests { mod simplpedpop { use crate::olaf::data_structures::{ - AllMessage, CHACHA20POLY1305_LENGTH, RECIPIENTS_HASH_LENGTH, + AllMessage, EncryptedSecretShare, CHACHA20POLY1305_LENGTH, RECIPIENTS_HASH_LENGTH, }; use crate::olaf::errors::DKGError; use crate::{Keypair, PublicKey}; @@ -205,7 +205,7 @@ mod tests { .map(|kp| kp.simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap()) .collect(); - messages[1].content.ciphertexts.pop(); + messages[1].content.encrypted_secret_shares.pop(); let result = keypairs[0].simplpedpop_recipient_all(&messages); @@ -234,7 +234,8 @@ mod tests { .map(|kp| kp.simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap()) .collect(); - messages[1].content.ciphertexts[0] = vec![1; CHACHA20POLY1305_LENGTH]; + messages[1].content.encrypted_secret_shares[0] = + EncryptedSecretShare(vec![1; CHACHA20POLY1305_LENGTH]); let result = keypairs[0].simplpedpop_recipient_all(&messages); diff --git a/src/olaf/utils.rs b/src/olaf/utils.rs index 8b21dd8..49036c1 100644 --- a/src/olaf/utils.rs +++ b/src/olaf/utils.rs @@ -1,14 +1,9 @@ use core::iter; use alloc::vec::Vec; -use aead::{generic_array::GenericArray, KeyInit, KeySizeUser}; -use chacha20poly1305::{aead::Aead, ChaCha20Poly1305, Nonce}; use curve25519_dalek::{traits::Identity, RistrettoPoint, Scalar}; use rand_core::{CryptoRng, RngCore}; -use crate::{context::SigningTranscript, PublicKey, SecretKey}; -use super::{ - data_structures::ENCRYPTION_NONCE_LENGTH, - errors::{DKGError, DKGResult}, -}; +use crate::{context::SigningTranscript, SecretKey}; +use super::errors::DKGError; pub(super) fn generate_identifier(recipients_hash: &[u8; 16], index: u16) -> Scalar { let mut pos = merlin::Transcript::new(b"Identifier"); @@ -95,92 +90,3 @@ pub(super) fn sum_commitments( } Ok(group_commitment) } - -pub(super) fn encrypt( - scalar_evaluation: &Scalar, - ephemeral_key: &Scalar, - mut transcript: T, - recipient: &PublicKey, - nonce: &[u8; ENCRYPTION_NONCE_LENGTH], - i: usize, -) -> DKGResult> { - transcript.commit_bytes(b"i", &i.to_le_bytes()); - transcript.commit_point(b"recipient", recipient.as_compressed()); - transcript.commit_point(b"key exchange", &(ephemeral_key * recipient.as_point()).compress()); - - let mut key: GenericArray::KeySize> = - Default::default(); - - transcript.challenge_bytes(b"", key.as_mut_slice()); - - let cipher = ChaCha20Poly1305::new(&key); - - let nonce = Nonce::from_slice(&nonce[..]); - - let ciphertext: Vec = cipher - .encrypt(nonce, &scalar_evaluation.to_bytes()[..]) - .map_err(DKGError::EncryptionError)?; - - Ok(ciphertext) -} - -pub(super) fn decrypt( - mut transcript: T, - recipient: &PublicKey, - key_exchange: &RistrettoPoint, - encrypted_scalar: &[u8], - nonce: &[u8; ENCRYPTION_NONCE_LENGTH], - i: usize, -) -> DKGResult { - transcript.commit_bytes(b"i", &i.to_le_bytes()); - transcript.commit_point(b"recipient", recipient.as_compressed()); - transcript.commit_point(b"key exchange", &key_exchange.compress()); - - let mut key: GenericArray::KeySize> = - Default::default(); - - transcript.challenge_bytes(b"", key.as_mut_slice()); - - let cipher = ChaCha20Poly1305::new(&key); - - let nonce = Nonce::from_slice(&nonce[..]); - - let plaintext = cipher.decrypt(nonce, encrypted_scalar).map_err(DKGError::DecryptionError)?; - - let mut bytes = [0; 32]; - bytes.copy_from_slice(&plaintext); - - Ok(Scalar::from_bytes_mod_order(bytes)) -} - -#[cfg(test)] -mod tests { - use merlin::Transcript; - use crate::Keypair; - use rand_core::OsRng; - use super::*; - - #[test] - fn test_encryption_decryption() { - let mut rng = OsRng; - let ephemeral_key = Keypair::generate(); - let recipient = Keypair::generate(); - let encryption_nonce = [1; ENCRYPTION_NONCE_LENGTH]; - let t = Transcript::new(b"label"); - let key_exchange = ephemeral_key.secret.key * recipient.public.as_point(); - let plaintext = Scalar::random(&mut rng); - - let encrypted_share = encrypt( - &plaintext, - &ephemeral_key.secret.key, - t.clone(), - &recipient.public, - &encryption_nonce, - 0, - ) - .unwrap(); - - decrypt(t, &recipient.public, &key_exchange, &encrypted_share, &encryption_nonce, 0) - .unwrap(); - } -} From 2e9b1534c25131d605b46f93c89b6c86731ebbdf Mon Sep 17 00:00:00 2001 From: Fiono11 Date: Wed, 8 May 2024 11:24:33 +0100 Subject: [PATCH 15/60] Implement SecretPolynomial and PolynomialCommitment types --- src/olaf/data_structures.rs | 145 ++++++++++++++++++++++++++++++------ src/olaf/errors.rs | 4 +- src/olaf/mod.rs | 4 +- src/olaf/simplpedpop.rs | 65 ++++++++-------- src/olaf/tests.rs | 6 +- src/olaf/utils.rs | 67 +---------------- 6 files changed, 168 insertions(+), 123 deletions(-) diff --git a/src/olaf/data_structures.rs b/src/olaf/data_structures.rs index 66f2997..198cbb4 100644 --- a/src/olaf/data_structures.rs +++ b/src/olaf/data_structures.rs @@ -2,13 +2,16 @@ #![allow(clippy::too_many_arguments)] +use core::iter; + use alloc::vec::Vec; -use curve25519_dalek::{ristretto::CompressedRistretto, RistrettoPoint, Scalar}; +use curve25519_dalek::{ristretto::CompressedRistretto, traits::Identity, RistrettoPoint, Scalar}; +use rand_core::{CryptoRng, RngCore}; use zeroize::ZeroizeOnDrop; use crate::{context::SigningTranscript, PublicKey, Signature, PUBLIC_KEY_LENGTH, SIGNATURE_LENGTH}; use super::{ errors::{DKGError, DKGResult}, - GroupPublicKey, VerifyingKey, MINIMUM_THRESHOLD, + GroupPublicKey, VerifyingShare, GENERATOR, MINIMUM_THRESHOLD, }; use aead::{generic_array::GenericArray, KeyInit, KeySizeUser}; use chacha20poly1305::{aead::Aead, ChaCha20Poly1305, Nonce}; @@ -126,6 +129,92 @@ impl EncryptedSecretShare { } } +/// The secret polynomial of a participant chosen at randoma nd used to generate the secret shares of all the participants (including itself). +#[derive(ZeroizeOnDrop)] +pub struct SecretPolynomial { + pub(super) coefficients: Vec, +} + +impl SecretPolynomial { + pub(super) fn generate(degree: usize, rng: &mut R) -> Self { + let mut coefficients = Vec::with_capacity(degree); + + let mut first = Scalar::random(rng); + while first == Scalar::ZERO { + first = Scalar::random(rng); + } + + coefficients.push(first); + coefficients.extend(iter::repeat_with(|| Scalar::random(rng)).take(degree - 1)); + + SecretPolynomial { coefficients } + } + + pub(super) fn evaluate(&self, x: &Scalar) -> Scalar { + 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 + } +} + +/// The polynomial commitment of a participant, used to verify the secret shares without revealing the polynomial. +pub struct PolynomialCommitment { + pub(super) coefficients_commitments: Vec, +} + +impl PolynomialCommitment { + pub(super) fn commit(secret_polynomial: &SecretPolynomial) -> Self { + let coefficients_commitments = secret_polynomial + .coefficients + .iter() + .map(|coefficient| GENERATOR * coefficient) + .collect(); + + Self { coefficients_commitments } + } + + pub(super) fn evaluate(&self, identifier: &Scalar) -> RistrettoPoint { + 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(super) 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 } + } +} + /// AllMessage packs together messages for all participants. /// /// We'd save bandwidth by having separate messages for each @@ -171,7 +260,7 @@ pub struct MessageContent { pub(super) encryption_nonce: [u8; ENCRYPTION_NONCE_LENGTH], pub(super) parameters: Parameters, pub(super) recipients_hash: [u8; RECIPIENTS_HASH_LENGTH], - pub(super) point_polynomial: Vec, + pub(super) polynomial_commitment: PolynomialCommitment, pub(super) encrypted_secret_shares: Vec, pub(super) ephemeral_key: PublicKey, pub(super) proof_of_possession: Signature, @@ -184,8 +273,8 @@ impl MessageContent { encryption_nonce: [u8; ENCRYPTION_NONCE_LENGTH], parameters: Parameters, recipients_hash: [u8; RECIPIENTS_HASH_LENGTH], - point_polynomial: Vec, - ciphertexts: Vec, + polynomial_commitment: PolynomialCommitment, + encrypted_secret_shares: Vec, ephemeral_key: PublicKey, proof_of_possession: Signature, ) -> Self { @@ -194,8 +283,8 @@ impl MessageContent { encryption_nonce, parameters, recipients_hash, - point_polynomial, - encrypted_secret_shares: ciphertexts, + polynomial_commitment, + encrypted_secret_shares, ephemeral_key, proof_of_possession, } @@ -210,7 +299,7 @@ impl MessageContent { bytes.extend(self.parameters.threshold.to_le_bytes()); bytes.extend(&self.recipients_hash); - for point in &self.point_polynomial { + for point in &self.polynomial_commitment.coefficients_commitments { bytes.extend(point.compress().to_bytes()); } @@ -257,16 +346,22 @@ impl MessageContent { .map_err(DKGError::DeserializationError)?; cursor += RECIPIENTS_HASH_LENGTH; - let mut point_polynomial = Vec::with_capacity(participants as usize); + let mut coefficients_commitments = Vec::with_capacity(participants as usize); + for _ in 0..participants { let point = CompressedRistretto::from_slice( &bytes[cursor..cursor + COMPRESSED_RISTRETTO_LENGTH], ) .map_err(DKGError::DeserializationError)?; - point_polynomial.push(point.decompress().ok_or(DKGError::InvalidRistrettoPoint)?); + + coefficients_commitments + .push(point.decompress().ok_or(DKGError::InvalidRistrettoPoint)?); + cursor += COMPRESSED_RISTRETTO_LENGTH; } + let polynomial_commitment = PolynomialCommitment { coefficients_commitments }; + let mut encrypted_secret_shares = Vec::new(); for _ in 0..participants { @@ -287,7 +382,7 @@ impl MessageContent { encryption_nonce, parameters: Parameters { participants, threshold }, recipients_hash, - point_polynomial, + polynomial_commitment, encrypted_secret_shares, ephemeral_key, proof_of_possession, @@ -346,12 +441,12 @@ impl DKGOutput { /// The content of the signed output of the SimplPedPoP protocol. pub struct DKGOutputContent { pub(super) group_public_key: GroupPublicKey, - pub(super) verifying_keys: Vec, + pub(super) verifying_keys: Vec, } impl DKGOutputContent { /// Creates the content of the SimplPedPoP output. - pub fn new(group_public_key: GroupPublicKey, verifying_keys: Vec) -> Self { + pub fn new(group_public_key: GroupPublicKey, verifying_keys: Vec) -> Self { Self { group_public_key, verifying_keys } } /// Serializes the DKGOutputContent into bytes. @@ -394,7 +489,7 @@ impl DKGOutputContent { let key_bytes = &bytes[cursor..cursor + PUBLIC_KEY_LENGTH]; cursor += PUBLIC_KEY_LENGTH; let key = PublicKey::from_bytes(key_bytes).map_err(DKGError::InvalidPublicKey)?; - verifying_keys.push(VerifyingKey(key)); + verifying_keys.push(VerifyingShare(key)); } Ok(DKGOutputContent { @@ -417,8 +512,9 @@ mod tests { let encryption_nonce = [1u8; ENCRYPTION_NONCE_LENGTH]; let parameters = Parameters { participants: 2, threshold: 1 }; let recipients_hash = [2u8; RECIPIENTS_HASH_LENGTH]; - let point_polynomial = + let coefficients_commitments = vec![RistrettoPoint::random(&mut OsRng), RistrettoPoint::random(&mut OsRng)]; + let polynomial_commitment = PolynomialCommitment { coefficients_commitments }; let encrypted_secret_shares = vec![ EncryptedSecretShare(vec![1; CHACHA20POLY1305_LENGTH]), EncryptedSecretShare(vec![1; CHACHA20POLY1305_LENGTH]), @@ -432,7 +528,7 @@ mod tests { encryption_nonce, parameters, recipients_hash, - point_polynomial, + polynomial_commitment, encrypted_secret_shares, ephemeral_key, proof_of_possession, @@ -462,9 +558,16 @@ mod tests { assert!(message .content - .point_polynomial + .polynomial_commitment + .coefficients_commitments .iter() - .zip(deserialized_message.content.point_polynomial.iter()) + .zip( + deserialized_message + .content + .polynomial_commitment + .coefficients_commitments + .iter() + ) .all(|(a, b)| a.compress() == b.compress())); assert!(message @@ -487,9 +590,9 @@ mod tests { let mut rng = OsRng; let group_public_key = RistrettoPoint::random(&mut rng); let verifying_keys = vec![ - VerifyingKey(PublicKey::from_point(RistrettoPoint::random(&mut rng))), - VerifyingKey(PublicKey::from_point(RistrettoPoint::random(&mut rng))), - VerifyingKey(PublicKey::from_point(RistrettoPoint::random(&mut rng))), + VerifyingShare(PublicKey::from_point(RistrettoPoint::random(&mut rng))), + VerifyingShare(PublicKey::from_point(RistrettoPoint::random(&mut rng))), + VerifyingShare(PublicKey::from_point(RistrettoPoint::random(&mut rng))), ]; let dkg_output_content = DKGOutputContent { diff --git a/src/olaf/errors.rs b/src/olaf/errors.rs index 9b8f17e..1b95d6b 100644 --- a/src/olaf/errors.rs +++ b/src/olaf/errors.rs @@ -33,8 +33,8 @@ pub enum DKGError { DifferentRecipientsHash, /// The number of messages should be 2 at least, which the minimum number of participants. InvalidNumberOfMessages, - /// The number of commitments per message should be equal to the number of participants - 1. - IncorrectNumberOfCommitments, + /// The degree of the polynomial commitment be equal to the number of participants - 1. + IncorrectPolynomialCommitmentDegree, /// The number of encrypted shares per message should be equal to the number of participants. IncorrectNumberOfEncryptedShares, /// Decryption error when decrypting an encrypted secret share. diff --git a/src/olaf/mod.rs b/src/olaf/mod.rs index 124f83e..682b746 100644 --- a/src/olaf/mod.rs +++ b/src/olaf/mod.rs @@ -16,7 +16,7 @@ const GENERATOR: RistrettoPoint = RISTRETTO_BASEPOINT_POINT; /// The group public key generated by the SimplPedPoP protocol. pub struct GroupPublicKey(PublicKey); -/// The verifying key of a participant in the SimplPedPoP protocol, used to verify its signature share. -pub struct VerifyingKey(PublicKey); +/// The verifying share of a participant in the SimplPedPoP protocol, used to verify its signature share. +pub struct VerifyingShare(PublicKey); /// The signing share of a participant in the SimplPedPoP protocol, used to produce its signature share. pub struct SigningShare(SecretKey); diff --git a/src/olaf/simplpedpop.rs b/src/olaf/simplpedpop.rs index b948f77..acc9b0a 100644 --- a/src/olaf/simplpedpop.rs +++ b/src/olaf/simplpedpop.rs @@ -9,14 +9,12 @@ use crate::{context::SigningTranscript, verify_batch, Keypair, PublicKey, Secret use super::{ data_structures::{ AllMessage, DKGOutput, DKGOutputContent, EncryptedSecretShare, MessageContent, Parameters, - SecretShare, ENCRYPTION_NONCE_LENGTH, RECIPIENTS_HASH_LENGTH, + PolynomialCommitment, SecretPolynomial, SecretShare, ENCRYPTION_NONCE_LENGTH, + RECIPIENTS_HASH_LENGTH, }, errors::{DKGError, DKGResult}, - utils::{ - derive_secret_key_from_scalar, evaluate_polynomial, evaluate_polynomial_commitment, - generate_coefficients, generate_identifier, sum_commitments, - }, - GroupPublicKey, VerifyingKey, GENERATOR, + utils::{derive_secret_key_from_scalar, generate_identifier}, + GroupPublicKey, VerifyingShare, GENERATOR, }; impl Keypair { @@ -46,17 +44,18 @@ impl Keypair { let mut recipients_hash = [0u8; RECIPIENTS_HASH_LENGTH]; recipients_transcript.challenge_bytes(b"finalize", &mut recipients_hash); - let coefficients = generate_coefficients(parameters.threshold as usize - 1, &mut rng); + let secret_polynomial = + SecretPolynomial::generate(parameters.threshold as usize - 1, &mut rng); let secret_shares: Vec = (0..parameters.participants) .map(|i| { let identifier = generate_identifier(&recipients_hash, i); - SecretShare(evaluate_polynomial(&identifier, &coefficients)) + let polynomial_evaluation = secret_polynomial.evaluate(&identifier); + SecretShare(polynomial_evaluation) }) .collect(); - let point_polynomial: Vec = - coefficients.iter().map(|c| GENERATOR * *c).collect(); + let polynomial_commitment = PolynomialCommitment::commit(&secret_polynomial); let mut encryption_transcript = merlin::Transcript::new(b"Encryption"); parameters.commit(&mut encryption_transcript); @@ -81,18 +80,21 @@ impl Keypair { .collect::>>()?; let pk = &PublicKey::from_point( - *point_polynomial + *polynomial_commitment + .coefficients_commitments .first() .expect("This never fails because the minimum threshold is 2"), ); - let secret = coefficients + let secret = secret_polynomial + .coefficients .first() .expect("This never fails because the minimum threshold is 2"); let secret_key = derive_secret_key_from_scalar(secret, &mut rng); - let secret_commitment = point_polynomial + let secret_commitment = polynomial_commitment + .coefficients_commitments .first() .expect("This never fails because the minimum threshold is 2"); @@ -106,7 +108,7 @@ impl Keypair { encryption_nonce, parameters, recipients_hash, - point_polynomial, + polynomial_commitment, ciphertexts, ephemeral_key.public, proof_of_possession, @@ -145,7 +147,8 @@ impl Keypair { let mut pops_transcripts = Vec::with_capacity(participants); let mut group_point = RistrettoPoint::identity(); let mut total_secret_share = Scalar::ZERO; - let mut total_polynomial_commitment = Vec::new(); + let mut total_polynomial_commitment = + PolynomialCommitment { coefficients_commitments: vec![] }; let mut identifiers = Vec::new(); for (j, message) in messages.iter().enumerate() { @@ -157,11 +160,12 @@ impl Keypair { } let content = &message.content; - let point_polynomial = &content.point_polynomial; + let polynomial_commitment = &content.polynomial_commitment; let encrypted_secret_shares = &content.encrypted_secret_shares; let public_key = PublicKey::from_point( - *point_polynomial + *polynomial_commitment + .coefficients_commitments .first() .expect("This never fails because the minimum threshold is 2"), ); @@ -176,9 +180,10 @@ impl Keypair { encryption_transcript.commit_point(b"contributor", content.sender.as_compressed()); encryption_transcript.append_message(b"nonce", &content.encryption_nonce); - if point_polynomial.len() != threshold - 1 { - return Err(DKGError::IncorrectNumberOfCommitments); + if polynomial_commitment.coefficients_commitments.len() != threshold - 1 { + return Err(DKGError::IncorrectPolynomialCommitmentDegree); } + if encrypted_secret_shares.len() != participants { return Err(DKGError::IncorrectNumberOfEncryptedShares); } @@ -188,19 +193,21 @@ impl Keypair { signatures_transcripts.push(signature_transcript); let mut pop_transcript = Transcript::new(b"pop"); - let secret_commitment = point_polynomial + + let secret_commitment = polynomial_commitment + .coefficients_commitments .first() .expect("This never fails because the minimum threshold is 2"); + pop_transcript .append_message(b"secret commitment", secret_commitment.compress().as_bytes()); + pops_transcripts.push(pop_transcript); - if total_polynomial_commitment.is_empty() { - total_polynomial_commitment = point_polynomial.clone(); - } else { - total_polynomial_commitment = - sum_commitments(&[&total_polynomial_commitment, point_polynomial])?; - } + total_polynomial_commitment = PolynomialCommitment::sum_polynomial_commitments(&[ + &total_polynomial_commitment, + &polynomial_commitment, + ]); let key_exchange = self.secret.key * content.ephemeral_key.into_point(); @@ -222,7 +229,7 @@ impl Keypair { i, ) { if secret_share.0 * GENERATOR - == evaluate_polynomial_commitment(&identifiers[i], point_polynomial) + == polynomial_commitment.evaluate(&identifiers[i]) { secret_shares.push(secret_share); secret_share_found = true; @@ -242,8 +249,8 @@ impl Keypair { .map_err(DKGError::InvalidSignature)?; for id in &identifiers { - let evaluation = evaluate_polynomial_commitment(id, &total_polynomial_commitment); - verifying_keys.push(VerifyingKey(PublicKey::from_point(evaluation))); + let evaluation = total_polynomial_commitment.evaluate(id); + verifying_keys.push(VerifyingShare(PublicKey::from_point(evaluation))); } let dkg_output_content = DKGOutputContent::new( diff --git a/src/olaf/tests.rs b/src/olaf/tests.rs index c9044d7..44107ed 100644 --- a/src/olaf/tests.rs +++ b/src/olaf/tests.rs @@ -68,7 +68,7 @@ mod tests { "All DKG outputs should have identical group public keys and verifying keys." ); - // Verify that all verifying_keys are valid + // Verify that all verifying_shares are valid for i in 0..participants { for j in 0..participants { assert_eq!( @@ -179,14 +179,14 @@ mod tests { .map(|kp| kp.simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap()) .collect(); - messages[1].content.point_polynomial.pop(); + messages[1].content.polynomial_commitment.coefficients_commitments.pop(); let result = keypairs[0].simplpedpop_recipient_all(&messages); match result { Ok(_) => panic!("Expected an error, but got Ok."), Err(e) => match e { - DKGError::IncorrectNumberOfCommitments => assert!(true), + DKGError::IncorrectPolynomialCommitmentDegree => assert!(true), _ => panic!("Expected DKGError::IncorrectNumberOfCommitments, but got {:?}", e), }, } diff --git a/src/olaf/utils.rs b/src/olaf/utils.rs index 49036c1..a8db1f1 100644 --- a/src/olaf/utils.rs +++ b/src/olaf/utils.rs @@ -1,9 +1,6 @@ -use core::iter; -use alloc::vec::Vec; -use curve25519_dalek::{traits::Identity, RistrettoPoint, Scalar}; +use curve25519_dalek::Scalar; use rand_core::{CryptoRng, RngCore}; use crate::{context::SigningTranscript, SecretKey}; -use super::errors::DKGError; pub(super) fn generate_identifier(recipients_hash: &[u8; 16], index: u16) -> Scalar { let mut pos = merlin::Transcript::new(b"Identifier"); @@ -12,38 +9,6 @@ pub(super) fn generate_identifier(recipients_hash: &[u8; 16], index: u16) -> Sca pos.challenge_scalar(b"evaluation position") } -/// Evaluate the polynomial with the given coefficients (constant term first) -/// at the point x=identifier using Horner's method. -pub(super) fn evaluate_polynomial(identifier: &Scalar, coefficients: &[Scalar]) -> Scalar { - let mut value = Scalar::ZERO; - - let ell_scalar = identifier; - for coeff in coefficients.iter().skip(1).rev() { - value += *coeff; - value *= ell_scalar; - } - value += *coefficients.first().expect("coefficients must have at least one element"); - value -} - -/// Return a vector of randomly generated polynomial coefficients ([`Scalar`]s). -pub(super) fn generate_coefficients( - size: usize, - rng: &mut R, -) -> Vec { - let mut coefficients = Vec::with_capacity(size); - - let mut first = Scalar::random(rng); - while first == Scalar::ZERO { - first = Scalar::random(rng); - } - - coefficients.push(first); - coefficients.extend(iter::repeat_with(|| Scalar::random(rng)).take(size - 1)); - - coefficients -} - pub(super) fn derive_secret_key_from_scalar( scalar: &Scalar, mut rng: R, @@ -60,33 +25,3 @@ pub(super) fn derive_secret_key_from_scalar( SecretKey::from_bytes(&bytes[..]) .expect("This never fails because bytes has length 64 and the key is a scalar") } - -pub(super) fn evaluate_polynomial_commitment( - identifier: &Scalar, - commitment: &[RistrettoPoint], -) -> RistrettoPoint { - let i = identifier; - - let (_, result) = commitment - .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(super) fn sum_commitments( - commitments: &[&Vec], -) -> Result, DKGError> { - let mut group_commitment = - vec![ - RistrettoPoint::identity(); - commitments.first().ok_or(DKGError::IncorrectNumberOfCommitments)?.len() - ]; - for commitment in commitments { - for (i, c) in group_commitment.iter_mut().enumerate() { - *c += commitment.get(i).ok_or(DKGError::IncorrectNumberOfCommitments)?; - } - } - Ok(group_commitment) -} From 9420ff43e237f022947940e0019855fb641c2186 Mon Sep 17 00:00:00 2001 From: Fiono11 Date: Wed, 8 May 2024 11:30:33 +0100 Subject: [PATCH 16/60] Restructuring of files --- src/olaf/mod.rs | 32 ++++++++++++++++++++--- src/olaf/simplpedpop.rs | 5 ++-- src/olaf/tests.rs | 4 +-- src/olaf/{data_structures.rs => types.rs} | 2 +- src/olaf/utils.rs | 27 ------------------- 5 files changed, 33 insertions(+), 37 deletions(-) rename src/olaf/{data_structures.rs => types.rs} (99%) delete mode 100644 src/olaf/utils.rs diff --git a/src/olaf/mod.rs b/src/olaf/mod.rs index 682b746..3df764c 100644 --- a/src/olaf/mod.rs +++ b/src/olaf/mod.rs @@ -1,15 +1,15 @@ //! Implementation of the Olaf protocol (), which is composed of the Distributed //! Key Generation (DKG) protocol SimplPedPoP and the Threshold Signing protocol FROST. -use curve25519_dalek::{constants::RISTRETTO_BASEPOINT_POINT, RistrettoPoint}; - +use curve25519_dalek::{constants::RISTRETTO_BASEPOINT_POINT, RistrettoPoint, Scalar}; use crate::{PublicKey, SecretKey}; +use rand_core::{RngCore, CryptoRng}; +use crate::context::SigningTranscript; pub mod errors; pub mod simplpedpop; mod tests; -pub mod data_structures; -mod utils; +mod types; const MINIMUM_THRESHOLD: u16 = 2; const GENERATOR: RistrettoPoint = RISTRETTO_BASEPOINT_POINT; @@ -20,3 +20,27 @@ pub struct GroupPublicKey(PublicKey); pub struct VerifyingShare(PublicKey); /// The signing share of a participant in the SimplPedPoP protocol, used to produce its signature share. pub struct SigningShare(SecretKey); + +pub(super) fn generate_identifier(recipients_hash: &[u8; 16], index: u16) -> Scalar { + let mut pos = merlin::Transcript::new(b"Identifier"); + pos.append_message(b"RecipientsHash", recipients_hash); + pos.append_message(b"i", &index.to_le_bytes()[..]); + pos.challenge_scalar(b"evaluation position") +} + +pub(super) fn derive_secret_key_from_scalar( + scalar: &Scalar, + mut rng: R, +) -> SecretKey { + let mut bytes = [0u8; 64]; + let mut nonce: [u8; 32] = [0u8; 32]; + + rng.fill_bytes(&mut nonce); + let secret_bytes = scalar.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") +} diff --git a/src/olaf/simplpedpop.rs b/src/olaf/simplpedpop.rs index acc9b0a..e7d14ec 100644 --- a/src/olaf/simplpedpop.rs +++ b/src/olaf/simplpedpop.rs @@ -7,14 +7,13 @@ use merlin::Transcript; use rand_core::RngCore; use crate::{context::SigningTranscript, verify_batch, Keypair, PublicKey, SecretKey}; use super::{ - data_structures::{ + types::{ AllMessage, DKGOutput, DKGOutputContent, EncryptedSecretShare, MessageContent, Parameters, PolynomialCommitment, SecretPolynomial, SecretShare, ENCRYPTION_NONCE_LENGTH, RECIPIENTS_HASH_LENGTH, }, errors::{DKGError, DKGResult}, - utils::{derive_secret_key_from_scalar, generate_identifier}, - GroupPublicKey, VerifyingShare, GENERATOR, + derive_secret_key_from_scalar, generate_identifier, GroupPublicKey, VerifyingShare, GENERATOR, }; impl Keypair { diff --git a/src/olaf/tests.rs b/src/olaf/tests.rs index 44107ed..cc5bdc4 100644 --- a/src/olaf/tests.rs +++ b/src/olaf/tests.rs @@ -1,8 +1,9 @@ #[cfg(test)] mod tests { mod simplpedpop { - use crate::olaf::data_structures::{ + use crate::olaf::types::{ AllMessage, EncryptedSecretShare, CHACHA20POLY1305_LENGTH, RECIPIENTS_HASH_LENGTH, + Parameters, }; use crate::olaf::errors::DKGError; use crate::{Keypair, PublicKey}; @@ -11,7 +12,6 @@ mod tests { use curve25519_dalek::traits::Identity; use merlin::Transcript; use rand::Rng; - use crate::olaf::data_structures::Parameters; use crate::olaf::MINIMUM_THRESHOLD; const MAXIMUM_PARTICIPANTS: u16 = 10; diff --git a/src/olaf/data_structures.rs b/src/olaf/types.rs similarity index 99% rename from src/olaf/data_structures.rs rename to src/olaf/types.rs index 198cbb4..fb50f1f 100644 --- a/src/olaf/data_structures.rs +++ b/src/olaf/types.rs @@ -131,7 +131,7 @@ impl EncryptedSecretShare { /// The secret polynomial of a participant chosen at randoma nd used to generate the secret shares of all the participants (including itself). #[derive(ZeroizeOnDrop)] -pub struct SecretPolynomial { +pub(super) struct SecretPolynomial { pub(super) coefficients: Vec, } diff --git a/src/olaf/utils.rs b/src/olaf/utils.rs deleted file mode 100644 index a8db1f1..0000000 --- a/src/olaf/utils.rs +++ /dev/null @@ -1,27 +0,0 @@ -use curve25519_dalek::Scalar; -use rand_core::{CryptoRng, RngCore}; -use crate::{context::SigningTranscript, SecretKey}; - -pub(super) fn generate_identifier(recipients_hash: &[u8; 16], index: u16) -> Scalar { - let mut pos = merlin::Transcript::new(b"Identifier"); - pos.append_message(b"RecipientsHash", recipients_hash); - pos.append_message(b"i", &index.to_le_bytes()[..]); - pos.challenge_scalar(b"evaluation position") -} - -pub(super) fn derive_secret_key_from_scalar( - scalar: &Scalar, - mut rng: R, -) -> SecretKey { - let mut bytes = [0u8; 64]; - let mut nonce: [u8; 32] = [0u8; 32]; - - rng.fill_bytes(&mut nonce); - let secret_bytes = scalar.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") -} From 69473c41e3ec149ef7fba8c3f31f78a82b638158 Mon Sep 17 00:00:00 2001 From: Fiono11 Date: Wed, 8 May 2024 11:56:11 +0100 Subject: [PATCH 17/60] Remove derive_key_from_scalar --- src/olaf/mod.rs | 18 ------------------ src/olaf/simplpedpop.rs | 22 ++++++++++++++-------- src/olaf/tests.rs | 2 +- 3 files changed, 15 insertions(+), 27 deletions(-) diff --git a/src/olaf/mod.rs b/src/olaf/mod.rs index 3df764c..d9ad16f 100644 --- a/src/olaf/mod.rs +++ b/src/olaf/mod.rs @@ -3,7 +3,6 @@ use curve25519_dalek::{constants::RISTRETTO_BASEPOINT_POINT, RistrettoPoint, Scalar}; use crate::{PublicKey, SecretKey}; -use rand_core::{RngCore, CryptoRng}; use crate::context::SigningTranscript; pub mod errors; @@ -27,20 +26,3 @@ pub(super) fn generate_identifier(recipients_hash: &[u8; 16], index: u16) -> Sca pos.append_message(b"i", &index.to_le_bytes()[..]); pos.challenge_scalar(b"evaluation position") } - -pub(super) fn derive_secret_key_from_scalar( - scalar: &Scalar, - mut rng: R, -) -> SecretKey { - let mut bytes = [0u8; 64]; - let mut nonce: [u8; 32] = [0u8; 32]; - - rng.fill_bytes(&mut nonce); - let secret_bytes = scalar.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") -} diff --git a/src/olaf/simplpedpop.rs b/src/olaf/simplpedpop.rs index e7d14ec..481a168 100644 --- a/src/olaf/simplpedpop.rs +++ b/src/olaf/simplpedpop.rs @@ -7,13 +7,14 @@ use merlin::Transcript; use rand_core::RngCore; use crate::{context::SigningTranscript, verify_batch, Keypair, PublicKey, SecretKey}; use super::{ + errors::{DKGError, DKGResult}, + generate_identifier, types::{ AllMessage, DKGOutput, DKGOutputContent, EncryptedSecretShare, MessageContent, Parameters, PolynomialCommitment, SecretPolynomial, SecretShare, ENCRYPTION_NONCE_LENGTH, RECIPIENTS_HASH_LENGTH, }, - errors::{DKGError, DKGResult}, - derive_secret_key_from_scalar, generate_identifier, GroupPublicKey, VerifyingShare, GENERATOR, + GroupPublicKey, SigningShare, VerifyingShare, GENERATOR, }; impl Keypair { @@ -85,12 +86,15 @@ impl Keypair { .expect("This never fails because the minimum threshold is 2"), ); - let secret = secret_polynomial + let secret = *secret_polynomial .coefficients .first() .expect("This never fails because the minimum threshold is 2"); - let secret_key = derive_secret_key_from_scalar(secret, &mut rng); + let mut nonce: [u8; 32] = [0u8; 32]; + crate::getrandom_or_panic().fill_bytes(&mut nonce); + + let secret_key = SecretKey { key: secret, nonce }; let secret_commitment = polynomial_commitment .coefficients_commitments @@ -124,7 +128,7 @@ impl Keypair { pub fn simplpedpop_recipient_all( &self, messages: &[AllMessage], - ) -> DKGResult<(DKGOutput, SecretKey)> { + ) -> DKGResult<(DKGOutput, SigningShare)> { let first_message = &messages[0]; let parameters = &first_message.content.parameters; let threshold = parameters.threshold as usize; @@ -262,9 +266,11 @@ impl Keypair { let signature = self.sign(dkg_output_transcript); let dkg_output = DKGOutput::new(self.public, dkg_output_content, signature); - let secret_key = - derive_secret_key_from_scalar(&total_secret_share, &mut crate::getrandom_or_panic()); + let mut nonce: [u8; 32] = [0u8; 32]; + crate::getrandom_or_panic().fill_bytes(&mut nonce); + + let secret_key = SecretKey { key: total_secret_share, nonce }; - Ok((dkg_output, secret_key)) + Ok((dkg_output, SigningShare(secret_key))) } } diff --git a/src/olaf/tests.rs b/src/olaf/tests.rs index cc5bdc4..41d654d 100644 --- a/src/olaf/tests.rs +++ b/src/olaf/tests.rs @@ -73,7 +73,7 @@ mod tests { for j in 0..participants { assert_eq!( dkg_outputs[i].0.content.verifying_keys[j].0, - (dkg_outputs[j].1.to_public()), + (dkg_outputs[j].1 .0.to_public()), "Verification of total secret shares failed!" ); } From 51d16cda8306f1f08553c77f176ebf7828b90be3 Mon Sep 17 00:00:00 2001 From: Fiono11 Date: Wed, 8 May 2024 16:33:06 +0100 Subject: [PATCH 18/60] Remove ephemeral key --- benches/olaf_benchmarks.rs | 6 +-- src/olaf/mod.rs | 2 + src/olaf/simplpedpop.rs | 90 +++++++++++++++++++------------------- src/olaf/types.rs | 69 ++++++----------------------- 4 files changed, 63 insertions(+), 104 deletions(-) diff --git a/benches/olaf_benchmarks.rs b/benches/olaf_benchmarks.rs index 66b29bd..e4f3643 100644 --- a/benches/olaf_benchmarks.rs +++ b/benches/olaf_benchmarks.rs @@ -2,7 +2,7 @@ use criterion::criterion_main; mod olaf_benches { use criterion::{criterion_group, BenchmarkId, Criterion}; - use schnorrkel::{olaf::data_structures::AllMessage, Keypair, PublicKey}; + use schnorrkel::{olaf::AllMessage, Keypair, PublicKey}; fn benchmark_simplpedpop(c: &mut Criterion) { let mut group = c.benchmark_group("SimplPedPoP"); @@ -12,9 +12,9 @@ mod olaf_benches { .warm_up_time(std::time::Duration::from_secs(2)) .measurement_time(std::time::Duration::from_secs(300)); - for &n in [1000].iter() { + for &n in [3, 10, 100, 1000].iter() { let participants = n; - let threshold = 100; //(n * 2 + 2) / 3; + let threshold = (n * 2 + 2) / 3; let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); let public_keys: Vec = keypairs.iter().map(|kp| kp.public).collect(); diff --git a/src/olaf/mod.rs b/src/olaf/mod.rs index d9ad16f..e8c2321 100644 --- a/src/olaf/mod.rs +++ b/src/olaf/mod.rs @@ -10,6 +10,8 @@ pub mod simplpedpop; mod tests; mod types; +pub use types::AllMessage; + const MINIMUM_THRESHOLD: u16 = 2; const GENERATOR: RistrettoPoint = RISTRETTO_BASEPOINT_POINT; diff --git a/src/olaf/simplpedpop.rs b/src/olaf/simplpedpop.rs index 481a168..d56b390 100644 --- a/src/olaf/simplpedpop.rs +++ b/src/olaf/simplpedpop.rs @@ -11,8 +11,8 @@ use super::{ generate_identifier, types::{ AllMessage, DKGOutput, DKGOutputContent, EncryptedSecretShare, MessageContent, Parameters, - PolynomialCommitment, SecretPolynomial, SecretShare, ENCRYPTION_NONCE_LENGTH, - RECIPIENTS_HASH_LENGTH, + PolynomialCommitment, SecretPolynomial, SecretShare, CHACHA20POLY1305_KEY_LENGTH, + ENCRYPTION_NONCE_LENGTH, RECIPIENTS_HASH_LENGTH, }, GroupPublicKey, SigningShare, VerifyingShare, GENERATOR, }; @@ -65,27 +65,6 @@ impl Keypair { rng.fill_bytes(&mut encryption_nonce); encryption_transcript.append_message(b"nonce", &encryption_nonce); - let ephemeral_key = Keypair::generate(); - - let ciphertexts: Vec = (0..parameters.participants) - .map(|i| { - secret_shares[i as usize].encrypt( - &ephemeral_key.secret.key, - encryption_transcript.clone(), - &recipients[i as usize], - &encryption_nonce, - i as usize, - ) - }) - .collect::>>()?; - - let pk = &PublicKey::from_point( - *polynomial_commitment - .coefficients_commitments - .first() - .expect("This never fails because the minimum threshold is 2"), - ); - let secret = *secret_polynomial .coefficients .first() @@ -94,17 +73,36 @@ impl Keypair { let mut nonce: [u8; 32] = [0u8; 32]; crate::getrandom_or_panic().fill_bytes(&mut nonce); - let secret_key = SecretKey { key: secret, nonce }; + let ephemeral_key = SecretKey { key: secret, nonce }; + + let ciphertexts: Vec = (0..parameters.participants as usize) + .map(|i| { + let recipient = recipients[i]; + let key_exchange = ephemeral_key.key * recipient.into_point(); + let mut encryption_transcript = encryption_transcript.clone(); + + encryption_transcript.commit_point(b"recipient", &recipient.as_compressed()); + encryption_transcript.commit_point(b"key exchange", &key_exchange.compress()); + encryption_transcript.append_message(b"i", &i.to_le_bytes()); + + let mut key_bytes = [0; CHACHA20POLY1305_KEY_LENGTH]; + encryption_transcript.challenge_bytes(b"key", &mut key_bytes); + + secret_shares[i as usize].encrypt(&key_bytes, &encryption_nonce) + }) + .collect::>>()?; let secret_commitment = polynomial_commitment .coefficients_commitments .first() .expect("This never fails because the minimum threshold is 2"); + let pk = &PublicKey::from_point(*secret_commitment); + let mut pop_transcript = Transcript::new(b"pop"); pop_transcript .append_message(b"secret commitment", secret_commitment.compress().as_bytes()); - let proof_of_possession = secret_key.sign(pop_transcript, pk); + let proof_of_possession = ephemeral_key.sign(pop_transcript, pk); let message_content = MessageContent::new( self.public, @@ -113,7 +111,6 @@ impl Keypair { recipients_hash, polynomial_commitment, ciphertexts, - ephemeral_key.public, proof_of_possession, ); @@ -166,12 +163,12 @@ impl Keypair { let polynomial_commitment = &content.polynomial_commitment; let encrypted_secret_shares = &content.encrypted_secret_shares; - let public_key = PublicKey::from_point( - *polynomial_commitment - .coefficients_commitments - .first() - .expect("This never fails because the minimum threshold is 2"), - ); + let secret_commitment = polynomial_commitment + .coefficients_commitments + .first() + .expect("This never fails because the minimum threshold is 2"); + + let public_key = PublicKey::from_point(*secret_commitment); public_keys.push(public_key); proofs_of_possession.push(content.proof_of_possession); @@ -197,11 +194,6 @@ impl Keypair { let mut pop_transcript = Transcript::new(b"pop"); - let secret_commitment = polynomial_commitment - .coefficients_commitments - .first() - .expect("This never fails because the minimum threshold is 2"); - pop_transcript .append_message(b"secret commitment", secret_commitment.compress().as_bytes()); @@ -212,10 +204,20 @@ impl Keypair { &polynomial_commitment, ]); - let key_exchange = self.secret.key * content.ephemeral_key.into_point(); + let key_exchange = self.secret.key * secret_commitment; + + encryption_transcript.commit_point(b"recipient", &self.public.as_compressed()); + encryption_transcript.commit_point(b"key exchange", &key_exchange.compress()); + + let mut secret_share_found = false; for (i, encrypted_secret_share) in encrypted_secret_shares.iter().enumerate() { - let mut secret_share_found = false; + let mut encryption_transcript = encryption_transcript.clone(); + + encryption_transcript.append_message(b"i", &i.to_le_bytes()); + + let mut key_bytes = [0; CHACHA20POLY1305_KEY_LENGTH]; + encryption_transcript.challenge_bytes(b"key", &mut key_bytes); if identifiers.len() != participants { let identifier = @@ -224,13 +226,9 @@ impl Keypair { } if !secret_share_found { - if let Ok(secret_share) = encrypted_secret_share.decrypt( - encryption_transcript.clone(), - &self.public, - &key_exchange, - &content.encryption_nonce, - i, - ) { + if let Ok(secret_share) = + encrypted_secret_share.decrypt(&key_bytes, &content.encryption_nonce) + { if secret_share.0 * GENERATOR == polynomial_commitment.evaluate(&identifiers[i]) { diff --git a/src/olaf/types.rs b/src/olaf/types.rs index fb50f1f..d386ec6 100644 --- a/src/olaf/types.rs +++ b/src/olaf/types.rs @@ -3,7 +3,6 @@ #![allow(clippy::too_many_arguments)] use core::iter; - use alloc::vec::Vec; use curve25519_dalek::{ristretto::CompressedRistretto, traits::Identity, RistrettoPoint, Scalar}; use rand_core::{CryptoRng, RngCore}; @@ -13,7 +12,7 @@ use super::{ errors::{DKGError, DKGResult}, GroupPublicKey, VerifyingShare, GENERATOR, MINIMUM_THRESHOLD, }; -use aead::{generic_array::GenericArray, KeyInit, KeySizeUser}; +use aead::KeyInit; use chacha20poly1305::{aead::Aead, ChaCha20Poly1305, Nonce}; pub(super) const COMPRESSED_RISTRETTO_LENGTH: usize = 32; @@ -21,6 +20,7 @@ pub(super) const U16_LENGTH: usize = 2; pub(super) const ENCRYPTION_NONCE_LENGTH: usize = 12; pub(super) const RECIPIENTS_HASH_LENGTH: usize = 16; pub(super) const CHACHA20POLY1305_LENGTH: usize = 64; +pub(super) const CHACHA20POLY1305_KEY_LENGTH: usize = 32; /// The parameters of a given execution of the SimplPedPoP protocol. #[derive(Clone, PartialEq, Eq)] @@ -61,27 +61,12 @@ impl Parameters { pub(super) struct SecretShare(pub(super) Scalar); impl SecretShare { - pub(super) fn encrypt( + pub(super) fn encrypt( &self, - ephemeral_key: &Scalar, - mut transcript: T, - recipient: &PublicKey, + key: &[u8; CHACHA20POLY1305_KEY_LENGTH], nonce: &[u8; ENCRYPTION_NONCE_LENGTH], - i: usize, ) -> DKGResult { - transcript.commit_bytes(b"i", &i.to_le_bytes()); - transcript.commit_point(b"recipient", recipient.as_compressed()); - transcript - .commit_point(b"key exchange", &(ephemeral_key * recipient.as_point()).compress()); - - let mut key: GenericArray< - u8, - ::KeySize, - > = Default::default(); - - transcript.challenge_bytes(b"", key.as_mut_slice()); - - let cipher = ChaCha20Poly1305::new(&key); + let cipher = ChaCha20Poly1305::new(&(*key).into()); let nonce = Nonce::from_slice(&nonce[..]); @@ -97,26 +82,12 @@ impl SecretShare { pub struct EncryptedSecretShare(pub(super) Vec); impl EncryptedSecretShare { - pub(super) fn decrypt( + pub(super) fn decrypt( &self, - mut transcript: T, - recipient: &PublicKey, - key_exchange: &RistrettoPoint, + key: &[u8; CHACHA20POLY1305_KEY_LENGTH], nonce: &[u8; ENCRYPTION_NONCE_LENGTH], - i: usize, ) -> DKGResult { - transcript.commit_bytes(b"i", &i.to_le_bytes()); - transcript.commit_point(b"recipient", recipient.as_compressed()); - transcript.commit_point(b"key exchange", &key_exchange.compress()); - - let mut key: GenericArray< - u8, - ::KeySize, - > = Default::default(); - - transcript.challenge_bytes(b"", key.as_mut_slice()); - - let cipher = ChaCha20Poly1305::new(&key); + let cipher = ChaCha20Poly1305::new(&(*key).into()); let nonce = Nonce::from_slice(&nonce[..]); @@ -262,7 +233,6 @@ pub struct MessageContent { pub(super) recipients_hash: [u8; RECIPIENTS_HASH_LENGTH], pub(super) polynomial_commitment: PolynomialCommitment, pub(super) encrypted_secret_shares: Vec, - pub(super) ephemeral_key: PublicKey, pub(super) proof_of_possession: Signature, } @@ -275,7 +245,6 @@ impl MessageContent { recipients_hash: [u8; RECIPIENTS_HASH_LENGTH], polynomial_commitment: PolynomialCommitment, encrypted_secret_shares: Vec, - ephemeral_key: PublicKey, proof_of_possession: Signature, ) -> Self { Self { @@ -285,7 +254,6 @@ impl MessageContent { recipients_hash, polynomial_commitment, encrypted_secret_shares, - ephemeral_key, proof_of_possession, } } @@ -307,7 +275,6 @@ impl MessageContent { bytes.extend(ciphertext.0.clone()); } - bytes.extend(&self.ephemeral_key.to_bytes()); bytes.extend(&self.proof_of_possession.to_bytes()); bytes @@ -370,10 +337,6 @@ impl MessageContent { cursor += CHACHA20POLY1305_LENGTH; } - let ephemeral_key = PublicKey::from_bytes(&bytes[cursor..cursor + PUBLIC_KEY_LENGTH]) - .map_err(DKGError::InvalidPublicKey)?; - cursor += PUBLIC_KEY_LENGTH; - let proof_of_possession = Signature::from_bytes(&bytes[cursor..cursor + SIGNATURE_LENGTH]) .map_err(DKGError::InvalidSignature)?; @@ -384,7 +347,6 @@ impl MessageContent { recipients_hash, polynomial_commitment, encrypted_secret_shares, - ephemeral_key, proof_of_possession, }) } @@ -521,7 +483,6 @@ mod tests { ]; let proof_of_possession = sender.sign(Transcript::new(b"pop")); let signature = sender.sign(Transcript::new(b"sig")); - let ephemeral_key = PublicKey::from_point(RistrettoPoint::random(&mut OsRng)); let message_content = MessageContent::new( sender.public, @@ -530,7 +491,6 @@ mod tests { recipients_hash, polynomial_commitment, encrypted_secret_shares, - ephemeral_key, proof_of_possession, ); @@ -648,16 +608,15 @@ mod tests { let ephemeral_key = Keypair::generate(); let recipient = Keypair::generate(); let encryption_nonce = [1; ENCRYPTION_NONCE_LENGTH]; - let t = Transcript::new(b"label"); let key_exchange = ephemeral_key.secret.key * recipient.public.as_point(); let secret_share = SecretShare(Scalar::random(&mut rng)); + let mut transcript = Transcript::new(b"encryption"); + transcript.commit_point(b"key", &key_exchange.compress()); + let mut key_bytes = [0; CHACHA20POLY1305_KEY_LENGTH]; + transcript.challenge_bytes(b"key", &mut key_bytes); - let encrypted_share = secret_share - .encrypt(&ephemeral_key.secret.key, t.clone(), &recipient.public, &encryption_nonce, 0) - .unwrap(); + let encrypted_share = secret_share.encrypt(&key_bytes, &encryption_nonce).unwrap(); - encrypted_share - .decrypt(t, &recipient.public, &key_exchange, &encryption_nonce, 0) - .unwrap(); + encrypted_share.decrypt(&key_bytes, &encryption_nonce).unwrap(); } } From 0be043aae81a606d47a67a40dd822fb826e21363 Mon Sep 17 00:00:00 2001 From: Fiono11 Date: Wed, 8 May 2024 16:36:41 +0100 Subject: [PATCH 19/60] Add Identifier type --- src/olaf/mod.rs | 16 +++++++++++----- src/olaf/simplpedpop.rs | 13 ++++++------- src/olaf/types.rs | 2 +- 3 files changed, 18 insertions(+), 13 deletions(-) diff --git a/src/olaf/mod.rs b/src/olaf/mod.rs index e8c2321..a1f3676 100644 --- a/src/olaf/mod.rs +++ b/src/olaf/mod.rs @@ -22,9 +22,15 @@ pub struct VerifyingShare(PublicKey); /// The signing share of a participant in the SimplPedPoP protocol, used to produce its signature share. pub struct SigningShare(SecretKey); -pub(super) fn generate_identifier(recipients_hash: &[u8; 16], index: u16) -> Scalar { - let mut pos = merlin::Transcript::new(b"Identifier"); - pos.append_message(b"RecipientsHash", recipients_hash); - pos.append_message(b"i", &index.to_le_bytes()[..]); - pos.challenge_scalar(b"evaluation position") +/// The identifier of a participant in the Olaf protocol. +pub struct Identifier(Scalar); + +impl Identifier { + pub(super) fn generate(recipients_hash: &[u8; 16], index: u16) -> Identifier { + let mut pos = merlin::Transcript::new(b"Identifier"); + pos.append_message(b"RecipientsHash", recipients_hash); + pos.append_message(b"i", &index.to_le_bytes()[..]); + + Identifier(pos.challenge_scalar(b"evaluation position")) + } } diff --git a/src/olaf/simplpedpop.rs b/src/olaf/simplpedpop.rs index d56b390..1c5f10f 100644 --- a/src/olaf/simplpedpop.rs +++ b/src/olaf/simplpedpop.rs @@ -8,13 +8,12 @@ use rand_core::RngCore; use crate::{context::SigningTranscript, verify_batch, Keypair, PublicKey, SecretKey}; use super::{ errors::{DKGError, DKGResult}, - generate_identifier, types::{ AllMessage, DKGOutput, DKGOutputContent, EncryptedSecretShare, MessageContent, Parameters, PolynomialCommitment, SecretPolynomial, SecretShare, CHACHA20POLY1305_KEY_LENGTH, ENCRYPTION_NONCE_LENGTH, RECIPIENTS_HASH_LENGTH, }, - GroupPublicKey, SigningShare, VerifyingShare, GENERATOR, + GroupPublicKey, Identifier, SigningShare, VerifyingShare, GENERATOR, }; impl Keypair { @@ -49,8 +48,8 @@ impl Keypair { let secret_shares: Vec = (0..parameters.participants) .map(|i| { - let identifier = generate_identifier(&recipients_hash, i); - let polynomial_evaluation = secret_polynomial.evaluate(&identifier); + let identifier = Identifier::generate(&recipients_hash, i); + let polynomial_evaluation = secret_polynomial.evaluate(&identifier.0); SecretShare(polynomial_evaluation) }) .collect(); @@ -221,7 +220,7 @@ impl Keypair { if identifiers.len() != participants { let identifier = - generate_identifier(&first_message.content.recipients_hash, i as u16); + Identifier::generate(&first_message.content.recipients_hash, i as u16); identifiers.push(identifier); } @@ -230,7 +229,7 @@ impl Keypair { encrypted_secret_share.decrypt(&key_bytes, &content.encryption_nonce) { if secret_share.0 * GENERATOR - == polynomial_commitment.evaluate(&identifiers[i]) + == polynomial_commitment.evaluate(&identifiers[i].0) { secret_shares.push(secret_share); secret_share_found = true; @@ -250,7 +249,7 @@ impl Keypair { .map_err(DKGError::InvalidSignature)?; for id in &identifiers { - let evaluation = total_polynomial_commitment.evaluate(id); + let evaluation = total_polynomial_commitment.evaluate(&id.0); verifying_keys.push(VerifyingShare(PublicKey::from_point(evaluation))); } diff --git a/src/olaf/types.rs b/src/olaf/types.rs index d386ec6..c386c58 100644 --- a/src/olaf/types.rs +++ b/src/olaf/types.rs @@ -23,7 +23,7 @@ pub(super) const CHACHA20POLY1305_LENGTH: usize = 64; pub(super) const CHACHA20POLY1305_KEY_LENGTH: usize = 32; /// The parameters of a given execution of the SimplPedPoP protocol. -#[derive(Clone, PartialEq, Eq)] +#[derive(PartialEq, Eq)] pub struct Parameters { pub(super) participants: u16, pub(super) threshold: u16, From cc2fd938f195e409c5a78cf6cd7b1d633ed571b2 Mon Sep 17 00:00:00 2001 From: Fiono11 Date: Wed, 8 May 2024 16:53:10 +0100 Subject: [PATCH 20/60] Add identifiers to dkg_output --- src/olaf/mod.rs | 1 + src/olaf/simplpedpop.rs | 14 +++---- src/olaf/tests.rs | 16 ++++---- src/olaf/types.rs | 83 +++++++++++++++++++++++++---------------- 4 files changed, 65 insertions(+), 49 deletions(-) diff --git a/src/olaf/mod.rs b/src/olaf/mod.rs index a1f3676..290e52e 100644 --- a/src/olaf/mod.rs +++ b/src/olaf/mod.rs @@ -23,6 +23,7 @@ pub struct VerifyingShare(PublicKey); pub struct SigningShare(SecretKey); /// The identifier of a participant in the Olaf protocol. +#[derive(Clone, Copy)] pub struct Identifier(Scalar); impl Identifier { diff --git a/src/olaf/simplpedpop.rs b/src/olaf/simplpedpop.rs index 1c5f10f..ca5d31d 100644 --- a/src/olaf/simplpedpop.rs +++ b/src/olaf/simplpedpop.rs @@ -9,7 +9,7 @@ use crate::{context::SigningTranscript, verify_batch, Keypair, PublicKey, Secret use super::{ errors::{DKGError, DKGResult}, types::{ - AllMessage, DKGOutput, DKGOutputContent, EncryptedSecretShare, MessageContent, Parameters, + AllMessage, DKGOutput, DKGOutputMessage, EncryptedSecretShare, MessageContent, Parameters, PolynomialCommitment, SecretPolynomial, SecretShare, CHACHA20POLY1305_KEY_LENGTH, ENCRYPTION_NONCE_LENGTH, RECIPIENTS_HASH_LENGTH, }, @@ -124,7 +124,7 @@ impl Keypair { pub fn simplpedpop_recipient_all( &self, messages: &[AllMessage], - ) -> DKGResult<(DKGOutput, SigningShare)> { + ) -> DKGResult<(DKGOutputMessage, SigningShare)> { let first_message = &messages[0]; let parameters = &first_message.content.parameters; let threshold = parameters.threshold as usize; @@ -250,18 +250,16 @@ impl Keypair { for id in &identifiers { let evaluation = total_polynomial_commitment.evaluate(&id.0); - verifying_keys.push(VerifyingShare(PublicKey::from_point(evaluation))); + verifying_keys.push((*id, VerifyingShare(PublicKey::from_point(evaluation)))); } - let dkg_output_content = DKGOutputContent::new( - GroupPublicKey(PublicKey::from_point(group_point)), - verifying_keys, - ); + let dkg_output_content = + DKGOutput::new(GroupPublicKey(PublicKey::from_point(group_point)), verifying_keys); let mut dkg_output_transcript = Transcript::new(b"dkg output"); dkg_output_transcript.append_message(b"content", &dkg_output_content.to_bytes()); let signature = self.sign(dkg_output_transcript); - let dkg_output = DKGOutput::new(self.public, dkg_output_content, signature); + let dkg_output = DKGOutputMessage::new(self.public, dkg_output_content, signature); let mut nonce: [u8; 32] = [0u8; 32]; crate::getrandom_or_panic().fill_bytes(&mut nonce); diff --git a/src/olaf/tests.rs b/src/olaf/tests.rs index 41d654d..6995e07 100644 --- a/src/olaf/tests.rs +++ b/src/olaf/tests.rs @@ -54,17 +54,17 @@ mod tests { // Verify that all DKG outputs are equal for group_public_key and verifying_keys assert!( - dkg_outputs.windows(2).all(|w| w[0].0.content.group_public_key.0 - == w[1].0.content.group_public_key.0 - && w[0].0.content.verifying_keys.len() - == w[1].0.content.verifying_keys.len() + dkg_outputs.windows(2).all(|w| w[0].0.dkg_output.group_public_key.0 + == w[1].0.dkg_output.group_public_key.0 + && w[0].0.dkg_output.verifying_keys.len() + == w[1].0.dkg_output.verifying_keys.len() && w[0] .0 - .content + .dkg_output .verifying_keys .iter() - .zip(w[1].0.content.verifying_keys.iter()) - .all(|(a, b)| a.0 == b.0)), + .zip(w[1].0.dkg_output.verifying_keys.iter()) + .all(|((a, b), (c, d))| a.0 == c.0 && b.0 == d.0)), "All DKG outputs should have identical group public keys and verifying keys." ); @@ -72,7 +72,7 @@ mod tests { for i in 0..participants { for j in 0..participants { assert_eq!( - dkg_outputs[i].0.content.verifying_keys[j].0, + dkg_outputs[i].0.dkg_output.verifying_keys[j].1 .0, (dkg_outputs[j].1 .0.to_public()), "Verification of total secret shares failed!" ); diff --git a/src/olaf/types.rs b/src/olaf/types.rs index c386c58..31eec0c 100644 --- a/src/olaf/types.rs +++ b/src/olaf/types.rs @@ -10,7 +10,7 @@ use zeroize::ZeroizeOnDrop; use crate::{context::SigningTranscript, PublicKey, Signature, PUBLIC_KEY_LENGTH, SIGNATURE_LENGTH}; use super::{ errors::{DKGError, DKGResult}, - GroupPublicKey, VerifyingShare, GENERATOR, MINIMUM_THRESHOLD, + GroupPublicKey, Identifier, VerifyingShare, GENERATOR, MINIMUM_THRESHOLD, }; use aead::KeyInit; use chacha20poly1305::{aead::Aead, ChaCha20Poly1305, Nonce}; @@ -21,6 +21,7 @@ pub(super) const ENCRYPTION_NONCE_LENGTH: usize = 12; pub(super) const RECIPIENTS_HASH_LENGTH: usize = 16; pub(super) const CHACHA20POLY1305_LENGTH: usize = 64; pub(super) const CHACHA20POLY1305_KEY_LENGTH: usize = 32; +pub(super) const SCALAR_LENGTH: usize = 32; /// The parameters of a given execution of the SimplPedPoP protocol. #[derive(PartialEq, Eq)] @@ -353,16 +354,16 @@ impl MessageContent { } /// The signed output of the SimplPedPoP protocol. -pub struct DKGOutput { +pub struct DKGOutputMessage { pub(super) sender: PublicKey, - pub(super) content: DKGOutputContent, + pub(super) dkg_output: DKGOutput, pub(super) signature: Signature, } -impl DKGOutput { +impl DKGOutputMessage { /// Creates a signed SimplPedPoP output. - pub fn new(sender: PublicKey, content: DKGOutputContent, signature: Signature) -> Self { - Self { sender, content, signature } + pub fn new(sender: PublicKey, content: DKGOutput, signature: Signature) -> Self { + Self { sender, dkg_output: content, signature } } /// Serializes the DKGOutput into bytes. @@ -372,7 +373,7 @@ impl DKGOutput { let pk_bytes = self.sender.to_bytes(); bytes.extend(pk_bytes); - let content_bytes = self.content.to_bytes(); + let content_bytes = self.dkg_output.to_bytes(); bytes.extend(content_bytes); let signature_bytes = self.signature.to_bytes(); @@ -390,25 +391,28 @@ impl DKGOutput { cursor += PUBLIC_KEY_LENGTH; let content_bytes = &bytes[cursor..bytes.len() - SIGNATURE_LENGTH]; - let content = DKGOutputContent::from_bytes(content_bytes)?; + let dkg_output = DKGOutput::from_bytes(content_bytes)?; cursor = bytes.len() - SIGNATURE_LENGTH; let signature = Signature::from_bytes(&bytes[cursor..cursor + SIGNATURE_LENGTH]) .map_err(DKGError::InvalidSignature)?; - Ok(DKGOutput { sender, content, signature }) + Ok(DKGOutputMessage { sender, dkg_output, signature }) } } /// The content of the signed output of the SimplPedPoP protocol. -pub struct DKGOutputContent { +pub struct DKGOutput { pub(super) group_public_key: GroupPublicKey, - pub(super) verifying_keys: Vec, + pub(super) verifying_keys: Vec<(Identifier, VerifyingShare)>, } -impl DKGOutputContent { +impl DKGOutput { /// Creates the content of the SimplPedPoP output. - pub fn new(group_public_key: GroupPublicKey, verifying_keys: Vec) -> Self { + pub fn new( + group_public_key: GroupPublicKey, + verifying_keys: Vec<(Identifier, VerifyingShare)>, + ) -> Self { Self { group_public_key, verifying_keys } } /// Serializes the DKGOutputContent into bytes. @@ -421,17 +425,16 @@ impl DKGOutputContent { let key_count = self.verifying_keys.len() as u16; bytes.extend(key_count.to_le_bytes()); - for key in &self.verifying_keys { + for (id, key) in &self.verifying_keys { + bytes.extend(id.0.to_bytes()); bytes.extend(key.0.to_bytes()); } bytes } -} -impl DKGOutputContent { /// Deserializes the DKGOutputContent from bytes. - pub fn from_bytes(bytes: &[u8]) -> Result { + pub fn from_bytes(bytes: &[u8]) -> Result { let mut cursor = 0; let public_key_bytes = &bytes[cursor..cursor + PUBLIC_KEY_LENGTH]; // Ristretto points are 32 bytes when compressed @@ -447,14 +450,20 @@ impl DKGOutputContent { u16::from_le_bytes(key_count_bytes.try_into().map_err(DKGError::DeserializationError)?); let mut verifying_keys = Vec::with_capacity(key_count as usize); + for _ in 0..key_count { + let mut identifier_bytes = [0; SCALAR_LENGTH]; + identifier_bytes.copy_from_slice(&bytes[cursor..cursor + SCALAR_LENGTH]); + let identifier = Scalar::from_canonical_bytes(identifier_bytes).unwrap(); + cursor += SCALAR_LENGTH; + let key_bytes = &bytes[cursor..cursor + PUBLIC_KEY_LENGTH]; cursor += PUBLIC_KEY_LENGTH; let key = PublicKey::from_bytes(key_bytes).map_err(DKGError::InvalidPublicKey)?; - verifying_keys.push(VerifyingShare(key)); + verifying_keys.push((Identifier(identifier), VerifyingShare(key))); } - Ok(DKGOutputContent { + Ok(DKGOutput { group_public_key: GroupPublicKey(PublicKey::from_point(group_public_key)), verifying_keys, }) @@ -550,12 +559,21 @@ mod tests { let mut rng = OsRng; let group_public_key = RistrettoPoint::random(&mut rng); let verifying_keys = vec![ - VerifyingShare(PublicKey::from_point(RistrettoPoint::random(&mut rng))), - VerifyingShare(PublicKey::from_point(RistrettoPoint::random(&mut rng))), - VerifyingShare(PublicKey::from_point(RistrettoPoint::random(&mut rng))), + ( + Identifier(Scalar::random(&mut rng)), + VerifyingShare(PublicKey::from_point(RistrettoPoint::random(&mut rng))), + ), + ( + Identifier(Scalar::random(&mut rng)), + VerifyingShare(PublicKey::from_point(RistrettoPoint::random(&mut rng))), + ), + ( + Identifier(Scalar::random(&mut rng)), + VerifyingShare(PublicKey::from_point(RistrettoPoint::random(&mut rng))), + ), ]; - let dkg_output_content = DKGOutputContent { + let dkg_output = DKGOutput { group_public_key: GroupPublicKey(PublicKey::from_point(group_public_key)), verifying_keys, }; @@ -563,36 +581,35 @@ mod tests { let keypair = Keypair::generate(); let signature = keypair.sign(Transcript::new(b"test")); - let dkg_output = - DKGOutput { sender: keypair.public, content: dkg_output_content, signature }; + let dkg_output = DKGOutputMessage { sender: keypair.public, dkg_output, signature }; // Serialize the DKGOutput let bytes = dkg_output.to_bytes(); // Deserialize the DKGOutput let deserialized_dkg_output = - DKGOutput::from_bytes(&bytes).expect("Deserialization failed"); + DKGOutputMessage::from_bytes(&bytes).expect("Deserialization failed"); // Check if the deserialized content matches the original assert_eq!( - deserialized_dkg_output.content.group_public_key.0.as_compressed(), - dkg_output.content.group_public_key.0.as_compressed(), + deserialized_dkg_output.dkg_output.group_public_key.0.as_compressed(), + dkg_output.dkg_output.group_public_key.0.as_compressed(), "Group public keys do not match" ); assert_eq!( - deserialized_dkg_output.content.verifying_keys.len(), - dkg_output.content.verifying_keys.len(), + deserialized_dkg_output.dkg_output.verifying_keys.len(), + dkg_output.dkg_output.verifying_keys.len(), "Verifying keys counts do not match" ); assert!( deserialized_dkg_output - .content + .dkg_output .verifying_keys .iter() - .zip(dkg_output.content.verifying_keys.iter()) - .all(|(a, b)| a.0 == b.0), + .zip(dkg_output.dkg_output.verifying_keys.iter()) + .all(|((a, b), (c, d))| a.0 == c.0 && b.0 == d.0), "Verifying keys do not match" ); From d7d061daed573fbf90f9c07f524d65d3873bd8ce Mon Sep 17 00:00:00 2001 From: Fiono11 Date: Wed, 8 May 2024 19:04:15 +0100 Subject: [PATCH 21/60] Remove proof of possession signature --- src/olaf/simplpedpop.rs | 88 +++++++++++++++-------------------------- src/olaf/tests.rs | 27 ------------- src/olaf/types.rs | 16 -------- 3 files changed, 31 insertions(+), 100 deletions(-) diff --git a/src/olaf/simplpedpop.rs b/src/olaf/simplpedpop.rs index ca5d31d..495cb8c 100644 --- a/src/olaf/simplpedpop.rs +++ b/src/olaf/simplpedpop.rs @@ -9,9 +9,9 @@ use crate::{context::SigningTranscript, verify_batch, Keypair, PublicKey, Secret use super::{ errors::{DKGError, DKGResult}, types::{ - AllMessage, DKGOutput, DKGOutputMessage, EncryptedSecretShare, MessageContent, Parameters, - PolynomialCommitment, SecretPolynomial, SecretShare, CHACHA20POLY1305_KEY_LENGTH, - ENCRYPTION_NONCE_LENGTH, RECIPIENTS_HASH_LENGTH, + AllMessage, DKGOutput, DKGOutputMessage, MessageContent, Parameters, PolynomialCommitment, + SecretPolynomial, SecretShare, CHACHA20POLY1305_KEY_LENGTH, ENCRYPTION_NONCE_LENGTH, + RECIPIENTS_HASH_LENGTH, }, GroupPublicKey, Identifier, SigningShare, VerifyingShare, GENERATOR, }; @@ -37,22 +37,18 @@ impl Keypair { // full recipients list. let mut recipients_transcript = merlin::Transcript::new(b"RecipientsHash"); parameters.commit(&mut recipients_transcript); - for r in recipients.iter() { - recipients_transcript.commit_point(b"recipient", r.as_compressed()); + + for recipient in &recipients { + recipients_transcript.commit_point(b"recipient", recipient.as_compressed()); } + let mut recipients_hash = [0u8; RECIPIENTS_HASH_LENGTH]; recipients_transcript.challenge_bytes(b"finalize", &mut recipients_hash); let secret_polynomial = SecretPolynomial::generate(parameters.threshold as usize - 1, &mut rng); - let secret_shares: Vec = (0..parameters.participants) - .map(|i| { - let identifier = Identifier::generate(&recipients_hash, i); - let polynomial_evaluation = secret_polynomial.evaluate(&identifier.0); - SecretShare(polynomial_evaluation) - }) - .collect(); + let mut encrypted_secret_shares = Vec::new(); let polynomial_commitment = PolynomialCommitment::commit(&secret_polynomial); @@ -74,34 +70,29 @@ impl Keypair { let ephemeral_key = SecretKey { key: secret, nonce }; - let ciphertexts: Vec = (0..parameters.participants as usize) - .map(|i| { - let recipient = recipients[i]; - let key_exchange = ephemeral_key.key * recipient.into_point(); - let mut encryption_transcript = encryption_transcript.clone(); + for i in 0..parameters.participants { + let identifier = Identifier::generate(&recipients_hash, i); - encryption_transcript.commit_point(b"recipient", &recipient.as_compressed()); - encryption_transcript.commit_point(b"key exchange", &key_exchange.compress()); - encryption_transcript.append_message(b"i", &i.to_le_bytes()); + let polynomial_evaluation = secret_polynomial.evaluate(&identifier.0); - let mut key_bytes = [0; CHACHA20POLY1305_KEY_LENGTH]; - encryption_transcript.challenge_bytes(b"key", &mut key_bytes); + let secret_share = SecretShare(polynomial_evaluation); - secret_shares[i as usize].encrypt(&key_bytes, &encryption_nonce) - }) - .collect::>>()?; + let recipient = recipients[i as usize]; - let secret_commitment = polynomial_commitment - .coefficients_commitments - .first() - .expect("This never fails because the minimum threshold is 2"); + let key_exchange = ephemeral_key.key * recipient.into_point(); + + let mut encryption_transcript = encryption_transcript.clone(); + encryption_transcript.commit_point(b"recipient", recipient.as_compressed()); + encryption_transcript.commit_point(b"key exchange", &key_exchange.compress()); + encryption_transcript.append_message(b"i", &(i as usize).to_le_bytes()); + + let mut key_bytes = [0; CHACHA20POLY1305_KEY_LENGTH]; + encryption_transcript.challenge_bytes(b"key", &mut key_bytes); - let pk = &PublicKey::from_point(*secret_commitment); + let encrypted_secret_share = secret_share.encrypt(&key_bytes, &encryption_nonce)?; - let mut pop_transcript = Transcript::new(b"pop"); - pop_transcript - .append_message(b"secret commitment", secret_commitment.compress().as_bytes()); - let proof_of_possession = ephemeral_key.sign(pop_transcript, pk); + encrypted_secret_shares.push(encrypted_secret_share); + } let message_content = MessageContent::new( self.public, @@ -109,8 +100,7 @@ impl Keypair { parameters, recipients_hash, polynomial_commitment, - ciphertexts, - proof_of_possession, + encrypted_secret_shares, ); let mut signature_transcript = Transcript::new(b"signature"); @@ -138,12 +128,9 @@ impl Keypair { let mut secret_shares = Vec::with_capacity(participants); let mut verifying_keys = Vec::with_capacity(participants); - let mut public_keys = Vec::with_capacity(participants); - let mut proofs_of_possession = Vec::with_capacity(participants); let mut senders = Vec::with_capacity(participants); let mut signatures = Vec::with_capacity(participants); let mut signatures_transcripts = Vec::with_capacity(participants); - let mut pops_transcripts = Vec::with_capacity(participants); let mut group_point = RistrettoPoint::identity(); let mut total_secret_share = Scalar::ZERO; let mut total_polynomial_commitment = @@ -167,10 +154,6 @@ impl Keypair { .first() .expect("This never fails because the minimum threshold is 2"); - let public_key = PublicKey::from_point(*secret_commitment); - public_keys.push(public_key); - proofs_of_possession.push(content.proof_of_possession); - senders.push(content.sender); signatures.push(message.signature); @@ -191,13 +174,6 @@ impl Keypair { signature_transcript.append_message(b"message", &content.to_bytes()); signatures_transcripts.push(signature_transcript); - let mut pop_transcript = Transcript::new(b"pop"); - - pop_transcript - .append_message(b"secret commitment", secret_commitment.compress().as_bytes()); - - pops_transcripts.push(pop_transcript); - total_polynomial_commitment = PolynomialCommitment::sum_polynomial_commitments(&[ &total_polynomial_commitment, &polynomial_commitment, @@ -205,7 +181,7 @@ impl Keypair { let key_exchange = self.secret.key * secret_commitment; - encryption_transcript.commit_point(b"recipient", &self.public.as_compressed()); + encryption_transcript.commit_point(b"recipient", self.public.as_compressed()); encryption_transcript.commit_point(b"key exchange", &key_exchange.compress()); let mut secret_share_found = false; @@ -242,9 +218,6 @@ impl Keypair { group_point += secret_commitment; } - verify_batch(&mut pops_transcripts, &proofs_of_possession, &public_keys, false) - .map_err(DKGError::InvalidProofOfPossession)?; - verify_batch(&mut signatures_transcripts, &signatures, &senders, false) .map_err(DKGError::InvalidSignature)?; @@ -253,13 +226,14 @@ impl Keypair { verifying_keys.push((*id, VerifyingShare(PublicKey::from_point(evaluation)))); } - let dkg_output_content = + let dkg_output = DKGOutput::new(GroupPublicKey(PublicKey::from_point(group_point)), verifying_keys); + let mut dkg_output_transcript = Transcript::new(b"dkg output"); - dkg_output_transcript.append_message(b"content", &dkg_output_content.to_bytes()); + dkg_output_transcript.append_message(b"message", &dkg_output.to_bytes()); let signature = self.sign(dkg_output_transcript); - let dkg_output = DKGOutputMessage::new(self.public, dkg_output_content, signature); + let dkg_output = DKGOutputMessage::new(self.public, dkg_output, signature); let mut nonce: [u8; 32] = [0u8; 32]; crate::getrandom_or_panic().fill_bytes(&mut nonce); diff --git a/src/olaf/tests.rs b/src/olaf/tests.rs index 6995e07..c341803 100644 --- a/src/olaf/tests.rs +++ b/src/olaf/tests.rs @@ -248,33 +248,6 @@ mod tests { } } - #[test] - fn test_invalid_proof_of_possession() { - let threshold = 3; - let participants = 5; - - let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); - let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); - - let mut messages: Vec = keypairs - .iter() - .map(|kp| kp.simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap()) - .collect(); - - messages[1].content.proof_of_possession = - keypairs[1].secret.sign(Transcript::new(b"invalid"), &keypairs[1].public); - - let result = keypairs[0].simplpedpop_recipient_all(&messages); - - match result { - Ok(_) => panic!("Expected an error, but got Ok."), - Err(e) => match e { - DKGError::InvalidProofOfPossession(_) => assert!(true), - _ => panic!("Expected DKGError::InvalidProofOfPossession, but got {:?}", e), - }, - } - } - #[test] fn test_invalid_signature() { let threshold = 3; diff --git a/src/olaf/types.rs b/src/olaf/types.rs index 31eec0c..50585ea 100644 --- a/src/olaf/types.rs +++ b/src/olaf/types.rs @@ -234,7 +234,6 @@ pub struct MessageContent { pub(super) recipients_hash: [u8; RECIPIENTS_HASH_LENGTH], pub(super) polynomial_commitment: PolynomialCommitment, pub(super) encrypted_secret_shares: Vec, - pub(super) proof_of_possession: Signature, } impl MessageContent { @@ -246,7 +245,6 @@ impl MessageContent { recipients_hash: [u8; RECIPIENTS_HASH_LENGTH], polynomial_commitment: PolynomialCommitment, encrypted_secret_shares: Vec, - proof_of_possession: Signature, ) -> Self { Self { sender, @@ -255,7 +253,6 @@ impl MessageContent { recipients_hash, polynomial_commitment, encrypted_secret_shares, - proof_of_possession, } } /// Serialize MessageContent @@ -276,8 +273,6 @@ impl MessageContent { bytes.extend(ciphertext.0.clone()); } - bytes.extend(&self.proof_of_possession.to_bytes()); - bytes } @@ -338,9 +333,6 @@ impl MessageContent { cursor += CHACHA20POLY1305_LENGTH; } - let proof_of_possession = Signature::from_bytes(&bytes[cursor..cursor + SIGNATURE_LENGTH]) - .map_err(DKGError::InvalidSignature)?; - Ok(MessageContent { sender, encryption_nonce, @@ -348,7 +340,6 @@ impl MessageContent { recipients_hash, polynomial_commitment, encrypted_secret_shares, - proof_of_possession, }) } } @@ -490,7 +481,6 @@ mod tests { EncryptedSecretShare(vec![1; CHACHA20POLY1305_LENGTH]), EncryptedSecretShare(vec![1; CHACHA20POLY1305_LENGTH]), ]; - let proof_of_possession = sender.sign(Transcript::new(b"pop")); let signature = sender.sign(Transcript::new(b"sig")); let message_content = MessageContent::new( @@ -500,7 +490,6 @@ mod tests { recipients_hash, polynomial_commitment, encrypted_secret_shares, - proof_of_possession, ); let message = AllMessage::new(message_content, signature); @@ -546,11 +535,6 @@ mod tests { .zip(deserialized_message.content.encrypted_secret_shares.iter()) .all(|(a, b)| a.0 == b.0)); - assert_eq!( - message.content.proof_of_possession, - deserialized_message.content.proof_of_possession - ); - assert_eq!(message.signature, deserialized_message.signature); } From 9ed307f121adc033c94ce7c408ea45537147d4c0 Mon Sep 17 00:00:00 2001 From: Fiono11 Date: Wed, 8 May 2024 22:56:06 +0100 Subject: [PATCH 22/60] Improvements --- src/olaf/errors.rs | 2 +- src/olaf/mod.rs | 3 ++- src/olaf/simplpedpop.rs | 14 ++++++++------ src/olaf/types.rs | 8 +++----- 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/olaf/errors.rs b/src/olaf/errors.rs index 1b95d6b..33219a2 100644 --- a/src/olaf/errors.rs +++ b/src/olaf/errors.rs @@ -7,7 +7,7 @@ use crate::SignatureError; pub type DKGResult = Result; /// An error ocurred during the execution of the SimplPedPoP protocol. -#[derive(Debug, Clone)] +#[derive(Debug)] pub enum DKGError { /// Invalid Proof of Possession. InvalidProofOfPossession(SignatureError), diff --git a/src/olaf/mod.rs b/src/olaf/mod.rs index 290e52e..58d8a8a 100644 --- a/src/olaf/mod.rs +++ b/src/olaf/mod.rs @@ -4,6 +4,7 @@ use curve25519_dalek::{constants::RISTRETTO_BASEPOINT_POINT, RistrettoPoint, Scalar}; use crate::{PublicKey, SecretKey}; use crate::context::SigningTranscript; +use merlin::Transcript; pub mod errors; pub mod simplpedpop; @@ -28,7 +29,7 @@ pub struct Identifier(Scalar); impl Identifier { pub(super) fn generate(recipients_hash: &[u8; 16], index: u16) -> Identifier { - let mut pos = merlin::Transcript::new(b"Identifier"); + let mut pos = Transcript::new(b"Identifier"); pos.append_message(b"RecipientsHash", recipients_hash); pos.append_message(b"i", &index.to_le_bytes()[..]); diff --git a/src/olaf/simplpedpop.rs b/src/olaf/simplpedpop.rs index 495cb8c..2348580 100644 --- a/src/olaf/simplpedpop.rs +++ b/src/olaf/simplpedpop.rs @@ -5,7 +5,9 @@ use alloc::vec::Vec; use curve25519_dalek::{traits::Identity, RistrettoPoint, Scalar}; use merlin::Transcript; use rand_core::RngCore; -use crate::{context::SigningTranscript, verify_batch, Keypair, PublicKey, SecretKey}; +use crate::{ + context::SigningTranscript, verify_batch, Keypair, PublicKey, SecretKey, getrandom_or_panic, +}; use super::{ errors::{DKGError, DKGResult}, types::{ @@ -26,7 +28,7 @@ impl Keypair { let parameters = Parameters::generate(recipients.len() as u16, threshold); parameters.validate()?; - let mut rng = crate::getrandom_or_panic(); + let mut rng = getrandom_or_panic(); // We do not recipients.sort() because the protocol is simpler // if we require that all contributions provide the list in @@ -35,7 +37,7 @@ impl Keypair { // Instead we create a kind of session id by hashing the list // provided, but we provide only hash to recipients, not the // full recipients list. - let mut recipients_transcript = merlin::Transcript::new(b"RecipientsHash"); + let mut recipients_transcript = Transcript::new(b"RecipientsHash"); parameters.commit(&mut recipients_transcript); for recipient in &recipients { @@ -66,7 +68,7 @@ impl Keypair { .expect("This never fails because the minimum threshold is 2"); let mut nonce: [u8; 32] = [0u8; 32]; - crate::getrandom_or_panic().fill_bytes(&mut nonce); + rng.fill_bytes(&mut nonce); let ephemeral_key = SecretKey { key: secret, nonce }; @@ -157,7 +159,7 @@ impl Keypair { senders.push(content.sender); signatures.push(message.signature); - let mut encryption_transcript = merlin::Transcript::new(b"Encryption"); + let mut encryption_transcript = Transcript::new(b"Encryption"); parameters.commit(&mut encryption_transcript); encryption_transcript.commit_point(b"contributor", content.sender.as_compressed()); encryption_transcript.append_message(b"nonce", &content.encryption_nonce); @@ -236,7 +238,7 @@ impl Keypair { let dkg_output = DKGOutputMessage::new(self.public, dkg_output, signature); let mut nonce: [u8; 32] = [0u8; 32]; - crate::getrandom_or_panic().fill_bytes(&mut nonce); + getrandom_or_panic().fill_bytes(&mut nonce); let secret_key = SecretKey { key: total_secret_share, nonce }; diff --git a/src/olaf/types.rs b/src/olaf/types.rs index 50585ea..26f82b6 100644 --- a/src/olaf/types.rs +++ b/src/olaf/types.rs @@ -1,6 +1,4 @@ -//! SimplPedPoP data structures. - -#![allow(clippy::too_many_arguments)] +//! SimplPedPoP types. use core::iter; use alloc::vec::Vec; @@ -410,7 +408,7 @@ impl DKGOutput { pub fn to_bytes(&self) -> Vec { let mut bytes = Vec::new(); - let compressed_public_key = self.group_public_key.0.as_compressed(); // Assuming PublicKey can be compressed directly + let compressed_public_key = self.group_public_key.0.as_compressed(); bytes.extend(compressed_public_key.to_bytes().iter()); let key_count = self.verifying_keys.len() as u16; @@ -598,7 +596,7 @@ mod tests { ); assert_eq!( - deserialized_dkg_output.signature.s, dkg_output.signature.s, + deserialized_dkg_output.signature, dkg_output.signature, "Signatures do not match" ); } From 8b9726e9d94a0f980aa2c6082fa87bdec7726efc Mon Sep 17 00:00:00 2001 From: Fiono11 Date: Thu, 9 May 2024 00:08:16 +0100 Subject: [PATCH 23/60] Fix deserialization of dkg output --- src/olaf/types.rs | 36 ++++++++++++++++-------------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/src/olaf/types.rs b/src/olaf/types.rs index 26f82b6..7be711b 100644 --- a/src/olaf/types.rs +++ b/src/olaf/types.rs @@ -14,7 +14,7 @@ use aead::KeyInit; use chacha20poly1305::{aead::Aead, ChaCha20Poly1305, Nonce}; pub(super) const COMPRESSED_RISTRETTO_LENGTH: usize = 32; -pub(super) const U16_LENGTH: usize = 2; +pub(super) const VEC_LENGTH: usize = 2; pub(super) const ENCRYPTION_NONCE_LENGTH: usize = 12; pub(super) const RECIPIENTS_HASH_LENGTH: usize = 16; pub(super) const CHACHA20POLY1305_LENGTH: usize = 64; @@ -124,7 +124,6 @@ impl SecretPolynomial { 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; } @@ -289,17 +288,17 @@ impl MessageContent { cursor += ENCRYPTION_NONCE_LENGTH; let participants = u16::from_le_bytes( - bytes[cursor..cursor + U16_LENGTH] + bytes[cursor..cursor + VEC_LENGTH] .try_into() .map_err(DKGError::DeserializationError)?, ); - cursor += U16_LENGTH; + cursor += VEC_LENGTH; let threshold = u16::from_le_bytes( - bytes[cursor..cursor + U16_LENGTH] + bytes[cursor..cursor + VEC_LENGTH] .try_into() .map_err(DKGError::DeserializationError)?, ); - cursor += U16_LENGTH; + cursor += VEC_LENGTH; let recipients_hash: [u8; RECIPIENTS_HASH_LENGTH] = bytes [cursor..cursor + RECIPIENTS_HASH_LENGTH] @@ -355,7 +354,7 @@ impl DKGOutputMessage { Self { sender, dkg_output: content, signature } } - /// Serializes the DKGOutput into bytes. + /// Serializes the DKGOutputMessage into bytes. pub fn to_bytes(&self) -> Vec { let mut bytes = Vec::new(); @@ -371,7 +370,7 @@ impl DKGOutputMessage { bytes } - /// Deserializes the DKGOutput from bytes. + /// Deserializes the DKGOutputMessage from bytes. pub fn from_bytes(bytes: &[u8]) -> Result { let mut cursor = 0; @@ -404,7 +403,7 @@ impl DKGOutput { ) -> Self { Self { group_public_key, verifying_keys } } - /// Serializes the DKGOutputContent into bytes. + /// Serializes the DKGOutput into bytes. pub fn to_bytes(&self) -> Vec { let mut bytes = Vec::new(); @@ -422,27 +421,27 @@ impl DKGOutput { bytes } - /// Deserializes the DKGOutputContent from bytes. + /// Deserializes the DKGOutput from bytes. pub fn from_bytes(bytes: &[u8]) -> Result { let mut cursor = 0; - let public_key_bytes = &bytes[cursor..cursor + PUBLIC_KEY_LENGTH]; // Ristretto points are 32 bytes when compressed + let public_key_bytes = &bytes[cursor..cursor + PUBLIC_KEY_LENGTH]; cursor += PUBLIC_KEY_LENGTH; + let compressed_public_key = CompressedRistretto::from_slice(public_key_bytes) .map_err(DKGError::DeserializationError)?; + let group_public_key = compressed_public_key.decompress().ok_or(DKGError::InvalidRistrettoPoint)?; - let key_count_bytes = &bytes[cursor..cursor + U16_LENGTH]; - cursor += U16_LENGTH; - let key_count = - u16::from_le_bytes(key_count_bytes.try_into().map_err(DKGError::DeserializationError)?); + cursor += VEC_LENGTH; - let mut verifying_keys = Vec::with_capacity(key_count as usize); + let mut verifying_keys = Vec::new(); - for _ in 0..key_count { + while cursor < bytes.len() { let mut identifier_bytes = [0; SCALAR_LENGTH]; identifier_bytes.copy_from_slice(&bytes[cursor..cursor + SCALAR_LENGTH]); + // TODO: convert unwrap to error let identifier = Scalar::from_canonical_bytes(identifier_bytes).unwrap(); cursor += SCALAR_LENGTH; @@ -565,14 +564,11 @@ mod tests { let dkg_output = DKGOutputMessage { sender: keypair.public, dkg_output, signature }; - // Serialize the DKGOutput let bytes = dkg_output.to_bytes(); - // Deserialize the DKGOutput let deserialized_dkg_output = DKGOutputMessage::from_bytes(&bytes).expect("Deserialization failed"); - // Check if the deserialized content matches the original assert_eq!( deserialized_dkg_output.dkg_output.group_public_key.0.as_compressed(), dkg_output.dkg_output.group_public_key.0.as_compressed(), From 3771fd5df8a75e94effeab188ce170963bd92455 Mon Sep 17 00:00:00 2001 From: Fiono11 Date: Thu, 9 May 2024 12:42:33 +0100 Subject: [PATCH 24/60] Remove unwrap --- src/olaf/errors.rs | 2 ++ src/olaf/types.rs | 9 ++++++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/olaf/errors.rs b/src/olaf/errors.rs index 33219a2..1b5ae4e 100644 --- a/src/olaf/errors.rs +++ b/src/olaf/errors.rs @@ -23,6 +23,8 @@ pub enum DKGError { InvalidSignature(SignatureError), /// Invalid Ristretto Point. InvalidRistrettoPoint, + /// Invalid Scalar. + InvalidScalar, /// Invalid secret share. InvalidSecretShare, /// Deserialization Error. diff --git a/src/olaf/types.rs b/src/olaf/types.rs index 7be711b..408d941 100644 --- a/src/olaf/types.rs +++ b/src/olaf/types.rs @@ -5,7 +5,10 @@ use alloc::vec::Vec; use curve25519_dalek::{ristretto::CompressedRistretto, traits::Identity, RistrettoPoint, Scalar}; use rand_core::{CryptoRng, RngCore}; use zeroize::ZeroizeOnDrop; -use crate::{context::SigningTranscript, PublicKey, Signature, PUBLIC_KEY_LENGTH, SIGNATURE_LENGTH}; +use crate::{ + context::SigningTranscript, scalar_from_canonical_bytes, PublicKey, Signature, + PUBLIC_KEY_LENGTH, SIGNATURE_LENGTH, +}; use super::{ errors::{DKGError, DKGResult}, GroupPublicKey, Identifier, VerifyingShare, GENERATOR, MINIMUM_THRESHOLD, @@ -441,8 +444,8 @@ impl DKGOutput { while cursor < bytes.len() { let mut identifier_bytes = [0; SCALAR_LENGTH]; identifier_bytes.copy_from_slice(&bytes[cursor..cursor + SCALAR_LENGTH]); - // TODO: convert unwrap to error - let identifier = Scalar::from_canonical_bytes(identifier_bytes).unwrap(); + let identifier = + scalar_from_canonical_bytes(identifier_bytes).ok_or(DKGError::InvalidScalar)?; cursor += SCALAR_LENGTH; let key_bytes = &bytes[cursor..cursor + PUBLIC_KEY_LENGTH]; From 04546ed845e7ca0e15164de81e154a4867fdb5fb Mon Sep 17 00:00:00 2001 From: Fiono11 Date: Thu, 9 May 2024 12:48:47 +0100 Subject: [PATCH 25/60] Improve errors --- src/olaf/errors.rs | 26 +++++++++++++------------- src/olaf/simplpedpop.rs | 2 +- src/olaf/tests.rs | 2 +- src/olaf/types.rs | 6 +++--- 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/olaf/errors.rs b/src/olaf/errors.rs index 1b5ae4e..b5fd9ec 100644 --- a/src/olaf/errors.rs +++ b/src/olaf/errors.rs @@ -9,35 +9,35 @@ pub type DKGResult = Result; /// An error ocurred during the execution of the SimplPedPoP protocol. #[derive(Debug)] pub enum DKGError { - /// Invalid Proof of Possession. - InvalidProofOfPossession(SignatureError), /// Threshold cannot be greater than the number of participants. ExcessiveThreshold, /// Threshold must be at least 2. InsufficientThreshold, /// Number of participants is invalid. InvalidNumberOfParticipants, - /// Invalid PublicKey. + /// Invalid public key. InvalidPublicKey(SignatureError), - /// Invalid Signature. + /// Invalid group public key. + InvalidGroupPublicKey, + /// Invalid signature. InvalidSignature(SignatureError), - /// Invalid Ristretto Point. - InvalidRistrettoPoint, - /// Invalid Scalar. - InvalidScalar, + /// Invalid coefficient commitment of the polynomial commitment. + InvaliCoefficientCommitment, + /// Invalid identifier. + InvalidIdentifier, /// Invalid secret share. InvalidSecretShare, /// Deserialization Error. DeserializationError(TryFromSliceError), - /// The parameters of all messages should be equal. + /// The parameters of all messages must be equal. DifferentParameters, - /// The recipients hash of all messages should be equal. + /// The recipients hash of all messages must be equal. DifferentRecipientsHash, /// The number of messages should be 2 at least, which the minimum number of participants. InvalidNumberOfMessages, - /// The degree of the polynomial commitment be equal to the number of participants - 1. - IncorrectPolynomialCommitmentDegree, - /// The number of encrypted shares per message should be equal to the number of participants. + /// The number of coefficient commitments of the polynomial commitment must be equal to the threshold - 1. + IncorrectNumberOfCoefficientCommitments, + /// The number of encrypted shares per message must be equal to the number of participants. IncorrectNumberOfEncryptedShares, /// Decryption error when decrypting an encrypted secret share. DecryptionError(chacha20poly1305::Error), diff --git a/src/olaf/simplpedpop.rs b/src/olaf/simplpedpop.rs index 2348580..21488a2 100644 --- a/src/olaf/simplpedpop.rs +++ b/src/olaf/simplpedpop.rs @@ -165,7 +165,7 @@ impl Keypair { encryption_transcript.append_message(b"nonce", &content.encryption_nonce); if polynomial_commitment.coefficients_commitments.len() != threshold - 1 { - return Err(DKGError::IncorrectPolynomialCommitmentDegree); + return Err(DKGError::IncorrectNumberOfCoefficientCommitments); } if encrypted_secret_shares.len() != participants { diff --git a/src/olaf/tests.rs b/src/olaf/tests.rs index c341803..f66adfd 100644 --- a/src/olaf/tests.rs +++ b/src/olaf/tests.rs @@ -186,7 +186,7 @@ mod tests { match result { Ok(_) => panic!("Expected an error, but got Ok."), Err(e) => match e { - DKGError::IncorrectPolynomialCommitmentDegree => assert!(true), + DKGError::IncorrectNumberOfCoefficientCommitments => assert!(true), _ => panic!("Expected DKGError::IncorrectNumberOfCommitments, but got {:?}", e), }, } diff --git a/src/olaf/types.rs b/src/olaf/types.rs index 408d941..c79d5f8 100644 --- a/src/olaf/types.rs +++ b/src/olaf/types.rs @@ -318,7 +318,7 @@ impl MessageContent { .map_err(DKGError::DeserializationError)?; coefficients_commitments - .push(point.decompress().ok_or(DKGError::InvalidRistrettoPoint)?); + .push(point.decompress().ok_or(DKGError::InvaliCoefficientCommitment)?); cursor += COMPRESSED_RISTRETTO_LENGTH; } @@ -435,7 +435,7 @@ impl DKGOutput { .map_err(DKGError::DeserializationError)?; let group_public_key = - compressed_public_key.decompress().ok_or(DKGError::InvalidRistrettoPoint)?; + compressed_public_key.decompress().ok_or(DKGError::InvalidGroupPublicKey)?; cursor += VEC_LENGTH; @@ -445,7 +445,7 @@ impl DKGOutput { let mut identifier_bytes = [0; SCALAR_LENGTH]; identifier_bytes.copy_from_slice(&bytes[cursor..cursor + SCALAR_LENGTH]); let identifier = - scalar_from_canonical_bytes(identifier_bytes).ok_or(DKGError::InvalidScalar)?; + scalar_from_canonical_bytes(identifier_bytes).ok_or(DKGError::InvalidIdentifier)?; cursor += SCALAR_LENGTH; let key_bytes = &bytes[cursor..cursor + PUBLIC_KEY_LENGTH]; From 030ce7a10415c62f07f58497dc2ddb0152f29e05 Mon Sep 17 00:00:00 2001 From: Fiono11 Date: Thu, 9 May 2024 13:08:17 +0100 Subject: [PATCH 26/60] Add polynomial tests --- src/olaf/errors.rs | 2 +- src/olaf/simplpedpop.rs | 2 +- src/olaf/tests.rs | 5 ++- src/olaf/types.rs | 92 +++++++++++++++++++++++++++++++++++++++-- 4 files changed, 95 insertions(+), 6 deletions(-) diff --git a/src/olaf/errors.rs b/src/olaf/errors.rs index b5fd9ec..332f7d1 100644 --- a/src/olaf/errors.rs +++ b/src/olaf/errors.rs @@ -22,7 +22,7 @@ pub enum DKGError { /// Invalid signature. InvalidSignature(SignatureError), /// Invalid coefficient commitment of the polynomial commitment. - InvaliCoefficientCommitment, + InvalidCoefficientCommitment, /// Invalid identifier. InvalidIdentifier, /// Invalid secret share. diff --git a/src/olaf/simplpedpop.rs b/src/olaf/simplpedpop.rs index 21488a2..2ea04ae 100644 --- a/src/olaf/simplpedpop.rs +++ b/src/olaf/simplpedpop.rs @@ -164,7 +164,7 @@ impl Keypair { encryption_transcript.commit_point(b"contributor", content.sender.as_compressed()); encryption_transcript.append_message(b"nonce", &content.encryption_nonce); - if polynomial_commitment.coefficients_commitments.len() != threshold - 1 { + if polynomial_commitment.coefficients_commitments.len() != threshold { return Err(DKGError::IncorrectNumberOfCoefficientCommitments); } diff --git a/src/olaf/tests.rs b/src/olaf/tests.rs index f66adfd..a4b56f8 100644 --- a/src/olaf/tests.rs +++ b/src/olaf/tests.rs @@ -187,7 +187,10 @@ mod tests { Ok(_) => panic!("Expected an error, but got Ok."), Err(e) => match e { DKGError::IncorrectNumberOfCoefficientCommitments => assert!(true), - _ => panic!("Expected DKGError::IncorrectNumberOfCommitments, but got {:?}", e), + _ => panic!( + "Expected DKGError::IncorrectNumberOfCoefficientCommitments, but got {:?}", + e + ), }, } } diff --git a/src/olaf/types.rs b/src/olaf/types.rs index c79d5f8..c5d3e44 100644 --- a/src/olaf/types.rs +++ b/src/olaf/types.rs @@ -110,7 +110,7 @@ pub(super) struct SecretPolynomial { impl SecretPolynomial { pub(super) fn generate(degree: usize, rng: &mut R) -> Self { - let mut coefficients = Vec::with_capacity(degree); + let mut coefficients = Vec::with_capacity(degree + 1); let mut first = Scalar::random(rng); while first == Scalar::ZERO { @@ -118,7 +118,7 @@ impl SecretPolynomial { } coefficients.push(first); - coefficients.extend(iter::repeat_with(|| Scalar::random(rng)).take(degree - 1)); + coefficients.extend(iter::repeat_with(|| Scalar::random(rng)).take(degree)); SecretPolynomial { coefficients } } @@ -318,7 +318,7 @@ impl MessageContent { .map_err(DKGError::DeserializationError)?; coefficients_commitments - .push(point.decompress().ok_or(DKGError::InvaliCoefficientCommitment)?); + .push(point.decompress().ok_or(DKGError::InvalidCoefficientCommitment)?); cursor += COMPRESSED_RISTRETTO_LENGTH; } @@ -617,4 +617,90 @@ mod tests { encrypted_share.decrypt(&key_bytes, &encryption_nonce).unwrap(); } + + #[test] + fn test_generate_polynomial_commitment_valid() { + let degree = 3; + + let polynomial = SecretPolynomial::generate(degree, &mut OsRng); + + 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 = SecretPolynomial { 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"); + } } From 443a40fba05f95892c8a2aa67ba8c9b2f592211d Mon Sep 17 00:00:00 2001 From: Fiono11 Date: Fri, 10 May 2024 10:44:07 +0100 Subject: [PATCH 27/60] Reimplement ephemeral key and proof of possession --- src/olaf/errors.rs | 2 ++ src/olaf/simplpedpop.rs | 65 ++++++++++++++++++++++++++++++++++------- src/olaf/types.rs | 31 ++++++++++++++++++++ 3 files changed, 87 insertions(+), 11 deletions(-) diff --git a/src/olaf/errors.rs b/src/olaf/errors.rs index 332f7d1..2619b14 100644 --- a/src/olaf/errors.rs +++ b/src/olaf/errors.rs @@ -43,4 +43,6 @@ pub enum DKGError { DecryptionError(chacha20poly1305::Error), /// Encryption error when encrypting the secret share. EncryptionError(chacha20poly1305::Error), + /// Invalid Proof of Possession. + InvalidProofOfPossession(SignatureError), } diff --git a/src/olaf/simplpedpop.rs b/src/olaf/simplpedpop.rs index 2ea04ae..37c51b1 100644 --- a/src/olaf/simplpedpop.rs +++ b/src/olaf/simplpedpop.rs @@ -62,15 +62,7 @@ impl Keypair { rng.fill_bytes(&mut encryption_nonce); encryption_transcript.append_message(b"nonce", &encryption_nonce); - let secret = *secret_polynomial - .coefficients - .first() - .expect("This never fails because the minimum threshold is 2"); - - let mut nonce: [u8; 32] = [0u8; 32]; - rng.fill_bytes(&mut nonce); - - let ephemeral_key = SecretKey { key: secret, nonce }; + let ephemeral_key = Keypair::generate(); for i in 0..parameters.participants { let identifier = Identifier::generate(&recipients_hash, i); @@ -81,7 +73,7 @@ impl Keypair { let recipient = recipients[i as usize]; - let key_exchange = ephemeral_key.key * recipient.into_point(); + let key_exchange = ephemeral_key.secret.key * recipient.into_point(); let mut encryption_transcript = encryption_transcript.clone(); encryption_transcript.commit_point(b"recipient", recipient.as_compressed()); @@ -96,6 +88,33 @@ impl Keypair { encrypted_secret_shares.push(encrypted_secret_share); } + let pk = &PublicKey::from_point( + *polynomial_commitment + .coefficients_commitments + .first() + .expect("This never fails because the minimum threshold is 2"), + ); + + let secret = *secret_polynomial + .coefficients + .first() + .expect("This never fails because the minimum threshold is 2"); + + let mut nonce: [u8; 32] = [0u8; 32]; + crate::getrandom_or_panic().fill_bytes(&mut nonce); + + let secret_key = SecretKey { key: secret, nonce }; + + let secret_commitment = polynomial_commitment + .coefficients_commitments + .first() + .expect("This never fails because the minimum threshold is 2"); + + let mut pop_transcript = Transcript::new(b"pop"); + pop_transcript + .append_message(b"secret commitment", secret_commitment.compress().as_bytes()); + let proof_of_possession = secret_key.sign(pop_transcript, pk); + let message_content = MessageContent::new( self.public, encryption_nonce, @@ -103,6 +122,8 @@ impl Keypair { recipients_hash, polynomial_commitment, encrypted_secret_shares, + ephemeral_key.public, + proof_of_possession, ); let mut signature_transcript = Transcript::new(b"signature"); @@ -138,6 +159,9 @@ impl Keypair { let mut total_polynomial_commitment = PolynomialCommitment { coefficients_commitments: vec![] }; let mut identifiers = Vec::new(); + let mut proofs_of_possession = Vec::with_capacity(participants); + let mut pops_transcripts = Vec::with_capacity(participants); + let mut public_keys = Vec::with_capacity(participants); for (j, message) in messages.iter().enumerate() { if &message.content.parameters != parameters { @@ -156,6 +180,15 @@ impl Keypair { .first() .expect("This never fails because the minimum threshold is 2"); + let public_key = PublicKey::from_point( + *polynomial_commitment + .coefficients_commitments + .first() + .expect("This never fails because the minimum threshold is 2"), + ); + public_keys.push(public_key); + proofs_of_possession.push(content.proof_of_possession); + senders.push(content.sender); signatures.push(message.signature); @@ -176,12 +209,19 @@ impl Keypair { signature_transcript.append_message(b"message", &content.to_bytes()); signatures_transcripts.push(signature_transcript); + let mut pop_transcript = Transcript::new(b"pop"); + + pop_transcript + .append_message(b"secret commitment", secret_commitment.compress().as_bytes()); + + pops_transcripts.push(pop_transcript); + total_polynomial_commitment = PolynomialCommitment::sum_polynomial_commitments(&[ &total_polynomial_commitment, &polynomial_commitment, ]); - let key_exchange = self.secret.key * secret_commitment; + let key_exchange = self.secret.key * message.content.ephemeral_key.as_point(); encryption_transcript.commit_point(b"recipient", self.public.as_compressed()); encryption_transcript.commit_point(b"key exchange", &key_exchange.compress()); @@ -220,6 +260,9 @@ impl Keypair { group_point += secret_commitment; } + verify_batch(&mut pops_transcripts, &proofs_of_possession, &public_keys, false) + .map_err(DKGError::InvalidProofOfPossession)?; + verify_batch(&mut signatures_transcripts, &signatures, &senders, false) .map_err(DKGError::InvalidSignature)?; diff --git a/src/olaf/types.rs b/src/olaf/types.rs index c5d3e44..46ded90 100644 --- a/src/olaf/types.rs +++ b/src/olaf/types.rs @@ -1,5 +1,7 @@ //! SimplPedPoP types. +#![allow(clippy::too_many_arguments)] + use core::iter; use alloc::vec::Vec; use curve25519_dalek::{ristretto::CompressedRistretto, traits::Identity, RistrettoPoint, Scalar}; @@ -234,6 +236,8 @@ pub struct MessageContent { pub(super) recipients_hash: [u8; RECIPIENTS_HASH_LENGTH], pub(super) polynomial_commitment: PolynomialCommitment, pub(super) encrypted_secret_shares: Vec, + pub(super) ephemeral_key: PublicKey, + pub(super) proof_of_possession: Signature, } impl MessageContent { @@ -245,6 +249,8 @@ impl MessageContent { recipients_hash: [u8; RECIPIENTS_HASH_LENGTH], polynomial_commitment: PolynomialCommitment, encrypted_secret_shares: Vec, + ephemeral_key: PublicKey, + proof_of_possession: Signature, ) -> Self { Self { sender, @@ -253,6 +259,8 @@ impl MessageContent { recipients_hash, polynomial_commitment, encrypted_secret_shares, + ephemeral_key, + proof_of_possession, } } /// Serialize MessageContent @@ -273,6 +281,9 @@ impl MessageContent { bytes.extend(ciphertext.0.clone()); } + bytes.extend(&self.ephemeral_key.to_bytes()); + bytes.extend(&self.proof_of_possession.to_bytes()); + bytes } @@ -333,6 +344,13 @@ impl MessageContent { cursor += CHACHA20POLY1305_LENGTH; } + let ephemeral_key = PublicKey::from_bytes(&bytes[cursor..cursor + PUBLIC_KEY_LENGTH]) + .map_err(DKGError::InvalidPublicKey)?; + cursor += PUBLIC_KEY_LENGTH; + + let proof_of_possession = Signature::from_bytes(&bytes[cursor..cursor + SIGNATURE_LENGTH]) + .map_err(DKGError::InvalidSignature)?; + Ok(MessageContent { sender, encryption_nonce, @@ -340,6 +358,8 @@ impl MessageContent { recipients_hash, polynomial_commitment, encrypted_secret_shares, + ephemeral_key, + proof_of_possession, }) } } @@ -482,6 +502,8 @@ mod tests { EncryptedSecretShare(vec![1; CHACHA20POLY1305_LENGTH]), ]; let signature = sender.sign(Transcript::new(b"sig")); + let proof_of_possession = sender.sign(Transcript::new(b"pop")); + let ephemeral_key = PublicKey::from_point(RistrettoPoint::random(&mut OsRng)); let message_content = MessageContent::new( sender.public, @@ -490,6 +512,8 @@ mod tests { recipients_hash, polynomial_commitment, encrypted_secret_shares, + ephemeral_key, + proof_of_possession, ); let message = AllMessage::new(message_content, signature); @@ -535,6 +559,13 @@ mod tests { .zip(deserialized_message.content.encrypted_secret_shares.iter()) .all(|(a, b)| a.0 == b.0)); + assert_eq!(message.content.ephemeral_key, deserialized_message.content.ephemeral_key); + + assert_eq!( + message.content.proof_of_possession, + deserialized_message.content.proof_of_possession + ); + assert_eq!(message.signature, deserialized_message.signature); } From e1c24d1394bb2ee284427f56d06138ef9400abd9 Mon Sep 17 00:00:00 2001 From: Fiono11 Date: Thu, 9 May 2024 18:34:29 +0100 Subject: [PATCH 28/60] Implementation of FROST --- src/olaf/frost/errors.rs | 283 ++++++++++++++++++-- src/olaf/frost/mod.rs | 408 +++++++++++++++++++++++++++- src/olaf/frost/tests.rs | 333 ----------------------- src/olaf/frost/types.rs | 344 ++++++++++++++++++++++++ src/olaf/mod.rs | 51 +++- src/olaf/simplpedpop/errors.rs | 281 ++++++++++++++++++- src/olaf/simplpedpop/mod.rs | 475 ++++++++++++++++++++++++++++++--- src/olaf/simplpedpop/tests.rs | 338 ----------------------- src/olaf/simplpedpop/types.rs | 371 ++++++++++++++----------- 9 files changed, 1979 insertions(+), 905 deletions(-) delete mode 100644 src/olaf/frost/tests.rs delete mode 100644 src/olaf/simplpedpop/tests.rs diff --git a/src/olaf/frost/errors.rs b/src/olaf/frost/errors.rs index 4013d25..107431e 100644 --- a/src/olaf/frost/errors.rs +++ b/src/olaf/frost/errors.rs @@ -1,25 +1,276 @@ //! Errors of the FROST protocol. -/// A result for the FROST protocol. +use crate::SignatureError; + +/// A result for the SimplPedPoP protocol. pub type FROSTResult = Result; -/// An error ocurred during the execution of the FROST protocol -#[derive(Debug, Clone, Eq, PartialEq)] +/// An error ocurred during the execution of the SimplPedPoP protocol. +#[derive(Debug)] pub enum FROSTError { - /// Incorrect number of signing commitments. + /// Invalid Proof of Possession. + InvalidProofOfPossession(SignatureError), + /// The number of signing commitments must be at least equal to the threshold. + InvalidNumberOfSigningCommitments, + /// The number of signing commitments must be equal to the number of signature shares. IncorrectNumberOfSigningCommitments, - /// The participant's signing commitment is missing from the Signing Package - MissingSigningCommitment, - /// The participant's signing commitment is incorrect - IncorrectSigningCommitment, - /// This identifier does not belong to a participant in the signing process. - UnknownIdentifier, + /// The participant's signing commitment is missing. + MissingOwnSigningCommitment, /// Commitment equals the identity IdentitySigningCommitment, - /// Incorrect number of identifiers. - IncorrectNumberOfIdentifiers, - /// Signature verification failed. - InvalidSignature, - /// This identifier is duplicated. - DuplicatedIdentifier, + /// The number of veriyfing shares must be equal to the number of participants. + IncorrectNumberOfVerifyingShares, + /// The identifiers of the SimplPedPoP protocol must be the same of the FROST protocol. + InvalidIdentifier, + /// The output of the SimplPedPoP protocol must contain the participant's verifying share. + InvalidOwnVerifyingShare, + /// Invalid signature. + InvalidSignature(SignatureError), +} + +#[cfg(test)] +mod tests { + use alloc::vec::Vec; + use curve25519_dalek::{traits::Identity, RistrettoPoint}; + use rand_core::OsRng; + use crate::{ + olaf::{ + frost::types::{NonceCommitment, SigningCommitments}, + simplpedpop::{AllMessage, Parameters}, + }, + Keypair, PublicKey, + }; + use super::FROSTError; + + #[test] + fn test_incorrect_number_of_verifying_shares_error() { + let parameters = Parameters::generate(2, 2); + let participants = parameters.participants as usize; + let threshold = parameters.threshold as usize; + + let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public).collect(); + + let mut all_messages = Vec::new(); + for i in 0..participants { + let message: AllMessage = keypairs[i] + .simplpedpop_contribute_all(threshold as u16, public_keys.clone()) + .unwrap(); + all_messages.push(message); + } + + let mut dkg_outputs = Vec::new(); + + for kp in keypairs.iter() { + let dkg_output = kp.simplpedpop_recipient_all(&all_messages).unwrap(); + dkg_outputs.push(dkg_output); + } + + let mut all_signing_commitments = Vec::new(); + let mut all_signing_nonces = Vec::new(); + + for dkg_output in &dkg_outputs { + let (signing_nonces, signing_commitments) = dkg_output.1.commit(&mut OsRng); + all_signing_nonces.push(signing_nonces); + all_signing_commitments.push(signing_commitments); + } + + let message = b"message"; + let context = b"context"; + + dkg_outputs[0].0.dkg_output.verifying_keys.pop(); + + let result = dkg_outputs[0].1.sign( + context, + message, + &dkg_outputs[0].0.dkg_output, + &all_signing_commitments, + &all_signing_nonces[0], + ); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + FROSTError::IncorrectNumberOfVerifyingShares => assert!(true), + _ => { + panic!("Expected FROSTError::IncorrectNumberOfVerifyingShares, but got {:?}", e) + }, + }, + } + } + + #[test] + fn test_missing_own_signing_commitment_error() { + let parameters = Parameters::generate(2, 2); + let participants = parameters.participants as usize; + let threshold = parameters.threshold as usize; + + let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public).collect(); + + let mut all_messages = Vec::new(); + for i in 0..participants { + let message: AllMessage = keypairs[i] + .simplpedpop_contribute_all(threshold as u16, public_keys.clone()) + .unwrap(); + all_messages.push(message); + } + + let mut dkg_outputs = Vec::new(); + + for kp in keypairs.iter() { + let dkg_output = kp.simplpedpop_recipient_all(&all_messages).unwrap(); + dkg_outputs.push(dkg_output); + } + + let mut all_signing_commitments = Vec::new(); + let mut all_signing_nonces = Vec::new(); + + for dkg_output in &dkg_outputs { + let (signing_nonces, signing_commitments) = dkg_output.1.commit(&mut OsRng); + all_signing_nonces.push(signing_nonces); + all_signing_commitments.push(signing_commitments); + } + + let message = b"message"; + let context = b"context"; + + all_signing_commitments[0] = SigningCommitments { + hiding: NonceCommitment(RistrettoPoint::random(&mut OsRng)), + binding: NonceCommitment(RistrettoPoint::random(&mut OsRng)), + //identifier: Identifier(Scalar::random(&mut OsRng)), + }; + + let result = dkg_outputs[0].1.sign( + context, + message, + &dkg_outputs[0].0.dkg_output, + &all_signing_commitments, + &all_signing_nonces[0], + ); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + FROSTError::MissingOwnSigningCommitment => assert!(true), + _ => { + panic!("Expected FROSTError::MissingOwnSigningCommitment, but got {:?}", e) + }, + }, + } + } + + #[test] + fn test_identity_signing_commitment_error() { + let parameters = Parameters::generate(2, 2); + let participants = parameters.participants as usize; + let threshold = parameters.threshold as usize; + + let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public).collect(); + + let mut all_messages = Vec::new(); + for i in 0..participants { + let message: AllMessage = keypairs[i] + .simplpedpop_contribute_all(threshold as u16, public_keys.clone()) + .unwrap(); + all_messages.push(message); + } + + let mut dkg_outputs = Vec::new(); + + for kp in keypairs.iter() { + let dkg_output = kp.simplpedpop_recipient_all(&all_messages).unwrap(); + dkg_outputs.push(dkg_output); + } + + let mut all_signing_commitments = Vec::new(); + let mut all_signing_nonces = Vec::new(); + + for dkg_output in &dkg_outputs { + let (signing_nonces, signing_commitments) = dkg_output.1.commit(&mut OsRng); + all_signing_nonces.push(signing_nonces); + all_signing_commitments.push(signing_commitments); + } + + let message = b"message"; + let context = b"context"; + + all_signing_commitments[1].hiding = NonceCommitment(RistrettoPoint::identity()); + let result = dkg_outputs[0].1.sign( + context, + message, + &dkg_outputs[0].0.dkg_output, + &all_signing_commitments, + &all_signing_nonces[0], + ); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + FROSTError::IdentitySigningCommitment => assert!(true), + _ => { + panic!("Expected FROSTError::IdentitySigningCommitment, but got {:?}", e) + }, + }, + } + } + + #[test] + fn test_incorrect_number_of_signing_commitments_error() { + let parameters = Parameters::generate(2, 2); + let participants = parameters.participants as usize; + let threshold = parameters.threshold as usize; + + let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public).collect(); + + let mut all_messages = Vec::new(); + for i in 0..participants { + let message: AllMessage = keypairs[i] + .simplpedpop_contribute_all(threshold as u16, public_keys.clone()) + .unwrap(); + all_messages.push(message); + } + + let mut dkg_outputs = Vec::new(); + + for kp in keypairs.iter() { + let dkg_output = kp.simplpedpop_recipient_all(&all_messages).unwrap(); + dkg_outputs.push(dkg_output); + } + + let mut all_signing_commitments = Vec::new(); + let mut all_signing_nonces = Vec::new(); + + for dkg_output in &dkg_outputs { + let (signing_nonces, signing_commitments) = dkg_output.1.commit(&mut OsRng); + all_signing_nonces.push(signing_nonces); + all_signing_commitments.push(signing_commitments); + } + + let message = b"message"; + let context = b"context"; + + let result = dkg_outputs[0].1.sign( + context, + message, + &dkg_outputs[0].0.dkg_output, + &all_signing_commitments[..1], + &all_signing_nonces[0], + ); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + FROSTError::InvalidNumberOfSigningCommitments => assert!(true), + _ => { + panic!( + "Expected FROSTError::IncorrectNumberOfSigningCommitments, but got {:?}", + e + ) + }, + }, + } + } } diff --git a/src/olaf/frost/mod.rs b/src/olaf/frost/mod.rs index 18f36ac..d242224 100644 --- a/src/olaf/frost/mod.rs +++ b/src/olaf/frost/mod.rs @@ -1,12 +1,26 @@ //! Implementation of the FROST protocol (). -use super::SigningShare; +#![allow(non_snake_case)] -mod errors; mod types; -mod tests; +mod errors; + +use alloc::vec::Vec; +use curve25519_dalek::Scalar; +use rand_core::{CryptoRng, RngCore}; +use crate::Signature; +use self::{ + errors::{FROSTError, FROSTResult}, + types::{ + challenge, compute_binding_factor_list, compute_group_commitment, + derive_interpolating_value, BindingFactor, BindingFactorList, Challenge, SignatureShare, + SigningCommitments, SigningNonces, + }, +}; + +use super::{simplpedpop::DKGOutput, GroupPublicKey, SigningKeyPair, VerifyingShare}; -impl SigningShare { +impl SigningKeyPair { /// Done once by each participant, to generate _their_ nonces and commitments /// that are then used during signing. /// @@ -17,16 +31,20 @@ impl SigningShare { /// perform the first round. Batching entails generating more than one /// nonce/commitment pair at a time. Nonces should be stored in secret storage /// for later use, whereas the commitments are published. - pub fn preprocess(&self, num_nonces: u8) -> (Vec, Vec) { - let mut rng = crate::getrandom_or_panic(); - + pub fn preprocess( + &self, + num_nonces: u8, + rng: &mut R, + ) -> (Vec, Vec) + where + R: CryptoRng + RngCore, + { let mut signing_nonces: Vec = Vec::with_capacity(num_nonces as usize); - let mut signing_commitments: Vec = Vec::with_capacity(num_nonces as usize); for _ in 0..num_nonces { - let nonces = SigningNonces::new(&self.secret.key, &mut rng); + let nonces = SigningNonces::new(&self.0.secret, rng); signing_commitments.push(SigningCommitments::from(&nonces)); signing_nonces.push(nonces); } @@ -42,11 +60,379 @@ impl SigningShare { /// operation. /// /// [`commit`]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#name-round-one-commitment. - pub fn commit(&self) -> (SigningNonces, SigningCommitments) { - let (mut vec_signing_nonces, mut vec_signing_commitments) = self.preprocess(1); + pub fn commit(&self, rng: &mut R) -> (SigningNonces, SigningCommitments) + where + R: CryptoRng + RngCore, + { + let (mut vec_signing_nonces, mut vec_signing_commitments) = self.preprocess(1, rng); ( vec_signing_nonces.pop().expect("must have 1 element"), vec_signing_commitments.pop().expect("must have 1 element"), ) } + + /// Performed once by each participant selected for the signing operation. + /// + /// Implements [`sign`] from the spec. + /// + /// Receives the message to be signed and a set of signing commitments and a set + /// of randomizing commitments to be used in that signing operation, including + /// that for this participant. + /// + /// Assumes the participant has already determined which nonce corresponds with + /// the commitment that was assigned by the coordinator in the SigningPackage. + /// + /// [`sign`]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#name-round-two-signature-share-g + pub fn sign( + &self, + context: &[u8], + message: &[u8], + dkg_output: &DKGOutput, + all_signing_commitments: &[SigningCommitments], + signer_nonces: &SigningNonces, + ) -> FROSTResult { + if dkg_output.verifying_keys.len() != dkg_output.parameters.participants as usize { + return Err(FROSTError::IncorrectNumberOfVerifyingShares); + } + + if !all_signing_commitments.contains(&signer_nonces.commitments) { + return Err(FROSTError::MissingOwnSigningCommitment); + } + + let mut identifiers = Vec::new(); + let mut shares = Vec::new(); + + let mut index = 0; + + let own_verifying_share = VerifyingShare(self.0.public); + + for (i, (identifier, share)) in dkg_output.verifying_keys.iter().enumerate() { + identifiers.push(identifier); + shares.push(share); + + if share == &own_verifying_share { + index = i; + } + } + + if !shares.contains(&&own_verifying_share) { + return Err(FROSTError::InvalidOwnVerifyingShare); + } + + if all_signing_commitments.len() < dkg_output.parameters.threshold as usize { + return Err(FROSTError::InvalidNumberOfSigningCommitments); + } + + let binding_factor_list: BindingFactorList = compute_binding_factor_list( + all_signing_commitments, + &dkg_output.group_public_key, + message, + ); + + let group_commitment = + compute_group_commitment(all_signing_commitments, &binding_factor_list)?; + + let lambda_i = derive_interpolating_value( + identifiers[index], + dkg_output.verifying_keys.iter().map(|x| x.0).collect(), + )?; + + let challenge = + challenge(&group_commitment.0, &dkg_output.group_public_key, context, message); + + let signature_share = self.compute_signature_share( + signer_nonces, + &binding_factor_list.0[index].1, + &lambda_i, + &challenge, + ); + + Ok(signature_share) + } + + fn compute_signature_share( + &self, + signer_nonces: &SigningNonces, + binding_factor: &BindingFactor, + lambda_i: &Scalar, + challenge: &Challenge, + ) -> SignatureShare { + let z_share: Scalar = signer_nonces.hiding.0 + + (signer_nonces.binding.0 * binding_factor.0) + + (lambda_i * self.0.secret.key * challenge); + + SignatureShare { share: z_share } + } +} + +pub fn aggregate( + message: &[u8], + context: &[u8], + signing_commitments: &[SigningCommitments], + signature_shares: &Vec, + group_public_key: GroupPublicKey, +) -> Result { + if signing_commitments.len() != signature_shares.len() { + return Err(FROSTError::IncorrectNumberOfSigningCommitments); + } + + let binding_factor_list: BindingFactorList = + compute_binding_factor_list(signing_commitments, &group_public_key, message); + + let group_commitment = compute_group_commitment(signing_commitments, &binding_factor_list)?; + + let mut s = Scalar::ZERO; + + for signature_share in signature_shares { + s += signature_share.share; + } + + let signature = Signature { R: group_commitment.0.compress(), s }; + + group_public_key + .0 + .verify_simple(context, message, &signature) + .map_err(FROSTError::InvalidSignature)?; + + Ok(signature) +} + +#[cfg(test)] +mod tests { + use alloc::vec::Vec; + use rand::Rng; + use rand_core::OsRng; + use crate::{ + olaf::{ + simplpedpop::{AllMessage, Parameters}, + MINIMUM_THRESHOLD, + }, + Keypair, PublicKey, + }; + use super::{ + aggregate, + types::{SigningCommitments, SigningNonces}, + }; + + const MAXIMUM_PARTICIPANTS: u16 = 2; + const MINIMUM_PARTICIPANTS: u16 = 2; + const NONCES: u8 = 10; + + fn generate_parameters() -> Parameters { + let mut rng = rand::thread_rng(); + let participants = rng.gen_range(MINIMUM_PARTICIPANTS..=MAXIMUM_PARTICIPANTS); + let threshold = rng.gen_range(MINIMUM_THRESHOLD..=participants); + + Parameters { participants, threshold } + } + + #[test] + fn test_n_of_n_frost_with_simplpedpop() { + let parameters = generate_parameters(); + let participants = parameters.participants as usize; + let threshold = parameters.threshold as usize; + + let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public).collect(); + + let mut all_messages = Vec::new(); + for i in 0..participants { + let message: AllMessage = keypairs[i] + .simplpedpop_contribute_all(threshold as u16, public_keys.clone()) + .unwrap(); + all_messages.push(message); + } + + let mut dkg_outputs = Vec::new(); + + for kp in keypairs.iter() { + let dkg_output = kp.simplpedpop_recipient_all(&all_messages).unwrap(); + dkg_outputs.push(dkg_output); + } + + let mut all_signing_commitments = Vec::new(); + let mut all_signing_nonces = Vec::new(); + + for dkg_output in &dkg_outputs { + let (signing_nonces, signing_commitments) = dkg_output.1.commit(&mut OsRng); + all_signing_nonces.push(signing_nonces); + all_signing_commitments.push(signing_commitments); + } + + let mut signature_shares = Vec::new(); + + let message = b"message"; + let context = b"context"; + + for (i, dkg_output) in dkg_outputs.iter().enumerate() { + let signature_share = dkg_output + .1 + .sign( + context, + message, + &dkg_output.0.dkg_output, + &all_signing_commitments, + &all_signing_nonces[i], + ) + .unwrap(); + + signature_shares.push(signature_share); + } + + aggregate( + message, + context, + &all_signing_commitments, + &signature_shares, + dkg_outputs[0].0.dkg_output.group_public_key, + ) + .unwrap(); + } + + #[test] + fn test_t_of_n_frost_with_simplpedpop() { + let parameters = generate_parameters(); + let participants = parameters.participants as usize; + let threshold = parameters.threshold as usize; + + let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public).collect(); + + let mut all_messages = Vec::new(); + for i in 0..participants { + let message: AllMessage = keypairs[i] + .simplpedpop_contribute_all(threshold as u16, public_keys.clone()) + .unwrap(); + all_messages.push(message); + } + + let mut dkg_outputs = Vec::new(); + + for kp in keypairs.iter() { + let dkg_output = kp.simplpedpop_recipient_all(&all_messages).unwrap(); + dkg_outputs.push(dkg_output); + } + + let mut all_signing_commitments = Vec::new(); + let mut all_signing_nonces = Vec::new(); + + for dkg_output in &dkg_outputs[..threshold] { + let (signing_nonces, signing_commitments) = dkg_output.1.commit(&mut OsRng); + all_signing_nonces.push(signing_nonces); + all_signing_commitments.push(signing_commitments); + } + + let mut signature_shares = Vec::new(); + + let message = b"message"; + let context = b"context"; + + for (i, dkg_output) in dkg_outputs[..threshold].iter().enumerate() { + let signature_share = dkg_output + .1 + .sign( + context, + message, + &dkg_output.0.dkg_output, + &all_signing_commitments, + &all_signing_nonces[i], + ) + .unwrap(); + + signature_shares.push(signature_share); + } + + aggregate( + message, + context, + &all_signing_commitments, + &signature_shares, + dkg_outputs[0].0.dkg_output.group_public_key, + ) + .unwrap(); + } + + #[test] + fn test_preprocessing_frost_with_simplpedpop() { + let parameters = generate_parameters(); + let participants = parameters.participants as usize; + let threshold = parameters.threshold as usize; + + let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public).collect(); + + let mut all_messages = Vec::new(); + for i in 0..participants { + let message: AllMessage = keypairs[i] + .simplpedpop_contribute_all(threshold as u16, public_keys.clone()) + .unwrap(); + all_messages.push(message); + } + + let mut dkg_outputs = Vec::new(); + + for kp in &keypairs { + let dkg_output = kp.simplpedpop_recipient_all(&all_messages).unwrap(); + dkg_outputs.push(dkg_output); + } + + let group_public_key = dkg_outputs[0].0.dkg_output.group_public_key; + + let mut all_nonces_map: Vec> = Vec::new(); + let mut all_commitments_map: Vec> = Vec::new(); + + for dkg_output in &dkg_outputs { + let (nonces, commitments) = dkg_output.1.preprocess(NONCES, &mut OsRng); + + all_nonces_map.push(nonces); + all_commitments_map.push(commitments); + } + + let mut nonces: Vec<&SigningNonces> = Vec::new(); + let mut commitments: Vec> = Vec::new(); + + for i in 0..NONCES { + let mut comms = Vec::new(); + + for (j, _) in dkg_outputs.iter().enumerate() { + nonces.push(&all_nonces_map[j][i as usize]); + comms.push(all_commitments_map[j][i as usize].clone()) + } + commitments.push(comms); + } + + let mut signature_shares = Vec::new(); + + let mut messages = Vec::new(); + + for i in 0..NONCES { + let mut message = b"message".to_vec(); + message.extend_from_slice(&i.to_be_bytes()); + messages.push(message); + } + + let context = b"context"; + + for i in 0..NONCES { + let message = &messages[i as usize]; + + let commitments: Vec = commitments[i as usize].clone(); + + for (j, dkg_output) in dkg_outputs.iter().enumerate() { + let nonces_to_use = &all_nonces_map[j][i as usize]; + + let signature_share = dkg_output + .1 + .sign(context, &message, &dkg_output.0.dkg_output, &commitments, nonces_to_use) + .unwrap(); + + signature_shares.push(signature_share); + } + + aggregate(&message, context, &commitments, &signature_shares, group_public_key) + .unwrap(); + + signature_shares = Vec::new(); + } + } } diff --git a/src/olaf/frost/tests.rs b/src/olaf/frost/tests.rs deleted file mode 100644 index 211b29b..0000000 --- a/src/olaf/frost/tests.rs +++ /dev/null @@ -1,333 +0,0 @@ -#[cfg(test)] -mod tests { - use crate::{ - olaf::{ - frost::{ - aggregate, - data_structures::{ - KeyPackage, PublicKeyPackage, SignatureShare, SigningCommitments, - SigningNonces, SigningPackage, - }, - errors::FROSTError, - verify_signature, Identifier, - }, - GroupPublicKey, - }, - Keypair, Signature, - }; - use alloc::{collections::BTreeMap, vec::Vec}; - use curve25519_dalek::Scalar; - use rand_core::{CryptoRng, RngCore}; - - /// Test FROST signing with the given shares. - fn check_sign( - min_signers: u16, - key_packages: BTreeMap, - mut rng: R, - pubkey_package: PublicKeyPackage, - ) -> Result<(Vec, Signature, GroupPublicKey), FROSTError> { - let mut nonces_map: BTreeMap = BTreeMap::new(); - let mut commitments_map: BTreeMap = BTreeMap::new(); - - //////////////////////////////////////////////////////////////////////////// - // Round 1: generating nonces and signing commitments for each participant - //////////////////////////////////////////////////////////////////////////// - - for participant_identifier in key_packages.keys().take(min_signers as usize).cloned() { - // Generate one (1) nonce and one SigningCommitments instance for each - // participant, up to _min_signers_. - let sk = key_packages.get(&participant_identifier).unwrap().signing_share.clone(); - let keypair = Keypair::from(sk); - - let (nonces, commitments) = keypair.commit(); - nonces_map.insert(participant_identifier, nonces); - commitments_map.insert(participant_identifier, commitments); - } - - // This is what the signature aggregator / coordinator needs to do: - // - decide what message to sign - // - take one (unused) commitment per signing participant - let mut signature_shares = BTreeMap::new(); - let message = b"message to sign"; - let signing_package = SigningPackage::new(commitments_map, message); - - //////////////////////////////////////////////////////////////////////////// - // Round 2: each participant generates their signature share - //////////////////////////////////////////////////////////////////////////// - - for participant_identifier in nonces_map.keys() { - let key_package = key_packages.get(participant_identifier).unwrap(); - - let nonces_to_use = nonces_map.get(participant_identifier).unwrap(); - - check_sign_errors(signing_package.clone(), nonces_to_use.clone(), key_package.clone()); - - let sk = key_package.signing_share.clone(); - let keypair = Keypair::from(sk); - - // Each participant generates their signature share. - let signature_share = keypair.sign_frost( - &signing_package, - nonces_to_use, - key_package.verifying_key, - key_package.identifier, - key_package.min_signers, - )?; - signature_shares.insert(*participant_identifier, signature_share); - } - - //////////////////////////////////////////////////////////////////////////// - // Aggregation: collects the signing shares from all participants, - // generates the final signature. - //////////////////////////////////////////////////////////////////////////// - - #[cfg(not(feature = "cheater-detection"))] - let pubkey_package = PublicKeyPackage { - verifying_shares: BTreeMap::new(), - verifying_key: pubkey_package.verifying_key, - }; - - check_aggregate_errors( - signing_package.clone(), - signature_shares.clone(), - pubkey_package.clone(), - ); - - // Aggregate (also verifies the signature shares) - let group_signature = aggregate(&signing_package, &signature_shares, &pubkey_package)?; - - // Check that the threshold signature can be verified by the group public - // key (the verification key). - verify_signature(b"message to sign", &group_signature, &pubkey_package.verifying_key)?; - - // Check that the threshold signature can be verified by the group public - // key (the verification key) from KeyPackage.group_public_key - for (participant_identifier, _) in nonces_map.clone() { - let key_package = key_packages.get(&participant_identifier).unwrap(); - - verify_signature(b"message to sign", &group_signature, &key_package.verifying_key)?; - } - - Ok((message.to_vec(), group_signature, pubkey_package.verifying_key)) - } - - /// Test FROST signing with the given shares. - fn check_sign_preprocessing( - min_signers: u16, - key_packages: BTreeMap, - mut rng: R, - pubkey_package: PublicKeyPackage, - num_nonces: u8, - ) -> Result<(Vec>, Vec, GroupPublicKey), FROSTError> { - let mut nonces_map_vec: Vec> = Vec::new(); - let mut commitments_map_vec: Vec> = Vec::new(); - - //////////////////////////////////////////////////////////////////////////// - // Round 1: Generating nonces and signing commitments for each participant - //////////////////////////////////////////////////////////////////////////// - - // First, iterate to gather all nonces and commitments - let mut all_nonces_map: BTreeMap> = BTreeMap::new(); - let mut all_commitments_map: BTreeMap> = - BTreeMap::new(); - - for participant_identifier in key_packages.keys().take(min_signers as usize) { - let signing_share = - key_packages.get(&participant_identifier).unwrap().signing_share.clone(); - let keypair = Keypair::from(signing_share); - let (nonces, commitments) = keypair.preprocess(num_nonces); - - all_nonces_map.insert(participant_identifier.clone(), nonces); - all_commitments_map.insert(*participant_identifier, commitments); - } - - // Now distribute these nonces and commitments to individual participant maps - let mut nonces_map: BTreeMap = BTreeMap::new(); - let mut commitments_map: BTreeMap = BTreeMap::new(); - - for (id, nonces) in &all_nonces_map { - for nonce in nonces { - nonces_map.insert(id.clone(), nonce.clone()); - } - } - - for (id, commitments) in &all_commitments_map { - for commitment in commitments { - commitments_map.insert(id.clone(), commitment.clone()); - } - } - - nonces_map_vec.push(nonces_map); - commitments_map_vec.push(commitments_map); - - // This is what the signature aggregator / coordinator needs to do: - // - decide what message to sign - // - take one (unused) commitment per signing participant - let mut signature_shares = BTreeMap::new(); - - let mut messages = Vec::new(); - - for i in 0..num_nonces { - let mut message = b"message to sign".to_vec(); - message.extend_from_slice(&i.to_be_bytes()); - messages.push(message); - } - - //////////////////////////////////////////////////////////////////////////// - // Round 2: each participant generates their signature share - //////////////////////////////////////////////////////////////////////////// - - let mut signing_packages = Vec::new(); - let mut group_signatures = Vec::new(); - - for i in 0..commitments_map_vec.len() { - let message = &messages[i as usize]; - - let signing_package = - SigningPackage::new(commitments_map_vec[i as usize].clone(), message); - - signing_packages.push(signing_package.clone()); - - for participant_identifier in nonces_map_vec[i as usize].keys() { - let key_package = key_packages.get(participant_identifier).unwrap(); - - let nonces_to_use = nonces_map_vec[i as usize].get(participant_identifier).unwrap(); - - check_sign_errors( - signing_package.clone(), - nonces_to_use.clone(), - key_package.clone(), - ); - - let sk = key_package.signing_share.clone(); - let keypair = Keypair::from(sk); - - // Each participant generates their signature share. - let signature_share = keypair.sign_frost( - &signing_package, - nonces_to_use, - key_package.verifying_key, - key_package.identifier, - key_package.min_signers, - )?; - signature_shares.insert(*participant_identifier, signature_share); - } - - //////////////////////////////////////////////////////////////////////////// - // Aggregation: collects the signing shares from all participants, - // generates the final signature. - //////////////////////////////////////////////////////////////////////////// - - #[cfg(not(feature = "cheater-detection"))] - let pubkey_package = PublicKeyPackage { - verifying_shares: BTreeMap::new(), - verifying_key: pubkey_package.verifying_key, - }; - - check_aggregate_errors( - signing_package.clone(), - signature_shares.clone(), - pubkey_package.clone(), - ); - - // Aggregate (also verifies the signature shares) - let group_signature = aggregate(&signing_package, &signature_shares, &pubkey_package)?; - - group_signatures.push(group_signature); - - // Check that the threshold signature can be verified by the group public key. - verify_signature(message, &group_signature, &pubkey_package.verifying_key)?; - - // Check that the threshold signature can be verified by the group public - // key from KeyPackage.group_public_key - for (participant_identifier, _) in nonces_map_vec[i as usize].clone() { - let key_package = key_packages.get(&participant_identifier).unwrap(); - - verify_signature(message, &group_signature, &key_package.verifying_key)?; - } - } - - Ok((messages, group_signatures, pubkey_package.verifying_key)) - } - - fn check_sign_errors( - signing_package: SigningPackage, - signing_nonces: SigningNonces, - key_package: KeyPackage, - ) { - // Check if passing not enough commitments causes an error - - let mut commitments = signing_package.signing_commitments.clone(); - // Remove one commitment that's not from the key_package owner - let id = *commitments.keys().find(|&&id| id != key_package.identifier).unwrap(); - commitments.remove(&id); - let signing_package = SigningPackage::new(commitments, &signing_package.message); - - let sk = key_package.signing_share.clone(); - let keypair = Keypair::from(sk); - - let r = keypair.sign_frost( - &signing_package, - &signing_nonces, - key_package.verifying_key, - key_package.identifier, - key_package.min_signers, - ); - - assert_eq!(r, Err(FROSTError::IncorrectNumberOfSigningCommitments)); - } - - fn check_aggregate_errors( - signing_package: SigningPackage, - signature_shares: BTreeMap, - pubkey_package: PublicKeyPackage, - ) { - #[cfg(feature = "cheater-detection")] - check_aggregate_corrupted_share( - signing_package.clone(), - signature_shares.clone(), - pubkey_package.clone(), - ); - - check_aggregate_invalid_share_identifier_for_verifying_shares( - signing_package, - signature_shares, - pubkey_package, - ); - } - - #[cfg(feature = "cheater-detection")] - fn check_aggregate_corrupted_share( - signing_package: SigningPackage, - mut signature_shares: BTreeMap, - pubkey_package: PublicKeyPackage, - ) { - let one = Scalar::ONE; - // Corrupt a share - let id = *signature_shares.keys().next().unwrap(); - signature_shares.get_mut(&id).unwrap().share = signature_shares[&id].share + one; - let e = aggregate(&signing_package, &signature_shares, &pubkey_package).unwrap_err(); - assert_eq!(e, FROSTError::InvalidSignatureShare { culprit: id }); - } - - /// Test NCC-E008263-4VP audit finding (PublicKeyPackage). - /// Note that the SigningPackage part of the finding is not currently reachable - /// since it's caught by `compute_lagrange_coefficient()`, and the Binding Factor - /// part can't either since it's caught before by the PublicKeyPackage part. - fn check_aggregate_invalid_share_identifier_for_verifying_shares( - signing_package: SigningPackage, - mut signature_shares: BTreeMap, - pubkey_package: PublicKeyPackage, - ) { - let invalid_identifier = Identifier(Scalar::ZERO); - // Insert a new share (copied from other existing share) with an invalid identifier - signature_shares - .insert(invalid_identifier, signature_shares.values().next().unwrap().clone()); - // Should error, but not panic - aggregate(&signing_package, &signature_shares, &pubkey_package) - .expect_err("should not work"); - } - - #[test] - fn test_n_of_n_frost_with_simplpedpop() {} -} diff --git a/src/olaf/frost/types.rs b/src/olaf/frost/types.rs index 8b13789..8d1ad1f 100644 --- a/src/olaf/frost/types.rs +++ b/src/olaf/frost/types.rs @@ -1 +1,345 @@ +//! Internal types of the FROST protocol. +use alloc::{collections::BTreeSet, vec::Vec}; +use curve25519_dalek::{ + traits::{Identity, VartimeMultiscalarMul}, + RistrettoPoint, Scalar, +}; +use merlin::Transcript; +use rand_core::{CryptoRng, RngCore}; +use zeroize::ZeroizeOnDrop; +use crate::{ + context::{SigningContext, SigningTranscript}, + olaf::{GroupPublicKey, Identifier, GENERATOR}, + SecretKey, +}; +use super::errors::FROSTError; + +/// A participant's signature share, which the coordinator will aggregate with all other signer's +/// shares into the joint signature. +pub struct SignatureShare { + /// This participant's signature over the message. + pub(super) share: Scalar, +} + +pub(super) type Challenge = Scalar; + +/// Generates the challenge as is required for Schnorr signatures. +pub(super) fn challenge( + R: &RistrettoPoint, + verifying_key: &GroupPublicKey, + context: &[u8], + msg: &[u8], +) -> Challenge { + let mut transcript = SigningContext::new(context).bytes(msg); + + transcript.proto_name(b"Schnorr-sig"); + transcript.commit_point(b"sign:pk", verifying_key.0.as_compressed()); + transcript.commit_point(b"sign:R", &R.compress()); + transcript.challenge_scalar(b"sign:c") +} + +/// The binding factor, also known as _rho_ (ρ), ensures each signature share is strongly bound to a signing set, specific set +/// of commitments, and a specific message. +#[derive(Clone, PartialEq, Eq, Debug)] +pub(super) struct BindingFactor(pub(super) Scalar); + +/// A list of binding factors and their associated identifiers. +#[derive(Clone, Debug)] +//pub(super) struct BindingFactorList(pub(super) Vec); +pub(super) struct BindingFactorList(pub(super) Vec<(u16, BindingFactor)>); + +impl BindingFactorList { + /// Create a new [`BindingFactorList`] from a map of identifiers to binding factors. + pub(super) fn new(binding_factors: Vec<(u16, BindingFactor)>) -> Self { + Self(binding_factors) + } +} + +/// A scalar that is a signing nonce. +#[derive(ZeroizeOnDrop)] +pub(super) struct Nonce(pub(super) Scalar); + +impl Nonce { + /// Generates a new uniformly random signing nonce by sourcing fresh randomness and combining + /// with the secret signing share, to hedge against a bad RNG. + /// + /// Each participant generates signing nonces before performing a signing + /// operation. + /// + /// An implementation of `nonce_generate(secret)` from the [spec]. + /// + /// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#name-nonce-generation + pub(super) fn new(secret: &SecretKey, rng: &mut R) -> Self + where + R: CryptoRng + RngCore, + { + let mut random_bytes = [0; 32]; + rng.fill_bytes(&mut random_bytes[..]); + + Self::nonce_generate_from_random_bytes(secret, &random_bytes[..]) + } + + /// Generates a nonce from the given random bytes. + /// This function allows testing and MUST NOT be made public. + pub(super) fn nonce_generate_from_random_bytes( + secret: &SecretKey, + random_bytes: &[u8], + ) -> Self { + let mut transcript = Transcript::new(b"nonce_generate_from_random_bytes"); + + transcript.append_message(b"random bytes", random_bytes); + transcript.append_message(b"secret", secret.key.as_bytes()); + + Self(transcript.challenge_scalar(b"nonce")) + } +} + +/// A group element that is a commitment to a signing nonce share. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(super) struct NonceCommitment(pub(super) RistrettoPoint); + +impl From for NonceCommitment { + fn from(nonce: Nonce) -> Self { + From::from(&nonce) + } +} + +impl From<&Nonce> for NonceCommitment { + fn from(nonce: &Nonce) -> Self { + Self(GENERATOR * nonce.0) + } +} + +/// Comprised of hiding and binding nonces. +/// +/// Note that [`SigningNonces`] must be used *only once* for a signing +/// operation; re-using nonces will result in leakage of a signer's long-lived +/// signing key. +#[derive(ZeroizeOnDrop)] +pub struct SigningNonces { + pub(super) hiding: Nonce, + pub(super) binding: Nonce, + // The commitments to the nonces. This is precomputed to improve + // sign() performance, since it needs to check if the commitments + // to the participant's nonces are included in the commitments sent + // by the Coordinator, and this prevents having to recompute them. + #[zeroize(skip)] + pub(super) commitments: SigningCommitments, +} + +impl SigningNonces { + /// Generates a new signing nonce. + /// + /// Each participant generates signing nonces before performing a signing + /// operation. + pub(super) fn new(secret: &SecretKey, rng: &mut R) -> Self + where + R: CryptoRng + RngCore, + { + let hiding = Nonce::new(secret, rng); + let binding = Nonce::new(secret, rng); + + Self::from_nonces(hiding, binding) + } + + /// Generates a new [`SigningNonces`] from a pair of [`Nonce`]. + /// + /// # Security + /// + /// SigningNonces MUST NOT be repeated in different FROST signings. + /// Thus, if you're using this method (because e.g. you're writing it + /// to disk between rounds), be careful so that does not happen. + pub(super) fn from_nonces(hiding: Nonce, binding: Nonce) -> Self { + let hiding_commitment = (&hiding).into(); + let binding_commitment = (&binding).into(); + let commitments = SigningCommitments::new(hiding_commitment, binding_commitment); + + Self { hiding, binding, commitments } + } +} + +/// Published by each participant in the first round of the signing protocol. +/// +/// This step can be batched if desired by the implementation. Each +/// SigningCommitment can be used for exactly *one* signature. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub struct SigningCommitments { + /// Commitment to the hiding [`Nonce`]. + pub(super) hiding: NonceCommitment, + /// Commitment to the binding [`Nonce`]. + pub(super) binding: NonceCommitment, + //pub(super) identifier: Identifier, +} + +impl SigningCommitments { + /// Create new SigningCommitments + pub(super) fn new(hiding: NonceCommitment, binding: NonceCommitment) -> Self { + Self { hiding, binding } + } +} + +impl From<&SigningNonces> for SigningCommitments { + fn from(nonces: &SigningNonces) -> Self { + nonces.commitments + } +} + +/// One signer's share of the group commitment, derived from their individual signing commitments +/// and the binding factor _rho_. +#[derive(Clone, Copy, PartialEq)] +pub(super) struct GroupCommitmentShare(pub(super) RistrettoPoint); + +/// The product of all signers' individual commitments, published as part of the +/// final signature. +#[derive(Clone, PartialEq, Eq, Debug)] +pub(super) struct GroupCommitment(pub(super) RistrettoPoint); + +pub(super) fn compute_group_commitment( + signing_commitments: &[SigningCommitments], + binding_factor_list: &BindingFactorList, +) -> Result { + let identity = RistrettoPoint::identity(); + + let mut group_commitment = RistrettoPoint::identity(); + + // Number of signing participants we are iterating over. + let signers = signing_commitments.len(); + + let mut binding_scalars = Vec::with_capacity(signers); + + let mut binding_elements = Vec::with_capacity(signers); + + for (i, commitment) in signing_commitments.iter().enumerate() { + // The following check prevents a party from accidentally revealing their share. + // Note that the '&&' operator would be sufficient. + if identity == commitment.binding.0 || identity == commitment.hiding.0 { + return Err(FROSTError::IdentitySigningCommitment); + } + + let binding_factor = &binding_factor_list.0[i]; + + // Collect the binding commitments and their binding factors for one big + // multiscalar multiplication at the end. + binding_elements.push(commitment.binding.0); + binding_scalars.push(binding_factor.1 .0); + + group_commitment += commitment.hiding.0; + } + + let accumulated_binding_commitment: RistrettoPoint = + RistrettoPoint::vartime_multiscalar_mul(binding_scalars, binding_elements); + + group_commitment += accumulated_binding_commitment; + + Ok(GroupCommitment(group_commitment)) +} + +pub(super) fn compute_binding_factor_list( + signing_commitments: &[SigningCommitments], + verifying_key: &GroupPublicKey, + message: &[u8], +) -> BindingFactorList { + let mut transcripts = binding_factor_transcripts(signing_commitments, verifying_key, message); + + BindingFactorList::new( + transcripts + .iter_mut() + .map(|(identifier, transcript)| { + let binding_factor = transcript.challenge_scalar(b"binding factor"); + + (*identifier, BindingFactor(binding_factor)) + }) + .collect(), + ) +} + +pub(super) fn derive_interpolating_value( + signer_id: &Identifier, + identifiers: BTreeSet, +) -> Result { + compute_lagrange_coefficient(&identifiers, None, *signer_id) +} + +/// Generates a lagrange coefficient. +/// +/// The Lagrange polynomial for a set of points (x_j, y_j) for 0 <= j <= k +/// is ∑_{i=0}^k y_i.ℓ_i(x), where ℓ_i(x) is the Lagrange basis polynomial: +/// +/// ℓ_i(x) = ∏_{0≤j≤k; j≠i} (x - x_j) / (x_i - x_j). +/// +/// This computes ℓ_j(x) for the set of points `xs` and for the j corresponding +/// to the given xj. +/// +/// If `x` is None, it uses 0 for it (since Identifiers can't be 0). +pub(super) fn compute_lagrange_coefficient( + x_set: &BTreeSet, + x: Option, + x_i: Identifier, +) -> Result { + let mut num = Scalar::ONE; + let mut den = Scalar::ONE; + + for x_j in x_set.iter() { + if x_i == *x_j { + continue; + } + + if let Some(x) = x { + num *= x.0 - x_j.0; + den *= x_i.0 - x_j.0; + } else { + // Both signs inverted just to avoid requiring Neg (-*xj) + num *= x_j.0; + den *= x_j.0 - x_i.0; + } + } + + //if !x_i_found { + //return Err(FROSTError::UnknownIdentifier); + //} + + let inverse = num * den.invert(); + + Ok(inverse) +} + +pub(super) fn binding_factor_transcripts( + signing_commitments: &[SigningCommitments], + verifying_key: &GroupPublicKey, + message: &[u8], +) -> Vec<(u16, Transcript)> { + let mut transcript = Transcript::new(b"binding_factor"); + + transcript.commit_point(b"verifying_key", verifying_key.0.as_compressed()); + + transcript.append_message(b"message", message); + + transcript.append_message( + b"group_commitment", + encode_group_commitments(signing_commitments) + .challenge_scalar(b"encode_group_commitments") + .as_bytes(), + ); + + signing_commitments + .iter() + .enumerate() + .map(|(i, _)| { + transcript.append_message(b"identifier", &i.to_le_bytes()); + (i as u16, transcript.clone()) + }) + .collect() +} + +pub(super) fn encode_group_commitments(signing_commitments: &[SigningCommitments]) -> Transcript { + let mut transcript = Transcript::new(b"encode_group_commitments"); + + for item in signing_commitments { + //transcript.append_message(b"identifier", item.identifier.0.as_bytes()); + transcript.commit_point(b"hiding", &item.hiding.0.compress()); + transcript.commit_point(b"binding", &item.binding.0.compress()); + } + + transcript +} diff --git a/src/olaf/mod.rs b/src/olaf/mod.rs index 7b81f56..abdffcf 100644 --- a/src/olaf/mod.rs +++ b/src/olaf/mod.rs @@ -1,27 +1,34 @@ //! Implementation of the Olaf protocol (), which is composed of the Distributed //! Key Generation (DKG) protocol SimplPedPoP and the Threshold Signing protocol FROST. +mod simplpedpop; +mod frost; + +use core::cmp::Ordering; + use curve25519_dalek::{constants::RISTRETTO_BASEPOINT_POINT, RistrettoPoint, Scalar}; -use crate::{PublicKey, SecretKey}; -use crate::context::SigningTranscript; use merlin::Transcript; -mod simplpedpop; -//mod frost; +use crate::{context::SigningTranscript, Keypair, PublicKey}; pub(super) const MINIMUM_THRESHOLD: u16 = 2; pub(super) const GENERATOR: RistrettoPoint = RISTRETTO_BASEPOINT_POINT; -/// The group public key generated by the SimplPedPoP protocol. -pub struct GroupPublicKey(PublicKey); -/// The verifying share of a participant in the SimplPedPoP protocol, used to verify its signature share. -pub struct VerifyingShare(PublicKey); -/// The signing share of a participant in the SimplPedPoP protocol, used to produce its signature share. -pub struct SigningShare(SecretKey); +/// The group public key used by the Olaf protocol. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct GroupPublicKey(pub(crate) PublicKey); + +/// The verifying share of a participant in the Olaf protocol, used to verify its signature share. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct VerifyingShare(pub(crate) PublicKey); + +/// The signing keypair of a participant in the Olaf protocol, used to produce its signature share. +#[derive(Clone, Debug)] +pub struct SigningKeyPair(pub(crate) Keypair); /// The identifier of a participant in the Olaf protocol. -#[derive(Clone, Copy)] -pub struct Identifier(Scalar); +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct Identifier(pub(crate) Scalar); impl Identifier { pub(super) fn generate(recipients_hash: &[u8; 16], index: u16) -> Identifier { @@ -32,3 +39,23 @@ impl Identifier { Identifier(pos.challenge_scalar(b"evaluation position")) } } + +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()) + } +} diff --git a/src/olaf/simplpedpop/errors.rs b/src/olaf/simplpedpop/errors.rs index 8f169c4..b7770c0 100644 --- a/src/olaf/simplpedpop/errors.rs +++ b/src/olaf/simplpedpop/errors.rs @@ -4,11 +4,13 @@ use core::array::TryFromSliceError; use crate::SignatureError; /// A result for the SimplPedPoP protocol. -pub type DKGResult = Result; +pub type SPPResult = Result; /// An error ocurred during the execution of the SimplPedPoP protocol. #[derive(Debug)] -pub enum DKGError { +pub enum SPPError { + /// Invalid parameters. + InvalidParameters, /// Threshold cannot be greater than the number of participants. ExcessiveThreshold, /// Threshold must be at least 2. @@ -43,4 +45,279 @@ pub enum DKGError { DecryptionError(chacha20poly1305::Error), /// Encryption error when encrypting the secret share. EncryptionError(chacha20poly1305::Error), + /// Invalid Proof of Possession. + InvalidProofOfPossession(SignatureError), +} + +#[cfg(test)] +mod tests { + mod simplpedpop { + use crate::olaf::simplpedpop::errors::SPPError; + use crate::olaf::simplpedpop::types::{ + AllMessage, EncryptedSecretShare, CHACHA20POLY1305_LENGTH, RECIPIENTS_HASH_LENGTH, + }; + use crate::{Keypair, PublicKey}; + use alloc::vec::Vec; + use curve25519_dalek::ristretto::RistrettoPoint; + use curve25519_dalek::traits::Identity; + use merlin::Transcript; + + #[test] + fn test_invalid_number_of_messages() { + let threshold = 3; + let participants = 5; + + let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); + + let mut messages: Vec = keypairs + .iter() + .map(|kp| kp.simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap()) + .collect(); + + messages.pop(); + + let result = keypairs[0].simplpedpop_recipient_all(&messages); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + SPPError::InvalidNumberOfMessages => assert!(true), + _ => { + panic!("Expected DKGError::InvalidNumberOfMessages, but got {:?}", e) + }, + }, + } + } + + #[test] + fn test_different_parameters() { + let threshold = 3; + let participants = 5; + + let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); + + let mut messages: Vec = Vec::new(); + for i in 0..participants { + let message = + keypairs[i].simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap(); + messages.push(message); + } + + messages[1].content.parameters.threshold += 1; + + let result = keypairs[0].simplpedpop_recipient_all(&messages); + + // Check if the result is an error + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + SPPError::DifferentParameters => assert!(true), + _ => panic!("Expected DKGError::DifferentParameters, but got {:?}", e), + }, + } + } + + #[test] + fn test_different_recipients_hash() { + let threshold = 3; + let participants = 5; + + let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); + + let mut messages: Vec = keypairs + .iter() + .map(|kp| kp.simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap()) + .collect(); + + messages[1].content.recipients_hash = [1; RECIPIENTS_HASH_LENGTH]; + + let result = keypairs[0].simplpedpop_recipient_all(&messages); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + SPPError::DifferentRecipientsHash => assert!(true), + _ => { + panic!("Expected DKGError::DifferentRecipientsHash, but got {:?}", e) + }, + }, + } + } + + #[test] + fn test_incorrect_number_of_commitments() { + let threshold = 3; + let participants = 5; + + let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); + + let mut messages: Vec = keypairs + .iter() + .map(|kp| kp.simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap()) + .collect(); + + messages[1].content.polynomial_commitment.coefficients_commitments.pop(); + + let result = keypairs[0].simplpedpop_recipient_all(&messages); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + SPPError::IncorrectNumberOfCoefficientCommitments => assert!(true), + _ => panic!( + "Expected DKGError::IncorrectNumberOfCoefficientCommitments, but got {:?}", + e + ), + }, + } + } + + #[test] + fn test_incorrect_number_of_encrypted_shares() { + let threshold = 3; + let participants = 5; + + let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); + + let mut messages: Vec = keypairs + .iter() + .map(|kp| kp.simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap()) + .collect(); + + messages[1].content.encrypted_secret_shares.pop(); + + let result = keypairs[0].simplpedpop_recipient_all(&messages); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + SPPError::IncorrectNumberOfEncryptedShares => assert!(true), + _ => panic!( + "Expected DKGError::IncorrectNumberOfEncryptedShares, but got {:?}", + e + ), + }, + } + } + + #[test] + fn test_invalid_secret_share() { + let threshold = 3; + let participants = 5; + + let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); + + let mut messages: Vec = keypairs + .iter() + .map(|kp| kp.simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap()) + .collect(); + + messages[1].content.encrypted_secret_shares[0] = + EncryptedSecretShare(vec![1; CHACHA20POLY1305_LENGTH]); + + let result = keypairs[0].simplpedpop_recipient_all(&messages); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + SPPError::InvalidSecretShare => assert!(true), + _ => panic!("Expected DKGError::InvalidSecretShare, but got {:?}", e), + }, + } + } + + #[test] + fn test_invalid_signature() { + let threshold = 3; + let participants = 5; + + let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); + + let mut messages: Vec = keypairs + .iter() + .map(|kp| kp.simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap()) + .collect(); + + messages[1].signature = + keypairs[1].secret.sign(Transcript::new(b"invalid"), &keypairs[1].public); + + let result = keypairs[0].simplpedpop_recipient_all(&messages); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + SPPError::InvalidSignature(_) => assert!(true), + _ => panic!("Expected DKGError::InvalidSignature, but got {:?}", e), + }, + } + } + + #[test] + fn test_invalid_threshold() { + let keypair = Keypair::generate(); + let result = keypair.simplpedpop_contribute_all( + 1, + vec![ + PublicKey::from_point(RistrettoPoint::identity()), + PublicKey::from_point(RistrettoPoint::identity()), + ], + ); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + SPPError::InsufficientThreshold => assert!(true), + _ => panic!("Expected SPPError::InsufficientThreshold, but got {:?}", e), + }, + } + } + + #[test] + fn test_invalid_participants() { + let keypair = Keypair::generate(); + let result = keypair.simplpedpop_contribute_all( + 2, + vec![PublicKey::from_point(RistrettoPoint::identity())], + ); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + SPPError::InvalidNumberOfParticipants => { + assert!(true) + }, + _ => { + panic!("Expected SPPError::InvalidNumberOfParticipants, but got {:?}", e) + }, + }, + } + } + + #[test] + fn test_threshold_greater_than_participants() { + let keypair = Keypair::generate(); + let result = keypair.simplpedpop_contribute_all( + 3, + vec![ + PublicKey::from_point(RistrettoPoint::identity()), + PublicKey::from_point(RistrettoPoint::identity()), + ], + ); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + SPPError::ExcessiveThreshold => assert!(true), + _ => panic!("Expected SPPError::ExcessiveThreshold), but got {:?}", e), + }, + } + } + } } diff --git a/src/olaf/simplpedpop/mod.rs b/src/olaf/simplpedpop/mod.rs index a045214..160ac50 100644 --- a/src/olaf/simplpedpop/mod.rs +++ b/src/olaf/simplpedpop/mod.rs @@ -1,9 +1,12 @@ //! Implementation of the SimplPedPoP protocol (), 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. -mod tests; -mod errors; mod types; +mod errors; + +pub use self::types::{ + AllMessage, DKGOutput, DKGOutputMessage, MessageContent, Parameters, PolynomialCommitment, +}; use alloc::vec::Vec; use curve25519_dalek::{traits::Identity, RistrettoPoint, Scalar}; @@ -12,13 +15,14 @@ use rand_core::RngCore; use crate::{ context::SigningTranscript, verify_batch, Keypair, PublicKey, SecretKey, getrandom_or_panic, }; -use errors::{DKGError, DKGResult}; -use types::{ - AllMessage, DKGOutput, DKGOutputMessage, MessageContent, Parameters, SecretShare, - CHACHA20POLY1305_KEY_LENGTH, ENCRYPTION_NONCE_LENGTH, RECIPIENTS_HASH_LENGTH, - PolynomialCommitment, SecretPolynomial, +use self::{ + errors::{SPPError, SPPResult}, + types::{ + SecretPolynomial, SecretShare, CHACHA20POLY1305_KEY_LENGTH, ENCRYPTION_NONCE_LENGTH, + RECIPIENTS_HASH_LENGTH, + }, }; -use super::{GroupPublicKey, Identifier, SigningShare, VerifyingShare, GENERATOR}; +use super::{GroupPublicKey, Identifier, SigningKeyPair, VerifyingShare, GENERATOR}; impl Keypair { /// First round of the SimplPedPoP protocol. @@ -26,7 +30,7 @@ impl Keypair { &self, threshold: u16, recipients: Vec, - ) -> DKGResult { + ) -> SPPResult { let parameters = Parameters::generate(recipients.len() as u16, threshold); parameters.validate()?; @@ -54,7 +58,7 @@ impl Keypair { let mut encrypted_secret_shares = Vec::new(); - let polynomial_commitment = PolynomialCommitment::commit(&secret_polynomial); + let polynomial_commitment = secret_polynomial.commit(); let mut encryption_transcript = merlin::Transcript::new(b"Encryption"); parameters.commit(&mut encryption_transcript); @@ -64,15 +68,7 @@ impl Keypair { rng.fill_bytes(&mut encryption_nonce); encryption_transcript.append_message(b"nonce", &encryption_nonce); - let secret = *secret_polynomial - .coefficients - .first() - .expect("This never fails because the minimum threshold is 2"); - - let mut nonce: [u8; 32] = [0u8; 32]; - rng.fill_bytes(&mut nonce); - - let ephemeral_key = SecretKey { key: secret, nonce }; + let ephemeral_key = Keypair::generate(); for i in 0..parameters.participants { let identifier = Identifier::generate(&recipients_hash, i); @@ -83,7 +79,7 @@ impl Keypair { let recipient = recipients[i as usize]; - let key_exchange = ephemeral_key.key * recipient.into_point(); + let key_exchange = ephemeral_key.secret.key * recipient.into_point(); let mut encryption_transcript = encryption_transcript.clone(); encryption_transcript.commit_point(b"recipient", recipient.as_compressed()); @@ -98,6 +94,33 @@ impl Keypair { encrypted_secret_shares.push(encrypted_secret_share); } + let pk = &PublicKey::from_point( + *polynomial_commitment + .coefficients_commitments + .first() + .expect("This never fails because the minimum threshold is 2"), + ); + + let secret = *secret_polynomial + .coefficients + .first() + .expect("This never fails because the minimum threshold is 2"); + + let mut nonce: [u8; 32] = [0u8; 32]; + crate::getrandom_or_panic().fill_bytes(&mut nonce); + + let secret_key = SecretKey { key: secret, nonce }; + + let secret_commitment = polynomial_commitment + .coefficients_commitments + .first() + .expect("This never fails because the minimum threshold is 2"); + + let mut pop_transcript = Transcript::new(b"pop"); + pop_transcript + .append_message(b"secret commitment", secret_commitment.compress().as_bytes()); + let proof_of_possession = secret_key.sign(pop_transcript, pk); + let message_content = MessageContent::new( self.public, encryption_nonce, @@ -105,6 +128,8 @@ impl Keypair { recipients_hash, polynomial_commitment, encrypted_secret_shares, + ephemeral_key.public, + proof_of_possession, ); let mut signature_transcript = Transcript::new(b"signature"); @@ -118,7 +143,7 @@ impl Keypair { pub fn simplpedpop_recipient_all( &self, messages: &[AllMessage], - ) -> DKGResult<(DKGOutputMessage, SigningShare)> { + ) -> SPPResult<(DKGOutputMessage, SigningKeyPair)> { let first_message = &messages[0]; let parameters = &first_message.content.parameters; let threshold = parameters.threshold as usize; @@ -127,7 +152,7 @@ impl Keypair { first_message.content.parameters.validate()?; if messages.len() < participants { - return Err(DKGError::InvalidNumberOfMessages); + return Err(SPPError::InvalidNumberOfMessages); } let mut secret_shares = Vec::with_capacity(participants); @@ -140,23 +165,30 @@ impl Keypair { let mut total_polynomial_commitment = PolynomialCommitment { coefficients_commitments: vec![] }; let mut identifiers = Vec::new(); + let mut public_keys = Vec::with_capacity(participants); + let mut proofs_of_possession = Vec::with_capacity(participants); + let mut pops_transcripts = Vec::with_capacity(participants); for (j, message) in messages.iter().enumerate() { if &message.content.parameters != parameters { - return Err(DKGError::DifferentParameters); + return Err(SPPError::DifferentParameters); } if message.content.recipients_hash != first_message.content.recipients_hash { - return Err(DKGError::DifferentRecipientsHash); + return Err(SPPError::DifferentRecipientsHash); } let content = &message.content; let polynomial_commitment = &content.polynomial_commitment; let encrypted_secret_shares = &content.encrypted_secret_shares; - let secret_commitment = polynomial_commitment - .coefficients_commitments - .first() - .expect("This never fails because the minimum threshold is 2"); + let public_key = PublicKey::from_point( + *polynomial_commitment + .coefficients_commitments + .first() + .expect("This never fails because the minimum threshold is 2"), + ); + public_keys.push(public_key); + proofs_of_possession.push(content.proof_of_possession); senders.push(content.sender); signatures.push(message.signature); @@ -167,23 +199,35 @@ impl Keypair { encryption_transcript.append_message(b"nonce", &content.encryption_nonce); if polynomial_commitment.coefficients_commitments.len() != threshold { - return Err(DKGError::IncorrectNumberOfCoefficientCommitments); + return Err(SPPError::IncorrectNumberOfCoefficientCommitments); } if encrypted_secret_shares.len() != participants { - return Err(DKGError::IncorrectNumberOfEncryptedShares); + return Err(SPPError::IncorrectNumberOfEncryptedShares); } let mut signature_transcript = Transcript::new(b"signature"); signature_transcript.append_message(b"message", &content.to_bytes()); signatures_transcripts.push(signature_transcript); + let mut pop_transcript = Transcript::new(b"pop"); + + let secret_commitment = polynomial_commitment + .coefficients_commitments + .first() + .expect("This never fails because the minimum threshold is 2"); + + pop_transcript + .append_message(b"secret commitment", secret_commitment.compress().as_bytes()); + + pops_transcripts.push(pop_transcript); + total_polynomial_commitment = PolynomialCommitment::sum_polynomial_commitments(&[ &total_polynomial_commitment, &polynomial_commitment, ]); - let key_exchange = self.secret.key * secret_commitment; + let key_exchange = self.secret.key * message.content.ephemeral_key.as_point(); encryption_transcript.commit_point(b"recipient", self.public.as_compressed()); encryption_transcript.commit_point(b"key exchange", &key_exchange.compress()); @@ -205,9 +249,11 @@ impl Keypair { } if !secret_share_found { - if let Ok(secret_share) = - encrypted_secret_share.decrypt(&key_bytes, &content.encryption_nonce) - { + if let Ok(secret_share) = SecretShare::decrypt( + encrypted_secret_share, + &key_bytes, + &content.encryption_nonce, + ) { if secret_share.0 * GENERATOR == polynomial_commitment.evaluate(&identifiers[i].0) { @@ -218,20 +264,26 @@ impl Keypair { } } - total_secret_share += secret_shares.get(j).ok_or(DKGError::InvalidSecretShare)?.0; + total_secret_share += secret_shares.get(j).ok_or(SPPError::InvalidSecretShare)?.0; group_point += secret_commitment; } + verify_batch(&mut pops_transcripts, &proofs_of_possession, &public_keys, false) + .map_err(SPPError::InvalidProofOfPossession)?; + verify_batch(&mut signatures_transcripts, &signatures, &senders, false) - .map_err(DKGError::InvalidSignature)?; + .map_err(SPPError::InvalidSignature)?; for id in &identifiers { let evaluation = total_polynomial_commitment.evaluate(&id.0); verifying_keys.push((*id, VerifyingShare(PublicKey::from_point(evaluation)))); } - let dkg_output = - DKGOutput::new(GroupPublicKey(PublicKey::from_point(group_point)), verifying_keys); + let dkg_output = DKGOutput::new( + parameters, + GroupPublicKey(PublicKey::from_point(group_point)), + verifying_keys, + ); let mut dkg_output_transcript = Transcript::new(b"dkg output"); dkg_output_transcript.append_message(b"message", &dkg_output.to_bytes()); @@ -244,6 +296,351 @@ impl Keypair { let secret_key = SecretKey { key: total_secret_share, nonce }; - Ok((dkg_output, SigningShare(secret_key))) + let keypair = Keypair::from(secret_key); + + Ok((dkg_output, SigningKeyPair(keypair))) + } +} + +#[cfg(test)] +mod tests { + mod simplpedpop { + use crate::olaf::simplpedpop::errors::SPPError; + use crate::olaf::simplpedpop::types::{ + AllMessage, EncryptedSecretShare, Parameters, CHACHA20POLY1305_LENGTH, + RECIPIENTS_HASH_LENGTH, + }; + use crate::olaf::MINIMUM_THRESHOLD; + use crate::{Keypair, PublicKey}; + use alloc::vec::Vec; + use curve25519_dalek::ristretto::RistrettoPoint; + use curve25519_dalek::traits::Identity; + use merlin::Transcript; + use rand::Rng; + + const MAXIMUM_PARTICIPANTS: u16 = 10; + const MINIMUM_PARTICIPANTS: u16 = 2; + const PROTOCOL_RUNS: usize = 1; + + fn generate_parameters() -> Parameters { + let mut rng = rand::thread_rng(); + let participants = rng.gen_range(MINIMUM_PARTICIPANTS..=MAXIMUM_PARTICIPANTS); + let threshold = rng.gen_range(MINIMUM_THRESHOLD..=participants); + + Parameters { participants, threshold } + } + + #[test] + fn test_simplpedpop_protocol() { + for _ in 0..PROTOCOL_RUNS { + let parameters = generate_parameters(); + let participants = parameters.participants as usize; + let threshold = parameters.threshold as usize; + + let keypairs: Vec = + (0..participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public).collect(); + + let mut all_messages = Vec::new(); + for i in 0..participants { + let message: AllMessage = keypairs[i] + .simplpedpop_contribute_all(threshold as u16, public_keys.clone()) + .unwrap(); + all_messages.push(message); + } + + let mut dkg_outputs = Vec::new(); + + for kp in keypairs.iter() { + let dkg_output = kp.simplpedpop_recipient_all(&all_messages).unwrap(); + dkg_outputs.push(dkg_output); + } + + // Verify that all DKG outputs are equal for group_public_key and verifying_keys + assert!( + dkg_outputs.windows(2).all(|w| w[0].0.dkg_output.group_public_key.0 + == w[1].0.dkg_output.group_public_key.0 + && w[0].0.dkg_output.verifying_keys.len() + == w[1].0.dkg_output.verifying_keys.len() + && w[0] + .0 + .dkg_output + .verifying_keys + .iter() + .zip(w[1].0.dkg_output.verifying_keys.iter()) + .all(|((a, b), (c, d))| a.0 == c.0 && b.0 == d.0)), + "All DKG outputs should have identical group public keys and verifying keys." + ); + + // Verify that all verifying_shares are valid + for i in 0..participants { + for j in 0..participants { + assert_eq!( + dkg_outputs[i].0.dkg_output.verifying_keys[j].1 .0, + (dkg_outputs[j].1 .0.public), + "Verification of total secret shares failed!" + ); + } + } + } + } + + #[test] + fn test_invalid_number_of_messages() { + let threshold = 3; + let participants = 5; + + let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); + + let mut messages: Vec = keypairs + .iter() + .map(|kp| kp.simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap()) + .collect(); + + messages.pop(); + + let result = keypairs[0].simplpedpop_recipient_all(&messages); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + SPPError::InvalidNumberOfMessages => assert!(true), + _ => { + panic!("Expected DKGError::InvalidNumberOfMessages, but got {:?}", e) + }, + }, + } + } + + #[test] + fn test_different_parameters() { + let threshold = 3; + let participants = 5; + + let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); + + let mut messages: Vec = Vec::new(); + for i in 0..participants { + let message = + keypairs[i].simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap(); + messages.push(message); + } + + messages[1].content.parameters.threshold += 1; + + let result = keypairs[0].simplpedpop_recipient_all(&messages); + + // Check if the result is an error + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + SPPError::DifferentParameters => assert!(true), + _ => panic!("Expected DKGError::DifferentParameters, but got {:?}", e), + }, + } + } + + #[test] + fn test_different_recipients_hash() { + let threshold = 3; + let participants = 5; + + let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); + + let mut messages: Vec = keypairs + .iter() + .map(|kp| kp.simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap()) + .collect(); + + messages[1].content.recipients_hash = [1; RECIPIENTS_HASH_LENGTH]; + + let result = keypairs[0].simplpedpop_recipient_all(&messages); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + SPPError::DifferentRecipientsHash => assert!(true), + _ => { + panic!("Expected DKGError::DifferentRecipientsHash, but got {:?}", e) + }, + }, + } + } + + #[test] + fn test_incorrect_number_of_commitments() { + let threshold = 3; + let participants = 5; + + let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); + + let mut messages: Vec = keypairs + .iter() + .map(|kp| kp.simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap()) + .collect(); + + messages[1].content.polynomial_commitment.coefficients_commitments.pop(); + + let result = keypairs[0].simplpedpop_recipient_all(&messages); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + SPPError::IncorrectNumberOfCoefficientCommitments => assert!(true), + _ => panic!( + "Expected DKGError::IncorrectNumberOfCoefficientCommitments, but got {:?}", + e + ), + }, + } + } + + #[test] + fn test_incorrect_number_of_encrypted_shares() { + let threshold = 3; + let participants = 5; + + let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); + + let mut messages: Vec = keypairs + .iter() + .map(|kp| kp.simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap()) + .collect(); + + messages[1].content.encrypted_secret_shares.pop(); + + let result = keypairs[0].simplpedpop_recipient_all(&messages); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + SPPError::IncorrectNumberOfEncryptedShares => assert!(true), + _ => panic!( + "Expected DKGError::IncorrectNumberOfEncryptedShares, but got {:?}", + e + ), + }, + } + } + + #[test] + fn test_invalid_secret_share() { + let threshold = 3; + let participants = 5; + + let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); + + let mut messages: Vec = keypairs + .iter() + .map(|kp| kp.simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap()) + .collect(); + + messages[1].content.encrypted_secret_shares[0] = + EncryptedSecretShare(vec![1; CHACHA20POLY1305_LENGTH]); + + let result = keypairs[0].simplpedpop_recipient_all(&messages); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + SPPError::InvalidSecretShare => assert!(true), + _ => panic!("Expected DKGError::InvalidSecretShare, but got {:?}", e), + }, + } + } + + #[test] + fn test_invalid_signature() { + let threshold = 3; + let participants = 5; + + let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); + + let mut messages: Vec = keypairs + .iter() + .map(|kp| kp.simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap()) + .collect(); + + messages[1].signature = + keypairs[1].secret.sign(Transcript::new(b"invalid"), &keypairs[1].public); + + let result = keypairs[0].simplpedpop_recipient_all(&messages); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + SPPError::InvalidSignature(_) => assert!(true), + _ => panic!("Expected DKGError::InvalidSignature, but got {:?}", e), + }, + } + } + + #[test] + fn test_invalid_threshold() { + let keypair = Keypair::generate(); + let result = keypair.simplpedpop_contribute_all( + 1, + vec![ + PublicKey::from_point(RistrettoPoint::identity()), + PublicKey::from_point(RistrettoPoint::identity()), + ], + ); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + SPPError::InsufficientThreshold => assert!(true), + _ => panic!("Expected SPPError::InsufficientThreshold, but got {:?}", e), + }, + } + } + + #[test] + fn test_invalid_participants() { + let keypair = Keypair::generate(); + let result = keypair.simplpedpop_contribute_all( + 2, + vec![PublicKey::from_point(RistrettoPoint::identity())], + ); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + SPPError::InvalidNumberOfParticipants => { + assert!(true) + }, + _ => { + panic!("Expected SPPError::InvalidNumberOfParticipants, but got {:?}", e) + }, + }, + } + } + + #[test] + fn test_threshold_greater_than_participants() { + let keypair = Keypair::generate(); + let result = keypair.simplpedpop_contribute_all( + 3, + vec![ + PublicKey::from_point(RistrettoPoint::identity()), + PublicKey::from_point(RistrettoPoint::identity()), + ], + ); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + SPPError::ExcessiveThreshold => assert!(true), + _ => panic!("Expected SPPError::ExcessiveThreshold), but got {:?}", e), + }, + } + } } } diff --git a/src/olaf/simplpedpop/tests.rs b/src/olaf/simplpedpop/tests.rs deleted file mode 100644 index 0415de7..0000000 --- a/src/olaf/simplpedpop/tests.rs +++ /dev/null @@ -1,338 +0,0 @@ -#[cfg(test)] -mod tests { - mod simplpedpop { - use crate::olaf::simplpedpop::{AllMessage, RECIPIENTS_HASH_LENGTH, Parameters}; - use crate::olaf::simplpedpop::DKGError; - use crate::{Keypair, PublicKey}; - use alloc::vec::Vec; - use curve25519_dalek::ristretto::RistrettoPoint; - use curve25519_dalek::traits::Identity; - use merlin::Transcript; - use rand::Rng; - use crate::olaf::MINIMUM_THRESHOLD; - use crate::olaf::simplpedpop::types::{EncryptedSecretShare, CHACHA20POLY1305_LENGTH}; - - const MAXIMUM_PARTICIPANTS: u16 = 10; - const MINIMUM_PARTICIPANTS: u16 = 2; - const PROTOCOL_RUNS: usize = 1; - - fn generate_parameters() -> Parameters { - let mut rng = rand::thread_rng(); - let participants = rng.gen_range(MINIMUM_PARTICIPANTS..=MAXIMUM_PARTICIPANTS); - let threshold = rng.gen_range(MINIMUM_THRESHOLD..=participants); - - Parameters { participants, threshold } - } - - #[test] - fn test_simplpedpop_protocol() { - for _ in 0..PROTOCOL_RUNS { - let parameters = generate_parameters(); - let participants = parameters.participants as usize; - let threshold = parameters.threshold as usize; - - let keypairs: Vec = - (0..participants).map(|_| Keypair::generate()).collect(); - let public_keys: Vec = keypairs.iter().map(|kp| kp.public).collect(); - - let mut all_messages = Vec::new(); - for i in 0..participants { - let message: AllMessage = keypairs[i] - .simplpedpop_contribute_all(threshold as u16, public_keys.clone()) - .unwrap(); - all_messages.push(message); - } - - let mut dkg_outputs = Vec::new(); - - for kp in keypairs.iter() { - let dkg_output = kp.simplpedpop_recipient_all(&all_messages).unwrap(); - dkg_outputs.push(dkg_output); - } - - // Verify that all DKG outputs are equal for group_public_key and verifying_keys - assert!( - dkg_outputs.windows(2).all(|w| w[0].0.dkg_output.group_public_key.0 - == w[1].0.dkg_output.group_public_key.0 - && w[0].0.dkg_output.verifying_keys.len() - == w[1].0.dkg_output.verifying_keys.len() - && w[0] - .0 - .dkg_output - .verifying_keys - .iter() - .zip(w[1].0.dkg_output.verifying_keys.iter()) - .all(|((a, b), (c, d))| a.0 == c.0 && b.0 == d.0)), - "All DKG outputs should have identical group public keys and verifying keys." - ); - - // Verify that all verifying_shares are valid - for i in 0..participants { - for j in 0..participants { - assert_eq!( - dkg_outputs[i].0.dkg_output.verifying_keys[j].1 .0, - (dkg_outputs[j].1 .0.to_public()), - "Verification of total secret shares failed!" - ); - } - } - } - } - - #[test] - fn test_invalid_number_of_messages() { - let threshold = 3; - let participants = 5; - - let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); - let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); - - let mut messages: Vec = keypairs - .iter() - .map(|kp| kp.simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap()) - .collect(); - - messages.pop(); - - let result = keypairs[0].simplpedpop_recipient_all(&messages); - - match result { - Ok(_) => panic!("Expected an error, but got Ok."), - Err(e) => match e { - DKGError::InvalidNumberOfMessages => assert!(true), - _ => { - panic!("Expected DKGError::InvalidNumberOfMessages, but got {:?}", e) - }, - }, - } - } - - #[test] - fn test_different_parameters() { - let threshold = 3; - let participants = 5; - - let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); - let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); - - let mut messages: Vec = Vec::new(); - for i in 0..participants { - let message = - keypairs[i].simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap(); - messages.push(message); - } - - messages[1].content.parameters.threshold += 1; - - let result = keypairs[0].simplpedpop_recipient_all(&messages); - - // Check if the result is an error - match result { - Ok(_) => panic!("Expected an error, but got Ok."), - Err(e) => match e { - DKGError::DifferentParameters => assert!(true), - _ => panic!("Expected DKGError::DifferentParameters, but got {:?}", e), - }, - } - } - - #[test] - fn test_different_recipients_hash() { - let threshold = 3; - let participants = 5; - - let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); - let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); - - let mut messages: Vec = keypairs - .iter() - .map(|kp| kp.simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap()) - .collect(); - - messages[1].content.recipients_hash = [1; RECIPIENTS_HASH_LENGTH]; - - let result = keypairs[0].simplpedpop_recipient_all(&messages); - - match result { - Ok(_) => panic!("Expected an error, but got Ok."), - Err(e) => match e { - DKGError::DifferentRecipientsHash => assert!(true), - _ => { - panic!("Expected DKGError::DifferentRecipientsHash, but got {:?}", e) - }, - }, - } - } - - #[test] - fn test_incorrect_number_of_commitments() { - let threshold = 3; - let participants = 5; - - let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); - let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); - - let mut messages: Vec = keypairs - .iter() - .map(|kp| kp.simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap()) - .collect(); - - messages[1].content.polynomial_commitment.coefficients_commitments.pop(); - - let result = keypairs[0].simplpedpop_recipient_all(&messages); - - match result { - Ok(_) => panic!("Expected an error, but got Ok."), - Err(e) => match e { - DKGError::IncorrectNumberOfCoefficientCommitments => assert!(true), - _ => panic!( - "Expected DKGError::IncorrectNumberOfCoefficientCommitments, but got {:?}", - e - ), - }, - } - } - - #[test] - fn test_incorrect_number_of_encrypted_shares() { - let threshold = 3; - let participants = 5; - - let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); - let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); - - let mut messages: Vec = keypairs - .iter() - .map(|kp| kp.simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap()) - .collect(); - - messages[1].content.encrypted_secret_shares.pop(); - - let result = keypairs[0].simplpedpop_recipient_all(&messages); - - match result { - Ok(_) => panic!("Expected an error, but got Ok."), - Err(e) => match e { - DKGError::IncorrectNumberOfEncryptedShares => assert!(true), - _ => panic!( - "Expected DKGError::IncorrectNumberOfEncryptedShares, but got {:?}", - e - ), - }, - } - } - - #[test] - fn test_invalid_secret_share() { - let threshold = 3; - let participants = 5; - - let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); - let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); - - let mut messages: Vec = keypairs - .iter() - .map(|kp| kp.simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap()) - .collect(); - - messages[1].content.encrypted_secret_shares[0] = - EncryptedSecretShare(vec![1; CHACHA20POLY1305_LENGTH]); - - let result = keypairs[0].simplpedpop_recipient_all(&messages); - - match result { - Ok(_) => panic!("Expected an error, but got Ok."), - Err(e) => match e { - DKGError::InvalidSecretShare => assert!(true), - _ => panic!("Expected DKGError::InvalidSecretShare, but got {:?}", e), - }, - } - } - - #[test] - fn test_invalid_signature() { - let threshold = 3; - let participants = 5; - - let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); - let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); - - let mut messages: Vec = keypairs - .iter() - .map(|kp| kp.simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap()) - .collect(); - - messages[1].signature = - keypairs[1].secret.sign(Transcript::new(b"invalid"), &keypairs[1].public); - - let result = keypairs[0].simplpedpop_recipient_all(&messages); - - match result { - Ok(_) => panic!("Expected an error, but got Ok."), - Err(e) => match e { - DKGError::InvalidSignature(_) => assert!(true), - _ => panic!("Expected DKGError::InvalidSignature, but got {:?}", e), - }, - } - } - - #[test] - fn test_invalid_threshold() { - let keypair = Keypair::generate(); - let result = keypair.simplpedpop_contribute_all( - 1, - vec![ - PublicKey::from_point(RistrettoPoint::identity()), - PublicKey::from_point(RistrettoPoint::identity()), - ], - ); - - match result { - Ok(_) => panic!("Expected an error, but got Ok."), - Err(e) => match e { - DKGError::InsufficientThreshold => assert!(true), - _ => panic!("Expected DKGError::InsufficientThreshold, but got {:?}", e), - }, - } - } - - #[test] - fn test_invalid_participants() { - let keypair = Keypair::generate(); - let result = keypair.simplpedpop_contribute_all( - 2, - vec![PublicKey::from_point(RistrettoPoint::identity())], - ); - - match result { - Ok(_) => panic!("Expected an error, but got Ok."), - Err(e) => match e { - DKGError::InvalidNumberOfParticipants => assert!(true), - _ => { - panic!("Expected DKGError::InvalidNumberOfParticipants, but got {:?}", e) - }, - }, - } - } - - #[test] - fn test_threshold_greater_than_participants() { - let keypair = Keypair::generate(); - let result = keypair.simplpedpop_contribute_all( - 3, - vec![ - PublicKey::from_point(RistrettoPoint::identity()), - PublicKey::from_point(RistrettoPoint::identity()), - ], - ); - - match result { - Ok(_) => panic!("Expected an error, but got Ok."), - Err(e) => match e { - DKGError::ExcessiveThreshold => assert!(true), - _ => panic!("Expected DKGError::ExcessiveThreshold, but got {:?}", e), - }, - } - } - } -} diff --git a/src/olaf/simplpedpop/types.rs b/src/olaf/simplpedpop/types.rs index 2f6b2b9..e9b55da 100644 --- a/src/olaf/simplpedpop/types.rs +++ b/src/olaf/simplpedpop/types.rs @@ -1,64 +1,29 @@ -//! SimplPedPoP types. +//! Types of the SimplPedPoP protocol. + +#![allow(clippy::too_many_arguments)] use core::iter; use alloc::vec::Vec; -use curve25519_dalek::{ristretto::CompressedRistretto, traits::Identity, RistrettoPoint, Scalar}; use rand_core::{CryptoRng, RngCore}; use zeroize::ZeroizeOnDrop; -use crate::{ - context::SigningTranscript, scalar_from_canonical_bytes, PublicKey, Signature, - PUBLIC_KEY_LENGTH, SIGNATURE_LENGTH, -}; -use super::{ - errors::{DKGError, DKGResult}, - GroupPublicKey, Identifier, VerifyingShare, GENERATOR, -}; use aead::KeyInit; use chacha20poly1305::{aead::Aead, ChaCha20Poly1305, Nonce}; -use crate::olaf::MINIMUM_THRESHOLD; +use curve25519_dalek::{ristretto::CompressedRistretto, traits::Identity, RistrettoPoint, Scalar}; +use crate::{ + context::SigningTranscript, + olaf::{GroupPublicKey, Identifier, VerifyingShare, GENERATOR, MINIMUM_THRESHOLD}, + scalar_from_canonical_bytes, PublicKey, Signature, PUBLIC_KEY_LENGTH, SIGNATURE_LENGTH, +}; +use super::errors::{SPPError, SPPResult}; pub(super) const COMPRESSED_RISTRETTO_LENGTH: usize = 32; -pub(super) const VEC_LENGTH: usize = 2; +pub(super) const U16_LENGTH: usize = 2; pub(super) const ENCRYPTION_NONCE_LENGTH: usize = 12; pub(super) const RECIPIENTS_HASH_LENGTH: usize = 16; pub(super) const CHACHA20POLY1305_LENGTH: usize = 64; pub(super) const CHACHA20POLY1305_KEY_LENGTH: usize = 32; pub(super) const SCALAR_LENGTH: usize = 32; - -/// The parameters of a given execution of the SimplPedPoP protocol. -#[derive(PartialEq, Eq)] -pub struct Parameters { - pub(super) participants: u16, - pub(super) threshold: u16, -} - -impl Parameters { - /// Create new parameters. - pub fn generate(participants: u16, threshold: u16) -> Parameters { - Parameters { participants, threshold } - } - - pub(super) fn validate(&self) -> Result<(), DKGError> { - if self.threshold < MINIMUM_THRESHOLD { - return Err(DKGError::InsufficientThreshold); - } - - if self.participants < MINIMUM_THRESHOLD { - return Err(DKGError::InvalidNumberOfParticipants); - } - - if self.threshold > self.participants { - return Err(DKGError::ExcessiveThreshold); - } - - Ok(()) - } - - pub(super) 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()); - } -} +pub(super) const VEC_LENGTH: usize = 2; #[derive(ZeroizeOnDrop)] pub(super) struct SecretShare(pub(super) Scalar); @@ -68,33 +33,30 @@ impl SecretShare { &self, key: &[u8; CHACHA20POLY1305_KEY_LENGTH], nonce: &[u8; ENCRYPTION_NONCE_LENGTH], - ) -> DKGResult { + ) -> SPPResult { let cipher = ChaCha20Poly1305::new(&(*key).into()); let nonce = Nonce::from_slice(&nonce[..]); let ciphertext: Vec = cipher .encrypt(nonce, &self.0.to_bytes()[..]) - .map_err(DKGError::EncryptionError)?; + .map_err(SPPError::EncryptionError)?; Ok(EncryptedSecretShare(ciphertext)) } -} -#[derive(Clone)] -pub struct EncryptedSecretShare(pub(super) Vec); - -impl EncryptedSecretShare { pub(super) fn decrypt( - &self, + encrypted_secret_share: &EncryptedSecretShare, key: &[u8; CHACHA20POLY1305_KEY_LENGTH], nonce: &[u8; ENCRYPTION_NONCE_LENGTH], - ) -> DKGResult { + ) -> SPPResult { let cipher = ChaCha20Poly1305::new(&(*key).into()); let nonce = Nonce::from_slice(&nonce[..]); - let plaintext = cipher.decrypt(nonce, &self.0[..]).map_err(DKGError::DecryptionError)?; + let plaintext = cipher + .decrypt(nonce, &encrypted_secret_share.0[..]) + .map_err(SPPError::DecryptionError)?; let mut bytes = [0; 32]; bytes.copy_from_slice(&plaintext); @@ -134,25 +96,76 @@ impl SecretPolynomial { value } + + pub(super) fn commit(&self) -> PolynomialCommitment { + let coefficients_commitments = + self.coefficients.iter().map(|coefficient| GENERATOR * coefficient).collect(); + + PolynomialCommitment { coefficients_commitments } + } } -/// The polynomial commitment of a participant, used to verify the secret shares without revealing the polynomial. -pub struct PolynomialCommitment { - pub(super) coefficients_commitments: Vec, +/// The parameters of a given execution of the SimplPedPoP protocol. +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct Parameters { + pub(crate) participants: u16, + pub(crate) threshold: u16, } -impl PolynomialCommitment { - pub(super) fn commit(secret_polynomial: &SecretPolynomial) -> Self { - let coefficients_commitments = secret_polynomial - .coefficients - .iter() - .map(|coefficient| GENERATOR * coefficient) - .collect(); +impl Parameters { + /// Create new parameters. + pub fn generate(participants: u16, threshold: u16) -> Parameters { + Parameters { participants, threshold } + } + + pub(super) fn validate(&self) -> Result<(), SPPError> { + if self.threshold < MINIMUM_THRESHOLD { + return Err(SPPError::InsufficientThreshold); + } + + if self.participants < MINIMUM_THRESHOLD { + return Err(SPPError::InvalidNumberOfParticipants); + } + + if self.threshold > self.participants { + return Err(SPPError::ExcessiveThreshold); + } - Self { coefficients_commitments } + Ok(()) } - pub(super) fn evaluate(&self, identifier: &Scalar) -> RistrettoPoint { + pub(super) 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()); + } + + pub fn to_bytes(&self) -> [u8; U16_LENGTH * 2] { + let mut bytes = [0u8; U16_LENGTH * 2]; + bytes[0..U16_LENGTH].copy_from_slice(&self.participants.to_le_bytes()); + bytes[U16_LENGTH..U16_LENGTH * 2].copy_from_slice(&self.threshold.to_le_bytes()); + bytes + } + + /// Constructs `Parameters` from a byte array. + pub fn from_bytes(bytes: &[u8]) -> SPPResult { + if bytes.len() != U16_LENGTH * 2 { + return Err(SPPError::InvalidParameters); + } + + let participants = u16::from_le_bytes([bytes[0], bytes[1]]); + let threshold = u16::from_le_bytes([bytes[2], bytes[3]]); + + Ok(Parameters { participants, threshold }) + } +} + +/// The polynomial commitment of a participant, used to verify the secret shares without revealing the polynomial. +pub struct PolynomialCommitment { + pub(crate) coefficients_commitments: Vec, +} + +impl PolynomialCommitment { + pub(crate) fn evaluate(&self, identifier: &Scalar) -> RistrettoPoint { let i = identifier; let (_, result) = self @@ -165,7 +178,7 @@ impl PolynomialCommitment { result } - pub(super) fn sum_polynomial_commitments( + pub(crate) fn sum_polynomial_commitments( polynomials_commitments: &[&PolynomialCommitment], ) -> PolynomialCommitment { let max_length = polynomials_commitments @@ -188,6 +201,11 @@ impl PolynomialCommitment { } } +#[derive(Clone)] +pub struct EncryptedSecretShare(pub(super) Vec); + +impl EncryptedSecretShare {} + /// AllMessage packs together messages for all participants. /// /// We'd save bandwidth by having separate messages for each @@ -214,14 +232,14 @@ impl AllMessage { } /// Deserialize AllMessage from bytes - pub fn from_bytes(bytes: &[u8]) -> Result { + pub fn from_bytes(bytes: &[u8]) -> Result { let mut cursor = 0; let content = MessageContent::from_bytes(&bytes[cursor..])?; cursor += content.to_bytes().len(); let signature = Signature::from_bytes(&bytes[cursor..cursor + SIGNATURE_LENGTH]) - .map_err(DKGError::InvalidSignature)?; + .map_err(SPPError::InvalidSignature)?; Ok(AllMessage { content, signature }) } @@ -235,6 +253,8 @@ pub struct MessageContent { pub(super) recipients_hash: [u8; RECIPIENTS_HASH_LENGTH], pub(super) polynomial_commitment: PolynomialCommitment, pub(super) encrypted_secret_shares: Vec, + pub(super) ephemeral_key: PublicKey, + pub(super) proof_of_possession: Signature, } impl MessageContent { @@ -246,6 +266,8 @@ impl MessageContent { recipients_hash: [u8; RECIPIENTS_HASH_LENGTH], polynomial_commitment: PolynomialCommitment, encrypted_secret_shares: Vec, + ephemeral_key: PublicKey, + proof_of_possession: Signature, ) -> Self { Self { sender, @@ -254,8 +276,11 @@ impl MessageContent { recipients_hash, polynomial_commitment, encrypted_secret_shares, + ephemeral_key, + proof_of_possession, } } + /// Serialize MessageContent pub fn to_bytes(&self) -> Vec { let mut bytes = Vec::new(); @@ -274,40 +299,35 @@ impl MessageContent { bytes.extend(ciphertext.0.clone()); } + bytes.extend(&self.ephemeral_key.to_bytes()); + bytes.extend(&self.proof_of_possession.to_bytes()); + bytes } /// Deserialize MessageContent from bytes - pub fn from_bytes(bytes: &[u8]) -> Result { + pub fn from_bytes(bytes: &[u8]) -> Result { let mut cursor = 0; let sender = PublicKey::from_bytes(&bytes[cursor..cursor + PUBLIC_KEY_LENGTH]) - .map_err(DKGError::InvalidPublicKey)?; + .map_err(SPPError::InvalidPublicKey)?; cursor += PUBLIC_KEY_LENGTH; let encryption_nonce: [u8; ENCRYPTION_NONCE_LENGTH] = bytes [cursor..cursor + ENCRYPTION_NONCE_LENGTH] .try_into() - .map_err(DKGError::DeserializationError)?; + .map_err(SPPError::DeserializationError)?; cursor += ENCRYPTION_NONCE_LENGTH; - let participants = u16::from_le_bytes( - bytes[cursor..cursor + VEC_LENGTH] - .try_into() - .map_err(DKGError::DeserializationError)?, - ); - cursor += VEC_LENGTH; - let threshold = u16::from_le_bytes( - bytes[cursor..cursor + VEC_LENGTH] - .try_into() - .map_err(DKGError::DeserializationError)?, - ); - cursor += VEC_LENGTH; + let parameters = Parameters::from_bytes(&bytes[cursor..cursor + U16_LENGTH * 2])?; + cursor += U16_LENGTH * 2; + + let participants = parameters.participants; let recipients_hash: [u8; RECIPIENTS_HASH_LENGTH] = bytes [cursor..cursor + RECIPIENTS_HASH_LENGTH] .try_into() - .map_err(DKGError::DeserializationError)?; + .map_err(SPPError::DeserializationError)?; cursor += RECIPIENTS_HASH_LENGTH; let mut coefficients_commitments = Vec::with_capacity(participants as usize); @@ -316,10 +336,10 @@ impl MessageContent { let point = CompressedRistretto::from_slice( &bytes[cursor..cursor + COMPRESSED_RISTRETTO_LENGTH], ) - .map_err(DKGError::DeserializationError)?; + .map_err(SPPError::DeserializationError)?; coefficients_commitments - .push(point.decompress().ok_or(DKGError::InvalidCoefficientCommitment)?); + .push(point.decompress().ok_or(SPPError::InvalidCoefficientCommitment)?); cursor += COMPRESSED_RISTRETTO_LENGTH; } @@ -334,22 +354,32 @@ impl MessageContent { cursor += CHACHA20POLY1305_LENGTH; } + let ephemeral_key = PublicKey::from_bytes(&bytes[cursor..cursor + PUBLIC_KEY_LENGTH]) + .map_err(SPPError::InvalidPublicKey)?; + cursor += PUBLIC_KEY_LENGTH; + + let proof_of_possession = Signature::from_bytes(&bytes[cursor..cursor + SIGNATURE_LENGTH]) + .map_err(SPPError::InvalidSignature)?; + Ok(MessageContent { sender, encryption_nonce, - parameters: Parameters { participants, threshold }, + parameters, recipients_hash, polynomial_commitment, encrypted_secret_shares, + ephemeral_key, + proof_of_possession, }) } } /// The signed output of the SimplPedPoP protocol. +#[derive(Debug)] pub struct DKGOutputMessage { - pub(super) sender: PublicKey, - pub(super) dkg_output: DKGOutput, - pub(super) signature: Signature, + pub(crate) sender: PublicKey, + pub(crate) dkg_output: DKGOutput, + pub(crate) signature: Signature, } impl DKGOutputMessage { @@ -375,11 +405,11 @@ impl DKGOutputMessage { } /// Deserializes the DKGOutputMessage from bytes. - pub fn from_bytes(bytes: &[u8]) -> Result { + pub fn from_bytes(bytes: &[u8]) -> Result { let mut cursor = 0; let pk_bytes = &bytes[..PUBLIC_KEY_LENGTH]; - let sender = PublicKey::from_bytes(pk_bytes).map_err(DKGError::InvalidPublicKey)?; + let sender = PublicKey::from_bytes(pk_bytes).map_err(SPPError::InvalidPublicKey)?; cursor += PUBLIC_KEY_LENGTH; let content_bytes = &bytes[cursor..bytes.len() - SIGNATURE_LENGTH]; @@ -387,30 +417,37 @@ impl DKGOutputMessage { cursor = bytes.len() - SIGNATURE_LENGTH; let signature = Signature::from_bytes(&bytes[cursor..cursor + SIGNATURE_LENGTH]) - .map_err(DKGError::InvalidSignature)?; + .map_err(SPPError::InvalidSignature)?; Ok(DKGOutputMessage { sender, dkg_output, signature }) } } /// The content of the signed output of the SimplPedPoP protocol. +#[derive(Clone, Debug)] pub struct DKGOutput { - pub(super) group_public_key: GroupPublicKey, - pub(super) verifying_keys: Vec<(Identifier, VerifyingShare)>, + pub(crate) parameters: Parameters, + pub(crate) group_public_key: GroupPublicKey, + pub(crate) verifying_keys: Vec<(Identifier, VerifyingShare)>, } impl DKGOutput { /// Creates the content of the SimplPedPoP output. pub fn new( + parameters: &Parameters, group_public_key: GroupPublicKey, verifying_keys: Vec<(Identifier, VerifyingShare)>, ) -> Self { - Self { group_public_key, verifying_keys } + let parameters = Parameters::generate(parameters.participants, parameters.threshold); + + Self { group_public_key, verifying_keys, parameters } } /// Serializes the DKGOutput into bytes. pub fn to_bytes(&self) -> Vec { let mut bytes = Vec::new(); + bytes.extend(self.parameters.to_bytes()); + let compressed_public_key = self.group_public_key.0.as_compressed(); bytes.extend(compressed_public_key.to_bytes().iter()); @@ -426,38 +463,42 @@ impl DKGOutput { } /// Deserializes the DKGOutput from bytes. - pub fn from_bytes(bytes: &[u8]) -> Result { + pub fn from_bytes(bytes: &[u8]) -> Result { let mut cursor = 0; + let parameters = Parameters::from_bytes(&bytes[cursor..cursor + U16_LENGTH * 2])?; + cursor += U16_LENGTH * 2; + let public_key_bytes = &bytes[cursor..cursor + PUBLIC_KEY_LENGTH]; cursor += PUBLIC_KEY_LENGTH; let compressed_public_key = CompressedRistretto::from_slice(public_key_bytes) - .map_err(DKGError::DeserializationError)?; + .map_err(SPPError::DeserializationError)?; let group_public_key = - compressed_public_key.decompress().ok_or(DKGError::InvalidGroupPublicKey)?; - - cursor += VEC_LENGTH; + compressed_public_key.decompress().ok_or(SPPError::InvalidGroupPublicKey)?; let mut verifying_keys = Vec::new(); + cursor += VEC_LENGTH; + while cursor < bytes.len() { let mut identifier_bytes = [0; SCALAR_LENGTH]; identifier_bytes.copy_from_slice(&bytes[cursor..cursor + SCALAR_LENGTH]); let identifier = - scalar_from_canonical_bytes(identifier_bytes).ok_or(DKGError::InvalidIdentifier)?; + scalar_from_canonical_bytes(identifier_bytes).ok_or(SPPError::InvalidIdentifier)?; cursor += SCALAR_LENGTH; let key_bytes = &bytes[cursor..cursor + PUBLIC_KEY_LENGTH]; cursor += PUBLIC_KEY_LENGTH; - let key = PublicKey::from_bytes(key_bytes).map_err(DKGError::InvalidPublicKey)?; + let key = PublicKey::from_bytes(key_bytes).map_err(SPPError::InvalidPublicKey)?; verifying_keys.push((Identifier(identifier), VerifyingShare(key))); } Ok(DKGOutput { group_public_key: GroupPublicKey(PublicKey::from_point(group_public_key)), verifying_keys, + parameters, }) } } @@ -466,14 +507,60 @@ impl DKGOutput { mod tests { use merlin::Transcript; use rand_core::OsRng; - use crate::Keypair; + use crate::{context::SigningTranscript, Keypair}; use super::*; + use curve25519_dalek::RistrettoPoint; + + #[test] + fn test_encryption_decryption() { + let mut rng = OsRng; + let ephemeral_key = Keypair::generate(); + let recipient = Keypair::generate(); + let encryption_nonce = [1; ENCRYPTION_NONCE_LENGTH]; + let key_exchange = ephemeral_key.secret.key * recipient.public.as_point(); + let secret_share = SecretShare(Scalar::random(&mut rng)); + let mut transcript = Transcript::new(b"encryption"); + transcript.commit_point(b"key", &key_exchange.compress()); + let mut key_bytes = [0; CHACHA20POLY1305_KEY_LENGTH]; + transcript.challenge_bytes(b"key", &mut key_bytes); + + let encrypted_share = secret_share.encrypt(&key_bytes, &encryption_nonce).unwrap(); + + SecretShare::decrypt(&encrypted_share, &key_bytes, &encryption_nonce).unwrap(); + } + + #[test] + fn test_generate_polynomial_commitment_valid() { + let degree = 3; + + let polynomial = SecretPolynomial::generate(degree, &mut OsRng); + + let polynomial_commitment = polynomial.commit(); + + 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 = SecretPolynomial { 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_serialize_deserialize_all_message() { let sender = Keypair::generate(); let encryption_nonce = [1u8; ENCRYPTION_NONCE_LENGTH]; - let parameters = Parameters { participants: 2, threshold: 1 }; + let parameters = Parameters { participants: 2, threshold: 2 }; let recipients_hash = [2u8; RECIPIENTS_HASH_LENGTH]; let coefficients_commitments = vec![RistrettoPoint::random(&mut OsRng), RistrettoPoint::random(&mut OsRng)]; @@ -482,7 +569,9 @@ mod tests { EncryptedSecretShare(vec![1; CHACHA20POLY1305_LENGTH]), EncryptedSecretShare(vec![1; CHACHA20POLY1305_LENGTH]), ]; + let proof_of_possession = sender.sign(Transcript::new(b"pop")); let signature = sender.sign(Transcript::new(b"sig")); + let ephemeral_key = PublicKey::from_point(RistrettoPoint::random(&mut OsRng)); let message_content = MessageContent::new( sender.public, @@ -491,6 +580,8 @@ mod tests { recipients_hash, polynomial_commitment, encrypted_secret_shares, + ephemeral_key, + proof_of_possession, ); let message = AllMessage::new(message_content, signature); @@ -536,6 +627,11 @@ mod tests { .zip(deserialized_message.content.encrypted_secret_shares.iter()) .all(|(a, b)| a.0 == b.0)); + assert_eq!( + message.content.proof_of_possession, + deserialized_message.content.proof_of_possession + ); + assert_eq!(message.signature, deserialized_message.signature); } @@ -557,8 +653,10 @@ mod tests { VerifyingShare(PublicKey::from_point(RistrettoPoint::random(&mut rng))), ), ]; + let parameters = Parameters::generate(2, 2); let dkg_output = DKGOutput { + parameters, group_public_key: GroupPublicKey(PublicKey::from_point(group_public_key)), verifying_keys, }; @@ -601,51 +699,6 @@ mod tests { ); } - #[test] - fn test_encryption_decryption() { - let mut rng = OsRng; - let ephemeral_key = Keypair::generate(); - let recipient = Keypair::generate(); - let encryption_nonce = [1; ENCRYPTION_NONCE_LENGTH]; - let key_exchange = ephemeral_key.secret.key * recipient.public.as_point(); - let secret_share = SecretShare(Scalar::random(&mut rng)); - let mut transcript = Transcript::new(b"encryption"); - transcript.commit_point(b"key", &key_exchange.compress()); - let mut key_bytes = [0; CHACHA20POLY1305_KEY_LENGTH]; - transcript.challenge_bytes(b"key", &mut key_bytes); - - let encrypted_share = secret_share.encrypt(&key_bytes, &encryption_nonce).unwrap(); - - encrypted_share.decrypt(&key_bytes, &encryption_nonce).unwrap(); - } - - #[test] - fn test_generate_polynomial_commitment_valid() { - let degree = 3; - - let polynomial = SecretPolynomial::generate(degree, &mut OsRng); - - 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 = SecretPolynomial { 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 { @@ -704,4 +757,14 @@ mod tests { assert_eq!(result, expected, "The evaluated commitment does not match the expected result"); } + + #[test] + fn test_parameters_serialization() { + let params = Parameters::generate(3, 2); + let bytes = params.to_bytes(); + let result = Parameters::from_bytes(&bytes).unwrap(); + + assert_eq!(params.participants, result.participants); + assert_eq!(params.threshold, result.threshold); + } } From d8308e0a8b52cb753a5f89f1ff63a717561f3b33 Mon Sep 17 00:00:00 2001 From: Fiono11 Date: Tue, 14 May 2024 11:25:02 +0100 Subject: [PATCH 29/60] Add test --- src/olaf/frost/errors.rs | 58 +++++++++++++++++++++++++++++++++++++ src/olaf/frost/mod.rs | 4 +-- src/olaf/frost/types.rs | 4 +-- src/olaf/mod.rs | 25 +--------------- src/olaf/simplpedpop/mod.rs | 6 ++-- 5 files changed, 66 insertions(+), 31 deletions(-) diff --git a/src/olaf/frost/errors.rs b/src/olaf/frost/errors.rs index 107431e..85c926c 100644 --- a/src/olaf/frost/errors.rs +++ b/src/olaf/frost/errors.rs @@ -37,11 +37,69 @@ mod tests { olaf::{ frost::types::{NonceCommitment, SigningCommitments}, simplpedpop::{AllMessage, Parameters}, + SigningKeypair, }, Keypair, PublicKey, }; use super::FROSTError; + #[test] + fn test_invalid_own_verifying_share_error() { + let parameters = Parameters::generate(2, 2); + let participants = parameters.participants as usize; + let threshold = parameters.threshold as usize; + + let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public).collect(); + + let mut all_messages = Vec::new(); + for i in 0..participants { + let message: AllMessage = keypairs[i] + .simplpedpop_contribute_all(threshold as u16, public_keys.clone()) + .unwrap(); + all_messages.push(message); + } + + let mut dkg_outputs = Vec::new(); + + for kp in keypairs.iter() { + let dkg_output = kp.simplpedpop_recipient_all(&all_messages).unwrap(); + dkg_outputs.push(dkg_output); + } + + let mut all_signing_commitments = Vec::new(); + let mut all_signing_nonces = Vec::new(); + + for dkg_output in &dkg_outputs { + let (signing_nonces, signing_commitments) = dkg_output.1.commit(&mut OsRng); + all_signing_nonces.push(signing_nonces); + all_signing_commitments.push(signing_commitments); + } + + let message = b"message"; + let context = b"context"; + + dkg_outputs[0].1 = SigningKeypair(Keypair::generate()); + + let result = dkg_outputs[0].1.sign( + context, + message, + &dkg_outputs[0].0.dkg_output, + &all_signing_commitments, + &all_signing_nonces[0], + ); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + FROSTError::InvalidOwnVerifyingShare => assert!(true), + _ => { + panic!("Expected FROSTError::InvalidOwnVerifyingShare, but got {:?}", e) + }, + }, + } + } + #[test] fn test_incorrect_number_of_verifying_shares_error() { let parameters = Parameters::generate(2, 2); diff --git a/src/olaf/frost/mod.rs b/src/olaf/frost/mod.rs index d242224..3109604 100644 --- a/src/olaf/frost/mod.rs +++ b/src/olaf/frost/mod.rs @@ -18,9 +18,9 @@ use self::{ }, }; -use super::{simplpedpop::DKGOutput, GroupPublicKey, SigningKeyPair, VerifyingShare}; +use super::{simplpedpop::DKGOutput, GroupPublicKey, SigningKeypair, VerifyingShare}; -impl SigningKeyPair { +impl SigningKeypair { /// Done once by each participant, to generate _their_ nonces and commitments /// that are then used during signing. /// diff --git a/src/olaf/frost/types.rs b/src/olaf/frost/types.rs index 8d1ad1f..6760461 100644 --- a/src/olaf/frost/types.rs +++ b/src/olaf/frost/types.rs @@ -256,7 +256,7 @@ pub(super) fn compute_binding_factor_list( pub(super) fn derive_interpolating_value( signer_id: &Identifier, - identifiers: BTreeSet, + identifiers: Vec, ) -> Result { compute_lagrange_coefficient(&identifiers, None, *signer_id) } @@ -273,7 +273,7 @@ pub(super) fn derive_interpolating_value( /// /// If `x` is None, it uses 0 for it (since Identifiers can't be 0). pub(super) fn compute_lagrange_coefficient( - x_set: &BTreeSet, + x_set: &Vec, x: Option, x_i: Identifier, ) -> Result { diff --git a/src/olaf/mod.rs b/src/olaf/mod.rs index abdffcf..7249a38 100644 --- a/src/olaf/mod.rs +++ b/src/olaf/mod.rs @@ -4,11 +4,8 @@ mod simplpedpop; mod frost; -use core::cmp::Ordering; - use curve25519_dalek::{constants::RISTRETTO_BASEPOINT_POINT, RistrettoPoint, Scalar}; use merlin::Transcript; - use crate::{context::SigningTranscript, Keypair, PublicKey}; pub(super) const MINIMUM_THRESHOLD: u16 = 2; @@ -24,7 +21,7 @@ pub struct VerifyingShare(pub(crate) PublicKey); /// The signing keypair of a participant in the Olaf protocol, used to produce its signature share. #[derive(Clone, Debug)] -pub struct SigningKeyPair(pub(crate) Keypair); +pub struct SigningKeypair(pub(crate) Keypair); /// The identifier of a participant in the Olaf protocol. #[derive(Copy, Clone, Debug, PartialEq, Eq)] @@ -39,23 +36,3 @@ impl Identifier { Identifier(pos.challenge_scalar(b"evaluation position")) } } - -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()) - } -} diff --git a/src/olaf/simplpedpop/mod.rs b/src/olaf/simplpedpop/mod.rs index 160ac50..1234c93 100644 --- a/src/olaf/simplpedpop/mod.rs +++ b/src/olaf/simplpedpop/mod.rs @@ -22,7 +22,7 @@ use self::{ RECIPIENTS_HASH_LENGTH, }, }; -use super::{GroupPublicKey, Identifier, SigningKeyPair, VerifyingShare, GENERATOR}; +use super::{GroupPublicKey, Identifier, SigningKeypair, VerifyingShare, GENERATOR}; impl Keypair { /// First round of the SimplPedPoP protocol. @@ -143,7 +143,7 @@ impl Keypair { pub fn simplpedpop_recipient_all( &self, messages: &[AllMessage], - ) -> SPPResult<(DKGOutputMessage, SigningKeyPair)> { + ) -> SPPResult<(DKGOutputMessage, SigningKeypair)> { let first_message = &messages[0]; let parameters = &first_message.content.parameters; let threshold = parameters.threshold as usize; @@ -298,7 +298,7 @@ impl Keypair { let keypair = Keypair::from(secret_key); - Ok((dkg_output, SigningKeyPair(keypair))) + Ok((dkg_output, SigningKeypair(keypair))) } } From 3bf17d162eeb15a549175f7876196026c45db162 Mon Sep 17 00:00:00 2001 From: Fiono11 Date: Tue, 14 May 2024 11:25:17 +0100 Subject: [PATCH 30/60] Add test --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 43eef8d..5208751 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -248,7 +248,7 @@ pub mod derive; pub mod cert; pub mod errors; -//#[cfg(all(feature = "alloc", feature = "aead"))] +#[cfg(all(feature = "alloc", feature = "aead"))] pub mod olaf; #[cfg(all(feature = "aead", feature = "getrandom"))] From f5b0f4106c5642dcd54b300909501621fa967ac3 Mon Sep 17 00:00:00 2001 From: Fiono11 Date: Tue, 14 May 2024 12:07:16 +0100 Subject: [PATCH 31/60] Add frost benchmarks --- benches/olaf_benchmarks.rs | 105 +++++++++++++++++++++++++++++++--- src/olaf/frost/mod.rs | 18 ++++++ src/olaf/frost/types.rs | 9 +-- src/olaf/mod.rs | 6 +- src/olaf/simplpedpop/types.rs | 7 ++- 5 files changed, 127 insertions(+), 18 deletions(-) diff --git a/benches/olaf_benchmarks.rs b/benches/olaf_benchmarks.rs index e4f3643..89e65dc 100644 --- a/benches/olaf_benchmarks.rs +++ b/benches/olaf_benchmarks.rs @@ -1,17 +1,14 @@ use criterion::criterion_main; mod olaf_benches { + use rand_core::OsRng; use criterion::{criterion_group, BenchmarkId, Criterion}; - use schnorrkel::{olaf::AllMessage, Keypair, PublicKey}; + use schnorrkel::olaf::{simplpedpop::AllMessage, frost::aggregate}; + use schnorrkel::keys::{PublicKey, Keypair}; 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(300)); - for &n in [3, 10, 100, 1000].iter() { let participants = n; let threshold = (n * 2 + 2) / 3; @@ -46,11 +43,105 @@ mod olaf_benches { group.finish(); } + fn benchmark_frost(c: &mut Criterion) { + let mut group = c.benchmark_group("FROST"); + + for &n in [3, 10, 100, 1000].iter() { + let participants = n; + let threshold = (n * 2 + 2) / 3; + + let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public).collect(); + + // Each participant creates an AllMessage + let mut all_messages = Vec::new(); + for i in 0..participants { + let message: AllMessage = keypairs[i] + .simplpedpop_contribute_all(threshold as u16, public_keys.clone()) + .unwrap(); + all_messages.push(message); + } + + let mut dkg_outputs = Vec::new(); + + for kp in keypairs.iter() { + let dkg_output = kp.simplpedpop_recipient_all(&all_messages).unwrap(); + dkg_outputs.push(dkg_output); + } + + let mut all_signing_commitments = Vec::new(); + let mut all_signing_nonces = Vec::new(); + + for dkg_output in &dkg_outputs { + let (signing_nonces, signing_commitments) = dkg_output.1.commit(&mut OsRng); + all_signing_nonces.push(signing_nonces); + all_signing_commitments.push(signing_commitments); + } + + group.bench_function(BenchmarkId::new("round1", participants), |b| { + b.iter(|| { + dkg_outputs[0].1.commit(&mut OsRng); + }) + }); + + let mut signature_shares = Vec::new(); + + let message = b"message"; + let context = b"context"; + + group.bench_function(BenchmarkId::new("round2", participants), |b| { + b.iter(|| { + dkg_outputs[0] + .1 + .sign( + context, + message, + &dkg_outputs[0].0.dkg_output, + &all_signing_commitments, + &all_signing_nonces[0], + ) + .unwrap(); + }) + }); + + for (i, dkg_output) in dkg_outputs.iter().enumerate() { + let signature_share = dkg_output + .1 + .sign( + context, + message, + &dkg_output.0.dkg_output, + &all_signing_commitments, + &all_signing_nonces[i], + ) + .unwrap(); + + signature_shares.push(signature_share); + } + + group.bench_function(BenchmarkId::new("aggregate", participants), |b| { + b.iter(|| { + aggregate( + message, + context, + &all_signing_commitments, + &signature_shares, + dkg_outputs[0].0.dkg_output.group_public_key, + ) + .unwrap(); + }) + }); + } + + group.finish(); + } + criterion_group! { name = olaf_benches; config = Criterion::default(); targets = - benchmark_simplpedpop, + //benchmark_simplpedpop, + benchmark_frost, } } diff --git a/src/olaf/frost/mod.rs b/src/olaf/frost/mod.rs index 3109604..0122ffa 100644 --- a/src/olaf/frost/mod.rs +++ b/src/olaf/frost/mod.rs @@ -165,6 +165,24 @@ impl SigningKeypair { } } +/// Aggregates the signature shares to produce a final signature that +/// can be verified with the group public key. +/// +/// `signature_shares` maps the identifier of each participant to the +/// [`round2::SignatureShare`] they sent. These identifiers must come from whatever mapping +/// the coordinator has between communication channels and participants, i.e. +/// they must have assurance that the [`round2::SignatureShare`] came from +/// the participant with that identifier. +/// +/// This operation is performed by a coordinator that can communicate with all +/// the signing participants before publishing the final signature. The +/// coordinator can be one of the participants or a semi-trusted third party +/// (who is trusted to not perform denial of service attacks, but does not learn +/// any secret information). Note that because the coordinator is trusted to +/// report misbehaving parties in order to avoid publishing an invalid +/// signature, if the coordinator themselves is a signer and misbehaves, they +/// can avoid that step. However, at worst, this results in a denial of +/// service attack due to publishing an invalid signature. pub fn aggregate( message: &[u8], context: &[u8], diff --git a/src/olaf/frost/types.rs b/src/olaf/frost/types.rs index 6760461..52ee7e7 100644 --- a/src/olaf/frost/types.rs +++ b/src/olaf/frost/types.rs @@ -1,6 +1,6 @@ //! Internal types of the FROST protocol. -use alloc::{collections::BTreeSet, vec::Vec}; +use alloc::vec::Vec; use curve25519_dalek::{ traits::{Identity, VartimeMultiscalarMul}, RistrettoPoint, Scalar, @@ -185,11 +185,6 @@ impl From<&SigningNonces> for SigningCommitments { } } -/// One signer's share of the group commitment, derived from their individual signing commitments -/// and the binding factor _rho_. -#[derive(Clone, Copy, PartialEq)] -pub(super) struct GroupCommitmentShare(pub(super) RistrettoPoint); - /// The product of all signers' individual commitments, published as part of the /// final signature. #[derive(Clone, PartialEq, Eq, Debug)] @@ -273,7 +268,7 @@ pub(super) fn derive_interpolating_value( /// /// If `x` is None, it uses 0 for it (since Identifiers can't be 0). pub(super) fn compute_lagrange_coefficient( - x_set: &Vec, + x_set: &[Identifier], x: Option, x_i: Identifier, ) -> Result { diff --git a/src/olaf/mod.rs b/src/olaf/mod.rs index 7249a38..55b4b9e 100644 --- a/src/olaf/mod.rs +++ b/src/olaf/mod.rs @@ -1,8 +1,10 @@ //! Implementation of the Olaf protocol (), which is composed of the Distributed //! Key Generation (DKG) protocol SimplPedPoP and the Threshold Signing protocol FROST. -mod simplpedpop; -mod frost; +/// Implementation of the SimplPedPoP protocol. +pub mod simplpedpop; +/// Implementation of the FROST protocol. +pub mod frost; use curve25519_dalek::{constants::RISTRETTO_BASEPOINT_POINT, RistrettoPoint, Scalar}; use merlin::Transcript; diff --git a/src/olaf/simplpedpop/types.rs b/src/olaf/simplpedpop/types.rs index e9b55da..4db4ea8 100644 --- a/src/olaf/simplpedpop/types.rs +++ b/src/olaf/simplpedpop/types.rs @@ -139,6 +139,7 @@ impl Parameters { t.commit_bytes(b"participants", &self.participants.to_le_bytes()); } + /// Serializes `Parameters` into a byte array. pub fn to_bytes(&self) -> [u8; U16_LENGTH * 2] { let mut bytes = [0u8; U16_LENGTH * 2]; bytes[0..U16_LENGTH].copy_from_slice(&self.participants.to_le_bytes()); @@ -378,7 +379,8 @@ impl MessageContent { #[derive(Debug)] pub struct DKGOutputMessage { pub(crate) sender: PublicKey, - pub(crate) dkg_output: DKGOutput, + /// The output of the SimplPedPoP protocol. + pub dkg_output: DKGOutput, pub(crate) signature: Signature, } @@ -427,7 +429,8 @@ impl DKGOutputMessage { #[derive(Clone, Debug)] pub struct DKGOutput { pub(crate) parameters: Parameters, - pub(crate) group_public_key: GroupPublicKey, + /// The group public key generated by the SimplPedPoP protocol. + pub group_public_key: GroupPublicKey, pub(crate) verifying_keys: Vec<(Identifier, VerifyingShare)>, } From 8e779fc4b77e0349475cd7a8bd3d64fdee078e37 Mon Sep 17 00:00:00 2001 From: Fiono11 Date: Tue, 14 May 2024 12:56:39 +0100 Subject: [PATCH 32/60] Refractoring --- benches/olaf_benchmarks.rs | 2 - src/olaf/frost/mod.rs | 47 ++++--- src/olaf/frost/types.rs | 251 ++++++++++++---------------------- src/olaf/simplpedpop/mod.rs | 5 +- src/olaf/simplpedpop/types.rs | 39 +++++- 5 files changed, 162 insertions(+), 182 deletions(-) diff --git a/benches/olaf_benchmarks.rs b/benches/olaf_benchmarks.rs index 89e65dc..a4ca67c 100644 --- a/benches/olaf_benchmarks.rs +++ b/benches/olaf_benchmarks.rs @@ -16,7 +16,6 @@ mod olaf_benches { let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); let public_keys: Vec = keypairs.iter().map(|kp| kp.public).collect(); - // Each participant creates an AllMessage let mut all_messages = Vec::new(); for i in 0..participants { let message: AllMessage = keypairs[i] @@ -53,7 +52,6 @@ mod olaf_benches { let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); let public_keys: Vec = keypairs.iter().map(|kp| kp.public).collect(); - // Each participant creates an AllMessage let mut all_messages = Vec::new(); for i in 0..participants { let message: AllMessage = keypairs[i] diff --git a/src/olaf/frost/mod.rs b/src/olaf/frost/mod.rs index 0122ffa..bcee7d4 100644 --- a/src/olaf/frost/mod.rs +++ b/src/olaf/frost/mod.rs @@ -8,17 +8,22 @@ mod errors; use alloc::vec::Vec; use curve25519_dalek::Scalar; use rand_core::{CryptoRng, RngCore}; -use crate::Signature; +use crate::{ + context::{SigningContext, SigningTranscript}, + Signature, +}; use self::{ errors::{FROSTError, FROSTResult}, types::{ - challenge, compute_binding_factor_list, compute_group_commitment, - derive_interpolating_value, BindingFactor, BindingFactorList, Challenge, SignatureShare, - SigningCommitments, SigningNonces, + BindingFactor, BindingFactorList, GroupCommitment, SignatureShare, SigningCommitments, + SigningNonces, }, }; -use super::{simplpedpop::DKGOutput, GroupPublicKey, SigningKeypair, VerifyingShare}; +use super::{ + simplpedpop::{DKGOutput, SecretPolynomial}, + GroupPublicKey, SigningKeypair, VerifyingShare, +}; impl SigningKeypair { /// Done once by each participant, to generate _their_ nonces and commitments @@ -123,22 +128,32 @@ impl SigningKeypair { return Err(FROSTError::InvalidNumberOfSigningCommitments); } - let binding_factor_list: BindingFactorList = compute_binding_factor_list( + let binding_factor_list: BindingFactorList = BindingFactorList::compute( all_signing_commitments, &dkg_output.group_public_key, message, ); let group_commitment = - compute_group_commitment(all_signing_commitments, &binding_factor_list)?; + GroupCommitment::compute(all_signing_commitments, &binding_factor_list)?; - let lambda_i = derive_interpolating_value( - identifiers[index], - dkg_output.verifying_keys.iter().map(|x| x.0).collect(), - )?; + let identifiers_vec: Vec<_> = dkg_output.verifying_keys.iter().map(|x| x.0).collect(); + + let lambda_i = SecretPolynomial::compute_lagrange_coefficient( + &identifiers_vec, + None, + *identifiers[index], + ); - let challenge = - challenge(&group_commitment.0, &dkg_output.group_public_key, context, message); + let mut transcript = SigningContext::new(context).bytes(message); + transcript.proto_name(b"Schnorr-sig"); + { + let this = &mut transcript; + let compressed = dkg_output.group_public_key.0.as_compressed(); + this.append_message(b"sign:pk", compressed.as_bytes()); + }; + transcript.commit_point(b"sign:R", &group_commitment.0.compress()); + let challenge = transcript.challenge_scalar(b"sign:c"); let signature_share = self.compute_signature_share( signer_nonces, @@ -155,7 +170,7 @@ impl SigningKeypair { signer_nonces: &SigningNonces, binding_factor: &BindingFactor, lambda_i: &Scalar, - challenge: &Challenge, + challenge: &Scalar, ) -> SignatureShare { let z_share: Scalar = signer_nonces.hiding.0 + (signer_nonces.binding.0 * binding_factor.0) @@ -195,9 +210,9 @@ pub fn aggregate( } let binding_factor_list: BindingFactorList = - compute_binding_factor_list(signing_commitments, &group_public_key, message); + BindingFactorList::compute(signing_commitments, &group_public_key, message); - let group_commitment = compute_group_commitment(signing_commitments, &binding_factor_list)?; + let group_commitment = GroupCommitment::compute(signing_commitments, &binding_factor_list)?; let mut s = Scalar::ZERO; diff --git a/src/olaf/frost/types.rs b/src/olaf/frost/types.rs index 52ee7e7..91fff18 100644 --- a/src/olaf/frost/types.rs +++ b/src/olaf/frost/types.rs @@ -9,8 +9,8 @@ use merlin::Transcript; use rand_core::{CryptoRng, RngCore}; use zeroize::ZeroizeOnDrop; use crate::{ - context::{SigningContext, SigningTranscript}, - olaf::{GroupPublicKey, Identifier, GENERATOR}, + context::SigningTranscript, + olaf::{GroupPublicKey, GENERATOR}, SecretKey, }; use super::errors::FROSTError; @@ -22,31 +22,11 @@ pub struct SignatureShare { pub(super) share: Scalar, } -pub(super) type Challenge = Scalar; - -/// Generates the challenge as is required for Schnorr signatures. -pub(super) fn challenge( - R: &RistrettoPoint, - verifying_key: &GroupPublicKey, - context: &[u8], - msg: &[u8], -) -> Challenge { - let mut transcript = SigningContext::new(context).bytes(msg); - - transcript.proto_name(b"Schnorr-sig"); - transcript.commit_point(b"sign:pk", verifying_key.0.as_compressed()); - transcript.commit_point(b"sign:R", &R.compress()); - transcript.challenge_scalar(b"sign:c") -} - /// The binding factor, also known as _rho_ (ρ), ensures each signature share is strongly bound to a signing set, specific set /// of commitments, and a specific message. -#[derive(Clone, PartialEq, Eq, Debug)] pub(super) struct BindingFactor(pub(super) Scalar); /// A list of binding factors and their associated identifiers. -#[derive(Clone, Debug)] -//pub(super) struct BindingFactorList(pub(super) Vec); pub(super) struct BindingFactorList(pub(super) Vec<(u16, BindingFactor)>); impl BindingFactorList { @@ -54,6 +34,68 @@ impl BindingFactorList { pub(super) fn new(binding_factors: Vec<(u16, BindingFactor)>) -> Self { Self(binding_factors) } + + pub(super) fn compute( + signing_commitments: &[SigningCommitments], + verifying_key: &GroupPublicKey, + message: &[u8], + ) -> BindingFactorList { + let mut transcripts = BindingFactorList::binding_factor_transcripts( + signing_commitments, + verifying_key, + message, + ); + + BindingFactorList::new( + transcripts + .iter_mut() + .map(|(identifier, transcript)| { + let binding_factor = transcript.challenge_scalar(b"binding factor"); + + (*identifier, BindingFactor(binding_factor)) + }) + .collect(), + ) + } + + fn binding_factor_transcripts( + signing_commitments: &[SigningCommitments], + verifying_key: &GroupPublicKey, + message: &[u8], + ) -> Vec<(u16, Transcript)> { + let mut transcript = Transcript::new(b"binding_factor"); + + transcript.commit_point(b"verifying_key", verifying_key.0.as_compressed()); + + transcript.append_message(b"message", message); + + transcript.append_message( + b"group_commitment", + BindingFactorList::encode_group_commitments(signing_commitments) + .challenge_scalar(b"encode_group_commitments") + .as_bytes(), + ); + + signing_commitments + .iter() + .enumerate() + .map(|(i, _)| { + transcript.append_message(b"identifier", &i.to_le_bytes()); + (i as u16, transcript.clone()) + }) + .collect() + } + + fn encode_group_commitments(signing_commitments: &[SigningCommitments]) -> Transcript { + let mut transcript = Transcript::new(b"encode_group_commitments"); + + for item in signing_commitments { + transcript.commit_point(b"hiding", &item.hiding.0.compress()); + transcript.commit_point(b"binding", &item.binding.0.compress()); + } + + transcript + } } /// A scalar that is a signing nonce. @@ -165,11 +207,8 @@ impl SigningNonces { /// SigningCommitment can be used for exactly *one* signature. #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub struct SigningCommitments { - /// Commitment to the hiding [`Nonce`]. pub(super) hiding: NonceCommitment, - /// Commitment to the binding [`Nonce`]. pub(super) binding: NonceCommitment, - //pub(super) identifier: Identifier, } impl SigningCommitments { @@ -187,154 +226,46 @@ impl From<&SigningNonces> for SigningCommitments { /// The product of all signers' individual commitments, published as part of the /// final signature. -#[derive(Clone, PartialEq, Eq, Debug)] pub(super) struct GroupCommitment(pub(super) RistrettoPoint); -pub(super) fn compute_group_commitment( - signing_commitments: &[SigningCommitments], - binding_factor_list: &BindingFactorList, -) -> Result { - let identity = RistrettoPoint::identity(); - - let mut group_commitment = RistrettoPoint::identity(); - - // Number of signing participants we are iterating over. - let signers = signing_commitments.len(); - - let mut binding_scalars = Vec::with_capacity(signers); +impl GroupCommitment { + pub(super) fn compute( + signing_commitments: &[SigningCommitments], + binding_factor_list: &BindingFactorList, + ) -> Result { + let identity = RistrettoPoint::identity(); - let mut binding_elements = Vec::with_capacity(signers); + let mut group_commitment = RistrettoPoint::identity(); - for (i, commitment) in signing_commitments.iter().enumerate() { - // The following check prevents a party from accidentally revealing their share. - // Note that the '&&' operator would be sufficient. - if identity == commitment.binding.0 || identity == commitment.hiding.0 { - return Err(FROSTError::IdentitySigningCommitment); - } - - let binding_factor = &binding_factor_list.0[i]; + // Number of signing participants we are iterating over. + let signers = signing_commitments.len(); - // Collect the binding commitments and their binding factors for one big - // multiscalar multiplication at the end. - binding_elements.push(commitment.binding.0); - binding_scalars.push(binding_factor.1 .0); + let mut binding_scalars = Vec::with_capacity(signers); - group_commitment += commitment.hiding.0; - } + let mut binding_elements = Vec::with_capacity(signers); - let accumulated_binding_commitment: RistrettoPoint = - RistrettoPoint::vartime_multiscalar_mul(binding_scalars, binding_elements); + for (i, commitment) in signing_commitments.iter().enumerate() { + // The following check prevents a party from accidentally revealing their share. + // Note that the '&&' operator would be sufficient. + if identity == commitment.binding.0 || identity == commitment.hiding.0 { + return Err(FROSTError::IdentitySigningCommitment); + } - group_commitment += accumulated_binding_commitment; - - Ok(GroupCommitment(group_commitment)) -} + let binding_factor = &binding_factor_list.0[i]; -pub(super) fn compute_binding_factor_list( - signing_commitments: &[SigningCommitments], - verifying_key: &GroupPublicKey, - message: &[u8], -) -> BindingFactorList { - let mut transcripts = binding_factor_transcripts(signing_commitments, verifying_key, message); + // Collect the binding commitments and their binding factors for one big + // multiscalar multiplication at the end. + binding_elements.push(commitment.binding.0); + binding_scalars.push(binding_factor.1 .0); - BindingFactorList::new( - transcripts - .iter_mut() - .map(|(identifier, transcript)| { - let binding_factor = transcript.challenge_scalar(b"binding factor"); - - (*identifier, BindingFactor(binding_factor)) - }) - .collect(), - ) -} - -pub(super) fn derive_interpolating_value( - signer_id: &Identifier, - identifiers: Vec, -) -> Result { - compute_lagrange_coefficient(&identifiers, None, *signer_id) -} - -/// Generates a lagrange coefficient. -/// -/// The Lagrange polynomial for a set of points (x_j, y_j) for 0 <= j <= k -/// is ∑_{i=0}^k y_i.ℓ_i(x), where ℓ_i(x) is the Lagrange basis polynomial: -/// -/// ℓ_i(x) = ∏_{0≤j≤k; j≠i} (x - x_j) / (x_i - x_j). -/// -/// This computes ℓ_j(x) for the set of points `xs` and for the j corresponding -/// to the given xj. -/// -/// If `x` is None, it uses 0 for it (since Identifiers can't be 0). -pub(super) fn compute_lagrange_coefficient( - x_set: &[Identifier], - x: Option, - x_i: Identifier, -) -> Result { - let mut num = Scalar::ONE; - let mut den = Scalar::ONE; - - for x_j in x_set.iter() { - if x_i == *x_j { - continue; + group_commitment += commitment.hiding.0; } - if let Some(x) = x { - num *= x.0 - x_j.0; - den *= x_i.0 - x_j.0; - } else { - // Both signs inverted just to avoid requiring Neg (-*xj) - num *= x_j.0; - den *= x_j.0 - x_i.0; - } - } - - //if !x_i_found { - //return Err(FROSTError::UnknownIdentifier); - //} + let accumulated_binding_commitment: RistrettoPoint = + RistrettoPoint::vartime_multiscalar_mul(binding_scalars, binding_elements); - let inverse = num * den.invert(); - - Ok(inverse) -} + group_commitment += accumulated_binding_commitment; -pub(super) fn binding_factor_transcripts( - signing_commitments: &[SigningCommitments], - verifying_key: &GroupPublicKey, - message: &[u8], -) -> Vec<(u16, Transcript)> { - let mut transcript = Transcript::new(b"binding_factor"); - - transcript.commit_point(b"verifying_key", verifying_key.0.as_compressed()); - - transcript.append_message(b"message", message); - - transcript.append_message( - b"group_commitment", - encode_group_commitments(signing_commitments) - .challenge_scalar(b"encode_group_commitments") - .as_bytes(), - ); - - signing_commitments - .iter() - .enumerate() - .map(|(i, _)| { - transcript.append_message(b"identifier", &i.to_le_bytes()); - (i as u16, transcript.clone()) - }) - .collect() -} - -pub(super) fn encode_group_commitments(signing_commitments: &[SigningCommitments]) -> Transcript { - let mut transcript = Transcript::new(b"encode_group_commitments"); - - for item in signing_commitments { - //transcript.append_message(b"identifier", item.identifier.0.as_bytes()); - transcript.commit_point(b"hiding", &item.hiding.0.compress()); - transcript.commit_point(b"binding", &item.binding.0.compress()); + Ok(GroupCommitment(group_commitment)) } - - transcript } diff --git a/src/olaf/simplpedpop/mod.rs b/src/olaf/simplpedpop/mod.rs index 1234c93..da83402 100644 --- a/src/olaf/simplpedpop/mod.rs +++ b/src/olaf/simplpedpop/mod.rs @@ -7,7 +7,7 @@ mod errors; pub use self::types::{ AllMessage, DKGOutput, DKGOutputMessage, MessageContent, Parameters, PolynomialCommitment, }; - +pub(crate) use self::types::SecretPolynomial; use alloc::vec::Vec; use curve25519_dalek::{traits::Identity, RistrettoPoint, Scalar}; use merlin::Transcript; @@ -18,8 +18,7 @@ use crate::{ use self::{ errors::{SPPError, SPPResult}, types::{ - SecretPolynomial, SecretShare, CHACHA20POLY1305_KEY_LENGTH, ENCRYPTION_NONCE_LENGTH, - RECIPIENTS_HASH_LENGTH, + SecretShare, CHACHA20POLY1305_KEY_LENGTH, ENCRYPTION_NONCE_LENGTH, RECIPIENTS_HASH_LENGTH, }, }; use super::{GroupPublicKey, Identifier, SigningKeypair, VerifyingShare, GENERATOR}; diff --git a/src/olaf/simplpedpop/types.rs b/src/olaf/simplpedpop/types.rs index 4db4ea8..c73432c 100644 --- a/src/olaf/simplpedpop/types.rs +++ b/src/olaf/simplpedpop/types.rs @@ -67,7 +67,7 @@ impl SecretShare { /// The secret polynomial of a participant chosen at randoma nd used to generate the secret shares of all the participants (including itself). #[derive(ZeroizeOnDrop)] -pub(super) struct SecretPolynomial { +pub(crate) struct SecretPolynomial { pub(super) coefficients: Vec, } @@ -103,6 +103,43 @@ impl SecretPolynomial { PolynomialCommitment { coefficients_commitments } } + + /// Generates a lagrange coefficient. + /// + /// The Lagrange polynomial for a set of points (x_j, y_j) for 0 <= j <= k + /// is ∑_{i=0}^k y_i.ℓ_i(x), where ℓ_i(x) is the Lagrange basis polynomial: + /// + /// ℓ_i(x) = ∏_{0≤j≤k; j≠i} (x - x_j) / (x_i - x_j). + /// + /// This computes ℓ_j(x) for the set of points `xs` and for the j corresponding + /// to the given xj. + /// + /// If `x` is None, it uses 0 for it (since Identifiers can't be 0). + pub(crate) fn compute_lagrange_coefficient( + x_set: &[Identifier], + x: Option, + x_i: Identifier, + ) -> Scalar { + let mut num = Scalar::ONE; + let mut den = Scalar::ONE; + + for x_j in x_set.iter() { + if x_i == *x_j { + continue; + } + + if let Some(x) = x { + num *= x.0 - x_j.0; + den *= x_i.0 - x_j.0; + } else { + // Both signs inverted just to avoid requiring Neg (-*xj) + num *= x_j.0; + den *= x_j.0 - x_i.0; + } + } + + num * den.invert() + } } /// The parameters of a given execution of the SimplPedPoP protocol. From 206d051cee9eba557b163835229ad833296c33a1 Mon Sep 17 00:00:00 2001 From: Fiono11 Date: Thu, 16 May 2024 11:56:36 +0100 Subject: [PATCH 33/60] Improvements --- benches/olaf_benchmarks.rs | 24 +- src/olaf/frost/errors.rs | 75 +++--- src/olaf/frost/mod.rs | 76 +++--- src/olaf/frost/types.rs | 6 +- src/olaf/mod.rs | 10 +- src/olaf/simplpedpop/errors.rs | 453 +++++++++++++++++---------------- src/olaf/simplpedpop/mod.rs | 420 ++++++------------------------ src/olaf/simplpedpop/types.rs | 100 +++++--- 8 files changed, 467 insertions(+), 697 deletions(-) diff --git a/benches/olaf_benchmarks.rs b/benches/olaf_benchmarks.rs index a4ca67c..6ae9c26 100644 --- a/benches/olaf_benchmarks.rs +++ b/benches/olaf_benchmarks.rs @@ -60,25 +60,25 @@ mod olaf_benches { all_messages.push(message); } - let mut dkg_outputs = Vec::new(); + let mut spp_outputs = Vec::new(); for kp in keypairs.iter() { - let dkg_output = kp.simplpedpop_recipient_all(&all_messages).unwrap(); - dkg_outputs.push(dkg_output); + let spp_output = kp.simplpedpop_recipient_all(&all_messages).unwrap(); + spp_outputs.push(spp_output); } let mut all_signing_commitments = Vec::new(); let mut all_signing_nonces = Vec::new(); - for dkg_output in &dkg_outputs { - let (signing_nonces, signing_commitments) = dkg_output.1.commit(&mut OsRng); + for spp_output in &spp_outputs { + let (signing_nonces, signing_commitments) = spp_output.1.commit(&mut OsRng); all_signing_nonces.push(signing_nonces); all_signing_commitments.push(signing_commitments); } group.bench_function(BenchmarkId::new("round1", participants), |b| { b.iter(|| { - dkg_outputs[0].1.commit(&mut OsRng); + spp_outputs[0].1.commit(&mut OsRng); }) }); @@ -89,12 +89,12 @@ mod olaf_benches { group.bench_function(BenchmarkId::new("round2", participants), |b| { b.iter(|| { - dkg_outputs[0] + spp_outputs[0] .1 .sign( context, message, - &dkg_outputs[0].0.dkg_output, + spp_outputs[0].0.spp_output(), &all_signing_commitments, &all_signing_nonces[0], ) @@ -102,13 +102,13 @@ mod olaf_benches { }) }); - for (i, dkg_output) in dkg_outputs.iter().enumerate() { - let signature_share = dkg_output + for (i, spp_output) in spp_outputs.iter().enumerate() { + let signature_share = spp_output .1 .sign( context, message, - &dkg_output.0.dkg_output, + spp_output.0.spp_output(), &all_signing_commitments, &all_signing_nonces[i], ) @@ -124,7 +124,7 @@ mod olaf_benches { context, &all_signing_commitments, &signature_shares, - dkg_outputs[0].0.dkg_output.group_public_key, + spp_outputs[0].0.spp_output().threshold_public_key(), ) .unwrap(); }) diff --git a/src/olaf/frost/errors.rs b/src/olaf/frost/errors.rs index 85c926c..78b3877 100644 --- a/src/olaf/frost/errors.rs +++ b/src/olaf/frost/errors.rs @@ -60,18 +60,18 @@ mod tests { all_messages.push(message); } - let mut dkg_outputs = Vec::new(); + let mut spp_outputs = Vec::new(); for kp in keypairs.iter() { - let dkg_output = kp.simplpedpop_recipient_all(&all_messages).unwrap(); - dkg_outputs.push(dkg_output); + let spp_output = kp.simplpedpop_recipient_all(&all_messages).unwrap(); + spp_outputs.push(spp_output); } let mut all_signing_commitments = Vec::new(); let mut all_signing_nonces = Vec::new(); - for dkg_output in &dkg_outputs { - let (signing_nonces, signing_commitments) = dkg_output.1.commit(&mut OsRng); + for spp_output in &spp_outputs { + let (signing_nonces, signing_commitments) = spp_output.1.commit(&mut OsRng); all_signing_nonces.push(signing_nonces); all_signing_commitments.push(signing_commitments); } @@ -79,12 +79,12 @@ mod tests { let message = b"message"; let context = b"context"; - dkg_outputs[0].1 = SigningKeypair(Keypair::generate()); + spp_outputs[0].1 = SigningKeypair(Keypair::generate()); - let result = dkg_outputs[0].1.sign( + let result = spp_outputs[0].1.sign( context, message, - &dkg_outputs[0].0.dkg_output, + &spp_outputs[0].0.spp_output, &all_signing_commitments, &all_signing_nonces[0], ); @@ -117,18 +117,18 @@ mod tests { all_messages.push(message); } - let mut dkg_outputs = Vec::new(); + let mut spp_outputs = Vec::new(); for kp in keypairs.iter() { - let dkg_output = kp.simplpedpop_recipient_all(&all_messages).unwrap(); - dkg_outputs.push(dkg_output); + let spp_output = kp.simplpedpop_recipient_all(&all_messages).unwrap(); + spp_outputs.push(spp_output); } let mut all_signing_commitments = Vec::new(); let mut all_signing_nonces = Vec::new(); - for dkg_output in &dkg_outputs { - let (signing_nonces, signing_commitments) = dkg_output.1.commit(&mut OsRng); + for spp_output in &spp_outputs { + let (signing_nonces, signing_commitments) = spp_output.1.commit(&mut OsRng); all_signing_nonces.push(signing_nonces); all_signing_commitments.push(signing_commitments); } @@ -136,12 +136,12 @@ mod tests { let message = b"message"; let context = b"context"; - dkg_outputs[0].0.dkg_output.verifying_keys.pop(); + spp_outputs[0].0.spp_output.verifying_keys.pop(); - let result = dkg_outputs[0].1.sign( + let result = spp_outputs[0].1.sign( context, message, - &dkg_outputs[0].0.dkg_output, + &spp_outputs[0].0.spp_output, &all_signing_commitments, &all_signing_nonces[0], ); @@ -174,18 +174,18 @@ mod tests { all_messages.push(message); } - let mut dkg_outputs = Vec::new(); + let mut spp_outputs = Vec::new(); for kp in keypairs.iter() { - let dkg_output = kp.simplpedpop_recipient_all(&all_messages).unwrap(); - dkg_outputs.push(dkg_output); + let spp_output = kp.simplpedpop_recipient_all(&all_messages).unwrap(); + spp_outputs.push(spp_output); } let mut all_signing_commitments = Vec::new(); let mut all_signing_nonces = Vec::new(); - for dkg_output in &dkg_outputs { - let (signing_nonces, signing_commitments) = dkg_output.1.commit(&mut OsRng); + for spp_output in &spp_outputs { + let (signing_nonces, signing_commitments) = spp_output.1.commit(&mut OsRng); all_signing_nonces.push(signing_nonces); all_signing_commitments.push(signing_commitments); } @@ -196,13 +196,12 @@ mod tests { all_signing_commitments[0] = SigningCommitments { hiding: NonceCommitment(RistrettoPoint::random(&mut OsRng)), binding: NonceCommitment(RistrettoPoint::random(&mut OsRng)), - //identifier: Identifier(Scalar::random(&mut OsRng)), }; - let result = dkg_outputs[0].1.sign( + let result = spp_outputs[0].1.sign( context, message, - &dkg_outputs[0].0.dkg_output, + &spp_outputs[0].0.spp_output, &all_signing_commitments, &all_signing_nonces[0], ); @@ -235,18 +234,18 @@ mod tests { all_messages.push(message); } - let mut dkg_outputs = Vec::new(); + let mut spp_outputs = Vec::new(); for kp in keypairs.iter() { - let dkg_output = kp.simplpedpop_recipient_all(&all_messages).unwrap(); - dkg_outputs.push(dkg_output); + let spp_output = kp.simplpedpop_recipient_all(&all_messages).unwrap(); + spp_outputs.push(spp_output); } let mut all_signing_commitments = Vec::new(); let mut all_signing_nonces = Vec::new(); - for dkg_output in &dkg_outputs { - let (signing_nonces, signing_commitments) = dkg_output.1.commit(&mut OsRng); + for spp_output in &spp_outputs { + let (signing_nonces, signing_commitments) = spp_output.1.commit(&mut OsRng); all_signing_nonces.push(signing_nonces); all_signing_commitments.push(signing_commitments); } @@ -255,10 +254,10 @@ mod tests { let context = b"context"; all_signing_commitments[1].hiding = NonceCommitment(RistrettoPoint::identity()); - let result = dkg_outputs[0].1.sign( + let result = spp_outputs[0].1.sign( context, message, - &dkg_outputs[0].0.dkg_output, + &spp_outputs[0].0.spp_output, &all_signing_commitments, &all_signing_nonces[0], ); @@ -291,18 +290,18 @@ mod tests { all_messages.push(message); } - let mut dkg_outputs = Vec::new(); + let mut spp_outputs = Vec::new(); for kp in keypairs.iter() { - let dkg_output = kp.simplpedpop_recipient_all(&all_messages).unwrap(); - dkg_outputs.push(dkg_output); + let spp_output = kp.simplpedpop_recipient_all(&all_messages).unwrap(); + spp_outputs.push(spp_output); } let mut all_signing_commitments = Vec::new(); let mut all_signing_nonces = Vec::new(); - for dkg_output in &dkg_outputs { - let (signing_nonces, signing_commitments) = dkg_output.1.commit(&mut OsRng); + for spp_output in &spp_outputs { + let (signing_nonces, signing_commitments) = spp_output.1.commit(&mut OsRng); all_signing_nonces.push(signing_nonces); all_signing_commitments.push(signing_commitments); } @@ -310,10 +309,10 @@ mod tests { let message = b"message"; let context = b"context"; - let result = dkg_outputs[0].1.sign( + let result = spp_outputs[0].1.sign( context, message, - &dkg_outputs[0].0.dkg_output, + &spp_outputs[0].0.spp_output, &all_signing_commitments[..1], &all_signing_nonces[0], ); diff --git a/src/olaf/frost/mod.rs b/src/olaf/frost/mod.rs index bcee7d4..f9dbda5 100644 --- a/src/olaf/frost/mod.rs +++ b/src/olaf/frost/mod.rs @@ -21,8 +21,8 @@ use self::{ }; use super::{ - simplpedpop::{DKGOutput, SecretPolynomial}, - GroupPublicKey, SigningKeypair, VerifyingShare, + simplpedpop::{SPPOutput, SecretPolynomial}, + ThresholdPublicKey, SigningKeypair, VerifyingShare, }; impl SigningKeypair { @@ -92,11 +92,11 @@ impl SigningKeypair { &self, context: &[u8], message: &[u8], - dkg_output: &DKGOutput, + spp_output: &SPPOutput, all_signing_commitments: &[SigningCommitments], signer_nonces: &SigningNonces, ) -> FROSTResult { - if dkg_output.verifying_keys.len() != dkg_output.parameters.participants as usize { + if spp_output.verifying_keys.len() != spp_output.parameters.participants as usize { return Err(FROSTError::IncorrectNumberOfVerifyingShares); } @@ -111,7 +111,7 @@ impl SigningKeypair { let own_verifying_share = VerifyingShare(self.0.public); - for (i, (identifier, share)) in dkg_output.verifying_keys.iter().enumerate() { + for (i, (identifier, share)) in spp_output.verifying_keys.iter().enumerate() { identifiers.push(identifier); shares.push(share); @@ -124,20 +124,20 @@ impl SigningKeypair { return Err(FROSTError::InvalidOwnVerifyingShare); } - if all_signing_commitments.len() < dkg_output.parameters.threshold as usize { + if all_signing_commitments.len() < spp_output.parameters.threshold as usize { return Err(FROSTError::InvalidNumberOfSigningCommitments); } let binding_factor_list: BindingFactorList = BindingFactorList::compute( all_signing_commitments, - &dkg_output.group_public_key, + &spp_output.threshold_public_key, message, ); let group_commitment = GroupCommitment::compute(all_signing_commitments, &binding_factor_list)?; - let identifiers_vec: Vec<_> = dkg_output.verifying_keys.iter().map(|x| x.0).collect(); + let identifiers_vec: Vec<_> = spp_output.verifying_keys.iter().map(|x| x.0).collect(); let lambda_i = SecretPolynomial::compute_lagrange_coefficient( &identifiers_vec, @@ -149,7 +149,7 @@ impl SigningKeypair { transcript.proto_name(b"Schnorr-sig"); { let this = &mut transcript; - let compressed = dkg_output.group_public_key.0.as_compressed(); + let compressed = spp_output.threshold_public_key.0.as_compressed(); this.append_message(b"sign:pk", compressed.as_bytes()); }; transcript.commit_point(b"sign:R", &group_commitment.0.compress()); @@ -203,7 +203,7 @@ pub fn aggregate( context: &[u8], signing_commitments: &[SigningCommitments], signature_shares: &Vec, - group_public_key: GroupPublicKey, + group_public_key: ThresholdPublicKey, ) -> Result { if signing_commitments.len() != signature_shares.len() { return Err(FROSTError::IncorrectNumberOfSigningCommitments); @@ -276,18 +276,18 @@ mod tests { all_messages.push(message); } - let mut dkg_outputs = Vec::new(); + let mut spp_outputs = Vec::new(); for kp in keypairs.iter() { - let dkg_output = kp.simplpedpop_recipient_all(&all_messages).unwrap(); - dkg_outputs.push(dkg_output); + let spp_output = kp.simplpedpop_recipient_all(&all_messages).unwrap(); + spp_outputs.push(spp_output); } let mut all_signing_commitments = Vec::new(); let mut all_signing_nonces = Vec::new(); - for dkg_output in &dkg_outputs { - let (signing_nonces, signing_commitments) = dkg_output.1.commit(&mut OsRng); + for spp_output in &spp_outputs { + let (signing_nonces, signing_commitments) = spp_output.1.commit(&mut OsRng); all_signing_nonces.push(signing_nonces); all_signing_commitments.push(signing_commitments); } @@ -297,13 +297,13 @@ mod tests { let message = b"message"; let context = b"context"; - for (i, dkg_output) in dkg_outputs.iter().enumerate() { - let signature_share = dkg_output + for (i, spp_output) in spp_outputs.iter().enumerate() { + let signature_share = spp_output .1 .sign( context, message, - &dkg_output.0.dkg_output, + &spp_output.0.spp_output, &all_signing_commitments, &all_signing_nonces[i], ) @@ -317,7 +317,7 @@ mod tests { context, &all_signing_commitments, &signature_shares, - dkg_outputs[0].0.dkg_output.group_public_key, + spp_outputs[0].0.spp_output.threshold_public_key, ) .unwrap(); } @@ -339,18 +339,18 @@ mod tests { all_messages.push(message); } - let mut dkg_outputs = Vec::new(); + let mut spp_outputs = Vec::new(); for kp in keypairs.iter() { - let dkg_output = kp.simplpedpop_recipient_all(&all_messages).unwrap(); - dkg_outputs.push(dkg_output); + let spp_output = kp.simplpedpop_recipient_all(&all_messages).unwrap(); + spp_outputs.push(spp_output); } let mut all_signing_commitments = Vec::new(); let mut all_signing_nonces = Vec::new(); - for dkg_output in &dkg_outputs[..threshold] { - let (signing_nonces, signing_commitments) = dkg_output.1.commit(&mut OsRng); + for spp_output in &spp_outputs[..threshold] { + let (signing_nonces, signing_commitments) = spp_output.1.commit(&mut OsRng); all_signing_nonces.push(signing_nonces); all_signing_commitments.push(signing_commitments); } @@ -360,13 +360,13 @@ mod tests { let message = b"message"; let context = b"context"; - for (i, dkg_output) in dkg_outputs[..threshold].iter().enumerate() { - let signature_share = dkg_output + for (i, spp_output) in spp_outputs[..threshold].iter().enumerate() { + let signature_share = spp_output .1 .sign( context, message, - &dkg_output.0.dkg_output, + &spp_output.0.spp_output, &all_signing_commitments, &all_signing_nonces[i], ) @@ -380,7 +380,7 @@ mod tests { context, &all_signing_commitments, &signature_shares, - dkg_outputs[0].0.dkg_output.group_public_key, + spp_outputs[0].0.spp_output.threshold_public_key, ) .unwrap(); } @@ -402,20 +402,20 @@ mod tests { all_messages.push(message); } - let mut dkg_outputs = Vec::new(); + let mut spp_outputs = Vec::new(); for kp in &keypairs { - let dkg_output = kp.simplpedpop_recipient_all(&all_messages).unwrap(); - dkg_outputs.push(dkg_output); + let spp_output = kp.simplpedpop_recipient_all(&all_messages).unwrap(); + spp_outputs.push(spp_output); } - let group_public_key = dkg_outputs[0].0.dkg_output.group_public_key; + let group_public_key = spp_outputs[0].0.spp_output.threshold_public_key; let mut all_nonces_map: Vec> = Vec::new(); let mut all_commitments_map: Vec> = Vec::new(); - for dkg_output in &dkg_outputs { - let (nonces, commitments) = dkg_output.1.preprocess(NONCES, &mut OsRng); + for spp_output in &spp_outputs { + let (nonces, commitments) = spp_output.1.preprocess(NONCES, &mut OsRng); all_nonces_map.push(nonces); all_commitments_map.push(commitments); @@ -427,7 +427,7 @@ mod tests { for i in 0..NONCES { let mut comms = Vec::new(); - for (j, _) in dkg_outputs.iter().enumerate() { + for (j, _) in spp_outputs.iter().enumerate() { nonces.push(&all_nonces_map[j][i as usize]); comms.push(all_commitments_map[j][i as usize].clone()) } @@ -451,12 +451,12 @@ mod tests { let commitments: Vec = commitments[i as usize].clone(); - for (j, dkg_output) in dkg_outputs.iter().enumerate() { + for (j, spp_output) in spp_outputs.iter().enumerate() { let nonces_to_use = &all_nonces_map[j][i as usize]; - let signature_share = dkg_output + let signature_share = spp_output .1 - .sign(context, &message, &dkg_output.0.dkg_output, &commitments, nonces_to_use) + .sign(context, &message, &spp_output.0.spp_output, &commitments, nonces_to_use) .unwrap(); signature_shares.push(signature_share); diff --git a/src/olaf/frost/types.rs b/src/olaf/frost/types.rs index 91fff18..ffa5ba7 100644 --- a/src/olaf/frost/types.rs +++ b/src/olaf/frost/types.rs @@ -10,7 +10,7 @@ use rand_core::{CryptoRng, RngCore}; use zeroize::ZeroizeOnDrop; use crate::{ context::SigningTranscript, - olaf::{GroupPublicKey, GENERATOR}, + olaf::{ThresholdPublicKey, GENERATOR}, SecretKey, }; use super::errors::FROSTError; @@ -37,7 +37,7 @@ impl BindingFactorList { pub(super) fn compute( signing_commitments: &[SigningCommitments], - verifying_key: &GroupPublicKey, + verifying_key: &ThresholdPublicKey, message: &[u8], ) -> BindingFactorList { let mut transcripts = BindingFactorList::binding_factor_transcripts( @@ -60,7 +60,7 @@ impl BindingFactorList { fn binding_factor_transcripts( signing_commitments: &[SigningCommitments], - verifying_key: &GroupPublicKey, + verifying_key: &ThresholdPublicKey, message: &[u8], ) -> Vec<(u16, Transcript)> { let mut transcript = Transcript::new(b"binding_factor"); diff --git a/src/olaf/mod.rs b/src/olaf/mod.rs index 55b4b9e..de7125e 100644 --- a/src/olaf/mod.rs +++ b/src/olaf/mod.rs @@ -13,19 +13,19 @@ use crate::{context::SigningTranscript, Keypair, PublicKey}; pub(super) const MINIMUM_THRESHOLD: u16 = 2; pub(super) const GENERATOR: RistrettoPoint = RISTRETTO_BASEPOINT_POINT; -/// The group public key used by the Olaf protocol. +/// The threshold public key generated in the SimplPedPoP protocol, used to validate the threshold signatures of the FROST protocol. #[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub struct GroupPublicKey(pub(crate) PublicKey); +pub struct ThresholdPublicKey(pub(crate) PublicKey); -/// The verifying share of a participant in the Olaf protocol, used to verify its signature share. +/// The verifying share of a participant generated in the SimplPedPoP protocol, used to verify its signatures shares in the FROST protocol. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct VerifyingShare(pub(crate) PublicKey); -/// The signing keypair of a participant in the Olaf protocol, used to produce its signature share. +/// The signing keypair of a participant generated in the SimplPedPoP protocol, used to produce its signatures shares in the FROST protocol. #[derive(Clone, Debug)] pub struct SigningKeypair(pub(crate) Keypair); -/// The identifier of a participant in the Olaf protocol. +/// The identifier of a participant, which must be the same in the SimplPedPoP protocol and in the FROST protocol. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct Identifier(pub(crate) Scalar); diff --git a/src/olaf/simplpedpop/errors.rs b/src/olaf/simplpedpop/errors.rs index b7770c0..caff496 100644 --- a/src/olaf/simplpedpop/errors.rs +++ b/src/olaf/simplpedpop/errors.rs @@ -51,273 +51,288 @@ pub enum SPPError { #[cfg(test)] mod tests { - mod simplpedpop { - use crate::olaf::simplpedpop::errors::SPPError; - use crate::olaf::simplpedpop::types::{ - AllMessage, EncryptedSecretShare, CHACHA20POLY1305_LENGTH, RECIPIENTS_HASH_LENGTH, - }; - use crate::{Keypair, PublicKey}; - use alloc::vec::Vec; - use curve25519_dalek::ristretto::RistrettoPoint; - use curve25519_dalek::traits::Identity; - use merlin::Transcript; - - #[test] - fn test_invalid_number_of_messages() { - let threshold = 3; - let participants = 5; - - let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); - let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); - - let mut messages: Vec = keypairs - .iter() - .map(|kp| kp.simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap()) - .collect(); - - messages.pop(); - - let result = keypairs[0].simplpedpop_recipient_all(&messages); - - match result { - Ok(_) => panic!("Expected an error, but got Ok."), - Err(e) => match e { - SPPError::InvalidNumberOfMessages => assert!(true), - _ => { - panic!("Expected DKGError::InvalidNumberOfMessages, but got {:?}", e) - }, - }, - } - } + use crate::olaf::simplpedpop::errors::SPPError; + use crate::olaf::simplpedpop::types::{ + AllMessage, EncryptedSecretShare, CHACHA20POLY1305_LENGTH, RECIPIENTS_HASH_LENGTH, + }; + use crate::olaf::simplpedpop::Parameters; + use crate::olaf::MINIMUM_THRESHOLD; + use crate::{Keypair, PublicKey}; + use alloc::vec::Vec; + use curve25519_dalek::ristretto::RistrettoPoint; + use curve25519_dalek::traits::Identity; + use merlin::Transcript; + use rand::Rng; + + const MAXIMUM_PARTICIPANTS: u16 = 10; + const MINIMUM_PARTICIPANTS: u16 = 2; + + fn generate_parameters() -> Parameters { + let mut rng = rand::thread_rng(); + let participants = rng.gen_range(MINIMUM_PARTICIPANTS..=MAXIMUM_PARTICIPANTS); + let threshold = rng.gen_range(MINIMUM_THRESHOLD..=participants); + + Parameters { participants, threshold } + } - #[test] - fn test_different_parameters() { - let threshold = 3; - let participants = 5; + #[test] + fn test_invalid_number_of_messages() { + let parameters = generate_parameters(); + let participants = parameters.participants; + let threshold = parameters.threshold; - let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); - let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); + let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); - let mut messages: Vec = Vec::new(); - for i in 0..participants { - let message = - keypairs[i].simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap(); - messages.push(message); - } + let mut messages: Vec = keypairs + .iter() + .map(|kp| kp.simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap()) + .collect(); - messages[1].content.parameters.threshold += 1; + messages.pop(); - let result = keypairs[0].simplpedpop_recipient_all(&messages); + let result = keypairs[0].simplpedpop_recipient_all(&messages); - // Check if the result is an error - match result { - Ok(_) => panic!("Expected an error, but got Ok."), - Err(e) => match e { - SPPError::DifferentParameters => assert!(true), - _ => panic!("Expected DKGError::DifferentParameters, but got {:?}", e), + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + SPPError::InvalidNumberOfMessages => assert!(true), + _ => { + panic!("Expected DKGError::InvalidNumberOfMessages, but got {:?}", e) }, - } + }, } + } - #[test] - fn test_different_recipients_hash() { - let threshold = 3; - let participants = 5; - - let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); - let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); - - let mut messages: Vec = keypairs - .iter() - .map(|kp| kp.simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap()) - .collect(); + #[test] + fn test_different_parameters() { + let parameters = generate_parameters(); + let participants = parameters.participants; + let threshold = parameters.threshold; + + let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); + + let mut messages: Vec = Vec::new(); + for i in 0..participants { + let message = keypairs[i as usize] + .simplpedpop_contribute_all(threshold, public_keys.clone()) + .unwrap(); + messages.push(message); + } - messages[1].content.recipients_hash = [1; RECIPIENTS_HASH_LENGTH]; + messages[1].content.parameters.threshold += 1; - let result = keypairs[0].simplpedpop_recipient_all(&messages); + let result = keypairs[0].simplpedpop_recipient_all(&messages); - match result { - Ok(_) => panic!("Expected an error, but got Ok."), - Err(e) => match e { - SPPError::DifferentRecipientsHash => assert!(true), - _ => { - panic!("Expected DKGError::DifferentRecipientsHash, but got {:?}", e) - }, - }, - } + // Check if the result is an error + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + SPPError::DifferentParameters => assert!(true), + _ => panic!("Expected DKGError::DifferentParameters, but got {:?}", e), + }, } + } - #[test] - fn test_incorrect_number_of_commitments() { - let threshold = 3; - let participants = 5; + #[test] + fn test_different_recipients_hash() { + let parameters = generate_parameters(); + let participants = parameters.participants; + let threshold = parameters.threshold; - let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); - let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); + let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); - let mut messages: Vec = keypairs - .iter() - .map(|kp| kp.simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap()) - .collect(); + let mut messages: Vec = keypairs + .iter() + .map(|kp| kp.simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap()) + .collect(); - messages[1].content.polynomial_commitment.coefficients_commitments.pop(); + messages[1].content.recipients_hash = [1; RECIPIENTS_HASH_LENGTH]; - let result = keypairs[0].simplpedpop_recipient_all(&messages); + let result = keypairs[0].simplpedpop_recipient_all(&messages); - match result { - Ok(_) => panic!("Expected an error, but got Ok."), - Err(e) => match e { - SPPError::IncorrectNumberOfCoefficientCommitments => assert!(true), - _ => panic!( - "Expected DKGError::IncorrectNumberOfCoefficientCommitments, but got {:?}", - e - ), + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + SPPError::DifferentRecipientsHash => assert!(true), + _ => { + panic!("Expected DKGError::DifferentRecipientsHash, but got {:?}", e) }, - } + }, + } + } + + #[test] + fn test_incorrect_number_of_commitments() { + let parameters = generate_parameters(); + let participants = parameters.participants; + let threshold = parameters.threshold; + + let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); + + let mut messages: Vec = keypairs + .iter() + .map(|kp| kp.simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap()) + .collect(); + + messages[1].content.polynomial_commitment.coefficients_commitments.pop(); + + let result = keypairs[0].simplpedpop_recipient_all(&messages); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + SPPError::IncorrectNumberOfCoefficientCommitments => assert!(true), + _ => panic!( + "Expected DKGError::IncorrectNumberOfCoefficientCommitments, but got {:?}", + e + ), + }, } + } - #[test] - fn test_incorrect_number_of_encrypted_shares() { - let threshold = 3; - let participants = 5; + #[test] + fn test_incorrect_number_of_encrypted_shares() { + let parameters = generate_parameters(); + let participants = parameters.participants; + let threshold = parameters.threshold; - let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); - let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); + let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); - let mut messages: Vec = keypairs - .iter() - .map(|kp| kp.simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap()) - .collect(); + let mut messages: Vec = keypairs + .iter() + .map(|kp| kp.simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap()) + .collect(); - messages[1].content.encrypted_secret_shares.pop(); + messages[1].content.encrypted_secret_shares.pop(); - let result = keypairs[0].simplpedpop_recipient_all(&messages); + let result = keypairs[0].simplpedpop_recipient_all(&messages); - match result { - Ok(_) => panic!("Expected an error, but got Ok."), - Err(e) => match e { - SPPError::IncorrectNumberOfEncryptedShares => assert!(true), - _ => panic!( - "Expected DKGError::IncorrectNumberOfEncryptedShares, but got {:?}", - e - ), - }, - } + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + SPPError::IncorrectNumberOfEncryptedShares => assert!(true), + _ => panic!("Expected DKGError::IncorrectNumberOfEncryptedShares, but got {:?}", e), + }, } + } - #[test] - fn test_invalid_secret_share() { - let threshold = 3; - let participants = 5; + #[test] + fn test_invalid_secret_share() { + let parameters = generate_parameters(); + let participants = parameters.participants; + let threshold = parameters.threshold; - let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); - let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); + let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); - let mut messages: Vec = keypairs - .iter() - .map(|kp| kp.simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap()) - .collect(); + let mut messages: Vec = keypairs + .iter() + .map(|kp| kp.simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap()) + .collect(); - messages[1].content.encrypted_secret_shares[0] = - EncryptedSecretShare(vec![1; CHACHA20POLY1305_LENGTH]); + messages[1].content.encrypted_secret_shares[0] = + EncryptedSecretShare(vec![1; CHACHA20POLY1305_LENGTH]); - let result = keypairs[0].simplpedpop_recipient_all(&messages); + let result = keypairs[0].simplpedpop_recipient_all(&messages); - match result { - Ok(_) => panic!("Expected an error, but got Ok."), - Err(e) => match e { - SPPError::InvalidSecretShare => assert!(true), - _ => panic!("Expected DKGError::InvalidSecretShare, but got {:?}", e), - }, - } + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + SPPError::InvalidSecretShare => assert!(true), + _ => panic!("Expected DKGError::InvalidSecretShare, but got {:?}", e), + }, } + } - #[test] - fn test_invalid_signature() { - let threshold = 3; - let participants = 5; + #[test] + fn test_invalid_signature() { + let parameters = generate_parameters(); + let participants = parameters.participants; + let threshold = parameters.threshold; - let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); - let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); + let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); - let mut messages: Vec = keypairs - .iter() - .map(|kp| kp.simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap()) - .collect(); + let mut messages: Vec = keypairs + .iter() + .map(|kp| kp.simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap()) + .collect(); - messages[1].signature = - keypairs[1].secret.sign(Transcript::new(b"invalid"), &keypairs[1].public); + messages[1].signature = + keypairs[1].secret.sign(Transcript::new(b"invalid"), &keypairs[1].public); - let result = keypairs[0].simplpedpop_recipient_all(&messages); + let result = keypairs[0].simplpedpop_recipient_all(&messages); - match result { - Ok(_) => panic!("Expected an error, but got Ok."), - Err(e) => match e { - SPPError::InvalidSignature(_) => assert!(true), - _ => panic!("Expected DKGError::InvalidSignature, but got {:?}", e), - }, - } + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + SPPError::InvalidSignature(_) => assert!(true), + _ => panic!("Expected DKGError::InvalidSignature, but got {:?}", e), + }, } + } - #[test] - fn test_invalid_threshold() { - let keypair = Keypair::generate(); - let result = keypair.simplpedpop_contribute_all( - 1, - vec![ - PublicKey::from_point(RistrettoPoint::identity()), - PublicKey::from_point(RistrettoPoint::identity()), - ], - ); - - match result { - Ok(_) => panic!("Expected an error, but got Ok."), - Err(e) => match e { - SPPError::InsufficientThreshold => assert!(true), - _ => panic!("Expected SPPError::InsufficientThreshold, but got {:?}", e), - }, - } + #[test] + fn test_invalid_threshold() { + let keypair = Keypair::generate(); + let result = keypair.simplpedpop_contribute_all( + 1, + vec![ + PublicKey::from_point(RistrettoPoint::identity()), + PublicKey::from_point(RistrettoPoint::identity()), + ], + ); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + SPPError::InsufficientThreshold => assert!(true), + _ => panic!("Expected SPPError::InsufficientThreshold, but got {:?}", e), + }, } + } - #[test] - fn test_invalid_participants() { - let keypair = Keypair::generate(); - let result = keypair.simplpedpop_contribute_all( - 2, - vec![PublicKey::from_point(RistrettoPoint::identity())], - ); - - match result { - Ok(_) => panic!("Expected an error, but got Ok."), - Err(e) => match e { - SPPError::InvalidNumberOfParticipants => { - assert!(true) - }, - _ => { - panic!("Expected SPPError::InvalidNumberOfParticipants, but got {:?}", e) - }, + #[test] + fn test_invalid_participants() { + let keypair = Keypair::generate(); + let result = keypair + .simplpedpop_contribute_all(2, vec![PublicKey::from_point(RistrettoPoint::identity())]); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + SPPError::InvalidNumberOfParticipants => { + assert!(true) }, - } + _ => { + panic!("Expected SPPError::InvalidNumberOfParticipants, but got {:?}", e) + }, + }, } + } - #[test] - fn test_threshold_greater_than_participants() { - let keypair = Keypair::generate(); - let result = keypair.simplpedpop_contribute_all( - 3, - vec![ - PublicKey::from_point(RistrettoPoint::identity()), - PublicKey::from_point(RistrettoPoint::identity()), - ], - ); - - match result { - Ok(_) => panic!("Expected an error, but got Ok."), - Err(e) => match e { - SPPError::ExcessiveThreshold => assert!(true), - _ => panic!("Expected SPPError::ExcessiveThreshold), but got {:?}", e), - }, - } + #[test] + fn test_threshold_greater_than_participants() { + let keypair = Keypair::generate(); + let result = keypair.simplpedpop_contribute_all( + 3, + vec![ + PublicKey::from_point(RistrettoPoint::identity()), + PublicKey::from_point(RistrettoPoint::identity()), + ], + ); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + SPPError::ExcessiveThreshold => assert!(true), + _ => panic!("Expected SPPError::ExcessiveThreshold), but got {:?}", e), + }, } } } diff --git a/src/olaf/simplpedpop/mod.rs b/src/olaf/simplpedpop/mod.rs index da83402..b3a76e1 100644 --- a/src/olaf/simplpedpop/mod.rs +++ b/src/olaf/simplpedpop/mod.rs @@ -1,11 +1,11 @@ -//! Implementation of the SimplPedPoP protocol (), 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. +//! Implementation of the SimplPedPoP protocol (), a spp based on PedPoP, which in turn is based +//! on Pedersen's spp. All of them have as the fundamental building block the Shamir's Secret Sharing scheme. mod types; mod errors; pub use self::types::{ - AllMessage, DKGOutput, DKGOutputMessage, MessageContent, Parameters, PolynomialCommitment, + AllMessage, SPPOutput, SPPOutputMessage, MessageContent, Parameters, PolynomialCommitment, }; pub(crate) use self::types::SecretPolynomial; use alloc::vec::Vec; @@ -21,10 +21,18 @@ use self::{ SecretShare, CHACHA20POLY1305_KEY_LENGTH, ENCRYPTION_NONCE_LENGTH, RECIPIENTS_HASH_LENGTH, }, }; -use super::{GroupPublicKey, Identifier, SigningKeypair, VerifyingShare, GENERATOR}; +use super::{ThresholdPublicKey, Identifier, SigningKeypair, VerifyingShare, GENERATOR}; impl Keypair { /// First round of the SimplPedPoP protocol. + /// + /// We do not recipients.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 recipients, not the + /// full recipients list. pub fn simplpedpop_contribute_all( &self, threshold: u16, @@ -35,13 +43,6 @@ impl Keypair { let mut rng = getrandom_or_panic(); - // We do not recipients.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 recipients, not the - // full recipients list. let mut recipients_transcript = Transcript::new(b"RecipientsHash"); parameters.commit(&mut recipients_transcript); @@ -142,7 +143,7 @@ impl Keypair { pub fn simplpedpop_recipient_all( &self, messages: &[AllMessage], - ) -> SPPResult<(DKGOutputMessage, SigningKeypair)> { + ) -> SPPResult<(SPPOutputMessage, SigningKeypair)> { let first_message = &messages[0]; let parameters = &first_message.content.parameters; let threshold = parameters.threshold as usize; @@ -278,17 +279,17 @@ impl Keypair { verifying_keys.push((*id, VerifyingShare(PublicKey::from_point(evaluation)))); } - let dkg_output = DKGOutput::new( + let spp_output = SPPOutput::new( parameters, - GroupPublicKey(PublicKey::from_point(group_point)), + ThresholdPublicKey(PublicKey::from_point(group_point)), verifying_keys, ); - let mut dkg_output_transcript = Transcript::new(b"dkg output"); - dkg_output_transcript.append_message(b"message", &dkg_output.to_bytes()); + let mut spp_output_transcript = Transcript::new(b"spp output"); + spp_output_transcript.append_message(b"message", &spp_output.to_bytes()); - let signature = self.sign(dkg_output_transcript); - let dkg_output = DKGOutputMessage::new(self.public, dkg_output, signature); + let signature = self.sign(spp_output_transcript); + let spp_output = SPPOutputMessage::new(self.public, spp_output, signature); let mut nonce: [u8; 32] = [0u8; 32]; getrandom_or_panic().fill_bytes(&mut nonce); @@ -297,348 +298,81 @@ impl Keypair { let keypair = Keypair::from(secret_key); - Ok((dkg_output, SigningKeypair(keypair))) + Ok((spp_output, SigningKeypair(keypair))) } } #[cfg(test)] mod tests { - mod simplpedpop { - use crate::olaf::simplpedpop::errors::SPPError; - use crate::olaf::simplpedpop::types::{ - AllMessage, EncryptedSecretShare, Parameters, CHACHA20POLY1305_LENGTH, - RECIPIENTS_HASH_LENGTH, - }; - use crate::olaf::MINIMUM_THRESHOLD; - use crate::{Keypair, PublicKey}; - use alloc::vec::Vec; - use curve25519_dalek::ristretto::RistrettoPoint; - use curve25519_dalek::traits::Identity; - use merlin::Transcript; - use rand::Rng; - - const MAXIMUM_PARTICIPANTS: u16 = 10; - const MINIMUM_PARTICIPANTS: u16 = 2; - const PROTOCOL_RUNS: usize = 1; - - fn generate_parameters() -> Parameters { - let mut rng = rand::thread_rng(); - let participants = rng.gen_range(MINIMUM_PARTICIPANTS..=MAXIMUM_PARTICIPANTS); - let threshold = rng.gen_range(MINIMUM_THRESHOLD..=participants); - - Parameters { participants, threshold } - } - - #[test] - fn test_simplpedpop_protocol() { - for _ in 0..PROTOCOL_RUNS { - let parameters = generate_parameters(); - let participants = parameters.participants as usize; - let threshold = parameters.threshold as usize; - - let keypairs: Vec = - (0..participants).map(|_| Keypair::generate()).collect(); - let public_keys: Vec = keypairs.iter().map(|kp| kp.public).collect(); - - let mut all_messages = Vec::new(); - for i in 0..participants { - let message: AllMessage = keypairs[i] - .simplpedpop_contribute_all(threshold as u16, public_keys.clone()) - .unwrap(); - all_messages.push(message); - } - - let mut dkg_outputs = Vec::new(); - - for kp in keypairs.iter() { - let dkg_output = kp.simplpedpop_recipient_all(&all_messages).unwrap(); - dkg_outputs.push(dkg_output); - } - - // Verify that all DKG outputs are equal for group_public_key and verifying_keys - assert!( - dkg_outputs.windows(2).all(|w| w[0].0.dkg_output.group_public_key.0 - == w[1].0.dkg_output.group_public_key.0 - && w[0].0.dkg_output.verifying_keys.len() - == w[1].0.dkg_output.verifying_keys.len() - && w[0] - .0 - .dkg_output - .verifying_keys - .iter() - .zip(w[1].0.dkg_output.verifying_keys.iter()) - .all(|((a, b), (c, d))| a.0 == c.0 && b.0 == d.0)), - "All DKG outputs should have identical group public keys and verifying keys." - ); - - // Verify that all verifying_shares are valid - for i in 0..participants { - for j in 0..participants { - assert_eq!( - dkg_outputs[i].0.dkg_output.verifying_keys[j].1 .0, - (dkg_outputs[j].1 .0.public), - "Verification of total secret shares failed!" - ); - } - } - } - } - - #[test] - fn test_invalid_number_of_messages() { - let threshold = 3; - let participants = 5; - - let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); - let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); - - let mut messages: Vec = keypairs - .iter() - .map(|kp| kp.simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap()) - .collect(); - - messages.pop(); - - let result = keypairs[0].simplpedpop_recipient_all(&messages); - - match result { - Ok(_) => panic!("Expected an error, but got Ok."), - Err(e) => match e { - SPPError::InvalidNumberOfMessages => assert!(true), - _ => { - panic!("Expected DKGError::InvalidNumberOfMessages, but got {:?}", e) - }, - }, - } - } + use crate::olaf::simplpedpop::types::{AllMessage, Parameters}; + use crate::olaf::MINIMUM_THRESHOLD; + use crate::{Keypair, PublicKey}; + use alloc::vec::Vec; + use rand::Rng; + + const MAXIMUM_PARTICIPANTS: u16 = 10; + const MINIMUM_PARTICIPANTS: u16 = 2; + const PROTOCOL_RUNS: usize = 1; + + fn generate_parameters() -> Parameters { + let mut rng = rand::thread_rng(); + let participants = rng.gen_range(MINIMUM_PARTICIPANTS..=MAXIMUM_PARTICIPANTS); + let threshold = rng.gen_range(MINIMUM_THRESHOLD..=participants); + + Parameters { participants, threshold } + } - #[test] - fn test_different_parameters() { - let threshold = 3; - let participants = 5; + #[test] + fn test_simplpedpop_protocol() { + for _ in 0..PROTOCOL_RUNS { + let parameters = generate_parameters(); + let participants = parameters.participants as usize; + let threshold = parameters.threshold as usize; let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); - let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public).collect(); - let mut messages: Vec = Vec::new(); + let mut all_messages = Vec::new(); for i in 0..participants { - let message = - keypairs[i].simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap(); - messages.push(message); - } - - messages[1].content.parameters.threshold += 1; - - let result = keypairs[0].simplpedpop_recipient_all(&messages); - - // Check if the result is an error - match result { - Ok(_) => panic!("Expected an error, but got Ok."), - Err(e) => match e { - SPPError::DifferentParameters => assert!(true), - _ => panic!("Expected DKGError::DifferentParameters, but got {:?}", e), - }, - } - } - - #[test] - fn test_different_recipients_hash() { - let threshold = 3; - let participants = 5; - - let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); - let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); - - let mut messages: Vec = keypairs - .iter() - .map(|kp| kp.simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap()) - .collect(); - - messages[1].content.recipients_hash = [1; RECIPIENTS_HASH_LENGTH]; - - let result = keypairs[0].simplpedpop_recipient_all(&messages); - - match result { - Ok(_) => panic!("Expected an error, but got Ok."), - Err(e) => match e { - SPPError::DifferentRecipientsHash => assert!(true), - _ => { - panic!("Expected DKGError::DifferentRecipientsHash, but got {:?}", e) - }, - }, - } - } - - #[test] - fn test_incorrect_number_of_commitments() { - let threshold = 3; - let participants = 5; - - let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); - let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); - - let mut messages: Vec = keypairs - .iter() - .map(|kp| kp.simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap()) - .collect(); - - messages[1].content.polynomial_commitment.coefficients_commitments.pop(); - - let result = keypairs[0].simplpedpop_recipient_all(&messages); - - match result { - Ok(_) => panic!("Expected an error, but got Ok."), - Err(e) => match e { - SPPError::IncorrectNumberOfCoefficientCommitments => assert!(true), - _ => panic!( - "Expected DKGError::IncorrectNumberOfCoefficientCommitments, but got {:?}", - e - ), - }, - } - } - - #[test] - fn test_incorrect_number_of_encrypted_shares() { - let threshold = 3; - let participants = 5; - - let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); - let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); - - let mut messages: Vec = keypairs - .iter() - .map(|kp| kp.simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap()) - .collect(); - - messages[1].content.encrypted_secret_shares.pop(); - - let result = keypairs[0].simplpedpop_recipient_all(&messages); - - match result { - Ok(_) => panic!("Expected an error, but got Ok."), - Err(e) => match e { - SPPError::IncorrectNumberOfEncryptedShares => assert!(true), - _ => panic!( - "Expected DKGError::IncorrectNumberOfEncryptedShares, but got {:?}", - e - ), - }, - } - } - - #[test] - fn test_invalid_secret_share() { - let threshold = 3; - let participants = 5; - - let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); - let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); - - let mut messages: Vec = keypairs - .iter() - .map(|kp| kp.simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap()) - .collect(); - - messages[1].content.encrypted_secret_shares[0] = - EncryptedSecretShare(vec![1; CHACHA20POLY1305_LENGTH]); - - let result = keypairs[0].simplpedpop_recipient_all(&messages); - - match result { - Ok(_) => panic!("Expected an error, but got Ok."), - Err(e) => match e { - SPPError::InvalidSecretShare => assert!(true), - _ => panic!("Expected DKGError::InvalidSecretShare, but got {:?}", e), - }, - } - } - - #[test] - fn test_invalid_signature() { - let threshold = 3; - let participants = 5; - - let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); - let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); - - let mut messages: Vec = keypairs - .iter() - .map(|kp| kp.simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap()) - .collect(); - - messages[1].signature = - keypairs[1].secret.sign(Transcript::new(b"invalid"), &keypairs[1].public); - - let result = keypairs[0].simplpedpop_recipient_all(&messages); - - match result { - Ok(_) => panic!("Expected an error, but got Ok."), - Err(e) => match e { - SPPError::InvalidSignature(_) => assert!(true), - _ => panic!("Expected DKGError::InvalidSignature, but got {:?}", e), - }, - } - } - - #[test] - fn test_invalid_threshold() { - let keypair = Keypair::generate(); - let result = keypair.simplpedpop_contribute_all( - 1, - vec![ - PublicKey::from_point(RistrettoPoint::identity()), - PublicKey::from_point(RistrettoPoint::identity()), - ], - ); - - match result { - Ok(_) => panic!("Expected an error, but got Ok."), - Err(e) => match e { - SPPError::InsufficientThreshold => assert!(true), - _ => panic!("Expected SPPError::InsufficientThreshold, but got {:?}", e), - }, + let message: AllMessage = keypairs[i] + .simplpedpop_contribute_all(threshold as u16, public_keys.clone()) + .unwrap(); + all_messages.push(message); } - } - #[test] - fn test_invalid_participants() { - let keypair = Keypair::generate(); - let result = keypair.simplpedpop_contribute_all( - 2, - vec![PublicKey::from_point(RistrettoPoint::identity())], - ); + let mut spp_outputs = Vec::new(); - match result { - Ok(_) => panic!("Expected an error, but got Ok."), - Err(e) => match e { - SPPError::InvalidNumberOfParticipants => { - assert!(true) - }, - _ => { - panic!("Expected SPPError::InvalidNumberOfParticipants, but got {:?}", e) - }, - }, + for kp in keypairs.iter() { + let spp_output = kp.simplpedpop_recipient_all(&all_messages).unwrap(); + spp_output.0.verify_signature().unwrap(); + spp_outputs.push(spp_output); } - } - #[test] - fn test_threshold_greater_than_participants() { - let keypair = Keypair::generate(); - let result = keypair.simplpedpop_contribute_all( - 3, - vec![ - PublicKey::from_point(RistrettoPoint::identity()), - PublicKey::from_point(RistrettoPoint::identity()), - ], + // Verify that all spp outputs are equal for group_public_key and verifying_keys + assert!( + spp_outputs.windows(2).all(|w| w[0].0.spp_output.threshold_public_key.0 + == w[1].0.spp_output.threshold_public_key.0 + && w[0].0.spp_output.verifying_keys.len() + == w[1].0.spp_output.verifying_keys.len() + && w[0] + .0 + .spp_output + .verifying_keys + .iter() + .zip(w[1].0.spp_output.verifying_keys.iter()) + .all(|((a, b), (c, d))| a.0 == c.0 && b.0 == d.0)), + "All spp outputs should have identical group public keys and verifying keys." ); - match result { - Ok(_) => panic!("Expected an error, but got Ok."), - Err(e) => match e { - SPPError::ExcessiveThreshold => assert!(true), - _ => panic!("Expected SPPError::ExcessiveThreshold), but got {:?}", e), - }, + // Verify that all verifying_shares are valid + for i in 0..participants { + for j in 0..participants { + assert_eq!( + spp_outputs[i].0.spp_output.verifying_keys[j].1 .0, + (spp_outputs[j].1 .0.public), + "Verification of total secret shares failed!" + ); + } } } } diff --git a/src/olaf/simplpedpop/types.rs b/src/olaf/simplpedpop/types.rs index c73432c..abe962f 100644 --- a/src/olaf/simplpedpop/types.rs +++ b/src/olaf/simplpedpop/types.rs @@ -4,6 +4,7 @@ use core::iter; use alloc::vec::Vec; +use merlin::Transcript; use rand_core::{CryptoRng, RngCore}; use zeroize::ZeroizeOnDrop; use aead::KeyInit; @@ -11,7 +12,7 @@ use chacha20poly1305::{aead::Aead, ChaCha20Poly1305, Nonce}; use curve25519_dalek::{ristretto::CompressedRistretto, traits::Identity, RistrettoPoint, Scalar}; use crate::{ context::SigningTranscript, - olaf::{GroupPublicKey, Identifier, VerifyingShare, GENERATOR, MINIMUM_THRESHOLD}, + olaf::{ThresholdPublicKey, Identifier, VerifyingShare, GENERATOR, MINIMUM_THRESHOLD}, scalar_from_canonical_bytes, PublicKey, Signature, PUBLIC_KEY_LENGTH, SIGNATURE_LENGTH, }; use super::errors::{SPPError, SPPResult}; @@ -319,7 +320,7 @@ impl MessageContent { } } - /// Serialize MessageContent + /// Serialize MessageContent into bytes. pub fn to_bytes(&self) -> Vec { let mut bytes = Vec::new(); @@ -343,7 +344,7 @@ impl MessageContent { bytes } - /// Deserialize MessageContent from bytes + /// Deserialize MessageContent from bytes. pub fn from_bytes(bytes: &[u8]) -> Result { let mut cursor = 0; @@ -414,27 +415,27 @@ impl MessageContent { /// The signed output of the SimplPedPoP protocol. #[derive(Debug)] -pub struct DKGOutputMessage { +pub struct SPPOutputMessage { pub(crate) sender: PublicKey, /// The output of the SimplPedPoP protocol. - pub dkg_output: DKGOutput, + pub(crate) spp_output: SPPOutput, pub(crate) signature: Signature, } -impl DKGOutputMessage { +impl SPPOutputMessage { /// Creates a signed SimplPedPoP output. - pub fn new(sender: PublicKey, content: DKGOutput, signature: Signature) -> Self { - Self { sender, dkg_output: content, signature } + pub fn new(sender: PublicKey, content: SPPOutput, signature: Signature) -> Self { + Self { sender, spp_output: content, signature } } - /// Serializes the DKGOutputMessage into bytes. + /// Serializes the SPPOutputMessage into bytes. pub fn to_bytes(&self) -> Vec { let mut bytes = Vec::new(); let pk_bytes = self.sender.to_bytes(); bytes.extend(pk_bytes); - let content_bytes = self.dkg_output.to_bytes(); + let content_bytes = self.spp_output.to_bytes(); bytes.extend(content_bytes); let signature_bytes = self.signature.to_bytes(); @@ -443,7 +444,7 @@ impl DKGOutputMessage { bytes } - /// Deserializes the DKGOutputMessage from bytes. + /// Deserializes the SPPOutputMessage from bytes. pub fn from_bytes(bytes: &[u8]) -> Result { let mut cursor = 0; @@ -452,43 +453,64 @@ impl DKGOutputMessage { cursor += PUBLIC_KEY_LENGTH; let content_bytes = &bytes[cursor..bytes.len() - SIGNATURE_LENGTH]; - let dkg_output = DKGOutput::from_bytes(content_bytes)?; + let spp_output = SPPOutput::from_bytes(content_bytes)?; cursor = bytes.len() - SIGNATURE_LENGTH; let signature = Signature::from_bytes(&bytes[cursor..cursor + SIGNATURE_LENGTH]) .map_err(SPPError::InvalidSignature)?; - Ok(DKGOutputMessage { sender, dkg_output, signature }) + Ok(SPPOutputMessage { sender, spp_output, signature }) + } + + /// Returns the output of the SimplPedPoP protocol. + pub fn spp_output(&self) -> &SPPOutput { + &self.spp_output + } + + /// Verifies the signature of the message. + pub fn verify_signature(&self) -> SPPResult<()> { + let mut spp_output_transcript = Transcript::new(b"spp output"); + spp_output_transcript.append_message(b"message", &self.spp_output.to_bytes()); + + self.sender + .verify(spp_output_transcript, &self.signature) + .map_err(SPPError::InvalidSignature) } } /// The content of the signed output of the SimplPedPoP protocol. #[derive(Clone, Debug)] -pub struct DKGOutput { +pub struct SPPOutput { pub(crate) parameters: Parameters, - /// The group public key generated by the SimplPedPoP protocol. - pub group_public_key: GroupPublicKey, + /// The threshold public key generated by the SimplPedPoP protocol. + pub(crate) threshold_public_key: ThresholdPublicKey, pub(crate) verifying_keys: Vec<(Identifier, VerifyingShare)>, } -impl DKGOutput { +impl SPPOutput { /// Creates the content of the SimplPedPoP output. pub fn new( parameters: &Parameters, - group_public_key: GroupPublicKey, + threshold_public_key: ThresholdPublicKey, verifying_keys: Vec<(Identifier, VerifyingShare)>, ) -> Self { let parameters = Parameters::generate(parameters.participants, parameters.threshold); - Self { group_public_key, verifying_keys, parameters } + Self { threshold_public_key, verifying_keys, parameters } + } + + /// Returns the threshold public key. + pub fn threshold_public_key(&self) -> ThresholdPublicKey { + self.threshold_public_key } - /// Serializes the DKGOutput into bytes. + + /// Serializes the SPPOutput into bytes. pub fn to_bytes(&self) -> Vec { let mut bytes = Vec::new(); bytes.extend(self.parameters.to_bytes()); - let compressed_public_key = self.group_public_key.0.as_compressed(); + let compressed_public_key = self.threshold_public_key.0.as_compressed(); bytes.extend(compressed_public_key.to_bytes().iter()); let key_count = self.verifying_keys.len() as u16; @@ -502,7 +524,7 @@ impl DKGOutput { bytes } - /// Deserializes the DKGOutput from bytes. + /// Deserializes the SPPOutput from bytes. pub fn from_bytes(bytes: &[u8]) -> Result { let mut cursor = 0; @@ -535,8 +557,8 @@ impl DKGOutput { verifying_keys.push((Identifier(identifier), VerifyingShare(key))); } - Ok(DKGOutput { - group_public_key: GroupPublicKey(PublicKey::from_point(group_public_key)), + Ok(SPPOutput { + threshold_public_key: ThresholdPublicKey(PublicKey::from_point(group_public_key)), verifying_keys, parameters, }) @@ -676,7 +698,7 @@ mod tests { } #[test] - fn test_dkg_output_serialization() { + fn test_spp_output_serialization() { let mut rng = OsRng; let group_public_key = RistrettoPoint::random(&mut rng); let verifying_keys = vec![ @@ -695,46 +717,46 @@ mod tests { ]; let parameters = Parameters::generate(2, 2); - let dkg_output = DKGOutput { + let spp_output = SPPOutput { parameters, - group_public_key: GroupPublicKey(PublicKey::from_point(group_public_key)), + threshold_public_key: ThresholdPublicKey(PublicKey::from_point(group_public_key)), verifying_keys, }; let keypair = Keypair::generate(); let signature = keypair.sign(Transcript::new(b"test")); - let dkg_output = DKGOutputMessage { sender: keypair.public, dkg_output, signature }; + let spp_output = SPPOutputMessage { sender: keypair.public, spp_output, signature }; - let bytes = dkg_output.to_bytes(); + let bytes = spp_output.to_bytes(); - let deserialized_dkg_output = - DKGOutputMessage::from_bytes(&bytes).expect("Deserialization failed"); + let deserialized_spp_output = + SPPOutputMessage::from_bytes(&bytes).expect("Deserialization failed"); assert_eq!( - deserialized_dkg_output.dkg_output.group_public_key.0.as_compressed(), - dkg_output.dkg_output.group_public_key.0.as_compressed(), + deserialized_spp_output.spp_output.threshold_public_key.0.as_compressed(), + spp_output.spp_output.threshold_public_key.0.as_compressed(), "Group public keys do not match" ); assert_eq!( - deserialized_dkg_output.dkg_output.verifying_keys.len(), - dkg_output.dkg_output.verifying_keys.len(), + deserialized_spp_output.spp_output.verifying_keys.len(), + spp_output.spp_output.verifying_keys.len(), "Verifying keys counts do not match" ); assert!( - deserialized_dkg_output - .dkg_output + deserialized_spp_output + .spp_output .verifying_keys .iter() - .zip(dkg_output.dkg_output.verifying_keys.iter()) + .zip(spp_output.spp_output.verifying_keys.iter()) .all(|((a, b), (c, d))| a.0 == c.0 && b.0 == d.0), "Verifying keys do not match" ); assert_eq!( - deserialized_dkg_output.signature, dkg_output.signature, + deserialized_spp_output.signature, spp_output.signature, "Signatures do not match" ); } From c4484b6f5cca24ab9750bf908094c9fe3d1f3b57 Mon Sep 17 00:00:00 2001 From: Fiono11 Date: Thu, 16 May 2024 16:26:49 +0100 Subject: [PATCH 34/60] Implement SigningPackage --- benches/olaf_benchmarks.rs | 27 ++-- src/lib.rs | 2 +- src/olaf/frost/errors.rs | 65 +++++++--- src/olaf/frost/mod.rs | 231 +++++++++++++++++++++++---------- src/olaf/frost/types.rs | 169 +++++++++++++++++++++++- src/olaf/mod.rs | 2 + src/olaf/simplpedpop/errors.rs | 4 +- src/olaf/simplpedpop/mod.rs | 13 +- src/olaf/simplpedpop/types.rs | 133 +++++++------------ 9 files changed, 442 insertions(+), 204 deletions(-) diff --git a/benches/olaf_benchmarks.rs b/benches/olaf_benchmarks.rs index 6ae9c26..03aae94 100644 --- a/benches/olaf_benchmarks.rs +++ b/benches/olaf_benchmarks.rs @@ -3,8 +3,10 @@ use criterion::criterion_main; mod olaf_benches { use rand_core::OsRng; use criterion::{criterion_group, BenchmarkId, Criterion}; - use schnorrkel::olaf::{simplpedpop::AllMessage, frost::aggregate}; + use schnorrkel::olaf::frost::aggregate; use schnorrkel::keys::{PublicKey, Keypair}; + use schnorrkel::olaf::frost::{SigningNonces, SignatureShare, SigningCommitments}; + use schnorrkel::olaf::simplpedpop::AllMessage; fn benchmark_simplpedpop(c: &mut Criterion) { let mut group = c.benchmark_group("SimplPedPoP"); @@ -16,9 +18,10 @@ mod olaf_benches { let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); let public_keys: Vec = keypairs.iter().map(|kp| kp.public).collect(); - let mut all_messages = Vec::new(); + let mut all_messages: Vec = Vec::new(); + for i in 0..participants { - let message: AllMessage = keypairs[i] + let message = keypairs[i] .simplpedpop_contribute_all(threshold as u16, public_keys.clone()) .unwrap(); all_messages.push(message); @@ -54,7 +57,7 @@ mod olaf_benches { let mut all_messages = Vec::new(); for i in 0..participants { - let message: AllMessage = keypairs[i] + let message = keypairs[i] .simplpedpop_contribute_all(threshold as u16, public_keys.clone()) .unwrap(); all_messages.push(message); @@ -67,8 +70,8 @@ mod olaf_benches { spp_outputs.push(spp_output); } - let mut all_signing_commitments = Vec::new(); - let mut all_signing_nonces = Vec::new(); + let mut all_signing_commitments: Vec = Vec::new(); + let mut all_signing_nonces: Vec = Vec::new(); for spp_output in &spp_outputs { let (signing_nonces, signing_commitments) = spp_output.1.commit(&mut OsRng); @@ -82,7 +85,7 @@ mod olaf_benches { }) }); - let mut signature_shares = Vec::new(); + let mut signature_shares: Vec = Vec::new(); let message = b"message"; let context = b"context"; @@ -94,7 +97,7 @@ mod olaf_benches { .sign( context, message, - spp_outputs[0].0.spp_output(), + &spp_outputs[0].0, &all_signing_commitments, &all_signing_nonces[0], ) @@ -103,12 +106,12 @@ mod olaf_benches { }); for (i, spp_output) in spp_outputs.iter().enumerate() { - let signature_share = spp_output + let signature_share: SignatureShare = spp_output .1 .sign( context, message, - spp_output.0.spp_output(), + &spp_output.0, &all_signing_commitments, &all_signing_nonces[i], ) @@ -124,7 +127,7 @@ mod olaf_benches { context, &all_signing_commitments, &signature_shares, - spp_outputs[0].0.spp_output().threshold_public_key(), + spp_outputs[0].0.threshold_public_key(), ) .unwrap(); }) @@ -138,7 +141,7 @@ mod olaf_benches { name = olaf_benches; config = Criterion::default(); targets = - //benchmark_simplpedpop, + benchmark_simplpedpop, benchmark_frost, } } diff --git a/src/lib.rs b/src/lib.rs index 5208751..43eef8d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -248,7 +248,7 @@ pub mod derive; pub mod cert; pub mod errors; -#[cfg(all(feature = "alloc", feature = "aead"))] +//#[cfg(all(feature = "alloc", feature = "aead"))] pub mod olaf; #[cfg(all(feature = "aead", feature = "getrandom"))] diff --git a/src/olaf/frost/errors.rs b/src/olaf/frost/errors.rs index 78b3877..2d8ad48 100644 --- a/src/olaf/frost/errors.rs +++ b/src/olaf/frost/errors.rs @@ -1,6 +1,11 @@ //! Errors of the FROST protocol. -use crate::SignatureError; +use core::array::TryFromSliceError; + +use crate::{ + olaf::{simplpedpop::errors::SPPError, Identifier}, + SignatureError, +}; /// A result for the SimplPedPoP protocol. pub type FROSTResult = Result; @@ -22,10 +27,25 @@ pub enum FROSTError { IncorrectNumberOfVerifyingShares, /// The identifiers of the SimplPedPoP protocol must be the same of the FROST protocol. InvalidIdentifier, + /// Error deserializing the signature share. + SignatureShareDeserializationError, + /// The signature share is invalid. + InvalidSignatureShare { + /// The identifier of the signer whose share validation failed. + culprit: Identifier, + }, /// The output of the SimplPedPoP protocol must contain the participant's verifying share. InvalidOwnVerifyingShare, /// Invalid signature. InvalidSignature(SignatureError), + /// Deserialization error. + DeserializationError(TryFromSliceError), + /// Invalid nonce commitment. + InvalidNonceCommitment, + /// Invalid public key. + InvalidPublicKey(SignatureError), + /// Error deserializing the output message of the SimplPedPoP protocol. + SPPOutputMessageDeserializationError(SPPError), } #[cfg(test)] @@ -82,10 +102,10 @@ mod tests { spp_outputs[0].1 = SigningKeypair(Keypair::generate()); let result = spp_outputs[0].1.sign( - context, - message, - &spp_outputs[0].0.spp_output, - &all_signing_commitments, + context.to_vec(), + message.to_vec(), + spp_outputs[0].0.clone(), + all_signing_commitments.clone(), &all_signing_nonces[0], ); @@ -139,10 +159,10 @@ mod tests { spp_outputs[0].0.spp_output.verifying_keys.pop(); let result = spp_outputs[0].1.sign( - context, - message, - &spp_outputs[0].0.spp_output, - &all_signing_commitments, + context.to_vec(), + message.to_vec(), + spp_outputs[0].0.clone(), + all_signing_commitments.clone(), &all_signing_nonces[0], ); @@ -199,10 +219,10 @@ mod tests { }; let result = spp_outputs[0].1.sign( - context, - message, - &spp_outputs[0].0.spp_output, - &all_signing_commitments, + context.to_vec(), + message.to_vec(), + spp_outputs[0].0.clone(), + all_signing_commitments.clone(), &all_signing_nonces[0], ); @@ -254,11 +274,12 @@ mod tests { let context = b"context"; all_signing_commitments[1].hiding = NonceCommitment(RistrettoPoint::identity()); + let result = spp_outputs[0].1.sign( - context, - message, - &spp_outputs[0].0.spp_output, - &all_signing_commitments, + context.to_vec(), + message.to_vec(), + spp_outputs[0].0.clone(), + all_signing_commitments.clone(), &all_signing_nonces[0], ); @@ -306,14 +327,16 @@ mod tests { all_signing_commitments.push(signing_commitments); } + all_signing_commitments.pop(); + let message = b"message"; let context = b"context"; let result = spp_outputs[0].1.sign( - context, - message, - &spp_outputs[0].0.spp_output, - &all_signing_commitments[..1], + context.to_vec(), + message.to_vec(), + spp_outputs[0].0.clone(), + all_signing_commitments.clone(), &all_signing_nonces[0], ); diff --git a/src/olaf/frost/mod.rs b/src/olaf/frost/mod.rs index f9dbda5..93768e5 100644 --- a/src/olaf/frost/mod.rs +++ b/src/olaf/frost/mod.rs @@ -3,8 +3,9 @@ #![allow(non_snake_case)] mod types; -mod errors; +pub mod errors; +pub use self::types::{SignatureShare, SigningCommitments, SigningNonces}; use alloc::vec::Vec; use curve25519_dalek::Scalar; use rand_core::{CryptoRng, RngCore}; @@ -14,15 +15,10 @@ use crate::{ }; use self::{ errors::{FROSTError, FROSTResult}, - types::{ - BindingFactor, BindingFactorList, GroupCommitment, SignatureShare, SigningCommitments, - SigningNonces, - }, + types::{BindingFactor, BindingFactorList, GroupCommitment, SigningPackage}, }; - use super::{ - simplpedpop::{SPPOutput, SecretPolynomial}, - ThresholdPublicKey, SigningKeypair, VerifyingShare, + simplpedpop::SPPOutputMessage, Identifier, SigningKeypair, ThresholdPublicKey, VerifyingShare, }; impl SigningKeypair { @@ -90,12 +86,18 @@ impl SigningKeypair { /// [`sign`]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#name-round-two-signature-share-g pub fn sign( &self, - context: &[u8], - message: &[u8], - spp_output: &SPPOutput, - all_signing_commitments: &[SigningCommitments], + context: Vec, + message: Vec, + spp_output_message: SPPOutputMessage, + all_signing_commitments: Vec, signer_nonces: &SigningNonces, - ) -> FROSTResult { + ) -> FROSTResult { + let context_ref = &context; + let message_ref = &message; + let all_signing_commitments_ref = &all_signing_commitments; + + let spp_output = &spp_output_message.spp_output; + if spp_output.verifying_keys.len() != spp_output.parameters.participants as usize { return Err(FROSTError::IncorrectNumberOfVerifyingShares); } @@ -129,23 +131,19 @@ impl SigningKeypair { } let binding_factor_list: BindingFactorList = BindingFactorList::compute( - all_signing_commitments, + all_signing_commitments_ref, &spp_output.threshold_public_key, - message, + message_ref, ); let group_commitment = - GroupCommitment::compute(all_signing_commitments, &binding_factor_list)?; + GroupCommitment::compute(all_signing_commitments_ref, &binding_factor_list)?; let identifiers_vec: Vec<_> = spp_output.verifying_keys.iter().map(|x| x.0).collect(); - let lambda_i = SecretPolynomial::compute_lagrange_coefficient( - &identifiers_vec, - None, - *identifiers[index], - ); + let lambda_i = compute_lagrange_coefficient(&identifiers_vec, None, *identifiers[index]); - let mut transcript = SigningContext::new(context).bytes(message); + let mut transcript = SigningContext::new(context_ref).bytes(message_ref); transcript.proto_name(b"Schnorr-sig"); { let this = &mut transcript; @@ -162,7 +160,15 @@ impl SigningKeypair { &challenge, ); - Ok(signature_share) + let signing_package = SigningPackage { + message, + context, + signing_commitments: all_signing_commitments, + signature_share, + spp_output_message, + }; + + Ok(signing_package) } fn compute_signature_share( @@ -180,6 +186,43 @@ impl SigningKeypair { } } +/// Generates a lagrange coefficient. +/// +/// The Lagrange polynomial for a set of points (x_j, y_j) for 0 <= j <= k +/// is ∑_{i=0}^k y_i.ℓ_i(x), where ℓ_i(x) is the Lagrange basis polynomial: +/// +/// ℓ_i(x) = ∏_{0≤j≤k; j≠i} (x - x_j) / (x_i - x_j). +/// +/// This computes ℓ_j(x) for the set of points `xs` and for the j corresponding +/// to the given xj. +/// +/// If `x` is None, it uses 0 for it (since Identifiers can't be 0). +pub(super) fn compute_lagrange_coefficient( + x_set: &[Identifier], + x: Option, + x_i: Identifier, +) -> Scalar { + let mut num = Scalar::ONE; + let mut den = Scalar::ONE; + + for x_j in x_set.iter() { + if x_i == *x_j { + continue; + } + + if let Some(x) = x { + num *= x.0 - x_j.0; + den *= x_i.0 - x_j.0; + } else { + // Both signs inverted just to avoid requiring Neg (-*xj) + num *= x_j.0; + den *= x_j.0 - x_i.0; + } + } + + num * den.invert() +} + /// Aggregates the signature shares to produce a final signature that /// can be verified with the group public key. /// @@ -198,19 +241,29 @@ impl SigningKeypair { /// signature, if the coordinator themselves is a signer and misbehaves, they /// can avoid that step. However, at worst, this results in a denial of /// service attack due to publishing an invalid signature. -pub fn aggregate( - message: &[u8], - context: &[u8], - signing_commitments: &[SigningCommitments], - signature_shares: &Vec, - group_public_key: ThresholdPublicKey, -) -> Result { +pub fn aggregate(signing_packages: &[SigningPackage]) -> Result { + let message = &signing_packages[0].message; + let context = &signing_packages[0].context; + let signing_commitments = signing_packages[0].signing_commitments.as_slice(); + + let mut signature_shares = Vec::new(); + + for signing_package in signing_packages { + signature_shares.push(signing_package.signature_share.clone()); + } + + let threshold_public_key = + &signing_packages[0].spp_output_message.spp_output.threshold_public_key; + + // check that message, context, signing commitments and spp_output are the same + // verify signatures + if signing_commitments.len() != signature_shares.len() { return Err(FROSTError::IncorrectNumberOfSigningCommitments); } let binding_factor_list: BindingFactorList = - BindingFactorList::compute(signing_commitments, &group_public_key, message); + BindingFactorList::compute(signing_commitments, threshold_public_key, message); let group_commitment = GroupCommitment::compute(signing_commitments, &binding_factor_list)?; @@ -222,11 +275,58 @@ pub fn aggregate( let signature = Signature { R: group_commitment.0.compress(), s }; - group_public_key + threshold_public_key .0 .verify_simple(context, message, &signature) .map_err(FROSTError::InvalidSignature)?; + // Only if the verification of the aggregate signature failed; verify each share to find the cheater. + // This approach is more efficient since we don't need to verify all shares + // if the aggregate signature is valid (which should be the common case). + /*if let Err(err) = verification_result { + // Compute the per-message challenge. + let challenge = crate::challenge::( + &group_commitment.0, + &pubkeys.verifying_key, + signing_package.message().as_slice(), + ); + + // Verify the signature shares. + for (signature_share_identifier, signature_share) in signature_shares { + // Look up the public key for this signer, where `signer_pubkey` = _G.ScalarBaseMult(s[i])_, + // and where s[i] is a secret share of the constant term of _f_, the secret polynomial. + let signer_pubkey = pubkeys + .verifying_shares + .get(signature_share_identifier) + .ok_or(Error::UnknownIdentifier)?; + + // Compute Lagrange coefficient. + let lambda_i = derive_interpolating_value(signature_share_identifier, signing_package)?; + + let binding_factor = binding_factor_list + .get(signature_share_identifier) + .ok_or(Error::UnknownIdentifier)?; + + // Compute the commitment share. + let R_share = signing_package + .signing_commitment(signature_share_identifier) + .ok_or(Error::UnknownIdentifier)? + .to_group_commitment_share(binding_factor); + + // Compute relation values to verify this signature share. + signature_share.verify( + *signature_share_identifier, + &R_share, + signer_pubkey, + lambda_i, + &challenge, + )?; + } + + // We should never reach here; but we return the verification error to be safe. + return Err(err); + }*/ + Ok(signature) } @@ -292,34 +392,27 @@ mod tests { all_signing_commitments.push(signing_commitments); } - let mut signature_shares = Vec::new(); + let mut signing_packages = Vec::new(); let message = b"message"; let context = b"context"; for (i, spp_output) in spp_outputs.iter().enumerate() { - let signature_share = spp_output + let signing_package = spp_output .1 .sign( - context, - message, - &spp_output.0.spp_output, - &all_signing_commitments, + context.to_vec(), + message.to_vec(), + spp_output.0.clone(), + all_signing_commitments.clone(), &all_signing_nonces[i], ) .unwrap(); - signature_shares.push(signature_share); + signing_packages.push(signing_package); } - aggregate( - message, - context, - &all_signing_commitments, - &signature_shares, - spp_outputs[0].0.spp_output.threshold_public_key, - ) - .unwrap(); + aggregate(&signing_packages).unwrap(); } #[test] @@ -355,34 +448,27 @@ mod tests { all_signing_commitments.push(signing_commitments); } - let mut signature_shares = Vec::new(); + let mut signing_packages = Vec::new(); let message = b"message"; let context = b"context"; - for (i, spp_output) in spp_outputs[..threshold].iter().enumerate() { - let signature_share = spp_output + for (i, spp_output) in spp_outputs.iter().enumerate() { + let signing_package = spp_output .1 .sign( - context, - message, - &spp_output.0.spp_output, - &all_signing_commitments, + context.to_vec(), + message.to_vec(), + spp_output.0.clone(), + all_signing_commitments.clone(), &all_signing_nonces[i], ) .unwrap(); - signature_shares.push(signature_share); + signing_packages.push(signing_package); } - aggregate( - message, - context, - &all_signing_commitments, - &signature_shares, - spp_outputs[0].0.spp_output.threshold_public_key, - ) - .unwrap(); + aggregate(&signing_packages).unwrap(); } #[test] @@ -434,7 +520,7 @@ mod tests { commitments.push(comms); } - let mut signature_shares = Vec::new(); + let mut signing_packages = Vec::new(); let mut messages = Vec::new(); @@ -454,18 +540,23 @@ mod tests { for (j, spp_output) in spp_outputs.iter().enumerate() { let nonces_to_use = &all_nonces_map[j][i as usize]; - let signature_share = spp_output + let signing_package = spp_output .1 - .sign(context, &message, &spp_output.0.spp_output, &commitments, nonces_to_use) + .sign( + context.to_vec(), + message.to_vec(), + spp_output.0.clone(), + commitments.clone(), + nonces_to_use, + ) .unwrap(); - signature_shares.push(signature_share); + signing_packages.push(signing_package); } - aggregate(&message, context, &commitments, &signature_shares, group_public_key) - .unwrap(); + aggregate(&signing_packages).unwrap(); - signature_shares = Vec::new(); + signing_packages = Vec::new(); } } } diff --git a/src/olaf/frost/types.rs b/src/olaf/frost/types.rs index ffa5ba7..5dd33a1 100644 --- a/src/olaf/frost/types.rs +++ b/src/olaf/frost/types.rs @@ -1,7 +1,8 @@ -//! Internal types of the FROST protocol. +//! Types of the FROST protocol. use alloc::vec::Vec; use curve25519_dalek::{ + ristretto::CompressedRistretto, traits::{Identity, VartimeMultiscalarMul}, RistrettoPoint, Scalar, }; @@ -10,18 +11,58 @@ use rand_core::{CryptoRng, RngCore}; use zeroize::ZeroizeOnDrop; use crate::{ context::SigningTranscript, - olaf::{ThresholdPublicKey, GENERATOR}, - SecretKey, + olaf::{ + simplpedpop::SPPOutputMessage, Identifier, ThresholdPublicKey, VerifyingShare, + COMPRESSED_RISTRETTO_LENGTH, GENERATOR, SCALAR_LENGTH, + }, + scalar_from_canonical_bytes, SecretKey, }; -use super::errors::FROSTError; +use super::errors::{FROSTError, FROSTResult}; /// A participant's signature share, which the coordinator will aggregate with all other signer's /// shares into the joint signature. +#[derive(Clone)] pub struct SignatureShare { /// This participant's signature over the message. pub(super) share: Scalar, } +impl SignatureShare { + /// Serializes the `SignatureShare` into bytes. + pub fn to_bytes(&self) -> [u8; SCALAR_LENGTH] { + self.share.to_bytes() + } + + /// Deserializes the `SignatureShare` from bytes. + pub fn from_bytes(bytes: &[u8]) -> FROSTResult { + let mut share_bytes = [0; SCALAR_LENGTH]; + share_bytes.copy_from_slice(&bytes[..SCALAR_LENGTH]); + let share = scalar_from_canonical_bytes(share_bytes) + .ok_or(FROSTError::SignatureShareDeserializationError)?; + + Ok(SignatureShare { share }) + } + + pub(super) fn verify( + &self, + identifier: Identifier, + group_commitment_share: &GroupCommitmentShare, + verifying_share: &VerifyingShare, + lambda_i: Scalar, + challenge: &Scalar, + ) -> FROSTResult<()> { + if (GENERATOR * self.share) + != (group_commitment_share.0 + (verifying_share.0.as_point() * challenge * lambda_i)) + { + return Err(FROSTError::InvalidSignatureShare { culprit: identifier }); + } + + Ok(()) + } +} + +pub(super) struct GroupCommitmentShare(pub(super) RistrettoPoint); + /// The binding factor, also known as _rho_ (ρ), ensures each signature share is strongly bound to a signing set, specific set /// of commitments, and a specific message. pub(super) struct BindingFactor(pub(super) Scalar); @@ -116,7 +157,7 @@ impl Nonce { where R: CryptoRng + RngCore, { - let mut random_bytes = [0; 32]; + let mut random_bytes = [0; SCALAR_LENGTH]; rng.fill_bytes(&mut random_bytes[..]); Self::nonce_generate_from_random_bytes(secret, &random_bytes[..]) @@ -141,6 +182,23 @@ impl Nonce { #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub(super) struct NonceCommitment(pub(super) RistrettoPoint); +impl NonceCommitment { + /// Serializes the `NonceCommitment` into bytes. + pub(super) fn to_bytes(&self) -> [u8; 32] { + self.0.compress().to_bytes() + } + + /// Deserializes the `NonceCommitment` from bytes. + pub(super) fn from_bytes(bytes: &[u8]) -> FROSTResult { + let compressed = CompressedRistretto::from_slice(&bytes[..COMPRESSED_RISTRETTO_LENGTH]) + .map_err(FROSTError::DeserializationError)?; + + let point = compressed.decompress().ok_or(FROSTError::InvalidNonceCommitment)?; + + Ok(NonceCommitment(point)) + } +} + impl From for NonceCommitment { fn from(nonce: Nonce) -> Self { From::from(&nonce) @@ -216,6 +274,35 @@ impl SigningCommitments { pub(super) fn new(hiding: NonceCommitment, binding: NonceCommitment) -> Self { Self { hiding, binding } } + + /// Serializes the `SigningCommitments` into bytes. + pub fn to_bytes(&self) -> [u8; COMPRESSED_RISTRETTO_LENGTH * 2] { + // TODO: Add tests + let mut bytes = [0u8; COMPRESSED_RISTRETTO_LENGTH * 2]; + + let hiding_bytes = self.hiding.to_bytes(); + let binding_bytes = self.binding.to_bytes(); + + bytes[..COMPRESSED_RISTRETTO_LENGTH].copy_from_slice(&hiding_bytes); + bytes[COMPRESSED_RISTRETTO_LENGTH..].copy_from_slice(&binding_bytes); + + bytes + } + + /// Deserializes the `SigningCommitments` from bytes. + pub fn from_bytes(bytes: &[u8]) -> FROSTResult { + let hiding = NonceCommitment::from_bytes(&bytes[..COMPRESSED_RISTRETTO_LENGTH])?; + let binding = NonceCommitment::from_bytes(&bytes[COMPRESSED_RISTRETTO_LENGTH..])?; + + Ok(SigningCommitments { hiding, binding }) + } + + pub(super) fn to_group_commitment_share( + &self, + binding_factor: &BindingFactor, + ) -> GroupCommitmentShare { + GroupCommitmentShare(self.hiding.0 + (self.binding.0 * binding_factor.0)) + } } impl From<&SigningNonces> for SigningCommitments { @@ -224,6 +311,78 @@ impl From<&SigningNonces> for SigningCommitments { } } +pub struct SigningPackage { + pub(super) message: Vec, + pub(super) context: Vec, + pub(super) signing_commitments: Vec, + pub(super) signature_share: SignatureShare, + pub(super) spp_output_message: SPPOutputMessage, +} + +impl SigningPackage { + pub fn to_bytes(&self) -> Vec { + let mut bytes = Vec::new(); + + bytes.extend((self.message.len() as u32).to_le_bytes()); + bytes.extend(&self.message); + + bytes.extend((self.context.len() as u32).to_le_bytes()); + bytes.extend(&self.context); + + bytes.extend((self.signing_commitments.len() as u32).to_le_bytes()); + for commitment in &self.signing_commitments { + bytes.extend(commitment.to_bytes()); + } + + bytes.extend(self.signature_share.to_bytes()); + + bytes.extend(self.spp_output_message.to_bytes()); + + bytes + } + + pub fn from_bytes(bytes: &[u8]) -> FROSTResult { + let mut cursor = 0; + + let message_len = + u32::from_le_bytes(bytes[cursor..cursor + 4].try_into().unwrap()) as usize; + cursor += 4; + let message = bytes[cursor..cursor + message_len].to_vec(); + cursor += message_len; + + let context_len = + u32::from_le_bytes(bytes[cursor..cursor + 4].try_into().unwrap()) as usize; + cursor += 4; + let context = bytes[cursor..cursor + context_len].to_vec(); + cursor += context_len; + + let signing_commitments_len = + u32::from_le_bytes(bytes[cursor..cursor + 4].try_into().unwrap()) as usize; + cursor += 4; + let mut signing_commitments = Vec::with_capacity(signing_commitments_len); + for _ in 0..signing_commitments_len { + let commitment_bytes = &bytes[cursor..cursor + 64]; // Assuming each SigningCommitment is 64 bytes + cursor += 64; + signing_commitments.push(SigningCommitments::from_bytes(commitment_bytes)?); + } + + let share_bytes = &bytes[cursor..cursor + SCALAR_LENGTH]; + cursor += SCALAR_LENGTH; + let signature_share = SignatureShare::from_bytes(share_bytes)?; + + let spp_output_message = SPPOutputMessage::from_bytes(&bytes[cursor..]) + .map_err(FROSTError::SPPOutputMessageDeserializationError)?; + + Ok(SigningPackage { + message, + context, + signing_commitments, + signature_share, + spp_output_message, + }) + } +} + /// The product of all signers' individual commitments, published as part of the /// final signature. pub(super) struct GroupCommitment(pub(super) RistrettoPoint); diff --git a/src/olaf/mod.rs b/src/olaf/mod.rs index de7125e..9b6af14 100644 --- a/src/olaf/mod.rs +++ b/src/olaf/mod.rs @@ -12,6 +12,8 @@ use crate::{context::SigningTranscript, Keypair, PublicKey}; pub(super) const MINIMUM_THRESHOLD: u16 = 2; pub(super) const GENERATOR: RistrettoPoint = RISTRETTO_BASEPOINT_POINT; +pub(super) const COMPRESSED_RISTRETTO_LENGTH: usize = 32; +pub(crate) const SCALAR_LENGTH: usize = 32; /// The threshold public key generated in the SimplPedPoP protocol, used to validate the threshold signatures of the FROST protocol. #[derive(Copy, Clone, Debug, PartialEq, Eq)] diff --git a/src/olaf/simplpedpop/errors.rs b/src/olaf/simplpedpop/errors.rs index caff496..b1df897 100644 --- a/src/olaf/simplpedpop/errors.rs +++ b/src/olaf/simplpedpop/errors.rs @@ -19,8 +19,8 @@ pub enum SPPError { InvalidNumberOfParticipants, /// Invalid public key. InvalidPublicKey(SignatureError), - /// Invalid group public key. - InvalidGroupPublicKey, + /// Invalid threshold public key. + InvalidThresholdPublicKey, /// Invalid signature. InvalidSignature(SignatureError), /// Invalid coefficient commitment of the polynomial commitment. diff --git a/src/olaf/simplpedpop/mod.rs b/src/olaf/simplpedpop/mod.rs index b3a76e1..8a2348a 100644 --- a/src/olaf/simplpedpop/mod.rs +++ b/src/olaf/simplpedpop/mod.rs @@ -2,12 +2,10 @@ //! on Pedersen's spp. All of them have as the fundamental building block the Shamir's Secret Sharing scheme. mod types; -mod errors; +pub mod errors; -pub use self::types::{ - AllMessage, SPPOutput, SPPOutputMessage, MessageContent, Parameters, PolynomialCommitment, -}; -pub(crate) use self::types::SecretPolynomial; +pub use self::types::{AllMessage, SPPOutputMessage}; +pub(crate) use self::types::{PolynomialCommitment, SPPOutput, MessageContent, Parameters}; use alloc::vec::Vec; use curve25519_dalek::{traits::Identity, RistrettoPoint, Scalar}; use merlin::Transcript; @@ -18,7 +16,8 @@ use crate::{ use self::{ errors::{SPPError, SPPResult}, types::{ - SecretShare, CHACHA20POLY1305_KEY_LENGTH, ENCRYPTION_NONCE_LENGTH, RECIPIENTS_HASH_LENGTH, + SecretPolynomial, SecretShare, CHACHA20POLY1305_KEY_LENGTH, ENCRYPTION_NONCE_LENGTH, + RECIPIENTS_HASH_LENGTH, }, }; use super::{ThresholdPublicKey, Identifier, SigningKeypair, VerifyingShare, GENERATOR}; @@ -289,7 +288,7 @@ impl Keypair { spp_output_transcript.append_message(b"message", &spp_output.to_bytes()); let signature = self.sign(spp_output_transcript); - let spp_output = SPPOutputMessage::new(self.public, spp_output, signature); + let spp_output = SPPOutputMessage::new(VerifyingShare(self.public), spp_output, signature); let mut nonce: [u8; 32] = [0u8; 32]; getrandom_or_panic().fill_bytes(&mut nonce); diff --git a/src/olaf/simplpedpop/types.rs b/src/olaf/simplpedpop/types.rs index abe962f..8360621 100644 --- a/src/olaf/simplpedpop/types.rs +++ b/src/olaf/simplpedpop/types.rs @@ -12,18 +12,19 @@ use chacha20poly1305::{aead::Aead, ChaCha20Poly1305, Nonce}; use curve25519_dalek::{ristretto::CompressedRistretto, traits::Identity, RistrettoPoint, Scalar}; use crate::{ context::SigningTranscript, - olaf::{ThresholdPublicKey, Identifier, VerifyingShare, GENERATOR, MINIMUM_THRESHOLD}, + olaf::{ + Identifier, ThresholdPublicKey, VerifyingShare, COMPRESSED_RISTRETTO_LENGTH, GENERATOR, + MINIMUM_THRESHOLD, SCALAR_LENGTH, + }, scalar_from_canonical_bytes, PublicKey, Signature, PUBLIC_KEY_LENGTH, SIGNATURE_LENGTH, }; use super::errors::{SPPError, SPPResult}; -pub(super) const COMPRESSED_RISTRETTO_LENGTH: usize = 32; pub(super) const U16_LENGTH: usize = 2; pub(super) const ENCRYPTION_NONCE_LENGTH: usize = 12; pub(super) const RECIPIENTS_HASH_LENGTH: usize = 16; pub(super) const CHACHA20POLY1305_LENGTH: usize = 64; pub(super) const CHACHA20POLY1305_KEY_LENGTH: usize = 32; -pub(super) const SCALAR_LENGTH: usize = 32; pub(super) const VEC_LENGTH: usize = 2; #[derive(ZeroizeOnDrop)] @@ -68,7 +69,7 @@ impl SecretShare { /// The secret polynomial of a participant chosen at randoma nd used to generate the secret shares of all the participants (including itself). #[derive(ZeroizeOnDrop)] -pub(crate) struct SecretPolynomial { +pub(super) struct SecretPolynomial { pub(super) coefficients: Vec, } @@ -104,43 +105,6 @@ impl SecretPolynomial { PolynomialCommitment { coefficients_commitments } } - - /// Generates a lagrange coefficient. - /// - /// The Lagrange polynomial for a set of points (x_j, y_j) for 0 <= j <= k - /// is ∑_{i=0}^k y_i.ℓ_i(x), where ℓ_i(x) is the Lagrange basis polynomial: - /// - /// ℓ_i(x) = ∏_{0≤j≤k; j≠i} (x - x_j) / (x_i - x_j). - /// - /// This computes ℓ_j(x) for the set of points `xs` and for the j corresponding - /// to the given xj. - /// - /// If `x` is None, it uses 0 for it (since Identifiers can't be 0). - pub(crate) fn compute_lagrange_coefficient( - x_set: &[Identifier], - x: Option, - x_i: Identifier, - ) -> Scalar { - let mut num = Scalar::ONE; - let mut den = Scalar::ONE; - - for x_j in x_set.iter() { - if x_i == *x_j { - continue; - } - - if let Some(x) = x { - num *= x.0 - x_j.0; - den *= x_i.0 - x_j.0; - } else { - // Both signs inverted just to avoid requiring Neg (-*xj) - num *= x_j.0; - den *= x_j.0 - x_i.0; - } - } - - num * den.invert() - } } /// The parameters of a given execution of the SimplPedPoP protocol. @@ -152,7 +116,7 @@ pub struct Parameters { impl Parameters { /// Create new parameters. - pub fn generate(participants: u16, threshold: u16) -> Parameters { + pub(crate) fn generate(participants: u16, threshold: u16) -> Parameters { Parameters { participants, threshold } } @@ -178,7 +142,7 @@ impl Parameters { } /// Serializes `Parameters` into a byte array. - pub fn to_bytes(&self) -> [u8; U16_LENGTH * 2] { + pub(super) fn to_bytes(&self) -> [u8; U16_LENGTH * 2] { let mut bytes = [0u8; U16_LENGTH * 2]; bytes[0..U16_LENGTH].copy_from_slice(&self.participants.to_le_bytes()); bytes[U16_LENGTH..U16_LENGTH * 2].copy_from_slice(&self.threshold.to_le_bytes()); @@ -186,7 +150,7 @@ impl Parameters { } /// Constructs `Parameters` from a byte array. - pub fn from_bytes(bytes: &[u8]) -> SPPResult { + pub(super) fn from_bytes(bytes: &[u8]) -> SPPResult { if bytes.len() != U16_LENGTH * 2 { return Err(SPPError::InvalidParameters); } @@ -257,7 +221,7 @@ pub struct AllMessage { impl AllMessage { /// Creates a new message. - pub fn new(content: MessageContent, signature: Signature) -> Self { + pub(crate) fn new(content: MessageContent, signature: Signature) -> Self { Self { content, signature } } /// Serialize AllMessage @@ -298,7 +262,7 @@ pub struct MessageContent { impl MessageContent { /// Creates the content of the message. - pub fn new( + pub(super) fn new( sender: PublicKey, encryption_nonce: [u8; ENCRYPTION_NONCE_LENGTH], parameters: Parameters, @@ -321,7 +285,7 @@ impl MessageContent { } /// Serialize MessageContent into bytes. - pub fn to_bytes(&self) -> Vec { + pub(super) fn to_bytes(&self) -> Vec { let mut bytes = Vec::new(); bytes.extend(self.sender.to_bytes()); @@ -345,7 +309,7 @@ impl MessageContent { } /// Deserialize MessageContent from bytes. - pub fn from_bytes(bytes: &[u8]) -> Result { + pub(super) fn from_bytes(bytes: &[u8]) -> Result { let mut cursor = 0; let sender = PublicKey::from_bytes(&bytes[cursor..cursor + PUBLIC_KEY_LENGTH]) @@ -414,25 +378,23 @@ impl MessageContent { } /// The signed output of the SimplPedPoP protocol. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct SPPOutputMessage { - pub(crate) sender: PublicKey, - /// The output of the SimplPedPoP protocol. + pub(crate) signer: VerifyingShare, pub(crate) spp_output: SPPOutput, pub(crate) signature: Signature, } impl SPPOutputMessage { - /// Creates a signed SimplPedPoP output. - pub fn new(sender: PublicKey, content: SPPOutput, signature: Signature) -> Self { - Self { sender, spp_output: content, signature } + pub(crate) fn new(signer: VerifyingShare, content: SPPOutput, signature: Signature) -> Self { + Self { signer, signature, spp_output: content } } /// Serializes the SPPOutputMessage into bytes. pub fn to_bytes(&self) -> Vec { let mut bytes = Vec::new(); - let pk_bytes = self.sender.to_bytes(); + let pk_bytes = self.signer.0.to_bytes(); bytes.extend(pk_bytes); let content_bytes = self.spp_output.to_bytes(); @@ -449,7 +411,8 @@ impl SPPOutputMessage { let mut cursor = 0; let pk_bytes = &bytes[..PUBLIC_KEY_LENGTH]; - let sender = PublicKey::from_bytes(pk_bytes).map_err(SPPError::InvalidPublicKey)?; + let signer = + VerifyingShare(PublicKey::from_bytes(pk_bytes).map_err(SPPError::InvalidPublicKey)?); cursor += PUBLIC_KEY_LENGTH; let content_bytes = &bytes[cursor..bytes.len() - SIGNATURE_LENGTH]; @@ -459,12 +422,12 @@ impl SPPOutputMessage { let signature = Signature::from_bytes(&bytes[cursor..cursor + SIGNATURE_LENGTH]) .map_err(SPPError::InvalidSignature)?; - Ok(SPPOutputMessage { sender, spp_output, signature }) + Ok(SPPOutputMessage { signer, spp_output, signature }) } - /// Returns the output of the SimplPedPoP protocol. - pub fn spp_output(&self) -> &SPPOutput { - &self.spp_output + /// Returns the threshold public key. + pub fn threshold_public_key(&self) -> ThresholdPublicKey { + self.spp_output.threshold_public_key } /// Verifies the signature of the message. @@ -472,7 +435,8 @@ impl SPPOutputMessage { let mut spp_output_transcript = Transcript::new(b"spp output"); spp_output_transcript.append_message(b"message", &self.spp_output.to_bytes()); - self.sender + self.signer + .0 .verify(spp_output_transcript, &self.signature) .map_err(SPPError::InvalidSignature) } @@ -482,14 +446,12 @@ impl SPPOutputMessage { #[derive(Clone, Debug)] pub struct SPPOutput { pub(crate) parameters: Parameters, - /// The threshold public key generated by the SimplPedPoP protocol. pub(crate) threshold_public_key: ThresholdPublicKey, pub(crate) verifying_keys: Vec<(Identifier, VerifyingShare)>, } impl SPPOutput { - /// Creates the content of the SimplPedPoP output. - pub fn new( + pub(crate) fn new( parameters: &Parameters, threshold_public_key: ThresholdPublicKey, verifying_keys: Vec<(Identifier, VerifyingShare)>, @@ -499,13 +461,7 @@ impl SPPOutput { Self { threshold_public_key, verifying_keys, parameters } } - /// Returns the threshold public key. - pub fn threshold_public_key(&self) -> ThresholdPublicKey { - self.threshold_public_key - } - - /// Serializes the SPPOutput into bytes. - pub fn to_bytes(&self) -> Vec { + pub(crate) fn to_bytes(&self) -> Vec { let mut bytes = Vec::new(); bytes.extend(self.parameters.to_bytes()); @@ -525,7 +481,7 @@ impl SPPOutput { } /// Deserializes the SPPOutput from bytes. - pub fn from_bytes(bytes: &[u8]) -> Result { + pub(crate) fn from_bytes(bytes: &[u8]) -> Result { let mut cursor = 0; let parameters = Parameters::from_bytes(&bytes[cursor..cursor + U16_LENGTH * 2])?; @@ -537,8 +493,8 @@ impl SPPOutput { let compressed_public_key = CompressedRistretto::from_slice(public_key_bytes) .map_err(SPPError::DeserializationError)?; - let group_public_key = - compressed_public_key.decompress().ok_or(SPPError::InvalidGroupPublicKey)?; + let threshold_public_key = + compressed_public_key.decompress().ok_or(SPPError::InvalidThresholdPublicKey)?; let mut verifying_keys = Vec::new(); @@ -558,7 +514,7 @@ impl SPPOutput { } Ok(SPPOutput { - threshold_public_key: ThresholdPublicKey(PublicKey::from_point(group_public_key)), + threshold_public_key: ThresholdPublicKey(PublicKey::from_point(threshold_public_key)), verifying_keys, parameters, }) @@ -698,7 +654,7 @@ mod tests { } #[test] - fn test_spp_output_serialization() { + fn test_spp_output_message_serialization() { let mut rng = OsRng; let group_public_key = RistrettoPoint::random(&mut rng); let verifying_keys = vec![ @@ -726,37 +682,42 @@ mod tests { let keypair = Keypair::generate(); let signature = keypair.sign(Transcript::new(b"test")); - let spp_output = SPPOutputMessage { sender: keypair.public, spp_output, signature }; + let spp_output_message = + SPPOutputMessage { signer: VerifyingShare(keypair.public), spp_output, signature }; - let bytes = spp_output.to_bytes(); + let bytes = spp_output_message.to_bytes(); - let deserialized_spp_output = + let deserialized_spp_output_message = SPPOutputMessage::from_bytes(&bytes).expect("Deserialization failed"); assert_eq!( - deserialized_spp_output.spp_output.threshold_public_key.0.as_compressed(), - spp_output.spp_output.threshold_public_key.0.as_compressed(), + deserialized_spp_output_message + .spp_output + .threshold_public_key + .0 + .as_compressed(), + spp_output_message.spp_output.threshold_public_key.0.as_compressed(), "Group public keys do not match" ); assert_eq!( - deserialized_spp_output.spp_output.verifying_keys.len(), - spp_output.spp_output.verifying_keys.len(), + deserialized_spp_output_message.spp_output.verifying_keys.len(), + spp_output_message.spp_output.verifying_keys.len(), "Verifying keys counts do not match" ); assert!( - deserialized_spp_output + deserialized_spp_output_message .spp_output .verifying_keys .iter() - .zip(spp_output.spp_output.verifying_keys.iter()) + .zip(spp_output_message.spp_output.verifying_keys.iter()) .all(|((a, b), (c, d))| a.0 == c.0 && b.0 == d.0), "Verifying keys do not match" ); assert_eq!( - deserialized_spp_output.signature, spp_output.signature, + deserialized_spp_output_message.signature, spp_output_message.signature, "Signatures do not match" ); } From e3b1c631ca5277b0db7be417dfb49500a24318e3 Mon Sep 17 00:00:00 2001 From: Fiono11 Date: Thu, 16 May 2024 17:31:38 +0100 Subject: [PATCH 35/60] Add (de)serialization of SigningPackage test --- src/olaf/frost/mod.rs | 6 +- src/olaf/frost/types.rs | 136 ++++++++++++++++++++++++++++++++-- src/olaf/mod.rs | 3 +- src/olaf/simplpedpop/mod.rs | 2 +- src/olaf/simplpedpop/types.rs | 6 ++ 5 files changed, 139 insertions(+), 14 deletions(-) diff --git a/src/olaf/frost/mod.rs b/src/olaf/frost/mod.rs index 93768e5..f57365e 100644 --- a/src/olaf/frost/mod.rs +++ b/src/olaf/frost/mod.rs @@ -5,7 +5,7 @@ mod types; pub mod errors; -pub use self::types::{SignatureShare, SigningCommitments, SigningNonces}; +use self::types::{SignatureShare, SigningCommitments, SigningNonces}; use alloc::vec::Vec; use curve25519_dalek::Scalar; use rand_core::{CryptoRng, RngCore}; @@ -17,9 +17,7 @@ use self::{ errors::{FROSTError, FROSTResult}, types::{BindingFactor, BindingFactorList, GroupCommitment, SigningPackage}, }; -use super::{ - simplpedpop::SPPOutputMessage, Identifier, SigningKeypair, ThresholdPublicKey, VerifyingShare, -}; +use super::{simplpedpop::SPPOutputMessage, Identifier, SigningKeypair, VerifyingShare}; impl SigningKeypair { /// Done once by each participant, to generate _their_ nonces and commitments diff --git a/src/olaf/frost/types.rs b/src/olaf/frost/types.rs index 5dd33a1..790a27d 100644 --- a/src/olaf/frost/types.rs +++ b/src/olaf/frost/types.rs @@ -22,19 +22,19 @@ use super::errors::{FROSTError, FROSTResult}; /// A participant's signature share, which the coordinator will aggregate with all other signer's /// shares into the joint signature. #[derive(Clone)] -pub struct SignatureShare { +pub(super) struct SignatureShare { /// This participant's signature over the message. pub(super) share: Scalar, } impl SignatureShare { /// Serializes the `SignatureShare` into bytes. - pub fn to_bytes(&self) -> [u8; SCALAR_LENGTH] { + pub(super) fn to_bytes(&self) -> [u8; SCALAR_LENGTH] { self.share.to_bytes() } /// Deserializes the `SignatureShare` from bytes. - pub fn from_bytes(bytes: &[u8]) -> FROSTResult { + pub(super) fn from_bytes(bytes: &[u8]) -> FROSTResult { let mut share_bytes = [0; SCALAR_LENGTH]; share_bytes.copy_from_slice(&bytes[..SCALAR_LENGTH]); let share = scalar_from_canonical_bytes(share_bytes) @@ -184,7 +184,7 @@ pub(super) struct NonceCommitment(pub(super) RistrettoPoint); impl NonceCommitment { /// Serializes the `NonceCommitment` into bytes. - pub(super) fn to_bytes(&self) -> [u8; 32] { + pub(super) fn to_bytes(&self) -> [u8; COMPRESSED_RISTRETTO_LENGTH] { self.0.compress().to_bytes() } @@ -217,7 +217,7 @@ impl From<&Nonce> for NonceCommitment { /// operation; re-using nonces will result in leakage of a signer's long-lived /// signing key. #[derive(ZeroizeOnDrop)] -pub struct SigningNonces { +pub(super) struct SigningNonces { pub(super) hiding: Nonce, pub(super) binding: Nonce, // The commitments to the nonces. This is precomputed to improve @@ -264,7 +264,7 @@ impl SigningNonces { /// This step can be batched if desired by the implementation. Each /// SigningCommitment can be used for exactly *one* signature. #[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub struct SigningCommitments { +pub(super) struct SigningCommitments { pub(super) hiding: NonceCommitment, pub(super) binding: NonceCommitment, } @@ -276,7 +276,7 @@ impl SigningCommitments { } /// Serializes the `SigningCommitments` into bytes. - pub fn to_bytes(&self) -> [u8; COMPRESSED_RISTRETTO_LENGTH * 2] { + pub(super) fn to_bytes(&self) -> [u8; COMPRESSED_RISTRETTO_LENGTH * 2] { // TODO: Add tests let mut bytes = [0u8; COMPRESSED_RISTRETTO_LENGTH * 2]; @@ -290,7 +290,7 @@ impl SigningCommitments { } /// Deserializes the `SigningCommitments` from bytes. - pub fn from_bytes(bytes: &[u8]) -> FROSTResult { + pub(super) fn from_bytes(bytes: &[u8]) -> FROSTResult { let hiding = NonceCommitment::from_bytes(&bytes[..COMPRESSED_RISTRETTO_LENGTH])?; let binding = NonceCommitment::from_bytes(&bytes[COMPRESSED_RISTRETTO_LENGTH..])?; @@ -428,3 +428,123 @@ impl GroupCommitment { Ok(GroupCommitment(group_commitment)) } } + +#[cfg(test)] +mod tests { + use curve25519_dalek::{RistrettoPoint, Scalar}; + use merlin::Transcript; + use rand_core::OsRng; + use crate::{ + olaf::{ + simplpedpop::{Parameters, SPPOutput, SPPOutputMessage}, + Identifier, ThresholdPublicKey, VerifyingShare, GENERATOR, + }, + Keypair, PublicKey, + }; + + use super::{NonceCommitment, SignatureShare, SigningCommitments, SigningPackage}; + + #[test] + fn test_signing_package_serialization() { + let mut rng = OsRng; + let group_public_key = RistrettoPoint::random(&mut rng); + let verifying_keys = vec![ + ( + Identifier(Scalar::random(&mut rng)), + VerifyingShare(PublicKey::from_point(RistrettoPoint::random(&mut rng))), + ), + ( + Identifier(Scalar::random(&mut rng)), + VerifyingShare(PublicKey::from_point(RistrettoPoint::random(&mut rng))), + ), + ( + Identifier(Scalar::random(&mut rng)), + VerifyingShare(PublicKey::from_point(RistrettoPoint::random(&mut rng))), + ), + ]; + let parameters = Parameters::generate(2, 2); + + let spp_output = SPPOutput { + parameters, + threshold_public_key: ThresholdPublicKey(PublicKey::from_point(group_public_key)), + verifying_keys, + }; + + let keypair = Keypair::generate(); + let signature = keypair.sign(Transcript::new(b"test")); + + let spp_output_message = + SPPOutputMessage { signer: VerifyingShare(keypair.public), spp_output, signature }; + + let signing_commitments = vec![ + SigningCommitments { + hiding: NonceCommitment(Scalar::random(&mut rng) * GENERATOR), + binding: NonceCommitment(Scalar::random(&mut rng) * GENERATOR), + }, + SigningCommitments { + hiding: NonceCommitment(Scalar::random(&mut rng) * GENERATOR), + binding: NonceCommitment(Scalar::random(&mut rng) * GENERATOR), + }, + ]; + + let message = b"message".to_vec(); + let context = b"context".to_vec(); + let signature_share = SignatureShare { share: Scalar::random(&mut rng) }; + + let signing_package = SigningPackage { + message: message.clone(), + context: context.clone(), + signing_commitments: signing_commitments.clone(), + signature_share: signature_share.clone(), + spp_output_message: spp_output_message.clone(), + }; + + let signing_package_bytes = signing_package.to_bytes(); + let deserialized_signing_package = + SigningPackage::from_bytes(&signing_package_bytes).unwrap(); + + assert!(deserialized_signing_package.message == message); + assert!(deserialized_signing_package.context == context); + assert!(deserialized_signing_package.signing_commitments == signing_commitments); + assert!(deserialized_signing_package.signature_share.share == signature_share.share); + + assert_eq!( + deserialized_signing_package.spp_output_message.spp_output.parameters, + spp_output_message.spp_output.parameters, + "Group public keys do not match" + ); + + assert_eq!( + deserialized_signing_package + .spp_output_message + .spp_output + .threshold_public_key + .0 + .as_compressed(), + spp_output_message.spp_output.threshold_public_key.0.as_compressed(), + "Group public keys do not match" + ); + + assert_eq!( + deserialized_signing_package.spp_output_message.spp_output.verifying_keys.len(), + spp_output_message.spp_output.verifying_keys.len(), + "Verifying keys counts do not match" + ); + + assert!( + deserialized_signing_package + .spp_output_message + .spp_output + .verifying_keys + .iter() + .zip(spp_output_message.spp_output.verifying_keys.iter()) + .all(|((a, b), (c, d))| a.0 == c.0 && b.0 == d.0), + "Verifying keys do not match" + ); + + assert_eq!( + deserialized_signing_package.spp_output_message.signature, spp_output_message.signature, + "Signatures do not match" + ); + } +} diff --git a/src/olaf/mod.rs b/src/olaf/mod.rs index 9b6af14..fc481ff 100644 --- a/src/olaf/mod.rs +++ b/src/olaf/mod.rs @@ -8,6 +8,7 @@ pub mod frost; use curve25519_dalek::{constants::RISTRETTO_BASEPOINT_POINT, RistrettoPoint, Scalar}; use merlin::Transcript; +use zeroize::ZeroizeOnDrop; use crate::{context::SigningTranscript, Keypair, PublicKey}; pub(super) const MINIMUM_THRESHOLD: u16 = 2; @@ -24,7 +25,7 @@ pub struct ThresholdPublicKey(pub(crate) PublicKey); pub struct VerifyingShare(pub(crate) PublicKey); /// The signing keypair of a participant generated in the SimplPedPoP protocol, used to produce its signatures shares in the FROST protocol. -#[derive(Clone, Debug)] +#[derive(Clone, Debug, ZeroizeOnDrop)] pub struct SigningKeypair(pub(crate) Keypair); /// The identifier of a participant, which must be the same in the SimplPedPoP protocol and in the FROST protocol. diff --git a/src/olaf/simplpedpop/mod.rs b/src/olaf/simplpedpop/mod.rs index 8a2348a..3c936bb 100644 --- a/src/olaf/simplpedpop/mod.rs +++ b/src/olaf/simplpedpop/mod.rs @@ -347,7 +347,7 @@ mod tests { spp_outputs.push(spp_output); } - // Verify that all spp outputs are equal for group_public_key and verifying_keys + // Verify that all threshold_public_keys and verifying_keys are equal assert!( spp_outputs.windows(2).all(|w| w[0].0.spp_output.threshold_public_key.0 == w[1].0.spp_output.threshold_public_key.0 diff --git a/src/olaf/simplpedpop/types.rs b/src/olaf/simplpedpop/types.rs index 8360621..5030312 100644 --- a/src/olaf/simplpedpop/types.rs +++ b/src/olaf/simplpedpop/types.rs @@ -690,6 +690,12 @@ mod tests { let deserialized_spp_output_message = SPPOutputMessage::from_bytes(&bytes).expect("Deserialization failed"); + assert_eq!( + deserialized_spp_output_message.spp_output.parameters, + spp_output_message.spp_output.parameters, + "Group public keys do not match" + ); + assert_eq!( deserialized_spp_output_message .spp_output From 4020137ff0e1e12279a58a38e956ae3c325a63ae Mon Sep 17 00:00:00 2001 From: Fiono11 Date: Thu, 16 May 2024 17:43:40 +0100 Subject: [PATCH 36/60] Fix frost benchmark --- benches/olaf_benchmarks.rs | 33 +++++++++++++-------------------- src/lib.rs | 2 +- src/olaf/frost/mod.rs | 7 +++---- src/olaf/frost/types.rs | 14 +++++++++----- 4 files changed, 26 insertions(+), 30 deletions(-) diff --git a/benches/olaf_benchmarks.rs b/benches/olaf_benchmarks.rs index 03aae94..a8428a4 100644 --- a/benches/olaf_benchmarks.rs +++ b/benches/olaf_benchmarks.rs @@ -5,7 +5,7 @@ mod olaf_benches { use criterion::{criterion_group, BenchmarkId, Criterion}; use schnorrkel::olaf::frost::aggregate; use schnorrkel::keys::{PublicKey, Keypair}; - use schnorrkel::olaf::frost::{SigningNonces, SignatureShare, SigningCommitments}; + use schnorrkel::olaf::frost::{SigningPackage, SigningNonces, SigningCommitments}; use schnorrkel::olaf::simplpedpop::AllMessage; fn benchmark_simplpedpop(c: &mut Criterion) { @@ -85,7 +85,7 @@ mod olaf_benches { }) }); - let mut signature_shares: Vec = Vec::new(); + let mut signing_packages: Vec = Vec::new(); let message = b"message"; let context = b"context"; @@ -95,10 +95,10 @@ mod olaf_benches { spp_outputs[0] .1 .sign( - context, - message, - &spp_outputs[0].0, - &all_signing_commitments, + context.to_vec(), + message.to_vec(), + spp_outputs[0].0.clone(), + all_signing_commitments.clone(), &all_signing_nonces[0], ) .unwrap(); @@ -106,30 +106,23 @@ mod olaf_benches { }); for (i, spp_output) in spp_outputs.iter().enumerate() { - let signature_share: SignatureShare = spp_output + let signing_package: SigningPackage = spp_output .1 .sign( - context, - message, - &spp_output.0, - &all_signing_commitments, + context.to_vec(), + message.to_vec(), + spp_output.0.clone(), + all_signing_commitments.clone(), &all_signing_nonces[i], ) .unwrap(); - signature_shares.push(signature_share); + signing_packages.push(signing_package); } group.bench_function(BenchmarkId::new("aggregate", participants), |b| { b.iter(|| { - aggregate( - message, - context, - &all_signing_commitments, - &signature_shares, - spp_outputs[0].0.threshold_public_key(), - ) - .unwrap(); + aggregate(&signing_packages).unwrap(); }) }); } diff --git a/src/lib.rs b/src/lib.rs index 43eef8d..5208751 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -248,7 +248,7 @@ pub mod derive; pub mod cert; pub mod errors; -//#[cfg(all(feature = "alloc", feature = "aead"))] +#[cfg(all(feature = "alloc", feature = "aead"))] pub mod olaf; #[cfg(all(feature = "aead", feature = "getrandom"))] diff --git a/src/olaf/frost/mod.rs b/src/olaf/frost/mod.rs index f57365e..a26419d 100644 --- a/src/olaf/frost/mod.rs +++ b/src/olaf/frost/mod.rs @@ -5,7 +5,8 @@ mod types; pub mod errors; -use self::types::{SignatureShare, SigningCommitments, SigningNonces}; +pub use self::types::{SigningPackage, SigningNonces, SigningCommitments}; +use self::types::SignatureShare; use alloc::vec::Vec; use curve25519_dalek::Scalar; use rand_core::{CryptoRng, RngCore}; @@ -15,7 +16,7 @@ use crate::{ }; use self::{ errors::{FROSTError, FROSTResult}, - types::{BindingFactor, BindingFactorList, GroupCommitment, SigningPackage}, + types::{BindingFactor, BindingFactorList, GroupCommitment}, }; use super::{simplpedpop::SPPOutputMessage, Identifier, SigningKeypair, VerifyingShare}; @@ -493,8 +494,6 @@ mod tests { spp_outputs.push(spp_output); } - let group_public_key = spp_outputs[0].0.spp_output.threshold_public_key; - let mut all_nonces_map: Vec> = Vec::new(); let mut all_commitments_map: Vec> = Vec::new(); diff --git a/src/olaf/frost/types.rs b/src/olaf/frost/types.rs index 790a27d..8c9c41e 100644 --- a/src/olaf/frost/types.rs +++ b/src/olaf/frost/types.rs @@ -184,7 +184,7 @@ pub(super) struct NonceCommitment(pub(super) RistrettoPoint); impl NonceCommitment { /// Serializes the `NonceCommitment` into bytes. - pub(super) fn to_bytes(&self) -> [u8; COMPRESSED_RISTRETTO_LENGTH] { + pub(super) fn to_bytes(self) -> [u8; COMPRESSED_RISTRETTO_LENGTH] { self.0.compress().to_bytes() } @@ -217,7 +217,7 @@ impl From<&Nonce> for NonceCommitment { /// operation; re-using nonces will result in leakage of a signer's long-lived /// signing key. #[derive(ZeroizeOnDrop)] -pub(super) struct SigningNonces { +pub struct SigningNonces { pub(super) hiding: Nonce, pub(super) binding: Nonce, // The commitments to the nonces. This is precomputed to improve @@ -264,7 +264,7 @@ impl SigningNonces { /// This step can be batched if desired by the implementation. Each /// SigningCommitment can be used for exactly *one* signature. #[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub(super) struct SigningCommitments { +pub struct SigningCommitments { pub(super) hiding: NonceCommitment, pub(super) binding: NonceCommitment, } @@ -276,7 +276,7 @@ impl SigningCommitments { } /// Serializes the `SigningCommitments` into bytes. - pub(super) fn to_bytes(&self) -> [u8; COMPRESSED_RISTRETTO_LENGTH * 2] { + pub(super) fn to_bytes(self) -> [u8; COMPRESSED_RISTRETTO_LENGTH * 2] { // TODO: Add tests let mut bytes = [0u8; COMPRESSED_RISTRETTO_LENGTH * 2]; @@ -298,7 +298,7 @@ impl SigningCommitments { } pub(super) fn to_group_commitment_share( - &self, + self, binding_factor: &BindingFactor, ) -> GroupCommitmentShare { GroupCommitmentShare(self.hiding.0 + (self.binding.0 * binding_factor.0)) @@ -311,6 +311,8 @@ impl From<&SigningNonces> for SigningCommitments { } } +/// The signing package that each signer produces in the signing round of the FROST protocol and sends to the +/// combiner, which aggregates them into the final threshold signature. pub struct SigningPackage { pub(super) message: Vec, pub(super) context: Vec, @@ -320,6 +322,7 @@ pub struct SigningPackage { } impl SigningPackage { + /// Serializes SigningPackage into bytes. pub fn to_bytes(&self) -> Vec { let mut bytes = Vec::new(); @@ -341,6 +344,7 @@ impl SigningPackage { bytes } + /// Deserializes SigningPackage from bytes. pub fn from_bytes(bytes: &[u8]) -> FROSTResult { let mut cursor = 0; From d830b7b2c506050d5bcbdac0bb7efc05e5af712d Mon Sep 17 00:00:00 2001 From: Fiono11 Date: Thu, 16 May 2024 19:30:21 +0100 Subject: [PATCH 37/60] Implement cheater detection --- src/olaf/frost/errors.rs | 91 ++++++++++++++++- src/olaf/frost/mod.rs | 184 ++++++++++++++++++++-------------- src/olaf/frost/types.rs | 55 ++-------- src/olaf/simplpedpop/types.rs | 33 +----- 4 files changed, 210 insertions(+), 153 deletions(-) diff --git a/src/olaf/frost/errors.rs b/src/olaf/frost/errors.rs index 2d8ad48..2eb55c3 100644 --- a/src/olaf/frost/errors.rs +++ b/src/olaf/frost/errors.rs @@ -3,7 +3,7 @@ use core::array::TryFromSliceError; use crate::{ - olaf::{simplpedpop::errors::SPPError, Identifier}, + olaf::{simplpedpop::errors::SPPError, VerifyingShare}, SignatureError, }; @@ -31,8 +31,8 @@ pub enum FROSTError { SignatureShareDeserializationError, /// The signature share is invalid. InvalidSignatureShare { - /// The identifier of the signer whose share validation failed. - culprit: Identifier, + /// The verifying share of the culprit. + culprit: VerifyingShare, }, /// The output of the SimplPedPoP protocol must contain the participant's verifying share. InvalidOwnVerifyingShare, @@ -46,16 +46,29 @@ pub enum FROSTError { InvalidPublicKey(SignatureError), /// Error deserializing the output message of the SimplPedPoP protocol. SPPOutputMessageDeserializationError(SPPError), + /// The number of signing packages must be at least equal to the threshold. + InvalidNumberOfSigningPackages, + /// The messages of all signing packages must be equal. + MismatchedMessage, + /// The contexts of all signing packages must be equal. + MismatchedContext, + /// The signing commitments of all signing packages must be equal. + MismatchedSigningCommitments, + /// The output of the SimplPedPoP protocol of all signing packages be equal. + MismatchedSPPOutput, } #[cfg(test)] mod tests { use alloc::vec::Vec; - use curve25519_dalek::{traits::Identity, RistrettoPoint}; + use curve25519_dalek::{traits::Identity, RistrettoPoint, Scalar}; use rand_core::OsRng; use crate::{ olaf::{ - frost::types::{NonceCommitment, SigningCommitments}, + frost::{ + aggregate, + types::{NonceCommitment, SigningCommitments}, + }, simplpedpop::{AllMessage, Parameters}, SigningKeypair, }, @@ -63,6 +76,74 @@ mod tests { }; use super::FROSTError; + #[test] + fn test_invalid_signature_share() { + let parameters = Parameters::generate(2, 2); + let participants = parameters.participants as usize; + let threshold = parameters.threshold as usize; + + let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public).collect(); + + let mut all_messages = Vec::new(); + for i in 0..participants { + let message: AllMessage = keypairs[i] + .simplpedpop_contribute_all(threshold as u16, public_keys.clone()) + .unwrap(); + all_messages.push(message); + } + + let mut spp_outputs = Vec::new(); + + for kp in keypairs.iter() { + let spp_output = kp.simplpedpop_recipient_all(&all_messages).unwrap(); + spp_outputs.push(spp_output); + } + + let mut all_signing_commitments = Vec::new(); + let mut all_signing_nonces = Vec::new(); + + for spp_output in &spp_outputs[..threshold] { + let (signing_nonces, signing_commitments) = spp_output.1.commit(&mut OsRng); + all_signing_nonces.push(signing_nonces); + all_signing_commitments.push(signing_commitments); + } + + let mut signing_packages = Vec::new(); + + let message = b"message"; + let context = b"context"; + + for (i, spp_output) in spp_outputs.iter().enumerate() { + let signing_package = spp_output + .1 + .sign( + context.to_vec(), + message.to_vec(), + spp_output.0.clone(), + all_signing_commitments.clone(), + &all_signing_nonces[i], + ) + .unwrap(); + + signing_packages.push(signing_package); + } + + signing_packages[0].signature_share.share += Scalar::ONE; + + let result = aggregate(&signing_packages); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + FROSTError::InvalidSignatureShare { culprit } => { + assert_eq!(culprit, signing_packages[0].spp_output_message.signer); + }, + _ => panic!("Expected FROSTError::InvalidSignatureShare, but got {:?}", e), + }, + } + } + #[test] fn test_invalid_own_verifying_share_error() { let parameters = Parameters::generate(2, 2); diff --git a/src/olaf/frost/mod.rs b/src/olaf/frost/mod.rs index a26419d..abe076f 100644 --- a/src/olaf/frost/mod.rs +++ b/src/olaf/frost/mod.rs @@ -1,6 +1,7 @@ //! Implementation of the FROST protocol (). #![allow(non_snake_case)] +#![allow(clippy::result_large_err)] mod types; pub mod errors; @@ -9,6 +10,7 @@ pub use self::types::{SigningPackage, SigningNonces, SigningCommitments}; use self::types::SignatureShare; use alloc::vec::Vec; use curve25519_dalek::Scalar; +use merlin::Transcript; use rand_core::{CryptoRng, RngCore}; use crate::{ context::{SigningContext, SigningTranscript}, @@ -18,7 +20,9 @@ use self::{ errors::{FROSTError, FROSTResult}, types::{BindingFactor, BindingFactorList, GroupCommitment}, }; -use super::{simplpedpop::SPPOutputMessage, Identifier, SigningKeypair, VerifyingShare}; +use super::{ + simplpedpop::SPPOutputMessage, Identifier, SigningKeypair, ThresholdPublicKey, VerifyingShare, +}; impl SigningKeypair { /// Done once by each participant, to generate _their_ nonces and commitments @@ -91,10 +95,7 @@ impl SigningKeypair { all_signing_commitments: Vec, signer_nonces: &SigningNonces, ) -> FROSTResult { - let context_ref = &context; - let message_ref = &message; - let all_signing_commitments_ref = &all_signing_commitments; - + let threshold_public_key = &spp_output_message.threshold_public_key(); let spp_output = &spp_output_message.spp_output; if spp_output.verifying_keys.len() != spp_output.parameters.participants as usize { @@ -130,27 +131,20 @@ impl SigningKeypair { } let binding_factor_list: BindingFactorList = BindingFactorList::compute( - all_signing_commitments_ref, + &all_signing_commitments, &spp_output.threshold_public_key, - message_ref, + &message, ); let group_commitment = - GroupCommitment::compute(all_signing_commitments_ref, &binding_factor_list)?; + GroupCommitment::compute(&all_signing_commitments, &binding_factor_list)?; let identifiers_vec: Vec<_> = spp_output.verifying_keys.iter().map(|x| x.0).collect(); let lambda_i = compute_lagrange_coefficient(&identifiers_vec, None, *identifiers[index]); - let mut transcript = SigningContext::new(context_ref).bytes(message_ref); - transcript.proto_name(b"Schnorr-sig"); - { - let this = &mut transcript; - let compressed = spp_output.threshold_public_key.0.as_compressed(); - this.append_message(b"sign:pk", compressed.as_bytes()); - }; - transcript.commit_point(b"sign:R", &group_commitment.0.compress()); - let challenge = transcript.challenge_scalar(b"sign:c"); + let challenge = + compute_challenge(&context, &message, threshold_public_key, &group_commitment); let signature_share = self.compute_signature_share( signer_nonces, @@ -241,24 +235,47 @@ pub(super) fn compute_lagrange_coefficient( /// can avoid that step. However, at worst, this results in a denial of /// service attack due to publishing an invalid signature. pub fn aggregate(signing_packages: &[SigningPackage]) -> Result { - let message = &signing_packages[0].message; - let context = &signing_packages[0].context; - let signing_commitments = signing_packages[0].signing_commitments.as_slice(); - - let mut signature_shares = Vec::new(); + let parameters = &signing_packages[0].spp_output_message.spp_output.parameters; - for signing_package in signing_packages { - signature_shares.push(signing_package.signature_share.clone()); + if signing_packages.len() < parameters.threshold as usize { + return Err(FROSTError::InvalidNumberOfSigningPackages); } + let message = &signing_packages[0].message; + let context = &signing_packages[0].context; + let signing_commitments = &signing_packages[0].signing_commitments; let threshold_public_key = &signing_packages[0].spp_output_message.spp_output.threshold_public_key; + let mut signature_shares = Vec::new(); + + for signing_package in signing_packages.iter() { + if &signing_package.message != message { + return Err(FROSTError::MismatchedMessage); + } + if &signing_package.context != context { + return Err(FROSTError::MismatchedContext); + } + if signing_package.signing_commitments != *signing_commitments { + return Err(FROSTError::MismatchedSigningCommitments); + } + if signing_package.spp_output_message.spp_output + != signing_packages[0].spp_output_message.spp_output + { + return Err(FROSTError::MismatchedSPPOutput); + } + + signature_shares.push(signing_package.signature_share.clone()); - // check that message, context, signing commitments and spp_output are the same - // verify signatures + let mut transcript = Transcript::new(b"spp output"); + transcript + .append_message(b"message", &signing_package.spp_output_message.spp_output.to_bytes()); - if signing_commitments.len() != signature_shares.len() { - return Err(FROSTError::IncorrectNumberOfSigningCommitments); + signing_package + .spp_output_message + .signer + .0 + .verify(transcript, &signing_package.spp_output_message.signature) + .map_err(FROSTError::InvalidSignature)?; } let binding_factor_list: BindingFactorList = @@ -268,67 +285,88 @@ pub fn aggregate(signing_packages: &[SigningPackage]) -> Result = signing_packages[0] + .spp_output_message + .spp_output + .verifying_keys + .iter() + .map(|x| x.0) + .collect(); // Only if the verification of the aggregate signature failed; verify each share to find the cheater. // This approach is more efficient since we don't need to verify all shares // if the aggregate signature is valid (which should be the common case). - /*if let Err(err) = verification_result { - // Compute the per-message challenge. - let challenge = crate::challenge::( - &group_commitment.0, - &pubkeys.verifying_key, - signing_package.message().as_slice(), - ); - - // Verify the signature shares. - for (signature_share_identifier, signature_share) in signature_shares { - // Look up the public key for this signer, where `signer_pubkey` = _G.ScalarBaseMult(s[i])_, - // and where s[i] is a secret share of the constant term of _f_, the secret polynomial. - let signer_pubkey = pubkeys - .verifying_shares - .get(signature_share_identifier) - .ok_or(Error::UnknownIdentifier)?; - - // Compute Lagrange coefficient. - let lambda_i = derive_interpolating_value(signature_share_identifier, signing_package)?; - - let binding_factor = binding_factor_list - .get(signature_share_identifier) - .ok_or(Error::UnknownIdentifier)?; - - // Compute the commitment share. - let R_share = signing_package - .signing_commitment(signature_share_identifier) - .ok_or(Error::UnknownIdentifier)? - .to_group_commitment_share(binding_factor); - - // Compute relation values to verify this signature share. - signature_share.verify( - *signature_share_identifier, - &R_share, - signer_pubkey, - lambda_i, - &challenge, - )?; - } + if verification_result.is_err() { + // Compute the per-message challenge. + let challenge = + compute_challenge(context, message, threshold_public_key, &group_commitment); + + // Verify the signature shares. + for (j, signature_share) in signature_shares.iter().enumerate() { + let mut valid = false; + + for (i, (identifier, verifying_share)) in signing_packages[0] + .spp_output_message + .spp_output + .verifying_keys + .iter() + .enumerate() + { + let lambda_i = compute_lagrange_coefficient(&identifiers, None, *identifier); + + let binding_factor = + &binding_factor_list.0.get(i).ok_or(FROSTError::InvalidIdentifier)?.1; + + let R_share = signing_commitments[j].to_group_commitment_share(binding_factor); + + let result = + signature_share.verify(&R_share, verifying_share, lambda_i, &challenge); + + if result.is_ok() { + valid = true; + break; + } + } - // We should never reach here; but we return the verification error to be safe. - return Err(err); - }*/ + if !valid { + return Err(FROSTError::InvalidSignatureShare { + culprit: signing_packages[j].spp_output_message.signer, + }); + } + } + } Ok(signature) } +fn compute_challenge( + context: &[u8], + message: &[u8], + threshold_public_key: &ThresholdPublicKey, + group_commitment: &GroupCommitment, +) -> Scalar { + let mut transcript = SigningContext::new(context).bytes(message); + transcript.proto_name(b"Schnorr-sig"); + { + let this = &mut transcript; + let compressed = threshold_public_key.0.as_compressed(); + this.append_message(b"sign:pk", compressed.as_bytes()); + }; + transcript.commit_point(b"sign:R", &group_commitment.0.compress()); + transcript.challenge_scalar(b"sign:c") +} + #[cfg(test)] mod tests { use alloc::vec::Vec; diff --git a/src/olaf/frost/types.rs b/src/olaf/frost/types.rs index 8c9c41e..8844437 100644 --- a/src/olaf/frost/types.rs +++ b/src/olaf/frost/types.rs @@ -12,7 +12,7 @@ use zeroize::ZeroizeOnDrop; use crate::{ context::SigningTranscript, olaf::{ - simplpedpop::SPPOutputMessage, Identifier, ThresholdPublicKey, VerifyingShare, + simplpedpop::SPPOutputMessage, ThresholdPublicKey, VerifyingShare, COMPRESSED_RISTRETTO_LENGTH, GENERATOR, SCALAR_LENGTH, }, scalar_from_canonical_bytes, SecretKey, @@ -45,7 +45,6 @@ impl SignatureShare { pub(super) fn verify( &self, - identifier: Identifier, group_commitment_share: &GroupCommitmentShare, verifying_share: &VerifyingShare, lambda_i: Scalar, @@ -54,7 +53,7 @@ impl SignatureShare { if (GENERATOR * self.share) != (group_commitment_share.0 + (verifying_share.0.as_point() * challenge * lambda_i)) { - return Err(FROSTError::InvalidSignatureShare { culprit: identifier }); + return Err(FROSTError::InvalidSignatureShare { culprit: *verifying_share }); } Ok(()) @@ -65,6 +64,7 @@ pub(super) struct GroupCommitmentShare(pub(super) RistrettoPoint); /// The binding factor, also known as _rho_ (ρ), ensures each signature share is strongly bound to a signing set, specific set /// of commitments, and a specific message. +#[derive(Clone)] pub(super) struct BindingFactor(pub(super) Scalar); /// A list of binding factors and their associated identifiers. @@ -312,7 +312,7 @@ impl From<&SigningNonces> for SigningCommitments { } /// The signing package that each signer produces in the signing round of the FROST protocol and sends to the -/// combiner, which aggregates them into the final threshold signature. +/// coordinator, which aggregates them into the final threshold signature. pub struct SigningPackage { pub(super) message: Vec, pub(super) context: Vec, @@ -477,8 +477,11 @@ mod tests { let keypair = Keypair::generate(); let signature = keypair.sign(Transcript::new(b"test")); - let spp_output_message = - SPPOutputMessage { signer: VerifyingShare(keypair.public), spp_output, signature }; + let spp_output_message = SPPOutputMessage { + signer: VerifyingShare(keypair.public), + spp_output: spp_output.clone(), + signature, + }; let signing_commitments = vec![ SigningCommitments { @@ -511,44 +514,6 @@ mod tests { assert!(deserialized_signing_package.context == context); assert!(deserialized_signing_package.signing_commitments == signing_commitments); assert!(deserialized_signing_package.signature_share.share == signature_share.share); - - assert_eq!( - deserialized_signing_package.spp_output_message.spp_output.parameters, - spp_output_message.spp_output.parameters, - "Group public keys do not match" - ); - - assert_eq!( - deserialized_signing_package - .spp_output_message - .spp_output - .threshold_public_key - .0 - .as_compressed(), - spp_output_message.spp_output.threshold_public_key.0.as_compressed(), - "Group public keys do not match" - ); - - assert_eq!( - deserialized_signing_package.spp_output_message.spp_output.verifying_keys.len(), - spp_output_message.spp_output.verifying_keys.len(), - "Verifying keys counts do not match" - ); - - assert!( - deserialized_signing_package - .spp_output_message - .spp_output - .verifying_keys - .iter() - .zip(spp_output_message.spp_output.verifying_keys.iter()) - .all(|((a, b), (c, d))| a.0 == c.0 && b.0 == d.0), - "Verifying keys do not match" - ); - - assert_eq!( - deserialized_signing_package.spp_output_message.signature, spp_output_message.signature, - "Signatures do not match" - ); + assert!(deserialized_signing_package.spp_output_message.spp_output == spp_output); } } diff --git a/src/olaf/simplpedpop/types.rs b/src/olaf/simplpedpop/types.rs index 5030312..fd8c653 100644 --- a/src/olaf/simplpedpop/types.rs +++ b/src/olaf/simplpedpop/types.rs @@ -378,7 +378,7 @@ impl MessageContent { } /// The signed output of the SimplPedPoP protocol. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct SPPOutputMessage { pub(crate) signer: VerifyingShare, pub(crate) spp_output: SPPOutput, @@ -443,7 +443,7 @@ impl SPPOutputMessage { } /// The content of the signed output of the SimplPedPoP protocol. -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct SPPOutput { pub(crate) parameters: Parameters, pub(crate) threshold_public_key: ThresholdPublicKey, @@ -691,37 +691,10 @@ mod tests { SPPOutputMessage::from_bytes(&bytes).expect("Deserialization failed"); assert_eq!( - deserialized_spp_output_message.spp_output.parameters, - spp_output_message.spp_output.parameters, + deserialized_spp_output_message.spp_output, spp_output_message.spp_output, "Group public keys do not match" ); - assert_eq!( - deserialized_spp_output_message - .spp_output - .threshold_public_key - .0 - .as_compressed(), - spp_output_message.spp_output.threshold_public_key.0.as_compressed(), - "Group public keys do not match" - ); - - assert_eq!( - deserialized_spp_output_message.spp_output.verifying_keys.len(), - spp_output_message.spp_output.verifying_keys.len(), - "Verifying keys counts do not match" - ); - - assert!( - deserialized_spp_output_message - .spp_output - .verifying_keys - .iter() - .zip(spp_output_message.spp_output.verifying_keys.iter()) - .all(|((a, b), (c, d))| a.0 == c.0 && b.0 == d.0), - "Verifying keys do not match" - ); - assert_eq!( deserialized_spp_output_message.signature, spp_output_message.signature, "Signatures do not match" From 8e091d301608198c888d87d739adfb534e9ca706 Mon Sep 17 00:00:00 2001 From: Fiono11 Date: Thu, 16 May 2024 23:12:23 +0100 Subject: [PATCH 38/60] Improvements --- src/olaf/frost/errors.rs | 31 +++++++++----- src/olaf/frost/mod.rs | 79 +++++++++++++---------------------- src/olaf/frost/types.rs | 49 +++++++--------------- src/olaf/simplpedpop/mod.rs | 4 +- src/olaf/simplpedpop/types.rs | 11 +++-- 5 files changed, 74 insertions(+), 100 deletions(-) diff --git a/src/olaf/frost/errors.rs b/src/olaf/frost/errors.rs index 2eb55c3..f3c51aa 100644 --- a/src/olaf/frost/errors.rs +++ b/src/olaf/frost/errors.rs @@ -2,6 +2,8 @@ use core::array::TryFromSliceError; +use alloc::vec::Vec; + use crate::{ olaf::{simplpedpop::errors::SPPError, VerifyingShare}, SignatureError, @@ -31,8 +33,8 @@ pub enum FROSTError { SignatureShareDeserializationError, /// The signature share is invalid. InvalidSignatureShare { - /// The verifying share of the culprit. - culprit: VerifyingShare, + /// The verifying share(s) of the culprit(s). + culprit: Vec, }, /// The output of the SimplPedPoP protocol must contain the participant's verifying share. InvalidOwnVerifyingShare, @@ -44,8 +46,8 @@ pub enum FROSTError { InvalidNonceCommitment, /// Invalid public key. InvalidPublicKey(SignatureError), - /// Error deserializing the output message of the SimplPedPoP protocol. - SPPOutputMessageDeserializationError(SPPError), + /// Error deserializing the output of the SimplPedPoP protocol. + SPPOutputDeserializationError(SPPError), /// The number of signing packages must be at least equal to the threshold. InvalidNumberOfSigningPackages, /// The messages of all signing packages must be equal. @@ -120,7 +122,7 @@ mod tests { .sign( context.to_vec(), message.to_vec(), - spp_output.0.clone(), + spp_output.0.spp_output.clone(), all_signing_commitments.clone(), &all_signing_nonces[i], ) @@ -130,6 +132,7 @@ mod tests { } signing_packages[0].signature_share.share += Scalar::ONE; + signing_packages[1].signature_share.share += Scalar::ONE; let result = aggregate(&signing_packages); @@ -137,7 +140,13 @@ mod tests { Ok(_) => panic!("Expected an error, but got Ok."), Err(e) => match e { FROSTError::InvalidSignatureShare { culprit } => { - assert_eq!(culprit, signing_packages[0].spp_output_message.signer); + assert_eq!( + culprit, + vec![ + spp_outputs[0].0.spp_output.verifying_keys[0].1, + spp_outputs[0].0.spp_output.verifying_keys[1].1 + ] + ); }, _ => panic!("Expected FROSTError::InvalidSignatureShare, but got {:?}", e), }, @@ -185,7 +194,7 @@ mod tests { let result = spp_outputs[0].1.sign( context.to_vec(), message.to_vec(), - spp_outputs[0].0.clone(), + spp_outputs[0].0.spp_output.clone(), all_signing_commitments.clone(), &all_signing_nonces[0], ); @@ -242,7 +251,7 @@ mod tests { let result = spp_outputs[0].1.sign( context.to_vec(), message.to_vec(), - spp_outputs[0].0.clone(), + spp_outputs[0].0.spp_output.clone(), all_signing_commitments.clone(), &all_signing_nonces[0], ); @@ -302,7 +311,7 @@ mod tests { let result = spp_outputs[0].1.sign( context.to_vec(), message.to_vec(), - spp_outputs[0].0.clone(), + spp_outputs[0].0.spp_output.clone(), all_signing_commitments.clone(), &all_signing_nonces[0], ); @@ -359,7 +368,7 @@ mod tests { let result = spp_outputs[0].1.sign( context.to_vec(), message.to_vec(), - spp_outputs[0].0.clone(), + spp_outputs[0].0.spp_output.clone(), all_signing_commitments.clone(), &all_signing_nonces[0], ); @@ -416,7 +425,7 @@ mod tests { let result = spp_outputs[0].1.sign( context.to_vec(), message.to_vec(), - spp_outputs[0].0.clone(), + spp_outputs[0].0.spp_output.clone(), all_signing_commitments.clone(), &all_signing_nonces[0], ); diff --git a/src/olaf/frost/mod.rs b/src/olaf/frost/mod.rs index abe076f..dac244e 100644 --- a/src/olaf/frost/mod.rs +++ b/src/olaf/frost/mod.rs @@ -20,9 +20,7 @@ use self::{ errors::{FROSTError, FROSTResult}, types::{BindingFactor, BindingFactorList, GroupCommitment}, }; -use super::{ - simplpedpop::SPPOutputMessage, Identifier, SigningKeypair, ThresholdPublicKey, VerifyingShare, -}; +use super::{simplpedpop::SPPOutput, Identifier, SigningKeypair, ThresholdPublicKey, VerifyingShare}; impl SigningKeypair { /// Done once by each participant, to generate _their_ nonces and commitments @@ -91,12 +89,11 @@ impl SigningKeypair { &self, context: Vec, message: Vec, - spp_output_message: SPPOutputMessage, + spp_output: SPPOutput, all_signing_commitments: Vec, signer_nonces: &SigningNonces, ) -> FROSTResult { - let threshold_public_key = &spp_output_message.threshold_public_key(); - let spp_output = &spp_output_message.spp_output; + let threshold_public_key = &spp_output.threshold_public_key; if spp_output.verifying_keys.len() != spp_output.parameters.participants as usize { return Err(FROSTError::IncorrectNumberOfVerifyingShares); @@ -158,7 +155,7 @@ impl SigningKeypair { context, signing_commitments: all_signing_commitments, signature_share, - spp_output_message, + spp_output, }; Ok(signing_package) @@ -235,7 +232,7 @@ pub(super) fn compute_lagrange_coefficient( /// can avoid that step. However, at worst, this results in a denial of /// service attack due to publishing an invalid signature. pub fn aggregate(signing_packages: &[SigningPackage]) -> Result { - let parameters = &signing_packages[0].spp_output_message.spp_output.parameters; + let parameters = &signing_packages[0].spp_output.parameters; if signing_packages.len() < parameters.threshold as usize { return Err(FROSTError::InvalidNumberOfSigningPackages); @@ -244,8 +241,7 @@ pub fn aggregate(signing_packages: &[SigningPackage]) -> Result Result Result = signing_packages[0] - .spp_output_message - .spp_output - .verifying_keys - .iter() - .map(|x| x.0) - .collect(); + let identifiers: Vec = + signing_packages[0].spp_output.verifying_keys.iter().map(|x| x.0).collect(); + + let verifying_shares: Vec = + signing_packages[0].spp_output.verifying_keys.iter().map(|x| x.1).collect(); + + let mut valid_shares = Vec::new(); // Only if the verification of the aggregate signature failed; verify each share to find the cheater. // This approach is more efficient since we don't need to verify all shares @@ -314,14 +300,8 @@ pub fn aggregate(signing_packages: &[SigningPackage]) -> Result Result FROSTResult<()> { - if (GENERATOR * self.share) - != (group_commitment_share.0 + (verifying_share.0.as_point() * challenge * lambda_i)) - { - return Err(FROSTError::InvalidSignatureShare { culprit: *verifying_share }); - } - - Ok(()) + ) -> bool { + (GENERATOR * self.share) + == (group_commitment_share.0 + (verifying_share.0.as_point() * challenge * lambda_i)) } } @@ -318,7 +313,7 @@ pub struct SigningPackage { pub(super) context: Vec, pub(super) signing_commitments: Vec, pub(super) signature_share: SignatureShare, - pub(super) spp_output_message: SPPOutputMessage, + pub(super) spp_output: SPPOutput, } impl SigningPackage { @@ -339,7 +334,7 @@ impl SigningPackage { bytes.extend(self.signature_share.to_bytes()); - bytes.extend(self.spp_output_message.to_bytes()); + bytes.extend(self.spp_output.to_bytes()); bytes } @@ -374,16 +369,10 @@ impl SigningPackage { cursor += SCALAR_LENGTH; let signature_share = SignatureShare::from_bytes(share_bytes)?; - let spp_output_message = SPPOutputMessage::from_bytes(&bytes[cursor..]) - .map_err(FROSTError::SPPOutputMessageDeserializationError)?; + let spp_output = SPPOutput::from_bytes(&bytes[cursor..]) + .map_err(FROSTError::SPPOutputDeserializationError)?; - Ok(SigningPackage { - message, - context, - signing_commitments, - signature_share, - spp_output_message, - }) + Ok(SigningPackage { message, context, signing_commitments, signature_share, spp_output }) } } @@ -436,14 +425,13 @@ impl GroupCommitment { #[cfg(test)] mod tests { use curve25519_dalek::{RistrettoPoint, Scalar}; - use merlin::Transcript; use rand_core::OsRng; use crate::{ olaf::{ - simplpedpop::{Parameters, SPPOutput, SPPOutputMessage}, + simplpedpop::{Parameters, SPPOutput}, Identifier, ThresholdPublicKey, VerifyingShare, GENERATOR, }, - Keypair, PublicKey, + PublicKey, }; use super::{NonceCommitment, SignatureShare, SigningCommitments, SigningPackage}; @@ -474,15 +462,6 @@ mod tests { verifying_keys, }; - let keypair = Keypair::generate(); - let signature = keypair.sign(Transcript::new(b"test")); - - let spp_output_message = SPPOutputMessage { - signer: VerifyingShare(keypair.public), - spp_output: spp_output.clone(), - signature, - }; - let signing_commitments = vec![ SigningCommitments { hiding: NonceCommitment(Scalar::random(&mut rng) * GENERATOR), @@ -503,7 +482,7 @@ mod tests { context: context.clone(), signing_commitments: signing_commitments.clone(), signature_share: signature_share.clone(), - spp_output_message: spp_output_message.clone(), + spp_output: spp_output.clone(), }; let signing_package_bytes = signing_package.to_bytes(); @@ -514,6 +493,6 @@ mod tests { assert!(deserialized_signing_package.context == context); assert!(deserialized_signing_package.signing_commitments == signing_commitments); assert!(deserialized_signing_package.signature_share.share == signature_share.share); - assert!(deserialized_signing_package.spp_output_message.spp_output == spp_output); + assert!(deserialized_signing_package.spp_output == spp_output); } } diff --git a/src/olaf/simplpedpop/mod.rs b/src/olaf/simplpedpop/mod.rs index 3c936bb..6f05f29 100644 --- a/src/olaf/simplpedpop/mod.rs +++ b/src/olaf/simplpedpop/mod.rs @@ -4,8 +4,8 @@ mod types; pub mod errors; -pub use self::types::{AllMessage, SPPOutputMessage}; -pub(crate) use self::types::{PolynomialCommitment, SPPOutput, MessageContent, Parameters}; +pub use self::types::{AllMessage, SPPOutputMessage, SPPOutput}; +pub(crate) use self::types::{PolynomialCommitment, MessageContent, Parameters}; use alloc::vec::Vec; use curve25519_dalek::{traits::Identity, RistrettoPoint, Scalar}; use merlin::Transcript; diff --git a/src/olaf/simplpedpop/types.rs b/src/olaf/simplpedpop/types.rs index fd8c653..f57e217 100644 --- a/src/olaf/simplpedpop/types.rs +++ b/src/olaf/simplpedpop/types.rs @@ -425,9 +425,9 @@ impl SPPOutputMessage { Ok(SPPOutputMessage { signer, spp_output, signature }) } - /// Returns the threshold public key. - pub fn threshold_public_key(&self) -> ThresholdPublicKey { - self.spp_output.threshold_public_key + /// Returns the output of the SimplPedPoP protocol. + pub fn spp_output(&self) -> SPPOutput { + self.spp_output.clone() } /// Verifies the signature of the message. @@ -519,6 +519,11 @@ impl SPPOutput { parameters, }) } + + /// Returns the threshold public key. + pub fn threshold_public_key(&self) -> ThresholdPublicKey { + self.threshold_public_key + } } #[cfg(test)] From b0a0ed9a64c2a34b1c9ec10538a5c4165b69e760 Mon Sep 17 00:00:00 2001 From: Fiono11 Date: Fri, 17 May 2024 12:28:52 +0100 Subject: [PATCH 39/60] Improvements --- benches/olaf_benchmarks.rs | 4 +- benches/schnorr_benchmarks.rs | 18 ++----- src/olaf/frost/errors.rs | 16 +++--- src/olaf/frost/mod.rs | 52 ++++++++---------- src/olaf/frost/types.rs | 99 ++++++++++++++++++++++++---------- src/olaf/simplpedpop/errors.rs | 2 + src/olaf/simplpedpop/mod.rs | 4 ++ 7 files changed, 112 insertions(+), 83 deletions(-) diff --git a/benches/olaf_benchmarks.rs b/benches/olaf_benchmarks.rs index a8428a4..53319f9 100644 --- a/benches/olaf_benchmarks.rs +++ b/benches/olaf_benchmarks.rs @@ -97,7 +97,7 @@ mod olaf_benches { .sign( context.to_vec(), message.to_vec(), - spp_outputs[0].0.clone(), + spp_outputs[0].0.clone().spp_output(), all_signing_commitments.clone(), &all_signing_nonces[0], ) @@ -111,7 +111,7 @@ mod olaf_benches { .sign( context.to_vec(), message.to_vec(), - spp_output.0.clone(), + spp_output.0.clone().spp_output(), all_signing_commitments.clone(), &all_signing_nonces[i], ) diff --git a/benches/schnorr_benchmarks.rs b/benches/schnorr_benchmarks.rs index ef0d829..32cd049 100644 --- a/benches/schnorr_benchmarks.rs +++ b/benches/schnorr_benchmarks.rs @@ -22,9 +22,7 @@ mod schnorr_benches { let msg: &[u8] = b""; let ctx = signing_context(b"this signature does this thing"); - c.bench_function("Schnorr signing", move |b| { - b.iter(|| keypair.sign(ctx.bytes(msg))) - }); + c.bench_function("Schnorr signing", move |b| b.iter(|| keypair.sign(ctx.bytes(msg)))); } fn verify(c: &mut Criterion) { @@ -47,10 +45,8 @@ mod schnorr_benches { let keypairs: Vec = (0..size).map(|_| Keypair::generate()).collect(); let msg: &[u8] = b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; let ctx = signing_context(b"this signature does this thing"); - let signatures: Vec = keypairs - .iter() - .map(|key| key.sign(ctx.bytes(msg))) - .collect(); + let signatures: Vec = + keypairs.iter().map(|key| key.sign(ctx.bytes(msg))).collect(); let public_keys: Vec = keypairs.iter().map(|key| key.public).collect(); b.iter(|| { let transcripts = ::std::iter::once(ctx.bytes(msg)).cycle().take(size); @@ -61,9 +57,7 @@ mod schnorr_benches { } fn key_generation(c: &mut Criterion) { - c.bench_function("Schnorr keypair generation", move |b| { - b.iter(|| Keypair::generate()) - }); + c.bench_function("Schnorr keypair generation", move |b| b.iter(|| Keypair::generate())); } criterion_group! { @@ -77,6 +71,4 @@ mod schnorr_benches { } } -criterion_main!( - schnorr_benches::schnorr_benches, -); +criterion_main!(schnorr_benches::schnorr_benches,); diff --git a/src/olaf/frost/errors.rs b/src/olaf/frost/errors.rs index f3c51aa..79c11ea 100644 --- a/src/olaf/frost/errors.rs +++ b/src/olaf/frost/errors.rs @@ -50,14 +50,10 @@ pub enum FROSTError { SPPOutputDeserializationError(SPPError), /// The number of signing packages must be at least equal to the threshold. InvalidNumberOfSigningPackages, - /// The messages of all signing packages must be equal. - MismatchedMessage, - /// The contexts of all signing packages must be equal. - MismatchedContext, - /// The signing commitments of all signing packages must be equal. - MismatchedSigningCommitments, - /// The output of the SimplPedPoP protocol of all signing packages be equal. - MismatchedSPPOutput, + /// The common data of all the signing packages must be the same. + MismatchedCommonData, + /// The signing packages are empty. + EmptySigningPackages, } #[cfg(test)] @@ -131,8 +127,8 @@ mod tests { signing_packages.push(signing_package); } - signing_packages[0].signature_share.share += Scalar::ONE; - signing_packages[1].signature_share.share += Scalar::ONE; + signing_packages[0].signer_data.signature_share.share += Scalar::ONE; + signing_packages[1].signer_data.signature_share.share += Scalar::ONE; let result = aggregate(&signing_packages); diff --git a/src/olaf/frost/mod.rs b/src/olaf/frost/mod.rs index dac244e..f5cba4e 100644 --- a/src/olaf/frost/mod.rs +++ b/src/olaf/frost/mod.rs @@ -7,10 +7,9 @@ mod types; pub mod errors; pub use self::types::{SigningPackage, SigningNonces, SigningCommitments}; -use self::types::SignatureShare; +use self::types::{CommonData, SignatureShare, SignerData}; use alloc::vec::Vec; use curve25519_dalek::Scalar; -use merlin::Transcript; use rand_core::{CryptoRng, RngCore}; use crate::{ context::{SigningContext, SigningTranscript}, @@ -150,14 +149,16 @@ impl SigningKeypair { &challenge, ); - let signing_package = SigningPackage { + let signer_data = SignerData { signature_share }; + let common_data = CommonData { message, context, signing_commitments: all_signing_commitments, - signature_share, spp_output, }; + let signing_package = SigningPackage { signer_data, common_data }; + Ok(signing_package) } @@ -232,36 +233,30 @@ pub(super) fn compute_lagrange_coefficient( /// can avoid that step. However, at worst, this results in a denial of /// service attack due to publishing an invalid signature. pub fn aggregate(signing_packages: &[SigningPackage]) -> Result { - let parameters = &signing_packages[0].spp_output.parameters; + if signing_packages.is_empty() { + return Err(FROSTError::EmptySigningPackages); + } + + let parameters = &signing_packages[0].common_data.spp_output.parameters; if signing_packages.len() < parameters.threshold as usize { return Err(FROSTError::InvalidNumberOfSigningPackages); } - let message = &signing_packages[0].message; - let context = &signing_packages[0].context; - let signing_commitments = &signing_packages[0].signing_commitments; - let threshold_public_key = &signing_packages[0].spp_output.threshold_public_key; + let common_data = &signing_packages[0].common_data; + let message = &common_data.message; + let context = &common_data.context; + let signing_commitments = &common_data.signing_commitments; + let threshold_public_key = &common_data.spp_output.threshold_public_key; + let spp_output = &common_data.spp_output; let mut signature_shares = Vec::new(); for signing_package in signing_packages.iter() { - if &signing_package.message != message { - return Err(FROSTError::MismatchedMessage); - } - if &signing_package.context != context { - return Err(FROSTError::MismatchedContext); + if &signing_package.common_data != common_data { + return Err(FROSTError::MismatchedCommonData); } - if signing_package.signing_commitments != *signing_commitments { - return Err(FROSTError::MismatchedSigningCommitments); - } - if signing_package.spp_output != signing_packages[0].spp_output { - return Err(FROSTError::MismatchedSPPOutput); - } - - signature_shares.push(signing_package.signature_share.clone()); - let mut transcript = Transcript::new(b"spp output"); - transcript.append_message(b"message", &signing_package.spp_output.to_bytes()); + signature_shares.push(signing_package.signer_data.signature_share.clone()); } let binding_factor_list: BindingFactorList = @@ -282,11 +277,10 @@ pub fn aggregate(signing_packages: &[SigningPackage]) -> Result = - signing_packages[0].spp_output.verifying_keys.iter().map(|x| x.0).collect(); + let identifiers: Vec = spp_output.verifying_keys.iter().map(|x| x.0).collect(); let verifying_shares: Vec = - signing_packages[0].spp_output.verifying_keys.iter().map(|x| x.1).collect(); + spp_output.verifying_keys.iter().map(|x| x.1).collect(); let mut valid_shares = Vec::new(); @@ -300,9 +294,7 @@ pub fn aggregate(signing_packages: &[SigningPackage]) -> Result for SigningCommitments { } } -/// The signing package that each signer produces in the signing round of the FROST protocol and sends to the -/// coordinator, which aggregates them into the final threshold signature. -pub struct SigningPackage { +#[derive(Clone, PartialEq, Eq)] +pub(super) struct CommonData { pub(super) message: Vec, pub(super) context: Vec, pub(super) signing_commitments: Vec, - pub(super) signature_share: SignatureShare, pub(super) spp_output: SPPOutput, } -impl SigningPackage { - /// Serializes SigningPackage into bytes. - pub fn to_bytes(&self) -> Vec { +impl CommonData { + /// Serializes CommonData into bytes. + fn to_bytes(&self) -> Vec { let mut bytes = Vec::new(); bytes.extend((self.message.len() as u32).to_le_bytes()); @@ -332,15 +330,13 @@ impl SigningPackage { bytes.extend(commitment.to_bytes()); } - bytes.extend(self.signature_share.to_bytes()); - bytes.extend(self.spp_output.to_bytes()); bytes } - /// Deserializes SigningPackage from bytes. - pub fn from_bytes(bytes: &[u8]) -> FROSTResult { + /// Deserializes CommonData from bytes. + fn from_bytes(bytes: &[u8]) -> FROSTResult { let mut cursor = 0; let message_len = @@ -365,14 +361,62 @@ impl SigningPackage { signing_commitments.push(SigningCommitments::from_bytes(commitment_bytes)?); } - let share_bytes = &bytes[cursor..cursor + SCALAR_LENGTH]; - cursor += SCALAR_LENGTH; - let signature_share = SignatureShare::from_bytes(share_bytes)?; - let spp_output = SPPOutput::from_bytes(&bytes[cursor..]) .map_err(FROSTError::SPPOutputDeserializationError)?; - Ok(SigningPackage { message, context, signing_commitments, signature_share, spp_output }) + Ok(CommonData { message, context, signing_commitments, spp_output }) + } +} + +#[derive(Clone)] +pub(super) struct SignerData { + pub(super) signature_share: SignatureShare, +} + +impl SignerData { + /// Serializes SignerData into bytes. + fn to_bytes(&self) -> Vec { + let mut bytes = Vec::new(); + + bytes.extend(self.signature_share.to_bytes()); + + bytes + } + + /// Deserializes SignerData from bytes. + pub fn from_bytes(bytes: &[u8]) -> FROSTResult { + let share_bytes = &bytes[..SCALAR_LENGTH]; + let signature_share = SignatureShare::from_bytes(share_bytes)?; + + Ok(SignerData { signature_share }) + } +} + +/// The signing package that each signer produces in the signing round of the FROST protocol and sends to the +/// coordinator, which aggregates them into the final threshold signature. +pub struct SigningPackage { + pub(super) signer_data: SignerData, + pub(super) common_data: CommonData, +} + +impl SigningPackage { + /// Serializes SigningPackage into bytes. + pub fn to_bytes(&self) -> Vec { + let mut bytes = Vec::new(); + + bytes.extend(self.signer_data.to_bytes()); + bytes.extend(self.common_data.to_bytes()); + + bytes + } + + /// Deserializes SigningPackage from bytes. + pub fn from_bytes(bytes: &[u8]) -> FROSTResult { + let signer_data = SignerData::from_bytes(&bytes[..SCALAR_LENGTH])?; + + let common_data = CommonData::from_bytes(&bytes[SCALAR_LENGTH..])?; + + Ok(SigningPackage { common_data, signer_data }) } } @@ -428,6 +472,7 @@ mod tests { use rand_core::OsRng; use crate::{ olaf::{ + frost::types::{CommonData, SignerData}, simplpedpop::{Parameters, SPPOutput}, Identifier, ThresholdPublicKey, VerifyingShare, GENERATOR, }, @@ -477,22 +522,20 @@ mod tests { let context = b"context".to_vec(); let signature_share = SignatureShare { share: Scalar::random(&mut rng) }; - let signing_package = SigningPackage { - message: message.clone(), - context: context.clone(), - signing_commitments: signing_commitments.clone(), - signature_share: signature_share.clone(), - spp_output: spp_output.clone(), - }; + let common_data = CommonData { message, context, signing_commitments, spp_output }; + let signer_data = SignerData { signature_share }; + + let signing_package = + SigningPackage { signer_data: signer_data.clone(), common_data: common_data.clone() }; let signing_package_bytes = signing_package.to_bytes(); let deserialized_signing_package = SigningPackage::from_bytes(&signing_package_bytes).unwrap(); - assert!(deserialized_signing_package.message == message); - assert!(deserialized_signing_package.context == context); - assert!(deserialized_signing_package.signing_commitments == signing_commitments); - assert!(deserialized_signing_package.signature_share.share == signature_share.share); - assert!(deserialized_signing_package.spp_output == spp_output); + assert!( + deserialized_signing_package.signer_data.signature_share.share + == signer_data.signature_share.share + ); + assert!(deserialized_signing_package.common_data == common_data); } } diff --git a/src/olaf/simplpedpop/errors.rs b/src/olaf/simplpedpop/errors.rs index b1df897..7b29c6d 100644 --- a/src/olaf/simplpedpop/errors.rs +++ b/src/olaf/simplpedpop/errors.rs @@ -47,6 +47,8 @@ pub enum SPPError { EncryptionError(chacha20poly1305::Error), /// Invalid Proof of Possession. InvalidProofOfPossession(SignatureError), + /// The messages are empty. + EmptyMessages, } #[cfg(test)] diff --git a/src/olaf/simplpedpop/mod.rs b/src/olaf/simplpedpop/mod.rs index 6f05f29..fcbc032 100644 --- a/src/olaf/simplpedpop/mod.rs +++ b/src/olaf/simplpedpop/mod.rs @@ -143,6 +143,10 @@ impl Keypair { &self, messages: &[AllMessage], ) -> SPPResult<(SPPOutputMessage, SigningKeypair)> { + if messages.is_empty() { + return Err(SPPError::EmptyMessages); + } + let first_message = &messages[0]; let parameters = &first_message.content.parameters; let threshold = parameters.threshold as usize; From f67a4f34a2b38fe985e9b3499a150ba291700fbc Mon Sep 17 00:00:00 2001 From: Fiono11 Date: Fri, 17 May 2024 13:10:53 +0100 Subject: [PATCH 40/60] Improvements --- src/olaf/frost/errors.rs | 254 ++++++++++++++++++++++++++++++++-- src/olaf/frost/mod.rs | 7 +- src/olaf/frost/types.rs | 28 ++-- src/olaf/simplpedpop/types.rs | 6 +- 4 files changed, 256 insertions(+), 39 deletions(-) diff --git a/src/olaf/frost/errors.rs b/src/olaf/frost/errors.rs index 79c11ea..3a61ff4 100644 --- a/src/olaf/frost/errors.rs +++ b/src/olaf/frost/errors.rs @@ -15,20 +15,14 @@ pub type FROSTResult = Result; /// An error ocurred during the execution of the SimplPedPoP protocol. #[derive(Debug)] pub enum FROSTError { - /// Invalid Proof of Possession. - InvalidProofOfPossession(SignatureError), /// The number of signing commitments must be at least equal to the threshold. InvalidNumberOfSigningCommitments, - /// The number of signing commitments must be equal to the number of signature shares. - IncorrectNumberOfSigningCommitments, /// The participant's signing commitment is missing. MissingOwnSigningCommitment, /// Commitment equals the identity IdentitySigningCommitment, /// The number of veriyfing shares must be equal to the number of participants. IncorrectNumberOfVerifyingShares, - /// The identifiers of the SimplPedPoP protocol must be the same of the FROST protocol. - InvalidIdentifier, /// Error deserializing the signature share. SignatureShareDeserializationError, /// The signature share is invalid. @@ -44,14 +38,14 @@ pub enum FROSTError { DeserializationError(TryFromSliceError), /// Invalid nonce commitment. InvalidNonceCommitment, - /// Invalid public key. - InvalidPublicKey(SignatureError), /// Error deserializing the output of the SimplPedPoP protocol. SPPOutputDeserializationError(SPPError), /// The number of signing packages must be at least equal to the threshold. InvalidNumberOfSigningPackages, /// The common data of all the signing packages must be the same. MismatchedCommonData, + /// The number of signature shares and the number of signing commitments must be the same. + MismatchedSignatureSharesAndSigningCommitments, /// The signing packages are empty. EmptySigningPackages, } @@ -60,23 +54,53 @@ pub enum FROSTError { mod tests { use alloc::vec::Vec; use curve25519_dalek::{traits::Identity, RistrettoPoint, Scalar}; + use rand::Rng; use rand_core::OsRng; use crate::{ olaf::{ frost::{ aggregate, types::{NonceCommitment, SigningCommitments}, + SigningPackage, }, simplpedpop::{AllMessage, Parameters}, - SigningKeypair, + SigningKeypair, MINIMUM_THRESHOLD, }, Keypair, PublicKey, }; use super::FROSTError; + const MAXIMUM_PARTICIPANTS: u16 = 2; + const MINIMUM_PARTICIPANTS: u16 = 2; + + fn generate_parameters() -> Parameters { + let mut rng = rand::thread_rng(); + let participants = rng.gen_range(MINIMUM_PARTICIPANTS..=MAXIMUM_PARTICIPANTS); + let threshold = rng.gen_range(MINIMUM_THRESHOLD..=participants); + + Parameters { participants, threshold } + } + + #[test] + fn test_empty_signing_packages() { + let signing_packages: Vec = Vec::new(); + + let result = aggregate(&signing_packages); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + FROSTError::EmptySigningPackages => assert!(true), + _ => { + panic!("Expected FROSTError::EmptySigningPackages, but got {:?}", e) + }, + }, + } + } + #[test] fn test_invalid_signature_share() { - let parameters = Parameters::generate(2, 2); + let parameters = generate_parameters(); let participants = parameters.participants as usize; let threshold = parameters.threshold as usize; @@ -149,9 +173,209 @@ mod tests { } } + #[test] + fn test_mismatched_signature_shares_and_signing_commitments_error() { + let parameters = generate_parameters(); + let participants = parameters.participants as usize; + let threshold = parameters.threshold as usize; + + let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public).collect(); + + let mut all_messages = Vec::new(); + for i in 0..participants { + let message: AllMessage = keypairs[i] + .simplpedpop_contribute_all(threshold as u16, public_keys.clone()) + .unwrap(); + all_messages.push(message); + } + + let mut spp_outputs = Vec::new(); + + for kp in keypairs.iter() { + let spp_output = kp.simplpedpop_recipient_all(&all_messages).unwrap(); + spp_outputs.push(spp_output); + } + + let mut all_signing_commitments = Vec::new(); + let mut all_signing_nonces = Vec::new(); + + for spp_output in &spp_outputs { + let (signing_nonces, signing_commitments) = spp_output.1.commit(&mut OsRng); + all_signing_nonces.push(signing_nonces); + all_signing_commitments.push(signing_commitments); + } + + let mut signing_packages = Vec::new(); + + let message = b"message"; + let context = b"context"; + + for (i, spp_output) in spp_outputs.iter().enumerate() { + let mut signing_package = spp_output + .1 + .sign( + context.to_vec(), + message.to_vec(), + spp_output.0.spp_output.clone(), + all_signing_commitments.clone(), + &all_signing_nonces[i], + ) + .unwrap(); + + signing_package.common_data.signing_commitments.pop(); + + signing_packages.push(signing_package); + } + + let result = aggregate(&signing_packages); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + FROSTError::MismatchedSignatureSharesAndSigningCommitments => assert!(true), + _ => { + panic!("Expected FROSTError::MismatchedSignatureSharesAndSigningCommitments, but got {:?}", e) + }, + }, + } + } + + #[test] + fn test_mismatched_common_data_error() { + let parameters = generate_parameters(); + let participants = parameters.participants as usize; + let threshold = parameters.threshold as usize; + + let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public).collect(); + + let mut all_messages = Vec::new(); + for i in 0..participants { + let message: AllMessage = keypairs[i] + .simplpedpop_contribute_all(threshold as u16, public_keys.clone()) + .unwrap(); + all_messages.push(message); + } + + let mut spp_outputs = Vec::new(); + + for kp in keypairs.iter() { + let spp_output = kp.simplpedpop_recipient_all(&all_messages).unwrap(); + spp_outputs.push(spp_output); + } + + let mut all_signing_commitments = Vec::new(); + let mut all_signing_nonces = Vec::new(); + + for spp_output in &spp_outputs { + let (signing_nonces, signing_commitments) = spp_output.1.commit(&mut OsRng); + all_signing_nonces.push(signing_nonces); + all_signing_commitments.push(signing_commitments); + } + + let mut signing_packages = Vec::new(); + + let message = b"message"; + let context = b"context"; + + for (i, spp_output) in spp_outputs.iter().enumerate() { + let signing_package = spp_output + .1 + .sign( + context.to_vec(), + message.to_vec(), + spp_output.0.spp_output.clone(), + all_signing_commitments.clone(), + &all_signing_nonces[i], + ) + .unwrap(); + + signing_packages.push(signing_package); + } + + signing_packages[0].common_data.context = b"invalid_context".to_vec(); + + let result = aggregate(&signing_packages); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + FROSTError::MismatchedCommonData => assert!(true), + _ => { + panic!("Expected FROSTError::MismatchedCommonData, but got {:?}", e) + }, + }, + } + } + + #[test] + fn test_invalid_number_of_signing_packages_error() { + let parameters = generate_parameters(); + let participants = parameters.participants as usize; + let threshold = parameters.threshold as usize; + + let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public).collect(); + + let mut all_messages = Vec::new(); + for i in 0..participants { + let message: AllMessage = keypairs[i] + .simplpedpop_contribute_all(threshold as u16, public_keys.clone()) + .unwrap(); + all_messages.push(message); + } + + let mut spp_outputs = Vec::new(); + + for kp in keypairs.iter() { + let spp_output = kp.simplpedpop_recipient_all(&all_messages).unwrap(); + spp_outputs.push(spp_output); + } + + let mut all_signing_commitments = Vec::new(); + let mut all_signing_nonces = Vec::new(); + + for spp_output in &spp_outputs { + let (signing_nonces, signing_commitments) = spp_output.1.commit(&mut OsRng); + all_signing_nonces.push(signing_nonces); + all_signing_commitments.push(signing_commitments); + } + + let mut signing_packages = Vec::new(); + + let message = b"message"; + let context = b"context"; + + let signing_package = spp_outputs[0] + .1 + .sign( + context.to_vec(), + message.to_vec(), + spp_outputs[0].0.spp_output.clone(), + all_signing_commitments.clone(), + &all_signing_nonces[0], + ) + .unwrap(); + + signing_packages.push(signing_package); + + let result = aggregate(&signing_packages); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + FROSTError::InvalidNumberOfSigningPackages => assert!(true), + _ => { + panic!("Expected FROSTError::InvalidNumberOfSigningPackages, but got {:?}", e) + }, + }, + } + } + #[test] fn test_invalid_own_verifying_share_error() { - let parameters = Parameters::generate(2, 2); + let parameters = generate_parameters(); let participants = parameters.participants as usize; let threshold = parameters.threshold as usize; @@ -208,7 +432,7 @@ mod tests { #[test] fn test_incorrect_number_of_verifying_shares_error() { - let parameters = Parameters::generate(2, 2); + let parameters = generate_parameters(); let participants = parameters.participants as usize; let threshold = parameters.threshold as usize; @@ -265,7 +489,7 @@ mod tests { #[test] fn test_missing_own_signing_commitment_error() { - let parameters = Parameters::generate(2, 2); + let parameters = generate_parameters(); let participants = parameters.participants as usize; let threshold = parameters.threshold as usize; @@ -325,7 +549,7 @@ mod tests { #[test] fn test_identity_signing_commitment_error() { - let parameters = Parameters::generate(2, 2); + let parameters = generate_parameters(); let participants = parameters.participants as usize; let threshold = parameters.threshold as usize; @@ -382,7 +606,7 @@ mod tests { #[test] fn test_incorrect_number_of_signing_commitments_error() { - let parameters = Parameters::generate(2, 2); + let parameters = generate_parameters(); let participants = parameters.participants as usize; let threshold = parameters.threshold as usize; diff --git a/src/olaf/frost/mod.rs b/src/olaf/frost/mod.rs index f5cba4e..c3600cc 100644 --- a/src/olaf/frost/mod.rs +++ b/src/olaf/frost/mod.rs @@ -259,6 +259,10 @@ pub fn aggregate(signing_packages: &[SigningPackage]) -> Result Result [u8; SCALAR_LENGTH] { + fn to_bytes(&self) -> [u8; SCALAR_LENGTH] { self.share.to_bytes() } - /// Deserializes the `SignatureShare` from bytes. - pub(super) fn from_bytes(bytes: &[u8]) -> FROSTResult { + fn from_bytes(bytes: &[u8]) -> FROSTResult { let mut share_bytes = [0; SCALAR_LENGTH]; share_bytes.copy_from_slice(&bytes[..SCALAR_LENGTH]); let share = scalar_from_canonical_bytes(share_bytes) @@ -160,10 +158,7 @@ impl Nonce { /// Generates a nonce from the given random bytes. /// This function allows testing and MUST NOT be made public. - pub(super) fn nonce_generate_from_random_bytes( - secret: &SecretKey, - random_bytes: &[u8], - ) -> Self { + fn nonce_generate_from_random_bytes(secret: &SecretKey, random_bytes: &[u8]) -> Self { let mut transcript = Transcript::new(b"nonce_generate_from_random_bytes"); transcript.append_message(b"random bytes", random_bytes); @@ -178,13 +173,12 @@ impl Nonce { pub(super) struct NonceCommitment(pub(super) RistrettoPoint); impl NonceCommitment { - /// Serializes the `NonceCommitment` into bytes. - pub(super) fn to_bytes(self) -> [u8; COMPRESSED_RISTRETTO_LENGTH] { + fn to_bytes(self) -> [u8; COMPRESSED_RISTRETTO_LENGTH] { self.0.compress().to_bytes() } /// Deserializes the `NonceCommitment` from bytes. - pub(super) fn from_bytes(bytes: &[u8]) -> FROSTResult { + fn from_bytes(bytes: &[u8]) -> FROSTResult { let compressed = CompressedRistretto::from_slice(&bytes[..COMPRESSED_RISTRETTO_LENGTH]) .map_err(FROSTError::DeserializationError)?; @@ -245,7 +239,7 @@ impl SigningNonces { /// SigningNonces MUST NOT be repeated in different FROST signings. /// Thus, if you're using this method (because e.g. you're writing it /// to disk between rounds), be careful so that does not happen. - pub(super) fn from_nonces(hiding: Nonce, binding: Nonce) -> Self { + fn from_nonces(hiding: Nonce, binding: Nonce) -> Self { let hiding_commitment = (&hiding).into(); let binding_commitment = (&binding).into(); let commitments = SigningCommitments::new(hiding_commitment, binding_commitment); @@ -265,14 +259,11 @@ pub struct SigningCommitments { } impl SigningCommitments { - /// Create new SigningCommitments - pub(super) fn new(hiding: NonceCommitment, binding: NonceCommitment) -> Self { + fn new(hiding: NonceCommitment, binding: NonceCommitment) -> Self { Self { hiding, binding } } - /// Serializes the `SigningCommitments` into bytes. - pub(super) fn to_bytes(self) -> [u8; COMPRESSED_RISTRETTO_LENGTH * 2] { - // TODO: Add tests + fn to_bytes(self) -> [u8; COMPRESSED_RISTRETTO_LENGTH * 2] { let mut bytes = [0u8; COMPRESSED_RISTRETTO_LENGTH * 2]; let hiding_bytes = self.hiding.to_bytes(); @@ -284,8 +275,7 @@ impl SigningCommitments { bytes } - /// Deserializes the `SigningCommitments` from bytes. - pub(super) fn from_bytes(bytes: &[u8]) -> FROSTResult { + fn from_bytes(bytes: &[u8]) -> FROSTResult { let hiding = NonceCommitment::from_bytes(&bytes[..COMPRESSED_RISTRETTO_LENGTH])?; let binding = NonceCommitment::from_bytes(&bytes[COMPRESSED_RISTRETTO_LENGTH..])?; diff --git a/src/olaf/simplpedpop/types.rs b/src/olaf/simplpedpop/types.rs index f57e217..806c906 100644 --- a/src/olaf/simplpedpop/types.rs +++ b/src/olaf/simplpedpop/types.rs @@ -164,11 +164,11 @@ impl Parameters { /// The polynomial commitment of a participant, used to verify the secret shares without revealing the polynomial. pub struct PolynomialCommitment { - pub(crate) coefficients_commitments: Vec, + pub(super) coefficients_commitments: Vec, } impl PolynomialCommitment { - pub(crate) fn evaluate(&self, identifier: &Scalar) -> RistrettoPoint { + pub(super) fn evaluate(&self, identifier: &Scalar) -> RistrettoPoint { let i = identifier; let (_, result) = self @@ -181,7 +181,7 @@ impl PolynomialCommitment { result } - pub(crate) fn sum_polynomial_commitments( + pub(super) fn sum_polynomial_commitments( polynomials_commitments: &[&PolynomialCommitment], ) -> PolynomialCommitment { let max_length = polynomials_commitments From 524b01eb4261e713b04a0feb69106a555f09859e Mon Sep 17 00:00:00 2001 From: Fiono11 Date: Fri, 17 May 2024 15:54:07 +0100 Subject: [PATCH 41/60] Merge changes from final_frost branch to simplpedpop --- benches/olaf_benchmarks.rs | 14 +- benches/schnorr_benchmarks.rs | 18 +- src/olaf/errors.rs | 48 -- src/olaf/mod.rs | 39 +- src/olaf/simplpedpop/errors.rs | 340 +++++++++++++ .../{simplpedpop.rs => simplpedpop/mod.rs} | 172 +++++-- src/olaf/{ => simplpedpop}/types.rs | 462 ++++++++++-------- src/olaf/tests.rs | 340 ------------- 8 files changed, 754 insertions(+), 679 deletions(-) delete mode 100644 src/olaf/errors.rs create mode 100644 src/olaf/simplpedpop/errors.rs rename src/olaf/{simplpedpop.rs => simplpedpop/mod.rs} (65%) rename src/olaf/{ => simplpedpop}/types.rs (71%) delete mode 100644 src/olaf/tests.rs diff --git a/benches/olaf_benchmarks.rs b/benches/olaf_benchmarks.rs index e4f3643..b178bee 100644 --- a/benches/olaf_benchmarks.rs +++ b/benches/olaf_benchmarks.rs @@ -2,16 +2,12 @@ use criterion::criterion_main; mod olaf_benches { use criterion::{criterion_group, BenchmarkId, Criterion}; - use schnorrkel::{olaf::AllMessage, Keypair, PublicKey}; + use schnorrkel::keys::{PublicKey, Keypair}; + use schnorrkel::olaf::simplpedpop::AllMessage; 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(300)); - for &n in [3, 10, 100, 1000].iter() { let participants = n; let threshold = (n * 2 + 2) / 3; @@ -19,10 +15,10 @@ mod olaf_benches { let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); let public_keys: Vec = keypairs.iter().map(|kp| kp.public).collect(); - // Each participant creates an AllMessage - let mut all_messages = Vec::new(); + let mut all_messages: Vec = Vec::new(); + for i in 0..participants { - let message: AllMessage = keypairs[i] + let message = keypairs[i] .simplpedpop_contribute_all(threshold as u16, public_keys.clone()) .unwrap(); all_messages.push(message); diff --git a/benches/schnorr_benchmarks.rs b/benches/schnorr_benchmarks.rs index ef0d829..32cd049 100644 --- a/benches/schnorr_benchmarks.rs +++ b/benches/schnorr_benchmarks.rs @@ -22,9 +22,7 @@ mod schnorr_benches { let msg: &[u8] = b""; let ctx = signing_context(b"this signature does this thing"); - c.bench_function("Schnorr signing", move |b| { - b.iter(|| keypair.sign(ctx.bytes(msg))) - }); + c.bench_function("Schnorr signing", move |b| b.iter(|| keypair.sign(ctx.bytes(msg)))); } fn verify(c: &mut Criterion) { @@ -47,10 +45,8 @@ mod schnorr_benches { let keypairs: Vec = (0..size).map(|_| Keypair::generate()).collect(); let msg: &[u8] = b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; let ctx = signing_context(b"this signature does this thing"); - let signatures: Vec = keypairs - .iter() - .map(|key| key.sign(ctx.bytes(msg))) - .collect(); + let signatures: Vec = + keypairs.iter().map(|key| key.sign(ctx.bytes(msg))).collect(); let public_keys: Vec = keypairs.iter().map(|key| key.public).collect(); b.iter(|| { let transcripts = ::std::iter::once(ctx.bytes(msg)).cycle().take(size); @@ -61,9 +57,7 @@ mod schnorr_benches { } fn key_generation(c: &mut Criterion) { - c.bench_function("Schnorr keypair generation", move |b| { - b.iter(|| Keypair::generate()) - }); + c.bench_function("Schnorr keypair generation", move |b| b.iter(|| Keypair::generate())); } criterion_group! { @@ -77,6 +71,4 @@ mod schnorr_benches { } } -criterion_main!( - schnorr_benches::schnorr_benches, -); +criterion_main!(schnorr_benches::schnorr_benches,); diff --git a/src/olaf/errors.rs b/src/olaf/errors.rs deleted file mode 100644 index 2619b14..0000000 --- a/src/olaf/errors.rs +++ /dev/null @@ -1,48 +0,0 @@ -//! Errors of the Olaf protocol. - -use core::array::TryFromSliceError; -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)] -pub enum DKGError { - /// Threshold cannot be greater than the number of participants. - ExcessiveThreshold, - /// Threshold must be at least 2. - InsufficientThreshold, - /// Number of participants is invalid. - InvalidNumberOfParticipants, - /// Invalid public key. - InvalidPublicKey(SignatureError), - /// Invalid group public key. - InvalidGroupPublicKey, - /// Invalid signature. - InvalidSignature(SignatureError), - /// Invalid coefficient commitment of the polynomial commitment. - InvalidCoefficientCommitment, - /// Invalid identifier. - InvalidIdentifier, - /// Invalid secret share. - InvalidSecretShare, - /// Deserialization Error. - DeserializationError(TryFromSliceError), - /// The parameters of all messages must be equal. - DifferentParameters, - /// The recipients hash of all messages must be equal. - DifferentRecipientsHash, - /// The number of messages should be 2 at least, which the minimum number of participants. - InvalidNumberOfMessages, - /// The number of coefficient commitments of the polynomial commitment must be equal to the threshold - 1. - IncorrectNumberOfCoefficientCommitments, - /// The number of encrypted shares per message must be equal to the number of participants. - IncorrectNumberOfEncryptedShares, - /// Decryption error when decrypting an encrypted secret share. - DecryptionError(chacha20poly1305::Error), - /// Encryption error when encrypting the secret share. - EncryptionError(chacha20poly1305::Error), - /// Invalid Proof of Possession. - InvalidProofOfPossession(SignatureError), -} diff --git a/src/olaf/mod.rs b/src/olaf/mod.rs index 58d8a8a..00f310e 100644 --- a/src/olaf/mod.rs +++ b/src/olaf/mod.rs @@ -1,31 +1,34 @@ //! Implementation of the Olaf protocol (), which is composed of the Distributed //! Key Generation (DKG) protocol SimplPedPoP and the Threshold Signing protocol FROST. +/// Implementation of the SimplPedPoP protocol. +pub mod simplpedpop; + use curve25519_dalek::{constants::RISTRETTO_BASEPOINT_POINT, RistrettoPoint, Scalar}; -use crate::{PublicKey, SecretKey}; -use crate::context::SigningTranscript; use merlin::Transcript; +use zeroize::ZeroizeOnDrop; +use crate::{context::SigningTranscript, Keypair, PublicKey}; -pub mod errors; -pub mod simplpedpop; -mod tests; -mod types; +pub(super) const MINIMUM_THRESHOLD: u16 = 2; +pub(super) const GENERATOR: RistrettoPoint = RISTRETTO_BASEPOINT_POINT; +pub(super) const COMPRESSED_RISTRETTO_LENGTH: usize = 32; +pub(crate) const SCALAR_LENGTH: usize = 32; -pub use types::AllMessage; +/// The threshold public key generated in the SimplPedPoP protocol, used to validate the threshold signatures of the FROST protocol. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct ThresholdPublicKey(pub(crate) PublicKey); -const MINIMUM_THRESHOLD: u16 = 2; -const GENERATOR: RistrettoPoint = RISTRETTO_BASEPOINT_POINT; +/// The verifying share of a participant generated in the SimplPedPoP protocol, used to verify its signatures shares in the FROST protocol. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct VerifyingShare(pub(crate) PublicKey); -/// The group public key generated by the SimplPedPoP protocol. -pub struct GroupPublicKey(PublicKey); -/// The verifying share of a participant in the SimplPedPoP protocol, used to verify its signature share. -pub struct VerifyingShare(PublicKey); -/// The signing share of a participant in the SimplPedPoP protocol, used to produce its signature share. -pub struct SigningShare(SecretKey); +/// The signing keypair of a participant generated in the SimplPedPoP protocol, used to produce its signatures shares in the FROST protocol. +#[derive(Clone, Debug, ZeroizeOnDrop)] +pub struct SigningKeypair(pub(crate) Keypair); -/// The identifier of a participant in the Olaf protocol. -#[derive(Clone, Copy)] -pub struct Identifier(Scalar); +/// The identifier of a participant, which must be the same in the SimplPedPoP protocol and in the FROST protocol. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct Identifier(pub(crate) Scalar); impl Identifier { pub(super) fn generate(recipients_hash: &[u8; 16], index: u16) -> Identifier { diff --git a/src/olaf/simplpedpop/errors.rs b/src/olaf/simplpedpop/errors.rs new file mode 100644 index 0000000..7b29c6d --- /dev/null +++ b/src/olaf/simplpedpop/errors.rs @@ -0,0 +1,340 @@ +//! Errors of the SimplPedPoP protocol. + +use core::array::TryFromSliceError; +use crate::SignatureError; + +/// A result for the SimplPedPoP protocol. +pub type SPPResult = Result; + +/// An error ocurred during the execution of the SimplPedPoP protocol. +#[derive(Debug)] +pub enum SPPError { + /// Invalid parameters. + InvalidParameters, + /// Threshold cannot be greater than the number of participants. + ExcessiveThreshold, + /// Threshold must be at least 2. + InsufficientThreshold, + /// Number of participants is invalid. + InvalidNumberOfParticipants, + /// Invalid public key. + InvalidPublicKey(SignatureError), + /// Invalid threshold public key. + InvalidThresholdPublicKey, + /// Invalid signature. + InvalidSignature(SignatureError), + /// Invalid coefficient commitment of the polynomial commitment. + InvalidCoefficientCommitment, + /// Invalid identifier. + InvalidIdentifier, + /// Invalid secret share. + InvalidSecretShare, + /// Deserialization Error. + DeserializationError(TryFromSliceError), + /// The parameters of all messages must be equal. + DifferentParameters, + /// The recipients hash of all messages must be equal. + DifferentRecipientsHash, + /// The number of messages should be 2 at least, which the minimum number of participants. + InvalidNumberOfMessages, + /// The number of coefficient commitments of the polynomial commitment must be equal to the threshold - 1. + IncorrectNumberOfCoefficientCommitments, + /// The number of encrypted shares per message must be equal to the number of participants. + IncorrectNumberOfEncryptedShares, + /// Decryption error when decrypting an encrypted secret share. + DecryptionError(chacha20poly1305::Error), + /// Encryption error when encrypting the secret share. + EncryptionError(chacha20poly1305::Error), + /// Invalid Proof of Possession. + InvalidProofOfPossession(SignatureError), + /// The messages are empty. + EmptyMessages, +} + +#[cfg(test)] +mod tests { + use crate::olaf::simplpedpop::errors::SPPError; + use crate::olaf::simplpedpop::types::{ + AllMessage, EncryptedSecretShare, CHACHA20POLY1305_LENGTH, RECIPIENTS_HASH_LENGTH, + }; + use crate::olaf::simplpedpop::Parameters; + use crate::olaf::MINIMUM_THRESHOLD; + use crate::{Keypair, PublicKey}; + use alloc::vec::Vec; + use curve25519_dalek::ristretto::RistrettoPoint; + use curve25519_dalek::traits::Identity; + use merlin::Transcript; + use rand::Rng; + + const MAXIMUM_PARTICIPANTS: u16 = 10; + const MINIMUM_PARTICIPANTS: u16 = 2; + + fn generate_parameters() -> Parameters { + let mut rng = rand::thread_rng(); + let participants = rng.gen_range(MINIMUM_PARTICIPANTS..=MAXIMUM_PARTICIPANTS); + let threshold = rng.gen_range(MINIMUM_THRESHOLD..=participants); + + Parameters { participants, threshold } + } + + #[test] + fn test_invalid_number_of_messages() { + let parameters = generate_parameters(); + let participants = parameters.participants; + let threshold = parameters.threshold; + + let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); + + let mut messages: Vec = keypairs + .iter() + .map(|kp| kp.simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap()) + .collect(); + + messages.pop(); + + let result = keypairs[0].simplpedpop_recipient_all(&messages); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + SPPError::InvalidNumberOfMessages => assert!(true), + _ => { + panic!("Expected DKGError::InvalidNumberOfMessages, but got {:?}", e) + }, + }, + } + } + + #[test] + fn test_different_parameters() { + let parameters = generate_parameters(); + let participants = parameters.participants; + let threshold = parameters.threshold; + + let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); + + let mut messages: Vec = Vec::new(); + for i in 0..participants { + let message = keypairs[i as usize] + .simplpedpop_contribute_all(threshold, public_keys.clone()) + .unwrap(); + messages.push(message); + } + + messages[1].content.parameters.threshold += 1; + + let result = keypairs[0].simplpedpop_recipient_all(&messages); + + // Check if the result is an error + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + SPPError::DifferentParameters => assert!(true), + _ => panic!("Expected DKGError::DifferentParameters, but got {:?}", e), + }, + } + } + + #[test] + fn test_different_recipients_hash() { + let parameters = generate_parameters(); + let participants = parameters.participants; + let threshold = parameters.threshold; + + let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); + + let mut messages: Vec = keypairs + .iter() + .map(|kp| kp.simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap()) + .collect(); + + messages[1].content.recipients_hash = [1; RECIPIENTS_HASH_LENGTH]; + + let result = keypairs[0].simplpedpop_recipient_all(&messages); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + SPPError::DifferentRecipientsHash => assert!(true), + _ => { + panic!("Expected DKGError::DifferentRecipientsHash, but got {:?}", e) + }, + }, + } + } + + #[test] + fn test_incorrect_number_of_commitments() { + let parameters = generate_parameters(); + let participants = parameters.participants; + let threshold = parameters.threshold; + + let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); + + let mut messages: Vec = keypairs + .iter() + .map(|kp| kp.simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap()) + .collect(); + + messages[1].content.polynomial_commitment.coefficients_commitments.pop(); + + let result = keypairs[0].simplpedpop_recipient_all(&messages); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + SPPError::IncorrectNumberOfCoefficientCommitments => assert!(true), + _ => panic!( + "Expected DKGError::IncorrectNumberOfCoefficientCommitments, but got {:?}", + e + ), + }, + } + } + + #[test] + fn test_incorrect_number_of_encrypted_shares() { + let parameters = generate_parameters(); + let participants = parameters.participants; + let threshold = parameters.threshold; + + let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); + + let mut messages: Vec = keypairs + .iter() + .map(|kp| kp.simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap()) + .collect(); + + messages[1].content.encrypted_secret_shares.pop(); + + let result = keypairs[0].simplpedpop_recipient_all(&messages); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + SPPError::IncorrectNumberOfEncryptedShares => assert!(true), + _ => panic!("Expected DKGError::IncorrectNumberOfEncryptedShares, but got {:?}", e), + }, + } + } + + #[test] + fn test_invalid_secret_share() { + let parameters = generate_parameters(); + let participants = parameters.participants; + let threshold = parameters.threshold; + + let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); + + let mut messages: Vec = keypairs + .iter() + .map(|kp| kp.simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap()) + .collect(); + + messages[1].content.encrypted_secret_shares[0] = + EncryptedSecretShare(vec![1; CHACHA20POLY1305_LENGTH]); + + let result = keypairs[0].simplpedpop_recipient_all(&messages); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + SPPError::InvalidSecretShare => assert!(true), + _ => panic!("Expected DKGError::InvalidSecretShare, but got {:?}", e), + }, + } + } + + #[test] + fn test_invalid_signature() { + let parameters = generate_parameters(); + let participants = parameters.participants; + let threshold = parameters.threshold; + + let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); + + let mut messages: Vec = keypairs + .iter() + .map(|kp| kp.simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap()) + .collect(); + + messages[1].signature = + keypairs[1].secret.sign(Transcript::new(b"invalid"), &keypairs[1].public); + + let result = keypairs[0].simplpedpop_recipient_all(&messages); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + SPPError::InvalidSignature(_) => assert!(true), + _ => panic!("Expected DKGError::InvalidSignature, but got {:?}", e), + }, + } + } + + #[test] + fn test_invalid_threshold() { + let keypair = Keypair::generate(); + let result = keypair.simplpedpop_contribute_all( + 1, + vec![ + PublicKey::from_point(RistrettoPoint::identity()), + PublicKey::from_point(RistrettoPoint::identity()), + ], + ); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + SPPError::InsufficientThreshold => assert!(true), + _ => panic!("Expected SPPError::InsufficientThreshold, but got {:?}", e), + }, + } + } + + #[test] + fn test_invalid_participants() { + let keypair = Keypair::generate(); + let result = keypair + .simplpedpop_contribute_all(2, vec![PublicKey::from_point(RistrettoPoint::identity())]); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + SPPError::InvalidNumberOfParticipants => { + assert!(true) + }, + _ => { + panic!("Expected SPPError::InvalidNumberOfParticipants, but got {:?}", e) + }, + }, + } + } + + #[test] + fn test_threshold_greater_than_participants() { + let keypair = Keypair::generate(); + let result = keypair.simplpedpop_contribute_all( + 3, + vec![ + PublicKey::from_point(RistrettoPoint::identity()), + PublicKey::from_point(RistrettoPoint::identity()), + ], + ); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + SPPError::ExcessiveThreshold => assert!(true), + _ => panic!("Expected SPPError::ExcessiveThreshold), but got {:?}", e), + }, + } + } +} diff --git a/src/olaf/simplpedpop.rs b/src/olaf/simplpedpop/mod.rs similarity index 65% rename from src/olaf/simplpedpop.rs rename to src/olaf/simplpedpop/mod.rs index 37c51b1..fcbc032 100644 --- a/src/olaf/simplpedpop.rs +++ b/src/olaf/simplpedpop/mod.rs @@ -1,6 +1,11 @@ -//! Implementation of the SimplPedPoP protocol (), 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. +//! Implementation of the SimplPedPoP protocol (), a spp based on PedPoP, which in turn is based +//! on Pedersen's spp. All of them have as the fundamental building block the Shamir's Secret Sharing scheme. +mod types; +pub mod errors; + +pub use self::types::{AllMessage, SPPOutputMessage, SPPOutput}; +pub(crate) use self::types::{PolynomialCommitment, MessageContent, Parameters}; use alloc::vec::Vec; use curve25519_dalek::{traits::Identity, RistrettoPoint, Scalar}; use merlin::Transcript; @@ -8,35 +13,35 @@ use rand_core::RngCore; use crate::{ context::SigningTranscript, verify_batch, Keypair, PublicKey, SecretKey, getrandom_or_panic, }; -use super::{ - errors::{DKGError, DKGResult}, +use self::{ + errors::{SPPError, SPPResult}, types::{ - AllMessage, DKGOutput, DKGOutputMessage, MessageContent, Parameters, PolynomialCommitment, SecretPolynomial, SecretShare, CHACHA20POLY1305_KEY_LENGTH, ENCRYPTION_NONCE_LENGTH, RECIPIENTS_HASH_LENGTH, }, - GroupPublicKey, Identifier, SigningShare, VerifyingShare, GENERATOR, }; +use super::{ThresholdPublicKey, Identifier, SigningKeypair, VerifyingShare, GENERATOR}; impl Keypair { /// First round of the SimplPedPoP protocol. + /// + /// We do not recipients.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 recipients, not the + /// full recipients list. pub fn simplpedpop_contribute_all( &self, threshold: u16, recipients: Vec, - ) -> DKGResult { + ) -> SPPResult { let parameters = Parameters::generate(recipients.len() as u16, threshold); parameters.validate()?; let mut rng = getrandom_or_panic(); - // We do not recipients.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 recipients, not the - // full recipients list. let mut recipients_transcript = Transcript::new(b"RecipientsHash"); parameters.commit(&mut recipients_transcript); @@ -52,7 +57,7 @@ impl Keypair { let mut encrypted_secret_shares = Vec::new(); - let polynomial_commitment = PolynomialCommitment::commit(&secret_polynomial); + let polynomial_commitment = secret_polynomial.commit(); let mut encryption_transcript = merlin::Transcript::new(b"Encryption"); parameters.commit(&mut encryption_transcript); @@ -137,7 +142,11 @@ impl Keypair { pub fn simplpedpop_recipient_all( &self, messages: &[AllMessage], - ) -> DKGResult<(DKGOutputMessage, SigningShare)> { + ) -> SPPResult<(SPPOutputMessage, SigningKeypair)> { + if messages.is_empty() { + return Err(SPPError::EmptyMessages); + } + let first_message = &messages[0]; let parameters = &first_message.content.parameters; let threshold = parameters.threshold as usize; @@ -146,7 +155,7 @@ impl Keypair { first_message.content.parameters.validate()?; if messages.len() < participants { - return Err(DKGError::InvalidNumberOfMessages); + return Err(SPPError::InvalidNumberOfMessages); } let mut secret_shares = Vec::with_capacity(participants); @@ -159,27 +168,22 @@ impl Keypair { let mut total_polynomial_commitment = PolynomialCommitment { coefficients_commitments: vec![] }; let mut identifiers = Vec::new(); + let mut public_keys = Vec::with_capacity(participants); let mut proofs_of_possession = Vec::with_capacity(participants); let mut pops_transcripts = Vec::with_capacity(participants); - let mut public_keys = Vec::with_capacity(participants); for (j, message) in messages.iter().enumerate() { if &message.content.parameters != parameters { - return Err(DKGError::DifferentParameters); + return Err(SPPError::DifferentParameters); } if message.content.recipients_hash != first_message.content.recipients_hash { - return Err(DKGError::DifferentRecipientsHash); + return Err(SPPError::DifferentRecipientsHash); } let content = &message.content; let polynomial_commitment = &content.polynomial_commitment; let encrypted_secret_shares = &content.encrypted_secret_shares; - let secret_commitment = polynomial_commitment - .coefficients_commitments - .first() - .expect("This never fails because the minimum threshold is 2"); - let public_key = PublicKey::from_point( *polynomial_commitment .coefficients_commitments @@ -198,11 +202,11 @@ impl Keypair { encryption_transcript.append_message(b"nonce", &content.encryption_nonce); if polynomial_commitment.coefficients_commitments.len() != threshold { - return Err(DKGError::IncorrectNumberOfCoefficientCommitments); + return Err(SPPError::IncorrectNumberOfCoefficientCommitments); } if encrypted_secret_shares.len() != participants { - return Err(DKGError::IncorrectNumberOfEncryptedShares); + return Err(SPPError::IncorrectNumberOfEncryptedShares); } let mut signature_transcript = Transcript::new(b"signature"); @@ -211,6 +215,11 @@ impl Keypair { let mut pop_transcript = Transcript::new(b"pop"); + let secret_commitment = polynomial_commitment + .coefficients_commitments + .first() + .expect("This never fails because the minimum threshold is 2"); + pop_transcript .append_message(b"secret commitment", secret_commitment.compress().as_bytes()); @@ -243,9 +252,11 @@ impl Keypair { } if !secret_share_found { - if let Ok(secret_share) = - encrypted_secret_share.decrypt(&key_bytes, &content.encryption_nonce) - { + if let Ok(secret_share) = SecretShare::decrypt( + encrypted_secret_share, + &key_bytes, + &content.encryption_nonce, + ) { if secret_share.0 * GENERATOR == polynomial_commitment.evaluate(&identifiers[i].0) { @@ -256,35 +267,116 @@ impl Keypair { } } - total_secret_share += secret_shares.get(j).ok_or(DKGError::InvalidSecretShare)?.0; + total_secret_share += secret_shares.get(j).ok_or(SPPError::InvalidSecretShare)?.0; group_point += secret_commitment; } verify_batch(&mut pops_transcripts, &proofs_of_possession, &public_keys, false) - .map_err(DKGError::InvalidProofOfPossession)?; + .map_err(SPPError::InvalidProofOfPossession)?; verify_batch(&mut signatures_transcripts, &signatures, &senders, false) - .map_err(DKGError::InvalidSignature)?; + .map_err(SPPError::InvalidSignature)?; for id in &identifiers { let evaluation = total_polynomial_commitment.evaluate(&id.0); verifying_keys.push((*id, VerifyingShare(PublicKey::from_point(evaluation)))); } - let dkg_output = - DKGOutput::new(GroupPublicKey(PublicKey::from_point(group_point)), verifying_keys); + let spp_output = SPPOutput::new( + parameters, + ThresholdPublicKey(PublicKey::from_point(group_point)), + verifying_keys, + ); - let mut dkg_output_transcript = Transcript::new(b"dkg output"); - dkg_output_transcript.append_message(b"message", &dkg_output.to_bytes()); + let mut spp_output_transcript = Transcript::new(b"spp output"); + spp_output_transcript.append_message(b"message", &spp_output.to_bytes()); - let signature = self.sign(dkg_output_transcript); - let dkg_output = DKGOutputMessage::new(self.public, dkg_output, signature); + let signature = self.sign(spp_output_transcript); + let spp_output = SPPOutputMessage::new(VerifyingShare(self.public), spp_output, signature); let mut nonce: [u8; 32] = [0u8; 32]; getrandom_or_panic().fill_bytes(&mut nonce); let secret_key = SecretKey { key: total_secret_share, nonce }; - Ok((dkg_output, SigningShare(secret_key))) + let keypair = Keypair::from(secret_key); + + Ok((spp_output, SigningKeypair(keypair))) + } +} + +#[cfg(test)] +mod tests { + use crate::olaf::simplpedpop::types::{AllMessage, Parameters}; + use crate::olaf::MINIMUM_THRESHOLD; + use crate::{Keypair, PublicKey}; + use alloc::vec::Vec; + use rand::Rng; + + const MAXIMUM_PARTICIPANTS: u16 = 10; + const MINIMUM_PARTICIPANTS: u16 = 2; + const PROTOCOL_RUNS: usize = 1; + + fn generate_parameters() -> Parameters { + let mut rng = rand::thread_rng(); + let participants = rng.gen_range(MINIMUM_PARTICIPANTS..=MAXIMUM_PARTICIPANTS); + let threshold = rng.gen_range(MINIMUM_THRESHOLD..=participants); + + Parameters { participants, threshold } + } + + #[test] + fn test_simplpedpop_protocol() { + for _ in 0..PROTOCOL_RUNS { + let parameters = generate_parameters(); + let participants = parameters.participants as usize; + let threshold = parameters.threshold as usize; + + let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public).collect(); + + let mut all_messages = Vec::new(); + for i in 0..participants { + let message: AllMessage = keypairs[i] + .simplpedpop_contribute_all(threshold as u16, public_keys.clone()) + .unwrap(); + all_messages.push(message); + } + + let mut spp_outputs = Vec::new(); + + for kp in keypairs.iter() { + let spp_output = kp.simplpedpop_recipient_all(&all_messages).unwrap(); + spp_output.0.verify_signature().unwrap(); + spp_outputs.push(spp_output); + } + + // Verify that all threshold_public_keys and verifying_keys are equal + assert!( + spp_outputs.windows(2).all(|w| w[0].0.spp_output.threshold_public_key.0 + == w[1].0.spp_output.threshold_public_key.0 + && w[0].0.spp_output.verifying_keys.len() + == w[1].0.spp_output.verifying_keys.len() + && w[0] + .0 + .spp_output + .verifying_keys + .iter() + .zip(w[1].0.spp_output.verifying_keys.iter()) + .all(|((a, b), (c, d))| a.0 == c.0 && b.0 == d.0)), + "All spp outputs should have identical group public keys and verifying keys." + ); + + // Verify that all verifying_shares are valid + for i in 0..participants { + for j in 0..participants { + assert_eq!( + spp_outputs[i].0.spp_output.verifying_keys[j].1 .0, + (spp_outputs[j].1 .0.public), + "Verification of total secret shares failed!" + ); + } + } + } } } diff --git a/src/olaf/types.rs b/src/olaf/simplpedpop/types.rs similarity index 71% rename from src/olaf/types.rs rename to src/olaf/simplpedpop/types.rs index 46ded90..806c906 100644 --- a/src/olaf/types.rs +++ b/src/olaf/simplpedpop/types.rs @@ -1,65 +1,31 @@ -//! SimplPedPoP types. +//! Types of the SimplPedPoP protocol. #![allow(clippy::too_many_arguments)] use core::iter; use alloc::vec::Vec; -use curve25519_dalek::{ristretto::CompressedRistretto, traits::Identity, RistrettoPoint, Scalar}; +use merlin::Transcript; use rand_core::{CryptoRng, RngCore}; use zeroize::ZeroizeOnDrop; -use crate::{ - context::SigningTranscript, scalar_from_canonical_bytes, PublicKey, Signature, - PUBLIC_KEY_LENGTH, SIGNATURE_LENGTH, -}; -use super::{ - errors::{DKGError, DKGResult}, - GroupPublicKey, Identifier, VerifyingShare, GENERATOR, MINIMUM_THRESHOLD, -}; use aead::KeyInit; use chacha20poly1305::{aead::Aead, ChaCha20Poly1305, Nonce}; +use curve25519_dalek::{ristretto::CompressedRistretto, traits::Identity, RistrettoPoint, Scalar}; +use crate::{ + context::SigningTranscript, + olaf::{ + Identifier, ThresholdPublicKey, VerifyingShare, COMPRESSED_RISTRETTO_LENGTH, GENERATOR, + MINIMUM_THRESHOLD, SCALAR_LENGTH, + }, + scalar_from_canonical_bytes, PublicKey, Signature, PUBLIC_KEY_LENGTH, SIGNATURE_LENGTH, +}; +use super::errors::{SPPError, SPPResult}; -pub(super) const COMPRESSED_RISTRETTO_LENGTH: usize = 32; -pub(super) const VEC_LENGTH: usize = 2; +pub(super) const U16_LENGTH: usize = 2; pub(super) const ENCRYPTION_NONCE_LENGTH: usize = 12; pub(super) const RECIPIENTS_HASH_LENGTH: usize = 16; pub(super) const CHACHA20POLY1305_LENGTH: usize = 64; pub(super) const CHACHA20POLY1305_KEY_LENGTH: usize = 32; -pub(super) const SCALAR_LENGTH: usize = 32; - -/// The parameters of a given execution of the SimplPedPoP protocol. -#[derive(PartialEq, Eq)] -pub struct Parameters { - pub(super) participants: u16, - pub(super) threshold: u16, -} - -impl Parameters { - /// Create new parameters. - pub fn generate(participants: u16, threshold: u16) -> Parameters { - Parameters { participants, threshold } - } - - pub(super) fn validate(&self) -> Result<(), DKGError> { - if self.threshold < MINIMUM_THRESHOLD { - return Err(DKGError::InsufficientThreshold); - } - - if self.participants < MINIMUM_THRESHOLD { - return Err(DKGError::InvalidNumberOfParticipants); - } - - if self.threshold > self.participants { - return Err(DKGError::ExcessiveThreshold); - } - - Ok(()) - } - - pub(super) 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()); - } -} +pub(super) const VEC_LENGTH: usize = 2; #[derive(ZeroizeOnDrop)] pub(super) struct SecretShare(pub(super) Scalar); @@ -69,33 +35,30 @@ impl SecretShare { &self, key: &[u8; CHACHA20POLY1305_KEY_LENGTH], nonce: &[u8; ENCRYPTION_NONCE_LENGTH], - ) -> DKGResult { + ) -> SPPResult { let cipher = ChaCha20Poly1305::new(&(*key).into()); let nonce = Nonce::from_slice(&nonce[..]); let ciphertext: Vec = cipher .encrypt(nonce, &self.0.to_bytes()[..]) - .map_err(DKGError::EncryptionError)?; + .map_err(SPPError::EncryptionError)?; Ok(EncryptedSecretShare(ciphertext)) } -} -#[derive(Clone)] -pub struct EncryptedSecretShare(pub(super) Vec); - -impl EncryptedSecretShare { pub(super) fn decrypt( - &self, + encrypted_secret_share: &EncryptedSecretShare, key: &[u8; CHACHA20POLY1305_KEY_LENGTH], nonce: &[u8; ENCRYPTION_NONCE_LENGTH], - ) -> DKGResult { + ) -> SPPResult { let cipher = ChaCha20Poly1305::new(&(*key).into()); let nonce = Nonce::from_slice(&nonce[..]); - let plaintext = cipher.decrypt(nonce, &self.0[..]).map_err(DKGError::DecryptionError)?; + let plaintext = cipher + .decrypt(nonce, &encrypted_secret_share.0[..]) + .map_err(SPPError::DecryptionError)?; let mut bytes = [0; 32]; bytes.copy_from_slice(&plaintext); @@ -135,6 +98,68 @@ impl SecretPolynomial { value } + + pub(super) fn commit(&self) -> PolynomialCommitment { + let coefficients_commitments = + self.coefficients.iter().map(|coefficient| GENERATOR * coefficient).collect(); + + PolynomialCommitment { coefficients_commitments } + } +} + +/// The parameters of a given execution of the SimplPedPoP protocol. +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct Parameters { + pub(crate) participants: u16, + pub(crate) threshold: u16, +} + +impl Parameters { + /// Create new parameters. + pub(crate) fn generate(participants: u16, threshold: u16) -> Parameters { + Parameters { participants, threshold } + } + + pub(super) fn validate(&self) -> Result<(), SPPError> { + if self.threshold < MINIMUM_THRESHOLD { + return Err(SPPError::InsufficientThreshold); + } + + if self.participants < MINIMUM_THRESHOLD { + return Err(SPPError::InvalidNumberOfParticipants); + } + + if self.threshold > self.participants { + return Err(SPPError::ExcessiveThreshold); + } + + Ok(()) + } + + pub(super) 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()); + } + + /// Serializes `Parameters` into a byte array. + pub(super) fn to_bytes(&self) -> [u8; U16_LENGTH * 2] { + let mut bytes = [0u8; U16_LENGTH * 2]; + bytes[0..U16_LENGTH].copy_from_slice(&self.participants.to_le_bytes()); + bytes[U16_LENGTH..U16_LENGTH * 2].copy_from_slice(&self.threshold.to_le_bytes()); + bytes + } + + /// Constructs `Parameters` from a byte array. + pub(super) fn from_bytes(bytes: &[u8]) -> SPPResult { + if bytes.len() != U16_LENGTH * 2 { + return Err(SPPError::InvalidParameters); + } + + let participants = u16::from_le_bytes([bytes[0], bytes[1]]); + let threshold = u16::from_le_bytes([bytes[2], bytes[3]]); + + Ok(Parameters { participants, threshold }) + } } /// The polynomial commitment of a participant, used to verify the secret shares without revealing the polynomial. @@ -143,16 +168,6 @@ pub struct PolynomialCommitment { } impl PolynomialCommitment { - pub(super) fn commit(secret_polynomial: &SecretPolynomial) -> Self { - let coefficients_commitments = secret_polynomial - .coefficients - .iter() - .map(|coefficient| GENERATOR * coefficient) - .collect(); - - Self { coefficients_commitments } - } - pub(super) fn evaluate(&self, identifier: &Scalar) -> RistrettoPoint { let i = identifier; @@ -189,6 +204,11 @@ impl PolynomialCommitment { } } +#[derive(Clone)] +pub struct EncryptedSecretShare(pub(super) Vec); + +impl EncryptedSecretShare {} + /// AllMessage packs together messages for all participants. /// /// We'd save bandwidth by having separate messages for each @@ -201,7 +221,7 @@ pub struct AllMessage { impl AllMessage { /// Creates a new message. - pub fn new(content: MessageContent, signature: Signature) -> Self { + pub(crate) fn new(content: MessageContent, signature: Signature) -> Self { Self { content, signature } } /// Serialize AllMessage @@ -215,14 +235,14 @@ impl AllMessage { } /// Deserialize AllMessage from bytes - pub fn from_bytes(bytes: &[u8]) -> Result { + pub fn from_bytes(bytes: &[u8]) -> Result { let mut cursor = 0; let content = MessageContent::from_bytes(&bytes[cursor..])?; cursor += content.to_bytes().len(); let signature = Signature::from_bytes(&bytes[cursor..cursor + SIGNATURE_LENGTH]) - .map_err(DKGError::InvalidSignature)?; + .map_err(SPPError::InvalidSignature)?; Ok(AllMessage { content, signature }) } @@ -242,7 +262,7 @@ pub struct MessageContent { impl MessageContent { /// Creates the content of the message. - pub fn new( + pub(super) fn new( sender: PublicKey, encryption_nonce: [u8; ENCRYPTION_NONCE_LENGTH], parameters: Parameters, @@ -263,8 +283,9 @@ impl MessageContent { proof_of_possession, } } - /// Serialize MessageContent - pub fn to_bytes(&self) -> Vec { + + /// Serialize MessageContent into bytes. + pub(super) fn to_bytes(&self) -> Vec { let mut bytes = Vec::new(); bytes.extend(self.sender.to_bytes()); @@ -287,37 +308,29 @@ impl MessageContent { bytes } - /// Deserialize MessageContent from bytes - pub fn from_bytes(bytes: &[u8]) -> Result { + /// Deserialize MessageContent from bytes. + pub(super) fn from_bytes(bytes: &[u8]) -> Result { let mut cursor = 0; let sender = PublicKey::from_bytes(&bytes[cursor..cursor + PUBLIC_KEY_LENGTH]) - .map_err(DKGError::InvalidPublicKey)?; + .map_err(SPPError::InvalidPublicKey)?; cursor += PUBLIC_KEY_LENGTH; let encryption_nonce: [u8; ENCRYPTION_NONCE_LENGTH] = bytes [cursor..cursor + ENCRYPTION_NONCE_LENGTH] .try_into() - .map_err(DKGError::DeserializationError)?; + .map_err(SPPError::DeserializationError)?; cursor += ENCRYPTION_NONCE_LENGTH; - let participants = u16::from_le_bytes( - bytes[cursor..cursor + VEC_LENGTH] - .try_into() - .map_err(DKGError::DeserializationError)?, - ); - cursor += VEC_LENGTH; - let threshold = u16::from_le_bytes( - bytes[cursor..cursor + VEC_LENGTH] - .try_into() - .map_err(DKGError::DeserializationError)?, - ); - cursor += VEC_LENGTH; + let parameters = Parameters::from_bytes(&bytes[cursor..cursor + U16_LENGTH * 2])?; + cursor += U16_LENGTH * 2; + + let participants = parameters.participants; let recipients_hash: [u8; RECIPIENTS_HASH_LENGTH] = bytes [cursor..cursor + RECIPIENTS_HASH_LENGTH] .try_into() - .map_err(DKGError::DeserializationError)?; + .map_err(SPPError::DeserializationError)?; cursor += RECIPIENTS_HASH_LENGTH; let mut coefficients_commitments = Vec::with_capacity(participants as usize); @@ -326,10 +339,10 @@ impl MessageContent { let point = CompressedRistretto::from_slice( &bytes[cursor..cursor + COMPRESSED_RISTRETTO_LENGTH], ) - .map_err(DKGError::DeserializationError)?; + .map_err(SPPError::DeserializationError)?; coefficients_commitments - .push(point.decompress().ok_or(DKGError::InvalidCoefficientCommitment)?); + .push(point.decompress().ok_or(SPPError::InvalidCoefficientCommitment)?); cursor += COMPRESSED_RISTRETTO_LENGTH; } @@ -345,16 +358,16 @@ impl MessageContent { } let ephemeral_key = PublicKey::from_bytes(&bytes[cursor..cursor + PUBLIC_KEY_LENGTH]) - .map_err(DKGError::InvalidPublicKey)?; + .map_err(SPPError::InvalidPublicKey)?; cursor += PUBLIC_KEY_LENGTH; let proof_of_possession = Signature::from_bytes(&bytes[cursor..cursor + SIGNATURE_LENGTH]) - .map_err(DKGError::InvalidSignature)?; + .map_err(SPPError::InvalidSignature)?; Ok(MessageContent { sender, encryption_nonce, - parameters: Parameters { participants, threshold }, + parameters, recipients_hash, polynomial_commitment, encrypted_secret_shares, @@ -365,26 +378,26 @@ impl MessageContent { } /// The signed output of the SimplPedPoP protocol. -pub struct DKGOutputMessage { - pub(super) sender: PublicKey, - pub(super) dkg_output: DKGOutput, - pub(super) signature: Signature, +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct SPPOutputMessage { + pub(crate) signer: VerifyingShare, + pub(crate) spp_output: SPPOutput, + pub(crate) signature: Signature, } -impl DKGOutputMessage { - /// Creates a signed SimplPedPoP output. - pub fn new(sender: PublicKey, content: DKGOutput, signature: Signature) -> Self { - Self { sender, dkg_output: content, signature } +impl SPPOutputMessage { + pub(crate) fn new(signer: VerifyingShare, content: SPPOutput, signature: Signature) -> Self { + Self { signer, signature, spp_output: content } } - /// Serializes the DKGOutputMessage into bytes. + /// Serializes the SPPOutputMessage into bytes. pub fn to_bytes(&self) -> Vec { let mut bytes = Vec::new(); - let pk_bytes = self.sender.to_bytes(); + let pk_bytes = self.signer.0.to_bytes(); bytes.extend(pk_bytes); - let content_bytes = self.dkg_output.to_bytes(); + let content_bytes = self.spp_output.to_bytes(); bytes.extend(content_bytes); let signature_bytes = self.signature.to_bytes(); @@ -393,44 +406,67 @@ impl DKGOutputMessage { bytes } - /// Deserializes the DKGOutputMessage from bytes. - pub fn from_bytes(bytes: &[u8]) -> Result { + /// Deserializes the SPPOutputMessage from bytes. + pub fn from_bytes(bytes: &[u8]) -> Result { let mut cursor = 0; let pk_bytes = &bytes[..PUBLIC_KEY_LENGTH]; - let sender = PublicKey::from_bytes(pk_bytes).map_err(DKGError::InvalidPublicKey)?; + let signer = + VerifyingShare(PublicKey::from_bytes(pk_bytes).map_err(SPPError::InvalidPublicKey)?); cursor += PUBLIC_KEY_LENGTH; let content_bytes = &bytes[cursor..bytes.len() - SIGNATURE_LENGTH]; - let dkg_output = DKGOutput::from_bytes(content_bytes)?; + let spp_output = SPPOutput::from_bytes(content_bytes)?; cursor = bytes.len() - SIGNATURE_LENGTH; let signature = Signature::from_bytes(&bytes[cursor..cursor + SIGNATURE_LENGTH]) - .map_err(DKGError::InvalidSignature)?; + .map_err(SPPError::InvalidSignature)?; - Ok(DKGOutputMessage { sender, dkg_output, signature }) + Ok(SPPOutputMessage { signer, spp_output, signature }) + } + + /// Returns the output of the SimplPedPoP protocol. + pub fn spp_output(&self) -> SPPOutput { + self.spp_output.clone() + } + + /// Verifies the signature of the message. + pub fn verify_signature(&self) -> SPPResult<()> { + let mut spp_output_transcript = Transcript::new(b"spp output"); + spp_output_transcript.append_message(b"message", &self.spp_output.to_bytes()); + + self.signer + .0 + .verify(spp_output_transcript, &self.signature) + .map_err(SPPError::InvalidSignature) } } /// The content of the signed output of the SimplPedPoP protocol. -pub struct DKGOutput { - pub(super) group_public_key: GroupPublicKey, - pub(super) verifying_keys: Vec<(Identifier, VerifyingShare)>, +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct SPPOutput { + pub(crate) parameters: Parameters, + pub(crate) threshold_public_key: ThresholdPublicKey, + pub(crate) verifying_keys: Vec<(Identifier, VerifyingShare)>, } -impl DKGOutput { - /// Creates the content of the SimplPedPoP output. - pub fn new( - group_public_key: GroupPublicKey, +impl SPPOutput { + pub(crate) fn new( + parameters: &Parameters, + threshold_public_key: ThresholdPublicKey, verifying_keys: Vec<(Identifier, VerifyingShare)>, ) -> Self { - Self { group_public_key, verifying_keys } + let parameters = Parameters::generate(parameters.participants, parameters.threshold); + + Self { threshold_public_key, verifying_keys, parameters } } - /// Serializes the DKGOutput into bytes. - pub fn to_bytes(&self) -> Vec { + + pub(crate) fn to_bytes(&self) -> Vec { let mut bytes = Vec::new(); - let compressed_public_key = self.group_public_key.0.as_compressed(); + bytes.extend(self.parameters.to_bytes()); + + let compressed_public_key = self.threshold_public_key.0.as_compressed(); bytes.extend(compressed_public_key.to_bytes().iter()); let key_count = self.verifying_keys.len() as u16; @@ -444,55 +480,110 @@ impl DKGOutput { bytes } - /// Deserializes the DKGOutput from bytes. - pub fn from_bytes(bytes: &[u8]) -> Result { + /// Deserializes the SPPOutput from bytes. + pub(crate) fn from_bytes(bytes: &[u8]) -> Result { let mut cursor = 0; + let parameters = Parameters::from_bytes(&bytes[cursor..cursor + U16_LENGTH * 2])?; + cursor += U16_LENGTH * 2; + let public_key_bytes = &bytes[cursor..cursor + PUBLIC_KEY_LENGTH]; cursor += PUBLIC_KEY_LENGTH; let compressed_public_key = CompressedRistretto::from_slice(public_key_bytes) - .map_err(DKGError::DeserializationError)?; - - let group_public_key = - compressed_public_key.decompress().ok_or(DKGError::InvalidGroupPublicKey)?; + .map_err(SPPError::DeserializationError)?; - cursor += VEC_LENGTH; + let threshold_public_key = + compressed_public_key.decompress().ok_or(SPPError::InvalidThresholdPublicKey)?; let mut verifying_keys = Vec::new(); + cursor += VEC_LENGTH; + while cursor < bytes.len() { let mut identifier_bytes = [0; SCALAR_LENGTH]; identifier_bytes.copy_from_slice(&bytes[cursor..cursor + SCALAR_LENGTH]); let identifier = - scalar_from_canonical_bytes(identifier_bytes).ok_or(DKGError::InvalidIdentifier)?; + scalar_from_canonical_bytes(identifier_bytes).ok_or(SPPError::InvalidIdentifier)?; cursor += SCALAR_LENGTH; let key_bytes = &bytes[cursor..cursor + PUBLIC_KEY_LENGTH]; cursor += PUBLIC_KEY_LENGTH; - let key = PublicKey::from_bytes(key_bytes).map_err(DKGError::InvalidPublicKey)?; + let key = PublicKey::from_bytes(key_bytes).map_err(SPPError::InvalidPublicKey)?; verifying_keys.push((Identifier(identifier), VerifyingShare(key))); } - Ok(DKGOutput { - group_public_key: GroupPublicKey(PublicKey::from_point(group_public_key)), + Ok(SPPOutput { + threshold_public_key: ThresholdPublicKey(PublicKey::from_point(threshold_public_key)), verifying_keys, + parameters, }) } + + /// Returns the threshold public key. + pub fn threshold_public_key(&self) -> ThresholdPublicKey { + self.threshold_public_key + } } #[cfg(test)] mod tests { use merlin::Transcript; use rand_core::OsRng; - use crate::Keypair; + use crate::{context::SigningTranscript, Keypair}; use super::*; + use curve25519_dalek::RistrettoPoint; + + #[test] + fn test_encryption_decryption() { + let mut rng = OsRng; + let ephemeral_key = Keypair::generate(); + let recipient = Keypair::generate(); + let encryption_nonce = [1; ENCRYPTION_NONCE_LENGTH]; + let key_exchange = ephemeral_key.secret.key * recipient.public.as_point(); + let secret_share = SecretShare(Scalar::random(&mut rng)); + let mut transcript = Transcript::new(b"encryption"); + transcript.commit_point(b"key", &key_exchange.compress()); + let mut key_bytes = [0; CHACHA20POLY1305_KEY_LENGTH]; + transcript.challenge_bytes(b"key", &mut key_bytes); + + let encrypted_share = secret_share.encrypt(&key_bytes, &encryption_nonce).unwrap(); + + SecretShare::decrypt(&encrypted_share, &key_bytes, &encryption_nonce).unwrap(); + } + + #[test] + fn test_generate_polynomial_commitment_valid() { + let degree = 3; + + let polynomial = SecretPolynomial::generate(degree, &mut OsRng); + + let polynomial_commitment = polynomial.commit(); + + 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 = SecretPolynomial { 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_serialize_deserialize_all_message() { let sender = Keypair::generate(); let encryption_nonce = [1u8; ENCRYPTION_NONCE_LENGTH]; - let parameters = Parameters { participants: 2, threshold: 1 }; + let parameters = Parameters { participants: 2, threshold: 2 }; let recipients_hash = [2u8; RECIPIENTS_HASH_LENGTH]; let coefficients_commitments = vec![RistrettoPoint::random(&mut OsRng), RistrettoPoint::random(&mut OsRng)]; @@ -501,8 +592,8 @@ mod tests { EncryptedSecretShare(vec![1; CHACHA20POLY1305_LENGTH]), EncryptedSecretShare(vec![1; CHACHA20POLY1305_LENGTH]), ]; - let signature = sender.sign(Transcript::new(b"sig")); let proof_of_possession = sender.sign(Transcript::new(b"pop")); + let signature = sender.sign(Transcript::new(b"sig")); let ephemeral_key = PublicKey::from_point(RistrettoPoint::random(&mut OsRng)); let message_content = MessageContent::new( @@ -559,8 +650,6 @@ mod tests { .zip(deserialized_message.content.encrypted_secret_shares.iter()) .all(|(a, b)| a.0 == b.0)); - assert_eq!(message.content.ephemeral_key, deserialized_message.content.ephemeral_key); - assert_eq!( message.content.proof_of_possession, deserialized_message.content.proof_of_possession @@ -570,7 +659,7 @@ mod tests { } #[test] - fn test_dkg_output_serialization() { + fn test_spp_output_message_serialization() { let mut rng = OsRng; let group_public_key = RistrettoPoint::random(&mut rng); let verifying_keys = vec![ @@ -587,95 +676,36 @@ mod tests { VerifyingShare(PublicKey::from_point(RistrettoPoint::random(&mut rng))), ), ]; + let parameters = Parameters::generate(2, 2); - let dkg_output = DKGOutput { - group_public_key: GroupPublicKey(PublicKey::from_point(group_public_key)), + let spp_output = SPPOutput { + parameters, + threshold_public_key: ThresholdPublicKey(PublicKey::from_point(group_public_key)), verifying_keys, }; let keypair = Keypair::generate(); let signature = keypair.sign(Transcript::new(b"test")); - let dkg_output = DKGOutputMessage { sender: keypair.public, dkg_output, signature }; + let spp_output_message = + SPPOutputMessage { signer: VerifyingShare(keypair.public), spp_output, signature }; - let bytes = dkg_output.to_bytes(); + let bytes = spp_output_message.to_bytes(); - let deserialized_dkg_output = - DKGOutputMessage::from_bytes(&bytes).expect("Deserialization failed"); + let deserialized_spp_output_message = + SPPOutputMessage::from_bytes(&bytes).expect("Deserialization failed"); assert_eq!( - deserialized_dkg_output.dkg_output.group_public_key.0.as_compressed(), - dkg_output.dkg_output.group_public_key.0.as_compressed(), + deserialized_spp_output_message.spp_output, spp_output_message.spp_output, "Group public keys do not match" ); assert_eq!( - deserialized_dkg_output.dkg_output.verifying_keys.len(), - dkg_output.dkg_output.verifying_keys.len(), - "Verifying keys counts do not match" - ); - - assert!( - deserialized_dkg_output - .dkg_output - .verifying_keys - .iter() - .zip(dkg_output.dkg_output.verifying_keys.iter()) - .all(|((a, b), (c, d))| a.0 == c.0 && b.0 == d.0), - "Verifying keys do not match" - ); - - assert_eq!( - deserialized_dkg_output.signature, dkg_output.signature, + deserialized_spp_output_message.signature, spp_output_message.signature, "Signatures do not match" ); } - #[test] - fn test_encryption_decryption() { - let mut rng = OsRng; - let ephemeral_key = Keypair::generate(); - let recipient = Keypair::generate(); - let encryption_nonce = [1; ENCRYPTION_NONCE_LENGTH]; - let key_exchange = ephemeral_key.secret.key * recipient.public.as_point(); - let secret_share = SecretShare(Scalar::random(&mut rng)); - let mut transcript = Transcript::new(b"encryption"); - transcript.commit_point(b"key", &key_exchange.compress()); - let mut key_bytes = [0; CHACHA20POLY1305_KEY_LENGTH]; - transcript.challenge_bytes(b"key", &mut key_bytes); - - let encrypted_share = secret_share.encrypt(&key_bytes, &encryption_nonce).unwrap(); - - encrypted_share.decrypt(&key_bytes, &encryption_nonce).unwrap(); - } - - #[test] - fn test_generate_polynomial_commitment_valid() { - let degree = 3; - - let polynomial = SecretPolynomial::generate(degree, &mut OsRng); - - 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 = SecretPolynomial { 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 { @@ -734,4 +764,14 @@ mod tests { assert_eq!(result, expected, "The evaluated commitment does not match the expected result"); } + + #[test] + fn test_parameters_serialization() { + let params = Parameters::generate(3, 2); + let bytes = params.to_bytes(); + let result = Parameters::from_bytes(&bytes).unwrap(); + + assert_eq!(params.participants, result.participants); + assert_eq!(params.threshold, result.threshold); + } } diff --git a/src/olaf/tests.rs b/src/olaf/tests.rs deleted file mode 100644 index a4b56f8..0000000 --- a/src/olaf/tests.rs +++ /dev/null @@ -1,340 +0,0 @@ -#[cfg(test)] -mod tests { - mod simplpedpop { - use crate::olaf::types::{ - AllMessage, EncryptedSecretShare, CHACHA20POLY1305_LENGTH, RECIPIENTS_HASH_LENGTH, - Parameters, - }; - use crate::olaf::errors::DKGError; - use crate::{Keypair, PublicKey}; - use alloc::vec::Vec; - use curve25519_dalek::ristretto::RistrettoPoint; - use curve25519_dalek::traits::Identity; - use merlin::Transcript; - use rand::Rng; - use crate::olaf::MINIMUM_THRESHOLD; - - const MAXIMUM_PARTICIPANTS: u16 = 10; - const MINIMUM_PARTICIPANTS: u16 = 2; - const PROTOCOL_RUNS: usize = 1; - - fn generate_parameters() -> Parameters { - let mut rng = rand::thread_rng(); - let participants = rng.gen_range(MINIMUM_PARTICIPANTS..=MAXIMUM_PARTICIPANTS); - let threshold = rng.gen_range(MINIMUM_THRESHOLD..=participants); - - Parameters { participants, threshold } - } - - #[test] - fn test_simplpedpop_protocol() { - for _ in 0..PROTOCOL_RUNS { - let parameters = generate_parameters(); - let participants = parameters.participants as usize; - let threshold = parameters.threshold as usize; - - let keypairs: Vec = - (0..participants).map(|_| Keypair::generate()).collect(); - let public_keys: Vec = keypairs.iter().map(|kp| kp.public).collect(); - - let mut all_messages = Vec::new(); - for i in 0..participants { - let message: AllMessage = keypairs[i] - .simplpedpop_contribute_all(threshold as u16, public_keys.clone()) - .unwrap(); - all_messages.push(message); - } - - let mut dkg_outputs = Vec::new(); - - for kp in keypairs.iter() { - let dkg_output = kp.simplpedpop_recipient_all(&all_messages).unwrap(); - dkg_outputs.push(dkg_output); - } - - // Verify that all DKG outputs are equal for group_public_key and verifying_keys - assert!( - dkg_outputs.windows(2).all(|w| w[0].0.dkg_output.group_public_key.0 - == w[1].0.dkg_output.group_public_key.0 - && w[0].0.dkg_output.verifying_keys.len() - == w[1].0.dkg_output.verifying_keys.len() - && w[0] - .0 - .dkg_output - .verifying_keys - .iter() - .zip(w[1].0.dkg_output.verifying_keys.iter()) - .all(|((a, b), (c, d))| a.0 == c.0 && b.0 == d.0)), - "All DKG outputs should have identical group public keys and verifying keys." - ); - - // Verify that all verifying_shares are valid - for i in 0..participants { - for j in 0..participants { - assert_eq!( - dkg_outputs[i].0.dkg_output.verifying_keys[j].1 .0, - (dkg_outputs[j].1 .0.to_public()), - "Verification of total secret shares failed!" - ); - } - } - } - } - - #[test] - fn test_invalid_number_of_messages() { - let threshold = 3; - let participants = 5; - - let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); - let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); - - let mut messages: Vec = keypairs - .iter() - .map(|kp| kp.simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap()) - .collect(); - - messages.pop(); - - let result = keypairs[0].simplpedpop_recipient_all(&messages); - - match result { - Ok(_) => panic!("Expected an error, but got Ok."), - Err(e) => match e { - DKGError::InvalidNumberOfMessages => assert!(true), - _ => { - panic!("Expected DKGError::InvalidNumberOfMessages, but got {:?}", e) - }, - }, - } - } - - #[test] - fn test_different_parameters() { - let threshold = 3; - let participants = 5; - - let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); - let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); - - let mut messages: Vec = Vec::new(); - for i in 0..participants { - let message = - keypairs[i].simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap(); - messages.push(message); - } - - messages[1].content.parameters.threshold += 1; - - let result = keypairs[0].simplpedpop_recipient_all(&messages); - - // Check if the result is an error - match result { - Ok(_) => panic!("Expected an error, but got Ok."), - Err(e) => match e { - DKGError::DifferentParameters => assert!(true), - _ => panic!("Expected DKGError::DifferentParameters, but got {:?}", e), - }, - } - } - - #[test] - fn test_different_recipients_hash() { - let threshold = 3; - let participants = 5; - - let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); - let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); - - let mut messages: Vec = keypairs - .iter() - .map(|kp| kp.simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap()) - .collect(); - - messages[1].content.recipients_hash = [1; RECIPIENTS_HASH_LENGTH]; - - let result = keypairs[0].simplpedpop_recipient_all(&messages); - - match result { - Ok(_) => panic!("Expected an error, but got Ok."), - Err(e) => match e { - DKGError::DifferentRecipientsHash => assert!(true), - _ => { - panic!("Expected DKGError::DifferentRecipientsHash, but got {:?}", e) - }, - }, - } - } - - #[test] - fn test_incorrect_number_of_commitments() { - let threshold = 3; - let participants = 5; - - let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); - let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); - - let mut messages: Vec = keypairs - .iter() - .map(|kp| kp.simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap()) - .collect(); - - messages[1].content.polynomial_commitment.coefficients_commitments.pop(); - - let result = keypairs[0].simplpedpop_recipient_all(&messages); - - match result { - Ok(_) => panic!("Expected an error, but got Ok."), - Err(e) => match e { - DKGError::IncorrectNumberOfCoefficientCommitments => assert!(true), - _ => panic!( - "Expected DKGError::IncorrectNumberOfCoefficientCommitments, but got {:?}", - e - ), - }, - } - } - - #[test] - fn test_incorrect_number_of_encrypted_shares() { - let threshold = 3; - let participants = 5; - - let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); - let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); - - let mut messages: Vec = keypairs - .iter() - .map(|kp| kp.simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap()) - .collect(); - - messages[1].content.encrypted_secret_shares.pop(); - - let result = keypairs[0].simplpedpop_recipient_all(&messages); - - match result { - Ok(_) => panic!("Expected an error, but got Ok."), - Err(e) => match e { - DKGError::IncorrectNumberOfEncryptedShares => assert!(true), - _ => panic!( - "Expected DKGError::IncorrectNumberOfEncryptedShares, but got {:?}", - e - ), - }, - } - } - - #[test] - fn test_invalid_secret_share() { - let threshold = 3; - let participants = 5; - - let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); - let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); - - let mut messages: Vec = keypairs - .iter() - .map(|kp| kp.simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap()) - .collect(); - - messages[1].content.encrypted_secret_shares[0] = - EncryptedSecretShare(vec![1; CHACHA20POLY1305_LENGTH]); - - let result = keypairs[0].simplpedpop_recipient_all(&messages); - - match result { - Ok(_) => panic!("Expected an error, but got Ok."), - Err(e) => match e { - DKGError::InvalidSecretShare => assert!(true), - _ => panic!("Expected DKGError::InvalidSecretShare, but got {:?}", e), - }, - } - } - - #[test] - fn test_invalid_signature() { - let threshold = 3; - let participants = 5; - - let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); - let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); - - let mut messages: Vec = keypairs - .iter() - .map(|kp| kp.simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap()) - .collect(); - - messages[1].signature = - keypairs[1].secret.sign(Transcript::new(b"invalid"), &keypairs[1].public); - - let result = keypairs[0].simplpedpop_recipient_all(&messages); - - match result { - Ok(_) => panic!("Expected an error, but got Ok."), - Err(e) => match e { - DKGError::InvalidSignature(_) => assert!(true), - _ => panic!("Expected DKGError::InvalidSignature, but got {:?}", e), - }, - } - } - - #[test] - fn test_invalid_threshold() { - let keypair = Keypair::generate(); - let result = keypair.simplpedpop_contribute_all( - 1, - vec![ - PublicKey::from_point(RistrettoPoint::identity()), - PublicKey::from_point(RistrettoPoint::identity()), - ], - ); - - match result { - Ok(_) => panic!("Expected an error, but got Ok."), - Err(e) => match e { - DKGError::InsufficientThreshold => assert!(true), - _ => panic!("Expected DKGError::InsufficientThreshold, but got {:?}", e), - }, - } - } - - #[test] - fn test_invalid_participants() { - let keypair = Keypair::generate(); - let result = keypair.simplpedpop_contribute_all( - 2, - vec![PublicKey::from_point(RistrettoPoint::identity())], - ); - - match result { - Ok(_) => panic!("Expected an error, but got Ok."), - Err(e) => match e { - DKGError::InvalidNumberOfParticipants => assert!(true), - _ => { - panic!("Expected DKGError::InvalidNumberOfParticipants, but got {:?}", e) - }, - }, - } - } - - #[test] - fn test_threshold_greater_than_participants() { - let keypair = Keypair::generate(); - let result = keypair.simplpedpop_contribute_all( - 3, - vec![ - PublicKey::from_point(RistrettoPoint::identity()), - PublicKey::from_point(RistrettoPoint::identity()), - ], - ); - - match result { - Ok(_) => panic!("Expected an error, but got Ok."), - Err(e) => match e { - DKGError::ExcessiveThreshold => assert!(true), - _ => panic!("Expected DKGError::ExcessiveThreshold, but got {:?}", e), - }, - } - } - } -} From 1a9c02411004fcd6074fb4c1af354ee18b645c0e Mon Sep 17 00:00:00 2001 From: Fiono11 Date: Fri, 17 May 2024 15:57:54 +0100 Subject: [PATCH 42/60] Undo formatting --- benches/schnorr_benchmarks.rs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/benches/schnorr_benchmarks.rs b/benches/schnorr_benchmarks.rs index 32cd049..ef0d829 100644 --- a/benches/schnorr_benchmarks.rs +++ b/benches/schnorr_benchmarks.rs @@ -22,7 +22,9 @@ mod schnorr_benches { let msg: &[u8] = b""; let ctx = signing_context(b"this signature does this thing"); - c.bench_function("Schnorr signing", move |b| b.iter(|| keypair.sign(ctx.bytes(msg)))); + c.bench_function("Schnorr signing", move |b| { + b.iter(|| keypair.sign(ctx.bytes(msg))) + }); } fn verify(c: &mut Criterion) { @@ -45,8 +47,10 @@ mod schnorr_benches { let keypairs: Vec = (0..size).map(|_| Keypair::generate()).collect(); let msg: &[u8] = b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; let ctx = signing_context(b"this signature does this thing"); - let signatures: Vec = - keypairs.iter().map(|key| key.sign(ctx.bytes(msg))).collect(); + let signatures: Vec = keypairs + .iter() + .map(|key| key.sign(ctx.bytes(msg))) + .collect(); let public_keys: Vec = keypairs.iter().map(|key| key.public).collect(); b.iter(|| { let transcripts = ::std::iter::once(ctx.bytes(msg)).cycle().take(size); @@ -57,7 +61,9 @@ mod schnorr_benches { } fn key_generation(c: &mut Criterion) { - c.bench_function("Schnorr keypair generation", move |b| b.iter(|| Keypair::generate())); + c.bench_function("Schnorr keypair generation", move |b| { + b.iter(|| Keypair::generate()) + }); } criterion_group! { @@ -71,4 +77,6 @@ mod schnorr_benches { } } -criterion_main!(schnorr_benches::schnorr_benches,); +criterion_main!( + schnorr_benches::schnorr_benches, +); From 20d539fdbc4aff6aa148e905261ce12af6f10d5e Mon Sep 17 00:00:00 2001 From: Fiono11 Date: Fri, 17 May 2024 15:59:25 +0100 Subject: [PATCH 43/60] Undo formatting --- benches/schnorr_benchmarks.rs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/benches/schnorr_benchmarks.rs b/benches/schnorr_benchmarks.rs index 32cd049..ef0d829 100644 --- a/benches/schnorr_benchmarks.rs +++ b/benches/schnorr_benchmarks.rs @@ -22,7 +22,9 @@ mod schnorr_benches { let msg: &[u8] = b""; let ctx = signing_context(b"this signature does this thing"); - c.bench_function("Schnorr signing", move |b| b.iter(|| keypair.sign(ctx.bytes(msg)))); + c.bench_function("Schnorr signing", move |b| { + b.iter(|| keypair.sign(ctx.bytes(msg))) + }); } fn verify(c: &mut Criterion) { @@ -45,8 +47,10 @@ mod schnorr_benches { let keypairs: Vec = (0..size).map(|_| Keypair::generate()).collect(); let msg: &[u8] = b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; let ctx = signing_context(b"this signature does this thing"); - let signatures: Vec = - keypairs.iter().map(|key| key.sign(ctx.bytes(msg))).collect(); + let signatures: Vec = keypairs + .iter() + .map(|key| key.sign(ctx.bytes(msg))) + .collect(); let public_keys: Vec = keypairs.iter().map(|key| key.public).collect(); b.iter(|| { let transcripts = ::std::iter::once(ctx.bytes(msg)).cycle().take(size); @@ -57,7 +61,9 @@ mod schnorr_benches { } fn key_generation(c: &mut Criterion) { - c.bench_function("Schnorr keypair generation", move |b| b.iter(|| Keypair::generate())); + c.bench_function("Schnorr keypair generation", move |b| { + b.iter(|| Keypair::generate()) + }); } criterion_group! { @@ -71,4 +77,6 @@ mod schnorr_benches { } } -criterion_main!(schnorr_benches::schnorr_benches,); +criterion_main!( + schnorr_benches::schnorr_benches, +); From 1402b1e3fc727292160d42364a9015d6464342ab Mon Sep 17 00:00:00 2001 From: Fiono11 Date: Mon, 6 May 2024 11:33:35 +0100 Subject: [PATCH 44/60] Implementation of SimplPedPoP Improvements Add tests Fix tests Return keys from simplpedpop_recipient_all instead of points and scalars Replace pub(crate) with pub(super) Fix in ciphertexts loop Add wrapper types Implement SecretShare and EncryptedSecretShare types Implement SecretPolynomial and PolynomialCommitment types Restructuring of files Remove derive_key_from_scalar Remove ephemeral key Add Identifier type Add identifiers to dkg_output Remove proof of possession signature Improvements Fix deserialization of dkg output Remove unwrap Improve errors Add polynomial tests Reimplement ephemeral key and proof of possession Merge changes from final_frost branch to simplpedpop Undo formatting --- Cargo.toml | 33 +- benches/olaf_benchmarks.rs | 53 +++ src/lib.rs | 3 + src/olaf/mod.rs | 41 ++ src/olaf/simplpedpop/errors.rs | 340 +++++++++++++++ src/olaf/simplpedpop/mod.rs | 382 ++++++++++++++++ src/olaf/simplpedpop/types.rs | 777 +++++++++++++++++++++++++++++++++ 7 files changed, 1625 insertions(+), 4 deletions(-) create mode 100644 benches/olaf_benchmarks.rs create mode 100644 src/olaf/mod.rs create mode 100644 src/olaf/simplpedpop/errors.rs create mode 100644 src/olaf/simplpedpop/mod.rs create mode 100644 src/olaf/simplpedpop/types.rs diff --git a/Cargo.toml b/Cargo.toml index 9943958..7797028 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ curve25519-dalek = { version = "4.1.0", default-features = false, features = [ "zeroize", "precomputed-tables", "legacy_compatibility", + "rand_core", ] } subtle = { version = "2.4.1", default-features = false } merlin = { version = "3.0.0", default-features = false } @@ -32,7 +33,10 @@ 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"] } +zeroize = { version = "1.6", default-features = false, features = [ + "zeroize_derive", +] } +chacha20poly1305 = { version = "0.10.1", default-features = false } [dev-dependencies] rand = "0.8.5" @@ -47,17 +51,38 @@ serde_json = "1.0.68" name = "schnorr_benchmarks" harness = false +[[bench]] +name = "olaf_benchmarks" +required-features = ["alloc", "aead"] +harness = false + [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", +] asm = ["sha2/asm"] serde = ["serde_crate", "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 diff --git a/benches/olaf_benchmarks.rs b/benches/olaf_benchmarks.rs new file mode 100644 index 0000000..b178bee --- /dev/null +++ b/benches/olaf_benchmarks.rs @@ -0,0 +1,53 @@ +use criterion::criterion_main; + +mod olaf_benches { + use criterion::{criterion_group, BenchmarkId, Criterion}; + use schnorrkel::keys::{PublicKey, Keypair}; + use schnorrkel::olaf::simplpedpop::AllMessage; + + fn benchmark_simplpedpop(c: &mut Criterion) { + let mut group = c.benchmark_group("SimplPedPoP"); + + for &n in [3, 10, 100, 1000].iter() { + let participants = n; + let threshold = (n * 2 + 2) / 3; + + let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public).collect(); + + let mut all_messages: Vec = Vec::new(); + + for i in 0..participants { + let message = keypairs[i] + .simplpedpop_contribute_all(threshold as u16, public_keys.clone()) + .unwrap(); + all_messages.push(message); + } + + group.bench_function(BenchmarkId::new("round1", participants), |b| { + b.iter(|| { + keypairs[0] + .simplpedpop_contribute_all(threshold as u16, public_keys.clone()) + .unwrap(); + }) + }); + + group.bench_function(BenchmarkId::new("round2", participants), |b| { + b.iter(|| { + keypairs[0].simplpedpop_recipient_all(&all_messages).unwrap(); + }) + }); + } + + group.finish(); + } + + criterion_group! { + name = olaf_benches; + config = Criterion::default(); + targets = + benchmark_simplpedpop, + } +} + +criterion_main!(olaf_benches::olaf_benches); diff --git a/src/lib.rs b/src/lib.rs index a7e373b..5208751 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -248,6 +248,9 @@ pub mod derive; pub mod cert; pub mod errors; +#[cfg(all(feature = "alloc", feature = "aead"))] +pub mod olaf; + #[cfg(all(feature = "aead", feature = "getrandom"))] pub mod aead; diff --git a/src/olaf/mod.rs b/src/olaf/mod.rs new file mode 100644 index 0000000..00f310e --- /dev/null +++ b/src/olaf/mod.rs @@ -0,0 +1,41 @@ +//! Implementation of the Olaf protocol (), which is composed of the Distributed +//! Key Generation (DKG) protocol SimplPedPoP and the Threshold Signing protocol FROST. + +/// Implementation of the SimplPedPoP protocol. +pub mod simplpedpop; + +use curve25519_dalek::{constants::RISTRETTO_BASEPOINT_POINT, RistrettoPoint, Scalar}; +use merlin::Transcript; +use zeroize::ZeroizeOnDrop; +use crate::{context::SigningTranscript, Keypair, PublicKey}; + +pub(super) const MINIMUM_THRESHOLD: u16 = 2; +pub(super) const GENERATOR: RistrettoPoint = RISTRETTO_BASEPOINT_POINT; +pub(super) const COMPRESSED_RISTRETTO_LENGTH: usize = 32; +pub(crate) const SCALAR_LENGTH: usize = 32; + +/// The threshold public key generated in the SimplPedPoP protocol, used to validate the threshold signatures of the FROST protocol. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct ThresholdPublicKey(pub(crate) PublicKey); + +/// The verifying share of a participant generated in the SimplPedPoP protocol, used to verify its signatures shares in the FROST protocol. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct VerifyingShare(pub(crate) PublicKey); + +/// The signing keypair of a participant generated in the SimplPedPoP protocol, used to produce its signatures shares in the FROST protocol. +#[derive(Clone, Debug, ZeroizeOnDrop)] +pub struct SigningKeypair(pub(crate) Keypair); + +/// The identifier of a participant, which must be the same in the SimplPedPoP protocol and in the FROST protocol. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct Identifier(pub(crate) Scalar); + +impl Identifier { + pub(super) fn generate(recipients_hash: &[u8; 16], index: u16) -> Identifier { + let mut pos = Transcript::new(b"Identifier"); + pos.append_message(b"RecipientsHash", recipients_hash); + pos.append_message(b"i", &index.to_le_bytes()[..]); + + Identifier(pos.challenge_scalar(b"evaluation position")) + } +} diff --git a/src/olaf/simplpedpop/errors.rs b/src/olaf/simplpedpop/errors.rs new file mode 100644 index 0000000..7b29c6d --- /dev/null +++ b/src/olaf/simplpedpop/errors.rs @@ -0,0 +1,340 @@ +//! Errors of the SimplPedPoP protocol. + +use core::array::TryFromSliceError; +use crate::SignatureError; + +/// A result for the SimplPedPoP protocol. +pub type SPPResult = Result; + +/// An error ocurred during the execution of the SimplPedPoP protocol. +#[derive(Debug)] +pub enum SPPError { + /// Invalid parameters. + InvalidParameters, + /// Threshold cannot be greater than the number of participants. + ExcessiveThreshold, + /// Threshold must be at least 2. + InsufficientThreshold, + /// Number of participants is invalid. + InvalidNumberOfParticipants, + /// Invalid public key. + InvalidPublicKey(SignatureError), + /// Invalid threshold public key. + InvalidThresholdPublicKey, + /// Invalid signature. + InvalidSignature(SignatureError), + /// Invalid coefficient commitment of the polynomial commitment. + InvalidCoefficientCommitment, + /// Invalid identifier. + InvalidIdentifier, + /// Invalid secret share. + InvalidSecretShare, + /// Deserialization Error. + DeserializationError(TryFromSliceError), + /// The parameters of all messages must be equal. + DifferentParameters, + /// The recipients hash of all messages must be equal. + DifferentRecipientsHash, + /// The number of messages should be 2 at least, which the minimum number of participants. + InvalidNumberOfMessages, + /// The number of coefficient commitments of the polynomial commitment must be equal to the threshold - 1. + IncorrectNumberOfCoefficientCommitments, + /// The number of encrypted shares per message must be equal to the number of participants. + IncorrectNumberOfEncryptedShares, + /// Decryption error when decrypting an encrypted secret share. + DecryptionError(chacha20poly1305::Error), + /// Encryption error when encrypting the secret share. + EncryptionError(chacha20poly1305::Error), + /// Invalid Proof of Possession. + InvalidProofOfPossession(SignatureError), + /// The messages are empty. + EmptyMessages, +} + +#[cfg(test)] +mod tests { + use crate::olaf::simplpedpop::errors::SPPError; + use crate::olaf::simplpedpop::types::{ + AllMessage, EncryptedSecretShare, CHACHA20POLY1305_LENGTH, RECIPIENTS_HASH_LENGTH, + }; + use crate::olaf::simplpedpop::Parameters; + use crate::olaf::MINIMUM_THRESHOLD; + use crate::{Keypair, PublicKey}; + use alloc::vec::Vec; + use curve25519_dalek::ristretto::RistrettoPoint; + use curve25519_dalek::traits::Identity; + use merlin::Transcript; + use rand::Rng; + + const MAXIMUM_PARTICIPANTS: u16 = 10; + const MINIMUM_PARTICIPANTS: u16 = 2; + + fn generate_parameters() -> Parameters { + let mut rng = rand::thread_rng(); + let participants = rng.gen_range(MINIMUM_PARTICIPANTS..=MAXIMUM_PARTICIPANTS); + let threshold = rng.gen_range(MINIMUM_THRESHOLD..=participants); + + Parameters { participants, threshold } + } + + #[test] + fn test_invalid_number_of_messages() { + let parameters = generate_parameters(); + let participants = parameters.participants; + let threshold = parameters.threshold; + + let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); + + let mut messages: Vec = keypairs + .iter() + .map(|kp| kp.simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap()) + .collect(); + + messages.pop(); + + let result = keypairs[0].simplpedpop_recipient_all(&messages); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + SPPError::InvalidNumberOfMessages => assert!(true), + _ => { + panic!("Expected DKGError::InvalidNumberOfMessages, but got {:?}", e) + }, + }, + } + } + + #[test] + fn test_different_parameters() { + let parameters = generate_parameters(); + let participants = parameters.participants; + let threshold = parameters.threshold; + + let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); + + let mut messages: Vec = Vec::new(); + for i in 0..participants { + let message = keypairs[i as usize] + .simplpedpop_contribute_all(threshold, public_keys.clone()) + .unwrap(); + messages.push(message); + } + + messages[1].content.parameters.threshold += 1; + + let result = keypairs[0].simplpedpop_recipient_all(&messages); + + // Check if the result is an error + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + SPPError::DifferentParameters => assert!(true), + _ => panic!("Expected DKGError::DifferentParameters, but got {:?}", e), + }, + } + } + + #[test] + fn test_different_recipients_hash() { + let parameters = generate_parameters(); + let participants = parameters.participants; + let threshold = parameters.threshold; + + let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); + + let mut messages: Vec = keypairs + .iter() + .map(|kp| kp.simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap()) + .collect(); + + messages[1].content.recipients_hash = [1; RECIPIENTS_HASH_LENGTH]; + + let result = keypairs[0].simplpedpop_recipient_all(&messages); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + SPPError::DifferentRecipientsHash => assert!(true), + _ => { + panic!("Expected DKGError::DifferentRecipientsHash, but got {:?}", e) + }, + }, + } + } + + #[test] + fn test_incorrect_number_of_commitments() { + let parameters = generate_parameters(); + let participants = parameters.participants; + let threshold = parameters.threshold; + + let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); + + let mut messages: Vec = keypairs + .iter() + .map(|kp| kp.simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap()) + .collect(); + + messages[1].content.polynomial_commitment.coefficients_commitments.pop(); + + let result = keypairs[0].simplpedpop_recipient_all(&messages); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + SPPError::IncorrectNumberOfCoefficientCommitments => assert!(true), + _ => panic!( + "Expected DKGError::IncorrectNumberOfCoefficientCommitments, but got {:?}", + e + ), + }, + } + } + + #[test] + fn test_incorrect_number_of_encrypted_shares() { + let parameters = generate_parameters(); + let participants = parameters.participants; + let threshold = parameters.threshold; + + let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); + + let mut messages: Vec = keypairs + .iter() + .map(|kp| kp.simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap()) + .collect(); + + messages[1].content.encrypted_secret_shares.pop(); + + let result = keypairs[0].simplpedpop_recipient_all(&messages); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + SPPError::IncorrectNumberOfEncryptedShares => assert!(true), + _ => panic!("Expected DKGError::IncorrectNumberOfEncryptedShares, but got {:?}", e), + }, + } + } + + #[test] + fn test_invalid_secret_share() { + let parameters = generate_parameters(); + let participants = parameters.participants; + let threshold = parameters.threshold; + + let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); + + let mut messages: Vec = keypairs + .iter() + .map(|kp| kp.simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap()) + .collect(); + + messages[1].content.encrypted_secret_shares[0] = + EncryptedSecretShare(vec![1; CHACHA20POLY1305_LENGTH]); + + let result = keypairs[0].simplpedpop_recipient_all(&messages); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + SPPError::InvalidSecretShare => assert!(true), + _ => panic!("Expected DKGError::InvalidSecretShare, but got {:?}", e), + }, + } + } + + #[test] + fn test_invalid_signature() { + let parameters = generate_parameters(); + let participants = parameters.participants; + let threshold = parameters.threshold; + + let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); + + let mut messages: Vec = keypairs + .iter() + .map(|kp| kp.simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap()) + .collect(); + + messages[1].signature = + keypairs[1].secret.sign(Transcript::new(b"invalid"), &keypairs[1].public); + + let result = keypairs[0].simplpedpop_recipient_all(&messages); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + SPPError::InvalidSignature(_) => assert!(true), + _ => panic!("Expected DKGError::InvalidSignature, but got {:?}", e), + }, + } + } + + #[test] + fn test_invalid_threshold() { + let keypair = Keypair::generate(); + let result = keypair.simplpedpop_contribute_all( + 1, + vec![ + PublicKey::from_point(RistrettoPoint::identity()), + PublicKey::from_point(RistrettoPoint::identity()), + ], + ); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + SPPError::InsufficientThreshold => assert!(true), + _ => panic!("Expected SPPError::InsufficientThreshold, but got {:?}", e), + }, + } + } + + #[test] + fn test_invalid_participants() { + let keypair = Keypair::generate(); + let result = keypair + .simplpedpop_contribute_all(2, vec![PublicKey::from_point(RistrettoPoint::identity())]); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + SPPError::InvalidNumberOfParticipants => { + assert!(true) + }, + _ => { + panic!("Expected SPPError::InvalidNumberOfParticipants, but got {:?}", e) + }, + }, + } + } + + #[test] + fn test_threshold_greater_than_participants() { + let keypair = Keypair::generate(); + let result = keypair.simplpedpop_contribute_all( + 3, + vec![ + PublicKey::from_point(RistrettoPoint::identity()), + PublicKey::from_point(RistrettoPoint::identity()), + ], + ); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + SPPError::ExcessiveThreshold => assert!(true), + _ => panic!("Expected SPPError::ExcessiveThreshold), but got {:?}", e), + }, + } + } +} diff --git a/src/olaf/simplpedpop/mod.rs b/src/olaf/simplpedpop/mod.rs new file mode 100644 index 0000000..fcbc032 --- /dev/null +++ b/src/olaf/simplpedpop/mod.rs @@ -0,0 +1,382 @@ +//! Implementation of the SimplPedPoP protocol (), a spp based on PedPoP, which in turn is based +//! on Pedersen's spp. All of them have as the fundamental building block the Shamir's Secret Sharing scheme. + +mod types; +pub mod errors; + +pub use self::types::{AllMessage, SPPOutputMessage, SPPOutput}; +pub(crate) use self::types::{PolynomialCommitment, MessageContent, Parameters}; +use alloc::vec::Vec; +use curve25519_dalek::{traits::Identity, RistrettoPoint, Scalar}; +use merlin::Transcript; +use rand_core::RngCore; +use crate::{ + context::SigningTranscript, verify_batch, Keypair, PublicKey, SecretKey, getrandom_or_panic, +}; +use self::{ + errors::{SPPError, SPPResult}, + types::{ + SecretPolynomial, SecretShare, CHACHA20POLY1305_KEY_LENGTH, ENCRYPTION_NONCE_LENGTH, + RECIPIENTS_HASH_LENGTH, + }, +}; +use super::{ThresholdPublicKey, Identifier, SigningKeypair, VerifyingShare, GENERATOR}; + +impl Keypair { + /// First round of the SimplPedPoP protocol. + /// + /// We do not recipients.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 recipients, not the + /// full recipients list. + pub fn simplpedpop_contribute_all( + &self, + threshold: u16, + recipients: Vec, + ) -> SPPResult { + let parameters = Parameters::generate(recipients.len() as u16, threshold); + parameters.validate()?; + + let mut rng = getrandom_or_panic(); + + let mut recipients_transcript = Transcript::new(b"RecipientsHash"); + parameters.commit(&mut recipients_transcript); + + for recipient in &recipients { + recipients_transcript.commit_point(b"recipient", recipient.as_compressed()); + } + + let mut recipients_hash = [0u8; RECIPIENTS_HASH_LENGTH]; + recipients_transcript.challenge_bytes(b"finalize", &mut recipients_hash); + + let secret_polynomial = + SecretPolynomial::generate(parameters.threshold as usize - 1, &mut rng); + + let mut encrypted_secret_shares = Vec::new(); + + let polynomial_commitment = secret_polynomial.commit(); + + let mut encryption_transcript = merlin::Transcript::new(b"Encryption"); + parameters.commit(&mut encryption_transcript); + encryption_transcript.commit_point(b"contributor", self.public.as_compressed()); + + let mut encryption_nonce = [0u8; ENCRYPTION_NONCE_LENGTH]; + rng.fill_bytes(&mut encryption_nonce); + encryption_transcript.append_message(b"nonce", &encryption_nonce); + + let ephemeral_key = Keypair::generate(); + + for i in 0..parameters.participants { + let identifier = Identifier::generate(&recipients_hash, i); + + let polynomial_evaluation = secret_polynomial.evaluate(&identifier.0); + + let secret_share = SecretShare(polynomial_evaluation); + + let recipient = recipients[i as usize]; + + let key_exchange = ephemeral_key.secret.key * recipient.into_point(); + + let mut encryption_transcript = encryption_transcript.clone(); + encryption_transcript.commit_point(b"recipient", recipient.as_compressed()); + encryption_transcript.commit_point(b"key exchange", &key_exchange.compress()); + encryption_transcript.append_message(b"i", &(i as usize).to_le_bytes()); + + let mut key_bytes = [0; CHACHA20POLY1305_KEY_LENGTH]; + encryption_transcript.challenge_bytes(b"key", &mut key_bytes); + + let encrypted_secret_share = secret_share.encrypt(&key_bytes, &encryption_nonce)?; + + encrypted_secret_shares.push(encrypted_secret_share); + } + + let pk = &PublicKey::from_point( + *polynomial_commitment + .coefficients_commitments + .first() + .expect("This never fails because the minimum threshold is 2"), + ); + + let secret = *secret_polynomial + .coefficients + .first() + .expect("This never fails because the minimum threshold is 2"); + + let mut nonce: [u8; 32] = [0u8; 32]; + crate::getrandom_or_panic().fill_bytes(&mut nonce); + + let secret_key = SecretKey { key: secret, nonce }; + + let secret_commitment = polynomial_commitment + .coefficients_commitments + .first() + .expect("This never fails because the minimum threshold is 2"); + + let mut pop_transcript = Transcript::new(b"pop"); + pop_transcript + .append_message(b"secret commitment", secret_commitment.compress().as_bytes()); + let proof_of_possession = secret_key.sign(pop_transcript, pk); + + let message_content = MessageContent::new( + self.public, + encryption_nonce, + parameters, + recipients_hash, + polynomial_commitment, + encrypted_secret_shares, + ephemeral_key.public, + proof_of_possession, + ); + + let mut signature_transcript = Transcript::new(b"signature"); + signature_transcript.append_message(b"message", &message_content.to_bytes()); + let signature = self.sign(signature_transcript); + + Ok(AllMessage::new(message_content, signature)) + } + + /// Second round of the SimplPedPoP protocol. + pub fn simplpedpop_recipient_all( + &self, + messages: &[AllMessage], + ) -> SPPResult<(SPPOutputMessage, SigningKeypair)> { + if messages.is_empty() { + return Err(SPPError::EmptyMessages); + } + + let first_message = &messages[0]; + let parameters = &first_message.content.parameters; + let threshold = parameters.threshold as usize; + let participants = parameters.participants as usize; + + first_message.content.parameters.validate()?; + + if messages.len() < participants { + return Err(SPPError::InvalidNumberOfMessages); + } + + let mut secret_shares = Vec::with_capacity(participants); + let mut verifying_keys = Vec::with_capacity(participants); + let mut senders = Vec::with_capacity(participants); + let mut signatures = Vec::with_capacity(participants); + let mut signatures_transcripts = Vec::with_capacity(participants); + let mut group_point = RistrettoPoint::identity(); + let mut total_secret_share = Scalar::ZERO; + let mut total_polynomial_commitment = + PolynomialCommitment { coefficients_commitments: vec![] }; + let mut identifiers = Vec::new(); + let mut public_keys = Vec::with_capacity(participants); + let mut proofs_of_possession = Vec::with_capacity(participants); + let mut pops_transcripts = Vec::with_capacity(participants); + + for (j, message) in messages.iter().enumerate() { + if &message.content.parameters != parameters { + return Err(SPPError::DifferentParameters); + } + if message.content.recipients_hash != first_message.content.recipients_hash { + return Err(SPPError::DifferentRecipientsHash); + } + + let content = &message.content; + let polynomial_commitment = &content.polynomial_commitment; + let encrypted_secret_shares = &content.encrypted_secret_shares; + + let public_key = PublicKey::from_point( + *polynomial_commitment + .coefficients_commitments + .first() + .expect("This never fails because the minimum threshold is 2"), + ); + public_keys.push(public_key); + proofs_of_possession.push(content.proof_of_possession); + + senders.push(content.sender); + signatures.push(message.signature); + + let mut encryption_transcript = Transcript::new(b"Encryption"); + parameters.commit(&mut encryption_transcript); + encryption_transcript.commit_point(b"contributor", content.sender.as_compressed()); + encryption_transcript.append_message(b"nonce", &content.encryption_nonce); + + if polynomial_commitment.coefficients_commitments.len() != threshold { + return Err(SPPError::IncorrectNumberOfCoefficientCommitments); + } + + if encrypted_secret_shares.len() != participants { + return Err(SPPError::IncorrectNumberOfEncryptedShares); + } + + let mut signature_transcript = Transcript::new(b"signature"); + signature_transcript.append_message(b"message", &content.to_bytes()); + signatures_transcripts.push(signature_transcript); + + let mut pop_transcript = Transcript::new(b"pop"); + + let secret_commitment = polynomial_commitment + .coefficients_commitments + .first() + .expect("This never fails because the minimum threshold is 2"); + + pop_transcript + .append_message(b"secret commitment", secret_commitment.compress().as_bytes()); + + pops_transcripts.push(pop_transcript); + + total_polynomial_commitment = PolynomialCommitment::sum_polynomial_commitments(&[ + &total_polynomial_commitment, + &polynomial_commitment, + ]); + + let key_exchange = self.secret.key * message.content.ephemeral_key.as_point(); + + encryption_transcript.commit_point(b"recipient", self.public.as_compressed()); + encryption_transcript.commit_point(b"key exchange", &key_exchange.compress()); + + let mut secret_share_found = false; + + for (i, encrypted_secret_share) in encrypted_secret_shares.iter().enumerate() { + let mut encryption_transcript = encryption_transcript.clone(); + + encryption_transcript.append_message(b"i", &i.to_le_bytes()); + + let mut key_bytes = [0; CHACHA20POLY1305_KEY_LENGTH]; + encryption_transcript.challenge_bytes(b"key", &mut key_bytes); + + if identifiers.len() != participants { + let identifier = + Identifier::generate(&first_message.content.recipients_hash, i as u16); + identifiers.push(identifier); + } + + if !secret_share_found { + if let Ok(secret_share) = SecretShare::decrypt( + encrypted_secret_share, + &key_bytes, + &content.encryption_nonce, + ) { + if secret_share.0 * GENERATOR + == polynomial_commitment.evaluate(&identifiers[i].0) + { + secret_shares.push(secret_share); + secret_share_found = true; + } + } + } + } + + total_secret_share += secret_shares.get(j).ok_or(SPPError::InvalidSecretShare)?.0; + group_point += secret_commitment; + } + + verify_batch(&mut pops_transcripts, &proofs_of_possession, &public_keys, false) + .map_err(SPPError::InvalidProofOfPossession)?; + + verify_batch(&mut signatures_transcripts, &signatures, &senders, false) + .map_err(SPPError::InvalidSignature)?; + + for id in &identifiers { + let evaluation = total_polynomial_commitment.evaluate(&id.0); + verifying_keys.push((*id, VerifyingShare(PublicKey::from_point(evaluation)))); + } + + let spp_output = SPPOutput::new( + parameters, + ThresholdPublicKey(PublicKey::from_point(group_point)), + verifying_keys, + ); + + let mut spp_output_transcript = Transcript::new(b"spp output"); + spp_output_transcript.append_message(b"message", &spp_output.to_bytes()); + + let signature = self.sign(spp_output_transcript); + let spp_output = SPPOutputMessage::new(VerifyingShare(self.public), spp_output, signature); + + let mut nonce: [u8; 32] = [0u8; 32]; + getrandom_or_panic().fill_bytes(&mut nonce); + + let secret_key = SecretKey { key: total_secret_share, nonce }; + + let keypair = Keypair::from(secret_key); + + Ok((spp_output, SigningKeypair(keypair))) + } +} + +#[cfg(test)] +mod tests { + use crate::olaf::simplpedpop::types::{AllMessage, Parameters}; + use crate::olaf::MINIMUM_THRESHOLD; + use crate::{Keypair, PublicKey}; + use alloc::vec::Vec; + use rand::Rng; + + const MAXIMUM_PARTICIPANTS: u16 = 10; + const MINIMUM_PARTICIPANTS: u16 = 2; + const PROTOCOL_RUNS: usize = 1; + + fn generate_parameters() -> Parameters { + let mut rng = rand::thread_rng(); + let participants = rng.gen_range(MINIMUM_PARTICIPANTS..=MAXIMUM_PARTICIPANTS); + let threshold = rng.gen_range(MINIMUM_THRESHOLD..=participants); + + Parameters { participants, threshold } + } + + #[test] + fn test_simplpedpop_protocol() { + for _ in 0..PROTOCOL_RUNS { + let parameters = generate_parameters(); + let participants = parameters.participants as usize; + let threshold = parameters.threshold as usize; + + let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public).collect(); + + let mut all_messages = Vec::new(); + for i in 0..participants { + let message: AllMessage = keypairs[i] + .simplpedpop_contribute_all(threshold as u16, public_keys.clone()) + .unwrap(); + all_messages.push(message); + } + + let mut spp_outputs = Vec::new(); + + for kp in keypairs.iter() { + let spp_output = kp.simplpedpop_recipient_all(&all_messages).unwrap(); + spp_output.0.verify_signature().unwrap(); + spp_outputs.push(spp_output); + } + + // Verify that all threshold_public_keys and verifying_keys are equal + assert!( + spp_outputs.windows(2).all(|w| w[0].0.spp_output.threshold_public_key.0 + == w[1].0.spp_output.threshold_public_key.0 + && w[0].0.spp_output.verifying_keys.len() + == w[1].0.spp_output.verifying_keys.len() + && w[0] + .0 + .spp_output + .verifying_keys + .iter() + .zip(w[1].0.spp_output.verifying_keys.iter()) + .all(|((a, b), (c, d))| a.0 == c.0 && b.0 == d.0)), + "All spp outputs should have identical group public keys and verifying keys." + ); + + // Verify that all verifying_shares are valid + for i in 0..participants { + for j in 0..participants { + assert_eq!( + spp_outputs[i].0.spp_output.verifying_keys[j].1 .0, + (spp_outputs[j].1 .0.public), + "Verification of total secret shares failed!" + ); + } + } + } + } +} diff --git a/src/olaf/simplpedpop/types.rs b/src/olaf/simplpedpop/types.rs new file mode 100644 index 0000000..806c906 --- /dev/null +++ b/src/olaf/simplpedpop/types.rs @@ -0,0 +1,777 @@ +//! Types of the SimplPedPoP protocol. + +#![allow(clippy::too_many_arguments)] + +use core::iter; +use alloc::vec::Vec; +use merlin::Transcript; +use rand_core::{CryptoRng, RngCore}; +use zeroize::ZeroizeOnDrop; +use aead::KeyInit; +use chacha20poly1305::{aead::Aead, ChaCha20Poly1305, Nonce}; +use curve25519_dalek::{ristretto::CompressedRistretto, traits::Identity, RistrettoPoint, Scalar}; +use crate::{ + context::SigningTranscript, + olaf::{ + Identifier, ThresholdPublicKey, VerifyingShare, COMPRESSED_RISTRETTO_LENGTH, GENERATOR, + MINIMUM_THRESHOLD, SCALAR_LENGTH, + }, + scalar_from_canonical_bytes, PublicKey, Signature, PUBLIC_KEY_LENGTH, SIGNATURE_LENGTH, +}; +use super::errors::{SPPError, SPPResult}; + +pub(super) const U16_LENGTH: usize = 2; +pub(super) const ENCRYPTION_NONCE_LENGTH: usize = 12; +pub(super) const RECIPIENTS_HASH_LENGTH: usize = 16; +pub(super) const CHACHA20POLY1305_LENGTH: usize = 64; +pub(super) const CHACHA20POLY1305_KEY_LENGTH: usize = 32; +pub(super) const VEC_LENGTH: usize = 2; + +#[derive(ZeroizeOnDrop)] +pub(super) struct SecretShare(pub(super) Scalar); + +impl SecretShare { + pub(super) fn encrypt( + &self, + key: &[u8; CHACHA20POLY1305_KEY_LENGTH], + nonce: &[u8; ENCRYPTION_NONCE_LENGTH], + ) -> SPPResult { + let cipher = ChaCha20Poly1305::new(&(*key).into()); + + let nonce = Nonce::from_slice(&nonce[..]); + + let ciphertext: Vec = cipher + .encrypt(nonce, &self.0.to_bytes()[..]) + .map_err(SPPError::EncryptionError)?; + + Ok(EncryptedSecretShare(ciphertext)) + } + + pub(super) fn decrypt( + encrypted_secret_share: &EncryptedSecretShare, + key: &[u8; CHACHA20POLY1305_KEY_LENGTH], + nonce: &[u8; ENCRYPTION_NONCE_LENGTH], + ) -> SPPResult { + let cipher = ChaCha20Poly1305::new(&(*key).into()); + + let nonce = Nonce::from_slice(&nonce[..]); + + let plaintext = cipher + .decrypt(nonce, &encrypted_secret_share.0[..]) + .map_err(SPPError::DecryptionError)?; + + let mut bytes = [0; 32]; + bytes.copy_from_slice(&plaintext); + + Ok(SecretShare(Scalar::from_bytes_mod_order(bytes))) + } +} + +/// The secret polynomial of a participant chosen at randoma nd used to generate the secret shares of all the participants (including itself). +#[derive(ZeroizeOnDrop)] +pub(super) struct SecretPolynomial { + pub(super) coefficients: Vec, +} + +impl SecretPolynomial { + pub(super) fn generate(degree: usize, rng: &mut R) -> Self { + let mut coefficients = Vec::with_capacity(degree + 1); + + let mut first = Scalar::random(rng); + while first == Scalar::ZERO { + first = Scalar::random(rng); + } + + coefficients.push(first); + coefficients.extend(iter::repeat_with(|| Scalar::random(rng)).take(degree)); + + SecretPolynomial { coefficients } + } + + pub(super) fn evaluate(&self, x: &Scalar) -> Scalar { + let mut value = + *self.coefficients.last().expect("coefficients must have at least one element"); + + for coeff in self.coefficients.iter().rev().skip(1) { + value = value * x + coeff; + } + + value + } + + pub(super) fn commit(&self) -> PolynomialCommitment { + let coefficients_commitments = + self.coefficients.iter().map(|coefficient| GENERATOR * coefficient).collect(); + + PolynomialCommitment { coefficients_commitments } + } +} + +/// The parameters of a given execution of the SimplPedPoP protocol. +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct Parameters { + pub(crate) participants: u16, + pub(crate) threshold: u16, +} + +impl Parameters { + /// Create new parameters. + pub(crate) fn generate(participants: u16, threshold: u16) -> Parameters { + Parameters { participants, threshold } + } + + pub(super) fn validate(&self) -> Result<(), SPPError> { + if self.threshold < MINIMUM_THRESHOLD { + return Err(SPPError::InsufficientThreshold); + } + + if self.participants < MINIMUM_THRESHOLD { + return Err(SPPError::InvalidNumberOfParticipants); + } + + if self.threshold > self.participants { + return Err(SPPError::ExcessiveThreshold); + } + + Ok(()) + } + + pub(super) 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()); + } + + /// Serializes `Parameters` into a byte array. + pub(super) fn to_bytes(&self) -> [u8; U16_LENGTH * 2] { + let mut bytes = [0u8; U16_LENGTH * 2]; + bytes[0..U16_LENGTH].copy_from_slice(&self.participants.to_le_bytes()); + bytes[U16_LENGTH..U16_LENGTH * 2].copy_from_slice(&self.threshold.to_le_bytes()); + bytes + } + + /// Constructs `Parameters` from a byte array. + pub(super) fn from_bytes(bytes: &[u8]) -> SPPResult { + if bytes.len() != U16_LENGTH * 2 { + return Err(SPPError::InvalidParameters); + } + + let participants = u16::from_le_bytes([bytes[0], bytes[1]]); + let threshold = u16::from_le_bytes([bytes[2], bytes[3]]); + + Ok(Parameters { participants, threshold }) + } +} + +/// The polynomial commitment of a participant, used to verify the secret shares without revealing the polynomial. +pub struct PolynomialCommitment { + pub(super) coefficients_commitments: Vec, +} + +impl PolynomialCommitment { + pub(super) fn evaluate(&self, identifier: &Scalar) -> RistrettoPoint { + 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(super) 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 } + } +} + +#[derive(Clone)] +pub struct EncryptedSecretShare(pub(super) Vec); + +impl EncryptedSecretShare {} + +/// AllMessage packs together messages for all participants. +/// +/// We'd save bandwidth by having separate messages for each +/// participant, but typical thresholds lie between 1/2 and 2/3, +/// so this doubles or tripples bandwidth usage. +pub struct AllMessage { + pub(super) content: MessageContent, + pub(super) signature: Signature, +} + +impl AllMessage { + /// Creates a new message. + pub(crate) fn new(content: MessageContent, signature: Signature) -> Self { + Self { content, signature } + } + /// Serialize AllMessage + pub fn to_bytes(&self) -> Vec { + let mut bytes = Vec::new(); + + bytes.extend(self.content.to_bytes()); + bytes.extend(self.signature.to_bytes()); + + bytes + } + + /// Deserialize AllMessage from bytes + pub fn from_bytes(bytes: &[u8]) -> Result { + let mut cursor = 0; + + let content = MessageContent::from_bytes(&bytes[cursor..])?; + cursor += content.to_bytes().len(); + + let signature = Signature::from_bytes(&bytes[cursor..cursor + SIGNATURE_LENGTH]) + .map_err(SPPError::InvalidSignature)?; + + Ok(AllMessage { content, signature }) + } +} + +/// The contents of the message destined to all participants. +pub struct MessageContent { + pub(super) sender: PublicKey, + pub(super) encryption_nonce: [u8; ENCRYPTION_NONCE_LENGTH], + pub(super) parameters: Parameters, + pub(super) recipients_hash: [u8; RECIPIENTS_HASH_LENGTH], + pub(super) polynomial_commitment: PolynomialCommitment, + pub(super) encrypted_secret_shares: Vec, + pub(super) ephemeral_key: PublicKey, + pub(super) proof_of_possession: Signature, +} + +impl MessageContent { + /// Creates the content of the message. + pub(super) fn new( + sender: PublicKey, + encryption_nonce: [u8; ENCRYPTION_NONCE_LENGTH], + parameters: Parameters, + recipients_hash: [u8; RECIPIENTS_HASH_LENGTH], + polynomial_commitment: PolynomialCommitment, + encrypted_secret_shares: Vec, + ephemeral_key: PublicKey, + proof_of_possession: Signature, + ) -> Self { + Self { + sender, + encryption_nonce, + parameters, + recipients_hash, + polynomial_commitment, + encrypted_secret_shares, + ephemeral_key, + proof_of_possession, + } + } + + /// Serialize MessageContent into bytes. + pub(super) fn to_bytes(&self) -> Vec { + let mut bytes = Vec::new(); + + bytes.extend(self.sender.to_bytes()); + bytes.extend(&self.encryption_nonce); + bytes.extend(self.parameters.participants.to_le_bytes()); + bytes.extend(self.parameters.threshold.to_le_bytes()); + bytes.extend(&self.recipients_hash); + + for point in &self.polynomial_commitment.coefficients_commitments { + bytes.extend(point.compress().to_bytes()); + } + + for ciphertext in &self.encrypted_secret_shares { + bytes.extend(ciphertext.0.clone()); + } + + bytes.extend(&self.ephemeral_key.to_bytes()); + bytes.extend(&self.proof_of_possession.to_bytes()); + + bytes + } + + /// Deserialize MessageContent from bytes. + pub(super) fn from_bytes(bytes: &[u8]) -> Result { + let mut cursor = 0; + + let sender = PublicKey::from_bytes(&bytes[cursor..cursor + PUBLIC_KEY_LENGTH]) + .map_err(SPPError::InvalidPublicKey)?; + cursor += PUBLIC_KEY_LENGTH; + + let encryption_nonce: [u8; ENCRYPTION_NONCE_LENGTH] = bytes + [cursor..cursor + ENCRYPTION_NONCE_LENGTH] + .try_into() + .map_err(SPPError::DeserializationError)?; + cursor += ENCRYPTION_NONCE_LENGTH; + + let parameters = Parameters::from_bytes(&bytes[cursor..cursor + U16_LENGTH * 2])?; + cursor += U16_LENGTH * 2; + + let participants = parameters.participants; + + let recipients_hash: [u8; RECIPIENTS_HASH_LENGTH] = bytes + [cursor..cursor + RECIPIENTS_HASH_LENGTH] + .try_into() + .map_err(SPPError::DeserializationError)?; + cursor += RECIPIENTS_HASH_LENGTH; + + let mut coefficients_commitments = Vec::with_capacity(participants as usize); + + for _ in 0..participants { + let point = CompressedRistretto::from_slice( + &bytes[cursor..cursor + COMPRESSED_RISTRETTO_LENGTH], + ) + .map_err(SPPError::DeserializationError)?; + + coefficients_commitments + .push(point.decompress().ok_or(SPPError::InvalidCoefficientCommitment)?); + + cursor += COMPRESSED_RISTRETTO_LENGTH; + } + + let polynomial_commitment = PolynomialCommitment { coefficients_commitments }; + + let mut encrypted_secret_shares = Vec::new(); + + for _ in 0..participants { + let ciphertext = bytes[cursor..cursor + CHACHA20POLY1305_LENGTH].to_vec(); + encrypted_secret_shares.push(EncryptedSecretShare(ciphertext)); + cursor += CHACHA20POLY1305_LENGTH; + } + + let ephemeral_key = PublicKey::from_bytes(&bytes[cursor..cursor + PUBLIC_KEY_LENGTH]) + .map_err(SPPError::InvalidPublicKey)?; + cursor += PUBLIC_KEY_LENGTH; + + let proof_of_possession = Signature::from_bytes(&bytes[cursor..cursor + SIGNATURE_LENGTH]) + .map_err(SPPError::InvalidSignature)?; + + Ok(MessageContent { + sender, + encryption_nonce, + parameters, + recipients_hash, + polynomial_commitment, + encrypted_secret_shares, + ephemeral_key, + proof_of_possession, + }) + } +} + +/// The signed output of the SimplPedPoP protocol. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct SPPOutputMessage { + pub(crate) signer: VerifyingShare, + pub(crate) spp_output: SPPOutput, + pub(crate) signature: Signature, +} + +impl SPPOutputMessage { + pub(crate) fn new(signer: VerifyingShare, content: SPPOutput, signature: Signature) -> Self { + Self { signer, signature, spp_output: content } + } + + /// Serializes the SPPOutputMessage into bytes. + pub fn to_bytes(&self) -> Vec { + let mut bytes = Vec::new(); + + let pk_bytes = self.signer.0.to_bytes(); + bytes.extend(pk_bytes); + + let content_bytes = self.spp_output.to_bytes(); + bytes.extend(content_bytes); + + let signature_bytes = self.signature.to_bytes(); + bytes.extend(signature_bytes); + + bytes + } + + /// Deserializes the SPPOutputMessage from bytes. + pub fn from_bytes(bytes: &[u8]) -> Result { + let mut cursor = 0; + + let pk_bytes = &bytes[..PUBLIC_KEY_LENGTH]; + let signer = + VerifyingShare(PublicKey::from_bytes(pk_bytes).map_err(SPPError::InvalidPublicKey)?); + cursor += PUBLIC_KEY_LENGTH; + + let content_bytes = &bytes[cursor..bytes.len() - SIGNATURE_LENGTH]; + let spp_output = SPPOutput::from_bytes(content_bytes)?; + + cursor = bytes.len() - SIGNATURE_LENGTH; + let signature = Signature::from_bytes(&bytes[cursor..cursor + SIGNATURE_LENGTH]) + .map_err(SPPError::InvalidSignature)?; + + Ok(SPPOutputMessage { signer, spp_output, signature }) + } + + /// Returns the output of the SimplPedPoP protocol. + pub fn spp_output(&self) -> SPPOutput { + self.spp_output.clone() + } + + /// Verifies the signature of the message. + pub fn verify_signature(&self) -> SPPResult<()> { + let mut spp_output_transcript = Transcript::new(b"spp output"); + spp_output_transcript.append_message(b"message", &self.spp_output.to_bytes()); + + self.signer + .0 + .verify(spp_output_transcript, &self.signature) + .map_err(SPPError::InvalidSignature) + } +} + +/// The content of the signed output of the SimplPedPoP protocol. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct SPPOutput { + pub(crate) parameters: Parameters, + pub(crate) threshold_public_key: ThresholdPublicKey, + pub(crate) verifying_keys: Vec<(Identifier, VerifyingShare)>, +} + +impl SPPOutput { + pub(crate) fn new( + parameters: &Parameters, + threshold_public_key: ThresholdPublicKey, + verifying_keys: Vec<(Identifier, VerifyingShare)>, + ) -> Self { + let parameters = Parameters::generate(parameters.participants, parameters.threshold); + + Self { threshold_public_key, verifying_keys, parameters } + } + + pub(crate) fn to_bytes(&self) -> Vec { + let mut bytes = Vec::new(); + + bytes.extend(self.parameters.to_bytes()); + + let compressed_public_key = self.threshold_public_key.0.as_compressed(); + bytes.extend(compressed_public_key.to_bytes().iter()); + + let key_count = self.verifying_keys.len() as u16; + bytes.extend(key_count.to_le_bytes()); + + for (id, key) in &self.verifying_keys { + bytes.extend(id.0.to_bytes()); + bytes.extend(key.0.to_bytes()); + } + + bytes + } + + /// Deserializes the SPPOutput from bytes. + pub(crate) fn from_bytes(bytes: &[u8]) -> Result { + let mut cursor = 0; + + let parameters = Parameters::from_bytes(&bytes[cursor..cursor + U16_LENGTH * 2])?; + cursor += U16_LENGTH * 2; + + let public_key_bytes = &bytes[cursor..cursor + PUBLIC_KEY_LENGTH]; + cursor += PUBLIC_KEY_LENGTH; + + let compressed_public_key = CompressedRistretto::from_slice(public_key_bytes) + .map_err(SPPError::DeserializationError)?; + + let threshold_public_key = + compressed_public_key.decompress().ok_or(SPPError::InvalidThresholdPublicKey)?; + + let mut verifying_keys = Vec::new(); + + cursor += VEC_LENGTH; + + while cursor < bytes.len() { + let mut identifier_bytes = [0; SCALAR_LENGTH]; + identifier_bytes.copy_from_slice(&bytes[cursor..cursor + SCALAR_LENGTH]); + let identifier = + scalar_from_canonical_bytes(identifier_bytes).ok_or(SPPError::InvalidIdentifier)?; + cursor += SCALAR_LENGTH; + + let key_bytes = &bytes[cursor..cursor + PUBLIC_KEY_LENGTH]; + cursor += PUBLIC_KEY_LENGTH; + let key = PublicKey::from_bytes(key_bytes).map_err(SPPError::InvalidPublicKey)?; + verifying_keys.push((Identifier(identifier), VerifyingShare(key))); + } + + Ok(SPPOutput { + threshold_public_key: ThresholdPublicKey(PublicKey::from_point(threshold_public_key)), + verifying_keys, + parameters, + }) + } + + /// Returns the threshold public key. + pub fn threshold_public_key(&self) -> ThresholdPublicKey { + self.threshold_public_key + } +} + +#[cfg(test)] +mod tests { + use merlin::Transcript; + use rand_core::OsRng; + use crate::{context::SigningTranscript, Keypair}; + use super::*; + use curve25519_dalek::RistrettoPoint; + + #[test] + fn test_encryption_decryption() { + let mut rng = OsRng; + let ephemeral_key = Keypair::generate(); + let recipient = Keypair::generate(); + let encryption_nonce = [1; ENCRYPTION_NONCE_LENGTH]; + let key_exchange = ephemeral_key.secret.key * recipient.public.as_point(); + let secret_share = SecretShare(Scalar::random(&mut rng)); + let mut transcript = Transcript::new(b"encryption"); + transcript.commit_point(b"key", &key_exchange.compress()); + let mut key_bytes = [0; CHACHA20POLY1305_KEY_LENGTH]; + transcript.challenge_bytes(b"key", &mut key_bytes); + + let encrypted_share = secret_share.encrypt(&key_bytes, &encryption_nonce).unwrap(); + + SecretShare::decrypt(&encrypted_share, &key_bytes, &encryption_nonce).unwrap(); + } + + #[test] + fn test_generate_polynomial_commitment_valid() { + let degree = 3; + + let polynomial = SecretPolynomial::generate(degree, &mut OsRng); + + let polynomial_commitment = polynomial.commit(); + + 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 = SecretPolynomial { 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_serialize_deserialize_all_message() { + let sender = Keypair::generate(); + let encryption_nonce = [1u8; ENCRYPTION_NONCE_LENGTH]; + let parameters = Parameters { participants: 2, threshold: 2 }; + let recipients_hash = [2u8; RECIPIENTS_HASH_LENGTH]; + let coefficients_commitments = + vec![RistrettoPoint::random(&mut OsRng), RistrettoPoint::random(&mut OsRng)]; + let polynomial_commitment = PolynomialCommitment { coefficients_commitments }; + let encrypted_secret_shares = vec![ + EncryptedSecretShare(vec![1; CHACHA20POLY1305_LENGTH]), + EncryptedSecretShare(vec![1; CHACHA20POLY1305_LENGTH]), + ]; + let proof_of_possession = sender.sign(Transcript::new(b"pop")); + let signature = sender.sign(Transcript::new(b"sig")); + let ephemeral_key = PublicKey::from_point(RistrettoPoint::random(&mut OsRng)); + + let message_content = MessageContent::new( + sender.public, + encryption_nonce, + parameters, + recipients_hash, + polynomial_commitment, + encrypted_secret_shares, + ephemeral_key, + proof_of_possession, + ); + + let message = AllMessage::new(message_content, signature); + + let bytes = message.to_bytes(); + + let deserialized_message = AllMessage::from_bytes(&bytes).expect("Failed to deserialize"); + + assert_eq!(message.content.sender, deserialized_message.content.sender); + + assert_eq!(message.content.encryption_nonce, deserialized_message.content.encryption_nonce); + + assert_eq!( + message.content.parameters.participants, + deserialized_message.content.parameters.participants + ); + + assert_eq!( + message.content.parameters.threshold, + deserialized_message.content.parameters.threshold + ); + + assert_eq!(message.content.recipients_hash, deserialized_message.content.recipients_hash); + + assert!(message + .content + .polynomial_commitment + .coefficients_commitments + .iter() + .zip( + deserialized_message + .content + .polynomial_commitment + .coefficients_commitments + .iter() + ) + .all(|(a, b)| a.compress() == b.compress())); + + assert!(message + .content + .encrypted_secret_shares + .iter() + .zip(deserialized_message.content.encrypted_secret_shares.iter()) + .all(|(a, b)| a.0 == b.0)); + + assert_eq!( + message.content.proof_of_possession, + deserialized_message.content.proof_of_possession + ); + + assert_eq!(message.signature, deserialized_message.signature); + } + + #[test] + fn test_spp_output_message_serialization() { + let mut rng = OsRng; + let group_public_key = RistrettoPoint::random(&mut rng); + let verifying_keys = vec![ + ( + Identifier(Scalar::random(&mut rng)), + VerifyingShare(PublicKey::from_point(RistrettoPoint::random(&mut rng))), + ), + ( + Identifier(Scalar::random(&mut rng)), + VerifyingShare(PublicKey::from_point(RistrettoPoint::random(&mut rng))), + ), + ( + Identifier(Scalar::random(&mut rng)), + VerifyingShare(PublicKey::from_point(RistrettoPoint::random(&mut rng))), + ), + ]; + let parameters = Parameters::generate(2, 2); + + let spp_output = SPPOutput { + parameters, + threshold_public_key: ThresholdPublicKey(PublicKey::from_point(group_public_key)), + verifying_keys, + }; + + let keypair = Keypair::generate(); + let signature = keypair.sign(Transcript::new(b"test")); + + let spp_output_message = + SPPOutputMessage { signer: VerifyingShare(keypair.public), spp_output, signature }; + + let bytes = spp_output_message.to_bytes(); + + let deserialized_spp_output_message = + SPPOutputMessage::from_bytes(&bytes).expect("Deserialization failed"); + + assert_eq!( + deserialized_spp_output_message.spp_output, spp_output_message.spp_output, + "Group public keys do not match" + ); + + assert_eq!( + deserialized_spp_output_message.signature, spp_output_message.signature, + "Signatures do not match" + ); + } + + #[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"); + } + + #[test] + fn test_parameters_serialization() { + let params = Parameters::generate(3, 2); + let bytes = params.to_bytes(); + let result = Parameters::from_bytes(&bytes).unwrap(); + + assert_eq!(params.participants, result.participants); + assert_eq!(params.threshold, result.threshold); + } +} From e5b1f7b15b778073b5babdc04e220b3b0fbd4e95 Mon Sep 17 00:00:00 2001 From: Fiono11 Date: Sat, 18 May 2024 11:06:40 +0100 Subject: [PATCH 45/60] Improvements --- benches/olaf_benchmarks.rs | 4 ++ src/olaf/simplpedpop/mod.rs | 4 +- src/olaf/simplpedpop/types.rs | 97 +++++++++++++++-------------------- 3 files changed, 48 insertions(+), 57 deletions(-) diff --git a/benches/olaf_benchmarks.rs b/benches/olaf_benchmarks.rs index 53319f9..2e101f1 100644 --- a/benches/olaf_benchmarks.rs +++ b/benches/olaf_benchmarks.rs @@ -11,6 +11,8 @@ mod olaf_benches { fn benchmark_simplpedpop(c: &mut Criterion) { let mut group = c.benchmark_group("SimplPedPoP"); + group.sample_size(10); + for &n in [3, 10, 100, 1000].iter() { let participants = n; let threshold = (n * 2 + 2) / 3; @@ -48,6 +50,8 @@ mod olaf_benches { fn benchmark_frost(c: &mut Criterion) { let mut group = c.benchmark_group("FROST"); + group.sample_size(10); + for &n in [3, 10, 100, 1000].iter() { let participants = n; let threshold = (n * 2 + 2) / 3; diff --git a/src/olaf/simplpedpop/mod.rs b/src/olaf/simplpedpop/mod.rs index fcbc032..10f0aa5 100644 --- a/src/olaf/simplpedpop/mod.rs +++ b/src/olaf/simplpedpop/mod.rs @@ -4,8 +4,8 @@ mod types; pub mod errors; -pub use self::types::{AllMessage, SPPOutputMessage, SPPOutput}; -pub(crate) use self::types::{PolynomialCommitment, MessageContent, Parameters}; +pub use self::types::{AllMessage, SPPOutputMessage, SPPOutput, Parameters}; +pub(crate) use self::types::{PolynomialCommitment, MessageContent}; use alloc::vec::Vec; use curve25519_dalek::{traits::Identity, RistrettoPoint, Scalar}; use merlin::Transcript; diff --git a/src/olaf/simplpedpop/types.rs b/src/olaf/simplpedpop/types.rs index 806c906..a82eeb9 100644 --- a/src/olaf/simplpedpop/types.rs +++ b/src/olaf/simplpedpop/types.rs @@ -115,7 +115,6 @@ pub struct Parameters { } impl Parameters { - /// Create new parameters. pub(crate) fn generate(participants: u16, threshold: u16) -> Parameters { Parameters { participants, threshold } } @@ -141,7 +140,6 @@ impl Parameters { t.commit_bytes(b"participants", &self.participants.to_le_bytes()); } - /// Serializes `Parameters` into a byte array. pub(super) fn to_bytes(&self) -> [u8; U16_LENGTH * 2] { let mut bytes = [0u8; U16_LENGTH * 2]; bytes[0..U16_LENGTH].copy_from_slice(&self.participants.to_le_bytes()); @@ -149,7 +147,6 @@ impl Parameters { bytes } - /// Constructs `Parameters` from a byte array. pub(super) fn from_bytes(bytes: &[u8]) -> SPPResult { if bytes.len() != U16_LENGTH * 2 { return Err(SPPError::InvalidParameters); @@ -163,6 +160,7 @@ impl Parameters { } /// The polynomial commitment of a participant, used to verify the secret shares without revealing the polynomial. +#[derive(Debug, PartialEq, Eq)] pub struct PolynomialCommitment { pub(super) coefficients_commitments: Vec, } @@ -204,7 +202,7 @@ impl PolynomialCommitment { } } -#[derive(Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct EncryptedSecretShare(pub(super) Vec); impl EncryptedSecretShare {} @@ -214,6 +212,7 @@ impl EncryptedSecretShare {} /// We'd save bandwidth by having separate messages for each /// participant, but typical thresholds lie between 1/2 and 2/3, /// so this doubles or tripples bandwidth usage. +#[derive(Debug, PartialEq, Eq)] pub struct AllMessage { pub(super) content: MessageContent, pub(super) signature: Signature, @@ -249,6 +248,7 @@ impl AllMessage { } /// The contents of the message destined to all participants. +#[derive(Debug, PartialEq, Eq)] pub struct MessageContent { pub(super) sender: PublicKey, pub(super) encryption_nonce: [u8; ENCRYPTION_NONCE_LENGTH], @@ -613,49 +613,7 @@ mod tests { let deserialized_message = AllMessage::from_bytes(&bytes).expect("Failed to deserialize"); - assert_eq!(message.content.sender, deserialized_message.content.sender); - - assert_eq!(message.content.encryption_nonce, deserialized_message.content.encryption_nonce); - - assert_eq!( - message.content.parameters.participants, - deserialized_message.content.parameters.participants - ); - - assert_eq!( - message.content.parameters.threshold, - deserialized_message.content.parameters.threshold - ); - - assert_eq!(message.content.recipients_hash, deserialized_message.content.recipients_hash); - - assert!(message - .content - .polynomial_commitment - .coefficients_commitments - .iter() - .zip( - deserialized_message - .content - .polynomial_commitment - .coefficients_commitments - .iter() - ) - .all(|(a, b)| a.compress() == b.compress())); - - assert!(message - .content - .encrypted_secret_shares - .iter() - .zip(deserialized_message.content.encrypted_secret_shares.iter()) - .all(|(a, b)| a.0 == b.0)); - - assert_eq!( - message.content.proof_of_possession, - deserialized_message.content.proof_of_possession - ); - - assert_eq!(message.signature, deserialized_message.signature); + assert_eq!(message, deserialized_message); } #[test] @@ -695,15 +653,44 @@ mod tests { let deserialized_spp_output_message = SPPOutputMessage::from_bytes(&bytes).expect("Deserialization failed"); - assert_eq!( - deserialized_spp_output_message.spp_output, spp_output_message.spp_output, - "Group public keys do not match" - ); + assert_eq!(deserialized_spp_output_message, spp_output_message); + } - assert_eq!( - deserialized_spp_output_message.signature, spp_output_message.signature, - "Signatures do not match" - ); + #[test] + fn test_spp_output_message_verification() { + let mut rng = OsRng; + let group_public_key = RistrettoPoint::random(&mut rng); + let verifying_keys = vec![ + ( + Identifier(Scalar::random(&mut rng)), + VerifyingShare(PublicKey::from_point(RistrettoPoint::random(&mut rng))), + ), + ( + Identifier(Scalar::random(&mut rng)), + VerifyingShare(PublicKey::from_point(RistrettoPoint::random(&mut rng))), + ), + ( + Identifier(Scalar::random(&mut rng)), + VerifyingShare(PublicKey::from_point(RistrettoPoint::random(&mut rng))), + ), + ]; + let parameters = Parameters::generate(2, 2); + + let spp_output = SPPOutput { + parameters, + threshold_public_key: ThresholdPublicKey(PublicKey::from_point(group_public_key)), + verifying_keys, + }; + + let keypair = Keypair::generate(); + let mut transcript = Transcript::new(b"spp output"); + transcript.append_message(b"message", &spp_output.to_bytes()); + let signature = keypair.sign(transcript); + + let spp_output_message = + SPPOutputMessage { signer: VerifyingShare(keypair.public), spp_output, signature }; + + spp_output_message.verify_signature().unwrap() } #[test] From 3d67f38c430d6400e9dab3d47bb98a0a26576eb6 Mon Sep 17 00:00:00 2001 From: Fiono11 Date: Sat, 18 May 2024 15:53:49 +0100 Subject: [PATCH 46/60] Sign the whole message with the secret of the polynomial --- src/olaf/simplpedpop/errors.rs | 11 ++++++--- src/olaf/simplpedpop/mod.rs | 41 +++++++++++++++------------------- src/olaf/simplpedpop/types.rs | 40 ++++++++++++++++----------------- 3 files changed, 45 insertions(+), 47 deletions(-) diff --git a/src/olaf/simplpedpop/errors.rs b/src/olaf/simplpedpop/errors.rs index 7b29c6d..2e45a98 100644 --- a/src/olaf/simplpedpop/errors.rs +++ b/src/olaf/simplpedpop/errors.rs @@ -1,7 +1,7 @@ //! Errors of the SimplPedPoP protocol. use core::array::TryFromSliceError; -use crate::SignatureError; +use crate::{PublicKey, SignatureError}; /// A result for the SimplPedPoP protocol. pub type SPPResult = Result; @@ -28,7 +28,10 @@ pub enum SPPError { /// Invalid identifier. InvalidIdentifier, /// Invalid secret share. - InvalidSecretShare, + InvalidSecretShare { + /// The sender of the invalid secret share. + culprit: PublicKey, + }, /// Deserialization Error. DeserializationError(TryFromSliceError), /// The parameters of all messages must be equal. @@ -245,7 +248,9 @@ mod tests { match result { Ok(_) => panic!("Expected an error, but got Ok."), Err(e) => match e { - SPPError::InvalidSecretShare => assert!(true), + SPPError::InvalidSecretShare { culprit } => { + assert_eq!(culprit, messages[1].content.sender); + }, _ => panic!("Expected DKGError::InvalidSecretShare, but got {:?}", e), }, } diff --git a/src/olaf/simplpedpop/mod.rs b/src/olaf/simplpedpop/mod.rs index 10f0aa5..0677bba 100644 --- a/src/olaf/simplpedpop/mod.rs +++ b/src/olaf/simplpedpop/mod.rs @@ -1,11 +1,13 @@ //! Implementation of the SimplPedPoP protocol (), a spp based on PedPoP, which in turn is based //! on Pedersen's spp. All of them have as the fundamental building block the Shamir's Secret Sharing scheme. +#![allow(clippy::result_large_err)] + mod types; pub mod errors; -pub use self::types::{AllMessage, SPPOutputMessage, SPPOutput, Parameters}; -pub(crate) use self::types::{PolynomialCommitment, MessageContent}; +pub use self::types::{AllMessage, SPPOutputMessage, SPPOutput}; +pub(crate) use self::types::{PolynomialCommitment, MessageContent, Parameters}; use alloc::vec::Vec; use curve25519_dalek::{traits::Identity, RistrettoPoint, Scalar}; use merlin::Transcript; @@ -110,16 +112,6 @@ impl Keypair { let secret_key = SecretKey { key: secret, nonce }; - let secret_commitment = polynomial_commitment - .coefficients_commitments - .first() - .expect("This never fails because the minimum threshold is 2"); - - let mut pop_transcript = Transcript::new(b"pop"); - pop_transcript - .append_message(b"secret commitment", secret_commitment.compress().as_bytes()); - let proof_of_possession = secret_key.sign(pop_transcript, pk); - let message_content = MessageContent::new( self.public, encryption_nonce, @@ -128,14 +120,17 @@ impl Keypair { polynomial_commitment, encrypted_secret_shares, ephemeral_key.public, - proof_of_possession, ); let mut signature_transcript = Transcript::new(b"signature"); - signature_transcript.append_message(b"message", &message_content.to_bytes()); + signature_transcript.append_message(b"message_sig", &message_content.to_bytes()); let signature = self.sign(signature_transcript); - Ok(AllMessage::new(message_content, signature)) + let mut pop_transcript = Transcript::new(b"pop"); + pop_transcript.append_message(b"message_pop", &message_content.to_bytes()); + let proof_of_possession = secret_key.sign(pop_transcript, pk); + + Ok(AllMessage::new(message_content, signature, proof_of_possession)) } /// Second round of the SimplPedPoP protocol. @@ -191,7 +186,7 @@ impl Keypair { .expect("This never fails because the minimum threshold is 2"), ); public_keys.push(public_key); - proofs_of_possession.push(content.proof_of_possession); + proofs_of_possession.push(message.proof_of_possession); senders.push(content.sender); signatures.push(message.signature); @@ -210,21 +205,18 @@ impl Keypair { } let mut signature_transcript = Transcript::new(b"signature"); - signature_transcript.append_message(b"message", &content.to_bytes()); + signature_transcript.append_message(b"message_sig", &content.to_bytes()); signatures_transcripts.push(signature_transcript); let mut pop_transcript = Transcript::new(b"pop"); + pop_transcript.append_message(b"message_pop", &content.to_bytes()); + pops_transcripts.push(pop_transcript); let secret_commitment = polynomial_commitment .coefficients_commitments .first() .expect("This never fails because the minimum threshold is 2"); - pop_transcript - .append_message(b"secret commitment", secret_commitment.compress().as_bytes()); - - pops_transcripts.push(pop_transcript); - total_polynomial_commitment = PolynomialCommitment::sum_polynomial_commitments(&[ &total_polynomial_commitment, &polynomial_commitment, @@ -267,7 +259,10 @@ impl Keypair { } } - total_secret_share += secret_shares.get(j).ok_or(SPPError::InvalidSecretShare)?.0; + total_secret_share += secret_shares + .get(j) + .ok_or(SPPError::InvalidSecretShare { culprit: message.content.sender })? + .0; group_point += secret_commitment; } diff --git a/src/olaf/simplpedpop/types.rs b/src/olaf/simplpedpop/types.rs index a82eeb9..6a17509 100644 --- a/src/olaf/simplpedpop/types.rs +++ b/src/olaf/simplpedpop/types.rs @@ -109,7 +109,7 @@ impl SecretPolynomial { /// The parameters of a given execution of the SimplPedPoP protocol. #[derive(Clone, PartialEq, Eq, Debug)] -pub struct Parameters { +pub(crate) struct Parameters { pub(crate) participants: u16, pub(crate) threshold: u16, } @@ -161,7 +161,7 @@ impl Parameters { /// The polynomial commitment of a participant, used to verify the secret shares without revealing the polynomial. #[derive(Debug, PartialEq, Eq)] -pub struct PolynomialCommitment { +pub(crate) struct PolynomialCommitment { pub(super) coefficients_commitments: Vec, } @@ -203,9 +203,7 @@ impl PolynomialCommitment { } #[derive(Debug, Clone, PartialEq, Eq)] -pub struct EncryptedSecretShare(pub(super) Vec); - -impl EncryptedSecretShare {} +pub(crate) struct EncryptedSecretShare(pub(super) Vec); /// AllMessage packs together messages for all participants. /// @@ -216,12 +214,17 @@ impl EncryptedSecretShare {} pub struct AllMessage { pub(super) content: MessageContent, pub(super) signature: Signature, + pub(super) proof_of_possession: Signature, } impl AllMessage { /// Creates a new message. - pub(crate) fn new(content: MessageContent, signature: Signature) -> Self { - Self { content, signature } + pub(crate) fn new( + content: MessageContent, + signature: Signature, + proof_of_possession: Signature, + ) -> Self { + Self { content, signature, proof_of_possession } } /// Serialize AllMessage pub fn to_bytes(&self) -> Vec { @@ -229,6 +232,7 @@ impl AllMessage { bytes.extend(self.content.to_bytes()); bytes.extend(self.signature.to_bytes()); + bytes.extend(self.proof_of_possession.to_bytes()); bytes } @@ -242,14 +246,18 @@ impl AllMessage { let signature = Signature::from_bytes(&bytes[cursor..cursor + SIGNATURE_LENGTH]) .map_err(SPPError::InvalidSignature)?; + cursor += SIGNATURE_LENGTH; + + let proof_of_possession = Signature::from_bytes(&bytes[cursor..cursor + SIGNATURE_LENGTH]) + .map_err(SPPError::InvalidSignature)?; - Ok(AllMessage { content, signature }) + Ok(AllMessage { content, signature, proof_of_possession }) } } /// The contents of the message destined to all participants. #[derive(Debug, PartialEq, Eq)] -pub struct MessageContent { +pub(crate) struct MessageContent { pub(super) sender: PublicKey, pub(super) encryption_nonce: [u8; ENCRYPTION_NONCE_LENGTH], pub(super) parameters: Parameters, @@ -257,7 +265,6 @@ pub struct MessageContent { pub(super) polynomial_commitment: PolynomialCommitment, pub(super) encrypted_secret_shares: Vec, pub(super) ephemeral_key: PublicKey, - pub(super) proof_of_possession: Signature, } impl MessageContent { @@ -270,7 +277,6 @@ impl MessageContent { polynomial_commitment: PolynomialCommitment, encrypted_secret_shares: Vec, ephemeral_key: PublicKey, - proof_of_possession: Signature, ) -> Self { Self { sender, @@ -280,7 +286,6 @@ impl MessageContent { polynomial_commitment, encrypted_secret_shares, ephemeral_key, - proof_of_possession, } } @@ -303,7 +308,6 @@ impl MessageContent { } bytes.extend(&self.ephemeral_key.to_bytes()); - bytes.extend(&self.proof_of_possession.to_bytes()); bytes } @@ -359,10 +363,6 @@ impl MessageContent { let ephemeral_key = PublicKey::from_bytes(&bytes[cursor..cursor + PUBLIC_KEY_LENGTH]) .map_err(SPPError::InvalidPublicKey)?; - cursor += PUBLIC_KEY_LENGTH; - - let proof_of_possession = Signature::from_bytes(&bytes[cursor..cursor + SIGNATURE_LENGTH]) - .map_err(SPPError::InvalidSignature)?; Ok(MessageContent { sender, @@ -372,7 +372,6 @@ impl MessageContent { polynomial_commitment, encrypted_secret_shares, ephemeral_key, - proof_of_possession, }) } } @@ -592,8 +591,8 @@ mod tests { EncryptedSecretShare(vec![1; CHACHA20POLY1305_LENGTH]), EncryptedSecretShare(vec![1; CHACHA20POLY1305_LENGTH]), ]; - let proof_of_possession = sender.sign(Transcript::new(b"pop")); let signature = sender.sign(Transcript::new(b"sig")); + let proof_of_possession = sender.sign(Transcript::new(b"pop")); let ephemeral_key = PublicKey::from_point(RistrettoPoint::random(&mut OsRng)); let message_content = MessageContent::new( @@ -604,10 +603,9 @@ mod tests { polynomial_commitment, encrypted_secret_shares, ephemeral_key, - proof_of_possession, ); - let message = AllMessage::new(message_content, signature); + let message = AllMessage::new(message_content, signature, proof_of_possession); let bytes = message.to_bytes(); From 63cbcdf0c221a19cecb0807f905b5affaae63206 Mon Sep 17 00:00:00 2001 From: Fiono11 Date: Sat, 18 May 2024 15:53:49 +0100 Subject: [PATCH 47/60] Sign the whole message with the secret of the polynomial --- src/olaf/simplpedpop/errors.rs | 11 +++-- src/olaf/simplpedpop/mod.rs | 37 ++++++-------- src/olaf/simplpedpop/types.rs | 89 +++++++++------------------------- 3 files changed, 48 insertions(+), 89 deletions(-) diff --git a/src/olaf/simplpedpop/errors.rs b/src/olaf/simplpedpop/errors.rs index 7b29c6d..2e45a98 100644 --- a/src/olaf/simplpedpop/errors.rs +++ b/src/olaf/simplpedpop/errors.rs @@ -1,7 +1,7 @@ //! Errors of the SimplPedPoP protocol. use core::array::TryFromSliceError; -use crate::SignatureError; +use crate::{PublicKey, SignatureError}; /// A result for the SimplPedPoP protocol. pub type SPPResult = Result; @@ -28,7 +28,10 @@ pub enum SPPError { /// Invalid identifier. InvalidIdentifier, /// Invalid secret share. - InvalidSecretShare, + InvalidSecretShare { + /// The sender of the invalid secret share. + culprit: PublicKey, + }, /// Deserialization Error. DeserializationError(TryFromSliceError), /// The parameters of all messages must be equal. @@ -245,7 +248,9 @@ mod tests { match result { Ok(_) => panic!("Expected an error, but got Ok."), Err(e) => match e { - SPPError::InvalidSecretShare => assert!(true), + SPPError::InvalidSecretShare { culprit } => { + assert_eq!(culprit, messages[1].content.sender); + }, _ => panic!("Expected DKGError::InvalidSecretShare, but got {:?}", e), }, } diff --git a/src/olaf/simplpedpop/mod.rs b/src/olaf/simplpedpop/mod.rs index fcbc032..0677bba 100644 --- a/src/olaf/simplpedpop/mod.rs +++ b/src/olaf/simplpedpop/mod.rs @@ -1,6 +1,8 @@ //! Implementation of the SimplPedPoP protocol (), a spp based on PedPoP, which in turn is based //! on Pedersen's spp. All of them have as the fundamental building block the Shamir's Secret Sharing scheme. +#![allow(clippy::result_large_err)] + mod types; pub mod errors; @@ -110,16 +112,6 @@ impl Keypair { let secret_key = SecretKey { key: secret, nonce }; - let secret_commitment = polynomial_commitment - .coefficients_commitments - .first() - .expect("This never fails because the minimum threshold is 2"); - - let mut pop_transcript = Transcript::new(b"pop"); - pop_transcript - .append_message(b"secret commitment", secret_commitment.compress().as_bytes()); - let proof_of_possession = secret_key.sign(pop_transcript, pk); - let message_content = MessageContent::new( self.public, encryption_nonce, @@ -128,14 +120,17 @@ impl Keypair { polynomial_commitment, encrypted_secret_shares, ephemeral_key.public, - proof_of_possession, ); let mut signature_transcript = Transcript::new(b"signature"); - signature_transcript.append_message(b"message", &message_content.to_bytes()); + signature_transcript.append_message(b"message_sig", &message_content.to_bytes()); let signature = self.sign(signature_transcript); - Ok(AllMessage::new(message_content, signature)) + let mut pop_transcript = Transcript::new(b"pop"); + pop_transcript.append_message(b"message_pop", &message_content.to_bytes()); + let proof_of_possession = secret_key.sign(pop_transcript, pk); + + Ok(AllMessage::new(message_content, signature, proof_of_possession)) } /// Second round of the SimplPedPoP protocol. @@ -191,7 +186,7 @@ impl Keypair { .expect("This never fails because the minimum threshold is 2"), ); public_keys.push(public_key); - proofs_of_possession.push(content.proof_of_possession); + proofs_of_possession.push(message.proof_of_possession); senders.push(content.sender); signatures.push(message.signature); @@ -210,21 +205,18 @@ impl Keypair { } let mut signature_transcript = Transcript::new(b"signature"); - signature_transcript.append_message(b"message", &content.to_bytes()); + signature_transcript.append_message(b"message_sig", &content.to_bytes()); signatures_transcripts.push(signature_transcript); let mut pop_transcript = Transcript::new(b"pop"); + pop_transcript.append_message(b"message_pop", &content.to_bytes()); + pops_transcripts.push(pop_transcript); let secret_commitment = polynomial_commitment .coefficients_commitments .first() .expect("This never fails because the minimum threshold is 2"); - pop_transcript - .append_message(b"secret commitment", secret_commitment.compress().as_bytes()); - - pops_transcripts.push(pop_transcript); - total_polynomial_commitment = PolynomialCommitment::sum_polynomial_commitments(&[ &total_polynomial_commitment, &polynomial_commitment, @@ -267,7 +259,10 @@ impl Keypair { } } - total_secret_share += secret_shares.get(j).ok_or(SPPError::InvalidSecretShare)?.0; + total_secret_share += secret_shares + .get(j) + .ok_or(SPPError::InvalidSecretShare { culprit: message.content.sender })? + .0; group_point += secret_commitment; } diff --git a/src/olaf/simplpedpop/types.rs b/src/olaf/simplpedpop/types.rs index 806c906..0e5f03d 100644 --- a/src/olaf/simplpedpop/types.rs +++ b/src/olaf/simplpedpop/types.rs @@ -109,7 +109,7 @@ impl SecretPolynomial { /// The parameters of a given execution of the SimplPedPoP protocol. #[derive(Clone, PartialEq, Eq, Debug)] -pub struct Parameters { +pub(crate) struct Parameters { pub(crate) participants: u16, pub(crate) threshold: u16, } @@ -163,7 +163,8 @@ impl Parameters { } /// The polynomial commitment of a participant, used to verify the secret shares without revealing the polynomial. -pub struct PolynomialCommitment { +#[derive(Debug, PartialEq, Eq)] +pub(crate) struct PolynomialCommitment { pub(super) coefficients_commitments: Vec, } @@ -204,25 +205,29 @@ impl PolynomialCommitment { } } -#[derive(Clone)] -pub struct EncryptedSecretShare(pub(super) Vec); - -impl EncryptedSecretShare {} +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) struct EncryptedSecretShare(pub(super) Vec); /// AllMessage packs together messages for all participants. /// /// We'd save bandwidth by having separate messages for each /// participant, but typical thresholds lie between 1/2 and 2/3, /// so this doubles or tripples bandwidth usage. +#[derive(Debug, PartialEq, Eq)] pub struct AllMessage { pub(super) content: MessageContent, pub(super) signature: Signature, + pub(super) proof_of_possession: Signature, } impl AllMessage { /// Creates a new message. - pub(crate) fn new(content: MessageContent, signature: Signature) -> Self { - Self { content, signature } + pub(crate) fn new( + content: MessageContent, + signature: Signature, + proof_of_possession: Signature, + ) -> Self { + Self { content, signature, proof_of_possession } } /// Serialize AllMessage pub fn to_bytes(&self) -> Vec { @@ -230,6 +235,7 @@ impl AllMessage { bytes.extend(self.content.to_bytes()); bytes.extend(self.signature.to_bytes()); + bytes.extend(self.proof_of_possession.to_bytes()); bytes } @@ -243,13 +249,18 @@ impl AllMessage { let signature = Signature::from_bytes(&bytes[cursor..cursor + SIGNATURE_LENGTH]) .map_err(SPPError::InvalidSignature)?; + cursor += SIGNATURE_LENGTH; + + let proof_of_possession = Signature::from_bytes(&bytes[cursor..cursor + SIGNATURE_LENGTH]) + .map_err(SPPError::InvalidSignature)?; - Ok(AllMessage { content, signature }) + Ok(AllMessage { content, signature, proof_of_possession }) } } /// The contents of the message destined to all participants. -pub struct MessageContent { +#[derive(Debug, PartialEq, Eq)] +pub(crate) struct MessageContent { pub(super) sender: PublicKey, pub(super) encryption_nonce: [u8; ENCRYPTION_NONCE_LENGTH], pub(super) parameters: Parameters, @@ -257,7 +268,6 @@ pub struct MessageContent { pub(super) polynomial_commitment: PolynomialCommitment, pub(super) encrypted_secret_shares: Vec, pub(super) ephemeral_key: PublicKey, - pub(super) proof_of_possession: Signature, } impl MessageContent { @@ -270,7 +280,6 @@ impl MessageContent { polynomial_commitment: PolynomialCommitment, encrypted_secret_shares: Vec, ephemeral_key: PublicKey, - proof_of_possession: Signature, ) -> Self { Self { sender, @@ -280,7 +289,6 @@ impl MessageContent { polynomial_commitment, encrypted_secret_shares, ephemeral_key, - proof_of_possession, } } @@ -303,7 +311,6 @@ impl MessageContent { } bytes.extend(&self.ephemeral_key.to_bytes()); - bytes.extend(&self.proof_of_possession.to_bytes()); bytes } @@ -359,10 +366,6 @@ impl MessageContent { let ephemeral_key = PublicKey::from_bytes(&bytes[cursor..cursor + PUBLIC_KEY_LENGTH]) .map_err(SPPError::InvalidPublicKey)?; - cursor += PUBLIC_KEY_LENGTH; - - let proof_of_possession = Signature::from_bytes(&bytes[cursor..cursor + SIGNATURE_LENGTH]) - .map_err(SPPError::InvalidSignature)?; Ok(MessageContent { sender, @@ -372,7 +375,6 @@ impl MessageContent { polynomial_commitment, encrypted_secret_shares, ephemeral_key, - proof_of_possession, }) } } @@ -592,8 +594,8 @@ mod tests { EncryptedSecretShare(vec![1; CHACHA20POLY1305_LENGTH]), EncryptedSecretShare(vec![1; CHACHA20POLY1305_LENGTH]), ]; - let proof_of_possession = sender.sign(Transcript::new(b"pop")); let signature = sender.sign(Transcript::new(b"sig")); + let proof_of_possession = sender.sign(Transcript::new(b"pop")); let ephemeral_key = PublicKey::from_point(RistrettoPoint::random(&mut OsRng)); let message_content = MessageContent::new( @@ -604,58 +606,15 @@ mod tests { polynomial_commitment, encrypted_secret_shares, ephemeral_key, - proof_of_possession, ); - let message = AllMessage::new(message_content, signature); + let message = AllMessage::new(message_content, signature, proof_of_possession); let bytes = message.to_bytes(); let deserialized_message = AllMessage::from_bytes(&bytes).expect("Failed to deserialize"); - assert_eq!(message.content.sender, deserialized_message.content.sender); - - assert_eq!(message.content.encryption_nonce, deserialized_message.content.encryption_nonce); - - assert_eq!( - message.content.parameters.participants, - deserialized_message.content.parameters.participants - ); - - assert_eq!( - message.content.parameters.threshold, - deserialized_message.content.parameters.threshold - ); - - assert_eq!(message.content.recipients_hash, deserialized_message.content.recipients_hash); - - assert!(message - .content - .polynomial_commitment - .coefficients_commitments - .iter() - .zip( - deserialized_message - .content - .polynomial_commitment - .coefficients_commitments - .iter() - ) - .all(|(a, b)| a.compress() == b.compress())); - - assert!(message - .content - .encrypted_secret_shares - .iter() - .zip(deserialized_message.content.encrypted_secret_shares.iter()) - .all(|(a, b)| a.0 == b.0)); - - assert_eq!( - message.content.proof_of_possession, - deserialized_message.content.proof_of_possession - ); - - assert_eq!(message.signature, deserialized_message.signature); + assert_eq!(message, deserialized_message); } #[test] From 86c8bee2d558ad183931ef6923aeb1fec0fc74ce Mon Sep 17 00:00:00 2001 From: Fiono11 Date: Mon, 20 May 2024 15:47:22 +0100 Subject: [PATCH 48/60] Fixes --- src/olaf/frost/errors.rs | 29 ++++------- src/olaf/frost/mod.rs | 31 +++++------- src/olaf/frost/types.rs | 46 ++++++++--------- src/olaf/mod.rs | 34 ++++++++++++- src/olaf/simplpedpop/errors.rs | 19 ++----- src/olaf/simplpedpop/mod.rs | 15 +----- src/olaf/simplpedpop/types.rs | 90 ++++++++++++---------------------- 7 files changed, 114 insertions(+), 150 deletions(-) diff --git a/src/olaf/frost/errors.rs b/src/olaf/frost/errors.rs index 3a61ff4..a84bdec 100644 --- a/src/olaf/frost/errors.rs +++ b/src/olaf/frost/errors.rs @@ -21,7 +21,7 @@ pub enum FROSTError { MissingOwnSigningCommitment, /// Commitment equals the identity IdentitySigningCommitment, - /// The number of veriyfing shares must be equal to the number of participants. + /// The number of veriyfing shares must be equal to the number of signers. IncorrectNumberOfVerifyingShares, /// Error deserializing the signature share. SignatureShareDeserializationError, @@ -54,7 +54,6 @@ pub enum FROSTError { mod tests { use alloc::vec::Vec; use curve25519_dalek::{traits::Identity, RistrettoPoint, Scalar}; - use rand::Rng; use rand_core::OsRng; use crate::{ olaf::{ @@ -63,24 +62,14 @@ mod tests { types::{NonceCommitment, SigningCommitments}, SigningPackage, }, - simplpedpop::{AllMessage, Parameters}, - SigningKeypair, MINIMUM_THRESHOLD, + simplpedpop::AllMessage, + test_utils::generate_parameters, + SigningKeypair, }, Keypair, PublicKey, }; use super::FROSTError; - const MAXIMUM_PARTICIPANTS: u16 = 2; - const MINIMUM_PARTICIPANTS: u16 = 2; - - fn generate_parameters() -> Parameters { - let mut rng = rand::thread_rng(); - let participants = rng.gen_range(MINIMUM_PARTICIPANTS..=MAXIMUM_PARTICIPANTS); - let threshold = rng.gen_range(MINIMUM_THRESHOLD..=participants); - - Parameters { participants, threshold } - } - #[test] fn test_empty_signing_packages() { let signing_packages: Vec = Vec::new(); @@ -125,7 +114,7 @@ mod tests { let mut all_signing_commitments = Vec::new(); let mut all_signing_nonces = Vec::new(); - for spp_output in &spp_outputs[..threshold] { + for spp_output in &spp_outputs { let (signing_nonces, signing_commitments) = spp_output.1.commit(&mut OsRng); all_signing_nonces.push(signing_nonces); all_signing_commitments.push(signing_commitments); @@ -637,8 +626,6 @@ mod tests { all_signing_commitments.push(signing_commitments); } - all_signing_commitments.pop(); - let message = b"message"; let context = b"context"; @@ -646,7 +633,11 @@ mod tests { context.to_vec(), message.to_vec(), spp_outputs[0].0.spp_output.clone(), - all_signing_commitments.clone(), + all_signing_commitments + .into_iter() + .take(parameters.threshold as usize - 1) + .collect::>() + .clone(), &all_signing_nonces[0], ); diff --git a/src/olaf/frost/mod.rs b/src/olaf/frost/mod.rs index c3600cc..e541dbc 100644 --- a/src/olaf/frost/mod.rs +++ b/src/olaf/frost/mod.rs @@ -93,8 +93,13 @@ impl SigningKeypair { signer_nonces: &SigningNonces, ) -> FROSTResult { let threshold_public_key = &spp_output.threshold_public_key; + let len = all_signing_commitments.len(); - if spp_output.verifying_keys.len() != spp_output.parameters.participants as usize { + if len < spp_output.parameters.threshold as usize { + return Err(FROSTError::InvalidNumberOfSigningCommitments); + } + + if spp_output.verifying_keys.len() != len { return Err(FROSTError::IncorrectNumberOfVerifyingShares); } @@ -346,13 +351,9 @@ fn compute_challenge( #[cfg(test)] mod tests { use alloc::vec::Vec; - use rand::Rng; use rand_core::OsRng; use crate::{ - olaf::{ - simplpedpop::{AllMessage, Parameters}, - MINIMUM_THRESHOLD, - }, + olaf::{simplpedpop::AllMessage, test_utils::generate_parameters}, Keypair, PublicKey, }; use super::{ @@ -360,18 +361,8 @@ mod tests { types::{SigningCommitments, SigningNonces}, }; - const MAXIMUM_PARTICIPANTS: u16 = 2; - const MINIMUM_PARTICIPANTS: u16 = 2; const NONCES: u8 = 10; - fn generate_parameters() -> Parameters { - let mut rng = rand::thread_rng(); - let participants = rng.gen_range(MINIMUM_PARTICIPANTS..=MAXIMUM_PARTICIPANTS); - let threshold = rng.gen_range(MINIMUM_THRESHOLD..=participants); - - Parameters { participants, threshold } - } - #[test] fn test_n_of_n_frost_with_simplpedpop() { let parameters = generate_parameters(); @@ -448,7 +439,11 @@ mod tests { let mut spp_outputs = Vec::new(); for kp in keypairs.iter() { - let spp_output = kp.simplpedpop_recipient_all(&all_messages).unwrap(); + let mut spp_output = kp.simplpedpop_recipient_all(&all_messages).unwrap(); + + spp_output.0.spp_output.verifying_keys = + spp_output.0.spp_output.verifying_keys.into_iter().take(threshold).collect(); + spp_outputs.push(spp_output); } @@ -466,7 +461,7 @@ mod tests { let message = b"message"; let context = b"context"; - for (i, spp_output) in spp_outputs.iter().enumerate() { + for (i, spp_output) in spp_outputs[..threshold].iter().enumerate() { let signing_package = spp_output .1 .sign( diff --git a/src/olaf/frost/types.rs b/src/olaf/frost/types.rs index b6e8d3a..e01852d 100644 --- a/src/olaf/frost/types.rs +++ b/src/olaf/frost/types.rs @@ -458,15 +458,17 @@ impl GroupCommitment { #[cfg(test)] mod tests { - use curve25519_dalek::{RistrettoPoint, Scalar}; + use alloc::vec::Vec; + use curve25519_dalek::Scalar; use rand_core::OsRng; use crate::{ olaf::{ frost::types::{CommonData, SignerData}, - simplpedpop::{Parameters, SPPOutput}, - Identifier, ThresholdPublicKey, VerifyingShare, GENERATOR, + simplpedpop::AllMessage, + test_utils::generate_parameters, + GENERATOR, }, - PublicKey, + Keypair, PublicKey, }; use super::{NonceCommitment, SignatureShare, SigningCommitments, SigningPackage}; @@ -474,28 +476,22 @@ mod tests { #[test] fn test_signing_package_serialization() { let mut rng = OsRng; - let group_public_key = RistrettoPoint::random(&mut rng); - let verifying_keys = vec![ - ( - Identifier(Scalar::random(&mut rng)), - VerifyingShare(PublicKey::from_point(RistrettoPoint::random(&mut rng))), - ), - ( - Identifier(Scalar::random(&mut rng)), - VerifyingShare(PublicKey::from_point(RistrettoPoint::random(&mut rng))), - ), - ( - Identifier(Scalar::random(&mut rng)), - VerifyingShare(PublicKey::from_point(RistrettoPoint::random(&mut rng))), - ), - ]; - let parameters = Parameters::generate(2, 2); + let parameters = generate_parameters(); + let participants = parameters.participants as usize; + let threshold = parameters.threshold as usize; + + let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public).collect(); + + let mut all_messages = Vec::new(); + for i in 0..participants { + let message: AllMessage = keypairs[i] + .simplpedpop_contribute_all(threshold as u16, public_keys.clone()) + .unwrap(); + all_messages.push(message); + } - let spp_output = SPPOutput { - parameters, - threshold_public_key: ThresholdPublicKey(PublicKey::from_point(group_public_key)), - verifying_keys, - }; + let spp_output = keypairs[0].simplpedpop_recipient_all(&all_messages).unwrap().0.spp_output; let signing_commitments = vec![ SigningCommitments { diff --git a/src/olaf/mod.rs b/src/olaf/mod.rs index fc481ff..9cc1340 100644 --- a/src/olaf/mod.rs +++ b/src/olaf/mod.rs @@ -9,7 +9,7 @@ pub mod frost; use curve25519_dalek::{constants::RISTRETTO_BASEPOINT_POINT, RistrettoPoint, Scalar}; use merlin::Transcript; use zeroize::ZeroizeOnDrop; -use crate::{context::SigningTranscript, Keypair, PublicKey}; +use crate::{context::SigningTranscript, Keypair, PublicKey, SignatureError, KEYPAIR_LENGTH}; pub(super) const MINIMUM_THRESHOLD: u16 = 2; pub(super) const GENERATOR: RistrettoPoint = RISTRETTO_BASEPOINT_POINT; @@ -28,6 +28,19 @@ pub struct VerifyingShare(pub(crate) PublicKey); #[derive(Clone, Debug, ZeroizeOnDrop)] pub struct SigningKeypair(pub(crate) Keypair); +impl SigningKeypair { + /// Serializes `SigningKeypair` to bytes. + pub fn to_bytes(&self) -> [u8; KEYPAIR_LENGTH] { + self.0.to_bytes() + } + + /// Deserializes a `SigningKeypair` from bytes. + pub fn from_bytes(bytes: &[u8]) -> Result { + let keypair = Keypair::from_bytes(bytes)?; + Ok(SigningKeypair(keypair)) + } +} + /// The identifier of a participant, which must be the same in the SimplPedPoP protocol and in the FROST protocol. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct Identifier(pub(crate) Scalar); @@ -41,3 +54,22 @@ impl Identifier { Identifier(pos.challenge_scalar(b"evaluation position")) } } + +#[cfg(test)] +pub(crate) mod test_utils { + use rand::{thread_rng, Rng}; + use crate::olaf::simplpedpop::Parameters; + + const MAXIMUM_PARTICIPANTS: u16 = 10; + const MINIMUM_PARTICIPANTS: u16 = 2; + + pub(crate) fn generate_parameters() -> Parameters { + use super::MINIMUM_THRESHOLD; + + let mut rng = thread_rng(); + let participants = rng.gen_range(MINIMUM_PARTICIPANTS..=MAXIMUM_PARTICIPANTS); + let threshold = rng.gen_range(MINIMUM_THRESHOLD..=participants); + + Parameters { participants, threshold } + } +} diff --git a/src/olaf/simplpedpop/errors.rs b/src/olaf/simplpedpop/errors.rs index 2e45a98..867a685 100644 --- a/src/olaf/simplpedpop/errors.rs +++ b/src/olaf/simplpedpop/errors.rs @@ -23,6 +23,10 @@ pub enum SPPError { InvalidThresholdPublicKey, /// Invalid signature. InvalidSignature(SignatureError), + /// Error deserializing signature. + ErrorDeserializingSignature(SignatureError), + /// Error deserializing proof of possession. + ErrorDeserializingProofOfPossession(SignatureError), /// Invalid coefficient commitment of the polynomial commitment. InvalidCoefficientCommitment, /// Invalid identifier. @@ -60,25 +64,12 @@ mod tests { use crate::olaf::simplpedpop::types::{ AllMessage, EncryptedSecretShare, CHACHA20POLY1305_LENGTH, RECIPIENTS_HASH_LENGTH, }; - use crate::olaf::simplpedpop::Parameters; - use crate::olaf::MINIMUM_THRESHOLD; + use crate::olaf::test_utils::generate_parameters; use crate::{Keypair, PublicKey}; use alloc::vec::Vec; use curve25519_dalek::ristretto::RistrettoPoint; use curve25519_dalek::traits::Identity; use merlin::Transcript; - use rand::Rng; - - const MAXIMUM_PARTICIPANTS: u16 = 10; - const MINIMUM_PARTICIPANTS: u16 = 2; - - fn generate_parameters() -> Parameters { - let mut rng = rand::thread_rng(); - let participants = rng.gen_range(MINIMUM_PARTICIPANTS..=MAXIMUM_PARTICIPANTS); - let threshold = rng.gen_range(MINIMUM_THRESHOLD..=participants); - - Parameters { participants, threshold } - } #[test] fn test_invalid_number_of_messages() { diff --git a/src/olaf/simplpedpop/mod.rs b/src/olaf/simplpedpop/mod.rs index 0677bba..ceb7398 100644 --- a/src/olaf/simplpedpop/mod.rs +++ b/src/olaf/simplpedpop/mod.rs @@ -302,24 +302,13 @@ impl Keypair { #[cfg(test)] mod tests { - use crate::olaf::simplpedpop::types::{AllMessage, Parameters}; - use crate::olaf::MINIMUM_THRESHOLD; + use crate::olaf::simplpedpop::types::AllMessage; + use crate::olaf::test_utils::generate_parameters; use crate::{Keypair, PublicKey}; use alloc::vec::Vec; - use rand::Rng; - const MAXIMUM_PARTICIPANTS: u16 = 10; - const MINIMUM_PARTICIPANTS: u16 = 2; const PROTOCOL_RUNS: usize = 1; - fn generate_parameters() -> Parameters { - let mut rng = rand::thread_rng(); - let participants = rng.gen_range(MINIMUM_PARTICIPANTS..=MAXIMUM_PARTICIPANTS); - let threshold = rng.gen_range(MINIMUM_THRESHOLD..=participants); - - Parameters { participants, threshold } - } - #[test] fn test_simplpedpop_protocol() { for _ in 0..PROTOCOL_RUNS { diff --git a/src/olaf/simplpedpop/types.rs b/src/olaf/simplpedpop/types.rs index 6a17509..fb3f071 100644 --- a/src/olaf/simplpedpop/types.rs +++ b/src/olaf/simplpedpop/types.rs @@ -23,7 +23,7 @@ use super::errors::{SPPError, SPPResult}; pub(super) const U16_LENGTH: usize = 2; pub(super) const ENCRYPTION_NONCE_LENGTH: usize = 12; pub(super) const RECIPIENTS_HASH_LENGTH: usize = 16; -pub(super) const CHACHA20POLY1305_LENGTH: usize = 64; +pub(super) const CHACHA20POLY1305_LENGTH: usize = 48; pub(super) const CHACHA20POLY1305_KEY_LENGTH: usize = 32; pub(super) const VEC_LENGTH: usize = 2; @@ -245,11 +245,11 @@ impl AllMessage { cursor += content.to_bytes().len(); let signature = Signature::from_bytes(&bytes[cursor..cursor + SIGNATURE_LENGTH]) - .map_err(SPPError::InvalidSignature)?; + .map_err(SPPError::ErrorDeserializingSignature)?; cursor += SIGNATURE_LENGTH; let proof_of_possession = Signature::from_bytes(&bytes[cursor..cursor + SIGNATURE_LENGTH]) - .map_err(SPPError::InvalidSignature)?; + .map_err(SPPError::ErrorDeserializingProofOfPossession)?; Ok(AllMessage { content, signature, proof_of_possession }) } @@ -339,7 +339,7 @@ impl MessageContent { let mut coefficients_commitments = Vec::with_capacity(participants as usize); - for _ in 0..participants { + for _ in 0..parameters.threshold { let point = CompressedRistretto::from_slice( &bytes[cursor..cursor + COMPRESSED_RISTRETTO_LENGTH], ) @@ -419,7 +419,7 @@ impl SPPOutputMessage { cursor = bytes.len() - SIGNATURE_LENGTH; let signature = Signature::from_bytes(&bytes[cursor..cursor + SIGNATURE_LENGTH]) - .map_err(SPPError::InvalidSignature)?; + .map_err(SPPError::ErrorDeserializingSignature)?; Ok(SPPOutputMessage { signer, spp_output, signature }) } @@ -529,7 +529,7 @@ impl SPPOutput { mod tests { use merlin::Transcript; use rand_core::OsRng; - use crate::{context::SigningTranscript, Keypair}; + use crate::{context::SigningTranscript, olaf::test_utils::generate_parameters, Keypair}; use super::*; use curve25519_dalek::RistrettoPoint; @@ -580,32 +580,15 @@ mod tests { #[test] fn test_serialize_deserialize_all_message() { - let sender = Keypair::generate(); - let encryption_nonce = [1u8; ENCRYPTION_NONCE_LENGTH]; - let parameters = Parameters { participants: 2, threshold: 2 }; - let recipients_hash = [2u8; RECIPIENTS_HASH_LENGTH]; - let coefficients_commitments = - vec![RistrettoPoint::random(&mut OsRng), RistrettoPoint::random(&mut OsRng)]; - let polynomial_commitment = PolynomialCommitment { coefficients_commitments }; - let encrypted_secret_shares = vec![ - EncryptedSecretShare(vec![1; CHACHA20POLY1305_LENGTH]), - EncryptedSecretShare(vec![1; CHACHA20POLY1305_LENGTH]), - ]; - let signature = sender.sign(Transcript::new(b"sig")); - let proof_of_possession = sender.sign(Transcript::new(b"pop")); - let ephemeral_key = PublicKey::from_point(RistrettoPoint::random(&mut OsRng)); + let parameters = generate_parameters(); - let message_content = MessageContent::new( - sender.public, - encryption_nonce, - parameters, - recipients_hash, - polynomial_commitment, - encrypted_secret_shares, - ephemeral_key, - ); + let keypairs: Vec = + (0..parameters.participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public).collect(); - let message = AllMessage::new(message_content, signature, proof_of_possession); + let message: AllMessage = keypairs[0] + .simplpedpop_contribute_all(parameters.threshold as u16, public_keys.clone()) + .unwrap(); let bytes = message.to_bytes(); @@ -616,42 +599,29 @@ mod tests { #[test] fn test_spp_output_message_serialization() { - let mut rng = OsRng; - let group_public_key = RistrettoPoint::random(&mut rng); - let verifying_keys = vec![ - ( - Identifier(Scalar::random(&mut rng)), - VerifyingShare(PublicKey::from_point(RistrettoPoint::random(&mut rng))), - ), - ( - Identifier(Scalar::random(&mut rng)), - VerifyingShare(PublicKey::from_point(RistrettoPoint::random(&mut rng))), - ), - ( - Identifier(Scalar::random(&mut rng)), - VerifyingShare(PublicKey::from_point(RistrettoPoint::random(&mut rng))), - ), - ]; - let parameters = Parameters::generate(2, 2); - - let spp_output = SPPOutput { - parameters, - threshold_public_key: ThresholdPublicKey(PublicKey::from_point(group_public_key)), - verifying_keys, - }; - - let keypair = Keypair::generate(); - let signature = keypair.sign(Transcript::new(b"test")); + let parameters = generate_parameters(); + let participants = parameters.participants as usize; + let threshold = parameters.threshold as usize; + + let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public).collect(); + + let mut all_messages = Vec::new(); + for i in 0..participants { + let message: AllMessage = keypairs[i] + .simplpedpop_contribute_all(threshold as u16, public_keys.clone()) + .unwrap(); + all_messages.push(message); + } - let spp_output_message = - SPPOutputMessage { signer: VerifyingShare(keypair.public), spp_output, signature }; + let spp_output = keypairs[0].simplpedpop_recipient_all(&all_messages).unwrap(); - let bytes = spp_output_message.to_bytes(); + let bytes = spp_output.0.to_bytes(); let deserialized_spp_output_message = SPPOutputMessage::from_bytes(&bytes).expect("Deserialization failed"); - assert_eq!(deserialized_spp_output_message, spp_output_message); + assert_eq!(deserialized_spp_output_message, spp_output.0); } #[test] From 4835946c21ef121826e201b1054b22804bdc697f Mon Sep 17 00:00:00 2001 From: Fiono11 Date: Mon, 20 May 2024 17:49:58 +0100 Subject: [PATCH 49/60] Complete serialization of frost --- src/olaf/frost/mod.rs | 1 + src/olaf/frost/types.rs | 147 +++++++++++++++++++++++++--------- src/olaf/simplpedpop/types.rs | 2 +- 3 files changed, 111 insertions(+), 39 deletions(-) diff --git a/src/olaf/frost/mod.rs b/src/olaf/frost/mod.rs index e541dbc..f84c973 100644 --- a/src/olaf/frost/mod.rs +++ b/src/olaf/frost/mod.rs @@ -61,6 +61,7 @@ impl SigningKeypair { /// operation. /// /// [`commit`]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#name-round-one-commitment. + // TODO: remove randomness pub fn commit(&self, rng: &mut R) -> (SigningNonces, SigningCommitments) where R: CryptoRng + RngCore, diff --git a/src/olaf/frost/types.rs b/src/olaf/frost/types.rs index e01852d..f964584 100644 --- a/src/olaf/frost/types.rs +++ b/src/olaf/frost/types.rs @@ -21,7 +21,7 @@ use super::errors::{FROSTError, FROSTResult}; /// A participant's signature share, which the coordinator will aggregate with all other signer's /// shares into the joint signature. -#[derive(Clone)] +#[derive(Clone, PartialEq, Eq)] pub(super) struct SignatureShare { /// This participant's signature over the message. pub(super) share: Scalar, @@ -133,7 +133,7 @@ impl BindingFactorList { } /// A scalar that is a signing nonce. -#[derive(ZeroizeOnDrop)] +#[derive(Debug, Clone, ZeroizeOnDrop, PartialEq, Eq)] pub(super) struct Nonce(pub(super) Scalar); impl Nonce { @@ -166,6 +166,14 @@ impl Nonce { Self(transcript.challenge_scalar(b"nonce")) } + + fn to_bytes(&self) -> [u8; SCALAR_LENGTH] { + self.0.to_bytes() + } + + fn from_bytes(bytes: [u8; SCALAR_LENGTH]) -> Self { + Nonce(Scalar::from_bytes_mod_order(bytes)) + } } /// A group element that is a commitment to a signing nonce share. @@ -205,7 +213,7 @@ impl From<&Nonce> for NonceCommitment { /// Note that [`SigningNonces`] must be used *only once* for a signing /// operation; re-using nonces will result in leakage of a signer's long-lived /// signing key. -#[derive(ZeroizeOnDrop)] +#[derive(Debug, Clone, ZeroizeOnDrop, PartialEq, Eq)] pub struct SigningNonces { pub(super) hiding: Nonce, pub(super) binding: Nonce, @@ -232,6 +240,38 @@ impl SigningNonces { Self::from_nonces(hiding, binding) } + /// Serializes SigningNonces into bytes. + pub fn to_bytes(self) -> Vec { + let mut bytes = Vec::new(); + + bytes.extend(self.hiding.to_bytes()); + bytes.extend(self.binding.to_bytes()); + bytes.extend(self.commitments.to_bytes()); + + bytes + } + + /// Deserializes SigningNonces from bytes. + pub fn from_bytes(bytes: &[u8]) -> FROSTResult { + let mut cursor = 0; + + let mut hiding_bytes = [0; 32]; + hiding_bytes.copy_from_slice(&bytes[cursor..cursor + SCALAR_LENGTH]); + + let hiding = Nonce::from_bytes(hiding_bytes); + cursor += SCALAR_LENGTH; + + let mut binding_bytes = [0; 32]; + binding_bytes.copy_from_slice(&bytes[cursor..cursor + SCALAR_LENGTH]); + + let binding = Nonce::from_bytes(binding_bytes); + cursor += SCALAR_LENGTH; + + let commitments = SigningCommitments::from_bytes(&bytes[cursor..])?; + + Ok(Self { hiding, binding, commitments }) + } + /// Generates a new [`SigningNonces`] from a pair of [`Nonce`]. /// /// # Security @@ -263,7 +303,8 @@ impl SigningCommitments { Self { hiding, binding } } - fn to_bytes(self) -> [u8; COMPRESSED_RISTRETTO_LENGTH * 2] { + /// Serializes SigningCommitments into bytes. + pub fn to_bytes(self) -> [u8; COMPRESSED_RISTRETTO_LENGTH * 2] { let mut bytes = [0u8; COMPRESSED_RISTRETTO_LENGTH * 2]; let hiding_bytes = self.hiding.to_bytes(); @@ -275,7 +316,8 @@ impl SigningCommitments { bytes } - fn from_bytes(bytes: &[u8]) -> FROSTResult { + /// Deserializes SigningCommitments from bytes. + pub fn from_bytes(bytes: &[u8]) -> FROSTResult { let hiding = NonceCommitment::from_bytes(&bytes[..COMPRESSED_RISTRETTO_LENGTH])?; let binding = NonceCommitment::from_bytes(&bytes[COMPRESSED_RISTRETTO_LENGTH..])?; @@ -358,7 +400,7 @@ impl CommonData { } } -#[derive(Clone)] +#[derive(Clone, PartialEq, Eq)] pub(super) struct SignerData { pub(super) signature_share: SignatureShare, } @@ -384,6 +426,7 @@ impl SignerData { /// The signing package that each signer produces in the signing round of the FROST protocol and sends to the /// coordinator, which aggregates them into the final threshold signature. +#[derive(PartialEq, Eq)] pub struct SigningPackage { pub(super) signer_data: SignerData, pub(super) common_data: CommonData, @@ -459,22 +502,47 @@ impl GroupCommitment { #[cfg(test)] mod tests { use alloc::vec::Vec; - use curve25519_dalek::Scalar; use rand_core::OsRng; use crate::{ - olaf::{ - frost::types::{CommonData, SignerData}, - simplpedpop::AllMessage, - test_utils::generate_parameters, - GENERATOR, - }, + olaf::{simplpedpop::AllMessage, test_utils::generate_parameters}, Keypair, PublicKey, }; + use super::{SigningCommitments, SigningNonces, SigningPackage}; - use super::{NonceCommitment, SignatureShare, SigningCommitments, SigningPackage}; + #[test] + fn test_round1_serialization() { + let mut rng = OsRng; + let parameters = generate_parameters(); + let participants = parameters.participants as usize; + let threshold = parameters.threshold as usize; + + let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public).collect(); + + let mut all_messages = Vec::new(); + for i in 0..participants { + let message: AllMessage = keypairs[i] + .simplpedpop_contribute_all(threshold as u16, public_keys.clone()) + .unwrap(); + all_messages.push(message); + } + + let spp_output = keypairs[0].simplpedpop_recipient_all(&all_messages).unwrap(); + + let (signing_nonces, signing_commitments) = spp_output.1.commit(&mut rng); + + let nonces_bytes = signing_nonces.clone().to_bytes(); + let commitments_bytes = signing_commitments.clone().to_bytes(); + + let deserialized_nonces = SigningNonces::from_bytes(&nonces_bytes).unwrap(); + let deserialized_commitments = SigningCommitments::from_bytes(&commitments_bytes).unwrap(); + + assert_eq!(signing_nonces, deserialized_nonces); + assert_eq!(signing_commitments, deserialized_commitments); + } #[test] - fn test_signing_package_serialization() { + fn test_round2_serialization() { let mut rng = OsRng; let parameters = generate_parameters(); let participants = parameters.participants as usize; @@ -491,37 +559,40 @@ mod tests { all_messages.push(message); } - let spp_output = keypairs[0].simplpedpop_recipient_all(&all_messages).unwrap().0.spp_output; + let mut spp_outputs = Vec::new(); + + for kp in keypairs.iter() { + let spp_output = kp.simplpedpop_recipient_all(&all_messages).unwrap(); + spp_outputs.push(spp_output); + } - let signing_commitments = vec![ - SigningCommitments { - hiding: NonceCommitment(Scalar::random(&mut rng) * GENERATOR), - binding: NonceCommitment(Scalar::random(&mut rng) * GENERATOR), - }, - SigningCommitments { - hiding: NonceCommitment(Scalar::random(&mut rng) * GENERATOR), - binding: NonceCommitment(Scalar::random(&mut rng) * GENERATOR), - }, - ]; + let mut all_signing_commitments = Vec::new(); + let mut all_signing_nonces = Vec::new(); - let message = b"message".to_vec(); - let context = b"context".to_vec(); - let signature_share = SignatureShare { share: Scalar::random(&mut rng) }; + for spp_output in &spp_outputs { + let (signing_nonces, signing_commitments) = spp_output.1.commit(&mut rng); + all_signing_nonces.push(signing_nonces); + all_signing_commitments.push(signing_commitments); + } - let common_data = CommonData { message, context, signing_commitments, spp_output }; - let signer_data = SignerData { signature_share }; + let message = b"message"; + let context = b"context"; - let signing_package = - SigningPackage { signer_data: signer_data.clone(), common_data: common_data.clone() }; + let signing_package = spp_outputs[0] + .1 + .sign( + context.to_vec(), + message.to_vec(), + spp_outputs[0].0.spp_output.clone(), + all_signing_commitments.clone(), + &all_signing_nonces[0], + ) + .unwrap(); let signing_package_bytes = signing_package.to_bytes(); let deserialized_signing_package = SigningPackage::from_bytes(&signing_package_bytes).unwrap(); - assert!( - deserialized_signing_package.signer_data.signature_share.share - == signer_data.signature_share.share - ); - assert!(deserialized_signing_package.common_data == common_data); + assert!(deserialized_signing_package == signing_package); } } diff --git a/src/olaf/simplpedpop/types.rs b/src/olaf/simplpedpop/types.rs index fb3f071..e0ff0ac 100644 --- a/src/olaf/simplpedpop/types.rs +++ b/src/olaf/simplpedpop/types.rs @@ -60,7 +60,7 @@ impl SecretShare { .decrypt(nonce, &encrypted_secret_share.0[..]) .map_err(SPPError::DecryptionError)?; - let mut bytes = [0; 32]; + let mut bytes = [0; SCALAR_LENGTH]; bytes.copy_from_slice(&plaintext); Ok(SecretShare(Scalar::from_bytes_mod_order(bytes))) From 35518c62cadf38699f62680301be5456872d14a6 Mon Sep 17 00:00:00 2001 From: Fiono11 Date: Mon, 20 May 2024 15:47:22 +0100 Subject: [PATCH 50/60] Fixes --- src/olaf/mod.rs | 34 ++++++++++++- src/olaf/simplpedpop/errors.rs | 19 ++----- src/olaf/simplpedpop/mod.rs | 15 +----- src/olaf/simplpedpop/types.rs | 92 ++++++++++++++++------------------ 4 files changed, 84 insertions(+), 76 deletions(-) diff --git a/src/olaf/mod.rs b/src/olaf/mod.rs index 00f310e..54bf29d 100644 --- a/src/olaf/mod.rs +++ b/src/olaf/mod.rs @@ -7,7 +7,7 @@ pub mod simplpedpop; use curve25519_dalek::{constants::RISTRETTO_BASEPOINT_POINT, RistrettoPoint, Scalar}; use merlin::Transcript; use zeroize::ZeroizeOnDrop; -use crate::{context::SigningTranscript, Keypair, PublicKey}; +use crate::{context::SigningTranscript, Keypair, PublicKey, SignatureError, KEYPAIR_LENGTH}; pub(super) const MINIMUM_THRESHOLD: u16 = 2; pub(super) const GENERATOR: RistrettoPoint = RISTRETTO_BASEPOINT_POINT; @@ -26,6 +26,19 @@ pub struct VerifyingShare(pub(crate) PublicKey); #[derive(Clone, Debug, ZeroizeOnDrop)] pub struct SigningKeypair(pub(crate) Keypair); +impl SigningKeypair { + /// Serializes `SigningKeypair` to bytes. + pub fn to_bytes(&self) -> [u8; KEYPAIR_LENGTH] { + self.0.to_bytes() + } + + /// Deserializes a `SigningKeypair` from bytes. + pub fn from_bytes(bytes: &[u8]) -> Result { + let keypair = Keypair::from_bytes(bytes)?; + Ok(SigningKeypair(keypair)) + } +} + /// The identifier of a participant, which must be the same in the SimplPedPoP protocol and in the FROST protocol. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct Identifier(pub(crate) Scalar); @@ -39,3 +52,22 @@ impl Identifier { Identifier(pos.challenge_scalar(b"evaluation position")) } } + +#[cfg(test)] +pub(crate) mod test_utils { + use rand::{thread_rng, Rng}; + use crate::olaf::simplpedpop::Parameters; + + const MAXIMUM_PARTICIPANTS: u16 = 10; + const MINIMUM_PARTICIPANTS: u16 = 2; + + pub(crate) fn generate_parameters() -> Parameters { + use super::MINIMUM_THRESHOLD; + + let mut rng = thread_rng(); + let participants = rng.gen_range(MINIMUM_PARTICIPANTS..=MAXIMUM_PARTICIPANTS); + let threshold = rng.gen_range(MINIMUM_THRESHOLD..=participants); + + Parameters { participants, threshold } + } +} diff --git a/src/olaf/simplpedpop/errors.rs b/src/olaf/simplpedpop/errors.rs index 2e45a98..867a685 100644 --- a/src/olaf/simplpedpop/errors.rs +++ b/src/olaf/simplpedpop/errors.rs @@ -23,6 +23,10 @@ pub enum SPPError { InvalidThresholdPublicKey, /// Invalid signature. InvalidSignature(SignatureError), + /// Error deserializing signature. + ErrorDeserializingSignature(SignatureError), + /// Error deserializing proof of possession. + ErrorDeserializingProofOfPossession(SignatureError), /// Invalid coefficient commitment of the polynomial commitment. InvalidCoefficientCommitment, /// Invalid identifier. @@ -60,25 +64,12 @@ mod tests { use crate::olaf::simplpedpop::types::{ AllMessage, EncryptedSecretShare, CHACHA20POLY1305_LENGTH, RECIPIENTS_HASH_LENGTH, }; - use crate::olaf::simplpedpop::Parameters; - use crate::olaf::MINIMUM_THRESHOLD; + use crate::olaf::test_utils::generate_parameters; use crate::{Keypair, PublicKey}; use alloc::vec::Vec; use curve25519_dalek::ristretto::RistrettoPoint; use curve25519_dalek::traits::Identity; use merlin::Transcript; - use rand::Rng; - - const MAXIMUM_PARTICIPANTS: u16 = 10; - const MINIMUM_PARTICIPANTS: u16 = 2; - - fn generate_parameters() -> Parameters { - let mut rng = rand::thread_rng(); - let participants = rng.gen_range(MINIMUM_PARTICIPANTS..=MAXIMUM_PARTICIPANTS); - let threshold = rng.gen_range(MINIMUM_THRESHOLD..=participants); - - Parameters { participants, threshold } - } #[test] fn test_invalid_number_of_messages() { diff --git a/src/olaf/simplpedpop/mod.rs b/src/olaf/simplpedpop/mod.rs index 0677bba..ceb7398 100644 --- a/src/olaf/simplpedpop/mod.rs +++ b/src/olaf/simplpedpop/mod.rs @@ -302,24 +302,13 @@ impl Keypair { #[cfg(test)] mod tests { - use crate::olaf::simplpedpop::types::{AllMessage, Parameters}; - use crate::olaf::MINIMUM_THRESHOLD; + use crate::olaf::simplpedpop::types::AllMessage; + use crate::olaf::test_utils::generate_parameters; use crate::{Keypair, PublicKey}; use alloc::vec::Vec; - use rand::Rng; - const MAXIMUM_PARTICIPANTS: u16 = 10; - const MINIMUM_PARTICIPANTS: u16 = 2; const PROTOCOL_RUNS: usize = 1; - fn generate_parameters() -> Parameters { - let mut rng = rand::thread_rng(); - let participants = rng.gen_range(MINIMUM_PARTICIPANTS..=MAXIMUM_PARTICIPANTS); - let threshold = rng.gen_range(MINIMUM_THRESHOLD..=participants); - - Parameters { participants, threshold } - } - #[test] fn test_simplpedpop_protocol() { for _ in 0..PROTOCOL_RUNS { diff --git a/src/olaf/simplpedpop/types.rs b/src/olaf/simplpedpop/types.rs index 0e5f03d..fb3f071 100644 --- a/src/olaf/simplpedpop/types.rs +++ b/src/olaf/simplpedpop/types.rs @@ -23,7 +23,7 @@ use super::errors::{SPPError, SPPResult}; pub(super) const U16_LENGTH: usize = 2; pub(super) const ENCRYPTION_NONCE_LENGTH: usize = 12; pub(super) const RECIPIENTS_HASH_LENGTH: usize = 16; -pub(super) const CHACHA20POLY1305_LENGTH: usize = 64; +pub(super) const CHACHA20POLY1305_LENGTH: usize = 48; pub(super) const CHACHA20POLY1305_KEY_LENGTH: usize = 32; pub(super) const VEC_LENGTH: usize = 2; @@ -115,7 +115,6 @@ pub(crate) struct Parameters { } impl Parameters { - /// Create new parameters. pub(crate) fn generate(participants: u16, threshold: u16) -> Parameters { Parameters { participants, threshold } } @@ -141,7 +140,6 @@ impl Parameters { t.commit_bytes(b"participants", &self.participants.to_le_bytes()); } - /// Serializes `Parameters` into a byte array. pub(super) fn to_bytes(&self) -> [u8; U16_LENGTH * 2] { let mut bytes = [0u8; U16_LENGTH * 2]; bytes[0..U16_LENGTH].copy_from_slice(&self.participants.to_le_bytes()); @@ -149,7 +147,6 @@ impl Parameters { bytes } - /// Constructs `Parameters` from a byte array. pub(super) fn from_bytes(bytes: &[u8]) -> SPPResult { if bytes.len() != U16_LENGTH * 2 { return Err(SPPError::InvalidParameters); @@ -248,11 +245,11 @@ impl AllMessage { cursor += content.to_bytes().len(); let signature = Signature::from_bytes(&bytes[cursor..cursor + SIGNATURE_LENGTH]) - .map_err(SPPError::InvalidSignature)?; + .map_err(SPPError::ErrorDeserializingSignature)?; cursor += SIGNATURE_LENGTH; let proof_of_possession = Signature::from_bytes(&bytes[cursor..cursor + SIGNATURE_LENGTH]) - .map_err(SPPError::InvalidSignature)?; + .map_err(SPPError::ErrorDeserializingProofOfPossession)?; Ok(AllMessage { content, signature, proof_of_possession }) } @@ -342,7 +339,7 @@ impl MessageContent { let mut coefficients_commitments = Vec::with_capacity(participants as usize); - for _ in 0..participants { + for _ in 0..parameters.threshold { let point = CompressedRistretto::from_slice( &bytes[cursor..cursor + COMPRESSED_RISTRETTO_LENGTH], ) @@ -422,7 +419,7 @@ impl SPPOutputMessage { cursor = bytes.len() - SIGNATURE_LENGTH; let signature = Signature::from_bytes(&bytes[cursor..cursor + SIGNATURE_LENGTH]) - .map_err(SPPError::InvalidSignature)?; + .map_err(SPPError::ErrorDeserializingSignature)?; Ok(SPPOutputMessage { signer, spp_output, signature }) } @@ -532,7 +529,7 @@ impl SPPOutput { mod tests { use merlin::Transcript; use rand_core::OsRng; - use crate::{context::SigningTranscript, Keypair}; + use crate::{context::SigningTranscript, olaf::test_utils::generate_parameters, Keypair}; use super::*; use curve25519_dalek::RistrettoPoint; @@ -583,32 +580,15 @@ mod tests { #[test] fn test_serialize_deserialize_all_message() { - let sender = Keypair::generate(); - let encryption_nonce = [1u8; ENCRYPTION_NONCE_LENGTH]; - let parameters = Parameters { participants: 2, threshold: 2 }; - let recipients_hash = [2u8; RECIPIENTS_HASH_LENGTH]; - let coefficients_commitments = - vec![RistrettoPoint::random(&mut OsRng), RistrettoPoint::random(&mut OsRng)]; - let polynomial_commitment = PolynomialCommitment { coefficients_commitments }; - let encrypted_secret_shares = vec![ - EncryptedSecretShare(vec![1; CHACHA20POLY1305_LENGTH]), - EncryptedSecretShare(vec![1; CHACHA20POLY1305_LENGTH]), - ]; - let signature = sender.sign(Transcript::new(b"sig")); - let proof_of_possession = sender.sign(Transcript::new(b"pop")); - let ephemeral_key = PublicKey::from_point(RistrettoPoint::random(&mut OsRng)); + let parameters = generate_parameters(); - let message_content = MessageContent::new( - sender.public, - encryption_nonce, - parameters, - recipients_hash, - polynomial_commitment, - encrypted_secret_shares, - ephemeral_key, - ); + let keypairs: Vec = + (0..parameters.participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public).collect(); - let message = AllMessage::new(message_content, signature, proof_of_possession); + let message: AllMessage = keypairs[0] + .simplpedpop_contribute_all(parameters.threshold as u16, public_keys.clone()) + .unwrap(); let bytes = message.to_bytes(); @@ -619,6 +599,33 @@ mod tests { #[test] fn test_spp_output_message_serialization() { + let parameters = generate_parameters(); + let participants = parameters.participants as usize; + let threshold = parameters.threshold as usize; + + let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public).collect(); + + let mut all_messages = Vec::new(); + for i in 0..participants { + let message: AllMessage = keypairs[i] + .simplpedpop_contribute_all(threshold as u16, public_keys.clone()) + .unwrap(); + all_messages.push(message); + } + + let spp_output = keypairs[0].simplpedpop_recipient_all(&all_messages).unwrap(); + + let bytes = spp_output.0.to_bytes(); + + let deserialized_spp_output_message = + SPPOutputMessage::from_bytes(&bytes).expect("Deserialization failed"); + + assert_eq!(deserialized_spp_output_message, spp_output.0); + } + + #[test] + fn test_spp_output_message_verification() { let mut rng = OsRng; let group_public_key = RistrettoPoint::random(&mut rng); let verifying_keys = vec![ @@ -644,25 +651,14 @@ mod tests { }; let keypair = Keypair::generate(); - let signature = keypair.sign(Transcript::new(b"test")); + let mut transcript = Transcript::new(b"spp output"); + transcript.append_message(b"message", &spp_output.to_bytes()); + let signature = keypair.sign(transcript); let spp_output_message = SPPOutputMessage { signer: VerifyingShare(keypair.public), spp_output, signature }; - let bytes = spp_output_message.to_bytes(); - - let deserialized_spp_output_message = - SPPOutputMessage::from_bytes(&bytes).expect("Deserialization failed"); - - assert_eq!( - deserialized_spp_output_message.spp_output, spp_output_message.spp_output, - "Group public keys do not match" - ); - - assert_eq!( - deserialized_spp_output_message.signature, spp_output_message.signature, - "Signatures do not match" - ); + spp_output_message.verify_signature().unwrap() } #[test] From d8ca0ddfa0747866fb70cb2d25320b1cf9a0584b Mon Sep 17 00:00:00 2001 From: Fiono11 Date: Tue, 21 May 2024 19:17:17 +0100 Subject: [PATCH 51/60] Use system randomness in frost --- benches/olaf_benchmarks.rs | 5 ++--- src/olaf/frost/errors.rs | 18 +++++++++--------- src/olaf/frost/mod.rs | 28 +++++++++------------------- src/olaf/frost/types.rs | 13 ++----------- src/olaf/simplpedpop/types.rs | 3 +-- 5 files changed, 23 insertions(+), 44 deletions(-) diff --git a/benches/olaf_benchmarks.rs b/benches/olaf_benchmarks.rs index 2e101f1..5da97d8 100644 --- a/benches/olaf_benchmarks.rs +++ b/benches/olaf_benchmarks.rs @@ -1,7 +1,6 @@ use criterion::criterion_main; mod olaf_benches { - use rand_core::OsRng; use criterion::{criterion_group, BenchmarkId, Criterion}; use schnorrkel::olaf::frost::aggregate; use schnorrkel::keys::{PublicKey, Keypair}; @@ -78,14 +77,14 @@ mod olaf_benches { let mut all_signing_nonces: Vec = Vec::new(); for spp_output in &spp_outputs { - let (signing_nonces, signing_commitments) = spp_output.1.commit(&mut OsRng); + let (signing_nonces, signing_commitments) = spp_output.1.commit(); all_signing_nonces.push(signing_nonces); all_signing_commitments.push(signing_commitments); } group.bench_function(BenchmarkId::new("round1", participants), |b| { b.iter(|| { - spp_outputs[0].1.commit(&mut OsRng); + spp_outputs[0].1.commit(); }) }); diff --git a/src/olaf/frost/errors.rs b/src/olaf/frost/errors.rs index a84bdec..e92b24d 100644 --- a/src/olaf/frost/errors.rs +++ b/src/olaf/frost/errors.rs @@ -115,7 +115,7 @@ mod tests { let mut all_signing_nonces = Vec::new(); for spp_output in &spp_outputs { - let (signing_nonces, signing_commitments) = spp_output.1.commit(&mut OsRng); + let (signing_nonces, signing_commitments) = spp_output.1.commit(); all_signing_nonces.push(signing_nonces); all_signing_commitments.push(signing_commitments); } @@ -190,7 +190,7 @@ mod tests { let mut all_signing_nonces = Vec::new(); for spp_output in &spp_outputs { - let (signing_nonces, signing_commitments) = spp_output.1.commit(&mut OsRng); + let (signing_nonces, signing_commitments) = spp_output.1.commit(); all_signing_nonces.push(signing_nonces); all_signing_commitments.push(signing_commitments); } @@ -258,7 +258,7 @@ mod tests { let mut all_signing_nonces = Vec::new(); for spp_output in &spp_outputs { - let (signing_nonces, signing_commitments) = spp_output.1.commit(&mut OsRng); + let (signing_nonces, signing_commitments) = spp_output.1.commit(); all_signing_nonces.push(signing_nonces); all_signing_commitments.push(signing_commitments); } @@ -326,7 +326,7 @@ mod tests { let mut all_signing_nonces = Vec::new(); for spp_output in &spp_outputs { - let (signing_nonces, signing_commitments) = spp_output.1.commit(&mut OsRng); + let (signing_nonces, signing_commitments) = spp_output.1.commit(); all_signing_nonces.push(signing_nonces); all_signing_commitments.push(signing_commitments); } @@ -390,7 +390,7 @@ mod tests { let mut all_signing_nonces = Vec::new(); for spp_output in &spp_outputs { - let (signing_nonces, signing_commitments) = spp_output.1.commit(&mut OsRng); + let (signing_nonces, signing_commitments) = spp_output.1.commit(); all_signing_nonces.push(signing_nonces); all_signing_commitments.push(signing_commitments); } @@ -447,7 +447,7 @@ mod tests { let mut all_signing_nonces = Vec::new(); for spp_output in &spp_outputs { - let (signing_nonces, signing_commitments) = spp_output.1.commit(&mut OsRng); + let (signing_nonces, signing_commitments) = spp_output.1.commit(); all_signing_nonces.push(signing_nonces); all_signing_commitments.push(signing_commitments); } @@ -504,7 +504,7 @@ mod tests { let mut all_signing_nonces = Vec::new(); for spp_output in &spp_outputs { - let (signing_nonces, signing_commitments) = spp_output.1.commit(&mut OsRng); + let (signing_nonces, signing_commitments) = spp_output.1.commit(); all_signing_nonces.push(signing_nonces); all_signing_commitments.push(signing_commitments); } @@ -564,7 +564,7 @@ mod tests { let mut all_signing_nonces = Vec::new(); for spp_output in &spp_outputs { - let (signing_nonces, signing_commitments) = spp_output.1.commit(&mut OsRng); + let (signing_nonces, signing_commitments) = spp_output.1.commit(); all_signing_nonces.push(signing_nonces); all_signing_commitments.push(signing_commitments); } @@ -621,7 +621,7 @@ mod tests { let mut all_signing_nonces = Vec::new(); for spp_output in &spp_outputs { - let (signing_nonces, signing_commitments) = spp_output.1.commit(&mut OsRng); + let (signing_nonces, signing_commitments) = spp_output.1.commit(); all_signing_nonces.push(signing_nonces); all_signing_commitments.push(signing_commitments); } diff --git a/src/olaf/frost/mod.rs b/src/olaf/frost/mod.rs index f84c973..1c32e51 100644 --- a/src/olaf/frost/mod.rs +++ b/src/olaf/frost/mod.rs @@ -10,7 +10,7 @@ pub use self::types::{SigningPackage, SigningNonces, SigningCommitments}; use self::types::{CommonData, SignatureShare, SignerData}; use alloc::vec::Vec; use curve25519_dalek::Scalar; -use rand_core::{CryptoRng, RngCore}; +use getrandom_or_panic::getrandom_or_panic; use crate::{ context::{SigningContext, SigningTranscript}, Signature, @@ -32,20 +32,14 @@ impl SigningKeypair { /// perform the first round. Batching entails generating more than one /// nonce/commitment pair at a time. Nonces should be stored in secret storage /// for later use, whereas the commitments are published. - pub fn preprocess( - &self, - num_nonces: u8, - rng: &mut R, - ) -> (Vec, Vec) - where - R: CryptoRng + RngCore, - { + pub fn preprocess(&self, num_nonces: u8) -> (Vec, Vec) { + let mut rng = getrandom_or_panic(); let mut signing_nonces: Vec = Vec::with_capacity(num_nonces as usize); let mut signing_commitments: Vec = Vec::with_capacity(num_nonces as usize); for _ in 0..num_nonces { - let nonces = SigningNonces::new(&self.0.secret, rng); + let nonces = SigningNonces::new(&self.0.secret, &mut rng); signing_commitments.push(SigningCommitments::from(&nonces)); signing_nonces.push(nonces); } @@ -62,11 +56,8 @@ impl SigningKeypair { /// /// [`commit`]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#name-round-one-commitment. // TODO: remove randomness - pub fn commit(&self, rng: &mut R) -> (SigningNonces, SigningCommitments) - where - R: CryptoRng + RngCore, - { - let (mut vec_signing_nonces, mut vec_signing_commitments) = self.preprocess(1, rng); + pub fn commit(&self) -> (SigningNonces, SigningCommitments) { + let (mut vec_signing_nonces, mut vec_signing_commitments) = self.preprocess(1); ( vec_signing_nonces.pop().expect("must have 1 element"), vec_signing_commitments.pop().expect("must have 1 element"), @@ -352,7 +343,6 @@ fn compute_challenge( #[cfg(test)] mod tests { use alloc::vec::Vec; - use rand_core::OsRng; use crate::{ olaf::{simplpedpop::AllMessage, test_utils::generate_parameters}, Keypair, PublicKey, @@ -392,7 +382,7 @@ mod tests { let mut all_signing_nonces = Vec::new(); for spp_output in &spp_outputs { - let (signing_nonces, signing_commitments) = spp_output.1.commit(&mut OsRng); + let (signing_nonces, signing_commitments) = spp_output.1.commit(); all_signing_nonces.push(signing_nonces); all_signing_commitments.push(signing_commitments); } @@ -452,7 +442,7 @@ mod tests { let mut all_signing_nonces = Vec::new(); for spp_output in &spp_outputs[..threshold] { - let (signing_nonces, signing_commitments) = spp_output.1.commit(&mut OsRng); + let (signing_nonces, signing_commitments) = spp_output.1.commit(); all_signing_nonces.push(signing_nonces); all_signing_commitments.push(signing_commitments); } @@ -508,7 +498,7 @@ mod tests { let mut all_commitments_map: Vec> = Vec::new(); for spp_output in &spp_outputs { - let (nonces, commitments) = spp_output.1.preprocess(NONCES, &mut OsRng); + let (nonces, commitments) = spp_output.1.preprocess(NONCES); all_nonces_map.push(nonces); all_commitments_map.push(commitments); diff --git a/src/olaf/frost/types.rs b/src/olaf/frost/types.rs index f964584..7fad36c 100644 --- a/src/olaf/frost/types.rs +++ b/src/olaf/frost/types.rs @@ -196,12 +196,6 @@ impl NonceCommitment { } } -impl From for NonceCommitment { - fn from(nonce: Nonce) -> Self { - From::from(&nonce) - } -} - impl From<&Nonce> for NonceCommitment { fn from(nonce: &Nonce) -> Self { Self(GENERATOR * nonce.0) @@ -502,7 +496,6 @@ impl GroupCommitment { #[cfg(test)] mod tests { use alloc::vec::Vec; - use rand_core::OsRng; use crate::{ olaf::{simplpedpop::AllMessage, test_utils::generate_parameters}, Keypair, PublicKey, @@ -511,7 +504,6 @@ mod tests { #[test] fn test_round1_serialization() { - let mut rng = OsRng; let parameters = generate_parameters(); let participants = parameters.participants as usize; let threshold = parameters.threshold as usize; @@ -529,7 +521,7 @@ mod tests { let spp_output = keypairs[0].simplpedpop_recipient_all(&all_messages).unwrap(); - let (signing_nonces, signing_commitments) = spp_output.1.commit(&mut rng); + let (signing_nonces, signing_commitments) = spp_output.1.commit(); let nonces_bytes = signing_nonces.clone().to_bytes(); let commitments_bytes = signing_commitments.clone().to_bytes(); @@ -543,7 +535,6 @@ mod tests { #[test] fn test_round2_serialization() { - let mut rng = OsRng; let parameters = generate_parameters(); let participants = parameters.participants as usize; let threshold = parameters.threshold as usize; @@ -570,7 +561,7 @@ mod tests { let mut all_signing_nonces = Vec::new(); for spp_output in &spp_outputs { - let (signing_nonces, signing_commitments) = spp_output.1.commit(&mut rng); + let (signing_nonces, signing_commitments) = spp_output.1.commit(); all_signing_nonces.push(signing_nonces); all_signing_commitments.push(signing_commitments); } diff --git a/src/olaf/simplpedpop/types.rs b/src/olaf/simplpedpop/types.rs index e0ff0ac..91822b4 100644 --- a/src/olaf/simplpedpop/types.rs +++ b/src/olaf/simplpedpop/types.rs @@ -295,8 +295,7 @@ impl MessageContent { bytes.extend(self.sender.to_bytes()); bytes.extend(&self.encryption_nonce); - bytes.extend(self.parameters.participants.to_le_bytes()); - bytes.extend(self.parameters.threshold.to_le_bytes()); + bytes.extend(self.parameters.to_bytes()); bytes.extend(&self.recipients_hash); for point in &self.polynomial_commitment.coefficients_commitments { From 46b7d30e6d314026f46f2e5015099f9d613f40b9 Mon Sep 17 00:00:00 2001 From: Fiono11 Date: Sat, 1 Jun 2024 13:46:05 +0100 Subject: [PATCH 52/60] Small improvements --- src/olaf/frost/errors.rs | 4 ++-- src/olaf/simplpedpop/mod.rs | 10 ++++++++-- src/olaf/simplpedpop/types.rs | 3 +-- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/olaf/frost/errors.rs b/src/olaf/frost/errors.rs index e92b24d..ff47c7f 100644 --- a/src/olaf/frost/errors.rs +++ b/src/olaf/frost/errors.rs @@ -594,7 +594,7 @@ mod tests { } #[test] - fn test_incorrect_number_of_signing_commitments_error() { + fn test_invalid_number_of_signing_commitments_error() { let parameters = generate_parameters(); let participants = parameters.participants as usize; let threshold = parameters.threshold as usize; @@ -647,7 +647,7 @@ mod tests { FROSTError::InvalidNumberOfSigningCommitments => assert!(true), _ => { panic!( - "Expected FROSTError::IncorrectNumberOfSigningCommitments, but got {:?}", + "Expected FROSTError::InvalidNumberOfSigningCommitments, but got {:?}", e ) }, diff --git a/src/olaf/simplpedpop/mod.rs b/src/olaf/simplpedpop/mod.rs index ceb7398..6fb871d 100644 --- a/src/olaf/simplpedpop/mod.rs +++ b/src/olaf/simplpedpop/mod.rs @@ -317,9 +317,15 @@ mod tests { let threshold = parameters.threshold as usize; let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); - let public_keys: Vec = keypairs.iter().map(|kp| kp.public).collect(); + + let recipients_keypairs: Vec = + (0..participants).map(|_| Keypair::generate()).collect(); + + let public_keys: Vec = + recipients_keypairs.iter().map(|kp| kp.public).collect(); let mut all_messages = Vec::new(); + for i in 0..participants { let message: AllMessage = keypairs[i] .simplpedpop_contribute_all(threshold as u16, public_keys.clone()) @@ -329,7 +335,7 @@ mod tests { let mut spp_outputs = Vec::new(); - for kp in keypairs.iter() { + for kp in recipients_keypairs.iter() { let spp_output = kp.simplpedpop_recipient_all(&all_messages).unwrap(); spp_output.0.verify_signature().unwrap(); spp_outputs.push(spp_output); diff --git a/src/olaf/simplpedpop/types.rs b/src/olaf/simplpedpop/types.rs index 91822b4..8eebe6f 100644 --- a/src/olaf/simplpedpop/types.rs +++ b/src/olaf/simplpedpop/types.rs @@ -464,8 +464,7 @@ impl SPPOutput { bytes.extend(self.parameters.to_bytes()); - let compressed_public_key = self.threshold_public_key.0.as_compressed(); - bytes.extend(compressed_public_key.to_bytes().iter()); + bytes.extend(self.threshold_public_key.0.to_bytes()); let key_count = self.verifying_keys.len() as u16; bytes.extend(key_count.to_le_bytes()); From fddd00285f331d57e52161e9fc13d766eb68e0f2 Mon Sep 17 00:00:00 2001 From: Fiono11 Date: Wed, 5 Jun 2024 17:40:55 +0100 Subject: [PATCH 53/60] Make tpk content pub --- src/olaf/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/olaf/mod.rs b/src/olaf/mod.rs index 9cc1340..5629b5b 100644 --- a/src/olaf/mod.rs +++ b/src/olaf/mod.rs @@ -18,7 +18,7 @@ pub(crate) const SCALAR_LENGTH: usize = 32; /// The threshold public key generated in the SimplPedPoP protocol, used to validate the threshold signatures of the FROST protocol. #[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub struct ThresholdPublicKey(pub(crate) PublicKey); +pub struct ThresholdPublicKey(pub PublicKey); /// The verifying share of a participant generated in the SimplPedPoP protocol, used to verify its signatures shares in the FROST protocol. #[derive(Copy, Clone, Debug, PartialEq, Eq)] From b0741a92c20ebb6dd2e5ad35654105de7790e9af Mon Sep 17 00:00:00 2001 From: Jeff Burdges Date: Tue, 30 Jul 2024 17:48:58 +0200 Subject: [PATCH 54/60] Upgrade curve25519-dalek Appears this works everywhere --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 7797028..53a1eb6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ aead = { version = "0.5.2", default-features = false, optional = true } arrayref = { version = "0.3.7", default-features = false } # needs to match parity-scale-code which is "=0.7.0" arrayvec = { version = "0.7.4", default-features = false } -curve25519-dalek = { version = "4.1.0", default-features = false, features = [ +curve25519-dalek = { version = "4.1.3", default-features = false, features = [ "digest", "zeroize", "precomputed-tables", From 1cfc580291cc313d0aef84d5968596162adfaed7 Mon Sep 17 00:00:00 2001 From: Jeff Burdges Date: Tue, 30 Jul 2024 18:18:42 +0200 Subject: [PATCH 55/60] Make aead a default feature now --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 53a1eb6..1415b2d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -57,7 +57,7 @@ required-features = ["alloc", "aead"] harness = false [features] -default = ["std", "getrandom"] +default = ["std", "getrandom", "aead"] preaudit_deprecated = [] nightly = [] alloc = [ From 6829d16b4e8b23112319cc7c6247f0226f58a66c Mon Sep 17 00:00:00 2001 From: Jeff Burdges Date: Tue, 30 Jul 2024 18:27:58 +0200 Subject: [PATCH 56/60] Not FROST since removing 1-round version --- src/olaf/frost/errors.rs | 46 ++++++++++++++++++++-------------------- src/olaf/frost/mod.rs | 36 ++++++++++++++++++------------- src/olaf/frost/types.rs | 32 ++++++++++++++-------------- 3 files changed, 60 insertions(+), 54 deletions(-) diff --git a/src/olaf/frost/errors.rs b/src/olaf/frost/errors.rs index ff47c7f..fb97914 100644 --- a/src/olaf/frost/errors.rs +++ b/src/olaf/frost/errors.rs @@ -10,11 +10,11 @@ use crate::{ }; /// A result for the SimplPedPoP protocol. -pub type FROSTResult = Result; +pub type MultiSigResult = Result; /// An error ocurred during the execution of the SimplPedPoP protocol. #[derive(Debug)] -pub enum FROSTError { +pub enum MultiSigError { /// The number of signing commitments must be at least equal to the threshold. InvalidNumberOfSigningCommitments, /// The participant's signing commitment is missing. @@ -68,7 +68,7 @@ mod tests { }, Keypair, PublicKey, }; - use super::FROSTError; + use super::MultiSigError; #[test] fn test_empty_signing_packages() { @@ -79,9 +79,9 @@ mod tests { match result { Ok(_) => panic!("Expected an error, but got Ok."), Err(e) => match e { - FROSTError::EmptySigningPackages => assert!(true), + MultiSigError::EmptySigningPackages => assert!(true), _ => { - panic!("Expected FROSTError::EmptySigningPackages, but got {:?}", e) + panic!("Expected MultiSigError::EmptySigningPackages, but got {:?}", e) }, }, } @@ -148,7 +148,7 @@ mod tests { match result { Ok(_) => panic!("Expected an error, but got Ok."), Err(e) => match e { - FROSTError::InvalidSignatureShare { culprit } => { + MultiSigError::InvalidSignatureShare { culprit } => { assert_eq!( culprit, vec![ @@ -157,7 +157,7 @@ mod tests { ] ); }, - _ => panic!("Expected FROSTError::InvalidSignatureShare, but got {:?}", e), + _ => panic!("Expected MultiSigError::InvalidSignatureShare, but got {:?}", e), }, } } @@ -222,9 +222,9 @@ mod tests { match result { Ok(_) => panic!("Expected an error, but got Ok."), Err(e) => match e { - FROSTError::MismatchedSignatureSharesAndSigningCommitments => assert!(true), + MultiSigError::MismatchedSignatureSharesAndSigningCommitments => assert!(true), _ => { - panic!("Expected FROSTError::MismatchedSignatureSharesAndSigningCommitments, but got {:?}", e) + panic!("Expected MultiSigError::MismatchedSignatureSharesAndSigningCommitments, but got {:?}", e) }, }, } @@ -290,9 +290,9 @@ mod tests { match result { Ok(_) => panic!("Expected an error, but got Ok."), Err(e) => match e { - FROSTError::MismatchedCommonData => assert!(true), + MultiSigError::MismatchedCommonData => assert!(true), _ => { - panic!("Expected FROSTError::MismatchedCommonData, but got {:?}", e) + panic!("Expected MultiSigError::MismatchedCommonData, but got {:?}", e) }, }, } @@ -354,9 +354,9 @@ mod tests { match result { Ok(_) => panic!("Expected an error, but got Ok."), Err(e) => match e { - FROSTError::InvalidNumberOfSigningPackages => assert!(true), + MultiSigError::InvalidNumberOfSigningPackages => assert!(true), _ => { - panic!("Expected FROSTError::InvalidNumberOfSigningPackages, but got {:?}", e) + panic!("Expected MultiSigError::InvalidNumberOfSigningPackages, but got {:?}", e) }, }, } @@ -411,9 +411,9 @@ mod tests { match result { Ok(_) => panic!("Expected an error, but got Ok."), Err(e) => match e { - FROSTError::InvalidOwnVerifyingShare => assert!(true), + MultiSigError::InvalidOwnVerifyingShare => assert!(true), _ => { - panic!("Expected FROSTError::InvalidOwnVerifyingShare, but got {:?}", e) + panic!("Expected MultiSigError::InvalidOwnVerifyingShare, but got {:?}", e) }, }, } @@ -468,9 +468,9 @@ mod tests { match result { Ok(_) => panic!("Expected an error, but got Ok."), Err(e) => match e { - FROSTError::IncorrectNumberOfVerifyingShares => assert!(true), + MultiSigError::IncorrectNumberOfVerifyingShares => assert!(true), _ => { - panic!("Expected FROSTError::IncorrectNumberOfVerifyingShares, but got {:?}", e) + panic!("Expected MultiSigError::IncorrectNumberOfVerifyingShares, but got {:?}", e) }, }, } @@ -528,9 +528,9 @@ mod tests { match result { Ok(_) => panic!("Expected an error, but got Ok."), Err(e) => match e { - FROSTError::MissingOwnSigningCommitment => assert!(true), + MultiSigError::MissingOwnSigningCommitment => assert!(true), _ => { - panic!("Expected FROSTError::MissingOwnSigningCommitment, but got {:?}", e) + panic!("Expected MultiSigError::MissingOwnSigningCommitment, but got {:?}", e) }, }, } @@ -585,9 +585,9 @@ mod tests { match result { Ok(_) => panic!("Expected an error, but got Ok."), Err(e) => match e { - FROSTError::IdentitySigningCommitment => assert!(true), + MultiSigError::IdentitySigningCommitment => assert!(true), _ => { - panic!("Expected FROSTError::IdentitySigningCommitment, but got {:?}", e) + panic!("Expected MultiSigError::IdentitySigningCommitment, but got {:?}", e) }, }, } @@ -644,10 +644,10 @@ mod tests { match result { Ok(_) => panic!("Expected an error, but got Ok."), Err(e) => match e { - FROSTError::InvalidNumberOfSigningCommitments => assert!(true), + MultiSigError::InvalidNumberOfSigningCommitments => assert!(true), _ => { panic!( - "Expected FROSTError::InvalidNumberOfSigningCommitments, but got {:?}", + "Expected MultiSigError::InvalidNumberOfSigningCommitments, but got {:?}", e ) }, diff --git a/src/olaf/frost/mod.rs b/src/olaf/frost/mod.rs index 1c32e51..0e5fdce 100644 --- a/src/olaf/frost/mod.rs +++ b/src/olaf/frost/mod.rs @@ -1,4 +1,10 @@ -//! Implementation of the FROST protocol (). +//! Two-nonce non-deterministic MultiSig +//! +//! Intentionally prohibits the dangerous precomputed nonces +//! for which FROST was designed, but otherwise similar to +//! 2-round non-deterministic MultiSig protocols like +//! FROST, MuSig2, DWMS, etc. +//! See https://eprint.iacr.org/2020/852 or others #![allow(non_snake_case)] #![allow(clippy::result_large_err)] @@ -16,7 +22,7 @@ use crate::{ Signature, }; use self::{ - errors::{FROSTError, FROSTResult}, + errors::{MultiSigError, MultiSigResult}, types::{BindingFactor, BindingFactorList, GroupCommitment}, }; use super::{simplpedpop::SPPOutput, Identifier, SigningKeypair, ThresholdPublicKey, VerifyingShare}; @@ -83,20 +89,20 @@ impl SigningKeypair { spp_output: SPPOutput, all_signing_commitments: Vec, signer_nonces: &SigningNonces, - ) -> FROSTResult { + ) -> MultiSigResult { let threshold_public_key = &spp_output.threshold_public_key; let len = all_signing_commitments.len(); if len < spp_output.parameters.threshold as usize { - return Err(FROSTError::InvalidNumberOfSigningCommitments); + return Err(MultiSigError::InvalidNumberOfSigningCommitments); } if spp_output.verifying_keys.len() != len { - return Err(FROSTError::IncorrectNumberOfVerifyingShares); + return Err(MultiSigError::IncorrectNumberOfVerifyingShares); } if !all_signing_commitments.contains(&signer_nonces.commitments) { - return Err(FROSTError::MissingOwnSigningCommitment); + return Err(MultiSigError::MissingOwnSigningCommitment); } let mut identifiers = Vec::new(); @@ -116,11 +122,11 @@ impl SigningKeypair { } if !shares.contains(&&own_verifying_share) { - return Err(FROSTError::InvalidOwnVerifyingShare); + return Err(MultiSigError::InvalidOwnVerifyingShare); } if all_signing_commitments.len() < spp_output.parameters.threshold as usize { - return Err(FROSTError::InvalidNumberOfSigningCommitments); + return Err(MultiSigError::InvalidNumberOfSigningCommitments); } let binding_factor_list: BindingFactorList = BindingFactorList::compute( @@ -229,15 +235,15 @@ pub(super) fn compute_lagrange_coefficient( /// signature, if the coordinator themselves is a signer and misbehaves, they /// can avoid that step. However, at worst, this results in a denial of /// service attack due to publishing an invalid signature. -pub fn aggregate(signing_packages: &[SigningPackage]) -> Result { +pub fn aggregate(signing_packages: &[SigningPackage]) -> Result { if signing_packages.is_empty() { - return Err(FROSTError::EmptySigningPackages); + return Err(MultiSigError::EmptySigningPackages); } let parameters = &signing_packages[0].common_data.spp_output.parameters; if signing_packages.len() < parameters.threshold as usize { - return Err(FROSTError::InvalidNumberOfSigningPackages); + return Err(MultiSigError::InvalidNumberOfSigningPackages); } let common_data = &signing_packages[0].common_data; @@ -250,14 +256,14 @@ pub fn aggregate(signing_packages: &[SigningPackage]) -> Result Result = spp_output.verifying_keys.iter().map(|x| x.0).collect(); @@ -317,7 +323,7 @@ pub fn aggregate(signing_packages: &[SigningPackage]) -> Result FROSTResult { + fn from_bytes(bytes: &[u8]) -> MultiSigResult { let mut share_bytes = [0; SCALAR_LENGTH]; share_bytes.copy_from_slice(&bytes[..SCALAR_LENGTH]); let share = scalar_from_canonical_bytes(share_bytes) - .ok_or(FROSTError::SignatureShareDeserializationError)?; + .ok_or(MultiSigError::SignatureShareDeserializationError)?; Ok(SignatureShare { share }) } @@ -186,11 +186,11 @@ impl NonceCommitment { } /// Deserializes the `NonceCommitment` from bytes. - fn from_bytes(bytes: &[u8]) -> FROSTResult { + fn from_bytes(bytes: &[u8]) -> MultiSigResult { let compressed = CompressedRistretto::from_slice(&bytes[..COMPRESSED_RISTRETTO_LENGTH]) - .map_err(FROSTError::DeserializationError)?; + .map_err(MultiSigError::DeserializationError)?; - let point = compressed.decompress().ok_or(FROSTError::InvalidNonceCommitment)?; + let point = compressed.decompress().ok_or(MultiSigError::InvalidNonceCommitment)?; Ok(NonceCommitment(point)) } @@ -246,7 +246,7 @@ impl SigningNonces { } /// Deserializes SigningNonces from bytes. - pub fn from_bytes(bytes: &[u8]) -> FROSTResult { + pub fn from_bytes(bytes: &[u8]) -> MultiSigResult { let mut cursor = 0; let mut hiding_bytes = [0; 32]; @@ -311,7 +311,7 @@ impl SigningCommitments { } /// Deserializes SigningCommitments from bytes. - pub fn from_bytes(bytes: &[u8]) -> FROSTResult { + pub fn from_bytes(bytes: &[u8]) -> MultiSigResult { let hiding = NonceCommitment::from_bytes(&bytes[..COMPRESSED_RISTRETTO_LENGTH])?; let binding = NonceCommitment::from_bytes(&bytes[COMPRESSED_RISTRETTO_LENGTH..])?; @@ -362,7 +362,7 @@ impl CommonData { } /// Deserializes CommonData from bytes. - fn from_bytes(bytes: &[u8]) -> FROSTResult { + fn from_bytes(bytes: &[u8]) -> MultiSigResult { let mut cursor = 0; let message_len = @@ -388,7 +388,7 @@ impl CommonData { } let spp_output = SPPOutput::from_bytes(&bytes[cursor..]) - .map_err(FROSTError::SPPOutputDeserializationError)?; + .map_err(MultiSigError::SPPOutputDeserializationError)?; Ok(CommonData { message, context, signing_commitments, spp_output }) } @@ -410,7 +410,7 @@ impl SignerData { } /// Deserializes SignerData from bytes. - pub fn from_bytes(bytes: &[u8]) -> FROSTResult { + pub fn from_bytes(bytes: &[u8]) -> MultiSigResult { let share_bytes = &bytes[..SCALAR_LENGTH]; let signature_share = SignatureShare::from_bytes(share_bytes)?; @@ -418,7 +418,7 @@ impl SignerData { } } -/// The signing package that each signer produces in the signing round of the FROST protocol and sends to the +/// The signing package that each signer produces in the signing round of the multi-signature protocol and sends to the /// coordinator, which aggregates them into the final threshold signature. #[derive(PartialEq, Eq)] pub struct SigningPackage { @@ -438,7 +438,7 @@ impl SigningPackage { } /// Deserializes SigningPackage from bytes. - pub fn from_bytes(bytes: &[u8]) -> FROSTResult { + pub fn from_bytes(bytes: &[u8]) -> MultiSigResult { let signer_data = SignerData::from_bytes(&bytes[..SCALAR_LENGTH])?; let common_data = CommonData::from_bytes(&bytes[SCALAR_LENGTH..])?; @@ -455,7 +455,7 @@ impl GroupCommitment { pub(super) fn compute( signing_commitments: &[SigningCommitments], binding_factor_list: &BindingFactorList, - ) -> Result { + ) -> Result { let identity = RistrettoPoint::identity(); let mut group_commitment = RistrettoPoint::identity(); @@ -471,7 +471,7 @@ impl GroupCommitment { // The following check prevents a party from accidentally revealing their share. // Note that the '&&' operator would be sufficient. if identity == commitment.binding.0 || identity == commitment.hiding.0 { - return Err(FROSTError::IdentitySigningCommitment); + return Err(MultiSigError::IdentitySigningCommitment); } let binding_factor = &binding_factor_list.0[i]; From f4d26d69bc11f5ef3916037beab859f596594817 Mon Sep 17 00:00:00 2001 From: Jeff Burdges Date: Tue, 30 Jul 2024 18:29:45 +0200 Subject: [PATCH 57/60] Again not FROST since removing 1-round version --- benches/olaf_benchmarks.rs | 10 +++++----- src/olaf/mod.rs | 4 ++-- src/olaf/{frost => multisig}/errors.rs | 4 ++-- src/olaf/{frost => multisig}/mod.rs | 0 src/olaf/{frost => multisig}/types.rs | 0 5 files changed, 9 insertions(+), 9 deletions(-) rename src/olaf/{frost => multisig}/errors.rs (99%) rename src/olaf/{frost => multisig}/mod.rs (100%) rename src/olaf/{frost => multisig}/types.rs (100%) diff --git a/benches/olaf_benchmarks.rs b/benches/olaf_benchmarks.rs index 5da97d8..2086d1f 100644 --- a/benches/olaf_benchmarks.rs +++ b/benches/olaf_benchmarks.rs @@ -2,9 +2,9 @@ use criterion::criterion_main; mod olaf_benches { use criterion::{criterion_group, BenchmarkId, Criterion}; - use schnorrkel::olaf::frost::aggregate; + use schnorrkel::olaf::multisig::aggregate; use schnorrkel::keys::{PublicKey, Keypair}; - use schnorrkel::olaf::frost::{SigningPackage, SigningNonces, SigningCommitments}; + use schnorrkel::olaf::multisig::{SigningPackage, SigningNonces, SigningCommitments}; use schnorrkel::olaf::simplpedpop::AllMessage; fn benchmark_simplpedpop(c: &mut Criterion) { @@ -46,8 +46,8 @@ mod olaf_benches { group.finish(); } - fn benchmark_frost(c: &mut Criterion) { - let mut group = c.benchmark_group("FROST"); + fn benchmark_multisig(c: &mut Criterion) { + let mut group = c.benchmark_group("multisig"); group.sample_size(10); @@ -138,7 +138,7 @@ mod olaf_benches { config = Criterion::default(); targets = benchmark_simplpedpop, - benchmark_frost, + benchmark_multisig, } } diff --git a/src/olaf/mod.rs b/src/olaf/mod.rs index 7a2b746..2d49927 100644 --- a/src/olaf/mod.rs +++ b/src/olaf/mod.rs @@ -4,8 +4,8 @@ /// Implementation of the SimplPedPoP protocol. pub mod simplpedpop; -/// Implementation of the FROST protocol. -pub mod frost; +/// Implementation of the two-round non-deterministic multisig protocol. +pub mod multisig; use curve25519_dalek::{constants::RISTRETTO_BASEPOINT_POINT, RistrettoPoint, Scalar}; use merlin::Transcript; diff --git a/src/olaf/frost/errors.rs b/src/olaf/multisig/errors.rs similarity index 99% rename from src/olaf/frost/errors.rs rename to src/olaf/multisig/errors.rs index fb97914..d279675 100644 --- a/src/olaf/frost/errors.rs +++ b/src/olaf/multisig/errors.rs @@ -1,4 +1,4 @@ -//! Errors of the FROST protocol. +//! Errors of the two-round threshold multisig protocol. use core::array::TryFromSliceError; @@ -57,7 +57,7 @@ mod tests { use rand_core::OsRng; use crate::{ olaf::{ - frost::{ + multisig::{ aggregate, types::{NonceCommitment, SigningCommitments}, SigningPackage, diff --git a/src/olaf/frost/mod.rs b/src/olaf/multisig/mod.rs similarity index 100% rename from src/olaf/frost/mod.rs rename to src/olaf/multisig/mod.rs diff --git a/src/olaf/frost/types.rs b/src/olaf/multisig/types.rs similarity index 100% rename from src/olaf/frost/types.rs rename to src/olaf/multisig/types.rs From 394059514f4d3f8d2548b2d5132f8befbce2a0b1 Mon Sep 17 00:00:00 2001 From: Jeff Burdges Date: Tue, 30 Jul 2024 19:08:41 +0200 Subject: [PATCH 58/60] Remove the FRO of FROST aka the dangerous 1-round We could expose this behind some HAZMAT feature I guess, not sure. --- src/olaf/multisig/mod.rs | 5 ++++- src/olaf/multisig/types.rs | 18 +++++++++++------- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/olaf/multisig/mod.rs b/src/olaf/multisig/mod.rs index 0e5fdce..2ef8582 100644 --- a/src/olaf/multisig/mod.rs +++ b/src/olaf/multisig/mod.rs @@ -38,7 +38,9 @@ impl SigningKeypair { /// perform the first round. Batching entails generating more than one /// nonce/commitment pair at a time. Nonces should be stored in secret storage /// for later use, whereas the commitments are published. - pub fn preprocess(&self, num_nonces: u8) -> (Vec, Vec) { + /// + /// TODO: Already made private, next remove Vec or make HAZMAT + fn preprocess(&self, num_nonces: u8) -> (Vec, Vec) { let mut rng = getrandom_or_panic(); let mut signing_nonces: Vec = Vec::with_capacity(num_nonces as usize); let mut signing_commitments: Vec = @@ -476,6 +478,7 @@ mod tests { aggregate(&signing_packages).unwrap(); } + // Test is likely HAZMAT #[test] fn test_preprocessing_frost_with_simplpedpop() { let parameters = generate_parameters(); diff --git a/src/olaf/multisig/types.rs b/src/olaf/multisig/types.rs index 01ca045..dbbf14d 100644 --- a/src/olaf/multisig/types.rs +++ b/src/olaf/multisig/types.rs @@ -133,7 +133,7 @@ impl BindingFactorList { } /// A scalar that is a signing nonce. -#[derive(Debug, Clone, ZeroizeOnDrop, PartialEq, Eq)] +#[derive(Debug, ZeroizeOnDrop, PartialEq, Eq)] pub(super) struct Nonce(pub(super) Scalar); impl Nonce { @@ -167,6 +167,7 @@ impl Nonce { Self(transcript.challenge_scalar(b"nonce")) } + /* HAZMAT fn to_bytes(&self) -> [u8; SCALAR_LENGTH] { self.0.to_bytes() } @@ -174,6 +175,7 @@ impl Nonce { fn from_bytes(bytes: [u8; SCALAR_LENGTH]) -> Self { Nonce(Scalar::from_bytes_mod_order(bytes)) } + */ } /// A group element that is a commitment to a signing nonce share. @@ -207,7 +209,7 @@ impl From<&Nonce> for NonceCommitment { /// Note that [`SigningNonces`] must be used *only once* for a signing /// operation; re-using nonces will result in leakage of a signer's long-lived /// signing key. -#[derive(Debug, Clone, ZeroizeOnDrop, PartialEq, Eq)] +#[derive(Debug, ZeroizeOnDrop, PartialEq, Eq)] pub struct SigningNonces { pub(super) hiding: Nonce, pub(super) binding: Nonce, @@ -234,6 +236,7 @@ impl SigningNonces { Self::from_nonces(hiding, binding) } + /* HAZMAT /// Serializes SigningNonces into bytes. pub fn to_bytes(self) -> Vec { let mut bytes = Vec::new(); @@ -265,6 +268,7 @@ impl SigningNonces { Ok(Self { hiding, binding, commitments }) } + */ /// Generates a new [`SigningNonces`] from a pair of [`Nonce`]. /// @@ -500,7 +504,7 @@ mod tests { olaf::{simplpedpop::AllMessage, test_utils::generate_parameters}, Keypair, PublicKey, }; - use super::{SigningCommitments, SigningNonces, SigningPackage}; + use super::{SigningCommitments, SigningPackage}; // SigningNonces #[test] fn test_round1_serialization() { @@ -521,15 +525,15 @@ mod tests { let spp_output = keypairs[0].simplpedpop_recipient_all(&all_messages).unwrap(); - let (signing_nonces, signing_commitments) = spp_output.1.commit(); + let (_signing_nonces, signing_commitments) = spp_output.1.commit(); - let nonces_bytes = signing_nonces.clone().to_bytes(); + // HAZMAT: let nonces_bytes = signing_nonces.clone().to_bytes(); let commitments_bytes = signing_commitments.clone().to_bytes(); - let deserialized_nonces = SigningNonces::from_bytes(&nonces_bytes).unwrap(); + // HAZMAT: let deserialized_nonces = SigningNonces::from_bytes(&nonces_bytes).unwrap(); let deserialized_commitments = SigningCommitments::from_bytes(&commitments_bytes).unwrap(); - assert_eq!(signing_nonces, deserialized_nonces); + // HAZMAT: assert_eq!(signing_nonces, deserialized_nonces); assert_eq!(signing_commitments, deserialized_commitments); } From fa7989bf98d2081bec4652a667af604c2a6929bd Mon Sep 17 00:00:00 2001 From: Jeff Burdges Date: Tue, 30 Jul 2024 19:24:41 +0200 Subject: [PATCH 59/60] rand_chacha fix maybe? --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 1415b2d..4c83dfc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,10 +37,10 @@ zeroize = { version = "1.6", default-features = false, features = [ "zeroize_derive", ] } chacha20poly1305 = { version = "0.10.1", default-features = false } +rand_chacha = { version = "0.3.1", default-features = false } [dev-dependencies] rand = "0.8.5" -rand_chacha = { version = "0.3.1", default-features = false } hex-literal = "0.4.1" sha3 = "0.10.8" bincode = "1.3.3" From bde258ec6ff35f8d9deefe44b780a87aa7aa75ad Mon Sep 17 00:00:00 2001 From: Jeff Burdges Date: Tue, 30 Jul 2024 19:26:51 +0200 Subject: [PATCH 60/60] Ahh maybe this --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index 4c83dfc..3aa6833 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,6 +58,7 @@ harness = false [features] default = ["std", "getrandom", "aead"] +rand_chacha = [] preaudit_deprecated = [] nightly = [] alloc = [