From 5eb61f3a8778766f520887529e0ad1efa608d1c6 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Tue, 12 Jul 2022 01:28:01 -0400 Subject: [PATCH] Fix https://github.com/serai-dex/serai/issues/14. --- coins/monero/src/frost.rs | 25 ++-- coins/monero/src/ringct/clsag/multisig.rs | 66 ++++------ coins/monero/src/tests/clsag.rs | 3 +- coins/monero/src/wallet/send/multisig.rs | 12 +- coins/monero/tests/send.rs | 2 +- crypto/frost/Cargo.toml | 2 + crypto/frost/src/algorithm.rs | 43 ++++--- crypto/frost/src/sign.rs | 147 ++++++++++++++++------ crypto/frost/src/tests/vectors.rs | 2 +- crypto/transcript/src/lib.rs | 15 ++- crypto/transcript/src/merlin.rs | 4 + 11 files changed, 198 insertions(+), 123 deletions(-) diff --git a/coins/monero/src/frost.rs b/coins/monero/src/frost.rs index ea36be25..82b61a65 100644 --- a/coins/monero/src/frost.rs +++ b/coins/monero/src/frost.rs @@ -7,7 +7,7 @@ use curve25519_dalek::{scalar::Scalar, edwards::EdwardsPoint}; use group::{Group, GroupEncoding}; -use transcript::RecommendedTranscript; +use transcript::{Transcript, RecommendedTranscript}; use dalek_ff_group as dfg; use dleq::{Generators, DLEqProof}; @@ -21,6 +21,10 @@ pub enum MultisigError { InvalidKeyImage(u16) } +fn transcript() -> RecommendedTranscript { + RecommendedTranscript::new(b"monero_key_image_dleq") +} + #[allow(non_snake_case)] pub(crate) fn write_dleq( rng: &mut R, @@ -35,7 +39,7 @@ pub(crate) fn write_dleq( // the proper order if they want to reach consensus // It'd be a poor API to have CLSAG define a new transcript solely to pass here, just to try to // merge later in some form, when it should instead just merge xH (as it does) - &mut RecommendedTranscript::new(b"DLEq Proof"), + &mut transcript(), Generators::new(dfg::EdwardsPoint::generator(), dfg::EdwardsPoint(H)), dfg::Scalar(x) ).serialize(&mut res).unwrap(); @@ -45,16 +49,15 @@ pub(crate) fn write_dleq( #[allow(non_snake_case)] pub(crate) fn read_dleq( serialized: &[u8], - start: usize, H: EdwardsPoint, l: u16, xG: dfg::EdwardsPoint ) -> Result { - if serialized.len() < start + 96 { + if serialized.len() != 96 { Err(MultisigError::InvalidDLEqProof(l))?; } - let bytes = (&serialized[(start + 0) .. (start + 32)]).try_into().unwrap(); + let bytes = (&serialized[.. 32]).try_into().unwrap(); // dfg ensures the point is torsion free let xH = Option::::from( dfg::EdwardsPoint::from_bytes(&bytes)).ok_or(MultisigError::InvalidDLEqProof(l) @@ -64,13 +67,13 @@ pub(crate) fn read_dleq( Err(MultisigError::InvalidDLEqProof(l))?; } - let proof = DLEqProof::::deserialize( - &mut Cursor::new(&serialized[(start + 32) .. (start + 96)]) + DLEqProof::::deserialize( + &mut Cursor::new(&serialized[32 ..]) + ).map_err(|_| MultisigError::InvalidDLEqProof(l))?.verify( + &mut transcript(), + Generators::new(dfg::EdwardsPoint::generator(), dfg::EdwardsPoint(H)), + (xG, xH) ).map_err(|_| MultisigError::InvalidDLEqProof(l))?; - let mut transcript = RecommendedTranscript::new(b"DLEq Proof"); - proof.verify(&mut transcript, Generators::new(dfg::EdwardsPoint::generator(), dfg::EdwardsPoint(H)), (xG, xH)) - .map_err(|_| MultisigError::InvalidDLEqProof(l))?; - Ok(xH) } diff --git a/coins/monero/src/ringct/clsag/multisig.rs b/coins/monero/src/ringct/clsag/multisig.rs index 8365ff6d..5d5748ea 100644 --- a/coins/monero/src/ringct/clsag/multisig.rs +++ b/coins/monero/src/ringct/clsag/multisig.rs @@ -6,7 +6,7 @@ use rand_chacha::ChaCha12Rng; use curve25519_dalek::{ constants::ED25519_BASEPOINT_TABLE, - traits::Identity, + traits::{Identity, IsIdentity}, scalar::Scalar, edwards::EdwardsPoint }; @@ -76,7 +76,6 @@ pub struct ClsagMultisig { H: EdwardsPoint, // Merged here as CLSAG needs it, passing it would be a mess, yet having it beforehand requires a round image: EdwardsPoint, - AH: (dfg::EdwardsPoint, dfg::EdwardsPoint), details: Arc>>, @@ -87,15 +86,15 @@ pub struct ClsagMultisig { impl ClsagMultisig { pub fn new( transcript: RecommendedTranscript, + output_key: EdwardsPoint, details: Arc>> ) -> Result { Ok( ClsagMultisig { transcript, - H: EdwardsPoint::identity(), + H: hash_to_point(output_key), image: EdwardsPoint::identity(), - AH: (dfg::EdwardsPoint::identity(), dfg::EdwardsPoint::identity()), details, @@ -106,7 +105,7 @@ impl ClsagMultisig { } pub fn serialized_len() -> usize { - 3 * (32 + 64) + 32 + (2 * 32) } fn input(&self) -> ClsagInput { @@ -122,22 +121,18 @@ impl Algorithm for ClsagMultisig { type Transcript = RecommendedTranscript; type Signature = (Clsag, EdwardsPoint); + fn nonces(&self) -> Vec> { + vec![vec![dfg::EdwardsPoint::generator(), dfg::EdwardsPoint(self.H)]] + } + fn preprocess_addendum( &mut self, rng: &mut R, - view: &FrostView, - nonces: &[dfg::Scalar; 2] + view: &FrostView ) -> Vec { - self.H = hash_to_point(view.group_key().0); - - let mut serialized = Vec::with_capacity(ClsagMultisig::serialized_len()); + let mut serialized = Vec::with_capacity(Self::serialized_len()); serialized.extend((view.secret_share().0 * self.H).compress().to_bytes()); serialized.extend(write_dleq(rng, self.H, view.secret_share().0)); - - serialized.extend((nonces[0].0 * self.H).compress().to_bytes()); - serialized.extend(write_dleq(rng, self.H, nonces[0].0)); - serialized.extend((nonces[1].0 * self.H).compress().to_bytes()); - serialized.extend(write_dleq(rng, self.H, nonces[1].0)); serialized } @@ -145,42 +140,27 @@ impl Algorithm for ClsagMultisig { &mut self, view: &FrostView, l: u16, - commitments: &[dfg::EdwardsPoint; 2], serialized: &[u8] ) -> Result<(), FrostError> { - if serialized.len() != ClsagMultisig::serialized_len() { + if serialized.len() != Self::serialized_len() { // Not an optimal error but... Err(FrostError::InvalidCommitment(l))?; } - if self.AH.0.is_identity().into() { + if self.image.is_identity().into() { self.transcript.domain_separate(b"CLSAG"); self.input().transcript(&mut self.transcript); self.transcript.append_message(b"mask", &self.mask().to_bytes()); } - // Uses the same format FROST does for the expected commitments (nonce * G where this is nonce * H) - // The following technically shouldn't need to be committed to, as we've committed to equivalents, - // yet it doesn't hurt and may resolve some unknown issues self.transcript.append_message(b"participant", &l.to_be_bytes()); - - let mut cursor = 0; - self.transcript.append_message(b"image_share", &serialized[cursor .. (cursor + 32)]); + self.transcript.append_message(b"key_image_share", &serialized[.. 32]); self.image += read_dleq( serialized, - cursor, self.H, l, view.verification_share(l) ).map_err(|_| FrostError::InvalidCommitment(l))?.0; - cursor += 96; - - self.transcript.append_message(b"commitment_D_H", &serialized[cursor .. (cursor + 32)]); - self.AH.0 += read_dleq(serialized, cursor, self.H, l, commitments[0]).map_err(|_| FrostError::InvalidCommitment(l))?; - cursor += 96; - - self.transcript.append_message(b"commitment_E_H", &serialized[cursor .. (cursor + 32)]); - self.AH.1 += read_dleq(serialized, cursor, self.H, l, commitments[1]).map_err(|_| FrostError::InvalidCommitment(l))?; Ok(()) } @@ -192,14 +172,10 @@ impl Algorithm for ClsagMultisig { fn sign_share( &mut self, view: &FrostView, - nonce_sum: dfg::EdwardsPoint, - b: dfg::Scalar, - nonce: dfg::Scalar, + nonce_sums: &[Vec], + nonces: &[dfg::Scalar], msg: &[u8] ) -> dfg::Scalar { - // Apply the binding factor to the H variant of the nonce - self.AH.0 += self.AH.1 * b; - // Use the transcript to get a seeded random number generator // The transcript contains private data, preventing passive adversaries from recreating this // process even if they have access to commitments (specifically, the ring index being signed @@ -216,12 +192,12 @@ impl Algorithm for ClsagMultisig { &self.input(), self.mask(), &self.msg.as_ref().unwrap(), - nonce_sum.0, - self.AH.0.0 + nonce_sums[0][0].0, + nonce_sums[0][1].0 ); self.interim = Some(Interim { p, c, clsag, pseudo_out }); - let share = dfg::Scalar(nonce.0 - (p * view.secret_share().0)); + let share = dfg::Scalar(nonces[0].0 - (p * view.secret_share().0)); share } @@ -230,7 +206,7 @@ impl Algorithm for ClsagMultisig { fn verify( &self, _: dfg::EdwardsPoint, - _: dfg::EdwardsPoint, + _: &[Vec], sum: dfg::Scalar ) -> Option { let interim = self.interim.as_ref().unwrap(); @@ -251,12 +227,12 @@ impl Algorithm for ClsagMultisig { fn verify_share( &self, verification_share: dfg::EdwardsPoint, - nonce: dfg::EdwardsPoint, + nonces: &[Vec], share: dfg::Scalar, ) -> bool { let interim = self.interim.as_ref().unwrap(); return (&share.0 * &ED25519_BASEPOINT_TABLE) == ( - nonce.0 - (interim.p * verification_share.0) + nonces[0][0].0 - (interim.p * verification_share.0) ); } } diff --git a/coins/monero/src/tests/clsag.rs b/coins/monero/src/tests/clsag.rs index 17cb9940..d48d4c4a 100644 --- a/coins/monero/src/tests/clsag.rs +++ b/coins/monero/src/tests/clsag.rs @@ -6,7 +6,7 @@ use rand::{RngCore, rngs::OsRng}; use curve25519_dalek::{constants::ED25519_BASEPOINT_TABLE, scalar::Scalar}; #[cfg(feature = "multisig")] -use transcript::RecommendedTranscript; +use transcript::{Transcript, RecommendedTranscript}; #[cfg(feature = "multisig")] use frost::curve::Ed25519; @@ -102,6 +102,7 @@ fn clsag_multisig() -> Result<(), MultisigError> { &mut OsRng, ClsagMultisig::new( RecommendedTranscript::new(b"Monero Serai CLSAG Test"), + keys[&1].group_key().0, Arc::new(RwLock::new(Some( ClsagDetails::new( ClsagInput::new( diff --git a/coins/monero/src/wallet/send/multisig.rs b/coins/monero/src/wallet/send/multisig.rs index 1bf30d96..33dee744 100644 --- a/coins/monero/src/wallet/send/multisig.rs +++ b/coins/monero/src/wallet/send/multisig.rs @@ -112,6 +112,7 @@ impl SignableTransaction { AlgorithmMachine::new( ClsagMultisig::new( transcript.clone(), + input.key, inputs[i].clone() ).map_err(|e| TransactionError::MultisigError(e))?, Arc::new(offset), @@ -159,7 +160,10 @@ impl PreprocessMachine for TransactionMachine { rng: &mut R ) -> (TransactionSignMachine, Vec) { // Iterate over each CLSAG calling preprocess - let mut serialized = Vec::with_capacity(self.clsags.len() * (64 + ClsagMultisig::serialized_len())); + let mut serialized = Vec::with_capacity( + // D_{G, H}, E_{G, H}, DLEqs, key image addendum + self.clsags.len() * ((2 * (32 + 32)) + (2 * (32 + 32)) + ClsagMultisig::serialized_len()) + ); let clsags = self.clsags.drain(..).map(|clsag| { let (clsag, preprocess) = clsag.preprocess(rng); serialized.extend(&preprocess); @@ -224,8 +228,8 @@ impl SignMachine for TransactionSignMachine { } } - // FROST commitments, image, H commitments, and their proofs - let clsag_len = 64 + ClsagMultisig::serialized_len(); + // FROST commitments and their DLEqs, and the image and its DLEq + let clsag_len = (2 * (32 + 32)) + (2 * (32 + 32)) + ClsagMultisig::serialized_len(); for (l, commitments) in &commitments { if commitments.len() != (self.clsags.len() * clsag_len) { Err(FrostError::InvalidCommitment(*l))?; @@ -246,7 +250,7 @@ impl SignMachine for TransactionSignMachine { for c in 0 .. self.clsags.len() { for (l, preprocess) in &commitments[c] { images[c] += CompressedEdwardsY( - preprocess[64 .. 96].try_into().map_err(|_| FrostError::InvalidCommitment(*l))? + preprocess[(clsag_len - 96) .. (clsag_len - 64)].try_into().map_err(|_| FrostError::InvalidCommitment(*l))? ).decompress().ok_or(FrostError::InvalidCommitment(*l))?; } } diff --git a/coins/monero/tests/send.rs b/coins/monero/tests/send.rs index c875a023..44b68a7a 100644 --- a/coins/monero/tests/send.rs +++ b/coins/monero/tests/send.rs @@ -14,7 +14,7 @@ use curve25519_dalek::constants::ED25519_BASEPOINT_TABLE; #[cfg(feature = "multisig")] use dalek_ff_group::Scalar; #[cfg(feature = "multisig")] -use transcript::RecommendedTranscript; +use transcript::{Transcript, RecommendedTranscript}; #[cfg(feature = "multisig")] use frost::{curve::Ed25519, tests::{THRESHOLD, key_gen, sign}}; diff --git a/crypto/frost/Cargo.toml b/crypto/frost/Cargo.toml index 436c3966..e68c166b 100644 --- a/crypto/frost/Cargo.toml +++ b/crypto/frost/Cargo.toml @@ -28,6 +28,8 @@ transcript = { package = "flexible-transcript", path = "../transcript", version multiexp = { path = "../multiexp", version = "0.1", features = ["batch"] } +dleq = { package = "dleq", path = "../dleq", version = "0.1", features = ["serialize"] } + [dev-dependencies] rand = "0.8" diff --git a/crypto/frost/src/algorithm.rs b/crypto/frost/src/algorithm.rs index a85bab11..12f48e52 100644 --- a/crypto/frost/src/algorithm.rs +++ b/crypto/frost/src/algorithm.rs @@ -13,14 +13,18 @@ pub trait Algorithm: Clone { /// The resulting type of the signatures this algorithm will produce type Signature: Clone + PartialEq + Debug; + /// Obtain a mutable borrow of the underlying transcript fn transcript(&mut self) -> &mut Self::Transcript; + /// Obtain the list of nonces to generate, as specified by the basepoints to create commitments + /// against per-nonce. These are not committed to by FROST on the underlying transcript + fn nonces(&self) -> Vec>; + /// Generate an addendum to FROST"s preprocessing stage fn preprocess_addendum( &mut self, rng: &mut R, params: &FrostView, - nonces: &[C::F; 2], ) -> Vec; /// Proccess the addendum for the specified participant. Guaranteed to be ordered @@ -28,7 +32,6 @@ pub trait Algorithm: Clone { &mut self, params: &FrostView, l: u16, - commitments: &[C::G; 2], serialized: &[u8], ) -> Result<(), FrostError>; @@ -39,15 +42,14 @@ pub trait Algorithm: Clone { fn sign_share( &mut self, params: &FrostView, - nonce_sum: C::G, - binding: C::F, - nonce: C::F, + nonce_sums: &[Vec], + nonces: &[C::F], msg: &[u8], ) -> C::F; /// Verify a signature #[must_use] - fn verify(&self, group_key: C::G, nonce: C::G, sum: C::F) -> Option; + fn verify(&self, group_key: C::G, nonces: &[Vec], sum: C::F) -> Option; /// Verify a specific share given as a response. Used to determine blame if signature /// verification fails @@ -55,7 +57,7 @@ pub trait Algorithm: Clone { fn verify_share( &self, verification_share: C::G, - nonce: C::G, + nonces: &[Vec], share: C::F, ) -> bool; } @@ -66,6 +68,10 @@ pub struct IetfTranscript(Vec); impl Transcript for IetfTranscript { type Challenge = Vec; + fn new(_: &'static [u8]) -> IetfTranscript { + unimplemented!("IetfTranscript should not be used with multiple nonce protocols"); + } + fn domain_separate(&mut self, _: &[u8]) {} fn append_message(&mut self, _: &'static [u8], message: &[u8]) { @@ -115,11 +121,14 @@ impl> Algorithm for Schnorr { &mut self.transcript } + fn nonces(&self) -> Vec> { + vec![vec![C::GENERATOR]] + } + fn preprocess_addendum( &mut self, _: &mut R, _: &FrostView, - _: &[C::F; 2], ) -> Vec { vec![] } @@ -128,7 +137,6 @@ impl> Algorithm for Schnorr { &mut self, _: &FrostView, _: u16, - _: &[C::G; 2], _: &[u8], ) -> Result<(), FrostError> { Ok(()) @@ -137,19 +145,18 @@ impl> Algorithm for Schnorr { fn sign_share( &mut self, params: &FrostView, - nonce_sum: C::G, - _: C::F, - nonce: C::F, + nonce_sums: &[Vec], + nonces: &[C::F], msg: &[u8], ) -> C::F { - let c = H::hram(&nonce_sum, ¶ms.group_key(), msg); + let c = H::hram(&nonce_sums[0][0], ¶ms.group_key(), msg); self.c = Some(c); - schnorr::sign::(params.secret_share(), nonce, c).s + schnorr::sign::(params.secret_share(), nonces[0], c).s } #[must_use] - fn verify(&self, group_key: C::G, nonce: C::G, sum: C::F) -> Option { - let sig = SchnorrSignature { R: nonce, s: sum }; + fn verify(&self, group_key: C::G, nonces: &[Vec], sum: C::F) -> Option { + let sig = SchnorrSignature { R: nonces[0][0], s: sum }; if schnorr::verify::(group_key, self.c.unwrap(), &sig) { Some(sig) } else { @@ -161,13 +168,13 @@ impl> Algorithm for Schnorr { fn verify_share( &self, verification_share: C::G, - nonce: C::G, + nonces: &[Vec], share: C::F, ) -> bool { schnorr::verify::( verification_share, self.c.unwrap(), - &SchnorrSignature { R: nonce, s: share} + &SchnorrSignature { R: nonces[0][0], s: share} ) } } diff --git a/crypto/frost/src/sign.rs b/crypto/frost/src/sign.rs index 057ddc47..eab8a035 100644 --- a/crypto/frost/src/sign.rs +++ b/crypto/frost/src/sign.rs @@ -3,12 +3,14 @@ use std::{sync::Arc, collections::HashMap}; use rand_core::{RngCore, CryptoRng}; -use group::{ff::{Field, PrimeField}, GroupEncoding}; +use group::{ff::{Field, PrimeField}, Group, GroupEncoding}; use transcript::Transcript; +use dleq::{Generators, DLEqProof}; + use crate::{ - curve::{Curve, G_len, F_from_slice, G_from_slice}, + curve::{Curve, F_len, G_len, F_from_slice, G_from_slice}, FrostError, FrostParams, FrostKeys, FrostView, algorithm::Algorithm, @@ -69,8 +71,12 @@ impl> Params { } } +fn nonce_transcript() -> T { + T::new(b"FROST_nonce_dleq") +} + pub(crate) struct PreprocessPackage { - pub(crate) nonces: [C::F; 2], + pub(crate) nonces: Vec<[C::F; 2]>, pub(crate) serialized: Vec, } @@ -80,30 +86,53 @@ fn preprocess>( rng: &mut R, params: &mut Params, ) -> PreprocessPackage { - let nonces = [ - C::random_nonce(params.view().secret_share(), &mut *rng), - C::random_nonce(params.view().secret_share(), &mut *rng) - ]; - let commitments = [C::GENERATOR * nonces[0], C::GENERATOR * nonces[1]]; - let mut serialized = commitments[0].to_bytes().as_ref().to_vec(); - serialized.extend(commitments[1].to_bytes().as_ref()); + let mut serialized = Vec::with_capacity(2 * G_len::()); + let nonces = params.algorithm.nonces().iter().cloned().map( + |mut generators| { + let nonces = [ + C::random_nonce(params.view().secret_share(), &mut *rng), + C::random_nonce(params.view().secret_share(), &mut *rng) + ]; - serialized.extend( - ¶ms.algorithm.preprocess_addendum( - rng, - ¶ms.view, - &nonces - ) - ); + let commit = |generator: C::G| { + let commitments = [generator * nonces[0], generator * nonces[1]]; + [commitments[0].to_bytes().as_ref(), commitments[1].to_bytes().as_ref()].concat().to_vec() + }; + + let first = generators.remove(0); + serialized.extend(commit(first)); + + // Iterate over the rest + for generator in generators.iter() { + serialized.extend(commit(*generator)); + // Provide a DLEq to verify these commitments are for the same nonce + // TODO: Provide a single DLEq. See https://github.com/serai-dex/serai/issues/34 + for nonce in nonces { + DLEqProof::prove( + &mut *rng, + // Uses an independent transcript as each signer must do this now, yet we validate them + // sequentially by the global order. Avoids needing to clone the transcript around + &mut nonce_transcript::(), + Generators::new(first, *generator), + nonce + ).serialize(&mut serialized).unwrap(); + } + } + + nonces + } + ).collect::>(); + + serialized.extend(¶ms.algorithm.preprocess_addendum(rng, ¶ms.view)); PreprocessPackage { nonces, serialized } } #[allow(non_snake_case)] struct Package { - B: HashMap, + B: HashMap>>, binding: C::F, - R: C::G, + Rs: Vec>, share: Vec } @@ -137,27 +166,59 @@ fn sign_with_share>( let mut B = HashMap::::with_capacity(params.view.included.len()); // Get the binding factor + let nonces = params.algorithm.nonces(); let mut addendums = HashMap::new(); let binding = { let transcript = params.algorithm.transcript(); // Parse the commitments for l in ¶ms.view.included { transcript.append_message(b"participant", &l.to_be_bytes()); + let serialized = commitments.remove(l).unwrap(); - let commitments = commitments.remove(l).unwrap(); let mut read_commitment = |c, label| { - let commitment = &commitments[c .. (c + G_len::())]; + let commitment = &serialized[c .. (c + G_len::())]; transcript.append_message(label, commitment); G_from_slice::(commitment).map_err(|_| FrostError::InvalidCommitment(*l)) }; + // While this doesn't note which nonce/basepoint this is for, those are expected to be + // static. Beyond that, they're committed to in the DLEq proof transcripts, ensuring + // consistency. While this is suboptimal, it maintains IETF compliance, and Algorithm is + // documented accordingly #[allow(non_snake_case)] - let mut read_D_E = || Ok( - [read_commitment(0, b"commitment_D")?, read_commitment(G_len::(), b"commitment_E")?] - ); + let mut read_D_E = |c| Ok([ + read_commitment(c, b"commitment_D")?, + read_commitment(c + G_len::(), b"commitment_E")? + ]); - B.insert(*l, read_D_E()?); - addendums.insert(*l, commitments[(G_len::() * 2) ..].to_vec()); + let mut c = 0; + let mut commitments = Vec::with_capacity(nonces.len()); + for (n, nonce_generators) in nonces.clone().iter_mut().enumerate() { + commitments.push(Vec::with_capacity(nonce_generators.len())); + + let first = nonce_generators.remove(0); + commitments[n].push(read_D_E(c)?); + c += 2 * G_len::(); + + let mut c = 2 * G_len::(); + for generator in nonce_generators { + commitments[n].push(read_D_E(c)?); + c += 2 * G_len::(); + for de in 0 .. 2 { + DLEqProof::deserialize( + &mut std::io::Cursor::new(&serialized[c .. (c + (2 * F_len::()))]) + ).map_err(|_| FrostError::InvalidCommitment(*l))?.verify( + &mut nonce_transcript::(), + Generators::new(first, *generator), + (commitments[n][0][de], commitments[n][commitments[n].len() - 1][de]) + ).map_err(|_| FrostError::InvalidCommitment(*l))?; + c += 2 * F_len::(); + } + } + + addendums.insert(*l, serialized[c ..].to_vec()); + } + B.insert(*l, commitments); } // Append the message to the transcript @@ -169,22 +230,32 @@ fn sign_with_share>( // Process the addendums for l in ¶ms.view.included { - params.algorithm.process_addendum(¶ms.view, *l, &B[l], &addendums[l])?; + params.algorithm.process_addendum(¶ms.view, *l, &addendums[l])?; } #[allow(non_snake_case)] - let R = { - B.values().map(|B| B[0]).sum::() + (B.values().map(|B| B[1]).sum::() * binding) - }; + let mut Rs = Vec::with_capacity(nonces.len()); + for n in 0 .. nonces.len() { + Rs.push(vec![C::G::identity(); nonces[n].len()]); + #[allow(non_snake_case)] + for g in 0 .. nonces[n].len() { + Rs[n][g] = { + B.values().map(|B| B[n][g][0]).sum::() + + (B.values().map(|B| B[n][g][1]).sum::() * binding) + }; + } + } + let share = params.algorithm.sign_share( ¶ms.view, - R, - binding, - our_preprocess.nonces[0] + (our_preprocess.nonces[1] * binding), + &Rs, + &our_preprocess.nonces.iter().map( + |nonces| nonces[0] + (nonces[1] * binding) + ).collect::>(), msg ).to_repr().as_ref().to_vec(); - Ok((Package { B, binding, R, share: share.clone() }, share)) + Ok((Package { B, binding, Rs, share: share.clone() }, share)) } fn complete>( @@ -206,7 +277,7 @@ fn complete>( // Perform signature validation instead of individual share validation // For the success route, which should be much more frequent, this should be faster // It also acts as an integrity check of this library's signing function - let res = sign_params.algorithm.verify(sign_params.view.group_key, sign.R, sum); + let res = sign_params.algorithm.verify(sign_params.view.group_key, &sign.Rs, sum); if let Some(res) = res { return Ok(res); } @@ -216,7 +287,11 @@ fn complete>( for l in &sign_params.view.included { if !sign_params.algorithm.verify_share( sign_params.view.verification_share(*l), - sign.B[l][0] + (sign.B[l][1] * sign.binding), + &sign.B[l].iter().map( + |nonces| nonces.iter().map( + |commitments| commitments[0] + (commitments[1] * sign.binding) + ).collect() + ).collect::>(), responses[l] ) { Err(FrostError::InvalidShare(*l))?; diff --git a/crypto/frost/src/tests/vectors.rs b/crypto/frost/src/tests/vectors.rs index 7a5d1af5..7fc2458c 100644 --- a/crypto/frost/src/tests/vectors.rs +++ b/crypto/frost/src/tests/vectors.rs @@ -105,7 +105,7 @@ pub fn test_with_vectors< serialized.extend((C::GENERATOR * nonces[1]).to_bytes().as_ref()); let (machine, serialized) = machine.unsafe_override_preprocess( - PreprocessPackage { nonces, serialized: serialized.clone() } + PreprocessPackage { nonces: vec![nonces], serialized: serialized.clone() } ); commitments.insert(i, serialized); diff --git a/crypto/transcript/src/lib.rs b/crypto/transcript/src/lib.rs index eff02b5a..72663b93 100644 --- a/crypto/transcript/src/lib.rs +++ b/crypto/transcript/src/lib.rs @@ -10,6 +10,9 @@ use digest::{typenum::type_operators::IsGreaterOrEqual, consts::U256, Digest, Ou pub trait Transcript { type Challenge: Clone + Send + Sync + AsRef<[u8]>; + /// Create a new transcript with the specified name + fn new(name: &'static [u8]) -> Self; + /// Apply a domain separator to the transcript fn domain_separate(&mut self, label: &'static [u8]); @@ -62,17 +65,17 @@ impl DigestTranscript { self.0.update(u64::try_from(value.len()).unwrap().to_le_bytes()); self.0.update(value); } - - pub fn new(name: &'static [u8]) -> Self { - let mut res = DigestTranscript(D::new()); - res.append(DigestTranscriptMember::Name, name); - res - } } impl Transcript for DigestTranscript { type Challenge = Output; + fn new(name: &'static [u8]) -> Self { + let mut res = DigestTranscript(D::new()); + res.append(DigestTranscriptMember::Name, name); + res + } + fn domain_separate(&mut self, label: &[u8]) { self.append(DigestTranscriptMember::Domain, label); } diff --git a/crypto/transcript/src/merlin.rs b/crypto/transcript/src/merlin.rs index d0c60cc9..882fea81 100644 --- a/crypto/transcript/src/merlin.rs +++ b/crypto/transcript/src/merlin.rs @@ -17,6 +17,10 @@ impl Transcript for MerlinTranscript { // this wrapper should be secure with this setting type Challenge = [u8; 64]; + fn new(name: &'static [u8]) -> Self { + MerlinTranscript(merlin::Transcript::new(name)) + } + fn domain_separate(&mut self, label: &'static [u8]) { self.append_message(b"dom-sep", label); }