From b93d5ea4ecf401a2f021618722547ca07cd22a67 Mon Sep 17 00:00:00 2001 From: Fiono11 Date: Mon, 6 May 2024 11:33:35 +0100 Subject: [PATCH 01/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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 e1c24d1394bb2ee284427f56d06138ef9400abd9 Mon Sep 17 00:00:00 2001 From: Fiono11 Date: Thu, 9 May 2024 18:34:29 +0100 Subject: [PATCH 27/47] 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 28/47] 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 29/47] 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 30/47] 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 31/47] 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 32/47] 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 33/47] 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 34/47] 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 35/47] 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 36/47] 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 37/47] 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 38/47] 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 39/47] 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 20d539fdbc4aff6aa148e905261ce12af6f10d5e Mon Sep 17 00:00:00 2001 From: Fiono11 Date: Fri, 17 May 2024 15:59:25 +0100 Subject: [PATCH 40/47] 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 e5b1f7b15b778073b5babdc04e220b3b0fbd4e95 Mon Sep 17 00:00:00 2001 From: Fiono11 Date: Sat, 18 May 2024 11:06:40 +0100 Subject: [PATCH 41/47] 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 42/47] 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 86c8bee2d558ad183931ef6923aeb1fec0fc74ce Mon Sep 17 00:00:00 2001 From: Fiono11 Date: Mon, 20 May 2024 15:47:22 +0100 Subject: [PATCH 43/47] 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 44/47] 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 d8ca0ddfa0747866fb70cb2d25320b1cf9a0584b Mon Sep 17 00:00:00 2001 From: Fiono11 Date: Tue, 21 May 2024 19:17:17 +0100 Subject: [PATCH 45/47] 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 46/47] 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 47/47] 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)]