diff --git a/crypto/schnorr/Cargo.toml b/crypto/schnorr/Cargo.toml index f26b3e13..9606206d 100644 --- a/crypto/schnorr/Cargo.toml +++ b/crypto/schnorr/Cargo.toml @@ -18,11 +18,11 @@ rand_core = "0.6" zeroize = { version = "1.5", features = ["zeroize_derive"] } digest = "0.10" +transcript = { package = "flexible-transcript", path = "../transcript", version = "0.2" } group = "0.12" -ciphersuite = { path = "../ciphersuite", version = "0.1" } - multiexp = { path = "../multiexp", version = "0.2", features = ["batch"] } +ciphersuite = { path = "../ciphersuite", version = "0.1" } [dev-dependencies] hex = "0.4" diff --git a/crypto/schnorr/src/aggregate.rs b/crypto/schnorr/src/aggregate.rs index 933470de..c528d9b3 100644 --- a/crypto/schnorr/src/aggregate.rs +++ b/crypto/schnorr/src/aggregate.rs @@ -2,32 +2,27 @@ use std::io::{self, Read, Write}; use zeroize::Zeroize; -use digest::Digest; +use transcript::{Transcript, SecureDigest, DigestTranscript}; use group::{ ff::{Field, PrimeField}, Group, GroupEncoding, - prime::PrimeGroup, }; - use multiexp::multiexp_vartime; - use ciphersuite::Ciphersuite; use crate::SchnorrSignature; -fn digest() -> D { - D::new_with_prefix(b"Schnorr Aggregate") -} - // Performs a big-endian modular reduction of the hash value // This is used by the below aggregator to prevent mutability // Only an 128-bit scalar is needed to offer 128-bits of security against malleability per // https://cr.yp.to/badbatch/badbatch-20120919.pdf // Accordingly, while a 256-bit hash used here with a 256-bit ECC will have bias, it shouldn't be // an issue -fn scalar_from_digest(digest: D) -> F { - let bytes = digest.finalize(); +fn scalar_from_digest( + digest: &mut DigestTranscript, +) -> F { + let bytes = digest.challenge(b"aggregation_weight"); debug_assert_eq!(bytes.len() % 8, 0); let mut res = F::zero(); @@ -44,12 +39,6 @@ fn scalar_from_digest(digest: D) -> F { res } -fn digest_yield(digest: D, i: usize) -> F { - scalar_from_digest(digest.chain_update( - u32::try_from(i).expect("more than 4 billion signatures in aggregate").to_le_bytes(), - )) -} - /// Aggregate Schnorr signature as defined in . #[allow(non_snake_case)] #[derive(Clone, PartialEq, Eq, Debug, Zeroize)] @@ -96,23 +85,31 @@ impl SchnorrAggregate { /// Perform signature verification. /// - /// This challenge must be properly crafted, which means being binding to the public key, nonce, - /// and any message. Failure to do so will let a malicious adversary to forge signatures for + /// Challenges must be properly crafted, which means being binding to the public key, nonce, and + /// any message. Failure to do so will let a malicious adversary to forge signatures for /// different keys/messages. + /// + /// The DST used here must prevent a collision with whatever hash function produced the + /// challenges. #[must_use] - pub fn verify(&self, keys_and_challenges: &[(C::G, C::F)]) -> bool { + pub fn verify( + &self, + dst: &'static [u8], + keys_and_challenges: &[(C::G, C::F)], + ) -> bool { if self.Rs.len() != keys_and_challenges.len() { return false; } - let mut digest = digest::(); - for (key, challenge) in keys_and_challenges { - digest.update(challenge.to_repr().as_ref()); + let mut digest = DigestTranscript::::new(dst); + digest.domain_separate(b"signatures"); + for (_, challenge) in keys_and_challenges { + digest.append_message(b"challenge", challenge.to_repr()); } let mut pairs = Vec::with_capacity((2 * keys_and_challenges.len()) + 1); for (i, (key, challenge)) in keys_and_challenges.iter().enumerate() { - let z = digest_yield(digest.clone(), i); + let z = scalar_from_digest(&mut digest); pairs.push((z, self.Rs[i])); pairs.push((z * challenge, *key)); } @@ -123,31 +120,30 @@ impl SchnorrAggregate { #[allow(non_snake_case)] #[derive(Clone, Debug, Zeroize)] -pub struct SchnorrAggregator { - digest: D, +pub struct SchnorrAggregator { + digest: DigestTranscript, sigs: Vec>, } -impl Default for SchnorrAggregator { - fn default() -> Self { - Self { digest: digest(), sigs: vec![] } - } -} - -impl SchnorrAggregator { +impl SchnorrAggregator { /// Create a new aggregator. - pub fn new() -> Self { - Self::default() + /// + /// The DST used here must prevent a collision with whatever hash function produced the + /// challenges. + pub fn new(dst: &'static [u8]) -> Self { + let mut res = Self { digest: DigestTranscript::::new(dst), sigs: vec![] }; + res.digest.domain_separate(b"signatures"); + res } /// Aggregate a signature. - pub fn aggregate(&mut self, public_key: C::G, challenge: C::F, sig: SchnorrSignature) { - self.digest.update(challenge.to_repr().as_ref()); + pub fn aggregate(&mut self, challenge: C::F, sig: SchnorrSignature) { + self.digest.append_message(b"challenge", challenge.to_repr()); self.sigs.push(sig); } /// Complete aggregation, returning None if none were aggregated. - pub fn complete(self) -> Option> { + pub fn complete(mut self) -> Option> { if self.sigs.is_empty() { return None; } @@ -156,7 +152,7 @@ impl SchnorrAggregator { SchnorrAggregate { Rs: Vec::with_capacity(self.sigs.len()), s: C::F::zero() }; for i in 0 .. self.sigs.len() { aggregate.Rs.push(self.sigs[i].R); - aggregate.s += self.sigs[i].s * digest_yield::<_, C::F>(self.digest.clone(), i); + aggregate.s += self.sigs[i].s * scalar_from_digest::<_, C::F>(&mut self.digest); } Some(aggregate) } diff --git a/crypto/schnorr/src/tests/mod.rs b/crypto/schnorr/src/tests/mod.rs index e70460d9..c2791870 100644 --- a/crypto/schnorr/src/tests/mod.rs +++ b/crypto/schnorr/src/tests/mod.rs @@ -79,15 +79,17 @@ pub(crate) fn batch_verify() { } pub(crate) fn aggregate() { + const DST: &[u8] = b"Schnorr Aggregator Test"; + // Create 5 signatures let mut keys = vec![]; let mut challenges = vec![]; - let mut aggregator = SchnorrAggregator::::new(); + let mut aggregator = SchnorrAggregator::::new(DST); for i in 0 .. 5 { keys.push(Zeroizing::new(C::random_nonzero_F(&mut OsRng))); + // In practice, this MUST be a secure challenge binding to the nonce, key, and any message challenges.push(C::random_nonzero_F(&mut OsRng)); aggregator.aggregate( - C::generator() * keys[i].deref(), challenges[i], SchnorrSignature::::sign( &keys[i], @@ -101,12 +103,13 @@ pub(crate) fn aggregate() { let aggregate = SchnorrAggregate::::read::<&[u8]>(&mut aggregate.serialize().as_ref()).unwrap(); assert!(aggregate.verify::( + DST, keys .iter() .map(|key| C::generator() * key.deref()) .zip(challenges.iter().cloned()) .collect::>() - .as_ref() + .as_ref(), )); }