diff --git a/benches/bench.rs b/benches/bench.rs index e825f38..65ea345 100644 --- a/benches/bench.rs +++ b/benches/bench.rs @@ -44,9 +44,9 @@ fn bench_batch_verify(c: &mut Criterion) { &sigs, |b, sigs| { b.iter(|| { - let mut batch = BatchVerifier::new(); + let mut batch = batch::Verifier::new(); for (vk_bytes, sig) in sigs.iter().cloned() { - batch.queue(vk_bytes, sig, b""); + batch.queue((vk_bytes, sig, b"")); } batch.verify(thread_rng()) }) @@ -58,9 +58,9 @@ fn bench_batch_verify(c: &mut Criterion) { &sigs, |b, sigs| { b.iter(|| { - let mut batch = BatchVerifier::new(); + let mut batch = batch::Verifier::new(); for (vk_bytes, sig) in sigs.iter().cloned() { - batch.queue(vk_bytes, sig, b""); + batch.queue((vk_bytes, sig, b"")); } batch.verify(thread_rng()) }) diff --git a/src/batch.rs b/src/batch.rs index e438e83..7afea0e 100644 --- a/src/batch.rs +++ b/src/batch.rs @@ -1,3 +1,47 @@ +//! Performs batch Ed25519 signature verification. +//! +//! Batch verification asks whether *all* signatures in some set are valid, +//! rather than asking whether *each* of them is valid. This allows sharing +//! computations among all signature verifications, performing less work overall +//! at the cost of higher latency (the entire batch must complete), complexity of +//! caller code (which must assemble a batch of signatures across work-items), +//! and loss of the ability to easily pinpoint failing signatures. +//! +//! In addition to these general tradeoffs, design flaws in Ed25519 specifically +//! mean that batched verification may not agree with individual verification. +//! Some signature may verify as part of a batch but not on its own. In +//! particular, this batching implementation does not perform the same +//! verification checks as the standalone implementation in this crate. **It +//! should therefore not be used in consensus-critical applications.** +//! +//! This batch verification implementation is adaptive in the sense that it +//! detects multiple signatures created with the same verification key and +//! automatically coalesces terms in the final verification equation. In the +//! limiting case where all signatures in the batch are made with the same +//! verification key, coalesced batch verification runs twice as fast as ordinary +//! batch verification. +//! +//! ![benchmark](https://www.zfnd.org/images/coalesced-batch-graph.png) +//! +//! This optimization doesn't help much with Zcash, where public keys are random, +//! but could be useful in proof-of-stake systems where signatures come from a +//! set of validators (except for the consensus behavior described above, which +//! will be addressed in a future version of this library). +//! +//! # Example +//! ``` +//! # use ed25519_zebra::*; +//! let mut batch = batch::Verifier::new(); +//! for _ in 0..32 { +//! let sk = SigningKey::new(rand::thread_rng()); +//! let vk_bytes = VerificationKeyBytes::from(&sk); +//! let msg = b"BatchVerifyTest"; +//! let sig = sk.sign(&msg[..]); +//! batch.queue((vk_bytes, sig, &msg[..])); +//! } +//! assert!(batch.verify(rand::thread_rng()).is_ok()); +//! ``` + use std::collections::HashMap; use curve25519_dalek::{ @@ -17,66 +61,20 @@ fn gen_u128(mut rng: R) -> u128 { u128::from_le_bytes(bytes) } -/// Performs batch Ed25519 signature verification. +/// A batch verification item. /// -/// Batch verification asks whether *all* signatures in some set are valid, -/// rather than asking whether *each* of them is valid. This allows sharing -/// computations among all signature verifications, performing less work overall -/// at the cost of higher latency (the entire batch must complete), complexity of -/// caller code (which must assemble a batch of signatures across work-items), -/// and loss of the ability to easily pinpoint failing signatures. -/// -/// In addition to these general tradeoffs, design flaws in Ed25519 specifically -/// mean that batched verification may not agree with individual verification. -/// Some signature may verify as part of a batch but not on its own. In -/// particular, this batching implementation does not perform the same -/// verification checks as the standalone implementation in this crate. **It -/// should therefore not be used in consensus-critical applications.** -/// -/// This batch verification implementation is adaptive in the sense that it -/// detects multiple signatures created with the same verification key and -/// automatically coalesces terms in the final verification equation. In the -/// limiting case where all signatures in the batch are made with the same -/// verification key, coalesced batch verification runs twice as fast as ordinary -/// batch verification. -/// -/// ![benchmark](https://www.zfnd.org/images/coalesced-batch-graph.png) -/// -/// This optimization doesn't help much with Zcash, where public keys are random, -/// but could be useful in proof-of-stake systems where signatures come from a -/// set of validators (except for the consensus behavior described above, which -/// will be addressed in a future version of this library). -/// -/// # Example -/// ``` -/// # use ed25519_zebra::*; -/// let mut batch = BatchVerifier::new(); -/// for _ in 0..32 { -/// let sk = SigningKey::new(rand::thread_rng()); -/// let vk_bytes = VerificationKeyBytes::from(&sk); -/// let msg = b"BatchVerifyTest"; -/// let sig = sk.sign(&msg[..]); -/// batch.queue(vk_bytes, sig, &msg[..]); -/// } -/// assert!(batch.verify(rand::thread_rng()).is_ok()); -/// ``` -#[derive(Default)] -pub struct BatchVerifier { - /// Signature data queued for verification. - signatures: HashMap>, - /// Caching this count avoids a hash traversal to figure out - /// how much to preallocate. - batch_size: usize, +/// This struct exists to allow batch processing to be decoupled from the +/// lifetime of the message. This is useful when using the batch verification API +/// in an async context. +pub struct Item { + vk_bytes: VerificationKeyBytes, + sig: Signature, + k: Scalar, } -impl BatchVerifier { - /// Construct a new batch verifier. - pub fn new() -> BatchVerifier { - BatchVerifier::default() - } - - /// Queue a (key, signature, message) tuple for verification. - pub fn queue(&mut self, vk_bytes: VerificationKeyBytes, sig: Signature, msg: &[u8]) { +impl<'msg, M: AsRef<[u8]> + ?Sized> From<(VerificationKeyBytes, Signature, &'msg M)> for Item { + fn from(tup: (VerificationKeyBytes, Signature, &'msg M)) -> Self { + let (vk_bytes, sig, msg) = tup; // Compute k now to avoid dependency on the msg lifetime. let k = Scalar::from_hash( Sha512::default() @@ -84,6 +82,29 @@ impl BatchVerifier { .chain(&vk_bytes.0[..]) .chain(msg), ); + Self { vk_bytes, sig, k } + } +} + +/// A batch verification context. +#[derive(Default)] +pub struct Verifier { + /// Signature data queued for verification. + signatures: HashMap>, + /// Caching this count avoids a hash traversal to figure out + /// how much to preallocate. + batch_size: usize, +} + +impl Verifier { + /// Construct a new batch verifier. + pub fn new() -> Verifier { + Verifier::default() + } + + /// Queue a (key, signature, message) tuple for verification. + pub fn queue>(&mut self, item: I) { + let Item { vk_bytes, sig, k } = item.into(); self.signatures .entry(vk_bytes) diff --git a/src/lib.rs b/src/lib.rs index ca569f5..9510695 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,14 +6,13 @@ //! Docs require the `nightly` feature until RFC 1990 lands. -mod batch; +pub mod batch; mod constants; mod error; mod signature; mod signing_key; mod verification_key; -pub use batch::BatchVerifier; pub use error::Error; pub use signature::Signature; pub use signing_key::SigningKey; diff --git a/tests/batch.rs b/tests/batch.rs index bdc342b..43ebf25 100644 --- a/tests/batch.rs +++ b/tests/batch.rs @@ -4,13 +4,13 @@ use ed25519_zebra::*; #[test] fn batch_verify() { - let mut batch = BatchVerifier::new(); + let mut batch = batch::Verifier::new(); for _ in 0..32 { let sk = SigningKey::new(thread_rng()); let pk_bytes = VerificationKeyBytes::from(&sk); let msg = b"BatchVerifyTest"; let sig = sk.sign(&msg[..]); - batch.queue(pk_bytes, sig, &msg[..]); + batch.queue((pk_bytes, sig, msg)); } assert!(batch.verify(thread_rng()).is_ok()); }