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..370bdb45e95 100644 --- a/benches/generators.rs +++ b/benches/generators.rs @@ -9,14 +9,15 @@ 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, StrongClockRng, + 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::new().unwrap(); + let mut rng = $gen; let mut buf = [0u8; BYTES_LEN]; b.iter(|| { for _ in 0..RAND_BENCH_N { @@ -29,19 +30,20 @@ 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(2)); +gen_bytes!(gen_bytes_strongclock, StrongClockRng::new()); macro_rules! gen_usize { - ($fnn:ident, $gen:ident) => { + ($fnn:ident, $gen:expr) => { #[bench] fn $fnn(b: &mut Bencher) { - let mut rng = $gen::new().unwrap(); + let mut rng = $gen; b.iter(|| { for _ in 0..RAND_BENCH_N { black_box(usize::rand(&mut rng, Default)); @@ -52,18 +54,20 @@ 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(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) => { #[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()); @@ -78,3 +82,45 @@ 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_clock0(b: &mut Bencher) { + b.iter(|| { + for _ in 0..RAND_BENCH_N { + 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/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..b70e60aeaa9 --- /dev/null +++ b/src/clock_rng.rs @@ -0,0 +1,209 @@ +// 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 core::num::Wrapping as w; + +/// 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. +#[derive(Debug)] +pub struct ClockRng { + state: w, +} + +impl ClockRng { + /// Create a `ClockRng`, and call `advance` a few times to improve initial + /// endianness. + /// + /// 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..rounds { 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 { + 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 { + // Throw away the low-precision part and use the rest twice. + impls::next_u64_via_u32(self) + } + + #[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)) + } +} + +/// "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 {} + } +} + +impl Rng for StrongClockRng { + fn next_u32(&mut self) -> u32 { + // 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 { + // 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 { + 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)) + } +} + +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 +} + +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; + use super::{ClockRng, StrongClockRng}; + + #[test] + fn distinct() { + 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()); + } + + #[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 8c79dc9a7ee..eb94b58b283 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -257,6 +257,8 @@ 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; #[cfg(feature="std")] @@ -269,6 +271,9 @@ 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; 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(32); + 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) };