Fix batch build (#220)

* Fixed bench when `batch` feature is not present

* Added bench build regression test to CI

* Fixed batch build more generally

* Simplified batch cfg gates in benches

* Updated criterion

* Made CI batch-nondeterministic test use nostd

* Fix batch_deterministic build

* Removed bad compile error when batch and batch_deterministic are selected
This commit is contained in:
Michael Rosenberg 2023-01-02 00:59:19 -05:00 committed by GitHub
parent 616d55c36c
commit e2ed3133a6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 89 additions and 87 deletions

View File

@ -2,9 +2,9 @@ name: Rust
on:
push:
branches: [ '*' ]
branches: [ '**' ]
pull_request:
branches: [ 'main', 'develop', 'release/2.0' ]
branches: [ '**' ]
env:
CARGO_TERM_COLOR: always
@ -32,7 +32,7 @@ jobs:
- run: cargo test --target ${{ matrix.target }} --features batch
- run: cargo test --target ${{ matrix.target }} --features batch_deterministic
- run: cargo test --target ${{ matrix.target }} --features serde
- run: cargo test --target ${{ matrix.target }} --features pkcs8
- run: cargo test --target ${{ matrix.target }} --features pem
build-simd:
name: Test simd backend (nightly)
@ -68,6 +68,7 @@ jobs:
- uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@stable
- run: cargo build --benches --features batch
- run: cargo build --benches --features batch_deterministic
rustfmt:
name: Check formatting
@ -87,4 +88,4 @@ jobs:
- uses: dtolnay/rust-toolchain@1.65
with:
components: clippy
- run: cargo clippy
- run: cargo clippy

View File

@ -28,7 +28,7 @@ curve25519-dalek = { version = "=4.0.0-pre.3", default-features = false, feature
ed25519 = { version = "=2.0.0-pre.1", default-features = false }
merlin = { version = "3", default-features = false, optional = true }
rand = { version = "0.8", default-features = false, optional = true }
rand_core = { version = "0.6", default-features = false, optional = true }
rand_core = { version = "0.6.4", default-features = false, optional = true }
serde = { version = "1.0", default-features = false, optional = true }
serde_bytes = { version = "0.11", optional = true }
sha2 = { version = "0.10", default-features = false }
@ -38,16 +38,16 @@ zeroize = { version = "1.5", default-features = false }
hex = "0.4"
bincode = "1.0"
serde_json = "1.0"
criterion = "0.3"
criterion = { version = "0.4", features = ["html_reports"] }
hex-literal = "0.3"
rand = "0.8"
rand_core = { version = "0.6.4", default-features = false }
serde = { version = "1.0", features = ["derive"] }
toml = { version = "0.5" }
[[bench]]
name = "ed25519_benchmarks"
harness = false
required-features = ["batch"]
[features]
default = ["std", "rand"]
@ -55,7 +55,7 @@ alloc = ["curve25519-dalek/alloc", "ed25519/alloc", "rand?/alloc", "serde?/alloc
std = ["alloc", "ed25519/std", "rand?/std", "serde?/std", "sha2/std"]
asm = ["sha2/asm"]
batch = ["alloc", "merlin", "rand/std"]
batch = ["alloc", "merlin", "rand"]
# This feature enables deterministic batch verification.
batch_deterministic = ["alloc", "merlin", "rand"]
# This features turns off stricter checking for scalar malleability in signatures

View File

@ -261,8 +261,12 @@ comprising either avx2 or avx512 backends. To use them, compile with
The standard variants of batch signature verification (i.e. many signatures made
with potentially many different public keys over potentially many different
message) is available via the `batch` feature. It uses synthetic randomness, as
noted above.
messages) is available via the `batch` feature. It uses synthetic randomness, as
noted above. Batch verification requires allocation, so this won't function in
heapless settings.
Batch verification is slightly faster with the `std` feature enabled, since it
permits us to use `rand::thread_rng`.
### Deterministic Batch Signature Verification

View File

@ -7,15 +7,13 @@
// Authors:
// - isis agora lovecruft <isis@patternsinthevoid.net>
use criterion::{criterion_group, criterion_main, Criterion};
use criterion::{criterion_group, Criterion};
mod ed25519_benches {
use super::*;
use ed25519_dalek::verify_batch;
use ed25519_dalek::Signature;
use ed25519_dalek::Signer;
use ed25519_dalek::SigningKey;
use ed25519_dalek::VerifyingKey;
use rand::prelude::ThreadRng;
use rand::thread_rng;
@ -49,14 +47,17 @@ mod ed25519_benches {
});
}
#[cfg(any(feature = "batch", feature = "batch_deterministic"))]
fn verify_batch_signatures(c: &mut Criterion) {
use ed25519_dalek::verify_batch;
static BATCH_SIZES: [usize; 8] = [4, 8, 16, 32, 64, 96, 128, 256];
// TODO: use BenchmarkGroups instead.
#[allow(deprecated)]
c.bench_function_over_inputs(
"Ed25519 batch signature verification",
|b, &&size| {
// Benchmark batch verification for all the above batch sizes
let mut group = c.benchmark_group("Ed25519 batch signature verification");
for size in BATCH_SIZES {
let name = format!("size={size}");
group.bench_function(name, |b| {
let mut csprng: ThreadRng = thread_rng();
let keypairs: Vec<SigningKey> = (0..size)
.map(|_| SigningKey::generate(&mut csprng))
@ -65,15 +66,18 @@ mod ed25519_benches {
let messages: Vec<&[u8]> = (0..size).map(|_| msg).collect();
let signatures: Vec<Signature> =
keypairs.iter().map(|key| key.sign(&msg)).collect();
let verifying_keys: Vec<VerifyingKey> =
let verifying_keys: Vec<_> =
keypairs.iter().map(|key| key.verifying_key()).collect();
b.iter(|| verify_batch(&messages[..], &signatures[..], &verifying_keys[..]));
},
&BATCH_SIZES,
);
});
}
}
// If the above function isn't defined, make a placeholder function
#[cfg(not(any(feature = "batch", feature = "batch_deterministic")))]
fn verify_batch_signatures(_: &mut Criterion) {}
fn key_generation(c: &mut Criterion) {
let mut csprng: ThreadRng = thread_rng();
@ -94,4 +98,4 @@ mod ed25519_benches {
}
}
criterion_main!(ed25519_benches::ed25519_benches);
criterion::criterion_main!(ed25519_benches::ed25519_benches);

View File

@ -27,11 +27,7 @@ pub use curve25519_dalek::digest::Digest;
use merlin::Transcript;
#[cfg(all(feature = "batch", not(feature = "batch_deterministic")))]
use rand::thread_rng;
use rand::Rng;
#[cfg(all(not(feature = "batch"), feature = "batch_deterministic"))]
use rand_core;
use sha2::Sha512;
@ -40,6 +36,19 @@ use crate::errors::SignatureError;
use crate::signature::InternalSignature;
use crate::VerifyingKey;
/// Gets an RNG from the system, or the zero RNG if we're in deterministic mode. If available, we
/// prefer `thread_rng`, since it's faster than `OsRng`.
fn get_rng() -> impl rand_core::CryptoRngCore {
#[cfg(all(feature = "batch_deterministic", not(feature = "batch")))]
return ZeroRng;
#[cfg(all(feature = "batch", feature = "std"))]
return rand::thread_rng();
#[cfg(all(feature = "batch", not(feature = "std")))]
return rand::rngs::OsRng;
}
trait BatchTranscript {
fn append_scalars(&mut self, scalars: &Vec<Scalar>);
fn append_message_lengths(&mut self, message_lengths: &Vec<usize>);
@ -63,10 +72,9 @@ impl BatchTranscript for Transcript {
/// Append the lengths of the messages into the transcript.
///
/// This is done out of an (potential over-)abundance of caution, to guard
/// against the unlikely event of collisions. However, a nicer way to do
/// this would be to append the message length before the message, but this
/// is messy w.r.t. the calculations of the `H(R||A||M)`s above.
/// This is done out of an (potential over-)abundance of caution, to guard against the unlikely
/// event of collisions. However, a nicer way to do this would be to append the message length
/// before the message, but this is messy w.r.t. the calculations of the `H(R||A||M)`s above.
fn append_message_lengths(&mut self, message_lengths: &Vec<usize>) {
for (i, len) in message_lengths.iter().enumerate() {
self.append_u64(b"", i as u64);
@ -75,13 +83,12 @@ impl BatchTranscript for Transcript {
}
}
/// An implementation of `rand_core::RngCore` which does nothing, to provide
/// purely deterministic transcript-based nonces, rather than synthetically
/// random nonces.
#[cfg(all(not(feature = "batch"), feature = "batch_deterministic"))]
struct ZeroRng {}
/// An implementation of `rand_core::RngCore` which does nothing, to provide purely deterministic
/// transcript-based nonces, rather than synthetically random nonces.
#[cfg(feature = "batch_deterministic")]
struct ZeroRng;
#[cfg(all(not(feature = "batch"), feature = "batch_deterministic"))]
#[cfg(feature = "batch_deterministic")]
impl rand_core::RngCore for ZeroRng {
fn next_u32(&mut self) -> u32 {
rand_core::impls::next_u32_via_fill(self)
@ -107,14 +114,9 @@ impl rand_core::RngCore for ZeroRng {
}
}
#[cfg(all(not(feature = "batch"), feature = "batch_deterministic"))]
#[cfg(feature = "batch_deterministic")]
impl rand_core::CryptoRng for ZeroRng {}
#[cfg(all(not(feature = "batch"), feature = "batch_deterministic"))]
fn zero_rng() -> ZeroRng {
ZeroRng {}
}
/// Verify a batch of `signatures` on `messages` with their respective `verifying_keys`.
///
/// # Inputs
@ -199,7 +201,7 @@ fn zero_rng() -> ZeroRng {
/// use rand::rngs::OsRng;
///
/// # fn main() {
/// let mut csprng = OsRng{};
/// let mut csprng = OsRng;
/// let signing_keys: Vec<_> = (0..64).map(|_| SigningKey::generate(&mut csprng)).collect();
/// let msg: &[u8] = b"They're good dogs Brant";
/// let messages: Vec<&[u8]> = (0..64).map(|_| msg).collect();
@ -249,25 +251,21 @@ pub fn verify_batch(
})
.collect();
// Collect the message lengths and the scalar portions of the signatures,
// and add them into the transcript.
// Collect the message lengths and the scalar portions of the signatures, and add them into the
// transcript.
let message_lengths: Vec<usize> = messages.iter().map(|i| i.len()).collect();
let scalars: Vec<Scalar> = signatures.iter().map(|i| i.s).collect();
// Build a PRNG based on a transcript of the H(R || A || M)s seen thus far.
// This provides synthethic randomness in the default configuration, and
// purely deterministic in the case of compiling with the
// "batch_deterministic" feature.
// Build a PRNG based on a transcript of the H(R || A || M)s seen thus far. This provides
// synthethic randomness in the default configuration, and purely deterministic in the case of
// compiling with the "batch_deterministic" feature.
let mut transcript: Transcript = Transcript::new(b"ed25519 batch verification");
transcript.append_scalars(&hrams);
transcript.append_message_lengths(&message_lengths);
transcript.append_scalars(&scalars);
#[cfg(all(feature = "batch", not(feature = "batch_deterministic")))]
let mut prng = transcript.build_rng().finalize(&mut thread_rng());
#[cfg(all(not(feature = "batch"), feature = "batch_deterministic"))]
let mut prng = transcript.build_rng().finalize(&mut zero_rng());
let mut prng = transcript.build_rng().finalize(&mut get_rng());
// Select a random 128-bit scalar for each signature.
let zs: Vec<Scalar> = signatures

View File

@ -18,28 +18,26 @@
//! secure pseudorandom number generator (CSPRNG). For this example, we'll use
//! the operating system's builtin PRNG:
//!
//! ```
//! # #[cfg(feature = "std")]
#![cfg_attr(feature = "rand", doc = "```")]
#![cfg_attr(not(feature = "rand"), doc = "```ignore")]
//! # fn main() {
//! use rand::rngs::OsRng;
//! use ed25519_dalek::SigningKey;
//! use ed25519_dalek::Signature;
//!
//! let mut csprng = OsRng{};
//! let mut csprng = OsRng;
//! let signing_key: SigningKey = SigningKey::generate(&mut csprng);
//! # }
//! #
//! # #[cfg(not(feature = "std"))]
//! # fn main() { }
//! ```
//!
//! We can now use this `signing_key` to sign a message:
//!
//! ```
#![cfg_attr(feature = "rand", doc = "```")]
#![cfg_attr(not(feature = "rand"), doc = "```ignore")]
//! # fn main() {
//! # use rand::rngs::OsRng;
//! # use ed25519_dalek::SigningKey;
//! # let mut csprng = OsRng{};
//! # let mut csprng = OsRng;
//! # let signing_key: SigningKey = SigningKey::generate(&mut csprng);
//! use ed25519_dalek::{Signature, Signer};
//! let message: &[u8] = b"This is a test of the tsunami alert system.";
@ -50,11 +48,12 @@
//! As well as to verify that this is, indeed, a valid signature on
//! that `message`:
//!
//! ```
#![cfg_attr(feature = "rand", doc = "```")]
#![cfg_attr(not(feature = "rand"), doc = "```ignore")]
//! # fn main() {
//! # use rand::rngs::OsRng;
//! # use ed25519_dalek::{SigningKey, Signature, Signer};
//! # let mut csprng = OsRng{};
//! # let mut csprng = OsRng;
//! # let signing_key: SigningKey = SigningKey::generate(&mut csprng);
//! # let message: &[u8] = b"This is a test of the tsunami alert system.";
//! # let signature: Signature = signing_key.sign(message);
@ -66,7 +65,8 @@
//! Anyone else, given the `public` half of the `signing_key` can also easily
//! verify this signature:
//!
//! ```
#![cfg_attr(feature = "rand", doc = "```")]
#![cfg_attr(not(feature = "rand"), doc = "```ignore")]
//! # fn main() {
//! # use rand::rngs::OsRng;
//! # use ed25519_dalek::SigningKey;
@ -91,7 +91,8 @@
//! secret key to anyone else, since they will only need the public key to
//! verify your signatures!)
//!
//! ```
#![cfg_attr(feature = "rand", doc = "```")]
#![cfg_attr(not(feature = "rand"), doc = "```ignore")]
//! # fn main() {
//! # use rand::rngs::OsRng;
//! # use ed25519_dalek::{SigningKey, Signature, Signer, VerifyingKey};
@ -110,14 +111,15 @@
//!
//! And similarly, decoded from bytes with `::from_bytes()`:
//!
//! ```
#![cfg_attr(feature = "rand", doc = "```")]
#![cfg_attr(not(feature = "rand"), doc = "```ignore")]
//! # use std::convert::TryFrom;
//! # use rand::rngs::OsRng;
//! # use std::convert::TryInto;
//! # use ed25519_dalek::{SigningKey, Signature, Signer, VerifyingKey, SecretKey, SignatureError};
//! # use ed25519_dalek::{PUBLIC_KEY_LENGTH, SECRET_KEY_LENGTH, KEYPAIR_LENGTH, SIGNATURE_LENGTH};
//! # fn do_test() -> Result<(SigningKey, VerifyingKey, Signature), SignatureError> {
//! # let mut csprng = OsRng{};
//! # let mut csprng = OsRng;
//! # let signing_key_orig: SigningKey = SigningKey::generate(&mut csprng);
//! # let message: &[u8] = b"This is a test of the tsunami alert system.";
//! # let signature_orig: Signature = signing_key_orig.sign(message);
@ -164,13 +166,13 @@
//!
#![cfg_attr(feature = "pem", doc = "```")]
#![cfg_attr(not(feature = "pem"), doc = "```ignore")]
//! use ed25519_dalek::{VerifyingKey, pkcs8::DecodeVerifyingKey};
//! use ed25519_dalek::{VerifyingKey, pkcs8::DecodePublicKey};
//!
//! let pem = "-----BEGIN PUBLIC KEY-----
//! MCowBQYDK2VwAyEAGb9ECWmEzf6FQbrBZ9w7lshQhqowtrbLDFw4rXAxZuE=
//! -----END PUBLIC KEY-----";
//!
//! let verifying_key = VerifyingKey::from_verifying_key_pem(pem)
//! let verifying_key = VerifyingKey::from_public_key_pem(pem)
//! .expect("invalid public key PEM");
//! ```
//!
@ -187,13 +189,13 @@
//! They can be then serialised into any of the wire formats which serde supports.
//! For example, using [bincode](https://github.com/TyOverby/bincode):
//!
//! ```
//! # #[cfg(feature = "serde")]
#![cfg_attr(all(feature = "rand", feature = "serde"), doc = "```")]
#![cfg_attr(not(all(feature = "rand", feature = "serde")), doc = "```ignore")]
//! # fn main() {
//! # use rand::rngs::OsRng;
//! # use ed25519_dalek::{SigningKey, Signature, Signer, Verifier, VerifyingKey};
//! use bincode::serialize;
//! # let mut csprng = OsRng{};
//! # let mut csprng = OsRng;
//! # let signing_key: SigningKey = SigningKey::generate(&mut csprng);
//! # let message: &[u8] = b"This is a test of the tsunami alert system.";
//! # let signature: Signature = signing_key.sign(message);
@ -203,22 +205,20 @@
//! let encoded_verifying_key: Vec<u8> = serialize(&verifying_key).unwrap();
//! let encoded_signature: Vec<u8> = serialize(&signature).unwrap();
//! # }
//! # #[cfg(not(feature = "serde"))]
//! # fn main() {}
//! ```
//!
//! After sending the `encoded_verifying_key` and `encoded_signature`, the
//! recipient may deserialise them and verify:
//!
//! ```
//! # #[cfg(feature = "serde")]
#![cfg_attr(all(feature = "rand", feature = "serde"), doc = "```")]
#![cfg_attr(not(all(feature = "rand", feature = "serde")), doc = "```ignore")]
//! # fn main() {
//! # use rand::rngs::OsRng;
//! # use ed25519_dalek::{SigningKey, Signature, Signer, Verifier, VerifyingKey};
//! # use bincode::serialize;
//! use bincode::deserialize;
//!
//! # let mut csprng = OsRng{};
//! # let mut csprng = OsRng;
//! # let signing_key: SigningKey = SigningKey::generate(&mut csprng);
//! let message: &[u8] = b"This is a test of the tsunami alert system.";
//! # let signature: Signature = signing_key.sign(message);
@ -236,8 +236,6 @@
//!
//! assert!(verified);
//! # }
//! # #[cfg(not(feature = "serde"))]
//! # fn main() {}
//! ```
#![no_std]

View File

@ -13,7 +13,7 @@
use ed25519::pkcs8::{self, DecodePrivateKey};
#[cfg(feature = "rand")]
use rand::{CryptoRng, RngCore};
use rand_core::CryptoRngCore;
#[cfg(feature = "serde")]
use serde::de::Error as SerdeError;
@ -168,7 +168,7 @@ impl SigningKey {
/// use ed25519_dalek::SigningKey;
/// use ed25519_dalek::Signature;
///
/// let mut csprng = OsRng{};
/// let mut csprng = OsRng;
/// let signing_key: SigningKey = SigningKey::generate(&mut csprng);
///
/// # }
@ -187,10 +187,7 @@ impl SigningKey {
/// which is available with `use sha2::Sha512` as in the example above.
/// Other suitable hash functions include Keccak-512 and Blake2b-512.
#[cfg(feature = "rand")]
pub fn generate<R>(csprng: &mut R) -> SigningKey
where
R: CryptoRng + RngCore,
{
pub fn generate<R: CryptoRngCore + ?Sized>(csprng: &mut R) -> SigningKey {
let mut secret = SecretKey::default();
csprng.fill_bytes(&mut secret);
Self::from_bytes(&secret)

View File

@ -209,7 +209,7 @@ mod integrations {
let good: &[u8] = "test message".as_bytes();
let bad: &[u8] = "wrong message".as_bytes();
let mut csprng = OsRng {};
let mut csprng = OsRng;
signing_key = SigningKey::generate(&mut csprng);
good_sig = signing_key.sign(&good);