From b25c4e495130a3f5d56e8bc1e1cf0960b968eea5 Mon Sep 17 00:00:00 2001 From: Fiono11 Date: Wed, 22 May 2024 11:38:42 +0100 Subject: [PATCH] Implementation of Olaf --- curve25519-dalek/src/olaf/frost/errors.rs | 691 +++++++++++++++++ curve25519-dalek/src/olaf/frost/mod.rs | 611 +++++++++++++++ curve25519-dalek/src/olaf/frost/types.rs | 624 +++++++++++++++ curve25519-dalek/src/olaf/mod.rs | 94 +++ .../src/olaf/simplpedpop/errors.rs | 410 ++++++++++ curve25519-dalek/src/olaf/simplpedpop/mod.rs | 343 +++++++++ .../src/olaf/simplpedpop/types.rs | 713 ++++++++++++++++++ 7 files changed, 3486 insertions(+) create mode 100644 curve25519-dalek/src/olaf/frost/errors.rs create mode 100644 curve25519-dalek/src/olaf/frost/mod.rs create mode 100644 curve25519-dalek/src/olaf/frost/types.rs create mode 100644 curve25519-dalek/src/olaf/mod.rs create mode 100644 curve25519-dalek/src/olaf/simplpedpop/errors.rs create mode 100644 curve25519-dalek/src/olaf/simplpedpop/mod.rs create mode 100644 curve25519-dalek/src/olaf/simplpedpop/types.rs diff --git a/curve25519-dalek/src/olaf/frost/errors.rs b/curve25519-dalek/src/olaf/frost/errors.rs new file mode 100644 index 000000000..d128e9783 --- /dev/null +++ b/curve25519-dalek/src/olaf/frost/errors.rs @@ -0,0 +1,691 @@ +//! Errors of the FROST protocol. + +use core::array::TryFromSliceError; + +use alloc::vec::Vec; + +use crate::{ + olaf::{simplpedpop::errors::SPPError, VerifyingShare}, + SignatureError, +}; + +/// A result for the SimplPedPoP protocol. +pub type FROSTResult = Result; + +/// An error ocurred during the execution of the SimplPedPoP protocol. +#[derive(Debug)] +pub enum FROSTError { + /// The number of signing commitments must be at least equal to the threshold. + InvalidNumberOfSigningCommitments, + /// 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 signers. + IncorrectNumberOfVerifyingShares, + /// Error deserializing the signature share. + SignatureShareDeserializationError, + /// The signature share is invalid. + InvalidSignatureShare { + /// The verifying share(s) of the culprit(s). + culprit: Vec, + }, + /// 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, + /// 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, +} + +#[cfg(test)] +mod tests { + use super::FROSTError; + use crate::{ + olaf::{ + frost::{ + aggregate, + types::{NonceCommitment, SigningCommitments}, + SigningPackage, + }, + simplpedpop::AllMessage, + test_utils::generate_parameters, + SigningKeypair, GENERATOR, + }, + SigningKey, VerifyingKey, + }; + use alloc::vec::Vec; + use curve25519_dalek::{traits::Identity, EdwardsPoint, Scalar}; + use rand_core::OsRng; + + #[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 = generate_parameters(); + let participants = parameters.participants as usize; + let threshold = parameters.threshold as usize; + + let mut rng = OsRng; + let mut keypairs: Vec = (0..participants) + .map(|_| SigningKey::generate(&mut rng)) + .collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.verifying_key).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_mut() { + 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 rng); + 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( + message, + &spp_output.0.spp_output, + &all_signing_commitments, + &all_signing_nonces[i], + ) + .unwrap(); + + signing_packages.push(signing_package); + } + + 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); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + FROSTError::InvalidSignatureShare { culprit } => { + 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 + ), + }, + } + } + + #[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 mut rng = OsRng; + let mut keypairs: Vec = (0..participants) + .map(|_| SigningKey::generate(&mut rng)) + .collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.verifying_key).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_mut() { + 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 rng); + 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( + message, + &spp_output.0.spp_output, + &all_signing_commitments, + &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 mut rng = OsRng; + let mut keypairs: Vec = (0..participants) + .map(|_| SigningKey::generate(&mut rng)) + .collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.verifying_key).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_mut() { + 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 rng); + 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( + message, + &spp_output.0.spp_output, + &all_signing_commitments, + &all_signing_nonces[i], + ) + .unwrap(); + + signing_packages.push(signing_package); + } + + signing_packages[0].common_data.message = b"invalid_message".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 mut rng = OsRng; + let mut keypairs: Vec = (0..participants) + .map(|_| SigningKey::generate(&mut rng)) + .collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.verifying_key).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_mut() { + 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 rng); + 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( + message, + &spp_outputs[0].0.spp_output, + &all_signing_commitments, + &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 = generate_parameters(); + let participants = parameters.participants as usize; + let threshold = parameters.threshold as usize; + + let mut rng = OsRng; + let mut keypairs: Vec = (0..participants) + .map(|_| SigningKey::generate(&mut rng)) + .collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.verifying_key).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_mut() { + 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 rng); + all_signing_nonces.push(signing_nonces); + all_signing_commitments.push(signing_commitments); + } + + let message = b"message"; + let context = b"context"; + + spp_outputs[0].1 = SigningKeypair(SigningKey::generate(&mut rng)); + + let result = spp_outputs[0].1.sign( + message, + &spp_outputs[0].0.spp_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 = generate_parameters(); + let participants = parameters.participants as usize; + let threshold = parameters.threshold as usize; + + let mut rng = OsRng; + let mut keypairs: Vec = (0..participants) + .map(|_| SigningKey::generate(&mut rng)) + .collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.verifying_key).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_mut() { + 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 rng); + all_signing_nonces.push(signing_nonces); + all_signing_commitments.push(signing_commitments); + } + + let message = b"message"; + let context = b"context"; + + spp_outputs[0].0.spp_output.verifying_keys.pop(); + + let result = spp_outputs[0].1.sign( + message, + &spp_outputs[0].0.spp_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 = generate_parameters(); + let participants = parameters.participants as usize; + let threshold = parameters.threshold as usize; + + let mut rng = OsRng; + let mut keypairs: Vec = (0..participants) + .map(|_| SigningKey::generate(&mut rng)) + .collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.verifying_key).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_mut() { + 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 rng); + 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(Scalar::random(&mut OsRng) * GENERATOR), + binding: NonceCommitment(Scalar::random(&mut OsRng) * GENERATOR), + }; + + let result = spp_outputs[0].1.sign( + message, + &spp_outputs[0].0.spp_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 = generate_parameters(); + let participants = parameters.participants as usize; + let threshold = parameters.threshold as usize; + + let mut rng = OsRng; + let mut keypairs: Vec = (0..participants) + .map(|_| SigningKey::generate(&mut rng)) + .collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.verifying_key).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_mut() { + 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 rng); + 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(EdwardsPoint::identity()); + + let result = spp_outputs[0].1.sign( + message, + &spp_outputs[0].0.spp_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 = generate_parameters(); + let participants = parameters.participants as usize; + let threshold = parameters.threshold as usize; + + let mut rng = OsRng; + let mut keypairs: Vec = (0..participants) + .map(|_| SigningKey::generate(&mut rng)) + .collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.verifying_key).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_mut() { + 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 rng); + all_signing_nonces.push(signing_nonces); + all_signing_commitments.push(signing_commitments); + } + + let message = b"message"; + + let result = spp_outputs[0].1.sign( + message, + &spp_outputs[0].0.spp_output, + &all_signing_commitments + .into_iter() + .take(parameters.threshold as usize - 1) + .collect::>(), + &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::InvalidNumberOfSigningCommitments, but got {:?}", + e + ) + } + }, + } + } +} diff --git a/curve25519-dalek/src/olaf/frost/mod.rs b/curve25519-dalek/src/olaf/frost/mod.rs new file mode 100644 index 000000000..ecdacb7d1 --- /dev/null +++ b/curve25519-dalek/src/olaf/frost/mod.rs @@ -0,0 +1,611 @@ +//! Implementation of the FROST protocol (). + +#![allow(non_snake_case)] + +mod errors; +mod types; + +use self::{ + errors::{FROSTError, FROSTResult}, + types::{ + BindingFactor, BindingFactorList, CommonData, GroupCommitment, SignatureShare, SignerData, + SigningCommitments, SigningNonces, SigningPackage, + }, +}; +use super::{ + simplpedpop::SPPOutput, Identifier, SigningKeypair, ThresholdPublicKey, VerifyingShare, +}; +use alloc::vec::Vec; +use curve25519_dalek::Scalar; +use ed25519::{signature::Verifier, Signature}; +use merlin::Transcript; +use rand_core::{CryptoRng, RngCore}; +use sha2::{Digest, Sha512}; + +impl SigningKeypair { + /// 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( + &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.0.secret_key, rng); + signing_commitments.push(SigningCommitments::from(&nonces)); + signing_nonces.push(nonces); + } + + (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, 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 + /// 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, + message: &[u8], + spp_output: &SPPOutput, + all_signing_commitments: &[SigningCommitments], + signer_nonces: &SigningNonces, + ) -> FROSTResult { + let len = all_signing_commitments.len(); + + if len < spp_output.parameters.threshold as usize { + return Err(FROSTError::InvalidNumberOfSigningCommitments); + } + + if spp_output.verifying_keys.len() != len { + 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.verifying_key); + + for (i, (identifier, share)) in spp_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() < spp_output.parameters.threshold as usize { + return Err(FROSTError::InvalidNumberOfSigningCommitments); + } + + let binding_factor_list: BindingFactorList = BindingFactorList::compute( + all_signing_commitments, + &spp_output.threshold_public_key, + message, + &[], + ); + + let group_commitment = + 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 preimage = vec![]; + + preimage.extend_from_slice(group_commitment.0.compress().as_bytes()); + preimage.extend_from_slice(spp_output.threshold_public_key.0.as_bytes()); + preimage.extend_from_slice(&message); + + let challenge = hash_to_scalar(&[&preimage[..]]); + + let signature_share = self.compute_signature_share( + signer_nonces, + &binding_factor_list.0[index].1, + &lambda_i, + &challenge, + ); + + let signer_data = SignerData { signature_share }; + let common_data = CommonData { + message: message.to_vec(), + signing_commitments: all_signing_commitments.to_vec(), + spp_output: spp_output.clone(), + }; + + let signing_package = SigningPackage { + signer_data, + common_data, + }; + + Ok(signing_package) + } + + fn compute_signature_share( + &self, + signer_nonces: &SigningNonces, + binding_factor: &BindingFactor, + lambda_i: &Scalar, + challenge: &Scalar, + ) -> SignatureShare { + let z_share: Scalar = signer_nonces.hiding.0 + + (signer_nonces.binding.0 * binding_factor.0) + + (lambda_i * Scalar::from_canonical_bytes(self.0.secret_key).unwrap() * challenge); + + SignatureShare { share: z_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_packages: &[SigningPackage]) -> Result { + 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 common_data = &signing_packages[0].common_data; + let message = &common_data.message; + 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.common_data != common_data { + return Err(FROSTError::MismatchedCommonData); + } + + signature_shares.push(signing_package.signer_data.signature_share.clone()); + } + + if signature_shares.len() != signing_commitments.len() { + return Err(FROSTError::MismatchedSignatureSharesAndSigningCommitments); + } + + let binding_factor_list: BindingFactorList = + BindingFactorList::compute(signing_commitments, &threshold_public_key, message, &[]); + + let group_commitment = GroupCommitment::compute(signing_commitments, &binding_factor_list)?; + + let mut s = Scalar::ZERO; + + for signature_share in &signature_shares { + s += signature_share.share; + } + + let mut bytes = [0; 64]; + bytes[0..32].copy_from_slice(group_commitment.0.compress().as_bytes()); + bytes[32..].copy_from_slice(s.as_bytes()); + + let group_signature = Signature::from_bytes(&bytes); + + let verification_result = threshold_public_key + .0 + .verify(message, &group_signature) + .map_err(FROSTError::InvalidSignature); + + let identifiers: Vec = spp_output.verifying_keys.iter().map(|x| x.0).collect(); + + let verifying_shares: Vec = + 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 + // if the aggregate signature is valid (which should be the common case). + if verification_result.is_err() { + // Compute the per-message challenge. + let mut preimage = vec![]; + + preimage.extend_from_slice(group_commitment.0.compress().as_bytes()); + preimage.extend_from_slice(spp_output.threshold_public_key.0.as_bytes()); + preimage.extend_from_slice(&message); + + let challenge = hash_to_scalar(&[&preimage[..]]); + + // Verify the signature shares. + for (j, signature_share) in signature_shares.iter().enumerate() { + for (i, (identifier, verifying_share)) in spp_output.verifying_keys.iter().enumerate() { + let lambda_i = compute_lagrange_coefficient(&identifiers, None, *identifier); + + let binding_factor = &binding_factor_list.0.get(i).expect("This never fails because signature_shares.len() == signing_commitments.len().").1; + + let R_share = signing_commitments[j].to_group_commitment_share(binding_factor); + + if signature_share.verify(&R_share, verifying_share, lambda_i, &challenge) { + valid_shares.push(*verifying_share); + break; + } + } + } + + let mut invalid_shares = Vec::new(); + + for verifying_share in verifying_shares { + if !valid_shares.contains(&verifying_share) { + invalid_shares.push(verifying_share); + } + } + + return Err(FROSTError::InvalidSignatureShare { + culprit: invalid_shares, + }); + } + + Ok(group_signature) +} + +pub(super) fn hash_to_array(inputs: &[&[u8]]) -> [u8; 64] { + let mut h = Sha512::new(); + for i in inputs { + h.update(i); + } + let mut output = [0u8; 64]; + output.copy_from_slice(h.finalize().as_slice()); + output +} + +pub(super) fn hash_to_scalar(inputs: &[&[u8]]) -> Scalar { + let output = hash_to_array(inputs); + Scalar::from_bytes_mod_order_wide(&output) +} + +/// 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() +} + +#[cfg(test)] +mod tests { + use super::{ + aggregate, + types::{SigningCommitments, SigningNonces}, + }; + use crate::{ + olaf::{ + simplpedpop::{AllMessage, Parameters}, + test_utils::generate_parameters, + MINIMUM_THRESHOLD, + }, + SigningKey, VerifyingKey, + }; + use alloc::vec::Vec; + use rand::Rng; + use rand_core::OsRng; + + const NONCES: u8 = 10; + + #[test] + fn test_n_of_n_frost_with_simplpedpop() { + let mut rng = OsRng; + let parameters = generate_parameters(); + let participants = parameters.participants as usize; + let threshold = parameters.threshold as usize; + + let mut keypairs: Vec = (0..participants) + .map(|_| SigningKey::generate(&mut rng)) + .collect(); + let public_keys: Vec = + keypairs.iter_mut().map(|kp| kp.verifying_key).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 &mut keypairs { + 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"; + + for (i, spp_output) in spp_outputs.iter().enumerate() { + let signature_share = spp_output + .1 + .sign( + message, + &spp_output.0.spp_output, + &all_signing_commitments, + &all_signing_nonces[i], + ) + .unwrap(); + + signing_packages.push(signature_share); + } + + aggregate(&signing_packages).unwrap(); + } + + #[test] + fn test_t_of_n_frost_with_simplpedpop() { + let mut rng = OsRng; + let parameters = generate_parameters(); + let participants = parameters.participants as usize; + let threshold = parameters.threshold as usize; + + let mut keypairs: Vec = (0..participants) + .map(|_| SigningKey::generate(&mut rng)) + .collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.verifying_key).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 &mut keypairs { + 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); + } + + 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"; + + for (i, spp_output) in spp_outputs[..threshold].iter().enumerate() { + let signature_share = spp_output + .1 + .sign( + message, + &spp_output.0.spp_output, + &all_signing_commitments, + &all_signing_nonces[i], + ) + .unwrap(); + + signing_packages.push(signature_share); + } + + aggregate(&signing_packages).unwrap(); + } + + #[test] + fn test_preprocessing_frost_with_simplpedpop() { + let mut rng = OsRng; + let parameters = generate_parameters(); + let participants = parameters.participants as usize; + let threshold = parameters.threshold as usize; + + let mut keypairs: Vec = (0..participants) + .map(|_| SigningKey::generate(&mut rng)) + .collect(); + let public_keys: Vec = + keypairs.iter_mut().map(|kp| kp.verifying_key).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 &mut keypairs { + let spp_output = kp.simplpedpop_recipient_all(&all_messages).unwrap(); + 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(); + + 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); + } + + 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 spp_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 signing_packages = 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); + } + + for i in 0..NONCES { + let message = &messages[i as usize]; + + let commitments: Vec = commitments[i as usize].clone(); + + 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 + .1 + .sign( + &message, + &spp_output.0.spp_output, + &commitments, + nonces_to_use, + ) + .unwrap(); + + signing_packages.push(signature_share); + } + + aggregate(&signing_packages).unwrap(); + + signing_packages = Vec::new(); + } + } +} diff --git a/curve25519-dalek/src/olaf/frost/types.rs b/curve25519-dalek/src/olaf/frost/types.rs new file mode 100644 index 000000000..b349354ed --- /dev/null +++ b/curve25519-dalek/src/olaf/frost/types.rs @@ -0,0 +1,624 @@ +//! Internal types of the FROST protocol. + +use super::{ + errors::{FROSTError, FROSTResult}, + hash_to_array, hash_to_scalar, +}; +use crate::{ + olaf::{ + scalar_from_canonical_bytes, simplpedpop::SPPOutput, ThresholdPublicKey, VerifyingShare, + COMPRESSED_EDWARDS_LENGTH, GENERATOR, SCALAR_LENGTH, + }, + SecretKey, +}; +use alloc::vec::Vec; +use curve25519_dalek::{ + edwards::CompressedEdwardsY, + traits::{Identity, VartimeMultiscalarMul}, + EdwardsPoint, Scalar, +}; +use merlin::Transcript; +use rand_core::{CryptoRng, RngCore}; +use zeroize::ZeroizeOnDrop; + +/// A participant's signature share, which the coordinator will aggregate with all other signer's +/// shares into the joint signature. +#[derive(Clone, PartialEq, Eq)] +pub(super) struct SignatureShare { + /// This participant's signature over the message. + pub(super) share: Scalar, +} + +impl SignatureShare { + fn to_bytes(&self) -> [u8; SCALAR_LENGTH] { + self.share.to_bytes() + } + + 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, + group_commitment_share: &GroupCommitmentShare, + verifying_share: &VerifyingShare, + lambda_i: Scalar, + challenge: &Scalar, + ) -> bool { + (GENERATOR * self.share) + == (group_commitment_share.0 + (verifying_share.0.point * challenge * lambda_i)) + } +} + +pub(super) struct GroupCommitmentShare(pub(super) EdwardsPoint); + +/// 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); + +/// A list of binding factors and their associated identifiers. +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) + } + + pub(super) fn compute( + signing_commitments: &[SigningCommitments], + verifying_key: &ThresholdPublicKey, + message: &[u8], + additional_prefix: &[u8], + ) -> BindingFactorList { + let preimages = BindingFactorList::binding_factor_preimages( + signing_commitments, + verifying_key, + message, + additional_prefix, + ); + + BindingFactorList( + preimages + .iter() + .map(|(identifier, preimage)| { + let binding_factor = hash_to_scalar(&[preimage]); + (*identifier, BindingFactor(binding_factor)) + }) + .collect(), + ) + } + + fn binding_factor_preimages( + signing_commitments: &[SigningCommitments], + verifying_key: &ThresholdPublicKey, + message: &[u8], + additional_prefix: &[u8], + ) -> Vec<(u16, Vec)> { + let mut binding_factor_input_prefix = vec![]; + + // The length of a serialized verifying key of the same cipersuite does + // not change between runs of the protocol, so we don't need to hash to + // get a fixed length. + binding_factor_input_prefix.extend_from_slice(verifying_key.0.as_bytes()); + + // The message is hashed with H4 to force the variable-length message + // into a fixed-length byte string, same for hashing the variable-sized + // (between runs of the protocol) set of group commitments, but with H5. + binding_factor_input_prefix.extend_from_slice(hash_to_array(&[message]).as_ref()); + binding_factor_input_prefix.extend_from_slice( + hash_to_array(&[ + &BindingFactorList::encode_group_commitments(&signing_commitments)[..].as_ref(), + ]) + .as_ref(), + ); + binding_factor_input_prefix.extend_from_slice(additional_prefix); + + signing_commitments + .iter() + .enumerate() + .map(|(i, _)| { + let mut binding_factor_input = vec![]; + + binding_factor_input.extend_from_slice(&binding_factor_input_prefix); + binding_factor_input.extend_from_slice(&i.to_le_bytes()); + (i as u16, binding_factor_input) + }) + .collect() + } + + fn encode_group_commitments(signing_commitments: &[SigningCommitments]) -> Vec { + let mut bytes = vec![]; + + for item in signing_commitments { + bytes.extend_from_slice(item.hiding.0.compress().as_bytes()); + bytes.extend_from_slice(item.binding.0.compress().as_bytes()); + } + + bytes + } +} + +/// A scalar that is a signing nonce. +#[derive(Debug, Clone, ZeroizeOnDrop, PartialEq, Eq)] +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); + let mut buf = [0; 64]; + transcript.challenge_bytes(b"nonce", &mut buf); + let nonce = Scalar::from_bytes_mod_order_wide(&buf); + + Self(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. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(super) struct NonceCommitment(pub(super) EdwardsPoint); + +impl NonceCommitment { + fn to_bytes(self) -> [u8; COMPRESSED_EDWARDS_LENGTH] { + self.0.compress().to_bytes() + } + + /// Deserializes the `NonceCommitment` from bytes. + fn from_bytes(bytes: &[u8]) -> FROSTResult { + let compressed = CompressedEdwardsY::from_slice(&bytes[..COMPRESSED_EDWARDS_LENGTH]) + .map_err(FROSTError::DeserializationError)?; + + let point = compressed + .decompress() + .ok_or(FROSTError::InvalidNonceCommitment)?; + + Ok(NonceCommitment(point)) + } +} + +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, ZeroizeOnDrop, PartialEq, Eq)] +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) + } + + /// 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 + /// + /// 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. + 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 { + pub(super) hiding: NonceCommitment, + pub(super) binding: NonceCommitment, +} + +impl SigningCommitments { + /// Create new SigningCommitments + pub(super) fn new(hiding: NonceCommitment, binding: NonceCommitment) -> Self { + Self { hiding, binding } + } + + /// Serializes SigningCommitments into bytes. + pub fn to_bytes(self) -> [u8; COMPRESSED_EDWARDS_LENGTH * 2] { + let mut bytes = [0u8; COMPRESSED_EDWARDS_LENGTH * 2]; + + let hiding_bytes = self.hiding.to_bytes(); + let binding_bytes = self.binding.to_bytes(); + + bytes[..COMPRESSED_EDWARDS_LENGTH].copy_from_slice(&hiding_bytes); + bytes[COMPRESSED_EDWARDS_LENGTH..].copy_from_slice(&binding_bytes); + + bytes + } + + /// Deserializes SigningCommitments from bytes. + pub fn from_bytes(bytes: &[u8]) -> FROSTResult { + let hiding = NonceCommitment::from_bytes(&bytes[..COMPRESSED_EDWARDS_LENGTH])?; + let binding = NonceCommitment::from_bytes(&bytes[COMPRESSED_EDWARDS_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 { + fn from(nonces: &SigningNonces) -> Self { + nonces.commitments + } +} + +/// The product of all signers' individual commitments, published as part of the +/// final signature. +pub(super) struct GroupCommitment(pub(super) EdwardsPoint); + +impl GroupCommitment { + pub(super) fn compute( + signing_commitments: &[SigningCommitments], + binding_factor_list: &BindingFactorList, + ) -> Result { + let identity = EdwardsPoint::identity(); + + let mut group_commitment = EdwardsPoint::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: EdwardsPoint = + EdwardsPoint::vartime_multiscalar_mul(binding_scalars, binding_elements); + + group_commitment += accumulated_binding_commitment; + + Ok(GroupCommitment(group_commitment)) + } +} + +#[derive(Clone, PartialEq, Eq)] +pub(super) struct CommonData { + pub(super) message: Vec, + pub(super) signing_commitments: Vec, + pub(super) spp_output: SPPOutput, +} + +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()); + bytes.extend(&self.message); + + 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.spp_output.to_bytes()); + + bytes + } + + /// Deserializes CommonData from bytes. + 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 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 spp_output = SPPOutput::from_bytes(&bytes[cursor..]) + .map_err(FROSTError::SPPOutputDeserializationError)?; + + Ok(CommonData { + message, + signing_commitments, + spp_output, + }) + } +} + +#[derive(Clone, PartialEq, Eq)] +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. +#[derive(PartialEq, Eq)] +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, + }) + } +} + +#[cfg(test)] +mod tests { + use super::{SigningCommitments, SigningNonces, SigningPackage}; + use crate::{ + olaf::{simplpedpop::AllMessage, test_utils::generate_parameters}, + SigningKey, VerifyingKey, + }; + use alloc::vec::Vec; + use rand_core::OsRng; + + #[test] + fn test_round1_serialization() { + let parameters = generate_parameters(); + let participants = parameters.participants as usize; + let threshold = parameters.threshold as usize; + + let mut rng = OsRng; + let mut keypairs: Vec = (0..participants) + .map(|_| SigningKey::generate(&mut rng)) + .collect(); + let public_keys: Vec = + keypairs.iter_mut().map(|kp| kp.verifying_key).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_round2_serialization() { + let parameters = generate_parameters(); + let participants = parameters.participants as usize; + let threshold = parameters.threshold as usize; + + let mut rng = OsRng; + let mut keypairs: Vec = (0..participants) + .map(|_| SigningKey::generate(&mut rng)) + .collect(); + let public_keys: Vec = + keypairs.iter_mut().map(|kp| kp.verifying_key).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_mut() { + 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 rng); + all_signing_nonces.push(signing_nonces); + all_signing_commitments.push(signing_commitments); + } + + let message = b"message"; + + let signing_package = spp_outputs[0] + .1 + .sign( + message, + &spp_outputs[0].0.spp_output, + &all_signing_commitments, + &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 == signing_package); + } +} diff --git a/curve25519-dalek/src/olaf/mod.rs b/curve25519-dalek/src/olaf/mod.rs new file mode 100644 index 000000000..3df282e80 --- /dev/null +++ b/curve25519-dalek/src/olaf/mod.rs @@ -0,0 +1,94 @@ +//! Implementation of the Olaf protocol (), which is composed of the Distributed +//! Key Generation (DKG) protocol SimplPedPoP and the Threshold Signing protocol FROST. + +/// Implementation of the FROST protocol. +pub mod frost; +/// Implementation of the SimplPedPoP protocol. +pub mod simplpedpop; + +use crate::{SignatureError, SigningKey, VerifyingKey, KEYPAIR_LENGTH}; +use curve25519_dalek::{constants::ED25519_BASEPOINT_POINT, EdwardsPoint, Scalar}; +use merlin::Transcript; + +pub(super) const MINIMUM_THRESHOLD: u16 = 2; +pub(super) const GENERATOR: EdwardsPoint = ED25519_BASEPOINT_POINT; +pub(crate) const SCALAR_LENGTH: usize = 32; +pub(super) const COMPRESSED_EDWARDS_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) VerifyingKey); + +/// 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) VerifyingKey); + +/// 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) SigningKey); + +impl SigningKeypair { + /// Serializes `SigningKeypair` to bytes. + pub fn to_bytes(&self) -> [u8; KEYPAIR_LENGTH] { + self.0.to_keypair_bytes() + } + + /// Deserializes a `SigningKeypair` from bytes. + pub fn from_bytes(bytes: &[u8]) -> Result { + let mut keypair_bytes = [0; 64]; + keypair_bytes.copy_from_slice(bytes); + + let keypair = SigningKey::from_keypair_bytes(&keypair_bytes)?; + Ok(SigningKeypair(keypair)) + } +} + +/// The identifier of a participant, which is the same in the SimplPedPoP protocol and in the FROST protocol. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct Identifier(pub(crate) Scalar); + +impl Identifier { + pub(super) fn generate(recipients_hash: &[u8; 16], index: u16) -> Identifier { + let mut pos = Transcript::new(b"Identifier"); + pos.append_message(b"RecipientsHash", recipients_hash); + pos.append_message(b"i", &index.to_le_bytes()[..]); + + let mut buf = [0; 64]; + pos.challenge_bytes(b"identifier", &mut buf); + + Identifier(Scalar::from_bytes_mod_order_wide(&buf)) + } +} + +pub(crate) fn scalar_from_canonical_bytes(bytes: [u8; 32]) -> Option { + let key = Scalar::from_canonical_bytes(bytes); + + // Note: this is a `CtOption` so we have to do this to extract the value. + if bool::from(key.is_none()) { + return None; + } + + Some(key.unwrap()) +} + +#[cfg(test)] +pub(crate) mod test_utils { + use crate::olaf::simplpedpop::Parameters; + use rand::{thread_rng, Rng}; + + 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/curve25519-dalek/src/olaf/simplpedpop/errors.rs b/curve25519-dalek/src/olaf/simplpedpop/errors.rs new file mode 100644 index 000000000..577cd3f45 --- /dev/null +++ b/curve25519-dalek/src/olaf/simplpedpop/errors.rs @@ -0,0 +1,410 @@ +//! Errors of the Olaf protocol. + +use crate::SignatureError; +use core::array::TryFromSliceError; + +/// A result for the SimplPedPoP protocol. +pub type SPPResult = Result; + +/// An error ocurred during the execution of the SimplPedPoP protocol. +#[derive(Debug)] +pub enum SPPError { + /// Invalid parameters. + InvalidParameters, + /// Threshold cannot be greater than the number of participants. + ExcessiveThreshold, + /// Threshold must be at least 2. + InsufficientThreshold, + /// Number of participants is invalid. + InvalidNumberOfParticipants, + /// Invalid public key. + InvalidPublicKey(SignatureError), + /// Invalid group public key. + InvalidGroupPublicKey, + /// Invalid signature. + InvalidSignature(SignatureError), + /// Invalid coefficient commitment of the polynomial commitment. + InvalidCoefficientCommitment, + /// Invalid identifier. + InvalidIdentifier, + /// Invalid secret share. + InvalidSecretShare, + /// Deserialization Error. + DeserializationError(TryFromSliceError), + /// The parameters of all messages must be equal. + DifferentParameters, + /// The recipients hash of all messages must be equal. + DifferentRecipientsHash, + /// The number of messages should be 2 at least, which the minimum number of participants. + InvalidNumberOfMessages, + /// The number of coefficient commitments of the polynomial commitment must be equal to the threshold - 1. + IncorrectNumberOfCoefficientCommitments, + /// The number of encrypted shares per message must be equal to the number of participants. + IncorrectNumberOfEncryptedShares, + /// Decryption error when decrypting an encrypted secret share. + DecryptionError(chacha20poly1305::Error), + /// Encryption error when encrypting the secret share. + EncryptionError(chacha20poly1305::Error), +} + +#[cfg(test)] +mod tests { + use crate::olaf::simplpedpop::errors::SPPError; + use crate::olaf::simplpedpop::types::{ + AllMessage, EncryptedSecretShare, Parameters, CHACHA20POLY1305_LENGTH, + RECIPIENTS_HASH_LENGTH, + }; + use crate::olaf::{GENERATOR, MINIMUM_THRESHOLD}; + use crate::{SigningKey, VerifyingKey}; + use alloc::vec::Vec; + use curve25519_dalek::Scalar; + use ed25519::signature::Signer; + use rand::Rng; + use rand_core::OsRng; + + const MAXIMUM_PARTICIPANTS: u16 = 10; + const MINIMUM_PARTICIPANTS: u16 = 2; + + fn generate_parameters() -> Parameters { + let mut rng = rand::thread_rng(); + let participants = rng.gen_range(MINIMUM_PARTICIPANTS..=MAXIMUM_PARTICIPANTS); + let threshold = rng.gen_range(MINIMUM_THRESHOLD..=participants); + + Parameters { + participants, + threshold, + } + } + + #[test] + fn test_invalid_number_of_messages() { + let mut rng = OsRng; + let parameters = generate_parameters(); + let participants = parameters.participants; + let threshold = parameters.threshold; + + let mut keypairs: Vec = (0..participants) + .map(|_| SigningKey::generate(&mut rng)) + .collect(); + + let public_keys: Vec = keypairs + .iter_mut() + .map(|kp| kp.verifying_key.clone()) + .collect(); + + let mut messages: Vec = keypairs + .iter_mut() + .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 SPPError::InvalidNumberOfMessages, but got {:?}", + e + ) + } + }, + } + } + + #[test] + fn test_different_parameters() { + let parameters = generate_parameters(); + let participants = parameters.participants; + let threshold = parameters.threshold; + let mut rng = OsRng; + + let mut keypairs: Vec = (0..participants) + .map(|_| SigningKey::generate(&mut rng)) + .collect(); + let public_keys: Vec = + keypairs.iter().map(|kp| kp.verifying_key.clone()).collect(); + + let mut messages: Vec = Vec::new(); + for i in 0..participants { + let message = keypairs[i as usize] + .simplpedpop_contribute_all(threshold, public_keys.clone()) + .unwrap(); + messages.push(message); + } + + messages[1].content.parameters.threshold += 1; + + let result = keypairs[0].simplpedpop_recipient_all(&messages); + + // Check if the result is an error + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + SPPError::DifferentParameters => assert!(true), + _ => panic!("Expected SPPError::DifferentParameters, but got {:?}", e), + }, + } + } + + #[test] + fn test_different_recipients_hash() { + let parameters = generate_parameters(); + let participants = parameters.participants; + let threshold = parameters.threshold; + let mut rng = OsRng; + + let mut keypairs: Vec = (0..participants) + .map(|_| SigningKey::generate(&mut rng)) + .collect(); + let public_keys: Vec = + keypairs.iter().map(|kp| kp.verifying_key.clone()).collect(); + + let mut messages: Vec = keypairs + .iter_mut() + .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 SPPError::DifferentRecipientsHash, but got {:?}", + e + ) + } + }, + } + } + + #[test] + fn test_incorrect_number_of_commitments() { + let parameters = generate_parameters(); + let participants = parameters.participants; + let threshold = parameters.threshold; + let mut rng = OsRng; + + let mut keypairs: Vec = (0..participants) + .map(|_| SigningKey::generate(&mut rng)) + .collect(); + let public_keys: Vec = + keypairs.iter().map(|kp| kp.verifying_key.clone()).collect(); + + let mut messages: Vec = keypairs + .iter_mut() + .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 SPPError::IncorrectNumberOfCoefficientCommitments, but got {:?}", + e + ), + }, + } + } + + #[test] + fn test_incorrect_number_of_encrypted_shares() { + let parameters = generate_parameters(); + let participants = parameters.participants; + let threshold = parameters.threshold; + let mut rng = OsRng; + + let mut keypairs: Vec = (0..participants) + .map(|_| SigningKey::generate(&mut rng)) + .collect(); + let public_keys: Vec = + keypairs.iter().map(|kp| kp.verifying_key.clone()).collect(); + + let mut messages: Vec = keypairs + .iter_mut() + .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 SPPError::IncorrectNumberOfEncryptedShares, but got {:?}", + e + ), + }, + } + } + + #[test] + fn test_invalid_secret_share() { + let parameters = generate_parameters(); + let participants = parameters.participants; + let threshold = parameters.threshold; + let mut rng = OsRng; + + let mut keypairs: Vec = (0..participants) + .map(|_| SigningKey::generate(&mut rng)) + .collect(); + let public_keys: Vec = + keypairs.iter().map(|kp| kp.verifying_key.clone()).collect(); + + let mut messages: Vec = keypairs + .iter_mut() + .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 SPPError::InvalidSecretShare, but got {:?}", e), + }, + } + } + + #[test] + fn test_invalid_signature() { + let parameters = generate_parameters(); + let participants = parameters.participants; + let threshold = parameters.threshold; + let mut rng = OsRng; + + let mut keypairs: Vec = (0..participants) + .map(|_| SigningKey::generate(&mut rng)) + .collect(); + + let public_keys: Vec = + keypairs.iter().map(|kp| kp.verifying_key.clone()).collect(); + + let mut messages: Vec = keypairs + .iter_mut() + .map(|kp| { + kp.simplpedpop_contribute_all(threshold, public_keys.clone()) + .unwrap() + }) + .collect(); + + messages[1].signature = keypairs[1].sign(b"invalid"); + + 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 SPPError::InvalidSignature, but got {:?}", e), + }, + } + } + + #[test] + fn test_invalid_threshold() { + let mut rng = OsRng; + let mut keypair = SigningKey::generate(&mut rng); + + let result = keypair.simplpedpop_contribute_all( + 1, + vec![ + VerifyingKey::from(Scalar::random(&mut rng) * GENERATOR), + VerifyingKey::from(Scalar::random(&mut rng) * GENERATOR), + ], + ); + + 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 mut rng = OsRng; + let mut keypair = SigningKey::generate(&mut rng); + + let result = keypair.simplpedpop_contribute_all( + 2, + vec![VerifyingKey::from(Scalar::random(&mut rng) * GENERATOR)], + ); + + 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 mut rng = OsRng; + let mut keypair = SigningKey::generate(&mut rng); + + let result = keypair.simplpedpop_contribute_all( + 3, + vec![ + VerifyingKey::from(Scalar::random(&mut rng) * GENERATOR), + VerifyingKey::from(Scalar::random(&mut rng) * GENERATOR), + ], + ); + + 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/curve25519-dalek/src/olaf/simplpedpop/mod.rs b/curve25519-dalek/src/olaf/simplpedpop/mod.rs new file mode 100644 index 000000000..50b9c52e1 --- /dev/null +++ b/curve25519-dalek/src/olaf/simplpedpop/mod.rs @@ -0,0 +1,343 @@ +pub mod errors; +mod types; + +pub(crate) use self::types::SecretPolynomial; +pub use self::types::{AllMessage, Parameters, SPPOutput}; +use self::{ + errors::{SPPError, SPPResult}, + types::{ + MessageContent, PolynomialCommitment, SPPOutputMessage, SecretShare, + CHACHA20POLY1305_KEY_LENGTH, ENCRYPTION_NONCE_LENGTH, RECIPIENTS_HASH_LENGTH, + }, +}; +use crate::{olaf::GENERATOR, SigningKey, VerifyingKey}; +use alloc::vec::Vec; +use curve25519_dalek::{traits::Identity, EdwardsPoint, Scalar}; +use ed25519::signature::{SignerMut, Verifier}; +use merlin::Transcript; +use rand_core::{OsRng, RngCore}; + +use super::{Identifier, SigningKeypair, ThresholdPublicKey, VerifyingShare}; + +impl SigningKey { + /// First round of the SimplPedPoP protocol. + pub fn simplpedpop_contribute_all( + &mut self, + threshold: u16, + recipients: Vec, + ) -> SPPResult { + let parameters = Parameters::generate(recipients.len() as u16, threshold); + parameters.validate()?; + + let mut rng = OsRng; + + // 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); + + for recipient in &recipients { + recipients_transcript.append_message(b"recipient", recipient.as_bytes()); + } + + let mut recipients_hash = [0u8; RECIPIENTS_HASH_LENGTH]; + recipients_transcript.challenge_bytes(b"finalize", &mut recipients_hash); + + let secret_polynomial = + SecretPolynomial::generate(parameters.threshold as usize - 1, &mut rng); + + let mut encrypted_secret_shares = Vec::new(); + + let polynomial_commitment = PolynomialCommitment::commit(&secret_polynomial); + + let mut encryption_transcript = Transcript::new(b"Encryption"); + parameters.commit(&mut encryption_transcript); + encryption_transcript.append_message(b"contributor", self.verifying_key.as_bytes()); + + let mut encryption_nonce = [0u8; ENCRYPTION_NONCE_LENGTH]; + 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 = SigningKey::from_bytes(secret.as_bytes()); + //let ephemeral_key = SigningKey::generate(&mut rng); + + for i in 0..parameters.participants { + let identifier = Identifier::generate(&recipients_hash, i); + + let polynomial_evaluation = secret_polynomial.evaluate(&identifier.0); + + let secret_share = SecretShare(polynomial_evaluation); + + let recipient = recipients[i as usize]; + + let key_exchange: EdwardsPoint = secret * recipient.point; + + let mut encryption_transcript = encryption_transcript.clone(); + encryption_transcript.append_message(b"recipient", recipient.as_bytes()); + encryption_transcript + .append_message(b"key exchange", &key_exchange.compress().as_bytes()[..]); + encryption_transcript.append_message(b"i", &(i as usize).to_le_bytes()); + + let mut key_bytes = [0; CHACHA20POLY1305_KEY_LENGTH]; + encryption_transcript.challenge_bytes(b"key", &mut key_bytes); + + let encrypted_secret_share = secret_share.encrypt(&key_bytes, &encryption_nonce)?; + + encrypted_secret_shares.push(encrypted_secret_share); + } + + let message_content = MessageContent::new( + self.verifying_key, + encryption_nonce, + parameters, + recipients_hash, + polynomial_commitment, + encrypted_secret_shares, + ); + + let signature = self.sign(&message_content.to_bytes()); + + Ok(AllMessage::new(message_content, signature)) + } + + /// Second round of the SimplPedPoP protocol. + pub fn simplpedpop_recipient_all( + &mut self, + messages: &[AllMessage], + ) -> SPPResult<(SPPOutputMessage, SigningKeypair)> { + let first_message = &messages[0]; + let parameters = &first_message.content.parameters; + let threshold = parameters.threshold as usize; + let participants = parameters.participants as usize; + + first_message.content.parameters.validate()?; + + if messages.len() < participants { + return Err(SPPError::InvalidNumberOfMessages); + } + + let mut secret_shares = Vec::with_capacity(participants); + let mut verifying_keys = Vec::with_capacity(participants); + let mut senders = Vec::with_capacity(participants); + let mut signatures = Vec::with_capacity(participants); + let mut signatures_messages = Vec::with_capacity(participants); + let mut group_point = EdwardsPoint::identity(); + let mut total_secret_share = Scalar::ZERO; + let mut total_polynomial_commitment = PolynomialCommitment { + coefficients_commitments: vec![], + }; + let mut identifiers = Vec::new(); + + for (j, message) in messages.iter().enumerate() { + if &message.content.parameters != parameters { + return Err(SPPError::DifferentParameters); + } + if message.content.recipients_hash != first_message.content.recipients_hash { + return Err(SPPError::DifferentRecipientsHash); + } + + let content = &message.content; + let polynomial_commitment = &content.polynomial_commitment; + let encrypted_secret_shares = &content.encrypted_secret_shares; + + let secret_commitment: EdwardsPoint = *polynomial_commitment + .coefficients_commitments + .first() + .expect("This never fails because the minimum threshold is 2"); + + senders.push(content.sender); + signatures.push(message.signature); + + let mut encryption_transcript = Transcript::new(b"Encryption"); + parameters.commit(&mut encryption_transcript); + encryption_transcript.append_message(b"contributor", content.sender.as_bytes()); + encryption_transcript.append_message(b"nonce", &content.encryption_nonce); + + if polynomial_commitment.coefficients_commitments.len() != threshold { + return Err(SPPError::IncorrectNumberOfCoefficientCommitments); + } + + if encrypted_secret_shares.len() != participants { + return Err(SPPError::IncorrectNumberOfEncryptedShares); + } + + signatures_messages.push(content.to_bytes()); + + total_polynomial_commitment = PolynomialCommitment::sum_polynomial_commitments(&[ + &total_polynomial_commitment, + &polynomial_commitment, + ]); + + let key_exchange: EdwardsPoint = self.to_scalar() * secret_commitment; + + assert!(self.to_scalar() * GENERATOR == self.verifying_key.point); + + encryption_transcript.append_message(b"recipient", self.verifying_key.as_bytes()); + encryption_transcript + .append_message(b"key exchange", &key_exchange.compress().as_bytes()[..]); + + let mut secret_share_found = false; + + for (i, encrypted_secret_share) in encrypted_secret_shares.iter().enumerate() { + let mut encryption_transcript = encryption_transcript.clone(); + + encryption_transcript.append_message(b"i", &i.to_le_bytes()); + + let mut key_bytes = [0; CHACHA20POLY1305_KEY_LENGTH]; + encryption_transcript.challenge_bytes(b"key", &mut key_bytes); + + if identifiers.len() != participants { + let identifier = + Identifier::generate(&first_message.content.recipients_hash, i as u16); + identifiers.push(identifier); + } + + if !secret_share_found { + if let Ok(secret_share) = + encrypted_secret_share.decrypt(&key_bytes, &content.encryption_nonce) + { + if secret_share.0 * GENERATOR + == polynomial_commitment.evaluate(&identifiers[i].0) + { + secret_shares.push(secret_share); + secret_share_found = true; + } + } + } + } + + total_secret_share += secret_shares.get(j).ok_or(SPPError::InvalidSecretShare)?.0; + group_point += secret_commitment; + + message + .content + .sender + .verify(&message.content.to_bytes(), &message.signature) + .map_err(SPPError::InvalidSignature)?; + } + + for id in &identifiers { + let evaluation = total_polynomial_commitment.evaluate(&id.0); + verifying_keys.push((*id, VerifyingShare(VerifyingKey::from(evaluation)))); + } + + let spp_output = SPPOutput::new( + parameters, + ThresholdPublicKey(VerifyingKey::from(group_point)), + verifying_keys, + ); + + let signature = self.sign(&spp_output.to_bytes()); + let spp_output = SPPOutputMessage::new(self.verifying_key, spp_output, signature); + + let mut nonce: [u8; 32] = [0u8; 32]; + OsRng.fill_bytes(&mut nonce); + + let signing_key = SigningKey { + secret_key: *total_secret_share.as_bytes(), + verifying_key: VerifyingKey::from(total_secret_share * GENERATOR), + }; + + Ok((spp_output, SigningKeypair(signing_key))) + } +} + +#[cfg(test)] +mod tests { + use crate::olaf::simplpedpop::types::{AllMessage, Parameters}; + use crate::olaf::{GENERATOR, MINIMUM_THRESHOLD}; + use crate::{SigningKey, VerifyingKey}; + use alloc::vec::Vec; + use curve25519_dalek::Scalar; + use rand::Rng; + use rand_core::OsRng; + + 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 mut keypairs: Vec = (0..participants) + .map(|_| SigningKey::generate(&mut OsRng)) + .collect(); + + let public_keys: Vec = + keypairs.iter().map(|kp| kp.verifying_key).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_mut() { + let spp_output = kp.simplpedpop_recipient_all(&all_messages).unwrap(); + spp_outputs.push(spp_output); + } + + // 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." + ); + + // 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.point, + (Scalar::from_canonical_bytes(spp_outputs[j].1 .0.secret_key).unwrap() + * GENERATOR), + "Verification of total secret shares failed!" + ); + } + } + } + } +} diff --git a/curve25519-dalek/src/olaf/simplpedpop/types.rs b/curve25519-dalek/src/olaf/simplpedpop/types.rs new file mode 100644 index 000000000..72853fdfa --- /dev/null +++ b/curve25519-dalek/src/olaf/simplpedpop/types.rs @@ -0,0 +1,713 @@ +//! SimplPedPoP types. + +use super::{ + errors::{SPPError, SPPResult}, + Identifier, ThresholdPublicKey, VerifyingShare, GENERATOR, +}; +use crate::{ + olaf::{scalar_from_canonical_bytes, MINIMUM_THRESHOLD}, + VerifyingKey, PUBLIC_KEY_LENGTH, SIGNATURE_LENGTH, +}; +use alloc::vec::Vec; +use chacha20poly1305::{aead::Aead, ChaCha20Poly1305, KeyInit, Nonce}; +use core::iter; +use curve25519_dalek::{edwards::CompressedEdwardsY, traits::Identity, EdwardsPoint, Scalar}; +use ed25519::Signature; +use merlin::Transcript; +use rand_core::{CryptoRng, RngCore}; +use zeroize::ZeroizeOnDrop; + +pub(super) const COMPRESSED_EDWARDS_LENGTH: usize = 32; +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 = 48; +pub(super) const CHACHA20POLY1305_KEY_LENGTH: usize = 32; +pub(super) const SCALAR_LENGTH: usize = 32; +pub(super) const U16_LENGTH: usize = 2; + +/// The parameters of a given execution of the SimplPedPoP protocol. +#[derive(Debug, 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(super) fn validate(&self) -> Result<(), SPPError> { + if self.threshold < MINIMUM_THRESHOLD { + return Err(SPPError::InsufficientThreshold); + } + + if self.participants < MINIMUM_THRESHOLD { + return Err(SPPError::InvalidNumberOfParticipants); + } + + if self.threshold > self.participants { + return Err(SPPError::ExcessiveThreshold); + } + + Ok(()) + } + + pub(super) fn commit(&self, t: &mut Transcript) { + t.append_message(b"threshold", &self.threshold.to_le_bytes()); + t.append_message(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()); + 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, + }) + } +} + +#[derive(ZeroizeOnDrop)] +pub(super) struct SecretShare(pub(super) Scalar); + +impl SecretShare { + pub(super) fn encrypt( + &self, + key: &[u8; CHACHA20POLY1305_KEY_LENGTH], + nonce: &[u8; ENCRYPTION_NONCE_LENGTH], + ) -> SPPResult { + let cipher = ChaCha20Poly1305::new(&(*key).into()); + + let nonce = Nonce::from_slice(&nonce[..]); + + let ciphertext: Vec = cipher + .encrypt(nonce, &self.0.to_bytes()[..]) + .map_err(SPPError::EncryptionError)?; + + Ok(EncryptedSecretShare(ciphertext)) + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct EncryptedSecretShare(pub(super) Vec); + +impl EncryptedSecretShare { + pub(super) fn decrypt( + &self, + key: &[u8; CHACHA20POLY1305_KEY_LENGTH], + nonce: &[u8; ENCRYPTION_NONCE_LENGTH], + ) -> SPPResult { + let cipher = ChaCha20Poly1305::new(&(*key).into()); + + let nonce = Nonce::from_slice(&nonce[..]); + + let plaintext = cipher + .decrypt(nonce, &self.0[..]) + .map_err(SPPError::DecryptionError)?; + + let mut bytes = [0; 32]; + bytes.copy_from_slice(&plaintext); + + Ok(SecretShare(Scalar::from_bytes_mod_order(bytes))) + } +} + +/// The secret polynomial of a participant chosen at randoma nd used to generate the secret shares of all the participants (including itself). +#[derive(ZeroizeOnDrop)] +pub(crate) struct SecretPolynomial { + pub(super) coefficients: Vec, +} + +impl SecretPolynomial { + pub(super) fn generate(degree: usize, rng: &mut R) -> Self { + let mut coefficients = Vec::with_capacity(degree + 1); + + let mut first = Scalar::random(rng); + while first == Scalar::ZERO { + first = Scalar::random(rng); + } + + coefficients.push(first); + coefficients.extend(iter::repeat_with(|| Scalar::random(rng)).take(degree)); + + SecretPolynomial { coefficients } + } + + pub(super) fn evaluate(&self, x: &Scalar) -> Scalar { + let mut value = *self + .coefficients + .last() + .expect("coefficients must have at least one element"); + + // 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. +#[derive(Debug, PartialEq, Eq)] +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) -> EdwardsPoint { + let i = identifier; + + let (_, result) = self.coefficients_commitments.iter().fold( + (Scalar::ONE, EdwardsPoint::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![EdwardsPoint::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 +/// 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, +} + +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 mut signature_bytes = [0; SIGNATURE_LENGTH]; + signature_bytes.copy_from_slice(&bytes[cursor..cursor + SIGNATURE_LENGTH]); + + let signature = Signature::from_bytes(&signature_bytes); + + Ok(AllMessage { content, signature }) + } +} + +/// The contents of the message destined to all participants. +#[derive(Debug, PartialEq, Eq)] +pub struct MessageContent { + pub(super) sender: VerifyingKey, + pub(super) encryption_nonce: [u8; ENCRYPTION_NONCE_LENGTH], + pub(super) parameters: Parameters, + pub(super) recipients_hash: [u8; RECIPIENTS_HASH_LENGTH], + pub(super) polynomial_commitment: PolynomialCommitment, + pub(super) encrypted_secret_shares: Vec, +} + +impl MessageContent { + /// Creates the content of the message. + pub fn new( + sender: VerifyingKey, + encryption_nonce: [u8; ENCRYPTION_NONCE_LENGTH], + parameters: Parameters, + recipients_hash: [u8; RECIPIENTS_HASH_LENGTH], + polynomial_commitment: PolynomialCommitment, + encrypted_secret_shares: Vec, + ) -> Self { + Self { + sender, + encryption_nonce, + parameters, + recipients_hash, + polynomial_commitment, + encrypted_secret_shares, + } + } + /// 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.to_bytes()); + bytes.extend(&self.recipients_hash); + + for point in &self.polynomial_commitment.coefficients_commitments { + bytes.extend(point.compress().to_bytes()); + } + + for ciphertext in &self.encrypted_secret_shares { + bytes.extend(ciphertext.0.clone()); + } + + bytes + } + + /// Deserialize MessageContent from bytes + pub fn from_bytes(bytes: &[u8]) -> Result { + let mut cursor = 0; + + let mut public_key_bytes = [0; PUBLIC_KEY_LENGTH]; + public_key_bytes.copy_from_slice(&bytes[cursor..cursor + PUBLIC_KEY_LENGTH]); + + let sender = + VerifyingKey::from_bytes(&public_key_bytes).map_err(SPPError::InvalidPublicKey)?; + cursor += PUBLIC_KEY_LENGTH; + + let encryption_nonce: [u8; ENCRYPTION_NONCE_LENGTH] = bytes + [cursor..cursor + ENCRYPTION_NONCE_LENGTH] + .try_into() + .map_err(SPPError::DeserializationError)?; + cursor += ENCRYPTION_NONCE_LENGTH; + + let parameters = Parameters::from_bytes(&bytes[cursor..cursor + U16_LENGTH * 2])?; + cursor += U16_LENGTH * 2; + + let participants = parameters.participants; + + let recipients_hash: [u8; RECIPIENTS_HASH_LENGTH] = bytes + [cursor..cursor + RECIPIENTS_HASH_LENGTH] + .try_into() + .map_err(SPPError::DeserializationError)?; + cursor += RECIPIENTS_HASH_LENGTH; + + let mut coefficients_commitments = Vec::with_capacity(participants as usize); + + for _ in 0..parameters.threshold { + let point = + CompressedEdwardsY::from_slice(&bytes[cursor..cursor + COMPRESSED_EDWARDS_LENGTH]) + .map_err(SPPError::DeserializationError)?; + + coefficients_commitments.push( + point + .decompress() + .ok_or(SPPError::InvalidCoefficientCommitment)?, + ); + + cursor += COMPRESSED_EDWARDS_LENGTH; + } + + let polynomial_commitment = PolynomialCommitment { + coefficients_commitments, + }; + + let mut encrypted_secret_shares = Vec::new(); + + for _ in 0..participants { + let ciphertext = bytes[cursor..cursor + CHACHA20POLY1305_LENGTH].to_vec(); + encrypted_secret_shares.push(EncryptedSecretShare(ciphertext)); + cursor += CHACHA20POLY1305_LENGTH; + } + + Ok(MessageContent { + sender, + encryption_nonce, + parameters, + recipients_hash, + polynomial_commitment, + encrypted_secret_shares, + }) + } +} + +/// The signed output of the SimplPedPoP protocol. +#[derive(Debug, PartialEq, Eq)] +pub struct SPPOutputMessage { + pub(super) sender: VerifyingKey, + pub spp_output: SPPOutput, + pub(super) signature: Signature, +} + +impl SPPOutputMessage { + /// Creates a signed SimplPedPoP output. + pub fn new(sender: VerifyingKey, content: SPPOutput, signature: Signature) -> Self { + Self { + sender, + spp_output: content, + signature, + } + } + + /// Serializes the SPPOutput 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.spp_output.to_bytes(); + bytes.extend(content_bytes); + + let signature_bytes = self.signature.to_bytes(); + bytes.extend(signature_bytes); + + bytes + } + + /// Deserializes the SPPOutput from bytes. + pub fn from_bytes(bytes: &[u8]) -> Result { + let mut cursor = 0; + + let mut pk_bytes = [0; PUBLIC_KEY_LENGTH]; + pk_bytes.copy_from_slice(&bytes[..PUBLIC_KEY_LENGTH]); + + let sender = VerifyingKey::from_bytes(&pk_bytes).map_err(SPPError::InvalidPublicKey)?; + cursor += PUBLIC_KEY_LENGTH; + + let content_bytes = &bytes[cursor..bytes.len() - SIGNATURE_LENGTH]; + let spp_output = SPPOutput::from_bytes(content_bytes)?; + + cursor = bytes.len() - SIGNATURE_LENGTH; + + let mut sig_bytes = [0; SIGNATURE_LENGTH]; + sig_bytes.copy_from_slice(&bytes[cursor..cursor + SIGNATURE_LENGTH]); + + let signature = Signature::from_bytes(&sig_bytes); + + Ok(SPPOutputMessage { + sender, + spp_output, + signature, + }) + } +} + +/// The content of the signed output of the SimplPedPoP protocol. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct SPPOutput { + pub(crate) parameters: Parameters, + pub threshold_public_key: ThresholdPublicKey, + pub verifying_keys: Vec<(Identifier, VerifyingShare)>, +} + +impl SPPOutput { + /// Creates the content of the SimplPedPoP output. + pub fn new( + parameters: &Parameters, + group_public_key: ThresholdPublicKey, + verifying_keys: Vec<(Identifier, VerifyingShare)>, + ) -> Self { + let parameters = Parameters::generate(parameters.participants, parameters.threshold); + + Self { + parameters, + threshold_public_key: group_public_key, + verifying_keys, + } + } + /// Serializes the SPPOutputContent into bytes. + pub fn to_bytes(&self) -> Vec { + let mut bytes = Vec::new(); + + bytes.extend(self.parameters.to_bytes()); + + let compressed_public_key = self.threshold_public_key.0.compressed; + bytes.extend(compressed_public_key.to_bytes().iter()); + + let key_count = self.verifying_keys.len() as u16; + bytes.extend(key_count.to_le_bytes()); + + for (id, key) in &self.verifying_keys { + bytes.extend(id.0.to_bytes()); + bytes.extend(key.0.to_bytes()); + } + + bytes + } + + /// Deserializes the SPPOutputContent from bytes. + 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 mut public_key_bytes = [0; PUBLIC_KEY_LENGTH]; + public_key_bytes.copy_from_slice(&bytes[cursor..cursor + PUBLIC_KEY_LENGTH]); + cursor += PUBLIC_KEY_LENGTH; + + let group_public_key = ThresholdPublicKey( + VerifyingKey::from_bytes(&public_key_bytes).map_err(SPPError::InvalidPublicKey)?, + ); + + cursor += VEC_LENGTH; + + let mut verifying_keys = Vec::new(); + + while cursor < bytes.len() { + let mut identifier_bytes = [0; SCALAR_LENGTH]; + identifier_bytes.copy_from_slice(&bytes[cursor..cursor + SCALAR_LENGTH]); + + let identifier = + scalar_from_canonical_bytes(identifier_bytes).ok_or(SPPError::InvalidIdentifier)?; + cursor += SCALAR_LENGTH; + + let mut vk_bytes = [0; PUBLIC_KEY_LENGTH]; + vk_bytes.copy_from_slice(&bytes[cursor..cursor + PUBLIC_KEY_LENGTH]); + cursor += PUBLIC_KEY_LENGTH; + + let key = VerifyingKey::from_bytes(&vk_bytes).map_err(SPPError::InvalidPublicKey)?; + verifying_keys.push((Identifier(identifier), VerifyingShare(key))); + } + + Ok(SPPOutput { + parameters, + threshold_public_key: group_public_key, + verifying_keys, + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{olaf::test_utils::generate_parameters, SigningKey}; + use merlin::Transcript; + use rand_core::OsRng; + + #[test] + fn test_serialize_deserialize_all_message() { + let parameters = generate_parameters(); + let participants = parameters.participants as usize; + let threshold = parameters.threshold as usize; + + let mut keypairs: Vec = (0..participants) + .map(|_| SigningKey::generate(&mut OsRng)) + .collect(); + + let public_keys: Vec = keypairs.iter().map(|kp| kp.verifying_key).collect(); + + let message: AllMessage = keypairs[0] + .simplpedpop_contribute_all(threshold as u16, public_keys.clone()) + .unwrap(); + + let bytes = message.to_bytes(); + + let deserialized_message = AllMessage::from_bytes(&bytes).expect("Failed to deserialize"); + + assert_eq!(message, deserialized_message); + } + + #[test] + fn test_spp_output_serialization() { + let parameters = generate_parameters(); + let participants = parameters.participants as usize; + let threshold = parameters.threshold as usize; + + let mut keypairs: Vec = (0..participants) + .map(|_| SigningKey::generate(&mut OsRng)) + .collect(); + + let public_keys: Vec = keypairs.iter().map(|kp| kp.verifying_key).collect(); + + let mut all_messages = Vec::new(); + for i in 0..participants { + let message: AllMessage = keypairs[i] + .simplpedpop_contribute_all(threshold as u16, public_keys.clone()) + .unwrap(); + all_messages.push(message); + } + + let spp_output = keypairs[0] + .simplpedpop_recipient_all(&all_messages) + .unwrap(); + + let bytes = spp_output.0.to_bytes(); + + let deserialized_spp_output_message = + SPPOutputMessage::from_bytes(&bytes).expect("Deserialization failed"); + + assert_eq!(deserialized_spp_output_message, spp_output.0); + } + + #[test] + fn test_encryption_decryption() { + let mut rng = OsRng; + let ephemeral_key = SigningKey::generate(&mut rng); + let recipient = SigningKey::generate(&mut rng); + let encryption_nonce = [1; ENCRYPTION_NONCE_LENGTH]; + let key_exchange = ephemeral_key.to_scalar() * recipient.verifying_key.point; + let secret_share = SecretShare(Scalar::random(&mut rng)); + let mut transcript = Transcript::new(b"encryption"); + transcript.append_message(b"key", &key_exchange.compress().as_bytes()[..]); + 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 { + 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" + ); + } +}