diff --git a/.travis.yml b/.travis.yml index 7a99953..4c8d19e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,23 +7,14 @@ rust: env: - TEST_COMMAND=test FEATURES='' + - TEST_COMMAND=test FEATURES=--features="serde" matrix: include: - - rust: stable - env: TEST_COMMAND=test FEATURES=--features="serde" - - rust: beta - env: TEST_COMMAND=test FEATURES=--features="serde" - rust: nightly - env: TEST_COMMAND=test FEATURES=--features="serde" - - rust: nightly - env: TEST_COMMAND=build FEATURES=--no-default-features + env: TEST_COMMAND=build FEATURES="--no-default-features --features=u32_backend" - rust: nightly env: TEST_COMMAND=test FEATURES=--features="nightly" - - rust: nightly - env: TEST_COMMAND=bench FEATURES=--features="bench" - - rust: nightly - env: TEST_COMMAND=bench FEATURES=--features="nightly bench" script: - cargo $TEST_COMMAND $FEATURES diff --git a/Cargo.toml b/Cargo.toml index 55d6173..e37ea8d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ed25519-dalek" -version = "0.6.2" +version = "0.7.0" authors = ["Isis Lovecruft "] readme = "README.md" license = "BSD-3-Clause" @@ -16,16 +16,12 @@ exclude = [ ".gitignore", "TESTVECTORS", "res/*" ] travis-ci = { repository = "dalek-cryptography/ed25519-dalek", branch = "master"} [dependencies.curve25519-dalek] -version = "0.16" -default-features = false - -[dependencies.subtle] -version = "0.6" +version = "0.18" default-features = false [dependencies.rand] -optional = true -version = "0.4" +version = "0.5" +default-features = false [dependencies.digest] version = "^0.7" @@ -50,11 +46,19 @@ default-features = false hex = "^0.3" sha2 = "^0.7" bincode = "^0.9" +criterion = "0.2" + +[[bench]] +name = "ed25519_benchmarks" +harness = false [features] -default = ["std"] -std = ["rand", "subtle/std", "curve25519-dalek/std", "failure/std"] -bench = [] -nightly = ["curve25519-dalek/nightly", "subtle/nightly"] +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"] asm = ["sha2/asm"] yolocrypto = ["curve25519-dalek/yolocrypto"] +u64_backend = ["curve25519-dalek/u64_backend"] +u32_backend = ["curve25519-dalek/u32_backend"] +avx2_backend = ["curve25519-dalek/avx2_backend"] diff --git a/README.md b/README.md index 869b755..3ba0546 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# ed25519-dalek [![](https://img.shields.io/crates/v/ed25519-dalek.svg)](https://crates.io/crates/ed25519-dalek) [![](https://docs.rs/ed25519-dalek/badge.svg)](https://docs.rs/ed25519-dalek) [![](https://travis-ci.org/isislovecruft/ed25519-dalek.svg?branch=master)](https://travis-ci.org/isislovecruft/ed25519-dalek?branch=master) +# ed25519-dalek [![](https://img.shields.io/crates/v/ed25519-dalek.svg)](https://crates.io/crates/ed25519-dalek) [![](https://docs.rs/ed25519-dalek/badge.svg)](https://docs.rs/ed25519-dalek) [![](https://travis-ci.org/dalek-cryptography/ed25519-dalek.svg?branch=master)](https://travis-ci.org/dalek-cryptography/ed25519-dalek?branch=master) Fast and efficient Rust implementation of ed25519 key generation, signing, and verification in Rust. @@ -14,64 +14,51 @@ 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 i5 Sandy Bridge running at 2.6 GHz, with TurboBoost enabled (and -also running in QubesOS with *lots* of other VMs executing), this code -achieves the following performance benchmarks: +On an Intel i9-7900X running at 3.30 GHz, without TurboBoost, this code achieves +the following performance benchmarks: - ∃!isisⒶwintermute:(develop *$)~/code/rust/ed25519 ∴ cargo bench --features="bench" - Finished release [optimized] target(s) in 0.0 secs - Running target/release/deps/ed25519_dalek-281c2d7a2379edae + ∃!isisⒶmistakenot:(master *=)~/code/rust/ed25519-dalek ∴ cargo bench + Compiling ed25519-dalek v0.7.0 (file:///home/isis/code/rust/ed25519-dalek) + Finished release [optimized] target(s) in 3.11s + Running target/release/deps/ed25519_benchmarks-721332beed423bce - running 6 tests - test ed25519::test::golden ... ignored - test ed25519::test::sign_verify ... ignored - test ed25519::test::unmarshal_marshal ... ignored - test ed25519::bench::key_generation ... bench: 54,571 ns/iter (+/- 7,861) - test ed25519::bench::sign ... bench: 70,009 ns/iter (+/- 22,812) - test ed25519::bench::verify ... bench: 185,619 ns/iter (+/- 24,117) + Ed25519 signing time: [15.617 us 15.630 us 15.647 us] + Ed25519 signature verification time: [45.930 us 45.968 us 46.011 us] + Ed25519 keypair generation time: [15.440 us 15.465 us 15.492 us] - test result: ok. 0 passed; 0 failed; 3 ignored; 3 measured +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" + 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 + Ed25519 signing time: [15.923 us 15.945 us 15.967 us] + Ed25519 signature verification time: [33.382 us 33.411 us 33.445 us] + Ed25519 keypair generation time: [15.246 us 15.260 us 15.275 us] In comparison, the equivalent package in Golang performs as follows: ∃!isisⒶwintermute:(master *=)~/code/go/src/github.com/agl/ed25519 ∴ go test -bench . - PASS - BenchmarkKeyGeneration 20000 85880 ns/op - BenchmarkSigning 20000 89115 ns/op - BenchmarkVerification 10000 212585 ns/op - ok github.com/agl/ed25519 7.500s + BenchmarkKeyGeneration 30000 47007 ns/op + BenchmarkSigning 30000 48820 ns/op + BenchmarkVerification 10000 119701 ns/op + ok github.com/agl/ed25519 5.775s -Making key generation, signing, and verification a rough average of one third -faster, one fifth faster, and one eighth faster respectively. Of course, this +Making key generation and signing a rough average of 2x faster, and +verification 2.5-3x faster depending on the availability of avx2. Of course, this is just my machine, and these results—nowhere near rigorous—should be taken with a handful of salt. -Additionally, if you're on the Rust nightly channel, be sure to build with -`cargo build --features="nightly"`, which uses Rust's experimental support for -the `u128` type in curve25519-dalek to speed up field arithmetic by roughly a -factor of two. The benchmarks using nightly (on the same machine as above) -are: +Translating to a rough cycle count: we multiply by a factor of 3.3 to convert +nanoseconds to cycles per second on a 3300 Mhz CPU, that's 110256 cycles for +verification and 52618 for signing, which is competitive with hand-optimised +assembly implementations. - ∃!isisⒶwintermute:(develop *$)~/code/rust/ed25519 ∴ cargo bench --features="bench nightly" - Finished release [optimized] target(s) in 0.0 secs - Running target/release/deps/ed25519_dalek-9d7f8674ae11ac39 - - running 6 tests - test ed25519::test::golden ... ignored - test ed25519::test::sign_verify ... ignored - test ed25519::test::unmarshal_marshal ... ignored - test ed25519::bench::key_generation ... bench: 31,160 ns/iter (+/- 8,597) - test ed25519::bench::sign ... bench: 40,565 ns/iter (+/- 4,758) - test ed25519::bench::verify ... bench: 106,146 ns/iter (+/- 2,796) - - test result: ok. 0 passed; 0 failed; 3 ignored; 3 measured - -Translating to a rough cycle count: we multiply by a factor of 2.6 to convert -nanoseconds to cycles per second on a 2.6 GHz CPU, that's 275979 cycles for -verification and 105469 for signing, which is -[competitive with the optimised assembly version](https://ed25519.cr.yp.to/) -included in the SUPERCOP benchmarking suite (albeit their numbers are for the -older Nehalem microarchitecture). +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. 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 @@ -82,7 +69,7 @@ valuable than simply cycle counts alone. # Warnings ed25519-dalek and -[our elliptic curve library](https://github.com/isislovecruft/curve25519-dalek) +[our elliptic curve library](https://github.com/dalek-cryptography/curve25519-dalek) (which this code uses) have received *one* formal cryptographic and security review. Neither have yet received what we would consider *sufficient* peer review by other qualified cryptographers to be considered in any way, shape, @@ -96,7 +83,7 @@ or form, safe. The signatures produced by this library are malleable, as discussed in [the original paper](https://ed25519.cr.yp.to/ed25519-20110926.pdf): -![](https://github.com/isislovecruft/ed25519-dalek/blob/develop/res/ed25519-malleability.png) +![](https://github.com/dalek-cryptography/ed25519-dalek/blob/master/res/ed25519-malleability.png) We could eliminate the malleability property by multiplying by the curve cofactor, however, this would cause our implementation to *not* match the @@ -111,49 +98,67 @@ In short, if malleable signatures are bad for your protocol, don't use them. Consider using a curve25519-based Verifiable Random Function (VRF), such as [Trevor Perrin's VXEdDSA](https://www.whispersystems.org/docs/specifications/xeddsa/), instead. We -[plan](https://github.com/isislovecruft/curve25519-dalek/issues/9) to +[plan](https://github.com/dalek-cryptography/curve25519-dalek/issues/9) to eventually support VXEdDSA in curve25519-dalek. # Installation To install, add the following to your project's `Cargo.toml`: - [dependencies.ed25519-dalek] - version = "^0.6" +```toml +[dependencies.ed25519-dalek] +version = "^0.7" +``` Then, in your library or executable source, add: - extern crate ed25519_dalek +```rust +extern crate ed25519_dalek; +``` + +# Features To cause your application to build `ed25519-dalek` with the nightly feature enabled by default, instead do: - [dependencies.ed25519-dalek] - version = "^0.6" - features = ["nightly"] +```toml +[dependencies.ed25519-dalek] +version = "^0.7" +features = ["nightly"] +``` To cause your application to instead build with the nightly feature enabled when someone builds with `cargo build --features="nightly"` add the following to the `Cargo.toml`: - [features] - nightly = ["ed25519-dalek/nightly"] - -Using the `nightly` feature will nearly double the latency of signing and -verification. +```toml +[features] +nightly = ["ed25519-dalek/nightly"] +``` To enable [serde](https://serde.rs) support, build `ed25519-dalek` with: - [dependencies.ed25519-dalek] - version = "^0.6" - features = ["serde"] +```toml +[dependencies.ed25519-dalek] +version = "^0.7" +features = ["serde"] +``` +By default, `ed25519-dalek` builds against `curve25519-dalek`'s `u64_backend` +feature, which uses Rust's `i128` feature to achieve roughly double the speed as +the `u32_backend` feature. When targetting 32-bit systems, however, you'll +likely want to compile with + `cargo build --no-default-features --features="u32_backend"`. +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 memzero it before mutating to store the + 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). diff --git a/benches/ed25519_benchmarks.rs b/benches/ed25519_benchmarks.rs new file mode 100644 index 0000000..b9ac890 --- /dev/null +++ b/benches/ed25519_benchmarks.rs @@ -0,0 +1,80 @@ +// -*- mode: rust; -*- +// +// This file is part of ed25519-dalek. +// Copyright (c) 2018 Isis Lovecruft +// See LICENSE for licensing information. +// +// Authors: +// - Isis Agora Lovecruft + +#[macro_use] +extern crate criterion; +extern crate ed25519_dalek; +extern crate rand; +extern crate sha2; + +use criterion::Criterion; + +mod ed25519_benches { + use super::*; + use ed25519_dalek::ExpandedSecretKey; + use ed25519_dalek::Keypair; + use ed25519_dalek::Signature; + use rand::thread_rng; + use rand::ThreadRng; + use sha2::Sha512; + + fn sign(c: &mut Criterion) { + let mut csprng: ThreadRng = thread_rng(); + let keypair: Keypair = Keypair::generate::(&mut csprng); + let msg: &[u8] = b""; + + c.bench_function("Ed25519 signing", move |b| { + b.iter(| | keypair.sign::(msg)) + }); + } + + fn sign_expanded_key(c: &mut Criterion) { + let mut csprng: ThreadRng = thread_rng(); + let keypair: Keypair = Keypair::generate::(&mut csprng); + let expanded: ExpandedSecretKey = keypair.secret.expand::(); + let msg: &[u8] = b""; + + c.bench_function("Ed25519 signing with an expanded secret key", move |b| { + b.iter(| | expanded.sign::(msg, &keypair.public)) + }); + } + + fn verify(c: &mut Criterion) { + let mut csprng: ThreadRng = thread_rng(); + let keypair: Keypair = Keypair::generate::(&mut csprng); + let msg: &[u8] = b""; + let sig: Signature = keypair.sign::(msg); + + c.bench_function("Ed25519 signature verification", move |b| { + b.iter(| | keypair.verify::(msg, &sig)) + }); + } + + fn key_generation(c: &mut Criterion) { + let mut csprng: ThreadRng = thread_rng(); + + c.bench_function("Ed25519 keypair generation", move |b| { + b.iter(| | Keypair::generate::(&mut csprng)) + }); + } + + criterion_group!{ + name = ed25519_benches; + config = Criterion::default(); + targets = + sign, + sign_expanded_key, + verify, + key_generation, + } +} + +criterion_main!( + ed25519_benches::ed25519_benches, +); diff --git a/src/ed25519.rs b/src/ed25519.rs index e7a656c..919ecb4 100644 --- a/src/ed25519.rs +++ b/src/ed25519.rs @@ -12,7 +12,7 @@ use core::fmt::{Debug}; -#[cfg(feature = "std")] +use rand::CryptoRng; use rand::Rng; #[cfg(feature = "serde")] @@ -36,9 +36,7 @@ use curve25519_dalek::edwards::CompressedEdwardsY; use curve25519_dalek::edwards::EdwardsPoint; use curve25519_dalek::scalar::Scalar; -use subtle::ConstantTimeEq; - -use errors::DecodingError; +use errors::SignatureError; use errors::InternalError; /// The length of a curve25519 EdDSA `Signature`, in bytes. @@ -69,10 +67,11 @@ pub const EXPANDED_SECRET_KEY_LENGTH: usize = EXPANDED_SECRET_KEY_KEY_LENGTH + E /// These signatures, unlike the ed25519 signature reference implementation, are /// "detached"—that is, they do **not** include a copy of the message which has /// been signed. -#[derive(Copy)] +#[allow(non_snake_case)] +#[derive(Copy, Eq, PartialEq)] #[repr(C)] pub struct Signature { - /// `r` is an `EdwardsPoint`, formed by using an hash function with + /// `R` is an `EdwardsPoint`, formed by using an hash function with /// 512-bits output to produce the digest of: /// /// - the nonce half of the `ExpandedSecretKey`, and @@ -80,8 +79,8 @@ pub struct Signature { /// /// This digest is then interpreted as a `Scalar` and reduced into an /// element in ℤ/lℤ. The scalar is then multiplied by the distinguished - /// basepoint to produce `r`, and `EdwardsPoint`. - pub (crate) r: CompressedEdwardsY, + /// basepoint to produce `R`, and `EdwardsPoint`. + pub (crate) R: CompressedEdwardsY, /// `s` is a `Scalar`, formed by using an hash function with 512-bits output /// to produce the digest of: @@ -101,21 +100,7 @@ impl Clone for Signature { impl Debug for Signature { fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { - write!(f, "Signature( r: {:?}, s: {:?} )", &self.r, &self.s) - } -} - -impl Eq for Signature {} - -impl PartialEq for Signature { - fn eq(&self, other: &Signature) -> bool { - let mut equal: u8 = 0; - - for i in 0..32 { - equal |= self.r.0[i] ^ other.r.0[i]; - equal |= self.s[i] ^ other.s[i]; - } - equal == 0 + write!(f, "Signature( R: {:?}, s: {:?} )", &self.R, &self.s) } } @@ -125,16 +110,16 @@ impl Signature { pub fn to_bytes(&self) -> [u8; SIGNATURE_LENGTH] { let mut signature_bytes: [u8; SIGNATURE_LENGTH] = [0u8; SIGNATURE_LENGTH]; - signature_bytes[..32].copy_from_slice(&self.r.as_bytes()[..]); + signature_bytes[..32].copy_from_slice(&self.R.as_bytes()[..]); signature_bytes[32..].copy_from_slice(&self.s.as_bytes()[..]); signature_bytes } /// Construct a `Signature` from a slice of bytes. #[inline] - pub fn from_bytes(bytes: &[u8]) -> Result { + pub fn from_bytes(bytes: &[u8]) -> Result { if bytes.len() != SIGNATURE_LENGTH { - return Err(DecodingError(InternalError::BytesLengthError{ + return Err(SignatureError(InternalError::BytesLengthError{ name: "Signature", length: SIGNATURE_LENGTH })); } let mut lower: [u8; 32] = [0u8; 32]; @@ -144,10 +129,10 @@ impl Signature { upper.copy_from_slice(&bytes[32..]); if upper[31] & 224 != 0 { - return Err(DecodingError(InternalError::ScalarFormatError)); + return Err(SignatureError(InternalError::ScalarFormatError)); } - Ok(Signature{ r: CompressedEdwardsY(lower), s: Scalar::from_bits(upper) }) + Ok(Signature{ R: CompressedEdwardsY(lower), s: Scalar::from_bits(upper) }) } } @@ -215,9 +200,9 @@ impl SecretKey { /// # /// use ed25519_dalek::SecretKey; /// use ed25519_dalek::SECRET_KEY_LENGTH; - /// use ed25519_dalek::DecodingError; + /// use ed25519_dalek::SignatureError; /// - /// # fn doctest() -> Result { + /// # fn doctest() -> Result { /// let secret_key_bytes: [u8; SECRET_KEY_LENGTH] = [ /// 157, 097, 177, 157, 239, 253, 090, 096, /// 186, 132, 074, 244, 146, 236, 044, 196, @@ -238,11 +223,11 @@ impl SecretKey { /// # Returns /// /// A `Result` whose okay value is an EdDSA `SecretKey` or whose error value - /// is an `DecodingError` wrapping the internal error that occurred. + /// is an `SignatureError` wrapping the internal error that occurred. #[inline] - pub fn from_bytes(bytes: &[u8]) -> Result { + pub fn from_bytes(bytes: &[u8]) -> Result { if bytes.len() != SECRET_KEY_LENGTH { - return Err(DecodingError(InternalError::BytesLengthError{ + return Err(SignatureError(InternalError::BytesLengthError{ name: "SecretKey", length: SECRET_KEY_LENGTH })); } let mut bits: [u8; 32] = [0u8; 32]; @@ -260,8 +245,9 @@ impl SecretKey { /// extern crate sha2; /// extern crate ed25519_dalek; /// + /// # #[cfg(feature = "std")] /// # fn main() { - /// + /// # /// use rand::Rng; /// use rand::OsRng; /// use sha2::Sha512; @@ -271,8 +257,10 @@ impl SecretKey { /// /// let mut csprng: OsRng = OsRng::new().unwrap(); /// let secret_key: SecretKey = SecretKey::generate(&mut csprng); - /// /// # } + /// # + /// # #[cfg(not(feature = "std"))] + /// # fn main() { } /// ``` /// /// Afterwards, you can generate the corresponding public—provided you also @@ -287,13 +275,14 @@ impl SecretKey { /// # fn main() { /// # /// # use rand::Rng; - /// # use rand::OsRng; + /// # use rand::ChaChaRng; + /// # use rand::SeedableRng; /// # use sha2::Sha512; /// # use ed25519_dalek::PublicKey; /// # use ed25519_dalek::SecretKey; /// # use ed25519_dalek::Signature; /// # - /// # let mut csprng: OsRng = OsRng::new().unwrap(); + /// # let mut csprng: ChaChaRng = ChaChaRng::from_seed([0u8; 32]); /// # let secret_key: SecretKey = SecretKey::generate(&mut csprng); /// /// let public_key: PublicKey = PublicKey::from_secret::(&secret_key); @@ -306,11 +295,10 @@ impl SecretKey { /// /// # Input /// - /// A CSPRNG with a `fill_bytes()` method, e.g. the one returned - /// from `rand::OsRng::new()` (in the `rand` crate). - /// - #[cfg(feature = "std")] - pub fn generate(csprng: &mut Rng) -> SecretKey { + /// A CSPRNG with a `fill_bytes()` method, e.g. `rand::ChaChaRng` + pub fn generate(csprng: &mut T) -> SecretKey + where T: CryptoRng + Rng, + { let mut sk: SecretKey = SecretKey([0u8; 32]); csprng.fill_bytes(&mut sk.0); @@ -398,6 +386,7 @@ impl<'a> From<&'a SecretKey> for ExpandedSecretKey { /// # extern crate sha2; /// # extern crate ed25519_dalek; /// # + /// # #[cfg(all(feature = "std", feature = "sha2"))] /// # fn main() { /// # /// use rand::{Rng, OsRng}; @@ -408,6 +397,9 @@ impl<'a> From<&'a SecretKey> for ExpandedSecretKey { /// let secret_key: SecretKey = SecretKey::generate(&mut csprng); /// let expanded_secret_key: ExpandedSecretKey = ExpandedSecretKey::from(&secret_key); /// # } + /// # + /// # #[cfg(any(not(feature = "std"), not(feature = "sha2")))] + /// # fn main() {} /// ``` fn from(secret_key: &'a SecretKey) -> ExpandedSecretKey { ExpandedSecretKey::from_secret_key::(&secret_key) @@ -430,7 +422,7 @@ impl ExpandedSecretKey { /// # extern crate sha2; /// # extern crate ed25519_dalek; /// # - /// # #[cfg(feature = "sha2")] + /// # #[cfg(all(feature = "sha2", feature = "std"))] /// # fn main() { /// # /// use rand::{Rng, OsRng}; @@ -445,7 +437,7 @@ impl ExpandedSecretKey { /// assert!(&expanded_secret_key_bytes[..] != &[0u8; 64][..]); /// # } /// # - /// # #[cfg(not(feature = "sha2"))] + /// # #[cfg(any(not(feature = "sha2"), not(feature = "std")))] /// # fn main() { } /// ``` #[inline] @@ -462,7 +454,7 @@ impl ExpandedSecretKey { /// # Returns /// /// A `Result` whose okay value is an EdDSA `ExpandedSecretKey` or whose - /// error value is an `DecodingError` describing the error that occurred. + /// error value is an `SignatureError` describing the error that occurred. /// /// # Examples /// @@ -471,13 +463,13 @@ impl ExpandedSecretKey { /// # extern crate sha2; /// # extern crate ed25519_dalek; /// # + /// # #[cfg(all(feature = "sha2", feature = "std"))] + /// # fn do_test() -> Result { + /// # /// use rand::{Rng, OsRng}; /// use ed25519_dalek::{SecretKey, ExpandedSecretKey}; - /// use ed25519_dalek::DecodingError; + /// use ed25519_dalek::SignatureError; /// - /// # #[cfg(feature = "sha2")] - /// # fn do_test() -> Result { - /// # /// let mut csprng: OsRng = OsRng::new().unwrap(); /// let secret_key: SecretKey = SecretKey::generate(&mut csprng); /// let expanded_secret_key: ExpandedSecretKey = ExpandedSecretKey::from(&secret_key); @@ -487,19 +479,19 @@ impl ExpandedSecretKey { /// # Ok(expanded_secret_key_again) /// # } /// # - /// # #[cfg(feature = "sha2")] + /// # #[cfg(all(feature = "sha2", feature = "std"))] /// # fn main() { /// # let result = do_test(); /// # assert!(result.is_ok()); /// # } /// # - /// # #[cfg(not(feature = "sha2"))] - /// # fn main() {} + /// # #[cfg(any(not(feature = "sha2"), not(feature = "std")))] + /// # fn main() { } /// ``` #[inline] - pub fn from_bytes(bytes: &[u8]) -> Result { + pub fn from_bytes(bytes: &[u8]) -> Result { if bytes.len() != EXPANDED_SECRET_KEY_LENGTH { - return Err(DecodingError(InternalError::BytesLengthError{ + return Err(SignatureError(InternalError::BytesLengthError{ name: "ExpandedSecretKey", length: EXPANDED_SECRET_KEY_LENGTH })); } let mut lower: [u8; 32] = [0u8; 32]; @@ -521,7 +513,8 @@ impl ExpandedSecretKey { /// # extern crate sha2; /// # extern crate ed25519_dalek; /// # - /// # fn do_test() { + /// # #[cfg(all(feature = "std", feature = "sha2"))] + /// # fn main() { /// # /// use rand::{Rng, OsRng}; /// use sha2::Sha512; @@ -532,7 +525,8 @@ impl ExpandedSecretKey { /// let expanded_secret_key: ExpandedSecretKey = ExpandedSecretKey::from_secret_key::(&secret_key); /// # } /// # - /// # fn main() { do_test(); } + /// # #[cfg(any(not(feature = "sha2"), not(feature = "std")))] + /// # fn main() { } /// ``` pub fn from_secret_key(secret_key: &SecretKey) -> ExpandedSecretKey where D: Digest + Default { @@ -555,35 +549,110 @@ impl ExpandedSecretKey { } /// Sign a message with this `ExpandedSecretKey`. + #[allow(non_snake_case)] pub fn sign(&self, message: &[u8], public_key: &PublicKey) -> Signature where D: Digest + Default { let mut h: D = D::default(); - let mut hash: [u8; 64] = [0u8; 64]; - let mesg_digest: Scalar; - let hram_digest: Scalar; - let r: EdwardsPoint; + let R: CompressedEdwardsY; + let r: Scalar; let s: Scalar; + let k: Scalar; h.input(&self.nonce); h.input(&message); - hash.copy_from_slice(h.fixed_result().as_slice()); - mesg_digest = Scalar::from_bytes_mod_order_wide(&hash); - - r = &mesg_digest * &constants::ED25519_BASEPOINT_TABLE; + r = Scalar::from_hash(h); + R = (&r * &constants::ED25519_BASEPOINT_TABLE).compress(); h = D::default(); - h.input(r.compress().as_bytes()); + h.input(R.as_bytes()); h.input(public_key.as_bytes()); h.input(&message); - hash.copy_from_slice(h.fixed_result().as_slice()); - hram_digest = Scalar::from_bytes_mod_order_wide(&hash); + k = Scalar::from_hash(h); + s = &(&k * &self.key) + &r; - s = &(&hram_digest * &self.key) + &mesg_digest; - - Signature{ r: r.compress(), s: s } + Signature{ R, s } } + + /// Sign a `prehashed_message` with this `ExpandedSecretKey` using the + /// Ed25519ph algorithm defined in [RFC8032 §5.1][rfc8032]. + /// + /// # Inputs + /// + /// * `prehashed_message` is an instantiated hash digest with 512-bits of + /// output which has had the message to be signed previously fed into its + /// state. + /// * `public_key` is a [`PublicKey`] which corresponds to this secret key. + /// * `context` is an optional context string, up to 255 bytes inclusive, + /// which may be used to provide additional domain separation. If not + /// set, this will default to an empty string. + /// + /// # Returns + /// + /// An Ed25519ph [`Signature`] on the `prehashed_message`. + /// + /// [rfc8032]: https://tools.ietf.org/html/rfc8032#section-5.1 + #[allow(non_snake_case)] + pub fn sign_prehashed(&self, + prehashed_message: D, + public_key: &PublicKey, + context: Option<&'static [u8]>) -> Signature + where D: Digest + Default + { + let mut h: D = D::default(); + let mut prehash: [u8; 64] = [0u8; 64]; + let R: CompressedEdwardsY; + let r: Scalar; + let s: Scalar; + let k: Scalar; + + let ctx: &[u8] = context.unwrap_or(b""); // By default, the context is an empty string. + + debug_assert!(ctx.len() <= 255, "The context must not be longer than 255 octets."); + + let ctx_len: u8 = ctx.len() as u8; + + // Get the result of the pre-hashed message. + prehash.copy_from_slice(prehashed_message.fixed_result().as_slice()); + + // This is the dumbest, ten-years-late, non-admission of fucking up the + // domain separation I have ever seen. Why am I still required to put + // the upper half "prefix" of the hashed "secret key" in here? Why + // can't the user just supply their own nonce and decide for themselves + // whether or not they want a deterministic signature scheme? Why does + // the message go into what's ostensibly the signature domain separation + // hash? Why wasn't there always a way to provide a context string? + // + // ... + // + // This is a really fucking stupid bandaid, and the damned scheme is + // still bleeding from malleability, for fuck's sake. + h.input(b"SigEd25519 no Ed25519 collisions"); + h.input(&[1]); // Ed25519ph + h.input(&[ctx_len]); + h.input(ctx); + h.input(&self.nonce); + h.input(&prehash); + + r = Scalar::from_hash(h); + R = (&r * &constants::ED25519_BASEPOINT_TABLE).compress(); + + h = D::default(); + h.input(b"SigEd25519 no Ed25519 collisions"); + h.input(&[1]); // Ed25519ph + h.input(&[ctx_len]); + h.input(ctx); + h.input(R.as_bytes()); + h.input(public_key.as_bytes()); + h.input(&prehash); + + k = Scalar::from_hash(h); + s = &(&k * &self.key) + &r; + + Signature{ R, s } + } + } #[cfg(feature = "serde")] @@ -652,9 +721,9 @@ impl PublicKey { /// # /// use ed25519_dalek::PublicKey; /// use ed25519_dalek::PUBLIC_KEY_LENGTH; - /// use ed25519_dalek::DecodingError; + /// use ed25519_dalek::SignatureError; /// - /// # fn doctest() -> Result { + /// # fn doctest() -> Result { /// let public_key_bytes: [u8; PUBLIC_KEY_LENGTH] = [ /// 215, 90, 152, 1, 130, 177, 10, 183, 213, 75, 254, 211, 201, 100, 7, 58, /// 14, 225, 114, 243, 218, 166, 35, 37, 175, 2, 26, 104, 247, 7, 81, 26]; @@ -672,11 +741,11 @@ impl PublicKey { /// # Returns /// /// A `Result` whose okay value is an EdDSA `PublicKey` or whose error value - /// is an `DecodingError` describing the error that occurred. + /// is an `SignatureError` describing the error that occurred. #[inline] - pub fn from_bytes(bytes: &[u8]) -> Result { + pub fn from_bytes(bytes: &[u8]) -> Result { if bytes.len() != PUBLIC_KEY_LENGTH { - return Err(DecodingError(InternalError::BytesLengthError{ + return Err(SignatureError(InternalError::BytesLengthError{ name: "PublicKey", length: PUBLIC_KEY_LENGTH })); } let mut bits: [u8; 32] = [0u8; 32]; @@ -685,12 +754,6 @@ impl PublicKey { Ok(PublicKey(CompressedEdwardsY(bits))) } - /// Convert this public key to its underlying extended twisted Edwards coordinate. - #[inline] - fn decompress(&self) -> Option { - self.0.decompress() - } - /// Derive this public key from its corresponding `SecretKey`. #[allow(unused_assignments)] pub fn from_secret(secret_key: &SecretKey) -> PublicKey @@ -718,37 +781,87 @@ impl PublicKey { /// /// # Return /// - /// Returns true if the signature was successfully verified, and - /// false otherwise. - pub fn verify(&self, message: &[u8], signature: &Signature) -> bool + /// Returns `Ok(())` if the signature is valid, and `Err` otherwise. + #[allow(non_snake_case)] + pub fn verify(&self, message: &[u8], signature: &Signature) -> Result<(), SignatureError> where D: Digest + Default { - use curve25519_dalek::edwards::vartime; - let mut h: D = D::default(); - let mut a: EdwardsPoint; - let ao: Option; - let mut digest: [u8; 64] = [0u8; 64]; + let R: EdwardsPoint; + let k: Scalar; - ao = self.decompress(); + let A: EdwardsPoint = match self.0.decompress() { + Some(x) => x, + None => return Err(SignatureError(InternalError::PointDecompressionError)), + }; - if ao.is_some() { - a = ao.unwrap(); - } else { - return false; - } - a = -(&a); - - h.input(signature.r.as_bytes()); + h.input(signature.R.as_bytes()); h.input(self.as_bytes()); h.input(&message); - digest.copy_from_slice(h.fixed_result().as_slice()); + k = Scalar::from_hash(h); + R = EdwardsPoint::vartime_double_scalar_mul_basepoint(&k, &(-A), &signature.s); - let digest_reduced: Scalar = Scalar::from_bytes_mod_order_wide(&digest); - let r: EdwardsPoint = vartime::double_scalar_mul_basepoint(&digest_reduced, &a, &signature.s); + if R.compress() == signature.R { + Ok(()) + } else { + Err(SignatureError(InternalError::VerifyError)) + } + } - (signature.r.as_bytes()).ct_eq(r.compress().as_bytes()).unwrap_u8() == 1 + /// Verify a `signature` on a `prehashed_message` using the Ed25519ph algorithm. + /// + /// # Inputs + /// + /// * `prehashed_message` is an instantiated hash digest with 512-bits of + /// output which has had the message to be signed previously fed into its + /// state. + /// * `context` is an optional context string, up to 255 bytes inclusive, + /// which may be used to provide additional domain separation. If not + /// set, this will default to an empty string. + /// * `signature` is a purported Ed25519ph [`Signature`] on the `prehashed_message`. + /// + /// # Returns + /// + /// Returns `true` if the `signature` was a valid signature created by this + /// `Keypair` on the `prehashed_message`. + /// + /// [rfc8032]: https://tools.ietf.org/html/rfc8032#section-5.1 + #[allow(non_snake_case)] + pub fn verify_prehashed(&self, + prehashed_message: D, + context: Option<&[u8]>, + signature: &Signature) -> Result<(), SignatureError> + where D: Digest + Default + { + let mut h: D = D::default(); + let R: EdwardsPoint; + let k: Scalar; + + let ctx: &[u8] = context.unwrap_or(b""); + debug_assert!(ctx.len() <= 255, "The context must not be longer than 255 octets."); + + let A: EdwardsPoint = match self.0.decompress() { + Some(x) => x, + None => return Err(SignatureError(InternalError::PointDecompressionError)), + }; + + h.input(b"SigEd25519 no Ed25519 collisions"); + h.input(&[1]); // Ed25519ph + h.input(&[ctx.len() as u8]); + h.input(ctx); + h.input(signature.R.as_bytes()); + h.input(self.as_bytes()); + h.input(prehashed_message.fixed_result().as_slice()); + + k = Scalar::from_hash(h); + R = EdwardsPoint::vartime_double_scalar_mul_basepoint(&k, &(-A), &signature.s); + + if R.compress() == signature.R { + Ok(()) + } else { + Err(SignatureError(InternalError::VerifyError)) + } } } @@ -826,10 +939,10 @@ impl Keypair { /// # Returns /// /// A `Result` whose okay value is an EdDSA `Keypair` or whose error value - /// is an `DecodingError` describing the error that occurred. - pub fn from_bytes<'a>(bytes: &'a [u8]) -> Result { + /// is an `SignatureError` describing the error that occurred. + pub fn from_bytes<'a>(bytes: &'a [u8]) -> Result { if bytes.len() != KEYPAIR_LENGTH { - return Err(DecodingError(InternalError::BytesLengthError{ + return Err(SignatureError(InternalError::BytesLengthError{ name: "Keypair", length: KEYPAIR_LENGTH})); } let secret = SecretKey::from_bytes(&bytes[..SECRET_KEY_LENGTH])?; @@ -847,6 +960,7 @@ impl Keypair { /// extern crate sha2; /// extern crate ed25519_dalek; /// + /// # #[cfg(all(feature = "std", feature = "sha2"))] /// # fn main() { /// /// use rand::Rng; @@ -855,25 +969,28 @@ impl Keypair { /// use ed25519_dalek::Keypair; /// use ed25519_dalek::Signature; /// - /// let mut cspring: OsRng = OsRng::new().unwrap(); - /// let keypair: Keypair = Keypair::generate::(&mut cspring); + /// let mut csprng: OsRng = OsRng::new().unwrap(); + /// let keypair: Keypair = Keypair::generate::(&mut csprng); /// /// # } + /// # + /// # #[cfg(any(not(feature = "sha2"), not(feature = "std")))] + /// # fn main() { } /// ``` /// /// # Input /// - /// A CSPRNG with a `fill_bytes()` method, e.g. the one returned - /// from `rand::OsRng::new()` (in the `rand` crate). + /// A CSPRNG with a `fill_bytes()` method, e.g. `rand::ChaChaRng`. /// /// The caller must also supply a hash function which implements the /// `Digest` and `Default` traits, and which returns 512 bits of output. /// The standard hash function used for most ed25519 libraries is SHA-512, /// which is available with `use sha2::Sha512` as in the example above. /// Other suitable hash functions include Keccak-512 and Blake2b-512. - #[cfg(feature = "std")] - pub fn generate(csprng: &mut Rng) -> Keypair - where D: Digest + Default { + pub fn generate(csprng: &mut R) -> Keypair + where D: Digest + Default, + R: CryptoRng + Rng, + { let sk: SecretKey = SecretKey::generate(csprng); let pk: PublicKey = PublicKey::from_secret::(&sk); @@ -886,11 +1003,181 @@ impl Keypair { self.secret.expand::().sign::(&message, &self.public) } + /// Sign a `prehashed_message` with this `Keypair` using the + /// Ed25519ph algorithm defined in [RFC8032 §5.1][rfc8032]. + /// + /// # Inputs + /// + /// * `prehashed_message` is an instantiated hash digest with 512-bits of + /// output which has had the message to be signed previously fed into its + /// state. + /// * `context` is an optional context string, up to 255 bytes inclusive, + /// which may be used to provide additional domain separation. If not + /// set, this will default to an empty string. + /// + /// # Returns + /// + /// An Ed25519ph [`Signature`] on the `prehashed_message`. + /// + /// # Examples + /// + /// ``` + /// extern crate ed25519_dalek; + /// extern crate rand; + /// extern crate sha2; + /// + /// use ed25519_dalek::Keypair; + /// use ed25519_dalek::Signature; + /// use rand::thread_rng; + /// use rand::ThreadRng; + /// use sha2::Sha512; + /// + /// # #[cfg(all(feature = "std", feature = "sha2"))] + /// # fn main() { + /// let mut csprng: ThreadRng = thread_rng(); + /// let keypair: Keypair = Keypair::generate::(&mut csprng); + /// let message: &[u8] = b"All I want is to pet all of the dogs."; + /// + /// // Create a hash digest object which we'll feed the message into: + /// let prehashed: Sha512 = Sha512::default(); + /// + /// prehashed.input(message); + /// # } + /// # + /// # #[cfg(any(not(feature = "sha2"), not(feature = "std")))] + /// # fn main() { } + /// ``` + /// + /// If you want, you can optionally pass a "context". It is generally a + /// good idea to choose a context and try to make it unique to your project + /// and this specific usage of signatures. + /// + /// For example, without this, if you were to [convert your OpenPGP key + /// to a Bitcoin key][terrible_idea] (just as an example, and also Don't + /// Ever Do That) and someone tricked you into signing an "email" which was + /// actually a Bitcoin transaction moving all your magic internet money to + /// their address, it'd be a valid transaction. + /// + /// By adding a context, this trick becomes impossible, because the context + /// is concatenated into the hash, which is then signed. So, going with the + /// previous example, if your bitcoin wallet used a context of + /// "BitcoinWalletAppTxnSigning" and OpenPGP used a context (this is likely + /// the least of their safety problems) of "GPGsCryptoIsntConstantTimeLol", + /// then the signatures produced by both could never match the other, even + /// if they signed the exact same message with the same key. + /// + /// Let's add a context for good measure (remember, you'll want to choose + /// your own!): + /// + /// ``` + /// # extern crate ed25519_dalek; + /// # extern crate rand; + /// # extern crate sha2; + /// # + /// # use ed25519_dalek::Keypair; + /// # use ed25519_dalek::Signature; + /// # use rand::thread_rng; + /// # use rand::ThreadRng; + /// # use sha2::Sha512; + /// # + /// # #[cfg(all(feature = "std", feature = "sha2"))] + /// # fn main() { + /// # let mut csprng: ThreadRng = thread_rng(); + /// # let keypair: Keypair = Keypair::generate::(&mut csprng); + /// # let message: &[u8] = b"All I want is to pet all of the dogs."; + /// # let prehashed: Sha512 = Sha512::default(); + /// # prehashed.input(message); + /// # + /// let context: &[u8] = "Ed25519DalekSignPrehashedDoctest"; + /// + /// let sig: Signature = keypair.sign_prehashed(prehashed, Some(context)); + /// # } + /// # + /// # #[cfg(any(not(feature = "sha2"), not(feature = "std")))] + /// # fn main() { } + /// ``` + /// + /// [rfc8032]: https://tools.ietf.org/html/rfc8032#section-5.1 + /// [terrible_idea]: https://github.com/isislovecruft/scripts/blob/master/gpgkey2bc.py + pub fn sign_prehashed(&self, + prehashed_message: D, + context: Option<&'static [u8]>) -> Signature + where D: Digest + Default + { + self.secret.expand::().sign_prehashed::(prehashed_message, &self.public, context) + } + /// Verify a signature on a message with this keypair's public key. - pub fn verify(&self, message: &[u8], signature: &Signature) -> bool + pub fn verify(&self, message: &[u8], signature: &Signature) -> Result<(), SignatureError> where D: Digest + Default { self.public.verify::(message, signature) } + + /// Verify a `signature` on a `prehashed_message` using the Ed25519ph algorithm. + /// + /// # Inputs + /// + /// * `prehashed_message` is an instantiated hash digest with 512-bits of + /// output which has had the message to be signed previously fed into its + /// state. + /// * `context` is an optional context string, up to 255 bytes inclusive, + /// which may be used to provide additional domain separation. If not + /// set, this will default to an empty string. + /// * `signature` is a purported Ed25519ph [`Signature`] on the `prehashed_message`. + /// + /// # Returns + /// + /// Returns `true` if the `signature` was a valid signature created by this + /// `Keypair` on the `prehashed_message`. + /// + /// # Examples + /// + /// ``` + /// extern crate ed25519_dalek; + /// extern crate rand; + /// extern crate sha2; + /// + /// use ed25519_dalek::Keypair; + /// use ed25519_dalek::Signature; + /// use rand::thread_rng; + /// use rand::ThreadRng; + /// use sha2::Sha512; + /// + /// # #[cfg(all(feature = "std", feature = "sha2"))] + /// # fn main() { + /// let mut csprng: ThreadRng = thread_rng(); + /// let keypair: Keypair = Keypair::generate::(&mut csprng); + /// let message: &[u8] = b"All I want is to pet all of the dogs."; + /// + /// let prehashed: Sha512 = Sha512::default(); + /// prehashed.input(message); + /// + /// let context: &[u8] = "Ed25519DalekSignPrehashedDoctest"; + /// + /// let sig: Signature = keypair.sign_prehashed(prehashed, Some(context)); + /// + /// // The sha2::Sha512 struct doesn't implement Copy, so we'll have to create a new one: + /// let prehashed_again: Sha512 = Sha512::default(); + /// prehashed_again.input(message); + /// + /// let valid: bool = keypair.public.verify_prehashed(prehashed_again, context, sig); + /// + /// assert!(valid); + /// # } + /// # + /// # #[cfg(any(not(feature = "sha2"), not(feature = "std")))] + /// # fn main() { } + /// ``` + /// + /// [rfc8032]: https://tools.ietf.org/html/rfc8032#section-5.1 + pub fn verify_prehashed(&self, + prehashed_message: D, + context: Option<&[u8]>, + signature: &Signature) -> Result<(), SignatureError> + where D: Digest + Default + { + self.public.verify_prehashed::(prehashed_message, context, signature) + } } #[cfg(feature = "serde")] @@ -937,8 +1224,8 @@ mod test { use std::fs::File; use std::string::String; use std::vec::Vec; - use curve25519_dalek::edwards::EdwardsPoint; - use rand::OsRng; + use rand::ChaChaRng; + use rand::SeedableRng; use hex::FromHex; use sha2::Sha512; use super::*; @@ -969,34 +1256,9 @@ mod test { 216, 085, 134, 144, 129, 149, 041, 081, 063, 120, 126, 100, 092, 059, 050, 011, ]; - #[test] - fn unmarshal_marshal() { // TestUnmarshalMarshal - let mut cspring: OsRng; - let mut keypair: Keypair; - let mut x: Option; - let a: EdwardsPoint; - let public: PublicKey; - - cspring = OsRng::new().unwrap(); - - // from_bytes() fails if vx²-u=0 and vx²+u=0 - loop { - keypair = Keypair::generate::(&mut cspring); - x = keypair.public.decompress(); - - if x.is_some() { - a = x.unwrap(); - break; - } - } - public = PublicKey(a.compress()); - - assert!(keypair.public.0 == public.0); - } - #[test] fn sign_verify() { // TestSignVerify - let mut cspring: OsRng; + let mut csprng: ChaChaRng; let keypair: Keypair; let good_sig: Signature; let bad_sig: Signature; @@ -1004,16 +1266,16 @@ mod test { let good: &[u8] = "test message".as_bytes(); let bad: &[u8] = "wrong message".as_bytes(); - cspring = OsRng::new().unwrap(); - keypair = Keypair::generate::(&mut cspring); + csprng = ChaChaRng::from_seed([0u8; 32]); + keypair = Keypair::generate::(&mut csprng); good_sig = keypair.sign::(&good); bad_sig = keypair.sign::(&bad); - assert!(keypair.verify::(&good, &good_sig) == true, + assert!(keypair.verify::(&good, &good_sig).is_ok(), "Verification of a valid signature failed!"); - assert!(keypair.verify::(&good, &bad_sig) == false, + assert!(keypair.verify::(&good, &bad_sig).is_err(), "Verification of a signature on a different message passed!"); - assert!(keypair.verify::(&bad, &good_sig) == false, + assert!(keypair.verify::(&bad, &good_sig).is_err(), "Verification of a signature on a different message passed!"); } @@ -1058,15 +1320,86 @@ mod test { let sig2: Signature = keypair.sign::(&msg_bytes); assert!(sig1 == sig2, "Signature bytes not equal on line {}", lineno); - assert!(keypair.verify::(&msg_bytes, &sig2), + assert!(keypair.verify::(&msg_bytes, &sig2).is_ok(), "Signature verification failed on line {}", lineno); } } + // From https://tools.ietf.org/html/rfc8032#section-7.3 + #[test] + fn ed25519ph_rf8032_test_vector() { + let secret_key: &[u8] = b"833fe62409237b9d62ec77587520911e9a759cec1d19755b7da901b96dca3d42"; + let public_key: &[u8] = b"ec172b93ad5e563bf4932c70e1245034c35467ef2efd4d64ebf819683467e2bf"; + let message: &[u8] = b"616263"; + let signature: &[u8] = b"98a70222f0b8121aa9d30f813d683f809e462b469c7ff87639499bb94e6dae4131f85042463c2a355a2003d062adf5aaa10b8c61e636062aaad11c2a26083406"; + + let sec_bytes: Vec = FromHex::from_hex(secret_key).unwrap(); + let pub_bytes: Vec = FromHex::from_hex(public_key).unwrap(); + let msg_bytes: Vec = FromHex::from_hex(message).unwrap(); + let sig_bytes: Vec = FromHex::from_hex(signature).unwrap(); + + let secret: SecretKey = SecretKey::from_bytes(&sec_bytes[..SECRET_KEY_LENGTH]).unwrap(); + let public: PublicKey = PublicKey::from_bytes(&pub_bytes[..PUBLIC_KEY_LENGTH]).unwrap(); + let keypair: Keypair = Keypair{ secret: secret, public: public }; + let sig1: Signature = Signature::from_bytes(&sig_bytes[..]).unwrap(); + + let mut prehash_for_signing: Sha512 = Sha512::default(); + let mut prehash_for_verifying: Sha512 = Sha512::default(); + + prehash_for_signing.input(&msg_bytes[..]); + prehash_for_verifying.input(&msg_bytes[..]); + + let sig2: Signature = keypair.sign_prehashed(prehash_for_signing, None); + + assert!(sig1 == sig2, + "Original signature from test vectors doesn't equal signature produced:\ + \noriginal:\n{:?}\nproduced:\n{:?}", sig1, sig2); + assert!(keypair.verify_prehashed(prehash_for_verifying, None, &sig2).is_ok(), + "Could not verify ed25519ph signature!"); + } + + #[test] + fn ed25519ph_sign_verify() { + let mut csprng: ChaChaRng; + let keypair: Keypair; + let good_sig: Signature; + let bad_sig: Signature; + + let good: &[u8] = b"test message"; + let bad: &[u8] = b"wrong message"; + + // ugh… there's no `impl Copy for Sha512`… i hope we can all agree these are the same hashes + let mut prehashed_good1: Sha512 = Sha512::default(); + prehashed_good1.input(good); + let mut prehashed_good2: Sha512 = Sha512::default(); + prehashed_good2.input(good); + let mut prehashed_good3: Sha512 = Sha512::default(); + prehashed_good3.input(good); + + let mut prehashed_bad1: Sha512 = Sha512::default(); + prehashed_bad1.input(bad); + let mut prehashed_bad2: Sha512 = Sha512::default(); + prehashed_bad2.input(bad); + + let context: &[u8] = b"testing testing 1 2 3"; + + csprng = ChaChaRng::from_seed([0u8; 32]); + keypair = Keypair::generate::(&mut csprng); + good_sig = keypair.sign_prehashed::(prehashed_good1, Some(context)); + bad_sig = keypair.sign_prehashed::(prehashed_bad1, Some(context)); + + assert!(keypair.verify_prehashed::(prehashed_good2, Some(context), &good_sig).is_ok(), + "Verification of a valid signature failed!"); + assert!(keypair.verify_prehashed::(prehashed_good3, Some(context), &bad_sig).is_err(), + "Verification of a signature on a different message passed!"); + assert!(keypair.verify_prehashed::(prehashed_bad2, Some(context), &good_sig).is_err(), + "Verification of a signature on a different message passed!"); + } + #[test] fn public_key_from_bytes() { // Make another function so that we can test the ? operator. - fn do_the_test() -> Result { + fn do_the_test() -> Result { let public_key_bytes: [u8; PUBLIC_KEY_LENGTH] = [ 215, 090, 152, 001, 130, 177, 010, 183, 213, 075, 254, 211, 201, 100, 007, 058, @@ -1116,79 +1449,3 @@ mod test { } } } - -#[cfg(all(test, feature = "bench"))] -mod bench { - use test::Bencher; - use rand::OsRng; - use sha2::Sha512; - use super::*; - - /// A fake RNG which simply returns zeroes. - struct ZeroRng; - - impl ZeroRng { - pub fn new() -> ZeroRng { - ZeroRng - } - } - - impl Rng for ZeroRng { - fn next_u32(&mut self) -> u32 { 0u32 } - - fn fill_bytes(&mut self, bytes: &mut [u8]) { - for i in 0 .. bytes.len() { - bytes[i] = 0; - } - } - } - - #[bench] - fn sign(b: &mut Bencher) { - let mut csprng: OsRng = OsRng::new().unwrap(); - let keypair: Keypair = Keypair::generate::(&mut csprng); - let msg: &[u8] = b""; - - b.iter(| | keypair.sign::(msg)); - } - - #[bench] - fn sign_expanded_key(b: &mut Bencher) { - let mut csprng: OsRng = OsRng::new().unwrap(); - let keypair: Keypair = Keypair::generate::(&mut csprng); - let expanded: ExpandedSecretKey = keypair.secret.expand::(); - let msg: &[u8] = b""; - - b.iter(| | expanded.sign::(msg, &keypair.public)); - } - - #[bench] - fn verify(b: &mut Bencher) { - let mut csprng: OsRng = OsRng::new().unwrap(); - let keypair: Keypair = Keypair::generate::(&mut csprng); - let msg: &[u8] = b""; - let sig: Signature = keypair.sign::(msg); - - b.iter(| | keypair.verify::(msg, &sig)); - } - - #[bench] - fn key_generation(b: &mut Bencher) { - let mut rng: ZeroRng = ZeroRng::new(); - - b.iter(| | Keypair::generate::(&mut rng)); - } - - #[bench] - fn underlying_scalar_mult_basepoint(b: &mut Bencher) { - use curve25519_dalek::constants::ED25519_BASEPOINT_TABLE; - - let scalar: Scalar = Scalar::from_bits([ - 20, 130, 129, 196, 247, 182, 211, 102, - 11, 168, 169, 131, 159, 69, 126, 35, - 109, 193, 175, 54, 118, 234, 138, 81, - 60, 183, 80, 186, 92, 248, 132, 13, ]); - - b.iter(| | &scalar * &ED25519_BASEPOINT_TABLE); - } -} diff --git a/src/errors.rs b/src/errors.rs index 57968dd..bf568a6 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -20,7 +20,6 @@ use core::fmt::Display; /// need to pay any attention to these. #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] pub (crate) enum InternalError { - #[allow(dead_code)] PointDecompressionError, ScalarFormatError, /// An error in the length of bytes handed to a constructor. @@ -29,55 +28,52 @@ pub (crate) enum InternalError { /// returning the error, and the `length` in bytes which its constructor /// expects. BytesLengthError{ name: &'static str, length: usize }, + /// The verification equation wasn't satisfied + VerifyError, } impl Display for InternalError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { InternalError::PointDecompressionError - => write!(f, "Cannot decompress extended twisted edwards point"), + => write!(f, "Cannot decompress Edwards point"), InternalError::ScalarFormatError => write!(f, "Cannot use scalar with high-bit set"), InternalError::BytesLengthError{ name: n, length: l} => write!(f, "{} must be {} bytes in length", n, l), + InternalError::VerifyError + => write!(f, "Verification equation was not satisfied"), } } } impl ::failure::Fail for InternalError {} -/// Errors which may occur in the `from_bytes()` constructors of `PublicKey`, -/// `SecretKey`, `ExpandedSecretKey`, `Keypair`, and `Signature`. -/// -/// There was an internal problem due to parsing the `Signature`. +/// Errors which may occur while processing signatures and keypairs. /// /// This error may arise due to: /// +/// * Being given bytes with a length different to what was expected. +/// /// * A problem decompressing `r`, a curve point, in the `Signature`, or the /// curve point for a `PublicKey`. +/// /// * A problem with the format of `s`, a scalar, in the `Signature`. This /// is only raised if the high-bit of the scalar was set. (Scalars must /// only be constructed from 255-bit integers.) -/// * Being given bytes with a length different to what was expected. +/// +/// * Failure of a signature to satisfy the verification equation. #[derive(Clone, Copy, Eq, PartialEq, Hash, Debug)] -pub struct DecodingError(pub (crate) InternalError); +pub struct SignatureError(pub (crate) InternalError); -impl Display for DecodingError { +impl Display for SignatureError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self.0 { - InternalError::PointDecompressionError => write!(f, "{}", self.0), - InternalError::ScalarFormatError => write!(f, "{}", self.0), - InternalError::BytesLengthError{ name: _, length: _ } => write!(f, "{}", self.0), - } + write!(f, "{}", self.0) } } -impl ::failure::Fail for DecodingError { +impl ::failure::Fail for SignatureError { fn cause(&self) -> Option<&::failure::Fail> { - match self.0 { - InternalError::PointDecompressionError => Some(&self.0), - InternalError::ScalarFormatError => Some(&self.0), - InternalError::BytesLengthError{ name: _, length: _} => Some(&self.0), - } + Some(&self.0) } } diff --git a/src/lib.rs b/src/lib.rs index a9a34e4..b74999a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,7 +15,7 @@ //! //! First, we need to generate a `Keypair`, which includes both public and //! secret halves of an asymmetric key. To do so, we need a cryptographically -//! secure pseudorandom number generator (CSPRING), and a hash function which +//! secure pseudorandom number generator (CSPRNG), and a hash function which //! has 512 bits of output. For this example, we'll use the operating //! system's builtin PRNG and SHA-512 to generate a keypair: //! @@ -24,6 +24,7 @@ //! extern crate sha2; //! extern crate ed25519_dalek; //! +//! # #[cfg(all(feature = "std", feature = "sha2"))] //! # fn main() { //! use rand::Rng; //! use rand::OsRng; @@ -31,9 +32,12 @@ //! use ed25519_dalek::Keypair; //! use ed25519_dalek::Signature; //! -//! let mut cspring: OsRng = OsRng::new().unwrap(); -//! let keypair: Keypair = Keypair::generate::(&mut cspring); +//! let mut csprng: OsRng = OsRng::new().unwrap(); +//! let keypair: Keypair = Keypair::generate::(&mut csprng); //! # } +//! # +//! # #[cfg(any(not(feature = "std"), not(feature = "sha2")))] +//! # fn main() { } //! ``` //! //! We can now use this `keypair` to sign a message: @@ -44,12 +48,13 @@ //! # extern crate ed25519_dalek; //! # fn main() { //! # use rand::Rng; -//! # use rand::OsRng; +//! # use rand::ChaChaRng; +//! # use rand::SeedableRng; //! # use sha2::Sha512; //! # use ed25519_dalek::Keypair; //! # use ed25519_dalek::Signature; -//! # let mut cspring: OsRng = OsRng::new().unwrap(); -//! # let keypair: Keypair = Keypair::generate::(&mut cspring); +//! # let mut csprng: ChaChaRng = ChaChaRng::from_seed([0u8; 32]); +//! # let keypair: Keypair = Keypair::generate::(&mut csprng); //! let message: &[u8] = "This is a test of the tsunami alert system.".as_bytes(); //! let signature: Signature = keypair.sign::(message); //! # } @@ -64,17 +69,16 @@ //! # extern crate ed25519_dalek; //! # fn main() { //! # use rand::Rng; -//! # use rand::OsRng; +//! # use rand::ChaChaRng; +//! # use rand::SeedableRng; //! # use sha2::Sha512; //! # use ed25519_dalek::Keypair; //! # use ed25519_dalek::Signature; -//! # let mut cspring: OsRng = OsRng::new().unwrap(); -//! # let keypair: Keypair = Keypair::generate::(&mut cspring); +//! # let mut csprng: ChaChaRng = ChaChaRng::from_seed([0u8; 32]); +//! # let keypair: Keypair = Keypair::generate::(&mut csprng); //! # let message: &[u8] = "This is a test of the tsunami alert system.".as_bytes(); //! # let signature: Signature = keypair.sign::(message); -//! let verified: bool = keypair.verify::(message, &signature); -//! -//! assert!(verified); +//! assert!(keypair.verify::(message, &signature).is_ok()); //! # } //! ``` //! @@ -87,20 +91,19 @@ //! # extern crate ed25519_dalek; //! # fn main() { //! # use rand::Rng; -//! # use rand::OsRng; +//! # use rand::ChaChaRng; +//! # use rand::SeedableRng; //! # use sha2::Sha512; //! # use ed25519_dalek::Keypair; //! # use ed25519_dalek::Signature; //! use ed25519_dalek::PublicKey; -//! # let mut cspring: OsRng = OsRng::new().unwrap(); -//! # let keypair: Keypair = Keypair::generate::(&mut cspring); +//! # let mut csprng: ChaChaRng = ChaChaRng::from_seed([0u8; 32]); +//! # let keypair: Keypair = Keypair::generate::(&mut csprng); //! # let message: &[u8] = "This is a test of the tsunami alert system.".as_bytes(); //! # let signature: Signature = keypair.sign::(message); //! //! let public_key: PublicKey = keypair.public; -//! let verified: bool = public_key.verify::(message, &signature); -//! -//! assert!(verified); +//! assert!(public_key.verify::(message, &signature).is_ok()); //! # } //! ``` //! @@ -117,16 +120,15 @@ //! # extern crate sha2; //! # extern crate ed25519_dalek; //! # fn main() { -//! # use rand::{Rng, OsRng}; +//! # use rand::{Rng, ChaChaRng, SeedableRng}; //! # use sha2::Sha512; //! # use ed25519_dalek::{Keypair, Signature, PublicKey}; //! use ed25519_dalek::{PUBLIC_KEY_LENGTH, SECRET_KEY_LENGTH, KEYPAIR_LENGTH, SIGNATURE_LENGTH}; -//! # let mut cspring: OsRng = OsRng::new().unwrap(); -//! # let keypair: Keypair = Keypair::generate::(&mut cspring); +//! # let mut csprng: ChaChaRng = ChaChaRng::from_seed([0u8; 32]); +//! # let keypair: Keypair = Keypair::generate::(&mut csprng); //! # let message: &[u8] = "This is a test of the tsunami alert system.".as_bytes(); //! # let signature: Signature = keypair.sign::(message); //! # let public_key: PublicKey = keypair.public; -//! # let verified: bool = public_key.verify::(message, &signature); //! //! let public_key_bytes: [u8; PUBLIC_KEY_LENGTH] = public_key.to_bytes(); //! let secret_key_bytes: [u8; SECRET_KEY_LENGTH] = keypair.secret.to_bytes(); @@ -141,13 +143,13 @@ //! # extern crate rand; //! # extern crate sha2; //! # extern crate ed25519_dalek; -//! # use rand::{Rng, OsRng}; +//! # use rand::{Rng, ChaChaRng, SeedableRng}; //! # use sha2::Sha512; -//! # use ed25519_dalek::{Keypair, Signature, PublicKey, SecretKey, DecodingError}; +//! # use ed25519_dalek::{Keypair, Signature, PublicKey, SecretKey, SignatureError}; //! # use ed25519_dalek::{PUBLIC_KEY_LENGTH, SECRET_KEY_LENGTH, KEYPAIR_LENGTH, SIGNATURE_LENGTH}; -//! # fn do_test() -> Result<(SecretKey, PublicKey, Keypair, Signature), DecodingError> { -//! # let mut cspring: OsRng = OsRng::new().unwrap(); -//! # let keypair_orig: Keypair = Keypair::generate::(&mut cspring); +//! # fn do_test() -> Result<(SecretKey, PublicKey, Keypair, Signature), SignatureError> { +//! # let mut csprng: ChaChaRng = ChaChaRng::from_seed([0u8; 32]); +//! # let keypair_orig: Keypair = Keypair::generate::(&mut csprng); //! # let message: &[u8] = "This is a test of the tsunami alert system.".as_bytes(); //! # let signature_orig: Signature = keypair_orig.sign::(message); //! # let public_key_bytes: [u8; PUBLIC_KEY_LENGTH] = keypair_orig.public.to_bytes(); @@ -173,7 +175,7 @@ //! types additionally come with built-in [serde](https://serde.rs) support by //! building `ed25519-dalek` via: //! -//! ```ignore,bash +//! ```bash //! $ cargo build --features="serde" //! ``` //! @@ -191,12 +193,12 @@ //! //! # #[cfg(feature = "serde")] //! # fn main() { -//! # use rand::{Rng, OsRng}; +//! # use rand::{Rng, ChaChaRng, SeedableRng}; //! # use sha2::Sha512; //! # use ed25519_dalek::{Keypair, Signature, PublicKey}; //! use bincode::{serialize, Infinite}; -//! # let mut cspring: OsRng = OsRng::new().unwrap(); -//! # let keypair: Keypair = Keypair::generate::(&mut cspring); +//! # let mut csprng: ChaChaRng = ChaChaRng::from_seed([0u8; 32]); +//! # let keypair: Keypair = Keypair::generate::(&mut csprng); //! # let message: &[u8] = "This is a test of the tsunami alert system.".as_bytes(); //! # let signature: Signature = keypair.sign::(message); //! # let public_key: PublicKey = keypair.public; @@ -223,14 +225,14 @@ //! # //! # #[cfg(feature = "serde")] //! # fn main() { -//! # use rand::{Rng, OsRng}; +//! # use rand::{Rng, ChaChaRng, SeedableRng}; //! # use sha2::Sha512; //! # use ed25519_dalek::{Keypair, Signature, PublicKey}; //! # use bincode::{serialize, Infinite}; //! use bincode::{deserialize}; //! -//! # let mut cspring: OsRng = OsRng::new().unwrap(); -//! # let keypair: Keypair = Keypair::generate::(&mut cspring); +//! # let mut csprng: ChaChaRng = ChaChaRng::from_seed([0u8; 32]); +//! # let keypair: Keypair = Keypair::generate::(&mut csprng); //! let message: &[u8] = "This is a test of the tsunami alert system.".as_bytes(); //! # let signature: Signature = keypair.sign::(message); //! # let public_key: PublicKey = keypair.public; @@ -260,10 +262,7 @@ extern crate curve25519_dalek; extern crate generic_array; extern crate digest; -extern crate subtle; extern crate failure; - -#[cfg(feature = "std")] extern crate rand; #[cfg(any(feature = "std", test))]