From 1c9f484d97c1fdcbb807ea0f91313b1880a7ba4b Mon Sep 17 00:00:00 2001 From: Arnaud Castellanos Galea Date: Mon, 30 Sep 2019 16:42:52 +0800 Subject: [PATCH 01/20] Drop the static lifetime for context in sign_prehashed --- src/ed25519.rs | 2 +- src/secret.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ed25519.rs b/src/ed25519.rs index 2d144ce..17a6d5a 100644 --- a/src/ed25519.rs +++ b/src/ed25519.rs @@ -358,7 +358,7 @@ impl Keypair { pub fn sign_prehashed( &self, prehashed_message: D, - context: Option<&'static [u8]>, + context: Option<&[u8]>, ) -> Signature where D: Digest, diff --git a/src/secret.rs b/src/secret.rs index 3bfeb7c..4c8c2ce 100644 --- a/src/secret.rs +++ b/src/secret.rs @@ -462,11 +462,11 @@ impl ExpandedSecretKey { /// /// [rfc8032]: https://tools.ietf.org/html/rfc8032#section-5.1 #[allow(non_snake_case)] - pub fn sign_prehashed( + pub fn sign_prehashed<'a, D>( &self, prehashed_message: D, public_key: &PublicKey, - context: Option<&'static [u8]>, + context: Option<&'a [u8]>, ) -> Signature where D: Digest, From 7dd99afb67a552274f1eb180edbc149083543a7e Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Fri, 4 Oct 2019 02:43:38 +0000 Subject: [PATCH 02/20] Remove most of the rand_os crate, which is only used for testing. --- Cargo.toml | 2 +- src/lib.rs | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 453d72e..b385b0d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -65,7 +65,7 @@ harness = false [features] default = ["std", "u64_backend"] std = ["curve25519-dalek/std", "rand_os", "sha2/std"] -alloc = ["curve25519-dalek/alloc", "rand_os"] +alloc = ["curve25519-dalek/alloc"] nightly = ["curve25519-dalek/nightly", "clear_on_drop/nightly"] batch = ["rand"] asm = ["sha2/asm"] diff --git a/src/lib.rs b/src/lib.rs index fcd52af..13784fb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -262,8 +262,6 @@ extern crate curve25519_dalek; extern crate failure; #[cfg(all(feature = "batch", any(feature = "std", feature = "alloc", test)))] extern crate rand; -#[cfg(any(feature = "std", test))] -extern crate rand_os; extern crate rand_core; #[cfg(feature = "serde")] extern crate serde; From 28eed1cba0acdd0e9804324118cae95c08eaa9d8 Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Fri, 27 Sep 2019 00:37:30 +0000 Subject: [PATCH 03/20] Add PublicKey::verify_strict() and Keypair::verify_strict() methods. --- benches/ed25519_benchmarks.rs | 12 +++++ src/ed25519.rs | 72 +++++++++++++++++++++++++ src/public.rs | 99 +++++++++++++++++++++++++++++++++++ 3 files changed, 183 insertions(+) diff --git a/benches/ed25519_benchmarks.rs b/benches/ed25519_benchmarks.rs index e07eb61..0af2812 100644 --- a/benches/ed25519_benchmarks.rs +++ b/benches/ed25519_benchmarks.rs @@ -56,6 +56,17 @@ mod ed25519_benches { }); } + fn verify_strict(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 strict signature verification", move |b| { + b.iter(| | keypair.verify_strict(msg, &sig)) + }); + } + fn verify_batch_signatures(c: &mut Criterion) { static BATCH_SIZES: [usize; 8] = [4, 8, 16, 32, 64, 96, 128, 256]; @@ -90,6 +101,7 @@ mod ed25519_benches { sign, sign_expanded_key, verify, + verify_strict, verify_batch_signatures, key_generation, } diff --git a/src/ed25519.rs b/src/ed25519.rs index a76b091..43da922 100644 --- a/src/ed25519.rs +++ b/src/ed25519.rs @@ -447,6 +447,78 @@ impl Keypair { { self.public.verify_prehashed(prehashed_message, context, signature) } + + /// Strictly verify a signature on a message with this keypair's public key. + /// + /// # On The (Multiple) Sources of Malleability in Ed25519 Signatures + /// + /// This version of verification is technically non-RFC8032 compliant. The + /// following explains why. + /// + /// 1. Scalar Malleability + /// + /// The authors of the RFC explicitly stated that verification of an ed25519 + /// signature must fail if the scalar `s` is not properly reduced mod \ell: + /// + /// > To verify a signature on a message M using public key A, with F + /// > being 0 for Ed25519ctx, 1 for Ed25519ph, and if Ed25519ctx or + /// > Ed25519ph is being used, C being the context, first split the + /// > signature into two 32-octet halves. Decode the first half as a + /// > point R, and the second half as an integer S, in the range + /// > 0 <= s < L. Decode the public key A as point A'. If any of the + /// > decodings fail (including S being out of range), the signature is + /// > invalid.) + /// + /// All `verify_*()` functions within ed25519-dalek perform this check. + /// + /// 2. Point malleability + /// + /// The authors of the RFC added in a malleability check to step #3 in + /// §5.1.7, for small torsion components in the `R` value of the signature, + /// *which is not strictly required*, as they state: + /// + /// > Check the group equation [8][S]B = [8]R + [8][k]A'. It's + /// > sufficient, but not required, to instead check [S]B = R + [k]A'. + /// + /// # History of Malleability Checks + /// + /// As originally defined (cf. the "Malleability" section in the README of + /// this repo), ed25519 signatures didn't consider *any* form of + /// malleability to be an issue. Later the scalar malleability was + /// considered important. Still later, particularly with interests in + /// cryptocurrency design and in unique identities (e.g. for Signal users, + /// Tor onion services, etc.), the group element malleability became a + /// concern. + /// + /// However, libraries had already been created to conform to the original + /// definition. One well-used library in particular even implemented the + /// group element malleability check, *but only for batch verification*! + /// Which meant that even using the same library, a single signature could + /// verify fine individually, but suddenly, when verifying it with a bunch + /// of other signatures, the whole batch would fail! + /// + /// # "Strict" Verification + /// + /// This method performs *both* of the above signature malleability checks. + /// + /// It must be done as a separate method because one doesn't simply get to + /// change the definition of a cryptographic primitive ten years + /// after-the-fact with zero consideration for backwards compatibility in + /// hardware and protocols which have it already have the older definition + /// baked in. + /// + /// # Return + /// + /// Returns `Ok(())` if the signature is valid, and `Err` otherwise. + #[allow(non_snake_case)] + pub fn verify_strict( + &self, + message: &[u8], + signature: &Signature, + ) -> Result<(), SignatureError> + { + self.public.verify_strict(message, signature) + } } #[cfg(feature = "serde")] diff --git a/src/public.rs b/src/public.rs index ae3bfa3..f25c88f 100644 --- a/src/public.rs +++ b/src/public.rs @@ -244,6 +244,105 @@ impl PublicKey { Err(SignatureError(InternalError::VerifyError)) } } + + /// Strictly verify a signature on a message with this keypair's public key. + /// + /// # On The (Multiple) Sources of Malleability in Ed25519 Signatures + /// + /// This version of verification is technically non-RFC8032 compliant. The + /// following explains why. + /// + /// 1. Scalar Malleability + /// + /// The authors of the RFC explicitly stated that verification of an ed25519 + /// signature must fail if the scalar `s` is not properly reduced mod \ell: + /// + /// > To verify a signature on a message M using public key A, with F + /// > being 0 for Ed25519ctx, 1 for Ed25519ph, and if Ed25519ctx or + /// > Ed25519ph is being used, C being the context, first split the + /// > signature into two 32-octet halves. Decode the first half as a + /// > point R, and the second half as an integer S, in the range + /// > 0 <= s < L. Decode the public key A as point A'. If any of the + /// > decodings fail (including S being out of range), the signature is + /// > invalid.) + /// + /// All `verify_*()` functions within ed25519-dalek perform this check. + /// + /// 2. Point malleability + /// + /// The authors of the RFC added in a malleability check to step #3 in + /// §5.1.7, for small torsion components in the `R` value of the signature, + /// *which is not strictly required*, as they state: + /// + /// > Check the group equation [8][S]B = [8]R + [8][k]A'. It's + /// > sufficient, but not required, to instead check [S]B = R + [k]A'. + /// + /// # History of Malleability Checks + /// + /// As originally defined (cf. the "Malleability" section in the README of + /// this repo), ed25519 signatures didn't consider *any* form of + /// malleability to be an issue. Later the scalar malleability was + /// considered important. Still later, particularly with interests in + /// cryptocurrency design and in unique identities (e.g. for Signal users, + /// Tor onion services, etc.), the group element malleability became a + /// concern. + /// + /// However, libraries had already been created to conform to the original + /// definition. One well-used library in particular even implemented the + /// group element malleability check, *but only for batch verification*! + /// Which meant that even using the same library, a single signature could + /// verify fine individually, but suddenly, when verifying it with a bunch + /// of other signatures, the whole batch would fail! + /// + /// # "Strict" Verification + /// + /// This method performs *both* of the above signature malleability checks. + /// + /// It must be done as a separate method because one doesn't simply get to + /// change the definition of a cryptographic primitive ten years + /// after-the-fact with zero consideration for backwards compatibility in + /// hardware and protocols which have it already have the older definition + /// baked in. + /// + /// # Return + /// + /// Returns `Ok(())` if the signature is valid, and `Err` otherwise. + #[allow(non_snake_case)] + pub fn verify_strict( + &self, + message: &[u8], + signature: &Signature, + ) -> Result<(), SignatureError> + { + let mut h: Sha512 = Sha512::new(); + let R: EdwardsPoint; + let k: Scalar; + let minus_A: EdwardsPoint = -self.1; + let signature_R: EdwardsPoint; + + match signature.R.decompress() { + None => return Err(SignatureError(InternalError::VerifyError)), + Some(x) => signature_R = x, + } + + // Logical OR is fine here as we're not trying to be constant time. + if signature_R.is_small_order() || self.1.is_small_order() { + return Err(SignatureError(InternalError::VerifyError)); + } + + h.input(signature.R.as_bytes()); + h.input(self.as_bytes()); + h.input(&message); + + k = Scalar::from_hash(h); + R = EdwardsPoint::vartime_double_scalar_mul_basepoint(&k, &(minus_A), &signature.s); + + if R == signature_R { + Ok(()) + } else { + Err(SignatureError(InternalError::VerifyError)) + } + } } #[cfg(feature = "serde")] From ce2260afab60c6ef1cda5c7571aef1f69019c7d9 Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Thu, 3 Oct 2019 23:42:16 +0000 Subject: [PATCH 04/20] Implement stricter scalar malleability checking for signatures. Previously, we were checking that the highest 3 bits were unset, which still leaves 2^253 - 2^252 + 27742317777372353535851937790883648493 potential scalars for the `s` component of a signature which are not strictly mod \ell. This change fixes that. Note: This change makes ed25519-dalek incompatible with ed25519-donna in that some signatures produced by donna will be verifiable by donna but NOT VERIFIABLE by dalek. On the other hand, libsodium exports a -DED25519_COMPAT feature, which when enabled, means it is compatible with dalek with the `legacy_compatibility` feature disabled. Otherwise, libsodium's behaviour is identical to the behaviour enabled by default in this patch. --- Cargo.toml | 2 ++ src/signature.rs | 34 +++++++++++++++++++++++++++++++--- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 453d72e..28411f1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -69,6 +69,8 @@ alloc = ["curve25519-dalek/alloc", "rand_os"] nightly = ["curve25519-dalek/nightly", "clear_on_drop/nightly"] batch = ["rand"] asm = ["sha2/asm"] +# This features turns off stricter checking for scalar malleability in signatures +legacy_compatibility = [] yolocrypto = ["curve25519-dalek/yolocrypto"] u64_backend = ["curve25519-dalek/u64_backend"] u32_backend = ["curve25519-dalek/u32_backend"] diff --git a/src/signature.rs b/src/signature.rs index d5079fd..653155d 100644 --- a/src/signature.rs +++ b/src/signature.rs @@ -71,6 +71,31 @@ impl Debug for Signature { } } +#[cfg(feature = "legacy_compatibility")] +#[inline(always)] +fn check_scalar(bytes: [u8; 32]) -> Result { + // The highest 3 bits must not be set. No other checking for the + // remaining 2^253 - 2^252 + 27742317777372353535851937790883648493 + // potential non-reduced scalars is performed. + // + // This is compatible with ed25519-donna and libsodium when + // -DED25519_COMPAT is NOT specified. + if bytes[31] & 224 != 0 { + return Err(SignatureError(InternalError::ScalarFormatError)); + } + + Ok(Scalar::from_bits(bytes)) +} + +#[cfg(not(feature = "legacy_compatibility"))] +#[inline(always)] +fn check_scalar(bytes: [u8; 32]) -> Result { + match Scalar::from_canonical_bytes(bytes) { + None => return Err(SignatureError(InternalError::ScalarFormatError)), + Some(x) => return Ok(x), + }; +} + impl Signature { /// Convert this `Signature` to a byte array. #[inline] @@ -97,13 +122,16 @@ impl Signature { lower.copy_from_slice(&bytes[..32]); upper.copy_from_slice(&bytes[32..]); - if upper[31] & 224 != 0 { - return Err(SignatureError(InternalError::ScalarFormatError)); + let s: Scalar; + + match check_scalar(upper) { + Ok(x) => s = x, + Err(x) => return Err(x), } Ok(Signature { R: CompressedEdwardsY(lower), - s: Scalar::from_bits(upper), + s: s, }) } } From 2d5fe86f3062ec5edf36a73d5b4799d9756ce3ce Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Mon, 7 Oct 2019 19:03:15 +0000 Subject: [PATCH 05/20] Document anti-malleability features/functionality. --- README.md | 42 ++++++++++++++++++++++++++++++++++++++--- src/signature.rs | 49 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 9cb233c..6941314 100644 --- a/README.md +++ b/README.md @@ -108,9 +108,45 @@ after the fact, breaking compatibility with every other implementation. 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/dalek-cryptography/curve25519-dalek/issues/9) to -eventually support VXEdDSA in curve25519-dalek. +instead. + +#### The `legacy_compatibility` Feature + +By default, this library performs a stricter check for malleability in the +scalar component of a signature, upon signature deserialisation. This stricter +check, that `s < \ell` where `\ell` is the order of the basepoint, is +[mandated by RFC8032](https://tools.ietf.org/html/rfc8032#section-5.1.7). +However, that RFC was standardised a decade after the original paper, which, as +described above, (usually, falsely) stated that malleability was inconsequential. + +Because of this, most ed25519 implementations only perform a limited, hackier +check that the most significant three bits of the scalar are unset. If you need +compatibility with legacy implementations, including: + +* ed25519-donna +* Golang's /x/crypto ed25519 +* libsodium (only when built with `-DED25519_COMPAT`) +* NaCl's "ref" implementation +* probably a bunch of others + +then enable `ed25519-dalek`'s `legacy_compatibility` feature. Please note and +be forewarned that doing so allows for signature malleability, meaning that +there may be two different and "valid" signatures with the same key for the same +message, which is obviously incredibly dangerous in a number of contexts, +including—but not limited to—identification protocols and cryptocurrency +transactions. + +#### The `verify_strict()` Function + +The scalar component of a signature is not the only source of signature +malleability, however. Both the public key used for signature verification and +the group element component of the signature are malleable, as they may contain +a small torsion component as a consquence of the curve25519 group not being of +prime order, but having a small cofactor of 8. + +If you wish to also eliminate this source of signature malleability, please +review the +[documentation for the `verify_strict()` function](https://doc.dalek.rs/ed25519_dalek/struct.PublicKey.html#method.verify_strict). # Installation diff --git a/src/signature.rs b/src/signature.rs index 653155d..8bcbbe3 100644 --- a/src/signature.rs +++ b/src/signature.rs @@ -108,6 +108,55 @@ impl Signature { } /// Construct a `Signature` from a slice of bytes. + /// + /// # Scalar Malleability Checking + /// + /// As originally specified in the ed25519 paper (cf. the "Malleability" + /// section of the README in this repo), no checks whatsoever were performed + /// for signature malleability. + /// + /// Later, a semi-functional, hacky check was added to most libraries to + /// "ensure" that the scalar portion, `s`, of the signature was reduced `mod + /// \ell`, the order of the basepoint: + /// + /// ```ignore + /// if signature.s[31] & 224 != 0 { + /// return Err(); + /// } + /// ``` + /// + /// This bit-twiddling ensures that the most significant three bits of the + /// scalar are not set: + /// + /// ```python,ignore + /// >>> 0b00010000 & 224 + /// 0 + /// >>> 0b00100000 & 224 + /// 32 + /// >>> 0b01000000 & 224 + /// 64 + /// >>> 0b10000000 & 224 + /// 128 + /// ``` + /// + /// However, this check is hacky and insufficient to check that the scalar is + /// fully reduced `mod \ell = 2^252 + 27742317777372353535851937790883648493` as + /// it leaves us with a guanteed bound of 253 bits. This means that there are + /// `2^253 - 2^252 + 2774231777737235353585193779088364849311` remaining scalars + /// which could cause malleabilllity. + /// + /// RFC8032 [states](https://tools.ietf.org/html/rfc8032#section-5.1.7): + /// + /// > To verify a signature on a message M using public key A, [...] + /// > first split the signature into two 32-octet halves. Decode the first + /// > half as a point R, and the second half as an integer S, in the range + /// > 0 <= s < L. Decode the public key A as point A'. If any of the + /// > decodings fail (including S being out of range), the signature is + /// > invalid. + /// + /// However, by the time this was standardised, most libraries in use were + /// only checking the most significant three bits. (See also the + /// documentation for `PublicKey.verify_strict`.) #[inline] pub fn from_bytes(bytes: &[u8]) -> Result { if bytes.len() != SIGNATURE_LENGTH { From a065bee381d7bfe63d3eda2ed66c6cd40ce85695 Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Mon, 7 Oct 2019 23:01:10 +0000 Subject: [PATCH 06/20] Enable Rust 2018. --- Cargo.toml | 1 + src/lib.rs | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b385b0d..d4cf4e9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,7 @@ [package] name = "ed25519-dalek" version = "1.0.0-pre.2" +edition = "2018" authors = ["isis lovecruft "] readme = "README.md" license = "BSD-3-Clause" diff --git a/src/lib.rs b/src/lib.rs index 13784fb..1b0476a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -247,8 +247,6 @@ #![no_std] #![warn(future_incompatible)] -#![warn(rust_2018_compatibility)] -#![warn(rust_2018_idioms)] #![deny(missing_docs)] // refuse to compile if documentation is missing #[cfg(any(feature = "std", test))] From deca36d07421df7b8cc5e1124bac5d4f777c6a01 Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Fri, 11 Oct 2019 21:33:55 +0000 Subject: [PATCH 07/20] Add an optimisation to succeed fast for scalars whose 4th MSB is unset. This is only done during signature verification. --- src/signature.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/signature.rs b/src/signature.rs index 8bcbbe3..59da225 100644 --- a/src/signature.rs +++ b/src/signature.rs @@ -90,6 +90,18 @@ fn check_scalar(bytes: [u8; 32]) -> Result { #[cfg(not(feature = "legacy_compatibility"))] #[inline(always)] fn check_scalar(bytes: [u8; 32]) -> Result { + // Since this is only used in signature deserialisation (i.e. upon + // verification), we can do a "succeed fast" trick by checking that the most + // significant 4 bits are unset. If they are unset, we can succeed fast + // because we are guaranteed that the scalar is fully reduced. However, if + // the 4th most significant bit is set, we must do the full reduction check, + // as the order of the basepoint is roughly a 2^(252.5) bit number. + // + // This succeed-fast trick should succeed for roughly half of all scalars. + if bytes[31] & 240 == 0 { + return Ok(Scalar::from_bits(bytes)) + } + match Scalar::from_canonical_bytes(bytes) { None => return Err(SignatureError(InternalError::ScalarFormatError)), Some(x) => return Ok(x), From f1d4c4a732eff4ff195970ddb81146dcf2b8a773 Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Wed, 16 Oct 2019 20:57:31 +0000 Subject: [PATCH 08/20] Remove panics from batch verification API in lieu of better error handling. --- src/ed25519.rs | 18 +++++++++--------- src/errors.rs | 10 ++++++++++ 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/ed25519.rs b/src/ed25519.rs index 9352595..f1da97b 100644 --- a/src/ed25519.rs +++ b/src/ed25519.rs @@ -50,11 +50,6 @@ pub use crate::signature::*; /// * `public_keys` is a slice of `PublicKey`s. /// * `csprng` is an implementation of `Rng + CryptoRng`. /// -/// # Panics -/// -/// This function will panic if the `messages, `signatures`, and `public_keys` -/// slices are not equal length. -/// /// # Returns /// /// * A `Result` whose `Ok` value is an emtpy tuple and whose `Err` value is a @@ -93,10 +88,15 @@ pub fn verify_batch( public_keys: &[PublicKey], ) -> Result<(), SignatureError> { - const ASSERT_MESSAGE: &'static str = "The number of messages, signatures, and public keys must be equal."; - assert!(signatures.len() == messages.len(), ASSERT_MESSAGE); - assert!(signatures.len() == public_keys.len(), ASSERT_MESSAGE); - assert!(public_keys.len() == messages.len(), ASSERT_MESSAGE); + if signatures.len() != messages.len() || + signatures.len() != public_keys.len() || + public_keys.len() != messages.len() { + return Err(SignatureError(InternalError::ArrayLengthError{ + name_a: "signatures", length_a: signatures.len(), + name_b: "messages", length_b: messages.len(), + name_c: "public_keys", length_c: public_keys.len(), + })); + } #[cfg(feature = "alloc")] use alloc::vec::Vec; diff --git a/src/errors.rs b/src/errors.rs index 6597f73..6e88124 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -33,6 +33,11 @@ pub(crate) enum InternalError { }, /// The verification equation wasn't satisfied VerifyError, + /// Two arrays did not match in size, making the called signature + /// verification method impossible. + ArrayLengthError{ name_a: &'static str, length_a: usize, + name_b: &'static str, length_b: usize, + name_c: &'static str, length_c: usize, }, } impl Display for InternalError { @@ -46,6 +51,11 @@ impl Display for InternalError { => write!(f, "{} must be {} bytes in length", n, l), InternalError::VerifyError => write!(f, "Verification equation was not satisfied"), + InternalError::ArrayLengthError{ name_a: na, length_a: la, + name_b: nb, length_b: lb, + name_c: nc, length_c: lc, } + => write!(f, "Arrays must be the same length: {} has length {}, + {} has length {}, {} has length {}.", na, la, nb, lb, nc, lc), } } } From c3f4c7a67ed666a69d4787134a74e0f179914485 Mon Sep 17 00:00:00 2001 From: Michael Lodder Date: Mon, 21 Oct 2019 09:03:08 -0600 Subject: [PATCH 09/20] Update to latest rand Signed-off-by: Michael Lodder --- Cargo.toml | 24 +++++------------ src/ed25519.rs | 34 +++++++++++------------- src/lib.rs | 69 ++++++++++++++++++------------------------------ src/secret.rs | 34 +++++++++++------------- tests/ed25519.rs | 13 +++++---- 5 files changed, 70 insertions(+), 104 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 453d72e..95161a8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,20 +19,11 @@ travis-ci = { repository = "dalek-cryptography/ed25519-dalek", branch = "master" version = "1" default-features = false -[dependencies.rand_core] -version = "0.3" -default-features = false - [dependencies.rand] -version = "0.6" -features = ["i128_support"] +version = "0.7" default-features = false optional = true -[dependencies.rand_os] -version = "0.1" -optional = true - [dependencies.serde] version = "^1.0" optional = true @@ -49,11 +40,10 @@ default-features = false version = "0.2" [dev-dependencies] -hex = "^0.3" +hex = "^0.4" bincode = "^0.9" -criterion = "0.2" -rand = "0.6" -rand_os = "0.1" +criterion = "0.3" +rand = "0.7" [[bench]] name = "ed25519_benchmarks" @@ -64,9 +54,9 @@ harness = false [features] default = ["std", "u64_backend"] -std = ["curve25519-dalek/std", "rand_os", "sha2/std"] -alloc = ["curve25519-dalek/alloc", "rand_os"] -nightly = ["curve25519-dalek/nightly", "clear_on_drop/nightly"] +std = ["curve25519-dalek/std", "sha2/std", "rand/std"] +alloc = ["curve25519-dalek/alloc", "rand/alloc"] +nightly = ["curve25519-dalek/nightly", "clear_on_drop/nightly", "rand/nightly"] batch = ["rand"] asm = ["sha2/asm"] yolocrypto = ["curve25519-dalek/yolocrypto"] diff --git a/src/ed25519.rs b/src/ed25519.rs index a76b091..f8e2cbc 100644 --- a/src/ed25519.rs +++ b/src/ed25519.rs @@ -12,7 +12,7 @@ #[allow(unused_imports)] use core::default::Default; -use rand_core::{CryptoRng, RngCore}; +use rand::{CryptoRng, RngCore}; #[cfg(feature = "serde")] use serde::de::Error as SerdeError; @@ -65,16 +65,16 @@ pub use crate::signature::*; /// /// ``` /// extern crate ed25519_dalek; -/// extern crate rand_os; +/// extern crate rand; /// /// use ed25519_dalek::verify_batch; /// use ed25519_dalek::Keypair; /// use ed25519_dalek::PublicKey; /// use ed25519_dalek::Signature; -/// use rand_os::OsRng; +/// use rand::rngs::OsRng; /// /// # fn main() { -/// let mut csprng: OsRng = OsRng::new().unwrap(); +/// let mut csprng = OsRng{}; /// let keypairs: Vec = (0..64).map(|_| Keypair::generate(&mut csprng)).collect(); /// let msg: &[u8] = b"They're good dogs Brant"; /// let messages: Vec<&[u8]> = (0..64).map(|_| msg).collect(); @@ -216,19 +216,17 @@ impl Keypair { /// # Example /// /// ``` - /// extern crate rand_core; - /// extern crate rand_os; + /// extern crate rand; /// extern crate ed25519_dalek; /// /// # #[cfg(feature = "std")] /// # fn main() { /// - /// use rand_core::{CryptoRng, RngCore}; - /// use rand_os::OsRng; + /// use rand::rngs::OsRng; /// use ed25519_dalek::Keypair; /// use ed25519_dalek::Signature; /// - /// let mut csprng: OsRng = OsRng::new().unwrap(); + /// let mut csprng = OsRng{}; /// let keypair: Keypair = Keypair::generate(&mut csprng); /// /// # } @@ -283,17 +281,17 @@ impl Keypair { /// /// ``` /// extern crate ed25519_dalek; - /// extern crate rand_os; + /// extern crate rand; /// /// use ed25519_dalek::Digest; /// use ed25519_dalek::Keypair; /// use ed25519_dalek::Sha512; /// use ed25519_dalek::Signature; - /// use rand_os::OsRng; + /// use rand::rngs::OsRng; /// /// # #[cfg(feature = "std")] /// # fn main() { - /// let mut csprng = OsRng::new().unwrap(); + /// let mut csprng = OsRng{}; /// let keypair: Keypair = Keypair::generate(&mut csprng); /// let message: &[u8] = b"All I want is to pet all of the dogs."; /// @@ -330,17 +328,17 @@ impl Keypair { /// /// ``` /// # extern crate ed25519_dalek; - /// # extern crate rand_os; + /// # extern crate rand; /// # /// # use ed25519_dalek::Digest; /// # use ed25519_dalek::Keypair; /// # use ed25519_dalek::Signature; /// # use ed25519_dalek::Sha512; - /// # use rand_os::OsRng; + /// # use rand::rngs::OsRng; /// # /// # #[cfg(feature = "std")] /// # fn main() { - /// # let mut csprng: OsRng = OsRng::new().unwrap(); + /// # let mut csprng = OsRng{}; /// # let keypair: Keypair = Keypair::generate(&mut csprng); /// # let message: &[u8] = b"All I want is to pet all of the dogs."; /// # let mut prehashed: Sha512 = Sha512::new(); @@ -401,17 +399,17 @@ impl Keypair { /// /// ``` /// extern crate ed25519_dalek; - /// extern crate rand_os; + /// extern crate rand; /// /// use ed25519_dalek::Digest; /// use ed25519_dalek::Keypair; /// use ed25519_dalek::Signature; /// use ed25519_dalek::Sha512; - /// use rand_os::OsRng; + /// use rand::rngs::OsRng; /// /// # #[cfg(feature = "std")] /// # fn main() { - /// let mut csprng: OsRng = OsRng::new().unwrap(); + /// let mut csprng = OsRng{}; /// let keypair: Keypair = Keypair::generate(&mut csprng); /// let message: &[u8] = b"All I want is to pet all of the dogs."; /// diff --git a/src/lib.rs b/src/lib.rs index fcd52af..2974124 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,18 +19,16 @@ //! the operating system's builtin PRNG: //! //! ``` -//! extern crate rand_core; -//! extern crate rand_os; +//! extern crate rand; //! extern crate ed25519_dalek; //! //! # #[cfg(feature = "std")] //! # fn main() { -//! use rand_core::RngCore; -//! use rand_os::OsRng; +//! use rand::rngs::OsRng; //! use ed25519_dalek::Keypair; //! use ed25519_dalek::Signature; //! -//! let mut csprng: OsRng = OsRng::new().unwrap(); +//! let mut csprng = OsRng{}; //! let keypair: Keypair = Keypair::generate(&mut csprng); //! # } //! # @@ -41,15 +39,13 @@ //! We can now use this `keypair` to sign a message: //! //! ``` -//! # extern crate rand_core; -//! # extern crate rand_os; +//! # extern crate rand; //! # extern crate ed25519_dalek; //! # fn main() { -//! # use rand_core::RngCore; -//! # use rand_os::OsRng; +//! # use rand::rngs::OsRng; //! # use ed25519_dalek::Keypair; //! # use ed25519_dalek::Signature; -//! # let mut csprng = OsRng::new().unwrap(); +//! # let mut csprng = OsRng{}; //! # let keypair: Keypair = Keypair::generate(&mut csprng); //! let message: &[u8] = b"This is a test of the tsunami alert system."; //! let signature: Signature = keypair.sign(message); @@ -60,15 +56,13 @@ //! that `message`: //! //! ``` -//! # extern crate rand_core; -//! # extern crate rand_os; +//! # extern crate rand; //! # extern crate ed25519_dalek; //! # fn main() { -//! # use rand_core::RngCore; -//! # use rand_os::OsRng; +//! # use rand::rngs::OsRng; //! # use ed25519_dalek::Keypair; //! # use ed25519_dalek::Signature; -//! # let mut csprng = OsRng::new().unwrap(); +//! # let mut csprng = OsRng{}; //! # let keypair: Keypair = Keypair::generate(&mut csprng); //! # let message: &[u8] = b"This is a test of the tsunami alert system."; //! # let signature: Signature = keypair.sign(message); @@ -80,16 +74,14 @@ //! verify this signature: //! //! ``` -//! # extern crate rand_core; -//! # extern crate rand_os; +//! # extern crate rand; //! # extern crate ed25519_dalek; //! # fn main() { -//! # use rand_core::RngCore; -//! # use rand_os::OsRng; +//! # use rand::rngs::OsRng; //! # use ed25519_dalek::Keypair; //! # use ed25519_dalek::Signature; //! use ed25519_dalek::PublicKey; -//! # let mut csprng = OsRng::new().unwrap(); +//! # let mut csprng = OsRng{}; //! # let keypair: Keypair = Keypair::generate(&mut csprng); //! # let message: &[u8] = b"This is a test of the tsunami alert system."; //! # let signature: Signature = keypair.sign(message); @@ -108,15 +100,13 @@ //! verify your signatures!) //! //! ``` -//! # extern crate rand_core; -//! # extern crate rand_os; +//! # extern crate rand; //! # extern crate ed25519_dalek; //! # fn main() { -//! # use rand_core::RngCore; -//! # use rand_os::OsRng; +//! # use rand::rngs::OsRng; //! # use ed25519_dalek::{Keypair, Signature, PublicKey}; //! use ed25519_dalek::{PUBLIC_KEY_LENGTH, SECRET_KEY_LENGTH, KEYPAIR_LENGTH, SIGNATURE_LENGTH}; -//! # let mut csprng = OsRng::new().unwrap(); +//! # let mut csprng = OsRng{}; //! # let keypair: Keypair = Keypair::generate(&mut csprng); //! # let message: &[u8] = b"This is a test of the tsunami alert system."; //! # let signature: Signature = keypair.sign(message); @@ -132,15 +122,13 @@ //! And similarly, decoded from bytes with `::from_bytes()`: //! //! ``` -//! # extern crate rand_core; -//! # extern crate rand_os; +//! # extern crate rand; //! # extern crate ed25519_dalek; -//! # use rand_core::RngCore; -//! # use rand_os::OsRng; +//! # use rand::rngs::OsRng; //! # 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), SignatureError> { -//! # let mut csprng = OsRng::new().unwrap(); +//! # let mut csprng = OsRng{}; //! # let keypair_orig: Keypair = Keypair::generate(&mut csprng); //! # let message: &[u8] = b"This is a test of the tsunami alert system."; //! # let signature_orig: Signature = keypair_orig.sign(message); @@ -175,8 +163,7 @@ //! For example, using [bincode](https://github.com/TyOverby/bincode): //! //! ``` -//! # extern crate rand_core; -//! # extern crate rand_os; +//! # extern crate rand; //! # extern crate ed25519_dalek; //! # #[cfg(feature = "serde")] //! extern crate serde; @@ -185,11 +172,10 @@ //! //! # #[cfg(feature = "serde")] //! # fn main() { -//! # use rand_core::RngCore; -//! # use rand_os::OsRng; +//! # use rand::rngs::OsRng; //! # use ed25519_dalek::{Keypair, Signature, PublicKey}; //! use bincode::{serialize, Infinite}; -//! # let mut csprng = OsRng::new().unwrap(); +//! # let mut csprng = OsRng{}; //! # let keypair: Keypair = Keypair::generate(&mut csprng); //! # let message: &[u8] = b"This is a test of the tsunami alert system."; //! # let signature: Signature = keypair.sign(message); @@ -207,8 +193,7 @@ //! recipient may deserialise them and verify: //! //! ``` -//! # extern crate rand_core; -//! # extern crate rand_os; +//! # extern crate rand; //! # extern crate ed25519_dalek; //! # #[cfg(feature = "serde")] //! # extern crate serde; @@ -217,13 +202,12 @@ //! # //! # #[cfg(feature = "serde")] //! # fn main() { -//! # use rand_core::RngCore; -//! # use rand_os::OsRng; +//! # use rand::rngs::OsRng; //! # use ed25519_dalek::{Keypair, Signature, PublicKey}; //! # use bincode::{serialize, Infinite}; //! use bincode::{deserialize}; //! -//! # let mut csprng = OsRng::new().unwrap(); +//! # let mut csprng = OsRng{}; //! # let keypair: Keypair = Keypair::generate(&mut csprng); //! let message: &[u8] = b"This is a test of the tsunami alert system."; //! # let signature: Signature = keypair.sign(message); @@ -260,11 +244,8 @@ extern crate alloc; extern crate clear_on_drop; extern crate curve25519_dalek; extern crate failure; -#[cfg(all(feature = "batch", any(feature = "std", feature = "alloc", test)))] +#[cfg(any(feature = "batch", feature = "std", feature = "alloc", test))] extern crate rand; -#[cfg(any(feature = "std", test))] -extern crate rand_os; -extern crate rand_core; #[cfg(feature = "serde")] extern crate serde; extern crate sha2; diff --git a/src/secret.rs b/src/secret.rs index 5393e4d..8dd96d1 100644 --- a/src/secret.rs +++ b/src/secret.rs @@ -19,7 +19,7 @@ use curve25519_dalek::digest::Digest; use curve25519_dalek::edwards::CompressedEdwardsY; use curve25519_dalek::scalar::Scalar; -use rand_core::{CryptoRng, RngCore}; +use rand::{CryptoRng, RngCore}; use sha2::Sha512; @@ -125,18 +125,18 @@ impl SecretKey { /// # Example /// /// ``` - /// extern crate rand_os; + /// extern crate rand; /// extern crate ed25519_dalek; /// /// # #[cfg(feature = "std")] /// # fn main() { /// # - /// use rand_os::OsRng; + /// use rand::rngs::OsRng; /// use ed25519_dalek::PublicKey; /// use ed25519_dalek::SecretKey; /// use ed25519_dalek::Signature; /// - /// let mut csprng: OsRng = OsRng::new().unwrap(); + /// let mut csprng = OsRng{}; /// let secret_key: SecretKey = SecretKey::generate(&mut csprng); /// # } /// # @@ -147,17 +147,17 @@ impl SecretKey { /// Afterwards, you can generate the corresponding public: /// /// ``` - /// # extern crate rand_os; + /// # extern crate rand; /// # extern crate ed25519_dalek; /// # /// # fn main() { /// # - /// # use rand_os::OsRng; + /// # use rand::rngs::OsRng; /// # use ed25519_dalek::PublicKey; /// # use ed25519_dalek::SecretKey; /// # use ed25519_dalek::Signature; /// # - /// # let mut csprng = OsRng::new().unwrap(); + /// # let mut csprng = OsRng{}; /// # let secret_key: SecretKey = SecretKey::generate(&mut csprng); /// /// let public_key: PublicKey = (&secret_key).into(); @@ -270,18 +270,16 @@ impl<'a> From<&'a SecretKey> for ExpandedSecretKey { /// # Examples /// /// ``` - /// # extern crate rand_core; - /// # extern crate rand_os; + /// # extern crate rand; /// # extern crate sha2; /// # extern crate ed25519_dalek; /// # /// # fn main() { /// # - /// use rand_core::RngCore; - /// use rand_os::OsRng; + /// use rand::rngs::OsRng; /// use ed25519_dalek::{SecretKey, ExpandedSecretKey}; /// - /// let mut csprng = OsRng::new().unwrap(); + /// let mut csprng = OsRng{}; /// let secret_key: SecretKey = SecretKey::generate(&mut csprng); /// let expanded_secret_key: ExpandedSecretKey = ExpandedSecretKey::from(&secret_key); /// # } @@ -318,17 +316,17 @@ impl ExpandedSecretKey { /// # Examples /// /// ``` - /// # extern crate rand_os; + /// # extern crate rand; /// # extern crate sha2; /// # extern crate ed25519_dalek; /// # /// # #[cfg(feature = "std")] /// # fn main() { /// # - /// use rand_os::OsRng; + /// use rand::rngs::OsRng; /// use ed25519_dalek::{SecretKey, ExpandedSecretKey}; /// - /// let mut csprng: OsRng = OsRng::new().unwrap(); + /// let mut csprng = OsRng{}; /// let secret_key: SecretKey = SecretKey::generate(&mut csprng); /// let expanded_secret_key: ExpandedSecretKey = ExpandedSecretKey::from(&secret_key); /// let expanded_secret_key_bytes: [u8; 64] = expanded_secret_key.to_bytes(); @@ -358,7 +356,7 @@ impl ExpandedSecretKey { /// # Examples /// /// ``` - /// # extern crate rand_os; + /// # extern crate rand; /// # extern crate sha2; /// # extern crate ed25519_dalek; /// # @@ -367,11 +365,11 @@ impl ExpandedSecretKey { /// # #[cfg(feature = "std")] /// # fn do_test() -> Result { /// # - /// use rand_os::OsRng; + /// use rand::rngs::OsRng; /// use ed25519_dalek::{SecretKey, ExpandedSecretKey}; /// use ed25519_dalek::SignatureError; /// - /// let mut csprng: OsRng = OsRng::new().unwrap(); + /// let mut csprng = OsRng{}; /// let secret_key: SecretKey = SecretKey::generate(&mut csprng); /// let expanded_secret_key: ExpandedSecretKey = ExpandedSecretKey::from(&secret_key); /// let bytes: [u8; 64] = expanded_secret_key.to_bytes(); diff --git a/tests/ed25519.rs b/tests/ed25519.rs index 555b952..88a24df 100644 --- a/tests/ed25519.rs +++ b/tests/ed25519.rs @@ -13,15 +13,13 @@ extern crate bincode; extern crate ed25519_dalek; extern crate hex; -extern crate rand_os; extern crate sha2; +extern crate rand; use ed25519_dalek::*; use hex::FromHex; -use rand_os::OsRng; - use sha2::Sha512; #[cfg(test)] @@ -113,6 +111,7 @@ mod vectors { #[cfg(test)] mod integrations { use super::*; + use rand::rngs::OsRng; #[test] fn sign_verify() { // TestSignVerify @@ -123,7 +122,7 @@ mod integrations { let good: &[u8] = "test message".as_bytes(); let bad: &[u8] = "wrong message".as_bytes(); - let mut csprng: OsRng = OsRng::new().unwrap(); + let mut csprng = OsRng{}; keypair = Keypair::generate(&mut csprng); good_sig = keypair.sign(&good); @@ -146,7 +145,7 @@ mod integrations { let good: &[u8] = b"test message"; let bad: &[u8] = b"wrong message"; - let mut csprng: OsRng = OsRng::new().unwrap(); + let mut csprng = OsRng{}; // 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(); @@ -186,7 +185,7 @@ mod integrations { b"Fuck dumbin' it down, spit ice, skip jewellery: Molotov cocktails on me like accessories.", b"Hey, I never cared about your bucks, so if I run up with a mask on, probably got a gas can too.", b"And I'm not here to fill 'er up. Nope, we came to riot, here to incite, we don't want any of your stuff.", ]; - let mut csprng: OsRng = OsRng::new().unwrap(); + let mut csprng = OsRng{}; let mut keypairs: Vec = Vec::new(); let mut signatures: Vec = Vec::new(); @@ -204,7 +203,7 @@ mod integrations { #[test] fn pubkey_from_secret_and_expanded_secret() { - let mut csprng = OsRng::new().unwrap(); + let mut csprng = OsRng{}; let secret: SecretKey = SecretKey::generate(&mut csprng); let expanded_secret: ExpandedSecretKey = (&secret).into(); let public_from_secret: PublicKey = (&secret).into(); // XXX eww From ecb6fd8ec4fb68d9430db41a551fc2a1529c9d90 Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Fri, 25 Oct 2019 22:18:42 +0000 Subject: [PATCH 10/20] Cleanup dependencies in Cargo.toml. --- Cargo.toml | 30 +++++++----------------------- 1 file changed, 7 insertions(+), 23 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 09bcc67..7c582b1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,29 +16,13 @@ exclude = [ ".gitignore", "TESTVECTORS", "res/*" ] [badges] travis-ci = { repository = "dalek-cryptography/ed25519-dalek", branch = "master"} -[dependencies.curve25519-dalek] -version = "1" -default-features = false - -[dependencies.rand] -version = "0.7" -default-features = false -optional = true - -[dependencies.serde] -version = "^1.0" -optional = true - -[dependencies.sha2] -version = "^0.8" -default-features = false - -[dependencies.failure] -version = "^0.1.1" -default-features = false - -[dependencies.clear_on_drop] -version = "0.2" +[dependencies] +clear_on_drop = { version = "0.2" } +curve25519-dalek = { version = "1", default-features = false } +failure = { version = "0.1", default-features = false } +rand = { version = "0.7", default-features = false, optional = true } +serde = { version = "1.0", optional = true } +sha2 = { version = "0.8", default-features = false } [dev-dependencies] hex = "^0.4" From 81f906ca30ebe3ff95f4b64612c702b1abdf34eb Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Sat, 26 Oct 2019 04:16:33 +0000 Subject: [PATCH 11/20] Replace failure dependency with impls of std::error::Error. --- Cargo.toml | 1 - src/errors.rs | 11 ++++++++--- src/lib.rs | 1 - 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 7c582b1..27838cc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,6 @@ travis-ci = { repository = "dalek-cryptography/ed25519-dalek", branch = "master" [dependencies] clear_on_drop = { version = "0.2" } curve25519-dalek = { version = "1", default-features = false } -failure = { version = "0.1", default-features = false } rand = { version = "0.7", default-features = false, optional = true } serde = { version = "1.0", optional = true } sha2 = { version = "0.8", default-features = false } diff --git a/src/errors.rs b/src/errors.rs index 6597f73..ba59180 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -16,6 +16,9 @@ use core::fmt; use core::fmt::Display; +#[cfg(feature = "std")] +use std::error::Error; + /// Internal errors. Most application-level developers will likely not /// need to pay any attention to these. #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] @@ -50,7 +53,8 @@ impl Display for InternalError { } } -impl ::failure::Fail for InternalError {} +#[cfg(feature = "std")] +impl Error for InternalError { } /// Errors which may occur while processing signatures and keypairs. /// @@ -75,8 +79,9 @@ impl Display for SignatureError { } } -impl ::failure::Fail for SignatureError { - fn cause(&self) -> Option<&dyn (::failure::Fail)> { +#[cfg(feature = "std")] +impl Error for SignatureError { + fn source(&self) -> Option<&(dyn Error + 'static)> { Some(&self.0) } } diff --git a/src/lib.rs b/src/lib.rs index e40f4d5..92f1d45 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -241,7 +241,6 @@ extern crate std; extern crate alloc; extern crate clear_on_drop; extern crate curve25519_dalek; -extern crate failure; #[cfg(any(feature = "batch", feature = "std", feature = "alloc", test))] extern crate rand; #[cfg(feature = "serde")] From 15d0a6596f6c419ae57f324900b53c6bd5b4c224 Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Thu, 14 Nov 2019 22:01:34 +0000 Subject: [PATCH 12/20] Document batch verification on docs.rs and fix false autolinking. --- Cargo.toml | 5 +++++ src/ed25519.rs | 4 ++-- src/public.rs | 4 ++-- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 27838cc..09b8ef2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,11 @@ exclude = [ ".gitignore", "TESTVECTORS", "res/*" ] [badges] travis-ci = { repository = "dalek-cryptography/ed25519-dalek", branch = "master"} +[package.metadata.docs.rs] +# Disabled for now since this is borked; tracking https://github.com/rust-lang/docs.rs/issues/302 +# rustdoc-args = ["--html-in-header", ".cargo/registry/src/github.com-1ecc6299db9ec823/curve25519-dalek-0.13.2/rustdoc-include-katex-header.html"] +features = ["nightly", "batch"] + [dependencies] clear_on_drop = { version = "0.2" } curve25519-dalek = { version = "1", default-features = false } diff --git a/src/ed25519.rs b/src/ed25519.rs index 8bddb67..831cc1d 100644 --- a/src/ed25519.rs +++ b/src/ed25519.rs @@ -475,8 +475,8 @@ impl Keypair { /// §5.1.7, for small torsion components in the `R` value of the signature, /// *which is not strictly required*, as they state: /// - /// > Check the group equation [8][S]B = [8]R + [8][k]A'. It's - /// > sufficient, but not required, to instead check [S]B = R + [k]A'. + /// > Check the group equation \[8\]\[S\]B = \[8\]R + \[8\]\[k\]A'. It's + /// > sufficient, but not required, to instead check \[S\]B = R + \[k\]A'. /// /// # History of Malleability Checks /// diff --git a/src/public.rs b/src/public.rs index f25c88f..f901fcf 100644 --- a/src/public.rs +++ b/src/public.rs @@ -274,8 +274,8 @@ impl PublicKey { /// §5.1.7, for small torsion components in the `R` value of the signature, /// *which is not strictly required*, as they state: /// - /// > Check the group equation [8][S]B = [8]R + [8][k]A'. It's - /// > sufficient, but not required, to instead check [S]B = R + [k]A'. + /// > Check the group equation \[8\]\[S\]B = \[8\]R + \[8\]\[k\]A'. It's + /// > sufficient, but not required, to instead check \[S\]B = R + \[k\]A'. /// /// # History of Malleability Checks /// From ee67f36ba9362f41b5a46c7be99ea2ef398075fa Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Thu, 21 Nov 2019 00:31:10 +0000 Subject: [PATCH 13/20] Move verify_batch() to new batch module. --- src/ed25519.rs | 127 ++----------------------------------------------- src/lib.rs | 2 + 2 files changed, 6 insertions(+), 123 deletions(-) diff --git a/src/ed25519.rs b/src/ed25519.rs index 7cfca3f..7c55a65 100644 --- a/src/ed25519.rs +++ b/src/ed25519.rs @@ -7,9 +7,8 @@ // Authors: // - isis agora lovecruft -//! ed25519 keypairs and batch verification. +//! ed25519 keypairs. -#[allow(unused_imports)] use core::default::Default; use rand::{CryptoRng, RngCore}; @@ -19,139 +18,21 @@ use serde::de::Error as SerdeError; #[cfg(feature = "serde")] use serde::de::Visitor; #[cfg(feature = "serde")] -use serde::{Deserialize, Serialize}; -#[cfg(feature = "serde")] -use serde::{Deserializer, Serializer}; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; pub use sha2::Sha512; use curve25519_dalek::digest::generic_array::typenum::U64; pub use curve25519_dalek::digest::Digest; -#[cfg(all(feature = "batch", any(feature = "alloc", feature = "std")))] -use curve25519_dalek::constants; -#[cfg(all(feature = "batch", any(feature = "alloc", feature = "std")))] -use curve25519_dalek::edwards::EdwardsPoint; -#[cfg(all(feature = "batch", any(feature = "alloc", feature = "std")))] -use curve25519_dalek::scalar::Scalar; - +#[cfg(all(feature = "batch", any(feature = "std", feature = "alloc")))] +pub use crate::batch::*; pub use crate::constants::*; pub use crate::errors::*; pub use crate::public::*; pub use crate::secret::*; pub use crate::signature::*; -/// Verify a batch of `signatures` on `messages` with their respective `public_keys`. -/// -/// # Inputs -/// -/// * `messages` is a slice of byte slices, one per signed message. -/// * `signatures` is a slice of `Signature`s. -/// * `public_keys` is a slice of `PublicKey`s. -/// * `csprng` is an implementation of `Rng + CryptoRng`. -/// -/// # Returns -/// -/// * A `Result` whose `Ok` value is an emtpy tuple and whose `Err` value is a -/// `SignatureError` containing a description of the internal error which -/// occured. -/// -/// # Examples -/// -/// ``` -/// extern crate ed25519_dalek; -/// extern crate rand; -/// -/// use ed25519_dalek::verify_batch; -/// use ed25519_dalek::Keypair; -/// use ed25519_dalek::PublicKey; -/// use ed25519_dalek::Signature; -/// use rand::rngs::OsRng; -/// -/// # fn main() { -/// let mut csprng = OsRng{}; -/// let keypairs: Vec = (0..64).map(|_| Keypair::generate(&mut csprng)).collect(); -/// let msg: &[u8] = b"They're good dogs Brant"; -/// let messages: Vec<&[u8]> = (0..64).map(|_| msg).collect(); -/// let signatures: Vec = keypairs.iter().map(|key| key.sign(&msg)).collect(); -/// let public_keys: Vec = keypairs.iter().map(|key| key.public).collect(); -/// -/// let result = verify_batch(&messages[..], &signatures[..], &public_keys[..]); -/// assert!(result.is_ok()); -/// # } -/// ``` -#[cfg(all(feature = "batch", any(feature = "alloc", feature = "std")))] -#[allow(non_snake_case)] -pub fn verify_batch( - messages: &[&[u8]], - signatures: &[Signature], - public_keys: &[PublicKey], -) -> Result<(), SignatureError> -{ - if signatures.len() != messages.len() || - signatures.len() != public_keys.len() || - public_keys.len() != messages.len() { - return Err(SignatureError(InternalError::ArrayLengthError{ - name_a: "signatures", length_a: signatures.len(), - name_b: "messages", length_b: messages.len(), - name_c: "public_keys", length_c: public_keys.len(), - })); - } - - #[cfg(feature = "alloc")] - use alloc::vec::Vec; - #[cfg(feature = "std")] - use std::vec::Vec; - - use core::iter::once; - use rand::{Rng, thread_rng}; - - use curve25519_dalek::traits::IsIdentity; - use curve25519_dalek::traits::VartimeMultiscalarMul; - - // Select a random 128-bit scalar for each signature. - let zs: Vec = signatures - .iter() - .map(|_| Scalar::from(thread_rng().gen::())) - .collect(); - - // Compute the basepoint coefficient, ∑ s[i]z[i] (mod l) - let B_coefficient: Scalar = signatures - .iter() - .map(|sig| sig.s) - .zip(zs.iter()) - .map(|(s, z)| z * s) - .sum(); - - // Compute H(R || A || M) for each (signature, public_key, message) triplet - let hrams = (0..signatures.len()).map(|i| { - let mut h: Sha512 = Sha512::default(); - h.input(signatures[i].R.as_bytes()); - h.input(public_keys[i].as_bytes()); - h.input(&messages[i]); - Scalar::from_hash(h) - }); - - // Multiply each H(R || A || M) by the random value - let zhrams = hrams.zip(zs.iter()).map(|(hram, z)| hram * z); - - let Rs = signatures.iter().map(|sig| sig.R.decompress()); - let As = public_keys.iter().map(|pk| Some(pk.1)); - let B = once(Some(constants::ED25519_BASEPOINT_POINT)); - - // Compute (-∑ z[i]s[i] (mod l)) B + ∑ z[i]R[i] + ∑ (z[i]H(R||A||M)[i] (mod l)) A[i] = 0 - let id = EdwardsPoint::optional_multiscalar_mul( - once(-B_coefficient).chain(zs.iter().cloned()).chain(zhrams), - B.chain(Rs).chain(As), - ).ok_or_else(|| SignatureError(InternalError::VerifyError))?; - - if id.is_identity() { - Ok(()) - } else { - Err(SignatureError(InternalError::VerifyError)) - } -} - /// An ed25519 keypair. #[derive(Debug, Default)] // we derive Default in order to use the clear() method in Drop pub struct Keypair { diff --git a/src/lib.rs b/src/lib.rs index 92f1d45..bce5edf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -247,6 +247,8 @@ extern crate rand; extern crate serde; extern crate sha2; +#[cfg(all(feature = "batch", any(feature = "std", feature = "alloc")))] +mod batch; mod constants; mod ed25519; mod errors; From 85a218ac402bece6778540ec1376a944dfb0c2d0 Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Fri, 22 Nov 2019 23:29:14 +0000 Subject: [PATCH 14/20] Implement deterministic batch verification and synthetic nonce generation. --- Cargo.toml | 10 ++- src/batch.rs | 203 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 2 + 3 files changed, 212 insertions(+), 3 deletions(-) create mode 100644 src/batch.rs diff --git a/Cargo.toml b/Cargo.toml index 09b8ef2..1c8b73c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,8 @@ features = ["nightly", "batch"] [dependencies] clear_on_drop = { version = "0.2" } -curve25519-dalek = { version = "1", default-features = false } +curve25519-dalek = { version = "2.0.0-alpha.1", default-features = false } +merlin = { version = "1", default-features = false, optional = true } rand = { version = "0.7", default-features = false, optional = true } serde = { version = "1.0", optional = true } sha2 = { version = "0.8", default-features = false } @@ -46,11 +47,14 @@ default = ["std", "u64_backend"] std = ["curve25519-dalek/std", "sha2/std", "rand/std"] alloc = ["curve25519-dalek/alloc", "rand/alloc"] nightly = ["curve25519-dalek/nightly", "clear_on_drop/nightly", "rand/nightly"] -batch = ["rand"] +batch = ["merlin", "rand"] +# This feature enables deterministic batch verification. +batch_deterministic = ["merlin", "rand"] asm = ["sha2/asm"] # This features turns off stricter checking for scalar malleability in signatures legacy_compatibility = [] -yolocrypto = ["curve25519-dalek/yolocrypto"] u64_backend = ["curve25519-dalek/u64_backend"] u32_backend = ["curve25519-dalek/u32_backend"] +# Deprecated curve25519-dalek feature, use "simd_backend" instead: avx2_backend = ["curve25519-dalek/avx2_backend"] +simd_backend = ["curve25519-dalek/simd_backend"] \ No newline at end of file diff --git a/src/batch.rs b/src/batch.rs new file mode 100644 index 0000000..7368ccf --- /dev/null +++ b/src/batch.rs @@ -0,0 +1,203 @@ +// -*- mode: rust; -*- +// +// This file is part of ed25519-dalek. +// Copyright (c) 2017-2019 isis lovecruft +// See LICENSE for licensing information. +// +// Authors: +// - isis agora lovecruft + +//! Batch signature verification. + +#[cfg(feature = "alloc")] +use alloc::vec::Vec; +#[cfg(feature = "std")] +use std::vec::Vec; + +use core::iter::once; + +use curve25519_dalek::constants; +use curve25519_dalek::edwards::EdwardsPoint; +use curve25519_dalek::scalar::Scalar; +use curve25519_dalek::traits::IsIdentity; +use curve25519_dalek::traits::VartimeMultiscalarMul; + +pub use curve25519_dalek::digest::Digest; + +use merlin::Transcript; + +#[cfg(all(feature = "batch", not(feature = "batch_deterministic")))] +use rand::{Rng, thread_rng}; +#[cfg(all(not(feature = "batch"), feature = "batch_deterministic"))] +use rand::{CryptoRng, RngCore}; + +use sha2::Sha512; + +use crate::errors::InternalError; +use crate::errors::SignatureError; +use crate::public::PublicKey; +use crate::signature::Signature; + +trait BatchTranscript { + fn append_hrams(&mut self, hrams: &Vec); +} + +impl BatchTranscript for Transcript { + /// Add all the computed `H(R||A||M)`s to the protocol transcript. + /// + /// Each is also prefixed with their index in the vector. + fn append_hrams(&mut self, hrams: &Vec) { + for (i, hram) in hrams.iter().enumerate() { + self.append_u64(b"", i as u64); + self.append_message(b"hram", hram.as_bytes()); + } + } +} + +/// An implementation of `rand_core::RngCore` which does nothing, to provide +/// purely deterministic transcript-based nonces, rather than synthetically +/// random nonces. +#[cfg(all(not(feature = "batch"), feature = "batch_deterministic"))] +struct ZeroRng {} + +#[cfg(all(not(feature = "batch"), feature = "batch_deterministic"))] +impl rand_core::RngCore for ZeroRng { + fn next_u32(&mut self) -> u32 { + rand_core::impls::next_u32_via_fill(self) + } + + fn next_u64(&mut self) -> u64 { + rand_core::impls::next_u64_via_fill(self) + } + + fn fill_bytes(&mut self, dest: &mut [u8]) { } + + fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core::Error> { + self.fill_bytes(dest); + Ok(()) + } +} + +#[cfg(all(not(feature = "batch"), feature = "batch_deterministic"))] +impl rand_core::CryptoRng for ZeroRng {} + +#[cfg(all(not(feature = "batch"), feature = "batch_deterministic"))] +fn zero_rng() -> ZeroRng { + ZeroRng +} + +/// Verify a batch of `signatures` on `messages` with their respective `public_keys`. +/// +/// # Inputs +/// +/// * `messages` is a slice of byte slices, one per signed message. +/// * `signatures` is a slice of `Signature`s. +/// * `public_keys` is a slice of `PublicKey`s. +/// * `csprng` is an implementation of `Rng + CryptoRng`. +/// +/// # Returns +/// +/// * A `Result` whose `Ok` value is an emtpy tuple and whose `Err` value is a +/// `SignatureError` containing a description of the internal error which +/// occured. +/// +/// # Examples +/// +/// ``` +/// extern crate ed25519_dalek; +/// extern crate rand; +/// +/// use ed25519_dalek::verify_batch; +/// use ed25519_dalek::Keypair; +/// use ed25519_dalek::PublicKey; +/// use ed25519_dalek::Signature; +/// use rand::rngs::OsRng; +/// +/// # fn main() { +/// let mut csprng = OsRng{}; +/// let keypairs: Vec = (0..64).map(|_| Keypair::generate(&mut csprng)).collect(); +/// let msg: &[u8] = b"They're good dogs Brant"; +/// let messages: Vec<&[u8]> = (0..64).map(|_| msg).collect(); +/// let signatures: Vec = keypairs.iter().map(|key| key.sign(&msg)).collect(); +/// let public_keys: Vec = keypairs.iter().map(|key| key.public).collect(); +/// +/// let result = verify_batch(&messages[..], &signatures[..], &public_keys[..]); +/// assert!(result.is_ok()); +/// # } +/// ``` +#[cfg(all(any(feature = "batch", feature = "batch_deterministic"), + any(feature = "alloc", feature = "std")))] +#[allow(non_snake_case)] +pub fn verify_batch( + messages: &[&[u8]], + signatures: &[Signature], + public_keys: &[PublicKey], +) -> Result<(), SignatureError> +{ + // Return an Error if any of the vectors were not the same size as the others. + if signatures.len() != messages.len() || + signatures.len() != public_keys.len() || + public_keys.len() != messages.len() { + return Err(SignatureError(InternalError::ArrayLengthError{ + name_a: "signatures", length_a: signatures.len(), + name_b: "messages", length_b: messages.len(), + name_c: "public_keys", length_c: public_keys.len(), + })); + } + + // Compute H(R || A || M) for each (signature, public_key, message) triplet + let hrams: Vec = (0..signatures.len()).map(|i| { + let mut h: Sha512 = Sha512::default(); + h.input(signatures[i].R.as_bytes()); + h.input(public_keys[i].as_bytes()); + h.input(&messages[i]); + Scalar::from_hash(h) + }).collect(); + + // Build a PRNG based on a transcript of the H(R || A || M)s seen thus far. + // This provides synthethic randomness in the default configuration, and + // purely deterministic in the case of compiling with the + // "batch_deterministic" feature. + let transcript: Transcript = Transcript::new(b"ed25519 batch verification"); + + transcript.append_hrams(&hrams); + + #[cfg(all(feature = "batch", not(feature = "batch_deterministic")))] + let mut prng = transcript.build_rng().finalize(&mut thread_rng()); + #[cfg(all(not(feature = "batch"), feature = "batch_deterministic"))] + let mut prng = transcript.build_rng().finalize(&mut zero_rng()); + + // Select a random 128-bit scalar for each signature. + let zs: Vec = signatures + .iter() + .map(|_| Scalar::from(thread_rng().gen::())) + .collect(); + + + // Compute the basepoint coefficient, ∑ s[i]z[i] (mod l) + let B_coefficient: Scalar = signatures + .iter() + .map(|sig| sig.s) + .zip(zs.iter()) + .map(|(s, z)| z * s) + .sum(); + + // Multiply each H(R || A || M) by the random value + let zhrams = hrams.iter().zip(zs.iter()).map(|(hram, z)| hram * z); + + let Rs = signatures.iter().map(|sig| sig.R.decompress()); + let As = public_keys.iter().map(|pk| Some(pk.1)); + let B = once(Some(constants::ED25519_BASEPOINT_POINT)); + + // Compute (-∑ z[i]s[i] (mod l)) B + ∑ z[i]R[i] + ∑ (z[i]H(R||A||M)[i] (mod l)) A[i] = 0 + let id = EdwardsPoint::optional_multiscalar_mul( + once(-B_coefficient).chain(zs.iter().cloned()).chain(zhrams), + B.chain(Rs).chain(As), + ).ok_or_else(|| SignatureError(InternalError::VerifyError))?; + + if id.is_identity() { + Ok(()) + } else { + Err(SignatureError(InternalError::VerifyError)) + } +} diff --git a/src/lib.rs b/src/lib.rs index bce5edf..097e784 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -241,6 +241,8 @@ extern crate std; extern crate alloc; extern crate clear_on_drop; extern crate curve25519_dalek; +#[cfg(all(any(feature = "batch", feature = "batch_deterministic"), any(feature = "std", feature = "alloc")))] +extern crate merlin; #[cfg(any(feature = "batch", feature = "std", feature = "alloc", test))] extern crate rand; #[cfg(feature = "serde")] From bd6a8977297922dccd5a2ac94cb4be819d731232 Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Fri, 22 Nov 2019 23:40:46 +0000 Subject: [PATCH 15/20] Actually use the transcript PRNG. --- src/batch.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/batch.rs b/src/batch.rs index 7368ccf..d8c513c 100644 --- a/src/batch.rs +++ b/src/batch.rs @@ -170,7 +170,7 @@ pub fn verify_batch( // Select a random 128-bit scalar for each signature. let zs: Vec = signatures .iter() - .map(|_| Scalar::from(thread_rng().gen::())) + .map(|_| Scalar::from(prng.gen::())) .collect(); From ec551145e966894fd320c11eb7d1cdc9922d3b1a Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Fri, 22 Nov 2019 23:21:01 +0000 Subject: [PATCH 16/20] Add message lengths into nonce generator protocol transcript. --- src/batch.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/batch.rs b/src/batch.rs index d8c513c..2cde684 100644 --- a/src/batch.rs +++ b/src/batch.rs @@ -40,6 +40,7 @@ use crate::signature::Signature; trait BatchTranscript { fn append_hrams(&mut self, hrams: &Vec); + fn append_message_lengths(&mut self, message_lengths: &Vec); } impl BatchTranscript for Transcript { @@ -48,10 +49,18 @@ impl BatchTranscript for Transcript { /// Each is also prefixed with their index in the vector. fn append_hrams(&mut self, hrams: &Vec) { for (i, hram) in hrams.iter().enumerate() { + // XXX add message length into transcript self.append_u64(b"", i as u64); self.append_message(b"hram", hram.as_bytes()); } } + + fn append_message_lengths(&mut self, message_lengths: &Vec) { + for (i, len) in message_lengths.iter().enumerate() { + self.append_u64(b"", i as u64); + self.append_u64(b"mlen", len as u64); + } + } } /// An implementation of `rand_core::RngCore` which does nothing, to provide @@ -154,6 +163,9 @@ pub fn verify_batch( Scalar::from_hash(h) }).collect(); + // Collect the message lengths to add into the transcript. + let message_lengths: Vec = messages.iter().map(|i| i.len()).collect(); + // Build a PRNG based on a transcript of the H(R || A || M)s seen thus far. // This provides synthethic randomness in the default configuration, and // purely deterministic in the case of compiling with the @@ -161,6 +173,7 @@ pub fn verify_batch( let transcript: Transcript = Transcript::new(b"ed25519 batch verification"); transcript.append_hrams(&hrams); + transcript.append_message_lengths(&message_lengths); #[cfg(all(feature = "batch", not(feature = "batch_deterministic")))] let mut prng = transcript.build_rng().finalize(&mut thread_rng()); From 8938069053d2ec59a063f4b98edc4a91a6d5b5c5 Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Tue, 26 Nov 2019 22:51:37 +0000 Subject: [PATCH 17/20] Update curve25519-dalek dependency to 2.0.0. --- Cargo.toml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 09b8ef2..c4e1359 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ features = ["nightly", "batch"] [dependencies] clear_on_drop = { version = "0.2" } -curve25519-dalek = { version = "1", default-features = false } +curve25519-dalek = { version = "2", default-features = false } rand = { version = "0.7", default-features = false, optional = true } serde = { version = "1.0", optional = true } sha2 = { version = "0.8", default-features = false } @@ -50,7 +50,6 @@ batch = ["rand"] asm = ["sha2/asm"] # This features turns off stricter checking for scalar malleability in signatures legacy_compatibility = [] -yolocrypto = ["curve25519-dalek/yolocrypto"] u64_backend = ["curve25519-dalek/u64_backend"] u32_backend = ["curve25519-dalek/u32_backend"] -avx2_backend = ["curve25519-dalek/avx2_backend"] +simd_backend = ["curve25519-dalek/simd_backend"] From 1be2a65777ffc198d8fbca419a1178a9e9f1c08b Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Sat, 23 Nov 2019 01:34:13 +0000 Subject: [PATCH 18/20] Maybe I should try compiling my code before showing other cryptographers? lol --- Cargo.toml | 7 ++++--- src/batch.rs | 21 +++++++++++++++------ src/lib.rs | 4 +++- 3 files changed, 22 insertions(+), 10 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1c8b73c..c0ab84f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,8 +24,9 @@ features = ["nightly", "batch"] [dependencies] clear_on_drop = { version = "0.2" } curve25519-dalek = { version = "2.0.0-alpha.1", default-features = false } -merlin = { version = "1", default-features = false, optional = true } +merlin = { version = "1", default-features = false, optional = true, git = "https://github.com/isislovecruft/merlin", branch = "develop" } rand = { version = "0.7", default-features = false, optional = true } +rand_core = { version = "0.5", default-features = false, optional = true } serde = { version = "1.0", optional = true } sha2 = { version = "0.8", default-features = false } @@ -49,7 +50,7 @@ alloc = ["curve25519-dalek/alloc", "rand/alloc"] nightly = ["curve25519-dalek/nightly", "clear_on_drop/nightly", "rand/nightly"] batch = ["merlin", "rand"] # This feature enables deterministic batch verification. -batch_deterministic = ["merlin", "rand"] +batch_deterministic = ["merlin", "rand", "rand_core"] asm = ["sha2/asm"] # This features turns off stricter checking for scalar malleability in signatures legacy_compatibility = [] @@ -57,4 +58,4 @@ u64_backend = ["curve25519-dalek/u64_backend"] u32_backend = ["curve25519-dalek/u32_backend"] # Deprecated curve25519-dalek feature, use "simd_backend" instead: avx2_backend = ["curve25519-dalek/avx2_backend"] -simd_backend = ["curve25519-dalek/simd_backend"] \ No newline at end of file +simd_backend = ["curve25519-dalek/simd_backend"] diff --git a/src/batch.rs b/src/batch.rs index 2cde684..a778934 100644 --- a/src/batch.rs +++ b/src/batch.rs @@ -26,10 +26,11 @@ pub use curve25519_dalek::digest::Digest; use merlin::Transcript; +use rand::Rng; #[cfg(all(feature = "batch", not(feature = "batch_deterministic")))] -use rand::{Rng, thread_rng}; +use rand::thread_rng; #[cfg(all(not(feature = "batch"), feature = "batch_deterministic"))] -use rand::{CryptoRng, RngCore}; +use rand_core; use sha2::Sha512; @@ -58,7 +59,7 @@ impl BatchTranscript for Transcript { fn append_message_lengths(&mut self, message_lengths: &Vec) { for (i, len) in message_lengths.iter().enumerate() { self.append_u64(b"", i as u64); - self.append_u64(b"mlen", len as u64); + self.append_u64(b"mlen", *len as u64); } } } @@ -79,7 +80,15 @@ impl rand_core::RngCore for ZeroRng { rand_core::impls::next_u64_via_fill(self) } - fn fill_bytes(&mut self, dest: &mut [u8]) { } + /// A no-op function which leaves the destination bytes for randomness unchanged. + /// + /// In this case, the internal merlin code is initialising the destination + /// by doing `[0u8; …]`, which means that when we call + /// `merlin::TranscriptRngBuilder.finalize()`, rather than rekeying the + /// STROBE state based on external randomness, we're doing an + /// `ENC_{state}(00000000000000000000000000000000)` operation, which is + /// identical to the STROBE `MAC` operation. + fn fill_bytes(&mut self, _dest: &mut [u8]) { } fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core::Error> { self.fill_bytes(dest); @@ -92,7 +101,7 @@ impl rand_core::CryptoRng for ZeroRng {} #[cfg(all(not(feature = "batch"), feature = "batch_deterministic"))] fn zero_rng() -> ZeroRng { - ZeroRng + ZeroRng {} } /// Verify a batch of `signatures` on `messages` with their respective `public_keys`. @@ -170,7 +179,7 @@ pub fn verify_batch( // This provides synthethic randomness in the default configuration, and // purely deterministic in the case of compiling with the // "batch_deterministic" feature. - let transcript: Transcript = Transcript::new(b"ed25519 batch verification"); + let mut transcript: Transcript = Transcript::new(b"ed25519 batch verification"); transcript.append_hrams(&hrams); transcript.append_message_lengths(&message_lengths); diff --git a/src/lib.rs b/src/lib.rs index 097e784..32aff4e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -249,7 +249,7 @@ extern crate rand; extern crate serde; extern crate sha2; -#[cfg(all(feature = "batch", any(feature = "std", feature = "alloc")))] +#[cfg(all(any(feature = "batch", feature = "batch_deterministic"), any(feature = "std", feature = "alloc")))] mod batch; mod constants; mod ed25519; @@ -260,3 +260,5 @@ mod signature; // Export everything public in ed25519. pub use crate::ed25519::*; +#[cfg(all(any(feature = "batch", feature = "batch_deterministic"), any(feature = "std", feature = "alloc")))] +pub use crate::batch::*; From 52a7fc88b62cfb264641cc468bc5fa770d97ae99 Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Wed, 27 Nov 2019 22:26:54 +0000 Subject: [PATCH 19/20] Update README w.r.t. new features, malleability, synthethic randomness. --- README.md | 119 ++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 89 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index 6941314..49766fb 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,15 @@ verification in Rust. Documentation is available [here](https://docs.rs/ed25519-dalek). +# Installation + +To install, add the following to your project's `Cargo.toml`: + +```toml +[dependencies.ed25519-dalek] +version = "1" +``` + # Benchmarks On an Intel Skylake i9-7900X running at 3.30 GHz, without TurboBoost, this code achieves @@ -89,14 +98,20 @@ can read qhasm, making it more readily and more easily auditable. We're of the opinion that, ultimately, these features—combined with speed—are more valuable than simply cycle counts alone. -### A Note on Signature Malleability +# A Note on Signature Malleability 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/dalek-cryptography/ed25519-dalek/blob/master/res/ed25519-malleability.png) -We could eliminate the malleability property by multiplying by the curve +While the scalar component of our `Signature` struct is strictly *not* +malleable, because reduction checks are put in place upon `Signature` +deserialisation from bytes, for all types of signatures in this crate, +there is still the question of potential malleability due to the group +element components. + +We could eliminate the latter malleability property by multiplying by the curve cofactor, however, this would cause our implementation to *not* match the behaviour of every other implementation in existence. As of this writing, [RFC 8032](https://tools.ietf.org/html/rfc8032), "Edwards-Curve Digital @@ -105,12 +120,16 @@ While we agree that the stronger check should be done, it is our opinion that one shouldn't get to change the definition of "ed25519 verification" a decade after the fact, breaking compatibility with every other implementation. -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. +However, if you require this, please see the documentation for the +`verify_strict()` function, which does the full checks for the group elements. +This functionality is available by default. -#### The `legacy_compatibility` Feature +If for some reason—although we strongely advise you not to—you need to conform +to the original specification of ed25519 signatures as in the excerpt from the +paper above, you can disable scalar malleability checking via +`--features='legacy_compatibility'`. **WE STRONGLY ADVISE AGAINST THIS.** + +## The `legacy_compatibility` Feature By default, this library performs a stricter check for malleability in the scalar component of a signature, upon signature deserialisation. This stricter @@ -136,7 +155,7 @@ message, which is obviously incredibly dangerous in a number of contexts, including—but not limited to—identification protocols and cryptocurrency transactions. -#### The `verify_strict()` Function +## The `verify_strict()` Function The scalar component of a signature is not the only source of signature malleability, however. Both the public key used for signature verification and @@ -148,23 +167,51 @@ If you wish to also eliminate this source of signature malleability, please review the [documentation for the `verify_strict()` function](https://doc.dalek.rs/ed25519_dalek/struct.PublicKey.html#method.verify_strict). -# Installation +# A Note on Randomness Generation -To install, add the following to your project's `Cargo.toml`: +The original paper's specification and the standarisation of RFC8032 do not +specify precisely how randomness is to be generated, other than using a CSPRNG +(Cryptographically Secure Random Number Generator). Particularly in the case of +signature verification, where the security proof _relies_ on the uniqueness of +the blinding factors/nonces, it is paramount that these samples of randomness be +unguessable to an adversary. Because of this, a current growing belief among +cryptographers is that it is safer to prefer _synthetic randomness_. -```toml -[dependencies.ed25519-dalek] -version = "1" -``` +To explain synthetic randomness, we should first explain how `ed25519-dalek` +handles generation of _deterministic randomness_. This mode is disabled by +default due to a tiny-but-not-nonexistent chance that this mode will open users +up to fault attacks, wherein an adversary who controls all of the inputs to +batch verification (i.e. the public keys, signatures, and messages) can craft +them in a specialised manner such as to induce a fault (e.g. causing a +mistakenly flipped bit in RAM, overheating a processor, etc.). In the +deterministic mode, we seed the PRNG which generates our blinding factors/nonces +by creating +[a PRNG based on the Fiat-Shamir transform of the public inputs](https://merlin.cool/transcript/rng.html). +This mode is potentially useful to protocols which require strong auditability +guarantees, as well as those which do not have access to secure system-/chip- +provided randomness. This feature can be enabled via +`--features='batch_deterministic'`. Note that we _do not_ support deterministic +signing, due to the numerous pitfalls therein, including a re-used nonce +accidentally revealing the secret key. -Then, in your library or executable source, add: - -```rust -extern crate ed25519_dalek; -``` +In the default mode, we do as above in the fully deterministic mode, but we +ratchet the underlying keccak-f1600 function (used for the provided +transcript-based PRNG) forward additionally based on some system-/chip- provided +randomness. This provides _synthetic randomness_, that is, randomness based on +both deterministic and undeterinistic data. The reason for doing this is to +prevent badly seeded system RNGs from ruining the security of the signature +verification scheme. # Features +## #![no_std] + +This library aims to be `#![no_std]` compliant. If batch verification is +required (`--features='batch'`), please enable either of the `std` or `alloc` +features. + +## Nightly Compilers + To cause your application to build `ed25519-dalek` with the nightly feature enabled by default, instead do: @@ -183,19 +230,31 @@ to the `Cargo.toml`: nightly = ["ed25519-dalek/nightly"] ``` -To enable [serde](https://serde.rs) support, build `ed25519-dalek` with: +## Serde -```toml -[dependencies.ed25519-dalek] -version = "1" -features = ["serde"] -``` +To enable [serde](https://serde.rs) support, build `ed25519-dalek` with the +`serde` feature. + +## (Micro)Architecture Specific Backends 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"` +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 `simd_backend`s, currently +comprising either avx2 or avx512 backends. To use them, compile with +`RUSTFLAGS="-C target_cpu=native" cargo build --no-default-features +--features="simd_backend"` + +## Batch Signature Verification + +The standard variants of batch signature verification (i.e. many signatures made +with potentially many different public keys over potentially many different +message) is available via the `batch` feature. It uses synthetic randomness, as +noted above. + +### Deterministic Batch Signature Verification + +The same notion of batch signature verification as above, but with purely +deterministic randomness can be enabled via the `batch_deterministic` feature. From 29a06e494ddd4f370256596b7103125295073435 Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Fri, 6 Dec 2019 23:42:52 +0000 Subject: [PATCH 20/20] Bump ed25519-dalek version to 1.0.0-pre.3. --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 87fb087..c9d77a6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ed25519-dalek" -version = "1.0.0-pre.2" +version = "1.0.0-pre.3" edition = "2018" authors = ["isis lovecruft "] readme = "README.md"