From e97b539ef769ba8ae5f130662218cf69f208f643 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 30 Oct 2017 19:58:57 +0100 Subject: [PATCH] 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 | 599 ++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 2 + 3 files changed, 620 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..884c5d1063e --- /dev/null +++ b/src/jitter_rng.rs @@ -0,0 +1,599 @@ +// 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 Rng; +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 theCPU 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 { + // 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 trow-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 ec->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; + + if current_delta == 0 || delta2 == 0 || delta3 == 0 { + true + } else { + false + } + } + + // 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` + 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: u32 = 300; + const CLEARCACHE: u32 = 100; + + for i in 0..(CLEARCACHE + TESTLOOPCOUNT) { + let time = (self.timer)(); + self.prev_time = time; + // Do some work, invoke core entropy collection logic + black_box(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 + if delta > old_delta { + delta_sum += delta - old_delta; + } else { + delta_sum += old_delta - delta; + } + 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 <= 1 { + 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(()) + } + + /// 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) + } +} + +#[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. + dur.as_secs() << 32 | 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)) + } +} 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;