diff --git a/Cargo.toml b/Cargo.toml index 9943958..7797028 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ curve25519-dalek = { version = "4.1.0", default-features = false, features = [ "zeroize", "precomputed-tables", "legacy_compatibility", + "rand_core", ] } subtle = { version = "2.4.1", default-features = false } merlin = { version = "3.0.0", default-features = false } @@ -32,7 +33,10 @@ serde_bytes = { version = "0.11.5", default-features = false, optional = true } cfg-if = { version = "1.0.0", optional = true } sha2 = { version = "0.10.7", default-features = false } failure = { version = "0.1.8", default-features = false, optional = true } -zeroize = { version = "1.6", default-features = false, features = ["zeroize_derive"] } +zeroize = { version = "1.6", default-features = false, features = [ + "zeroize_derive", +] } +chacha20poly1305 = { version = "0.10.1", default-features = false } [dev-dependencies] rand = "0.8.5" @@ -47,17 +51,38 @@ serde_json = "1.0.68" name = "schnorr_benchmarks" harness = false +[[bench]] +name = "olaf_benchmarks" +required-features = ["alloc", "aead"] +harness = false + [features] default = ["std", "getrandom"] preaudit_deprecated = [] nightly = [] -alloc = ["curve25519-dalek/alloc", "rand_core/alloc", "getrandom_or_panic/alloc", "serde_bytes/alloc"] -std = ["alloc", "getrandom", "serde_bytes/std", "rand_core/std", "getrandom_or_panic/std"] +alloc = [ + "curve25519-dalek/alloc", + "rand_core/alloc", + "getrandom_or_panic/alloc", + "serde_bytes/alloc", +] +std = [ + "alloc", + "getrandom", + "serde_bytes/std", + "rand_core/std", + "getrandom_or_panic/std", + "chacha20poly1305/std", +] asm = ["sha2/asm"] serde = ["serde_crate", "serde_bytes", "cfg-if"] # We cannot make getrandom a direct dependency because rand_core makes # getrandom a feature name, which requires forwarding. -getrandom = ["rand_core/getrandom", "getrandom_or_panic/getrandom", "aead?/getrandom"] +getrandom = [ + "rand_core/getrandom", + "getrandom_or_panic/getrandom", + "aead?/getrandom", +] # We thus cannot forward the wasm-bindgen feature of getrandom, # but our consumers could depend upon getrandom and activate its # wasm-bindgen feature themselve, which works due to cargo features diff --git a/benches/olaf_benchmarks.rs b/benches/olaf_benchmarks.rs new file mode 100644 index 0000000..b178bee --- /dev/null +++ b/benches/olaf_benchmarks.rs @@ -0,0 +1,53 @@ +use criterion::criterion_main; + +mod olaf_benches { + use criterion::{criterion_group, BenchmarkId, Criterion}; + use schnorrkel::keys::{PublicKey, Keypair}; + use schnorrkel::olaf::simplpedpop::AllMessage; + + fn benchmark_simplpedpop(c: &mut Criterion) { + let mut group = c.benchmark_group("SimplPedPoP"); + + for &n in [3, 10, 100, 1000].iter() { + let participants = n; + let threshold = (n * 2 + 2) / 3; + + let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public).collect(); + + let mut all_messages: Vec = Vec::new(); + + for i in 0..participants { + let message = keypairs[i] + .simplpedpop_contribute_all(threshold as u16, public_keys.clone()) + .unwrap(); + all_messages.push(message); + } + + group.bench_function(BenchmarkId::new("round1", participants), |b| { + b.iter(|| { + keypairs[0] + .simplpedpop_contribute_all(threshold as u16, public_keys.clone()) + .unwrap(); + }) + }); + + group.bench_function(BenchmarkId::new("round2", participants), |b| { + b.iter(|| { + keypairs[0].simplpedpop_recipient_all(&all_messages).unwrap(); + }) + }); + } + + group.finish(); + } + + criterion_group! { + name = olaf_benches; + config = Criterion::default(); + targets = + benchmark_simplpedpop, + } +} + +criterion_main!(olaf_benches::olaf_benches); diff --git a/src/lib.rs b/src/lib.rs index a7e373b..5208751 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -248,6 +248,9 @@ pub mod derive; pub mod cert; pub mod errors; +#[cfg(all(feature = "alloc", feature = "aead"))] +pub mod olaf; + #[cfg(all(feature = "aead", feature = "getrandom"))] pub mod aead; diff --git a/src/olaf/mod.rs b/src/olaf/mod.rs new file mode 100644 index 0000000..54bf29d --- /dev/null +++ b/src/olaf/mod.rs @@ -0,0 +1,73 @@ +//! Implementation of the Olaf protocol (), which is composed of the Distributed +//! Key Generation (DKG) protocol SimplPedPoP and the Threshold Signing protocol FROST. + +/// Implementation of the SimplPedPoP protocol. +pub mod simplpedpop; + +use curve25519_dalek::{constants::RISTRETTO_BASEPOINT_POINT, RistrettoPoint, Scalar}; +use merlin::Transcript; +use zeroize::ZeroizeOnDrop; +use crate::{context::SigningTranscript, Keypair, PublicKey, SignatureError, KEYPAIR_LENGTH}; + +pub(super) const MINIMUM_THRESHOLD: u16 = 2; +pub(super) const GENERATOR: RistrettoPoint = RISTRETTO_BASEPOINT_POINT; +pub(super) const COMPRESSED_RISTRETTO_LENGTH: usize = 32; +pub(crate) const SCALAR_LENGTH: usize = 32; + +/// The threshold public key generated in the SimplPedPoP protocol, used to validate the threshold signatures of the FROST protocol. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct ThresholdPublicKey(pub(crate) PublicKey); + +/// The verifying share of a participant generated in the SimplPedPoP protocol, used to verify its signatures shares in the FROST protocol. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct VerifyingShare(pub(crate) PublicKey); + +/// The signing keypair of a participant generated in the SimplPedPoP protocol, used to produce its signatures shares in the FROST protocol. +#[derive(Clone, Debug, ZeroizeOnDrop)] +pub struct SigningKeypair(pub(crate) Keypair); + +impl SigningKeypair { + /// Serializes `SigningKeypair` to bytes. + pub fn to_bytes(&self) -> [u8; KEYPAIR_LENGTH] { + self.0.to_bytes() + } + + /// Deserializes a `SigningKeypair` from bytes. + pub fn from_bytes(bytes: &[u8]) -> Result { + let keypair = Keypair::from_bytes(bytes)?; + Ok(SigningKeypair(keypair)) + } +} + +/// The identifier of a participant, which must be the same in the SimplPedPoP protocol and in the FROST protocol. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct Identifier(pub(crate) Scalar); + +impl Identifier { + pub(super) fn generate(recipients_hash: &[u8; 16], index: u16) -> Identifier { + let mut pos = Transcript::new(b"Identifier"); + pos.append_message(b"RecipientsHash", recipients_hash); + pos.append_message(b"i", &index.to_le_bytes()[..]); + + Identifier(pos.challenge_scalar(b"evaluation position")) + } +} + +#[cfg(test)] +pub(crate) mod test_utils { + use rand::{thread_rng, Rng}; + use crate::olaf::simplpedpop::Parameters; + + const MAXIMUM_PARTICIPANTS: u16 = 10; + const MINIMUM_PARTICIPANTS: u16 = 2; + + pub(crate) fn generate_parameters() -> Parameters { + use super::MINIMUM_THRESHOLD; + + let mut rng = thread_rng(); + let participants = rng.gen_range(MINIMUM_PARTICIPANTS..=MAXIMUM_PARTICIPANTS); + let threshold = rng.gen_range(MINIMUM_THRESHOLD..=participants); + + Parameters { participants, threshold } + } +} diff --git a/src/olaf/simplpedpop/errors.rs b/src/olaf/simplpedpop/errors.rs new file mode 100644 index 0000000..867a685 --- /dev/null +++ b/src/olaf/simplpedpop/errors.rs @@ -0,0 +1,336 @@ +//! Errors of the SimplPedPoP protocol. + +use core::array::TryFromSliceError; +use crate::{PublicKey, SignatureError}; + +/// A result for the SimplPedPoP protocol. +pub type SPPResult = Result; + +/// An error ocurred during the execution of the SimplPedPoP protocol. +#[derive(Debug)] +pub enum SPPError { + /// Invalid parameters. + InvalidParameters, + /// Threshold cannot be greater than the number of participants. + ExcessiveThreshold, + /// Threshold must be at least 2. + InsufficientThreshold, + /// Number of participants is invalid. + InvalidNumberOfParticipants, + /// Invalid public key. + InvalidPublicKey(SignatureError), + /// Invalid threshold public key. + InvalidThresholdPublicKey, + /// Invalid signature. + InvalidSignature(SignatureError), + /// Error deserializing signature. + ErrorDeserializingSignature(SignatureError), + /// Error deserializing proof of possession. + ErrorDeserializingProofOfPossession(SignatureError), + /// Invalid coefficient commitment of the polynomial commitment. + InvalidCoefficientCommitment, + /// Invalid identifier. + InvalidIdentifier, + /// Invalid secret share. + InvalidSecretShare { + /// The sender of the invalid secret share. + culprit: PublicKey, + }, + /// Deserialization Error. + DeserializationError(TryFromSliceError), + /// The parameters of all messages must be equal. + DifferentParameters, + /// The recipients hash of all messages must be equal. + DifferentRecipientsHash, + /// The number of messages should be 2 at least, which the minimum number of participants. + InvalidNumberOfMessages, + /// The number of coefficient commitments of the polynomial commitment must be equal to the threshold - 1. + IncorrectNumberOfCoefficientCommitments, + /// The number of encrypted shares per message must be equal to the number of participants. + IncorrectNumberOfEncryptedShares, + /// Decryption error when decrypting an encrypted secret share. + DecryptionError(chacha20poly1305::Error), + /// Encryption error when encrypting the secret share. + EncryptionError(chacha20poly1305::Error), + /// Invalid Proof of Possession. + InvalidProofOfPossession(SignatureError), + /// The messages are empty. + EmptyMessages, +} + +#[cfg(test)] +mod tests { + use crate::olaf::simplpedpop::errors::SPPError; + use crate::olaf::simplpedpop::types::{ + AllMessage, EncryptedSecretShare, CHACHA20POLY1305_LENGTH, RECIPIENTS_HASH_LENGTH, + }; + use crate::olaf::test_utils::generate_parameters; + use crate::{Keypair, PublicKey}; + use alloc::vec::Vec; + use curve25519_dalek::ristretto::RistrettoPoint; + use curve25519_dalek::traits::Identity; + use merlin::Transcript; + + #[test] + fn test_invalid_number_of_messages() { + let parameters = generate_parameters(); + let participants = parameters.participants; + let threshold = parameters.threshold; + + let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); + + let mut messages: Vec = keypairs + .iter() + .map(|kp| kp.simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap()) + .collect(); + + messages.pop(); + + let result = keypairs[0].simplpedpop_recipient_all(&messages); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + SPPError::InvalidNumberOfMessages => assert!(true), + _ => { + panic!("Expected DKGError::InvalidNumberOfMessages, but got {:?}", e) + }, + }, + } + } + + #[test] + fn test_different_parameters() { + let parameters = generate_parameters(); + let participants = parameters.participants; + let threshold = parameters.threshold; + + let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); + + let mut messages: Vec = Vec::new(); + for i in 0..participants { + let message = keypairs[i as usize] + .simplpedpop_contribute_all(threshold, public_keys.clone()) + .unwrap(); + messages.push(message); + } + + messages[1].content.parameters.threshold += 1; + + let result = keypairs[0].simplpedpop_recipient_all(&messages); + + // Check if the result is an error + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + SPPError::DifferentParameters => assert!(true), + _ => panic!("Expected DKGError::DifferentParameters, but got {:?}", e), + }, + } + } + + #[test] + fn test_different_recipients_hash() { + let parameters = generate_parameters(); + let participants = parameters.participants; + let threshold = parameters.threshold; + + let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); + + let mut messages: Vec = keypairs + .iter() + .map(|kp| kp.simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap()) + .collect(); + + messages[1].content.recipients_hash = [1; RECIPIENTS_HASH_LENGTH]; + + let result = keypairs[0].simplpedpop_recipient_all(&messages); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + SPPError::DifferentRecipientsHash => assert!(true), + _ => { + panic!("Expected DKGError::DifferentRecipientsHash, but got {:?}", e) + }, + }, + } + } + + #[test] + fn test_incorrect_number_of_commitments() { + let parameters = generate_parameters(); + let participants = parameters.participants; + let threshold = parameters.threshold; + + let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); + + let mut messages: Vec = keypairs + .iter() + .map(|kp| kp.simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap()) + .collect(); + + messages[1].content.polynomial_commitment.coefficients_commitments.pop(); + + let result = keypairs[0].simplpedpop_recipient_all(&messages); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + SPPError::IncorrectNumberOfCoefficientCommitments => assert!(true), + _ => panic!( + "Expected DKGError::IncorrectNumberOfCoefficientCommitments, but got {:?}", + e + ), + }, + } + } + + #[test] + fn test_incorrect_number_of_encrypted_shares() { + let parameters = generate_parameters(); + let participants = parameters.participants; + let threshold = parameters.threshold; + + let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); + + let mut messages: Vec = keypairs + .iter() + .map(|kp| kp.simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap()) + .collect(); + + messages[1].content.encrypted_secret_shares.pop(); + + let result = keypairs[0].simplpedpop_recipient_all(&messages); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + SPPError::IncorrectNumberOfEncryptedShares => assert!(true), + _ => panic!("Expected DKGError::IncorrectNumberOfEncryptedShares, but got {:?}", e), + }, + } + } + + #[test] + fn test_invalid_secret_share() { + let parameters = generate_parameters(); + let participants = parameters.participants; + let threshold = parameters.threshold; + + let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); + + let mut messages: Vec = keypairs + .iter() + .map(|kp| kp.simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap()) + .collect(); + + messages[1].content.encrypted_secret_shares[0] = + EncryptedSecretShare(vec![1; CHACHA20POLY1305_LENGTH]); + + let result = keypairs[0].simplpedpop_recipient_all(&messages); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + SPPError::InvalidSecretShare { culprit } => { + assert_eq!(culprit, messages[1].content.sender); + }, + _ => panic!("Expected DKGError::InvalidSecretShare, but got {:?}", e), + }, + } + } + + #[test] + fn test_invalid_signature() { + let parameters = generate_parameters(); + let participants = parameters.participants; + let threshold = parameters.threshold; + + let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public.clone()).collect(); + + let mut messages: Vec = keypairs + .iter() + .map(|kp| kp.simplpedpop_contribute_all(threshold, public_keys.clone()).unwrap()) + .collect(); + + messages[1].signature = + keypairs[1].secret.sign(Transcript::new(b"invalid"), &keypairs[1].public); + + let result = keypairs[0].simplpedpop_recipient_all(&messages); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + SPPError::InvalidSignature(_) => assert!(true), + _ => panic!("Expected DKGError::InvalidSignature, but got {:?}", e), + }, + } + } + + #[test] + fn test_invalid_threshold() { + let keypair = Keypair::generate(); + let result = keypair.simplpedpop_contribute_all( + 1, + vec![ + PublicKey::from_point(RistrettoPoint::identity()), + PublicKey::from_point(RistrettoPoint::identity()), + ], + ); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + SPPError::InsufficientThreshold => assert!(true), + _ => panic!("Expected SPPError::InsufficientThreshold, but got {:?}", e), + }, + } + } + + #[test] + fn test_invalid_participants() { + let keypair = Keypair::generate(); + let result = keypair + .simplpedpop_contribute_all(2, vec![PublicKey::from_point(RistrettoPoint::identity())]); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + SPPError::InvalidNumberOfParticipants => { + assert!(true) + }, + _ => { + panic!("Expected SPPError::InvalidNumberOfParticipants, but got {:?}", e) + }, + }, + } + } + + #[test] + fn test_threshold_greater_than_participants() { + let keypair = Keypair::generate(); + let result = keypair.simplpedpop_contribute_all( + 3, + vec![ + PublicKey::from_point(RistrettoPoint::identity()), + PublicKey::from_point(RistrettoPoint::identity()), + ], + ); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => match e { + SPPError::ExcessiveThreshold => assert!(true), + _ => panic!("Expected SPPError::ExcessiveThreshold), but got {:?}", e), + }, + } + } +} diff --git a/src/olaf/simplpedpop/mod.rs b/src/olaf/simplpedpop/mod.rs new file mode 100644 index 0000000..ceb7398 --- /dev/null +++ b/src/olaf/simplpedpop/mod.rs @@ -0,0 +1,366 @@ +//! Implementation of the SimplPedPoP protocol (), a spp based on PedPoP, which in turn is based +//! on Pedersen's spp. All of them have as the fundamental building block the Shamir's Secret Sharing scheme. + +#![allow(clippy::result_large_err)] + +mod types; +pub mod errors; + +pub use self::types::{AllMessage, SPPOutputMessage, SPPOutput}; +pub(crate) use self::types::{PolynomialCommitment, MessageContent, Parameters}; +use alloc::vec::Vec; +use curve25519_dalek::{traits::Identity, RistrettoPoint, Scalar}; +use merlin::Transcript; +use rand_core::RngCore; +use crate::{ + context::SigningTranscript, verify_batch, Keypair, PublicKey, SecretKey, getrandom_or_panic, +}; +use self::{ + errors::{SPPError, SPPResult}, + types::{ + SecretPolynomial, SecretShare, CHACHA20POLY1305_KEY_LENGTH, ENCRYPTION_NONCE_LENGTH, + RECIPIENTS_HASH_LENGTH, + }, +}; +use super::{ThresholdPublicKey, Identifier, SigningKeypair, VerifyingShare, GENERATOR}; + +impl Keypair { + /// First round of the SimplPedPoP protocol. + /// + /// We do not recipients.sort() because the protocol is simpler + /// if we require that all contributions provide the list in + /// exactly the same order. + /// + /// Instead we create a kind of session id by hashing the list + /// provided, but we provide only hash to recipients, not the + /// full recipients list. + pub fn simplpedpop_contribute_all( + &self, + threshold: u16, + recipients: Vec, + ) -> SPPResult { + let parameters = Parameters::generate(recipients.len() as u16, threshold); + parameters.validate()?; + + let mut rng = getrandom_or_panic(); + + let mut recipients_transcript = Transcript::new(b"RecipientsHash"); + parameters.commit(&mut recipients_transcript); + + for recipient in &recipients { + recipients_transcript.commit_point(b"recipient", recipient.as_compressed()); + } + + let mut recipients_hash = [0u8; RECIPIENTS_HASH_LENGTH]; + recipients_transcript.challenge_bytes(b"finalize", &mut recipients_hash); + + let secret_polynomial = + SecretPolynomial::generate(parameters.threshold as usize - 1, &mut rng); + + let mut encrypted_secret_shares = Vec::new(); + + let polynomial_commitment = secret_polynomial.commit(); + + let mut encryption_transcript = merlin::Transcript::new(b"Encryption"); + parameters.commit(&mut encryption_transcript); + encryption_transcript.commit_point(b"contributor", self.public.as_compressed()); + + let mut encryption_nonce = [0u8; ENCRYPTION_NONCE_LENGTH]; + rng.fill_bytes(&mut encryption_nonce); + encryption_transcript.append_message(b"nonce", &encryption_nonce); + + let ephemeral_key = Keypair::generate(); + + for i in 0..parameters.participants { + let identifier = Identifier::generate(&recipients_hash, i); + + let polynomial_evaluation = secret_polynomial.evaluate(&identifier.0); + + let secret_share = SecretShare(polynomial_evaluation); + + let recipient = recipients[i as usize]; + + let key_exchange = ephemeral_key.secret.key * recipient.into_point(); + + let mut encryption_transcript = encryption_transcript.clone(); + encryption_transcript.commit_point(b"recipient", recipient.as_compressed()); + encryption_transcript.commit_point(b"key exchange", &key_exchange.compress()); + encryption_transcript.append_message(b"i", &(i as usize).to_le_bytes()); + + let mut key_bytes = [0; CHACHA20POLY1305_KEY_LENGTH]; + encryption_transcript.challenge_bytes(b"key", &mut key_bytes); + + let encrypted_secret_share = secret_share.encrypt(&key_bytes, &encryption_nonce)?; + + encrypted_secret_shares.push(encrypted_secret_share); + } + + let pk = &PublicKey::from_point( + *polynomial_commitment + .coefficients_commitments + .first() + .expect("This never fails because the minimum threshold is 2"), + ); + + let secret = *secret_polynomial + .coefficients + .first() + .expect("This never fails because the minimum threshold is 2"); + + let mut nonce: [u8; 32] = [0u8; 32]; + crate::getrandom_or_panic().fill_bytes(&mut nonce); + + let secret_key = SecretKey { key: secret, nonce }; + + let message_content = MessageContent::new( + self.public, + encryption_nonce, + parameters, + recipients_hash, + polynomial_commitment, + encrypted_secret_shares, + ephemeral_key.public, + ); + + let mut signature_transcript = Transcript::new(b"signature"); + signature_transcript.append_message(b"message_sig", &message_content.to_bytes()); + let signature = self.sign(signature_transcript); + + let mut pop_transcript = Transcript::new(b"pop"); + pop_transcript.append_message(b"message_pop", &message_content.to_bytes()); + let proof_of_possession = secret_key.sign(pop_transcript, pk); + + Ok(AllMessage::new(message_content, signature, proof_of_possession)) + } + + /// Second round of the SimplPedPoP protocol. + pub fn simplpedpop_recipient_all( + &self, + messages: &[AllMessage], + ) -> SPPResult<(SPPOutputMessage, SigningKeypair)> { + if messages.is_empty() { + return Err(SPPError::EmptyMessages); + } + + let first_message = &messages[0]; + let parameters = &first_message.content.parameters; + let threshold = parameters.threshold as usize; + let participants = parameters.participants as usize; + + first_message.content.parameters.validate()?; + + if messages.len() < participants { + return Err(SPPError::InvalidNumberOfMessages); + } + + let mut secret_shares = Vec::with_capacity(participants); + let mut verifying_keys = Vec::with_capacity(participants); + let mut senders = Vec::with_capacity(participants); + let mut signatures = Vec::with_capacity(participants); + let mut signatures_transcripts = Vec::with_capacity(participants); + let mut group_point = RistrettoPoint::identity(); + let mut total_secret_share = Scalar::ZERO; + let mut total_polynomial_commitment = + PolynomialCommitment { coefficients_commitments: vec![] }; + let mut identifiers = Vec::new(); + let mut public_keys = Vec::with_capacity(participants); + let mut proofs_of_possession = Vec::with_capacity(participants); + let mut pops_transcripts = Vec::with_capacity(participants); + + for (j, message) in messages.iter().enumerate() { + if &message.content.parameters != parameters { + return Err(SPPError::DifferentParameters); + } + if message.content.recipients_hash != first_message.content.recipients_hash { + return Err(SPPError::DifferentRecipientsHash); + } + + let content = &message.content; + let polynomial_commitment = &content.polynomial_commitment; + let encrypted_secret_shares = &content.encrypted_secret_shares; + + let public_key = PublicKey::from_point( + *polynomial_commitment + .coefficients_commitments + .first() + .expect("This never fails because the minimum threshold is 2"), + ); + public_keys.push(public_key); + proofs_of_possession.push(message.proof_of_possession); + + senders.push(content.sender); + signatures.push(message.signature); + + let mut encryption_transcript = Transcript::new(b"Encryption"); + parameters.commit(&mut encryption_transcript); + encryption_transcript.commit_point(b"contributor", content.sender.as_compressed()); + encryption_transcript.append_message(b"nonce", &content.encryption_nonce); + + if polynomial_commitment.coefficients_commitments.len() != threshold { + return Err(SPPError::IncorrectNumberOfCoefficientCommitments); + } + + if encrypted_secret_shares.len() != participants { + return Err(SPPError::IncorrectNumberOfEncryptedShares); + } + + let mut signature_transcript = Transcript::new(b"signature"); + signature_transcript.append_message(b"message_sig", &content.to_bytes()); + signatures_transcripts.push(signature_transcript); + + let mut pop_transcript = Transcript::new(b"pop"); + pop_transcript.append_message(b"message_pop", &content.to_bytes()); + pops_transcripts.push(pop_transcript); + + let secret_commitment = polynomial_commitment + .coefficients_commitments + .first() + .expect("This never fails because the minimum threshold is 2"); + + total_polynomial_commitment = PolynomialCommitment::sum_polynomial_commitments(&[ + &total_polynomial_commitment, + &polynomial_commitment, + ]); + + let key_exchange = self.secret.key * message.content.ephemeral_key.as_point(); + + encryption_transcript.commit_point(b"recipient", self.public.as_compressed()); + encryption_transcript.commit_point(b"key exchange", &key_exchange.compress()); + + let mut secret_share_found = false; + + for (i, encrypted_secret_share) in encrypted_secret_shares.iter().enumerate() { + let mut encryption_transcript = encryption_transcript.clone(); + + encryption_transcript.append_message(b"i", &i.to_le_bytes()); + + let mut key_bytes = [0; CHACHA20POLY1305_KEY_LENGTH]; + encryption_transcript.challenge_bytes(b"key", &mut key_bytes); + + if identifiers.len() != participants { + let identifier = + Identifier::generate(&first_message.content.recipients_hash, i as u16); + identifiers.push(identifier); + } + + if !secret_share_found { + if let Ok(secret_share) = SecretShare::decrypt( + encrypted_secret_share, + &key_bytes, + &content.encryption_nonce, + ) { + if secret_share.0 * GENERATOR + == polynomial_commitment.evaluate(&identifiers[i].0) + { + secret_shares.push(secret_share); + secret_share_found = true; + } + } + } + } + + total_secret_share += secret_shares + .get(j) + .ok_or(SPPError::InvalidSecretShare { culprit: message.content.sender })? + .0; + group_point += secret_commitment; + } + + verify_batch(&mut pops_transcripts, &proofs_of_possession, &public_keys, false) + .map_err(SPPError::InvalidProofOfPossession)?; + + verify_batch(&mut signatures_transcripts, &signatures, &senders, false) + .map_err(SPPError::InvalidSignature)?; + + for id in &identifiers { + let evaluation = total_polynomial_commitment.evaluate(&id.0); + verifying_keys.push((*id, VerifyingShare(PublicKey::from_point(evaluation)))); + } + + let spp_output = SPPOutput::new( + parameters, + ThresholdPublicKey(PublicKey::from_point(group_point)), + verifying_keys, + ); + + let mut spp_output_transcript = Transcript::new(b"spp output"); + spp_output_transcript.append_message(b"message", &spp_output.to_bytes()); + + let signature = self.sign(spp_output_transcript); + let spp_output = SPPOutputMessage::new(VerifyingShare(self.public), spp_output, signature); + + let mut nonce: [u8; 32] = [0u8; 32]; + getrandom_or_panic().fill_bytes(&mut nonce); + + let secret_key = SecretKey { key: total_secret_share, nonce }; + + let keypair = Keypair::from(secret_key); + + Ok((spp_output, SigningKeypair(keypair))) + } +} + +#[cfg(test)] +mod tests { + use crate::olaf::simplpedpop::types::AllMessage; + use crate::olaf::test_utils::generate_parameters; + use crate::{Keypair, PublicKey}; + use alloc::vec::Vec; + + const PROTOCOL_RUNS: usize = 1; + + #[test] + fn test_simplpedpop_protocol() { + for _ in 0..PROTOCOL_RUNS { + let parameters = generate_parameters(); + let participants = parameters.participants as usize; + let threshold = parameters.threshold as usize; + + let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public).collect(); + + let mut all_messages = Vec::new(); + for i in 0..participants { + let message: AllMessage = keypairs[i] + .simplpedpop_contribute_all(threshold as u16, public_keys.clone()) + .unwrap(); + all_messages.push(message); + } + + let mut spp_outputs = Vec::new(); + + for kp in keypairs.iter() { + let spp_output = kp.simplpedpop_recipient_all(&all_messages).unwrap(); + spp_output.0.verify_signature().unwrap(); + spp_outputs.push(spp_output); + } + + // Verify that all threshold_public_keys and verifying_keys are equal + assert!( + spp_outputs.windows(2).all(|w| w[0].0.spp_output.threshold_public_key.0 + == w[1].0.spp_output.threshold_public_key.0 + && w[0].0.spp_output.verifying_keys.len() + == w[1].0.spp_output.verifying_keys.len() + && w[0] + .0 + .spp_output + .verifying_keys + .iter() + .zip(w[1].0.spp_output.verifying_keys.iter()) + .all(|((a, b), (c, d))| a.0 == c.0 && b.0 == d.0)), + "All spp outputs should have identical group public keys and verifying keys." + ); + + // Verify that all verifying_shares are valid + for i in 0..participants { + for j in 0..participants { + assert_eq!( + spp_outputs[i].0.spp_output.verifying_keys[j].1 .0, + (spp_outputs[j].1 .0.public), + "Verification of total secret shares failed!" + ); + } + } + } + } +} diff --git a/src/olaf/simplpedpop/types.rs b/src/olaf/simplpedpop/types.rs new file mode 100644 index 0000000..fb3f071 --- /dev/null +++ b/src/olaf/simplpedpop/types.rs @@ -0,0 +1,732 @@ +//! Types of the SimplPedPoP protocol. + +#![allow(clippy::too_many_arguments)] + +use core::iter; +use alloc::vec::Vec; +use merlin::Transcript; +use rand_core::{CryptoRng, RngCore}; +use zeroize::ZeroizeOnDrop; +use aead::KeyInit; +use chacha20poly1305::{aead::Aead, ChaCha20Poly1305, Nonce}; +use curve25519_dalek::{ristretto::CompressedRistretto, traits::Identity, RistrettoPoint, Scalar}; +use crate::{ + context::SigningTranscript, + olaf::{ + Identifier, ThresholdPublicKey, VerifyingShare, COMPRESSED_RISTRETTO_LENGTH, GENERATOR, + MINIMUM_THRESHOLD, SCALAR_LENGTH, + }, + scalar_from_canonical_bytes, PublicKey, Signature, PUBLIC_KEY_LENGTH, SIGNATURE_LENGTH, +}; +use super::errors::{SPPError, SPPResult}; + +pub(super) const U16_LENGTH: usize = 2; +pub(super) const ENCRYPTION_NONCE_LENGTH: usize = 12; +pub(super) const RECIPIENTS_HASH_LENGTH: usize = 16; +pub(super) const CHACHA20POLY1305_LENGTH: usize = 48; +pub(super) const CHACHA20POLY1305_KEY_LENGTH: usize = 32; +pub(super) const VEC_LENGTH: usize = 2; + +#[derive(ZeroizeOnDrop)] +pub(super) struct SecretShare(pub(super) Scalar); + +impl SecretShare { + pub(super) fn encrypt( + &self, + key: &[u8; CHACHA20POLY1305_KEY_LENGTH], + nonce: &[u8; ENCRYPTION_NONCE_LENGTH], + ) -> SPPResult { + let cipher = ChaCha20Poly1305::new(&(*key).into()); + + let nonce = Nonce::from_slice(&nonce[..]); + + let ciphertext: Vec = cipher + .encrypt(nonce, &self.0.to_bytes()[..]) + .map_err(SPPError::EncryptionError)?; + + Ok(EncryptedSecretShare(ciphertext)) + } + + pub(super) fn decrypt( + encrypted_secret_share: &EncryptedSecretShare, + key: &[u8; CHACHA20POLY1305_KEY_LENGTH], + nonce: &[u8; ENCRYPTION_NONCE_LENGTH], + ) -> SPPResult { + let cipher = ChaCha20Poly1305::new(&(*key).into()); + + let nonce = Nonce::from_slice(&nonce[..]); + + let plaintext = cipher + .decrypt(nonce, &encrypted_secret_share.0[..]) + .map_err(SPPError::DecryptionError)?; + + let mut bytes = [0; 32]; + bytes.copy_from_slice(&plaintext); + + Ok(SecretShare(Scalar::from_bytes_mod_order(bytes))) + } +} + +/// The secret polynomial of a participant chosen at randoma nd used to generate the secret shares of all the participants (including itself). +#[derive(ZeroizeOnDrop)] +pub(super) struct SecretPolynomial { + pub(super) coefficients: Vec, +} + +impl SecretPolynomial { + pub(super) fn generate(degree: usize, rng: &mut R) -> Self { + let mut coefficients = Vec::with_capacity(degree + 1); + + let mut first = Scalar::random(rng); + while first == Scalar::ZERO { + first = Scalar::random(rng); + } + + coefficients.push(first); + coefficients.extend(iter::repeat_with(|| Scalar::random(rng)).take(degree)); + + SecretPolynomial { coefficients } + } + + pub(super) fn evaluate(&self, x: &Scalar) -> Scalar { + let mut value = + *self.coefficients.last().expect("coefficients must have at least one element"); + + for coeff in self.coefficients.iter().rev().skip(1) { + value = value * x + coeff; + } + + value + } + + pub(super) fn commit(&self) -> PolynomialCommitment { + let coefficients_commitments = + self.coefficients.iter().map(|coefficient| GENERATOR * coefficient).collect(); + + PolynomialCommitment { coefficients_commitments } + } +} + +/// The parameters of a given execution of the SimplPedPoP protocol. +#[derive(Clone, PartialEq, Eq, Debug)] +pub(crate) struct Parameters { + pub(crate) participants: u16, + pub(crate) threshold: u16, +} + +impl Parameters { + pub(crate) fn generate(participants: u16, threshold: u16) -> Parameters { + Parameters { participants, threshold } + } + + pub(super) fn validate(&self) -> Result<(), SPPError> { + if self.threshold < MINIMUM_THRESHOLD { + return Err(SPPError::InsufficientThreshold); + } + + if self.participants < MINIMUM_THRESHOLD { + return Err(SPPError::InvalidNumberOfParticipants); + } + + if self.threshold > self.participants { + return Err(SPPError::ExcessiveThreshold); + } + + Ok(()) + } + + pub(super) fn commit(&self, t: &mut T) { + t.commit_bytes(b"threshold", &self.threshold.to_le_bytes()); + t.commit_bytes(b"participants", &self.participants.to_le_bytes()); + } + + pub(super) fn to_bytes(&self) -> [u8; U16_LENGTH * 2] { + let mut bytes = [0u8; U16_LENGTH * 2]; + bytes[0..U16_LENGTH].copy_from_slice(&self.participants.to_le_bytes()); + bytes[U16_LENGTH..U16_LENGTH * 2].copy_from_slice(&self.threshold.to_le_bytes()); + bytes + } + + pub(super) fn from_bytes(bytes: &[u8]) -> SPPResult { + if bytes.len() != U16_LENGTH * 2 { + return Err(SPPError::InvalidParameters); + } + + let participants = u16::from_le_bytes([bytes[0], bytes[1]]); + let threshold = u16::from_le_bytes([bytes[2], bytes[3]]); + + Ok(Parameters { participants, threshold }) + } +} + +/// The polynomial commitment of a participant, used to verify the secret shares without revealing the polynomial. +#[derive(Debug, PartialEq, Eq)] +pub(crate) struct PolynomialCommitment { + pub(super) coefficients_commitments: Vec, +} + +impl PolynomialCommitment { + pub(super) fn evaluate(&self, identifier: &Scalar) -> RistrettoPoint { + let i = identifier; + + let (_, result) = self + .coefficients_commitments + .iter() + .fold((Scalar::ONE, RistrettoPoint::identity()), |(i_to_the_k, sum_so_far), comm_k| { + (i * i_to_the_k, sum_so_far + comm_k * i_to_the_k) + }); + + result + } + + pub(super) fn sum_polynomial_commitments( + polynomials_commitments: &[&PolynomialCommitment], + ) -> PolynomialCommitment { + let max_length = polynomials_commitments + .iter() + .map(|c| c.coefficients_commitments.len()) + .max() + .unwrap_or(0); + + let mut total_commitment = vec![RistrettoPoint::identity(); max_length]; + + for polynomial_commitment in polynomials_commitments { + for (i, coeff_commitment) in + polynomial_commitment.coefficients_commitments.iter().enumerate() + { + total_commitment[i] += coeff_commitment; + } + } + + PolynomialCommitment { coefficients_commitments: total_commitment } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) struct EncryptedSecretShare(pub(super) Vec); + +/// AllMessage packs together messages for all participants. +/// +/// We'd save bandwidth by having separate messages for each +/// participant, but typical thresholds lie between 1/2 and 2/3, +/// so this doubles or tripples bandwidth usage. +#[derive(Debug, PartialEq, Eq)] +pub struct AllMessage { + pub(super) content: MessageContent, + pub(super) signature: Signature, + pub(super) proof_of_possession: Signature, +} + +impl AllMessage { + /// Creates a new message. + pub(crate) fn new( + content: MessageContent, + signature: Signature, + proof_of_possession: Signature, + ) -> Self { + Self { content, signature, proof_of_possession } + } + /// 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.extend(self.proof_of_possession.to_bytes()); + + bytes + } + + /// Deserialize AllMessage from bytes + pub fn from_bytes(bytes: &[u8]) -> Result { + let mut cursor = 0; + + let content = MessageContent::from_bytes(&bytes[cursor..])?; + cursor += content.to_bytes().len(); + + let signature = Signature::from_bytes(&bytes[cursor..cursor + SIGNATURE_LENGTH]) + .map_err(SPPError::ErrorDeserializingSignature)?; + cursor += SIGNATURE_LENGTH; + + let proof_of_possession = Signature::from_bytes(&bytes[cursor..cursor + SIGNATURE_LENGTH]) + .map_err(SPPError::ErrorDeserializingProofOfPossession)?; + + Ok(AllMessage { content, signature, proof_of_possession }) + } +} + +/// The contents of the message destined to all participants. +#[derive(Debug, PartialEq, Eq)] +pub(crate) struct MessageContent { + pub(super) sender: PublicKey, + pub(super) encryption_nonce: [u8; ENCRYPTION_NONCE_LENGTH], + pub(super) parameters: Parameters, + pub(super) recipients_hash: [u8; RECIPIENTS_HASH_LENGTH], + pub(super) polynomial_commitment: PolynomialCommitment, + pub(super) encrypted_secret_shares: Vec, + pub(super) ephemeral_key: PublicKey, +} + +impl MessageContent { + /// Creates the content of the message. + pub(super) fn new( + sender: PublicKey, + encryption_nonce: [u8; ENCRYPTION_NONCE_LENGTH], + parameters: Parameters, + recipients_hash: [u8; RECIPIENTS_HASH_LENGTH], + polynomial_commitment: PolynomialCommitment, + encrypted_secret_shares: Vec, + ephemeral_key: PublicKey, + ) -> Self { + Self { + sender, + encryption_nonce, + parameters, + recipients_hash, + polynomial_commitment, + encrypted_secret_shares, + ephemeral_key, + } + } + + /// Serialize MessageContent into bytes. + pub(super) fn to_bytes(&self) -> Vec { + let mut bytes = Vec::new(); + + bytes.extend(self.sender.to_bytes()); + bytes.extend(&self.encryption_nonce); + bytes.extend(self.parameters.participants.to_le_bytes()); + bytes.extend(self.parameters.threshold.to_le_bytes()); + bytes.extend(&self.recipients_hash); + + for point in &self.polynomial_commitment.coefficients_commitments { + bytes.extend(point.compress().to_bytes()); + } + + for ciphertext in &self.encrypted_secret_shares { + bytes.extend(ciphertext.0.clone()); + } + + bytes.extend(&self.ephemeral_key.to_bytes()); + + bytes + } + + /// Deserialize MessageContent from bytes. + pub(super) fn from_bytes(bytes: &[u8]) -> Result { + let mut cursor = 0; + + let sender = PublicKey::from_bytes(&bytes[cursor..cursor + PUBLIC_KEY_LENGTH]) + .map_err(SPPError::InvalidPublicKey)?; + cursor += PUBLIC_KEY_LENGTH; + + let encryption_nonce: [u8; ENCRYPTION_NONCE_LENGTH] = bytes + [cursor..cursor + ENCRYPTION_NONCE_LENGTH] + .try_into() + .map_err(SPPError::DeserializationError)?; + cursor += ENCRYPTION_NONCE_LENGTH; + + let parameters = Parameters::from_bytes(&bytes[cursor..cursor + U16_LENGTH * 2])?; + cursor += U16_LENGTH * 2; + + let participants = parameters.participants; + + let recipients_hash: [u8; RECIPIENTS_HASH_LENGTH] = bytes + [cursor..cursor + RECIPIENTS_HASH_LENGTH] + .try_into() + .map_err(SPPError::DeserializationError)?; + cursor += RECIPIENTS_HASH_LENGTH; + + let mut coefficients_commitments = Vec::with_capacity(participants as usize); + + for _ in 0..parameters.threshold { + let point = CompressedRistretto::from_slice( + &bytes[cursor..cursor + COMPRESSED_RISTRETTO_LENGTH], + ) + .map_err(SPPError::DeserializationError)?; + + coefficients_commitments + .push(point.decompress().ok_or(SPPError::InvalidCoefficientCommitment)?); + + cursor += COMPRESSED_RISTRETTO_LENGTH; + } + + let polynomial_commitment = PolynomialCommitment { coefficients_commitments }; + + let mut encrypted_secret_shares = Vec::new(); + + for _ in 0..participants { + let ciphertext = bytes[cursor..cursor + CHACHA20POLY1305_LENGTH].to_vec(); + encrypted_secret_shares.push(EncryptedSecretShare(ciphertext)); + cursor += CHACHA20POLY1305_LENGTH; + } + + let ephemeral_key = PublicKey::from_bytes(&bytes[cursor..cursor + PUBLIC_KEY_LENGTH]) + .map_err(SPPError::InvalidPublicKey)?; + + Ok(MessageContent { + sender, + encryption_nonce, + parameters, + recipients_hash, + polynomial_commitment, + encrypted_secret_shares, + ephemeral_key, + }) + } +} + +/// The signed output of the SimplPedPoP protocol. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct SPPOutputMessage { + pub(crate) signer: VerifyingShare, + pub(crate) spp_output: SPPOutput, + pub(crate) signature: Signature, +} + +impl SPPOutputMessage { + pub(crate) fn new(signer: VerifyingShare, content: SPPOutput, signature: Signature) -> Self { + Self { signer, signature, spp_output: content } + } + + /// Serializes the SPPOutputMessage into bytes. + pub fn to_bytes(&self) -> Vec { + let mut bytes = Vec::new(); + + let pk_bytes = self.signer.0.to_bytes(); + bytes.extend(pk_bytes); + + let content_bytes = self.spp_output.to_bytes(); + bytes.extend(content_bytes); + + let signature_bytes = self.signature.to_bytes(); + bytes.extend(signature_bytes); + + bytes + } + + /// Deserializes the SPPOutputMessage from bytes. + pub fn from_bytes(bytes: &[u8]) -> Result { + let mut cursor = 0; + + let pk_bytes = &bytes[..PUBLIC_KEY_LENGTH]; + let signer = + VerifyingShare(PublicKey::from_bytes(pk_bytes).map_err(SPPError::InvalidPublicKey)?); + cursor += PUBLIC_KEY_LENGTH; + + let content_bytes = &bytes[cursor..bytes.len() - SIGNATURE_LENGTH]; + let spp_output = SPPOutput::from_bytes(content_bytes)?; + + cursor = bytes.len() - SIGNATURE_LENGTH; + let signature = Signature::from_bytes(&bytes[cursor..cursor + SIGNATURE_LENGTH]) + .map_err(SPPError::ErrorDeserializingSignature)?; + + Ok(SPPOutputMessage { signer, spp_output, signature }) + } + + /// Returns the output of the SimplPedPoP protocol. + pub fn spp_output(&self) -> SPPOutput { + self.spp_output.clone() + } + + /// Verifies the signature of the message. + pub fn verify_signature(&self) -> SPPResult<()> { + let mut spp_output_transcript = Transcript::new(b"spp output"); + spp_output_transcript.append_message(b"message", &self.spp_output.to_bytes()); + + self.signer + .0 + .verify(spp_output_transcript, &self.signature) + .map_err(SPPError::InvalidSignature) + } +} + +/// The content of the signed output of the SimplPedPoP protocol. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct SPPOutput { + pub(crate) parameters: Parameters, + pub(crate) threshold_public_key: ThresholdPublicKey, + pub(crate) verifying_keys: Vec<(Identifier, VerifyingShare)>, +} + +impl SPPOutput { + pub(crate) fn new( + parameters: &Parameters, + threshold_public_key: ThresholdPublicKey, + verifying_keys: Vec<(Identifier, VerifyingShare)>, + ) -> Self { + let parameters = Parameters::generate(parameters.participants, parameters.threshold); + + Self { threshold_public_key, verifying_keys, parameters } + } + + pub(crate) fn to_bytes(&self) -> Vec { + let mut bytes = Vec::new(); + + bytes.extend(self.parameters.to_bytes()); + + let compressed_public_key = self.threshold_public_key.0.as_compressed(); + bytes.extend(compressed_public_key.to_bytes().iter()); + + let key_count = self.verifying_keys.len() as u16; + bytes.extend(key_count.to_le_bytes()); + + for (id, key) in &self.verifying_keys { + bytes.extend(id.0.to_bytes()); + bytes.extend(key.0.to_bytes()); + } + + bytes + } + + /// Deserializes the SPPOutput from bytes. + pub(crate) fn from_bytes(bytes: &[u8]) -> Result { + let mut cursor = 0; + + let parameters = Parameters::from_bytes(&bytes[cursor..cursor + U16_LENGTH * 2])?; + cursor += U16_LENGTH * 2; + + let public_key_bytes = &bytes[cursor..cursor + PUBLIC_KEY_LENGTH]; + cursor += PUBLIC_KEY_LENGTH; + + let compressed_public_key = CompressedRistretto::from_slice(public_key_bytes) + .map_err(SPPError::DeserializationError)?; + + let threshold_public_key = + compressed_public_key.decompress().ok_or(SPPError::InvalidThresholdPublicKey)?; + + let mut verifying_keys = Vec::new(); + + cursor += VEC_LENGTH; + + while cursor < bytes.len() { + let mut identifier_bytes = [0; SCALAR_LENGTH]; + identifier_bytes.copy_from_slice(&bytes[cursor..cursor + SCALAR_LENGTH]); + let identifier = + scalar_from_canonical_bytes(identifier_bytes).ok_or(SPPError::InvalidIdentifier)?; + cursor += SCALAR_LENGTH; + + let key_bytes = &bytes[cursor..cursor + PUBLIC_KEY_LENGTH]; + cursor += PUBLIC_KEY_LENGTH; + let key = PublicKey::from_bytes(key_bytes).map_err(SPPError::InvalidPublicKey)?; + verifying_keys.push((Identifier(identifier), VerifyingShare(key))); + } + + Ok(SPPOutput { + threshold_public_key: ThresholdPublicKey(PublicKey::from_point(threshold_public_key)), + verifying_keys, + parameters, + }) + } + + /// Returns the threshold public key. + pub fn threshold_public_key(&self) -> ThresholdPublicKey { + self.threshold_public_key + } +} + +#[cfg(test)] +mod tests { + use merlin::Transcript; + use rand_core::OsRng; + use crate::{context::SigningTranscript, olaf::test_utils::generate_parameters, Keypair}; + use super::*; + use curve25519_dalek::RistrettoPoint; + + #[test] + fn test_encryption_decryption() { + let mut rng = OsRng; + let ephemeral_key = Keypair::generate(); + let recipient = Keypair::generate(); + let encryption_nonce = [1; ENCRYPTION_NONCE_LENGTH]; + let key_exchange = ephemeral_key.secret.key * recipient.public.as_point(); + let secret_share = SecretShare(Scalar::random(&mut rng)); + let mut transcript = Transcript::new(b"encryption"); + transcript.commit_point(b"key", &key_exchange.compress()); + let mut key_bytes = [0; CHACHA20POLY1305_KEY_LENGTH]; + transcript.challenge_bytes(b"key", &mut key_bytes); + + let encrypted_share = secret_share.encrypt(&key_bytes, &encryption_nonce).unwrap(); + + SecretShare::decrypt(&encrypted_share, &key_bytes, &encryption_nonce).unwrap(); + } + + #[test] + fn test_generate_polynomial_commitment_valid() { + let degree = 3; + + let polynomial = SecretPolynomial::generate(degree, &mut OsRng); + + let polynomial_commitment = polynomial.commit(); + + assert_eq!(polynomial.coefficients.len(), degree as usize + 1); + + assert_eq!(polynomial_commitment.coefficients_commitments.len(), degree as usize + 1); + } + + #[test] + fn test_evaluate_polynomial() { + let coefficients: Vec = + vec![Scalar::from(3u64), Scalar::from(2u64), Scalar::from(1u64)]; // Polynomial x^2 + 2x + 3 + + let polynomial = SecretPolynomial { coefficients }; + + let value = Scalar::from(5u64); // x = 5 + + let result = polynomial.evaluate(&value); + + assert_eq!(result, Scalar::from(38u64)); // 5^2 + 2*5 + 3 + } + + #[test] + fn test_serialize_deserialize_all_message() { + let parameters = generate_parameters(); + + let keypairs: Vec = + (0..parameters.participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public).collect(); + + let message: AllMessage = keypairs[0] + .simplpedpop_contribute_all(parameters.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_message_serialization() { + let parameters = generate_parameters(); + let participants = parameters.participants as usize; + let threshold = parameters.threshold as usize; + + let keypairs: Vec = (0..participants).map(|_| Keypair::generate()).collect(); + let public_keys: Vec = keypairs.iter().map(|kp| kp.public).collect(); + + let mut all_messages = Vec::new(); + for i in 0..participants { + let message: AllMessage = keypairs[i] + .simplpedpop_contribute_all(threshold as u16, public_keys.clone()) + .unwrap(); + all_messages.push(message); + } + + let spp_output = keypairs[0].simplpedpop_recipient_all(&all_messages).unwrap(); + + let bytes = spp_output.0.to_bytes(); + + let deserialized_spp_output_message = + SPPOutputMessage::from_bytes(&bytes).expect("Deserialization failed"); + + assert_eq!(deserialized_spp_output_message, spp_output.0); + } + + #[test] + fn test_spp_output_message_verification() { + let mut rng = OsRng; + let group_public_key = RistrettoPoint::random(&mut rng); + let verifying_keys = vec![ + ( + Identifier(Scalar::random(&mut rng)), + VerifyingShare(PublicKey::from_point(RistrettoPoint::random(&mut rng))), + ), + ( + Identifier(Scalar::random(&mut rng)), + VerifyingShare(PublicKey::from_point(RistrettoPoint::random(&mut rng))), + ), + ( + Identifier(Scalar::random(&mut rng)), + VerifyingShare(PublicKey::from_point(RistrettoPoint::random(&mut rng))), + ), + ]; + let parameters = Parameters::generate(2, 2); + + let spp_output = SPPOutput { + parameters, + threshold_public_key: ThresholdPublicKey(PublicKey::from_point(group_public_key)), + verifying_keys, + }; + + let keypair = Keypair::generate(); + let mut transcript = Transcript::new(b"spp output"); + transcript.append_message(b"message", &spp_output.to_bytes()); + let signature = keypair.sign(transcript); + + let spp_output_message = + SPPOutputMessage { signer: VerifyingShare(keypair.public), spp_output, signature }; + + spp_output_message.verify_signature().unwrap() + } + + #[test] + fn test_sum_secret_polynomial_commitments() { + let polynomial_commitment1 = PolynomialCommitment { + coefficients_commitments: vec![ + GENERATOR * Scalar::from(1u64), // Constant + GENERATOR * Scalar::from(2u64), // Linear + GENERATOR * Scalar::from(3u64), // Quadratic + ], + }; + + let polynomial_commitment2 = PolynomialCommitment { + coefficients_commitments: vec![ + GENERATOR * Scalar::from(4u64), // Constant + GENERATOR * Scalar::from(5u64), // Linear + GENERATOR * Scalar::from(6u64), // Quadratic + ], + }; + + let summed_polynomial_commitments = PolynomialCommitment::sum_polynomial_commitments(&[ + &polynomial_commitment1, + &polynomial_commitment2, + ]); + + let expected_coefficients_commitments = vec![ + GENERATOR * Scalar::from(5u64), // 1 + 4 = 5 + GENERATOR * Scalar::from(7u64), // 2 + 5 = 7 + GENERATOR * Scalar::from(9u64), // 3 + 6 = 9 + ]; + + assert_eq!( + summed_polynomial_commitments.coefficients_commitments, + expected_coefficients_commitments, + "Coefficient commitments do not match" + ); + } + + #[test] + fn test_evaluate_polynomial_commitment() { + // f(x) = 3 + 2x + x^2 + let constant_coefficient_commitment = Scalar::from(3u64) * GENERATOR; + let linear_commitment = Scalar::from(2u64) * GENERATOR; + let quadratic_commitment = Scalar::from(1u64) * GENERATOR; + + // Note the order and inclusion of the constant term + let coefficients_commitments = + vec![constant_coefficient_commitment, linear_commitment, quadratic_commitment]; + + let polynomial_commitment = PolynomialCommitment { coefficients_commitments }; + + let value = Scalar::from(2u64); + + // f(2) = 11 + let expected = Scalar::from(11u64) * GENERATOR; + + let result = polynomial_commitment.evaluate(&value); + + assert_eq!(result, expected, "The evaluated commitment does not match the expected result"); + } + + #[test] + fn test_parameters_serialization() { + let params = Parameters::generate(3, 2); + let bytes = params.to_bytes(); + let result = Parameters::from_bytes(&bytes).unwrap(); + + assert_eq!(params.participants, result.participants); + assert_eq!(params.threshold, result.threshold); + } +}