From 5ede5b9e8f1d82475e97338049b120b0915a80fc Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Wed, 13 Jul 2022 23:29:48 -0400 Subject: [PATCH] Update the DLEq proof for any amount of generators The two-generator limit wasn't required nor beneficial. This does theoretically optimize FROST, yet not for any current constructions. A follow up proof which would optimize current constructions has been noted in #38. Adds explicit no_std support to the core DLEq proof. Closes #34. --- coins/monero/src/frost.rs | 8 +-- crypto/dleq/Cargo.toml | 7 ++- crypto/dleq/src/cross_group/aos.rs | 5 +- crypto/dleq/src/cross_group/bits.rs | 2 +- crypto/dleq/src/cross_group/mod.rs | 20 +++++- crypto/dleq/src/lib.rs | 79 ++++++++---------------- crypto/dleq/src/tests/cross_group/mod.rs | 3 +- crypto/dleq/src/tests/mod.rs | 46 +++++++++----- crypto/frost/src/sign.rs | 45 +++++++------- 9 files changed, 110 insertions(+), 105 deletions(-) diff --git a/coins/monero/src/frost.rs b/coins/monero/src/frost.rs index 262a782a..9b648716 100644 --- a/coins/monero/src/frost.rs +++ b/coins/monero/src/frost.rs @@ -9,7 +9,7 @@ use group::{Group, GroupEncoding}; use transcript::{Transcript, RecommendedTranscript}; use dalek_ff_group as dfg; -use dleq::{Generators, DLEqProof}; +use dleq::DLEqProof; #[derive(Clone, Error, Debug)] pub enum MultisigError { @@ -40,7 +40,7 @@ pub(crate) fn write_dleq( // 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 transcript(), - Generators::new(dfg::EdwardsPoint::generator(), dfg::EdwardsPoint(H)), + &[dfg::EdwardsPoint::generator(), dfg::EdwardsPoint(H)], dfg::Scalar(x) ).serialize(&mut res).unwrap(); res @@ -68,8 +68,8 @@ pub(crate) fn read_dleq( serialized ).map_err(|_| MultisigError::InvalidDLEqProof(l))?.verify( &mut transcript(), - Generators::new(dfg::EdwardsPoint::generator(), dfg::EdwardsPoint(H)), - (xG, xH) + &[dfg::EdwardsPoint::generator(), dfg::EdwardsPoint(H)], + &[xG, xH] ).map_err(|_| MultisigError::InvalidDLEqProof(l))?; Ok(xH) diff --git a/crypto/dleq/Cargo.toml b/crypto/dleq/Cargo.toml index 340130d0..c4b74845 100644 --- a/crypto/dleq/Cargo.toml +++ b/crypto/dleq/Cargo.toml @@ -30,9 +30,10 @@ dalek-ff-group = { path = "../dalek-ff-group" } transcript = { package = "flexible-transcript", path = "../transcript", features = ["recommended"] } [features] -serialize = [] -experimental = ["multiexp"] +std = [] +serialize = ["std"] +experimental = ["std", "multiexp"] secure_capacity_difference = [] -# Only applies to cross_group, yet is default to ensure security +# Only applies to experimental, yet is default to ensure security default = ["secure_capacity_difference"] diff --git a/crypto/dleq/src/cross_group/aos.rs b/crypto/dleq/src/cross_group/aos.rs index b73cb8b2..604e6b8d 100644 --- a/crypto/dleq/src/cross_group/aos.rs +++ b/crypto/dleq/src/cross_group/aos.rs @@ -6,9 +6,8 @@ use group::{ff::{Field, PrimeFieldBits}, prime::PrimeGroup}; use multiexp::BatchVerifier; -use crate::{ - Generators, - cross_group::{DLEqError, scalar::{scalar_convert, mutual_scalar_from_bytes}} +use crate::cross_group::{ + Generators, DLEqError, scalar::{scalar_convert, mutual_scalar_from_bytes} }; #[cfg(feature = "serialize")] diff --git a/crypto/dleq/src/cross_group/bits.rs b/crypto/dleq/src/cross_group/bits.rs index 5f55d181..44c9ee0a 100644 --- a/crypto/dleq/src/cross_group/bits.rs +++ b/crypto/dleq/src/cross_group/bits.rs @@ -5,7 +5,7 @@ use transcript::Transcript; use group::{ff::PrimeFieldBits, prime::PrimeGroup}; use multiexp::BatchVerifier; -use crate::{Generators, cross_group::{DLEqError, aos::{Re, Aos}}}; +use crate::cross_group::{Generators, DLEqError, aos::{Re, Aos}}; #[cfg(feature = "serialize")] use std::io::{Read, Write}; diff --git a/crypto/dleq/src/cross_group/mod.rs b/crypto/dleq/src/cross_group/mod.rs index 370b6c8e..a10eb294 100644 --- a/crypto/dleq/src/cross_group/mod.rs +++ b/crypto/dleq/src/cross_group/mod.rs @@ -8,8 +8,6 @@ use transcript::Transcript; use group::{ff::{Field, PrimeField, PrimeFieldBits}, prime::PrimeGroup}; use multiexp::BatchVerifier; -use crate::Generators; - pub mod scalar; use scalar::{scalar_convert, mutual_scalar_from_bytes}; @@ -35,6 +33,24 @@ pub(crate) fn read_point(r: &mut R) -> std::io::Result { + pub primary: G, + pub alt: G +} + +impl Generators { + pub fn new(primary: G, alt: G) -> Generators { + Generators { primary, alt } + } + + fn transcript(&self, transcript: &mut T) { + transcript.domain_separate(b"generators"); + transcript.append_message(b"primary", self.primary.to_bytes().as_ref()); + transcript.append_message(b"alternate", self.alt.to_bytes().as_ref()); + } +} + #[derive(Error, PartialEq, Eq, Debug)] pub enum DLEqError { #[error("invalid proof of knowledge")] diff --git a/crypto/dleq/src/lib.rs b/crypto/dleq/src/lib.rs index 1c7069ca..c6af96ed 100644 --- a/crypto/dleq/src/lib.rs +++ b/crypto/dleq/src/lib.rs @@ -1,4 +1,5 @@ -use thiserror::Error; +#![cfg_attr(not(feature = "std"), no_std)] + use rand_core::{RngCore, CryptoRng}; use transcript::Transcript; @@ -15,24 +16,6 @@ pub mod cross_group; #[cfg(test)] mod tests; -#[derive(Clone, Copy, PartialEq, Eq)] -pub struct Generators { - primary: G, - alt: G -} - -impl Generators { - pub fn new(primary: G, alt: G) -> Generators { - Generators { primary, alt } - } - - fn transcript(&self, transcript: &mut T) { - transcript.domain_separate(b"generators"); - transcript.append_message(b"primary", self.primary.to_bytes().as_ref()); - transcript.append_message(b"alternate", self.alt.to_bytes().as_ref()); - } -} - pub(crate) fn challenge(transcript: &mut T) -> F { // From here, there are three ways to get a scalar under the ff/group API // 1: Scalar::random(ChaCha12Rng::from_seed(self.transcript.rng_seed(b"challenge"))) @@ -70,9 +53,8 @@ fn read_scalar(r: &mut R) -> io::Result { Ok(scalar.unwrap()) } -#[derive(Error, Debug)] +#[derive(Debug)] pub enum DLEqError { - #[error("invalid proof")] InvalidProof } @@ -84,34 +66,26 @@ pub struct DLEqProof { #[allow(non_snake_case)] impl DLEqProof { - fn challenge( - transcript: &mut T, - generators: Generators, - nonces: (G, G), - points: (G, G) - ) -> G::Scalar { - generators.transcript(transcript); - transcript.domain_separate(b"dleq"); - transcript.append_message(b"nonce_primary", nonces.0.to_bytes().as_ref()); - transcript.append_message(b"nonce_alternate", nonces.1.to_bytes().as_ref()); - transcript.append_message(b"point_primary", points.0.to_bytes().as_ref()); - transcript.append_message(b"point_alternate", points.1.to_bytes().as_ref()); - challenge(transcript) + fn transcript(transcript: &mut T, generator: G, nonce: G, point: G) { + transcript.append_message(b"generator", generator.to_bytes().as_ref()); + transcript.append_message(b"nonce", nonce.to_bytes().as_ref()); + transcript.append_message(b"point", point.to_bytes().as_ref()); } pub fn prove( rng: &mut R, transcript: &mut T, - generators: Generators, + generators: &[G], scalar: G::Scalar ) -> DLEqProof { let r = G::Scalar::random(rng); - let c = Self::challenge( - transcript, - generators, - (generators.primary * r, generators.alt * r), - (generators.primary * scalar, generators.alt * scalar) - ); + + transcript.domain_separate(b"dleq"); + for generator in generators { + Self::transcript(transcript, *generator, *generator * r, *generator * scalar); + } + + let c = challenge(transcript); let s = r + (c * scalar); DLEqProof { c, s } @@ -120,18 +94,19 @@ impl DLEqProof { pub fn verify( &self, transcript: &mut T, - generators: Generators, - points: (G, G) + generators: &[G], + points: &[G] ) -> Result<(), DLEqError> { - if self.c != Self::challenge( - transcript, - generators, - ( - (generators.primary * self.s) - (points.0 * self.c), - (generators.alt * self.s) - (points.1 * self.c) - ), - points - ) { + if generators.len() != points.len() { + Err(DLEqError::InvalidProof)?; + } + + transcript.domain_separate(b"dleq"); + for (generator, point) in generators.iter().zip(points) { + Self::transcript(transcript, *generator, (*generator * self.s) - (*point * self.c), *point); + } + + if self.c != challenge(transcript) { Err(DLEqError::InvalidProof)?; } diff --git a/crypto/dleq/src/tests/cross_group/mod.rs b/crypto/dleq/src/tests/cross_group/mod.rs index bf9b8548..061c7b1b 100644 --- a/crypto/dleq/src/tests/cross_group/mod.rs +++ b/crypto/dleq/src/tests/cross_group/mod.rs @@ -12,10 +12,9 @@ use dalek_ff_group::{self as dfg, EdwardsPoint}; use transcript::{Transcript, RecommendedTranscript}; use crate::{ - Generators, cross_group::{ scalar::mutual_scalar_from_bytes, - ClassicLinearDLEq, EfficientLinearDLEq, ConciseLinearDLEq, CompromiseLinearDLEq + Generators, ClassicLinearDLEq, EfficientLinearDLEq, ConciseLinearDLEq, CompromiseLinearDLEq } }; diff --git a/crypto/dleq/src/tests/mod.rs b/crypto/dleq/src/tests/mod.rs index 27e53b4b..ac7b8b77 100644 --- a/crypto/dleq/src/tests/mod.rs +++ b/crypto/dleq/src/tests/mod.rs @@ -11,33 +11,47 @@ use k256::{Scalar, ProjectivePoint}; use transcript::{Transcript, RecommendedTranscript}; -use crate::{Generators, DLEqProof}; +use crate::DLEqProof; #[test] fn test_dleq() { let transcript = || RecommendedTranscript::new(b"DLEq Proof Test"); - let generators = Generators::new( + let generators = [ ProjectivePoint::GENERATOR, ProjectivePoint::from_bytes( &(hex!("0250929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0").into()) + ).unwrap(), + // Just an increment of the last byte from the previous, where the previous two are valid + ProjectivePoint::from_bytes( + &(hex!("0250929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac4").into()) + ).unwrap(), + ProjectivePoint::from_bytes( + &(hex!("0250929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803aca").into()) + ).unwrap(), + ProjectivePoint::from_bytes( + &(hex!("0250929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803acb").into()) ).unwrap() - ); + ]; - let key = Scalar::random(&mut OsRng); - let proof = DLEqProof::prove(&mut OsRng, &mut transcript(), generators, key); + for i in 0 .. 5 { + let key = Scalar::random(&mut OsRng); + let proof = DLEqProof::prove(&mut OsRng, &mut transcript(), &generators[.. i], key); - let keys = (generators.primary * key, generators.alt * key); - proof.verify(&mut transcript(), generators, keys).unwrap(); + let mut keys = [ProjectivePoint::GENERATOR; 5]; + for k in 0 .. 5 { + keys[k] = generators[k] * key; + } + proof.verify(&mut transcript(), &generators[.. i], &keys[.. i]).unwrap(); - #[cfg(feature = "serialize")] - { - let mut buf = vec![]; - proof.serialize(&mut buf).unwrap(); - let deserialized = DLEqProof::::deserialize( - &mut std::io::Cursor::new(&buf) - ).unwrap(); - assert_eq!(proof, deserialized); - deserialized.verify(&mut transcript(), generators, keys).unwrap(); + #[cfg(feature = "serialize")] + { + let mut buf = vec![]; + proof.serialize(&mut buf).unwrap(); + let deserialized = DLEqProof::::deserialize( + &mut std::io::Cursor::new(&buf) + ).unwrap(); + assert_eq!(proof, deserialized); + } } } diff --git a/crypto/frost/src/sign.rs b/crypto/frost/src/sign.rs index 9aacf55f..5e4f01d1 100644 --- a/crypto/frost/src/sign.rs +++ b/crypto/frost/src/sign.rs @@ -8,7 +8,7 @@ use transcript::Transcript; use group::{ff::{Field, PrimeField}, Group, GroupEncoding}; use multiexp::multiexp_vartime; -use dleq::{Generators, DLEqProof}; +use dleq::DLEqProof; use crate::{ curve::Curve, @@ -88,8 +88,8 @@ fn preprocess>( params: &mut Params, ) -> (PreprocessPackage, Vec) { let mut serialized = Vec::with_capacity(2 * C::G_len()); - let (nonces, commitments) = params.algorithm.nonces().iter().cloned().map( - |mut generators| { + let (nonces, commitments) = params.algorithm.nonces().iter().map( + |generators| { let nonces = [ C::random_nonce(params.view().secret_share(), &mut *rng), C::random_nonce(params.view().secret_share(), &mut *rng) @@ -103,21 +103,23 @@ fn preprocess>( }; let mut commitments = Vec::with_capacity(generators.len()); - let first = generators.remove(0); - commitments.push(commit(first, &mut serialized)); - - // Iterate over the rest for generator in generators.iter() { commitments.push(commit(*generator, &mut serialized)); - // 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 + } + + // Provide a DLEq proof to verify these commitments are for the same nonce + if generators.len() >= 2 { + // Uses an independent transcript as each signer must do this now, yet we validate them + // sequentially by the global order. Avoids needing to clone and fork the transcript around + let mut transcript = nonce_transcript::(); + + // This could be further optimized with a multi-nonce proof. + // See https://github.com/serai-dex/serai/issues/38 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), + &mut transcript, + &generators, nonce ).serialize(&mut serialized).unwrap(); } @@ -203,21 +205,20 @@ fn sign_with_share>( 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>(&mut cursor, *l)?); - transcript(params.algorithm.transcript(), commitments[n][0]); - - for generator in nonce_generators { + for _ in 0 .. nonce_generators.len() { commitments[n].push(read_D_E::<_, C>(&mut cursor, *l)?); transcript(params.algorithm.transcript(), commitments[n][commitments[n].len() - 1]); + } + + if nonce_generators.len() >= 2 { + let mut transcript = nonce_transcript::(); for de in 0 .. 2 { DLEqProof::deserialize( &mut cursor ).map_err(|_| FrostError::InvalidCommitment(*l))?.verify( - &mut nonce_transcript::(), - Generators::new(first, *generator), - (commitments[n][0][de], commitments[n][commitments[n].len() - 1][de]) + &mut transcript, + &nonce_generators, + &commitments[n].iter().map(|commitments| commitments[de]).collect::>(), ).map_err(|_| FrostError::InvalidCommitment(*l))?; } }