From 3aa580ac4b1f435142271f25a9665bef9d7b3d3e Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 26 Jan 2018 21:48:26 +0100 Subject: [PATCH 1/7] Replace timer function with trait --- src/jitter.rs | 106 +++++++++++++++++++++++++++++++++----------------- 1 file changed, 70 insertions(+), 36 deletions(-) diff --git a/src/jitter.rs b/src/jitter.rs index a3914834b5d..4db1ddddec3 100644 --- a/src/jitter.rs +++ b/src/jitter.rs @@ -48,12 +48,12 @@ const MEMORY_SIZE: usize = MEMORY_BLOCKS * MEMORY_BLOCKSIZE; // 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 { +pub struct JitterRng { data: u64, // Actual random number // Number of rounds to run the entropy collector per 64 bits rounds: u32, // Timer and previous time stamp, used by `measure_jitter` - timer: fn() -> u64, + timer: T, prev_time: u64, // Deltas used for the stuck test last_delta: i64, @@ -66,7 +66,7 @@ pub struct JitterRng { } // Custom Debug implementation that does not expose the internal state -impl fmt::Debug for JitterRng { +impl fmt::Debug for JitterRng { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "JitterRng {{}}") } @@ -126,16 +126,16 @@ impl From for Error { #[cfg(feature="std")] static JITTER_ROUNDS: AtomicUsize = ATOMIC_USIZE_INIT; -impl JitterRng { +#[cfg(feature="std")] +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. The test result is cached to make subsequent calls faster. - #[cfg(feature="std")] - pub fn new() -> Result { - let mut ec = JitterRng::new_with_timer(platform::get_nstime); + pub fn new() -> Result, TimerError> { + let mut ec = JitterRng::new_with_timer(Timer::default()); let mut rounds = JITTER_ROUNDS.load(Ordering::Relaxed) as u32; if rounds == 0 { // No result yet: run test. @@ -147,6 +147,9 @@ impl JitterRng { ec.set_rounds(rounds); Ok(ec) } +} + +impl JitterRng { /// Create a new `JitterRng`. /// A custom timer can be supplied, making it possible to use `JitterRng` in @@ -157,11 +160,11 @@ impl JitterRng { /// 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`, and optionally call `set_rounds()`. - pub fn new_with_timer(timer: fn() -> u64) -> JitterRng { + pub fn new_with_timer(timer: T) -> JitterRng { let mut ec = JitterRng { data: 0, rounds: 64, - timer: timer, + timer: timer.clone(), prev_time: 0, last_delta: 0, last_delta2: 0, @@ -172,7 +175,7 @@ impl JitterRng { // Fill `data`, `prev_time`, `last_delta` and `last_delta2` with // non-zero values. - ec.prev_time = timer(); + ec.prev_time = timer.get_nstime(); ec.gen_entropy(); // Do a single read from `self.mem` to make sure the Memory Access noise @@ -209,7 +212,7 @@ impl JitterRng { fn random_loop_cnt(&mut self, n_bits: u32) -> u32 { let mut rounds = 0; - let mut time = (self.timer)(); + let mut time = self.timer.get_nstime(); // Mix with the current state of the random number balance the random // loop counter a bit more. time ^= self.data; @@ -349,7 +352,7 @@ impl JitterRng { // Get time stamp and calculate time delta to previous // invocation to measure the timing variations - let time = (self.timer)(); + let time = self.timer.get_nstime(); // Note: wrapping_sub combined with a cast to `i64` generates a correct // delta, even in the unlikely case this is a timer that is not strictly // monotonic. @@ -469,10 +472,10 @@ impl JitterRng { for i in 0..(CLEARCACHE + TESTLOOPCOUNT) { // Measure time delta of core entropy collection logic - let time = (self.timer)(); + let time = self.timer.get_nstime(); self.memaccess(true); self.lfsr_time(time, true); - let time2 = (self.timer)(); + let time2 = self.timer.get_nstime(); // Test whether timer works if time == 0 || time2 == 0 { @@ -616,28 +619,33 @@ impl JitterRng { /// /// ```rust,no_run /// use rand::JitterRng; + /// use rand::jitter::JitterTimer; /// /// # 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}; + /// #[derive(Clone, Debug, Default)] + /// struct Timer; + /// impl JitterTimer for Timer { + /// fn get_nstime(&self) -> 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 + /// 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); + /// let mut rng = JitterRng::new_with_timer(Timer); /// /// // 1_000_000 results are required for the NIST SP 800-90B Entropy /// // Estimation Suite @@ -665,18 +673,35 @@ impl JitterRng { /// ``` #[cfg(feature="std")] pub fn timer_stats(&mut self, var_rounds: bool) -> i64 { - let time = platform::get_nstime(); + let timer = Timer::default(); + let time = timer.get_nstime(); self.memaccess(var_rounds); self.lfsr_time(time, var_rounds); - let time2 = platform::get_nstime(); + let time2 = timer.get_nstime(); time2.wrapping_sub(time) as i64 } } -#[cfg(feature="std")] -mod platform { - #[cfg(not(any(target_os = "macos", target_os = "ios", target_os = "windows", all(target_arch = "wasm32", not(target_os = "emscripten")))))] - pub fn get_nstime() -> u64 { +/// Timer to be used by `JitterRng`. +/// +/// Implement this trait to supply a custom timer to `JitterRng` with +/// `new_with_timer`, making it possible to use in `no_std` environments. +/// +/// The timer must have nanosecond precision. +pub trait JitterTimer: Sized { + fn get_nstime(&self) -> u64; +} + +#[derive(Clone, Debug, Default)] +pub struct Timer; + +#[cfg(all(feature="std", + not(any(target_os = "macos", + target_os = "ios", + target_os = "windows", + all(target_arch = "wasm32", not(target_os = "emscripten"))))))] +impl JitterTimer for Timer { + fn get_nstime(&self) -> u64 { use std::time::{SystemTime, UNIX_EPOCH}; let dur = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); @@ -686,9 +711,12 @@ mod platform { // (log2(10^9) == 29.9). dur.as_secs() << 30 | dur.subsec_nanos() as u64 } +} - #[cfg(any(target_os = "macos", target_os = "ios"))] - pub fn get_nstime() -> u64 { +#[cfg(all(feature="std", + any(target_os = "macos", target_os = "ios")))] +impl JitterTimer for Timer { + fn get_nstime(&self) -> u64 { extern crate libc; // On Mac OS and iOS std::time::SystemTime only has 1000ns resolution. // We use `mach_absolute_time` instead. This provides a CPU dependent unit, @@ -698,9 +726,11 @@ mod platform { // use the raw result. unsafe { libc::mach_absolute_time() } } +} - #[cfg(target_os = "windows")] - pub fn get_nstime() -> u64 { +#[cfg(all(feature="std", target_os = "windows"))] +impl JitterTimer for Timer { + fn get_nstime(&self) -> u64 { extern crate winapi; unsafe { let mut t = super::mem::zeroed(); @@ -708,9 +738,13 @@ mod platform { *t.QuadPart() as u64 } } +} - #[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))] - pub fn get_nstime() -> u64 { +#[cfg(all(feature="std", + target_arch = "wasm32", + not(target_os = "emscripten")))] +impl JitterTimer for Timer { + fn get_nstime(&self) -> u64 { unreachable!() } } @@ -725,7 +759,7 @@ fn black_box(dummy: T) -> T { } } -impl Rng for JitterRng { +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() { From 16a9993d844ab442f91205ed743925093c7ed702 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 26 Jan 2018 22:04:45 +0100 Subject: [PATCH 2/7] Replace Option with bool --- src/jitter.rs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/jitter.rs b/src/jitter.rs index 4db1ddddec3..572f05754b2 100644 --- a/src/jitter.rs +++ b/src/jitter.rs @@ -62,7 +62,7 @@ pub struct JitterRng { mem_prev_index: usize, mem: [u8; MEMORY_SIZE], // Make `next_u32` not waste 32 bits - data_remaining: Option, + data_half_used: bool, } // Custom Debug implementation that does not expose the internal state @@ -170,7 +170,7 @@ impl JitterRng { last_delta2: 0, mem_prev_index: 0, mem: [0; MEMORY_SIZE], - data_remaining: None, + data_half_used: false, }; // Fill `data`, `prev_time`, `last_delta` and `last_delta2` with @@ -762,16 +762,18 @@ fn black_box(dummy: T) -> T { 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 + if self.data_half_used { + self.data_half_used = false; + (self.data >> 32) as u32 } else { - let data = self.next_u64(); - self.data_remaining = Some((data >> 32) as u32); - data as u32 + self.data = self.next_u64(); + self.data_half_used = true; + self.data as u32 } } fn next_u64(&mut self) -> u64 { + self.data_half_used = false; self.gen_entropy() } From 4603c09a172b3b25cf483704e2135088bd47746e Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 26 Jan 2018 22:11:25 +0100 Subject: [PATCH 3/7] Make index u16 --- src/jitter.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/jitter.rs b/src/jitter.rs index 572f05754b2..a6b5bad5ba4 100644 --- a/src/jitter.rs +++ b/src/jitter.rs @@ -59,7 +59,7 @@ pub struct JitterRng { last_delta: i64, last_delta2: i64, // Memory for the Memory Access noise source - mem_prev_index: usize, + mem_prev_index: u16, mem: [u8; MEMORY_SIZE], // Make `next_u32` not waste 32 bits data_half_used: bool, @@ -303,7 +303,7 @@ impl JitterRng { let mut acc_loop_cnt = 128; if var_rounds { acc_loop_cnt += self.random_loop_cnt(4) }; - let mut index = self.mem_prev_index; + let mut index = self.mem_prev_index as usize; 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. @@ -316,7 +316,7 @@ impl JitterRng { let tmp = self.mem[index]; self.mem[index] = tmp.wrapping_add(1); } - self.mem_prev_index = index; + self.mem_prev_index = index as u16; } From d8394a15ad53b55a80d02f9f1be33bf8bfee847e Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Wed, 31 Jan 2018 17:34:07 +0100 Subject: [PATCH 4/7] Make rounds u8 --- src/jitter.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/jitter.rs b/src/jitter.rs index a6b5bad5ba4..197810a032e 100644 --- a/src/jitter.rs +++ b/src/jitter.rs @@ -51,7 +51,7 @@ const MEMORY_SIZE: usize = MEMORY_BLOCKS * MEMORY_BLOCKSIZE; pub struct JitterRng { data: u64, // Actual random number // Number of rounds to run the entropy collector per 64 bits - rounds: u32, + rounds: u8, // Timer and previous time stamp, used by `measure_jitter` timer: T, prev_time: u64, @@ -136,7 +136,7 @@ impl JitterRng { /// returned. The test result is cached to make subsequent calls faster. pub fn new() -> Result, TimerError> { let mut ec = JitterRng::new_with_timer(Timer::default()); - let mut rounds = JITTER_ROUNDS.load(Ordering::Relaxed) as u32; + let mut rounds = JITTER_ROUNDS.load(Ordering::Relaxed) as u8; if rounds == 0 { // No result yet: run test. // This allows the timer test to run multiple times; we don't care. @@ -195,7 +195,7 @@ impl JitterRng { /// can be used. The `test_timer()` function returns the minimum number of /// rounds required for full strength (platform dependent), so one may use /// `rng.set_rounds(rng.test_timer()?);` or cache the value. - pub fn set_rounds(&mut self, rounds: u32) { + pub fn set_rounds(&mut self, rounds: u8) { assert!(rounds > 0); self.rounds = rounds; } @@ -449,7 +449,7 @@ impl JitterRng { /// 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 { + pub fn test_timer(&mut self) -> Result { debug!("JitterRng: testing timer ..."); // 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 @@ -572,7 +572,7 @@ impl JitterRng { fn log2(x: u64) -> u32 { 64 - x.leading_zeros() } // pow(δ, FACTOR) must be representable; if you have overflow reduce FACTOR - Ok(64 * 2 * FACTOR / (log2(delta_average.pow(FACTOR)) + 1)) + Ok((64u32 * 2 * FACTOR / (log2(delta_average.pow(FACTOR)) + 1)) as u8) } /// Statistical test: return the timer delta of one normal run of the From 40241ab00a0ae7d44c48791799dc3cda24507a38 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Wed, 31 Jan 2018 17:37:16 +0100 Subject: [PATCH 5/7] Reduce deltas to i32 --- src/jitter.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/jitter.rs b/src/jitter.rs index 197810a032e..4653b2072f0 100644 --- a/src/jitter.rs +++ b/src/jitter.rs @@ -56,8 +56,8 @@ pub struct JitterRng { timer: T, prev_time: u64, // Deltas used for the stuck test - last_delta: i64, - last_delta2: i64, + last_delta: i32, + last_delta2: i32, // Memory for the Memory Access noise source mem_prev_index: u16, mem: [u8; MEMORY_SIZE], @@ -329,7 +329,7 @@ impl JitterRng { // 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 { + fn stuck(&mut self, current_delta: i32) -> bool { let delta2 = self.last_delta - current_delta; let delta3 = delta2 - self.last_delta2; @@ -356,7 +356,7 @@ impl JitterRng { // Note: wrapping_sub combined with a cast to `i64` generates a correct // delta, even in the unlikely case this is a timer that is not strictly // monotonic. - let current_delta = time.wrapping_sub(self.prev_time) as i64; + let current_delta = time.wrapping_sub(self.prev_time) as i64 as i32; self.prev_time = time; // Call the next noise source which also injects the data @@ -481,7 +481,7 @@ impl JitterRng { if time == 0 || time2 == 0 { return Err(TimerError::NoTimer); } - let delta = time2.wrapping_sub(time) as i64; + let delta = time2.wrapping_sub(time) as i64 as i32; // Test whether timer is fine grained enough to provide delta even // when called shortly after each other -- this implies that we also From 0d052bd53b30efb88a8b79fda216f50a8762e483 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Wed, 31 Jan 2018 21:44:56 +0100 Subject: [PATCH 6/7] Split JitterRng state in a permanent and temporary state --- src/jitter.rs | 148 ++++++++++++++++++++++++++++---------------------- 1 file changed, 83 insertions(+), 65 deletions(-) diff --git a/src/jitter.rs b/src/jitter.rs index 4653b2072f0..44ff94c6275 100644 --- a/src/jitter.rs +++ b/src/jitter.rs @@ -52,17 +52,45 @@ pub struct JitterRng { data: u64, // Actual random number // Number of rounds to run the entropy collector per 64 bits rounds: u8, - // Timer and previous time stamp, used by `measure_jitter` + // Timer used by `measure_jitter` timer: T, + // Memory for the Memory Access noise source FIXME + mem_prev_index: u16, + // Make `next_u32` not waste 32 bits + data_half_used: bool, +} + +// Entropy collector state. +// These values are not necessary to preserve across runs. +struct EcState { + // Previous time stamp to determine the timer delta prev_time: u64, // Deltas used for the stuck test last_delta: i32, last_delta2: i32, // Memory for the Memory Access noise source - mem_prev_index: u16, mem: [u8; MEMORY_SIZE], - // Make `next_u32` not waste 32 bits - data_half_used: bool, +} + +impl EcState { + // 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: i32) -> 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 + } } // Custom Debug implementation that does not expose the internal state @@ -135,22 +163,25 @@ impl JitterRng { /// hundred times. If this does not pass basic quality tests, an error is /// returned. The test result is cached to make subsequent calls faster. pub fn new() -> Result, TimerError> { - let mut ec = JitterRng::new_with_timer(Timer::default()); + let mut state = JitterRng::new_with_timer(Timer::default()); + let mut rounds = JITTER_ROUNDS.load(Ordering::Relaxed) as u8; if rounds == 0 { // No result yet: run test. // This allows the timer test to run multiple times; we don't care. - rounds = ec.test_timer()?; + rounds = state.test_timer()?; JITTER_ROUNDS.store(rounds as usize, Ordering::Relaxed); info!("JitterRng: using {} rounds per u64 output", rounds); } - ec.set_rounds(rounds); - Ok(ec) + state.set_rounds(rounds); + + // Fill `data` with a non-zero value. + state.gen_entropy(); + Ok(state) } } impl JitterRng { - /// Create a new `JitterRng`. /// A custom timer can be supplied, making it possible to use `JitterRng` in /// `no_std` environments. @@ -159,32 +190,17 @@ impl JitterRng { /// /// 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`, and optionally call `set_rounds()`. + /// `JitterRng`, and optionally call `set_rounds()`. Also it is important to + /// call `gen_entropy` once before using the first result to initialize the + /// entropy collection pool. pub fn new_with_timer(timer: T) -> JitterRng { - let mut ec = JitterRng { + JitterRng { data: 0, rounds: 64, timer: timer.clone(), - prev_time: 0, - last_delta: 0, - last_delta2: 0, mem_prev_index: 0, - mem: [0; MEMORY_SIZE], data_half_used: false, - }; - - // Fill `data`, `prev_time`, `last_delta` and `last_delta2` with - // non-zero values. - ec.prev_time = timer.get_nstime(); - 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 + } } /// Configures how many rounds are used to generate each 64-bit value. @@ -299,7 +315,7 @@ 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, var_rounds: bool) { + fn memaccess(&mut self, mem: &mut [u8; MEMORY_SIZE], var_rounds: bool) { let mut acc_loop_cnt = 128; if var_rounds { acc_loop_cnt += self.random_loop_cnt(4) }; @@ -313,42 +329,21 @@ impl JitterRng { // 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); + mem[index] = mem[index].wrapping_add(1); } self.mem_prev_index = index as u16; } - - // 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: i32) -> 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 + // Ensure that `ec.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<()> { + fn measure_jitter(&mut self, ec: &mut EcState) -> Option<()> { // Invoke one noise source before time measurement to add variations - self.memaccess(true); + self.memaccess(&mut ec.mem, true); // Get time stamp and calculate time delta to previous // invocation to measure the timing variations @@ -356,15 +351,15 @@ impl JitterRng { // Note: wrapping_sub combined with a cast to `i64` generates a correct // delta, even in the unlikely case this is a timer that is not strictly // monotonic. - let current_delta = time.wrapping_sub(self.prev_time) as i64 as i32; - self.prev_time = time; + let current_delta = time.wrapping_sub(ec.prev_time) as i64 as i32; + ec.prev_time = time; // Call the next noise source which also injects the data self.lfsr_time(current_delta as u64, true); // Check whether we have a stuck measurement (i.e. does the last // measurement holds entropy?). - if self.stuck(current_delta) { return None }; + if ec.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 @@ -427,18 +422,28 @@ impl JitterRng { fn gen_entropy(&mut self) -> u64 { trace!("JitterRng: collecting entropy"); - - // Prime `self.prev_time`, and run the noice sources to make sure the + + // Prime `ec.prev_time`, and run the noice sources to make sure the // first loop round collects the expected entropy. - let _ = self.measure_jitter(); + let mut ec = EcState { + prev_time: self.timer.get_nstime(), + last_delta: 0, + last_delta2: 0, + mem: [0; MEMORY_SIZE], + }; + let _ = self.measure_jitter(&mut ec); 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. - while self.measure_jitter().is_none() {} + while self.measure_jitter(&mut ec).is_none() {} } + // Do a single read from `self.mem` to make sure the Memory Access noise + // source is not optimised out. + black_box(ec.mem[0]); + self.stir_pool(); self.data } @@ -465,6 +470,13 @@ impl JitterRng { let mut count_mod = 0; let mut count_stuck = 0; + let mut ec = EcState { + prev_time: self.timer.get_nstime(), + last_delta: 0, + last_delta2: 0, + mem: [0; MEMORY_SIZE], + }; + // TESTLOOPCOUNT needs some loops to identify edge systems. // 100 is definitely too little. const TESTLOOPCOUNT: u64 = 300; @@ -473,7 +485,7 @@ impl JitterRng { for i in 0..(CLEARCACHE + TESTLOOPCOUNT) { // Measure time delta of core entropy collection logic let time = self.timer.get_nstime(); - self.memaccess(true); + self.memaccess(&mut ec.mem, true); self.lfsr_time(time, true); let time2 = self.timer.get_nstime(); @@ -497,7 +509,7 @@ impl JitterRng { // measurements. if i < CLEARCACHE { continue; } - if self.stuck(delta) { count_stuck += 1; } + if ec.stuck(delta) { count_stuck += 1; } // Test whether we have an increasing timer. if !(time2 > time) { time_backwards += 1; } @@ -513,6 +525,10 @@ impl JitterRng { old_delta = delta; } + // Do a single read from `self.mem` to make sure the Memory Access noise + // source is not optimised out. + black_box(ec.mem[0]); + // 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 @@ -674,8 +690,10 @@ impl JitterRng { #[cfg(feature="std")] pub fn timer_stats(&mut self, var_rounds: bool) -> i64 { let timer = Timer::default(); + let mut mem = [0; MEMORY_SIZE]; + let time = timer.get_nstime(); - self.memaccess(var_rounds); + self.memaccess(&mut mem, var_rounds); self.lfsr_time(time, var_rounds); let time2 = timer.get_nstime(); time2.wrapping_sub(time) as i64 From 36093a7cde7857df8d15b8767e8b769578abb11d Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 1 Feb 2018 08:12:11 +0100 Subject: [PATCH 7/7] Use a lookup-table when estimating the number of JitterRng rounds --- src/jitter.rs | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/src/jitter.rs b/src/jitter.rs index 44ff94c6275..7585304ae42 100644 --- a/src/jitter.rs +++ b/src/jitter.rs @@ -569,26 +569,29 @@ impl JitterRng { // available bits of entropy per round here for two reasons: // 1. Simple estimates of the available bits (like Shannon entropy) are // too optimistic. - // 2) Unless we want to waste a lot of time during intialization, there - // only a small number of samples are available. + // 2. Unless we want to waste a lot of time during intialization, there + // only a small number of samples are available. // // Therefore we use a very simple and conservative estimate: // `let bits_of_entropy = log2(delta_average) / 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; - // println!("delta_average: {}", delta_average); - - const FACTOR: u32 = 3; - fn log2(x: u64) -> u32 { 64 - x.leading_zeros() } - // pow(δ, FACTOR) must be representable; if you have overflow reduce FACTOR - Ok((64u32 * 2 * FACTOR / (log2(delta_average.pow(FACTOR)) + 1)) as u8) + if delta_average >= 16 { + let log2 = 64 - delta_average.leading_zeros(); + // Do something similar to roundup(64/(log2/2)): + Ok( ((64u32 * 2 + log2 - 1) / log2) as u8) + } else { + // For values < 16 the rounding error becomes too large, use a + // lookup table. + // Values 0 and 1 are invalid, and filtered out by the + // `delta_sum < TESTLOOPCOUNT` test above. + let log2_lookup = [0, 0, 128, 81, 64, 56, 50, 46, + 43, 41, 39, 38, 36, 35, 34, 33]; + Ok(log2_lookup[delta_average as usize]) + } } /// Statistical test: return the timer delta of one normal run of the