From 304d591756c4ef90dd261c164bba0c4905087f3d Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Tue, 14 Mar 2017 21:36:35 +0000 Subject: [PATCH 01/38] Make key generation generic to hash function choice. --- Cargo.toml | 7 +++++++ src/ed25519.rs | 49 +++++++++++++++++++++++++++++++++++++++++-------- src/lib.rs | 3 +++ 3 files changed, 51 insertions(+), 8 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 06e004d..f4628cb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,13 @@ default-features = false optional = true version = "^0.3" +[dependencies.digest] +version = "0.4" + +[dependencies.generic-array] +# same version that digest depends on +version = "^0.6" + [dev-dependencies] rustc-serialize = "0.3" diff --git a/src/ed25519.rs b/src/ed25519.rs index f431563..1109572 100644 --- a/src/ed25519.rs +++ b/src/ed25519.rs @@ -12,11 +12,14 @@ use core::fmt::Debug; -use sha2::{Digest, Sha512}; +use sha2::Sha512; #[cfg(feature = "std")] use rand::Rng; +use digest::Digest; +use generic_array::typenum::U64; + use curve25519_dalek::curve; use curve25519_dalek::curve::BasepointMult; use curve25519_dalek::curve::CompressedEdwardsY; @@ -295,15 +298,45 @@ pub struct Keypair { impl Keypair { /// Generate an ed25519 keypair. /// + /// # Example + /// + /// ``` + /// extern crate rand; + /// extern crate ed25519; + /// extern crate sha2; + /// + /// # fn main() { + /// + /// use rand::Rng; + /// use rand::OsRng; + /// use sha2::Sha512; + /// use ed25519::Keypair; + /// use ed25519::Signature; + /// + /// let mut cspring: OsRng = OsRng::new().unwrap(); + /// let keypair: Keypair = Keypair::generate::(&mut cspring); + /// + /// # } + /// ``` + /// /// # Input /// /// A CSPRING with a `fill_bytes()` method, e.g. the one returned /// from `rand::OsRng::new()` (in the `rand` crate). + /// + /// 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. + /// // we reassign 0 bytes to the temp variable t to overwrite it #[cfg(feature = "std")] #[allow(unused_assignments)] - pub fn generate(cspring: &mut T) -> Keypair { - let mut h: Sha512 = Sha512::new(); + pub fn generate(cspring: &mut Rng) -> Keypair + where D: Digest + Default { + + let mut h: D = D::default(); let mut hash: [u8; 64] = [0u8; 64]; let mut t: [u8; 32] = [0u8; 32]; let mut sk: [u8; 64] = [0u8; 64]; @@ -390,7 +423,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() { @@ -414,7 +447,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); @@ -479,7 +512,7 @@ mod test { #[bench] fn bench_sign(b: &mut Bencher) { let mut cspring: OsRng = OsRng::new().unwrap(); - let keypair: Keypair = Keypair::generate(&mut cspring); + let keypair: Keypair = Keypair::generate::(&mut cspring); let msg: &[u8] = "test message".as_bytes(); b.iter(| | keypair.sign(msg)); @@ -488,7 +521,7 @@ mod test { #[bench] fn bench_verify(b: &mut Bencher) { let mut cspring: OsRng = OsRng::new().unwrap(); - let keypair: Keypair = Keypair::generate(&mut cspring); + let keypair: Keypair = Keypair::generate::(&mut cspring); let msg: &[u8] = "test message".as_bytes(); let sig: Signature = keypair.sign(msg); @@ -499,6 +532,6 @@ mod test { fn bench_key_generation(b: &mut Bencher) { let mut rng: ZeroRng = ZeroRng::new(); - b.iter(| | Keypair::generate(&mut rng)); + b.iter(| | Keypair::generate::(&mut rng)); } } diff --git a/src/lib.rs b/src/lib.rs index 59e545a..a5c655c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -72,6 +72,9 @@ extern crate curve25519_dalek; #[cfg(feature = "std")] extern crate rand; +extern crate generic_array; +extern crate digest; + #[cfg(test)] #[macro_use] extern crate std; From 85b0cd933cbe77589bb57e5d4cb07987aef944c7 Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Tue, 14 Mar 2017 21:37:46 +0000 Subject: [PATCH 02/38] Split off benchmarks into separate module and require --features=bench. This allows us to compile (and test) on the rustc stable and beta channels. Benchmarking is only available on nightly, with the --features=bench flag. --- Cargo.toml | 1 + src/ed25519.rs | 53 +++++++++++++++++++++++++++----------------------- src/lib.rs | 6 ++++-- 3 files changed, 34 insertions(+), 26 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f4628cb..3b823a6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,3 +37,4 @@ rustc-serialize = "0.3" [features] default = ["std"] std = ["rand"] +bench = [] diff --git a/src/ed25519.rs b/src/ed25519.rs index 1109572..acf5fd0 100644 --- a/src/ed25519.rs +++ b/src/ed25519.rs @@ -385,32 +385,11 @@ mod test { use std::fs::File; use std::string::String; use std::vec::Vec; - use test::Bencher; use curve25519_dalek::curve::ExtendedPoint; use rand::OsRng; - use rand::Rng; use rustc_serialize::hex::FromHex; use super::*; - /// A fake RNG which simply returns zeroes. - struct ZeroRng; - - impl ZeroRng { - fn new() -> ZeroRng { - ZeroRng - } - } - - impl Rng for ZeroRng { - fn next_u32(&mut self) -> u32 { 0u32 } - - fn fill_bytes(&mut self, bytes: &mut [u8]) { - for i in 0 .. bytes.len() { - bytes[i] = 0; - } - } - } - #[test] fn test_unmarshal_marshal() { // TestUnmarshalMarshal let mut cspring: OsRng; @@ -508,9 +487,35 @@ mod test { } } +} + +#[cfg(all(test, feature = "bench"))] +mod bench { + use test::Bencher; + use rand::OsRng; + use super::*; + + /// A fake RNG which simply returns zeroes. + pub struct ZeroRng; + + impl ZeroRng { + pub fn new() -> ZeroRng { + ZeroRng + } + } + + impl Rng for ZeroRng { + fn next_u32(&mut self) -> u32 { 0u32 } + + fn fill_bytes(&mut self, bytes: &mut [u8]) { + for i in 0 .. bytes.len() { + bytes[i] = 0; + } + } + } #[bench] - fn bench_sign(b: &mut Bencher) { + fn sign(b: &mut Bencher) { let mut cspring: OsRng = OsRng::new().unwrap(); let keypair: Keypair = Keypair::generate::(&mut cspring); let msg: &[u8] = "test message".as_bytes(); @@ -519,7 +524,7 @@ mod test { } #[bench] - fn bench_verify(b: &mut Bencher) { + fn verify(b: &mut Bencher) { let mut cspring: OsRng = OsRng::new().unwrap(); let keypair: Keypair = Keypair::generate::(&mut cspring); let msg: &[u8] = "test message".as_bytes(); @@ -529,7 +534,7 @@ mod test { } #[bench] - fn bench_key_generation(b: &mut Bencher) { + fn key_generation(b: &mut Bencher) { let mut rng: ZeroRng = ZeroRng::new(); b.iter(| | Keypair::generate::(&mut rng)); diff --git a/src/lib.rs b/src/lib.rs index a5c655c..f2f6345 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -62,7 +62,7 @@ #![no_std] #![feature(rand)] #![allow(unused_features)] -#![feature(test)] +#![cfg_attr(feature = "bench", feature(test))] #[macro_use] extern crate arrayref; @@ -78,8 +78,10 @@ extern crate digest; #[cfg(test)] #[macro_use] extern crate std; -#[cfg(test)] + +#[cfg(all(test, feature = "bench"))] extern crate test; + #[cfg(test)] extern crate rustc_serialize; From 807d52f655906d8092e77d067baa7c7ff7b9a64e Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Tue, 14 Mar 2017 21:39:28 +0000 Subject: [PATCH 03/38] Refuse to compile if documentation is missing. --- src/ed25519.rs | 1 + src/lib.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/src/ed25519.rs b/src/ed25519.rs index acf5fd0..81e9c36 100644 --- a/src/ed25519.rs +++ b/src/ed25519.rs @@ -28,6 +28,7 @@ use curve25519_dalek::curve::ProjectivePoint; use curve25519_dalek::scalar::Scalar; use curve25519_dalek::subtle::arrays_equal_ct; +/// The length of an ed25519 `Signature`, in bytes. pub const SIGNATURE_LENGTH: usize = 64; /// An ed25519 signature. diff --git a/src/lib.rs b/src/lib.rs index f2f6345..3744e91 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -63,6 +63,7 @@ #![feature(rand)] #![allow(unused_features)] #![cfg_attr(feature = "bench", feature(test))] +#![deny(missing_docs)] // refuse to compile if documentation is missing #[macro_use] extern crate arrayref; From 9dc9bbed4eacac0d0cdf8eb4568a464efff82a98 Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Tue, 14 Mar 2017 21:48:36 +0000 Subject: [PATCH 04/38] Add a nightly feature which depends on curve25519-dalek/nightly. --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index 3b823a6..2b27b9e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,3 +38,4 @@ rustc-serialize = "0.3" default = ["std"] std = ["rand"] bench = [] +nightly = ["curve25519-dalek/nightly"] From 5a30f4eb0f55cae7090f65d6b0a37c1eec057579 Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Tue, 14 Mar 2017 21:49:07 +0000 Subject: [PATCH 05/38] Make from_bytes() for keys public. --- src/ed25519.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/ed25519.rs b/src/ed25519.rs index 81e9c36..31dbac8 100644 --- a/src/ed25519.rs +++ b/src/ed25519.rs @@ -133,8 +133,7 @@ impl SecretKey { /// /// A `SecretKey`. #[inline] - #[allow(dead_code)] - fn from_bytes(bytes: &[u8]) -> SecretKey { + pub fn from_bytes(bytes: &[u8]) -> SecretKey { SecretKey(*array_ref!(bytes, 0, 64)) } @@ -229,8 +228,7 @@ impl PublicKey { /// /// A `PublicKey`. #[inline] - #[allow(dead_code)] - fn from_bytes(bytes: &[u8]) -> PublicKey { + pub fn from_bytes(bytes: &[u8]) -> PublicKey { PublicKey(CompressedEdwardsY(*array_ref!(bytes, 0, 32))) } From d00c3f9f3c1043c739a390bb99d264e5f2cbc250 Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Tue, 14 Mar 2017 21:50:17 +0000 Subject: [PATCH 06/38] Make all the doctests actually run. --- src/ed25519.rs | 6 ++--- src/lib.rs | 71 +++++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 61 insertions(+), 16 deletions(-) diff --git a/src/ed25519.rs b/src/ed25519.rs index 31dbac8..d131033 100644 --- a/src/ed25519.rs +++ b/src/ed25519.rs @@ -301,16 +301,16 @@ impl Keypair { /// /// ``` /// extern crate rand; - /// extern crate ed25519; /// extern crate sha2; + /// extern crate ed25519_dalek; /// /// # fn main() { /// /// use rand::Rng; /// use rand::OsRng; /// use sha2::Sha512; - /// use ed25519::Keypair; - /// use ed25519::Signature; + /// use ed25519_dalek::Keypair; + /// use ed25519_dalek::Signature; /// /// let mut cspring: OsRng = OsRng::new().unwrap(); /// let keypair: Keypair = Keypair::generate::(&mut cspring); diff --git a/src/lib.rs b/src/lib.rs index 3744e91..480d9d8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,49 +14,94 @@ //! //! Creating an ed25519 signature on a message is simple. //! -//! 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 random number generator (CSPRING). For -//! this example, we'll use the operating system's builtin PRNG to -//! generate a keypair: +//! 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 +//! has 512 bits of output. For this example, we'll use the operating +//! system's builtin PRNG and SHA-512 to generate a keypair: //! -//! ```ignore +//! ``` //! extern crate rand; -//! extern crate ed25519; +//! extern crate sha2; +//! extern crate ed25519_dalek; //! +//! # fn main() { //! use rand::Rng; //! use rand::OsRng; -//! use ed25519::Keypair; -//! use ed25519::Signature; +//! 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 keypair: Keypair = Keypair::generate::(&mut cspring); +//! # } //! ``` //! //! We can now use this `keypair` to sign a message: //! -//! ```ignore +//! ``` +//! # extern crate rand; +//! # extern crate sha2; +//! # extern crate ed25519_dalek; +//! # fn main() { +//! # use rand::Rng; +//! # use rand::OsRng; +//! # 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 message: &[u8] = "This is a test of the tsunami alert system.".as_bytes(); //! let signature: Signature = keypair.sign(message); +//! # } //! ``` //! //! As well as to verify that this is, indeed, a valid signature on //! that `message`: //! -//! ```ignore +//! ``` +//! # extern crate rand; +//! # extern crate sha2; +//! # extern crate ed25519_dalek; +//! # fn main() { +//! # use rand::Rng; +//! # use rand::OsRng; +//! # 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 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); +//! # } //! ``` //! //! Anyone else, given the `public` half of the `keypair` can also easily //! verify this signature: //! -//! ```ignore +//! ``` +//! # extern crate rand; +//! # extern crate sha2; +//! # extern crate ed25519_dalek; +//! # fn main() { +//! # use rand::Rng; +//! # use rand::OsRng; +//! # 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 message: &[u8] = "This is a test of the tsunami alert system.".as_bytes(); +//! # let signature: Signature = keypair.sign(message); //! let public_key: PublicKey = keypair.public; //! let verified: bool = public_key.verify(message, &signature); //! //! assert!(verified); +//! # } //! ``` #![no_std] From 6522761ca640baf21326c01b3a4de2ac5b071df3 Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Tue, 14 Mar 2017 21:50:35 +0000 Subject: [PATCH 07/38] Add a .travis.yml file. --- .travis.yml | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..881c0b3 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,30 @@ +language: rust + +rust: + - stable + - beta + - nightly + +env: + - TEST_COMMAND=test FEATURES='' + - TEST_COMMAND=test FEATURES='nightly' + - TEST_COMMAND=bench FEATURES='bench' + - TEST_COMMAND=bench FEATURES='nightly bench' + +matrix: + exclude: + - rust: stable + env: TEST_COMMAND=bench FEATURES='bench' + - rust: beta + env: TEST_COMMAND=bench FEATURES='bench' + - rust: stable + env: TEST_COMMAND=bench FEATURES='nightly bench' + - rust: beta + env: TEST_COMMAND=bench FEATURES='nightly bench' + - rust: stable + env: TEST_COMMAND=test FEATURES='nightly' + - rust: beta + env: TEST_COMMAND=test FEATURES='nightly' + +script: + - cargo $TEST_COMMAND --features="$FEATURES" From 4a4460a8ad283d68b716b070a201701504d9dead Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Tue, 14 Mar 2017 22:09:13 +0000 Subject: [PATCH 08/38] Test both "std" and "no-std" in CI. --- .travis.yml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 881c0b3..b40c613 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,8 @@ rust: - nightly env: - - TEST_COMMAND=test FEATURES='' + - TEST_COMMAND=test FEATURES='no-std' + - TEST_COMMAND=test FEATURES='std' - TEST_COMMAND=test FEATURES='nightly' - TEST_COMMAND=bench FEATURES='bench' - TEST_COMMAND=bench FEATURES='nightly bench' @@ -18,13 +19,13 @@ matrix: - rust: beta env: TEST_COMMAND=bench FEATURES='bench' - rust: stable - env: TEST_COMMAND=bench FEATURES='nightly bench' + env: TEST_COMMAND=test FEATURES='nightly' - rust: beta - env: TEST_COMMAND=bench FEATURES='nightly bench' + env: TEST_COMMAND=test FEATURES='nightly' - rust: stable - env: TEST_COMMAND=test FEATURES='nightly' + env: TEST_COMMAND=bench FEATURES='nightly bench' - rust: beta - env: TEST_COMMAND=test FEATURES='nightly' + env: TEST_COMMAND=bench FEATURES='nightly bench' script: - cargo $TEST_COMMAND --features="$FEATURES" From ad4e726e495dec59b5dcf83946a29da94cab29fe Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Tue, 14 Mar 2017 22:19:59 +0000 Subject: [PATCH 09/38] Change .travis.yml to use include directives. --- .travis.yml | 29 ++++++++++------------------- 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/.travis.yml b/.travis.yml index b40c613..acc4c6c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,26 +6,17 @@ rust: - nightly env: - - TEST_COMMAND=test FEATURES='no-std' - - TEST_COMMAND=test FEATURES='std' - - TEST_COMMAND=test FEATURES='nightly' - - TEST_COMMAND=bench FEATURES='bench' - - TEST_COMMAND=bench FEATURES='nightly bench' + - TEST_COMMAND=test FEATURES='' + - TEST_COMMAND=test FEATURES='--features="no-std"' matrix: - exclude: - - rust: stable - env: TEST_COMMAND=bench FEATURES='bench' - - rust: beta - env: TEST_COMMAND=bench FEATURES='bench' - - rust: stable - env: TEST_COMMAND=test FEATURES='nightly' - - rust: beta - env: TEST_COMMAND=test FEATURES='nightly' - - rust: stable - env: TEST_COMMAND=bench FEATURES='nightly bench' - - rust: beta - env: TEST_COMMAND=bench FEATURES='nightly bench' + include: + - rust: nightly + env: TEST_COMMAND=test FEATURES='--features="nightly"' + - rust: nightly + env: TEST_COMMAND=bench FEATURES='--features="bench"' + - rust: nightly + env: TEST_COMMAND=bench FEATURES='--features="nightly bench"' script: - - cargo $TEST_COMMAND --features="$FEATURES" + - cargo $TEST_COMMAND $FEATURES From 030c3e537337ccf434a395c5207028087c3539e2 Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Tue, 14 Mar 2017 23:00:26 +0000 Subject: [PATCH 10/38] Make std feature depend on curve25519-dalek/std. --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 2b27b9e..6753c3f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,6 +36,6 @@ rustc-serialize = "0.3" [features] default = ["std"] -std = ["rand"] +std = ["rand", "curve25519-dalek/std"] bench = [] nightly = ["curve25519-dalek/nightly"] From 9ea46e0a7c1a64831de18f870e80f3b235319026 Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Tue, 14 Mar 2017 23:04:56 +0000 Subject: [PATCH 11/38] Change CI test for --features=no-std to --no-default-features. --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index acc4c6c..d3b9866 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,10 +7,11 @@ rust: env: - TEST_COMMAND=test FEATURES='' - - TEST_COMMAND=test FEATURES='--features="no-std"' matrix: include: + - rust: nightly + env: TEST_COMMAND=build FEATURES='--no-default-features' - rust: nightly env: TEST_COMMAND=test FEATURES='--features="nightly"' - rust: nightly From 23a14cebb4305e429c77a87e63e6c8d1ed0b645f Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Tue, 14 Mar 2017 23:11:47 +0000 Subject: [PATCH 12/38] Add a Travis badge to Cargo.toml. --- Cargo.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 6753c3f..98dd770 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,9 @@ categories = ["cryptography", "no-std"] description = "Fast and efficient ed25519 signing and verification in pure Rust." exclude = [ ".gitignore", "TESTVECTORS" ] +[badges] +travis-ci = { repository = "isislovecruft/ed25519-dalek", branch = "master"} + [dependencies] arrayref = "0.3.3" sha2 = "^0.4" From 4bdbf89eebb95912905f891abd0f6ca8d80a4b0b Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Tue, 14 Mar 2017 23:13:07 +0000 Subject: [PATCH 13/38] Add Travis badge to README. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3397bbb..afafb66 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# ed25519-dalek ![](https://img.shields.io/crates/v/ed25519-dalek.svg) ![](https://docs.rs/ed25519-dalek/badge.svg) +# ed25519-dalek ![](https://img.shields.io/crates/v/ed25519-dalek.svg) ![](https://docs.rs/ed25519-dalek/badge.svg) ![](https://travis-ci.org/isislovecruft/ed25519-dalek.svg?branch=master) Fast and efficient Rust implementation of ed25519 key generation, signing, and verification in Rust. From 4eaa1321eee64c39bc0a8ff63779858c815c3de2 Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Tue, 14 Mar 2017 23:15:19 +0000 Subject: [PATCH 14/38] Feature gate rand on nightly. We can't use features on non-nightly channels. --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 480d9d8..73daa41 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -105,7 +105,7 @@ //! ``` #![no_std] -#![feature(rand)] +#![cfg_attr(feature = "nightly", feature(rand))] #![allow(unused_features)] #![cfg_attr(feature = "bench", feature(test))] #![deny(missing_docs)] // refuse to compile if documentation is missing From b5531c125712de615d91ceeedf81b4a2e5cfbb1c Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Tue, 14 Mar 2017 23:19:38 +0000 Subject: [PATCH 15/38] Remove test_ prefix from test functions. --- src/ed25519.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ed25519.rs b/src/ed25519.rs index d131033..4e3c468 100644 --- a/src/ed25519.rs +++ b/src/ed25519.rs @@ -390,7 +390,7 @@ mod test { use super::*; #[test] - fn test_unmarshal_marshal() { // TestUnmarshalMarshal + fn unmarshal_marshal() { // TestUnmarshalMarshal let mut cspring: OsRng; let mut keypair: Keypair; let mut x: Option; @@ -415,7 +415,7 @@ mod test { } #[test] - fn test_sign_verify() { // TestSignVerify + fn sign_verify() { // TestSignVerify let mut cspring: OsRng; let keypair: Keypair; let good_sig: Signature; @@ -443,7 +443,7 @@ mod test { #[cfg(test)] #[cfg(not(release))] #[test] - fn test_golden() { // TestGolden + fn golden() { // TestGolden let mut line: String; let mut lineno: usize = 0; From b1105618e717d61e03859c73e3b497e52e777a29 Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Tue, 14 Mar 2017 23:43:02 +0000 Subject: [PATCH 16/38] Make verification dependent on hash function. --- src/ed25519.rs | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/ed25519.rs b/src/ed25519.rs index 4e3c468..4f9665b 100644 --- a/src/ed25519.rs +++ b/src/ed25519.rs @@ -244,8 +244,10 @@ impl PublicKey { /// /// Returns true if the signature was successfully verified, and /// false otherwise. - pub fn verify(&self, message: &[u8], signature: &Signature) -> bool { - let mut h: Sha512 = Sha512::new(); + pub fn verify(&self, message: &[u8], signature: &Signature) -> bool + where D: Digest + Default { + + let mut h: D = D::default(); let mut a: ExtendedPoint; let ao: Option; let r: ProjectivePoint; @@ -372,8 +374,9 @@ impl Keypair { } /// Verify a signature on a message with this keypair's public key. - pub fn verify(&self, message: &[u8], signature: &Signature) -> bool { - self.public.verify(message, signature) + pub fn verify(&self, message: &[u8], signature: &Signature) -> bool + where D: Digest + Default { + self.public.verify::(message, signature) } } @@ -429,11 +432,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) == true, "Verification of a valid signature failed!"); - assert!(keypair.verify(&good, &bad_sig) == false, + assert!(keypair.verify::(&good, &bad_sig) == false, "Verification of a signature on a different message passed!"); - assert!(keypair.verify(&bad, &good_sig) == false, + assert!(keypair.verify::(&bad, &good_sig) == false, "Verification of a signature on a different message passed!"); } @@ -482,8 +485,8 @@ mod test { println!("{:?}", pub_bytes); assert!(sig1 == sig2, "Signature bytes not equal on line {}", lineno); - assert!(public_key.verify(&message, &sig2), "Signature verification failed on line {}", lineno); - + assert!(public_key.verify::(&message, &sig2), + "Signature verification failed on line {}", lineno); } } } @@ -529,7 +532,7 @@ mod bench { let msg: &[u8] = "test message".as_bytes(); let sig: Signature = keypair.sign(msg); - b.iter(| | keypair.verify(msg, &sig)); + b.iter(| | keypair.verify::(msg, &sig)); } #[bench] From 03f20fdae3eb151186ffa93dd664a1d3b7e646cf Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Tue, 14 Mar 2017 23:43:50 +0000 Subject: [PATCH 17/38] ZeroRng in benchmarks doesn't need to be public. --- src/ed25519.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ed25519.rs b/src/ed25519.rs index 4f9665b..4a505b5 100644 --- a/src/ed25519.rs +++ b/src/ed25519.rs @@ -498,7 +498,7 @@ mod bench { use super::*; /// A fake RNG which simply returns zeroes. - pub struct ZeroRng; + struct ZeroRng; impl ZeroRng { pub fn new() -> ZeroRng { From 1e2fa1025e017c53e9d44533be04d62e03147e5e Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Tue, 14 Mar 2017 23:44:21 +0000 Subject: [PATCH 18/38] Implement a ZeroDigest for use in benchmarks. --- src/ed25519.rs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/ed25519.rs b/src/ed25519.rs index 4a505b5..f41a7bc 100644 --- a/src/ed25519.rs +++ b/src/ed25519.rs @@ -493,6 +493,8 @@ mod test { #[cfg(all(test, feature = "bench"))] mod bench { + use generic_array::GenericArray; + use generic_array::typenum::{U64, U128}; use test::Bencher; use rand::OsRng; use super::*; @@ -516,6 +518,27 @@ mod bench { } } + /// A fake hash function which simply returns zeroes. + struct ZeroDigest; + + impl ZeroDigest { + pub fn new() -> ZeroDigest { + ZeroDigest + } + } + + impl Digest for ZeroDigest { + type OutputSize = U64; + type BlockSize = U128; + + fn input(&mut self, _input: &[u8]) { } + fn result(self) -> GenericArray { GenericArray::default() } + } + + impl Default for ZeroDigest { + fn default() -> Self { Self::new() } + } + #[bench] fn sign(b: &mut Bencher) { let mut cspring: OsRng = OsRng::new().unwrap(); From aa6e0a4324b8ef40949384b54dd02853c51bb901 Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Tue, 14 Mar 2017 23:44:49 +0000 Subject: [PATCH 19/38] Benchmark signing/verifying blank messages. --- src/ed25519.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ed25519.rs b/src/ed25519.rs index f41a7bc..d80664f 100644 --- a/src/ed25519.rs +++ b/src/ed25519.rs @@ -543,7 +543,7 @@ mod bench { fn sign(b: &mut Bencher) { let mut cspring: OsRng = OsRng::new().unwrap(); let keypair: Keypair = Keypair::generate::(&mut cspring); - let msg: &[u8] = "test message".as_bytes(); + let msg: &[u8] = "".as_bytes(); b.iter(| | keypair.sign(msg)); } @@ -552,7 +552,7 @@ mod bench { fn verify(b: &mut Bencher) { let mut cspring: OsRng = OsRng::new().unwrap(); let keypair: Keypair = Keypair::generate::(&mut cspring); - let msg: &[u8] = "test message".as_bytes(); + let msg: &[u8] = "".as_bytes(); let sig: Signature = keypair.sign(msg); b.iter(| | keypair.verify::(msg, &sig)); From e4c085706cda56219f5bdc2ff76d20ef648184d9 Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Tue, 14 Mar 2017 23:54:43 +0000 Subject: [PATCH 20/38] Make signing generic to hash function choice. --- src/ed25519.rs | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/ed25519.rs b/src/ed25519.rs index d80664f..38c54dc 100644 --- a/src/ed25519.rs +++ b/src/ed25519.rs @@ -138,8 +138,10 @@ impl SecretKey { } /// Sign a message with this keypair's secret key. - pub fn sign(&self, message: &[u8]) -> Signature { - let mut h: Sha512 = Sha512::new(); + pub fn sign(&self, message: &[u8]) -> Signature + where D: Digest + Default { + + let mut h: D = D::default(); let mut hash: [u8; 64] = [0u8; 64]; let mut signature_bytes: [u8; 64] = [0u8; SIGNATURE_LENGTH]; let mut expanded_key_secret: Scalar; @@ -160,7 +162,7 @@ impl SecretKey { expanded_key_secret[31] &= 63; expanded_key_secret[31] |= 64; - h = Sha512::new(); + h = D::default(); h.input(&hash[32..]); h.input(&message); hash.copy_from_slice(h.result().as_slice()); @@ -169,7 +171,7 @@ impl SecretKey { r = ExtendedPoint::basepoint_mult(&mesg_digest); - h = Sha512::new(); + h = D::default(); h.input(&r.compress_edwards().to_bytes()[..]); h.input(public_key); h.input(&message); @@ -369,8 +371,9 @@ impl Keypair { } /// Sign a message with this keypair's secret key. - pub fn sign(&self, message: &[u8]) -> Signature { - self.secret.sign(message) + pub fn sign(&self, message: &[u8]) -> Signature + where D: Digest + Default { + self.secret.sign::(message) } /// Verify a signature on a message with this keypair's public key. @@ -429,8 +432,8 @@ mod test { cspring = OsRng::new().unwrap(); keypair = Keypair::generate::(&mut cspring); - good_sig = keypair.sign(&good); - bad_sig = keypair.sign(&bad); + good_sig = keypair.sign::(&good); + bad_sig = keypair.sign::(&bad); assert!(keypair.verify::(&good, &good_sig) == true, "Verification of a valid signature failed!"); @@ -479,7 +482,7 @@ mod test { let secret_key: SecretKey = SecretKey::from_bytes(&sec_bytes); let public_key: PublicKey = PublicKey::from_bytes(&pub_bytes); - let sig2: Signature = secret_key.sign(&message); + let sig2: Signature = secret_key.sign::(&message); println!("{:?}", sec_bytes); println!("{:?}", pub_bytes); @@ -545,7 +548,7 @@ mod bench { let keypair: Keypair = Keypair::generate::(&mut cspring); let msg: &[u8] = "".as_bytes(); - b.iter(| | keypair.sign(msg)); + b.iter(| | keypair.sign::(msg)); } #[bench] @@ -553,7 +556,7 @@ mod bench { let mut cspring: OsRng = OsRng::new().unwrap(); let keypair: Keypair = Keypair::generate::(&mut cspring); let msg: &[u8] = "".as_bytes(); - let sig: Signature = keypair.sign(msg); + let sig: Signature = keypair.sign::(msg); b.iter(| | keypair.verify::(msg, &sig)); } From f1dd165208b89f60cd01b93b73042883d1bb5b13 Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Wed, 15 Mar 2017 18:42:58 +0000 Subject: [PATCH 21/38] Use array_ref! for getting digest during signing. --- src/ed25519.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/ed25519.rs b/src/ed25519.rs index 38c54dc..215eab8 100644 --- a/src/ed25519.rs +++ b/src/ed25519.rs @@ -253,7 +253,7 @@ impl PublicKey { let mut a: ExtendedPoint; let ao: Option; let r: ProjectivePoint; - let mut digest: [u8; 64]; + let digest: [u8; 64]; let digest_reduced: Scalar; if signature.0[63] & 224 != 0 { @@ -268,16 +268,15 @@ impl PublicKey { } a = -(&a); - digest = [0u8; 64]; - let top_half: &[u8; 32] = array_ref!(&signature.0, 32, 32); let bottom_half: &[u8; 32] = array_ref!(&signature.0, 0, 32); h.input(&bottom_half[..]); h.input(&self.to_bytes()); h.input(&message); - digest.copy_from_slice(h.result().as_slice()); + let digest_bytes = h.result(); + digest = *array_ref!(digest_bytes, 0, 64); digest_reduced = Scalar::reduce(&digest); r = curve::double_scalar_mult_vartime(&digest_reduced, &a, &Scalar(*top_half)); From 5e84eedfbb29fd0d49ca119eb3716118cf6bae57 Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Wed, 15 Mar 2017 18:44:59 +0000 Subject: [PATCH 22/38] Add benchmarks with blake2b. --- src/ed25519.rs | 28 ++++++++++++++++++++++++++++ src/lib.rs | 4 ++++ 2 files changed, 32 insertions(+) diff --git a/src/ed25519.rs b/src/ed25519.rs index 215eab8..74863c6 100644 --- a/src/ed25519.rs +++ b/src/ed25519.rs @@ -495,6 +495,8 @@ mod test { #[cfg(all(test, feature = "bench"))] mod bench { + use blake2::Blake2b; + use digest::Digest; use generic_array::GenericArray; use generic_array::typenum::{U64, U128}; use test::Bencher; @@ -566,4 +568,30 @@ mod bench { b.iter(| | Keypair::generate::(&mut rng)); } + + #[bench] + fn blake2b_sign(b: &mut Bencher) { + let mut cspring: OsRng = OsRng::new().unwrap(); + let keypair: Keypair = Keypair::generate::(&mut cspring); + let msg: &[u8] = "".as_bytes(); + + b.iter(| | keypair.sign::(msg)); + } + + #[bench] + fn blake2b_verify(b: &mut Bencher) { + let mut cspring: OsRng = OsRng::new().unwrap(); + let keypair: Keypair = Keypair::generate::(&mut cspring); + let msg: &[u8] = "".as_bytes(); + let sig: Signature = keypair.sign::(msg); + + b.iter(| | keypair.verify::(msg, &sig)); + } + + #[bench] + fn blake2b_key_generation(b: &mut Bencher) { + let mut rng: ZeroRng = ZeroRng::new(); + + b.iter(| | Keypair::generate::(&mut rng)); + } } diff --git a/src/lib.rs b/src/lib.rs index 73daa41..41b11c0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -131,6 +131,10 @@ extern crate test; #[cfg(test)] extern crate rustc_serialize; +#[cfg(all(test, feature = "bench"))] +extern crate blake2; + + mod ed25519; // Export everything public in ed25519. From 394d1face2f525b92bcb7af941e8f84997a0a695 Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Wed, 15 Mar 2017 18:53:48 +0000 Subject: [PATCH 23/38] Remove the ZeroDigest from the benchmark suite. It turns out that testing scalar_mult_vartime() on all zeroes is fast. --- Cargo.toml | 1 + src/ed25519.rs | 23 ----------------------- 2 files changed, 1 insertion(+), 23 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 98dd770..e5af2f7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,6 +36,7 @@ version = "^0.6" [dev-dependencies] rustc-serialize = "0.3" +blake2 = "^0.4" [features] default = ["std"] diff --git a/src/ed25519.rs b/src/ed25519.rs index 74863c6..87d38d7 100644 --- a/src/ed25519.rs +++ b/src/ed25519.rs @@ -497,8 +497,6 @@ mod test { mod bench { use blake2::Blake2b; use digest::Digest; - use generic_array::GenericArray; - use generic_array::typenum::{U64, U128}; use test::Bencher; use rand::OsRng; use super::*; @@ -522,27 +520,6 @@ mod bench { } } - /// A fake hash function which simply returns zeroes. - struct ZeroDigest; - - impl ZeroDigest { - pub fn new() -> ZeroDigest { - ZeroDigest - } - } - - impl Digest for ZeroDigest { - type OutputSize = U64; - type BlockSize = U128; - - fn input(&mut self, _input: &[u8]) { } - fn result(self) -> GenericArray { GenericArray::default() } - } - - impl Default for ZeroDigest { - fn default() -> Self { Self::new() } - } - #[bench] fn sign(b: &mut Bencher) { let mut cspring: OsRng = OsRng::new().unwrap(); From e869d14387dd334015c33ad53b8b35b0f14cf2bb Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Wed, 15 Mar 2017 18:56:24 +0000 Subject: [PATCH 24/38] Fix doctests to specify hash function choice. --- src/lib.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 41b11c0..ab6cb6a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -52,7 +52,7 @@ //! # let mut cspring: OsRng = OsRng::new().unwrap(); //! # 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 signature: Signature = keypair.sign::(message); //! # } //! ``` //! @@ -72,8 +72,8 @@ //! # let mut cspring: OsRng = OsRng::new().unwrap(); //! # 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); +//! # let signature: Signature = keypair.sign::(message); +//! let verified: bool = keypair.verify::(message, &signature); //! //! assert!(verified); //! # } @@ -96,9 +96,9 @@ //! # let mut cspring: OsRng = OsRng::new().unwrap(); //! # 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 signature: Signature = keypair.sign::(message); //! let public_key: PublicKey = keypair.public; -//! let verified: bool = public_key.verify(message, &signature); +//! let verified: bool = public_key.verify::(message, &signature); //! //! assert!(verified); //! # } From 89d246ec153e1c7c6591e0835db4526d82702dc2 Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Wed, 15 Mar 2017 18:57:03 +0000 Subject: [PATCH 25/38] Rearrange extern crates. --- src/lib.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index ab6cb6a..45d96af 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -114,23 +114,22 @@ extern crate arrayref; extern crate sha2; extern crate curve25519_dalek; +extern crate generic_array; +extern crate digest; #[cfg(feature = "std")] extern crate rand; -extern crate generic_array; -extern crate digest; - #[cfg(test)] #[macro_use] extern crate std; -#[cfg(all(test, feature = "bench"))] -extern crate test; - #[cfg(test)] extern crate rustc_serialize; +#[cfg(all(test, feature = "bench"))] +extern crate test; + #[cfg(all(test, feature = "bench"))] extern crate blake2; From 645d4e0d8c67fcd84c4f4186e768e717f4250048 Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Wed, 15 Mar 2017 19:01:03 +0000 Subject: [PATCH 26/38] The sha2 crate is no longer a required dependency. --- Cargo.toml | 2 +- src/ed25519.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e5af2f7..c46bffc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,6 @@ travis-ci = { repository = "isislovecruft/ed25519-dalek", branch = "master"} [dependencies] arrayref = "0.3.3" -sha2 = "^0.4" [dependencies.curve25519-dalek] version = "^0.6" @@ -37,6 +36,7 @@ version = "^0.6" [dev-dependencies] rustc-serialize = "0.3" blake2 = "^0.4" +sha2 = "^0.4" [features] default = ["std"] diff --git a/src/ed25519.rs b/src/ed25519.rs index 87d38d7..7c483ba 100644 --- a/src/ed25519.rs +++ b/src/ed25519.rs @@ -12,8 +12,6 @@ use core::fmt::Debug; -use sha2::Sha512; - #[cfg(feature = "std")] use rand::Rng; @@ -392,6 +390,7 @@ mod test { use curve25519_dalek::curve::ExtendedPoint; use rand::OsRng; use rustc_serialize::hex::FromHex; + use sha2::Sha512; use super::*; #[test] @@ -499,6 +498,7 @@ mod bench { use digest::Digest; use test::Bencher; use rand::OsRng; + use sha2::Sha512; use super::*; /// A fake RNG which simply returns zeroes. From b188516b22bb8da8dd38bbc3afdd65afaa8e89af Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Wed, 15 Mar 2017 19:07:21 +0000 Subject: [PATCH 27/38] Remove unused import digest::Digest from bench module. --- src/ed25519.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ed25519.rs b/src/ed25519.rs index 7c483ba..f486320 100644 --- a/src/ed25519.rs +++ b/src/ed25519.rs @@ -495,7 +495,6 @@ mod test { #[cfg(all(test, feature = "bench"))] mod bench { use blake2::Blake2b; - use digest::Digest; use test::Bencher; use rand::OsRng; use sha2::Sha512; From 054e9ce6b85494de7c80c85b88ce9a0c848c4a6a Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Wed, 15 Mar 2017 19:09:15 +0000 Subject: [PATCH 28/38] Remove blake2b benchmarks. --- Cargo.toml | 1 - src/ed25519.rs | 27 --------------------------- src/lib.rs | 3 --- 3 files changed, 31 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c46bffc..f61d3c3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,7 +35,6 @@ version = "^0.6" [dev-dependencies] rustc-serialize = "0.3" -blake2 = "^0.4" sha2 = "^0.4" [features] diff --git a/src/ed25519.rs b/src/ed25519.rs index f486320..fda76cd 100644 --- a/src/ed25519.rs +++ b/src/ed25519.rs @@ -494,7 +494,6 @@ mod test { #[cfg(all(test, feature = "bench"))] mod bench { - use blake2::Blake2b; use test::Bencher; use rand::OsRng; use sha2::Sha512; @@ -544,30 +543,4 @@ mod bench { b.iter(| | Keypair::generate::(&mut rng)); } - - #[bench] - fn blake2b_sign(b: &mut Bencher) { - let mut cspring: OsRng = OsRng::new().unwrap(); - let keypair: Keypair = Keypair::generate::(&mut cspring); - let msg: &[u8] = "".as_bytes(); - - b.iter(| | keypair.sign::(msg)); - } - - #[bench] - fn blake2b_verify(b: &mut Bencher) { - let mut cspring: OsRng = OsRng::new().unwrap(); - let keypair: Keypair = Keypair::generate::(&mut cspring); - let msg: &[u8] = "".as_bytes(); - let sig: Signature = keypair.sign::(msg); - - b.iter(| | keypair.verify::(msg, &sig)); - } - - #[bench] - fn blake2b_key_generation(b: &mut Bencher) { - let mut rng: ZeroRng = ZeroRng::new(); - - b.iter(| | Keypair::generate::(&mut rng)); - } } diff --git a/src/lib.rs b/src/lib.rs index 45d96af..c5ca4a8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -130,9 +130,6 @@ extern crate rustc_serialize; #[cfg(all(test, feature = "bench"))] extern crate test; -#[cfg(all(test, feature = "bench"))] -extern crate blake2; - mod ed25519; From 71c2bc7687bfec00da01723171395f35533bba54 Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Wed, 15 Mar 2017 21:05:07 +0000 Subject: [PATCH 29/38] Revise README with new benchmarks, warning, and install instructions. --- README.md | 127 +++++++++++++++++++++++++++++++-------- ed25519-malleability.png | Bin 0 -> 44136 bytes 2 files changed, 103 insertions(+), 24 deletions(-) create mode 100644 ed25519-malleability.png diff --git a/README.md b/README.md index afafb66..d9061b9 100644 --- a/README.md +++ b/README.md @@ -3,26 +3,36 @@ Fast and efficient Rust implementation of ed25519 key generation, signing, and verification in Rust. +# Documentation + +Documentation is available [here](https://docs.rs/ed25519-dalek). + # Benchmarks +You need to pass the `--features="bench"` flag to run the benchmarks. The +reason for feature-gating the benchmarks is that Rust's `test::Bencher` is +unstable, and thus only works on the nightly channel. (We'd like people to be +able to compile and test on the stable and beta channels too!) + On an Intel 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:(release/0.1.0 *$)~/code/rust/ed25519 ∴ cargo bench - Finished release [optimized] target(s) in 0.0 secs - Running target/release/deps/ed25519-0135748522c518d8 + ∃!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 - running 5 tests - test ed25519::test::test_sign_verify ... ignored - test ed25519::test::test_unmarshal_marshal ... ignored - test ed25519::test::bench_key_generation ... bench: 54,837 ns/iter (+/- 11,613) - test ed25519::test::bench_sign ... bench: 69,735 ns/iter (+/- 21,902) - test ed25519::test::bench_verify ... bench: 183,891 ns/iter (+/- 75,304) + running 6 tests + test ed25519::test::golden ... ignored + test ed25519::test::sign_verify ... ignored + test ed25519::test::unmarshal_marshal ... ignored + test ed25519::bench::key_generation ... bench: 54,571 ns/iter (+/- 7,861) + test ed25519::bench::sign ... bench: 70,009 ns/iter (+/- 22,812) + test ed25519::bench::verify ... bench: 185,619 ns/iter (+/- 24,117) - test result: ok. 0 passed; 0 failed; 2 ignored; 3 measured + test result: ok. 0 passed; 0 failed; 3 ignored; 3 measured -In comparision, the equivalent package in Golang performs as follows: +In comparison, the equivalent package in Golang performs as follows: ∃!isisⒶwintermute:(master *=)~/code/go/src/github.com/agl/ed25519 ∴ go test -bench . PASS @@ -34,36 +44,105 @@ In comparision, the equivalent package in Golang performs as follows: Making key generation, signing, and verification a rough average of one third faster, one fifth faster, and one eighth faster respectively. Of course, this is just my machine, and these results—nowhere near rigorous—should be taken -with a fistful of salt. +with a handful of salt. -## Warning +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: -[Our elliptic curve library](https://github.com/isislovecruft/curve25519-dalek) -(which this code uses) has **not** yet received sufficient peer review by -other qualified cryptographers to be considered in any way, shape, or form, -safe. + ∃!isisⒶwintermute:(develop *$)~/code/rust/ed25519 ∴ cargo bench --features="bench nightly" + Finished release [optimized] target(s) in 0.0 secs + Running target/release/deps/ed25519_dalek-9d7f8674ae11ac39 + + running 6 tests + test ed25519::test::golden ... ignored + test ed25519::test::sign_verify ... ignored + test ed25519::test::unmarshal_marshal ... ignored + test ed25519::bench::key_generation ... bench: 31,160 ns/iter (+/- 8,597) + test ed25519::bench::sign ... bench: 40,565 ns/iter (+/- 4,758) + test ed25519::bench::verify ... bench: 106,146 ns/iter (+/- 2,796) + + test result: ok. 0 passed; 0 failed; 3 ignored; 3 measured + +Translating to a rough cycle count: we multiply by a factor of 2.6 to convert +nanoseconds to cycles per second on a 2.6 GHz CPU, that's 275979 cycles for +verification and 105469 for signing, which is +[competitive with the optimised assembly version](https://ed25519.cr.yp.to/) +included in the SUPERCOP benchmarking suite (albeit their numbers are for the +older Nehalem microarchitecture). + +Additionally, thanks to Rust, this implementation has both type and memory +safety. Not to mention that it's readable for everyone, making ours arguable +more readily auditable. We're of the opinion that these features—combined +with speed—are ultimately more valuable than sole cycle count. + +# Warnings + +ed25519-dalek and +[our elliptic curve library](https://github.com/isislovecruft/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, +or form, safe. **USE AT YOUR OWN RISK** -# Documentation +## A Note on Signature Malleability -Documentation is available [here](https://docs.rs/ed25519-dalek). +The signatures produced by this library are malleable, as defined in +[the original paper](https://ed25519.cr.yp.to/ed25519-20110926.pdf): + +![](https://raw.githubusercontent.com/isislovecruft/ed25519-dalek/develop/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 +behaviour of every other implementation in existence. While there is, as of +this writing, a +[draft RFC for EdDSA signatures](https://tools.ietf.org/html/rfc8032) which +specifies that the stronger check should be done (and while we agree that the +stronger check should be done), it is our opinion that one doesn't get to +change the definition of "ed25519 verification" a decade after the fact, +declaring every implementation (including one's own) to be non-conformant. + +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 +eventually support VXEdDSA in curve25519-dalek. # Installation -To install, add the following to the dependencies section of your project's -`Cargo.toml`: +To install, add the following to your project's `Cargo.toml`: - ed25519-dalek = "^0.2" + [dependencies.ed25519-dalek] + version = "^0.3" Then, in your library or executable source, add: extern crate ed25519_dalek +To cause your application to build `ed25519-dalek` with the nightly feature +enabled by default, instead do: + + [dependencies.ed25519-dalek] + version = "^0.3" + 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"] + + # TODO - * Maybe add methods to make exporting keys for backup easier. - * Benchmark in comparison to the ed25519_ref10 code. + * Maybe add methods to make exporting keys for backup easier. Maybe using + serde? * 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 diff --git a/ed25519-malleability.png b/ed25519-malleability.png new file mode 100644 index 0000000000000000000000000000000000000000..fe5896e99e3f912a8c924ee02bc687ba483eb9a2 GIT binary patch literal 44136 zcmb@ucOce(+dq8DmW1r=l}+}F?3q17Br>uxLiXO-d+)s>LfJwxLx}9`vJx_$L*MJV z@9VjL_wV`ZIsdr6t~xt?&d>Y(K91KqK4EGqayPKZun-8u4F!2=4FuxaEc`ryaSi@= z%b;c+{)6l!si1{{f$?=wbpe5(Mkq*2Xt`xHg_644oA_yUe8)hSl-SuvdPK72)Th$SdtT>r&1U;77>`2qlFnRi60lU{ij!S z*n7XghCk@VkKbKXEnNx5>C^o`w%ITxaPR-?HS)FOumOScuNMM09{<|km%rGxyUaAy ze_!jj{^O64{{Q~tl8+C4qj$wUPj+MXV!M`ikKg@>2}4Ng&}jB+c|GuYLh*zU|E?GP z)s-BlOT}#jcpAN{3jdBplsw^e4KI}2SGawqJFPwYxstZ__jyV9Mv4WT zu2R#C419-ak`EWB>nuhI?%jKD`?W#6RIB!6zRkyHu`{Tuio>m}s;brJ(z~>>Mz10# zhng|I-?Wy9O>g)7_q>v#R!n$!_{fMFQN+abbd%SgqtSf1h`|qw4ljOw8(iKyJ3mLi zNy(-_GHRpPtu`<+){cKKDm1ijc(_=N3B%u^$&i>hS-V^(F)=Yy#8u_Ng9kD)K?AEV zTwILaIv^oNzI|);`tu^?+w`>iN*|t)kx`G#%e|$Zz2)8w=hL&3gY)xq!s^|v#VuYV zGcz+;S+pR@$K0vOoW_i?@}_k%GBTBVwf@SCbQt(}c(qRl$yUQ@tUb+}mOBE_A3uKV zEbP@4d_yUT4KF>UcX@SXMNmYfytFjT_90UMx##ijN zH8wWRzGRkv`t*B${2c=WgJd@S*LHKw-|(gMk55h}CMO*QGkT+`e%rlqBs(t^Is;R51`|#oE-m+YzH0s^EclY-9S(uoZ zSXrOc(to=+*{W`%gx|T+A78FpZHhscb_4&`Eloqiiky<{lx!5$WDcX|7dtab$sBOc z?Up}8!B41&si`S)QMa11vd!^QO*Aw#xoFCu)Hn6@{{H?fEG(Own+E}Ib1Qd zPkP}OynX%pb#AT%GoklA&!D0f*Pq{F>Et>)I}d;TdSYry@6vd9=&Y%!$(@Rg2#t!O z5U|S_W5DBUeLcIlxLE6Rc>$|5w&AScUS-&{ztcERDei5k@mZ3T8dmw|d|NNGthV;V zdyn5ZRASf&w_lqRT_HDzGlT+)bCfF=s=EgVcXk)M%+1YV&tU0r$ZaXEHO_n2)z?WmW2@1*e89`cH#a+LXlRH) z9IOqaBMe(!L=CS+Jhij2G26D=X71Qm$=iNJ=ZAZf zT|z=aWaNOIuZ_qEF|X4=6)}1Gv4xI+VwFrXe0*-l6?ug=HFFvG@*h8b?Cg|ee&eML z_c!9ztE2t>JT)ed-#;o#OG&v*e5Y*m_4VN*c8|~h{EoAG#NuDFm$-_w)OJGvcq9`{EAXrDX`G;OX=t&DRWOvPeUc+ zPF1FjB_}7>(sGD8c-=s&$#C!1j-t8*%UnSO`NT)9f|8QHj~}t_-E;jxK4f5tTN^t~ z!Nkjam!Cga{8=ES9s)7*^{a84_s-^KxDj&pFtJCn3=Y?(`a43nMp0eoTpKo`XY;LI zA2rkxtUecQtgmjTKf|CZ9_sq(QxeDi+KjpPy#OYeL&&AH!DiJ6%hY_tHCVh=aB3cXs> zg?7K!SR>=(&KGAVX=!A(wmYw0;W#-RJV@tHO-=2eQG_Gp=;&x*a66<6389=V<^^xu zCH??=R*#J+zTfcu%U}De0}KkZ2|JUO25ZCFm;0;N5eu*d;AeCMl#P+eNq#Z03;0`y zRF;q~Oe`!%2L}YgX0G`c93u?>PZ>f^s~;0JTwpQNVL$%b`ZDnGW9!NH+bPGZ1IZKR zy7OXvm{A-D#6PeP-~F_6`pZ_xDc^H$G?G^B5l=U+A<(Mn+b^=`6KU zQW~&tJ1>HwSyuLO?cht5QN*|Y7-kiE{cZTtczAf)+S-lw^Ht^Lbu~4|zrGkVXq%dw zCnhDy7Jr8FYin(N3PtSIt5*W{-!Pav&A6c}x7p8&!P-6hQU$g7oyYH?4&~apMg(kU=x0hSg98Ht zp>Plr5owf;mzA*~+JkT0hHu4{X9`!UuC9JtzzzlB{Nly8CMS!p4YpGs!);M<6pux& z$ujt36%`cJOm7DVqvZ;AQ`wmIqHj+UZqt$DtU*_X{lv(~czSx8*T%yz5q(ckzEX#v zRHi#^Z&>_NSy2&gmL{y&%a_9b)D#pHm6eq?{C}XGx@}YQIVWP$Uaz)()X`>PKI}{5 z+q?X(7fFiCtKa#sO+Qipi1KJ!dm8@UOW>!73C8xl?a|BDGWR#DBSa<`vahHxRexvC z-^BL&)i_C7kIg&0f*S8fGga>zZz7S~-6s-D7QlxVO`5S!yQ)1{{wZ9!Md8IvSABe4 z{PTwWvhRttnRmHRh*XG1Z{|JzSjE%gOXHBflXSqi2JfDdtL>T#Rw9&T`ucgSxU{L% z`nz>IE=ner;^2b^gAWGFKKV~7d1eWk^dU?z&VK)~&@s#7D@iTSj25|kyIyM|wX0eo z2b~xR0VO=B7}@~r0CF5TV`HE7kz7wPs-nyN0Zz-<`g&ei;M3EWC=$n>t;NM_0DG@L2OGx+y z1W38N3n>o|4+Cl`9dG>{49hEYgn2c<5VL{E% z@YC`h7kQwZCRx!Cut5Fhp?U#PteGrF@_=dWr%#{!0|IiN!*a{cv-4FD)(A z*3u#%AXu<(EHA(6CD2h$_E&dvF%uPy8tsfcJUpD8>nkcN9=#_sdMoVuV|KACWNl?- z_U&_I1ea+yhG_LMmig!Lab7Mi-`@|Not>eG!E1r;00|iln^M%R!ygsl3uQGw_zFB% z=-188&hEBR^E{hmgPnVP?B@1zZ78$JX-$JE!TsR-#Q1o7&~#K()cR7<(9n?2<)3+0 z-Ks!=cmOJ@;^)rWQ?<}QYJU+E6N|g;<`fm7A#$p##Vv+1;_s*`;xh^eG}hL}4y<-+ zy1KfuvX)m>RY8Ft&KCC>FHz^3>+iP$hzSLZ@Xx?~8-}U4fdYa=#W!_zuyqGMeKKtG zzIWEY^!@u0REZR09Y1JjnHd?dz6bmJ@7}%h^zu45H~@I{%9tfGAtSAv0&adKi}@5D1zIo$RxcWMP(SYBQp zz`ClcSLb56;o(@#Y6+6L?XF)KnD_TrmBg~e;r1eYlai7uD|Y~kn&_u45w(dpOp3bi zn^{;`xNUE2Y%DFkezEfqMM6tkTP}*s{Mj?sQmavH0xTILqfBYq4rCV>7w%M1lXir? z{ZF7-*JXnm8eX`$9Ybl)Eq(kPKP=Ef1go~PZw9Yhhc<e|*>`w5PzK9`f zR+Pq!k9!W-o?Iwyc&)1x;ZLogpa5WyM21l{>kPJGr9mUmELdyUdL`xM$XESxukp{( z&exflhluvRSh`W^+qZ8c+M!06+sdr#&}8JCL616KjU_ZTHU@kO@0-F(9*7DH>o^#f z#l3Ok27JS%r6stJh<4yWi-X#N%YM&H?B`qW*?$vFxrAK+Kf5?PZ=Z6)5@Dku6A_!k zZv+&dFB`Sw*aWu<)(y^lT;TKP&r7SS8s5B_d*|*1-8rB`(0N_W+uQrmqpX&e7L%c{ zTX)1fj>*d(DE1ppKP?Q!YV_=)Ch1J{v(@-fkJRD=b~8u~>e2To{+;1dS4o zo5MIV;+^Noe#_6+fL;xXz+5XYuk(DlXcRsGI@a3Si9l(U854(1U(@Q~b!b0BM#l+! ziA1`rgJCnbH_ldnGX{$C=VI0J@^W>V0_$+Op&PgP3knN!DVdp>J5FBGsU-Y>8l+qM z?=@5_h~^a#xWmXel*%K=)i#1}Bl_$54IH;JLP={p{Sb-TXYU!NB{X7x+{g52j+HL} zaPV`kK*$BB%Jy4;KQp=V$KF8m;sn}LrHDC!I7H?Jq_Ogj7lDggLqH*O_= zugzmS!)s(TcYJ&-6@oMI2N9>6EOq7775Qw0lpuC@M5tU|Fm@V!O z14DlSfwVKa(DA|bs@L6|T;a8~wF}JNno5I0u0IT6lLITI5Ov!Hq6839P7YlZC$50m zlT%rZ5-25MGPBA`LrO&`3vGh`g>MqGT~Pa^(W|t@g-2Ri^v4GZ&!566vYD!Z*TTZW z(voRmn^>iDqhh#Js{h{tUq$OxP*Z~&>TD~tNnD1c`bJWoopx)AgGk{8Zf9@r9dU80 zd$9Koz7MAFHTp+6@~36;J=2kw$H-J^bJ^07kU;t%$&Ww)r8Bs3dVCB}m+;XA9WCub zn%Y?o@dDqo$w*Qj*`~^xX&Z#EqibAAS=meIr$DYp697Jfu+dUcafJhQs|=@GTscoU zDWiLz)-l+wQTP1=grlNc@m$N_VH;QR9s4O^6jmy+jw|Li1qDlf=`%(tS`2%)h6=t z@|wtVlxs|-z&q)=!bc*KuTDt3WG=J^Mp|0a;jDY0EI8btlV(K6M6y3IBdv9gTE}Bi%Jg&)3PDsEg@&QD-)DwZ#prNmiiHNbX zC#`KqZx*GziBN+goSDgfJWnGPtenn&15xAg+YV@fh=@pvO7Z8m%ZoqEnnj|X$7(aA}o8Qr41JjreTPynk_wNEZCF4ptARkJVZ zYAdF6e~x|rytqlk$M=OWnzM`rK@&I@mrfuG)yGF@d}CNcg=Hi z^tqgS^?6Po-;lSntMc>n8}VS`mdI5b5e9%XdI;r}C-oB?n+_Rd$1j`>geS8s3R>7^ zSo{jh0R@wU*l6&r>FDTa=rF#C##DQkPJC3CZt9}u|MBBT*0sYh1x&*;+PAn?!2Za{ z$e@BQ7wTiY{Dbsu3LP=q=0jCw_h)wz`J$!5jQa!@S&QXYwD`sSoeNl{xfa*0jg9`9 zk8oN?LoBW^jkY#FKmX;~n)q`pI?T5pP^-L{IXQ{nuVJR=eBSW41(pLS=)g@hgXg8- zB`(>or$ZT~u-z*wZ%{pz09nipKmx#yoUh`$CudM?Z3p4@2K8|yF^n9ixT*?A zWdanq$F!bqVMI_gGIDI$+1R#scA_anj(Vwla(Qw(rH@x0s)jYLgj0v`{L!w-gIIvYcd$UR42Pa+3r#jU7v(BLFzkdB9z*>dd zIyyQE?9#B=8M^I{P&_(d6;1#fKzoA5keZUhX3)UK$@#Oo?V=gr1kiY38C#p1OazMR z>Z5@7JUu<38;Rb#7li*nSsDHdM1+=(4!)`P)?{UOcehLg(O{;Ct~G0GYpa9%5ioxc z{(fw1xC4v?;G8aC4_c+e##mv?iyaM3&43+AcC72ySy@<$9;7p}e1ak>U5A7K2?x3| zXoMNDu^pzhfcNO@Y3D{pYD!Av<>V~jiq|FmBm&UPEi9axW(U&+@&&{{9e$!e=@}d2 z78E>&uk5dil53+0tY7eXp2<@{Z`ZF!z#4d-9+tj)cLr+?e3zhwF9XO*YU(r~wKs3x zq^G6D#l;={oTnlr{B`>489-ZrzCJ#e(0<{zwa<010$#JW1`G+{zpT6*N(*Sxz*9Ch zH_Iw3--9X&NGCEf^41-dGoW|kXS)G{(r1-91rJ z==!dJ(t-77WRRl;EPnsa7~k%5ak7@gZUEp&QAz3Z=g+XuN6;hU3O)`E5pmCf5L5WE zIzK-?J-r2#dR~1iSO@_+u+O>cu*3-=m$4q7m|clY?X~sx7E^IG zxlL|2o&ZuG9^Uh%?l4Z{R#A_mZ2+}@e*X+%9Yj5Uq@lqLojg|_?5(U$pbtX5QB_qPo}MPnzW{YdAprTsoAp*re`w+Tk?SNf`J=Dv>VOCD z{`9#N;paaBasWsjsv4EJcXz{;j)!Fw)b)V&wHJe6Bhi-~!3-`EY{vM~>S|8s%%o`e zG60DqysPz!bXq-6K;0dg=lz)X{yl{Rt^((&-Aug|a4HP}SF&eeZ|X0g3Fq-8BqoxQ zkc94h#qb9e2>!W}mnaZiSh5nWvcyEfFlhj|K=-#Lzr)8{T3g4-7wqplvLp%vuI>1O zhlk=0a4AT#wx*_CuP4grFH!GAR`J^#571=U&hQj#YiiOzG(M)Fmuulp)zT-@#G11p z66HHMIs5ogk)53#fIwl2f4`ZIj?ODGKE?IB4`gL!pFJzrXieiNCDfmTW>C6-$zQr0 zFg2Noj}1^fNFWAm2>|>HH)P!OV_Uqptyfv|W*^tAZ44^(>nf?J48g(QTw7}dteBCJ z0h<_Dr_| z2CL7E&IfWfT$2g7xVWD8NJvS&PWB6-oHRGT1Yh9&MM0$R{jk2HAyvld{1lAq); zE|U8AnQKWbb0yUG)0t5;Nbgga+*M2U7{G^LjEj3HVdmk9j98#MyJ;-uY38}Ty_v8a zz=v{7gA^w$V`Qv3uh**=BwZ26@Q)owcTf=M`_C&!_ZL^KW5spSfVH(XP@yU40wN-= zpSlNDhk%zv4%JYl7r5EU?fQ$)XC#YtLPS4CVoAt-JY)vwc*->>5 z4_AYA(X}Y*eMV9w&#eM4V4+~5pktGa%+9`Z+cOms>tLE8_g-=8)>XRXBBmuaPS?Owy|`R8E!n?*xs&ya`@W( zI>MwUoDgUs>`gGiz@Ji5R0N?pO~|PTKYfeSp=lPVA`ed;RAHuam0BF_f`7h1=})I7 zLlBanPg)F9Mv6*4o~1zhi39iyYNfM_%P2wZ2Y0w~;Hc1T7B<8$PgDg21PXcYVAe7w zZiYT8Zv1fKZOtJ>H9tN%30Mk?K_!K&qqo-rq=J3b*aQg>RFad4gCqfX+k*rG_S|mr z&SOGQD9a@)Iuh-!fE*s3Y`ZKA=uNW}D#FT0MKFY3g*23MdB} zz@f*CqC|xh;YQq?pI(po<{!8%Trphxp~25xN#2QY@$!NwL!gZJ>`EZHl0{`l#>U9R zz29l1#RuxeE4oNme>Pb56z5CMh_;71Eqx0lQIPcs;^i=*mb4GYXq0LJ#7q5nRD)$Y zc&tq`xp2>h2!vf=nPS*J<(6;(S*2Q3vPahWnA7%8WdMf_fWyOtE%+!g&S16Hi-7J%X}$m0rBy7p-Y* zgmSx)D%+-VdMp@9l`(h8x-oZeW76At>>GYdcG~s6=f(6iG>K={2rDhdiC z0|TW(4J+O>sQsS)zK=g_Z*K!G`n+)n<*ADg7i3Td1_oFVAT^+BTUlAb(JT180e_B- zM#sZ*OCkoegU-?1f&zL`QP1vBJh0hd&jEEO$q&|}Yxj9P{&{R{|Eql)xI@4K_fO8= zJo^H5ci_$u0B9g_5)uIsd`j?vKq^y}zdHy54S~xBBW9+~A{x45NZ0gx4}rs0a&Rbb zd~*nX)=J+)*f7{bJLwB26E06JmO&}$ZA*)BlOt-GEMt5$xsX2|SKfJ0zuA>{527Mi zXFxK*g>wJ>BQq*$u-RoR#{|9%I8e^Q6h&rFpYES{va?qL!!j{YE@Hx$u18ai&eL)N2N>@h;RgnTYMH8tbI!opfx#j=NcxrVZRE>aT`pg|PF zDFY~iA_1WCp@G4)%@G`#5q}c9PD>RwH)WPh4AHnyT7HuP;qvC@hxG&vQ+XQi-S#BK z){cCe2%5|Wf-54_1j%tCdcdg^cV5Sh=mAL>B;>2G!7TJ!Sey<%LtuCys;sWA?(&L@ z&wu-tC7Vx!(fPDaNl6Ln6@YgteEiO}13Z-J2HUToY}BH*m(&3w=rL_-X+cLpfhYv{ zE49ENc-=fax`^;l`a!v@G4azQ1Vjg?9S{{Riu=WxP@*Di9e`^PC!nD`iI0l|o`czA zX=}^J$M@d-ph$~na&mHWV*?chr9A@*B{~i{0`bo0l2QVfC?YXCySl21l7PVZ=ePGI zB}|C>E-rlj)Ua%@oB-i5{C7`00S7_uLV-JbUs$MJq$YjEIPcSyNpt zEeuk_3Vjl6S-1#1;}ZDuA#e7`gB?GAbxpvixIl|$93^k37O&_3>B0uM z3^9!p$fN`{|J`y`o__3d^wcSfQPlEIPIdYDPt45dMZL~X-?X%dH1BJT3mS4H_n5+$ zK}AQGLIn(TMfQoyP2Xa+2-TXTYsJwODAI-(_;$R|0S#eFgTyo@-b)ub+!e^LtWQvwLJDPdHHaD`eQ;=aSPnO^#}t%EWwm$X<62Pv6h0)Fp+vqs z_PnUK`uub_E9zq4%a<=8_-Y&D)W%-&2?px}nX~wm;+ORZP%f;usdhX{MuO+>IaiSv z+sAE}f5f4|$jQkabBaD^&~Z~Q5Y8(pv26E60(BO{pZwlSf^V*V@;69o_x|#2(u~YZ z0lQfyz3xw+l+{Q;KDV2F3)n6Xxn$vu{v;EAM2S@43k4JjpjcO)iNF7X)uTt0h@AR* zf~;?#uV0-1ag+}ooep~XBri^B=K@xB-t!=;;0I@^>-9IR#wG&OMQp^<|);D0#> zcIXJ3#CRo~1~4$BR6*PrrJb_t{G6djpGwe$r-)4={kpD3DJ(pSJ&}CQkZtZUhd$@D zW6nyVm%l*5J{Sr6`?GU%s65IUf_qT!cZ2aii9h{B;_SBwbQuYOjV7gW?zWi_9ucAB z;_?Pu0FVRYIYJHB{<_1xV-hf-7g8$Xif6f6 z4xJVL>f{0K8WkJm2M@AE-Ql4nW|BE{ zZlHIF`_G@(0#73Ebftp)hRE|JX(*)Nhdz6@b$LF2x%2J)AJ}iO+aUR&;+{yj=C-0< z^JiK5NBajV%&Em2O8s+pd;Av}-EJy34I=fwQnPE9cTB2y%&5)M(;~zlo}#pu(kly= z56G^bm*>CpHXsFo@P!`LAk#ERMG!a8~!>nLVYT`UZ4;UduerT&BE5! zNJAqoCWgyyR`6^L?89AXfKVqJ8X6G3|CX8A`XdpDT1`?KE9hBB2ueyyxQ}jT@mB>0 zZZraMJg?C~*Px|V7t?PhF5X55l8C4qmzbD098~TJx}+;vA35WBker+7D@%KOXc@5V zhEVN68z0F|;YN*|9ocY3_)h6MnMcvCF2P>3uy8#;b%DZm=T7leU=9!#1fYP%W(wG6 zdEzhq+|FZ~I!EnTpPR#*k~9esc&CJluB@z_Fh(A%@LZ9WXxle-h3)JyTF;M@ zSCbFEZ{n`Ys|%^rM$|@Oo&RQ=&7sIlvY6F9)}GHskv)DF1aI?^C5+YBeXk(K&jVU2+8546A-2^0fa(sMcwt~^|%m4Kfd9-M_vyZQROrSiL zjkzDiw-+y7fPNr(slD6C8JtdFh0~_6Cr``YHtvhJ#kQ@HXF9-==y~Yd)Ql{2hXEjP zRcR>_!t>%xAH13eArU)^wYj;A(629!m#+ksb^_r{Z$R>Jd^ef${79vP$5#Kz{zcLD7Xf)e*$9+uS9sLAtVpMZL;&qj1wih&aX2}pDw zD#$@}N-I3Aud9PMQi#gpqIly=?luOMyaMVW>asZwdugF1K`rth`MCdTbAoXBSI|{9 zH)mimu(V`4HO(bA4S;Ugpbs(~5!|^ay*M50>7dhtP`k3Wc1Pg>!BqVeX9zQEl`pRx z@XzzZ&#K@wLNefWeLeQgn>R5q`aXR^Ild-q^A58x^hGxGU(jo#t$1eu>2nyq&B@KB zd3Yyy7cd_bVoR*aN;C)!f!w*@v6WUsWCx42XAJVhz#M|Z}y?L*GZ7PL#RZr5$jFpofrrHJ46|b zgwBEZ$Y`|v`Xq(YJxRifeWQai8#qq=FZ+|wPbPbT1Kl9yre|Zbf#3&dI9xrwyFhLx%q!RJ?BO=Y)$ruxFG1IdRkFo)R#y{aVi1>!Sy^J>Xv7( zs#XyA>tN650l$Cvu=CDqxJ5v*x|zk#w6vwAyZ_06oKlv554 z4xlf9xrP=E;E02BPmSLmF(eP6=F8x~dvm4x(DLE0-LEQs4%VRe0RsbG~H^bO4eG5k%}P zY;4^aB#~}^ezg1?%DQKur}x&R;~K;SH8np2g@b*M#{byP?g!|y2gBmMy}cnpK~YrV zlwSF_-?SALu7b2vA%fUVnE&>%n&*NY7Kh{nCEUsAjD6AG?Y9a zp?qv>``&p&14UvII3q}i@S9gx38|w(;MqEOq-O_9x)5E3F+k&_ECK~s3Ta&Q6!jq5uw z1K331iU(HZn=T%lKuB7mEsg=R6wnj;ALIQn_CWf&g5qLez6O&syZPOEJsTJ_1!$2K zDe37{f)0=?8@1$pyMvyk5f>=_@{s8w>hVf`X{kaDrUIIlPKJ^s^BmPB_4J(IjSd|9 z}>K@sTkxKMYmo=W|CbA*nMuHs)_{1!Mq1e2@g` z%%sG484T$ffK`BR90W5!2RO48+nJn${!h)zdkB^a{C;TTP$ac>2Va51#c9-hxK-=p z>gq#V^9L`m?j^RV1}z1OgqY7oMP455Ti?{2JFqkmxbor&^3Ca8pZuYU@O*r$Fo$lrD`*u-R!o>P0UEzS# z2s+*$eN6vW@GY)I z%CEs10!hXw6!*O)8OSpV^dwHzbWkAxD_tjI+uhy0FaCK-VBbwt1^(zZ~L&x zv9O}8^kq99NGrR#RQ}Wu$KNc{F+*+n>2ti>@%8I%MFSlje&{bCqO4vY74c{|=QQMK z%**Ti^vMPoIhGE{#Fz-RC88v^lIm(I2-Yv$QLlRRUf8TJ2GXkA{tl{;sQU#y)uk@d zE!Kdz5cDUIuye}Gzjp0No_Z)_JqI9#K&ad~>Zl=*VT@9P<8((QgXwsbbd{FhW)~fi zJWx1l3GE*&osJ=Z86rJ}iMIc{vX+5G>Of2E2hp9@Rc!9sXnUU<2Y!>oapR$yI@ zo)q)w%PB8$Iy#7{fyIX+!C~BrjR46ZSYw8;Ky{Y+DrycoA++~*Wtg`ZfdWHLSaYw; z0i+k*3cW=rf-o467Y2}-E*?x4)ejsX)d7_T>7zwo_8n7R#8o^U})cz+H?u9YuO&w%NrQ9c&y-4@gF1K|C?TV2`QDSY3pat0D2MP zYu!zt98>(lBrH5LFc2Y`yFJ&^2+=lgXEL8HK?)jy|H#?{*!iHZR*{=vd2hS*>Jp)W zot~WRxxFJ~)gd4v8cO3+V&1_TyTizUTeuwyh&nSSX5styA~$V8W)2Q-NLoS+7xk6` zeH@5Gphskpoeq8X4d2Q$d^yK`&dV4i`0{WarYYp<3$N{f4)(kF;Px(#5x`M|{c zvrOlcKYrqjs4xKopsNw0*!=uSzgi{drT?D2A1f>40>8~a{4`{YCnYDJt?WGHnk&E? zVwS0%f(CY3Ut0^`7!45^5+Wif2qKt-HFIs=QOy|`apNN+zP(o(+;!}aGT*DQ!2LIR z=N79{jE;(Wmn0H21Qdx^l=raca{f^&WKrL@wnWT75_DWbg5?az83>CYBzuba?999Yms-qW0g(l%{8l(2{hNJ9SD-quoA z2UBX~^XC@FRmJ~r#kb+t%Khg?^i9~sbb{nVCkJcbhyj^_EaZu&Se9&9N&<>Mbw@{s z!{{miEwjvo&>KpK`@?cYIr5IrfD+Ij_1}2Lm;jKlnxHfX!x($Wn$SFk#eS-Z=|taBAf$38KqsObKtfrX82RyPe=3k28TYCqgSU4Ap8z)sUz5Oly?2=Mp+G1o#&NQjCksj8C2rDta!1b+)Kwgj$N_RozC zOmSzJC`--GR%MK*ASb^#J5bdd2K5JmhXVrxqiMZSx9_|aX8Yd~*SF3&qpmX6V2uVB zg9-)^6l(LkcZM)MQ5iD$G&v~=+GLe+n;1QP;P4t9#%s7hC|&v5<}jkLz7APE8jSt3 z6DO8LkhcN;fsG|4ini$3w2p-hUNDq>V7gPdDxdzB;B$uw7bv?5^kbZDXV-Sxm-iqe z@*$EG%5C@Z-mZY(EmW5d2U1Eo2pftB3q$qp{0veyq$QX2*A=@9=uMwI0X(u;3)WYd zbmwKii^AhDYWZO~#&B9P0AIP^Bsz{t>EZgXlH^rL#*_CiSxNZwkf&{Od*l%tawV0NOYc*Ua2PvaiD?>n+DtuP6VjG|C2?co4F2 zpeRp&`}T?m?~F5h7#ip-_|~JZ*qxCCp&GzMY75Y|SAe?NMQms4VPoW{0zwDjVbO9O z;uwb?0s{g9^7HA0bw2d;AR*K`AOr)|BY3g`{*dVoB`EEntwWG*FW|DYDT!v_%+LdY+V||A?5Alme`_L2u$Q*b< zkg-zB&?^*HR%&5Rc7nVGe98bsR?nD{Jv9Dn~XfZfecRB$6zb3f z^Tg-kARBftfP~qa#{rFvt{}p`dBcZ*u44uPDz{BA`ykkh@x5*nG~btQZcIwV*vS%H z212YsafM{jY>`CV=iM>q|M+aeag)^2{H}x3>wh>2an~Vi5dpgsd1a5BS~J5TMgz zQt>Zt)JgvPKsAFxv9`KOA?B&ab(O<`^{gx_^B*l?qZg9goT8=Lm&vctetc7hiIkM| zih8T46ls(?KBGr>R8d4Ac9BRFVMYW&&B<8}@sH8n*D#_)*6H!XYGa-AdTX3hY*Df`i zyv3G@ql1`~pS)T>7G69PFIkjIv6Em%TpWJNHM-o~+{ZxTxwV66NsqpIzu&;ey8-e8SObM0aUbd@khF!ig`4?6F5S&9 zses(^g4zIcZXgRo>zR5V|AD zaj{9L$}mblkAmq{Aa=mq*b+F5M_^21dTOemsOa_d_SJ|VI{Kf}Q+{6F1$QY8i8ZX} z(06~fT|Tnr#|nB4sxf%;keUQ}C+Da)zg4S{VB?Wj&r zT&Fa94TWP=8Wp`9(GiZ$R_!hFQbm(3=oGBQ07CL7hv z9mWMh*9oTBw&XMZbg%8HyFNZXmOF0;QU09;P(vV2v#cE)YGCh$g<)oJ=Kmw33emTL zHVCT<*qIC_9d6&=+S!S!Gl3Yug9lM+wEoo5l=mLKu})Ox#?Xh!72OeTsI*UEhMdDl z12k7%-Q?1z8BA5z8wLu98ev9-n;Q~c_{v(lUlw&q;$vWqNO_)OoFDp0H7UpAZ`YvQ z!%xL6v^0Z3TVC7&ih78hL2v+p_|If8<_AJFEMa2A{aiJ*FDu`_FR{IW36kTOgnqNm z+X$>9{ZI{-SOq$Ww1C!;TT3f0ZVWcaF48UUm6|(QwK6)pdKwiad12uc11PSAyv$m@5Ox)RO_bs+A~C$H6VAcl|vZWc5KE zfboWJpz?t01X&QlpDFje&ji`ocfjZL4}(Jwuw0O;D*sCQc!LcQdhhe*9P|GcTw$Ul zwX7!^PI?PAqp6_3WG>uU*ir2sgbQr~_TcCbUEJ@cFY5t|keI`eDFMgTD#2=`8%Jq` zE*3MPI2H_En0Pb6%F0RXhR6v@CaB4N9JY9l9LMvBm`%g&DFh;t@*dem;PKfB&xNfn z8#_DH%JN{dps&7sVFgaj?*bU<}YjEj2>xjVqMm{t9~U9R(9 zy88MjK=$S#Qv&aTYasG^ewT;GX|h6}TbuQk;wWmDxf{73bssb{h(EZ43y_kM0#e1X zbo;~v9|s3cz7dqDD<_BVSrWv`nww8S=p21>2s905b^sKEUd7GFXH;*g4vk66ehE{l zAe~OLiA+XX8e{_u2E|{cdJvaxg*-o;2S~&)+__^9v7^=zRx&`azKez#bMz3^?Tev- z%?OC&nT<`i{V;4;VId*R)|Keo)&e_*ve+dGW1T2)%t6uxH4LPChtoqV=#7DaD96>X z5}zP#r>bfX$^j%KTAbEijn7CM^fL2*fKIECuU?Vc&G_*yfTAa*nVEB-{zHP~&9h`^ z-_R0@i!Gq$_+u?eoKUSS|J5wPY{FH9b1n|JGrBi_)DkA3++MW#QlaOKT5|EgaLLwk zK|@eB&~7NgZ{wK*`^~<=+}{tG0HX$&UXj2BWh*}33GOHy768M2?3mHLtel+5W~nfj z4BGsFQIl3eVT4w)LeTIUR1t`{Q)7&QLGkF(H_!krwytdpx5cGs2?MTl_Az6Lg*+jQ zeffZo!({X-=NzaCfOGgv$OctZbhkYYc%3ouSKI~Ue?bcg6gSb)KCO4dEQR<&y#7ym z;Fg-r5G(3e=5sSm%?HI;E7azNKiKx`C=*$4sz&L$em|NoBp%kE%KKP?*9)Q&P#^Dc zMpHwr11&D77huF(z2@K-Y`pmWQ6D-dxY2Op*JY4STb(d5UM5qX9^LS#hVTSdtHCG* zgq{EZwYeY2a@9g=6o3GH;(G?d6@c`x6Tcsv!0ZU*R<*PfFSVJksfVT{q+i@l1zoG@ z#ZDS|dMi^z>rcaL{@;QEoiEE~;Atg+!NINYaZ*xGz*vXZT(g?7jwSC*Dvt%6mIaTm z;NsZudqBF~VIcewJ%r<-s{#)s$FTs&<8@MAd5%NNNSJFZ_ubg%+y!I?7|HmRgz%be zLP2@2q^hiVii+WXWt7Rt6smej6L=dVjHDmzYL5y$!5^!pDyd<)7LjJ~J{{~V`r2bPz{dd?jM|I?VJbv!`q#ih?*062B^Mn18XJR4h9KL(j*L;a!U;XlS73aFbw1eu8Md z6!a)jj#5sfOSFY~llCGc#;?k<7sC7L@8{da|GYn98}d0CaS>Y=Js&@;6lly+7I9x0 zrn_uDtELeNDM&2Lb-6MtCFeWgX)1cJ&B9(2YGZID8>p*4e)_ZlY(WKgaH#&tpn7Q3 z{0JUcZT0wF)mW$|W=cVJCCbLs+gE6uMa%_`ZMgjI&$X`}h@?_iZ!+Wn(SVy9(tN6% zm_gS^kCQifA zNT1i+mhq1;R^bU@@zx_I=}o(o^Jgu}qGsmizZx*+LSx!s%sZxDc z3j;oH1X?LBb{x7E1`UpBQNX0E`B=rXGq{Ry`ET_gI5RA$xyO3-vDq<8up;IIJ zo0NUqSa`g7KcQaE)W_LaJg$ie!`tJa1<9~!!kIT z!2AFlTR?9<$BXEZHvdF2&A3y$7U9`LE1(KOxEg@~BL^UXu8z)CTHwKh%DTE+Rcmo( z;r|(DwV!^Ciohix(9+Nto0%blaSBsYS_K47h!f0w!8Zl!076FxVapU$y8kJbuJe*g z^}+ni=*A&Du?e_0qmU3>11~>6;EgNp1j9y6Netr@#feeXr7%~Fe6jZ+R zvom>mjfW3sfG%%1zf@Dh4d?(LT^e@`OAF)-#%aJ;>^B?A z78i#c`s>$YFq{rV(v6LXf`VPA1G;|gk9e}pxFsZENk;Ur%?Jv#IJ4k43Rvtm^wa1T z_Z6)P479Y){~dSry)#tH&WV-9!KvU?n&sa|e0dx|by$9!?BeXt6uOyz+BrmbF{fg?D*Q`?g25kI_^Pby8^ED3{Afwg4>5We(Y>_kP}hbIfbb4~2+ z0h^T<6+MLrN%pWs-89%px9^%ttE=NRFuyglv$d^+#PR+6?LNHLRzg=A*aDoExX;tv6}!($LAh9$ZF=VKlg?gqzzUt3=fqQ1lV=`V;Vj06MBN(Z%zx98uw zpnuiMo*R&LFnfD?dPFHX|EDtB1Wz|Xk$^`ycza(>cc`h2Kq-Wn6+9VkVuBneq{nn) zYl}+QIrJ*k3-kLNHt$!6C}|ZWv1{I3#mF|-8M{lMun^bH zm%t(JkFbR)H7Pl{t1&`}7$CWtrQ@(Iq3yt<1(s^z;WKgcs0>Z2)+I{e=tAW5Wt(4*b&lJ)$c_-|3vJlqL;AVS7ExmS$}QhO!ExbDkTKD%`1EElUJnYau_ zJ}q=p2|=IY@i~&WwzuE8{(RIS4P#_Lg7@4+-Gm=^1_Cx-s!$s%Fne++8*j@fDyDj9 zd@wZJlHo!+Qy%|Sh}IJ2SU~XiF?pHaf59!Hrh~CX03#kd6xA$wYs1lY~F!+l;M?R}W6$grwi|;7?oH+Je&k5kzE+ z4bW!S*F&@ZDw|*nE^howEPM^E0;1kh|4Nx&NvY?$F!=^$nWQazmK5!l>m1ly-mAAo6hP(Dd}%X|&lscqmQKLxEYR z56WtSOv79n1`kr|cp{b!3J{$ol5|>2Aq!K#mxh!PFXhUMn4DI(1pGu&Rm(7$GA}Or zj?X3Z2jCib9}EiY_cRq`F<~lnM9X>Q9uw~;copbg!to5$Vp}zvRZT(;F+wM(GnPg;mK>706w@B9yH)N9NERj(J96JYw0>;mcY!GUVv z|Jn6U0_um2%9#ivy%i6tM-^)J)`v^p3(=nsZsLkDS`p9{occ=FMdqOynd|OD#nRdO zRD=5ZXFWYTYt*tT){&WyX^gXoj7i<4-u%(Ou6uZgeRw2o{Y5ocyM8f&aK151czJr# z5PcXuda##Z_jzs~#Vd`o7%0%(7F)+CB5D zb+YY&`@+mt(n0*$59t2S54e5rcow=OIFUQ8 z|M)4H9pMh!r`A8ACdj=kD~6l_33B)D{d~N>_Vq3I?HJCHu?D5q6YZ<_kRZZLI6)=z zB@*Ks5djF~tz1Y*h?!y%nQHTB%`g(1Y>XE==COZ&DEJ=ej{xv5!Pyuy^Bf)tq69$z zA3oHlKoPU{l5Qi9D8KX$Lw%)|D=JD#xKdyaUCUnL{-TYH_+cng{+5M+H3u@FuaH(D zk6~`&&BWkfDoDKPiG>|n-^FmO3A0yEJy?yq1Z+8`lJt+!jCUmHFB!B4n$TWgY>+hc zShb#eDb!Kso5Mn{n z>xTi13K{I%M@8>$1Hz{x5VQl4Z0pJ9-aa?rd6>uZysyVSvg}GFDP2aGRt;(&{wQW@ zLI;c#=6K0R7uM_{bawB~fxZdofsq(&gI<6~G*ZLs!5-=q0pu`gbtPjBw4D6UrGP=NdaNR0G;p>UCq)@rza_9=zuWpH6 z$3_r5KDNO(G&@g$sgt_)4EZQfYUYBo&B{`RQ-&v*n}-J!E&LF^8%ir1Iy>3dzCJMv z-mobj&VBIl{wFOY`Hc1@QZEed5I4|!5 zU_q0IuB%i8brW%HR67t-Nx)l;wx8c_s1=hG={963(<8!9pP-(k{%pJ4ij@(M^1Cn7 z?Gt`oQa%zd{g1n>yF7GpjYO+Nd|xsrAa9%wYasVTH+TbB#lF3JCB((+h0uz_QJoNe zXy?andl zES1;cgt36C~e++ue6j&NapwG=~A6={a{pHqRdfPH=#GADs?L} z1d)FMwgmKg+@6cjtN@HhCPOL&cVw4_Vrv)Ib%49aba%*g>4PuV4P8W9u;D|+!m*Cmday6RcmeVu;t zaBb1HMN8vjlj9@W_eXlad=C;nWx~=~sb&p2d~={I)Ly^{aAcuVJ96ZRZ!ZRVATS=z z&O?KPH{b-0ilWVY$~95Lk+7WSqwKWk%$LKj%|lu~>@Ie=Q`v5GW@=zy&bUT7`^O*M zZ&n{$7ibnTXA&M>3)j#OLk)k7>^!ShHFj_&HVq6c!uHX`%4nklhzx2;2$kSRq6V6+ zcMUvrXuFXSPB%stmJN`(1qK38n4+SWy;aYO_qCnPgzN*qbM@+bN51VPBJ%R~AZyX? z6;5{$?$G!N!yqXkfxB|EbSBVV2iUV<>@P7dgvb|d$XgKYxI6H82?(4!ckVcgcIfPt zJ9kv4YWBC_ZbqTOsEJq)#PbI`p!#Z^%Xj>zNQwrtp|<5(KUWnQih@Eavw1A(4MO2CVNcR>k$7)ZfBee!OO7?y(pcmBYh>EWF7=i$Cs zz-f$^js_IbG4|EL9O{+_6EH^NG(q9RSL0GHo^RGQ>IPk|jC5T?`WMvIg}RwjDfg|8 zTU;u8G`y3!LEB2%d{YVK(%F$secwDXPHW68)E@qD%H^wT`PaOVuddha7N4CtUmm>J zbMTfY%a8RVFlFJLy;|P!^q1ejVEK64YpYw5E@?hSbr4OfI(C zvEr?GCYfV2eFb?7Vxn*c#_EaRT)SFtri3Rcio7 zPE1T>k&AG!f`8{sH1NEGvU4OPgo=Xta4;@gw<9+u#fVapOQvgW*t)i7itfYm1a-#q zJtlGo#EF;rL+o=h$5yKrK95+;j#wukg5aNOdj4o5UoI zG#Say9d(l-sprrC(ObK?^QI=CAl!VDGtN%D#%C77SMZhuy&diY3RP59vpgrtBCmwK>-`PlN1z4 zvItkhw*iWDzG*wzN&~E4G_B;qg{Ho~kdg{lCth3X-|MU|pZ;OcC+kpEe(~aimXeZssKC&81LX!L2Fiqwi&La}aFQF9lgAp1;1Q@n8S(lCFV)0mKd9tK;p` zdQY&dl7|*jK~i!ICK2*x_zC_fOwnRyWu@w>1|&1d2=W-aZ+m9AGP=&}L;l@4dWNEc zEZ0G1%l7R_kIc+dB2~&+YLV&&9RMbL1jqDQ>=kyq)YhqW?}T$@7gBS$x!n&Q)ap*E zK3KQUCWTi~R7z@_Cqtsa(-$vV_Ld>cGDh`caWNe+{Pr!w!PmMvyVW6^1CDDpalJcq zoSAP~Mw}z-0q%>G@BWS!R&hxoc5b}%9y$dM^N3!1{0>2^$ZpDLHt$bB5sZ(I2PLwL0ew4AGF)Vi)o+fxKMxlYmAQUh869!^FdUtl0pnaV zi{$&o*HU$LJjdR3T02XhUH@L((7mLj*f7pHf}X1{7QwvKjI_Z)olp-$oBi^ZhZbdW z#~$VLV207Z+OR*F{N_=Bub&@cj916_G0?OHpaPm*uCKj|8uwqg%d=R##82!I%B(xY9zK&?0}03Td~^ zv+qSC&_V!p1swq!d%ePyva&;90eyQRR|CKY4F0bDL4Y)&6ZaWjA$00C268yeU+&8T zIb8}4T$o)WY+gZLz8w~BctyXAkB3YSMxwKVz0xn+8eJBGeNg7`lp=p%i-AG@D!*xZ z(bcg}EcgW@tgNl!>OYg6&9p2E)&k^8fqkxlQv zLG6L*7kVy5($ArIA**Y*y5uRm)j9xbQ-bhqdLqGq0=g2f z2>jZMmKKO>+5Fb*0cr)2T3$!Ir=_E#e0r;`>~pxzK!%hw(XNVhwnZ6-H-FEbyldAE z0&aQvihKOyN3ikV!xqu=S0Z)%#fzSvnf{)W=L5|vYPC@N1lLu5=0yn zAIvq;f&?`n5bvtVHfg}pCZQ_(SyvYfxL`sd`^utx;g%Evk5oCa_TQ9$$oOQgqw18#0E=qXfW ze<|s9`sp_!&KWl^$a^>jc;g)Ihx8x<2r%{~q4T#+{-p)DG>#HEeLT}h4C-2xN&u!e zkbEzrr6ovAqHRXKfLM#=2qd;ySvE+aeO(O;E(-Wmh`Lg9jGjn*BOkyqZ*MBp3O1F< zzk8SO>V`f;H#5Z;;|{=^sJXNRjNVNyeh?5LHW#8yfJeY&jl`&yA4rbyjyaei)bvvm zz}v%jy*c)Cg^Hk>Z(Uaxj8TzfSz*9Ou&}}M7L|iK4MA)eRLcX-$j579AtBfTe#a>u z1dQ%2V_{(#+G}ch62*f&QSUdEYz(OfkebxAw66aC6O+5q$2dC|oIKf%Y@A;QEvAQc zCTFc29ON>|zeJp6d)ok`^#x`QtW0j!D?k8+KR`ev+emWF&dn9ZTTbUz>2*IkhxR!% zB&5NuPT_dt5Q63q(J%d6H=*F6B$ouA+sX`!Z`}7USn(CfC7G34#uR!O@L6sw5+>8% zWGT2%Rm_`d8BYm)7Zs)U)`=J1WaOVF0R-PxdZ$(A9=}}rgHp0ww_^rEww}XqB*r-; z2XjN5kW=>1Norgb!toLxqMHaga%5YPMP+t+IvoKiwB!Iay8|54WSNhxE0w;w2X9%9 zudw$A%Z}qKk@n7kS8*LjrMWR&<6+~COEeH=jW4v=ow+TFaAvgYSX?}&l7se=C1|I$ zH7x-F)8Ht?fapnf7}BWZme)q{qvE~?VZAe$=kQ#rsduj@BH2OFTA zQnoi#n!tbRgJR?3@l?EJ)s0wBHLujn4<}R6r@+i9K%`&5BTn5%kpKk}lUHQ%0b#2M z6)o~VoOJv0bZ%y54`i)UtPJ({x9(tUev)P+hCEN)R&rA>aL>WjFw|x4dES1kD>ji( z)O8jcl?h24pe&0jv6Mb(q53G{_rqtUnp{Dquxb{ zV(Fxkqb7;hulv9>k7yuJ`haOrNQ2Om2T^6!y}h+F&v$bsUbuSI#KvY0?P-%MMplTI zE?oXu8+}q>6DKkF{(XE9?kvY4l zTvM2x@q&fawF10B2qHJCTBC%u2TBh_jKY{RhKqtO^ERLRF=j9E4_lUc~ zpKhzWv#5@-nVZL4!xn?{7JeC3!WX#=T7?76%`L7T;>%YEkc?t5bk_knk`c0dWs94a z|0PO7UvI_>x<<2sHGw3`e2h-?>0V>? z`?5yU?>g_*Z%%CbRg#h^r@A}b$pu;gB_zr0I~yZsjW2KVT2Dr)23P{2nW1PGZUu~a z$TmHqkqdb~PG3OlyB!_BjE>S1m=EEDWIoV_U;`Qg{VD21d8~&LZd(n(lw1Z(!pK!v z!@&VduLWA4tgQGwA|-~dJ?ak|lRvR{tiWZP@fk<_g9KB6Cf*;i2Ea1meM4cbJwF%? zIS=MtD$!+oBVx7^A>f2gV5wB5En}=+%ra4LmDNONPft;9(vk$`5DAF~-QCmaJG7A1 z7so=Ic??N#$q5Pbm_mSjlDmfP>RI&UV|9l*uCg2R%T;>Lc@!yui0tTdW&Cc=xh*^M{m+pRup zgf-$hp(UW#8VFzd1M`2W)eoGc!7qDp+&S+hh+M6s%-WszCXh4)QyJL93}}mAP{r;1 zPd$FuW)yuDb@d+@yul4Ohvv4D zkEMl$WdJ%cpF@s=0j#zGE=i|=(`{$EJCx^dRR;mFLY-uqnS(iC~44Hbgk%N#z!PE{ZmGEqg%_H*acj=rNEJDV&;3@I?QM_3hZ|M z>5M2zz9xBT@>4=HKv;*tYNdx*qwu+`53&_db0$0v12~UVoZjAbT+A?=Iu+W=9^Ah_ z`tsv1Uk<)tT#9O7i^+Ru=i5qEBIEOf(sS@YFhOJ+pFVTO!_~FWLVujDV5_+o&bb$T zeGvRn4-BO9fQP_i@HFWr&_GP`5qao%;qa~xKladKe1%#$P<*6rA<@bHHXVVv0NJ6o zvT-X*$jv`~{w!Sz-zL6|U6@obT!;FJaH84S!1?!I&@Cd55YK+k5B>X{b*QG1+XugWhdauE$zP>`XFfR!e-B+a?BkY}E42)e=Kk_TtiK-o zVET<%1A?%#O6Cj0s`#u?q@mH0($F|3SyoikjAkEV8R@OX9%yV$&Z*rjDq2o}gFggu zt@PUAfoBE@kDg%t$^cF~96JcNh};eG3&BCl!e@(J)+kO(v#eb4vdM674J9jMw7{N) zFG0O%Sh9jPCf9;pJo<&9;kJr)clPP-NXf7Wl4QdxX^}$}aT~5)ED3e@#wZQCA7L#p zk)Yq|y|RmL!lPLMz|nt)aX!KJ3~?XknG^gQy^z|7G2(XxMT-7J&Cf5Oa`4gc$l19; zU58?VI5x=lbhB~$V@5W`t{)@|oIEKppi#;dLMOEbQ&Navox&B&Qz+EH2kUsPHTr zOuT;&l~}adC%i)(Ag(uU@q4LmWEq)4id>(easye}pa^R#=ui}g_9Ys)`1qvkOIg3LA#FoP7QiTdp+Ebwzg-WxdKRr zHk|q)-ND3sboS-G8@pj<1x&yupq84OE4Xqc9Exko+k?-bDk20q(fNhyn=r)mAjBVI z4zTKy`Kv$%;B)mqp&^K{$Lt?%mW;2YLatLNF1S262QWx{8XMDLE2d8R_2$`|l(IqJ zyP`}306>|A9}cu=p~rcbu^|y@7E7rFCURGp;d+s;W~Q2kCEcaut);7lGpUuYB~T>e zLQs;f@fvBCJlTA%eTK`Du04Nle3=O+2F#5EZ{DoWBm+&sp(Mu1=~82$3i zvuBpOcfWf1lBw3pJPQ$g@I_;u1OojFJhRarY7dax;w*e>8N*fl!*x^ib^&dej*h3K z01<<&R_lshDXp}tNG2K6LW@|5o3J#kTv>VfG^T)^Rr}$6_F54`t!7B205c-T#oWVh z#mZ0ZOs*8-0(G{pD5B+vEoBUeYA7GkU!l91?66{|sZ_%BmI*z{QXZ>|tV&k&C}Zmy z>&|BdD2yt;k8S|A3PUXSo_;NOV}MOyjAu=>CV35wjBxSseb-+4rmcUqg_?)c^($L` z83B!IKN6p&XEN=nWTNU1VM1d`yZ@qtgBh6vBv! zs4lu!RAeZ|dEmtjnt>bb3g_-ZiUt`g{sg2akVWMoeuhG0I$YM`L4A7Iv=@94XVAx4 z!VOE=V%Jt7gjE6Ab}9)d=uo&Ls;T*h(9-jlUtoMd;68E#k&&TH9DqLvPCPc|G|TwnjTM zJEV{5eeIgc8l@|XOd*=Q39|h8+umm;*WBh3T+GBhcjO)OzEr-kAC%!GnjYQO?YB#) zv8Pylf(KkaYqg-@;y~y`{VZA=fH-g)@bUA54_WmWj&m!pTp8B5UX)s}VHXilnBEFe zX6gJ16=YzDal%^w8P;A5#zX|(I=U_lpyS@nu+l6Cj$U}>$`izQ;K2rL6BziVuY%=sIzBz*Vc-mj)VYBLbyxxbp$A}w z5~#?xlF3QHqM%=)8rXh}BgR1{bMZnO&Smt^2x`;*`Kb)j7Vwry&BM5+Q2Uw}Zs7<$ zwc@Iz?*9)#JmzRK9%wiN{w(n4>;V8LH*E#rN9E_I?pH!%2azSpEwTyN&qqhMiJ{u z^>_arJWTn-=^wJw_#&Hd9xumAi9T05*>hNXQt<4!i)@q26tW+&qs$YP9jn10xS*%_ z`HSfHAEpFJ7>;deT=G~`PF&PN96fGvAc~C&SMJ?=1;#9E&#}mRI3vv@@qxd-=^76R zhdGwYk>YkCHCEco=d{T~HHl|kvth!3hE@pPyu3c!r zkdeAN&e`U=Bbs@*#DGRt1y6~_Iirc4ou2mg@Q66SN#JCBJOfe%hG|HRpx199)K|Ko zo+Hr!;~-$(Jz-OZNGB|J2Z91s(%L|Ml8Hn;mYi+a@C~R3Zyd%&-qow&(`FUa8x1s+ z!I6e84ZuUq;*b4mRaVj_rlz^do)$=Hvy$HF#U+~DDQHaw>u4rPVr0aW#mO9NwjxHr z;A?(iN?@{u)x{W5w{0?MMzFgAXiJoeF*`#ZE!j*5@&FGgvccggeSrkGf53(>^hO=| z2lSVvxj3Ziu9dgiB35=2i$Fk?5)rsgH*H@(dsc?<)0HdJA-4t`&6$XPb|bKG%rpU8 zw^}Lc3EdLVH$LN$;(5o{>LOpJtwzXn%j3r~nwo14qT;R-7Ouk$S9GXz-KwHaqje$s zER_1kz6%vZTj!TaMmj&(e++&^C@{qXZ```ocR%#ba3uK^$5S^YY9<>Xj_4p(Ha0xw z@$2Nm%%Sn0ZeFzqq1>cd4(3>_M58WhNs*S9S5Z_%<%rDjR@sc*evJxgMomM$Tr>F2 zD@eIA5^LD4;xll3b{lXq{9fORwkJ<~my2;?y6?b>#qFPJ@wQ;z@JJwsKef%!fs+)I z?c|{_u&)F$N0(WUN&aWhj(7PT89PM)mUy~`b8x!=-m>z{aN#MPR)`83II<5rIJ@6T zng|+9@2oPEl735JRQJT~oY=lqlBBzj^*&!+VV{#NAu=;T4Y12G=>j&T+(jkx1?b8G9{UOktvwqb#`2dE|#cyAs*zP$7X zAnNq&EMyuDtN{H1*%1WFKT4@#CKzv=M%Y0K6^0Ei_p4X;pfWG#rj68*&K&-Vp_Xd+ zR7Ly^gza6-gpQNbAh?4;pFMpu>o^d@gd#iig@2R37?FSH&Z>!ge~%(gkjRs)m?2|m zcUC#1{fShq0YM9@)VR?&Q}8)Opr|!EL2Jbw!cHpE)b8$^R1W61Cla0%WZeYz>aQ<3 zn|5gZiV1U)Xq9|RzQd&ijxH5Ml*%FR1+EI-I2uCUv3hJ`B7C@sN%B2pzc8>~_}42< zCwYNSln3rQbs(SM%@h6rfjiZ4HqSnR)R&!Md?#|UcC_>E#U>CfVAtg3Pqyv7clR#S zGTP?w{&LZ)&}9+|A=4h!WdPh^>8m;NZ$J^`oCJXx27vWsE(R=vO~|NY%tVwT;s@|2 zM-fm99V>z-pdc?;`uuYweFuu#GTQppKGoEb0Wr{^qS zFiP@xz}dgOF_bGh;TnsQvf?>!gG?29KxLQ=Ky1R%(cj;{=h*jmXn(+80%wJA=v{k# z_f{LbW{;z2OEO1!MdvyNpM?yW#mMsDwGl@GI~PXu+&bGYxK*vlT113Yul`?PGo8k+o~QA$4}&!XcYP_)yg1WfZgBOPM)fHF)F;=r9r6a{ zr%bJ=*UIzplG{d8wu+^iNE@504)=KMSc%{t(^3l&Z`Qg|#ZcQu@EFeDO#1BM3Ec(o zi^oKNBi<;;xXD$fuqz_43k~eBIX z4M_XPB-ny2TepJR(ZCV{G{2a5n}fdp{0Tt`yjUxLBDoE45^90;qLqOpmka+JwXHrL zN@I8bKwGnlaT&D8&wG1?6kV2OMsk%ges&(0=sf-F(d|r5w%e9$W-DI4k6KP#o?4V{ z9+n2QrQ9~3p)Z&_XMP#DvZFB|r2meuY*up%NR zNn7UhUy3m#Qb;d0$;F~MLVI=e$A@B%m0Up9{F*&TE3_Z~dW^5lKi&M#_>+CnCiZ4o zy-;4c6K6u@}j`8S$am~v0~9@Y0ZoM4Ur|r z8w_NDG=dWa);6{34CAsWGypekNvqWDHr>@x-nK5FgL>xE1; zTs+R18nm01#*y62%*2FRTCea4_bbLkCMG6y3n-Sj(Rt&B7@z=JZ&wbj6_i@F{@B#;@;GM=K_3tZxVsEq+rcAJ1+gd`8ki#F9IJ8WKKxa^2V{9< z)YJek?RwYA$n@UaYtAxAkr9XE|e~h3o8w%kKw_y^Ld|SuWvfcBTl6$DTsuZ z&tmbJ;Yn`BNhcNvChY#vpDGJky6*SLp1{B`MQRA{kQ4u~Cf4}{KBAvLe-D#^9S@hV&gbA zi(lHfY*(O&#uXl~)&jw!6|X4=?Mv3l8OCn3+TnTJ?H91M55bSQI36I)nBT~?;)LCE zYo(TgnUH$jS9l_~T?yiUc6Rhf>N{TbxfTr-o!Ds4)V&5wkfGuA{hB~=uEY8~voiOA z?#~fXhR@WINStzasddzOMZsN3AY%Wb&WfPl`JJ(Jh?Mg1C-SjMCggel(gF;=M;o8{ zxMt`{s==O>j+JFdM{QI)R;-|fZT`ZITgJn7`Ks0(;_ zl9&6uJ?QC)=>};$qCg5=T%bmQoW8#0*qStIYS*$_`-P2znaPVX#R`r4nbPC+ zANzjOE|YYIo)=%dJPN%TR6q(}B$hLVOjJ;pj;`@W#IAq-$E|}jc@Gr3?G~~q^D&|H zaYOWZ9X8(+{6vkdH&h=Z)l)`brrf^lnabnIG0l6S-blulsT6fx;&U z%P3>zur*V|f2nEj2=R;WaHI6!b&1$3DDAQ*#cqp&#=e;)_sP6odZF0O7-Nwfc1sIn zH5QV#?WG#RZ$mNgTPx;L8Tl5ov~$_ZCWaBD70gGoX(0^cAha5TAN7&c-SEyIXCmU- z)w`<}gm8004uUEboa_<@eWoAC%z3&~ON(>0c)IqxA5Jv*I#^G4cT1m?`V})-O+&8p zI{?UQHL<~kCr+54 z69m_VHE379HeYC@!SRiKt(gC*nB0xqx3NrK^tR;Q7E}sAaei&PWS^tnS15%*hz<4Y z2*2k)eFl*wYDKI8 z>i+$sSn5Qk!|DeYY@Pv9Q*ZOBHYu`1JyHh(f&74*B)4j5*5a%CpaX=75xg_Nj#622 z-K5(2YH2?{=^M_6u6A7JmZ}IxUXawb?b~7U$H|PT4@Zwu_VH$yt9xn(ux_641<{2` z5efNFa)Z94BaqDnLLLqP^n;N3`kM2`p-V!N*WAnuc8MVw9-B@i*OWspuBDaR@g82# zcsv93ukKY> zhTQ?5;QtP^>4dhb1$_}9oM5Xb&jEkK0u1Etfaw5Lh^U3B?+YDTKfUp9agMs&tb}Jv zLSio#{mzS!Gp(eld)1&m_!qe$*W0K->eAJJ=*lcpS^yr7$Z|3+Lx{og#%*c$V1YPNQ)exAoo4woxppuv5JEq)iz5~=-TmP$5(2?IqAaiAh8 z^!rzL2Y&NE|Gm7d6(2J9LmZg{&$fgJaKIxuwXC^&tk>#)abx;{!5EGzn4(#8L zmi?@f`y=E%&4(R>2o2K#5}M+iac4&ZOTB%3=cPn)D954YGo{Cqly6xq7u!%kzUg0I zWQ#RpnNWj3`9vG(ggSt`4l_6OHi)!2O)f$whn!!_i^jSyO1jy{S1#XGSs_@81f^RU z`V48(Zxw zS`&F{GuF{Df$7PYx(oz1WFK#Dv6CASyBJ)79mq(zP}sOp+%fFoJO7v7fzHk%ub;HG zPR-1STfXY*;;8*}5ET&vC|*0pVcs}MYt+=$0a&~8N~E`f!+>B`frQ{5>{03N>iUj8 z1bIXXq;zr-mi}P*7#29={dsJ?j~#VH1a9)wfHSay62xA8WDjCYShXiC?c6!p^i~DZ zD~R)8X`F0#BTF~Doq|1pCDw_T{`##4CC$r+ERgkq5YGi1E##i`=qJeDsfD=_x4R4P z^JmXou^Q#e>rKJb3~3ZXXJ%*K2SdJyD*);${|%%xRKw_&eZN8PkKum4;pi^iH zbK1c6vNOl(6-+`yh728XLACX0c4~uMbgFl%2)mT{Zs5NIu z98&8uF6v}t*!4g|c=5U&CH%B@(BmE5Nm&PR)SL}UDGra!4P%ss^h=5vJFQ`K&+q{soHEHa zrpDxg796CWVQ{*&K4+e%fFb{oyuK!Q_7k7?s$amTc%KDowTnyC8~zP{;*g=Jm&lAx z^lw9SNSi7Rfenl6b}>{+;)aUM!L`K)S-A)V9qzn1vUuX(6^jqF*A7Pomi`vcUA59w z%CI{;J!W1V4Tuzc01);6(dJG5sm+T=lDA2o*7+CI^cny6hAweVOLsXTFwoRI|3b)l zy~eh;+%@QhaQJdEU{0svyOdUg ze&HR6ET=eU(X@odAyUjuvbooKmuqIKUpzc{<3z~Znkz4z>l}$nmhXY$1~gM$2l6DI zJUQ-BXeIr}hvGa48(O>#R#W^BOAkE{xiDhE>U_4Uv-%=ZEaHP(u_v<~bvbNieCPZ} zc0vc5zH7A*na_2A;*JS zxLy^FpYUi_(kX-g47i4^7nPTGz?#G2 zUmqnB^?s=r#WL!!zfndeQJ;>WrK7uUf0uT{IG%AF*=hIOULZrq&!AkZ$t31J>L2R9 z&;6OxD0chz!w`!ESPs2gPerWwm#6C57$7nTl0qCgbqXZH7qLp<&M@27-G}ZW^0B@2 zTMMZj0!i&Q%ghJ8ipniY&XgVuALmP+D=v}a19HYjB2^zlFg>1dhkNuwaa*Qtaf<#8 zOrD5Tu$F5XWe)M%`a~na!muO8>XuwpnfK^!i)WE~BrLUcb zSVIm6?2k-ZX|;tc|E{yJB=-528hp?sA|Fu!4KYm9Vmc}4yC7_?F`Zqpy@bfey<1$&Fp+)wbli5_qqERs0FZ#l4pwGF)Ejl3 z!)OfE8+GgFTqX!vRb^$=nW)O9w45dy0;UXnQ2!aj)vQXv$WGrcTLQFoYt_^0Jig1d4&~C!t9-uM|KyP-!PQB{=Y%6 zLl)7)mEB#Zza>(iDH%hD4W9A(o)5g1LyrCa8;p7f4#;^&J~k0d**cZO#^DkX_uBTV zvAc8;ja%uZb4)$wJK6ux{yvh9!WpSE`CzpVdad$1ch>K|O%fq4monfw=DEsyLk3$md6Ukr(!L8=8-Eq5xI?(jt;m z;7|KE1z=L+AD?}~U)>K4^7EU5IGt+_?ok*n5Xbx)31mfZ<#6IIJ%n^Id3l~h0}yv6 z*lW(l_I@gu9Ih|=AU1A$MeJXS!554|#f;(GqO>=A&e0?@hGIhdcaLq$$Zs$1o|_Iu z=E{@*i|e=VE#y{hpB+91rvdBO5GkxYgRffIX> zQrSc+6y_Q!9V&9R$w4Z?V&{H|ZX})O;~?E?U;y77Z1oVE7hLz;`ItSkWc9%VhF-!N zOv-n~ZNF5o0Oq-K>sCW}ff7|}sA`~zW#2_HkdbhR=OZV*qO2@h#y~(m3*4${AY*lj z8q6&N16oQ-oR{AJ%{6OeHUF=u>;UU}vgqpe{(epD0=aO(z;v6Do?aBfj?oM4%psd> zI|rT}kRE|OeO*5%YWJ9#31^b0auX^$eM@go;@($L!FVsB;{OW>o^OQ5{2qWddxjOY zWv5<-Li_<&EETM7XC)#qcQvrh9|(ZtC3tqADj+%6Tp2o>U62LCgn~dXG+(=9t!-^> zt*v?D8|&RI__D}HhC2hbD0Mw#&-^Dt<5fYz4Z4OqlmH4NiA`$mGf%F=m>fBQbn>Ro znoyDP2WwVT*W_HW>e^5cxG8kj`|#mubh+@-Ap312$ltrHW9iHL*fR?)ndUmTkmbhK zld&5MKG}sQ@BDFwz6X9Z{N^XxILxAiwkz)}-Zfq9`rS}nGss5*q;+@a5J$eqo?T5Q z^VAigPEM&%M6J*wgGO>C2S=oD7aX{d<=zLiBbXAe>cR{CWH8Dvx~tfE%QKXlgKzdY zmr8CElaflD@9%|PU{RV#_0zq_RUpVmW=q>2$qC{m=yP8HL}ti*t8;*W0{w!N6w@N3 zCtfcG@bl+ags0LGe*w0}vH>n}k~AaCG# zBQ~02p_vJA0;9?-BtfkejSmMeotpy+@$TAe>V7^(*Gx*cqnxR`hv($zT7y#2y?U8N z#r{envv(>gkSWm*A{O;m-k}OL(~t{t9v4iWNT5_&y^}vm@H`n~fmE|tH~;jt6cFs# zIr+JEappSoX~1yMo(#|J)Po4_?{?jf0l)0J5o-pH&h?{QQP&;deuY?c5UDH)obddE ze?Y1=$``I_*oUX$l^;e%rr|6b>WAbKM|jv^T_k1?w6yR-NL~tolMx&q@cf{lIk28C zytDAr!h??F7tG_hnxsb4^XgRhF8(hBg^274+NTuf{2S+$xQ+4=3^gEt?G=6IE;obG zcOl7G4TfCkjj&#qn%}|2walkK2B|549qN#Ai@}u&K`+chXNQ8V3OF1(O@w{-TS+su zwzpHa9ANMVW&t-ebXS81Jh|Rd#w?P?IdlcSVyO!pKKYj}dBM(znHPK!%ph=u<{C@j z2DXJ~0ptM=PcSx!85n7TZ2Ir`?{(~>q~gCC&Q5WIx+vhgaErt_gU-VWHt0411~u7m zNSPD>+kjf~8&=>#af#~{2D}ZE4MJ@&1`nA7Q%9P*sfkH{rJpMnGHq$Gbx9IEP8r%s zJ$?TNn0|9FDCLE2(FP7rVVe*(?wDZV*+9Z& zWF*Z1U|nSS;bj+bO3Gh?3=FF!3x>jrJT?KcKnasxieIZQ%)U^&G`9qh6SFj28G~yn z71zW@@ZPbf2t61qlMs$kBPx4)vsOl(OulVTrXw}t5c2_BKQeMbS;&b2)dRR1w)If) z%f}CiiCZXY)?BZ~_d1l`Ao{lhxhLDd}?<6dZb!{L!2x^A7 zxw=L#6<}U2P_`KJE6pgji1gaD2k^oU6{YygA04m8M^ke38+2B)jP+e^-WGSoYG->) z-!L@uXd%EOKA3QT1O^`h{8&`%1viTmHjWL$;p4%p=dki{WxR$3SC@zRSE_{5&c(6XRH%pUDqHj@(0G_}X8=l0MrNmC z4VqvIL+vwK0=3E@jGjbC3ux^>2syO`juilfiIF#vc$6)VVN*Non2n@ZifuvlZtobacAQ_s|+X ztFO+3X4%)eso7AACpnc`^z`4opmFiA45{SqzPxkub)H5S^Wlk!T5#4-;ewl^!uRti zaq!MS@HcfDkJKOU)`!2uYT}!(;2nU50o!tKqkcmcjD^e7yML5Cd-LY{yqA2DdUcX< zt_F2s1D>L$JKUV+)3u^t6Ht?XiBN-@J(m~d+9VHUKlOLpw{L0cq0rgd^g!SIQ*3nc zj}G_9>kINXbMlU4oVIkr>q})A+qlno@-2;-uI@W&(eccQ_o8z9pP)4s zfmZ9S{FwUlkS`Z6Yk`PNq%KRO#qu}}8sXo62G8cWlFkG_y7kT~Tl&le)nH)g$FOo5 zf)z+#o;0an!4L9%<>d8W&Q6La?hkYxX?=}dMo@FY$bbY2+C339m?H5XaR~_^xYXp5 zJ+*=a`|8!;s9*K>pNBej-#&(v&*S4i@L6Nv$2A6X3zZMzEfO0*k|A4f9cN;&hNH3Z z6QpTmj8oCSyb*N_K$C_LP4j@p z6n8IbDRP;h$c(mG5ZO|Eip^hmA%(v22D(D9L%`=TVq@-uI?*NmKd5p)Se$zD&BLZp zx48VNfEuDs@C-Y10ptWRj*Z_a$JV$+H*KrkAM@ZbOX885*{P>TOUjFmrfW{z`N5a# z5Xqdj{3AyJcLVbQZk9~KeCm?Gv|cp!75rzhZ^$;!F9q9)C?n4z4&BQl@#N*~h3Rc2 z(-QiI<$}WW;!a+_aZs7`)(wejQbbI_mn;)2ud3^P`#ASwh&Dta39$JWFcZSK7aV|5+PU+x&2m% z#^&IZL^VX*YoP~Y8s81m3ygyUc#3;^w424=0$Sy@q0TJ2>A z`~mvo$w$2cUi{??Amk-%yB?Is8PCNGFo5b}6^AKTriaFvBRiShG1hc5_L~vZC4sU$ za}cW{XcDeskR=l7Alfl};pw?nRmHEVo*7oB;0<$jrJTpoT5NWPgZ*B>N1oSEzEIO- zAnBQ6MNAOxY#@=majkB*(3Zk+RzzvhNKrXW%9JuWO$Y1*M=%lLQ}dnvOD4GpbsS(L zx)6)fj4HzjrdXWEN zr4B!v86-Z~-U>l-k%g4riCPT*_8x%f)7@pr|B#{oEwJV|; z<_9JxeW6>cjzIMtIQH{E*al2(1lAixn>_&e^7!%gFR5rnhu0u8jmku6Zx`bf63PQ# zcv{jKg#+D*nH~x@)sh{GkwzlOVtQ`w;SLHrhzHQOVH+Ti#Yd~8q5sx$1Z4;XoSfn9 z?p_f(chK;%-x@1BgHh3P8;n2T77)AtI%aM&*6h@`f#}M;9-+xW5G*6x40Nru!*hqn z62cHY*qGhaY+8fcmD@b~ z>6ifCF1ETb)*OKc3hj7j8DlX5y|MOUJ}U{x3bwX)h`N;su%@{taCY7h>^8)?^Ppvr*QT0z(lo}~Syx zmnci_KogJ`c% z;{km}gaScTIy7pWRJiu5E^E@_x<*8!Rq}Cieg#krrjw394&jld#iJ-Gi{WpFhPt3m zfAPY|TrGd^6^l-(6YY^G&ftR3!d0SU3r(h>* ze2bz1lZS1}xVp1&k~{XA;^~}E_-)yi_;`hrzn~xi4_QhI8&L;G3O4VlJk1Snu{cZ4 zmAq6SpAco?$#J4JwWV%(LgasUU^gsP=~DrDf>$#{d4Pg@!o@}J)7l^}8p247v)r~> zf5gt02~Q+J+~t5ViNJWsEJz(h8b|>;Io@t=-Pq2zYSqKNWe@@{qotYIE-CDhj3LvB zWySvqs2t-ey>yAT@0<3ygD3*9Hof_$P{GX?ii$YiQaJk_P|l~cN21lhCMAT&H3C*f z2;m~sWK1>z<#Jf!5~j6BmIrIo8h9$dApS9-Oe%Az<%vN?MBGftwUzV~TYxH>IIw^M zU^xSB31r5cnz3TK-)XkBE4}=^kkTsi#Dvl1CmGqflRb>%QX^T}=}z_~pMqKt$3KS7 zG^4{XtKf%GGXR|~)YHYES^1aAIkD=jlaCJ@G4}D}T9t;ah3aZ*b?cq`j#^0fjo^$# ziWK}SuiM&I5LAag1UgFQD5bztvi2M7zK}Qy#s-cJ7*R2o;kghBo1|tHK@o?Ub(O5G zPUzvLD-7$8_MBdd)e|8IN0Sf#BYZ6qVGcPKVSl2X22uv_e~>1+A>bXNEKsL$pQmLT z6ufbGVTf3ldj{tS)#tf*yEu^!7^1W0rOQ1!24*{UtQQp>2HXWvV<=X*;$i>LTCj$H zT(Z5R`@K+6dWxu(C}mo?fHR4?WMko_X5nQCG+RG&VUZo?>%XV#afMIk78HosYwIPX zi~y*mf|PG;d=b?f`g6%=;+X!~;I~A?o4T@Hy#{y6I4*md7l>WC_TR#U8Bf2W&{s07x*b{=SW4Er1 z?9M(v7R+e4W$hNM1C8lpGc-rG+X1b`*esV|IIxX7C3cA(fPG-BixB8JMrX#SB#zM z8tiV?VN=p8V6SPhG#5>xz!wTeM*I$QXRX)Q#GMZQj0H?$F9kD4y)yY9WA@cApe6_- zs-@iSYSsA?EoCl4fZkhF6tg*>-;Y_B=h51o?m^xlT)eSVVu>hlzP(LiF3a+n58B_P z>XD7v#$zD~loG^$F4Rbxb75bg=^vq+^q!SJemAUk^8JzOsK@Ub4!!SmHLBh{ogKDS zaa7`zcrY7%bOFi?;)7~65G07>3}sl6r2h<2391kM<{2L2Wwo><2s5T-;vie@5WDhg z#F7}-5$G<_F~bkxaaWiT&7fXn!}=p|Y;1YUbZl{<^w|f>Hy!!3@eB{kFu~4PALLI=dmg&~LLc1nMarVKdUfzHxdw8s&Nkt1nU5|-XW9D242*K%7 z@azM|(Y<9a5dnl^CCkIor~qgXxK7tgVQ*^g_{*2yVfe(q143WeWxIR#&!I*JbFRjr z$-26T9+TkZ|7g?5?fjNUwX#DMW-!q!tXxNpf9zPOc46VP~oOR~pedgl*Br%X=gMH=O3cILjQ9#nr&8&T^O=oFfj-0^95aH|U)89q*Hmv~WI1w-<8xVq&<^IHb_= zjnoL-YU5sZuASImy5mU6%IA^3YM9n*`>Wz(&)5VpRw8VV$!r~vRwO^1K zHC1=@EL>sJ%1e3w9m*jyt^kBX&)zo83{S2#c01LmvSk;@vxUjeh+aq_{L3mn(Yp zyr-x16*uvL=|Kj8FJZZo@8>zG|0^ORn)+h|@st*S2w$l@`FMl)7v;q7@3^ph?9ZRk zDaMK~g7BnK2WdnQMl79DH27~FyNG}N6^k^M$E&aKGc%r`e&aSnQ-f=IPEr34zYI23 literal 0 HcmV?d00001 From db965fd5e826648362d71db70f4b93a32df8be1d Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Wed, 15 Mar 2017 21:10:00 +0000 Subject: [PATCH 30/38] Move README image resourse to res/ and fix link. --- README.md | 2 +- .../ed25519-malleability.png | Bin 2 files changed, 1 insertion(+), 1 deletion(-) rename ed25519-malleability.png => res/ed25519-malleability.png (100%) diff --git a/README.md b/README.md index d9061b9..1a8affe 100644 --- a/README.md +++ b/README.md @@ -94,7 +94,7 @@ or form, safe. The signatures produced by this library are malleable, as defined in [the original paper](https://ed25519.cr.yp.to/ed25519-20110926.pdf): -![](https://raw.githubusercontent.com/isislovecruft/ed25519-dalek/develop/ed25519-malleability.png) +![](https://github.com/isislovecruft/ed25519-dalek/blob/develop/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 diff --git a/ed25519-malleability.png b/res/ed25519-malleability.png similarity index 100% rename from ed25519-malleability.png rename to res/ed25519-malleability.png From fc2725889cdade7a6ad299069bf97cc173fc46c4 Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Wed, 15 Mar 2017 21:13:09 +0000 Subject: [PATCH 31/38] It's a discussion and not a definition. --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 1a8affe..8510b57 100644 --- a/README.md +++ b/README.md @@ -87,11 +87,12 @@ review. Neither have yet received what we would consider *sufficient* peer review by other qualified cryptographers to be considered in any way, shape, or form, safe. -**USE AT YOUR OWN RISK** +**USE AT YOUR OWN RISK.** -## A Note on Signature Malleability -The signatures produced by this library are malleable, as defined in +### 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/isislovecruft/ed25519-dalek/blob/develop/res/ed25519-malleability.png) From 4cd8ffd7976f3b0e0d072b5aa1d8ae556f3a98b7 Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Wed, 15 Mar 2017 21:19:13 +0000 Subject: [PATCH 32/38] Explain the baseline "readability" in comparison. --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 8510b57..e4db7d4 100644 --- a/README.md +++ b/README.md @@ -74,9 +74,10 @@ included in the SUPERCOP benchmarking suite (albeit their numbers are for the older Nehalem microarchitecture). Additionally, thanks to Rust, this implementation has both type and memory -safety. Not to mention that it's readable for everyone, making ours arguable -more readily auditable. We're of the opinion that these features—combined -with speed—are ultimately more valuable than sole cycle count. +safety. It's also easily readable a much larger set of people than those who +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. # Warnings From b5ae6c4447a344394efcf0f43aca72224996c5ad Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Wed, 15 Mar 2017 21:25:44 +0000 Subject: [PATCH 33/38] RFC8032 isn't a draft anymore. --- README.md | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index e4db7d4..e22a109 100644 --- a/README.md +++ b/README.md @@ -100,13 +100,12 @@ The signatures produced by this library are malleable, as discussed in We could eliminate the 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. While there is, as of -this writing, a -[draft RFC for EdDSA signatures](https://tools.ietf.org/html/rfc8032) which -specifies that the stronger check should be done (and while we agree that the -stronger check should be done), it is our opinion that one doesn't get to -change the definition of "ed25519 verification" a decade after the fact, -declaring every implementation (including one's own) to be non-conformant. +behaviour of every other implementation in existence. As of this writing, +[RFC 8032](https://tools.ietf.org/html/rfc8032), "Edwards-Curve Digital +Signature Algorithm (EdDSA)," advises that the stronger check should be done. +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 From a6e74ba608acb8fbf24b57cddd7fe0b7ddc4fff3 Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Wed, 15 Mar 2017 22:34:06 +0000 Subject: [PATCH 34/38] Use b"" instead of "".as_bytes(). --- src/ed25519.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ed25519.rs b/src/ed25519.rs index fda76cd..c7f183c 100644 --- a/src/ed25519.rs +++ b/src/ed25519.rs @@ -522,7 +522,7 @@ mod bench { fn sign(b: &mut Bencher) { let mut cspring: OsRng = OsRng::new().unwrap(); let keypair: Keypair = Keypair::generate::(&mut cspring); - let msg: &[u8] = "".as_bytes(); + let msg: &[u8] = b""; b.iter(| | keypair.sign::(msg)); } @@ -531,7 +531,7 @@ mod bench { fn verify(b: &mut Bencher) { let mut cspring: OsRng = OsRng::new().unwrap(); let keypair: Keypair = Keypair::generate::(&mut cspring); - let msg: &[u8] = "".as_bytes(); + let msg: &[u8] = b""; let sig: Signature = keypair.sign::(msg); b.iter(| | keypair.verify::(msg, &sig)); From 0e535a931843cc45efefe502ac29c52fa333c4e1 Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Wed, 15 Mar 2017 22:34:46 +0000 Subject: [PATCH 35/38] The sha2 dependency is only for tests. --- src/lib.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index c5ca4a8..a10ac2f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -112,7 +112,6 @@ #[macro_use] extern crate arrayref; -extern crate sha2; extern crate curve25519_dalek; extern crate generic_array; extern crate digest; @@ -124,6 +123,9 @@ extern crate rand; #[macro_use] extern crate std; +#[cfg(test)] +extern crate sha2; + #[cfg(test)] extern crate rustc_serialize; From a3aa6078b4290f5e6dea4a6a026c50e01447a440 Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Wed, 15 Mar 2017 22:41:36 +0000 Subject: [PATCH 36/38] Add a TODO for adding benchmarks to Brian Smith's crypto-bench. --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index e22a109..544b48d 100644 --- a/README.md +++ b/README.md @@ -148,3 +148,5 @@ to the `Cargo.toml`: rather than using the rust-crypto implementation whose API requires that we allocate memory and memzero it before mutating to store the digest. + * Incorporate ed25519-dalek into Brian Smith's + [crypto-bench](https://github.com/briansmith/crypto-bench). From 20c19ac11d89976777aa8530163ff3e8d1551496 Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Wed, 15 Mar 2017 22:42:38 +0000 Subject: [PATCH 37/38] Bump version to 0.3.0. --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index f61d3c3..c7bedd6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ed25519-dalek" -version = "0.2.3" +version = "0.3.0" authors = ["Isis Lovecruft "] readme = "README.md" license = "CC0-1.0" From e93ce2c1e2f539ca6dd7c6b1673bc1b5a5fd78e7 Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Wed, 15 Mar 2017 22:48:56 +0000 Subject: [PATCH 38/38] Ignore res/ for packaging. --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index c7bedd6..f5166ef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ documentation = "https://docs.rs/ed25519-dalek" keywords = ["cryptography", "ed25519", "curve25519", "signature", "ECC"] categories = ["cryptography", "no-std"] description = "Fast and efficient ed25519 signing and verification in pure Rust." -exclude = [ ".gitignore", "TESTVECTORS" ] +exclude = [ ".gitignore", "TESTVECTORS", "res/*" ] [badges] travis-ci = { repository = "isislovecruft/ed25519-dalek", branch = "master"}