diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..1f48764 Binary files /dev/null and b/.DS_Store differ diff --git a/Cargo.toml b/Cargo.toml index 9943958..5822324 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,17 +22,24 @@ 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" +chacha20poly1305 = { version = "0.10.1", default-features = false } +hex = { version = "0.4", default-features = true, optional = true } [dev-dependencies] rand = "0.8.5" @@ -47,17 +54,38 @@ serde_json = "1.0.68" name = "schnorr_benchmarks" harness = false +[[bench]] +name = "olaf_benchmarks" +required-features = ["alloc", "aead"] + [features] +std = [ + "alloc", + "getrandom", + "serde_bytes/std", + "rand_core/std", + "getrandom_or_panic/std", + "chacha20poly1305/std", + "hex/std", +] 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", +] 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 @@ -66,3 +94,4 @@ getrandom = ["rand_core/getrandom", "getrandom_or_panic/getrandom", "aead?/getra # See https://github.com/rust-lang/cargo/issues/9210 # and https://github.com/w3f/schnorrkel/issues/65#issuecomment-786923588 aead = ["dep:aead"] +cheater-detection = [] diff --git a/benches/olaf_benchmarks.rs b/benches/olaf_benchmarks.rs new file mode 100644 index 0000000..a8279d5 --- /dev/null +++ b/benches/olaf_benchmarks.rs @@ -0,0 +1,277 @@ +use criterion::{criterion_group, criterion_main, Criterion}; + +mod olaf_benches { + use super::*; + use criterion::BenchmarkId; + use merlin::Transcript; + use rand_core::OsRng; + use schnorrkel::olaf::{ + errors::DKGResult, + identifier::Identifier, + keys::{GroupPublicKey, GroupPublicKeyShare}, + simplpedpop::{ + round1::{self, PrivateData, PublicData, PublicMessage}, + round2, + round2::Messages, + round3, Identifiers, Parameters, + }, + }; + use std::collections::{BTreeMap, BTreeSet}; + + fn generate_parameters(max_signers: u16, min_signers: u16) -> Vec { + (1..=max_signers).map(|_| Parameters::new(max_signers, min_signers)).collect() + } + + fn round1( + participants: u16, + threshold: u16, + ) -> (Vec, Vec, Vec, Vec>) { + let parameters_list = generate_parameters(participants, threshold); + + let mut all_public_messages_vec = Vec::new(); + let mut participants_round1_private_data = Vec::new(); + let mut participants_round1_public_data = Vec::new(); + + for i in 0..parameters_list.len() { + let (private_data, public_message, public_data) = + round1::run(parameters_list[i as usize].clone(), OsRng) + .expect("Round 1 should complete without errors!"); + + all_public_messages_vec.push(public_message.clone()); + participants_round1_public_data.push(public_data); + participants_round1_private_data.push(private_data); + } + + let mut received_round1_public_messages: Vec> = Vec::new(); + + let mut all_public_messages = BTreeSet::new(); + + for i in 0..participants { + all_public_messages.insert(all_public_messages_vec[i as usize].clone()); + } + + // Iterate through each participant to create a set of messages excluding their own. + for i in 0..participants as usize { + let own_message = PublicMessage::new(&participants_round1_public_data[i]); + + let mut messages_for_participant = BTreeSet::new(); + + for message in &all_public_messages { + if &own_message != message { + // Exclude the participant's own message. + messages_for_participant.insert(message.clone()); + } + } + + received_round1_public_messages.push(messages_for_participant); + } + + ( + parameters_list, + participants_round1_private_data, + participants_round1_public_data, + received_round1_public_messages, + ) + } + + fn round2( + parameters_list: &Vec, + participants_round1_private_data: Vec, + participants_round1_public_data: &Vec, + participants_round1_public_messages: &Vec>, + ) -> (Vec, Vec, Vec, Vec) { + let mut participants_round2_public_data = Vec::new(); + let mut participants_round2_public_messages = Vec::new(); + let mut participants_set_of_participants = Vec::new(); + let mut identifiers_vec = Vec::new(); + + for i in 0..*parameters_list[0].participants() { + let result = round2::run( + participants_round1_private_data[i as usize].clone(), + &participants_round1_public_data[i as usize].clone(), + participants_round1_public_messages[i as usize].clone(), + Transcript::new(b"simplpedpop"), + ) + .expect("Round 2 should complete without errors!"); + + participants_round2_public_data.push(result.0.clone()); + participants_round2_public_messages.push(result.1); + participants_set_of_participants.push(result.0.identifiers().clone()); + identifiers_vec.push(*result.0.identifiers().own_identifier()); + } + + ( + participants_round2_public_data, + participants_round2_public_messages, + participants_set_of_participants, + identifiers_vec, + ) + } + + fn round3( + participants_sets_of_participants: &Vec, + participants_round2_public_messages: &Vec, + participants_round2_public_data: &Vec, + participants_round1_public_data: &Vec, + participants_round1_private_data: Vec, + participants_round2_private_messages: Vec>, + identifiers_vec: &Vec, + ) -> DKGResult< + Vec<(GroupPublicKey, BTreeMap, round3::PrivateData)>, + > { + let mut participant_data_round3 = Vec::new(); + + for i in 0..participants_sets_of_participants.len() { + let received_round2_public_messages = participants_round2_public_messages + .iter() + .enumerate() + .filter(|(index, _msg)| { + identifiers_vec[*index] + != *participants_sets_of_participants[i as usize].own_identifier() + }) + .map(|(index, msg)| (identifiers_vec[index], msg.clone())) + .collect::>(); + + let mut round2_private_messages: Vec> = + Vec::new(); + + for participants in participants_sets_of_participants.iter() { + let mut messages_for_participant = BTreeMap::new(); + + for (i, round_messages) in participants_round2_private_messages.iter().enumerate() { + if let Some(message) = round_messages.get(&participants.own_identifier()) { + messages_for_participant.insert(identifiers_vec[i], message.clone()); + } + } + + round2_private_messages.push(messages_for_participant); + } + + let result = round3::run( + &received_round2_public_messages, + &participants_round2_public_data[i as usize], + &participants_round1_public_data[i as usize], + participants_round1_private_data[i as usize].clone(), + &round2_private_messages[i as usize], + )?; + + participant_data_round3.push(result); + } + + Ok(participant_data_round3) + } + + 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(parameters_list[0].clone(), OsRng).unwrap(); + }) + }); + + let ( + parameters_list, + participants_round1_private_data, + participants_round1_public_data, + participants_round1_public_messages, + ) = round1(participants, threshold); + + group.bench_function(BenchmarkId::new("round2", participants), |b| { + b.iter(|| { + round2::run( + participants_round1_private_data[0].clone(), + &participants_round1_public_data[0], + participants_round1_public_messages[0].clone(), + Transcript::new(b"simplpedpop"), + ) + .unwrap(); + }) + }); + + let ( + participants_round2_public_data, + participants_round2_messages, + participants_sets_of_participants, + identifiers_vec, + ) = round2( + ¶meters_list, + participants_round1_private_data.clone(), + &participants_round1_public_data, + &participants_round1_public_messages, + ); + + let participants_round2_public_messages: Vec = + participants_round2_messages + .iter() + .map(|msg| msg.public_message().clone()) + .collect(); + + let participants_round2_private_messages: Vec< + BTreeMap, + > = participants_round2_messages + .iter() + .map(|msg| msg.private_messages().clone()) + .collect(); + + let received_round2_public_messages = participants_round2_public_messages + .iter() + .enumerate() + .filter(|(index, _msg)| { + identifiers_vec[*index] + != *participants_sets_of_participants[0].own_identifier() + }) + .map(|(index, msg)| (identifiers_vec[index], msg.clone())) + .collect::>(); + + let mut round2_private_messages: Vec> = + Vec::new(); + + for participants in participants_sets_of_participants.iter() { + let mut messages_for_participant = BTreeMap::new(); + + for (i, round_messages) in participants_round2_private_messages.iter().enumerate() { + if let Some(message) = round_messages.get(&participants.own_identifier()) { + messages_for_participant.insert(identifiers_vec[i], message.clone()); + } + } + + round2_private_messages.push(messages_for_participant); + } + + group.bench_function(BenchmarkId::new("round3", participants), |b| { + b.iter(|| { + round3::run( + &received_round2_public_messages, + &participants_round2_public_data[0], + &participants_round1_public_data[0], + participants_round1_private_data[0].clone(), + &round2_private_messages[0], + ) + .unwrap(); + }) + }); + } + + group.finish(); + } + + criterion_group! { + name = olaf_benches; + config = Criterion::default(); + targets = + benchmark_simplpedpop, + } +} + +criterion_main!(olaf_benches::olaf_benches); diff --git a/benches/schnorr_benchmarks.rs b/benches/schnorr_benchmarks.rs index ef0d829..32cd049 100644 --- a/benches/schnorr_benchmarks.rs +++ b/benches/schnorr_benchmarks.rs @@ -22,9 +22,7 @@ mod schnorr_benches { let msg: &[u8] = b""; let ctx = signing_context(b"this signature does this thing"); - c.bench_function("Schnorr signing", move |b| { - b.iter(|| keypair.sign(ctx.bytes(msg))) - }); + c.bench_function("Schnorr signing", move |b| b.iter(|| keypair.sign(ctx.bytes(msg)))); } fn verify(c: &mut Criterion) { @@ -47,10 +45,8 @@ mod schnorr_benches { let keypairs: Vec = (0..size).map(|_| Keypair::generate()).collect(); let msg: &[u8] = b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; let ctx = signing_context(b"this signature does this thing"); - let signatures: Vec = keypairs - .iter() - .map(|key| key.sign(ctx.bytes(msg))) - .collect(); + let signatures: Vec = + keypairs.iter().map(|key| key.sign(ctx.bytes(msg))).collect(); let public_keys: Vec = keypairs.iter().map(|key| key.public).collect(); b.iter(|| { let transcripts = ::std::iter::once(ctx.bytes(msg)).cycle().take(size); @@ -61,9 +57,7 @@ mod schnorr_benches { } fn key_generation(c: &mut Criterion) { - c.bench_function("Schnorr keypair generation", move |b| { - b.iter(|| Keypair::generate()) - }); + c.bench_function("Schnorr keypair generation", move |b| b.iter(|| Keypair::generate())); } criterion_group! { @@ -77,6 +71,4 @@ mod schnorr_benches { } } -criterion_main!( - schnorr_benches::schnorr_benches, -); +criterion_main!(schnorr_benches::schnorr_benches,); diff --git a/rustfmt.toml b/rustfmt.toml index 248fb36..8cf89b5 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1 +1,30 @@ -ignore = ["/"] +# Basic +edition = "2021" +max_width = 100 +use_small_heuristics = "Max" + +# Imports +imports_granularity = "Preserve" +reorder_imports = false +reorder_modules = false + +# Consistency +newline_style = "Unix" + +# Misc +chain_width = 80 +spaces_around_ranges = false +reorder_impl_items = false +match_arm_leading_pipes = "Preserve" +match_arm_blocks = false +match_block_trailing_comma = true +trailing_comma = "Vertical" +# trailing_semicolon = false +# use_field_init_shorthand = true + +# where_single_line = true # does not work on fn +# brace_style = "AlwaysNextLine" # does not work on method fn + +# Format comments +comment_width = 100 +wrap_comments = true diff --git a/src/errors.rs b/src/errors.rs index 3344b86..761e42d 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -150,7 +150,7 @@ impl failure::Fail for SignatureError {} #[cfg(feature = "serde")] pub fn serde_error_from_signature_error(err: SignatureError) -> E where - E: serde_crate::de::Error, + E: serde::de::Error, { E::custom(err) } diff --git a/src/lib.rs b/src/lib.rs index a7e373b..5208751 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -248,6 +248,9 @@ pub mod derive; pub mod cert; pub mod errors; +#[cfg(all(feature = "alloc", feature = "aead"))] +pub mod olaf; + #[cfg(all(feature = "aead", feature = "getrandom"))] pub mod aead; diff --git a/src/musig.rs b/src/musig.rs index 8d89127..7705bc7 100644 --- a/src/musig.rs +++ b/src/musig.rs @@ -30,11 +30,12 @@ // See also https://github.com/lovesh/signature-schemes/issues/2 - -use core::borrow::{Borrow}; // BorrowMut +use core::borrow::{Borrow}; // BorrowMut #[cfg(feature = "alloc")] -use alloc::{collections::btree_map::{BTreeMap, Entry}}; +use alloc::{ + collections::btree_map::{BTreeMap, Entry}, +}; use arrayref::array_ref; use arrayvec::ArrayVec; @@ -42,14 +43,13 @@ use arrayvec::ArrayVec; use merlin::Transcript; use curve25519_dalek::constants; -use curve25519_dalek::ristretto::{CompressedRistretto,RistrettoPoint}; +use curve25519_dalek::ristretto::{CompressedRistretto, RistrettoPoint}; use curve25519_dalek::scalar::Scalar; use super::*; use crate::context::SigningTranscript; use crate::errors::MultiSignatureStage; - /// Rewinding immunity count plus one /// /// At least two so that our 2-round escape hatch `add_trusted` @@ -57,7 +57,6 @@ use crate::errors::MultiSignatureStage; /// 2-round multi-signatures. const REWINDS: usize = 3; - // === Agagregate public keys for multi-signatures === // /// Compute a transcript from which we may compute public key weightings. @@ -67,12 +66,13 @@ const REWINDS: usize = 3; /// We avoided a context: &'static [u8] here and in callers because they /// seem irrelevant to the security arguments in the MuSig paper. #[inline(always)] -fn commit_public_keys<'a,I>(keys: I) -> Transcript -where I: Iterator +fn commit_public_keys<'a, I>(keys: I) -> Transcript +where + I: Iterator, { let mut t = Transcript::new(b"MuSig-aggregate-public_key"); for pk in keys { - t.commit_point(b"pk-set", pk.as_compressed() ); + t.commit_point(b"pk-set", pk.as_compressed()); } t } @@ -83,7 +83,7 @@ where I: Iterator /// We cannot verify that the public key was ever entered into the /// transcript, so user facing callers should check this. fn compute_weighting(mut t: Transcript, pk: &PublicKey) -> Scalar { - t.commit_point(b"pk-choice", pk.as_compressed() ); + t.commit_point(b"pk-choice", pk.as_compressed()); t.challenge_scalar(b"") } @@ -101,100 +101,126 @@ pub trait AggregatePublicKey { fn public_key(&self) -> PublicKey; } -impl AggregatePublicKey for BTreeMap -where K: Borrow+Ord +impl AggregatePublicKey for BTreeMap +where + K: Borrow + Ord, { fn weighting(&self, choice: &PublicKey) -> Option { if !self.contains_key(choice) { return None; } - let t0 = commit_public_keys( self.keys().map(|pk| pk.borrow()) ); + let t0 = commit_public_keys(self.keys().map(|pk| pk.borrow())); Some(compute_weighting(t0, choice)) } fn public_key(&self) -> PublicKey { - let t0 = commit_public_keys( self.keys().map(|pk| pk.borrow()) ); - let point = self.keys().map(|pk| { - let pk = pk.borrow(); - compute_weighting(t0.clone(), pk) * pk.as_point() - }).sum(); + let t0 = commit_public_keys(self.keys().map(|pk| pk.borrow())); + let point = self + .keys() + .map(|pk| { + let pk = pk.borrow(); + compute_weighting(t0.clone(), pk) * pk.as_point() + }) + .sum(); PublicKey::from_point(point) } } /// Aggregation helper for public keys kept in slices -pub struct AggregatePublicKeySlice<'a,K>(&'a [K]) -where K: Borrow; +pub struct AggregatePublicKeySlice<'a, K>(&'a [K]) +where + K: Borrow; /// Aggregate public keys stored in a mutable slice -pub fn aggregate_public_key_from_slice<'a>(public_keys: &'a mut [PublicKey]) - -> Option> -{ - if public_keys.len() == 1 { return None; } +pub fn aggregate_public_key_from_slice<'a>( + public_keys: &'a mut [PublicKey], +) -> Option> { + if public_keys.len() == 1 { + return None; + } public_keys.sort_unstable(); - if public_keys.windows(2).any(|x| x[0]==x[1]) { return None; } + if public_keys.windows(2).any(|x| x[0] == x[1]) { + return None; + } Some(AggregatePublicKeySlice(public_keys)) } /// Aggregate public keys stored in a mutable slice -pub fn aggregate_public_key_from_refs_slice<'a>(public_keys: &'a mut [&'a PublicKey]) - -> Option> -{ - if public_keys.len() == 1 { return None; } +pub fn aggregate_public_key_from_refs_slice<'a>( + public_keys: &'a mut [&'a PublicKey], +) -> Option> { + if public_keys.len() == 1 { + return None; + } public_keys.sort_unstable(); - if public_keys.windows(2).any(|x| x[0]==x[1]) { return None; } + if public_keys.windows(2).any(|x| x[0] == x[1]) { + return None; + } Some(AggregatePublicKeySlice(public_keys)) } /// Aggregate public keys stored in a sorted slice -pub fn aggregate_public_key_from_sorted_slice<'a,K>(public_keys: &'a mut [K]) - -> Option> -where K: Borrow+PartialOrd +pub fn aggregate_public_key_from_sorted_slice<'a, K>( + public_keys: &'a mut [K], +) -> Option> +where + K: Borrow + PartialOrd, { - if public_keys.len() == 1 { return None; } - if public_keys.windows(2).any(|x| x[0] >= x[1]) { return None; } + if public_keys.len() == 1 { + return None; + } + if public_keys.windows(2).any(|x| x[0] >= x[1]) { + return None; + } Some(AggregatePublicKeySlice(public_keys)) } -impl<'a,K> AggregatePublicKey for AggregatePublicKeySlice<'a,K> -where K: Borrow+PartialEq +impl<'a, K> AggregatePublicKey for AggregatePublicKeySlice<'a, K> +where + K: Borrow + PartialEq, { fn weighting(&self, choice: &PublicKey) -> Option { if self.0.iter().any(|pk| pk.borrow() == choice) { return None; } - let t0 = commit_public_keys( self.0.iter().map(|pk| pk.borrow()) ); + let t0 = commit_public_keys(self.0.iter().map(|pk| pk.borrow())); Some(compute_weighting(t0, choice)) } fn public_key(&self) -> PublicKey { - let t0 = commit_public_keys( self.0.iter().map(|pk| pk.borrow()) ); - let point = self.0.iter().map(|pk| { - let pk = pk.borrow(); - compute_weighting(t0.clone(), pk) * pk.as_point() - } ).sum(); + let t0 = commit_public_keys(self.0.iter().map(|pk| pk.borrow())); + let point = self + .0 + .iter() + .map(|pk| { + let pk = pk.borrow(); + compute_weighting(t0.clone(), pk) * pk.as_point() + }) + .sum(); PublicKey::from_point(point) } } - // === Multi-signature protocol === // -const COMMITMENT_SIZE : usize = 16; +const COMMITMENT_SIZE: usize = 16; /// Commitments to `R_i` values shared between cosigners during signing -#[derive(Debug,Clone,Copy,PartialEq,Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct Commitment(pub [u8; COMMITMENT_SIZE]); impl Commitment { #[allow(non_snake_case)] fn for_R(R: I) -> Commitment - where I: IntoIterator + where + I: IntoIterator, { let mut t = Transcript::new(b"MuSig-commitment"); - for R0 in R.into_iter() { t.commit_point(b"sign:R",&R0); } + for R0 in R.into_iter() { + t.commit_point(b"sign:R", &R0); + } let mut commit = [0u8; COMMITMENT_SIZE]; - t.challenge_bytes(b"commitment",&mut commit[..]); + t.challenge_bytes(b"commitment", &mut commit[..]); Commitment(commit) } } @@ -202,9 +228,8 @@ impl Commitment { // TODO: serde_boilerplate!(Commitment); - -/// Internal representation of revealed points -#[derive(Debug,Clone,PartialEq,Eq)] +/// Internal representation of revealed points +#[derive(Debug, Clone, PartialEq, Eq)] struct RevealedPoints([RistrettoPoint; REWINDS]); impl RevealedPoints { @@ -218,9 +243,9 @@ impl RevealedPoints { fn to_reveal(&self) -> Reveal { // self.check_length() ?; - let mut reveal = [0u8; 32*REWINDS]; - for (o,i) in reveal.chunks_mut(32).zip(&self.0) { - o.copy_from_slice(i.compress().as_bytes()); + let mut reveal = [0u8; 32 * REWINDS]; + for (o, i) in reveal.chunks_mut(32).zip(&self.0) { + o.copy_from_slice(i.compress().as_bytes()); } Reveal(reveal) } @@ -228,11 +253,13 @@ impl RevealedPoints { /// Revealed `R_i` values shared between cosigners during signing // #[derive(Debug,Clone,PartialEq,Eq)] -pub struct Reveal(pub [u8; 32*REWINDS]); +pub struct Reveal(pub [u8; 32 * REWINDS]); // TODO: serde_boilerplate!(Reveal); impl Clone for Reveal { - fn clone(&self) -> Reveal { Reveal(self.0) } + fn clone(&self) -> Reveal { + Reveal(self.0) + } } impl PartialEq for Reveal { #[inline] @@ -240,40 +267,44 @@ impl PartialEq for Reveal { self.0[..] == other.0[..] } } -impl Eq for Reveal { } +impl Eq for Reveal {} impl Reveal { fn check_length(&self) -> SignatureResult<()> { - if self.0.len() % 32 == 0 { Ok(()) } else { Err(SignatureError::PointDecompressionError) } + if self.0.len() % 32 == 0 { + Ok(()) + } else { + Err(SignatureError::PointDecompressionError) + } } #[allow(non_snake_case)] - fn iter_points<'a>(&'a self) -> impl Iterator + 'a { - self.0.chunks(32).map( |R| CompressedRistretto( *array_ref![R,0,32] ) ) + fn iter_points<'a>(&'a self) -> impl Iterator + 'a { + self.0.chunks(32).map(|R| CompressedRistretto(*array_ref![R, 0, 32])) } fn to_commitment(&self) -> SignatureResult { - self.check_length() ?; - Ok(Commitment::for_R( self.iter_points() )) + self.check_length()?; + Ok(Commitment::for_R(self.iter_points())) } #[allow(clippy::wrong_self_convention)] fn into_points(&self) -> SignatureResult { - self.check_length() ?; - let a = self.iter_points().map( - |x| x.decompress().ok_or(SignatureError::PointDecompressionError) - ).collect::>>() ?; - Ok( RevealedPoints( a.into_inner().unwrap() ) ) + self.check_length()?; + let a = self + .iter_points() + .map(|x| x.decompress().ok_or(SignatureError::PointDecompressionError)) + .collect::>>()?; + Ok(RevealedPoints(a.into_inner().unwrap())) } } - #[allow(non_snake_case)] -#[derive(Debug,Clone,PartialEq,Eq)] +#[derive(Debug, Clone, PartialEq, Eq)] enum CoR { - Commit(Commitment), // H(R_i) - Reveal(RevealedPoints), // R_i - Cosigned { s: Scalar }, // s_i extracted from Cosignature type + Commit(Commitment), // H(R_i) + Reveal(RevealedPoints), // R_i + Cosigned { s: Scalar }, // s_i extracted from Cosignature type Collect { reveal: RevealedPoints, s: Scalar }, } @@ -300,24 +331,31 @@ impl CoR { */ fn set_revealed(&mut self, reveal: Reveal) -> SignatureResult<()> { - let commitment = reveal.to_commitment() ?; - let reveal = reveal.into_points() ?; - match self.clone() { // TODO: Remove .clone() here with #![feature(nll)] + let commitment = reveal.to_commitment()?; + let reveal = reveal.into_points()?; + match self.clone() { + // TODO: Remove .clone() here with #![feature(nll)] CoR::Collect { .. } => panic!("Internal error, set_reveal during collection phase."), CoR::Cosigned { .. } => panic!("Internal error, cosigning during reveal phase."), - CoR::Commit(c_old) => - if c_old==commitment { // TODO: Restore *c_old here with #![feature(nll)] + CoR::Commit(c_old) => { + if c_old == commitment { + // TODO: Restore *c_old here with #![feature(nll)] *self = CoR::Reveal(reveal); Ok(()) } else { let musig_stage = MultiSignatureStage::Commitment; - Err(SignatureError::MuSigInconsistent { musig_stage, duplicate: false, }) - }, - CoR::Reveal(reveal_old) => - if reveal_old == reveal { Ok(()) } else { // TODO: Restore *R_old here with #![feature(nll)] + Err(SignatureError::MuSigInconsistent { musig_stage, duplicate: false }) + } + }, + CoR::Reveal(reveal_old) => { + if reveal_old == reveal { + Ok(()) + } else { + // TODO: Restore *R_old here with #![feature(nll)] let musig_stage = MultiSignatureStage::Reveal; - Err(SignatureError::MuSigInconsistent { musig_stage, duplicate: true, }) - }, // Should we have a general duplicate reveal error for this case? + Err(SignatureError::MuSigInconsistent { musig_stage, duplicate: true }) + } + }, // Should we have a general duplicate reveal error for this case? } } @@ -326,42 +364,50 @@ impl CoR { match self { CoR::Collect { .. } => panic!("Internal error, set_cosigned during collection phase."), CoR::Commit(_) => { - let musig_stage = MultiSignatureStage::Reveal; - Err(SignatureError::MuSigAbsent { musig_stage, }) - }, + let musig_stage = MultiSignatureStage::Reveal; + Err(SignatureError::MuSigAbsent { musig_stage }) + }, CoR::Reveal(_) => { - *self = CoR::Cosigned { s }; + *self = CoR::Cosigned { s }; + Ok(()) + }, + CoR::Cosigned { s: s_old } => { + if *s_old == s { Ok(()) - }, - CoR::Cosigned { s: s_old } => - if *s_old==s { Ok(()) } else { + } else { let musig_stage = MultiSignatureStage::Cosignature; - Err(SignatureError::MuSigInconsistent { musig_stage, duplicate: true, }) - }, + Err(SignatureError::MuSigInconsistent { musig_stage, duplicate: true }) + } + }, } } } - /// Schnorr multi-signature (MuSig) container generic over its session types #[allow(non_snake_case)] -pub struct MuSig { +pub struct MuSig { t: T, - Rs: BTreeMap, - stage: S + Rs: BTreeMap, + stage: S, } -impl MuSig { +impl MuSig { /// Iterates over public keys. /// /// If `require_reveal=true` then we count only public key that revealed their `R` values. - pub fn public_keys(&self, require_reveal: bool) -> impl Iterator { - self.Rs.iter().filter_map( move |(pk,cor)| match cor { - CoR::Commit(_) => if require_reveal { None } else { Some(pk) }, + pub fn public_keys(&self, require_reveal: bool) -> impl Iterator { + self.Rs.iter().filter_map(move |(pk, cor)| match cor { + CoR::Commit(_) => { + if require_reveal { + None + } else { + Some(pk) + } + }, CoR::Reveal(_) => Some(pk), CoR::Cosigned { .. } => Some(pk), CoR::Collect { .. } => Some(pk), - } ) + }) } /// Aggregate public key @@ -369,52 +415,57 @@ impl MuSig { /// If `require_reveal=true` then we count only public key that revealed their `R` values. fn compute_public_key(&self, require_reveal: bool) -> PublicKey { let t0 = commit_public_keys(self.public_keys(require_reveal)); - let point = self.public_keys(require_reveal).map( |pk| - compute_weighting(t0.clone(), pk) * pk.as_point() - ).sum(); + let point = self + .public_keys(require_reveal) + .map(|pk| compute_weighting(t0.clone(), pk) * pk.as_point()) + .sum(); PublicKey::from_point(point) } /// Aggregate public key given currently revealed `R` values - pub fn public_key(&self) -> PublicKey - { self.compute_public_key(true) } + pub fn public_key(&self) -> PublicKey { + self.compute_public_key(true) + } /// Aggregate public key expected if all currently committed nodes fully participate - pub fn expected_public_key(&self) -> PublicKey - { self.compute_public_key(false) } + pub fn expected_public_key(&self) -> PublicKey { + self.compute_public_key(false) + } /// Iterator over the Rs values we actually use. /// /// Only compatable with `compute_public_key` when calling it with `require_reveal=true` #[allow(non_snake_case)] - fn iter_Rs(&self) -> impl Iterator { - self.Rs.iter().filter_map( |(pk,cor)| match cor { + fn iter_Rs(&self) -> impl Iterator { + self.Rs.iter().filter_map(|(pk, cor)| match cor { CoR::Commit(_) => None, - CoR::Reveal(reveal) => Some((pk,reveal)), - CoR::Cosigned { .. } => panic!("Internal error, compute_R called during cosigning phase."), - CoR::Collect { reveal, .. } => Some((pk,reveal)), - } ) + CoR::Reveal(reveal) => Some((pk, reveal)), + CoR::Cosigned { .. } => { + panic!("Internal error, compute_R called during cosigning phase.") + }, + CoR::Collect { reveal, .. } => Some((pk, reveal)), + }) } /// Computes the delinearizing `R` values. /// - /// Requires `self.t` be in its final state. + /// Requires `self.t` be in its final state. /// Only compatable with `compute_public_key` when calling it with `require_reveal=true` #[allow(non_snake_case)] fn rewinder(&self) -> impl Fn(&PublicKey) -> [Scalar; REWINDS] { let mut t0 = self.t.clone(); - for (pk,R) in self.iter_Rs() { - t0.commit_point(b"pk-set", pk.as_compressed() ); + for (pk, R) in self.iter_Rs() { + t0.commit_point(b"pk-set", pk.as_compressed()); for anR in R.0.iter() { - t0.commit_point(b"R",& anR.compress()); + t0.commit_point(b"R", &anR.compress()); } } move |pk| { let mut t1 = t0.clone(); - t1.commit_point(b"pk-choice", pk.as_compressed() ); + t1.commit_point(b"pk-choice", pk.as_compressed()); let mut a = ArrayVec::::new(); while !a.is_full() { - a.push( t1.challenge_scalar(b"R") ); + a.push(t1.challenge_scalar(b"R")); } a.into_inner().unwrap() } @@ -426,29 +477,36 @@ impl MuSig { /// Only compatable with `compute_public_key` when calling it with `require_reveal=true` #[allow(non_snake_case)] fn compute_R(&self, rewinder: F) -> CompressedRistretto - where F: Fn(&PublicKey) -> [Scalar; REWINDS] + where + F: Fn(&PublicKey) -> [Scalar; REWINDS], { - self.iter_Rs().map( |(pk,R)| - R.0.iter().zip(&rewinder(pk)).map(|(y,x)| x*y).sum::() - ).sum::().compress() + self.iter_Rs() + .map(|(pk, R)| { + R.0.iter().zip(&rewinder(pk)).map(|(y, x)| x * y).sum::() + }) + .sum::() + .compress() } } - /// Initial cosigning stages during which transcript modification /// remains possible but not advisable. pub trait TranscriptStages {} impl TranscriptStages for CommitStage where K: Borrow {} impl TranscriptStages for RevealStage where K: Borrow {} -impl MuSig -where T: SigningTranscript+Clone, S: TranscriptStages +impl MuSig +where + T: SigningTranscript + Clone, + S: TranscriptStages, { /// We permit extending the transcript whenever you like, so /// that say the message may be agreed upon in parallel to the /// commitments. We advise against doing so however, as this /// requires absolute faith in your random number generator, /// usually `rand::thread_rng()`. - pub fn transcript(&mut self) -> &mut T { &mut self.t } + pub fn transcript(&mut self) -> &mut T { + &mut self.t + } } impl Keypair { @@ -458,9 +516,11 @@ impl Keypair { /// copies of the private key, but the `MuSig::new` method /// can create an owned version, or use `Rc` or `Arc`. #[allow(non_snake_case)] - pub fn musig<'k,T>(&'k self, t: T) -> MuSig> - where T: SigningTranscript+Clone { - MuSig::new(self,t) + pub fn musig<'k, T>(&'k self, t: T) -> MuSig> + where + T: SigningTranscript + Clone, + { + MuSig::new(self, t) } } @@ -472,8 +532,10 @@ pub struct CommitStage> { R_me: Reveal, } -impl MuSig> -where K: Borrow, T: SigningTranscript+Clone +impl MuSig> +where + K: Borrow, + T: SigningTranscript + Clone, { /// Initialize a multi-signature aka cosignature protocol run. /// @@ -482,27 +544,27 @@ where K: Borrow, T: SigningTranscript+Clone /// for the `K = &'k Keypair` case. You could use `Rc` or `Arc` /// with this `MuSig::new` method, or even pass in an owned copy. #[allow(non_snake_case)] - pub fn new(keypair: K, t: T) -> MuSig> { + pub fn new(keypair: K, t: T) -> MuSig> { let nonce = &keypair.borrow().secret.nonce; let mut r_me = ArrayVec::::new(); for i in 0..REWINDS { - r_me.push( t.witness_scalar(b"MuSigWitness",&[nonce,&i.to_le_bytes()]) ); + r_me.push(t.witness_scalar(b"MuSigWitness", &[nonce, &i.to_le_bytes()])); } let r_me = r_me.into_inner().unwrap(); // context, message, nonce, but not &self.public.compressed let B = constants::RISTRETTO_BASEPOINT_TABLE; - let R_me_points: ArrayVec = r_me.iter() - .map(|r_me_i| r_me_i * B).collect(); + let R_me_points: ArrayVec = + r_me.iter().map(|r_me_i| r_me_i * B).collect(); let R_me_points = RevealedPoints(R_me_points.into_inner().unwrap()); let R_me = R_me_points.to_reveal(); let mut Rs = BTreeMap::new(); - Rs.insert(keypair.borrow().public, CoR::Reveal( R_me_points )); + Rs.insert(keypair.borrow().public, CoR::Reveal(R_me_points)); let stage = CommitStage { keypair, r_me, R_me }; - MuSig { t, Rs, stage, } + MuSig { t, Rs, stage } } /// Our commitment to our `R` to send to all other cosigners @@ -511,26 +573,31 @@ where K: Borrow, T: SigningTranscript+Clone } /// Add a new cosigner's public key and associated `R` bypassing our commitment phase. - pub fn add_their_commitment(&mut self, them: PublicKey, theirs: Commitment) - -> SignatureResult<()> - { + pub fn add_their_commitment( + &mut self, + them: PublicKey, + theirs: Commitment, + ) -> SignatureResult<()> { let theirs = CoR::Commit(theirs); match self.Rs.entry(them) { - Entry::Vacant(v) => { v.insert(theirs); }, - Entry::Occupied(o) => + Entry::Vacant(v) => { + v.insert(theirs); + }, + Entry::Occupied(o) => { if o.get() != &theirs { let musig_stage = MultiSignatureStage::Commitment; - return Err(SignatureError::MuSigInconsistent { musig_stage, duplicate: true, }); - }, + return Err(SignatureError::MuSigInconsistent { musig_stage, duplicate: true }); + } + }, } Ok(()) } /// Commit to reveal phase transition. #[allow(non_snake_case)] - pub fn reveal_stage(self) -> MuSig> { - let MuSig { t, Rs, stage: CommitStage { keypair, r_me, R_me, }, } = self; - MuSig { t, Rs, stage: RevealStage { keypair, r_me, R_me, }, } + pub fn reveal_stage(self) -> MuSig> { + let MuSig { t, Rs, stage: CommitStage { keypair, r_me, R_me } } = self; + MuSig { t, Rs, stage: RevealStage { keypair, r_me, R_me } } } } @@ -542,27 +609,28 @@ pub struct RevealStage> { R_me: Reveal, } -impl MuSig> -where K: Borrow, T: SigningTranscript+Clone +impl MuSig> +where + K: Borrow, + T: SigningTranscript + Clone, { /// Reveal our `R` contribution to send to all other cosigners - pub fn our_reveal(&self) -> &Reveal { &self.stage.R_me } + pub fn our_reveal(&self) -> &Reveal { + &self.stage.R_me + } // TODO: Permit `add_their_reveal` and `add_trusted` in `CommitStage` // using const generics, const fn, and replacing the `*Stage` types // with some enum. /// Include a revealed `R` value from a previously committed cosigner - pub fn add_their_reveal(&mut self, them: PublicKey, theirs: Reveal) - -> SignatureResult<()> - { + pub fn add_their_reveal(&mut self, them: PublicKey, theirs: Reveal) -> SignatureResult<()> { match self.Rs.entry(them) { Entry::Vacant(_) => { let musig_stage = MultiSignatureStage::Commitment; - Err(SignatureError::MuSigAbsent { musig_stage, }) + Err(SignatureError::MuSigAbsent { musig_stage }) }, - Entry::Occupied(mut o) => - o.get_mut().set_revealed(theirs), + Entry::Occupied(mut o) => o.get_mut().set_revealed(theirs), } } @@ -588,47 +656,49 @@ where K: Borrow, T: SigningTranscript+Clone /// with the middle ground being only something like Parity Signer. /// Also, any public keys controlled by an organization likely /// fail (c) too, making this only useful for individuals. - pub fn add_trusted(&mut self, them: PublicKey, theirs: Reveal) - -> SignatureResult<()> - { - let reveal = theirs.into_points() ?; + pub fn add_trusted(&mut self, them: PublicKey, theirs: Reveal) -> SignatureResult<()> { + let reveal = theirs.into_points()?; let theirs = CoR::Reveal(reveal); match self.Rs.entry(them) { - Entry::Vacant(v) => { v.insert(theirs); }, - Entry::Occupied(o) => + Entry::Vacant(v) => { + v.insert(theirs); + }, + Entry::Occupied(o) => { if o.get() != &theirs { let musig_stage = MultiSignatureStage::Reveal; - return Err(SignatureError::MuSigInconsistent { musig_stage, duplicate: true, }); - }, + return Err(SignatureError::MuSigInconsistent { musig_stage, duplicate: true }); + } + }, } Ok(()) } /// Reveal to cosign phase transition. #[allow(non_snake_case)] - pub fn cosign_stage(mut self) -> MuSig { + pub fn cosign_stage(mut self) -> MuSig { self.t.proto_name(b"Schnorr-sig"); let pk = *self.public_key().as_compressed(); - self.t.commit_point(b"sign:pk",&pk); + self.t.commit_point(b"sign:pk", &pk); let rewinder = self.rewinder(); let rewinds = rewinder(&self.stage.keypair.borrow().public); let R = self.compute_R(rewinder); - self.t.commit_point(b"sign:R",&R); + self.t.commit_point(b"sign:R", &R); let t0 = commit_public_keys(self.public_keys(true)); let a_me = compute_weighting(t0, &self.stage.keypair.borrow().public); - let c = self.t.challenge_scalar(b"sign:c"); // context, message, A/public_key, R=rG + let c = self.t.challenge_scalar(b"sign:c"); // context, message, A/public_key, R=rG - let mut s_me: Scalar = self.stage.r_me.iter().zip(&rewinds).map(|(y,x)| x*y).sum(); + let mut s_me: Scalar = self.stage.r_me.iter().zip(&rewinds).map(|(y, x)| x * y).sum(); s_me += c * a_me * self.stage.keypair.borrow().secret.key; zeroize::Zeroize::zeroize(&mut self.stage.r_me); - let MuSig { t, mut Rs, stage: RevealStage { .. }, } = self; - *(Rs.get_mut(&self.stage.keypair.borrow().public).expect("Rs known to contain this public; qed")) = CoR::Cosigned { s: s_me }; - MuSig { t, Rs, stage: CosignStage { R, s_me }, } + let MuSig { t, mut Rs, stage: RevealStage { .. } } = self; + *(Rs.get_mut(&self.stage.keypair.borrow().public) + .expect("Rs known to contain this public; qed")) = CoR::Cosigned { s: s_me }; + MuSig { t, Rs, stage: CosignStage { R, s_me } } } } @@ -642,97 +712,111 @@ pub struct CosignStage { } /// Cosignatures shared between cosigners during signing -#[derive(Debug,Clone,Copy,PartialEq,Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct Cosignature(pub [u8; 32]); -impl MuSig { +impl MuSig { /// Reveals our signature contribution pub fn our_cosignature(&self) -> Cosignature { Cosignature(self.stage.s_me.to_bytes()) } /// Include a cosignature from another cosigner - pub fn add_their_cosignature(&mut self, them: PublicKey, theirs: Cosignature) - -> SignatureResult<()> - { + pub fn add_their_cosignature( + &mut self, + them: PublicKey, + theirs: Cosignature, + ) -> SignatureResult<()> { let theirs = crate::scalar_from_canonical_bytes(theirs.0) - .ok_or(SignatureError::ScalarFormatError) ?; + .ok_or(SignatureError::ScalarFormatError)?; match self.Rs.entry(them) { Entry::Vacant(_) => { - let musig_stage = MultiSignatureStage::Reveal; - Err(SignatureError::MuSigAbsent { musig_stage, }) - }, - Entry::Occupied(mut o) => o.get_mut().set_cosigned(theirs) + let musig_stage = MultiSignatureStage::Reveal; + Err(SignatureError::MuSigAbsent { musig_stage }) + }, + Entry::Occupied(mut o) => o.get_mut().set_cosigned(theirs), } } /// Interate over the cosigners who successfully revaled and /// later cosigned. - pub fn cosigned(&self) -> impl Iterator { - self.Rs.iter().filter_map( |(pk,cor)| match cor { + pub fn cosigned(&self) -> impl Iterator { + self.Rs.iter().filter_map(|(pk, cor)| match cor { CoR::Commit(_) => None, CoR::Reveal(_) => None, CoR::Cosigned { .. } => Some(pk), - CoR::Collect { .. } => panic!("Collect found in Cosign phase.") - } ) + CoR::Collect { .. } => panic!("Collect found in Cosign phase."), + }) } /// Interate over the possible cosigners who successfully committed /// and revaled, but actually cosigned. - pub fn uncosigned(&self) -> impl Iterator { - self.Rs.iter().filter_map( |(pk,cor)| match cor { + pub fn uncosigned(&self) -> impl Iterator { + self.Rs.iter().filter_map(|(pk, cor)| match cor { CoR::Commit(_) => None, CoR::Reveal(_) => Some(pk), CoR::Cosigned { .. } => None, CoR::Collect { .. } => panic!("Collect found in Cosign phase."), - } ) + }) } /// Actually computes the cosignature #[allow(non_snake_case)] pub fn sign(&self) -> Option { // if self.uncosigned().all(|_| false) { return None; } // TODO: why does this fail? - if self.uncosigned().last().is_some() { return None; } - let s: Scalar = self.Rs.iter() - .filter_map( |(_pk,cor)| match cor { + if self.uncosigned().last().is_some() { + return None; + } + let s: Scalar = self + .Rs + .iter() + .filter_map(|(_pk, cor)| match cor { CoR::Commit(_) => None, - CoR::Reveal(_) => panic!("Internal error, MuSig::uncosigned broken."), + CoR::Reveal(_) => { + panic!("Internal error, MuSig::uncosigned broken.") + }, CoR::Cosigned { s, .. } => Some(s), CoR::Collect { .. } => panic!("Collect found in Cosign phase."), - } ).sum(); - Some(Signature { s, R: self.stage.R, }) + }) + .sum(); + Some(Signature { s, R: self.stage.R }) } } - /// Initialize a collector of cosignatures who does not themselves cosign. #[allow(non_snake_case)] -pub fn collect_cosignatures(mut t: T) -> MuSig { +pub fn collect_cosignatures(mut t: T) -> MuSig { t.proto_name(b"Schnorr-sig"); - MuSig { t, Rs: BTreeMap::new(), stage: CollectStage, } + MuSig { t, Rs: BTreeMap::new(), stage: CollectStage } } /// Initial stage for cosignature collectors who do not themselves cosign. pub struct CollectStage; -impl MuSig { +impl MuSig { /// Adds revealed `R` and cosignature into a cosignature collector #[allow(non_snake_case)] - pub fn add(&mut self, them: PublicKey, their_reveal: Reveal, their_cosignature: Cosignature) - -> SignatureResult<()> - { - let reveal = their_reveal.into_points() ?; + pub fn add( + &mut self, + them: PublicKey, + their_reveal: Reveal, + their_cosignature: Cosignature, + ) -> SignatureResult<()> { + let reveal = their_reveal.into_points()?; let s = crate::scalar_from_canonical_bytes(their_cosignature.0) - .ok_or(SignatureError::ScalarFormatError) ?; + .ok_or(SignatureError::ScalarFormatError)?; let cor = CoR::Collect { reveal, s }; match self.Rs.entry(them) { - Entry::Vacant(v) => { v.insert(cor); }, - Entry::Occupied(o) => + Entry::Vacant(v) => { + v.insert(cor); + }, + Entry::Occupied(o) => { if o.get() != &cor { let musig_stage = MultiSignatureStage::Reveal; - return Err(SignatureError::MuSigInconsistent { musig_stage, duplicate: true, }); - }, + return Err(SignatureError::MuSigInconsistent { musig_stage, duplicate: true }); + } + }, } Ok(()) } @@ -741,19 +825,22 @@ impl MuSig { #[allow(non_snake_case)] pub fn signature(mut self) -> Signature { let pk = *self.public_key().as_compressed(); - self.t.commit_point(b"sign:pk",&pk); + self.t.commit_point(b"sign:pk", &pk); let R = self.compute_R(self.rewinder()); - let s: Scalar = self.Rs.values().map(|cor| match cor { + let s: Scalar = self + .Rs + .values() + .map(|cor| match cor { CoR::Collect { s, .. } => s, _ => panic!("Reached CollectStage from another stage"), - }).sum(); - Signature { s, R, } + }) + .sum(); + Signature { s, R } } } - #[cfg(test)] mod tests { #[cfg(feature = "alloc")] @@ -764,7 +851,7 @@ mod tests { #[test] fn aggregation_btreeemap_vs_slice() { let mut vec: Vec = (0..16).map(|_| SecretKey::generate().to_public()).collect(); - let btm: BTreeMap = vec.iter().map( |x| (x.clone(),()) ).collect(); + let btm: BTreeMap = vec.iter().map(|x| (x.clone(), ())).collect(); debug_assert_eq!( btm.public_key(), aggregate_public_key_from_slice(vec.as_mut_slice()).unwrap().public_key() @@ -777,33 +864,41 @@ mod tests { let keypairs: Vec = (0..16).map(|_| Keypair::generate()).collect(); let t = signing_context(b"multi-sig").bytes(b"We are legion!"); - let mut commits: Vec<_> = keypairs.iter().map( |k| k.musig(t.clone()) ).collect(); + let mut commits: Vec<_> = keypairs.iter().map(|k| k.musig(t.clone())).collect(); for i in 0..commits.len() { - let r = commits[i].our_commitment(); + let r = commits[i].our_commitment(); for j in commits.iter_mut() { - assert!( j.add_their_commitment(keypairs[i].public.clone(),r) - .is_ok() != (r == j.our_commitment()) ); + assert!( + j.add_their_commitment(keypairs[i].public.clone(), r).is_ok() + != (r == j.our_commitment()) + ); } } let mut reveal_msgs: Vec = Vec::with_capacity(commits.len()); - let mut reveals: Vec<_> = commits.drain(..).map( |c| c.reveal_stage() ).collect(); + let mut reveals: Vec<_> = commits.drain(..).map(|c| c.reveal_stage()).collect(); for i in 0..reveals.len() { let r = reveals[i].our_reveal().clone(); for j in reveals.iter_mut() { - j.add_their_reveal(keypairs[i].public.clone(),r.clone()).unwrap(); + j.add_their_reveal(keypairs[i].public.clone(), r.clone()).unwrap(); } reveal_msgs.push(r); } let pk = reveals[0].public_key(); let mut cosign_msgs: Vec = Vec::with_capacity(reveals.len()); - let mut cosigns: Vec<_> = reveals.drain(..).map( |c| { assert_eq!(pk, c.public_key()); c.cosign_stage() } ).collect(); + let mut cosigns: Vec<_> = reveals + .drain(..) + .map(|c| { + assert_eq!(pk, c.public_key()); + c.cosign_stage() + }) + .collect(); for i in 0..cosigns.len() { assert_eq!(pk, cosigns[i].public_key()); let r = cosigns[i].our_cosignature(); for j in cosigns.iter_mut() { - j.add_their_cosignature(keypairs[i].public.clone(),r).unwrap(); + j.add_their_cosignature(keypairs[i].public.clone(), r).unwrap(); } cosign_msgs.push(r); assert_eq!(pk, cosigns[i].public_key()); @@ -812,11 +907,12 @@ mod tests { // let signature = cosigns[0].sign().unwrap(); let mut c = collect_cosignatures(t.clone()); for i in 0..cosigns.len() { - c.add(keypairs[i].public.clone(),reveal_msgs[i].clone(),cosign_msgs[i].clone()).unwrap(); + c.add(keypairs[i].public.clone(), reveal_msgs[i].clone(), cosign_msgs[i].clone()) + .unwrap(); } let signature = c.signature(); - assert!( pk.verify(t,&signature).is_ok() ); + assert!(pk.verify(t, &signature).is_ok()); for i in 0..cosigns.len() { assert_eq!(pk, cosigns[i].public_key()); assert_eq!(signature, cosigns[i].sign().unwrap()); diff --git a/src/olaf/errors.rs b/src/olaf/errors.rs new file mode 100644 index 0000000..301be35 --- /dev/null +++ b/src/olaf/errors.rs @@ -0,0 +1,80 @@ +//! Errors of the Olaf protocol. + +use super::identifier::Identifier; +use crate::SignatureError; + +/// A result for the SimplPedPoP protocol. +pub type DKGResult = Result; + +/// An error ocurred during the execution of the SimplPedPoP protocol. +#[derive(Debug, Clone, 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, + /// Secret share verification failed. + InvalidSecretShare(Identifier), + /// Invalid secret. + InvalidSecret, + /// Unknown identifier in round 1 public messages. + UnknownIdentifierRound1PublicMessages(Identifier), + /// Unknown identifier in round 2 public messages. + UnknownIdentifierRound2PublicMessages(Identifier), + /// Unknown identifier in round 2 private messages. + UnknownIdentifierRound2PrivateMessages, + /// 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 round 2 private messages. + IncorrectNumberOfRound2PrivateMessages { + /// The expected value. + expected: usize, + /// The actual value. + actual: usize, + }, + /// Decryption error when decrypting an encrypted secret share. + DecryptionError(chacha20poly1305::Error), + /// Encryption error when encrypting the secret share. + EncryptionError(chacha20poly1305::Error), + /// Incorrect number of coefficient commitments. + InvalidSecretPolynomialCommitment { + /// The expected value. + expected: usize, + /// The actual value. + actual: usize, + }, +} diff --git a/src/olaf/identifier.rs b/src/olaf/identifier.rs new file mode 100644 index 0000000..af56466 --- /dev/null +++ b/src/olaf/identifier.rs @@ -0,0 +1,168 @@ +//! The identifier of a participant in the Olaf protocol. + +use super::errors::DKGError; +use core::cmp::Ordering; +#[cfg(feature = "serde")] +use core::fmt; +use curve25519_dalek::Scalar; +#[cfg(feature = "serde")] +use serde::de::{self, Visitor}; +#[cfg(feature = "serde")] +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + +/// The identifier is represented by a Scalar. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct Identifier(pub(crate) Scalar); + +#[cfg(feature = "serde")] +impl Serialize for Identifier { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let scalar_bytes = self.0.to_bytes(); + let scalar_hex = hex::encode(scalar_bytes); + serializer.serialize_str(&scalar_hex) + } +} + +#[cfg(feature = "serde")] +struct IdentifierVisitor; + +#[cfg(feature = "serde")] +impl<'de> Visitor<'de> for IdentifierVisitor { + type Value = Identifier; + + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str("a hexadecimal string representing a Scalar") + } + + fn visit_str(self, value: &str) -> Result + where + E: de::Error, + { + let bytes = match hex::decode(value) { + Ok(b) => b, + Err(_) => return Err(E::custom("Invalid hexadecimal string")), + }; + if bytes.len() != 32 { + return Err(E::custom("Hexadecimal string must be exactly 32 bytes long")); + } + let mut bytes_array = [0u8; 32]; + bytes_array.copy_from_slice(&bytes); + + let scalar = Scalar::from_canonical_bytes(bytes_array); + if scalar.is_some().unwrap_u8() == 1 { + Ok(Identifier(scalar.unwrap())) + } else { + Err(E::custom("Invalid bytes for a Scalar")) + } + } +} + +#[cfg(feature = "serde")] +impl<'de> Deserialize<'de> for Identifier { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_str(IdentifierVisitor) + } +} + +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)) + } +} + +#[cfg(test)] +#[cfg(feature = "serde")] +mod tests { + use super::*; + use rand_core::OsRng; + + #[test] + fn test_serialize_deserialize_random_identifier() { + // Create a random Identifier + let random_scalar = Scalar::random(&mut OsRng); + let identifier = Identifier(random_scalar); + + // Serialize the Identifier + let serialized = serde_json::to_string(&identifier).expect("Failed to serialize"); + + // Deserialize the serialized string back into an Identifier + let deserialized: Result = serde_json::from_str(&serialized); + + // Check if the deserialized Identifier matches the original + assert!(deserialized.is_ok()); + assert_eq!(identifier, deserialized.unwrap()); + } + + #[test] + fn test_deserialize_invalid_hex_identifier() { + // Hexadecimal string with invalid characters (not valid hex) + let invalid_hex_scalar = + "\"g1c4c8a8ff4d21243af23e5ef23fea223b7cdde1baf31e56af77f872a8cc8402\""; + let result: Result = serde_json::from_str(invalid_hex_scalar); + + // Assert that the deserialization fails + assert!(result.is_err(), "Deserialization should fail for invalid hex characters"); + } + + #[test] + fn test_deserialize_invalid_length_identifier() { + // Incorrect length hexadecimal string (not 64 characters long) + let invalid_length_hex = "\"1c4c8a8ff4d21243af23e5ef23fea223b7cd\""; + let result: Result = serde_json::from_str(invalid_length_hex); + + // Assert that the deserialization fails due to length mismatch + assert!(result.is_err(), "Deserialization should fail for incorrect hex length"); + } +} diff --git a/src/olaf/keys.rs b/src/olaf/keys.rs new file mode 100644 index 0000000..f00ccd9 --- /dev/null +++ b/src/olaf/keys.rs @@ -0,0 +1,9 @@ +//! Olaf keys and key shares. + +use super::polynomial::CoefficientCommitment; +use crate::PublicKey; + +/// The group public key generated by the SimplPedPoP protocol. +pub type GroupPublicKey = PublicKey; +/// The group public key share of each participant in the SimplPedPoP protocol. +pub type GroupPublicKeyShare = CoefficientCommitment; diff --git a/src/olaf/mod.rs b/src/olaf/mod.rs new file mode 100644 index 0000000..d915964 --- /dev/null +++ b/src/olaf/mod.rs @@ -0,0 +1,9 @@ +//! Implementation of the Olaf protocol (), which is composed of the Distributed +//! Key Generation (DKG) protocol SimplPedPoP and the Threshold Signing protocol FROST. + +pub mod errors; +pub mod identifier; +pub mod keys; +mod polynomial; +pub mod simplpedpop; +mod tests; diff --git a/src/olaf/polynomial.rs b/src/olaf/polynomial.rs new file mode 100644 index 0000000..c97aeec --- /dev/null +++ b/src/olaf/polynomial.rs @@ -0,0 +1,195 @@ +//! Implementation of a polynomial and related operations. + +use crate::olaf::simplpedpop::GENERATOR; +use alloc::vec; +use alloc::vec::Vec; +use curve25519_dalek::{traits::Identity, RistrettoPoint, Scalar}; +use rand_core::{CryptoRng, RngCore}; +use zeroize::ZeroizeOnDrop; + +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)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct Polynomial { + pub(crate) coefficients: Vec, +} + +impl Polynomial { + pub(crate) fn generate(rng: &mut R, degree: u16) -> Self { + let mut coefficients = Vec::new(); + + for _ in 0..(degree as usize + 1) { + coefficients.push(Scalar::random(rng)); + } + + Self { coefficients } + } + + pub(crate) fn evaluate(&self, x: &Value) -> Value { + let mut value = + *self.coefficients.last().expect("coefficients must have at least one element"); + + // Process all coefficients except the last one, using Horner's method + for coeff in self.coefficients.iter().rev().skip(1) { + value = value * x + coeff; + } + + value + } +} + +/// A polynomial commitment. +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct PolynomialCommitment { + pub(crate) coefficients_commitments: Vec, +} + +impl PolynomialCommitment { + pub(crate) fn commit(polynomial: &Polynomial) -> Self { + let coefficients_commitments = polynomial + .coefficients + .iter() + .map(|coefficient| GENERATOR * coefficient) + .collect(); + + Self { coefficients_commitments } + } + + pub(crate) fn evaluate(&self, identifier: &Value) -> ValueCommitment { + let i = identifier; + + let (_, result) = self + .coefficients_commitments + .iter() + .fold((Scalar::ONE, RistrettoPoint::identity()), |(i_to_the_k, sum_so_far), comm_k| { + (i * i_to_the_k, sum_so_far + comm_k * i_to_the_k) + }); + result + } + + pub(crate) fn sum_polynomial_commitments( + polynomials_commitments: &[&PolynomialCommitment], + ) -> PolynomialCommitment { + let max_length = polynomials_commitments + .iter() + .map(|c| c.coefficients_commitments.len()) + .max() + .unwrap_or(0); + + let mut total_commitment = vec![RistrettoPoint::identity(); max_length]; + + for polynomial_commitment in polynomials_commitments { + for (i, coeff_commitment) in + polynomial_commitment.coefficients_commitments.iter().enumerate() + { + total_commitment[i] += coeff_commitment; + } + } + + PolynomialCommitment { coefficients_commitments: total_commitment } + } +} + +#[cfg(test)] +mod tests { + use crate::{ + olaf::polynomial::{Coefficient, Polynomial, PolynomialCommitment}, + olaf::simplpedpop::GENERATOR, + }; + + use alloc::vec::Vec; + use curve25519_dalek::Scalar; + use rand::rngs::OsRng; + + #[test] + fn test_generate_polynomial_commitment_valid() { + let degree = 3; + + let polynomial = Polynomial::generate(&mut OsRng, degree); + + let polynomial_commitment = PolynomialCommitment::commit(&polynomial); + + assert_eq!(polynomial.coefficients.len(), degree as usize + 1); + + assert_eq!(polynomial_commitment.coefficients_commitments.len(), degree as usize + 1); + } + + #[test] + fn test_evaluate_polynomial() { + let coefficients: Vec = + vec![Scalar::from(3u64), Scalar::from(2u64), Scalar::from(1u64)]; // Polynomial x^2 + 2x + 3 + + let polynomial = Polynomial { coefficients }; + + let value = Scalar::from(5u64); // x = 5 + + let result = polynomial.evaluate(&value); + + assert_eq!(result, Scalar::from(38u64)); // 5^2 + 2*5 + 3 + } + + #[test] + fn test_sum_secret_polynomial_commitments() { + let polynomial_commitment1 = PolynomialCommitment { + coefficients_commitments: vec![ + GENERATOR * Scalar::from(1u64), // Constant + GENERATOR * Scalar::from(2u64), // Linear + GENERATOR * Scalar::from(3u64), // Quadratic + ], + }; + + let polynomial_commitment2 = PolynomialCommitment { + coefficients_commitments: vec![ + GENERATOR * Scalar::from(4u64), // Constant + GENERATOR * Scalar::from(5u64), // Linear + GENERATOR * Scalar::from(6u64), // Quadratic + ], + }; + + let summed_polynomial_commitments = PolynomialCommitment::sum_polynomial_commitments(&[ + &polynomial_commitment1, + &polynomial_commitment2, + ]); + + let expected_coefficients_commitments = vec![ + GENERATOR * Scalar::from(5u64), // 1 + 4 = 5 + GENERATOR * Scalar::from(7u64), // 2 + 5 = 7 + GENERATOR * Scalar::from(9u64), // 3 + 6 = 9 + ]; + + assert_eq!( + summed_polynomial_commitments.coefficients_commitments, + expected_coefficients_commitments, + "Coefficient commitments do not match" + ); + } + + #[test] + fn test_evaluate_polynomial_commitment() { + // f(x) = 3 + 2x + x^2 + let constant_coefficient_commitment = Scalar::from(3u64) * GENERATOR; + let linear_commitment = Scalar::from(2u64) * GENERATOR; + let quadratic_commitment = Scalar::from(1u64) * GENERATOR; + + // Note the order and inclusion of the constant term + let coefficients_commitments = + vec![constant_coefficient_commitment, linear_commitment, quadratic_commitment]; + + let polynomial_commitment = PolynomialCommitment { coefficients_commitments }; + + let value = Scalar::from(2u64); + + // f(2) = 11 + let expected = Scalar::from(11u64) * GENERATOR; + + let result = polynomial_commitment.evaluate(&value); + + assert_eq!(result, expected, "The evaluated commitment does not match the expected result"); + } +} diff --git a/src/olaf/simplpedpop.rs b/src/olaf/simplpedpop.rs new file mode 100644 index 0000000..61c76cb --- /dev/null +++ b/src/olaf/simplpedpop.rs @@ -0,0 +1,943 @@ +//! Implementation of a modified version 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 modification consists of each participant sending the secret shares of the other participants only in round 2 +//! instead of in round 1. The reason for this is we use the secret commitments (the evaluations of the secret polynomial +//! commitments at zero) of round 1 to assign the identifiers of all the participants in round 2, which will then be +//! used to compute the corresponding secret shares. Finally, we encrypt and authenticate the secret shares with +//! Chacha20Poly1305, meaning they can be distributed to the participants by an untrusted coordinator instead of sending +//! them directly. +//! +//! The protocol is divided into three rounds. In each round some data and some messages are produced and some messages +//! are verified (if received from a previous round). Data is divided into public and private because in a given round we +//! want to pass a reference to the public data (performance reasons) and the private data itself so that it is zeroized +//! after getting out of scope (security reasons). Public messages are destined to all the other participants, while private +//! messages are destined to a single participant. + +use crate::{context::SigningTranscript, SecretKey, Signature}; +use aead::{generic_array::GenericArray, KeyInit, KeySizeUser}; +use alloc::{collections::BTreeSet, vec::Vec}; +use chacha20poly1305::{aead::Aead, ChaCha20Poly1305, Nonce}; +use curve25519_dalek::{constants::RISTRETTO_BASEPOINT_POINT, RistrettoPoint, Scalar}; +use derive_getters::Getters; +use merlin::Transcript; +use rand_core::{CryptoRng, RngCore}; +use zeroize::ZeroizeOnDrop; + +use super::{ + errors::{DKGError, DKGResult}, + identifier::Identifier, + polynomial::{Coefficient, CoefficientCommitment, Polynomial, PolynomialCommitment}, +}; + +pub(crate) const GENERATOR: RistrettoPoint = RISTRETTO_BASEPOINT_POINT; + +pub(crate) type SecretPolynomialCommitment = PolynomialCommitment; +pub(crate) type SecretPolynomial = Polynomial; +pub(crate) type TotalSecretShare = SecretShare; +pub(crate) type SecretCommitment = CoefficientCommitment; +pub(crate) type Certificate = Signature; +pub(crate) type ProofOfPossession = Signature; +pub(crate) type Secret = Coefficient; + +/// The parameters of a given execution of the SimplPedPoP protocol. +#[derive(Debug, Clone, Getters)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct Parameters { + pub(crate) participants: u16, + pub(crate) threshold: u16, +} + +impl Parameters { + /// Create new parameters. + pub fn new(participants: u16, threshold: u16) -> Parameters { + Parameters { participants, threshold } + } + + pub(crate) fn validate(&self) -> Result<(), DKGError> { + 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); + } + + Ok(()) + } +} + +/// The participants of a given execution of the SimplPedPoP protocol. +#[derive(Debug, Clone, Getters)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct Identifiers { + pub(crate) own_identifier: Identifier, + pub(crate) others_identifiers: BTreeSet, +} + +impl Identifiers { + /// Create new participants. + pub fn new( + own_identifier: Identifier, + others_identifiers: BTreeSet, + ) -> Identifiers { + Identifiers { own_identifier, others_identifiers } + } + + pub(crate) fn validate(&self, participants: u16) -> 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.others_identifiers.len() != participants as usize - 1 { + return Err(DKGError::IncorrectNumberOfIdentifiers { + expected: 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[..]) + .expect("This never fails because bytes has length 64 and the key is a scalar") +} + +/// 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) Scalar); + +impl SecretShare { + pub(crate) fn encrypt( + &self, + decryption_key: &Scalar, + encryption_key: &RistrettoPoint, + context: &[u8], + ) -> DKGResult { + let shared_secret = decryption_key * encryption_key; + + let mut transcript = Transcript::new(b"encryption"); + transcript.commit_point(b"shared secret", &shared_secret.compress()); + transcript.append_message(b"context", context); + + let mut bytes = [0; 12]; + transcript.challenge_bytes(b"nonce", &mut bytes); + + let nonce = Nonce::from_slice(&bytes[..]); + + let mut key: GenericArray< + u8, + ::KeySize, + > = Default::default(); + + transcript.challenge_bytes(b"", key.as_mut_slice()); + + let cipher = ChaCha20Poly1305::new(&key); + + let ciphertext: Vec = cipher + .encrypt(nonce, &self.0.as_bytes()[..]) + .map_err(DKGError::EncryptionError)?; + + Ok(EncryptedSecretShare(ciphertext)) + } +} + +/// An encrypted secret share, which can be sent directly to the intended participant or through an untrusted coordinator. +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct EncryptedSecretShare(pub(crate) Vec); + +impl EncryptedSecretShare { + pub(crate) fn decrypt( + &self, + decryption_key: &Scalar, + encryption_key: &RistrettoPoint, + context: &[u8], + ) -> DKGResult { + let shared_secret = decryption_key * encryption_key; + + let mut transcript = Transcript::new(b"encryption"); + transcript.commit_point(b"shared secret", &shared_secret.compress()); + transcript.append_message(b"context", context); + + let mut bytes = [0; 12]; + transcript.challenge_bytes(b"nonce", &mut bytes); + + let nonce = Nonce::from_slice(&bytes); + + let mut key: GenericArray< + u8, + ::KeySize, + > = Default::default(); + + transcript.challenge_bytes(b"", key.as_mut_slice()); + + let cipher = ChaCha20Poly1305::new(&key); + + let plaintext = cipher.decrypt(nonce, &self.0[..]).map_err(DKGError::DecryptionError)?; + + let mut bytes = [0; 32]; + bytes.copy_from_slice(&plaintext); + + Ok(SecretShare(Scalar::from_bytes_mod_order(bytes))) + } +} + +/// SimplPedPoP round 1. +/// +/// The participant commits to a secret polynomial f(x) of degree t-1, where t is the threshold of the DKG, by commiting +/// to each one of the t coefficients of the secret polynomial. +/// +/// It derives a secret key from the secret of the polynomial f(0) and uses it to generate a Proof of Possession of that +/// secret by signing a message with the secret key. +pub mod round1 { + use super::{ + derive_secret_key_from_secret, Parameters, ProofOfPossession, SecretPolynomial, + SecretPolynomialCommitment, + }; + use crate::{ + olaf::errors::DKGResult, + olaf::polynomial::{Polynomial, PolynomialCommitment}, + PublicKey, SecretKey, + }; + use core::cmp::Ordering; + use curve25519_dalek::Scalar; + use merlin::Transcript; + use rand_core::{CryptoRng, RngCore}; + + /// The private data generated by the participant in round 1. + #[derive(Debug, Clone)] + #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] + pub struct PrivateData { + pub(crate) secret_key: SecretKey, + pub(crate) secret_polynomial: SecretPolynomial, + } + + /// The public data generated by the participant in round 1. + #[derive(Debug, Clone)] + #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] + pub struct PublicData { + pub(crate) parameters: Parameters, + pub(crate) secret_polynomial_commitment: SecretPolynomialCommitment, + pub(crate) proof_of_possession: ProofOfPossession, + } + + /// Public message to be sent by the participant to all the other participants or to the coordinator in round 1. + #[derive(Debug, Clone, PartialEq, Eq)] + #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] + pub struct PublicMessage { + pub(crate) secret_polynomial_commitment: SecretPolynomialCommitment, + pub(crate) proof_of_possession: ProofOfPossession, + } + + impl PublicMessage { + /// Creates a new public message. + pub fn new(public_data: &PublicData) -> PublicMessage { + PublicMessage { + secret_polynomial_commitment: public_data.secret_polynomial_commitment.clone(), + proof_of_possession: public_data.proof_of_possession, + } + } + } + + impl PartialOrd for PublicMessage { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } + } + + impl Ord for PublicMessage { + fn cmp(&self, other: &Self) -> Ordering { + self.secret_polynomial_commitment + .coefficients_commitments + .first() + .expect("This never fails because the minimum threshold of the protocol is 2") + .compress() + .0 + .cmp( + &other + .secret_polynomial_commitment + .coefficients_commitments + .first() + .expect( + "This never fails because the minimum threshold of the protocol is 2", + ) + .compress() + .0, + ) + } + } + + /// Runs the round 1 of the SimplPedPoP protocol. + pub fn run( + parameters: Parameters, + mut rng: R, + ) -> DKGResult<(PrivateData, PublicMessage, PublicData)> { + parameters.validate()?; + + let (private_data, public_data) = generate_data(parameters, &mut rng); + + let public_message = PublicMessage::new(&public_data); + + Ok((private_data, public_message, public_data)) + } + + fn generate_data( + parameters: Parameters, + mut rng: R, + ) -> (PrivateData, PublicData) { + let secret_polynomial = loop { + let temp_polynomial = Polynomial::generate(&mut rng, *parameters.threshold() - 1); + // There must be a secret, which is the constant coefficient of the secret polynomial + if temp_polynomial + .coefficients + .first() + .expect("This never fails because the minimum threshold of the protocol is 2") + != &Scalar::ZERO + { + break temp_polynomial; + } + }; + + let secret_polynomial_commitment = PolynomialCommitment::commit(&secret_polynomial); + + // 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 + .coefficients + .first() + .expect("This never fails because the minimum threshold of the protocol is 2"), + rng, + ); + + let public_key = PublicKey::from_point( + *secret_polynomial_commitment + .coefficients_commitments + .first() + .expect("This never fails because the minimum threshold of the protocol is 2"), + ); + + let proof_of_possession = + secret_key.sign(Transcript::new(b"Proof of Possession"), &public_key); + + ( + PrivateData { secret_key, secret_polynomial }, + PublicData { parameters, secret_polynomial_commitment, proof_of_possession }, + ) + } +} + +/// SimplPedPoP round 2. +/// +/// The participant verifies the received messages of the other participants from round 1, the secret polynomial commitments +/// and the Proofs of Possession. +/// +/// It orders the secret commitments and uses that ordering to assing random identifiers to all the participants in the +/// protocol, including its own. +/// +/// It computes the secret shares of each participant based on their identifiers, encrypts and authenticates them using +/// Chacha20Poly1305 with a shared secret. +/// +/// 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). +pub mod round2 { + use super::{ + round1, Certificate, EncryptedSecretShare, Identifier, Identifiers, Parameters, + SecretCommitment, SecretPolynomial, SecretShare, + }; + use crate::{ + context::SigningTranscript, + olaf::errors::{DKGError, DKGResult}, + verify_batch, PublicKey, SecretKey, + }; + use alloc::{ + collections::{BTreeMap, BTreeSet}, + string::ToString, + vec, + vec::Vec, + }; + use curve25519_dalek::{RistrettoPoint, Scalar}; + use derive_getters::Getters; + use merlin::Transcript; + use sha2::{digest::Update, Digest, Sha512}; + + /// The public data of round 2. + #[derive(Debug, Clone, Getters)] + #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] + pub struct PublicData { + pub(crate) identifiers: Identifiers, + pub(crate) round1_public_messages: BTreeMap, + pub(crate) transcript: Scalar, + pub(crate) public_keys: Vec, + } + + /// 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, + } + + /// Private message to sent by a participant to another participant or to the coordinator in encrypted form in round 1. + #[derive(Debug, Clone)] + #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] + pub struct PrivateMessage { + pub(crate) encrypted_secret_share: EncryptedSecretShare, + } + + impl PrivateMessage { + /// Creates a new private message. + pub fn new( + secret_share: SecretShare, + deckey: Scalar, + enckey: RistrettoPoint, + context: &[u8], + ) -> DKGResult { + let encrypted_secret_share = secret_share.encrypt(&deckey, &enckey, context)?; + + Ok(PrivateMessage { encrypted_secret_share }) + } + } + + /// The messages to be sent by the participant in round 2. + #[derive(Debug, Clone, Getters)] + #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] + pub struct Messages { + // The identifier is the intended recipient of the private message. + private_messages: BTreeMap, + public_message: PublicMessage, + } + + fn validate_messages( + parameters: &Parameters, + round1_public_messages: &BTreeSet, + ) -> DKGResult<()> { + 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(), + }); + } + + for round1_public_message in round1_public_messages { + if round1_public_message + .secret_polynomial_commitment + .coefficients_commitments + .len() + != *parameters.threshold() as usize + { + return Err(DKGError::InvalidSecretPolynomialCommitment { + expected: *parameters.threshold() as usize, + actual: round1_public_message + .secret_polynomial_commitment + .coefficients_commitments + .len(), + }); + } + } + + Ok(()) + } + + /// Runs the round 2 of a SimplPedPoP protocol. + pub fn run( + round1_private_data: round1::PrivateData, + round1_public_data: &round1::PublicData, + round1_public_messages: BTreeSet, + transcript: T, + ) -> DKGResult<(PublicData, Messages)> { + round1_public_data.parameters.validate()?; + + validate_messages(&round1_public_data.parameters, &round1_public_messages)?; + + let public_keys = verify_round1_messages(&round1_public_messages)?; + + let public_data = generate_public_data( + round1_public_messages, + round1_public_data, + transcript, + public_keys, + ); + + let secret_commitment = round1_public_data + .secret_polynomial_commitment + .coefficients_commitments + .first() + .expect("This never fails because the minimum threshold of the protocol is 2"); + + let messages = generate_messages( + &public_data, + &round1_private_data.secret_polynomial, + round1_private_data.secret_key, + secret_commitment, + )?; + + Ok((public_data, messages)) + } + + fn generate_public_data( + round1_public_messages: BTreeSet, + round1_public_data: &round1::PublicData, + mut transcript: T, + public_keys: Vec, + ) -> PublicData { + let mut own_inserted = false; + + let own_first_coefficient_compressed = round1_public_data + .secret_polynomial_commitment + .coefficients_commitments + .first() + .expect("This never fails because the minimum threshold of the protocol is 2") + .compress(); + + // Writes the data of all the participants in the transcript ordered by their identifiers + for message in &round1_public_messages { + let message_first_coefficient_compressed = message + .secret_polynomial_commitment + .coefficients_commitments + .first() + .expect("This never fails because the minimum threshold of the protocol is 2") + .compress(); + + if own_first_coefficient_compressed.0 < message_first_coefficient_compressed.0 + && !own_inserted + { + // Writes own data in the transcript + transcript.commit_point(b"SecretCommitment", &own_first_coefficient_compressed); + + for coefficient_commitment in + &round1_public_data.secret_polynomial_commitment.coefficients_commitments + { + transcript + .commit_point(b"CoefficientCommitment", &coefficient_commitment.compress()); + } + + transcript + .commit_point(b"ProofOfPossessionR", &round1_public_data.proof_of_possession.R); + + own_inserted = true; + } + // Writes the data of the other participants in the transcript + transcript.commit_point(b"SecretCommitment", &message_first_coefficient_compressed); + + for coefficient_commitment in + &message.secret_polynomial_commitment.coefficients_commitments + { + transcript + .commit_point(b"CoefficientCommitment", &coefficient_commitment.compress()); + } + + transcript.commit_point(b"ProofOfPossessionR", &message.proof_of_possession.R); + } + + // Writes own data in the transcript if own identifier is the last one + if !own_inserted { + transcript.commit_point(b"SecretCommitment", &own_first_coefficient_compressed); + + for coefficient_commitment in + &round1_public_data.secret_polynomial_commitment.coefficients_commitments + { + transcript + .commit_point(b"CoefficientCommitment", &coefficient_commitment.compress()); + } + + transcript + .commit_point(b"ProofOfPossessionR", &round1_public_data.proof_of_possession.R); + } + + // Scalar generated from transcript used to generate random identifiers to the participants + let scalar = transcript.challenge_scalar(b"participants"); + + let (identifiers, round1_public_messages) = + generate_identifiers(round1_public_data, round1_public_messages, &scalar); + + PublicData { identifiers, round1_public_messages, transcript: scalar, public_keys } + } + + fn generate_identifiers( + round1_public_data: &round1::PublicData, + round1_public_messages_set: BTreeSet, + scalar: &Scalar, + ) -> (Identifiers, BTreeMap) { + let mut others_identifiers: BTreeSet = BTreeSet::new(); + let mut round1_public_messages: BTreeMap = + BTreeMap::new(); + + let mut secret_commitments: BTreeSet<[u8; 32]> = round1_public_messages_set + .iter() + .map(|msg| { + msg.secret_polynomial_commitment + .coefficients_commitments + .first() + .expect("This never fails because the minimum threshold of the protocol is 2") + .compress() + .0 + }) + .collect(); + + let own_secret_commitment = round1_public_data + .secret_polynomial_commitment + .coefficients_commitments + .first() + .expect("This never fails because the minimum threshold of the protocol is 2"); + + secret_commitments.insert(own_secret_commitment.compress().0); + + let mut index = 0; + for message in &secret_commitments { + if message == &own_secret_commitment.compress().0 { + break; + } + index += 1; + } + + for i in 0..secret_commitments.len() { + let input = Sha512::new().chain(scalar.as_bytes()).chain(i.to_string()); + let random_scalar = Scalar::from_hash(input); + others_identifiers.insert(Identifier(random_scalar)); + } + + let own_identifier = *others_identifiers + .iter() + .nth(index) + .expect("This never fails because the index < len"); + others_identifiers.remove(&own_identifier); + + for (id, message) in others_identifiers.iter().zip(round1_public_messages_set) { + round1_public_messages.insert(*id, message); + } + + let identifiers = Identifiers::new(own_identifier, others_identifiers); + + (identifiers, round1_public_messages) + } + + fn verify_round1_messages( + round1_public_messages: &BTreeSet, + ) -> DKGResult> { + let len = round1_public_messages.len(); + let mut public_keys = Vec::with_capacity(len); + let mut proofs_of_possession = Vec::with_capacity(len); + + // The public keys are the secret commitments of the participants + for round1_public_message in round1_public_messages { + let public_key = PublicKey::from_point( + *round1_public_message + .secret_polynomial_commitment + .coefficients_commitments + .first() + .expect("This never fails because the minimum threshold of the protocol is 2"), + ); + public_keys.push(public_key); + proofs_of_possession.push(round1_public_message.proof_of_possession); + } + + verify_batch( + vec![Transcript::new(b"Proof of Possession"); len], + &proofs_of_possession[..], + &public_keys[..], + false, + ) + .map_err(DKGError::InvalidProofOfPossession)?; + + Ok(public_keys) + } + + fn generate_messages( + round2_public_data: &PublicData, + secret_polynomial: &SecretPolynomial, + secret_key: SecretKey, + secret_commitment: &SecretCommitment, + ) -> DKGResult { + let mut private_messages = BTreeMap::new(); + + let enc_keys: Vec = round2_public_data + .round1_public_messages + .values() + .map(|msg| { + *msg.secret_polynomial_commitment + .coefficients_commitments + .first() + .expect("This never fails because the minimum threshold of the protocol is 2") + }) + .collect(); + + for (i, identifier) in round2_public_data.identifiers.others_identifiers.iter().enumerate() + { + let secret_share = secret_polynomial.evaluate(&identifier.0); + private_messages.insert( + *identifier, + PrivateMessage::new( + SecretShare(secret_share), + secret_key.key, + enc_keys[i], + identifier.0.as_bytes(), + )?, + ); + } + + let public_key = PublicKey::from_point(*secret_commitment); + + let mut transcript = Transcript::new(b"certificate"); + transcript.append_message(b"scalar", round2_public_data.transcript.as_bytes()); + + let certificate = secret_key.sign(transcript, &public_key); + + let public_message = PublicMessage { certificate }; + + Ok(Messages { private_messages, public_message }) + } +} + +/// SimplPedPoP round 3. +/// +/// The participant decrypts and verifies the secret shares received from the other participants from round 2, computes +/// its own secret share and its own total secret share, which corresponds to its share of the group public key. +/// +/// It 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, Identifiers, Parameters, SecretPolynomial, + SecretShare, TotalSecretShare, GENERATOR, + }; + use crate::{ + context::SigningTranscript, + olaf::{ + errors::{DKGError, DKGResult}, + keys::{GroupPublicKey, GroupPublicKeyShare}, + polynomial::PolynomialCommitment, + }, + verify_batch, + }; + use alloc::{collections::BTreeMap, vec, vec::Vec}; + use curve25519_dalek::Scalar; + use derive_getters::Getters; + use merlin::Transcript; + use zeroize::ZeroizeOnDrop; + + /// The private data of round 3. + #[derive(Debug, Clone, ZeroizeOnDrop, Getters)] + #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] + pub struct PrivateData { + pub(crate) total_secret_share: TotalSecretShare, + } + + fn validate_messages( + parameters: &Parameters, + round2_public_messages: &BTreeMap, + round1_public_messages: &BTreeMap, + round2_private_messages: &BTreeMap, + ) -> DKGResult<()> { + 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(), + }); + } + + if round2_private_messages.len() != *parameters.participants() as usize - 1 { + return Err(DKGError::IncorrectNumberOfRound2PrivateMessages { + expected: *parameters.participants() as usize - 1, + actual: round2_private_messages.len(), + }); + } + + Ok(()) + } + + /// Runs the round 3 of the SimplPedPoP protocol. + pub fn run( + round2_public_messages: &BTreeMap, + round2_public_data: &round2::PublicData, + round1_public_data: &round1::PublicData, + round1_private_data: round1::PrivateData, + round2_private_messages: &BTreeMap, + ) -> DKGResult<(GroupPublicKey, BTreeMap, PrivateData)> { + round1_public_data.parameters.validate()?; + + round2_public_data + .identifiers + .validate(round1_public_data.parameters.participants)?; + + validate_messages( + &round1_public_data.parameters, + round2_public_messages, + &round2_public_data.round1_public_messages, + round2_private_messages, + )?; + + let mut transcript = Transcript::new(b"certificate"); + transcript.append_message(b"scalar", round2_public_data.transcript.as_bytes()); + + verify_round2_public_messages(round2_public_messages, round2_public_data, transcript)?; + + let secret_shares = verify_round2_private_messages( + round2_public_data, + round2_private_messages, + &round1_private_data.secret_key.key, + )?; + + let private_data = generate_private_data( + &round2_public_data.identifiers, + &secret_shares, + &round1_private_data.secret_polynomial, + )?; + + let (group_public_key, group_public_key_shares) = + generate_public_data(round2_public_data, round1_public_data, &private_data)?; + + Ok((group_public_key, group_public_key_shares, private_data)) + } + + fn verify_round2_public_messages( + round2_public_messages: &BTreeMap, + round2_public_data: &round2::PublicData, + transcript: T, + ) -> DKGResult<()> { + verify_batch( + vec![transcript.clone(); round2_public_data.identifiers.others_identifiers.len()], + &round2_public_messages + .iter() + .map(|(id, msg)| { + if !round2_public_data.identifiers.others_identifiers().contains(id) { + Err(DKGError::UnknownIdentifierRound2PublicMessages(*id)) + } else { + Ok(msg.certificate) + } + }) + .collect::, DKGError>>()?, + &round2_public_data.public_keys[..], + false, + ) + .map_err(DKGError::InvalidCertificate) + } + + fn verify_round2_private_messages( + round2_public_data: &round2::PublicData, + round2_private_messages: &BTreeMap, + secret: &Scalar, + ) -> DKGResult> { + let mut secret_shares = BTreeMap::new(); + + for (i, (identifier, private_message)) in round2_private_messages.iter().enumerate() { + let secret_share = private_message.encrypted_secret_share.decrypt( + secret, + &round2_public_data.public_keys[i].into_point(), + round2_public_data.identifiers.own_identifier.0.as_bytes(), + )?; + + let expected_evaluation = GENERATOR * secret_share.0; + + secret_shares.insert(*identifier, secret_share); + + let evaluation = round2_public_data + .round1_public_messages + .get(identifier) + .ok_or(DKGError::UnknownIdentifierRound1PublicMessages(*identifier))? + .secret_polynomial_commitment + .evaluate(&round2_public_data.identifiers.own_identifier.0); + + if !(evaluation == expected_evaluation) { + return Err(DKGError::InvalidSecretShare(*identifier)); + } + } + + Ok(secret_shares) + } + + fn generate_private_data( + identifiers: &Identifiers, + secret_shares: &BTreeMap, + secret_polynomial: &SecretPolynomial, + ) -> DKGResult { + let own_secret_share = secret_polynomial.evaluate(&identifiers.own_identifier.0); + + let mut total_secret_share = Scalar::ZERO; + + for id in &identifiers.others_identifiers { + total_secret_share += + secret_shares.get(id).ok_or(DKGError::UnknownIdentifierRound2PrivateMessages)?.0; + } + + total_secret_share += own_secret_share; + + let private_data = PrivateData { total_secret_share: SecretShare(total_secret_share) }; + + Ok(private_data) + } + + fn generate_public_data( + round2_public_data: &round2::PublicData, + round1_public_data: &round1::PublicData, + round2_private_data: &PrivateData, + ) -> DKGResult<(GroupPublicKey, BTreeMap)> { + // Sum of the secret polynomial commitments of the other participants + let others_secret_polynomial_commitment = PolynomialCommitment::sum_polynomial_commitments( + &round2_public_data + .round1_public_messages + .values() + .map(|msg| &msg.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 group public key shares of all the participants, which correspond to the total secret shares commitments + let mut group_public_key_shares = BTreeMap::new(); + + for identifier in &round2_public_data.identifiers.others_identifiers { + let group_public_key_share = total_secret_polynomial_commitment.evaluate(&identifier.0); + + group_public_key_shares.insert(*identifier, group_public_key_share); + } + + let own_group_public_key_share = round2_private_data.total_secret_share.0 * GENERATOR; + + group_public_key_shares + .insert(round2_public_data.identifiers.own_identifier, own_group_public_key_share); + + let shared_public_key = GroupPublicKey::from_point( + *total_secret_polynomial_commitment + .coefficients_commitments + .first() + .expect("This never fails because the minimum threshold of the protocol is 2"), + ); + + Ok((shared_public_key, group_public_key_shares)) + } +} diff --git a/src/olaf/tests.rs b/src/olaf/tests.rs new file mode 100644 index 0000000..6debb7a --- /dev/null +++ b/src/olaf/tests.rs @@ -0,0 +1,942 @@ +#[cfg(test)] +mod tests { + use crate::olaf::{ + errors::DKGResult, + identifier::Identifier, + keys::{GroupPublicKey, GroupPublicKeyShare}, + simplpedpop::{ + self, + round1::{self, PrivateData, PublicData, PublicMessage}, + round2::{self, Messages}, + round3, Identifiers, Parameters, + }, + }; + use alloc::{ + collections::{BTreeMap, BTreeSet}, + vec::Vec, + }; + use merlin::Transcript; + use rand::{rngs::OsRng, Rng}; + + const MAXIMUM_PARTICIPANTS: u16 = 10; + const MINIMUM_PARTICIPANTS: u16 = 3; + const MININUM_THRESHOLD: u16 = 2; + const PROTOCOL_RUNS: usize = 1; + + fn generate_parameters() -> Vec { + let mut rng = rand::thread_rng(); + let max_signers = rng.gen_range(MINIMUM_PARTICIPANTS..=MAXIMUM_PARTICIPANTS); + let min_signers = rng.gen_range(MININUM_THRESHOLD..=max_signers); + + (1..=max_signers).map(|_| Parameters::new(max_signers, min_signers)).collect() + } + + fn round1() -> (Vec, Vec, Vec, Vec>) + { + let parameters_list = generate_parameters(); + + let mut all_public_messages_vec = Vec::new(); + let mut participants_round1_private_data = Vec::new(); + let mut participants_round1_public_data = Vec::new(); + + for i in 0..parameters_list.len() { + let (private_data, public_message, public_data) = + round1::run(parameters_list[i as usize].clone(), OsRng) + .expect("Round 1 should complete without errors!"); + + all_public_messages_vec.push(public_message.clone()); + participants_round1_public_data.push(public_data); + participants_round1_private_data.push(private_data); + } + + let mut received_round1_public_messages: Vec> = Vec::new(); + + let mut all_public_messages = BTreeSet::new(); + + for i in 0..parameters_list[0].participants { + all_public_messages.insert(all_public_messages_vec[i as usize].clone()); + } + + // Iterate through each participant to create a set of messages excluding their own. + for i in 0..parameters_list[0].participants { + let own_message = PublicMessage::new(&participants_round1_public_data[i as usize]); + + let mut messages_for_participant = BTreeSet::new(); + + for message in &all_public_messages { + if &own_message != message { + // Exclude the participant's own message. + messages_for_participant.insert(message.clone()); + } + } + + received_round1_public_messages.push(messages_for_participant); + } + + ( + parameters_list, + participants_round1_private_data, + participants_round1_public_data, + received_round1_public_messages, + ) + } + + fn round2( + parameters_list: &Vec, + participants_round1_private_data: Vec, + participants_round1_public_data: &Vec, + participants_round1_public_messages: &Vec>, + ) -> DKGResult<(Vec, Vec, Vec, Vec)> + { + let mut participants_round2_public_data = Vec::new(); + let mut participants_round2_public_messages = Vec::new(); + let mut participants_set_of_participants = Vec::new(); + let mut identifiers_vec = Vec::new(); + + for i in 0..parameters_list[0].participants { + let result = simplpedpop::round2::run( + participants_round1_private_data[i as usize].clone(), + &participants_round1_public_data[i as usize].clone(), + participants_round1_public_messages[i as usize].clone(), + Transcript::new(b"simplpedpop"), + )?; + + participants_round2_public_data.push(result.0.clone()); + participants_round2_public_messages.push(result.1); + participants_set_of_participants.push(result.0.identifiers.clone()); + identifiers_vec.push(result.0.identifiers.own_identifier); + } + + Ok(( + participants_round2_public_data, + participants_round2_public_messages, + participants_set_of_participants, + identifiers_vec, + )) + } + + fn round3( + participants_sets_of_participants: &Vec, + participants_round2_public_messages: &Vec, + participants_round2_public_data: &Vec, + participants_round1_public_data: &Vec, + participants_round1_private_data: Vec, + participants_round2_private_messages: Vec>, + identifiers_vec: &Vec, + ) -> DKGResult< + Vec<(GroupPublicKey, BTreeMap, round3::PrivateData)>, + > { + let mut participant_data_round3 = Vec::new(); + + for i in 0..participants_sets_of_participants.len() { + let received_round2_public_messages = participants_round2_public_messages + .iter() + .enumerate() + .filter(|(index, _msg)| { + identifiers_vec[*index] + != participants_sets_of_participants[i as usize].own_identifier + }) + .map(|(index, msg)| (identifiers_vec[index], msg.clone())) + .collect::>(); + + let mut round2_private_messages: Vec> = + Vec::new(); + + for participants in participants_sets_of_participants.iter() { + let mut messages_for_participant = BTreeMap::new(); + + for (i, round_messages) in participants_round2_private_messages.iter().enumerate() { + if let Some(message) = round_messages.get(&participants.own_identifier) { + messages_for_participant.insert(identifiers_vec[i], message.clone()); + } + } + + round2_private_messages.push(messages_for_participant); + } + + let result = round3::run( + &received_round2_public_messages, + &participants_round2_public_data[i as usize], + &participants_round1_public_data[i as usize], + participants_round1_private_data[i as usize].clone(), + &round2_private_messages[i as usize], + )?; + + participant_data_round3.push(result); + } + + Ok(participant_data_round3) + } + + mod simplpedpop_tests { + use super::*; + use crate::{ + olaf::{ + errors::DKGError, + polynomial::{Polynomial, PolynomialCommitment}, + simplpedpop::{EncryptedSecretShare, SecretShare}, + }, + PublicKey, SecretKey, SignatureError, + }; + use curve25519_dalek::{RistrettoPoint, Scalar}; + use simplpedpop_tests::simplpedpop::GENERATOR; + + #[test] + pub fn test_successful_simplpedpop() { + for _ in 0..PROTOCOL_RUNS { + let ( + parameters_list, + participants_round1_private_data, + participants_round1_public_data, + participants_round1_public_messages, + ) = round1(); + + let ( + participants_round2_public_data, + participants_round2_messages, + participants_sets_of_participants, + identifiers_vec, + ) = round2( + ¶meters_list, + participants_round1_private_data.clone(), + &participants_round1_public_data, + &participants_round1_public_messages, + ) + .unwrap(); + + let participants_data_round3 = round3( + &participants_sets_of_participants, + &participants_round2_messages + .iter() + .map(|msg| msg.public_message().clone()) + .collect(), + &participants_round2_public_data, + &participants_round1_public_data, + participants_round1_private_data, + participants_round2_messages + .iter() + .map(|msg| msg.private_messages().clone()) + .collect(), + &identifiers_vec, + ) + .unwrap(); + + let shared_public_keys: Vec = + participants_data_round3.iter().map(|state| state.0).collect(); + + assert!( + shared_public_keys.windows(2).all(|w| w[0] == w[1]), + "All participants must have the same group public key!" + ); + + for i in 0..parameters_list[0].participants { + assert_eq!( + participants_data_round3[i as usize] + .1 + .get(&participants_sets_of_participants[i as usize].own_identifier) + .unwrap() + .compress(), + (participants_data_round3[i as usize].2.total_secret_share.0 * GENERATOR) + .compress(), + "Verification of total secret shares failed!" + ); + } + } + } + + #[test] + fn test_incorrect_number_of_round1_public_messages_in_round2() { + let ( + parameters_list, + participants_round1_private_data, + participants_round1_public_data, + mut participants_round1_public_messages, + ) = round1(); + + participants_round1_public_messages[0].pop_last(); + + let result = round2( + ¶meters_list, + participants_round1_private_data.clone(), + &participants_round1_public_data, + &participants_round1_public_messages, + ); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => assert_eq!( + e, + DKGError::IncorrectNumberOfRound1PublicMessages { + expected: parameters_list[0].participants as usize - 1, + actual: parameters_list[0].participants as usize - 2, + }, + "Expected DKGError::IncorrectNumberOfRound1PublicMessages." + ), + } + } + + #[test] + fn test_invalid_secret_polynomial_commitment_in_round2() { + let ( + parameters_list, + participants_round1_private_data, + participants_round1_public_data, + mut participants_round1_public_messages, + ) = round1(); + + let mut new_message = participants_round1_public_messages[0].first().unwrap().clone(); + + new_message.secret_polynomial_commitment.coefficients_commitments.pop(); + + participants_round1_public_messages[0].pop_first(); + participants_round1_public_messages[0].insert(new_message); + + let result = round2( + ¶meters_list, + participants_round1_private_data.clone(), + &participants_round1_public_data, + &participants_round1_public_messages, + ); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => assert_eq!( + e, + DKGError::InvalidSecretPolynomialCommitment { + expected: *parameters_list[0].threshold() as usize, + actual: *parameters_list[0].threshold() as usize - 1, + }, + "Expected DKGError::IncorrectNumberOfRound1PublicMessages." + ), + } + } + + #[test] + fn test_invalid_secret_share_error_in_round3() { + let ( + parameters_list, + participants_round1_private_data, + participants_round1_public_data, + participants_round1_public_messages, + ) = round1(); + + let ( + participants_round2_public_data, + participants_round2_messages, + participants_sets_of_participants, + identifiers_vec, + ) = round2( + ¶meters_list, + participants_round1_private_data.clone(), + &participants_round1_public_data, + &participants_round1_public_messages, + ) + .unwrap(); + + let mut participants_round2_private_messages: Vec< + BTreeMap, + > = participants_round2_messages + .iter() + .map(|msg| msg.private_messages().clone()) + .collect(); + + let enc_keys: Vec = participants_round1_public_messages[1] + .iter() + .map(|msg| { + *msg.secret_polynomial_commitment.coefficients_commitments.first().unwrap() + }) + .collect(); + + let secret_share = SecretShare(Scalar::random(&mut OsRng)); + + let identifiers: BTreeSet = + participants_sets_of_participants[1].others_identifiers.clone(); + + let index = identifiers + .iter() + .position(|x| x == &participants_sets_of_participants[0].own_identifier) + .unwrap(); + + let enc_share = secret_share.encrypt( + &participants_round1_private_data[1].secret_key.key, + &enc_keys[index], + participants_sets_of_participants[0].own_identifier.0.as_bytes(), + ); + + let private_message = participants_round2_private_messages[1] + .get_mut(&participants_sets_of_participants[0].own_identifier) + .unwrap(); + + private_message.encrypted_secret_share = enc_share.unwrap(); + + let result = round3( + &participants_sets_of_participants, + &participants_round2_messages + .iter() + .map(|msg| msg.public_message().clone()) + .collect(), + &participants_round2_public_data, + &participants_round1_public_data, + participants_round1_private_data, + participants_round2_private_messages, + &identifiers_vec, + ); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => assert_eq!( + e, + DKGError::InvalidSecretShare( + participants_sets_of_participants[1].own_identifier + ), + "Expected DKGError::InvalidSecretShare." + ), + } + } + + #[test] + fn test_decryption_error_in_round3() { + let ( + parameters_list, + participants_round1_private_data, + participants_round1_public_data, + participants_round1_public_messages, + ) = round1(); + + let ( + participants_round2_public_data, + participants_round2_messages, + participants_sets_of_participants, + identifiers_vec, + ) = round2( + ¶meters_list, + participants_round1_private_data.clone(), + &participants_round1_public_data, + &participants_round1_public_messages, + ) + .unwrap(); + + let mut participants_round2_private_messages: Vec< + BTreeMap, + > = participants_round2_messages + .iter() + .map(|msg| msg.private_messages().clone()) + .collect(); + + let private_message = participants_round2_private_messages[1] + .get_mut(&participants_sets_of_participants[0].own_identifier) + .unwrap(); + + private_message.encrypted_secret_share = EncryptedSecretShare(vec![1]); + + let result = round3( + &participants_sets_of_participants, + &participants_round2_messages + .iter() + .map(|msg| msg.public_message().clone()) + .collect(), + &participants_round2_public_data, + &participants_round1_public_data, + participants_round1_private_data, + participants_round2_private_messages, + &identifiers_vec, + ); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => assert_eq!( + e, + DKGError::DecryptionError(chacha20poly1305::Error), + "Expected DKGError::DecryptionError." + ), + } + } + + #[test] + fn test_invalid_proof_of_possession_in_round2() { + let ( + parameters_list, + participants_round1_private_data, + participants_round1_public_data, + mut participants_round1_public_messages, + ) = round1(); + + let sk = SecretKey::generate(); + let proof_of_possession = sk.sign( + Transcript::new(b"invalid proof of possession"), + &PublicKey::from(sk.clone()), + ); + let msg = PublicMessage { + secret_polynomial_commitment: PolynomialCommitment::commit(&Polynomial::generate( + &mut OsRng, + parameters_list[0].threshold - 1, + )), + proof_of_possession, + }; + participants_round1_public_messages[0].pop_last(); + participants_round1_public_messages[0].insert(msg); + + let result = round2( + ¶meters_list, + participants_round1_private_data.clone(), + &participants_round1_public_data, + &participants_round1_public_messages, + ); + + 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 ( + parameters_list, + participants_round1_private_data, + participants_round1_public_data, + participants_round1_public_messages, + ) = round1(); + + let ( + mut participants_round2_public_data, + participants_round2_messages, + participants_sets_of_participants, + identifiers_vec, + ) = round2( + ¶meters_list, + participants_round1_private_data.clone(), + &participants_round1_public_data, + &participants_round1_public_messages, + ) + .unwrap(); + + participants_round2_public_data[0].transcript = Scalar::random(&mut OsRng); + + let participants_round2_private_messages: Vec< + BTreeMap, + > = participants_round2_messages + .iter() + .map(|msg| msg.private_messages().clone()) + .collect(); + + let result = round3( + &participants_sets_of_participants, + &participants_round2_messages + .iter() + .map(|msg| msg.public_message().clone()) + .collect(), + &participants_round2_public_data, + &participants_round1_public_data, + participants_round1_private_data, + participants_round2_private_messages, + &identifiers_vec, + ); + + 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 ( + parameters_list, + participants_round1_private_data, + participants_round1_public_data, + participants_round1_public_messages, + ) = round1(); + + let ( + participants_round2_public_data, + participants_round2_messages, + participants_sets_of_participants, + identifiers_vec, + ) = round2( + ¶meters_list, + participants_round1_private_data.clone(), + &participants_round1_public_data, + &participants_round1_public_messages, + ) + .unwrap(); + + let mut participants_round2_public_messages: Vec = + participants_round2_messages + .iter() + .map(|msg| msg.public_message().clone()) + .collect(); + + participants_round2_public_messages.pop(); + + let participants_round2_private_messages: Vec< + BTreeMap, + > = participants_round2_messages + .iter() + .map(|msg| msg.private_messages().clone()) + .collect(); + + let result = round3( + &participants_sets_of_participants, + &participants_round2_public_messages, + &participants_round2_public_data, + &participants_round1_public_data, + participants_round1_private_data, + participants_round2_private_messages, + &identifiers_vec, + ); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => assert_eq!( + e, + DKGError::IncorrectNumberOfRound2PublicMessages { + expected: *parameters_list[0].participants() as usize - 1, + actual: *parameters_list[0].participants() as usize - 2 + }, + "Expected DKGError::IncorrectNumberOfRound2PublicMessages." + ), + } + } + + #[test] + pub fn test_incorrect_number_of_round1_public_messages_in_round3() { + let ( + parameters_list, + participants_round1_private_data, + participants_round1_public_data, + participants_round1_public_messages, + ) = round1(); + + let ( + mut participants_round2_public_data, + participants_round2_messages, + participants_sets_of_participants, + identifiers_vec, + ) = round2( + ¶meters_list, + participants_round1_private_data.clone(), + &participants_round1_public_data, + &participants_round1_public_messages, + ) + .unwrap(); + + let participants_round2_public_messages: Vec = + participants_round2_messages + .iter() + .map(|msg| msg.public_message().clone()) + .collect(); + + participants_round2_public_data[0].round1_public_messages.pop_first(); + + let participants_round2_private_messages: Vec< + BTreeMap, + > = participants_round2_messages + .iter() + .map(|msg| msg.private_messages().clone()) + .collect(); + + let result = round3( + &participants_sets_of_participants, + &participants_round2_public_messages, + &participants_round2_public_data, + &participants_round1_public_data, + participants_round1_private_data, + participants_round2_private_messages, + &identifiers_vec, + ); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => assert_eq!( + e, + DKGError::IncorrectNumberOfRound1PublicMessages { + expected: *parameters_list[0].participants() as usize - 1, + actual: *parameters_list[0].participants() as usize - 2 + }, + "Expected DKGError::IncorrectNumberOfRound1PublicMessages." + ), + } + } + + #[test] + pub fn test_incorrect_number_of_round2_private_messages_in_round3() { + let ( + parameters_list, + participants_round1_private_data, + participants_round1_public_data, + participants_round1_public_messages, + ) = round1(); + + let ( + participants_round2_public_data, + participants_round2_messages, + participants_sets_of_participants, + identifiers_vec, + ) = round2( + ¶meters_list, + participants_round1_private_data.clone(), + &participants_round1_public_data, + &participants_round1_public_messages, + ) + .unwrap(); + + let participants_round2_public_messages: Vec = + participants_round2_messages + .iter() + .map(|msg| msg.public_message().clone()) + .collect(); + + let mut participants_round2_private_messages: Vec< + BTreeMap, + > = participants_round2_messages + .iter() + .map(|msg| msg.private_messages().clone()) + .collect(); + + participants_round2_private_messages[1].pop_last(); + + let result = round3( + &participants_sets_of_participants, + &participants_round2_public_messages, + &participants_round2_public_data, + &participants_round1_public_data, + participants_round1_private_data, + participants_round2_private_messages, + &identifiers_vec, + ); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => assert_eq!( + e, + DKGError::IncorrectNumberOfRound2PrivateMessages { + expected: *parameters_list[0].participants() as usize - 1, + actual: *parameters_list[0].participants() as usize - 2 + }, + "Expected DKGError::IncorrectNumberOfRound2PrivateMessages." + ), + } + } + + #[test] + pub fn test_unknown_identifier_from_round2_public_messages_in_round3() { + let ( + parameters_list, + participants_round1_private_data, + participants_round1_public_data, + participants_round1_public_messages, + ) = round1(); + + let ( + participants_round2_public_data, + participants_round2_messages, + participants_sets_of_participants, + mut identifiers_vec, + ) = round2( + ¶meters_list, + participants_round1_private_data.clone(), + &participants_round1_public_data, + &participants_round1_public_messages, + ) + .unwrap(); + + let participants_round2_public_messages: Vec = + participants_round2_messages + .iter() + .map(|msg| msg.public_message().clone()) + .collect(); + + identifiers_vec.pop(); + let unknown_identifier = Identifier(Scalar::random(&mut OsRng)); + identifiers_vec.push(unknown_identifier); + + let participants_round2_private_messages: Vec< + BTreeMap, + > = participants_round2_messages + .iter() + .map(|msg| msg.private_messages().clone()) + .collect(); + + let result = round3( + &participants_sets_of_participants, + &participants_round2_public_messages, + &participants_round2_public_data, + &participants_round1_public_data, + participants_round1_private_data, + participants_round2_private_messages, + &identifiers_vec, + ); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => assert_eq!( + e, + DKGError::UnknownIdentifierRound2PublicMessages(unknown_identifier), + "Expected DKGError::UnknownIdentifierRound2PublicMessages." + ), + } + } + + #[test] + fn test_unknown_identifier_from_round2_private_messages_in_round3() { + let ( + parameters_list, + participants_round1_private_data, + participants_round1_public_data, + participants_round1_public_messages, + ) = round1(); + + let ( + mut participants_round2_public_data, + participants_round2_messages, + participants_sets_of_participants, + identifiers_vec, + ) = round2( + ¶meters_list, + participants_round1_private_data.clone(), + &participants_round1_public_data, + &participants_round1_public_messages, + ) + .unwrap(); + + let participants_round2_public_messages: Vec = + participants_round2_messages + .iter() + .map(|msg| msg.public_message().clone()) + .collect(); + + let participants_round2_private_messages: Vec< + BTreeMap, + > = participants_round2_messages + .iter() + .map(|msg| msg.private_messages().clone()) + .collect(); + + let received_round2_public_messages = participants_round2_public_messages + .iter() + .enumerate() + .filter(|(index, _msg)| { + identifiers_vec[*index] != participants_sets_of_participants[0].own_identifier + }) + .map(|(index, msg)| (identifiers_vec[index], msg.clone())) + .collect::>(); + + let mut round2_private_messages: Vec> = + Vec::new(); + + for participants in participants_sets_of_participants.iter() { + let mut messages_for_participant = BTreeMap::new(); + + for (i, round_messages) in participants_round2_private_messages.iter().enumerate() { + if let Some(message) = round_messages.get(&participants.own_identifier) { + messages_for_participant.insert(identifiers_vec[i], message.clone()); + } + } + + round2_private_messages.push(messages_for_participant); + } + + let unknown_identifier = Identifier(Scalar::ONE); + + let private_message = round2_private_messages[0].pop_first().unwrap().1; + round2_private_messages[0].insert(unknown_identifier, private_message); + + let public_message = + participants_round2_public_data[0].round1_public_messages.pop_first().unwrap().1; + + participants_round2_public_data[0] + .round1_public_messages + .insert(unknown_identifier, public_message); + + let result = round3::run( + &received_round2_public_messages, + &participants_round2_public_data[0], + &participants_round1_public_data[0], + participants_round1_private_data[0].clone(), + &round2_private_messages[0], + ); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => assert_eq!( + e, + DKGError::UnknownIdentifierRound2PrivateMessages, + "Expected DKGError::UnknownIdentifierRound2PrivateMessages." + ), + } + } + + #[test] + fn test_invalid_threshold() { + let parameters = Parameters::new(3, 1); + let result = round1::run(parameters, &mut OsRng); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => assert_eq!( + e, + DKGError::InsufficientThreshold, + "Expected DKGError::InsufficientThreshold." + ), + } + } + + #[test] + fn test_invalid_participants() { + let parameters = Parameters::new(1, 2); + let result = round1::run(parameters, &mut OsRng); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => assert_eq!( + e, + DKGError::InvalidNumberOfParticipants, + "Expected DKGError::InvalidNumberOfParticipants." + ), + } + } + + #[test] + fn test_threshold_greater_than_participants() { + let parameters = Parameters::new(2, 3); + let result = round1::run(parameters, &mut OsRng); + + match result { + Ok(_) => panic!("Expected an error, but got Ok."), + Err(e) => assert_eq!( + e, + DKGError::ExcessiveThreshold, + "Expected DKGError::ExcessiveThreshold." + ), + } + } + + #[test] + fn test_encryption_decryption() { + let mut rng = OsRng; + let deckey = Scalar::random(&mut rng); + let enckey = RistrettoPoint::random(&mut rng); + let context = b"context"; + + let original_share = SecretShare(Scalar::random(&mut rng)); + + let encrypted_share = original_share.encrypt(&deckey, &enckey, context); + let decrypted_share = encrypted_share.unwrap().decrypt(&deckey, &enckey, context); + + assert_eq!( + original_share.0, + decrypted_share.unwrap().0, + "Decryption must return the original share!" + ); + } + } +} diff --git a/src/serdey.rs b/src/serdey.rs index 7b31ed3..8a8e2f5 100644 --- a/src/serdey.rs +++ b/src/serdey.rs @@ -13,15 +13,15 @@ #[cfg(feature = "serde")] #[rustfmt::skip] macro_rules! serde_boilerplate { ($t:ty) => { -impl serde_crate::Serialize for $t { - fn serialize(&self, serializer: S) -> Result where S: serde_crate::Serializer { +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) } } -impl<'d> serde_crate::Deserialize<'d> for $t { - fn deserialize(deserializer: D) -> Result where D: serde_crate::Deserializer<'d> { +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)?; diff --git a/src/vrf.rs b/src/vrf.rs index d4f4a08..eb3a9e6 100644 --- a/src/vrf.rs +++ b/src/vrf.rs @@ -155,8 +155,8 @@ where /// VRF SigningTranscript for malleable VRF outputs. /// /// *Warning* We caution that malleable VRF outputs are insecure when -/// used in conjunction with HDKD, as provided in dervie.rs. -/// Attackers could translate malleable VRF outputs from one soft subkey +/// used in conjunction with HDKD, as provided in dervie.rs. +/// Attackers could translate malleable VRF outputs from one soft subkey /// to another soft subkey, gaining early knowledge of the VRF output. /// We think most VRF applications for which HDKH sounds suitable /// benefit from using implicit certificates instead of HDKD anyways,