From a7ebceea7e99bd3c59a0897d02375e1d99361249 Mon Sep 17 00:00:00 2001 From: Fiono11 Date: Fri, 19 Apr 2024 11:44:02 +0100 Subject: [PATCH] Implementation of Olaf --- .DS_Store | Bin 0 -> 6148 bytes .gitignore | 1 + .rustfmt.toml | 31 + Cargo.toml | 43 +- annoucement.md | 4 +- benches/olaf_benchmarks.rs | 431 ++++++++++ benches/schnorr_benchmarks.rs | 18 +- rustfmt.toml | 31 +- src/aead.rs | 98 ++- src/batch.rs | 82 +- src/cert.rs | 67 +- src/context.rs | 100 ++- src/derive.rs | 126 +-- src/errors.rs | 9 +- src/keys.rs | 134 ++- src/lib.rs | 16 +- src/musig.rs | 550 +++++++----- src/olaf/errors.rs | 156 ++++ src/olaf/frost.rs | 788 +++++++++++++++++ src/olaf/identifier.rs | 175 ++++ src/olaf/keys.rs | 108 +++ src/olaf/mod.rs | 10 + src/olaf/polynomial.rs | 195 +++++ src/olaf/simplpedpop.rs | 926 ++++++++++++++++++++ src/olaf/tests.rs | 1501 +++++++++++++++++++++++++++++++++ src/points.rs | 16 +- src/serdey.rs | 28 +- src/sign.rs | 238 +++--- src/vrf.rs | 287 ++++--- 29 files changed, 5388 insertions(+), 781 deletions(-) create mode 100644 .DS_Store create mode 100644 .rustfmt.toml create mode 100644 benches/olaf_benchmarks.rs create mode 100644 src/olaf/errors.rs create mode 100644 src/olaf/frost.rs create mode 100644 src/olaf/identifier.rs create mode 100644 src/olaf/keys.rs create mode 100644 src/olaf/mod.rs create mode 100644 src/olaf/polynomial.rs create mode 100644 src/olaf/simplpedpop.rs create mode 100644 src/olaf/tests.rs diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..1f487643c31855e8a0f311ffce8c5cef9e99ed9d GIT binary patch literal 6148 zcmeHKJ8HvF5S)z-F}QJ=@?9Y}5XLz{E?_JNQiup5(yPk3aD}rERu}TBU##kOE%{`1hgF9lOFgF+Lq! zVgw+r84lwe m(T<7Hj(PBQd>2WX*L=_CUE!P 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(); + } + + /// Benchmark FROST signing with the specified ciphersuite. + fn benchmark_frost_t_of_n(c: &mut Criterion) { + let mut group = c.benchmark_group(format!("FROST")); + let mut rng = OsRng; + for &n in [3u16, 10, 100].iter() { + let max_signers = n; + let min_signers = (n * 2 + 2) / 3; + + let ( + parameters_list, + participants_round1_private_data, + participants_round1_public_data, + participants_round1_public_messages, + ) = round1(max_signers, min_signers); + + 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_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 mut key_packages: BTreeMap = BTreeMap::new(); + + let mut identifiers: Vec = participants_sets_of_participants[0] + .others_identifiers() + .iter() + .copied() + .collect(); + + identifiers.push(*participants_sets_of_participants[0].own_identifier()); + + for i in 0..*parameters_list[0].participants() { + key_packages.insert( + identifiers_vec[i as usize], + KeyPackage::new( + identifiers_vec[i as usize], + participants_data_round3[i as usize].2.total_secret_share().clone(), + participants_data_round3[i as usize] + .1 + .get(participants_sets_of_participants[i as usize].own_identifier()) + .unwrap() + .clone(), + participants_data_round3[0].0, + *parameters_list[0].threshold(), + ), + ); + } + + group.bench_with_input( + BenchmarkId::new("round1", min_signers), + &key_packages, + |b, key_packages| { + b.iter(|| { + let participant_identifier = + participants_sets_of_participants[0].own_identifier(); + frost::round1::commit( + key_packages.get(&participant_identifier).unwrap().signing_share(), + &mut rng, + ); + }) + }, + ); + + let mut nonces: BTreeMap = BTreeMap::new(); + let mut commitments: BTreeMap = BTreeMap::new(); + + for participant_index in 0..min_signers { + let participant_identifier = + *participants_sets_of_participants[participant_index as usize].own_identifier(); + let (nonce, commitment) = frost::round1::commit( + key_packages.get(&participant_identifier).unwrap().signing_share(), + &mut rng, + ); + nonces.insert(participant_identifier, nonce); + commitments.insert(participant_identifier, commitment); + } + + let message = "message to sign".as_bytes(); + let context = "context".as_bytes(); + + let signing_package = SigningPackage::new(commitments, message, context); + + group.bench_with_input( + BenchmarkId::new("round2", min_signers), + &(key_packages.clone(), nonces.clone(), signing_package.clone()), + |b, (key_packages, nonces, signing_package)| { + b.iter(|| { + let participant_identifier = + participants_sets_of_participants[0].own_identifier(); + let key_package = key_packages.get(&participant_identifier).unwrap(); + let nonces_to_use = &nonces.get(&participant_identifier).unwrap(); + frost::round2::sign(signing_package, nonces_to_use, key_package).unwrap(); + }) + }, + ); + + let mut signature_shares = BTreeMap::new(); + for participant_identifier in nonces.keys() { + let key_package = key_packages.get(participant_identifier).unwrap(); + let nonces_to_use = &nonces.get(participant_identifier).unwrap(); + let signature_share = + frost::round2::sign(&signing_package, nonces_to_use, key_package).unwrap(); + signature_shares.insert(*key_package.identifier(), signature_share); + } + + let pubkeys = PublicKeyPackage::new( + participants_data_round3[0].1.clone(), + participants_data_round3[0].0, + ); + + group.bench_with_input( + BenchmarkId::new("aggregate", min_signers), + &(signing_package.clone(), signature_shares.clone(), pubkeys), + |b, (signing_package, signature_shares, pubkeys)| { + b.iter(|| { + aggregate(signing_package, signature_shares, pubkeys).unwrap(); + }) + }, + ); + } + + group.finish(); + } + + criterion_group! { + name = olaf_benches; + config = Criterion::default(); + targets = + benchmark_simplpedpop, + benchmark_frost_t_of_n, + } +} + +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/aead.rs b/src/aead.rs index 6cb1b67..6484028 100644 --- a/src/aead.rs +++ b/src/aead.rs @@ -25,7 +25,10 @@ regarded as a pointer, not a recommendation. // use rand_core::{RngCore,CryptoRng}; -use aead::{KeyInit, KeySizeUser, generic_array::{GenericArray}}; +use aead::{ + KeyInit, KeySizeUser, + generic_array::{GenericArray}, +}; use curve25519_dalek::digest::generic_array::typenum::{U32}; @@ -33,17 +36,18 @@ use curve25519_dalek::{ ristretto::{CompressedRistretto}, // RistrettoPoint }; -use super::{SecretKey,PublicKey,Keypair,SignatureResult}; +use super::{SecretKey, PublicKey, Keypair, SignatureResult}; use crate::context::SigningTranscript; use crate::cert::AdaptorCertPublic; - -fn make_aead(mut t: T) -> AEAD -where T: SigningTranscript,AEAD: KeyInit +pub(crate) fn make_aead(mut t: T) -> AEAD +where + T: SigningTranscript, + AEAD: KeyInit, { let mut key: GenericArray::KeySize> = Default::default(); - t.challenge_bytes(b"",key.as_mut_slice()); + t.challenge_bytes(b"", key.as_mut_slice()); AEAD::new(&key) } @@ -56,7 +60,8 @@ impl SecretKey { /// Commit the results of a raw key exchange into a transcript pub fn commit_raw_key_exchange(&self, t: &mut T, ctx: &'static [u8], public: &PublicKey) - where T: SigningTranscript + where + T: SigningTranscript, { let p = self.raw_key_exchange(public); t.commit_point(ctx, &p); @@ -66,10 +71,11 @@ impl SecretKey { /// /// Requires the AEAD have a 32 byte public key and does not support a context. pub fn aead32_unauthenticated(&self, public: &PublicKey) -> AEAD - where AEAD: KeyInit + where + AEAD: KeyInit, { let mut key: GenericArray::KeySize> = Default::default(); - key.clone_from_slice( self.raw_key_exchange(public).as_bytes() ); + key.clone_from_slice(self.raw_key_exchange(public).as_bytes()); AEAD::new(&key) } } @@ -78,10 +84,12 @@ impl PublicKey { /// Initialize an AEAD to the public key `self` using an ephemeral key exchange. /// /// Returns the ephemeral public key and AEAD. - pub fn init_aead_unauthenticated(&self, ctx: &[u8]) -> (CompressedRistretto,AEAD) - { + pub fn init_aead_unauthenticated( + &self, + ctx: &[u8], + ) -> (CompressedRistretto, AEAD) { let ephemeral = Keypair::generate(); - let aead = ephemeral.aead_unauthenticated(ctx,self); + let aead = ephemeral.aead_unauthenticated(ctx, self); (ephemeral.public.into_compressed(), aead) } @@ -89,8 +97,9 @@ impl PublicKey { /// /// Returns the ephemeral public key and AEAD. /// Requires the AEAD have a 32 byte public key and does not support a context. - pub fn init_aead32_unauthenticated(&self) -> (CompressedRistretto,AEAD) - where AEAD: KeyInit + pub fn init_aead32_unauthenticated(&self) -> (CompressedRistretto, AEAD) + where + AEAD: KeyInit, { let secret = SecretKey::generate(); let aead = secret.aead32_unauthenticated(self); @@ -102,48 +111,51 @@ impl Keypair { /// Commit the results of a key exchange into a transcript /// including the public keys in sorted order. pub fn commit_key_exchange(&self, t: &mut T, ctx: &'static [u8], public: &PublicKey) - where T: SigningTranscript + where + T: SigningTranscript, { let mut pks = [self.public.as_compressed(), public.as_compressed()]; - pks.sort_unstable_by_key( |pk| pk.as_bytes() ); - for pk in &pks { t.commit_point(b"pk",pk); } - self.secret.commit_raw_key_exchange(t,ctx,public); + pks.sort_unstable_by_key(|pk| pk.as_bytes()); + for pk in &pks { + t.commit_point(b"pk", pk); + } + self.secret.commit_raw_key_exchange(t, ctx, public); } /// An AEAD from a key exchange with the specified public key. pub fn aead_unauthenticated(&self, ctx: &[u8], public: &PublicKey) -> AEAD { let mut t = merlin::Transcript::new(b"KEX"); - t.append_message(b"ctx",ctx); - self.commit_key_exchange(&mut t,b"kex",public); + t.append_message(b"ctx", ctx); + self.commit_key_exchange(&mut t, b"kex", public); make_aead(t) } /// Reciever's 2DH AEAD - pub fn reciever_aead( + pub fn reciever_aead( &self, mut t: T, ephemeral_pk: &PublicKey, static_pk: &PublicKey, ) -> AEAD - where T: SigningTranscript, AEAD: KeyInit + where + T: SigningTranscript, + AEAD: KeyInit, { - self.commit_key_exchange(&mut t,b"epk",ephemeral_pk); - self.commit_key_exchange(&mut t,b"epk",static_pk); + self.commit_key_exchange(&mut t, b"epk", ephemeral_pk); + self.commit_key_exchange(&mut t, b"epk", static_pk); make_aead(t) } /// Sender's 2DH AEAD - pub fn sender_aead( - &self, - mut t: T, - public: &PublicKey, - ) -> (CompressedRistretto,AEAD) - where T: SigningTranscript, AEAD: KeyInit + pub fn sender_aead(&self, mut t: T, public: &PublicKey) -> (CompressedRistretto, AEAD) + where + T: SigningTranscript, + AEAD: KeyInit, { let key = t.witness_scalar(b"make_esk", &[&self.secret.nonce]); let ekey = SecretKey { key, nonce: self.secret.nonce }.to_keypair(); - ekey.commit_key_exchange(&mut t,b"epk",public); - self.commit_key_exchange(&mut t,b"epk",public); + ekey.commit_key_exchange(&mut t, b"epk", public); + self.commit_key_exchange(&mut t, b"epk", public); (ekey.public.into_compressed(), make_aead(t)) } @@ -152,26 +164,34 @@ impl Keypair { /// Returns the AEAD constructed from an ephemeral key exchange /// with the public key computed form the sender's public key /// and their implicit Adaptor certificate. - pub fn reciever_aead_with_adaptor_cert( + pub fn reciever_aead_with_adaptor_cert( &self, t: T, cert_public: &AdaptorCertPublic, public: &PublicKey, ) -> SignatureResult - where T: SigningTranscript, AEAD: KeyInit + where + T: SigningTranscript, + AEAD: KeyInit, { - let epk = public.open_adaptor_cert(t,cert_public) ?; - Ok(self.aead_unauthenticated(b"",&epk)) + let epk = public.open_adaptor_cert(t, cert_public)?; + Ok(self.aead_unauthenticated(b"", &epk)) } /// Sender's AEAD with Adaptor certificate. /// /// Along with the AEAD, we return the implicit Adaptor certificate /// from which the receiver recreates the ephemeral public key. - pub fn sender_aead_with_adaptor_cert(&self, t: T, public: &PublicKey) -> (AdaptorCertPublic,AEAD) - where T: SigningTranscript+Clone, AEAD: KeyInit + pub fn sender_aead_with_adaptor_cert( + &self, + t: T, + public: &PublicKey, + ) -> (AdaptorCertPublic, AEAD) + where + T: SigningTranscript + Clone, + AEAD: KeyInit, { - let (cert,secret) = self.issue_self_adaptor_cert(t); + let (cert, secret) = self.issue_self_adaptor_cert(t); let aead = secret.to_keypair().aead_unauthenticated(b"", public); (cert, aead) } diff --git a/src/batch.rs b/src/batch.rs index 4be860b..bf9c994 100644 --- a/src/batch.rs +++ b/src/batch.rs @@ -21,8 +21,8 @@ use crate::context::{SigningTranscript}; #[cfg(feature = "alloc")] use alloc::vec::Vec; -const ASSERT_MESSAGE: &str = "The number of messages/transcripts, signatures, and public keys must be equal."; - +const ASSERT_MESSAGE: &str = + "The number of messages/transcripts, signatures, and public keys must be equal."; /// Verify a batch of `signatures` on `messages` with their respective `public_keys`. /// @@ -63,7 +63,7 @@ const ASSERT_MESSAGE: &str = "The number of messages/transcripts, signatures, an /// assert!( verify_batch(transcripts, &signatures[..], &public_keys[..], false).is_ok() ); /// # } /// ``` -pub fn verify_batch( +pub fn verify_batch( transcripts: I, signatures: &[Signature], public_keys: &[PublicKey], @@ -71,12 +71,19 @@ pub fn verify_batch( ) -> SignatureResult<()> where T: SigningTranscript, - I: IntoIterator, + I: IntoIterator, { - verify_batch_rng(transcripts, signatures, public_keys, deduplicate_public_keys, getrandom_or_panic()) + verify_batch_rng( + transcripts, + signatures, + public_keys, + deduplicate_public_keys, + getrandom_or_panic(), + ) } struct NotAnRng; +#[rustfmt::skip] impl rand_core::RngCore for NotAnRng { fn next_u32(&mut self) -> u32 { rand_core::impls::next_u32_via_fill(self) } @@ -98,13 +105,13 @@ impl rand_core::CryptoRng for NotAnRng {} /// /// We break the `R: CryptRng` requirement from `verify_batch_rng` /// here, but this appears fine using an Fiat-Shamir transform with -/// an argument similar to +/// an argument similar to /// [public key delinearization](https://crypto.stanford.edu/~dabo/pubs/papers/BLSmultisig.html). /// /// We caution deeterministic delinearization could interact poorly /// with other functionality, *if* one delinarization scalar were /// left constant. We do not make that mistake here. -pub fn verify_batch_deterministic( +pub fn verify_batch_deterministic( transcripts: I, signatures: &[Signature], public_keys: &[PublicKey], @@ -112,7 +119,7 @@ pub fn verify_batch_deterministic( ) -> SignatureResult<()> where T: SigningTranscript, - I: IntoIterator, + I: IntoIterator, { verify_batch_rng(transcripts, signatures, public_keys, deduplicate_public_keys, NotAnRng) } @@ -120,6 +127,7 @@ where /// Verify a batch of `signatures` on `messages` with their respective `public_keys`. /// /// Inputs and return agree with `verify_batch` except the user supplies their own random number generator. +#[rustfmt::skip] pub fn verify_batch_rng( transcripts: I, signatures: &[Signature], @@ -146,15 +154,16 @@ where verify_batch_equation( bs, zs, hrams, signatures, public_keys, deduplicate_public_keys ) } - trait HasR { #[allow(non_snake_case)] fn get_R(&self) -> &CompressedRistretto; } +#[rustfmt::skip] impl HasR for Signature { #[allow(non_snake_case)] fn get_R(&self) -> &CompressedRistretto { &self.R } } +#[rustfmt::skip] impl HasR for CompressedRistretto { #[allow(non_snake_case)] fn get_R(&self) -> &CompressedRistretto { self } @@ -163,6 +172,7 @@ impl HasR for CompressedRistretto { /// First phase of batch verification that computes the delinierizing /// coefficents and challenge hashes #[allow(non_snake_case)] +#[rustfmt::skip] fn prepare_batch( transcripts: I, signatures: &[impl HasR], @@ -221,6 +231,7 @@ where /// Last phase of batch verification that checks the verification equation #[allow(non_snake_case)] +#[rustfmt::skip] fn verify_batch_equation( bs: Scalar, zs: Vec, @@ -275,10 +286,8 @@ fn verify_batch_equation( if b { Ok(()) } else { Err(SignatureError::EquationFalse) } } - - /// Half-aggregated aka prepared batch signature -/// +/// /// Implementation of "Non-interactive half-aggregation of EdDSA and /// variantsof Schnorr signatures" by Konstantinos Chalkias, /// François Garillot, Yashvanth Kondi, and Valeria Nikolaenko @@ -289,10 +298,10 @@ pub struct PreparedBatch { Rs: Vec, } -impl PreparedBatch{ - +impl PreparedBatch { /// Create a half-aggregated aka prepared batch signature from many other signatures. #[allow(non_snake_case)] + #[rustfmt::skip] pub fn new( transcripts: I, signatures: &[Signature], @@ -319,6 +328,7 @@ impl PreparedBatch{ /// Verify a half-aggregated aka prepared batch signature #[allow(non_snake_case)] + #[rustfmt::skip] pub fn verify( &self, transcripts: I, @@ -345,29 +355,29 @@ impl PreparedBatch{ #[allow(non_snake_case)] pub fn read_bytes(&self, mut bytes: &[u8]) -> SignatureResult { use arrayref::array_ref; - if bytes.len() % 32 != 0 || bytes.len() < 64 { + if bytes.len() % 32 != 0 || bytes.len() < 64 { return Err(SignatureError::BytesLengthError { name: "PreparedBatch", description: "A Prepared batched signature", - length: 0 // TODO: Maybe get rid of this silly field? + length: 0, // TODO: Maybe get rid of this silly field? }); } let l = (bytes.len() % 32) - 1; let mut read = || { - let (head,tail) = bytes.split_at(32); + let (head, tail) = bytes.split_at(32); bytes = tail; - *array_ref![head,0,32] + *array_ref![head, 0, 32] }; let mut bs = read(); bs[31] &= 127; - let bs = super::sign::check_scalar(bs) ?; + let bs = super::sign::check_scalar(bs)?; let mut Rs = Vec::with_capacity(l); for _ in 0..l { - Rs.push( CompressedRistretto(read()) ); + Rs.push(CompressedRistretto(read())); } Ok(PreparedBatch { bs, Rs }) } - + /// Returns buffer size required for serialization #[allow(non_snake_case)] pub fn byte_len(&self) -> usize { @@ -377,18 +387,17 @@ impl PreparedBatch{ /// Serializes into exactly sized buffer #[allow(non_snake_case)] pub fn write_bytes(&self, mut bytes: &mut [u8]) { - assert!(bytes.len() == self.byte_len()); - let mut place = |s: &[u8]| reserve_mut(&mut bytes,32).copy_from_slice(s); + assert!(bytes.len() == self.byte_len()); + let mut place = |s: &[u8]| reserve_mut(&mut bytes, 32).copy_from_slice(s); let mut bs = self.bs.to_bytes(); bs[31] |= 128; place(&bs[..]); for R in self.Rs.iter() { place(R.as_bytes()); } - } + } } - pub fn reserve_mut<'heap, T>(heap: &mut &'heap mut [T], len: usize) -> &'heap mut [T] { let tmp: &'heap mut [T] = core::mem::take(&mut *heap); let (reserved, tmp) = tmp.split_at_mut(len); @@ -396,7 +405,6 @@ pub fn reserve_mut<'heap, T>(heap: &mut &'heap mut [T], len: usize) -> &'heap mu reserved } - #[cfg(test)] mod test { #[cfg(feature = "alloc")] @@ -425,28 +433,30 @@ mod test { for i in 0..messages.len() { let mut keypair: Keypair = Keypair::generate_with(&mut csprng); - if i == 3 || i == 4 { keypair = keypairs[0].clone(); } + if i == 3 || i == 4 { + keypair = keypairs[0].clone(); + } signatures.push(keypair.sign(ctx.bytes(messages[i]))); keypairs.push(keypair); } let mut public_keys: Vec = keypairs.iter().map(|key| key.public).collect(); - public_keys.swap(1,2); + public_keys.swap(1, 2); let transcripts = messages.iter().map(|m| ctx.bytes(m)); - assert!( verify_batch(transcripts, &signatures[..], &public_keys[..], false).is_err() ); + assert!(verify_batch(transcripts, &signatures[..], &public_keys[..], false).is_err()); let transcripts = messages.iter().map(|m| ctx.bytes(m)); - assert!( verify_batch(transcripts, &signatures[..], &public_keys[..], true).is_err() ); + assert!(verify_batch(transcripts, &signatures[..], &public_keys[..], true).is_err()); - public_keys.swap(1,2); + public_keys.swap(1, 2); let transcripts = messages.iter().map(|m| ctx.bytes(m)); - assert!( verify_batch(transcripts, &signatures[..], &public_keys[..], false).is_ok() ); + assert!(verify_batch(transcripts, &signatures[..], &public_keys[..], false).is_ok()); let transcripts = messages.iter().map(|m| ctx.bytes(m)); - assert!( verify_batch(transcripts, &signatures[..], &public_keys[..], true).is_ok() ); + assert!(verify_batch(transcripts, &signatures[..], &public_keys[..], true).is_ok()); - signatures.swap(1,2); + signatures.swap(1, 2); let transcripts = messages.iter().map(|m| ctx.bytes(m)); - assert!( verify_batch(transcripts, &signatures[..], &public_keys[..], false).is_err() ); + assert!(verify_batch(transcripts, &signatures[..], &public_keys[..], false).is_err()); let transcripts = messages.iter().map(|m| ctx.bytes(m)); - assert!( verify_batch(transcripts, &signatures[..], &public_keys[..], true).is_err() ); + assert!(verify_batch(transcripts, &signatures[..], &public_keys[..], true).is_err()); } } diff --git a/src/cert.rs b/src/cert.rs index 92bc06f..46e091f 100644 --- a/src/cert.rs +++ b/src/cert.rs @@ -7,7 +7,6 @@ // Authors: // - Jeffrey Burdges - //! ### Adaptor signature-based implicit certificate scheme for Ristretto //! //! [Implicit certificates](https://en.wikipedia.org/wiki/Implicit_certificate) @@ -57,7 +56,6 @@ use curve25519_dalek::scalar::Scalar; use super::*; use crate::context::SigningTranscript; - /// Adaptor Implicit Certificate Secret /// /// Issuing an Adaptor implicit certificate requires producing @@ -93,7 +91,7 @@ impl Keypair { /// Issue an Adaptor implicit certificate /// /// Aside from the issuing `Keypair` supplied as `self`, you provide both - /// (1) a `SigningTranscript` called `t` that incorporates both the context + /// (1) a `SigningTranscript` called `t` that incorporates both the context /// and the certificate requester's identity, and /// (2) the `seed_public_key` supplied by the certificate recipient /// in their certificate request. @@ -101,20 +99,24 @@ impl Keypair { /// certificate requester, ans from which the certificate requester /// derives their certified key pair. pub fn issue_adaptor_cert(&self, mut t: T, seed_public_key: &PublicKey) -> AdaptorCertSecret - where T: SigningTranscript + where + T: SigningTranscript, { t.proto_name(b"Adaptor"); - t.commit_point(b"issuer-pk",self.public.as_compressed()); + t.commit_point(b"issuer-pk", self.public.as_compressed()); // We cannot commit the `seed_public_key` to the transcript // because the whole point is to keep the transcript minimal. // Instead we consume it as witness datathat influences only k. - let k = t.witness_scalar(b"issuing",&[ &self.secret.nonce, seed_public_key.as_compressed().as_bytes() ]); + let k = t.witness_scalar( + b"issuing", + &[&self.secret.nonce, seed_public_key.as_compressed().as_bytes()], + ); // Compute the public key reconstruction data let gamma = seed_public_key.as_point() + &k * constants::RISTRETTO_BASEPOINT_TABLE; let gamma = gamma.compress(); - t.commit_point(b"gamma",&gamma); + t.commit_point(b"gamma", &gamma); let cert_public = AdaptorCertPublic(gamma.0); // Compute the secret key reconstruction data @@ -150,27 +152,28 @@ impl PublicKey { &self, mut t: T, seed_secret_key: &SecretKey, - cert_secret: AdaptorCertSecret + cert_secret: AdaptorCertSecret, ) -> SignatureResult<(AdaptorCertPublic, SecretKey)> - where T: SigningTranscript + where + T: SigningTranscript, { t.proto_name(b"Adaptor"); - t.commit_point(b"issuer-pk",self.as_compressed()); + t.commit_point(b"issuer-pk", self.as_compressed()); // Again we cannot commit much to the transcript, but we again // treat anything relevant as a witness when defining the let mut nonce = [0u8; 32]; - t.witness_bytes(b"accepting",&mut nonce, &[&cert_secret.0[..],&seed_secret_key.nonce]); + t.witness_bytes(b"accepting", &mut nonce, &[&cert_secret.0[..], &seed_secret_key.nonce]); let mut s = [0u8; 32]; s.copy_from_slice(&cert_secret.0[32..64]); - let s = crate::scalar_from_canonical_bytes(s).ok_or(SignatureError::ScalarFormatError) ?; - let cert_public : AdaptorCertPublic = cert_secret.into(); + let s = crate::scalar_from_canonical_bytes(s).ok_or(SignatureError::ScalarFormatError)?; + let cert_public: AdaptorCertPublic = cert_secret.into(); let gamma = CompressedRistretto(cert_public.0); - t.commit_point(b"gamma",&gamma); + t.commit_point(b"gamma", &gamma); let key = s + seed_secret_key.key; - Ok(( cert_public, SecretKey { key, nonce } )) + Ok((cert_public, SecretKey { key, nonce })) } } @@ -191,10 +194,15 @@ impl Keypair { /// only a digest `h` that incorporates any context and metadata /// pertaining to the issued key. pub fn issue_self_adaptor_cert(&self, t: T) -> (AdaptorCertPublic, SecretKey) - where T: SigningTranscript+Clone + where + T: SigningTranscript + Clone, { let mut bytes = [0u8; 96]; - t.witness_bytes(b"issue_self_adaptor_cert", &mut bytes, &[&self.secret.nonce, &self.secret.to_bytes() as &[u8]]); + t.witness_bytes( + b"issue_self_adaptor_cert", + &mut bytes, + &[&self.secret.nonce, &self.secret.to_bytes() as &[u8]], + ); let mut nonce = [0u8; 32]; nonce.copy_from_slice(&bytes[64..96]); @@ -205,25 +213,32 @@ impl Keypair { let seed = SecretKey { key, nonce }.to_keypair(); let cert_secret = self.issue_adaptor_cert(t.clone(), &seed.public); - self.public.accept_adaptor_cert(t, &seed.secret, cert_secret).expect("Cert issued above and known to produce signature errors; qed") + self.public + .accept_adaptor_cert(t, &seed.secret, cert_secret) + .expect("Cert issued above and known to produce signature errors; qed") } } impl PublicKey { /// Extract the certified pulic key from an adaptor certificate - /// + /// /// We've no confirmation that this public key was certified /// until we use it in some authenticated setting, like an AEAD /// or another signature. - pub fn open_adaptor_cert(&self, mut t: T, cert_public: &AdaptorCertPublic) -> SignatureResult - where T: SigningTranscript + pub fn open_adaptor_cert( + &self, + mut t: T, + cert_public: &AdaptorCertPublic, + ) -> SignatureResult + where + T: SigningTranscript, { t.proto_name(b"Adaptor"); - t.commit_point(b"issuer-pk",self.as_compressed()); + t.commit_point(b"issuer-pk", self.as_compressed()); let gamma = CompressedRistretto(cert_public.0); - t.commit_point(b"gamma",&gamma); - let gamma = gamma.decompress().ok_or(SignatureError::PointDecompressionError) ?; + t.commit_point(b"gamma", &gamma); + let gamma = gamma.decompress().ok_or(SignatureError::PointDecompressionError)?; let point = cert_public.derive_e(t) * self.as_point() + gamma; Ok(PublicKey::from_point(point)) @@ -242,8 +257,8 @@ mod tests { let mut csprng = rand_core::OsRng; let issuer = Keypair::generate_with(&mut csprng); - let (cert_public,secret_key) = issuer.issue_self_adaptor_cert(t.clone()); - let public_key = issuer.public.open_adaptor_cert(t,&cert_public).unwrap(); + let (cert_public, secret_key) = issuer.issue_self_adaptor_cert(t.clone()); + let public_key = issuer.public.open_adaptor_cert(t, &cert_public).unwrap(); assert_eq!(secret_key.to_public(), public_key); } } diff --git a/src/context.rs b/src/context.rs index 99fe041..bc6f06c 100644 --- a/src/context.rs +++ b/src/context.rs @@ -11,17 +11,16 @@ use core::cell::RefCell; -use rand_core::{RngCore,CryptoRng}; +use rand_core::{RngCore, CryptoRng}; use merlin::Transcript; -use curve25519_dalek::digest::{Update,FixedOutput,ExtendableOutput,XofReader}; -use curve25519_dalek::digest::generic_array::typenum::{U32,U64}; +use curve25519_dalek::digest::{Update, FixedOutput, ExtendableOutput, XofReader}; +use curve25519_dalek::digest::generic_array::typenum::{U32, U64}; use curve25519_dalek::ristretto::CompressedRistretto; // RistrettoPoint use curve25519_dalek::scalar::Scalar; - // === Signing context as transcript === // /// Schnorr signing transcript @@ -35,7 +34,7 @@ use curve25519_dalek::scalar::Scalar; /// abstract enough to support conventional hash functions as well. /// /// We warn however that conventional hash functions do not provide -/// strong enough domain seperation for usage via `&mut` references. +/// strong enough domain separation for usage via `&mut` references. /// /// We fold randomness into witness generation here too, which /// gives every function that takes a `SigningTranscript` a default @@ -83,14 +82,20 @@ pub trait SigningTranscript { /// Produce secret witness bytes from the protocol transcript /// and any "nonce seeds" kept with the secret keys. - fn witness_bytes_rng(&self, label: &'static [u8], dest: &mut [u8], nonce_seeds: &[&[u8]], rng: R) - where R: RngCore+CryptoRng; + fn witness_bytes_rng( + &self, + label: &'static [u8], + dest: &mut [u8], + nonce_seeds: &[&[u8]], + rng: R, + ) where + R: RngCore + CryptoRng; } - /// We delegates any mutable reference to its base type, like `&mut Rng` /// or similar to `BorrowMut<..>` do, but doing so here simplifies /// alternative implementations. +#[rustfmt::skip] impl SigningTranscript for &mut T where T: SigningTranscript + ?Sized, { @@ -134,8 +139,14 @@ impl SigningTranscript for Transcript { Transcript::challenge_bytes(self, label, dest) } - fn witness_bytes_rng(&self, label: &'static [u8], dest: &mut [u8], nonce_seeds: &[&[u8]], mut rng: R) - where R: RngCore+CryptoRng + fn witness_bytes_rng( + &self, + label: &'static [u8], + dest: &mut [u8], + nonce_seeds: &[&[u8]], + mut rng: R, + ) where + R: RngCore + CryptoRng, { let mut br = self.build_rng(); for ns in nonce_seeds { @@ -146,7 +157,6 @@ impl SigningTranscript for Transcript { } } - /// Schnorr signing context /// /// We expect users to have separate `SigningContext`s for each role @@ -169,7 +179,7 @@ pub struct SigningContext(Transcript); /// Initialize a signing context from a static byte string that /// identifies the signature's role in the larger protocol. #[inline(always)] -pub fn signing_context(context : &[u8]) -> SigningContext { +pub fn signing_context(context: &[u8]) -> SigningContext { SigningContext::new(context) } @@ -177,9 +187,9 @@ impl SigningContext { /// Initialize a signing context from a static byte string that /// identifies the signature's role in the larger protocol. #[inline(always)] - pub fn new(context : &[u8]) -> SigningContext { + pub fn new(context: &[u8]) -> SigningContext { let mut t = Transcript::new(b"SigningContext"); - t.append_message(b"",context); + t.append_message(b"", context); SigningContext(t) } @@ -187,7 +197,7 @@ impl SigningContext { /// /// Avoid this method when processing large slices because it /// calls `merlin::Transcript::append_message` directly and - /// `merlin` is designed for domain seperation, not performance. + /// `merlin` is designed for domain separation, not performance. #[inline(always)] pub fn bytes(&self, bytes: &[u8]) -> Transcript { let mut t = self.0.clone(); @@ -210,7 +220,7 @@ impl SigningContext { /// Initialize an owned signing transcript on a message provided as /// a hash function with 256 bit output. #[inline(always)] - pub fn hash256>(&self, h: D) -> Transcript { + pub fn hash256>(&self, h: D) -> Transcript { let mut prehash = [0u8; 32]; prehash.copy_from_slice(h.finalize_fixed().as_slice()); let mut t = self.0.clone(); @@ -221,7 +231,7 @@ impl SigningContext { /// Initialize an owned signing transcript on a message provided as /// a hash function with 512 bit output, usually a gross over kill. #[inline(always)] - pub fn hash512>(&self, h: D) -> Transcript { + pub fn hash512>(&self, h: D) -> Transcript { let mut prehash = [0u8; 64]; prehash.copy_from_slice(h.finalize_fixed().as_slice()); let mut t = self.0.clone(); @@ -230,7 +240,6 @@ impl SigningContext { } } - /// Very simple transcript construction from a modern hash function. /// /// We provide this transcript type to directly use conventional hash @@ -257,7 +266,8 @@ impl SigningContext { /// domain separation provided by our methods. We do this to make /// `&mut XoFTranscript : SigningTranscript` safe. pub struct XoFTranscript(H) -where H: Update + ExtendableOutput + Clone; +where + H: Update + ExtendableOutput + Clone; fn input_bytes(h: &mut H, bytes: &[u8]) { let l = bytes.len() as u64; @@ -266,7 +276,8 @@ fn input_bytes(h: &mut H, bytes: &[u8]) { } impl XoFTranscript -where H: Update + ExtendableOutput + Clone +where + H: Update + ExtendableOutput + Clone, { /// Create a `XoFTranscript` from a conventional hash functions with an extensible output mode. /// @@ -274,18 +285,24 @@ where H: Update + ExtendableOutput + Clone /// provided, so that our domain separation works correctly even /// when using `&mut XoFTranscript : SigningTranscript`. #[inline(always)] - pub fn new(h: H) -> XoFTranscript { XoFTranscript(h) } + pub fn new(h: H) -> XoFTranscript { + XoFTranscript(h) + } } impl From for XoFTranscript -where H: Update + ExtendableOutput + Clone +where + H: Update + ExtendableOutput + Clone, { #[inline(always)] - fn from(h: H) -> XoFTranscript { XoFTranscript(h) } + fn from(h: H) -> XoFTranscript { + XoFTranscript(h) + } } impl SigningTranscript for XoFTranscript -where H: Update + ExtendableOutput + Clone +where + H: Update + ExtendableOutput + Clone, { fn commit_bytes(&mut self, label: &'static [u8], bytes: &[u8]) { self.0.update(b"co"); @@ -301,8 +318,14 @@ where H: Update + ExtendableOutput + Clone self.0.clone().chain(b"xof").finalize_xof().read(dest); } - fn witness_bytes_rng(&self, label: &'static [u8], dest: &mut [u8], nonce_seeds: &[&[u8]], mut rng: R) - where R: RngCore+CryptoRng + fn witness_bytes_rng( + &self, + label: &'static [u8], + dest: &mut [u8], + nonce_seeds: &[&[u8]], + mut rng: R, + ) where + R: RngCore + CryptoRng, { let mut h = self.0.clone().chain(b"wb"); input_bytes(&mut h, label); @@ -319,7 +342,6 @@ where H: Update + ExtendableOutput + Clone } } - /// Schnorr signing transcript with the default `ThreadRng` replaced /// by an arbitrary `CryptoRng`. /// @@ -334,13 +356,16 @@ where H: Update + ExtendableOutput + Clone /// produces secure signatures, we recommend against doing this in /// production because we implement protocols like multi-signatures /// which likely become vulnerable when derandomized. -pub struct SigningTranscriptWithRng -where T: SigningTranscript, R: RngCore+CryptoRng +pub struct SigningTranscriptWithRng +where + T: SigningTranscript, + R: RngCore + CryptoRng, { t: T, rng: RefCell, } +#[rustfmt::skip] impl SigningTranscript for SigningTranscriptWithRng where T: SigningTranscript, R: RngCore+CryptoRng { @@ -366,12 +391,12 @@ where T: SigningTranscript, R: RngCore+CryptoRng /// however because, although such derandomization produces secure Schnorr /// signatures, we do implement protocols here like multi-signatures which /// likely become vulnerable when derandomized. -pub fn attach_rng(t: T, rng: R) -> SigningTranscriptWithRng -where T: SigningTranscript, R: RngCore+CryptoRng +pub fn attach_rng(t: T, rng: R) -> SigningTranscriptWithRng +where + T: SigningTranscript, + R: RngCore + CryptoRng, { - SigningTranscriptWithRng { - t, rng: RefCell::new(rng) - } + SigningTranscriptWithRng { t, rng: RefCell::new(rng) } } #[cfg(feature = "rand_chacha")] @@ -379,9 +404,10 @@ use rand_chacha::ChaChaRng; /// Attach a `ChaChaRng` to a `Transcript` to repalce the default `ThreadRng` #[cfg(feature = "rand_chacha")] -pub fn attach_chacharng(t: T, seed: [u8; 32]) -> SigningTranscriptWithRng -where T: SigningTranscript +pub fn attach_chacharng(t: T, seed: [u8; 32]) -> SigningTranscriptWithRng +where + T: SigningTranscript, { use rand_core::SeedableRng; - attach_rng(t,ChaChaRng::from_seed(seed)) + attach_rng(t, ChaChaRng::from_seed(seed)) } diff --git a/src/derive.rs b/src/derive.rs index f73242f..a2dc104 100644 --- a/src/derive.rs +++ b/src/derive.rs @@ -7,23 +7,23 @@ // Authors: // - Jeffrey Burdges -//! ### Implementation of "hierarchical deterministic key derivation" (HDKD) for Schnorr signatures on Ristretto -//! +//! ### Implementation of "hierarchical deterministic key derivation" (HDKD) for Schnorr signatures on Ristretto +//! //! *Warning* We warn that our VRF construction in vrf.rs supports //! malleable VRF outputs via the `Malleable` type, which becomes //! insecure when used in conjunction with our hierarchical key //! derivation methods here. -//! Attackers could translate malleable VRF outputs from one soft subkey +//! 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, //! which should also be secure in combination with HDKH. //! We always use non-malleable VRF inputs in our convenience methods. -//! We suggest using implicit certificates instead of HDKD when +//! We suggest using implicit certificates instead of HDKD when //! using VRFs. //! -//! +//! // use curve25519_dalek::digest::generic_array::typenum::U64; // use curve25519_dalek::digest::Digest; @@ -45,19 +45,20 @@ pub const CHAIN_CODE_LENGTH: usize = 32; /// chain codes fill this gap by being a high entropy secret shared /// between public and private key holders. These are produced by /// key derivations and can be incorporated into subsequence key -/// derivations. +/// derivations. /// See https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#extended-keys #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct ChainCode(pub [u8; CHAIN_CODE_LENGTH]); /// Key types that support "hierarchical deterministic" key derivation -pub trait Derivation : Sized { +pub trait Derivation: Sized { /// Derive key with subkey identified by a byte array /// presented via a `SigningTranscript`, and a chain code. fn derived_key(&self, t: T, cc: ChainCode) -> (Self, ChainCode) - where T: SigningTranscript; + where + T: SigningTranscript; - /// Derive key with subkey identified by a byte array + /// Derive key with subkey identified by a byte array /// and a chain code. We do not include a context here /// because the chain code could serve this purpose. fn derived_key_simple>(&self, cc: ChainCode, i: B) -> (Self, ChainCode) { @@ -68,12 +69,14 @@ pub trait Derivation : Sized { /// Derive key with subkey identified by a byte array /// and a chain code, and with external randomnesses. - fn derived_key_simple_rng(&self, cc: ChainCode, i: B, rng: R) -> (Self, ChainCode) - where B: AsRef<[u8]>, R: RngCore+CryptoRng + fn derived_key_simple_rng(&self, cc: ChainCode, i: B, rng: R) -> (Self, ChainCode) + where + B: AsRef<[u8]>, + R: RngCore + CryptoRng, { let mut t = merlin::Transcript::new(b"SchnorrRistrettoHDKD"); t.append_message(b"sign-bytes", i.as_ref()); - self.derived_key(super::context::attach_rng(t,rng), cc) + self.derived_key(super::context::attach_rng(t, rng), cc) } } @@ -87,10 +90,11 @@ impl PublicKey { /// /// We update the signing transcript as a side effect. fn derive_scalar_and_chaincode(&self, t: &mut T, cc: ChainCode) -> (Scalar, ChainCode) - where T: SigningTranscript + where + T: SigningTranscript, { - t.commit_bytes(b"chain-code",&cc.0); - t.commit_point(b"public-key",self.as_compressed()); + t.commit_bytes(b"chain-code", &cc.0); + t.commit_point(b"public-key", self.as_compressed()); let scalar = t.challenge_scalar(b"HDKD-scalar"); @@ -115,17 +119,21 @@ impl SecretKey { /// permissible mutations of `SecretKey`. This means only that /// we hash the `SecretKey`'s scalar, but not its nonce because /// the secret key remains valid if the nonce is changed. - pub fn hard_derive_mini_secret_key>(&self, cc: Option, i: B) - -> (MiniSecretKey,ChainCode) - { + pub fn hard_derive_mini_secret_key>( + &self, + cc: Option, + i: B, + ) -> (MiniSecretKey, ChainCode) { let mut t = merlin::Transcript::new(b"SchnorrRistrettoHDKD"); t.append_message(b"sign-bytes", i.as_ref()); - if let Some(c) = cc { t.append_message(b"chain-code", &c.0); } - t.append_message(b"secret-key",& self.key.to_bytes() as &[u8]); + if let Some(c) = cc { + t.append_message(b"chain-code", &c.0); + } + t.append_message(b"secret-key", &self.key.to_bytes() as &[u8]); - let mut msk = [0u8; MINI_SECRET_KEY_LENGTH]; - t.challenge_bytes(b"HDKD-hard",&mut msk); + let mut msk = [0u8; MINI_SECRET_KEY_LENGTH]; + t.challenge_bytes(b"HDKD-hard", &mut msk); let mut chaincode = [0u8; 32]; t.challenge_bytes(b"HDKD-chaincode", &mut chaincode); @@ -148,10 +156,13 @@ impl MiniSecretKey { /// permissible mutations of `SecretKey`. This means only that /// we hash the `SecretKey`'s scalar, but not its nonce because /// the secret key remains valid if the nonce is changed. - pub fn hard_derive_mini_secret_key>(&self, cc: Option, i: B, mode: ExpansionMode) - -> (MiniSecretKey,ChainCode) - { - self.expand(mode).hard_derive_mini_secret_key(cc,i) + pub fn hard_derive_mini_secret_key>( + &self, + cc: Option, + i: B, + mode: ExpansionMode, + ) -> (MiniSecretKey, ChainCode) { + self.expand(mode).hard_derive_mini_secret_key(cc, i) } } @@ -169,9 +180,12 @@ impl Keypair { /// permissible mutations of `SecretKey`. This means only that /// we hash the `SecretKey`'s scalar, but not its nonce because /// the secret key remains valid if the nonce is changed. - pub fn hard_derive_mini_secret_key>(&self, cc: Option, i: B) - -> (MiniSecretKey,ChainCode) { - self.secret.hard_derive_mini_secret_key(cc,i) + pub fn hard_derive_mini_secret_key>( + &self, + cc: Option, + i: B, + ) -> (MiniSecretKey, ChainCode) { + self.secret.hard_derive_mini_secret_key(cc, i) } /// Derive a secret key and new chain code from a key pair and chain code. @@ -179,7 +193,8 @@ impl Keypair { /// We expect the trait methods of `Keypair as Derivation` to be /// more useful since signing anything requires the public key too. pub fn derive_secret_key(&self, mut t: T, cc: ChainCode) -> (SecretKey, ChainCode) - where T: SigningTranscript + where + T: SigningTranscript, { let (scalar, chaincode) = self.public.derive_scalar_and_chaincode(&mut t, cc); @@ -190,18 +205,20 @@ impl Keypair { // We employ the witness mechanism here so that CSPRNG associated to our // `SigningTranscript` makes our new nonce seed independent from everything. let mut nonce = [0u8; 32]; - t.witness_bytes(b"HDKD-nonce", &mut nonce, &[&self.secret.nonce, &self.secret.to_bytes() as &[u8]]); + t.witness_bytes( + b"HDKD-nonce", + &mut nonce, + &[&self.secret.nonce, &self.secret.to_bytes() as &[u8]], + ); - (SecretKey { - key: self.secret.key + scalar, - nonce, - }, chaincode) + (SecretKey { key: self.secret.key + scalar, nonce }, chaincode) } } impl Derivation for Keypair { fn derived_key(&self, t: T, cc: ChainCode) -> (Keypair, ChainCode) - where T: SigningTranscript + where + T: SigningTranscript, { let (secret, chaincode) = self.derive_secret_key(t, cc); let public = secret.to_public(); @@ -211,7 +228,8 @@ impl Derivation for Keypair { impl Derivation for SecretKey { fn derived_key(&self, t: T, cc: ChainCode) -> (SecretKey, ChainCode) - where T: SigningTranscript + where + T: SigningTranscript, { self.clone().to_keypair().derive_secret_key(t, cc) } @@ -219,7 +237,8 @@ impl Derivation for SecretKey { impl Derivation for PublicKey { fn derived_key(&self, mut t: T, cc: ChainCode) -> (PublicKey, ChainCode) - where T: SigningTranscript + where + T: SigningTranscript, { let (scalar, chaincode) = self.derive_scalar_and_chaincode(&mut t, cc); let point = self.as_point() + (&scalar * constants::RISTRETTO_BASEPOINT_TABLE); @@ -246,16 +265,16 @@ impl ExtendedKey { /// Derive key with subkey identified by a byte array /// presented as a hash, and a chain code. pub fn derived_key(&self, t: T) -> ExtendedKey - where T: SigningTranscript + where + T: SigningTranscript, { let (key, chaincode) = self.key.derived_key(t, self.chaincode); ExtendedKey { key, chaincode } } - /// Derive key with subkey identified by a byte array and + /// Derive key with subkey identified by a byte array and /// a chain code in the extended key. - pub fn derived_key_simple>(&self, i: B) -> ExtendedKey - { + pub fn derived_key_simple>(&self, i: B) -> ExtendedKey { let (key, chaincode) = self.key.derived_key_simple(self.chaincode, i); ExtendedKey { key, chaincode } } @@ -275,10 +294,12 @@ impl ExtendedKey { /// permissible mutations of `SecretKey`. This means only that /// we hash the `SecretKey`'s scalar, but not its nonce because /// the secret key remains valid if the nonce is changed. - pub fn hard_derive_mini_secret_key>(&self, i: B, mode: ExpansionMode) - -> ExtendedKey - { - let (key,chaincode) = self.key.hard_derive_mini_secret_key(Some(self.chaincode), i); + pub fn hard_derive_mini_secret_key>( + &self, + i: B, + mode: ExpansionMode, + ) -> ExtendedKey { + let (key, chaincode) = self.key.hard_derive_mini_secret_key(Some(self.chaincode), i); let key = key.expand(mode); ExtendedKey { key, chaincode } } @@ -295,17 +316,14 @@ mod tests { #[test] fn derive_key_public_vs_private_paths() { let chaincode = ChainCode([0u8; CHAIN_CODE_LENGTH]); - let msg : &'static [u8] = b"Just some test message!"; + let msg: &'static [u8] = b"Just some test message!"; let mut h = Shake128::default().chain(msg); let mut csprng = rand_core::OsRng; let key = Keypair::generate_with(&mut csprng); - let mut extended_public_key = ExtendedKey { - key: key.public.clone(), - chaincode, - }; - let mut extended_keypair = ExtendedKey { key, chaincode, }; + let mut extended_public_key = ExtendedKey { key: key.public.clone(), chaincode }; + let mut extended_keypair = ExtendedKey { key, chaincode }; let ctx = signing_context(b"testing testing 1 2 3"); @@ -335,11 +353,11 @@ mod tests { "Verification of a valid signature failed!" ); assert!( - ! extended_public_key.key.verify(ctx.xof(h.clone()), &bad_sig).is_ok(), + !extended_public_key.key.verify(ctx.xof(h.clone()), &bad_sig).is_ok(), "Verification of a signature on a different message passed!" ); assert!( - ! extended_public_key.key.verify(ctx.xof(h_bad), &good_sig).is_ok(), + !extended_public_key.key.verify(ctx.xof(h_bad), &good_sig).is_ok(), "Verification of a signature on a different message passed!" ); } diff --git a/src/errors.rs b/src/errors.rs index e8c73c0..761e42d 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -17,7 +17,6 @@ use core::fmt; use core::fmt::Display; - /// `Result` specialized to this crate for convenience. pub type SignatureResult = Result; @@ -88,7 +87,7 @@ pub enum SignatureError { /// Describes the type returning the error description: &'static str, /// Length expected by the constructor in bytes - length: usize + length: usize, }, /// Signature not marked as schnorrkel, maybe try ed25519 instead. NotMarkedSchnorrkel, @@ -112,6 +111,7 @@ pub enum SignatureError { }, } +#[rustfmt::skip] impl Display for SignatureError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { use self::SignatureError::*; @@ -126,7 +126,7 @@ impl Display for SignatureError { write!(f, "The provided key is not valid"), BytesLengthError { name, length, .. } => write!(f, "{name} must be {length} bytes in length"), - NotMarkedSchnorrkel => + NotMarkedSchnorrkel => write!(f, "Signature bytes not marked as a schnorrkel signature"), MuSigAbsent { musig_stage, } => write!(f, "Absent {musig_stage} violated multi-signature protocol"), @@ -149,7 +149,8 @@ impl failure::Fail for SignatureError {} /// `impl From for E where E: serde::de::Error`. #[cfg(feature = "serde")] pub fn serde_error_from_signature_error(err: SignatureError) -> E -where E: serde_crate::de::Error +where + E: serde::de::Error, { E::custom(err) } diff --git a/src/keys.rs b/src/keys.rs index cfba66f..8f14aa7 100644 --- a/src/keys.rs +++ b/src/keys.rs @@ -13,19 +13,18 @@ use core::convert::AsRef; use core::fmt::{Debug}; -use rand_core::{RngCore,CryptoRng}; +use rand_core::{RngCore, CryptoRng}; use curve25519_dalek::constants; -use curve25519_dalek::ristretto::{CompressedRistretto,RistrettoPoint}; +use curve25519_dalek::ristretto::{CompressedRistretto, RistrettoPoint}; use curve25519_dalek::scalar::Scalar; -use subtle::{Choice,ConstantTimeEq}; +use subtle::{Choice, ConstantTimeEq}; use zeroize::Zeroize; use crate::scalars; use crate::points::RistrettoBoth; -use crate::errors::{SignatureError,SignatureResult}; - +use crate::errors::{SignatureError, SignatureResult}; /// The length of a Ristretto Schnorr `MiniSecretKey`, in bytes. pub const MINI_SECRET_KEY_LENGTH: usize = 32; @@ -45,7 +44,6 @@ pub const SECRET_KEY_LENGTH: usize = SECRET_KEY_KEY_LENGTH + SECRET_KEY_NONCE_LE /// The length of an Ristretto Schnorr `Keypair`, in bytes. pub const KEYPAIR_LENGTH: usize = SECRET_KEY_LENGTH + PUBLIC_KEY_LENGTH; - /// Methods for expanding a `MiniSecretKey` into a `SecretKey`. /// /// Our `SecretKey`s consist of a scalar and nonce seed, both 32 bytes, @@ -94,9 +92,9 @@ pub enum ExpansionMode { /// homomorphic properties unavailable from these seeds, so we renamed /// these and reserve `SecretKey` for what EdDSA calls an extended /// secret key. -#[derive(Clone,Zeroize)] +#[derive(Clone, Zeroize)] #[zeroize(drop)] -pub struct MiniSecretKey(pub (crate) [u8; MINI_SECRET_KEY_LENGTH]); +pub struct MiniSecretKey(pub(crate) [u8; MINI_SECRET_KEY_LENGTH]); impl Debug for MiniSecretKey { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { @@ -117,13 +115,13 @@ impl ConstantTimeEq for MiniSecretKey { } impl MiniSecretKey { - const DESCRIPTION : &'static str = "Analogous to ed25519 secret key as 32 bytes, see RFC8032."; + const DESCRIPTION: &'static str = "Analogous to ed25519 secret key as 32 bytes, see RFC8032."; /// Avoids importing `ExpansionMode` - pub const UNIFORM_MODE : ExpansionMode = ExpansionMode::Uniform; + pub const UNIFORM_MODE: ExpansionMode = ExpansionMode::Uniform; /// Avoids importing `ExpansionMode` - pub const ED25519_MODE : ExpansionMode = ExpansionMode::Ed25519; + pub const ED25519_MODE: ExpansionMode = ExpansionMode::Ed25519; /// Expand this `MiniSecretKey` into a `SecretKey` /// @@ -181,7 +179,10 @@ impl MiniSecretKey { /// # } /// ``` fn expand_ed25519(&self) -> SecretKey { - use sha2::{Sha512, digest::{Update,FixedOutput}}; + use sha2::{ + Sha512, + digest::{Update, FixedOutput}, + }; let mut h = Sha512::default(); h.update(self.as_bytes()); @@ -191,9 +192,9 @@ impl MiniSecretKey { // we do so to improve Ed25519 comparability. let mut key = [0u8; 32]; key.copy_from_slice(&r.as_slice()[0..32]); - key[0] &= 248; - key[31] &= 63; - key[31] |= 64; + key[0] &= 248; + key[31] &= 63; + key[31] |= 64; // We then divide by the cofactor to internally keep a clean // representation mod l. scalars::divide_scalar_bytes_by_cofactor(&mut key); @@ -204,7 +205,7 @@ impl MiniSecretKey { let mut nonce = [0u8; 32]; nonce.copy_from_slice(&r.as_slice()[32..64]); - SecretKey{ key, nonce } + SecretKey { key, nonce } } /// Derive the `SecretKey` corresponding to this `MiniSecretKey`. @@ -282,7 +283,7 @@ impl MiniSecretKey { return Err(SignatureError::BytesLengthError { name: "MiniSecretKey", description: MiniSecretKey::DESCRIPTION, - length: MINI_SECRET_KEY_LENGTH + length: MINI_SECRET_KEY_LENGTH, }); } let mut bits: [u8; 32] = [0u8; 32]; @@ -305,7 +306,8 @@ impl MiniSecretKey { /// /// A CSPRNG with a `fill_bytes()` method, e.g. `rand_chacha::ChaChaRng` pub fn generate_with(mut csprng: R) -> MiniSecretKey - where R: CryptoRng + RngCore, + where + R: CryptoRng + RngCore, { let mut sk: MiniSecretKey = MiniSecretKey([0u8; 32]); csprng.fill_bytes(&mut sk.0); @@ -342,7 +344,6 @@ impl MiniSecretKey { serde_boilerplate!(MiniSecretKey); - /// A secret key for use with Ristretto Schnorr signatures. /// /// Internally, these consist of a scalar mod l along with a seed for @@ -356,16 +357,16 @@ serde_boilerplate!(MiniSecretKey); /// We do not however attempt to keep the scalar's high bit set, especially /// not during hierarchical deterministic key derivations, so some Ed25519 /// libraries might compute the public key incorrectly from our secret key. -#[derive(Clone,Zeroize)] +#[derive(Clone, Zeroize)] #[zeroize(drop)] pub struct SecretKey { /// Actual public key represented as a scalar. - pub (crate) key: Scalar, + pub(crate) key: Scalar, /// Seed for deriving the nonces used in signing. /// /// We require this be random and secret or else key compromise attacks will ensue. /// Any modification here may disrupt some non-public key derivation techniques. - pub (crate) nonce: [u8; 32], + pub(crate) nonce: [u8; 32], } impl Debug for SecretKey { @@ -409,7 +410,8 @@ impl From<&MiniSecretKey> for SecretKey { */ impl SecretKey { - const DESCRIPTION : &'static str = "An ed25519-like expanded secret key as 64 bytes, as specified in RFC8032."; + const DESCRIPTION: &'static str = + "An ed25519-like expanded secret key as 64 bytes, as specified in RFC8032."; /// Convert this `SecretKey` into an array of 64 bytes with. /// @@ -461,7 +463,7 @@ impl SecretKey { #[inline] pub fn from_bytes(bytes: &[u8]) -> SignatureResult { if bytes.len() != SECRET_KEY_LENGTH { - return Err(SignatureError::BytesLengthError{ + return Err(SignatureError::BytesLengthError { name: "SecretKey", description: SecretKey::DESCRIPTION, length: SECRET_KEY_LENGTH, @@ -470,12 +472,13 @@ impl SecretKey { let mut key: [u8; 32] = [0u8; 32]; key.copy_from_slice(&bytes[00..32]); - let key = crate::scalar_from_canonical_bytes(key).ok_or(SignatureError::ScalarFormatError) ?; + let key = + crate::scalar_from_canonical_bytes(key).ok_or(SignatureError::ScalarFormatError)?; let mut nonce: [u8; 32] = [0u8; 32]; nonce.copy_from_slice(&bytes[32..64]); - Ok(SecretKey{ key, nonce }) + Ok(SecretKey { key, nonce }) } /// Convert this `SecretKey` into an array of 64 bytes, corresponding to @@ -496,7 +499,7 @@ impl SecretKey { bytes } - /* Unused tooling removed to reduce dependencies. + /* Unused tooling removed to reduce dependencies. /// Convert this `SecretKey` into an Ed25519 expanded secret key. #[cfg(feature = "ed25519_dalek")] pub fn to_ed25519_expanded_secret_key(&self) -> ed25519_dalek::ExpandedSecretKey { @@ -522,7 +525,7 @@ impl SecretKey { #[inline] pub fn from_ed25519_bytes(bytes: &[u8]) -> SignatureResult { if bytes.len() != SECRET_KEY_LENGTH { - return Err(SignatureError::BytesLengthError{ + return Err(SignatureError::BytesLengthError { name: "SecretKey", description: SecretKey::DESCRIPTION, length: SECRET_KEY_LENGTH, @@ -545,14 +548,15 @@ impl SecretKey { let mut nonce: [u8; 32] = [0u8; 32]; nonce.copy_from_slice(&bytes[32..64]); - Ok(SecretKey{ key, nonce }) + Ok(SecretKey { key, nonce }) } /// Generate an "unbiased" `SecretKey` directly from a user /// suplied `csprng` uniformly, bypassing the `MiniSecretKey` /// layer. pub fn generate_with(mut csprng: R) -> SecretKey - where R: CryptoRng + RngCore, + where + R: CryptoRng + RngCore, { let mut key: [u8; 64] = [0u8; 64]; csprng.fill_bytes(&mut key); @@ -583,7 +587,6 @@ impl SecretKey { serde_boilerplate!(SecretKey); - /// A Ristretto Schnorr public key. /// /// Internally, these are represented as a `RistrettoPoint`, meaning @@ -593,7 +596,7 @@ serde_boilerplate!(SecretKey); /// during deserialization, which improves error handling, but costs /// a compression during signing and verification. #[derive(Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct PublicKey(pub (crate) RistrettoBoth); +pub struct PublicKey(pub(crate) RistrettoBoth); impl Debug for PublicKey { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { @@ -622,8 +625,9 @@ impl AsRef<[u8]> for PublicKey { } } +#[rustfmt::skip] impl PublicKey { - const DESCRIPTION : &'static str = "A Ristretto Schnorr public key represented as a 32-byte Ristretto compressed point"; + const DESCRIPTION: &'static str = "A Ristretto Schnorr public key represented as a 32-byte Ristretto compressed point"; /// Access the compressed Ristretto form pub fn as_compressed(&self) -> &CompressedRistretto { self.0.as_compressed() } @@ -703,9 +707,8 @@ impl From for PublicKey { serde_boilerplate!(PublicKey); - /// A Ristretto Schnorr keypair. -#[derive(Clone,Debug)] +#[derive(Clone, Debug)] // #[derive(Clone,Zeroize)] // #[zeroize(drop)] pub struct Keypair { @@ -729,12 +732,12 @@ impl Drop for Keypair { impl From for Keypair { fn from(secret: SecretKey) -> Keypair { let public = secret.to_public(); - Keypair{ secret, public } + Keypair { secret, public } } } impl Keypair { - const DESCRIPTION : &'static str = "A 96 bytes Ristretto Schnorr keypair"; + const DESCRIPTION: &'static str = "A 96 bytes Ristretto Schnorr keypair"; /* const DESCRIPTION_LONG : &'static str = "An ristretto schnorr keypair, 96 bytes in total, where the \ @@ -768,8 +771,8 @@ impl Keypair { pub fn to_bytes(&self) -> [u8; KEYPAIR_LENGTH] { let mut bytes: [u8; KEYPAIR_LENGTH] = [0u8; KEYPAIR_LENGTH]; - bytes[..SECRET_KEY_LENGTH].copy_from_slice(& self.secret.to_bytes()); - bytes[SECRET_KEY_LENGTH..].copy_from_slice(& self.public.to_bytes()); + bytes[..SECRET_KEY_LENGTH].copy_from_slice(&self.secret.to_bytes()); + bytes[SECRET_KEY_LENGTH..].copy_from_slice(&self.public.to_bytes()); bytes } @@ -802,13 +805,13 @@ impl Keypair { return Err(SignatureError::BytesLengthError { name: "Keypair", description: Keypair::DESCRIPTION, - length: KEYPAIR_LENGTH + length: KEYPAIR_LENGTH, }); } - let secret = SecretKey::from_bytes(&bytes[..SECRET_KEY_LENGTH]) ?; - let public = PublicKey::from_bytes(&bytes[SECRET_KEY_LENGTH..]) ?; + let secret = SecretKey::from_bytes(&bytes[..SECRET_KEY_LENGTH])?; + let public = PublicKey::from_bytes(&bytes[SECRET_KEY_LENGTH..])?; - Ok(Keypair{ secret, public }) + Ok(Keypair { secret, public }) } /// Serialize `Keypair` to bytes with Ed25519 secret key format. @@ -823,8 +826,8 @@ impl Keypair { pub fn to_half_ed25519_bytes(&self) -> [u8; KEYPAIR_LENGTH] { let mut bytes: [u8; KEYPAIR_LENGTH] = [0u8; KEYPAIR_LENGTH]; - bytes[..SECRET_KEY_LENGTH].copy_from_slice(& self.secret.to_ed25519_bytes()); - bytes[SECRET_KEY_LENGTH..].copy_from_slice(& self.public.to_bytes()); + bytes[..SECRET_KEY_LENGTH].copy_from_slice(&self.secret.to_ed25519_bytes()); + bytes[SECRET_KEY_LENGTH..].copy_from_slice(&self.public.to_bytes()); bytes } @@ -855,13 +858,13 @@ impl Keypair { return Err(SignatureError::BytesLengthError { name: "Keypair", description: Keypair::DESCRIPTION, - length: KEYPAIR_LENGTH + length: KEYPAIR_LENGTH, }); } - let secret = SecretKey::from_ed25519_bytes(&bytes[..SECRET_KEY_LENGTH]) ?; - let public = PublicKey::from_bytes(&bytes[SECRET_KEY_LENGTH..]) ?; + let secret = SecretKey::from_ed25519_bytes(&bytes[..SECRET_KEY_LENGTH])?; + let public = PublicKey::from_bytes(&bytes[SECRET_KEY_LENGTH..])?; - Ok(Keypair{ secret, public }) + Ok(Keypair { secret, public }) } /// Generate a Ristretto Schnorr `Keypair` directly, @@ -891,12 +894,13 @@ impl Keypair { /// so our secret keys do not satisfy the high bit "clamping" /// imposed on Ed25519 keys. pub fn generate_with(csprng: R) -> Keypair - where R: CryptoRng + RngCore, + where + R: CryptoRng + RngCore, { let secret: SecretKey = SecretKey::generate_with(csprng); let public: PublicKey = secret.to_public(); - Keypair{ public, secret } + Keypair { public, secret } } /// Generate a Ristretto Schnorr `Keypair` directly, from a user @@ -909,7 +913,6 @@ impl Keypair { serde_boilerplate!(Keypair); - #[cfg(test)] mod test { // use std::vec::Vec; @@ -920,6 +923,7 @@ mod test { use curve25519_dalek::edwards::{CompressedEdwardsY}; // EdwardsPoint #[test] fn public_key_from_bytes() { + #[rustfmt::skip] static ED25519_PUBLIC_KEY : CompressedEdwardsY = CompressedEdwardsY([ 215, 090, 152, 001, 130, 177, 010, 183, 213, 075, 254, 211, 201, 100, 007, 058, @@ -954,14 +958,8 @@ mod test { #[test] fn derives_from_core() { let pk_d = PublicKey::default(); - debug_assert_eq!( - pk_d.as_point().compress(), - CompressedRistretto::default() - ); - debug_assert_eq!( - pk_d.as_compressed().decompress().unwrap(), - RistrettoPoint::default() - ); + debug_assert_eq!(pk_d.as_point().compress(), CompressedRistretto::default()); + debug_assert_eq!(pk_d.as_compressed().decompress().unwrap(), RistrettoPoint::default()); } #[cfg(feature = "getrandom")] @@ -977,9 +975,7 @@ mod test { use core::mem; use core::slice; - unsafe { - slice::from_raw_parts(x as *const T as *const u8, mem::size_of_val(x)) - } + unsafe { slice::from_raw_parts(x as *const T as *const u8, mem::size_of_val(x)) } } assert!(!as_bytes(&keypair).iter().all(|x| *x == 0u8)); @@ -992,11 +988,13 @@ mod test { let mini_secret: MiniSecretKey = MiniSecretKey::generate_with(&mut csprng); let secret: SecretKey = mini_secret.expand(ExpansionMode::Ed25519); - let public_from_mini_secret: PublicKey = mini_secret.expand_to_public(ExpansionMode::Ed25519); + let public_from_mini_secret: PublicKey = + mini_secret.expand_to_public(ExpansionMode::Ed25519); let public_from_secret: PublicKey = secret.to_public(); assert!(public_from_mini_secret == public_from_secret); let secret: SecretKey = mini_secret.expand(ExpansionMode::Uniform); - let public_from_mini_secret: PublicKey = mini_secret.expand_to_public(ExpansionMode::Uniform); + let public_from_mini_secret: PublicKey = + mini_secret.expand_to_public(ExpansionMode::Uniform); let public_from_secret: PublicKey = secret.to_public(); assert!(public_from_mini_secret == public_from_secret); } @@ -1004,11 +1002,7 @@ mod test { #[cfg(feature = "getrandom")] #[test] fn secret_key_can_be_converted_to_ed25519_bytes_and_back() { - let count = if cfg!(debug_assertions) { - 200000 - } else { - 2000000 - }; + let count = if cfg!(debug_assertions) { 200000 } else { 2000000 }; for _ in 0..count { let key = SecretKey::generate(); diff --git a/src/lib.rs b/src/lib.rs index 323bdaa..5208751 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -231,7 +231,7 @@ extern crate std; #[cfg(feature = "alloc")] extern crate alloc; -use getrandom_or_panic::{RngCore,CryptoRng,getrandom_or_panic}; +use getrandom_or_panic::{RngCore, CryptoRng, getrandom_or_panic}; use curve25519_dalek::scalar::Scalar; #[macro_use] @@ -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; @@ -256,17 +259,20 @@ mod batch; // Not safe because need randomness -#[cfg_attr(not(test), deprecated(since = "0.11.0", note = "This module will be replaced in the future"))] +#[cfg_attr( + not(test), + deprecated(since = "0.11.0", note = "This module will be replaced in the future") +)] #[cfg(feature = "std")] pub mod musig; pub use crate::keys::*; // {MiniSecretKey,SecretKey,PublicKey,Keypair,ExpansionMode}; + *_LENGTH pub use crate::context::{signing_context}; // SigningContext,SigningTranscript -pub use crate::sign::{Signature,SIGNATURE_LENGTH}; -pub use crate::errors::{SignatureError,SignatureResult}; +pub use crate::sign::{Signature, SIGNATURE_LENGTH}; +pub use crate::errors::{SignatureError, SignatureResult}; #[cfg(feature = "alloc")] -pub use crate::batch::{verify_batch,verify_batch_rng,verify_batch_deterministic,PreparedBatch}; +pub use crate::batch::{verify_batch, verify_batch_rng, verify_batch_deterministic, PreparedBatch}; pub(crate) fn scalar_from_canonical_bytes(bytes: [u8; 32]) -> Option { let key = Scalar::from_canonical_bytes(bytes); 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..d46b011 --- /dev/null +++ b/src/olaf/errors.rs @@ -0,0 +1,156 @@ +//! Errors of the Olaf protocol. + +use super::identifier::Identifier; +use crate::SignatureError; +use thiserror::Error; + +/// A result for the SimplPedPoP protocol. +pub type DKGResult = Result; + +/// Errors that can occur during the SimplPedPoP protocol. +#[derive(Error, Debug, Clone, Eq, PartialEq)] +pub enum DKGError { + /// Occurs when a proof of possession is invalid. Typically involves signature verification failure. + #[error("Invalid proof of possession: {0}")] + InvalidProofOfPossession(SignatureError), + + /// Error when a certificate does not meet the required validation criteria, usually involving cryptographic signature issues. + #[error("Invalid certificate: {0}")] + InvalidCertificate(SignatureError), + + /// Raised when the specified threshold exceeds the number of participants in the protocol. + #[error("Threshold cannot be greater than the number of participants")] + ExcessiveThreshold, + + /// This error is raised if the threshold for a cryptographic operation is set below the minimum required limit, which is 2. + #[error("Threshold must be at least 2")] + InsufficientThreshold, + + /// Indicates an error when the number of participants specified for the DKG process is not valid. + #[error("Number of participants is invalid")] + InvalidNumberOfParticipants, + + /// Error when the verification of a secret share fails for a specific identifier. + #[error("Secret share verification failed for identifier {0}")] + InvalidSecretShare(Identifier), + + /// Indicates a problem with the secrecy component of a protocol, such as incorrect or tampered secret data. + #[error("Invalid secret")] + InvalidSecret, + + /// Error when an unknown identifier is detected in round 1 of the public messages. + #[error("Unknown identifier in round 1 public messages: {0}")] + UnknownIdentifierRound1PublicMessages(Identifier), + + /// Similar to the round 1 error, but for unknown identifiers found in the public messages of round 2. + #[error("Unknown identifier in round 2 public messages: {0}")] + UnknownIdentifierRound2PublicMessages(Identifier), + + /// Error for unknown identifiers in private messages of round 2, typically indicating a data mismatch or synchronization issue. + #[error("Unknown identifier in round 2 private messages")] + UnknownIdentifierRound2PrivateMessages, + + /// Raised when an identifier is set to a zero scalar, which is not permissible in cryptographic operations. + #[error("Identifier cannot be a zero scalar")] + InvalidIdentifier, + + /// Indicates a discrepancy in the number of identifiers expected versus received. + #[error("Incorrect number of identifiers: expected {expected}, actual {actual}")] + IncorrectNumberOfIdentifiers { + /// The number of identifiers that were expected to be processed or received. + expected: usize, + /// The actual number of identifiers that were processed or received. + actual: usize, + }, + + /// Indicates a discrepancy in the number of private messages expected versus received. + #[error("Incorrect number of private messages: expected {expected}, actual {actual}")] + IncorrectNumberOfPrivateMessages { + /// The expected number of private messages according to protocol requirements. + expected: usize, + /// The actual number of private messages received or processed. + actual: usize, + }, + + /// Indicates a discrepancy in the number of round 1 public messages expected versus received. + #[error("Incorrect number of round 1 public messages: expected {expected}, actual {actual}")] + IncorrectNumberOfRound1PublicMessages { + /// The expected number of public messages in round 1 based on the protocol setup. + expected: usize, + /// The actual count of public messages received in round 1. + actual: usize, + }, + + /// Indicates a discrepancy in the number of round 2 public messages expected versus received. + #[error("Incorrect number of round 2 public messages: expected {expected}, actual {actual}")] + IncorrectNumberOfRound2PublicMessages { + /// The expected number of public messages in round 2 as defined by the protocol. + expected: usize, + /// The actual count of public messages received in round 2. + actual: usize, + }, + + /// Indicates a discrepancy in the number of round 2 private messages expected versus received. + #[error("Incorrect number of round 2 private messages: expected {expected}, actual {actual}")] + IncorrectNumberOfRound2PrivateMessages { + /// The number of private messages that were expected in round 2 of the protocol. + expected: usize, + /// The actual number of private messages that were received in round 2. + actual: usize, + }, + + /// Error occurring during the decryption of an encrypted secret share. + #[error("Decryption error when decrypting an encrypted secret share: {0}")] + DecryptionError(chacha20poly1305::Error), + + /// Error occurring during the encryption of a secret share. + #[error("Encryption error when encrypting the secret share: {0}")] + EncryptionError(chacha20poly1305::Error), + + /// Indicates a discrepancy in the number of coefficient commitments in the secret polynomial expected versus actual. + #[error("Incorrect number of coefficient commitments: expected {expected}, actual {actual}")] + InvalidSecretPolynomialCommitment { + /// /// The expected number of coefficients in the secret polynomial. + expected: usize, + /// The actual number of coefficients in the secret polynomial. + actual: usize, + }, +} + +/// A result for the FROST protocol. +pub type FROSTResult = Result; + +/// Errors that can occur during the FROST protocol. +#[derive(Error, Debug, Clone, Eq, PartialEq)] +pub enum FROSTError { + /// Signature share verification failed. + #[error("Signature share verification failed for signer {culprit}")] + InvalidSignatureShare { + /// The identifier of the signer whose share validation failed. + culprit: Identifier, + }, + /// Incorrect number of signing commitments. + #[error("Incorrect number of signing commitments")] + IncorrectNumberOfSigningCommitments, + /// The participant's signing commitment is missing from the Signing Package + #[error("The participant's signing commitment is missing from the Signing Package")] + MissingSigningCommitment, + /// The participant's signing commitment is incorrect + #[error("The participant's signing commitment is incorrect")] + IncorrectSigningCommitment, + /// This identifier does not belong to a participant in the signing process. + #[error("Unknown identifier")] + UnknownIdentifier, + /// Commitment equals the identity + #[error("Commitment equals the identity")] + IdentitySigningCommitment, + /// Incorrect number of identifiers. + #[error("Incorrect number of identifiers")] + IncorrectNumberOfIdentifiers, + /// Signature verification failed. + #[error("Signature verification failed: {0}")] + InvalidSignature(SignatureError), + /// This identifier is duplicated. + #[error("This identifier is duplicated")] + DuplicatedIdentifier, +} diff --git a/src/olaf/frost.rs b/src/olaf/frost.rs new file mode 100644 index 0000000..f373a01 --- /dev/null +++ b/src/olaf/frost.rs @@ -0,0 +1,788 @@ +//! Implementation of the FROST protocol (). + +#![allow(non_snake_case)] + +/// FROST round 1 functionality and types. +pub mod round1 { + #[cfg(feature = "cheater-detection")] + use crate::olaf::frost::BindingFactor; + use crate::{ + context::SigningTranscript, + olaf::{identifier::Identifier, keys::SigningShare}, + }; + use alloc::vec::Vec; + use curve25519_dalek::{constants::RISTRETTO_BASEPOINT_POINT, RistrettoPoint, Scalar}; + use derive_getters::Getters; + use merlin::Transcript; + use rand_core::{CryptoRng, RngCore}; + use std::{collections::BTreeMap, fmt::Debug}; + use zeroize::ZeroizeOnDrop; + + /// A scalar that is a signing nonce. + #[derive(Debug, Clone, PartialEq, Eq, ZeroizeOnDrop)] + #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] + pub struct Nonce(pub(crate) Scalar); + + impl Nonce { + /// Generates a new uniformly random signing nonce by sourcing fresh randomness and combining + /// with the secret signing share, to hedge against a bad RNG. + /// + /// Each participant generates signing nonces before performing a signing + /// operation. + /// + /// An implementation of `nonce_generate(secret)` from the [spec]. + /// + /// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#name-nonce-generation + pub fn new(secret: &SigningShare, rng: &mut R) -> Self + where + R: CryptoRng + RngCore, + { + let mut random_bytes = [0; 32]; + rng.fill_bytes(&mut random_bytes[..]); + + Self::nonce_generate_from_random_bytes(secret, &random_bytes[..]) + } + + /// Generates a nonce from the given random bytes. + /// This function allows testing and MUST NOT be made public. + pub(crate) fn nonce_generate_from_random_bytes( + secret: &SigningShare, + random_bytes: &[u8], + ) -> Self { + let mut transcript = Transcript::new(b"nonce_generate_from_random_bytes"); + + transcript.append_message(b"random bytes", random_bytes); + transcript.append_message(b"secret", secret.0.as_bytes()); + + Self(transcript.challenge_scalar(b"nonce")) + } + } + + /// A group element that is a commitment to a signing nonce share. + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] + pub struct NonceCommitment(pub(super) RistrettoPoint); + + impl From for NonceCommitment { + fn from(nonce: Nonce) -> Self { + From::from(&nonce) + } + } + + impl From<&Nonce> for NonceCommitment { + fn from(nonce: &Nonce) -> Self { + Self(RISTRETTO_BASEPOINT_POINT * nonce.0) + } + } + + /// Comprised of hiding and binding nonces. + /// + /// Note that [`SigningNonces`] must be used *only once* for a signing + /// operation; re-using nonces will result in leakage of a signer's long-lived + /// signing key. + #[derive(Debug, Clone, PartialEq, Eq, Getters, ZeroizeOnDrop)] + #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] + pub struct SigningNonces { + /// The hiding [`Nonce`]. + pub(crate) hiding: Nonce, + /// The binding [`Nonce`]. + pub(crate) binding: Nonce, + /// The commitments to the nonces. This is precomputed to improve + /// sign() performance, since it needs to check if the commitments + /// to the participant's nonces are included in the commitments sent + /// by the Coordinator, and this prevents having to recompute them. + #[zeroize(skip)] + pub(crate) commitments: SigningCommitments, + } + + impl SigningNonces { + /// Generates a new signing nonce. + /// + /// Each participant generates signing nonces before performing a signing + /// operation. + pub fn new(secret: &SigningShare, rng: &mut R) -> Self + where + R: CryptoRng + RngCore, + { + let hiding = Nonce::new(secret, rng); + let binding = Nonce::new(secret, rng); + + Self::from_nonces(hiding, binding) + } + + /// Generates a new [`SigningNonces`] from a pair of [`Nonce`]. + /// + /// # Security + /// + /// SigningNonces MUST NOT be repeated in different FROST signings. + /// Thus, if you're using this method (because e.g. you're writing it + /// to disk between rounds), be careful so that does not happen. + pub fn from_nonces(hiding: Nonce, binding: Nonce) -> Self { + let hiding_commitment = (&hiding).into(); + let binding_commitment = (&binding).into(); + let commitments = SigningCommitments::new(hiding_commitment, binding_commitment); + + Self { hiding, binding, commitments } + } + } + + /// Published by each participant in the first round of the signing protocol. + /// + /// This step can be batched if desired by the implementation. Each + /// SigningCommitment can be used for exactly *one* signature. + #[derive(Copy, Clone, Debug, Eq, PartialEq, Getters)] + #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] + pub struct SigningCommitments { + /// Commitment to the hiding [`Nonce`]. + pub(crate) hiding: NonceCommitment, + /// Commitment to the binding [`Nonce`]. + pub(crate) binding: NonceCommitment, + } + + impl SigningCommitments { + /// Create new SigningCommitments + pub fn new(hiding: NonceCommitment, binding: NonceCommitment) -> Self { + Self { hiding, binding } + } + + /// Computes the [signature commitment share] from these round one signing commitments. + /// + /// [signature commitment share]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#name-signature-share-verificatio + #[cfg(feature = "cheater-detection")] + pub(super) fn to_group_commitment_share( + self, + binding_factor: &BindingFactor, + ) -> GroupCommitmentShare { + GroupCommitmentShare(self.hiding.0 + (self.binding.0 * binding_factor.0)) + } + } + + impl From<&SigningNonces> for SigningCommitments { + fn from(nonces: &SigningNonces) -> Self { + nonces.commitments + } + } + + /// One signer's share of the group commitment, derived from their individual signing commitments + /// and the binding factor _rho_. + #[derive(Clone, Copy, PartialEq)] + pub struct GroupCommitmentShare(pub(super) RistrettoPoint); + + /// Encode the list of group signing commitments. + /// + /// Implements [`encode_group_commitment_list()`] from the spec. + /// + /// `signing_commitments` must contain the sorted map of participants + /// identifiers to the signing commitments they issued. + /// + /// Returns a byte string containing the serialized representation of the + /// commitment list. + /// + /// [`encode_group_commitment_list()`]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#name-list-operations + pub(crate) fn encode_group_commitments( + signing_commitments: &BTreeMap, + ) -> Transcript { + //let mut bytes = vec![]; + let mut transcript = Transcript::new(b"encode_group_commitments"); + + for (item_identifier, item) in signing_commitments { + transcript.append_message(b"identifier", item_identifier.0.as_bytes()); + transcript.commit_point(b"hiding", &item.hiding().0.compress()); + transcript.commit_point(b"binding", &item.binding().0.compress()); + } + + transcript + } + + /// Done once by each participant, to generate _their_ nonces and commitments + /// that are then used during signing. + /// + /// This is only needed if pre-processing is needed (for 1-round FROST). For + /// regular 2-round FROST, use [`commit`]. + /// + /// When performing signing using two rounds, num_nonces would equal 1, to + /// perform the first round. Batching entails generating more than one + /// nonce/commitment pair at a time. Nonces should be stored in secret storage + /// for later use, whereas the commitments are published. + pub fn preprocess( + num_nonces: u8, + secret: &SigningShare, + rng: &mut R, + ) -> (Vec, Vec) + where + R: CryptoRng + RngCore, + { + let mut signing_nonces: Vec = Vec::with_capacity(num_nonces as usize); + let mut signing_commitments: Vec = + Vec::with_capacity(num_nonces as usize); + + for _ in 0..num_nonces { + let nonces = SigningNonces::new(secret, rng); + signing_commitments.push(SigningCommitments::from(&nonces)); + signing_nonces.push(nonces); + } + + (signing_nonces, signing_commitments) + } + + /// Performed once by each participant selected for the signing operation. + /// + /// Implements [`commit`] from the spec. + /// + /// Generates the signing nonces and commitments to be used in the signing + /// operation. + /// + /// [`commit`]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#name-round-one-commitment. + pub fn commit(secret: &SigningShare, rng: &mut R) -> (SigningNonces, SigningCommitments) + where + R: CryptoRng + RngCore, + { + let (mut vec_signing_nonces, mut vec_signing_commitments) = preprocess(1, secret, rng); + ( + vec_signing_nonces.pop().expect("must have 1 element"), + vec_signing_commitments.pop().expect("must have 1 element"), + ) + } +} + +/// FROST round 2 functionality and types. +pub mod round2 { + use super::round1::{encode_group_commitments, SigningCommitments, SigningNonces}; + use crate::{ + context::{SigningContext, SigningTranscript}, + olaf::{ + errors::FROSTError, + identifier::Identifier, + keys::{KeyPackage, VerifyingKey}, + }, + }; + use alloc::{ + collections::{BTreeMap, BTreeSet}, + vec::Vec, + }; + use curve25519_dalek::{ + traits::{Identity, VartimeMultiscalarMul}, + RistrettoPoint, Scalar, + }; + use derive_getters::Getters; + use merlin::Transcript; + use std::fmt::Debug; + + pub(crate) type Challenge = Scalar; + + /// Generates the challenge as is required for Schnorr signatures. + pub(crate) fn challenge( + R: &RistrettoPoint, + verifying_key: &VerifyingKey, + context: &[u8], + msg: &[u8], + ) -> Challenge { + let mut transcript = SigningContext::new(context).bytes(msg); + + transcript.proto_name(b"Schnorr-sig"); + transcript.commit_point(b"sign:pk", verifying_key.as_compressed()); + transcript.commit_point(b"sign:R", &R.compress()); + transcript.challenge_scalar(b"sign:c") + } + + /// The binding factor, also known as _rho_ (ρ), ensures each signature share is strongly bound to a signing set, specific set + /// of commitments, and a specific message. + #[derive(Clone, PartialEq, Eq)] + pub(crate) struct BindingFactor(pub(crate) Scalar); + + /// A list of binding factors and their associated identifiers. + #[derive(Clone)] + pub(crate) struct BindingFactorList(BTreeMap); + + impl BindingFactorList { + /// Create a new [`BindingFactorList`] from a map of identifiers to binding factors. + pub fn new(binding_factors: BTreeMap) -> Self { + Self(binding_factors) + } + + /// Get the [`BindingFactor`] for the given identifier, or None if not found. + pub fn get(&self, key: &Identifier) -> Option<&BindingFactor> { + self.0.get(key) + } + + /// [`compute_binding_factors`] in the spec + /// + /// [`compute_binding_factors`]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-4.4. + pub(crate) fn compute_binding_factor_list( + signing_package: &SigningPackage, + verifying_key: &VerifyingKey, + ) -> BindingFactorList { + let mut transcripts = signing_package.binding_factor_transcripts(verifying_key); + + BindingFactorList::new( + transcripts + .iter_mut() + .map(|(identifier, transcript)| { + let binding_factor = transcript.challenge_scalar(b"binding factor"); + + (*identifier, BindingFactor(binding_factor)) + }) + .collect(), + ) + } + } + + /// The product of all signers' individual commitments, published as part of the + /// final signature. + #[derive(Clone, PartialEq, Eq)] + pub(crate) struct GroupCommitment(pub(crate) RistrettoPoint); + + /// Generates the group commitment which is published as part of the joint + /// Schnorr signature. + /// + /// Implements [`compute_group_commitment`] from the spec. + /// + /// [`compute_group_commitment`]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-4.5. + pub(crate) fn compute_group_commitment( + signing_package: &SigningPackage, + binding_factor_list: &BindingFactorList, + ) -> Result { + let identity = RistrettoPoint::identity(); + + let mut group_commitment = RistrettoPoint::identity(); + + // Number of signing participants we are iterating over. + let signers = signing_package.signing_commitments().len(); + + let mut binding_scalars = Vec::with_capacity(signers); + + let mut binding_elements = Vec::with_capacity(signers); + + for (commitment_identifier, commitment) in signing_package.signing_commitments() { + // The following check prevents a party from accidentally revealing their share. + // Note that the '&&' operator would be sufficient. + if identity == commitment.binding.0 || identity == commitment.hiding.0 { + return Err(FROSTError::IdentitySigningCommitment); + } + + let binding_factor = binding_factor_list + .get(commitment_identifier) + .ok_or(FROSTError::UnknownIdentifier)?; + + // Collect the binding commitments and their binding factors for one big + // multiscalar multiplication at the end. + binding_elements.push(commitment.binding.0); + binding_scalars.push(binding_factor.0); + + group_commitment += commitment.hiding.0; + } + + let accumulated_binding_commitment: RistrettoPoint = + RistrettoPoint::vartime_multiscalar_mul(binding_scalars, binding_elements); + + group_commitment += accumulated_binding_commitment; + + Ok(GroupCommitment(group_commitment)) + } + + /// A participant's signature share, which the coordinator will aggregate with all other signer's + /// shares into the joint signature. + #[derive(Debug, Clone, Copy, Eq, PartialEq, Getters)] + #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] + pub struct SignatureShare { + /// This participant's signature over the message. + pub(crate) share: Scalar, + } + + impl SignatureShare { + /// Tests if a signature share issued by a participant is valid before + /// aggregating it into a final joint signature to publish. + /// + /// This is the final step of [`verify_signature_share`] from the spec. + /// + /// [`verify_signature_share`]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#name-signature-share-verificatio + #[cfg(feature = "cheater-detection")] + pub(crate) fn verify( + &self, + identifier: Identifier, + group_commitment_share: &round1::GroupCommitmentShare, + verifying_share: &VerifyingShare, + lambda_i: Scalar, + challenge: &Challenge, + ) -> Result<(), FROSTError> { + if (GENERATOR * self.share) + != (group_commitment_share.0 + (verifying_share * challenge * lambda_i)) + { + return Err(FROSTError::InvalidSignatureShare { culprit: identifier }); + } + + Ok(()) + } + } + + /// Compute the signature share for a signing operation. + pub(crate) fn compute_signature_share( + signer_nonces: &SigningNonces, + binding_factor: BindingFactor, + lambda_i: Scalar, + key_package: &KeyPackage, + challenge: Challenge, + ) -> SignatureShare { + let z_share: Scalar = signer_nonces.hiding.0 + + (signer_nonces.binding.0 * binding_factor.0) + + (lambda_i * key_package.signing_share.0 * challenge); + + SignatureShare { share: z_share } + } + + /// Performed once by each participant selected for the signing operation. + /// + /// Implements [`sign`] from the spec. + /// + /// Receives the message to be signed and a set of signing commitments and a set + /// of randomizing commitments to be used in that signing operation, including + /// that for this participant. + /// + /// Assumes the participant has already determined which nonce corresponds with + /// the commitment that was assigned by the coordinator in the SigningPackage. + /// + /// [`sign`]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#name-round-two-signature-share-g + pub fn sign( + signing_package: &SigningPackage, + signer_nonces: &SigningNonces, + key_package: &KeyPackage, + ) -> Result { + if signing_package.signing_commitments().len() < key_package.min_signers as usize { + return Err(FROSTError::IncorrectNumberOfSigningCommitments); + } + + // Validate the signer's commitment is present in the signing package + let commitment = signing_package + .signing_commitments + .get(&key_package.identifier) + .ok_or(FROSTError::MissingSigningCommitment)?; + + // Validate if the signer's commitment exists + if &signer_nonces.commitments != commitment { + return Err(FROSTError::IncorrectSigningCommitment); + } + + // Encodes the signing commitment list produced in round one as part of generating [`BindingFactor`], the + // binding factor. + let binding_factor_list: BindingFactorList = BindingFactorList::compute_binding_factor_list( + signing_package, + &key_package.verifying_key, + ); + + let binding_factor: BindingFactor = binding_factor_list + .get(&key_package.identifier) + .ok_or(FROSTError::UnknownIdentifier)? + .clone(); + + // Compute the group commitment from signing commitments produced in round one. + let group_commitment = compute_group_commitment(signing_package, &binding_factor_list)?; + + // Compute Lagrange coefficient. + let lambda_i = derive_interpolating_value(key_package.identifier(), signing_package)?; + + // Compute the per-message challenge. + let challenge = challenge( + &group_commitment.0, + &key_package.verifying_key, + signing_package.context.as_slice(), + signing_package.message.as_slice(), + ); + + // Compute the Schnorr signature share. + let signature_share = compute_signature_share( + signer_nonces, + binding_factor, + lambda_i, + key_package, + challenge, + ); + + Ok(signature_share) + } + + /// Generated by the coordinator of the signing operation and distributed to + /// each signing party. + #[derive(Clone, Debug, PartialEq, Eq, Getters)] + pub struct SigningPackage { + /// The set of commitments participants published in the first round of the + /// protocol. + signing_commitments: BTreeMap, + /// Message which each participant will sign. + /// + /// Each signer should perform protocol-specific verification on the + /// message. + message: Vec, + context: Vec, + } + + impl SigningPackage { + /// Create a new `SigningPackage`. + /// + /// The `signing_commitments` are sorted by participant `identifier`. + pub fn new( + signing_commitments: BTreeMap, + message: &[u8], + context: &[u8], + ) -> SigningPackage { + SigningPackage { + signing_commitments, + message: message.to_vec(), + context: context.to_vec(), + } + } + + /// Get a signing commitment by its participant identifier, or None if not found. + pub fn signing_commitment(&self, identifier: &Identifier) -> Option { + self.signing_commitments.get(identifier).copied() + } + + /// Compute the transcripts to compute the per-signer binding factors. + pub fn binding_factor_transcripts( + &self, + verifying_key: &VerifyingKey, + ) -> Vec<(Identifier, Transcript)> { + let mut transcript = Transcript::new(b"binding_factor"); + + transcript.commit_point(b"verifying_key", verifying_key.as_compressed()); + + transcript.append_message(b"message", self.message()); + + transcript.append_message( + b"group_commitment", + encode_group_commitments(self.signing_commitments()) + .challenge_scalar(b"encode_group_commitments") + .as_bytes(), + ); + + self.signing_commitments() + .keys() + .map(|identifier| { + transcript.append_message(b"identifier", identifier.0.as_bytes()); + (*identifier, transcript.clone()) + }) + .collect() + } + } + + /// Generates the lagrange coefficient for the i'th participant (for `signer_id`). + /// + /// Implements [`derive_interpolating_value()`] from the spec. + /// + /// [`derive_interpolating_value()`]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#name-polynomials + pub(crate) fn derive_interpolating_value( + signer_id: &Identifier, + signing_package: &SigningPackage, + ) -> Result { + compute_lagrange_coefficient( + &signing_package.signing_commitments().keys().cloned().collect(), + None, + *signer_id, + ) + } + + /// Generates a lagrange coefficient. + /// + /// The Lagrange polynomial for a set of points (x_j, y_j) for 0 <= j <= k + /// is ∑_{i=0}^k y_i.ℓ_i(x), where ℓ_i(x) is the Lagrange basis polynomial: + /// + /// ℓ_i(x) = ∏_{0≤j≤k; j≠i} (x - x_j) / (x_i - x_j). + /// + /// This computes ℓ_j(x) for the set of points `xs` and for the j corresponding + /// to the given xj. + /// + /// If `x` is None, it uses 0 for it (since Identifiers can't be 0). + fn compute_lagrange_coefficient( + x_set: &BTreeSet, + x: Option, + x_i: Identifier, + ) -> Result { + if x_set.is_empty() { + return Err(FROSTError::IncorrectNumberOfIdentifiers); + } + let mut num = Scalar::ONE; + let mut den = Scalar::ONE; + + let mut x_i_found = false; + + for x_j in x_set.iter() { + if x_i == *x_j { + x_i_found = true; + continue; + } + + if let Some(x) = x { + num *= x.0 - x_j.0; + den *= x_i.0 - x_j.0; + } else { + // Both signs inverted just to avoid requiring Neg (-*xj) + num *= x_j.0; + den *= x_j.0 - x_i.0; + } + } + if !x_i_found { + return Err(FROSTError::UnknownIdentifier); + } + + let inverse = num * den.invert(); + + if inverse == Scalar::ZERO { + Err(FROSTError::DuplicatedIdentifier) + } else { + Ok(inverse) + } + } +} + +/// FROST round 3 functionality and types, which corresponds to the aggregation of the signature shares of the round 2. +pub mod round3 { + use crate::{ + olaf::{ + errors::FROSTError, + identifier::Identifier, + keys::{PublicKeyPackage, VerifyingKey}, + }, + Signature, + }; + use alloc::collections::BTreeMap; + use curve25519_dalek::Scalar; + + use super::round2::{compute_group_commitment, BindingFactorList, SignatureShare, SigningPackage}; + + /// Aggregates the signature shares to produce a final signature that + /// can be verified with the group public key. + /// + /// `signature_shares` maps the identifier of each participant to the + /// [`round2::SignatureShare`] they sent. These identifiers must come from whatever mapping + /// the coordinator has between communication channels and participants, i.e. + /// they must have assurance that the [`round2::SignatureShare`] came from + /// the participant with that identifier. + /// + /// This operation is performed by a coordinator that can communicate with all + /// the signing participants before publishing the final signature. The + /// coordinator can be one of the participants or a semi-trusted third party + /// (who is trusted to not perform denial of service attacks, but does not learn + /// any secret information). Note that because the coordinator is trusted to + /// report misbehaving parties in order to avoid publishing an invalid + /// signature, if the coordinator themselves is a signer and misbehaves, they + /// can avoid that step. However, at worst, this results in a denial of + /// service attack due to publishing an invalid signature. + pub fn aggregate( + signing_package: &SigningPackage, + signature_shares: &BTreeMap, + pubkeys: &PublicKeyPackage, + ) -> Result { + // Check if signing_package.signing_commitments and signature_shares have + // the same set of identifiers, and if they are all in pubkeys.verifying_shares. + if signing_package.signing_commitments().len() != signature_shares.len() { + return Err(FROSTError::UnknownIdentifier); + } + + if !signing_package.signing_commitments().keys().all(|id| { + #[cfg(feature = "cheater-detection")] + return signature_shares.contains_key(id) + && pubkeys.verifying_shares().contains_key(id); + #[cfg(not(feature = "cheater-detection"))] + return signature_shares.contains_key(id); + }) { + return Err(FROSTError::UnknownIdentifier); + } + + // Encodes the signing commitment list produced in round one as part of generating [`BindingFactor`], the + // binding factor. + let binding_factor_list: BindingFactorList = + BindingFactorList::compute_binding_factor_list(signing_package, &pubkeys.verifying_key); + + // Compute the group commitment from signing commitments produced in round one. + let group_commitment = compute_group_commitment(signing_package, &binding_factor_list)?; + + // The aggregation of the signature shares by summing them up, resulting in + // a plain Schnorr signature. + // + // Implements [`aggregate`] from the spec. + // + // [`aggregate`]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-5.3 + let mut s = Scalar::ZERO; + + for signature_share in signature_shares.values() { + s += signature_share.share; + } + + let signature = Signature { R: group_commitment.0.compress(), s }; + + // Verify the aggregate signature + let verification_result = verify_signature( + &signing_package.context(), + &signing_package.message(), + &signature, + pubkeys.verifying_key(), + ); + + // Only if the verification of the aggregate signature failed; verify each share to find the cheater. + // This approach is more efficient since we don't need to verify all shares + // if the aggregate signature is valid (which should be the common case). + #[cfg(feature = "cheater-detection")] + if let Err(err) = verification_result { + // Compute the per-message challenge. + let challenge = challenge( + &group_commitment.0, + pubkeys.verifying_key(), + signing_package.message().as_slice(), + ); + + // Verify the signature shares. + for (signature_share_identifier, signature_share) in signature_shares { + // Look up the public key for this signer, where `signer_pubkey` = _G.ScalarBaseMult(s[i])_, + // and where s[i] is a secret share of the constant term of _f_, the secret polynomial. + let signer_pubkey = pubkeys + .verifying_shares + .get(signature_share_identifier) + .ok_or(FROSTError::UnknownIdentifier)?; + + // Compute Lagrange coefficient. + let lambda_i = + derive_interpolating_value(signature_share_identifier, signing_package)?; + + let binding_factor = binding_factor_list + .get(signature_share_identifier) + .ok_or(FROSTError::UnknownIdentifier)?; + + // Compute the commitment share. + let R_share = signing_package + .signing_commitment(signature_share_identifier) + .ok_or(FROSTError::UnknownIdentifier)? + .to_group_commitment_share(binding_factor); + + // Compute relation values to verify this signature share. + signature_share.verify( + *signature_share_identifier, + &R_share, + signer_pubkey, + lambda_i, + &challenge, + )?; + } + + // We should never reach here; but we return the verification error to be safe. + return Err(err); + } + + #[cfg(not(feature = "cheater-detection"))] + verification_result?; + + Ok(signature) + } + + /// Verify a purported `signature` with a pre-hashed [`Challenge`] made by the group public key. + pub(crate) fn verify_signature( + context: &[u8], + msg: &[u8], + signature: &crate::Signature, + public_key: &VerifyingKey, + ) -> Result<(), FROSTError> { + public_key + .verify_simple(context, msg, signature) + .map_err(FROSTError::InvalidSignature)?; + + Ok(()) + } +} diff --git a/src/olaf/identifier.rs b/src/olaf/identifier.rs new file mode 100644 index 0000000..0cd7efa --- /dev/null +++ b/src/olaf/identifier.rs @@ -0,0 +1,175 @@ +//! The identifier of a participant in the Olaf protocol. + +use super::errors::DKGError; +use core::cmp::Ordering; +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); + +impl fmt::Display for Identifier { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let bytes = self.0.to_bytes(); + let hex_string = hex::encode(bytes); + write!(f, "{}", hex_string) + } +} + +#[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..6ce4f74 --- /dev/null +++ b/src/olaf/keys.rs @@ -0,0 +1,108 @@ +//! Olaf keys and key shares. + +use super::{ + errors::FROSTError, + identifier::Identifier, + polynomial::CoefficientCommitment, + simplpedpop::{SecretPolynomialCommitment, TotalSecretShare}, +}; +use crate::PublicKey; +use alloc::{ + collections::{BTreeMap, BTreeSet}, + vec::Vec, +}; +use derive_getters::Getters; +use zeroize::ZeroizeOnDrop; + +/// 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; + +pub(crate) type SigningShare = TotalSecretShare; +pub(crate) type VerifyingShare = GroupPublicKeyShare; +pub(crate) type VerifyingKey = GroupPublicKey; + +/// A FROST keypair, which is generated by the SimplPedPoP protocol. +#[derive(Clone, Debug, Getters, ZeroizeOnDrop)] +pub struct KeyPackage { + /// Denotes the participant identifier each secret share key package is owned by. + #[zeroize(skip)] + pub(crate) identifier: Identifier, + /// This participant's signing share. This is secret. + pub(crate) signing_share: SigningShare, + /// This participant's public key. + #[zeroize(skip)] + pub(crate) verifying_share: VerifyingShare, + /// The public key that represents the entire group. + #[zeroize(skip)] + pub(crate) verifying_key: VerifyingKey, + pub(crate) min_signers: u16, +} + +impl KeyPackage { + /// Create a new [`KeyPackage`] instance. + pub fn new( + identifier: Identifier, + signing_share: SigningShare, + verifying_share: VerifyingShare, + verifying_key: VerifyingKey, + min_signers: u16, + ) -> Self { + Self { identifier, signing_share, verifying_share, verifying_key, min_signers } + } +} + +/// Public data that contains all the signers' verifying shares as well as the +/// group verifying key. +/// +/// Used for verification purposes before publishing a signature. +#[derive(Clone, Debug, Getters)] +pub struct PublicKeyPackage { + /// The verifying shares for all participants. Used to validate signature + /// shares they generate. + pub(crate) verifying_shares: BTreeMap, + /// The joint public key for the entire group. + pub(crate) verifying_key: VerifyingKey, +} + +impl PublicKeyPackage { + /// Create a new [`PublicKeyPackage`] instance. + pub fn new( + verifying_shares: BTreeMap, + verifying_key: VerifyingKey, + ) -> Self { + Self { verifying_shares, verifying_key } + } + + /// Computes the public key package given a list of participant identifiers + /// and a [`VerifiableSecretSharingCommitment`]. This is useful in scenarios + /// where the commitments are published somewhere and it's desirable to + /// recreate the public key package from them. + pub fn from_commitment( + identifiers: &BTreeSet, + commitment: &mut SecretPolynomialCommitment, + ) -> Result { + let verifying_keys: BTreeMap<_, _> = + identifiers.iter().map(|id| (*id, commitment.evaluate(&id.0))).collect(); + Ok(PublicKeyPackage::new( + verifying_keys, + VerifyingKey::from_point(*commitment.coefficients_commitments.first().unwrap()), + )) + } + + /// Computes the public key package given a map of participant identifiers + /// and their [`VerifiableSecretSharingCommitment`] from a distributed key + /// generation process. This is useful in scenarios where the commitments + /// are published somewhere and it's desirable to recreate the public key + /// package from them. + pub fn from_dkg_commitments( + commitments: &BTreeMap, + ) -> Result { + let identifiers: BTreeSet<_> = commitments.keys().copied().collect(); + let commitments: Vec<&SecretPolynomialCommitment> = commitments.values().collect(); + let mut group_commitment = + super::polynomial::PolynomialCommitment::sum_polynomial_commitments(&commitments[..]); + Self::from_commitment(&identifiers, &mut group_commitment) + } +} diff --git a/src/olaf/mod.rs b/src/olaf/mod.rs new file mode 100644 index 0000000..4c38bc9 --- /dev/null +++ b/src/olaf/mod.rs @@ -0,0 +1,10 @@ +//! 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; +pub mod frost; 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..e001c0b --- /dev/null +++ b/src/olaf/simplpedpop.rs @@ -0,0 +1,926 @@ +//! 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::{aead::make_aead, context::SigningTranscript, SecretKey, Signature}; +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 cipher: ChaCha20Poly1305 = make_aead::(transcript); + let nonce = Nonce::from_slice(&bytes[..]); + + 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 cipher: ChaCha20Poly1305 = make_aead::(transcript); + let nonce = Nonce::from_slice(&bytes[..]); + + 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..d027243 --- /dev/null +++ b/src/olaf/tests.rs @@ -0,0 +1,1501 @@ +#[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; + const NONCES: u8 = 10; + + 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).unwrap(); + let decrypted_share = encrypted_share.decrypt(&deckey, &enckey, context); + + assert_eq!( + original_share.0, + decrypted_share.unwrap().0, + "Decryption must return the original share!" + ); + } + } + + mod frost_tests { + use super::*; + use crate::{ + olaf::{ + errors::FROSTError, + frost::{ + round1::{self, SigningCommitments, SigningNonces}, + round2::{sign, SignatureShare, SigningPackage}, + round3::{aggregate, verify_signature}, + }, + keys::{KeyPackage, PublicKeyPackage}, + }, + Signature, + }; + use curve25519_dalek::Scalar; + use rand_core::{CryptoRng, RngCore}; + + /// Test FROST signing with the given shares. + fn check_sign( + min_signers: u16, + key_packages: BTreeMap, + mut rng: R, + pubkey_package: PublicKeyPackage, + ) -> Result<(Vec, Signature, GroupPublicKey), FROSTError> { + let mut nonces_map: BTreeMap = BTreeMap::new(); + let mut commitments_map: BTreeMap = + BTreeMap::new(); + + //////////////////////////////////////////////////////////////////////////// + // Round 1: generating nonces and signing commitments for each participant + //////////////////////////////////////////////////////////////////////////// + + for participant_identifier in key_packages.keys().take(min_signers as usize).cloned() { + // Generate one (1) nonce and one SigningCommitments instance for each + // participant, up to _min_signers_. + let (nonces, commitments) = round1::commit( + key_packages.get(&participant_identifier).unwrap().signing_share(), + &mut rng, + ); + nonces_map.insert(participant_identifier, nonces); + commitments_map.insert(participant_identifier, commitments); + } + + // This is what the signature aggregator / coordinator needs to do: + // - decide what message to sign + // - take one (unused) commitment per signing participant + let mut signature_shares = BTreeMap::new(); + let message = b"message to sign"; + let context = b"context"; + + let signing_package = SigningPackage::new(commitments_map, message, context); + + //////////////////////////////////////////////////////////////////////////// + // Round 2: each participant generates their signature share + //////////////////////////////////////////////////////////////////////////// + + for participant_identifier in nonces_map.keys() { + let key_package = key_packages.get(participant_identifier).unwrap(); + + let nonces_to_use = nonces_map.get(participant_identifier).unwrap(); + + check_sign_errors( + signing_package.clone(), + nonces_to_use.clone(), + key_package.clone(), + ); + + // Each participant generates their signature share. + let signature_share = sign(&signing_package, nonces_to_use, key_package)?; + signature_shares.insert(*participant_identifier, signature_share); + } + + //////////////////////////////////////////////////////////////////////////// + // Aggregation: collects the signing shares from all participants, + // generates the final signature. + //////////////////////////////////////////////////////////////////////////// + + #[cfg(not(feature = "cheater-detection"))] + let pubkey_package = PublicKeyPackage { + verifying_shares: BTreeMap::new(), + verifying_key: pubkey_package.verifying_key, + }; + + check_aggregate_errors( + signing_package.clone(), + signature_shares.clone(), + pubkey_package.clone(), + ); + + // Aggregate (also verifies the signature shares) + let group_signature = aggregate(&signing_package, &signature_shares, &pubkey_package)?; + + // Check that the threshold signature can be verified by the group public + // key (the verification key). + + verify_signature(context, message, &group_signature, &pubkey_package.verifying_key)?; + + // Check that the threshold signature can be verified by the group public + // key (the verification key) from KeyPackage.group_public_key + for (participant_identifier, _) in nonces_map.clone() { + let key_package = key_packages.get(&participant_identifier).unwrap(); + + verify_signature(context, message, &group_signature, &key_package.verifying_key)?; + } + + Ok((message.to_vec(), group_signature, pubkey_package.verifying_key)) + } + + /// Test FROST signing with the given shares. + fn check_sign_preprocessing( + min_signers: u16, + key_packages: BTreeMap, + mut rng: R, + pubkey_package: PublicKeyPackage, + num_nonces: u8, + ) -> Result<(Vec>, Vec, GroupPublicKey), FROSTError> { + let mut nonces_map_vec: Vec> = Vec::new(); + let mut commitments_map_vec: Vec> = Vec::new(); + + //////////////////////////////////////////////////////////////////////////// + // Round 1: Generating nonces and signing commitments for each participant + //////////////////////////////////////////////////////////////////////////// + + // First, iterate to gather all nonces and commitments + let mut all_nonces_map: BTreeMap> = BTreeMap::new(); + let mut all_commitments_map: BTreeMap> = + BTreeMap::new(); + + for participant_identifier in key_packages.keys().take(min_signers as usize).cloned() { + let (nonces, commitments) = round1::preprocess( + num_nonces, + key_packages.get(&participant_identifier).unwrap().signing_share(), + &mut rng, + ); + + all_nonces_map.insert(participant_identifier.clone(), nonces); + all_commitments_map.insert(participant_identifier, commitments); + } + + // Now distribute these nonces and commitments to individual participant maps + let mut nonces_map: BTreeMap = BTreeMap::new(); + let mut commitments_map: BTreeMap = BTreeMap::new(); + + for (id, nonces) in &all_nonces_map { + for nonce in nonces { + nonces_map.insert(id.clone(), nonce.clone()); + } + } + + for (id, commitments) in &all_commitments_map { + for commitment in commitments { + commitments_map.insert(id.clone(), commitment.clone()); + } + } + + nonces_map_vec.push(nonces_map); + commitments_map_vec.push(commitments_map); + + // This is what the signature aggregator / coordinator needs to do: + // - decide what message to sign + // - take one (unused) commitment per signing participant + let mut signature_shares = BTreeMap::new(); + + let mut messages = Vec::new(); + + for i in 0..num_nonces { + let mut message = b"message to sign".to_vec(); + message.extend_from_slice(&i.to_be_bytes()); + messages.push(message); + } + + let context = b"context"; + + //////////////////////////////////////////////////////////////////////////// + // Round 2: each participant generates their signature share + //////////////////////////////////////////////////////////////////////////// + + let mut signing_packages = Vec::new(); + let mut group_signatures = Vec::new(); + + for i in 0..commitments_map_vec.len() { + let message = &messages[i as usize]; + + let signing_package = + SigningPackage::new(commitments_map_vec[i as usize].clone(), message, context); + + signing_packages.push(signing_package.clone()); + + for participant_identifier in nonces_map_vec[i as usize].keys() { + let key_package = key_packages.get(participant_identifier).unwrap(); + + let nonces_to_use = + nonces_map_vec[i as usize].get(participant_identifier).unwrap(); + + check_sign_errors( + signing_package.clone(), + nonces_to_use.clone(), + key_package.clone(), + ); + + // Each participant generates their signature share. + let signature_share = sign(&signing_package, nonces_to_use, key_package)?; + signature_shares.insert(*participant_identifier, signature_share); + } + + //////////////////////////////////////////////////////////////////////////// + // Aggregation: collects the signing shares from all participants, + // generates the final signature. + //////////////////////////////////////////////////////////////////////////// + + #[cfg(not(feature = "cheater-detection"))] + let pubkey_package = PublicKeyPackage { + verifying_shares: BTreeMap::new(), + verifying_key: pubkey_package.verifying_key, + }; + + check_aggregate_errors( + signing_package.clone(), + signature_shares.clone(), + pubkey_package.clone(), + ); + + // Aggregate (also verifies the signature shares) + let group_signature = + aggregate(&signing_package, &signature_shares, &pubkey_package)?; + + group_signatures.push(group_signature); + + // Check that the threshold signature can be verified by the group public key. + verify_signature( + context, + message, + &group_signature, + &pubkey_package.verifying_key, + )?; + + // Check that the threshold signature can be verified by the group public + // key from KeyPackage.group_public_key + for (participant_identifier, _) in nonces_map_vec[i as usize].clone() { + let key_package = key_packages.get(&participant_identifier).unwrap(); + + verify_signature( + context, + message, + &group_signature, + &key_package.verifying_key, + )?; + } + } + + Ok((messages, group_signatures, pubkey_package.verifying_key)) + } + + fn check_sign_errors( + signing_package: SigningPackage, + signing_nonces: round1::SigningNonces, + key_package: KeyPackage, + ) { + // Check if passing not enough commitments causes an error + + let mut commitments = signing_package.signing_commitments().clone(); + // Remove one commitment that's not from the key_package owner + let id = *commitments.keys().find(|&&id| id != key_package.identifier).unwrap(); + commitments.remove(&id); + + let signing_package = SigningPackage::new( + commitments, + signing_package.message(), + &signing_package.context(), + ); + + let r = sign(&signing_package, &signing_nonces, &key_package); + assert_eq!(r, Err(FROSTError::IncorrectNumberOfSigningCommitments)); + } + + fn check_aggregate_errors( + signing_package: SigningPackage, + signature_shares: BTreeMap, + pubkey_package: PublicKeyPackage, + ) { + #[cfg(feature = "cheater-detection")] + check_aggregate_corrupted_share( + signing_package.clone(), + signature_shares.clone(), + pubkey_package.clone(), + ); + + check_aggregate_invalid_share_identifier_for_verifying_shares( + signing_package, + signature_shares, + pubkey_package, + ); + } + + #[cfg(feature = "cheater-detection")] + fn check_aggregate_corrupted_share( + signing_package: SigningPackage, + mut signature_shares: BTreeMap, + pubkey_package: PublicKeyPackage, + ) { + let one = Scalar::ONE; + // Corrupt a share + let id = *signature_shares.keys().next().unwrap(); + signature_shares.get_mut(&id).unwrap().share = signature_shares[&id].share + one; + let e = aggregate(&signing_package, &signature_shares, &pubkey_package).unwrap_err(); + assert_eq!(e, FROSTError::InvalidSignatureShare { culprit: id }); + } + + /// Test NCC-E008263-4VP audit finding (PublicKeyPackage). + /// Note that the SigningPackage part of the finding is not currently reachable + /// since it's caught by `compute_lagrange_coefficient()`, and the Binding Factor + /// part can't either since it's caught before by the PublicKeyPackage part. + fn check_aggregate_invalid_share_identifier_for_verifying_shares( + signing_package: SigningPackage, + mut signature_shares: BTreeMap, + pubkey_package: PublicKeyPackage, + ) { + let invalid_identifier = Identifier(Scalar::ZERO); + // Insert a new share (copied from other existing share) with an invalid identifier + signature_shares.insert(invalid_identifier, *signature_shares.values().next().unwrap()); + // Should error, but not panic + aggregate(&signing_package, &signature_shares, &pubkey_package) + .expect_err("should not work"); + } + + #[test] + fn test_n_of_n_frost_with_simplpedpop() { + 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 mut key_packages: BTreeMap = BTreeMap::new(); + + let mut identifiers: Vec = participants_sets_of_participants[0] + .others_identifiers() + .iter() + .copied() + .collect(); + + identifiers.push(*participants_sets_of_participants[0].own_identifier()); + + for i in 0..parameters_list[0].participants { + key_packages.insert( + identifiers_vec[i as usize], + KeyPackage::new( + identifiers_vec[i as usize], + participants_data_round3[i as usize].2.total_secret_share.clone(), + participants_data_round3[i as usize] + .1 + .get(participants_sets_of_participants[i as usize].own_identifier()) + .unwrap() + .clone(), + participants_data_round3[0].0, + parameters_list[0].threshold, + ), + ); + } + + let pubkeys = PublicKeyPackage::new( + participants_data_round3[0].1.clone(), + participants_data_round3[0].0, + ); + + // Proceed with the signing test. + check_sign(*parameters_list[0].threshold(), key_packages, OsRng, pubkeys).unwrap(); + } + + #[test] + fn test_t_of_n_frost_with_simplpedpop() { + 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 mut key_packages: BTreeMap = BTreeMap::new(); + + let mut identifiers: Vec = participants_sets_of_participants[0] + .others_identifiers() + .iter() + .copied() + .collect(); + + identifiers.push(*participants_sets_of_participants[0].own_identifier()); + + for i in 0..parameters_list[0].threshold { + key_packages.insert( + identifiers_vec[i as usize], + KeyPackage::new( + identifiers_vec[i as usize], + participants_data_round3[i as usize].2.total_secret_share.clone(), + participants_data_round3[i as usize] + .1 + .get(participants_sets_of_participants[i as usize].own_identifier()) + .unwrap() + .clone(), + participants_data_round3[0].0, + parameters_list[0].threshold, + ), + ); + } + + let pubkeys = PublicKeyPackage::new( + participants_data_round3[0].1.clone(), + participants_data_round3[0].0, + ); + + // Proceed with the signing test. + check_sign(*parameters_list[0].threshold(), key_packages, OsRng, pubkeys).unwrap(); + } + + #[test] + fn test_preprocessing() { + 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 mut key_packages: BTreeMap = BTreeMap::new(); + + let mut identifiers: Vec = participants_sets_of_participants[0] + .others_identifiers() + .iter() + .copied() + .collect(); + + identifiers.push(*participants_sets_of_participants[0].own_identifier()); + + for i in 0..parameters_list[0].threshold { + key_packages.insert( + identifiers_vec[i as usize], + KeyPackage::new( + identifiers_vec[i as usize], + participants_data_round3[i as usize].2.total_secret_share.clone(), + participants_data_round3[i as usize] + .1 + .get(participants_sets_of_participants[i as usize].own_identifier()) + .unwrap() + .clone(), + participants_data_round3[0].0, + parameters_list[0].threshold, + ), + ); + } + + let pubkeys = PublicKeyPackage::new( + participants_data_round3[0].1.clone(), + participants_data_round3[0].0, + ); + + // Proceed with the signing test. + check_sign_preprocessing( + *parameters_list[0].threshold(), + key_packages, + OsRng, + pubkeys, + NONCES, + ) + .unwrap(); + } + } +} diff --git a/src/points.rs b/src/points.rs index 0f60398..c2af472 100644 --- a/src/points.rs +++ b/src/points.rs @@ -17,25 +17,22 @@ // We're discussing including some variant in curve25519-dalek directly in // https://github.com/dalek-cryptography/curve25519-dalek/pull/220 - use core::fmt::{Debug}; -use curve25519_dalek::ristretto::{CompressedRistretto,RistrettoPoint}; -use subtle::{ConstantTimeEq,Choice}; +use curve25519_dalek::ristretto::{CompressedRistretto, RistrettoPoint}; +use subtle::{ConstantTimeEq, Choice}; // use curve25519_dalek::scalar::Scalar; -use crate::errors::{SignatureError,SignatureResult}; - +use crate::errors::{SignatureError, SignatureResult}; /// Compressed Ristretto point length pub const RISTRETTO_POINT_LENGTH: usize = 32; - /// A `RistrettoBoth` contains both an uncompressed `RistrettoPoint` /// as well as the corresponding `CompressedRistretto`. It provides /// a convenient middle ground for protocols that both hash compressed /// points to derive scalars for use with uncompressed points. -#[derive(Copy, Clone, Default, Eq)] // PartialEq optimized below +#[derive(Copy, Clone, Default, Eq)] // PartialEq optimized below pub struct RistrettoBoth { compressed: CompressedRistretto, point: RistrettoPoint, @@ -49,12 +46,13 @@ impl Debug for RistrettoBoth { impl ConstantTimeEq for RistrettoBoth { fn ct_eq(&self, other: &RistrettoBoth) -> Choice { - self.compressed.ct_eq(&other.compressed) + self.compressed.ct_eq(&other.compressed) } } +#[rustfmt::skip] impl RistrettoBoth { - const DESCRIPTION : &'static str = "A ristretto point represented as a 32-byte compressed point"; + const DESCRIPTION: &'static str = "A ristretto point represented as a 32-byte compressed point"; // I dislike getter methods, and prefer direct field access, but doing // getters here permits the fields being private, and gives us faster diff --git a/src/serdey.rs b/src/serdey.rs index 64c5e26..8a8e2f5 100644 --- a/src/serdey.rs +++ b/src/serdey.rs @@ -11,16 +11,17 @@ //! ### Various and tooling related to serde #[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)?; @@ -38,7 +39,9 @@ impl<'d> serde_crate::Deserialize<'d> for $t { } } // macro_rules! serde_boilerplate #[cfg(not(feature = "serde"))] -macro_rules! serde_boilerplate { ($t:ty) => { } } +macro_rules! serde_boilerplate { + ($t:ty) => {}; +} #[cfg(all(test, feature = "serde"))] mod test { @@ -51,6 +54,7 @@ mod test { use crate::*; + #[rustfmt::skip] static COMPRESSED_PUBLIC_KEY : CompressedRistretto = CompressedRistretto([ 208, 120, 140, 129, 177, 179, 237, 159, 252, 160, 028, 013, 206, 005, 211, 241, @@ -58,6 +62,7 @@ mod test { 119, 046, 246, 029, 079, 080, 077, 084]); /* + #[rustfmt::skip] static ED25519_PUBLIC_KEY: CompressedEdwardsY = CompressedEdwardsY([ 130, 039, 155, 015, 062, 076, 188, 063, 124, 122, 026, 251, 233, 253, 225, 220, @@ -65,6 +70,7 @@ mod test { 160, 083, 172, 058, 219, 042, 086, 120, ]); */ + #[rustfmt::skip] static ED25519_SECRET_KEY: MiniSecretKey = MiniSecretKey([ 062, 070, 027, 163, 092, 182, 011, 003, 077, 234, 098, 004, 011, 127, 079, 228, @@ -72,6 +78,7 @@ mod test { 085, 251, 152, 002, 241, 042, 072, 054, ]); /// Ed25519 signature with the above keypair of a blank message. + #[rustfmt::skip] static SIGNATURE_BYTES: [u8; SIGNATURE_LENGTH] = [ 010, 126, 151, 143, 157, 064, 047, 001, 196, 140, 179, 058, 226, 152, 018, 102, @@ -82,7 +89,6 @@ mod test { 216, 085, 134, 144, 129, 149, 041, 081, 063, 120, 126, 100, 092, 059, 050, 138, ]; - #[test] fn serialize_deserialize_signature() { let signature: Signature = Signature::from_bytes(&SIGNATURE_BYTES).unwrap(); @@ -172,21 +178,21 @@ mod test { #[test] fn serialize_public_key_size() { let public_key = PublicKey::from_compressed(COMPRESSED_PUBLIC_KEY).unwrap(); - assert_eq!(serialized_size(&public_key).unwrap(), 32+8); // Size specific to bincode==1.0.1 + assert_eq!(serialized_size(&public_key).unwrap(), 32 + 8); // Size specific to bincode==1.0.1 } #[test] fn serialize_signature_size() { let signature: Signature = Signature::from_bytes(&SIGNATURE_BYTES).unwrap(); - assert_eq!(serialized_size(&signature).unwrap(), 64+8); // Size specific to bincode==1.0.1 + assert_eq!(serialized_size(&signature).unwrap(), 64 + 8); // Size specific to bincode==1.0.1 } #[test] fn serialize_secret_key_size() { - assert_eq!(serialized_size(&ED25519_SECRET_KEY).unwrap(), 32+8); + assert_eq!(serialized_size(&ED25519_SECRET_KEY).unwrap(), 32 + 8); let secret_key = ED25519_SECRET_KEY.expand(ExpansionMode::Ed25519); - assert_eq!(serialized_size(&secret_key).unwrap(), 64+8); // Sizes specific to bincode==1.0.1 + assert_eq!(serialized_size(&secret_key).unwrap(), 64 + 8); // Sizes specific to bincode==1.0.1 let secret_key = ED25519_SECRET_KEY.expand(ExpansionMode::Uniform); - assert_eq!(serialized_size(&secret_key).unwrap(), 64+8); // Sizes specific to bincode==1.0.1 + assert_eq!(serialized_size(&secret_key).unwrap(), 64 + 8); // Sizes specific to bincode==1.0.1 } } diff --git a/src/sign.rs b/src/sign.rs index d39f9d4..1169b19 100644 --- a/src/sign.rs +++ b/src/sign.rs @@ -11,16 +11,14 @@ //! ### Schnorr signature creation and verification, including batch verification. - use core::fmt::{Debug}; 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,SigningContext}; - +use crate::context::{SigningTranscript, SigningContext}; // === Actual signature type === // @@ -43,7 +41,7 @@ pub struct Signature { /// This digest is then interpreted as a `Scalar` and reduced into an /// element in ℤ/lℤ. The scalar is then multiplied by the distinguished /// basepoint to produce `R`, a `RistrettoPoint`. - pub (crate) R: CompressedRistretto, + pub(crate) R: CompressedRistretto, /// `s` is a `Scalar`, formed by using an hash function with 512-bits output /// to produce the digest of: @@ -54,7 +52,7 @@ pub struct Signature { /// /// This digest is then interpreted as a `Scalar` and reduced into an /// element in ℤ/lℤ. - pub (crate) s: Scalar, + pub(crate) s: Scalar, } impl Debug for Signature { @@ -74,14 +72,14 @@ pub(crate) fn check_scalar(bytes: [u8; 32]) -> SignatureResult { // This succeed-fast trick should succeed for roughly half of all scalars. if bytes[31] & 0b11110000 == 0 { #[allow(deprecated)] // Scalar's always reduced here, so this is OK. - return Ok(Scalar::from_bits(bytes)) + return Ok(Scalar::from_bits(bytes)); } crate::scalar_from_canonical_bytes(bytes).ok_or(SignatureError::ScalarFormatError) } impl Signature { - const DESCRIPTION : &'static str = "A 64 byte Ristretto Schnorr signature"; + const DESCRIPTION: &'static str = "A 64 byte Ristretto Schnorr signature"; /* const DESCRIPTION_LONG : &'static str = "A 64 byte Ristretto Schnorr signature, similar to an ed25519 \ @@ -119,7 +117,7 @@ impl Signature { return Err(SignatureError::BytesLengthError { name: "Signature", description: Signature::DESCRIPTION, - length: SIGNATURE_LENGTH + length: SIGNATURE_LENGTH, }); } @@ -132,7 +130,7 @@ impl Signature { } upper[31] &= 127; - Ok(Signature{ R: CompressedRistretto(lower), s: check_scalar(upper) ? }) + Ok(Signature { R: CompressedRistretto(lower), s: check_scalar(upper)? }) } /// Deprecated construction of a `Signature` from a slice of bytes @@ -144,7 +142,7 @@ impl Signature { return Err(SignatureError::BytesLengthError { name: "Signature", description: Signature::DESCRIPTION, - length: SIGNATURE_LENGTH + length: SIGNATURE_LENGTH, }); } let mut bytes0: [u8; SIGNATURE_LENGTH] = [0u8; SIGNATURE_LENGTH]; @@ -156,7 +154,6 @@ impl Signature { serde_boilerplate!(Signature); - // === Implement signing and verification operations on key types === // impl SecretKey { @@ -172,54 +169,55 @@ impl SecretKey { /// should be no attacks even if both the random number generator /// fails and the function gets called with the wrong public key. #[allow(non_snake_case)] - pub fn sign(&self, mut t: T, public_key: &PublicKey) -> Signature - { + pub fn sign(&self, mut t: T, public_key: &PublicKey) -> Signature { t.proto_name(b"Schnorr-sig"); - t.commit_point(b"sign:pk",public_key.as_compressed()); + t.commit_point(b"sign:pk", public_key.as_compressed()); - let mut r = t.witness_scalar(b"signing",&[&self.nonce]); // context, message, A/public_key + let mut r = t.witness_scalar(b"signing", &[&self.nonce]); // context, message, A/public_key let R = (&r * constants::RISTRETTO_BASEPOINT_TABLE).compress(); - t.commit_point(b"sign:R",&R); + t.commit_point(b"sign:R", &R); - let k: Scalar = t.challenge_scalar(b"sign:c"); // context, message, A/public_key, R=rG + let k: Scalar = t.challenge_scalar(b"sign:c"); // context, message, A/public_key, R=rG let s: Scalar = k * self.key + r; zeroize::Zeroize::zeroize(&mut r); - Signature{ R, s } + Signature { R, s } } /// Sign a message with this `SecretKey`, but doublecheck the result. pub fn sign_doublecheck(&self, t: T, public_key: &PublicKey) -> SignatureResult - where T: SigningTranscript+Clone + where + T: SigningTranscript + Clone, { - let sig = self.sign(t.clone(),public_key); - let sig = Signature::from_bytes(& sig.to_bytes()) ?; - PublicKey::from_bytes(& public_key.to_bytes()) ? - .verify(t,&sig).map(|()| sig) + let sig = self.sign(t.clone(), public_key); + let sig = Signature::from_bytes(&sig.to_bytes())?; + PublicKey::from_bytes(&public_key.to_bytes())?.verify(t, &sig).map(|()| sig) } /// Sign a message with this `SecretKey`. - pub fn sign_simple(&self, ctx: &[u8], msg: &[u8], public_key: &PublicKey) -> Signature - { + pub fn sign_simple(&self, ctx: &[u8], msg: &[u8], public_key: &PublicKey) -> Signature { let t = SigningContext::new(ctx).bytes(msg); - self.sign(t,public_key) + self.sign(t, public_key) } /// Sign a message with this `SecretKey`, but doublecheck the result. - pub fn sign_simple_doublecheck(&self, ctx: &[u8], msg: &[u8], public_key: &PublicKey) - -> SignatureResult - { + pub fn sign_simple_doublecheck( + &self, + ctx: &[u8], + msg: &[u8], + public_key: &PublicKey, + ) -> SignatureResult { let t = SigningContext::new(ctx).bytes(msg); - let sig = self.sign(t,public_key); - let sig = Signature::from_bytes(& sig.to_bytes()) ?; - PublicKey::from_bytes(& public_key.to_bytes()) ? - .verify_simple(ctx,msg,&sig).map(|()| sig) + let sig = self.sign(t, public_key); + let sig = Signature::from_bytes(&sig.to_bytes())?; + PublicKey::from_bytes(&public_key.to_bytes())? + .verify_simple(ctx, msg, &sig) + .map(|()| sig) } } - impl PublicKey { /// Verify a signature by this public key on a transcript. /// @@ -227,42 +225,54 @@ impl PublicKey { /// `SigningContext` and a message, as well as the signature /// to be verified. #[allow(non_snake_case)] - pub fn verify(&self, mut t: T, signature: &Signature) - -> SignatureResult<()> - { + pub fn verify( + &self, + mut t: T, + signature: &Signature, + ) -> SignatureResult<()> { let A: &RistrettoPoint = self.as_point(); t.proto_name(b"Schnorr-sig"); - t.commit_point(b"sign:pk",self.as_compressed()); - t.commit_point(b"sign:R",&signature.R); + t.commit_point(b"sign:pk", self.as_compressed()); + t.commit_point(b"sign:R", &signature.R); - let k: Scalar = t.challenge_scalar(b"sign:c"); // context, message, A/public_key, R=rG + let k: Scalar = t.challenge_scalar(b"sign:c"); // context, message, A/public_key, R=rG let R = RistrettoPoint::vartime_double_scalar_mul_basepoint(&k, &(-A), &signature.s); - if R.compress() == signature.R { Ok(()) } else { Err(SignatureError::EquationFalse) } + if R.compress() == signature.R { + Ok(()) + } else { + Err(SignatureError::EquationFalse) + } } /// Verify a signature by this public key on a message. - pub fn verify_simple(&self, ctx: &[u8], msg: &[u8], signature: &Signature) - -> SignatureResult<()> - { + pub fn verify_simple( + &self, + ctx: &[u8], + msg: &[u8], + signature: &Signature, + ) -> SignatureResult<()> { let t = SigningContext::new(ctx).bytes(msg); - self.verify(t,signature) + self.verify(t, signature) } /// A temporary verification routine for use in transitioning substrate testnets only. #[cfg(feature = "preaudit_deprecated")] #[allow(non_snake_case)] - pub fn verify_simple_preaudit_deprecated(&self, ctx: &'static [u8], msg: &[u8], sig: &[u8]) - -> SignatureResult<()> - { + pub fn verify_simple_preaudit_deprecated( + &self, + ctx: &'static [u8], + msg: &[u8], + sig: &[u8], + ) -> SignatureResult<()> { let t = SigningContext::new(ctx).bytes(msg); if let Ok(signature) = Signature::from_bytes(sig) { - return self.verify(t,&signature); + return self.verify(t, &signature); } - let signature = Signature::from_bytes_not_distinguished_from_ed25519(sig) ?; + let signature = Signature::from_bytes_not_distinguished_from_ed25519(sig)?; let mut t = merlin::Transcript::new(ctx); t.append_message(b"sign-bytes", msg); @@ -270,18 +280,20 @@ impl PublicKey { let A: &RistrettoPoint = self.as_point(); t.proto_name(b"Schnorr-sig"); - t.commit_point(b"pk",self.as_compressed()); - t.commit_point(b"no",&signature.R); + t.commit_point(b"pk", self.as_compressed()); + t.commit_point(b"no", &signature.R); - let k: Scalar = t.challenge_scalar(b""); // context, message, A/public_key, R=rG + let k: Scalar = t.challenge_scalar(b""); // context, message, A/public_key, R=rG let R = RistrettoPoint::vartime_double_scalar_mul_basepoint(&k, &(-A), &signature.s); - if R.compress() == signature.R { Ok(()) } else { Err(SignatureError::EquationFalse) } + if R.compress() == signature.R { + Ok(()) + } else { + Err(SignatureError::EquationFalse) + } } - } - impl Keypair { /// Sign a transcript with this keypair's secret key. /// @@ -343,14 +355,12 @@ impl Keypair { /// ``` /// // lol [terrible_idea]: https://github.com/isislovecruft/scripts/blob/master/gpgkey2bc.py - pub fn sign(&self, t: T) -> Signature - { + pub fn sign(&self, t: T) -> Signature { self.secret.sign(t, &self.public) } /// Sign a message with this keypair's secret key. - pub fn sign_simple(&self, ctx: &[u8], msg: &[u8]) -> Signature - { + pub fn sign_simple(&self, ctx: &[u8], msg: &[u8]) -> Signature { self.secret.sign_simple(ctx, msg, &self.public) } @@ -381,42 +391,41 @@ impl Keypair { /// # } /// # } /// ``` - pub fn verify(&self, t: T, signature: &Signature) -> SignatureResult<()> - { + pub fn verify(&self, t: T, signature: &Signature) -> SignatureResult<()> { self.public.verify(t, signature) } /// Verify a signature by keypair's public key on a message. - pub fn verify_simple(&self, ctx: &[u8], msg: &[u8], signature: &Signature) -> SignatureResult<()> - { + pub fn verify_simple( + &self, + ctx: &[u8], + msg: &[u8], + signature: &Signature, + ) -> SignatureResult<()> { self.public.verify_simple(ctx, msg, signature) } - /// Sign a message with this `SecretKey`, but doublecheck the result. pub fn sign_doublecheck(&self, t: T) -> SignatureResult - where T: SigningTranscript+Clone + where + T: SigningTranscript + Clone, { let sig = self.sign(t.clone()); - let sig = Signature::from_bytes(& sig.to_bytes()) ?; - PublicKey::from_bytes(& self.public.to_bytes()) ? - .verify(t,&sig).map(|()| sig) + let sig = Signature::from_bytes(&sig.to_bytes())?; + PublicKey::from_bytes(&self.public.to_bytes())?.verify(t, &sig).map(|()| sig) } /// Sign a message with this `SecretKey`, but doublecheck the result. - pub fn sign_simple_doublecheck(&self, ctx: &[u8], msg: &[u8]) - -> SignatureResult - { + pub fn sign_simple_doublecheck(&self, ctx: &[u8], msg: &[u8]) -> SignatureResult { let t = SigningContext::new(ctx).bytes(msg); let sig = self.sign(t); - let sig = Signature::from_bytes(& sig.to_bytes()) ?; - PublicKey::from_bytes(& self.public.to_bytes()) ? - .verify_simple(ctx,msg,&sig).map(|()| sig) + let sig = Signature::from_bytes(&sig.to_bytes())?; + PublicKey::from_bytes(&self.public.to_bytes())? + .verify_simple(ctx, msg, &sig) + .map(|()| sig) } - } - #[cfg(test)] mod test { use sha3::Shake128; @@ -424,47 +433,54 @@ mod test { use super::super::*; - #[cfg(feature = "getrandom")] #[test] fn sign_verify_bytes() { let good_sig: Signature; - let bad_sig: Signature; + let bad_sig: Signature; let ctx = signing_context(b"good"); let good: &[u8] = "test message".as_bytes(); - let bad: &[u8] = "wrong message".as_bytes(); + let bad: &[u8] = "wrong message".as_bytes(); let mut csprng = rand_core::OsRng; let keypair = Keypair::generate_with(&mut csprng); good_sig = keypair.sign(ctx.bytes(&good)); - bad_sig = keypair.sign(ctx.bytes(&bad)); + bad_sig = keypair.sign(ctx.bytes(&bad)); let good_sig = Signature::from_bytes(&good_sig.to_bytes()[..]).unwrap(); - let bad_sig = Signature::from_bytes(&bad_sig.to_bytes()[..]).unwrap(); - - assert!(keypair.verify(ctx.bytes(&good), &good_sig).is_ok(), - "Verification of a valid signature failed!"); - assert!(!keypair.verify(ctx.bytes(&good), &bad_sig).is_ok(), - "Verification of a signature on a different message passed!"); - assert!(!keypair.verify(ctx.bytes(&bad), &good_sig).is_ok(), - "Verification of a signature on a different message passed!"); - assert!(!keypair.verify(signing_context(b"bad").bytes(&good), &good_sig).is_ok(), - "Verification of a signature on a different message passed!"); + let bad_sig = Signature::from_bytes(&bad_sig.to_bytes()[..]).unwrap(); + + assert!( + keypair.verify(ctx.bytes(&good), &good_sig).is_ok(), + "Verification of a valid signature failed!" + ); + assert!( + !keypair.verify(ctx.bytes(&good), &bad_sig).is_ok(), + "Verification of a signature on a different message passed!" + ); + assert!( + !keypair.verify(ctx.bytes(&bad), &good_sig).is_ok(), + "Verification of a signature on a different message passed!" + ); + assert!( + !keypair.verify(signing_context(b"bad").bytes(&good), &good_sig).is_ok(), + "Verification of a signature on a different message passed!" + ); } #[cfg(feature = "getrandom")] #[test] fn sign_verify_xof() { let good_sig: Signature; - let bad_sig: Signature; + let bad_sig: Signature; let ctx = signing_context(b"testing testing 1 2 3"); let good: &[u8] = b"test message"; - let bad: &[u8] = b"wrong message"; + let bad: &[u8] = b"wrong message"; let prehashed_good: Shake128 = Shake128::default().chain(good); let prehashed_bad: Shake128 = Shake128::default().chain(bad); @@ -474,32 +490,42 @@ mod test { let keypair = Keypair::generate_with(&mut csprng); good_sig = keypair.sign(ctx.xof(prehashed_good.clone())); - bad_sig = keypair.sign(ctx.xof(prehashed_bad.clone())); + bad_sig = keypair.sign(ctx.xof(prehashed_bad.clone())); let good_sig_d = Signature::from_bytes(&good_sig.to_bytes()[..]).unwrap(); - let bad_sig_d = Signature::from_bytes(&bad_sig.to_bytes()[..]).unwrap(); + let bad_sig_d = Signature::from_bytes(&bad_sig.to_bytes()[..]).unwrap(); assert_eq!(good_sig, good_sig_d); assert_eq!(bad_sig, bad_sig_d); - assert!(keypair.verify(ctx.xof(prehashed_good.clone()), &good_sig).is_ok(), - "Verification of a valid signature failed!"); - assert!(! keypair.verify(ctx.xof(prehashed_good.clone()), &bad_sig).is_ok(), - "Verification of a signature on a different message passed!"); - assert!(! keypair.verify(ctx.xof(prehashed_bad.clone()), &good_sig).is_ok(), - "Verification of a signature on a different message passed!"); - assert!(! keypair.verify(signing_context(b"oops").xof(prehashed_good), &good_sig).is_ok(), - "Verification of a signature on a different message passed!"); + assert!( + keypair.verify(ctx.xof(prehashed_good.clone()), &good_sig).is_ok(), + "Verification of a valid signature failed!" + ); + assert!( + !keypair.verify(ctx.xof(prehashed_good.clone()), &bad_sig).is_ok(), + "Verification of a signature on a different message passed!" + ); + assert!( + !keypair.verify(ctx.xof(prehashed_bad.clone()), &good_sig).is_ok(), + "Verification of a signature on a different message passed!" + ); + assert!( + !keypair.verify(signing_context(b"oops").xof(prehashed_good), &good_sig).is_ok(), + "Verification of a signature on a different message passed!" + ); } #[cfg(feature = "preaudit_deprecated")] #[test] fn can_verify_know_preaudit_deprecated_message() { use hex_literal::hex; - const SIGNING_CTX : &'static [u8] = b"substrate"; + const SIGNING_CTX: &'static [u8] = b"substrate"; let message = b"Verifying that I am the owner of 5G9hQLdsKQswNPgB499DeA5PkFBbgkLPJWkkS6FAM6xGQ8xD. Hash: 221455a3\n"; let public = hex!("b4bfa1f7a5166695eb75299fd1c4c03ea212871c342f2c5dfea0902b2c246918"); let public = PublicKey::from_bytes(&public[..]).unwrap(); let signature = hex!("5a9755f069939f45d96aaf125cf5ce7ba1db998686f87f2fb3cbdea922078741a73891ba265f70c31436e18a9acd14d189d73c12317ab6c313285cd938453202"); - assert!( public.verify_simple_preaudit_deprecated(SIGNING_CTX,message,&signature[..]).is_ok() ); + assert!(public + .verify_simple_preaudit_deprecated(SIGNING_CTX, message, &signature[..]) + .is_ok()); } } diff --git a/src/vrf.rs b/src/vrf.rs index 71bb5d4..eb3a9e6 100644 --- a/src/vrf.rs +++ b/src/vrf.rs @@ -12,7 +12,7 @@ //! *Warning* We warn that our VRF construction supports malleable //! outputs via the `*malleable*` methods. These are insecure when //! used in conjunction with our HDKD provided in dervie.rs. -//! Attackers could translate malleable VRF outputs from one soft subkey +//! Attackers could translate malleable VRF outputs from one soft subkey //! to another soft subkey, gaining early knowledge of the VRF output. //! We suggest using either non-malleable VRFs or using implicit //! certificates instead of HDKD when using VRFs. @@ -89,7 +89,7 @@ use curve25519_dalek::ristretto::{CompressedRistretto, RistrettoPoint}; use curve25519_dalek::scalar::Scalar; use curve25519_dalek::traits::{IsIdentity}; // Identity #[cfg(feature = "alloc")] -use curve25519_dalek::traits::{MultiscalarMul,VartimeMultiscalarMul}; +use curve25519_dalek::traits::{MultiscalarMul, VartimeMultiscalarMul}; use merlin::Transcript; @@ -99,7 +99,7 @@ use crate::points::RistrettoBoth; // use crate::errors::SignatureError; /// Value for `kusama` paramater to `*dleq*` methods that yields the VRF for kusama. -/// +/// /// Greg Maxwell argue that nonce generation should hash all parameters /// that challenge generation does in https://moderncrypto.org/mail-archive/curves/2020/001012.html /// We support this position in principle as a defense in depth against @@ -109,23 +109,23 @@ use crate::points::RistrettoBoth; /// We cannot justify add this defense to the deployed VRF because /// several layers already address this attack, including merlin's /// witnesses and that signers normally only sign VRF outputs once. -/// +/// /// We suggest using Greg Maxwell's trick if you use a stand alone DLEQ /// proof though, meaning call `*dleq*` methods with `kusama: false`. /// /// see: https://github.com/w3f/schnorrkel/issues/53 // We currently lack tests for the case when this is false, but you can // rerun cargo test with this set to false for that. -pub const KUSAMA_VRF : bool = true; +pub const KUSAMA_VRF: bool = true; /// Length of VRF output. -pub const VRF_PREOUT_LENGTH : usize = 32; +pub const VRF_PREOUT_LENGTH: usize = 32; /// Length of the short VRF proof which lacks support for batch verification. -pub const VRF_PROOF_LENGTH : usize = 64; +pub const VRF_PROOF_LENGTH: usize = 64; /// Length of the longer VRF proof which supports batch verification. -pub const VRF_PROOF_BATCHABLE_LENGTH : usize = 96; +pub const VRF_PROOF_BATCHABLE_LENGTH: usize = 96; /// `SigningTranscript` helper trait that manages VRF output malleability. /// @@ -140,11 +140,14 @@ pub trait VRFSigningTranscript { fn transcript_with_malleability_addressed(self, publickey: &PublicKey) -> Self::T; } -impl VRFSigningTranscript for T where T: SigningTranscript { +impl VRFSigningTranscript for T +where + T: SigningTranscript, +{ type T = T; #[inline(always)] fn transcript_with_malleability_addressed(mut self, publickey: &PublicKey) -> T { - self.commit_point(b"vrf-nm-pk", publickey.as_compressed()); + self.commit_point(b"vrf-nm-pk", publickey.as_compressed()); self } } @@ -152,27 +155,32 @@ impl VRFSigningTranscript for T where T: SigningTranscript { /// 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, /// which should also be secure in combination with HDKD. /// We always use non-malleable VRF inputs in our convenience methods. #[derive(Clone)] +#[rustfmt::skip] pub struct Malleable(pub T); -impl VRFSigningTranscript for Malleable where T: SigningTranscript { +impl VRFSigningTranscript for Malleable +where + T: SigningTranscript, +{ type T = T; #[inline(always)] - fn transcript_with_malleability_addressed(self, _publickey: &PublicKey) -> T { self.0 } + fn transcript_with_malleability_addressed(self, _publickey: &PublicKey) -> T { + self.0 + } } - /// Create a malleable VRF input point by hashing a transcript to a point. /// /// *Warning* We caution that malleable VRF inputs 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, @@ -187,22 +195,23 @@ pub fn vrf_malleable_hash(mut t: T) -> RistrettoBoth { impl PublicKey { /// Create a non-malleable VRF input point by hashing a transcript to a point. pub fn vrf_hash(&self, t: T) -> RistrettoBoth - where T: VRFSigningTranscript { + where + T: VRFSigningTranscript, + { vrf_malleable_hash(t.transcript_with_malleability_addressed(self)) } /// Pair a non-malleable VRF output with the hash of the given transcript. pub fn vrf_attach_hash(&self, output: VRFPreOut, t: T) -> SignatureResult - where T: VRFSigningTranscript { - output.attach_input_hash(self,t) + where + T: VRFSigningTranscript, + { + output.attach_input_hash(self, t) } } /// VRF pre-output, possibly unverified. -#[deprecated( - since = "0.9.2", - note = "Please use VRFPreOut instead of VRFOutput" -)] +#[deprecated(since = "0.9.2", note = "Please use VRFPreOut instead of VRFOutput")] pub type VRFOutput = VRFPreOut; /// VRF pre-output, possibly unverified. @@ -243,7 +252,7 @@ impl VRFPreOut { return Err(SignatureError::BytesLengthError { name: "VRFPreOut", description: VRFPreOut::DESCRIPTION, - length: VRF_PREOUT_LENGTH + length: VRF_PREOUT_LENGTH, }); } let mut bits: [u8; 32] = [0u8; 32]; @@ -253,10 +262,14 @@ impl VRFPreOut { /// Pair a non-malleable VRF output with the hash of the given transcript. pub fn attach_input_hash(&self, public: &PublicKey, t: T) -> SignatureResult - where T: VRFSigningTranscript { + where + T: VRFSigningTranscript, + { let input = public.vrf_hash(t); - let output = RistrettoBoth::from_bytes_ser("VRFPreOut", VRFPreOut::DESCRIPTION, &self.0) ?; - if output.as_point().is_identity() { return Err(SignatureError::PointDecompressionError); } + let output = RistrettoBoth::from_bytes_ser("VRFPreOut", VRFPreOut::DESCRIPTION, &self.0)?; + if output.as_point().is_identity() { + return Err(SignatureError::PointDecompressionError); + } Ok(VRFInOut { input, output }) } } @@ -290,7 +303,7 @@ impl SecretKey { /// and note that `vrf_create_from_point` cannot check for /// problematic inputs like `attach_input_hash` does. pub fn vrf_create_from_compressed_point(&self, input: &VRFPreOut) -> SignatureResult { - let input = RistrettoBoth::from_compressed(CompressedRistretto(input.0)) ?; + let input = RistrettoBoth::from_compressed(CompressedRistretto(input.0))?; Ok(self.vrf_create_from_point(input)) } } @@ -339,7 +352,7 @@ impl VRFInOut { /// by Bernardo David, Peter Gazi, Aggelos Kiayias, and Alexander Russell. pub fn make_bytes>(&self, context: &[u8]) -> B { let mut t = Transcript::new(b"VRFResult"); - t.append_message(b"",context); + t.append_message(b"", context); self.commit(&mut t); let mut seed = B::default(); t.challenge_bytes(b"", seed.as_mut()); @@ -383,6 +396,7 @@ impl VRFInOut { pub fn make_merlin_rng(&self, context: &[u8]) -> merlin::TranscriptRng { // Very insecure hack except for our commit_witness_bytes below struct ZeroFakeRng; + #[rustfmt::skip] impl rand_core::RngCore for ZeroFakeRng { fn next_u32(&mut self) -> u32 { panic!() } fn next_u64(&mut self) -> u64 { panic!() } @@ -397,7 +411,7 @@ impl VRFInOut { impl rand_core::CryptoRng for ZeroFakeRng {} let mut t = Transcript::new(b"VRFResult"); - t.append_message(b"",context); + t.append_message(b"", context); self.commit(&mut t); t.build_rng().finalize(&mut ZeroFakeRng) } @@ -426,7 +440,8 @@ impl PublicKey { /// /// TODO: Add constant time 128 bit batched multiplication to dalek. /// TODO: Is rand_chacha's `gen::()` standardizable enough to - /// prefer it over merlin for the output? + /// prefer it over merlin for the output? + #[rustfmt::skip] pub fn vrfs_merge(&self, ps: &[B], vartime: bool) -> VRFInOut where B: Borrow, @@ -509,7 +524,7 @@ impl VRFProof { return Err(SignatureError::BytesLengthError { name: "VRFProof", description: VRFProof::DESCRIPTION, - length: VRF_PROOF_LENGTH + length: VRF_PROOF_LENGTH, }); } let mut c: [u8; 32] = [0u8; 32]; @@ -518,8 +533,8 @@ impl VRFProof { c.copy_from_slice(&bytes[..32]); s.copy_from_slice(&bytes[32..]); - let c = crate::scalar_from_canonical_bytes(c).ok_or(SignatureError::ScalarFormatError) ?; - let s = crate::scalar_from_canonical_bytes(s).ok_or(SignatureError::ScalarFormatError) ?; + let c = crate::scalar_from_canonical_bytes(c).ok_or(SignatureError::ScalarFormatError)?; + let s = crate::scalar_from_canonical_bytes(s).ok_or(SignatureError::ScalarFormatError)?; Ok(VRFProof { c, s }) } } @@ -573,12 +588,13 @@ impl VRFProofBatchable { Hr.copy_from_slice(&bytes[32..64]); s.copy_from_slice(&bytes[64..96]); - let s = crate::scalar_from_canonical_bytes(s).ok_or(SignatureError::ScalarFormatError) ?; + let s = crate::scalar_from_canonical_bytes(s).ok_or(SignatureError::ScalarFormatError)?; Ok(VRFProofBatchable { R: CompressedRistretto(R), Hr: CompressedRistretto(Hr), s }) } /// Return the shortened `VRFProof` for retransmitting in not batched situations #[allow(non_snake_case)] + #[rustfmt::skip] pub fn shorten_dleq(&self, mut t: T, public: &PublicKey, p: &VRFInOut, kusama: bool) -> VRFProof where T: SigningTranscript, { @@ -604,12 +620,17 @@ impl VRFProofBatchable { /// TODO: Avoid the error path here by avoiding decompressing, /// either locally here, or more likely by decompressing /// `VRFPreOut` in deserialization. - pub fn shorten_vrf( &self, public: &PublicKey, t: T, out: &VRFPreOut) - -> SignatureResult - where T: VRFSigningTranscript, + pub fn shorten_vrf( + &self, + public: &PublicKey, + t: T, + out: &VRFPreOut, + ) -> SignatureResult + where + T: VRFSigningTranscript, { - let p = out.attach_input_hash(public,t) ?; // Avoidable errors if decompressed earlier - let t0 = Transcript::new(b"VRF"); // We have context in t and another hear confuses batching + let p = out.attach_input_hash(public, t)?; // Avoidable errors if decompressed earlier + let t0 = Transcript::new(b"VRF"); // We have context in t and another hear confuses batching Ok(self.shorten_dleq(t0, public, &p, KUSAMA_VRF)) } } @@ -624,6 +645,7 @@ impl Keypair { /// using one of the `vrf_create_*` methods on `SecretKey`. /// If so, we produce a proof that this multiplication was done correctly. #[allow(non_snake_case)] + #[rustfmt::skip] pub fn dleq_proove(&self, mut t: T, p: &VRFInOut, kusama: bool) -> (VRFProof, VRFProofBatchable) where T: SigningTranscript, @@ -660,26 +682,27 @@ impl Keypair { /// VRFs repeatedly until they win some contest. In these case, /// you should probably use vrf_sign_n_check to gain access to the /// `VRFInOut` from `vrf_create_hash` first, and then avoid computing - /// the proof whenever you do not win. + /// the proof whenever you do not win. pub fn vrf_sign(&self, t: T) -> (VRFInOut, VRFProof, VRFProofBatchable) - where T: VRFSigningTranscript, + where + T: VRFSigningTranscript, { - self.vrf_sign_extra(t,Transcript::new(b"VRF")) + self.vrf_sign_extra(t, Transcript::new(b"VRF")) // We have context in t and another hear confuses batching } - /// Run VRF on one single input transcript and an extra message transcript, + /// Run VRF on one single input transcript and an extra message transcript, /// producing the outpus and corresponding short proof. - pub fn vrf_sign_extra(&self, t: T, extra: E) -> (VRFInOut, VRFProof, VRFProofBatchable) - where T: VRFSigningTranscript, - E: SigningTranscript, + pub fn vrf_sign_extra(&self, t: T, extra: E) -> (VRFInOut, VRFProof, VRFProofBatchable) + where + T: VRFSigningTranscript, + E: SigningTranscript, { let p = self.vrf_create_hash(t); let (proof, proof_batchable) = self.dleq_proove(extra, &p, KUSAMA_VRF); (p, proof, proof_batchable) } - /// Run VRF on one single input transcript, producing the outpus /// and corresponding short proof only if the result first passes /// some check. @@ -688,27 +711,39 @@ impl Keypair { /// VRFs repeatedly until they win some contest. In these case, /// you might use this function to short circuit computing the full /// proof. - pub fn vrf_sign_after_check(&self, t: T, mut check: F) - -> Option<(VRFInOut, VRFProof, VRFProofBatchable)> - where T: VRFSigningTranscript, - F: FnMut(&VRFInOut) -> bool, + pub fn vrf_sign_after_check( + &self, + t: T, + mut check: F, + ) -> Option<(VRFInOut, VRFProof, VRFProofBatchable)> + where + T: VRFSigningTranscript, + F: FnMut(&VRFInOut) -> bool, { - self.vrf_sign_extra_after_check(t, - |io| if check(io) { Some(Transcript::new(b"VRF")) } else { None } - ) + self.vrf_sign_extra_after_check(t, |io| { + if check(io) { + Some(Transcript::new(b"VRF")) + } else { + None + } + }) } /// Run VRF on one single input transcript, producing the outpus /// and corresponding short proof only if the result first passes /// some check, which itself returns an extra message transcript. - pub fn vrf_sign_extra_after_check(&self, t: T, mut check: F) - -> Option<(VRFInOut, VRFProof, VRFProofBatchable)> - where T: VRFSigningTranscript, - E: SigningTranscript, - F: FnMut(&VRFInOut) -> Option, + pub fn vrf_sign_extra_after_check( + &self, + t: T, + mut check: F, + ) -> Option<(VRFInOut, VRFProof, VRFProofBatchable)> + where + T: VRFSigningTranscript, + E: SigningTranscript, + F: FnMut(&VRFInOut) -> Option, { let p = self.vrf_create_hash(t); - let extra = check(&p) ?; + let extra = check(&p)?; let (proof, proof_batchable) = self.dleq_proove(extra, &p, KUSAMA_VRF); Some((p, proof, proof_batchable)) } @@ -735,16 +770,18 @@ impl Keypair { /// if even the hash of the message being signed is sensitive then /// you might reimplement some constant time variant. #[cfg(feature = "alloc")] - pub fn vrfs_sign_extra(&self, ts: I, extra: E) -> (Box<[VRFInOut]>, VRFProof, VRFProofBatchable) + pub fn vrfs_sign_extra( + &self, + ts: I, + extra: E, + ) -> (Box<[VRFInOut]>, VRFProof, VRFProofBatchable) where T: VRFSigningTranscript, E: SigningTranscript, I: IntoIterator, { - let ps = ts.into_iter() - .map(|t| self.vrf_create_hash(t)) - .collect::>(); - let p = self.public.vrfs_merge(&ps,true); + let ps = ts.into_iter().map(|t| self.vrf_create_hash(t)).collect::>(); + let p = self.public.vrfs_merge(&ps, true); let (proof, proof_batchable) = self.dleq_proove(extra, &p, KUSAMA_VRF); (ps.into_boxed_slice(), proof, proof_batchable) } @@ -762,6 +799,7 @@ impl PublicKey { /// risk the same flaws as DLEQ based blind signatures, and this /// version exploits the slightly faster basepoint arithmetic. #[allow(non_snake_case)] + #[rustfmt::skip] pub fn dleq_verify( &self, mut t: T, @@ -820,28 +858,29 @@ impl PublicKey { out: &VRFPreOut, proof: &VRFProof, ) -> SignatureResult<(VRFInOut, VRFProofBatchable)> { - self.vrf_verify_extra(t,out,proof,Transcript::new(b"VRF")) + self.vrf_verify_extra(t, out, proof, Transcript::new(b"VRF")) } /// Verify VRF proof for one single input transcript and corresponding output. - pub fn vrf_verify_extra( + pub fn vrf_verify_extra( &self, t: T, out: &VRFPreOut, proof: &VRFProof, extra: E, - ) -> SignatureResult<(VRFInOut, VRFProofBatchable)> - where T: VRFSigningTranscript, - E: SigningTranscript, + ) -> SignatureResult<(VRFInOut, VRFProofBatchable)> + where + T: VRFSigningTranscript, + E: SigningTranscript, { - let p = out.attach_input_hash(self,t)?; + let p = out.attach_input_hash(self, t)?; let proof_batchable = self.dleq_verify(extra, &p, proof, KUSAMA_VRF)?; Ok((p, proof_batchable)) } /// Verify a common VRF short proof for several input transcripts and corresponding outputs. #[cfg(feature = "alloc")] - pub fn vrfs_verify( + pub fn vrfs_verify( &self, transcripts: I, outs: &[O], @@ -852,11 +891,12 @@ impl PublicKey { I: IntoIterator, O: Borrow, { - self.vrfs_verify_extra(transcripts,outs,proof,Transcript::new(b"VRF")) + self.vrfs_verify_extra(transcripts, outs, proof, Transcript::new(b"VRF")) } /// Verify a common VRF short proof for several input transcripts and corresponding outputs. #[cfg(feature = "alloc")] + #[rustfmt::skip] pub fn vrfs_verify_extra( &self, transcripts: I, @@ -903,6 +943,7 @@ impl PublicKey { /// separate calls. #[cfg(feature = "alloc")] #[allow(non_snake_case)] +#[rustfmt::skip] pub fn dleq_verify_batch( ps: &[VRFInOut], proofs: &[VRFProofBatchable], @@ -984,16 +1025,14 @@ where I: IntoIterator, { let mut ts = transcripts.into_iter(); - let ps = ts.by_ref() + let ps = ts + .by_ref() .zip(publickeys) .zip(outs) - .map(|((t, pk), out)| out.attach_input_hash(pk,t)) + .map(|((t, pk), out)| out.attach_input_hash(pk, t)) .collect::>>()?; assert!(ts.next().is_none(), "Too few VRF outputs for VRF inputs."); - assert!( - ps.len() == outs.len(), - "Too few VRF inputs for VRF outputs." - ); + assert!(ps.len() == outs.len(), "Too few VRF inputs for VRF outputs."); if dleq_verify_batch(&ps[..], proofs, publickeys, KUSAMA_VRF).is_ok() { Ok(ps.into_boxed_slice()) } else { @@ -1021,26 +1060,19 @@ mod tests { let out1 = &io1.to_preout(); assert_eq!( proof1, - proof1batchable - .shorten_vrf(&keypair1.public, ctx.bytes(msg), &out1) - .unwrap(), + proof1batchable.shorten_vrf(&keypair1.public, ctx.bytes(msg), &out1).unwrap(), "Oops `shorten_vrf` failed" ); - let (io1too, proof1too) = keypair1.public.vrf_verify(ctx.bytes(msg), &out1, &proof1) + let (io1too, proof1too) = keypair1 + .public + .vrf_verify(ctx.bytes(msg), &out1, &proof1) .expect("Correct VRF verification failed!"); - assert_eq!( - io1too, io1, - "Output differs between signing and verification!" - ); + assert_eq!(io1too, io1, "Output differs between signing and verification!"); assert_eq!( proof1batchable, proof1too, "VRF verification yielded incorrect batchable proof" ); - assert_eq!( - keypair1.vrf_sign(ctx.bytes(msg)).0, - io1, - "Rerunning VRF gave different output" - ); + assert_eq!(keypair1.vrf_sign(ctx.bytes(msg)).0, io1, "Rerunning VRF gave different output"); assert!( keypair1.public.vrf_verify(ctx.bytes(b"not meow"), &out1, &proof1).is_err(), @@ -1067,16 +1099,16 @@ mod tests { let out1 = &io1.to_preout(); assert_eq!( proof1, - proof1batchable.shorten_vrf(&keypair1.public, Malleable(ctx.bytes(msg)), &out1).unwrap(), + proof1batchable + .shorten_vrf(&keypair1.public, Malleable(ctx.bytes(msg)), &out1) + .unwrap(), "Oops `shorten_vrf` failed" ); let (io1too, proof1too) = keypair1 - .public.vrf_verify(Malleable(ctx.bytes(msg)), &out1, &proof1) + .public + .vrf_verify(Malleable(ctx.bytes(msg)), &out1, &proof1) .expect("Correct VRF verification failed!"); - assert_eq!( - io1too, io1, - "Output differs between signing and verification!" - ); + assert_eq!(io1too, io1, "Output differs between signing and verification!"); assert_eq!( proof1batchable, proof1too, "VRF verification yielded incorrect batchable proof" @@ -1087,7 +1119,10 @@ mod tests { "Rerunning VRF gave different output" ); assert!( - keypair1.public.vrf_verify(Malleable(ctx.bytes(b"not meow")), &out1, &proof1).is_err(), + keypair1 + .public + .vrf_verify(Malleable(ctx.bytes(b"not meow")), &out1, &proof1) + .is_err(), "VRF verification with incorrect message passed!" ); @@ -1116,23 +1151,15 @@ mod tests { proofs12.1.shorten_dleq(t0.clone(), &keypair1.public, &io12, KUSAMA_VRF), "Oops `shorten_dleq` failed" ); - assert!(keypair1 - .public - .dleq_verify(t0.clone(), &io12, &proofs12.0, KUSAMA_VRF) - .is_ok()); - assert!(keypair2 - .public - .dleq_verify(t0.clone(), &io21, &proofs21.0, KUSAMA_VRF) - .is_ok()); + assert!(keypair1.public.dleq_verify(t0.clone(), &io12, &proofs12.0, KUSAMA_VRF).is_ok()); + assert!(keypair2.public.dleq_verify(t0.clone(), &io21, &proofs21.0, KUSAMA_VRF).is_ok()); } #[cfg(feature = "alloc")] #[test] fn vrfs_merged_and_batched() { let mut csprng = rand_core::OsRng; - let keypairs: Vec = (0..4) - .map(|_| Keypair::generate_with(&mut csprng)) - .collect(); + let keypairs: Vec = (0..4).map(|_| Keypair::generate_with(&mut csprng)).collect(); let ctx = signing_context(b"yo!"); let messages: [&[u8; 4]; 2] = [b"meow", b"woof"]; @@ -1145,52 +1172,42 @@ mod tests { )>>(); for (k, (ios, proof, proof_batchable)) in keypairs.iter().zip(&ios_n_proofs) { - let outs = ios - .iter() - .map(|io| io.to_preout()) - .collect::>(); + let outs = ios.iter().map(|io| io.to_preout()).collect::>(); let (ios_too, proof_too) = k .public .vrfs_verify(ts(), &outs, &proof) .expect("Valid VRF output verification failed!"); - assert_eq!( - ios_too, *ios, - "Output differs between signing and verification!" - ); - assert_eq!( - proof_too, *proof_batchable, - "Returning batchable proof failed!" - ); + assert_eq!(ios_too, *ios, "Output differs between signing and verification!"); + assert_eq!(proof_too, *proof_batchable, "Returning batchable proof failed!"); } for (k, (ios, proof, _proof_batchable)) in keypairs.iter().zip(&ios_n_proofs) { - let outs = ios.iter() - .rev() - .map(|io| io.to_preout()) - .collect::>(); + let outs = ios.iter().rev().map(|io| io.to_preout()).collect::>(); assert!( k.public.vrfs_verify(ts(), &outs, &proof).is_err(), "Incorrect VRF output verification passed!" ); } for (k, (ios, proof, _proof_batchable)) in keypairs.iter().rev().zip(&ios_n_proofs) { - let outs = ios.iter() - .map(|io| io.to_preout()) - .collect::>(); + let outs = ios.iter().map(|io| io.to_preout()).collect::>(); assert!( k.public.vrfs_verify(ts(), &outs, &proof).is_err(), "VRF output verification by a different signer passed!" ); } - let mut ios = keypairs.iter().enumerate() - .map(|(i, keypair)| keypair.public.vrfs_merge(&ios_n_proofs[i].0,true)) + let mut ios = keypairs + .iter() + .enumerate() + .map(|(i, keypair)| keypair.public.vrfs_merge(&ios_n_proofs[i].0, true)) .collect::>(); - let mut proofs = ios_n_proofs.iter() + let mut proofs = ios_n_proofs + .iter() .map(|(_ios, _proof, proof_batchable)| proof_batchable.clone()) .collect::>(); - let mut public_keys = keypairs.iter() + let mut public_keys = keypairs + .iter() .map(|keypair| keypair.public.clone()) .collect::>();