From 17b9571f0e53add5906aef03e96e2124e570150c Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sat, 20 Jan 2018 22:00:56 +0100 Subject: [PATCH 01/11] Add tests for ChaChaRng set_counter --- src/prng/chacha.rs | 118 ++++++++++++++++++++++++++++++++++++--------- 1 file changed, 96 insertions(+), 22 deletions(-) diff --git a/src/prng/chacha.rs b/src/prng/chacha.rs index 12c3c623031..53ac5730826 100644 --- a/src/prng/chacha.rs +++ b/src/prng/chacha.rs @@ -103,31 +103,38 @@ impl ChaChaRng { ChaChaRng::init([0; SEED_WORDS]) } - /// Sets the internal 128-bit ChaCha counter to - /// a user-provided value. This permits jumping - /// arbitrarily ahead (or backwards) in the pseudorandom stream. + /// Sets the internal 128-bit ChaCha counter to a user-provided value. This + /// permits jumping arbitrarily ahead (or backwards) in the pseudorandom + /// stream. /// - /// Since the nonce words are used to extend the counter to 128 bits, - /// users wishing to obtain the conventional ChaCha pseudorandom stream - /// associated with a particular nonce can call this function with - /// arguments `0, desired_nonce`. + /// The 128 bits used for the counter overlap with the nonce and smaller + /// counter of ChaCha when used as a stream cipher. It is in theory possible + /// to use `set_counter` to obtain the conventional ChaCha pseudorandom + /// stream associated with a particular nonce, but that is not a supported + /// use of this method. /// /// # Examples /// /// ```rust /// use rand::{Rng, ChaChaRng}; /// - /// let mut ra = ChaChaRng::new_unseeded(); - /// ra.set_counter(0u64, 1234567890u64); - /// println!("{:?}", ra.next_u32()); - /// println!("{:?}", ra.next_u32()); + /// let mut rng1 = ChaChaRng::new_unseeded(); // Use `ChaChaRng::new()` or + /// // `ChaChaRng::from_rng()` + /// // outside of testing. + /// let mut rng2 = rng1.clone(); + /// + /// // Skip to round 20. Because every round generates 16 `u32` values, this + /// // actually means skipping 320 values. + /// for _ in 0..(20*16) { rng1.next_u32(); } + /// rng2.set_counter(20, 0); + /// assert_eq!(rng1.next_u32(), rng2.next_u32()); /// ``` pub fn set_counter(&mut self, counter_low: u64, counter_high: u64) { - self.state[12] = (counter_low >> 0) as u32; + self.state[12] = counter_low as u32; self.state[13] = (counter_low >> 32) as u32; - self.state[14] = (counter_high >> 0) as u32; + self.state[14] = counter_high as u32; self.state[15] = (counter_high >> 32) as u32; - self.index = STATE_WORDS; // force recomputation + self.index = STATE_WORDS; // force recomputation on next use } /// Initializes `self.state` with the appropriate key and constants @@ -228,37 +235,89 @@ mod test { } #[test] - fn test_chacha_true_values() { + fn test_chacha_true_values_a() { // Test vectors 1 and 2 from // https://tools.ietf.org/html/draft-nir-cfrg-chacha20-poly1305-04 let seed = [0u8; 32]; - let mut rng1: ChaChaRng = SeedableRng::from_seed(seed); + let mut rng: ChaChaRng = SeedableRng::from_seed(seed); let mut results = [0u32; 16]; - for i in results.iter_mut() { *i = rng1.next_u32(); } + for i in results.iter_mut() { *i = rng.next_u32(); } let expected = [0xade0b876, 0x903df1a0, 0xe56a5d40, 0x28bd8653, 0xb819d2bd, 0x1aed8da0, 0xccef36a8, 0xc70d778b, 0x7c5941da, 0x8d485751, 0x3fe02477, 0x374ad8b8, 0xf4b8436a, 0x1ca11815, 0x69b687c3, 0x8665eeb2]; assert_eq!(results, expected); - for i in results.iter_mut() { *i = rng1.next_u32(); } + for i in results.iter_mut() { *i = rng.next_u32(); } let expected = [0xbee7079f, 0x7a385155, 0x7c97ba98, 0x0d082d73, 0xa0290fcb, 0x6965e348, 0x3e53c612, 0xed7aee32, 0x7621b729, 0x434ee69c, 0xb03371d5, 0xd539d874, 0x281fed31, 0x45fb0a51, 0x1f0ae1ac, 0x6f4d794b]; assert_eq!(results, expected); + } + #[test] + fn test_chacha_true_values_b() { + // Test vector 3 from + // https://tools.ietf.org/html/draft-nir-cfrg-chacha20-poly1305-04 + let seed = [0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1]; + let mut rng: ChaChaRng = SeedableRng::from_seed(seed); - let seed = [0,0,0,0, 1,0,0,0, 2,0,0,0, 3,0,0,0, 4,0,0,0, 5,0,0,0, 6,0,0,0, 7,0,0,0]; + // Skip block 0 + for _ in 0..16 { rng.next_u32(); } + + let mut results = [0u32; 16]; + for i in results.iter_mut() { *i = rng.next_u32(); } + let expected = [0x2452eb3a, 0x9249f8ec, 0x8d829d9b, 0xddd4ceb1, + 0xe8252083, 0x60818b01, 0xf38422b8, 0x5aaa49c9, + 0xbb00ca8e, 0xda3ba7b4, 0xc4b592d1, 0xfdf2732f, + 0x4436274e, 0x2561b3c8, 0xebdd4aa6, 0xa0136c00]; + assert_eq!(results, expected); + } + + #[test] + fn test_chacha_true_values_c() { + // Test vector 4 from + // https://tools.ietf.org/html/draft-nir-cfrg-chacha20-poly1305-04 + let seed = [0, 0xff, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0]; + let expected = [0xfb4dd572, 0x4bc42ef1, 0xdf922636, 0x327f1394, + 0xa78dea8f, 0x5e269039, 0xa1bebbc1, 0xcaf09aae, + 0xa25ab213, 0x48a6b46c, 0x1b9d9bcb, 0x092c5be6, + 0x546ca624, 0x1bec45d5, 0x87f47473, 0x96f0992e]; + let mut results = [0u32; 16]; + + // Test block 2 by skipping block 0 and 1 + let mut rng1: ChaChaRng = SeedableRng::from_seed(seed); + for _ in 0..32 { rng1.next_u32(); } + for i in results.iter_mut() { *i = rng1.next_u32(); } + assert_eq!(results, expected); + + // Test block 2 by using `set_counter` let mut rng2: ChaChaRng = SeedableRng::from_seed(seed); + rng2.set_counter(2, 0); + for i in results.iter_mut() { *i = rng2.next_u32(); } + assert_eq!(results, expected); + } + + #[test] + fn test_chacha_multiple_blocks() { + let seed = [0,0,0,0, 1,0,0,0, 2,0,0,0, 3,0,0,0, 4,0,0,0, 5,0,0,0, 6,0,0,0, 7,0,0,0]; + let mut rng: ChaChaRng = SeedableRng::from_seed(seed); // Store the 17*i-th 32-bit word, // i.e., the i-th word of the i-th 16-word block + let mut results = [0u32; 16]; for i in results.iter_mut() { - *i = rng2.next_u32(); + *i = rng.next_u32(); for _ in 0..16 { - rng2.next_u32(); + rng.next_u32(); } } let expected = [0xf225c81a, 0x6ab1be57, 0x04d42951, 0x70858036, @@ -274,7 +333,6 @@ mod test { let mut rng: ChaChaRng = SeedableRng::from_seed(seed); let mut results = [0u8; 32]; rng.fill_bytes(&mut results); - // Same as first values in test_isaac_true_values as bytes in LE order let expected = [118, 184, 224, 173, 160, 241, 61, 144, 64, 93, 106, 229, 83, 134, 189, 40, 189, 210, 25, 184, 160, 141, 237, 26, @@ -282,6 +340,22 @@ mod test { assert_eq!(results, expected); } + #[test] + fn test_chacha_set_counter() { + // Test vector 5 from + // https://tools.ietf.org/html/draft-nir-cfrg-chacha20-poly1305-04 + let seed = [0u8; 32]; + let mut rng: ChaChaRng = SeedableRng::from_seed(seed); + rng.set_counter(0, 2u64.to_be()); + + let mut results = [0u32; 16]; + for i in results.iter_mut() { *i = rng.next_u32(); } + let expected = [0x374dc6c2, 0x3736d58c, 0xb904e24a, 0xcd3f93ef, + 0x88228b1a, 0x96a4dfb3, 0x5b76ab72, 0xc727ee54, + 0x0e0e978a, 0xf3145c95, 0x1b748ea8, 0xf786c297, + 0x99c28f5f, 0x628314e8, 0x398a19fa, 0x6ded1b53]; + assert_eq!(results, expected); + } #[test] fn test_chacha_clone() { let seed = [0,0,0,0, 1,0,0,0, 2,0,0,0, 3,0,0,0, 4,0,0,0, 5,0,0,0, 6,0,0,0, 7,0,0,0]; From f6ddbe9323beeaf0c732d70ea105de05afbb11ad Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sun, 21 Jan 2018 08:06:35 +0100 Subject: [PATCH 02/11] Make the number of rounds in ChaCha variable --- src/prng/chacha.rs | 70 +++++++++++++++++++++++++++++++++++----------- 1 file changed, 54 insertions(+), 16 deletions(-) diff --git a/src/prng/chacha.rs b/src/prng/chacha.rs index 53ac5730826..2aa2181281f 100644 --- a/src/prng/chacha.rs +++ b/src/prng/chacha.rs @@ -16,8 +16,6 @@ use {impls, le}; const SEED_WORDS: usize = 8; // 8 words for the 256-bit key const STATE_WORDS: usize = 16; -const CHACHA_ROUNDS: u32 = 20; // Cryptographically secure from 8 upwards as of - // this writing /// A random number generator that uses the ChaCha20 algorithm [1]. /// @@ -33,6 +31,7 @@ pub struct ChaChaRng { buffer: [u32; STATE_WORDS], // Internal buffer of output state: [u32; STATE_WORDS], // Initial state index: usize, // Index into state + rounds: usize, } // Custom Debug implementation that does not expose the internal state @@ -66,19 +65,6 @@ macro_rules! double_round{ }} } -#[inline] -fn core(new: &mut [u32; STATE_WORDS], input: &[u32; STATE_WORDS]) { - *new = *input; - - for _ in 0..CHACHA_ROUNDS / 2 { - double_round!(new); - } - - for i in 0..STATE_WORDS { - new[i] = new[i].wrapping_add(input[i]); - } -} - impl ChaChaRng { /// Create an ChaCha random number generator using the default /// fixed key of 8 zero words. @@ -137,6 +123,31 @@ impl ChaChaRng { self.index = STATE_WORDS; // force recomputation on next use } + /// Sets the number of rounds to run the ChaCha core algorithm per block to + /// generate. + /// + /// By default this is set to 20. Other recommended values are 12 and 8, + /// which trade security for performance. `rounds` only supports values + /// that are multiples of 4 and less than or equal to 20. + /// + /// # Examples + /// + /// ```rust + /// use rand::{Rng, ChaChaRng}; + /// + /// let mut rng = ChaChaRng::new_unseeded(); // Use `ChaChaRng::new()` or + /// // `ChaChaRng::from_rng()` + /// // outside of testing. + /// rng.set_rounds(8); + /// + /// assert_eq!(rng.next_u32(), 0x2fef003e); + /// ``` + pub fn set_rounds(&mut self, rounds: usize) { + assert!([4usize, 8, 12, 16, 20].iter().any(|x| *x == rounds)); + self.rounds = rounds; + self.index = STATE_WORDS; // force recomputation on next use + } + /// Initializes `self.state` with the appropriate key and constants /// /// We deviate slightly from the ChaCha specification regarding @@ -163,12 +174,22 @@ impl ChaChaRng { seed[4], seed[5], seed[6], seed[7], // seed 0, 0, 0, 0], // counter index: STATE_WORDS, // generate on first use + rounds: 20, } } /// Refill the internal output buffer (`self.buffer`) fn update(&mut self) { - core(&mut self.buffer, &self.state); + let mut tmp = self.state; + + for _ in 0..self.rounds / 2 { + double_round!(tmp); + } + + for i in 0..STATE_WORDS { + self.buffer[i] = tmp[i].wrapping_add(self.state[i]); + } + self.index = 0; // update 128-bit counter self.state[12] = self.state[12].wrapping_add(1); @@ -356,6 +377,23 @@ mod test { 0x99c28f5f, 0x628314e8, 0x398a19fa, 0x6ded1b53]; assert_eq!(results, expected); } + + #[test] + fn test_chacha_set_rounds() { + let seed = [0u8; 32]; + let mut rng: ChaChaRng = SeedableRng::from_seed(seed); + rng.set_rounds(8); + + let mut results = [0u32; 16]; + for i in results.iter_mut() { *i = rng.next_u32(); } + + let expected = [0x2fef003e, 0xd6405f89, 0xe8b85b7f, 0xa1a5091f, + 0xc30e842c, 0x3b7f9ace, 0x88e11b18, 0x1e1a71ef, + 0x72e14c98, 0x416f21b9, 0x6753449f, 0x19566d45, + 0xa3424a31, 0x01b086da, 0xb8fd7b38, 0x42fe0c0e]; + assert_eq!(results, expected); + } + #[test] fn test_chacha_clone() { let seed = [0,0,0,0, 1,0,0,0, 2,0,0,0, 3,0,0,0, 4,0,0,0, 5,0,0,0, 6,0,0,0, 7,0,0,0]; From c22c5d506a05bfdfe4ad9dc0267abe9a316ead91 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sun, 21 Jan 2018 08:09:55 +0100 Subject: [PATCH 03/11] Extend ChaChaRng documentation --- src/prng/chacha.rs | 67 +++++++++++++++++++++++++++++----------------- 1 file changed, 43 insertions(+), 24 deletions(-) diff --git a/src/prng/chacha.rs b/src/prng/chacha.rs index 2aa2181281f..0050048b277 100644 --- a/src/prng/chacha.rs +++ b/src/prng/chacha.rs @@ -17,15 +17,51 @@ use {impls, le}; const SEED_WORDS: usize = 8; // 8 words for the 256-bit key const STATE_WORDS: usize = 16; -/// A random number generator that uses the ChaCha20 algorithm [1]. +/// A cryptographically secure random number generator that uses the ChaCha +/// algorithm. /// -/// The ChaCha algorithm is widely accepted as suitable for -/// cryptographic purposes, but this implementation has not been -/// verified as such. Prefer a generator like `OsRng` that defers to -/// the operating system for cases that need high security. +/// ChaCha is a stream cipher designed by Daniel J. Bernstein [1], that we use +/// as an RNG. It is an improved variant of the Salsa20 cipher family, which was +/// selected as one of the "stream ciphers suitable for widespread adoption" by +/// eSTREAM [2]. /// -/// [1]: D. J. Bernstein, [*ChaCha, a variant of -/// Salsa20*](https://cr.yp.to/chacha.html) +/// ChaCha uses add-rotate-xor (ARX) operations as basis. These are safe +/// against timing attacks, although that is mostly a concern for ciphers and +/// not for RNGs. Also it is very suitable for SIMD implementation. +/// Here we do not provide a SIMD implementation yet, except for what is +/// provided by auto-vectorisation. +/// +/// With the ChaCha algorithm it is possible to choose the number of rounds the +/// core algorithm should run. By default `ChaChaRng` is created as ChaCha20, +/// with means 20 rounds. The number of rounds is a tradeoff between performance +/// an security, 8 rounds are considered the minimum to be secure. A different +/// number of rounds can be set with [`set_rounds`]. +/// +/// We deviate slightly from the ChaCha specification regarding the nonce, which +/// is used to extend the counter to 128 bits. This is provably as strong as the +/// original cipher, though, since any distinguishing attack on our variant also +/// works against ChaCha with a chosen-nonce. See the XSalsa20 [3] security +/// proof for a more involved example of this. +/// +/// The modified word layout is: +/// +/// ```text +/// constant constant constant constant +/// key key key key +/// key key key key +/// counter counter counter counter +/// ``` +/// +/// [1]: D. J. Bernstein, [*ChaCha, a variant of Salsa20*]( +/// https://cr.yp.to/chacha.html) +/// +/// [2]: [eSTREAM: the ECRYPT Stream Cipher Project]( +/// http://www.ecrypt.eu.org/stream/) +/// +/// [3]: Daniel J. Bernstein. [*Extending the Salsa20 nonce.*]( +/// http://cr.yp.to/papers.html#xsalsa) +/// +/// [`set_rounds`]: #method.set_counter #[derive(Clone)] pub struct ChaChaRng { buffer: [u32; STATE_WORDS], // Internal buffer of output @@ -149,23 +185,6 @@ impl ChaChaRng { } /// Initializes `self.state` with the appropriate key and constants - /// - /// We deviate slightly from the ChaCha specification regarding - /// the nonce, which is used to extend the counter to 128 bits. - /// This is provably as strong as the original cipher, though, - /// since any distinguishing attack on our variant also works - /// against ChaCha with a chosen-nonce. See the XSalsa20 [1] - /// security proof for a more involved example of this. - /// - /// The modified word layout is: - /// ```text - /// constant constant constant constant - /// key key key key - /// key key key key - /// counter counter counter counter - /// ``` - /// [1]: Daniel J. Bernstein. [*Extending the Salsa20 - /// nonce.*](https://cr.yp.to/papers.html#xsalsa) fn init(seed: [u32; SEED_WORDS]) -> Self { ChaChaRng { buffer: [0; STATE_WORDS], From b05363853039fb51d861db01dbf8322607767192 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sun, 21 Jan 2018 08:28:37 +0100 Subject: [PATCH 04/11] Improve performance of ChaChaRng::update --- src/prng/chacha.rs | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/prng/chacha.rs b/src/prng/chacha.rs index 0050048b277..ae59751fc28 100644 --- a/src/prng/chacha.rs +++ b/src/prng/chacha.rs @@ -199,16 +199,22 @@ impl ChaChaRng { /// Refill the internal output buffer (`self.buffer`) fn update(&mut self) { - let mut tmp = self.state; - - for _ in 0..self.rounds / 2 { - double_round!(tmp); - } - - for i in 0..STATE_WORDS { - self.buffer[i] = tmp[i].wrapping_add(self.state[i]); + // For some reason extracting this part into a seperate function + // improves performance by 50%. + fn core(results: &mut [u32; STATE_WORDS], + state: &[u32; STATE_WORDS], + rounds: usize) + { + let mut tmp = *state; + for _ in 0..rounds / 2 { + double_round!(tmp); + } + for i in 0..STATE_WORDS { + results[i] = tmp[i].wrapping_add(state[i]); + } } + core(&mut self.buffer, &self.state, self.rounds); self.index = 0; // update 128-bit counter self.state[12] = self.state[12].wrapping_add(1); From 8abe13afeff252b0024ff47fbdc2da2b9ad967de Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sun, 21 Jan 2018 08:32:57 +0100 Subject: [PATCH 05/11] Fold ChaCha init into from_seed --- src/prng/chacha.rs | 29 ++++++++++++----------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/src/prng/chacha.rs b/src/prng/chacha.rs index ae59751fc28..916d2755d58 100644 --- a/src/prng/chacha.rs +++ b/src/prng/chacha.rs @@ -122,7 +122,7 @@ impl ChaChaRng { /// - 2917185654 /// - 2419978656 pub fn new_unseeded() -> ChaChaRng { - ChaChaRng::init([0; SEED_WORDS]) + ChaChaRng::from_seed([0; SEED_WORDS*4]) } /// Sets the internal 128-bit ChaCha counter to a user-provided value. This @@ -184,19 +184,6 @@ impl ChaChaRng { self.index = STATE_WORDS; // force recomputation on next use } - /// Initializes `self.state` with the appropriate key and constants - fn init(seed: [u32; SEED_WORDS]) -> Self { - ChaChaRng { - buffer: [0; STATE_WORDS], - state: [0x61707865, 0x3320646E, 0x79622D32, 0x6B206574, // constants - seed[0], seed[1], seed[2], seed[3], // seed - seed[4], seed[5], seed[6], seed[7], // seed - 0, 0, 0, 0], // counter - index: STATE_WORDS, // generate on first use - rounds: 20, - } - } - /// Refill the internal output buffer (`self.buffer`) fn update(&mut self) { // For some reason extracting this part into a seperate function @@ -256,9 +243,17 @@ impl Rng for ChaChaRng { impl SeedableRng for ChaChaRng { type Seed = [u8; SEED_WORDS*4]; fn from_seed(seed: Self::Seed) -> Self { - let mut seed_u32 = [0u32; SEED_WORDS]; - le::read_u32_into(&seed, &mut seed_u32); - ChaChaRng::init(seed_u32) + let mut seed_le = [0u32; SEED_WORDS]; + le::read_u32_into(&seed, &mut seed_le); + Self { + buffer: [0; STATE_WORDS], + state: [0x61707865, 0x3320646E, 0x79622D32, 0x6B206574, // constants + seed_le[0], seed_le[1], seed_le[2], seed_le[3], // seed + seed_le[4], seed_le[5], seed_le[6], seed_le[7], // seed + 0, 0, 0, 0], // counter + index: STATE_WORDS, // generate on first use + rounds: 20, + } } } From feb5b10dd456f245db71301820fb43c952ed5844 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sun, 21 Jan 2018 13:42:06 +0100 Subject: [PATCH 06/11] Address review comments --- src/prng/chacha.rs | 30 +++++++----------------------- 1 file changed, 7 insertions(+), 23 deletions(-) diff --git a/src/prng/chacha.rs b/src/prng/chacha.rs index 916d2755d58..ff5f07b8ba1 100644 --- a/src/prng/chacha.rs +++ b/src/prng/chacha.rs @@ -25,7 +25,7 @@ const STATE_WORDS: usize = 16; /// selected as one of the "stream ciphers suitable for widespread adoption" by /// eSTREAM [2]. /// -/// ChaCha uses add-rotate-xor (ARX) operations as basis. These are safe +/// ChaCha uses add-rotate-xor (ARX) operations as its basis. These are safe /// against timing attacks, although that is mostly a concern for ciphers and /// not for RNGs. Also it is very suitable for SIMD implementation. /// Here we do not provide a SIMD implementation yet, except for what is @@ -33,9 +33,9 @@ const STATE_WORDS: usize = 16; /// /// With the ChaCha algorithm it is possible to choose the number of rounds the /// core algorithm should run. By default `ChaChaRng` is created as ChaCha20, -/// with means 20 rounds. The number of rounds is a tradeoff between performance -/// an security, 8 rounds are considered the minimum to be secure. A different -/// number of rounds can be set with [`set_rounds`]. +/// which means 20 rounds. The number of rounds is a tradeoff between performance +/// and security, 8 rounds are considered the minimum to be secure. A different +/// number of rounds can be set using [`set_rounds`]. /// /// We deviate slightly from the ChaCha specification regarding the nonce, which /// is used to extend the counter to 128 bits. This is provably as strong as the @@ -132,8 +132,9 @@ impl ChaChaRng { /// The 128 bits used for the counter overlap with the nonce and smaller /// counter of ChaCha when used as a stream cipher. It is in theory possible /// to use `set_counter` to obtain the conventional ChaCha pseudorandom - /// stream associated with a particular nonce, but that is not a supported - /// use of this method. + /// stream associated with a particular nonce. This is not a supported use + /// of the RNG, because a nonce set that way is not treated as a constant + /// value but still as part of the counter, besides endian issues. /// /// # Examples /// @@ -381,23 +382,6 @@ mod test { assert_eq!(results, expected); } - #[test] - fn test_chacha_set_counter() { - // Test vector 5 from - // https://tools.ietf.org/html/draft-nir-cfrg-chacha20-poly1305-04 - let seed = [0u8; 32]; - let mut rng: ChaChaRng = SeedableRng::from_seed(seed); - rng.set_counter(0, 2u64.to_be()); - - let mut results = [0u32; 16]; - for i in results.iter_mut() { *i = rng.next_u32(); } - let expected = [0x374dc6c2, 0x3736d58c, 0xb904e24a, 0xcd3f93ef, - 0x88228b1a, 0x96a4dfb3, 0x5b76ab72, 0xc727ee54, - 0x0e0e978a, 0xf3145c95, 0x1b748ea8, 0xf786c297, - 0x99c28f5f, 0x628314e8, 0x398a19fa, 0x6ded1b53]; - assert_eq!(results, expected); - } - #[test] fn test_chacha_set_rounds() { let seed = [0u8; 32]; From 4a44964fc6d80b15845ac4170c7cec17fdc9db8c Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sun, 21 Jan 2018 13:52:23 +0100 Subject: [PATCH 07/11] Add benchmarks for ChaChaRng with 8 and 12 rounds --- benches/generators.rs | 52 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 48 insertions(+), 4 deletions(-) diff --git a/benches/generators.rs b/benches/generators.rs index fe54a883510..8f91da7d27b 100644 --- a/benches/generators.rs +++ b/benches/generators.rs @@ -33,11 +33,9 @@ gen_bytes!(gen_bytes_xorshift, XorShiftRng); gen_bytes!(gen_bytes_hc128, Hc128Rng); gen_bytes!(gen_bytes_isaac, IsaacRng); gen_bytes!(gen_bytes_isaac64, Isaac64Rng); -gen_bytes!(gen_bytes_chacha, ChaChaRng); gen_bytes!(gen_bytes_std, StdRng); gen_bytes!(gen_bytes_os, OsRng); - macro_rules! gen_uint { ($fnn:ident, $ty:ty, $gen:ident) => { #[bench] @@ -72,7 +70,6 @@ gen_uint!(gen_u32_xorshift, u32, XorShiftRng); gen_uint!(gen_u32_hc128, u32, Hc128Rng); gen_uint!(gen_u32_isaac, u32, IsaacRng); gen_uint!(gen_u32_isaac64, u32, Isaac64Rng); -gen_uint!(gen_u32_chacha, u32, ChaChaRng); gen_uint_new!(gen_u32_std, u32, StdRng); gen_uint_new!(gen_u32_os, u32, OsRng); @@ -80,10 +77,11 @@ gen_uint!(gen_u64_xorshift, u64, XorShiftRng); gen_uint!(gen_u64_hc128, u64, Hc128Rng); gen_uint!(gen_u64_isaac, u64, IsaacRng); gen_uint!(gen_u64_isaac64, u64, Isaac64Rng); -gen_uint!(gen_u64_chacha, u64, ChaChaRng); gen_uint_new!(gen_u64_std, u64, StdRng); gen_uint_new!(gen_u64_os, u64, OsRng); +// Do not test JitterRng like the others by running it RAND_BENCH_N times per, +// measurement, because it is way to slow. Only run it once #[bench] fn gen_u64_jitter(b: &mut Bencher) { let mut rng = JitterRng::new().unwrap(); @@ -118,3 +116,49 @@ fn init_jitter(b: &mut Bencher) { black_box(JitterRng::new().unwrap()); }); } + +macro_rules! chacha_rounds { + ($fn1:ident, $fn2:ident, $fn3:ident, $rounds:expr) => { + #[bench] + fn $fn1(b: &mut Bencher) { + let mut rng = ChaChaRng::new().unwrap(); + rng.set_rounds($rounds); + let mut buf = [0u8; BYTES_LEN]; + b.iter(|| { + for _ in 0..RAND_BENCH_N { + rng.fill_bytes(&mut buf); + black_box(buf); + } + }); + b.bytes = BYTES_LEN as u64 * RAND_BENCH_N; + } + + #[bench] + fn $fn2(b: &mut Bencher) { + let mut rng = ChaChaRng::new().unwrap(); + rng.set_rounds($rounds); + b.iter(|| { + for _ in 0..RAND_BENCH_N { + black_box(rng.gen::()); + } + }); + b.bytes = size_of::() as u64 * RAND_BENCH_N; + } + + #[bench] + fn $fn3(b: &mut Bencher) { + let mut rng = ChaChaRng::new().unwrap(); + rng.set_rounds($rounds); + b.iter(|| { + for _ in 0..RAND_BENCH_N { + black_box(rng.gen::()); + } + }); + b.bytes = size_of::() as u64 * RAND_BENCH_N; + } + } +} + +chacha_rounds!(gen_bytes_chacha8, gen_u32_chacha8, gen_u64_chacha8, 8); +chacha_rounds!(gen_bytes_chacha12, gen_u32_chacha12, gen_u64_chacha12, 12); +chacha_rounds!(gen_bytes_chacha20, gen_u32_chacha20, gen_u64_chacha20, 20); From c17f7f13100ac688c59a5fcc9a9a2e5672812a88 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Wed, 24 Jan 2018 07:28:43 +0100 Subject: [PATCH 08/11] Fix for Rust 1.15 --- src/prng/chacha.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/prng/chacha.rs b/src/prng/chacha.rs index ff5f07b8ba1..4c3e8d45bb5 100644 --- a/src/prng/chacha.rs +++ b/src/prng/chacha.rs @@ -246,7 +246,7 @@ impl SeedableRng for ChaChaRng { fn from_seed(seed: Self::Seed) -> Self { let mut seed_le = [0u32; SEED_WORDS]; le::read_u32_into(&seed, &mut seed_le); - Self { + ChaChaRng { buffer: [0; STATE_WORDS], state: [0x61707865, 0x3320646E, 0x79622D32, 0x6B206574, // constants seed_le[0], seed_le[1], seed_le[2], seed_le[3], // seed From fb5b3bb2ffc0dc1ca30c04e58817e4989ef1b96f Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Wed, 24 Jan 2018 07:41:44 +0100 Subject: [PATCH 09/11] Add back test_chacha_set_counter --- src/prng/chacha.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/prng/chacha.rs b/src/prng/chacha.rs index 4c3e8d45bb5..df4daa88109 100644 --- a/src/prng/chacha.rs +++ b/src/prng/chacha.rs @@ -382,6 +382,25 @@ mod test { assert_eq!(results, expected); } + #[test] + fn test_chacha_set_counter() { + // Test vector 5 from + // https://tools.ietf.org/html/draft-nir-cfrg-chacha20-poly1305-04 + // Although we do not support setting a nonce, we try it here anyway so + // we can use this test vector. + let seed = [0u8; 32]; + let mut rng: ChaChaRng = SeedableRng::from_seed(seed); + rng.set_counter(0, 2u64 << 56); + + let mut results = [0u32; 16]; + for i in results.iter_mut() { *i = rng.next_u32(); } + let expected = [0x374dc6c2, 0x3736d58c, 0xb904e24a, 0xcd3f93ef, + 0x88228b1a, 0x96a4dfb3, 0x5b76ab72, 0xc727ee54, + 0x0e0e978a, 0xf3145c95, 0x1b748ea8, 0xf786c297, + 0x99c28f5f, 0x628314e8, 0x398a19fa, 0x6ded1b53]; + assert_eq!(results, expected); + } + #[test] fn test_chacha_set_rounds() { let seed = [0u8; 32]; From 12e911cdf33c4e643eb571e5f58a24f94daedee6 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Wed, 24 Jan 2018 20:16:12 +0100 Subject: [PATCH 10/11] Fix spelling mistakes --- benches/generators.rs | 2 +- src/prng/chacha.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/benches/generators.rs b/benches/generators.rs index 8f91da7d27b..044d7b24e89 100644 --- a/benches/generators.rs +++ b/benches/generators.rs @@ -81,7 +81,7 @@ gen_uint_new!(gen_u64_std, u64, StdRng); gen_uint_new!(gen_u64_os, u64, OsRng); // Do not test JitterRng like the others by running it RAND_BENCH_N times per, -// measurement, because it is way to slow. Only run it once +// measurement, because it is way too slow. Only run it once. #[bench] fn gen_u64_jitter(b: &mut Bencher) { let mut rng = JitterRng::new().unwrap(); diff --git a/src/prng/chacha.rs b/src/prng/chacha.rs index df4daa88109..c74507619e1 100644 --- a/src/prng/chacha.rs +++ b/src/prng/chacha.rs @@ -187,7 +187,7 @@ impl ChaChaRng { /// Refill the internal output buffer (`self.buffer`) fn update(&mut self) { - // For some reason extracting this part into a seperate function + // For some reason extracting this part into a separate function // improves performance by 50%. fn core(results: &mut [u32; STATE_WORDS], state: &[u32; STATE_WORDS], From c320c7dd26fc21531054f17cffb29d271d51f6be Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 25 Jan 2018 08:30:14 +0100 Subject: [PATCH 11/11] Replace `SeedableRng::from_seed` with `ChaChaRng::from_seed` --- src/prng/chacha.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/prng/chacha.rs b/src/prng/chacha.rs index c74507619e1..192253decd6 100644 --- a/src/prng/chacha.rs +++ b/src/prng/chacha.rs @@ -281,7 +281,7 @@ mod test { // Test vectors 1 and 2 from // https://tools.ietf.org/html/draft-nir-cfrg-chacha20-poly1305-04 let seed = [0u8; 32]; - let mut rng: ChaChaRng = SeedableRng::from_seed(seed); + let mut rng = ChaChaRng::from_seed(seed); let mut results = [0u32; 16]; for i in results.iter_mut() { *i = rng.next_u32(); } @@ -307,7 +307,7 @@ mod test { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]; - let mut rng: ChaChaRng = SeedableRng::from_seed(seed); + let mut rng = ChaChaRng::from_seed(seed); // Skip block 0 for _ in 0..16 { rng.next_u32(); } @@ -336,13 +336,13 @@ mod test { let mut results = [0u32; 16]; // Test block 2 by skipping block 0 and 1 - let mut rng1: ChaChaRng = SeedableRng::from_seed(seed); + let mut rng1 = ChaChaRng::from_seed(seed); for _ in 0..32 { rng1.next_u32(); } for i in results.iter_mut() { *i = rng1.next_u32(); } assert_eq!(results, expected); // Test block 2 by using `set_counter` - let mut rng2: ChaChaRng = SeedableRng::from_seed(seed); + let mut rng2 = ChaChaRng::from_seed(seed); rng2.set_counter(2, 0); for i in results.iter_mut() { *i = rng2.next_u32(); } assert_eq!(results, expected); @@ -351,7 +351,7 @@ mod test { #[test] fn test_chacha_multiple_blocks() { let seed = [0,0,0,0, 1,0,0,0, 2,0,0,0, 3,0,0,0, 4,0,0,0, 5,0,0,0, 6,0,0,0, 7,0,0,0]; - let mut rng: ChaChaRng = SeedableRng::from_seed(seed); + let mut rng = ChaChaRng::from_seed(seed); // Store the 17*i-th 32-bit word, // i.e., the i-th word of the i-th 16-word block @@ -372,7 +372,7 @@ mod test { #[test] fn test_chacha_true_bytes() { let seed = [0u8; 32]; - let mut rng: ChaChaRng = SeedableRng::from_seed(seed); + let mut rng = ChaChaRng::from_seed(seed); let mut results = [0u8; 32]; rng.fill_bytes(&mut results); let expected = [118, 184, 224, 173, 160, 241, 61, 144, @@ -389,7 +389,7 @@ mod test { // Although we do not support setting a nonce, we try it here anyway so // we can use this test vector. let seed = [0u8; 32]; - let mut rng: ChaChaRng = SeedableRng::from_seed(seed); + let mut rng = ChaChaRng::from_seed(seed); rng.set_counter(0, 2u64 << 56); let mut results = [0u32; 16]; @@ -404,7 +404,7 @@ mod test { #[test] fn test_chacha_set_rounds() { let seed = [0u8; 32]; - let mut rng: ChaChaRng = SeedableRng::from_seed(seed); + let mut rng = ChaChaRng::from_seed(seed); rng.set_rounds(8); let mut results = [0u32; 16]; @@ -420,7 +420,7 @@ mod test { #[test] fn test_chacha_clone() { let seed = [0,0,0,0, 1,0,0,0, 2,0,0,0, 3,0,0,0, 4,0,0,0, 5,0,0,0, 6,0,0,0, 7,0,0,0]; - let mut rng: ChaChaRng = SeedableRng::from_seed(seed); + let mut rng = ChaChaRng::from_seed(seed); let mut clone = rng.clone(); for _ in 0..16 { assert_eq!(rng.next_u64(), clone.next_u64());