diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..5008ddf Binary files /dev/null and b/.DS_Store differ diff --git a/Cargo.toml b/Cargo.toml index 9943958..c0053e9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,17 +22,22 @@ curve25519-dalek = { version = "4.1.0", default-features = false, features = [ "zeroize", "precomputed-tables", "legacy_compatibility", + "rand_core", + "serde", ] } subtle = { version = "2.4.1", default-features = false } merlin = { version = "3.0.0", default-features = false } getrandom_or_panic = { version = "0.0.3", default-features = false } rand_core = { version = "0.6.2", default-features = false } -serde_crate = { version = "1.0.130", package = "serde", default-features = false, optional = true } +serde = { version = "1.0.130", default-features = false, optional = true } serde_bytes = { version = "0.11.5", default-features = false, optional = true } cfg-if = { version = "1.0.0", optional = true } sha2 = { version = "0.10.7", default-features = false } failure = { version = "0.1.8", default-features = false, optional = true } -zeroize = { version = "1.6", default-features = false, features = ["zeroize_derive"] } +zeroize = { version = "1.6", default-features = false, features = [ + "zeroize_derive", +] } +derive-getters = "0.3.0" [dev-dependencies] rand = "0.8.5" @@ -47,17 +52,36 @@ serde_json = "1.0.68" name = "schnorr_benchmarks" harness = false +[[bench]] +name = "simplpedpop_benchmarks" +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", +] asm = ["sha2/asm"] -serde = ["serde_crate", "serde_bytes", "cfg-if"] +serde = ["dep:serde", "serde_bytes", "cfg-if"] # We cannot make getrandom a direct dependency because rand_core makes # getrandom a feature name, which requires forwarding. -getrandom = ["rand_core/getrandom", "getrandom_or_panic/getrandom", "aead?/getrandom"] +getrandom = [ + "rand_core/getrandom", + "getrandom_or_panic/getrandom", + "aead?/getrandom", +] # We thus cannot forward the wasm-bindgen feature of getrandom, # but our consumers could depend upon getrandom and activate its # wasm-bindgen feature themselve, which works due to cargo features diff --git a/benches/simplpedpop_benchmarks.rs b/benches/simplpedpop_benchmarks.rs new file mode 100644 index 0000000..49aab33 --- /dev/null +++ b/benches/simplpedpop_benchmarks.rs @@ -0,0 +1,248 @@ +use criterion::{criterion_group, criterion_main, Criterion}; + +mod simplpedpop_benches { + use std::collections::{BTreeMap, BTreeSet}; + + use super::*; + use criterion::BenchmarkId; + use merlin::Transcript; + use rand_core::OsRng; + use schnorrkel::{ + identifier::Identifier, + simplpedpop::{ + round1::{self, PrivateData, PrivateMessage, PublicData, PublicMessage}, + round2, round3, Parameters, + }, + }; + + fn generate_parameters(max_signers: u16, min_signers: u16) -> Vec { + (1..=max_signers) + .map(|i| { + let own_identifier = i.try_into().expect("should be nonzero"); + + let others_identifiers = (1..=max_signers) + .filter_map(|j| { + if j != i { + Some(j.try_into().expect("should be nonzero")) + } else { + None + } + }) + .collect(); + + Parameters::new(max_signers, min_signers, own_identifier, others_identifiers) + }) + .collect() + } + + fn round2( + participants: u16, + parameters_list: Vec, + participants_round1_private_data: Vec, + participants_round1_public_data: Vec, + participants_round1_public_messages: Vec>, + participants_round1_private_messages: Vec>, + ) -> ( + Vec>, + Vec, + ) { + let mut participants_round2_public_data = Vec::new(); + let mut participants_round2_private_data = Vec::new(); + let mut participants_round2_public_messages = Vec::new(); + + for i in 0..participants { + let result = round2::run( + ¶meters_list[i as usize], + participants_round1_private_data[i as usize].clone(), + &participants_round1_public_data[i as usize].clone(), + &participants_round1_public_messages[i as usize].clone(), + participants_round1_private_messages[i as usize].clone(), + Transcript::new(b"transcript"), + ) + .expect("Round 2 should complete without errors!"); + + participants_round2_public_data.push(result.0); + participants_round2_private_data.push(result.1); + participants_round2_public_messages.push(result.2); + } + + ( + participants_round2_public_data, + participants_round2_public_messages, + ) + } + + fn round1( + participants: u16, + threshold: u16, + ) -> ( + Vec, + Vec, + Vec, + Vec>, + Vec>, + BTreeSet, + ) { + let parameters_list = generate_parameters(participants, threshold); + + let mut all_public_messages = Vec::new(); + let mut all_private_messages = Vec::new(); + let mut participants_round1_private_data = Vec::new(); + let mut participants_round1_public_data = Vec::new(); + let mut participants_round1_messages = Vec::new(); + + for parameters in parameters_list.iter() { + let (private_data, messages, public_data) = + round1::run(parameters, OsRng).expect("Round 1 should complete without errors!"); + + all_public_messages.push(( + parameters.own_identifier(), + messages.public_message().clone(), + )); + all_private_messages.push(( + parameters.own_identifier(), + messages.private_messages().clone(), + )); + participants_round1_messages.push(messages); + participants_round1_private_data.push(private_data); + participants_round1_public_data.push(public_data); + } + + let mut participants_round1_public_messages: Vec> = + Vec::new(); + let mut participants_round1_private_messages: Vec> = + Vec::new(); + + let mut identifiers: BTreeSet = parameters_list[0] + .others_identifiers() + .iter() + .copied() + .collect(); + + identifiers.insert(*parameters_list[0].own_identifier()); + + for identifier in &identifiers { + let mut all_public_msgs = BTreeMap::new(); + let mut received_private_msgs = BTreeMap::new(); + + for i in 0..participants { + all_public_msgs.insert( + *all_public_messages[i as usize].0, + all_public_messages[i as usize].1.clone(), + ); + } + + participants_round1_public_messages.push(all_public_msgs); + + for i in 0..participants { + if let Some(private_msg) = all_private_messages[i as usize].1.get(identifier) { + received_private_msgs + .insert(*all_private_messages[i as usize].0, private_msg.clone()); + } + } + + participants_round1_private_messages.push(received_private_msgs); + } + + for i in 0..participants { + participants_round1_public_messages[i as usize] + .remove(¶meters_list[i as usize].own_identifier()); + } + + ( + parameters_list, + participants_round1_private_data, + participants_round1_public_data, + participants_round1_public_messages, + participants_round1_private_messages, + identifiers, + ) + } + + fn benchmark_simplpedpop(c: &mut Criterion) { + let mut group = c.benchmark_group("SimplPedPoP"); + + group + .sample_size(10) + .warm_up_time(std::time::Duration::from_secs(2)) + .measurement_time(std::time::Duration::from_secs(30)); + + for &n in [3, 10, 100].iter() { + let participants = n; + let threshold = (n * 2 + 2) / 3; + let parameters_list = generate_parameters(participants, threshold); + + group.bench_function(BenchmarkId::new("round1", participants), |b| { + b.iter(|| { + round1::run(¶meters_list[0], OsRng).unwrap(); + }) + }); + + let ( + parameters_list, + participants_round1_private_data, + participants_round1_public_data, + participants_round1_public_messages, + participants_round1_private_messages, + identifiers, + ) = round1(participants, threshold); + + group.bench_function(BenchmarkId::new("round2", participants), |b| { + b.iter(|| { + round2::run( + ¶meters_list[0], + participants_round1_private_data[0].clone(), + &participants_round1_public_data[0].clone(), + &participants_round1_public_messages[0].clone(), + participants_round1_private_messages[0].clone(), + Transcript::new(b"transcript"), + ) + .unwrap(); + }) + }); + + let (participants_round2_public_data, participants_round2_public_messages) = round2( + participants, + parameters_list.clone(), + participants_round1_private_data.clone(), + participants_round1_public_data.clone(), + participants_round1_public_messages.clone(), + participants_round1_private_messages.clone(), + ); + + let identifiers_vec: Vec = identifiers.clone().iter().copied().collect(); + + let received_round2_public_messages = participants_round2_public_messages + .iter() + .enumerate() + .filter(|(index, _msg)| { + identifiers_vec[*index] != *parameters_list[0].own_identifier() + }) + .map(|(index, msg)| (identifiers_vec[index], msg.clone())) + .collect::>(); + + group.bench_function(BenchmarkId::new("round3", participants), |b| { + b.iter(|| { + round3::run( + ¶meters_list[0], + &received_round2_public_messages, + &participants_round2_public_data[0], + &participants_round1_public_data[0], + &participants_round1_public_messages[0], + ) + .unwrap(); + }) + }); + } + group.finish(); + } + + criterion_group! { + name = simplpedpop_benches; + config = Criterion::default(); + targets = + benchmark_simplpedpop, + } +} + +criterion_main!(simplpedpop_benches::simplpedpop_benches); diff --git a/src/batch.rs b/src/batch.rs index 4be860b..80e637d 100644 --- a/src/batch.rs +++ b/src/batch.rs @@ -16,13 +16,13 @@ use curve25519_dalek::ristretto::{CompressedRistretto, RistrettoPoint}; use curve25519_dalek::scalar::Scalar; use super::*; -use crate::context::{SigningTranscript}; +use crate::context::SigningTranscript; #[cfg(feature = "alloc")] use alloc::vec::Vec; -const ASSERT_MESSAGE: &str = "The number of messages/transcripts, signatures, and public keys must be equal."; - +const ASSERT_MESSAGE: &str = + "The number of messages/transcripts, signatures, and public keys must be equal."; /// Verify a batch of `signatures` on `messages` with their respective `public_keys`. /// @@ -63,7 +63,7 @@ const ASSERT_MESSAGE: &str = "The number of messages/transcripts, signatures, an /// assert!( verify_batch(transcripts, &signatures[..], &public_keys[..], false).is_ok() ); /// # } /// ``` -pub fn verify_batch( +pub fn verify_batch( transcripts: I, signatures: &[Signature], public_keys: &[PublicKey], @@ -71,19 +71,31 @@ pub fn verify_batch( ) -> SignatureResult<()> where T: SigningTranscript, - I: IntoIterator, + I: IntoIterator, { - verify_batch_rng(transcripts, signatures, public_keys, deduplicate_public_keys, getrandom_or_panic()) + verify_batch_rng( + transcripts, + signatures, + public_keys, + deduplicate_public_keys, + getrandom_or_panic(), + ) } struct NotAnRng; impl rand_core::RngCore for NotAnRng { - fn next_u32(&mut self) -> u32 { rand_core::impls::next_u32_via_fill(self) } + fn next_u32(&mut self) -> u32 { + rand_core::impls::next_u32_via_fill(self) + } - fn next_u64(&mut self) -> u64 { rand_core::impls::next_u64_via_fill(self) } + fn next_u64(&mut self) -> u64 { + rand_core::impls::next_u64_via_fill(self) + } /// A no-op function which leaves the destination bytes for randomness unchanged. - fn fill_bytes(&mut self, dest: &mut [u8]) { zeroize::Zeroize::zeroize(dest) } + fn fill_bytes(&mut self, dest: &mut [u8]) { + zeroize::Zeroize::zeroize(dest) + } fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core::Error> { self.fill_bytes(dest); @@ -98,13 +110,13 @@ impl rand_core::CryptoRng for NotAnRng {} /// /// We break the `R: CryptRng` requirement from `verify_batch_rng` /// here, but this appears fine using an Fiat-Shamir transform with -/// an argument similar to +/// an argument similar to /// [public key delinearization](https://crypto.stanford.edu/~dabo/pubs/papers/BLSmultisig.html). /// /// We caution deeterministic delinearization could interact poorly /// with other functionality, *if* one delinarization scalar were /// left constant. We do not make that mistake here. -pub fn verify_batch_deterministic( +pub fn verify_batch_deterministic( transcripts: I, signatures: &[Signature], public_keys: &[PublicKey], @@ -112,15 +124,21 @@ pub fn verify_batch_deterministic( ) -> SignatureResult<()> where T: SigningTranscript, - I: IntoIterator, + I: IntoIterator, { - verify_batch_rng(transcripts, signatures, public_keys, deduplicate_public_keys, NotAnRng) + verify_batch_rng( + transcripts, + signatures, + public_keys, + deduplicate_public_keys, + NotAnRng, + ) } /// Verify a batch of `signatures` on `messages` with their respective `public_keys`. /// /// Inputs and return agree with `verify_batch` except the user supplies their own random number generator. -pub fn verify_batch_rng( +pub fn verify_batch_rng( transcripts: I, signatures: &[Signature], public_keys: &[PublicKey], @@ -129,77 +147,89 @@ pub fn verify_batch_rng( ) -> SignatureResult<()> where T: SigningTranscript, - I: IntoIterator, - R: RngCore+CryptoRng, + I: IntoIterator, + R: RngCore + CryptoRng, { - assert!(signatures.len() == public_keys.len(), "{}", ASSERT_MESSAGE); // Check transcripts length below + assert!(signatures.len() == public_keys.len(), "{}", ASSERT_MESSAGE); // Check transcripts length below let (zs, hrams) = prepare_batch(transcripts, signatures, public_keys, rng); // Compute the basepoint coefficient, ∑ s[i]z[i] (mod l) - let bs: Scalar = signatures.iter() + let bs: Scalar = signatures + .iter() .map(|sig| sig.s) .zip(zs.iter()) .map(|(s, z)| z * s) .sum(); - verify_batch_equation( bs, zs, hrams, signatures, public_keys, deduplicate_public_keys ) + verify_batch_equation( + bs, + zs, + hrams, + signatures, + public_keys, + deduplicate_public_keys, + ) } - trait HasR { #[allow(non_snake_case)] fn get_R(&self) -> &CompressedRistretto; } impl HasR for Signature { #[allow(non_snake_case)] - fn get_R(&self) -> &CompressedRistretto { &self.R } + fn get_R(&self) -> &CompressedRistretto { + &self.R + } } impl HasR for CompressedRistretto { #[allow(non_snake_case)] - fn get_R(&self) -> &CompressedRistretto { self } + fn get_R(&self) -> &CompressedRistretto { + self + } } /// First phase of batch verification that computes the delinierizing /// coefficents and challenge hashes #[allow(non_snake_case)] -fn prepare_batch( +fn prepare_batch( transcripts: I, signatures: &[impl HasR], public_keys: &[PublicKey], mut rng: R, -) -> (Vec,Vec) +) -> (Vec, Vec) where T: SigningTranscript, - I: IntoIterator, - R: RngCore+CryptoRng, + I: IntoIterator, + R: RngCore + CryptoRng, { - // Assumulate public keys, signatures, and transcripts for pseudo-random delinearization scalars let mut zs_t = merlin::Transcript::new(b"V-RNG"); for pk in public_keys { - zs_t.commit_point(b"",pk.as_compressed()); + zs_t.commit_point(b"", pk.as_compressed()); } for sig in signatures { - zs_t.commit_point(b"",sig.get_R()); + zs_t.commit_point(b"", sig.get_R()); } // We might collect here anyways, but right now you cannot have // IntoIterator let mut transcripts = transcripts.into_iter(); // Compute H(R || A || M) for each (signature, public_key, message) triplet - let hrams: Vec = transcripts.by_ref() + let hrams: Vec = transcripts + .by_ref() .zip(0..signatures.len()) - .map( |(mut t,i)| { + .map(|(mut t, i)| { let mut d = [0u8; 16]; - t.witness_bytes_rng(b"", &mut d, &[&[]], NotAnRng); // Could speed this up using ZeroRng - zs_t.append_message(b"",&d); + t.witness_bytes_rng(b"", &mut d, &[&[]], NotAnRng); // Could speed this up using ZeroRng + zs_t.append_message(b"", &d); t.proto_name(b"Schnorr-sig"); - t.commit_point(b"sign:pk",public_keys[i].as_compressed()); - t.commit_point(b"sign:R",signatures[i].get_R()); - t.challenge_scalar(b"sign:c") // context, message, A/public_key, R=rG - } ).collect(); + t.commit_point(b"sign:pk", public_keys[i].as_compressed()); + t.commit_point(b"sign:R", signatures[i].get_R()); + t.challenge_scalar(b"sign:c") // context, message, A/public_key, R=rG + }) + .collect(); assert!(transcripts.next().is_none(), "{}", ASSERT_MESSAGE); assert!(hrams.len() == public_keys.len(), "{}", ASSERT_MESSAGE); @@ -228,8 +258,7 @@ fn verify_batch_equation( signatures: &[impl HasR], public_keys: &[PublicKey], deduplicate_public_keys: bool, -) -> SignatureResult<()> -{ +) -> SignatureResult<()> { use curve25519_dalek::traits::IsIdentity; use curve25519_dalek::traits::VartimeMultiscalarMul; @@ -240,7 +269,7 @@ fn verify_batch_equation( let Rs = signatures.iter().map(|sig| sig.get_R().decompress()); let mut ppks = Vec::new(); - let As = if ! deduplicate_public_keys { + let As = if !deduplicate_public_keys { // Multiply each H(R || A || M) by the random value for (hram, z) in hrams.iter_mut().zip(zs.iter()) { *hram *= z; @@ -248,37 +277,43 @@ fn verify_batch_equation( public_keys } else { // TODO: Actually deduplicate all if deduplicate_public_keys is set? - ppks.reserve( public_keys.len() ); + ppks.reserve(public_keys.len()); // Multiply each H(R || A || M) by the random value for i in 0..public_keys.len() { let zhram = hrams[i] * zs[i]; let j = ppks.len().checked_sub(1); if j.is_none() || ppks[j.unwrap()] != public_keys[i] { ppks.push(public_keys[i]); - hrams[ppks.len()-1] = zhram; + hrams[ppks.len() - 1] = zhram; } else { - hrams[ppks.len()-1] = hrams[ppks.len()-1] + zhram; + hrams[ppks.len() - 1] = hrams[ppks.len() - 1] + zhram; } } hrams.truncate(ppks.len()); ppks.as_slice() - }.iter().map(|pk| Some(*pk.as_point())); + } + .iter() + .map(|pk| Some(*pk.as_point())); // Compute (-∑ z[i]s[i] (mod l)) B + ∑ z[i]R[i] + ∑ (z[i]H(R||A||M)[i] (mod l)) A[i] = 0 let b = RistrettoPoint::optional_multiscalar_mul( once(-bs).chain(zs.iter().cloned()).chain(hrams), B.chain(Rs).chain(As), - ).map(|id| id.is_identity()).unwrap_or(false); + ) + .map(|id| id.is_identity()) + .unwrap_or(false); // We need not return SignatureError::PointDecompressionError because // the decompression failures occur for R represent invalid signatures. - if b { Ok(()) } else { Err(SignatureError::EquationFalse) } + if b { + Ok(()) + } else { + Err(SignatureError::EquationFalse) + } } - - /// Half-aggregated aka prepared batch signature -/// +/// /// Implementation of "Non-interactive half-aggregation of EdDSA and /// variantsof Schnorr signatures" by Konstantinos Chalkias, /// François Garillot, Yashvanth Kondi, and Valeria Nikolaenko @@ -289,37 +324,37 @@ pub struct PreparedBatch { Rs: Vec, } -impl PreparedBatch{ - +impl PreparedBatch { /// Create a half-aggregated aka prepared batch signature from many other signatures. #[allow(non_snake_case)] - pub fn new( + pub fn new( transcripts: I, signatures: &[Signature], public_keys: &[PublicKey], ) -> PreparedBatch where T: SigningTranscript, - I: IntoIterator, + I: IntoIterator, { - assert!(signatures.len() == public_keys.len(), "{}", ASSERT_MESSAGE); // Check transcripts length below + assert!(signatures.len() == public_keys.len(), "{}", ASSERT_MESSAGE); // Check transcripts length below let (zs, _hrams) = prepare_batch(transcripts, signatures, public_keys, NotAnRng); // Compute the basepoint coefficient, ∑ s[i]z[i] (mod l) - let bs: Scalar = signatures.iter() + let bs: Scalar = signatures + .iter() .map(|sig| sig.s) .zip(zs.iter()) .map(|(s, z)| z * s) .sum(); let Rs = signatures.iter().map(|sig| sig.R).collect(); - PreparedBatch { bs, Rs, } + PreparedBatch { bs, Rs } } /// Verify a half-aggregated aka prepared batch signature #[allow(non_snake_case)] - pub fn verify( + pub fn verify( &self, transcripts: I, public_keys: &[PublicKey], @@ -327,17 +362,19 @@ impl PreparedBatch{ ) -> SignatureResult<()> where T: SigningTranscript, - I: IntoIterator, + I: IntoIterator, { - assert!(self.Rs.len() == public_keys.len(), "{}", ASSERT_MESSAGE); // Check transcripts length below + assert!(self.Rs.len() == public_keys.len(), "{}", ASSERT_MESSAGE); // Check transcripts length below let (zs, hrams) = prepare_batch(transcripts, self.Rs.as_slice(), public_keys, NotAnRng); verify_batch_equation( self.bs, - zs, hrams, + zs, + hrams, self.Rs.as_slice(), - public_keys, deduplicate_public_keys + public_keys, + deduplicate_public_keys, ) } @@ -345,29 +382,29 @@ impl PreparedBatch{ #[allow(non_snake_case)] pub fn read_bytes(&self, mut bytes: &[u8]) -> SignatureResult { use arrayref::array_ref; - if bytes.len() % 32 != 0 || bytes.len() < 64 { + if bytes.len() % 32 != 0 || bytes.len() < 64 { return Err(SignatureError::BytesLengthError { name: "PreparedBatch", description: "A Prepared batched signature", - length: 0 // TODO: Maybe get rid of this silly field? + length: 0, // TODO: Maybe get rid of this silly field? }); } let l = (bytes.len() % 32) - 1; let mut read = || { - let (head,tail) = bytes.split_at(32); + let (head, tail) = bytes.split_at(32); bytes = tail; - *array_ref![head,0,32] + *array_ref![head, 0, 32] }; let mut bs = read(); bs[31] &= 127; - let bs = super::sign::check_scalar(bs) ?; + let bs = super::sign::check_scalar(bs)?; let mut Rs = Vec::with_capacity(l); for _ in 0..l { - Rs.push( CompressedRistretto(read()) ); + Rs.push(CompressedRistretto(read())); } Ok(PreparedBatch { bs, Rs }) } - + /// Returns buffer size required for serialization #[allow(non_snake_case)] pub fn byte_len(&self) -> usize { @@ -377,18 +414,17 @@ impl PreparedBatch{ /// Serializes into exactly sized buffer #[allow(non_snake_case)] pub fn write_bytes(&self, mut bytes: &mut [u8]) { - assert!(bytes.len() == self.byte_len()); - let mut place = |s: &[u8]| reserve_mut(&mut bytes,32).copy_from_slice(s); + assert!(bytes.len() == self.byte_len()); + let mut place = |s: &[u8]| reserve_mut(&mut bytes, 32).copy_from_slice(s); let mut bs = self.bs.to_bytes(); bs[31] |= 128; place(&bs[..]); for R in self.Rs.iter() { place(R.as_bytes()); } - } + } } - pub fn reserve_mut<'heap, T>(heap: &mut &'heap mut [T], len: usize) -> &'heap mut [T] { let tmp: &'heap mut [T] = core::mem::take(&mut *heap); let (reserved, tmp) = tmp.split_at_mut(len); @@ -396,7 +432,6 @@ pub fn reserve_mut<'heap, T>(heap: &mut &'heap mut [T], len: usize) -> &'heap mu reserved } - #[cfg(test)] mod test { #[cfg(feature = "alloc")] @@ -425,28 +460,30 @@ mod test { for i in 0..messages.len() { let mut keypair: Keypair = Keypair::generate_with(&mut csprng); - if i == 3 || i == 4 { keypair = keypairs[0].clone(); } + if i == 3 || i == 4 { + keypair = keypairs[0].clone(); + } signatures.push(keypair.sign(ctx.bytes(messages[i]))); keypairs.push(keypair); } let mut public_keys: Vec = keypairs.iter().map(|key| key.public).collect(); - public_keys.swap(1,2); + public_keys.swap(1, 2); let transcripts = messages.iter().map(|m| ctx.bytes(m)); - assert!( verify_batch(transcripts, &signatures[..], &public_keys[..], false).is_err() ); + assert!(verify_batch(transcripts, &signatures[..], &public_keys[..], false).is_err()); let transcripts = messages.iter().map(|m| ctx.bytes(m)); - assert!( verify_batch(transcripts, &signatures[..], &public_keys[..], true).is_err() ); + assert!(verify_batch(transcripts, &signatures[..], &public_keys[..], true).is_err()); - public_keys.swap(1,2); + public_keys.swap(1, 2); let transcripts = messages.iter().map(|m| ctx.bytes(m)); - assert!( verify_batch(transcripts, &signatures[..], &public_keys[..], false).is_ok() ); + assert!(verify_batch(transcripts, &signatures[..], &public_keys[..], false).is_ok()); let transcripts = messages.iter().map(|m| ctx.bytes(m)); - assert!( verify_batch(transcripts, &signatures[..], &public_keys[..], true).is_ok() ); + assert!(verify_batch(transcripts, &signatures[..], &public_keys[..], true).is_ok()); - signatures.swap(1,2); + signatures.swap(1, 2); let transcripts = messages.iter().map(|m| ctx.bytes(m)); - assert!( verify_batch(transcripts, &signatures[..], &public_keys[..], false).is_err() ); + assert!(verify_batch(transcripts, &signatures[..], &public_keys[..], false).is_err()); let transcripts = messages.iter().map(|m| ctx.bytes(m)); - assert!( verify_batch(transcripts, &signatures[..], &public_keys[..], true).is_err() ); + assert!(verify_batch(transcripts, &signatures[..], &public_keys[..], true).is_err()); } } diff --git a/src/context.rs b/src/context.rs index 99fe041..ac3d0dd 100644 --- a/src/context.rs +++ b/src/context.rs @@ -11,17 +11,16 @@ use core::cell::RefCell; -use rand_core::{RngCore,CryptoRng}; +use rand_core::{CryptoRng, RngCore}; use merlin::Transcript; -use curve25519_dalek::digest::{Update,FixedOutput,ExtendableOutput,XofReader}; -use curve25519_dalek::digest::generic_array::typenum::{U32,U64}; +use curve25519_dalek::digest::generic_array::typenum::{U32, U64}; +use curve25519_dalek::digest::{ExtendableOutput, FixedOutput, Update, XofReader}; use curve25519_dalek::ristretto::CompressedRistretto; // RistrettoPoint use curve25519_dalek::scalar::Scalar; - // === Signing context as transcript === // /// Schnorr signing transcript @@ -31,7 +30,7 @@ use curve25519_dalek::scalar::Scalar; /// transcript may exist before or persist after signing. /// /// In this trait, we provide an interface for Schnorr signature-like -/// constructions that is compatable with `merlin::Transcript`, but +/// constructions that is compatible with `merlin::Transcript`, but /// abstract enough to support conventional hash functions as well. /// /// We warn however that conventional hash functions do not provide @@ -83,42 +82,63 @@ pub trait SigningTranscript { /// Produce secret witness bytes from the protocol transcript /// and any "nonce seeds" kept with the secret keys. - fn witness_bytes_rng(&self, label: &'static [u8], dest: &mut [u8], nonce_seeds: &[&[u8]], rng: R) - where R: RngCore+CryptoRng; + fn witness_bytes_rng( + &self, + label: &'static [u8], + dest: &mut [u8], + nonce_seeds: &[&[u8]], + rng: R, + ) where + R: RngCore + CryptoRng; } - /// We delegates any mutable reference to its base type, like `&mut Rng` /// or similar to `BorrowMut<..>` do, but doing so here simplifies /// alternative implementations. impl SigningTranscript for &mut T -where T: SigningTranscript + ?Sized, +where + T: SigningTranscript + ?Sized, { #[inline(always)] - fn commit_bytes(&mut self, label: &'static [u8], bytes: &[u8]) - { (**self).commit_bytes(label,bytes) } + fn commit_bytes(&mut self, label: &'static [u8], bytes: &[u8]) { + (**self).commit_bytes(label, bytes) + } #[inline(always)] - fn proto_name(&mut self, label: &'static [u8]) - { (**self).proto_name(label) } + fn proto_name(&mut self, label: &'static [u8]) { + (**self).proto_name(label) + } #[inline(always)] - fn commit_point(&mut self, label: &'static [u8], compressed: &CompressedRistretto) - { (**self).commit_point(label, compressed) } + fn commit_point(&mut self, label: &'static [u8], compressed: &CompressedRistretto) { + (**self).commit_point(label, compressed) + } #[inline(always)] - fn challenge_bytes(&mut self, label: &'static [u8], dest: &mut [u8]) - { (**self).challenge_bytes(label,dest) } + fn challenge_bytes(&mut self, label: &'static [u8], dest: &mut [u8]) { + (**self).challenge_bytes(label, dest) + } #[inline(always)] - fn challenge_scalar(&mut self, label: &'static [u8]) -> Scalar - { (**self).challenge_scalar(label) } + fn challenge_scalar(&mut self, label: &'static [u8]) -> Scalar { + (**self).challenge_scalar(label) + } #[inline(always)] - fn witness_scalar(&self, label: &'static [u8], nonce_seeds: &[&[u8]]) -> Scalar - { (**self).witness_scalar(label,nonce_seeds) } + fn witness_scalar(&self, label: &'static [u8], nonce_seeds: &[&[u8]]) -> Scalar { + (**self).witness_scalar(label, nonce_seeds) + } #[inline(always)] - fn witness_bytes(&self, label: &'static [u8], dest: &mut [u8], nonce_seeds: &[&[u8]]) - { (**self).witness_bytes(label,dest,nonce_seeds) } + fn witness_bytes(&self, label: &'static [u8], dest: &mut [u8], nonce_seeds: &[&[u8]]) { + (**self).witness_bytes(label, dest, nonce_seeds) + } #[inline(always)] - fn witness_bytes_rng(&self, label: &'static [u8], dest: &mut [u8], nonce_seeds: &[&[u8]], rng: R) - where R: RngCore+CryptoRng - { (**self).witness_bytes_rng(label,dest,nonce_seeds,rng) } + fn witness_bytes_rng( + &self, + label: &'static [u8], + dest: &mut [u8], + nonce_seeds: &[&[u8]], + rng: R, + ) where + R: RngCore + CryptoRng, + { + (**self).witness_bytes_rng(label, dest, nonce_seeds, rng) + } } /// We delegate `SigningTranscript` methods to the corresponding @@ -134,8 +154,14 @@ impl SigningTranscript for Transcript { Transcript::challenge_bytes(self, label, dest) } - fn witness_bytes_rng(&self, label: &'static [u8], dest: &mut [u8], nonce_seeds: &[&[u8]], mut rng: R) - where R: RngCore+CryptoRng + fn witness_bytes_rng( + &self, + label: &'static [u8], + dest: &mut [u8], + nonce_seeds: &[&[u8]], + mut rng: R, + ) where + R: RngCore + CryptoRng, { let mut br = self.build_rng(); for ns in nonce_seeds { @@ -146,7 +172,6 @@ impl SigningTranscript for Transcript { } } - /// Schnorr signing context /// /// We expect users to have separate `SigningContext`s for each role @@ -169,7 +194,7 @@ pub struct SigningContext(Transcript); /// Initialize a signing context from a static byte string that /// identifies the signature's role in the larger protocol. #[inline(always)] -pub fn signing_context(context : &[u8]) -> SigningContext { +pub fn signing_context(context: &[u8]) -> SigningContext { SigningContext::new(context) } @@ -177,9 +202,9 @@ impl SigningContext { /// Initialize a signing context from a static byte string that /// identifies the signature's role in the larger protocol. #[inline(always)] - pub fn new(context : &[u8]) -> SigningContext { + pub fn new(context: &[u8]) -> SigningContext { let mut t = Transcript::new(b"SigningContext"); - t.append_message(b"",context); + t.append_message(b"", context); SigningContext(t) } @@ -210,7 +235,7 @@ impl SigningContext { /// Initialize an owned signing transcript on a message provided as /// a hash function with 256 bit output. #[inline(always)] - pub fn hash256>(&self, h: D) -> Transcript { + pub fn hash256>(&self, h: D) -> Transcript { let mut prehash = [0u8; 32]; prehash.copy_from_slice(h.finalize_fixed().as_slice()); let mut t = self.0.clone(); @@ -221,7 +246,7 @@ impl SigningContext { /// Initialize an owned signing transcript on a message provided as /// a hash function with 512 bit output, usually a gross over kill. #[inline(always)] - pub fn hash512>(&self, h: D) -> Transcript { + pub fn hash512>(&self, h: D) -> Transcript { let mut prehash = [0u8; 64]; prehash.copy_from_slice(h.finalize_fixed().as_slice()); let mut t = self.0.clone(); @@ -230,7 +255,6 @@ impl SigningContext { } } - /// Very simple transcript construction from a modern hash function. /// /// We provide this transcript type to directly use conventional hash @@ -257,7 +281,8 @@ impl SigningContext { /// domain separation provided by our methods. We do this to make /// `&mut XoFTranscript : SigningTranscript` safe. pub struct XoFTranscript(H) -where H: Update + ExtendableOutput + Clone; +where + H: Update + ExtendableOutput + Clone; fn input_bytes(h: &mut H, bytes: &[u8]) { let l = bytes.len() as u64; @@ -266,7 +291,8 @@ fn input_bytes(h: &mut H, bytes: &[u8]) { } impl XoFTranscript -where H: Update + ExtendableOutput + Clone +where + H: Update + ExtendableOutput + Clone, { /// Create a `XoFTranscript` from a conventional hash functions with an extensible output mode. /// @@ -274,18 +300,24 @@ where H: Update + ExtendableOutput + Clone /// provided, so that our domain separation works correctly even /// when using `&mut XoFTranscript : SigningTranscript`. #[inline(always)] - pub fn new(h: H) -> XoFTranscript { XoFTranscript(h) } + pub fn new(h: H) -> XoFTranscript { + XoFTranscript(h) + } } impl From for XoFTranscript -where H: Update + ExtendableOutput + Clone +where + H: Update + ExtendableOutput + Clone, { #[inline(always)] - fn from(h: H) -> XoFTranscript { XoFTranscript(h) } + fn from(h: H) -> XoFTranscript { + XoFTranscript(h) + } } impl SigningTranscript for XoFTranscript -where H: Update + ExtendableOutput + Clone +where + H: Update + ExtendableOutput + Clone, { fn commit_bytes(&mut self, label: &'static [u8], bytes: &[u8]) { self.0.update(b"co"); @@ -301,8 +333,14 @@ where H: Update + ExtendableOutput + Clone self.0.clone().chain(b"xof").finalize_xof().read(dest); } - fn witness_bytes_rng(&self, label: &'static [u8], dest: &mut [u8], nonce_seeds: &[&[u8]], mut rng: R) - where R: RngCore+CryptoRng + fn witness_bytes_rng( + &self, + label: &'static [u8], + dest: &mut [u8], + nonce_seeds: &[&[u8]], + mut rng: R, + ) where + R: RngCore + CryptoRng, { let mut h = self.0.clone().chain(b"wb"); input_bytes(&mut h, label); @@ -319,7 +357,6 @@ where H: Update + ExtendableOutput + Clone } } - /// Schnorr signing transcript with the default `ThreadRng` replaced /// by an arbitrary `CryptoRng`. /// @@ -334,29 +371,43 @@ where H: Update + ExtendableOutput + Clone /// produces secure signatures, we recommend against doing this in /// production because we implement protocols like multi-signatures /// which likely become vulnerable when derandomized. -pub struct SigningTranscriptWithRng -where T: SigningTranscript, R: RngCore+CryptoRng +pub struct SigningTranscriptWithRng +where + T: SigningTranscript, + R: RngCore + CryptoRng, { t: T, rng: RefCell, } -impl SigningTranscript for SigningTranscriptWithRng -where T: SigningTranscript, R: RngCore+CryptoRng +impl SigningTranscript for SigningTranscriptWithRng +where + T: SigningTranscript, + R: RngCore + CryptoRng, { - fn commit_bytes(&mut self, label: &'static [u8], bytes: &[u8]) - { self.t.commit_bytes(label, bytes) } - - fn challenge_bytes(&mut self, label: &'static [u8], dest: &mut [u8]) - { self.t.challenge_bytes(label, dest) } + fn commit_bytes(&mut self, label: &'static [u8], bytes: &[u8]) { + self.t.commit_bytes(label, bytes) + } - fn witness_bytes(&self, label: &'static [u8], dest: &mut [u8], nonce_seeds: &[&[u8]]) - { self.witness_bytes_rng(label, dest, nonce_seeds, &mut *self.rng.borrow_mut()) } + fn challenge_bytes(&mut self, label: &'static [u8], dest: &mut [u8]) { + self.t.challenge_bytes(label, dest) + } - fn witness_bytes_rng(&self, label: &'static [u8], dest: &mut [u8], nonce_seeds: &[&[u8]], rng: RR) - where RR: RngCore+CryptoRng - { self.t.witness_bytes_rng(label,dest,nonce_seeds,rng) } + fn witness_bytes(&self, label: &'static [u8], dest: &mut [u8], nonce_seeds: &[&[u8]]) { + self.witness_bytes_rng(label, dest, nonce_seeds, &mut *self.rng.borrow_mut()) + } + fn witness_bytes_rng( + &self, + label: &'static [u8], + dest: &mut [u8], + nonce_seeds: &[&[u8]], + rng: RR, + ) where + RR: RngCore + CryptoRng, + { + self.t.witness_bytes_rng(label, dest, nonce_seeds, rng) + } } /// Attach a `CryptoRng` to a `SigningTranscript` to replace the default `ThreadRng`. @@ -366,11 +417,14 @@ where T: SigningTranscript, R: RngCore+CryptoRng /// however because, although such derandomization produces secure Schnorr /// signatures, we do implement protocols here like multi-signatures which /// likely become vulnerable when derandomized. -pub fn attach_rng(t: T, rng: R) -> SigningTranscriptWithRng -where T: SigningTranscript, R: RngCore+CryptoRng +pub fn attach_rng(t: T, rng: R) -> SigningTranscriptWithRng +where + T: SigningTranscript, + R: RngCore + CryptoRng, { SigningTranscriptWithRng { - t, rng: RefCell::new(rng) + t, + rng: RefCell::new(rng), } } @@ -379,9 +433,10 @@ use rand_chacha::ChaChaRng; /// Attach a `ChaChaRng` to a `Transcript` to repalce the default `ThreadRng` #[cfg(feature = "rand_chacha")] -pub fn attach_chacharng(t: T, seed: [u8; 32]) -> SigningTranscriptWithRng -where T: SigningTranscript +pub fn attach_chacharng(t: T, seed: [u8; 32]) -> SigningTranscriptWithRng +where + T: SigningTranscript, { use rand_core::SeedableRng; - attach_rng(t,ChaChaRng::from_seed(seed)) + attach_rng(t, ChaChaRng::from_seed(seed)) } diff --git a/src/errors.rs b/src/errors.rs index e8c73c0..67baebd 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -14,10 +14,10 @@ // Display) should be snake cased, for some reason. #![allow(non_snake_case)] +use crate::identifier::Identifier; use core::fmt; use core::fmt::Display; - /// `Result` specialized to this crate for convenience. pub type SignatureResult = Result; @@ -88,7 +88,7 @@ pub enum SignatureError { /// Describes the type returning the error description: &'static str, /// Length expected by the constructor in bytes - length: usize + length: usize, }, /// Signature not marked as schnorrkel, maybe try ed25519 instead. NotMarkedSchnorrkel, @@ -116,26 +116,32 @@ impl Display for SignatureError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { use self::SignatureError::*; match *self { - EquationFalse => - write!(f, "Verification equation failed"), - PointDecompressionError => - write!(f, "Cannot decompress Ristretto point"), - ScalarFormatError => - write!(f, "Cannot use scalar with high-bit set"), - InvalidKey => - write!(f, "The provided key is not valid"), - BytesLengthError { name, length, .. } => - write!(f, "{name} must be {length} bytes in length"), - NotMarkedSchnorrkel => - write!(f, "Signature bytes not marked as a schnorrkel signature"), - MuSigAbsent { musig_stage, } => - write!(f, "Absent {musig_stage} violated multi-signature protocol"), - MuSigInconsistent { musig_stage, duplicate, } => + EquationFalse => write!(f, "Verification equation failed"), + PointDecompressionError => write!(f, "Cannot decompress Ristretto point"), + ScalarFormatError => write!(f, "Cannot use scalar with high-bit set"), + InvalidKey => write!(f, "The provided key is not valid"), + BytesLengthError { name, length, .. } => { + write!(f, "{name} must be {length} bytes in length") + } + NotMarkedSchnorrkel => { + write!(f, "Signature bytes not marked as a schnorrkel signature") + } + MuSigAbsent { musig_stage } => { + write!(f, "Absent {musig_stage} violated multi-signature protocol") + } + MuSigInconsistent { + musig_stage, + duplicate, + } => { if duplicate { write!(f, "Inconsistent duplicate {musig_stage} in multi-signature") } else { - write!(f, "Inconsistent {musig_stage} violated multi-signature protocol") - }, + write!( + f, + "Inconsistent {musig_stage} violated multi-signature protocol" + ) + } + } } } } @@ -149,7 +155,78 @@ impl failure::Fail for SignatureError {} /// `impl From for E where E: serde::de::Error`. #[cfg(feature = "serde")] pub fn serde_error_from_signature_error(err: SignatureError) -> E -where E: serde_crate::de::Error +where + E: serde::de::Error, { E::custom(err) } + +/// A result for the SimplPedPoP protocol. +pub type DKGResult = Result; + +/// An error ocurred during the execution of the SimplPedPoP protocol. +#[derive(Debug, Clone, Eq, PartialEq)] +pub enum DKGError { + /// Invalid Proof of Possession. + InvalidProofOfPossession(SignatureError), + /// Invalid certificate. + InvalidCertificate(SignatureError), + /// Threshold cannot be greater than the number of participants. + ExcessiveThreshold, + /// Threshold must be at least 2. + InsufficientThreshold, + /// Number of participants is invalid. + InvalidNumberOfParticipants, + /// Incorrect number of coefficient commitments of the secret polynomial. + IncorrectNumberOfCoefficientCommitments { + /// The expected value. + expected: usize, + /// The actual value. + actual: usize, + }, + /// Secret share verification failed. + InvalidSecretShare(Identifier), + /// Invalid secret. + InvalidSecret, + /// Unknown identifier. + UnknownIdentifier, + /// Shared public key mismatch. + SharedPublicKeyMismatch, + /// Identifier cannot be a zero scalar. + InvalidIdentifier, + /// Incorrect number of identifiers. + IncorrectNumberOfIdentifiers { + /// The expected value. + expected: usize, + /// The actual value. + actual: usize, + }, + /// Incorrect number of private messages. + IncorrectNumberOfPrivateMessages { + /// The expected value. + expected: usize, + /// The actual value. + actual: usize, + }, + /// Incorrect number of round 1 public messages. + IncorrectNumberOfRound1PublicMessages { + /// The expected value. + expected: usize, + /// The actual value. + actual: usize, + }, + /// Incorrect number of round 2 public messages. + IncorrectNumberOfRound2PublicMessages { + /// The expected value. + expected: usize, + /// The actual value. + actual: usize, + }, + /// Incorrect number of polynomial commitments. + IncorrectNumberOfSecretPolynomialCommitments { + /// The expected value. + expected: usize, + /// The actual value. + actual: usize, + }, +} diff --git a/src/identifier.rs b/src/identifier.rs new file mode 100644 index 0000000..ed1ed78 --- /dev/null +++ b/src/identifier.rs @@ -0,0 +1,62 @@ +//! The identifier of a participant in a multiparty protocol. + +use core::cmp::Ordering; + +use crate::errors::DKGError; +use curve25519_dalek::Scalar; + +/// The identifier is represented by a scalar. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct Identifier(pub(crate) Scalar); + +impl PartialOrd for Identifier { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for Identifier { + fn cmp(&self, other: &Self) -> Ordering { + let serialized_self = self.0.as_bytes(); + let serialized_other = other.0.as_bytes(); + + // The default cmp uses lexicographic order; so we need the elements in big endian + serialized_self + .as_ref() + .iter() + .rev() + .cmp(serialized_other.as_ref().iter().rev()) + } +} + +impl TryFrom for Identifier { + type Error = DKGError; + + fn try_from(n: u16) -> Result { + if n == 0 { + Err(DKGError::InvalidIdentifier) + } else { + // Classic left-to-right double-and-add algorithm that skips the first bit 1 (since + // identifiers are never zero, there is always a bit 1), thus `sum` starts with 1 too. + let one = Scalar::ONE; + let mut sum = Scalar::ONE; + + let bits = (n.to_be_bytes().len() as u32) * 8; + for i in (0..(bits - n.leading_zeros() - 1)).rev() { + sum = sum + sum; + if n & (1 << i) != 0 { + sum += one; + } + } + Ok(Self(sum)) + } + } +} + +impl TryFrom for Identifier { + type Error = DKGError; + + fn try_from(value: Scalar) -> Result { + Ok(Self(value)) + } +} diff --git a/src/lib.rs b/src/lib.rs index 323bdaa..31a21c5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -231,22 +231,29 @@ extern crate std; #[cfg(feature = "alloc")] extern crate alloc; -use getrandom_or_panic::{RngCore,CryptoRng,getrandom_or_panic}; use curve25519_dalek::scalar::Scalar; +use getrandom_or_panic::{getrandom_or_panic, CryptoRng, RngCore}; #[macro_use] mod serdey; +pub mod keys; pub mod points; mod scalars; -pub mod keys; +pub mod cert; pub mod context; -pub mod sign; -pub mod vrf; pub mod derive; -pub mod cert; pub mod errors; +pub mod identifier; +pub mod sign; +pub mod vrf; + +#[cfg(feature = "alloc")] +pub mod polynomial; + +#[cfg(feature = "alloc")] +pub mod simplpedpop; #[cfg(all(feature = "aead", feature = "getrandom"))] pub mod aead; @@ -256,17 +263,20 @@ mod batch; // Not safe because need randomness -#[cfg_attr(not(test), deprecated(since = "0.11.0", note = "This module will be replaced in the future"))] +#[cfg_attr( + not(test), + deprecated(since = "0.11.0", note = "This module will be replaced in the future") +)] #[cfg(feature = "std")] pub mod musig; +pub use crate::context::signing_context; // SigningContext,SigningTranscript +pub use crate::errors::{SignatureError, SignatureResult}; pub use crate::keys::*; // {MiniSecretKey,SecretKey,PublicKey,Keypair,ExpansionMode}; + *_LENGTH -pub use crate::context::{signing_context}; // SigningContext,SigningTranscript -pub use crate::sign::{Signature,SIGNATURE_LENGTH}; -pub use crate::errors::{SignatureError,SignatureResult}; +pub use crate::sign::{Signature, SIGNATURE_LENGTH}; #[cfg(feature = "alloc")] -pub use crate::batch::{verify_batch,verify_batch_rng,verify_batch_deterministic,PreparedBatch}; +pub use crate::batch::{verify_batch, verify_batch_deterministic, verify_batch_rng, PreparedBatch}; pub(crate) fn scalar_from_canonical_bytes(bytes: [u8; 32]) -> Option { let key = Scalar::from_canonical_bytes(bytes); diff --git a/src/polynomial.rs b/src/polynomial.rs new file mode 100644 index 0000000..3084d48 --- /dev/null +++ b/src/polynomial.rs @@ -0,0 +1,254 @@ +//! Implementation of a polynomial and related operations. + +use alloc::vec; +use alloc::vec::Vec; +use curve25519_dalek::{traits::Identity, RistrettoPoint, Scalar}; +use rand_core::{CryptoRng, RngCore}; +use zeroize::ZeroizeOnDrop; + +use crate::simplpedpop::GENERATOR; + +pub(crate) type Coefficient = Scalar; +pub(crate) type Value = Scalar; +pub(crate) type ValueCommitment = RistrettoPoint; +pub(crate) type CoefficientCommitment = RistrettoPoint; + +/// A polynomial. +#[derive(Debug, Clone, ZeroizeOnDrop)] +pub struct Polynomial { + pub(crate) constant_coefficient: Coefficient, + pub(crate) non_constant_coefficients: Vec, +} + +impl Polynomial { + pub(crate) fn generate(rng: &mut R, degree: u16) -> Self { + let constant_coefficient = Scalar::random(rng); + + let mut non_constant_coefficients = Vec::new(); + for _ in 0..degree as usize - 2 { + non_constant_coefficients.push(Scalar::random(rng)); + } + + Self { + constant_coefficient, + non_constant_coefficients, + } + } + + pub(crate) fn evaluate(&self, value: &Value) -> Value { + let ell_scalar = value; + let mut result = Scalar::ZERO; + + for coefficient in self.non_constant_coefficients.iter().rev() { + result = (result + coefficient) * ell_scalar; + } + + result += self.constant_coefficient; + + result + } +} + +/// A polynomial commitment. +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct PolynomialCommitment { + pub(crate) constant_coefficient_commitment: CoefficientCommitment, + pub(crate) non_constant_coefficients_commitments: Vec, +} + +impl PolynomialCommitment { + pub(crate) fn generate(polynomial: &Polynomial) -> Self { + let commitment = GENERATOR * polynomial.constant_coefficient; + + let non_constant_coefficients_commitments = polynomial + .non_constant_coefficients + .iter() + .map(|coefficient| GENERATOR * coefficient) + .collect(); + + Self { + constant_coefficient_commitment: commitment, + non_constant_coefficients_commitments, + } + } + + pub(crate) fn evaluate(&self, identifier: Value) -> ValueCommitment { + let mut sum = RistrettoPoint::identity(); + let mut i_to_the_k = Scalar::ONE; + + sum += self.constant_coefficient_commitment * i_to_the_k; + + for coefficient_commitment in &self.non_constant_coefficients_commitments { + i_to_the_k *= identifier; + sum += coefficient_commitment * i_to_the_k; + } + + sum + } + + pub(crate) fn sum_polynomial_commitments( + polynomials_commitments: &[&PolynomialCommitment], + ) -> PolynomialCommitment { + let max_length = polynomials_commitments + .iter() + .map(|c| c.non_constant_coefficients_commitments.len()) + .max() + .unwrap_or(0); + + let mut total_commitment = vec![RistrettoPoint::default(); max_length + 1]; + + for polynomial_commitment in polynomials_commitments { + total_commitment[0] += polynomial_commitment.constant_coefficient_commitment; + for (i, coeff_commitment) in polynomial_commitment + .non_constant_coefficients_commitments + .iter() + .enumerate() + { + if i < total_commitment.len() - 1 { + total_commitment[i + 1] += coeff_commitment; + } + } + } + + PolynomialCommitment { + constant_coefficient_commitment: total_commitment[0], + non_constant_coefficients_commitments: total_commitment[1..].to_vec(), + } + } +} + +#[cfg(test)] +mod tests { + use crate::{ + polynomial::{Coefficient, Polynomial, PolynomialCommitment}, + simplpedpop::GENERATOR, + }; + + use alloc::vec::Vec; + use curve25519_dalek::Scalar; + use rand::rngs::OsRng; + + #[test] + fn test_generate_polynomial_commitment_valid() { + let min_signers = 3; + + let polynomial = Polynomial::generate(&mut OsRng, min_signers); + + let polynomial_commitment = PolynomialCommitment::generate(&polynomial); + + assert_eq!( + polynomial.non_constant_coefficients.len() + 1, + (min_signers - 1) as usize + ); + + assert_eq!( + polynomial_commitment + .non_constant_coefficients_commitments + .len() + + 1, + (min_signers - 1) as usize + ); + } + + #[test] + fn test_evaluate_polynomial() { + let coefficients: Vec = + vec![Scalar::from(3u64), Scalar::from(2u64), Scalar::from(1u64)]; // Polynomial x^2 + 2x + 3 + + let polynomial = Polynomial { + constant_coefficient: coefficients[0], + non_constant_coefficients: coefficients[1..].to_vec(), + }; + + 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 { + constant_coefficient_commitment: GENERATOR * Scalar::from(1u64), + non_constant_coefficients_commitments: vec![ + GENERATOR * Scalar::from(2u64), + GENERATOR * Scalar::from(3u64), + ], + }; + + let polynomial_commitment2 = PolynomialCommitment { + constant_coefficient_commitment: GENERATOR * Scalar::from(4u64), + non_constant_coefficients_commitments: vec![ + GENERATOR * Scalar::from(5u64), + GENERATOR * Scalar::from(6u64), + ], + }; + + let summed_polynomial_commitments = PolynomialCommitment::sum_polynomial_commitments(&[ + &polynomial_commitment1, + &polynomial_commitment2, + ]); + + let expected_constant_coefficient_commitment = GENERATOR * Scalar::from(5u64); // 1 + 4 + let expected_non_constant_coefficients_commitments = vec![ + GENERATOR * Scalar::from(7u64), // 2 + 5 + GENERATOR * Scalar::from(9u64), // 3 + 6 + ]; + + assert_eq!( + summed_polynomial_commitments + .constant_coefficient_commitment + .compress(), + expected_constant_coefficient_commitment.compress(), + "Constant coefficients commitments do not match" + ); + + assert_eq!( + summed_polynomial_commitments + .non_constant_coefficients_commitments + .len(), + expected_non_constant_coefficients_commitments.len(), + "Non-constant coefficient commitment lengths do not match" + ); + + for (actual, expected) in summed_polynomial_commitments + .non_constant_coefficients_commitments + .iter() + .zip(expected_non_constant_coefficients_commitments.iter()) + { + assert_eq!( + actual.compress(), + expected.compress(), + "Non-constant coefficient commitments do not match" + ); + } + } + + #[test] + fn test_evaluate_polynomial_commitment() { + // f(x) = 3 + 2x + x^2 + let constant_commitment = Scalar::from(3u64) * GENERATOR; + let linear_commitment = Scalar::from(2u64) * GENERATOR; + let quadratic_commitment = Scalar::from(1u64) * GENERATOR; + + let polynomial_commitment = PolynomialCommitment { + constant_coefficient_commitment: constant_commitment, + non_constant_coefficients_commitments: vec![linear_commitment, quadratic_commitment], + }; + + let value = Scalar::from(2u64); + + // f(2) = 11 + let expected = Scalar::from(11u64) * GENERATOR; + + let result = polynomial_commitment.evaluate(value); + + assert_eq!( + result.compress(), + expected.compress(), + "The evaluated commitment does not match the expected result" + ); + } +} diff --git a/src/serdey.rs b/src/serdey.rs index 64c5e26..f152ef5 100644 --- a/src/serdey.rs +++ b/src/serdey.rs @@ -11,51 +11,60 @@ //! ### Various and tooling related to serde #[cfg(feature = "serde")] -macro_rules! serde_boilerplate { ($t:ty) => { -impl serde_crate::Serialize for $t { - fn serialize(&self, serializer: S) -> Result where S: serde_crate::Serializer { - let bytes = &self.to_bytes()[..]; - serde_bytes::Bytes::new(bytes).serialize(serializer) - } -} - -impl<'d> serde_crate::Deserialize<'d> for $t { - fn deserialize(deserializer: D) -> Result where D: serde_crate::Deserializer<'d> { - cfg_if::cfg_if!{ - if #[cfg(feature = "std")] { - let bytes = >::deserialize(deserializer)?; - } else if #[cfg(feature = "alloc")] { - let bytes = >::deserialize(deserializer)?; - } else { - let bytes = <&::serde_bytes::Bytes>::deserialize(deserializer)?; +macro_rules! serde_boilerplate { + ($t:ty) => { + impl serde::Serialize for $t { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let bytes = &self.to_bytes()[..]; + serde_bytes::Bytes::new(bytes).serialize(serializer) } } - Self::from_bytes(bytes.as_ref()) - .map_err(crate::errors::serde_error_from_signature_error) - } -} -} } // macro_rules! serde_boilerplate + impl<'d> serde::Deserialize<'d> for $t { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'d>, + { + cfg_if::cfg_if! { + if #[cfg(feature = "std")] { + let bytes = >::deserialize(deserializer)?; + } else if #[cfg(feature = "alloc")] { + let bytes = >::deserialize(deserializer)?; + } else { + let bytes = <&::serde_bytes::Bytes>::deserialize(deserializer)?; + } + } + + Self::from_bytes(bytes.as_ref()) + .map_err(crate::errors::serde_error_from_signature_error) + } + } + }; +} // macro_rules! serde_boilerplate #[cfg(not(feature = "serde"))] -macro_rules! serde_boilerplate { ($t:ty) => { } } +macro_rules! serde_boilerplate { + ($t:ty) => {}; +} #[cfg(all(test, feature = "serde"))] mod test { use std::vec::Vec; - use bincode::{serialize, serialized_size, deserialize}; - use serde_json::{to_value, from_value, to_string, from_str}; + use bincode::{deserialize, serialize, serialized_size}; + use serde_json::{from_str, from_value, to_string, to_value}; - use curve25519_dalek::ristretto::{CompressedRistretto}; + use curve25519_dalek::ristretto::CompressedRistretto; use crate::*; - static COMPRESSED_PUBLIC_KEY : CompressedRistretto = CompressedRistretto([ - 208, 120, 140, 129, 177, 179, 237, 159, - 252, 160, 028, 013, 206, 005, 211, 241, - 192, 218, 001, 097, 130, 241, 020, 169, - 119, 046, 246, 029, 079, 080, 077, 084]); + static COMPRESSED_PUBLIC_KEY: CompressedRistretto = CompressedRistretto([ + 208, 120, 140, 129, 177, 179, 237, 159, 252, 160, 028, 013, 206, 005, 211, 241, 192, 218, + 001, 097, 130, 241, 020, 169, 119, 046, 246, 029, 079, 080, 077, 084, + ]); /* static ED25519_PUBLIC_KEY: CompressedEdwardsY = CompressedEdwardsY([ @@ -66,22 +75,17 @@ mod test { */ static ED25519_SECRET_KEY: MiniSecretKey = MiniSecretKey([ - 062, 070, 027, 163, 092, 182, 011, 003, - 077, 234, 098, 004, 011, 127, 079, 228, - 243, 187, 150, 073, 201, 137, 076, 022, - 085, 251, 152, 002, 241, 042, 072, 054, ]); + 062, 070, 027, 163, 092, 182, 011, 003, 077, 234, 098, 004, 011, 127, 079, 228, 243, 187, + 150, 073, 201, 137, 076, 022, 085, 251, 152, 002, 241, 042, 072, 054, + ]); /// Ed25519 signature with the above keypair of a blank message. static SIGNATURE_BYTES: [u8; SIGNATURE_LENGTH] = [ - 010, 126, 151, 143, 157, 064, 047, 001, - 196, 140, 179, 058, 226, 152, 018, 102, - 160, 123, 080, 016, 210, 086, 196, 028, - 053, 231, 012, 157, 169, 019, 158, 063, - 045, 154, 238, 007, 053, 185, 227, 229, - 079, 108, 213, 080, 124, 252, 084, 167, - 216, 085, 134, 144, 129, 149, 041, 081, - 063, 120, 126, 100, 092, 059, 050, 138, ]; - + 010, 126, 151, 143, 157, 064, 047, 001, 196, 140, 179, 058, 226, 152, 018, 102, 160, 123, + 080, 016, 210, 086, 196, 028, 053, 231, 012, 157, 169, 019, 158, 063, 045, 154, 238, 007, + 053, 185, 227, 229, 079, 108, 213, 080, 124, 252, 084, 167, 216, 085, 134, 144, 129, 149, + 041, 081, 063, 120, 126, 100, 092, 059, 050, 138, + ]; #[test] fn serialize_deserialize_signature() { @@ -172,21 +176,21 @@ mod test { #[test] fn serialize_public_key_size() { let public_key = PublicKey::from_compressed(COMPRESSED_PUBLIC_KEY).unwrap(); - assert_eq!(serialized_size(&public_key).unwrap(), 32+8); // Size specific to bincode==1.0.1 + assert_eq!(serialized_size(&public_key).unwrap(), 32 + 8); // Size specific to bincode==1.0.1 } #[test] fn serialize_signature_size() { let signature: Signature = Signature::from_bytes(&SIGNATURE_BYTES).unwrap(); - assert_eq!(serialized_size(&signature).unwrap(), 64+8); // Size specific to bincode==1.0.1 + assert_eq!(serialized_size(&signature).unwrap(), 64 + 8); // Size specific to bincode==1.0.1 } #[test] fn serialize_secret_key_size() { - assert_eq!(serialized_size(&ED25519_SECRET_KEY).unwrap(), 32+8); + assert_eq!(serialized_size(&ED25519_SECRET_KEY).unwrap(), 32 + 8); let secret_key = ED25519_SECRET_KEY.expand(ExpansionMode::Ed25519); - assert_eq!(serialized_size(&secret_key).unwrap(), 64+8); // Sizes specific to bincode==1.0.1 + assert_eq!(serialized_size(&secret_key).unwrap(), 64 + 8); // Sizes specific to bincode==1.0.1 let secret_key = ED25519_SECRET_KEY.expand(ExpansionMode::Uniform); - assert_eq!(serialized_size(&secret_key).unwrap(), 64+8); // Sizes specific to bincode==1.0.1 + assert_eq!(serialized_size(&secret_key).unwrap(), 64 + 8); // Sizes specific to bincode==1.0.1 } } diff --git a/src/simplpedpop.rs b/src/simplpedpop.rs new file mode 100644 index 0000000..5bea66d --- /dev/null +++ b/src/simplpedpop.rs @@ -0,0 +1,1499 @@ +//! Implementation of SimplPedPoP (), a DKG based on PedPoP, which in turn is based +//! on Pedersen's DKG. +//! All of them have as the fundamental building block the Shamir's Secret Sharing scheme. +//! +//! The protocol is divided into three rounds. In each round some data and some messages are produced and verified +//! (if received from a previous round). Messages can be private, which means that they can be relayed through a +//! coordinator to its recipients, or private, which means they need to be sent directly to its recipients (unless +//! encrypted). + +use crate::{ + errors::DKGError, + identifier::Identifier, + polynomial::{Coefficient, CoefficientCommitment, Polynomial, PolynomialCommitment, Value}, + PublicKey, SecretKey, Signature, +}; +use alloc::collections::BTreeSet; +use curve25519_dalek::{constants::RISTRETTO_BASEPOINT_POINT, RistrettoPoint, Scalar}; +use derive_getters::Getters; +use rand_core::{CryptoRng, RngCore}; +use zeroize::ZeroizeOnDrop; + +pub(crate) const GENERATOR: RistrettoPoint = RISTRETTO_BASEPOINT_POINT; + +pub(crate) type SecretPolynomialCommitment = PolynomialCommitment; +pub(crate) type SecretPolynomial = Polynomial; +pub(crate) type SharedPublicKey = PublicKey; +pub(crate) type TotalSecretShare = SecretShare; +pub(crate) type TotalSecretShareCommitment = CoefficientCommitment; +pub(crate) type SecretCommitment = CoefficientCommitment; +pub(crate) type Certificate = Signature; +pub(crate) type ProofOfPossession = Signature; +pub(crate) type Secret = Coefficient; + +/// A secret share, which corresponds to an evaluation of a value that identifies a participant in a secret polynomial. +#[derive(Debug, Clone, ZeroizeOnDrop)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct SecretShare(pub(crate) Value); + +/// The parameters of a given execution of the SimplPedPoP protocol. +#[derive(Debug, Clone, Getters)] +pub struct Parameters { + participants: u16, + threshold: u16, + own_identifier: Identifier, + others_identifiers: BTreeSet, +} + +impl Parameters { + /// Create new parameters. + pub fn new( + participants: u16, + threshold: u16, + own_identifier: Identifier, + others_identifiers: BTreeSet, + ) -> Parameters { + Parameters { + participants, + threshold, + own_identifier, + others_identifiers, + } + } + + pub(crate) fn validate(&self) -> Result<(), DKGError> { + if self.own_identifier.0 == Scalar::ZERO { + return Err(DKGError::InvalidIdentifier); + } + + for other_identifier in &self.others_identifiers { + if other_identifier.0 == Scalar::ZERO { + return Err(DKGError::InvalidIdentifier); + } + } + + if self.threshold < 2 { + return Err(DKGError::InsufficientThreshold); + } + + if self.participants < 2 { + return Err(DKGError::InvalidNumberOfParticipants); + } + + if self.threshold > self.participants { + return Err(DKGError::ExcessiveThreshold); + } + + if self.others_identifiers.len() != self.participants as usize - 1 { + return Err(DKGError::IncorrectNumberOfIdentifiers { + expected: self.participants as usize, + actual: self.others_identifiers.len() + 1, + }); + } + + Ok(()) + } +} + +fn derive_secret_key_from_secret(secret: Secret, mut rng: R) -> SecretKey { + let mut bytes = [0u8; 64]; + let mut nonce: [u8; 32] = [0u8; 32]; + + rng.fill_bytes(&mut nonce); + let secret_bytes = secret.to_bytes(); + + bytes[..32].copy_from_slice(&secret_bytes[..]); + bytes[32..].copy_from_slice(&nonce[..]); + + SecretKey::from_bytes(&bytes[..]).unwrap() // This never fails because bytes has length 64 and the key is a scalar +} + +/// SimplPedPoP round 1. +/// +/// The participant commits to a secret polynomial f(x) of degree t-1, where t is the threshold of the DKG and n +/// is the total number of participants. +/// +/// From the secret polynomial it derives a secret key from the secret and the secret shares f(i)...f(n) and it +/// generates a Proof of Possession of the secret f(0) by signing a message with the secret key. +/// +/// It stores the secret key and its own secret share in its private data, sends each secret share directly to its +/// corresponding recipient and the polynomial commitment to all the other participants (or to the coordinator). +pub mod round1 { + use crate::polynomial::PolynomialCommitment; + use crate::{errors::DKGResult, polynomial::Polynomial}; + use crate::{PublicKey, SecretKey}; + use alloc::collections::BTreeMap; + use curve25519_dalek::Scalar; + use derive_getters::Getters; + use merlin::Transcript; + use rand_core::{CryptoRng, RngCore}; + use zeroize::ZeroizeOnDrop; + + use super::{ + derive_secret_key_from_secret, Identifier, Parameters, ProofOfPossession, SecretPolynomial, + SecretPolynomialCommitment, SecretShare, + }; + + /// The private data generated by the participant in round 1. + #[derive(Debug, Clone, Getters)] + pub struct PrivateData { + pub(crate) secret_key: SecretKey, + pub(crate) secret_share: SecretShare, + } + + /// The public data generated by the participant in round 1. + #[derive(Debug, Clone, Getters)] + pub struct PublicData { + pub(crate) secret_polynomial_commitment: SecretPolynomialCommitment, + pub(crate) proof_of_possession: ProofOfPossession, + } + + /// The messages to be sent by the participant in round 1. + #[derive(Debug, Clone, Getters)] + pub struct Messages { + private_messages: BTreeMap, + public_message: PublicMessage, + } + + /// Public message to be sent by the participant to all the other participants or to the coordinator in round 1. + #[derive(Debug, Clone, Getters)] + #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] + pub struct PublicMessage { + secret_polynomial_commitment: SecretPolynomialCommitment, + proof_of_possession: ProofOfPossession, + } + + impl PublicMessage { + /// Creates a new public message. + pub fn new( + secret_polynomial_commitment: SecretPolynomialCommitment, + proof_of_possession: ProofOfPossession, + ) -> PublicMessage { + PublicMessage { + secret_polynomial_commitment, + proof_of_possession, + } + } + } + + /// Private message to sent by a participant to another participant or to the coordinator in encrypted form in round 1. + #[derive(Debug, Clone, ZeroizeOnDrop, Getters)] + #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] + pub struct PrivateMessage { + pub(crate) secret_share: SecretShare, + } + + impl PrivateMessage { + /// Creates a new private message. + pub fn new(secret_share: SecretShare) -> PrivateMessage { + PrivateMessage { secret_share } + } + } + + /// Runs the round 1 of the SimplPedPoP protocol. + pub fn run( + parameters: &Parameters, + mut rng: R, + ) -> DKGResult<(PrivateData, Messages, PublicData)> { + parameters.validate()?; + + let (private_data, proof_of_possession, secret_polynomial, public_data) = + generate_data(parameters, &mut rng); + + let messages = generate_messages( + parameters, + &proof_of_possession, + &secret_polynomial, + &public_data.secret_polynomial_commitment, + ); + + Ok((private_data, messages, public_data)) + } + + fn generate_data( + parameters: &Parameters, + mut rng: R, + ) -> (PrivateData, ProofOfPossession, SecretPolynomial, PublicData) { + let secret_polynomial = loop { + let temp_polynomial = Polynomial::generate(&mut rng, *parameters.threshold()); + // There must be a secret, which is the constant coefficient of the secret polynomial + if temp_polynomial.constant_coefficient != Scalar::ZERO { + break temp_polynomial; + } + }; + + let secret_polynomial_commitment = PolynomialCommitment::generate(&secret_polynomial); + + let secret_share = secret_polynomial.evaluate(¶meters.own_identifier().0); + + // This secret key will be used to sign the proof of possession and the certificate + let secret_key = derive_secret_key_from_secret(secret_polynomial.constant_coefficient, rng); + + let public_key = + PublicKey::from_point(secret_polynomial_commitment.constant_coefficient_commitment); + + let proof_of_possession = + secret_key.sign(Transcript::new(b"Proof of Possession"), &public_key); + + ( + PrivateData { + secret_key, + secret_share: SecretShare(secret_share), + }, + proof_of_possession, + secret_polynomial, + PublicData { + secret_polynomial_commitment, + proof_of_possession, + }, + ) + } + + fn generate_messages( + parameters: &Parameters, + proof_of_possession: &ProofOfPossession, + secret_polynomial: &SecretPolynomial, + secret_polynomial_commitment: &SecretPolynomialCommitment, + ) -> Messages { + let mut private_messages = BTreeMap::new(); + + for identifier in ¶meters.others_identifiers { + let secret_share = secret_polynomial.evaluate(&identifier.0); + private_messages.insert(*identifier, PrivateMessage::new(SecretShare(secret_share))); + } + + let public_message = + PublicMessage::new(secret_polynomial_commitment.clone(), *proof_of_possession); + + Messages { + private_messages, + public_message, + } + } +} + +/// SimplPedPoP round 2. +/// +/// The participant verifies the received messages of the other participants from round 1, the polynomial commitments, +/// the Proofs of Possession and the secret shares. +/// +/// It stores its own total secret share privately, which corresponds to the sum of all its secret shares (including its own) +/// It signs a transcript of the protocol execution (certificate) with its secret key, which contains the PoPs and the polynomial +/// commitments from all the participants (including its own), and sends it to all the other participants (or the +/// coordinator). +pub mod round2 { + use super::{ + round1::{self, PrivateMessage}, + Certificate, Identifier, Parameters, SecretCommitment, SecretShare, TotalSecretShare, + GENERATOR, + }; + use crate::{ + context::SigningTranscript, + errors::{DKGError, DKGResult}, + verify_batch, PublicKey, SecretKey, + }; + use alloc::{collections::BTreeMap, vec, vec::Vec}; + use curve25519_dalek::Scalar; + use merlin::Transcript; + use zeroize::ZeroizeOnDrop; + + /// The public data of round 2. + #[derive(Debug, Clone)] + pub struct PublicData { + pub(crate) transcript: T, + } + + /// The private data of round 2. + #[derive(Debug, Clone, ZeroizeOnDrop)] + pub struct PrivateData { + pub(crate) total_secret_share: TotalSecretShare, + } + + /// The public message of round 2. + #[derive(Debug, Clone)] + #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] + pub struct PublicMessage { + pub(crate) certificate: Certificate, + } + + /// Runs the round 2 of a SimplPedPoP protocol. + pub fn run( + parameters: &Parameters, + round1_private_data: round1::PrivateData, + round1_public_data: &round1::PublicData, + round1_public_messages: &BTreeMap, + round1_private_messages: BTreeMap, + transcript: T, + ) -> DKGResult<(PublicData, PrivateData, PublicMessage)> { + parameters.validate()?; + + if round1_public_messages.len() != *parameters.participants() as usize - 1 { + return Err(DKGError::IncorrectNumberOfRound1PublicMessages { + expected: *parameters.participants() as usize - 1, + actual: round1_public_messages.len(), + }); + } + + if round1_private_messages.len() != *parameters.participants() as usize - 1 { + return Err(DKGError::IncorrectNumberOfPrivateMessages { + expected: *parameters.participants() as usize - 1, + actual: round1_private_messages.len(), + }); + } + + verify_round1_messages(parameters, round1_public_messages, &round1_private_messages)?; + + let private_data = generate_private_data( + parameters, + &round1_private_messages, + &round1_private_data.secret_share, + )?; + + let public_data = + generate_public_data(round1_public_messages, round1_public_data, transcript)?; + + let secret_commitment = round1_public_data + .secret_polynomial_commitment() + .constant_coefficient_commitment; + + let public_message = generate_public_message( + round1_private_data.secret_key, + &secret_commitment, + public_data.transcript.clone(), + ); + + Ok((public_data, private_data, public_message)) + } + + fn generate_private_data( + parameters: &Parameters, + private_messages: &BTreeMap, + own_secret_share: &SecretShare, + ) -> DKGResult { + let mut total_secret_share = Scalar::ZERO; + + for id in parameters.others_identifiers() { + total_secret_share += private_messages + .get(id) + .ok_or(DKGError::UnknownIdentifier)? + .secret_share() + .0; + } + + total_secret_share += own_secret_share.0; + + let private_data = PrivateData { + total_secret_share: SecretShare(total_secret_share), + }; + + Ok(private_data) + } + + fn generate_public_data( + round1_public_messages: &BTreeMap, + round1_public_data: &round1::PublicData, + mut transcript: T, + ) -> DKGResult> { + // Writes the data of all the participants in the transcript ordered by their identifiers + for identifier in 1..=round1_public_messages.len() + 1 { + if let Some(round1_public_message) = + round1_public_messages.get(&(identifier as u16).try_into().unwrap()) + // This never fails because we previously checked for invalid identifiers + { + // Writes the data of the other participants in the transcript + transcript.commit_point( + b"SecretCommitment", + &round1_public_message + .secret_polynomial_commitment() + .constant_coefficient_commitment + .compress(), + ); + + for coefficient_commitment in &round1_public_message + .secret_polynomial_commitment() + .non_constant_coefficients_commitments + { + transcript + .commit_point(b"CoefficientCommitment", &coefficient_commitment.compress()); + } + + transcript.commit_point( + b"ProofOfPossessionR", + &round1_public_message.proof_of_possession().R, + ); + } else { + // Writes the data of the participant in the transcript + transcript.commit_point( + b"SecretCommitment", + &round1_public_data + .secret_polynomial_commitment() + .constant_coefficient_commitment + .compress(), + ); + + for coefficient_commitment in &round1_public_data + .secret_polynomial_commitment() + .non_constant_coefficients_commitments + { + transcript + .commit_point(b"CoefficientCommitment", &coefficient_commitment.compress()); + } + + transcript.commit_point( + b"ProofOfPossessionR", + &round1_public_data.proof_of_possession().R, + ); + } + } + + Ok(PublicData { transcript }) + } + + fn verify_round1_messages( + parameters: &Parameters, + round1_public_messages: &BTreeMap, + round1_private_messages: &BTreeMap, + ) -> DKGResult<()> { + for identifier in parameters.others_identifiers() { + let public_message = round1_public_messages + .get(identifier) + .ok_or(DKGError::UnknownIdentifier)?; + + let private_message = round1_private_messages + .get(identifier) + .ok_or(DKGError::UnknownIdentifier)?; + + verify_round1_private_message( + parameters, + public_message, + private_message, + *identifier, + )?; + } + + verify_round1_public_messages(parameters, round1_public_messages) + } + + fn verify_round1_public_messages( + parameters: &Parameters, + round1_public_messages: &BTreeMap, + ) -> DKGResult<()> { + let mut public_keys = Vec::new(); + let mut proofs_of_possession = Vec::new(); + + // The public keys are the secret commitments of the participants + for (identifier, public_message) in round1_public_messages { + if identifier != parameters.own_identifier() { + let public_key = PublicKey::from_point( + public_message + .secret_polynomial_commitment() + .constant_coefficient_commitment, + ); + public_keys.push(public_key); + proofs_of_possession.push(*public_message.proof_of_possession()); + } + } + + verify_batch( + vec![Transcript::new(b"Proof of Possession"); parameters.participants as usize - 1], + &proofs_of_possession[..], + &public_keys[..], + false, + ) + .map_err(DKGError::InvalidProofOfPossession) + } + + fn verify_round1_private_message( + params: &Parameters, + round1_public_message: &round1::PublicMessage, + round1_private_message: &PrivateMessage, + identifier: Identifier, + ) -> DKGResult<()> { + let expected_evaluation = GENERATOR * round1_private_message.secret_share().0; + + let evaluation = round1_public_message + .secret_polynomial_commitment() + .evaluate(params.own_identifier().0); + + if !(evaluation == expected_evaluation) { + Err(DKGError::InvalidSecretShare(identifier)) + } else { + Ok(()) + } + } + + fn generate_public_message( + secret_key: SecretKey, + secret_commitment: &SecretCommitment, + transcript: T, + ) -> PublicMessage { + let public_key = PublicKey::from_point(*secret_commitment); + + let certificate = secret_key.sign(transcript, &public_key); + + PublicMessage { certificate } + } +} + +/// SimplPedPoP round 3. +/// +/// The participant verifies the certificates from all the other participants and generates the shared public +/// key and the total secret shares commitments of the other partipants. +pub mod round3 { + use super::{ + round1, round2, Certificate, Identifier, Parameters, SharedPublicKey, + TotalSecretShareCommitment, + }; + use crate::{ + context::SigningTranscript, + errors::{DKGError, DKGResult}, + polynomial::PolynomialCommitment, + verify_batch, PublicKey, + }; + use alloc::{collections::BTreeMap, vec, vec::Vec}; + use curve25519_dalek::Scalar; + + /// Runs the round 3 of the SimplPedPoP protocol. + pub fn run( + parameters: &Parameters, + round2_public_messages: &BTreeMap, + round2_public_data: &round2::PublicData, + round1_public_data: &round1::PublicData, + round1_public_messages: &BTreeMap, + ) -> DKGResult<( + SharedPublicKey, + BTreeMap, + )> { + parameters.validate()?; + + if round2_public_messages.len() != *parameters.participants() as usize - 1 { + return Err(DKGError::IncorrectNumberOfRound2PublicMessages { + expected: *parameters.participants() as usize - 1, + actual: round2_public_messages.len(), + }); + } + + if round1_public_messages.len() != *parameters.participants() as usize - 1 { + return Err(DKGError::IncorrectNumberOfRound1PublicMessages { + expected: *parameters.participants() as usize - 1, + actual: round1_public_messages.len(), + }); + } + + verify_round2_messages( + parameters, + round1_public_messages, + round2_public_messages, + round2_public_data, + )?; + + generate_public_data(parameters, round1_public_messages, round1_public_data) + } + + fn verify_round2_messages( + parameters: &Parameters, + round1_public_messages: &BTreeMap, + round2_public_messages: &BTreeMap, + round2_state: &round2::PublicData, + ) -> DKGResult<()> { + let mut public_keys = Vec::new(); + + // The public keys are the secret commitments of the participants + for (id, round1_public_message) in round1_public_messages { + if id != parameters.own_identifier() { + let public_key = PublicKey::from_point( + round1_public_message + .secret_polynomial_commitment() + .constant_coefficient_commitment, + ); + public_keys.push(public_key); + } + } + + verify_batch( + vec![round2_state.transcript.clone(); parameters.participants as usize - 1], + &round2_public_messages + .values() + .map(|x| x.certificate) + .collect::>(), + &public_keys[..], + false, + ) + .map_err(DKGError::InvalidCertificate) + } + + fn generate_public_data( + parameters: &Parameters, + round1_public_messages: &BTreeMap, + round1_public_data: &round1::PublicData, + ) -> DKGResult<( + SharedPublicKey, + BTreeMap, + )> { + // Sum of the secret polynomial commitments of the other participants + let others_secret_polynomial_commitment = PolynomialCommitment::sum_polynomial_commitments( + &round1_public_messages + .iter() + .map(|x| x.1.secret_polynomial_commitment()) + .collect::>(), + ); + + // The total secret polynomial commitment, which includes the secret polynomial commitment of the participant + let total_secret_polynomial_commitment = + PolynomialCommitment::sum_polynomial_commitments(&[ + &others_secret_polynomial_commitment, + &round1_public_data.secret_polynomial_commitment, + ]); + + // The total secret shares commitments of all the participants + let mut total_secret_shares_commitments = BTreeMap::new(); + + for identifier in parameters.others_identifiers() { + let total_secret_share_commitment = + total_secret_polynomial_commitment.evaluate(identifier.0); + + total_secret_shares_commitments.insert(*identifier, total_secret_share_commitment); + } + + let own_total_secret_share_commitment = + total_secret_polynomial_commitment.evaluate(parameters.own_identifier.0); + + total_secret_shares_commitments + .insert(parameters.own_identifier, own_total_secret_share_commitment); + + let shared_public_key = SharedPublicKey::from_point( + total_secret_polynomial_commitment.constant_coefficient_commitment, + ); + + // The shared public key corresponds to the secret commitment of the total secret polynomial commitment + if shared_public_key.as_point() + != &total_secret_polynomial_commitment.evaluate(Scalar::ZERO) + { + return Err(DKGError::SharedPublicKeyMismatch); + } + + Ok((shared_public_key, total_secret_shares_commitments)) + } +} + +#[cfg(test)] +mod tests { + use self::round1::{PrivateData, PublicData}; + use super::*; + use crate::SignatureError; + use alloc::borrow::ToOwned; + #[cfg(feature = "alloc")] + use alloc::{ + collections::{BTreeMap, BTreeSet}, + vec::Vec, + }; + use curve25519_dalek::Scalar; + use merlin::Transcript; + use rand::rngs::OsRng; + use tests::round1::{PrivateMessage, PublicMessage}; + + fn generate_parameters(max_signers: u16, min_signers: u16) -> Vec { + (1..=max_signers) + .map(|i| { + let own_identifier = i.try_into().expect("should be nonzero"); + + let others_identifiers = (1..=max_signers) + .filter_map(|j| { + if j != i { + Some(j.try_into().expect("should be nonzero")) + } else { + None + } + }) + .collect(); + + Parameters::new(max_signers, min_signers, own_identifier, others_identifiers) + }) + .collect() + } + + fn round1( + participants: u16, + threshold: u16, + ) -> ( + Vec, + Vec, + Vec, + Vec>, + Vec>, + BTreeSet, + ) { + let parameters_list = generate_parameters(participants, threshold); + + let mut all_public_messages = Vec::new(); + let mut all_private_messages = Vec::new(); + let mut participants_round1_private_data = Vec::new(); + let mut participants_round1_public_data = Vec::new(); + let mut participants_round1_messages = Vec::new(); + + for parameters in parameters_list.iter() { + let (private_data, messages, public_data) = + round1::run(parameters, OsRng).expect("Round 1 should complete without errors!"); + + all_public_messages.push(( + parameters.own_identifier(), + messages.public_message().clone(), + )); + all_private_messages.push(( + parameters.own_identifier(), + messages.private_messages().clone(), + )); + participants_round1_messages.push(messages); + participants_round1_private_data.push(private_data); + participants_round1_public_data.push(public_data); + } + + let mut participants_round1_public_messages: Vec> = + Vec::new(); + let mut participants_round1_private_messages: Vec> = + Vec::new(); + + let mut identifiers: BTreeSet = parameters_list[0] + .others_identifiers() + .iter() + .copied() + .collect(); + + identifiers.insert(*parameters_list[0].own_identifier()); + + for identifier in &identifiers { + let mut all_public_msgs = BTreeMap::new(); + let mut received_private_msgs = BTreeMap::new(); + + for i in 0..participants { + all_public_msgs.insert( + *all_public_messages[i as usize].0, + all_public_messages[i as usize].1.clone(), + ); + } + + participants_round1_public_messages.push(all_public_msgs); + + for i in 0..participants { + if let Some(private_msg) = all_private_messages[i as usize].1.get(identifier) { + received_private_msgs + .insert(*all_private_messages[i as usize].0, private_msg.clone()); + } + } + + participants_round1_private_messages.push(received_private_msgs); + } + + // Remove own public messages + for i in 0..participants { + participants_round1_public_messages[i as usize] + .remove(¶meters_list[i as usize].own_identifier()); + } + + ( + parameters_list, + participants_round1_private_data, + participants_round1_public_data, + participants_round1_public_messages, + participants_round1_private_messages, + identifiers, + ) + } + + fn round2( + participants: u16, + parameters_list: Vec, + participants_round1_private_data: Vec, + participants_round1_public_data: Vec, + participants_round1_public_messages: Vec>, + participants_round1_private_messages: Vec>, + ) -> ( + Vec>, + Vec, + Vec, + ) { + let mut participants_public_data_round2 = Vec::new(); + let mut participants_private_data_round2 = Vec::new(); + let mut participants_public_msgs_round2 = Vec::new(); + + for i in 0..participants { + let result = round2::run( + ¶meters_list[i as usize], + participants_round1_private_data[i as usize].clone(), + &participants_round1_public_data[i as usize].clone(), + &participants_round1_public_messages[i as usize].clone(), + participants_round1_private_messages[i as usize].clone(), + Transcript::new(b"transcript"), + ) + .expect("Round 2 should complete without errors!"); + + participants_public_data_round2.push(result.0); + participants_private_data_round2.push(result.1); + participants_public_msgs_round2.push(result.2); + } + + ( + participants_public_data_round2, + participants_private_data_round2, + participants_public_msgs_round2, + ) + } + + fn round3( + participants: u16, + identifiers: BTreeSet, + parameters_list: &Vec, + participants_round2_public_messages: Vec, + participants_round2_public_data: Vec>, + participants_round1_public_messages: Vec>, + participants_round1_public_data: Vec, + ) -> Vec<( + SharedPublicKey, + BTreeMap, + )> { + let mut participant_data_round3 = Vec::new(); + let identifiers_vec: Vec = identifiers.clone().iter().copied().collect(); + + for i in 0..participants { + let received_round2_public_messages = participants_round2_public_messages + .iter() + .enumerate() + .filter(|(index, _msg)| { + identifiers_vec[*index] != *parameters_list[i as usize].own_identifier() + }) + .map(|(index, msg)| (identifiers_vec[index], msg.clone())) + .collect::>(); + + let result = round3::run( + ¶meters_list[i as usize], + &received_round2_public_messages, + &participants_round2_public_data[i as usize], + &participants_round1_public_data[i as usize], + &participants_round1_public_messages[i as usize], + ) + .expect("Round 3 should complete without errors!"); + + participant_data_round3.push(result); + } + + participant_data_round3 + } + + #[test] + pub fn test_successful_simplpedpop() { + let participants: u16 = 5; + let threshold: u16 = 3; + + let ( + parameters_list, + participants_round1_private_data, + participants_round1_public_data, + mut participants_round1_public_messages, + participants_round1_private_messages, + identifiers, + ) = round1(participants, threshold); + + for i in 0..participants { + participants_round1_public_messages[i as usize] + .remove(¶meters_list[i as usize].own_identifier()); + } + + let ( + participants_public_data_round2, + participants_private_data_round2, + participants_public_msgs_round2, + ) = round2( + participants, + parameters_list.clone(), + participants_round1_private_data.clone(), + participants_round1_public_data.clone(), + participants_round1_public_messages.clone(), + participants_round1_private_messages.clone(), + ); + + let participant_data_round3 = round3( + participants, + identifiers, + ¶meters_list, + participants_public_msgs_round2, + participants_public_data_round2, + participants_round1_public_messages, + participants_round1_public_data, + ); + + let shared_public_keys: Vec = participant_data_round3 + .iter() + .map(|state| state.0) + .collect(); + + assert!( + shared_public_keys.windows(2).all(|w| w[0] == w[1]), + "All participants should have the same shared public key!" + ); + + for i in 0..participants { + assert_eq!( + participant_data_round3[i as usize] + .1 + .get(parameters_list[i as usize].own_identifier()) + .unwrap(), + &(participants_private_data_round2[i as usize] + .total_secret_share + .0 + * GENERATOR), + "Verification of total secret shares failed!" + ); + } + } + + #[test] + fn test_incorrect_number_of_round1_public_messages_in_round2() { + let participants: u16 = 5; + let threshold: u16 = 3; + + let ( + parameters_list, + participants_round1_private_data, + participants_round1_public_data, + mut participants_round1_public_messages, + participants_round1_private_messages, + _identifiers, + ) = round1(participants, threshold); + + participants_round1_public_messages[0].remove(&2.try_into().unwrap()); + + let result = round2::run( + ¶meters_list[0], + participants_round1_private_data[0].clone(), + &participants_round1_public_data[0], + &participants_round1_public_messages[0], + participants_round1_private_messages[0].clone(), + Transcript::new(b"transcript"), + ); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => assert_eq!( + e, + DKGError::IncorrectNumberOfRound1PublicMessages { + expected: participants as usize - 1, + actual: participants as usize - 2, + }, + "Expected DKGError::IncorrectNumberOfRound1PublicMessages." + ), + } + } + + #[test] + fn test_incorrect_number_of_private_messages_in_round2() { + let participants: u16 = 5; + let threshold: u16 = 3; + + let ( + parameters_list, + participants_round1_private_data, + participants_round1_public_data, + mut participants_round1_public_messages, + mut participants_round1_private_messages, + identifiers, + ) = round1(participants, threshold); + + for i in 0..participants { + participants_round1_public_messages[i as usize] + .remove(¶meters_list[i as usize].own_identifier()); + } + + let identifiers_vec: Vec = identifiers.iter().copied().collect(); + participants_round1_private_messages[0].remove(&identifiers_vec[1]); + + let result = round2::run( + ¶meters_list[0], + participants_round1_private_data[0].clone(), + &participants_round1_public_data[0], + &participants_round1_public_messages[0], + participants_round1_private_messages[0].clone(), + Transcript::new(b"transcript"), + ); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => assert_eq!( + e, + DKGError::IncorrectNumberOfPrivateMessages { + expected: participants as usize - 1, + actual: participants as usize - 2, + }, + "Expected DKGError::IncorrectNumberOfPrivateMessages." + ), + } + } + + #[test] + fn test_missing_private_message_in_round2() { + let participants: u16 = 5; + let threshold: u16 = 3; + + let ( + parameters_list, + participants_round1_private_data, + participants_round1_public_data, + mut participants_round1_public_messages, + mut participants_round1_private_messages, + identifiers, + ) = round1(participants, threshold); + + for i in 0..participants { + participants_round1_public_messages[i as usize] + .remove(¶meters_list[i as usize].own_identifier()); + } + + let identifiers_vec: Vec = identifiers.iter().copied().collect(); + participants_round1_private_messages[0].remove(&identifiers_vec[1]); + + let identifiers_vec: Vec = identifiers.iter().copied().collect(); + let private_message = participants_round1_private_messages[0] + .get(&identifiers_vec[2]) + .unwrap() + .clone(); + participants_round1_private_messages[0].remove(&identifiers_vec[1]); + participants_round1_private_messages[0] + .insert(Identifier(Scalar::random(&mut OsRng)), private_message); + + let result = round2::run( + ¶meters_list[0], + participants_round1_private_data[0].clone(), + &participants_round1_public_data[0], + &participants_round1_public_messages[0], + participants_round1_private_messages[0].clone(), + Transcript::new(b"transcript"), + ); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => assert_eq!( + e, + DKGError::UnknownIdentifier, + "Expected DKGError::UnknownIdentifier." + ), + } + } + + #[test] + fn test_missing_public_message_in_round2() { + let participants: u16 = 5; + let threshold: u16 = 3; + + let ( + parameters_list, + participants_round1_private_data, + participants_round1_public_data, + mut participants_round1_public_messages, + participants_round1_private_messages, + identifiers, + ) = round1(participants, threshold); + + for i in 0..participants { + participants_round1_public_messages[i as usize] + .remove(¶meters_list[i as usize].own_identifier()); + } + + round2( + participants, + parameters_list.clone(), + participants_round1_private_data.clone(), + participants_round1_public_data.clone(), + participants_round1_public_messages.clone(), + participants_round1_private_messages.clone(), + ); + + let identifiers_vec: Vec = identifiers.iter().copied().collect(); + let public_message = participants_round1_public_messages[0] + .get(&identifiers_vec[1]) + .unwrap() + .clone(); + participants_round1_public_messages[0].remove(&identifiers_vec[1]); + participants_round1_public_messages[0] + .insert(Identifier(Scalar::random(&mut OsRng)), public_message); + + let result = round2::run( + ¶meters_list[0], + participants_round1_private_data[0].clone(), + &participants_round1_public_data[0], + &participants_round1_public_messages[0], + participants_round1_private_messages[0].clone(), + Transcript::new(b"transcript"), + ); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => assert_eq!( + e, + DKGError::UnknownIdentifier, + "Expected DKGError::UnknownIdentifier." + ), + } + } + + #[test] + fn test_invalid_secret_share_error_in_round2() { + let participants: u16 = 5; + let threshold: u16 = 3; + + let ( + parameters_list, + participants_round1_private_data, + participants_round1_public_data, + mut participants_round1_public_messages, + mut participants_round1_private_messages, + identifiers, + ) = round1(participants, threshold); + + for i in 0..participants { + participants_round1_public_messages[i as usize] + .remove(¶meters_list[i as usize].own_identifier()); + } + + let identifiers_vec: Vec = identifiers.iter().copied().collect(); + + let private_message = participants_round1_private_messages[0] + .get_mut(&identifiers_vec[1]) + .unwrap(); + + private_message.secret_share.0 += Scalar::ONE; + + let result = round2::run( + ¶meters_list[0], + participants_round1_private_data[0].clone(), + &participants_round1_public_data[0], + &participants_round1_public_messages[0], + participants_round1_private_messages[0].clone(), + Transcript::new(b"transcript"), + ); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => assert_eq!( + e, + DKGError::InvalidSecretShare(identifiers_vec[1]), + "Expected DKGError::InvalidSecretShare." + ), + } + } + + #[test] + fn test_invalid_proof_of_possession_in_round2() { + let participants: u16 = 5; + let threshold: u16 = 3; + + let ( + parameters_list, + participants_round1_private_data, + participants_round1_public_data, + mut participants_round1_public_messages, + participants_round1_private_messages, + identifiers, + ) = round1(participants, threshold); + + for i in 0..participants { + participants_round1_public_messages[i as usize] + .remove(¶meters_list[i as usize].own_identifier()); + } + + let identifiers_vec: Vec = identifiers.iter().copied().collect(); + + let secret_polynomial_commitment = participants_round1_public_messages[0] + .get(&identifiers_vec[1]) + .unwrap() + .secret_polynomial_commitment() + .clone(); + + let sk = SecretKey::generate(); + let proof_of_possession = sk.sign(Transcript::new(b"b"), &PublicKey::from(sk.clone())); + let msg = PublicMessage::new(secret_polynomial_commitment, proof_of_possession); + participants_round1_public_messages[0].insert(identifiers_vec[1], msg); + + let result = round2::run( + ¶meters_list[0], + participants_round1_private_data[0].clone(), + &participants_round1_public_data[0], + &participants_round1_public_messages[0], + participants_round1_private_messages[0].clone(), + Transcript::new(b"transcript"), + ); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => assert_eq!( + e, + DKGError::InvalidProofOfPossession(SignatureError::EquationFalse), + "Expected DKGError::InvalidProofOfPossession." + ), + } + } + + #[test] + pub fn test_invalid_certificate_in_round3() { + let participants: u16 = 5; + let threshold: u16 = 3; + + let ( + parameters_list, + participants_round1_private_data, + participants_round1_public_data, + mut participants_round1_public_messages, + participants_round1_private_messages, + identifiers, + ) = round1(participants, threshold); + + for i in 0..participants { + participants_round1_public_messages[i as usize] + .remove(¶meters_list[i as usize].own_identifier()); + } + + let ( + participants_round2_public_data, + _participants_round2_private_data, + participants_round2_public_messages, + ) = round2( + participants, + parameters_list.clone(), + participants_round1_private_data.clone(), + participants_round1_public_data.clone(), + participants_round1_public_messages.clone(), + participants_round1_private_messages.clone(), + ); + + let identifiers_vec: Vec = identifiers.clone().iter().copied().collect(); + let mut received_round2_public_messages = BTreeMap::new(); + + for i in 0..participants { + received_round2_public_messages = participants_round2_public_messages + .iter() + .enumerate() + .filter(|(index, _msg)| { + identifiers_vec[*index] != *parameters_list[i as usize].own_identifier() + }) + .map(|(index, msg)| (identifiers_vec[index], msg.clone())) + .collect::>(); + } + + let mut public_message = received_round2_public_messages + .get_mut(&identifiers_vec[1]) + .unwrap() + .to_owned(); + + let sk = SecretKey::generate(); + + public_message.certificate = + sk.sign(Transcript::new(b"label"), &PublicKey::from(sk.clone())); + + received_round2_public_messages.insert(identifiers_vec[1], public_message); + + let result = round3::run( + ¶meters_list[0], + &received_round2_public_messages, + &participants_round2_public_data[0], + &participants_round1_public_data[0], + &participants_round1_public_messages[0], + ); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => assert_eq!( + e, + DKGError::InvalidCertificate(SignatureError::EquationFalse), + "Expected DKGError::InvalidCertificate." + ), + } + } + + #[test] + pub fn test_incorrect_number_of_round2_public_messages_in_round3() { + let participants: u16 = 5; + let threshold: u16 = 3; + + let ( + parameters_list, + participants_round1_private_data, + participants_round1_public_data, + mut participants_round1_public_messages, + participants_round1_private_messages, + identifiers, + ) = round1(participants, threshold); + + for i in 0..participants { + participants_round1_public_messages[i as usize] + .remove(¶meters_list[i as usize].own_identifier()); + } + + let ( + participants_round2_public_data, + _participants_round2_private_data, + participants_round2_public_messages, + ) = round2( + participants, + parameters_list.clone(), + participants_round1_private_data.clone(), + participants_round1_public_data.clone(), + participants_round1_public_messages.clone(), + participants_round1_private_messages.clone(), + ); + + let identifiers_vec: Vec = identifiers.clone().iter().copied().collect(); + let mut received_round2_public_messages = BTreeMap::new(); + + for i in 0..participants { + received_round2_public_messages = participants_round2_public_messages + .iter() + .enumerate() + .filter(|(index, _msg)| { + identifiers_vec[*index] != *parameters_list[i as usize].own_identifier() + }) + .map(|(index, msg)| (identifiers_vec[index], msg.clone())) + .collect::>(); + } + + received_round2_public_messages.pop_first(); + + let result = round3::run( + ¶meters_list[0], + &received_round2_public_messages, + &participants_round2_public_data[0], + &participants_round1_public_data[0], + &participants_round1_public_messages[0], + ); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => assert_eq!( + e, + DKGError::IncorrectNumberOfRound2PublicMessages { + expected: participants as usize - 1, + actual: participants as usize - 2 + }, + "Expected DKGError::IncorrectNumberOfRound2PublicMessages." + ), + } + } + + #[test] + pub fn test_incorrect_number_of_round1_public_messages_in_round3() { + let participants: u16 = 5; + let threshold: u16 = 3; + + let ( + parameters_list, + participants_round1_private_data, + participants_round1_public_data, + mut participants_round1_public_messages, + participants_round1_private_messages, + identifiers, + ) = round1(participants, threshold); + + for i in 0..participants { + participants_round1_public_messages[i as usize] + .remove(¶meters_list[i as usize].own_identifier()); + } + + let ( + participants_round2_public_data, + _participants_round2_private_data, + participants_round2_public_messages, + ) = round2( + participants, + parameters_list.clone(), + participants_round1_private_data.clone(), + participants_round1_public_data.clone(), + participants_round1_public_messages.clone(), + participants_round1_private_messages.clone(), + ); + + let identifiers_vec: Vec = identifiers.clone().iter().copied().collect(); + let mut received_round2_public_messages = BTreeMap::new(); + + for i in 0..participants { + received_round2_public_messages = participants_round2_public_messages + .iter() + .enumerate() + .filter(|(index, _msg)| { + identifiers_vec[*index] != *parameters_list[i as usize].own_identifier() + }) + .map(|(index, msg)| (identifiers_vec[index], msg.clone())) + .collect::>(); + } + + participants_round1_public_messages[0].pop_first(); + + let result = round3::run( + ¶meters_list[0], + &received_round2_public_messages, + &participants_round2_public_data[0], + &participants_round1_public_data[0], + &participants_round1_public_messages[0], + ); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => assert_eq!( + e, + DKGError::IncorrectNumberOfRound1PublicMessages { + expected: participants as usize - 1, + actual: participants as usize - 2 + }, + "Expected DKGError::IncorrectNumberOfRound1PublicMessages." + ), + } + } + + #[test] + fn test_invalid_own_identifier() { + let own_identifier = Identifier(Scalar::ZERO); + let parameters = Parameters::new(3, 2, own_identifier, BTreeSet::new()); + assert_eq!(parameters.validate(), Err(DKGError::InvalidIdentifier)); + } + + #[test] + fn test_invalid_other_identifier() { + let mut others_identifiers = BTreeSet::new(); + others_identifiers.insert(Identifier(Scalar::ZERO)); + + let parameters = Parameters::new(3, 2, Identifier(Scalar::ONE), others_identifiers); + assert_eq!(parameters.validate(), Err(DKGError::InvalidIdentifier)); + } + + #[test] + fn test_incorrect_number_of_identifiers() { + let mut others_identifiers = BTreeSet::new(); + others_identifiers.insert(Identifier(Scalar::ONE)); + + let parameters = Parameters::new(3, 2, Identifier(Scalar::ONE), others_identifiers); + assert_eq!( + parameters.validate(), + Err(DKGError::IncorrectNumberOfIdentifiers { + expected: 3, + actual: 2 + }) + ); + } + + #[test] + fn test_invalid_threshold() { + let parameters = Parameters::new(3, 1, Identifier(Scalar::ONE), BTreeSet::new()); + assert_eq!(parameters.validate(), Err(DKGError::InsufficientThreshold)); + } + + #[test] + fn test_invalid_participants() { + let parameters = Parameters::new(1, 2, Identifier(Scalar::ONE), BTreeSet::new()); + assert_eq!( + parameters.validate(), + Err(DKGError::InvalidNumberOfParticipants) + ); + } + + #[test] + fn test_threshold_greater_than_participants() { + let parameters = Parameters::new(2, 3, Identifier(Scalar::ONE), BTreeSet::new()); + assert_eq!(parameters.validate(), Err(DKGError::ExcessiveThreshold)); + } +}