diff --git a/Cargo.toml b/Cargo.toml index 107c46e..811b3a5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ default-features = false [dependencies.rand] version = "0.5" default-features = false +features = ["i128_support"] [dependencies.digest] version = "^0.7" @@ -59,6 +60,7 @@ harness = false default = ["std", "u64_backend"] # We don't add "rand/std" here because it would enable a bunch of Fuchsia dependencies. std = ["curve25519-dalek/std"] +alloc = ["curve25519-dalek/alloc"] nightly = ["curve25519-dalek/nightly", "rand/nightly", "clear_on_drop/nightly"] asm = ["sha2/asm"] yolocrypto = ["curve25519-dalek/yolocrypto"] diff --git a/benches/ed25519_benchmarks.rs b/benches/ed25519_benchmarks.rs index b9ac890..8347ea5 100644 --- a/benches/ed25519_benchmarks.rs +++ b/benches/ed25519_benchmarks.rs @@ -19,7 +19,9 @@ mod ed25519_benches { use super::*; use ed25519_dalek::ExpandedSecretKey; use ed25519_dalek::Keypair; + use ed25519_dalek::PublicKey; use ed25519_dalek::Signature; + use ed25519_dalek::verify_batch; use rand::thread_rng; use rand::ThreadRng; use sha2::Sha512; @@ -56,6 +58,25 @@ mod ed25519_benches { }); } + fn verify_batch_signatures(c: &mut Criterion) { + static BATCH_SIZES: [u8; 6] = [4, 8, 16, 32, 64, 96]; + + c.bench_function_over_inputs( + "Ed25519 batch signature verification", + |b, &&size| { + let mut csprng: ThreadRng = thread_rng(); + let keypairs: Vec = (0..size).map(|_| Keypair::generate::(&mut csprng)).collect(); + let msg: &[u8] = b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; + let messages: Vec<&[u8]> = (0..size).map(|_| msg).collect(); + let signatures: Vec = keypairs.iter().map(|key| key.sign::(&msg)).collect(); + let public_keys: Vec = keypairs.iter().map(|key| key.public).collect(); + + b.iter(|| verify_batch::(&messages[..], &signatures[..], &public_keys[..], &mut csprng)); + }, + &BATCH_SIZES, + ); + } + fn key_generation(c: &mut Criterion) { let mut csprng: ThreadRng = thread_rng(); @@ -71,6 +92,7 @@ mod ed25519_benches { sign, sign_expanded_key, verify, + verify_batch_signatures, key_generation, } } diff --git a/src/ed25519.rs b/src/ed25519.rs index 54bec6b..0e056ee 100644 --- a/src/ed25519.rs +++ b/src/ed25519.rs @@ -885,6 +885,124 @@ impl PublicKey { } } +/// Verify a batch of `signatures` on `messages` with their respective `public_keys`. +/// +/// # Inputs +/// +/// * `messages` is a slice of byte slices, one per signed message. +/// * `signatures` is a slice of `Signature`s. +/// * `public_keys` is a slice of `PublicKey`s. +/// * `csprng` is an implementation of `Rng + CryptoRng`, such as `rand::ThreadRng`. +/// +/// # Panics +/// +/// This function will panic if the `messages, `signatures`, and `public_keys` +/// slices are not equal length. +/// +/// # Returns +/// +/// * A `Result` whose `Ok` value is an emtpy tuple and whose `Err` value is a +/// `SignatureError` containing a description of the internal error which +/// occured. +/// +/// # Examples +/// +/// ``` +/// extern crate ed25519_dalek; +/// extern crate rand; +/// extern crate sha2; +/// +/// use ed25519_dalek::verify_batch; +/// use ed25519_dalek::Keypair; +/// use ed25519_dalek::PublicKey; +/// use ed25519_dalek::Signature; +/// use rand::thread_rng; +/// use rand::ThreadRng; +/// use sha2::Sha512; +/// +/// # fn main() { +/// let mut csprng: ThreadRng = thread_rng(); +/// let keypairs: Vec = (0..64).map(|_| Keypair::generate::(&mut csprng)).collect(); +/// let msg: &[u8] = b"They're good dogs Brant"; +/// let messages: Vec<&[u8]> = (0..64).map(|_| msg).collect(); +/// let signatures: Vec = keypairs.iter().map(|key| key.sign::(&msg)).collect(); +/// let public_keys: Vec = keypairs.iter().map(|key| key.public).collect(); +/// +/// let result = verify_batch::(&messages[..], &signatures[..], &public_keys[..], &mut csprng); +/// assert!(result.is_ok()); +/// # } +/// ``` +#[cfg(any(feature = "alloc", feature = "std"))] +#[allow(non_snake_case)] +pub fn verify_batch( + messages: &[&[u8]], + signatures: &[Signature], + public_keys: &[PublicKey], + csprng: &mut C, +) -> Result<(), SignatureError> +where + D: Digest + Default, + C: Rng + CryptoRng, +{ + const ASSERT_MESSAGE: &'static [u8] = b"The number of messages, signatures, and public keys must be equal."; + assert!(signatures.len() == messages.len(), ASSERT_MESSAGE); + assert!(signatures.len() == public_keys.len(), ASSERT_MESSAGE); + assert!(public_keys.len() == messages.len(), ASSERT_MESSAGE); + + #[cfg(feature = "alloc")] + use alloc::vec::Vec; + #[cfg(feature = "std")] + use std::vec::Vec; + + use core::iter::once; + use rand::thread_rng; + + use curve25519_dalek::traits::IsIdentity; + use curve25519_dalek::traits::VartimeMultiscalarMul; + + // Select a random 128-bit scalar for each signature. + let zs: Vec = signatures + .iter() + .map(|_| Scalar::from(thread_rng().gen::())) + .collect(); + + // Compute the basepoint coefficient, ∑ s[i]z[i] (mod l) + let B_coefficient: Scalar = signatures + .iter() + .map(|sig| sig.s) + .zip(zs.iter()) + .map(|(s, z)| z * s) + .sum(); + + // Compute H(R || A || M) for each (signature, public_key, message) triplet + let hrams = (0..signatures.len()).map(|i| { + let mut h: D = D::default(); + h.input(signatures[i].R.as_bytes()); + h.input(public_keys[i].as_bytes()); + h.input(&messages[i]); + Scalar::from_hash(h) + }); + + // Multiply each H(R || A || M) by the random value + let zhrams = hrams.zip(zs.iter()).map(|(hram, z)| hram * z); + + let Rs = signatures.iter().map(|sig| sig.R.decompress()); + let As = public_keys.iter().map(|pk| pk.0.decompress()); + let B = once(Some(constants::ED25519_BASEPOINT_POINT)); + + // Compute (-∑ z[i]s[i] (mod l)) B + ∑ z[i]R[i] + ∑ (z[i]H(R||A||M)[i] (mod l)) A[i] = 0 + let id = EdwardsPoint::optional_multiscalar_mul( + once(-B_coefficient).chain(zs.iter().cloned()).chain(zhrams), + B.chain(Rs).chain(As), + ).ok_or_else(|| SignatureError(InternalError::VerifyError))?; + + if id.is_identity() { + Ok(()) + } else { + Err(SignatureError(InternalError::VerifyError)) + } +} + #[cfg(feature = "serde")] impl Serialize for PublicKey { fn serialize(&self, serializer: S) -> Result where S: Serializer { @@ -1244,8 +1362,10 @@ mod test { use std::fs::File; use std::string::String; use std::vec::Vec; + use rand::thread_rng; use rand::ChaChaRng; use rand::SeedableRng; + use rand::ThreadRng; use hex::FromHex; use sha2::Sha512; use super::*; @@ -1416,6 +1536,32 @@ mod test { "Verification of a signature on a different message passed!"); } + #[test] + fn verify_batch_seven_signatures() { + let messages: [&[u8]; 7] = [ + b"Watch closely everyone, I'm going to show you how to kill a god.", + b"I'm not a cryptographer I just encrypt a lot.", + b"Still not a cryptographer.", + b"This is a test of the tsunami alert system. This is only a test.", + b"Fuck dumbin' it down, spit ice, skip jewellery: Molotov cocktails on me like accessories.", + b"Hey, I never cared about your bucks, so if I run up with a mask on, probably got a gas can too.", + b"And I'm not here to fill 'er up. Nope, we came to riot, here to incite, we don't want any of your stuff.", ]; + let mut csprng: ThreadRng = thread_rng(); + let mut keypairs: Vec = Vec::new(); + let mut signatures: Vec = Vec::new(); + + for i in 0..messages.len() { + let keypair: Keypair = Keypair::generate::(&mut csprng); + signatures.push(keypair.sign::(&messages[i])); + keypairs.push(keypair); + } + let public_keys: Vec = keypairs.iter().map(|key| key.public).collect(); + + let result = verify_batch::(&messages, &signatures[..], &public_keys[..], &mut csprng); + + assert!(result.is_ok()); + } + #[test] fn public_key_from_bytes() { // Make another function so that we can test the ? operator.