From d6f8ebf72d8e93816445de521916c8263b5d6a52 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Mon, 23 Oct 2017 12:29:02 +0100 Subject: [PATCH 1/7] =?UTF-8?q?NewSeeded:=20rename=20new=20=E2=86=92=20try?= =?UTF-8?q?=5Fnew,=20add=20new=5Fwith=5Ffallback?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rename OsRng::new → try_new too for consitency Add dependency on time crate (Note: better to use chrono, but has no real equivalent to precise_time_ns().) --- Cargo.toml | 1 + benches/distributions.rs | 6 ++--- benches/generators.rs | 6 ++--- benches/misc.rs | 4 +-- src/clock_rng.rs | 57 ++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 36 ++++++++++++++++++++++--- src/os.rs | 24 ++++++++--------- src/reseeding.rs | 3 ++- src/thread_local.rs | 3 ++- 9 files changed, 114 insertions(+), 26 deletions(-) create mode 100644 src/clock_rng.rs diff --git a/Cargo.toml b/Cargo.toml index 5f294b9e38a..aacea3b805e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,7 @@ i128_support = ["rand_core/i128_support"] [dependencies] libc = "0.2" +time = "0.1" rand_core = { path = 'rand_core' } [target.'cfg(target_os = "fuchsia")'.dependencies] diff --git a/benches/distributions.rs b/benches/distributions.rs index 05265d688e9..86de991bf4f 100644 --- a/benches/distributions.rs +++ b/benches/distributions.rs @@ -15,7 +15,7 @@ use rand::distributions::*; #[bench] fn distr_baseline(b: &mut Bencher) { - let mut rng = XorShiftRng::new().unwrap(); + let mut rng = XorShiftRng::try_new().unwrap(); b.iter(|| { for _ in 0..::RAND_BENCH_N { @@ -29,7 +29,7 @@ macro_rules! distr_range_int { ($fnn:ident, $ty:ty, $low:expr, $high:expr) => { #[bench] fn $fnn(b: &mut Bencher) { - let mut rng = XorShiftRng::new().unwrap(); + let mut rng = XorShiftRng::try_new().unwrap(); let distr = Range::new($low, $high); b.iter(|| { @@ -52,7 +52,7 @@ macro_rules! distr_float { ($fnn:ident, $distr:expr) => { #[bench] fn $fnn(b: &mut Bencher) { - let mut rng = XorShiftRng::new().unwrap(); + let mut rng = XorShiftRng::try_new().unwrap(); let distr = $distr; b.iter(|| { diff --git a/benches/generators.rs b/benches/generators.rs index 44adb0e1c2d..390493afe91 100644 --- a/benches/generators.rs +++ b/benches/generators.rs @@ -16,7 +16,7 @@ macro_rules! gen_bytes { ($fnn:ident, $gen:ident) => { #[bench] fn $fnn(b: &mut Bencher) { - let mut rng = $gen::new().unwrap(); + let mut rng = $gen::try_new().unwrap(); let mut buf = [0u8; BYTES_LEN]; b.iter(|| { for _ in 0..RAND_BENCH_N { @@ -41,7 +41,7 @@ macro_rules! gen_usize { ($fnn:ident, $gen:ident) => { #[bench] fn $fnn(b: &mut Bencher) { - let mut rng = $gen::new().unwrap(); + let mut rng = $gen::try_new().unwrap(); b.iter(|| { for _ in 0..RAND_BENCH_N { black_box(usize::rand(&mut rng, Default)); @@ -63,7 +63,7 @@ macro_rules! init_gen { ($fnn:ident, $gen:ident) => { #[bench] fn $fnn(b: &mut Bencher) { - let mut rng = XorShiftRng::new().unwrap(); + let mut rng = XorShiftRng::try_new().unwrap(); b.iter(|| { for _ in 0..RAND_BENCH_N { black_box($gen::from_rng(&mut rng).unwrap()); diff --git a/benches/misc.rs b/benches/misc.rs index feb41bf9d5a..b6abf645308 100644 --- a/benches/misc.rs +++ b/benches/misc.rs @@ -10,7 +10,7 @@ use rand::sequences::{sample, Shuffle}; #[bench] fn misc_shuffle_100(b: &mut Bencher) { - let mut rng = XorShiftRng::new().unwrap(); + let mut rng = XorShiftRng::try_new().unwrap(); let x : &mut [usize] = &mut [1; 100]; b.iter(|| { x.shuffle(&mut rng); @@ -20,7 +20,7 @@ fn misc_shuffle_100(b: &mut Bencher) { #[bench] fn misc_sample_10_of_100(b: &mut Bencher) { - let mut rng = XorShiftRng::new().unwrap(); + let mut rng = XorShiftRng::try_new().unwrap(); let x : &[usize] = &[1; 100]; b.iter(|| { black_box(sample(&mut rng, x, 10)); diff --git a/src/clock_rng.rs b/src/clock_rng.rs new file mode 100644 index 00000000000..39c3081eb47 --- /dev/null +++ b/src/clock_rng.rs @@ -0,0 +1,57 @@ +// 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. + +//! A not-very-random number generator using the system clock. + +use {Rng, Error}; +use rand_core::impls; +use time::precise_time_ns; + +/// Clock-based `Rng`. Not very random. +pub struct ClockRng { + high: Option, +} + +impl ClockRng { + /// Create a `ClockRng` (very low cost) + pub fn new() -> ClockRng { + ClockRng { high: None } + } +} + +impl Rng for ClockRng { + fn next_u32(&mut self) -> u32 { + // We want to use both parts of precise_time_ns(), low part first + if let Some(high) = self.high.take() { + high + } else { + let ns = precise_time_ns(); + self.high = Some((ns >> 32) as u32); + ns as u32 + } + } + + fn next_u64(&mut self) -> u64 { + precise_time_ns() + } + + #[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<(), Error> { + Ok(self.fill_bytes(dest)) + } +} diff --git a/src/lib.rs b/src/lib.rs index 8c79dc9a7ee..8129037b538 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -254,6 +254,7 @@ extern crate core; extern crate rand_core; +extern crate time; pub use rand_core::{Rng, CryptoRng, SeedFromRng, SeedableRng, Error, ErrorKind}; @@ -269,6 +270,10 @@ pub use thread_local::{ThreadRng, thread_rng, random, random_with}; use prng::IsaacWordRng; use distributions::range::Range; +// TODO: should we export this? +#[cfg(feature="std")] // not needed otherwise +mod clock_rng; + pub mod distributions; pub mod iter; pub mod mock; @@ -294,14 +299,37 @@ mod thread_local; #[cfg(feature="std")] pub trait NewSeeded: Sized { /// Creates a new instance, automatically seeded via `OsRng`. - fn new() -> Result; + fn try_new() -> Result; + + /// Creates a new instance. If possible, this will just use `try_new` to + /// get entropy from `OsRng`; if not, it will use the system clock for + /// entropy. + /// + /// Do not use this method for cryptography or anything requiring secure + /// random numbers. + /// + /// This method can in theory panic, depending on the RNG being created, + /// but normally it shouldn't (SeedFromRng::from_rng is allowed to return + /// an error, but this would normally only happen if the source RNG errors; + /// the one used here does not). + fn new_with_fallback() -> Self; } #[cfg(feature="std")] impl NewSeeded for R { - fn new() -> Result { - let mut r = OsRng::new()?; - Self::from_rng(&mut r) + fn try_new() -> Result { + let mut src = OsRng::try_new()?; + Self::from_rng(&mut src) + } + fn new_with_fallback() -> Self { + match Self::try_new() { + Ok(result) => result, + Err(_) => { + let mut src = clock_rng::ClockRng::new(); + Self::from_rng(&mut src).unwrap_or_else(|e| + panic!("Seeding from clock failed: {}", e)) + } + } } } diff --git a/src/os.rs b/src/os.rs index 1b8f791fcbd..4cb6be15e65 100644 --- a/src/os.rs +++ b/src/os.rs @@ -47,8 +47,8 @@ impl fmt::Debug for OsRng { impl OsRng { /// Create a new `OsRng`. - pub fn new() -> Result { - imp::OsRng::new().map(OsRng) + pub fn try_new() -> Result { + imp::OsRng::try_new().map(OsRng) } } @@ -211,7 +211,7 @@ mod imp { } impl OsRng { - pub fn new() -> Result { + pub fn try_new() -> Result { if is_getrandom_available() { return Ok(OsRng { inner: OsGetrandomRng }); } @@ -253,7 +253,7 @@ mod imp { } impl OsRng { - pub fn new() -> Result { + pub fn try_new() -> Result { Ok(OsRng) } pub fn try_fill(&mut self, v: &mut [u8]) -> Result<(), Error> { @@ -278,7 +278,7 @@ mod imp { pub struct OsRng; impl OsRng { - pub fn new() -> Result { + pub fn try_new() -> Result { Ok(OsRng) } pub fn try_fill(&mut self, v: &mut [u8]) -> Result<(), Error> { @@ -311,7 +311,7 @@ mod imp { pub struct OsRng; impl OsRng { - pub fn new() -> Result { + pub fn try_new() -> Result { Ok(OsRng) } pub fn try_fill(&mut self, v: &mut [u8]) -> Result<(), Error> { @@ -342,7 +342,7 @@ mod imp { } impl OsRng { - pub fn new() -> Result { + pub fn try_new() -> Result { let reader = File::open("rand:").unwrap(); let reader_rng = ReadRng(reader); @@ -364,7 +364,7 @@ mod imp { pub struct OsRng; impl OsRng { - pub fn new() -> Result { + pub fn try_new() -> Result { Ok(OsRng) } pub fn try_fill(&mut self, v: &mut [u8]) -> Result<(), Error> { @@ -399,7 +399,7 @@ mod imp { pub struct OsRng; impl OsRng { - pub fn new() -> Result { + pub fn try_new() -> Result { Ok(OsRng) } pub fn try_fill(&mut self, v: &mut [u8]) -> Result<(), Error> { @@ -447,7 +447,7 @@ mod imp { } impl OsRng { - pub fn new() -> Result { + pub fn try_new() -> Result { let mut iface = NaClIRTRandom { get_random_bytes: None, }; @@ -494,7 +494,7 @@ mod test { #[test] fn test_os_rng() { - let mut r = OsRng::new().unwrap(); + let mut r = OsRng::try_new().unwrap(); r.next_u32(); r.next_u64(); @@ -517,7 +517,7 @@ mod test { // deschedule to attempt to interleave things as much // as possible (XXX: is this a good test?) - let mut r = OsRng::new().unwrap(); + let mut r = OsRng::try_new().unwrap(); thread::yield_now(); let mut v = [0u8; 1000]; diff --git a/src/reseeding.rs b/src/reseeding.rs index 28f6fd37d5a..baa59da4881 100644 --- a/src/reseeding.rs +++ b/src/reseeding.rs @@ -122,7 +122,8 @@ pub struct ReseedWithNew; #[cfg(feature="std")] impl Reseeder for ReseedWithNew { fn reseed(&mut self, rng: &mut R) { - match R::new() { + // TODO: should we use new_with_fallback instead? + match R::try_new() { Ok(result) => *rng = result, // TODO: should we ignore and continue without reseeding? Err(e) => panic!("Reseeding failed: {:?}", e), diff --git a/src/thread_local.rs b/src/thread_local.rs index 0d7348ec244..d1a6f2cd74b 100644 --- a/src/thread_local.rs +++ b/src/thread_local.rs @@ -51,7 +51,8 @@ impl Rng for ThreadRng { thread_local!( static THREAD_RNG_KEY: Rc> = { - let r = match StdRng::new() { + // TODO: consider using new_with_fallback instead of try_new + let r = match StdRng::try_new() { Ok(r) => r, Err(e) => panic!("could not initialize thread_rng: {:?}", e) }; From a306a69650c926fcb2e0c8ae6554d08754b9801d Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Mon, 23 Oct 2017 19:51:44 +0100 Subject: [PATCH 2/7] ClockRng: use only highest precision 32 bits from time See https://github.com/dhardy/rand/pull/21 --- src/clock_rng.rs | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/src/clock_rng.rs b/src/clock_rng.rs index 39c3081eb47..7c3a8f9acd5 100644 --- a/src/clock_rng.rs +++ b/src/clock_rng.rs @@ -15,31 +15,26 @@ use rand_core::impls; use time::precise_time_ns; /// Clock-based `Rng`. Not very random. -pub struct ClockRng { - high: Option, -} +pub struct ClockRng {} impl ClockRng { /// Create a `ClockRng` (very low cost) pub fn new() -> ClockRng { - ClockRng { high: None } + ClockRng {} } } impl Rng for ClockRng { fn next_u32(&mut self) -> u32 { - // We want to use both parts of precise_time_ns(), low part first - if let Some(high) = self.high.take() { - high - } else { - let ns = precise_time_ns(); - self.high = Some((ns >> 32) as u32); - ns as u32 - } + // Take only the highest-precision 32 bits (~4 sec) and throw away the + // rest. If using repeatedly the rest will be identical each time anyway, + // and is also much more predictable. + precise_time_ns() as u32 } fn next_u64(&mut self) -> u64 { - precise_time_ns() + // Throw away the low-precision part and use the rest twice. + impls::next_u64_via_u32(self) } #[cfg(feature = "i128_support")] From 64f1f63a36a8666387e9c2895ff32607fb56a11c Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Tue, 24 Oct 2017 18:36:04 +0100 Subject: [PATCH 3/7] ClockRng: use std::time, improve algorithm Remove dependency on external crate Use variation on PCG to mix internal state and generate output Use multiple init cycles --- Cargo.toml | 1 - src/clock_rng.rs | 69 +++++++++++++++++++++++++++++++++++++++++------- src/lib.rs | 1 - 3 files changed, 60 insertions(+), 11 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index aacea3b805e..5f294b9e38a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,6 @@ i128_support = ["rand_core/i128_support"] [dependencies] libc = "0.2" -time = "0.1" rand_core = { path = 'rand_core' } [target.'cfg(target_os = "fuchsia")'.dependencies] diff --git a/src/clock_rng.rs b/src/clock_rng.rs index 7c3a8f9acd5..601c8d0eacb 100644 --- a/src/clock_rng.rs +++ b/src/clock_rng.rs @@ -12,24 +12,53 @@ use {Rng, Error}; use rand_core::impls; -use time::precise_time_ns; +use core::num::Wrapping as w; -/// Clock-based `Rng`. Not very random. -pub struct ClockRng {} +/// Clock-based `Rng`. +/// +/// This is designed as a fast, failsafe alternative to `OsRng`, getting its +/// entropy from the system clock. It could be used directly (but should be +/// considered low-quality and non-deterministic) or could be used to seed +/// another generator via `SeedFromRng`. +/// +/// The time is checked once per `u32` extracted and mixed into the current +/// state via a RNG, hence in theory long output sequences will contain slightly +/// more entropy than short ones. +pub struct ClockRng { + state: w, +} impl ClockRng { - /// Create a `ClockRng` (very low cost) + /// Create a `ClockRng`, and call `advance` a few times to improve initial + /// endianness. pub fn new() -> ClockRng { - ClockRng {} + let mut r = ClockRng { state: w(0) }; + for _ in 0..10 { r.advance(); } + r + } + + /// Advance the internal state (equivalent to calling `next_u32` but + /// without generating any output). + #[inline(always)] + pub fn advance(&mut self) { + // Permute the state with time via the PCG algorithm. + // Vary our increment (<<1 because it must be odd) + let incr = (w(get_time()) << 1) ^ w(17707716133202733827); + // Multipier from PCG source: + self.state = self.state * w(6364136223846793005) + incr; } } impl Rng for ClockRng { fn next_u32(&mut self) -> u32 { - // Take only the highest-precision 32 bits (~4 sec) and throw away the - // rest. If using repeatedly the rest will be identical each time anyway, - // and is also much more predictable. - precise_time_ns() as u32 + self.advance(); + let state = self.state; + + // PCG output function: + let xorshifted = ((state >> 18) ^ state) >> 27; + let rot = state >> 59; + let rot2 = (-rot) & w(31); + ((xorshifted >> rot.0 as usize) | (xorshifted << rot2.0 as usize)).0 as u32 } fn next_u64(&mut self) -> u64 { @@ -50,3 +79,25 @@ impl Rng for ClockRng { Ok(self.fill_bytes(dest)) } } + +fn get_time() -> u64 { + use std::time::{SystemTime, UNIX_EPOCH}; + + let dur = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); + dur.as_secs() * 1_000_000_000 + dur.subsec_nanos() as u64 +} + + +#[cfg(test)] +mod test { + use Rng; + use super::ClockRng; + + #[test] + fn distinct() { + let mut c1 = ClockRng::new(); + let mut c2 = ClockRng::new(); + // probabilistic; very small chance of accidental failure + assert!(c1.next_u64() != c2.next_u64()); + } +} diff --git a/src/lib.rs b/src/lib.rs index 8129037b538..58f5ede1655 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -254,7 +254,6 @@ extern crate core; extern crate rand_core; -extern crate time; pub use rand_core::{Rng, CryptoRng, SeedFromRng, SeedableRng, Error, ErrorKind}; From 07a350a67a3c5e61461c9b3b9f7602121dff27ea Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Tue, 24 Oct 2017 18:44:59 +0100 Subject: [PATCH 4/7] ClockRng: export and add benches --- benches/generators.rs | 45 +++++++++++++++++++++++++++---------------- src/clock_rng.rs | 1 + src/lib.rs | 3 +-- 3 files changed, 30 insertions(+), 19 deletions(-) diff --git a/benches/generators.rs b/benches/generators.rs index 390493afe91..6b50dee57d7 100644 --- a/benches/generators.rs +++ b/benches/generators.rs @@ -9,14 +9,14 @@ 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, ClockRng, OsRng, Rand, Default}; use rand::prng::{XorShiftRng, IsaacRng, Isaac64Rng, ChaChaRng}; macro_rules! gen_bytes { - ($fnn:ident, $gen:ident) => { + ($fnn:ident, $gen:expr) => { #[bench] fn $fnn(b: &mut Bencher) { - let mut rng = $gen::try_new().unwrap(); + let mut rng = $gen; let mut buf = [0u8; BYTES_LEN]; b.iter(|| { for _ in 0..RAND_BENCH_N { @@ -29,19 +29,19 @@ macro_rules! gen_bytes { } } -gen_bytes!(gen_bytes_xorshift, XorShiftRng); -gen_bytes!(gen_bytes_isaac, IsaacRng); -gen_bytes!(gen_bytes_isaac64, Isaac64Rng); -gen_bytes!(gen_bytes_chacha, ChaChaRng); -gen_bytes!(gen_bytes_std, StdRng); -gen_bytes!(gen_bytes_os, OsRng); +gen_bytes!(gen_bytes_xorshift, XorShiftRng::try_new().unwrap()); +gen_bytes!(gen_bytes_isaac, IsaacRng::try_new().unwrap()); +gen_bytes!(gen_bytes_isaac64, Isaac64Rng::try_new().unwrap()); +gen_bytes!(gen_bytes_chacha, ChaChaRng::try_new().unwrap()); +gen_bytes!(gen_bytes_std, StdRng::try_new().unwrap()); +gen_bytes!(gen_bytes_clock, ClockRng::new()); macro_rules! gen_usize { - ($fnn:ident, $gen:ident) => { + ($fnn:ident, $gen:expr) => { #[bench] fn $fnn(b: &mut Bencher) { - let mut rng = $gen::try_new().unwrap(); + let mut rng = $gen; b.iter(|| { for _ in 0..RAND_BENCH_N { black_box(usize::rand(&mut rng, Default)); @@ -52,12 +52,13 @@ macro_rules! gen_usize { } } -gen_usize!(gen_usize_xorshift, XorShiftRng); -gen_usize!(gen_usize_isaac, IsaacRng); -gen_usize!(gen_usize_isaac64, Isaac64Rng); -gen_usize!(gen_usize_chacha, ChaChaRng); -gen_usize!(gen_usize_std, StdRng); -gen_usize!(gen_usize_os, OsRng); +gen_usize!(gen_usize_xorshift, XorShiftRng::try_new().unwrap()); +gen_usize!(gen_usize_isaac, IsaacRng::try_new().unwrap()); +gen_usize!(gen_usize_isaac64, Isaac64Rng::try_new().unwrap()); +gen_usize!(gen_usize_chacha, ChaChaRng::try_new().unwrap()); +gen_usize!(gen_usize_std, StdRng::try_new().unwrap()); +gen_usize!(gen_usize_clock, ClockRng::new()); +gen_usize!(gen_usize_os, OsRng::try_new().unwrap()); macro_rules! init_gen { ($fnn:ident, $gen:ident) => { @@ -78,3 +79,13 @@ init_gen!(init_isaac, IsaacRng); init_gen!(init_isaac64, Isaac64Rng); init_gen!(init_chacha, ChaChaRng); init_gen!(init_std, StdRng); + +// Differs from above in that it doesn't have a seeding rng +#[bench] +fn init_clock(b: &mut Bencher) { + b.iter(|| { + for _ in 0..RAND_BENCH_N { + black_box(ClockRng::new()); + } + }); +} diff --git a/src/clock_rng.rs b/src/clock_rng.rs index 601c8d0eacb..646d7129b30 100644 --- a/src/clock_rng.rs +++ b/src/clock_rng.rs @@ -24,6 +24,7 @@ use core::num::Wrapping as w; /// The time is checked once per `u32` extracted and mixed into the current /// state via a RNG, hence in theory long output sequences will contain slightly /// more entropy than short ones. +#[derive(Debug)] pub struct ClockRng { state: w, } diff --git a/src/lib.rs b/src/lib.rs index 58f5ede1655..88bf84b49db 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -257,6 +257,7 @@ extern crate rand_core; pub use rand_core::{Rng, CryptoRng, SeedFromRng, SeedableRng, Error, ErrorKind}; +pub use clock_rng::ClockRng; #[cfg(feature="std")] pub use read::ReadRng; #[cfg(feature="std")] @@ -269,8 +270,6 @@ pub use thread_local::{ThreadRng, thread_rng, random, random_with}; use prng::IsaacWordRng; use distributions::range::Range; -// TODO: should we export this? -#[cfg(feature="std")] // not needed otherwise mod clock_rng; pub mod distributions; From bd6604ab160da5a29fb1dbaec19715429329b3c5 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Tue, 24 Oct 2017 19:24:43 +0100 Subject: [PATCH 5/7] ClockRng: make init rounds customisable --- benches/generators.rs | 40 ++++++++++++++++++++++++++++++++++++---- src/clock_rng.rs | 14 ++++++++++---- src/lib.rs | 2 +- 3 files changed, 47 insertions(+), 9 deletions(-) diff --git a/benches/generators.rs b/benches/generators.rs index 6b50dee57d7..c27eb10e2e9 100644 --- a/benches/generators.rs +++ b/benches/generators.rs @@ -34,7 +34,7 @@ gen_bytes!(gen_bytes_isaac, IsaacRng::try_new().unwrap()); gen_bytes!(gen_bytes_isaac64, Isaac64Rng::try_new().unwrap()); gen_bytes!(gen_bytes_chacha, ChaChaRng::try_new().unwrap()); gen_bytes!(gen_bytes_std, StdRng::try_new().unwrap()); -gen_bytes!(gen_bytes_clock, ClockRng::new()); +gen_bytes!(gen_bytes_clock, ClockRng::new(2)); macro_rules! gen_usize { @@ -57,7 +57,7 @@ gen_usize!(gen_usize_isaac, IsaacRng::try_new().unwrap()); gen_usize!(gen_usize_isaac64, Isaac64Rng::try_new().unwrap()); gen_usize!(gen_usize_chacha, ChaChaRng::try_new().unwrap()); gen_usize!(gen_usize_std, StdRng::try_new().unwrap()); -gen_usize!(gen_usize_clock, ClockRng::new()); +gen_usize!(gen_usize_clock, ClockRng::new(2)); gen_usize!(gen_usize_os, OsRng::try_new().unwrap()); macro_rules! init_gen { @@ -82,10 +82,42 @@ init_gen!(init_std, StdRng); // Differs from above in that it doesn't have a seeding rng #[bench] -fn init_clock(b: &mut Bencher) { +fn init_clock0(b: &mut Bencher) { b.iter(|| { for _ in 0..RAND_BENCH_N { - black_box(ClockRng::new()); + black_box(ClockRng::new(0)); + } + }); +} +#[bench] +fn init_clock2(b: &mut Bencher) { + b.iter(|| { + for _ in 0..RAND_BENCH_N { + black_box(ClockRng::new(2)); + } + }); +} +#[bench] +fn init_clock12(b: &mut Bencher) { + b.iter(|| { + for _ in 0..RAND_BENCH_N { + black_box(ClockRng::new(12)); + } + }); +} +#[bench] +fn init_clock20(b: &mut Bencher) { + b.iter(|| { + for _ in 0..RAND_BENCH_N { + black_box(ClockRng::new(20)); + } + }); +} +#[bench] +fn init_clock32(b: &mut Bencher) { + b.iter(|| { + for _ in 0..RAND_BENCH_N { + black_box(ClockRng::new(32)); } }); } diff --git a/src/clock_rng.rs b/src/clock_rng.rs index 646d7129b30..6efb4c57bfe 100644 --- a/src/clock_rng.rs +++ b/src/clock_rng.rs @@ -32,9 +32,15 @@ pub struct ClockRng { impl ClockRng { /// Create a `ClockRng`, and call `advance` a few times to improve initial /// endianness. - pub fn new() -> ClockRng { + /// + /// The number of `rounds` used during initialisation may be specified. + /// Recommended to use at least 2, and up to 32 for "best" initialisation + /// (using an estimate of 2-4 bits entropy per round, over 64 bits of state), + /// but any number (including 0) can be used. + /// Has some impact on init time. + pub fn new(rounds: usize) -> ClockRng { let mut r = ClockRng { state: w(0) }; - for _ in 0..10 { r.advance(); } + for _ in 0..rounds { r.advance(); } r } @@ -96,8 +102,8 @@ mod test { #[test] fn distinct() { - let mut c1 = ClockRng::new(); - let mut c2 = ClockRng::new(); + let mut c1 = ClockRng::new(0); + let mut c2 = ClockRng::new(0); // probabilistic; very small chance of accidental failure assert!(c1.next_u64() != c2.next_u64()); } diff --git a/src/lib.rs b/src/lib.rs index 88bf84b49db..d46711c2e29 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -323,7 +323,7 @@ impl NewSeeded for R { match Self::try_new() { Ok(result) => result, Err(_) => { - let mut src = clock_rng::ClockRng::new(); + let mut src = clock_rng::ClockRng::new(32); Self::from_rng(&mut src).unwrap_or_else(|e| panic!("Seeding from clock failed: {}", e)) } From e40a030a5d3e6cc2332f20cee5a688e9e3ffa8de Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Tue, 24 Oct 2017 20:04:02 +0100 Subject: [PATCH 6/7] Add StrongClockRng --- benches/generators.rs | 5 ++- src/clock_rng.rs | 84 +++++++++++++++++++++++++++++++++++++++++-- src/lib.rs | 2 +- 3 files changed, 87 insertions(+), 4 deletions(-) diff --git a/benches/generators.rs b/benches/generators.rs index c27eb10e2e9..370bdb45e95 100644 --- a/benches/generators.rs +++ b/benches/generators.rs @@ -9,7 +9,8 @@ const BYTES_LEN: usize = 1024; use std::mem::size_of; use test::{black_box, Bencher}; -use rand::{Rng, NewSeeded, SeedFromRng, StdRng, ClockRng, OsRng, Rand, Default}; +use rand::{Rng, NewSeeded, SeedFromRng, StdRng, ClockRng, StrongClockRng, + OsRng, Rand, Default}; use rand::prng::{XorShiftRng, IsaacRng, Isaac64Rng, ChaChaRng}; macro_rules! gen_bytes { @@ -35,6 +36,7 @@ gen_bytes!(gen_bytes_isaac64, Isaac64Rng::try_new().unwrap()); gen_bytes!(gen_bytes_chacha, ChaChaRng::try_new().unwrap()); gen_bytes!(gen_bytes_std, StdRng::try_new().unwrap()); gen_bytes!(gen_bytes_clock, ClockRng::new(2)); +gen_bytes!(gen_bytes_strongclock, StrongClockRng::new()); macro_rules! gen_usize { @@ -59,6 +61,7 @@ gen_usize!(gen_usize_chacha, ChaChaRng::try_new().unwrap()); gen_usize!(gen_usize_std, StdRng::try_new().unwrap()); gen_usize!(gen_usize_clock, ClockRng::new(2)); gen_usize!(gen_usize_os, OsRng::try_new().unwrap()); +gen_usize!(gen_usize_strongclock, StrongClockRng::new()); macro_rules! init_gen { ($fnn:ident, $gen:ident) => { diff --git a/src/clock_rng.rs b/src/clock_rng.rs index 6efb4c57bfe..69331abcff3 100644 --- a/src/clock_rng.rs +++ b/src/clock_rng.rs @@ -87,6 +87,75 @@ impl Rng for ClockRng { } } +/// "Strong" clock-based RNG (slow but suitable for initialising PRNGs) +/// +/// [Limited experiments](https://github.com/dhardy/estimate-entropy), +/// show roughly 1-3 bits of entropy per use of the high-resolution timer, +/// even in a tight loop, and also no observable bias. +/// This "RNG" exploits that by invoking the timer for every 2 bits of required +/// output. +/// +/// I will not recommend randomness based off of the system timer for +/// cryptography (in part because I don't know whether your timer behaves the +/// same as the ones I have tested, in part because this may be more vulnable +/// to side-channel attacks), but this should be fairly strong. +/// +/// Performance is terrible (approx 1/16th of `ClockRng`, which is itself +/// around 1/4 the speed of `ChaChaRng`), but this shouldn't matter for small +/// amounts of data (e.g. initialising a PRNG). +/// +/// ## Example +/// +/// ```rust +/// use rand::{StrongClockRng, SeedFromRng}; +/// use rand::prng::ChaChaRng; +/// +/// let mut rng = ChaChaRng::from_rng(StrongClockRng::new()); +/// ``` +#[derive(Debug)] +pub struct StrongClockRng {} + +impl StrongClockRng { + /// Create an instance + pub fn new() -> StrongClockRng { + StrongClockRng {} + } +} + +macro_rules! gen_strong { + ($ty:ty, $rounds:expr) => {{ + let mut x: $ty = 0; + for _ in 0..$rounds { + x <<= 2; + x ^= get_time() as $ty; + } + x + }} +} + +impl Rng for StrongClockRng { + fn next_u32(&mut self) -> u32 { + gen_strong!(u32, 16) + } + + fn next_u64(&mut self) -> u64 { + gen_strong!(u64, 32) + } + + #[cfg(feature = "i128_support")] + fn next_u128(&mut self) -> u128 { + gen_strong!(u128, 64) + } + + fn fill_bytes(&mut self, dest: &mut [u8]) { + impls::fill_bytes_via_u64(self, dest) + } + + fn try_fill(&mut self, dest: &mut [u8]) -> Result<(), Error> { + Ok(self.fill_bytes(dest)) + } +} + fn get_time() -> u64 { use std::time::{SystemTime, UNIX_EPOCH}; @@ -94,11 +163,10 @@ fn get_time() -> u64 { dur.as_secs() * 1_000_000_000 + dur.subsec_nanos() as u64 } - #[cfg(test)] mod test { use Rng; - use super::ClockRng; + use super::{ClockRng, StrongClockRng}; #[test] fn distinct() { @@ -107,4 +175,16 @@ mod test { // probabilistic; very small chance of accidental failure assert!(c1.next_u64() != c2.next_u64()); } + + #[test] + fn strong() { + let mut r = StrongClockRng::new(); + r.next_u32(); + r.next_u64(); + #[cfg(feature = "i128_support")] + r.next_u128(); + + // probabilistic; very small chance of accidental failure + assert!(r.next_u64() != r.next_u64()); + } } diff --git a/src/lib.rs b/src/lib.rs index d46711c2e29..d3cc471da5f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -257,7 +257,7 @@ extern crate rand_core; pub use rand_core::{Rng, CryptoRng, SeedFromRng, SeedableRng, Error, ErrorKind}; -pub use clock_rng::ClockRng; +pub use clock_rng::{ClockRng, StrongClockRng}; #[cfg(feature="std")] pub use read::ReadRng; #[cfg(feature="std")] From bbc7ad23dcb2fa38a0233f1643bbe95d6258f499 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Wed, 25 Oct 2017 12:38:56 +0100 Subject: [PATCH 7/7] Revise StrongClockRng (mostly better mixing) Also appears to double performance; this might be because the >sec parts of the time are now ignored and/or algorithm being more parallelizable. --- src/clock_rng.rs | 47 +++++++++++++++++++++++++++++++++-------------- src/lib.rs | 2 ++ 2 files changed, 35 insertions(+), 14 deletions(-) diff --git a/src/clock_rng.rs b/src/clock_rng.rs index 69331abcff3..b70e60aeaa9 100644 --- a/src/clock_rng.rs +++ b/src/clock_rng.rs @@ -122,29 +122,38 @@ impl StrongClockRng { } } -macro_rules! gen_strong { - ($ty:ty, $rounds:expr) => {{ - let mut x: $ty = 0; - for _ in 0..$rounds { - x <<= 2; - x ^= get_time() as $ty; - } - x - }} -} - impl Rng for StrongClockRng { fn next_u32(&mut self) -> u32 { - gen_strong!(u32, 16) + // Experiments show 4-5.5 bits per call, almost exclusively in the last + // 8 bits. So we can ignore the high-order stuff. Use double what we + // need and do some mixing. + let a = w(get_nanos() ^ (get_nanos() << 8) ^ + (get_nanos() << 16) ^ (get_nanos() << 24)); + let b = w(get_nanos() ^ (get_nanos() << 8) ^ + (get_nanos() << 16) ^ (get_nanos() << 24)); + + (a * w(867850457) + a * w(3073211807) + + b * w(3008088109) + b * w(4097541745)).0 } fn next_u64(&mut self) -> u64 { - gen_strong!(u64, 32) + // Same principle as next_u32, but with different constants. + let a = w(get_nanos64() ^ (get_nanos64() << 8) ^ + (get_nanos64() << 16) ^ (get_nanos64() << 24) ^ + (get_nanos64() << 32) ^ (get_nanos64() << 40) ^ + (get_nanos64() << 48) ^ (get_nanos64() << 56)); + let b = w(get_nanos64() ^ (get_nanos64() << 8) ^ + (get_nanos64() << 16) ^ (get_nanos64() << 24) ^ + (get_nanos64() << 32) ^ (get_nanos64() << 40) ^ + (get_nanos64() << 48) ^ (get_nanos64() << 56)); + + (a * w(988868490075816773) + a * w(9677555830353064821) + + b * w(15019246847900914081) + b * w(2632891317968328867)).0 } #[cfg(feature = "i128_support")] fn next_u128(&mut self) -> u128 { - gen_strong!(u128, 64) + impls::next_u128_via_u64(self) } fn fill_bytes(&mut self, dest: &mut [u8]) { @@ -163,6 +172,16 @@ fn get_time() -> u64 { dur.as_secs() * 1_000_000_000 + dur.subsec_nanos() as u64 } +fn get_nanos() -> u32 { + use std::time::{SystemTime, UNIX_EPOCH}; + + let dur = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); + dur.subsec_nanos() +} +fn get_nanos64() -> u64 { + get_nanos() as u64 +} + #[cfg(test)] mod test { use Rng; diff --git a/src/lib.rs b/src/lib.rs index d3cc471da5f..eb94b58b283 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -257,6 +257,7 @@ extern crate rand_core; pub use rand_core::{Rng, CryptoRng, SeedFromRng, SeedableRng, Error, ErrorKind}; +#[cfg(feature="std")] pub use clock_rng::{ClockRng, StrongClockRng}; #[cfg(feature="std")] pub use read::ReadRng; @@ -270,6 +271,7 @@ pub use thread_local::{ThreadRng, thread_rng, random, random_with}; use prng::IsaacWordRng; use distributions::range::Range; +#[cfg(feature="std")] mod clock_rng; pub mod distributions;