From 07e1d58c16b063add71a608604dca8c9ec8d1d14 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 30 Oct 2017 19:58:57 +0100 Subject: [PATCH 1/6] Implement `JitterRng`, based on `jitterentropy-library`. This is a pretty direct translation from C to Rust. Some notes per function: ### `random_loop_cnt` (`jent_loop_shuffle`) Because the `min` argument was always `0`, I removed that argument. The C code did not seem to fold the time optimally. When `bits` is set to `7`, as `mem_access` does, it will fold `64 / 7 = 9` times, leaving 1 bit unused. It is minor, but we use `folds = (64 + n_bits - 1) / n_bits;`, so all is used. We do not add 1 to the resulting loop count, this should be done in the calling code. `memaccess` already adds 128 anyway. For `lfsr_time` we use the loop count on a `throw_away` value, and then run the real calculation once. ### `lfsr_time` (`jent_lfsr_time`) We do not allow overriding `loop_cnt`, and also do not return the actual `loop_cnt` used. This only had some use in testing the C code, but was 'not needed during runtime'. Only the last round of the outer loop (in C) effect `self.data`. In Rust the inner loop is part of the `lfsr` helper function. All but the last round operate on a `throw_away` function, that does not get reset between the loop rounds. ### `memaccess` (`jent_memaccess`) We do not allow overriding `loop_cnt`, and also do not return the actual `loop_cnt` used. This only had some use in testing the C code, but was 'not needed during runtime'. We do not do NULL pointer checks, and running `JitterRng` without the Memory Access noise source is (currently) not supported. We (currently) do not support changing `memblocksize` and `memblocks` except by changing the constants `MEMORY_BLOCKS` and `MEMORY_BLOCKSIZE`. So instead of recalculating `wrap`, we can just re-use MEMORY_SIZE. Instead of a `memlocation` pointer we use indexing. The `index` is calculated before accessing `self.mem[index] instead of after. This convinces the compiler indexing is safe, and eliminates bounds checks. ### `stuck` (`jent_stuck`) For all delta's we use an `i64`, instead of an `u64` for the first delta in the C implementation. This handles a clock that may not be entirely monotonic (for example due to NTP) slightly better. Also, we return a `bool` instead of an `u64`. ### `measure_jitter` (`jent_measure_jitter`) It seemed clearer here to not return an `u64` or `bool`, but `Option<()>`. `Some` and `None` indicate clearly whether we have been able to add some entropy. For `current_delta` we use an `i64` instead of an `u64`. It is cast back to an `u64` for `lfsr_time`, which only cares about bits. ### `stir_pool` (`jent_stir_pool`) The C code does something difficult with initializing an `u64` with two `u32`'s in an `union`. The numbers it uses for initialization are from SHA-1, and the order does not really matter. The Rust code just sets the `u64`'s directly, and uses the constants in the order they appear in FIPS 180-4 section 5.3.1. The function tries to be constant time to prevent leaking timing information about the generated random number. Using a `trow_away` value like it does is optimised out, and I don't trust branches to be constant time. I used a bit mask trick instead, and verified the assembly does not include anything conditional. Not sure it matters anything, we just went through a lot of effort to have as much timing variance as possible to generate the random number. ### `gen_entropy` (`jent_gen_entropy`) We do not support oversampling, so no need to repeat the loop more times. `self.memaccess()` in `measure_jitter` is easily optimised out, because LLVM recognises we never read the results. Therefore we do a single read from `self.mem`, hidden to the optimizer with `black_box()`. We return `self.data` instead of requiring the calling code to read from it. ### (not included) `jent_read_entropy` Here we have the convienent `fill_bytes_via_u64` for. The C code calls `jent_gen_entropy` one last time after filling the buffer, to make sure that something reading the processes memory can not read the last generated random number. 'This call reduces the speed of the RNG by up to half`. It seems to me setting it to 0 is just as good. I did not bother with this. As an alternative a user caring very much about this can just call `next_u64` after receiving a result. ### `entropy_init` (`jent_entropy_init`) Wrap `lfsr_time` in the loop in a `black_box` to make sure it is not optimised out. For delta we use an `i64` instead of an `u64`. Instead of `lowdelta` we just use `delta`. It seems a hack to compile on some 32-bit architectures. --- benches/generators.rs | 25 +- src/jitter_rng.rs | 596 ++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 2 + 3 files changed, 617 insertions(+), 6 deletions(-) create mode 100644 src/jitter_rng.rs diff --git a/benches/generators.rs b/benches/generators.rs index 44adb0e1c2d..a48752cca31 100644 --- a/benches/generators.rs +++ b/benches/generators.rs @@ -9,7 +9,7 @@ const BYTES_LEN: usize = 1024; use std::mem::size_of; use test::{black_box, Bencher}; -use rand::{Rng, NewSeeded, SeedFromRng, StdRng, OsRng, Rand, Default}; +use rand::{Rng, NewSeeded, SeedFromRng, StdRng, OsRng, JitterRng, Rand, Default}; use rand::prng::{XorShiftRng, IsaacRng, Isaac64Rng, ChaChaRng}; macro_rules! gen_bytes { @@ -59,15 +59,22 @@ gen_usize!(gen_usize_chacha, ChaChaRng); gen_usize!(gen_usize_std, StdRng); gen_usize!(gen_usize_os, OsRng); +#[bench] +fn gen_usize_jitter(b: &mut Bencher) { + let mut rng = JitterRng::new().unwrap(); + b.iter(|| { + black_box(usize::rand(&mut rng, Default)); + }); + b.bytes = size_of::() as u64 * RAND_BENCH_N; +} + macro_rules! init_gen { ($fnn:ident, $gen:ident) => { #[bench] fn $fnn(b: &mut Bencher) { - let mut rng = XorShiftRng::new().unwrap(); + let mut rng = OsRng::new().unwrap(); b.iter(|| { - for _ in 0..RAND_BENCH_N { - black_box($gen::from_rng(&mut rng).unwrap()); - } + black_box($gen::from_rng(&mut rng).unwrap()); }); } } @@ -77,4 +84,10 @@ init_gen!(init_xorshift, XorShiftRng); init_gen!(init_isaac, IsaacRng); init_gen!(init_isaac64, Isaac64Rng); init_gen!(init_chacha, ChaChaRng); -init_gen!(init_std, StdRng); + +#[bench] +fn init_jitter(b: &mut Bencher) { + b.iter(|| { + black_box(JitterRng::new().unwrap()); + }); +} diff --git a/src/jitter_rng.rs b/src/jitter_rng.rs new file mode 100644 index 00000000000..25fbc26126a --- /dev/null +++ b/src/jitter_rng.rs @@ -0,0 +1,596 @@ +// Copyright 2017 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. +// +// Based on jitterentropy-library +// +// FIXME: does the 3-clause BSD license below allow us to dual-license with the +// MIT license? +// +// Copyright Stephan Mueller , 2014 - 2017 +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, and the entire permission notice in its entirety, +// including the disclaimer of warranties. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// 3. The name of the author may not be used to endorse or promote +// products derived from this software without specific prior +// written permission. +// +// THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED +// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF +// WHICH ARE HEREBY DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT +// OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR +// BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH +// DAMAGE. + +//! Non-physical true random number generator based on timing jitter. + +use {CryptoRng, Rng, Error}; +use rand_core; +use rand_core::impls; + +use core; +use core::fmt; + +const MEMORY_BLOCKS: usize = 64; +const MEMORY_BLOCKSIZE: usize = 32; +const MEMORY_SIZE: usize = MEMORY_BLOCKS * MEMORY_BLOCKSIZE; + +/// A true random number generator based on jitter in the CPU execution time, +/// and jitter in memory access time. +/// +/// This is a true random number generator, as opposed to pseudo-random +/// generators. Random numbers generated by `JitterRng` can be seen as fresh +/// entropy. A consequence is that is orders of magnitude slower than `OsRng` +/// and PRNGs (about 10^3 .. 10^6 slower). +/// +/// There are very few situations where using this RNG is appropriate. Only very +/// few applications require true entropy. A normal PRNG can be statistically +/// indistinguishable, and a cryptographic PRNG should also be as impossible to +/// predict. +/// +/// Use of `JitterRng` is recommended for initializing cryptographic PRNGs when +/// `OsRng` is not available. +/// +/// This implementation is based on +/// [Jitterentropy](http://www.chronox.de/jent.html) version 2.1.0. +// +// Note: the C implementation relies on being compiled without optimizations. +// This implementation goes through lengths to make the compiler not optimise +// out what is technically dead code, but that does influence timing jitter. +pub struct JitterRng { + data: u64, // Actual random number + // Timer and previous time stamp, used by `measure_jitter` + timer: fn() -> u64, + prev_time: u64, + // Deltas used for the stuck test + last_delta: i64, + last_delta2: i64, + // Memory for the Memory Access noise source + mem_prev_index: usize, + mem: [u8; MEMORY_SIZE], + // Make `next_u32` not waste 32 bits + data_remaining: Option, +} + +// Custom Debug implementation that does not expose the internal state +impl fmt::Debug for JitterRng { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "JitterRng {{}}") + } +} + +/// An error that can occur when intializing `JitterRng`. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Error { + kind: ErrorKind, +} + +/// Error kind which can be matched over. +#[derive(PartialEq, Eq, Debug, Copy, Clone)] +pub enum ErrorKind { + /// No timer available. + NoTimer, + /// Timer too coarse to use as an entropy source. + CoarseTimer, + /// Timer is not monotonically increasing. + NotMonotonic, + /// Variations of deltas of time too small. + TinyVariantions, + /// Too many stuck results (indicating no added entropy). + ToManyStuck, + #[doc(hidden)] + __Nonexhaustive, +} + +impl ErrorKind { + fn description(&self) -> &'static str { + match *self { + ErrorKind::NoTimer => "no timer available", + ErrorKind::CoarseTimer => "coarse timer", + ErrorKind::NotMonotonic => "timer not monotonic", + ErrorKind::TinyVariantions => "time delta variations too small", + ErrorKind::ToManyStuck => "to many stuck results", + ErrorKind::__Nonexhaustive => unreachable!(), + } + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self.kind { + ErrorKind::NoTimer => + write!(f, "No timer available."), + ErrorKind::CoarseTimer => + write!(f, "Timer too coarse to use as an entropy source."), + ErrorKind::NotMonotonic => + write!(f, "Timer is not monotonically increasing."), + ErrorKind::TinyVariantions => + write!(f, "Variations of deltas of time too small."), + ErrorKind::ToManyStuck => + write!(f, "To many stuck results (indicating no added entropy)."), + ErrorKind::__Nonexhaustive => unreachable!(), + } + } +} + +#[cfg(feature="std")] +impl ::std::error::Error for Error { + fn description(&self) -> &str { + self.kind.description() + } +} + +#[cfg(feature="std")] +fn new_error(kind: ErrorKind) -> rand_core::Error { + rand_core::Error { + kind: rand_core::ErrorKind::Unavailable, + cause: Some(Box::new(Error { kind: kind })), + } +} + +#[cfg(not(feature="std"))] +fn new_error(kind: ErrorKind) -> rand_core::Error { + rand_core::Error { + kind: rand_core::ErrorKind::Unavailable, + cause: kind.description(), + } +} + +impl JitterRng { + /// Create a new `JitterRng`. + /// Makes use of `std::time` for a timer. + /// + /// During initialization CPU execution timing jitter is measured a few + /// hundred times. If this does not pass basic quality tests, an error is + /// returned. + #[cfg(feature="std")] + pub fn new() -> Result { + JitterRng::new_with_timer(get_nstime) + } + + /// Create a new `JitterRng`. + /// A custom timer can be supplied, making it possible to use `JitterRng` in + /// `no_std` environments. + /// + /// The timer must have nanosecond precision. + /// + /// During initialization CPU execution timing jitter is measured a few + /// hundred times. If this does not pass basic quality tests, an error is + /// returned. + pub fn new_with_timer(timer: fn() -> u64) + -> Result { + let mut ec = JitterRng { + data: 0, + timer: timer, + prev_time: 0, + last_delta: 0, + last_delta2: 0, + mem_prev_index: 0, + mem: [0; MEMORY_SIZE], + data_remaining: None, + }; + + ec.entropy_init()?; + Ok(ec) + } + + // Calculate a random loop count used for the next round of an entropy + // collection, based on bits from a fresh value from the timer. + // + // The timer is folded to produce a number that contains at most `n_bits` + // bits. + // + // Note: A constant should be added to the resulting random loop count to + // prevent loops that run 0 times. + #[inline(never)] + fn random_loop_cnt(&mut self, n_bits: u32) -> u32 { + let mut rounds = 0; + + let mut time = (self.timer)(); + // Mix with the current state of the random number balance the random + // loop counter a bit more. + time ^= self.data; + + // We fold the time value as much as possible to ensure that as many + // bits of the time stamp are included as possible. + let folds = (64 + n_bits - 1) / n_bits; + let mask = (1 << n_bits) - 1; + for _ in 0..folds { + rounds ^= time & mask; + time = time >> n_bits; + } + + rounds as u32 + } + + // CPU jitter noise source + // Noise source based on the CPU execution time jitter + // + // This function injects the individual bits of the time value into the + // entropy pool using an LFSR. + // + // The code is deliberately inefficient with respect to the bit shifting. + // This function not only acts as folding operation, but this function's + // execution is used to measure the CPU execution time jitter. Any change to + // the loop in this function implies that careful retesting must be done. + #[inline(never)] + fn lfsr_time(&mut self, time: u64) { + fn lfsr(mut data: u64, time: u64) -> u64{ + for i in 1..65 { + let mut tmp = time << (64 - i); + tmp = tmp >> (64 - 1); + + // Fibonacci LSFR with polynomial of + // x^64 + x^61 + x^56 + x^31 + x^28 + x^23 + 1 which is + // primitive according to + // http://poincare.matf.bg.ac.rs/~ezivkovm/publications/primpol1.pdf + // (the shift values are the polynomial values minus one + // due to counting bits from 0 to 63). As the current + // position is always the LSB, the polynomial only needs + // to shift data in from the left without wrap. + data ^= tmp; + data ^= (data >> 63) & 1; + data ^= (data >> 60) & 1; + data ^= (data >> 55) & 1; + data ^= (data >> 30) & 1; + data ^= (data >> 27) & 1; + data ^= (data >> 22) & 1; + data = data.rotate_left(1); + } + data + } + + // Note: in the reference implementation only the last round effects + // `self.data`, all the other results are ignored. To make sure the + // other rounds are not optimised out, we first run all but the last + // round on a throw-away value instead of the real `self.data`. + let mut throw_away: u64 = 0; + for _ in 0..(self.random_loop_cnt(4)) { + throw_away = lfsr(throw_away, time); + } + black_box(throw_away); + + self.data = lfsr(self.data, time); + } + + // Memory Access noise source + // This is a noise source based on variations in memory access times + // + // This function performs memory accesses which will add to the timing + // variations due to an unknown amount of CPU wait states that need to be + // added when accessing memory. The memory size should be larger than the L1 + // caches as outlined in the documentation and the associated testing. + // + // The L1 cache has a very high bandwidth, albeit its access rate is usually + // slower than accessing CPU registers. Therefore, L1 accesses only add + // minimal variations as the CPU has hardly to wait. Starting with L2, + // significant variations are added because L2 typically does not belong to + // the CPU any more and therefore a wider range of CPU wait states is + // necessary for accesses. L3 and real memory accesses have even a wider + // range of wait states. However, to reliably access either L3 or memory, + // the `self.mem` memory must be quite large which is usually not desirable. + #[inline(never)] + fn memaccess(&mut self) { + let mut index = self.mem_prev_index; + for _ in 0..(self.random_loop_cnt(7) + 128) { + // Addition of memblocksize - 1 to index with wrap around logic to + // ensure that every memory location is hit evenly. + // The modulus also allows the compiler to remove the indexing + // bounds check. + index = (index + MEMORY_BLOCKSIZE - 1) % MEMORY_SIZE; + + // memory access: just add 1 to one byte + // memory access implies read from and write to memory location + let tmp = self.mem[index]; + self.mem[index] = tmp.wrapping_add(1); + } + self.mem_prev_index = index; + } + + + // Stuck test by checking the: + // - 1st derivation of the jitter measurement (time delta) + // - 2nd derivation of the jitter measurement (delta of time deltas) + // - 3rd derivation of the jitter measurement (delta of delta of time + // deltas) + // + // All values must always be non-zero. + // This test is a heuristic to see whether the last measurement holds + // entropy. + fn stuck(&mut self, current_delta: i64) -> bool { + let delta2 = self.last_delta - current_delta; + let delta3 = delta2 - self.last_delta2; + + self.last_delta = current_delta; + self.last_delta2 = delta2; + + current_delta == 0 || delta2 == 0 || delta3 == 0 + } + + // This is the heart of the entropy generation: calculate time deltas and + // use the CPU jitter in the time deltas. The jitter is injected into the + // entropy pool. + // + // Ensure that `self.prev_time` is primed before using the output of this + // function. This can be done by calling this function and not using its + // result. + fn measure_jitter(&mut self) -> Option<()> { + // Invoke one noise source before time measurement to add variations + self.memaccess(); + + // Get time stamp and calculate time delta to previous + // invocation to measure the timing variations + let time = (self.timer)(); + let current_delta = time.wrapping_sub(self.prev_time) as i64; + self.prev_time = time; + + // Call the next noise source which also injects the data + self.lfsr_time(current_delta as u64); + + // Check whether we have a stuck measurement (i.e. does the last + // measurement holds entropy?). + if self.stuck(current_delta) { return None }; + + // Rotate the data buffer by a prime number (any odd number would + // do) to ensure that every bit position of the input time stamp + // has an even chance of being merged with a bit position in the + // entropy pool. We do not use one here as the adjacent bits in + // successive time deltas may have some form of dependency. The + // chosen value of 7 implies that the low 7 bits of the next + // time delta value is concatenated with the current time delta. + self.data = self.data.rotate_left(7); + + Some(()) + } + + // Shuffle the pool a bit by mixing some value with a bijective function + // (XOR) into the pool. + // + // The function generates a mixer value that depends on the bits set and + // the location of the set bits in the random number generated by the + // entropy source. Therefore, based on the generated random number, this + // mixer value can have 2^64 different values. That mixer value is + // initialized with the first two SHA-1 constants. After obtaining the + // mixer value, it is XORed into the random number. + // + // The mixer value is not assumed to contain any entropy. But due to the + // XOR operation, it can also not destroy any entropy present in the + // entropy pool. + #[inline(never)] + fn stir_pool(&mut self) { + // This constant is derived from the first two 32 bit initialization + // vectors of SHA-1 as defined in FIPS 180-4 section 5.3.1 + // The order does not really matter as we do not rely on the specific + // numbers. We just pick the SHA-1 constants as they have a good mix of + // bit set and unset. + const CONSTANT: u64 = 0x67452301efcdab89; + + // The start value of the mixer variable is derived from the third + // and fourth 32 bit initialization vector of SHA-1 as defined in + // FIPS 180-4 section 5.3.1 + let mut mixer = 0x98badcfe10325476; + + // This is a constant time function to prevent leaking timing + // information about the random number. + // The normal code is: + // ``` + // for i in 0..64 { + // if ((self.data >> i) & 1) == 1 { mixer ^= CONSTANT; } + // } + // ``` + // This is a bit fragile, as LLVM really wants to use branches here, and + // we rely on it to not recognise the opportunity. + for i in 0..64 { + let apply = (self.data >> i) & 1; + let mask = !((apply as i64) - 1) as u64; + mixer ^= CONSTANT & mask; + mixer = mixer.rotate_left(1); + } + + self.data ^= mixer; + } + + fn gen_entropy(&mut self) -> u64 { + // Prime `self.prev_time`, and run the noice sources to make sure the + // first loop round collects the expected entropy. + let _ = self.measure_jitter(); + + for _ in 0..64 { + // If a stuck measurement is received, repeat measurement + // Note: we do not guard against an infinite loop, that would mean + // the timer suddenly became broken. + while self.measure_jitter().is_none() {} + } + + self.stir_pool(); + + // Do a single read from `self.mem` to make sure the Memory Access noise + // source is not optimised out. + black_box(self.mem[0]); + + self.data + } + + fn entropy_init(&mut self) -> Result<(), rand_core::Error> { + // We could add a check for system capabilities such as `clock_getres` + // or check for `CONFIG_X86_TSC`, but it does not make much sense as the + // following sanity checks verify that we have a high-resolution timer. + + let mut delta_sum = 0; + let mut old_delta = 0; + + let mut time_backwards = 0; + let mut count_mod = 0; + let mut count_stuck = 0; + + // TESTLOOPCOUNT needs some loops to identify edge systems. + // 100 is definitely too little. + const TESTLOOPCOUNT: u64 = 300; + const CLEARCACHE: u64 = 100; + + for i in 0..(CLEARCACHE + TESTLOOPCOUNT) { + // Invoke core entropy collection logic + let time = (self.timer)(); + self.lfsr_time(time); + let time2 = (self.timer)(); + + // Test whether timer works + if time == 0 || time2 == 0 { + return Err(new_error(ErrorKind::NoTimer)); + } + let delta = time2.wrapping_sub(time) as i64; + + // Test whether timer is fine grained enough to provide delta even + // when called shortly after each other -- this implies that we also + // have a high resolution timer + if delta == 0 { + return Err(new_error(ErrorKind::CoarseTimer)); + } + + // Up to here we did not modify any variable that will be + // evaluated later, but we already performed some work. Thus we + // already have had an impact on the caches, branch prediction, + // etc. with the goal to clear it to get the worst case + // measurements. + if i < CLEARCACHE { continue; } + + if self.stuck(delta) { count_stuck += 1; } + + // Test whether we have an increasing timer. + if !(time2 > time) { time_backwards += 1; } + + // Count the number of times the counter increases in steps of 100ns + // or greater. + if (delta % 100) == 0 { count_mod += 1; } + + // Ensure that we have a varying delta timer which is necessary for + // the calculation of entropy -- perform this check only after the + // first loop is executed as we need to prime the old_delta value + delta_sum += (delta - old_delta).abs() as u64; + old_delta = delta; + } + + // We allow the time to run backwards for up to three times. + // This can happen if the clock is being adjusted by NTP operations. + // If such an operation just happens to interfere with our test, it + // should not fail. The value of 3 should cover the NTP case being + // performed during our test run. + if time_backwards > 3 { + return Err(new_error(ErrorKind::NotMonotonic)); + } + + // Variations of deltas of time must on average be larger than 1 to + // ensure the entropy estimation implied with 1 is preserved + if delta_sum < (TESTLOOPCOUNT) { + return Err(new_error(ErrorKind::TinyVariantions)); + } + + // Ensure that we have variations in the time stamp below 100 for at + // least 10% of all checks -- on some platforms, the counter increments + // in multiples of 100, but not always + if count_mod > (TESTLOOPCOUNT/10 * 9) { + return Err(new_error(ErrorKind::CoarseTimer)); + } + + // If we have more than 90% stuck results, then this Jitter RNG is + // likely to not work well. + if count_stuck > (TESTLOOPCOUNT/10 * 9) { + return Err(new_error(ErrorKind::ToManyStuck)); + } + + Ok(()) + } +} + +#[cfg(feature="std")] +fn get_nstime() -> u64 { + use std::time::{SystemTime, UNIX_EPOCH}; + + let dur = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); + // The correct way to calculate the current time is + // `dur.as_secs() * 1_000_000_000 + dur.subsec_nanos() as u64` + // But this is faster, and the difference in terms of entropy is negligible + // (log2(10^9) == 29.9). + dur.as_secs() << 30 | dur.subsec_nanos() as u64 +} + +// A function that is opaque to the optimizer to assist in avoiding dead-code +// elimination. Taken from `bencher`. +fn black_box(dummy: T) -> T { + unsafe { + let ret = core::ptr::read_volatile(&dummy); + core::mem::forget(dummy); + ret + } +} + +impl Rng for JitterRng { + fn next_u32(&mut self) -> u32 { + // We want to use both parts of the generated entropy + if let Some(high) = self.data_remaining.take() { + high + } else { + let data = self.next_u64(); + self.data_remaining = Some((data >> 32) as u32); + data as u32 + } + } + + fn next_u64(&mut self) -> u64 { + self.gen_entropy() + } + + #[cfg(feature = "i128_support")] + fn next_u128(&mut self) -> u128 { + impls::next_u128_via_u64(self) + } + + fn fill_bytes(&mut self, dest: &mut [u8]) { + impls::fill_bytes_via_u64(self, dest) + } + + fn try_fill(&mut self, dest: &mut [u8]) -> Result<(), rand_core::Error> { + Ok(self.fill_bytes(dest)) + } +} + +impl CryptoRng for JitterRng {} diff --git a/src/lib.rs b/src/lib.rs index 8c79dc9a7ee..68ce87e7c92 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -261,6 +261,7 @@ pub use rand_core::{Rng, CryptoRng, SeedFromRng, SeedableRng, Error, ErrorKind}; pub use read::ReadRng; #[cfg(feature="std")] pub use os::OsRng; +pub use jitter_rng::JitterRng; pub use iter::iter; pub use distributions::{Distribution, Default, Rand}; #[cfg(feature="std")] @@ -271,6 +272,7 @@ use distributions::range::Range; pub mod distributions; pub mod iter; +pub mod jitter_rng; pub mod mock; pub mod prng; pub mod reseeding; From 6e40910714b60ce6ce4f7064cf9af8873734c41c Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 2 Nov 2017 21:49:25 +0100 Subject: [PATCH 2/6] Relicensing permission > One question about the license. All libraries in the Rust ecosystem try > to be dual-licensed with the permissive Apache 2.0 license and the MIT > license. I am not sure it is ok for me to do so, because your code used > the 3-clause BSD. The difference seems to be in the third clause: "The > name of the author may not be used to endorse or promote products > derived from this software without specific prior written permission." > Can you maybe give permission to license this Rust translation under the > MIT license? Granted. I am fine with the mentioned license as long as there is a reference to my code. --- src/jitter_rng.rs | 35 ++++------------------------------- 1 file changed, 4 insertions(+), 31 deletions(-) diff --git a/src/jitter_rng.rs b/src/jitter_rng.rs index 25fbc26126a..213bec654fd 100644 --- a/src/jitter_rng.rs +++ b/src/jitter_rng.rs @@ -8,38 +8,11 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. // -// Based on jitterentropy-library +// Based on jitterentropy-library, http://www.chronox.de/jent.html. +// Copyright Stephan Mueller , 2014 - 2017. // -// FIXME: does the 3-clause BSD license below allow us to dual-license with the -// MIT license? -// -// Copyright Stephan Mueller , 2014 - 2017 -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions -// are met: -// 1. Redistributions of source code must retain the above copyright -// notice, and the entire permission notice in its entirety, -// including the disclaimer of warranties. -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// 3. The name of the author may not be used to endorse or promote -// products derived from this software without specific prior -// written permission. -// -// THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED -// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF -// WHICH ARE HEREBY DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT -// OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR -// BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE -// USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH -// DAMAGE. +// With permission from Stephan Mueller to relicense the Rust translation under +// the MIT license. //! Non-physical true random number generator based on timing jitter. From 49e9bb0cd22835dc77daf911331480e1541a180f Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sat, 4 Nov 2017 20:20:39 +0100 Subject: [PATCH 3/6] Provide statistics function Add the function `timer_stats` (`jent_lfsr_var_stat` in C). Like in other the other functions we use a `int64` to represent a time delta instead of a `u64`. Instead of the `min` variable to indicate how many times the noice source loops should run at least, we use a `var_rounds` boolean. Setting `min` seems like an historic leftover, and does not fit `memaccess`. A simple bool covers everything needed for testing. This effects `timer_stats`, `lfsr_time` and `memaccess`. It is useful to be able to run the statistics function, even when initializing `JitterRng` might fail because of the `test_timer` function. Therefore `new_with_timer` does not automatically test the timer jitter, and expects the user code to do so. Now that `new_with_timer` calls `gen_entropy`, it was possible to move `black_box(ec.mem[0])` here instead of `measure_jitter`, so it is executed even less. # Conflicts: # src/jitter_rng.rs --- src/jitter_rng.rs | 207 ++++++++++++++++++++++++++++++++++++---------- 1 file changed, 164 insertions(+), 43 deletions(-) diff --git a/src/jitter_rng.rs b/src/jitter_rng.rs index 213bec654fd..e64cb0fa190 100644 --- a/src/jitter_rng.rs +++ b/src/jitter_rng.rs @@ -71,12 +71,18 @@ impl fmt::Debug for JitterRng { } } -/// An error that can occur when intializing `JitterRng`. +/// An error that can occur when `test_timer` fails. #[derive(Debug, Clone, PartialEq, Eq)] -pub struct Error { +pub struct TimerError { kind: ErrorKind, } +impl TimerError { + pub fn kind(&self) -> ErrorKind { + self.kind + } +} + /// Error kind which can be matched over. #[derive(PartialEq, Eq, Debug, Copy, Clone)] pub enum ErrorKind { @@ -107,7 +113,7 @@ impl ErrorKind { } } -impl fmt::Display for Error { +impl fmt::Display for TimerError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self.kind { ErrorKind::NoTimer => @@ -126,25 +132,27 @@ impl fmt::Display for Error { } #[cfg(feature="std")] -impl ::std::error::Error for Error { +impl ::std::error::Error for TimerError { fn description(&self) -> &str { self.kind.description() } } -#[cfg(feature="std")] -fn new_error(kind: ErrorKind) -> rand_core::Error { - rand_core::Error { - kind: rand_core::ErrorKind::Unavailable, - cause: Some(Box::new(Error { kind: kind })), +impl From for Error { + #[cfg(feature="std")] + fn from(err: TimerError) -> Error { + Error { + kind: rand_core::ErrorKind::Unavailable, + cause: Some(err.into()), + } } -} -#[cfg(not(feature="std"))] -fn new_error(kind: ErrorKind) -> rand_core::Error { - rand_core::Error { - kind: rand_core::ErrorKind::Unavailable, - cause: kind.description(), + #[cfg(not(feature="std"))] + fn from(err: TimerError) -> Error { + Error { + kind: rand_core::ErrorKind::Unavailable, + cause: errkind.description(), + } } } @@ -156,8 +164,10 @@ impl JitterRng { /// hundred times. If this does not pass basic quality tests, an error is /// returned. #[cfg(feature="std")] - pub fn new() -> Result { - JitterRng::new_with_timer(get_nstime) + pub fn new() -> Result { + let mut ec = JitterRng::new_with_timer(get_nstime); + ec.test_timer()?; + Ok(ec) } /// Create a new `JitterRng`. @@ -166,11 +176,10 @@ impl JitterRng { /// /// The timer must have nanosecond precision. /// - /// During initialization CPU execution timing jitter is measured a few - /// hundred times. If this does not pass basic quality tests, an error is - /// returned. - pub fn new_with_timer(timer: fn() -> u64) - -> Result { + /// This method is more low-level than `new()`. It is the responsibility of + /// the caller to run `test_timer` before using any numbers generated with + /// `JitterRng`. + pub fn new_with_timer(timer: fn() -> u64) -> JitterRng { let mut ec = JitterRng { data: 0, timer: timer, @@ -182,8 +191,18 @@ impl JitterRng { data_remaining: None, }; - ec.entropy_init()?; - Ok(ec) + // Fill `data`, `prev_time`, `last_delta` and `last_delta2` with + // non-zero values. + ec.prev_time = timer(); + ec.gen_entropy(); + + // Do a single read from `self.mem` to make sure the Memory Access noise + // source is not optimised out. + // Note: this read is important, it effects optimisations for the entire + // module! + black_box(ec.mem[0]); + + ec } // Calculate a random loop count used for the next round of an entropy @@ -226,7 +245,7 @@ impl JitterRng { // execution is used to measure the CPU execution time jitter. Any change to // the loop in this function implies that careful retesting must be done. #[inline(never)] - fn lfsr_time(&mut self, time: u64) { + fn lfsr_time(&mut self, time: u64, var_rounds: bool) { fn lfsr(mut data: u64, time: u64) -> u64{ for i in 1..65 { let mut tmp = time << (64 - i); @@ -256,8 +275,11 @@ impl JitterRng { // `self.data`, all the other results are ignored. To make sure the // other rounds are not optimised out, we first run all but the last // round on a throw-away value instead of the real `self.data`. + let mut lfsr_loop_cnt = 0; + if var_rounds { lfsr_loop_cnt = self.random_loop_cnt(4) }; + let mut throw_away: u64 = 0; - for _ in 0..(self.random_loop_cnt(4)) { + for _ in 0..lfsr_loop_cnt { throw_away = lfsr(throw_away, time); } black_box(throw_away); @@ -282,9 +304,12 @@ impl JitterRng { // range of wait states. However, to reliably access either L3 or memory, // the `self.mem` memory must be quite large which is usually not desirable. #[inline(never)] - fn memaccess(&mut self) { + fn memaccess(&mut self, var_rounds: bool) { + let mut acc_loop_cnt = 128; + if var_rounds { acc_loop_cnt += self.random_loop_cnt(4) }; + let mut index = self.mem_prev_index; - for _ in 0..(self.random_loop_cnt(7) + 128) { + for _ in 0..acc_loop_cnt { // Addition of memblocksize - 1 to index with wrap around logic to // ensure that every memory location is hit evenly. // The modulus also allows the compiler to remove the indexing @@ -328,7 +353,7 @@ impl JitterRng { // result. fn measure_jitter(&mut self) -> Option<()> { // Invoke one noise source before time measurement to add variations - self.memaccess(); + self.memaccess(true); // Get time stamp and calculate time delta to previous // invocation to measure the timing variations @@ -337,7 +362,7 @@ impl JitterRng { self.prev_time = time; // Call the next noise source which also injects the data - self.lfsr_time(current_delta as u64); + self.lfsr_time(current_delta as u64, true); // Check whether we have a stuck measurement (i.e. does the last // measurement holds entropy?). @@ -415,15 +440,12 @@ impl JitterRng { } self.stir_pool(); - - // Do a single read from `self.mem` to make sure the Memory Access noise - // source is not optimised out. - black_box(self.mem[0]); - self.data } - fn entropy_init(&mut self) -> Result<(), rand_core::Error> { + /// Basic quality tests on the timer, by measuring CPU timing jitter a few + /// hundred times. + pub fn test_timer(&mut self) -> Result<(), TimerError> { // We could add a check for system capabilities such as `clock_getres` // or check for `CONFIG_X86_TSC`, but it does not make much sense as the // following sanity checks verify that we have a high-resolution timer. @@ -443,12 +465,12 @@ impl JitterRng { for i in 0..(CLEARCACHE + TESTLOOPCOUNT) { // Invoke core entropy collection logic let time = (self.timer)(); - self.lfsr_time(time); + self.lfsr_time(time, true); let time2 = (self.timer)(); // Test whether timer works if time == 0 || time2 == 0 { - return Err(new_error(ErrorKind::NoTimer)); + return Err(TimerError { kind: ErrorKind::NoTimer }); } let delta = time2.wrapping_sub(time) as i64; @@ -456,7 +478,7 @@ impl JitterRng { // when called shortly after each other -- this implies that we also // have a high resolution timer if delta == 0 { - return Err(new_error(ErrorKind::CoarseTimer)); + return Err(TimerError { kind: ErrorKind::CoarseTimer }); } // Up to here we did not modify any variable that will be @@ -488,30 +510,129 @@ impl JitterRng { // should not fail. The value of 3 should cover the NTP case being // performed during our test run. if time_backwards > 3 { - return Err(new_error(ErrorKind::NotMonotonic)); + return Err(TimerError { kind: ErrorKind::NotMonotonic }); } // Variations of deltas of time must on average be larger than 1 to // ensure the entropy estimation implied with 1 is preserved if delta_sum < (TESTLOOPCOUNT) { - return Err(new_error(ErrorKind::TinyVariantions)); + return Err(TimerError { kind: ErrorKind::TinyVariantions }); } // Ensure that we have variations in the time stamp below 100 for at // least 10% of all checks -- on some platforms, the counter increments // in multiples of 100, but not always if count_mod > (TESTLOOPCOUNT/10 * 9) { - return Err(new_error(ErrorKind::CoarseTimer)); + return Err(TimerError { kind: ErrorKind::CoarseTimer }); } // If we have more than 90% stuck results, then this Jitter RNG is // likely to not work well. if count_stuck > (TESTLOOPCOUNT/10 * 9) { - return Err(new_error(ErrorKind::ToManyStuck)); + return Err(TimerError { kind: ErrorKind::ToManyStuck }); } Ok(()) } + + /// Statistical test: return the timer delta of one normal run of the + /// `JitterEntropy` entropy collector. + /// + /// Setting `var_rounds` to `true` will execute the memory access and the + /// CPU jitter noice sources a variable amount of times (just like a real + /// `JitterEntropy` round). + /// + /// Setting `var_rounds` to `false` will execute the noice sources the + /// minimal number of times. This can be used to measure the minimum amount + /// of entropy one round of entropy collector can collect in the worst case. + /// + /// # Example + /// + /// Use `timer_stats` to run the [NIST SP 800-90B Entropy Estimation Suite] + /// (https://github.com/usnistgov/SP800-90B_EntropyAssessment). + /// + /// This is the recommended way to test the quality of `JitterRng`. It + /// should be run before using the RNG on untested hardware, after changes + /// that could effect how the code is optimised, and after major compiler + /// compiler changes, like a new LLVM version. + /// + /// First generate two files `jitter_rng_var.bin` and `jitter_rng_var.min`. + /// + /// Execute `python noniid_main.py -v jitter_rng_var.bin 8`, and validate it + /// with `restart.py -v jitter_rng_var.bin 8 `. + /// This number is the expected amount of entropy that is at least available + /// for each round of the entropy collector. This number should be greater + /// than the amount estimated with `64 / test_timer()`. + /// + /// Execute `python noniid_main.py -v -u 4 jitter_rng_var.bin 4`, and + /// validate it with `restart.py -v -u 4 jitter_rng_var.bin 4 `. + /// This number is the expected amount of entropy that is available in the + /// last 4 bits of the timer delta after running noice sources. Note that + /// a value of 3.70 is the minimum estimated entropy for true randomness. + /// + /// Execute `python noniid_main.py -v -u 4 jitter_rng_var.bin 4`, and + /// validate it with `restart.py -v -u 4 jitter_rng_var.bin 4 `. + /// This number is the expected amount of entropy that is available to the + /// entropy collecter if both noice sources only run their minimal number of + /// times. This measures the absolute worst-case, and gives a lower bound + /// for the available entropy. + /// + /// ```rust + /// use rand::JitterRng; + /// + /// # use std::error::Error; + /// # use std::fs::File; + /// # use std::io::Write; + /// # + /// # fn try_main() -> Result<(), Box> { + /// fn get_nstime() -> u64 { + /// use std::time::{SystemTime, UNIX_EPOCH}; + /// + /// let dur = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); + /// // The correct way to calculate the current time is + /// // `dur.as_secs() * 1_000_000_000 + dur.subsec_nanos() as u64` + /// // But this is faster, and the difference in terms of entropy is + /// // negligible (log2(10^9) == 29.9). + /// dur.as_secs() << 30 | dur.subsec_nanos() as u64 + /// } + /// + /// // Do not initialize with `JitterRng::new`, but with `new_with_timer`. + /// // 'new' always runst `test_timer`, and can therefore fail to + /// // initialize. We want to be able to get the statistics even when the + /// // timer test fails. + /// let mut rng = JitterRng::new_with_timer(get_nstime); + /// + /// // 1_000_000 results are required for the NIST SP 800-90B Entropy + /// // Estimation Suite + /// // FIXME: this number is smaller here, otherwise the Doc-test is to slow + /// const ROUNDS: usize = 10_000; + /// let mut deltas_variable: Vec = Vec::with_capacity(ROUNDS); + /// let mut deltas_minimal: Vec = Vec::with_capacity(ROUNDS); + /// + /// for _ in 0..ROUNDS { + /// deltas_variable.push(rng.timer_stats(true) as u8); + /// deltas_minimal.push(rng.timer_stats(false) as u8); + /// } + /// + /// // Write out after the statistics collection loop, to not disturb the + /// // test results. + /// File::create("jitter_rng_var.bin")?.write(&deltas_variable)?; + /// File::create("jitter_rng_min.bin")?.write(&deltas_minimal)?; + /// # + /// # Ok(()) + /// # } + /// # + /// # fn main() { + /// # try_main().unwrap(); + /// # } + /// ``` + pub fn timer_stats(&mut self, var_rounds: bool) -> i64 { + let time = get_nstime(); + self.memaccess(var_rounds); + self.lfsr_time(time, var_rounds); + let time2 = get_nstime(); + time2.wrapping_sub(time) as i64 + } } #[cfg(feature="std")] From ed358de37964a011080440b5c9b2ac93e813298c Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Wed, 8 Nov 2017 16:31:13 +0100 Subject: [PATCH 4/6] Make the number of entropy colelction rounds variable --- src/jitter_rng.rs | 53 ++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 45 insertions(+), 8 deletions(-) diff --git a/src/jitter_rng.rs b/src/jitter_rng.rs index e64cb0fa190..c1fe5ab7580 100644 --- a/src/jitter_rng.rs +++ b/src/jitter_rng.rs @@ -51,6 +51,8 @@ const MEMORY_SIZE: usize = MEMORY_BLOCKS * MEMORY_BLOCKSIZE; // out what is technically dead code, but that does influence timing jitter. pub struct JitterRng { data: u64, // Actual random number + // Number of rounds to run the entropy collector per 64 bits + rounds: u16, // Timer and previous time stamp, used by `measure_jitter` timer: fn() -> u64, prev_time: u64, @@ -166,7 +168,7 @@ impl JitterRng { #[cfg(feature="std")] pub fn new() -> Result { let mut ec = JitterRng::new_with_timer(get_nstime); - ec.test_timer()?; + ec.rounds = ec.test_timer()?; Ok(ec) } @@ -182,6 +184,7 @@ impl JitterRng { pub fn new_with_timer(timer: fn() -> u64) -> JitterRng { let mut ec = JitterRng { data: 0, + rounds: 64, timer: timer, prev_time: 0, last_delta: 0, @@ -432,7 +435,7 @@ impl JitterRng { // first loop round collects the expected entropy. let _ = self.measure_jitter(); - for _ in 0..64 { + for _ in 0..self.rounds { // If a stuck measurement is received, repeat measurement // Note: we do not guard against an infinite loop, that would mean // the timer suddenly became broken. @@ -445,7 +448,11 @@ impl JitterRng { /// Basic quality tests on the timer, by measuring CPU timing jitter a few /// hundred times. - pub fn test_timer(&mut self) -> Result<(), TimerError> { + /// + /// If succesful, this will return the estimated number of rounds necessary + /// to collect 64 bits of entropy. Otherwise a `TimerError` with the cause + /// of the failure will be returned. + pub fn test_timer(&mut self) -> Result { // We could add a check for system capabilities such as `clock_getres` // or check for `CONFIG_X86_TSC`, but it does not make much sense as the // following sanity checks verify that we have a high-resolution timer. @@ -463,8 +470,9 @@ impl JitterRng { const CLEARCACHE: u64 = 100; for i in 0..(CLEARCACHE + TESTLOOPCOUNT) { - // Invoke core entropy collection logic + // Measure time delta of core entropy collection logic let time = (self.timer)(); + self.memaccess(true); self.lfsr_time(time, true); let time2 = (self.timer)(); @@ -513,9 +521,14 @@ impl JitterRng { return Err(TimerError { kind: ErrorKind::NotMonotonic }); } - // Variations of deltas of time must on average be larger than 1 to - // ensure the entropy estimation implied with 1 is preserved - if delta_sum < (TESTLOOPCOUNT) { + // Test that the available amount of entropy per round does not get to + // low. We expect 1 bit of entropy per round as a reasonable minimum + // (although less is possible, it means the collector loop has to run + // much more often). + // `assert!(delta_average >= log2(1))` + // `assert!(delta_sum / TESTLOOPCOUNT >= 1)` + // `assert!(delta_sum >= TESTLOOPCOUNT)` + if delta_sum < TESTLOOPCOUNT { return Err(TimerError { kind: ErrorKind::TinyVariantions }); } @@ -532,7 +545,31 @@ impl JitterRng { return Err(TimerError { kind: ErrorKind::ToManyStuck }); } - Ok(()) + // Estimate the number of `measure_jitter` rounds necessary for 64 bits + // of entropy. + // + // We don't try very hard to come up with a good estimate of the + // available bits of entropy per round here for two reasons: + // 1. Simple estimates of the available bits (like Shannon entropy) are + // to optimistic. + // 2) Unless we want to waste a lot of time during intialization, there + // is only a small amount of samples available. + // + // Therefore we use a very simple and conservative estimate: + // `let bits_of_entropy = log2(delta_average / TESTLOOPCOUNT) / 2`. + // + // The number of rounds `measure_jitter` should run to collect 64 bits + // of entropy is `64 / bits_of_entropy`. + // + // To have smaller rounding errors, intermediate values are multiplied + // by `FACTOR`. To compensate for `log2` and division rounding down, + // add 1. + let delta_average = delta_sum / TESTLOOPCOUNT; + + const FACTOR: u32 = 5; + fn log2(x: u64) -> u32 { 64 - x.leading_zeros() as u32 } + + Ok((64 * 2 * FACTOR / log2(delta_average.pow(FACTOR)) + 1) as u16) } /// Statistical test: return the timer delta of one normal run of the From c57aa1a6da68be8f1956008b917bf42df06864ed Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Wed, 8 Nov 2017 16:38:29 +0100 Subject: [PATCH 5/6] Address review comments --- src/jitter_rng.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/jitter_rng.rs b/src/jitter_rng.rs index c1fe5ab7580..bdf2474c21d 100644 --- a/src/jitter_rng.rs +++ b/src/jitter_rng.rs @@ -109,7 +109,7 @@ impl ErrorKind { ErrorKind::CoarseTimer => "coarse timer", ErrorKind::NotMonotonic => "timer not monotonic", ErrorKind::TinyVariantions => "time delta variations too small", - ErrorKind::ToManyStuck => "to many stuck results", + ErrorKind::ToManyStuck => "too many stuck results", ErrorKind::__Nonexhaustive => unreachable!(), } } @@ -127,7 +127,7 @@ impl fmt::Display for TimerError { ErrorKind::TinyVariantions => write!(f, "Variations of deltas of time too small."), ErrorKind::ToManyStuck => - write!(f, "To many stuck results (indicating no added entropy)."), + write!(f, "Too many stuck results (indicating no added entropy)."), ErrorKind::__Nonexhaustive => unreachable!(), } } @@ -361,6 +361,9 @@ impl JitterRng { // Get time stamp and calculate time delta to previous // invocation to measure the timing variations let time = (self.timer)(); + // Note: wrapping_sub combined with a cast to `i64` generates a correct + // delta, even in the unlikely case this is a timer than is not strictly + // monotonic. let current_delta = time.wrapping_sub(self.prev_time) as i64; self.prev_time = time; @@ -422,7 +425,7 @@ impl JitterRng { // we rely on it to not recognise the opportunity. for i in 0..64 { let apply = (self.data >> i) & 1; - let mask = !((apply as i64) - 1) as u64; + let mask = !apply.wrapping_sub(1); mixer ^= CONSTANT & mask; mixer = mixer.rotate_left(1); } From 14136e785d8339ba493d43ad9375eac0ae4183b5 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sat, 11 Nov 2017 16:25:27 +0100 Subject: [PATCH 6/6] Fix `no_std` errors --- Cargo.toml | 7 +++++-- benches/generators.rs | 2 +- src/jitter_rng.rs | 1 + 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5f294b9e38a..dc5da6bc5a7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,12 +16,15 @@ categories = ["algorithms"] [features] default = ["std"] nightly = ["i128_support"] -std = [] +std = ["rand_core/std"] i128_support = ["rand_core/i128_support"] [dependencies] libc = "0.2" -rand_core = { path = 'rand_core' } + +[dependencies.rand_core] +path = 'rand_core' +default-features = false [target.'cfg(target_os = "fuchsia")'.dependencies] fuchsia-zircon = "^0.2.1" diff --git a/benches/generators.rs b/benches/generators.rs index 568b2f5900a..67309f9b5b7 100644 --- a/benches/generators.rs +++ b/benches/generators.rs @@ -79,7 +79,7 @@ macro_rules! init_gen { ($fnn:ident, $gen:ident) => { #[bench] fn $fnn(b: &mut Bencher) { - let mut rng = OsRng::new().unwrap(); + let mut rng = XorShiftRng::new().unwrap(); b.iter(|| { black_box($gen::from_rng(&mut rng).unwrap()); }); diff --git a/src/jitter_rng.rs b/src/jitter_rng.rs index 2531cd2ec62..d22700b8e78 100644 --- a/src/jitter_rng.rs +++ b/src/jitter_rng.rs @@ -655,6 +655,7 @@ impl JitterRng { /// # try_main().unwrap(); /// # } /// ``` + #[cfg(feature="std")] pub fn timer_stats(&mut self, var_rounds: bool) -> i64 { let time = get_nstime(); self.memaccess(var_rounds);