Merge branch 'release/0.8.0'

This commit is contained in:
Isis Lovecruft 2018-07-27 21:57:12 +00:00
commit d472254ad3
Failed to extract signature
7 changed files with 4501 additions and 30 deletions

View file

@ -1,6 +1,6 @@
[package]
name = "ed25519-dalek"
version = "0.7.0"
version = "0.8.0"
authors = ["Isis Lovecruft <isis@patternsinthevoid.net>"]
readme = "README.md"
license = "BSD-3-Clause"
@ -16,12 +16,13 @@ exclude = [ ".gitignore", "TESTVECTORS", "res/*" ]
travis-ci = { repository = "dalek-cryptography/ed25519-dalek", branch = "master"}
[dependencies.curve25519-dalek]
version = "0.18"
version = "0.19"
default-features = false
[dependencies.rand]
version = "0.5"
default-features = false
features = ["i128_support"]
[dependencies.digest]
version = "^0.7"
@ -42,6 +43,9 @@ optional = true
version = "^0.1.1"
default-features = false
[dependencies.clear_on_drop]
version = "0.2"
[dev-dependencies]
hex = "^0.3"
sha2 = "^0.7"
@ -56,7 +60,8 @@ 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"]
nightly = ["curve25519-dalek/nightly", "rand/nightly"]
alloc = ["curve25519-dalek/alloc"]
nightly = ["curve25519-dalek/nightly", "rand/nightly", "clear_on_drop/nightly"]
asm = ["sha2/asm"]
yolocrypto = ["curve25519-dalek/yolocrypto"]
u64_backend = ["curve25519-dalek/u64_backend"]

View file

@ -1,4 +1,4 @@
Copyright (c) 2017 Isis Agora Lovecruft. All rights reserved.
Copyright (c) 2017-2018 Isis Agora Lovecruft. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are

View file

