From 51f176007e56bcbce822748938ca12eb93886e53 Mon Sep 17 00:00:00 2001 From: Robin Millette Date: Mon, 2 Apr 2018 17:25:40 -0400 Subject: [PATCH 01/24] fix links image and #9 --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 869b755..04139e7 100644 --- a/README.md +++ b/README.md @@ -96,7 +96,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,7 +111,7 @@ 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 From f6a631c2298183b8cb893c92a0b1666937c5c6ca Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Tue, 15 May 2018 23:34:01 +0000 Subject: [PATCH 02/24] WIP Update curve25519-dalek dependency to 0.17. --- Cargo.toml | 9 ++++++--- src/ed25519.rs | 23 ++++++++++++++--------- src/lib.rs | 12 ++++++------ 3 files changed, 26 insertions(+), 18 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 55d6173..f0ee015 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ exclude = [ ".gitignore", "TESTVECTORS", "res/*" ] travis-ci = { repository = "dalek-cryptography/ed25519-dalek", branch = "master"} [dependencies.curve25519-dalek] -version = "0.16" +version = "0.17" default-features = false [dependencies.subtle] @@ -25,7 +25,7 @@ default-features = false [dependencies.rand] optional = true -version = "0.4" +version = "0.5.0-pre.2" [dependencies.digest] version = "^0.7" @@ -52,9 +52,12 @@ sha2 = "^0.7" bincode = "^0.9" [features] -default = ["std"] +default = ["std", "u64_backend"] std = ["rand", "subtle/std", "curve25519-dalek/std", "failure/std"] bench = [] nightly = ["curve25519-dalek/nightly", "subtle/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/src/ed25519.rs b/src/ed25519.rs index e7a656c..1803aa1 100644 --- a/src/ed25519.rs +++ b/src/ed25519.rs @@ -12,6 +12,8 @@ use core::fmt::{Debug}; +#[cfg(feature = "std")] +use rand::CryptoRng; #[cfg(feature = "std")] use rand::Rng; @@ -310,7 +312,9 @@ impl SecretKey { /// from `rand::OsRng::new()` (in the `rand` crate). /// #[cfg(feature = "std")] - pub fn generate(csprng: &mut Rng) -> SecretKey { + pub fn generate(csprng: &mut T) -> SecretKey + where T: CryptoRng + Rng, + { let mut sk: SecretKey = SecretKey([0u8; 32]); csprng.fill_bytes(&mut sk.0); @@ -723,8 +727,6 @@ impl PublicKey { pub fn verify(&self, message: &[u8], signature: &Signature) -> bool where D: Digest + Default { - use curve25519_dalek::edwards::vartime; - let mut h: D = D::default(); let mut a: EdwardsPoint; let ao: Option; @@ -746,7 +748,8 @@ impl PublicKey { digest.copy_from_slice(h.fixed_result().as_slice()); let digest_reduced: Scalar = Scalar::from_bytes_mod_order_wide(&digest); - let r: EdwardsPoint = vartime::double_scalar_mul_basepoint(&digest_reduced, &a, &signature.s); + let r: EdwardsPoint = EdwardsPoint::vartime_double_scalar_mul_basepoint(&digest_reduced, + &a, &signature.s); (signature.r.as_bytes()).ct_eq(r.compress().as_bytes()).unwrap_u8() == 1 } @@ -856,7 +859,7 @@ impl Keypair { /// use ed25519_dalek::Signature; /// /// let mut cspring: OsRng = OsRng::new().unwrap(); - /// let keypair: Keypair = Keypair::generate::(&mut cspring); + /// let keypair: Keypair = Keypair::generate::(&mut cspring); /// /// # } /// ``` @@ -872,8 +875,10 @@ impl Keypair { /// 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); @@ -981,7 +986,7 @@ mod test { // from_bytes() fails if vx²-u=0 and vx²+u=0 loop { - keypair = Keypair::generate::(&mut cspring); + keypair = Keypair::generate::(&mut cspring); x = keypair.public.decompress(); if x.is_some() { @@ -1005,7 +1010,7 @@ mod test { let bad: &[u8] = "wrong message".as_bytes(); cspring = OsRng::new().unwrap(); - keypair = Keypair::generate::(&mut cspring); + keypair = Keypair::generate::(&mut cspring); good_sig = keypair.sign::(&good); bad_sig = keypair.sign::(&bad); diff --git a/src/lib.rs b/src/lib.rs index a9a34e4..99011e2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -32,7 +32,7 @@ //! use ed25519_dalek::Signature; //! //! let mut cspring: OsRng = OsRng::new().unwrap(); -//! let keypair: Keypair = Keypair::generate::(&mut cspring); +//! let keypair: Keypair = Keypair::generate::(&mut cspring); //! # } //! ``` //! @@ -49,7 +49,7 @@ //! # use ed25519_dalek::Keypair; //! # use ed25519_dalek::Signature; //! # let mut cspring: OsRng = OsRng::new().unwrap(); -//! # let keypair: Keypair = Keypair::generate::(&mut cspring); +//! # let keypair: Keypair = Keypair::generate::(&mut cspring); //! let message: &[u8] = "This is a test of the tsunami alert system.".as_bytes(); //! let signature: Signature = keypair.sign::(message); //! # } @@ -69,7 +69,7 @@ //! # use ed25519_dalek::Keypair; //! # use ed25519_dalek::Signature; //! # let mut cspring: OsRng = OsRng::new().unwrap(); -//! # let keypair: Keypair = Keypair::generate::(&mut cspring); +//! # let keypair: Keypair = Keypair::generate::(&mut cspring); //! # 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); @@ -93,7 +93,7 @@ //! # use ed25519_dalek::Signature; //! use ed25519_dalek::PublicKey; //! # let mut cspring: OsRng = OsRng::new().unwrap(); -//! # let keypair: Keypair = Keypair::generate::(&mut cspring); +//! # let keypair: Keypair = Keypair::generate::(&mut cspring); //! # let message: &[u8] = "This is a test of the tsunami alert system.".as_bytes(); //! # let signature: Signature = keypair.sign::(message); //! @@ -122,7 +122,7 @@ //! # 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 keypair: Keypair = Keypair::generate::(&mut cspring); //! # 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; @@ -147,7 +147,7 @@ //! # 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); +//! # let keypair_orig: Keypair = Keypair::generate::(&mut cspring); //! # 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(); From 3a54e970a6e02c2aa37f204dc66056dfebf49155 Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Wed, 16 May 2018 18:49:08 +0000 Subject: [PATCH 03/24] Change travis config to test various backends. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 7a99953..dc3f978 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,7 +17,7 @@ matrix: - 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 From 85a3776d2721f2fc174b076150b0d53e424fa638 Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Wed, 16 May 2018 19:56:09 +0000 Subject: [PATCH 04/24] Collapse part of the .travis build matrix. --- .travis.yml | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index dc3f978..753186e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,15 +7,10 @@ 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 --features=u32_backend" - rust: nightly From 47c1d869eceb3a90806f67bf6e9aaeb20e90d983 Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Wed, 16 May 2018 22:09:31 +0000 Subject: [PATCH 05/24] Make key generation work with no_std. --- Cargo.toml | 7 ++-- src/ed25519.rs | 88 +++++++++++++++++++++++++++----------------------- src/lib.rs | 59 +++++++++++++++++---------------- 3 files changed, 84 insertions(+), 70 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f0ee015..6a46b9d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,8 +24,8 @@ version = "0.6" default-features = false [dependencies.rand] -optional = true version = "0.5.0-pre.2" +default-features = false [dependencies.digest] version = "^0.7" @@ -53,9 +53,10 @@ bincode = "^0.9" [features] default = ["std", "u64_backend"] -std = ["rand", "subtle/std", "curve25519-dalek/std", "failure/std"] +# We don't add "rand/std" here because it would enable a bunch of Fuchsia dependencies. +std = ["subtle/std", "curve25519-dalek/std", "failure/std"] bench = [] -nightly = ["curve25519-dalek/nightly", "subtle/nightly"] +nightly = ["curve25519-dalek/nightly", "subtle/nightly", "rand/nightly"] asm = ["sha2/asm"] yolocrypto = ["curve25519-dalek/yolocrypto"] u64_backend = ["curve25519-dalek/u64_backend"] diff --git a/src/ed25519.rs b/src/ed25519.rs index 1803aa1..e01ec94 100644 --- a/src/ed25519.rs +++ b/src/ed25519.rs @@ -12,9 +12,7 @@ use core::fmt::{Debug}; -#[cfg(feature = "std")] use rand::CryptoRng; -#[cfg(feature = "std")] use rand::Rng; #[cfg(feature = "serde")] @@ -262,8 +260,9 @@ impl SecretKey { /// extern crate sha2; /// extern crate ed25519_dalek; /// + /// # #[cfg(feature = "std")] /// # fn main() { - /// + /// # /// use rand::Rng; /// use rand::OsRng; /// use sha2::Sha512; @@ -273,8 +272,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 @@ -289,13 +290,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); @@ -308,10 +310,7 @@ 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")] + /// A CSPRNG with a `fill_bytes()` method, e.g. `rand::ChaChaRng` pub fn generate(csprng: &mut T) -> SecretKey where T: CryptoRng + Rng, { @@ -402,6 +401,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}; @@ -412,6 +412,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) @@ -434,7 +437,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}; @@ -449,7 +452,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] @@ -475,13 +478,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; /// - /// # #[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); @@ -491,14 +494,14 @@ 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 { @@ -525,7 +528,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; @@ -536,7 +540,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 { @@ -850,6 +855,7 @@ impl Keypair { /// extern crate sha2; /// extern crate ed25519_dalek; /// + /// # #[cfg(all(feature = "std", feature = "sha2"))] /// # fn main() { /// /// use rand::Rng; @@ -858,23 +864,24 @@ 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 R) -> Keypair where D: Digest + Default, R: CryptoRng + Rng, @@ -943,7 +950,8 @@ mod test { 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::*; @@ -976,17 +984,17 @@ mod test { #[test] fn unmarshal_marshal() { // TestUnmarshalMarshal - let mut cspring: OsRng; + let mut csprng: ChaChaRng; let mut keypair: Keypair; let mut x: Option; let a: EdwardsPoint; let public: PublicKey; - cspring = OsRng::new().unwrap(); + csprng = ChaChaRng::from_seed([0u8; 32]); // from_bytes() fails if vx²-u=0 and vx²+u=0 loop { - keypair = Keypair::generate::(&mut cspring); + keypair = Keypair::generate::(&mut csprng); x = keypair.public.decompress(); if x.is_some() { @@ -1001,7 +1009,7 @@ mod test { #[test] fn sign_verify() { // TestSignVerify - let mut cspring: OsRng; + let mut csprng: ChaChaRng; let keypair: Keypair; let good_sig: Signature; let bad_sig: Signature; @@ -1009,8 +1017,8 @@ 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); @@ -1125,7 +1133,7 @@ mod test { #[cfg(all(test, feature = "bench"))] mod bench { use test::Bencher; - use rand::OsRng; + use rand::ChaChaRng; use sha2::Sha512; use super::*; @@ -1150,8 +1158,8 @@ mod bench { #[bench] fn sign(b: &mut Bencher) { - let mut csprng: OsRng = OsRng::new().unwrap(); - let keypair: Keypair = Keypair::generate::(&mut csprng); + let mut csprng: ChaChaRng = ChaChaRng::from_seed([0u8; 32]).unwrap(); + let keypair: Keypair = Keypair::generate::(&mut csprng); let msg: &[u8] = b""; b.iter(| | keypair.sign::(msg)); @@ -1159,8 +1167,8 @@ mod bench { #[bench] fn sign_expanded_key(b: &mut Bencher) { - let mut csprng: OsRng = OsRng::new().unwrap(); - let keypair: Keypair = Keypair::generate::(&mut csprng); + let mut csprng: ChaChaRng = ChaChaRng::from_seed([0u8; 32]).unwrap(); + let keypair: Keypair = Keypair::generate::(&mut csprng); let expanded: ExpandedSecretKey = keypair.secret.expand::(); let msg: &[u8] = b""; @@ -1169,8 +1177,8 @@ mod bench { #[bench] fn verify(b: &mut Bencher) { - let mut csprng: OsRng = OsRng::new().unwrap(); - let keypair: Keypair = Keypair::generate::(&mut csprng); + let mut csprng: ChaChaRng = ChaChaRng::from_seed([0u8; 32]).unwrap(); + let keypair: Keypair = Keypair::generate::(&mut csprng); let msg: &[u8] = b""; let sig: Signature = keypair.sign::(msg); @@ -1181,7 +1189,7 @@ mod bench { fn key_generation(b: &mut Bencher) { let mut rng: ZeroRng = ZeroRng::new(); - b.iter(| | Keypair::generate::(&mut rng)); + b.iter(| | Keypair::generate::(&mut rng)); } #[bench] diff --git a/src/lib.rs b/src/lib.rs index 99011e2..d516597 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,12 +69,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); //! let verified: bool = keypair.verify::(message, &signature); @@ -87,13 +93,14 @@ //! # 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); //! @@ -117,12 +124,12 @@ //! # 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; @@ -141,13 +148,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::{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); +//! # 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 +180,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 +198,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 +230,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; @@ -262,8 +269,6 @@ extern crate generic_array; extern crate digest; extern crate subtle; extern crate failure; - -#[cfg(feature = "std")] extern crate rand; #[cfg(any(feature = "std", test))] From 196fd24b638ff4c12633c0a11db0dfad33b86d1c Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Wed, 30 May 2018 20:59:44 +0000 Subject: [PATCH 06/24] Update README to explain features and update benches. --- README.md | 109 +++++++++++++++++++++++++++++------------------------- 1 file changed, 58 insertions(+), 51 deletions(-) diff --git a/README.md b/README.md index 869b755..ce3ce08 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/isislovecruft/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. @@ -18,19 +18,23 @@ 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: - ∃!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Ⓐwintermute:(master *=)~/code/rust/ed25519-dalek ∴ cargo bench --features="nightly 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_dalek-ae92163eefd0cc80 - running 6 tests + running 9 tests test ed25519::test::golden ... ignored + test ed25519::test::public_key_from_bytes ... 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) + test ed25519::bench::key_generation ... bench: 30,711 ns/iter (+/- 10,936) + test ed25519::bench::sign ... bench: 39,432 ns/iter (+/- 21,387) + test ed25519::bench::sign_expanded_key ... bench: 45,753 ns/iter (+/- 25,261) + test ed25519::bench::underlying_scalar_mult_basepoint ... bench: 25,455 ns/iter (+/- 10,587) + test ed25519::bench::verify ... bench: 91,408 ns/iter (+/- 31,193) - test result: ok. 0 passed; 0 failed; 3 ignored; 3 measured + test result: ok. 0 passed; 0 failed; 4 ignored; 5 measured; 0 filtered out In comparison, the equivalent package in Golang performs as follows: @@ -41,37 +45,22 @@ In comparison, the equivalent package in Golang performs as follows: BenchmarkVerification 10000 212585 ns/op ok github.com/agl/ed25519 7.500s -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, signing, and verification a rough average of 33% +faster, 44% faster, and 43% faster respectively. 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: - - ∃!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). +nanoseconds to cycles per second on a 2591 Mhz CPU, that's 237660 cycles for +verification and 102523 for signing, which for signing is competitive +with optimised assembly versions. + +Additionally, if you're on the Rust nightly channel, be sure to build with +`cargo build --features="nightly"` which enables more secure compiler +optimisation protections in the +[subtle](https://github.com/dalek-cryptography/subtle) crate. 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 @@ -118,42 +107,60 @@ eventually support VXEdDSA in curve25519-dalek. 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). From a7c318da6ee405c048253ad8130f677feb6a83ad Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Wed, 30 May 2018 21:01:21 +0000 Subject: [PATCH 07/24] Fix benchmarks to use new rand_core traits. --- src/ed25519.rs | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/ed25519.rs b/src/ed25519.rs index e01ec94..fc79054 100644 --- a/src/ed25519.rs +++ b/src/ed25519.rs @@ -1134,6 +1134,9 @@ mod test { mod bench { use test::Bencher; use rand::ChaChaRng; + use rand::Error; + use rand::RngCore; + use rand::SeedableRng; use sha2::Sha512; use super::*; @@ -1146,19 +1149,27 @@ mod bench { } } - impl Rng for ZeroRng { + impl RngCore for ZeroRng { fn next_u32(&mut self) -> u32 { 0u32 } + fn next_u64(&mut self) -> u64 { 0u64 } + fn fill_bytes(&mut self, bytes: &mut [u8]) { for i in 0 .. bytes.len() { bytes[i] = 0; } } + + fn try_fill_bytes(&mut self, bytes: &mut [u8]) -> Result<(), Error> { + Ok(self.fill_bytes(bytes)) + } } + impl CryptoRng for ZeroRng { } + #[bench] fn sign(b: &mut Bencher) { - let mut csprng: ChaChaRng = ChaChaRng::from_seed([0u8; 32]).unwrap(); + let mut csprng: ChaChaRng = ChaChaRng::from_seed([0u8; 32]); let keypair: Keypair = Keypair::generate::(&mut csprng); let msg: &[u8] = b""; @@ -1167,7 +1178,7 @@ mod bench { #[bench] fn sign_expanded_key(b: &mut Bencher) { - let mut csprng: ChaChaRng = ChaChaRng::from_seed([0u8; 32]).unwrap(); + let mut csprng: ChaChaRng = ChaChaRng::from_seed([0u8; 32]); let keypair: Keypair = Keypair::generate::(&mut csprng); let expanded: ExpandedSecretKey = keypair.secret.expand::(); let msg: &[u8] = b""; @@ -1177,7 +1188,7 @@ mod bench { #[bench] fn verify(b: &mut Bencher) { - let mut csprng: ChaChaRng = ChaChaRng::from_seed([0u8; 32]).unwrap(); + let mut csprng: ChaChaRng = ChaChaRng::from_seed([0u8; 32]); let keypair: Keypair = Keypair::generate::(&mut csprng); let msg: &[u8] = b""; let sig: Signature = keypair.sign::(msg); From 1dfe211aa1317fc6757b29e72d578937874999bf Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Wed, 11 Jul 2018 20:32:12 +0000 Subject: [PATCH 08/24] Remove failure/std from the default enabled features. --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 6a46b9d..5370a7f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,7 +54,7 @@ bincode = "^0.9" [features] default = ["std", "u64_backend"] # We don't add "rand/std" here because it would enable a bunch of Fuchsia dependencies. -std = ["subtle/std", "curve25519-dalek/std", "failure/std"] +std = ["subtle/std", "curve25519-dalek/std"] bench = [] nightly = ["curve25519-dalek/nightly", "subtle/nightly", "rand/nightly"] asm = ["sha2/asm"] From a4be92b27123218c8daccbb0a938a02d016d76de Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Wed, 11 Jul 2018 20:36:17 +0000 Subject: [PATCH 09/24] Update curve25519-dalek dependency to 0.18. --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5370a7f..06af84d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ exclude = [ ".gitignore", "TESTVECTORS", "res/*" ] travis-ci = { repository = "dalek-cryptography/ed25519-dalek", branch = "master"} [dependencies.curve25519-dalek] -version = "0.17" +version = "0.18" default-features = false [dependencies.subtle] @@ -24,7 +24,7 @@ version = "0.6" default-features = false [dependencies.rand] -version = "0.5.0-pre.2" +version = "0.5" default-features = false [dependencies.digest] From 164303eeacb1b14d3bdeeb2611670e7a9063360a Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Wed, 11 Jul 2018 22:41:14 +0000 Subject: [PATCH 10/24] Switch to using criterion for benchmarks. --- .travis.yml | 4 +- Cargo.toml | 6 +- benches/ed25519_benchmarks.rs | 114 ++++++++++++++++++++++++++++++++++ src/ed25519.rs | 87 -------------------------- 4 files changed, 121 insertions(+), 90 deletions(-) create mode 100644 benches/ed25519_benchmarks.rs diff --git a/.travis.yml b/.travis.yml index 753186e..b14a0d4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,9 +16,9 @@ matrix: - rust: nightly env: TEST_COMMAND=test FEATURES=--features="nightly" - rust: nightly - env: TEST_COMMAND=bench FEATURES=--features="bench" + env: TEST_COMMAND=bench FEATURES='' - rust: nightly - env: TEST_COMMAND=bench FEATURES=--features="nightly bench" + env: TEST_COMMAND=bench FEATURES=--features="nightly" script: - cargo $TEST_COMMAND $FEATURES diff --git a/Cargo.toml b/Cargo.toml index 06af84d..b22e552 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,12 +50,16 @@ default-features = false hex = "^0.3" sha2 = "^0.7" bincode = "^0.9" +criterion = "0.2" + +[[bench]] +name = "ed25519_benchmarks" +harness = false [features] default = ["std", "u64_backend"] # We don't add "rand/std" here because it would enable a bunch of Fuchsia dependencies. std = ["subtle/std", "curve25519-dalek/std"] -bench = [] nightly = ["curve25519-dalek/nightly", "subtle/nightly", "rand/nightly"] asm = ["sha2/asm"] yolocrypto = ["curve25519-dalek/yolocrypto"] diff --git a/benches/ed25519_benchmarks.rs b/benches/ed25519_benchmarks.rs new file mode 100644 index 0000000..b813b12 --- /dev/null +++ b/benches/ed25519_benchmarks.rs @@ -0,0 +1,114 @@ +// -*- 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 helpers { + use rand::CryptoRng; + use rand::Error; + use rand::RngCore; + + /// A fake RNG which simply returns zeroes. + pub struct ZeroRng; + + impl ZeroRng { + pub fn new() -> ZeroRng { + ZeroRng + } + } + + impl RngCore for ZeroRng { + fn next_u32(&mut self) -> u32 { 0u32 } + + fn next_u64(&mut self) -> u64 { 0u64 } + + fn fill_bytes(&mut self, bytes: &mut [u8]) { + for i in 0 .. bytes.len() { + bytes[i] = 0; + } + } + + fn try_fill_bytes(&mut self, bytes: &mut [u8]) -> Result<(), Error> { + Ok(self.fill_bytes(bytes)) + } + } + + impl CryptoRng for ZeroRng { } +} + +mod ed25519_benches { + use super::*; + use super::helpers::ZeroRng; + 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 rng: ZeroRng = ZeroRng::new(); + + c.bench_function("Ed25519 keypair generation", move |b| { + b.iter(| | Keypair::generate::(&mut rng)) + }); + } + + 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 fc79054..e4d5701 100644 --- a/src/ed25519.rs +++ b/src/ed25519.rs @@ -1129,90 +1129,3 @@ mod test { } } } - -#[cfg(all(test, feature = "bench"))] -mod bench { - use test::Bencher; - use rand::ChaChaRng; - use rand::Error; - use rand::RngCore; - use rand::SeedableRng; - use sha2::Sha512; - use super::*; - - /// A fake RNG which simply returns zeroes. - struct ZeroRng; - - impl ZeroRng { - pub fn new() -> ZeroRng { - ZeroRng - } - } - - impl RngCore for ZeroRng { - fn next_u32(&mut self) -> u32 { 0u32 } - - fn next_u64(&mut self) -> u64 { 0u64 } - - fn fill_bytes(&mut self, bytes: &mut [u8]) { - for i in 0 .. bytes.len() { - bytes[i] = 0; - } - } - - fn try_fill_bytes(&mut self, bytes: &mut [u8]) -> Result<(), Error> { - Ok(self.fill_bytes(bytes)) - } - } - - impl CryptoRng for ZeroRng { } - - #[bench] - fn sign(b: &mut Bencher) { - let mut csprng: ChaChaRng = ChaChaRng::from_seed([0u8; 32]); - 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: ChaChaRng = ChaChaRng::from_seed([0u8; 32]); - 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: ChaChaRng = ChaChaRng::from_seed([0u8; 32]); - 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); - } -} From bda9bba9d5d48d740ec33c8e56e1ac6894761c34 Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Thu, 12 Jul 2018 20:19:42 +0000 Subject: [PATCH 11/24] Remove ZeroRng from benchmarks. --- benches/ed25519_benchmarks.rs | 38 ++--------------------------------- 1 file changed, 2 insertions(+), 36 deletions(-) diff --git a/benches/ed25519_benchmarks.rs b/benches/ed25519_benchmarks.rs index b813b12..b9ac890 100644 --- a/benches/ed25519_benchmarks.rs +++ b/benches/ed25519_benchmarks.rs @@ -15,42 +15,8 @@ extern crate sha2; use criterion::Criterion; -mod helpers { - use rand::CryptoRng; - use rand::Error; - use rand::RngCore; - - /// A fake RNG which simply returns zeroes. - pub struct ZeroRng; - - impl ZeroRng { - pub fn new() -> ZeroRng { - ZeroRng - } - } - - impl RngCore for ZeroRng { - fn next_u32(&mut self) -> u32 { 0u32 } - - fn next_u64(&mut self) -> u64 { 0u64 } - - fn fill_bytes(&mut self, bytes: &mut [u8]) { - for i in 0 .. bytes.len() { - bytes[i] = 0; - } - } - - fn try_fill_bytes(&mut self, bytes: &mut [u8]) -> Result<(), Error> { - Ok(self.fill_bytes(bytes)) - } - } - - impl CryptoRng for ZeroRng { } -} - mod ed25519_benches { use super::*; - use super::helpers::ZeroRng; use ed25519_dalek::ExpandedSecretKey; use ed25519_dalek::Keypair; use ed25519_dalek::Signature; @@ -91,10 +57,10 @@ mod ed25519_benches { } fn key_generation(c: &mut Criterion) { - let mut rng: ZeroRng = ZeroRng::new(); + let mut csprng: ThreadRng = thread_rng(); c.bench_function("Ed25519 keypair generation", move |b| { - b.iter(| | Keypair::generate::(&mut rng)) + b.iter(| | Keypair::generate::(&mut csprng)) }); } From 9d58954578b0ebf73b15551002573ee0899cb2bf Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Thu, 12 Jul 2018 21:47:52 +0000 Subject: [PATCH 12/24] Avoid compressing R twice. --- src/ed25519.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ed25519.rs b/src/ed25519.rs index e4d5701..156f89a 100644 --- a/src/ed25519.rs +++ b/src/ed25519.rs @@ -570,7 +570,7 @@ impl ExpandedSecretKey { let mut hash: [u8; 64] = [0u8; 64]; let mesg_digest: Scalar; let hram_digest: Scalar; - let r: EdwardsPoint; + let r: CompressedEdwardsY; let s: Scalar; h.input(&self.nonce); @@ -579,10 +579,10 @@ impl ExpandedSecretKey { mesg_digest = Scalar::from_bytes_mod_order_wide(&hash); - r = &mesg_digest * &constants::ED25519_BASEPOINT_TABLE; + r = (&mesg_digest * &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()); @@ -591,7 +591,7 @@ impl ExpandedSecretKey { s = &(&hram_digest * &self.key) + &mesg_digest; - Signature{ r: r.compress(), s: s } + Signature{ r: r, s: s } } } From 68d2ff93f562dcca2611d9f63b1edb3532ec7950 Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Fri, 13 Jul 2018 20:14:31 +0000 Subject: [PATCH 13/24] =?UTF-8?q?Implement=20ed25519ph=20from=20RFC8032=20?= =?UTF-8?q?=C2=A75.1.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * FIXES #21: https://github.com/dalek-cryptography/ed25519-dalek/issues/21 --- src/ed25519.rs | 267 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 266 insertions(+), 1 deletion(-) diff --git a/src/ed25519.rs b/src/ed25519.rs index 156f89a..529e88a 100644 --- a/src/ed25519.rs +++ b/src/ed25519.rs @@ -593,6 +593,91 @@ impl ExpandedSecretKey { Signature{ r: r, s: 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 + 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 hash: [u8; 64] = [0u8; 64]; + let mut prehash: [u8; 64] = [0u8; 64]; + let mesg_digest: Scalar; + let hram_digest: Scalar; + let r: CompressedEdwardsY; + let s: Scalar; + + let ctx: &[u8] = match context { + Some(x) => x, + None => 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); + 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).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); + hash.copy_from_slice(h.fixed_result().as_slice()); + + hram_digest = Scalar::from_bytes_mod_order_wide(&hash); + + s = &(&hram_digest * &self.key) + &mesg_digest; + + Signature{ r: r, s: s } + } + } #[cfg(feature = "serde")] @@ -758,6 +843,63 @@ impl PublicKey { (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 + pub fn verify_prehashed(&self, + prehashed_message: D, + context: Option<&[u8]>, + signature: &Signature) -> bool + where D: Digest + Default + { + let mut h: D = D::default(); + let mut hash: [u8; 64] = [0u8; 64]; + + let mut a: EdwardsPoint = match self.decompress() { + Some(x) => x, + None => return false, + }; + a = -(&a); + + let ctx: &[u8] = match context { + Some(x) => x, + None => 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; + + h.input(b"SigEd25519 no Ed25519 collisions"); + h.input(&[1]); // Ed25519ph + h.input(&[ctx_len]); + h.input(ctx); + h.input(signature.r.as_bytes()); + h.input(self.as_bytes()); + h.input(prehashed_message.fixed_result().as_slice()); + hash.copy_from_slice(h.fixed_result().as_slice()); + + let digest_reduced: Scalar = Scalar::from_bytes_mod_order_wide(&hash); + let r: EdwardsPoint = EdwardsPoint::vartime_double_scalar_mul_basepoint(&digest_reduced, + &a, &signature.s); + + (signature.r.as_bytes()).ct_eq(r.compress().as_bytes()).unwrap_u8() == 1 + } } #[cfg(feature = "serde")] @@ -898,11 +1040,63 @@ 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`. + /// + /// [rfc8032]: https://tools.ietf.org/html/rfc8032#section-5.1 + 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 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`. + /// + /// [rfc8032]: https://tools.ietf.org/html/rfc8032#section-5.1 + pub fn verify_prehashed(&self, + prehashed_message: D, + context: Option<&[u8]>, + signature: &Signature) -> bool + where D: Digest + Default + { + self.public.verify_prehashed::(prehashed_message, context, signature) + } } #[cfg(feature = "serde")] @@ -1008,7 +1202,7 @@ mod test { } #[test] - fn sign_verify() { // TestSignVerify + fn ed25519_sign_verify() { // TestSignVerify let mut csprng: ChaChaRng; let keypair: Keypair; let good_sig: Signature; @@ -1076,6 +1270,77 @@ mod test { } } + // 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), + "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) == true, + "Verification of a valid signature failed!"); + assert!(keypair.verify_prehashed::(prehashed_good3, Some(context), &bad_sig) == false, + "Verification of a signature on a different message passed!"); + assert!(keypair.verify_prehashed::(prehashed_bad2, Some(context), &good_sig) == false, + "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. From fad39851aa80c3b6cdcfe2c064c53abe92609f17 Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Fri, 13 Jul 2018 21:57:31 +0000 Subject: [PATCH 14/24] Add doctests for sign_prehashed() and verify_prehashed(). --- src/ed25519.rs | 118 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 118 insertions(+) diff --git a/src/ed25519.rs b/src/ed25519.rs index 529e88a..13e2133 100644 --- a/src/ed25519.rs +++ b/src/ed25519.rs @@ -1056,7 +1056,86 @@ impl Keypair { /// /// 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 @@ -1088,6 +1167,45 @@ impl Keypair { /// 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, From f8373a9e70278a17f1910f8b33cafb32c50e9e07 Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Fri, 13 Jul 2018 23:57:27 +0000 Subject: [PATCH 15/24] Fix two more links in the README. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2cb81ac..342e395 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/dalek-cryptography/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. @@ -71,7 +71,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, From 030eff547f918e99e17d0d9e81692cb244208d0c Mon Sep 17 00:00:00 2001 From: Henry de Valence Date: Fri, 13 Jul 2018 12:39:45 -0700 Subject: [PATCH 16/24] Make `verify` return a `Result`. This also simplifies the verification logic. Because the verification check happens in variable time, we don't need to do a constant-time eq check at the end, so we can drop the `subtle` dependency entirely. The `DecodingError` type becomes `SignatureError` and is also used to signal failing verifications. --- Cargo.toml | 8 +-- README.md | 9 +-- src/ed25519.rs | 172 ++++++++++++++++++------------------------------- src/errors.rs | 36 +++++------ src/lib.rs | 14 ++-- 5 files changed, 86 insertions(+), 153 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b22e552..3da42c9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,10 +19,6 @@ travis-ci = { repository = "dalek-cryptography/ed25519-dalek", branch = "master" version = "0.18" default-features = false -[dependencies.subtle] -version = "0.6" -default-features = false - [dependencies.rand] version = "0.5" default-features = false @@ -59,8 +55,8 @@ harness = false [features] default = ["std", "u64_backend"] # We don't add "rand/std" here because it would enable a bunch of Fuchsia dependencies. -std = ["subtle/std", "curve25519-dalek/std"] -nightly = ["curve25519-dalek/nightly", "subtle/nightly", "rand/nightly"] +std = ["curve25519-dalek/std"] +nightly = ["curve25519-dalek/nightly", "rand/nightly"] asm = ["sha2/asm"] yolocrypto = ["curve25519-dalek/yolocrypto"] u64_backend = ["curve25519-dalek/u64_backend"] diff --git a/README.md b/README.md index 342e395..037eeb6 100644 --- a/README.md +++ b/README.md @@ -55,12 +55,9 @@ nanoseconds to cycles per second on a 2591 Mhz CPU, that's 237660 cycles for verification and 102523 for signing, which for signing is competitive with optimised assembly versions. -Additionally, if you're on the Rust nightly channel, be sure to build with -`cargo build --features="nightly"` which enables more secure compiler -optimisation protections in the -[subtle](https://github.com/dalek-cryptography/subtle) crate. 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, 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 diff --git a/src/ed25519.rs b/src/ed25519.rs index 13e2133..3bd43ac 100644 --- a/src/ed25519.rs +++ b/src/ed25519.rs @@ -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. @@ -132,9 +130,9 @@ impl Signature { /// 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,7 +142,7 @@ 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) }) @@ -215,9 +213,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 +236,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]; @@ -469,7 +467,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 /// @@ -479,11 +477,11 @@ impl ExpandedSecretKey { /// # extern crate ed25519_dalek; /// # /// # #[cfg(all(feature = "sha2", feature = "std"))] - /// # fn do_test() -> Result { + /// # fn do_test() -> Result { /// # /// use rand::{Rng, OsRng}; /// use ed25519_dalek::{SecretKey, ExpandedSecretKey}; - /// use ed25519_dalek::DecodingError; + /// use ed25519_dalek::SignatureError; /// /// let mut csprng: OsRng = OsRng::new().unwrap(); /// let secret_key: SecretKey = SecretKey::generate(&mut csprng); @@ -504,9 +502,9 @@ impl ExpandedSecretKey { /// # 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]; @@ -746,9 +744,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]; @@ -766,11 +764,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]; @@ -779,12 +777,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 @@ -812,36 +804,27 @@ 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 { - let mut h: D = D::default(); - let mut a: EdwardsPoint; - let ao: Option; - let mut digest: [u8; 64] = [0u8; 64]; - - ao = self.decompress(); - - if ao.is_some() { - a = ao.unwrap(); - } else { - return false; - } - a = -(&a); + let A = self.0.decompress() + .ok_or_else(|| SignatureError(InternalError::PointDecompressionError))?; + let mut h = D::default(); h.input(signature.r.as_bytes()); h.input(self.as_bytes()); h.input(&message); + let k = Scalar::from_hash(h); - digest.copy_from_slice(h.fixed_result().as_slice()); + let 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 = EdwardsPoint::vartime_double_scalar_mul_basepoint(&digest_reduced, - &a, &signature.s); - - (signature.r.as_bytes()).ct_eq(r.compress().as_bytes()).unwrap_u8() == 1 + if R.compress() == signature.r { + Ok(()) + } else { + Err(SignatureError(InternalError::VerifyError)) + } } /// Verify a `signature` on a `prehashed_message` using the Ed25519ph algorithm. @@ -862,43 +845,36 @@ impl PublicKey { /// `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) -> bool + signature: &Signature) -> Result<(), SignatureError> where D: Digest + Default { - let mut h: D = D::default(); - let mut hash: [u8; 64] = [0u8; 64]; - - let mut a: EdwardsPoint = match self.decompress() { - Some(x) => x, - None => return false, - }; - a = -(&a); - - let ctx: &[u8] = match context { - Some(x) => x, - None => b"", // By default, the context is an empty string. - }; + let ctx = context.unwrap_or(b""); debug_assert!(ctx.len() <= 255, "The context must not be longer than 255 octets."); - let ctx_len: u8 = ctx.len() as u8; + let A = self.0.decompress() + .ok_or_else(|| SignatureError(InternalError::PointDecompressionError))?; + let mut h = D::default(); h.input(b"SigEd25519 no Ed25519 collisions"); h.input(&[1]); // Ed25519ph - h.input(&[ctx_len]); + 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()); - hash.copy_from_slice(h.fixed_result().as_slice()); + let k = Scalar::from_hash(h); - let digest_reduced: Scalar = Scalar::from_bytes_mod_order_wide(&hash); - let r: EdwardsPoint = EdwardsPoint::vartime_double_scalar_mul_basepoint(&digest_reduced, - &a, &signature.s); + let R = EdwardsPoint::vartime_double_scalar_mul_basepoint(&k, &(-A), &signature.s); - (signature.r.as_bytes()).ct_eq(r.compress().as_bytes()).unwrap_u8() == 1 + if R.compress() == signature.r { + Ok(()) + } else { + Err(SignatureError(InternalError::VerifyError)) + } } } @@ -976,10 +952,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])?; @@ -1145,7 +1121,7 @@ impl Keypair { } /// 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) } @@ -1210,7 +1186,7 @@ impl Keypair { pub fn verify_prehashed(&self, prehashed_message: D, context: Option<&[u8]>, - signature: &Signature) -> bool + signature: &Signature) -> Result<(), SignatureError> where D: Digest + Default { self.public.verify_prehashed::(prehashed_message, context, signature) @@ -1261,7 +1237,6 @@ mod test { use std::fs::File; use std::string::String; use std::vec::Vec; - use curve25519_dalek::edwards::EdwardsPoint; use rand::ChaChaRng; use rand::SeedableRng; use hex::FromHex; @@ -1295,32 +1270,7 @@ mod test { 063, 120, 126, 100, 092, 059, 050, 011, ]; #[test] - fn unmarshal_marshal() { // TestUnmarshalMarshal - let mut csprng: ChaChaRng; - let mut keypair: Keypair; - let mut x: Option; - let a: EdwardsPoint; - let public: PublicKey; - - csprng = ChaChaRng::from_seed([0u8; 32]); - - // from_bytes() fails if vx²-u=0 and vx²+u=0 - loop { - keypair = Keypair::generate::(&mut csprng); - x = keypair.public.decompress(); - - if x.is_some() { - a = x.unwrap(); - break; - } - } - public = PublicKey(a.compress()); - - assert!(keypair.public.0 == public.0); - } - - #[test] - fn ed25519_sign_verify() { // TestSignVerify + fn sign_verify() { // TestSignVerify let mut csprng: ChaChaRng; let keypair: Keypair; let good_sig: Signature; @@ -1334,11 +1284,11 @@ mod test { 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!"); } @@ -1383,7 +1333,7 @@ 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); } } @@ -1417,7 +1367,7 @@ mod test { 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), + assert!(keypair.verify_prehashed(prehash_for_verifying, None, &sig2).is_ok(), "Could not verify ed25519ph signature!"); } @@ -1451,18 +1401,18 @@ mod test { 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) == true, + 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) == false, + 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) == false, + 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, 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 d516597..b74999a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -78,9 +78,7 @@ //! # 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()); //! # } //! ``` //! @@ -105,9 +103,7 @@ //! # 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()); //! # } //! ``` //! @@ -133,7 +129,6 @@ //! # 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(); @@ -150,9 +145,9 @@ //! # extern crate ed25519_dalek; //! # 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> { +//! # 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(); @@ -267,7 +262,6 @@ extern crate curve25519_dalek; extern crate generic_array; extern crate digest; -extern crate subtle; extern crate failure; extern crate rand; From 80075a0b41b5dedf6053ac012b2c24edefc81345 Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Sun, 15 Jul 2018 21:38:10 +0000 Subject: [PATCH 17/24] Cleanup signing code to use new dalek APIs and Rust syntax. --- src/ed25519.rs | 73 ++++++++++++++++++++++---------------------------- 1 file changed, 32 insertions(+), 41 deletions(-) diff --git a/src/ed25519.rs b/src/ed25519.rs index 3bd43ac..3cb59d7 100644 --- a/src/ed25519.rs +++ b/src/ed25519.rs @@ -67,6 +67,7 @@ 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. +#[allow(non_snake_case)] #[derive(Copy)] #[repr(C)] pub struct Signature { @@ -79,7 +80,7 @@ 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, + pub (crate) R: CompressedEdwardsY, /// `s` is a `Scalar`, formed by using an hash function with 512-bits output /// to produce the digest of: @@ -99,7 +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) + write!(f, "Signature( R: {:?}, s: {:?} )", &self.R, &self.s) } } @@ -110,7 +111,7 @@ impl PartialEq for Signature { let mut equal: u8 = 0; for i in 0..32 { - equal |= self.r.0[i] ^ other.r.0[i]; + equal |= self.R.0[i] ^ other.R.0[i]; equal |= self.s[i] ^ other.s[i]; } equal == 0 @@ -123,7 +124,7 @@ 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 } @@ -145,7 +146,7 @@ impl Signature { 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) }) } } @@ -562,34 +563,30 @@ 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: CompressedEdwardsY; + 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).compress(); + r = Scalar::from_hash(h); + R = (&r * &constants::ED25519_BASEPOINT_TABLE).compress(); h = D::default(); - h.input(r.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, s: s } + Signature{ R, s } } /// Sign a `prehashed_message` with this `ExpandedSecretKey` using the @@ -610,6 +607,7 @@ impl ExpandedSecretKey { /// 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, @@ -617,17 +615,14 @@ impl ExpandedSecretKey { where D: Digest + Default { let mut h: D = D::default(); - let mut hash: [u8; 64] = [0u8; 64]; let mut prehash: [u8; 64] = [0u8; 64]; - let mesg_digest: Scalar; - let hram_digest: Scalar; - let r: CompressedEdwardsY; + 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. - let ctx: &[u8] = match context { - Some(x) => x, - None => 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; @@ -653,27 +648,23 @@ impl ExpandedSecretKey { h.input(ctx); h.input(&self.nonce); h.input(&prehash); - 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).compress(); + 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(R.as_bytes()); h.input(public_key.as_bytes()); h.input(&prehash); - 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, s: s } + Signature{ R, s } } } @@ -813,14 +804,14 @@ impl PublicKey { .ok_or_else(|| SignatureError(InternalError::PointDecompressionError))?; let mut h = D::default(); - h.input(signature.r.as_bytes()); + h.input(signature.R.as_bytes()); h.input(self.as_bytes()); h.input(&message); let k = Scalar::from_hash(h); let R = EdwardsPoint::vartime_double_scalar_mul_basepoint(&k, &(-A), &signature.s); - if R.compress() == signature.r { + if R.compress() == signature.R { Ok(()) } else { Err(SignatureError(InternalError::VerifyError)) @@ -863,14 +854,14 @@ impl PublicKey { h.input(&[1]); // Ed25519ph h.input(&[ctx.len() as u8]); h.input(ctx); - h.input(signature.r.as_bytes()); + h.input(signature.R.as_bytes()); h.input(self.as_bytes()); h.input(prehashed_message.fixed_result().as_slice()); let k = Scalar::from_hash(h); let R = EdwardsPoint::vartime_double_scalar_mul_basepoint(&k, &(-A), &signature.s); - if R.compress() == signature.r { + if R.compress() == signature.R { Ok(()) } else { Err(SignatureError(InternalError::VerifyError)) From 5c26349b6c8961c615ef2d041965d656cf8b4be2 Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Sun, 15 Jul 2018 21:50:20 +0000 Subject: [PATCH 18/24] Derive Eq, PartialEq for Signature. --- src/ed25519.rs | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/src/ed25519.rs b/src/ed25519.rs index 3cb59d7..9fe85c3 100644 --- a/src/ed25519.rs +++ b/src/ed25519.rs @@ -68,7 +68,7 @@ pub const EXPANDED_SECRET_KEY_LENGTH: usize = EXPANDED_SECRET_KEY_KEY_LENGTH + E /// "detached"—that is, they do **not** include a copy of the message which has /// been signed. #[allow(non_snake_case)] -#[derive(Copy)] +#[derive(Copy, Eq, PartialEq)] #[repr(C)] pub struct Signature { /// `r` is an `EdwardsPoint`, formed by using an hash function with @@ -104,20 +104,6 @@ impl Debug for Signature { } } -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 - } -} - impl Signature { /// Convert this `Signature` to a byte array. #[inline] From cdbc302da784aee8b831b34ebee77abc6f899e06 Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Sun, 15 Jul 2018 21:51:23 +0000 Subject: [PATCH 19/24] Fix a couple docstrings which mentioned `r` instead of `R`. --- src/ed25519.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ed25519.rs b/src/ed25519.rs index 9fe85c3..5cc628b 100644 --- a/src/ed25519.rs +++ b/src/ed25519.rs @@ -71,7 +71,7 @@ pub const EXPANDED_SECRET_KEY_LENGTH: usize = EXPANDED_SECRET_KEY_KEY_LENGTH + E #[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 @@ -79,7 +79,7 @@ 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`. + /// basepoint to produce `R`, and `EdwardsPoint`. pub (crate) R: CompressedEdwardsY, /// `s` is a `Scalar`, formed by using an hash function with 512-bits output From 2e5363c6792adbee6cff01641d9fa042f5d4de2e Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Sun, 15 Jul 2018 22:04:55 +0000 Subject: [PATCH 20/24] Cleanup verification variable declarations. --- src/ed25519.rs | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/src/ed25519.rs b/src/ed25519.rs index 3bd43ac..c01edcd 100644 --- a/src/ed25519.rs +++ b/src/ed25519.rs @@ -809,16 +809,21 @@ impl PublicKey { pub fn verify(&self, message: &[u8], signature: &Signature) -> Result<(), SignatureError> where D: Digest + Default { - let A = self.0.decompress() - .ok_or_else(|| SignatureError(InternalError::PointDecompressionError))?; + let mut h: D = D::default(); + let R: EdwardsPoint; + let k: Scalar; + + let A: EdwardsPoint = match self.0.decompress() { + Ok(x) => x, + Err => Err(SignatureError(InternalError::PointDecompressionError))), + }; - let mut h = D::default(); h.input(signature.r.as_bytes()); h.input(self.as_bytes()); h.input(&message); - let k = Scalar::from_hash(h); - let R = EdwardsPoint::vartime_double_scalar_mul_basepoint(&k, &(-A), &signature.s); + k = Scalar::from_hash(h); + R = EdwardsPoint::vartime_double_scalar_mul_basepoint(&k, &(-A), &signature.s); if R.compress() == signature.r { Ok(()) @@ -852,13 +857,18 @@ impl PublicKey { signature: &Signature) -> Result<(), SignatureError> where D: Digest + Default { - let ctx = context.unwrap_or(b""); + 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 = self.0.decompress() - .ok_or_else(|| SignatureError(InternalError::PointDecompressionError))?; + let A: EdwardsPoint = match self.0.decompress() { + Ok(x) => x, + Err => Err(SignatureError(InternalError::PointDecompressionError))), + }; - let mut h = D::default(); h.input(b"SigEd25519 no Ed25519 collisions"); h.input(&[1]); // Ed25519ph h.input(&[ctx.len() as u8]); @@ -866,9 +876,9 @@ impl PublicKey { h.input(signature.r.as_bytes()); h.input(self.as_bytes()); h.input(prehashed_message.fixed_result().as_slice()); - let k = Scalar::from_hash(h); - let R = EdwardsPoint::vartime_double_scalar_mul_basepoint(&k, &(-A), &signature.s); + k = Scalar::from_hash(h); + R = EdwardsPoint::vartime_double_scalar_mul_basepoint(&k, &(-A), &signature.s); if R.compress() == signature.r { Ok(()) From 535f4752a686338ebfbc731bdb5d7f26edc9a4b7 Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Sun, 15 Jul 2018 22:07:42 +0000 Subject: [PATCH 21/24] Don't run `cargo bench` on Travis CI servers. --- .travis.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index b14a0d4..4c8d19e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,10 +15,6 @@ matrix: 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='' - - rust: nightly - env: TEST_COMMAND=bench FEATURES=--features="nightly" script: - cargo $TEST_COMMAND $FEATURES From 6c8ae573d90f58f946df063c17387c91826cb415 Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Sun, 15 Jul 2018 22:15:54 +0000 Subject: [PATCH 22/24] Bump ed25519-dalek version to 0.7.0. --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 3da42c9..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" From b32e39c801940bb5b8f2b84a52742cc3438370f9 Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Sun, 15 Jul 2018 22:21:32 +0000 Subject: [PATCH 23/24] Fix match statements on public key decompression. --- src/ed25519.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ed25519.rs b/src/ed25519.rs index 6cb2308..919ecb4 100644 --- a/src/ed25519.rs +++ b/src/ed25519.rs @@ -791,8 +791,8 @@ impl PublicKey { let k: Scalar; let A: EdwardsPoint = match self.0.decompress() { - Ok(x) => x, - Err => Err(SignatureError(InternalError::PointDecompressionError))), + Some(x) => x, + None => return Err(SignatureError(InternalError::PointDecompressionError)), }; h.input(signature.R.as_bytes()); @@ -842,8 +842,8 @@ impl PublicKey { debug_assert!(ctx.len() <= 255, "The context must not be longer than 255 octets."); let A: EdwardsPoint = match self.0.decompress() { - Ok(x) => x, - Err => Err(SignatureError(InternalError::PointDecompressionError))), + Some(x) => x, + None => return Err(SignatureError(InternalError::PointDecompressionError)), }; h.input(b"SigEd25519 no Ed25519 collisions"); From 5050049a6c870470af9a7e70c39e7d4b06d4b296 Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Sun, 15 Jul 2018 23:22:22 +0000 Subject: [PATCH 24/24] Update benchmarks in README. --- README.md | 55 ++++++++++++++++++++++++++++--------------------------- 1 file changed, 28 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 037eeb6..3ba0546 100644 --- a/README.md +++ b/README.md @@ -14,46 +14,47 @@ 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:(master *=)~/code/rust/ed25519-dalek ∴ cargo bench --features="nightly bench" + ∃!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_dalek-ae92163eefd0cc80 + Running target/release/deps/ed25519_benchmarks-721332beed423bce - running 9 tests - test ed25519::test::golden ... ignored - test ed25519::test::public_key_from_bytes ... ignored - test ed25519::test::sign_verify ... ignored - test ed25519::test::unmarshal_marshal ... ignored - test ed25519::bench::key_generation ... bench: 30,711 ns/iter (+/- 10,936) - test ed25519::bench::sign ... bench: 39,432 ns/iter (+/- 21,387) - test ed25519::bench::sign_expanded_key ... bench: 45,753 ns/iter (+/- 25,261) - test ed25519::bench::underlying_scalar_mult_basepoint ... bench: 25,455 ns/iter (+/- 10,587) - test ed25519::bench::verify ... bench: 91,408 ns/iter (+/- 31,193) + 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; 4 ignored; 5 measured; 0 filtered out +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 33% -faster, 44% faster, and 43% 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. -Translating to a rough cycle count: we multiply by a factor of 2.6 to convert -nanoseconds to cycles per second on a 2591 Mhz CPU, that's 237660 cycles for -verification and 102523 for signing, which for signing is competitive -with optimised assembly versions. +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. Additionally, if you're using a CSPRNG from the `rand` crate, the `nightly` feature will enable `u128`/`i128` features there, resulting in potentially