@ -9,12 +9,7 @@ Documentation is available [here](https://docs.rs/ed25519-dalek).
# Benchmarks
You need to pass the `--features="bench"` flag to run the benchmarks. The
reason for feature-gating the benchmarks is that Rust's `test::Bencher` is
unstable, and thus only works on the nightly channel. (We'd like people to be
able to compile and test on the stable and beta channels too!)
On an Intel i9-7900X running at 3.30 GHz, without TurboBoost, this code achieves
On an Intel Skylake i9-7900X running at 3.30 GHz, without TurboBoost, this code achieves
the following performance benchmarks:
∃!isisⒶmistakenot:(master *=)~/code/rust/ed25519-dalek ∴ cargo bench
@ -29,8 +24,8 @@ the following performance benchmarks:
By enabling the avx2 backend (on machines with compatible microarchitectures),
the performance for signature verification is greatly improved:
∃!isisⒶmistakenot:(master *=)~/code/rust/ed25519-dalek ∴ export RUSTFLAGS="-C target_cpu=native"
∃!isisⒶmistakenot:(master *=)~/code/rust/ed25519-dalek ∴ cargo bench --no-default-features --features "std avx2_backend"
∃!isisⒶmistakenot:(master *=)~/code/rust/ed25519-dalek ∴ export RUSTFLAGS=-Ctarget_cpu=native
∃!isisⒶmistakenot:(master *=)~/code/rust/ed25519-dalek ∴ cargo bench --features=avx2_backend
Compiling ed25519-dalek v0.7.0 (file:///home/isis/code/rust/ed25519-dalek)
Finished release [optimized] target(s) in 4.28s
Running target/release/deps/ed25519_benchmarks-e4866664de39c84d
@ -40,7 +35,7 @@ the performance for signature verification is greatly improved:
In comparison, the equivalent package in Golang performs as follows:
∃!isisⒶwintermute:(master *=)~/code/go/src/github.com/agl/ed25519 ∴ go test -bench .
∃!isisⒶmistakenot:(master *=)~/code/go/src/github.com/agl/ed25519 ∴ go test -bench .
BenchmarkKeyGeneration 30000 47007 ns/op
BenchmarkSigning 30000 48820 ns/op
BenchmarkVerification 10000 119701 ns/op
@ -60,6 +55,34 @@ Additionally, if you're using a CSPRNG from the `rand` crate, the `nightly`
feature will enable `u128`/`i128` features there, resulting in potentially
faster performance.
If your protocol or application is able to batch signatures for verification,
the `verify_batch()` function has greatly improved performance. On the
aforementioned Intel Skylake i9-7900X, verifying a batch of 96 signatures takes
1.7673ms. That's 18.4094us, or roughly 60750 cycles, per signature verification,
more than double the speed of batch verification given in the original paper
(this is likely not a fair comparison as that was a Nehalem machine).
The numbers after the `/` in the test name refer to the size of the batch:
∃!isisⒶmistakenot:(master *=)~/code/rust/ed25519-dalek ∴ export RUSTFLAGS=-Ctarget_cpu=native
∃!isisⒶmistakenot:(master *=)~/code/rust/ed25519-dalek ∴ cargo bench --features=avx2_backend batch
Compiling ed25519-dalek v0.8.0 (file:///home/isis/code/rust/ed25519-dalek)
Finished release [optimized] target(s) in 34.16s
Running target/release/deps/ed25519_benchmarks-cf0daf7d68fc71b6
Ed25519 batch signature verification/4 time: [105.20 us 106.04 us 106.99 us]
Ed25519 batch signature verification/8 time: [178.66 us 179.01 us 179.39 us]
Ed25519 batch signature verification/16 time: [325.65 us 326.67 us 327.90 us]
Ed25519 batch signature verification/32 time: [617.96 us 620.74 us 624.12 us]
Ed25519 batch signature verification/64 time: [1.1862 ms 1.1900 ms 1.1943 ms]
Ed25519 batch signature verification/96 time: [1.7611 ms 1.7673 ms 1.7742 ms]
Ed25519 batch signature verification/128 time: [2.3320 ms 2.3376 ms 2.3446 ms]
Ed25519 batch signature verification/256 time: [5.0124 ms 5.0290 ms 5.0491 ms]
As you can see, there's an optimal batch size for each machine, so you'll likely
want to your the benchmarks on your target CPU to discover the best size. For
this machine, around 100 signatures per batch is the optimum:
![](https://github.com/dalek-cryptography/ed25519-dalek/blob/master/res/batch-voilin-benchmark.svg)
Additionally, thanks to Rust, this implementation has both type and memory
safety. It's also easily readable by a much larger set of people than those who
can read qhasm, making it more readily and more easily auditable. We're of
@ -107,7 +130,7 @@ To install, add the following to your project's `Cargo.toml`:
```toml
[dependencies.ed25519-dalek]
version = "^0.7"
version = "^0.8"
```
Then, in your library or executable source, add:
@ -123,7 +146,7 @@ enabled by default, instead do:
```toml
[dependencies.ed25519-dalek]
version = "^0.7"
version = "^0.8"
features = ["nightly"]
```
@ -140,7 +163,7 @@ To enable [serde](https://serde.rs) support, build `ed25519-dalek` with:
```toml
[dependencies.ed25519-dalek]
version = "^0.7"
version = "^0.8"
features = ["serde"]
```
@ -152,13 +175,3 @@ likely want to compile with
If you're building for a machine with avx2 instructions, there's also the
experimental `avx2_backend`. To use it, compile with
`RUSTFLAGS="-C target_cpu=native" cargo build --no-default-features --features="avx2_backend"`
# TODO
* Batch signature verification, maybe?
* We can probably make this go even faster if we implement SHA512,
rather than using the rust-crypto implementation whose API requires
that we allocate memory and bzero it before mutating to store the
digest.
* Incorporate ed25519-dalek into Brian Smith's
[crypto-bench](https://github.com/briansmith/crypto-bench).

View file

@ -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: [usize; 8] = [4, 8, 16, 32, 64, 96, 128, 256];
c.bench_function_over_inputs(
"Ed25519 batch signature verification",
|b, &&size| {
let mut csprng: ThreadRng = thread_rng();
let keypairs: Vec<Keypair> = (0..size).map(|_| Keypair::generate::<Sha512, _>(&mut csprng)).collect();
let msg: &[u8] = b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
let messages: Vec<&[u8]> = (0..size).map(|_| msg).collect();
let signatures: Vec<Signature> = keypairs.iter().map(|key| key.sign::<Sha512>(&msg)).collect();
let public_keys: Vec<PublicKey> = keypairs.iter().map(|key| key.public).collect();
b.iter(|| verify_batch::<Sha512>(&messages[..], &signatures[..], &public_keys[..]));
},
&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,
}
}

File diff suppressed because it is too large Load diff

After

Width:  |  Height:  |  Size: 511 KiB

View file

@ -1,7 +1,7 @@
// -*- mode: rust; -*-
//
// This file is part of ed25519-dalek.
// Copyright (c) 2017 Isis Lovecruft
// Copyright (c) 2017-2018 Isis Lovecruft
// See LICENSE for licensing information.
//
// Authors:
@ -10,6 +10,7 @@
//! A Rust implementation of ed25519 EdDSA key generation, signing, and
//! verification.
use core::default::Default;
use core::fmt::{Debug};
use rand::CryptoRng;
@ -27,6 +28,8 @@ use serde::de::Visitor;
#[cfg(feature = "sha2")]
use sha2::Sha512;
use clear_on_drop::clear::Clear;
use digest::Digest;
use generic_array::typenum::U64;
@ -165,6 +168,7 @@ impl<'d> Deserialize<'d> for Signature {
/// An EdDSA secret key.
#[repr(C)]
#[derive(Default)] // we derive Default in order to use the clear() method in Drop
pub struct SecretKey(pub (crate) [u8; SECRET_KEY_LENGTH]);
impl Debug for SecretKey {
@ -173,6 +177,13 @@ impl Debug for SecretKey {
}
}
/// Overwrite secret key material with null bytes when it goes out of scope.
impl Drop for SecretKey {
fn drop(&mut self) {
self.0.clear();
}
}
impl SecretKey {
/// Expand this `SecretKey` into an `ExpandedSecretKey`.
pub fn expand<D>(&self) -> ExpandedSecretKey where D: Digest<OutputSize = U64> + Default {
@ -370,11 +381,20 @@ impl<'d> Deserialize<'d> for SecretKey {
// better-designed, Schnorr-based signature scheme, see Trevor Perrin's work on
// "generalised EdDSA" and "VXEdDSA".
#[repr(C)]
#[derive(Default)] // we derive Default in order to use the clear() method in Drop
pub struct ExpandedSecretKey {
pub (crate) key: Scalar,
pub (crate) nonce: [u8; 32],
}
/// Overwrite secret key material with null bytes when it goes out of scope.
impl Drop for ExpandedSecretKey {
fn drop(&mut self) {
self.key.clear();
self.nonce.clear();
}
}
#[cfg(feature = "sha2")]
impl<'a> From<&'a SecretKey> for ExpandedSecretKey {
/// Construct an `ExpandedSecretKey` from a `SecretKey`.
@ -683,7 +703,7 @@ impl<'d> Deserialize<'d> for ExpandedSecretKey {
}
/// An ed25519 public key.
#[derive(Copy, Clone, Eq, PartialEq)]
#[derive(Copy, Clone, Default, Eq, PartialEq)]
#[repr(C)]
pub struct PublicKey(pub (crate) CompressedEdwardsY);
@ -865,6 +885,119 @@ 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<Keypair> = (0..64).map(|_| Keypair::generate::<Sha512, _>(&mut csprng)).collect();
/// let msg: &[u8] = b"They're good dogs Brant";
/// let messages: Vec<&[u8]> = (0..64).map(|_| msg).collect();
/// let signatures: Vec<Signature> = keypairs.iter().map(|key| key.sign::<Sha512>(&msg)).collect();
/// let public_keys: Vec<PublicKey> = keypairs.iter().map(|key| key.public).collect();
///
/// let result = verify_batch::<Sha512>(&messages[..], &signatures[..], &public_keys[..]);
/// assert!(result.is_ok());
/// # }
/// ```
#[cfg(any(feature = "alloc", feature = "std"))]
#[allow(non_snake_case)]
pub fn verify_batch<D>(messages: &[&[u8]],
signatures: &[Signature],
public_keys: &[PublicKey]) -> Result<(), SignatureError>
where D: Digest<OutputSize = U64> + Default
{
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<Scalar> = signatures
.iter()
.map(|_| Scalar::from(thread_rng().gen::<u128>()))
.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<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer {
@ -894,7 +1027,7 @@ impl<'d> Deserialize<'d> for PublicKey {
}
/// An ed25519 keypair.
#[derive(Debug)]
#[derive(Debug, Default)] // we derive Default in order to use the clear() method in Drop
#[repr(C)]
pub struct Keypair {
/// The secret half of this keypair.
@ -1224,8 +1357,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::*;
@ -1396,6 +1531,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<Keypair> = Vec::new();
let mut signatures: Vec<Signature> = Vec::new();
for i in 0..messages.len() {
let keypair: Keypair = Keypair::generate::<Sha512, _>(&mut csprng);
signatures.push(keypair.sign::<Sha512>(&messages[i]));
keypairs.push(keypair);
}
let public_keys: Vec<PublicKey> = keypairs.iter().map(|key| key.public).collect();
let result = verify_batch::<Sha512>(&messages, &signatures[..], &public_keys[..]);
assert!(result.is_ok());
}
#[test]
fn public_key_from_bytes() {
// Make another function so that we can test the ? operator.
@ -1416,6 +1577,24 @@ mod test {
175, 002, 026, 104, 247, 007, 081, 026, ]))))
}
#[test]
fn keypair_clear_on_drop() {
let mut keypair: Keypair = Keypair::from_bytes(&[15u8; KEYPAIR_LENGTH][..]).unwrap();
keypair.clear();
fn as_bytes<T>(x: &T) -> &[u8] {
use core::mem;
use core::slice;
unsafe {
slice::from_raw_parts(x as *const T as *const u8, mem::size_of_val(x))
}
}
assert!(!as_bytes(&keypair).contains(&0x15));
}
#[cfg(all(test, feature = "serde"))]
use bincode::{serialize, deserialize, Infinite};

View file

@ -1,7 +1,7 @@
// -*- mode: rust; -*-
//
// This file is part of ed25519-dalek.
// Copyright (c) 2017 Isis Lovecruft
// Copyright (c) 2017-2018 Isis Lovecruft
// See LICENSE for licensing information.
//
// Authors:
@ -264,6 +264,7 @@ extern crate generic_array;
extern crate digest;
extern crate failure;
extern crate rand;
extern crate clear_on_drop;
#[cfg(any(feature = "std", test))]
#[macro_use]