diff --git a/benches/generators.rs b/benches/generators.rs index f0bc8a624e..52ef328ca5 100644 --- a/benches/generators.rs +++ b/benches/generators.rs @@ -12,6 +12,7 @@ use test::{black_box, Bencher}; use rand::{RngCore, Rng, SeedableRng, NewRng, StdRng, OsRng, JitterRng, EntropyRng}; use rand::{XorShiftRng, Hc128Rng, IsaacRng, Isaac64Rng, ChaChaRng}; use rand::reseeding::ReseedingRng; +use rand::prng::hc128::Hc128Core; use rand::thread_rng; macro_rules! gen_bytes { @@ -151,10 +152,13 @@ chacha_rounds!(gen_bytes_chacha12, gen_u32_chacha12, gen_u64_chacha12, 12); chacha_rounds!(gen_bytes_chacha20, gen_u32_chacha20, gen_u64_chacha20, 20); +const RESEEDING_THRESHOLD: u64 = 1024*1024*1024; // something high enough to get + // deterministic measurements + #[bench] fn reseeding_hc128_bytes(b: &mut Bencher) { - let mut rng = ReseedingRng::new(Hc128Rng::new().unwrap(), - 128*1024*1024, + let mut rng = ReseedingRng::new(Hc128Core::new().unwrap(), + RESEEDING_THRESHOLD, EntropyRng::new()); let mut buf = [0u8; BYTES_LEN]; b.iter(|| { @@ -170,8 +174,8 @@ macro_rules! reseeding_uint { ($fnn:ident, $ty:ty) => { #[bench] fn $fnn(b: &mut Bencher) { - let mut rng = ReseedingRng::new(Hc128Rng::new().unwrap(), - 128*1024*1024, + let mut rng = ReseedingRng::new(Hc128Core::new().unwrap(), + RESEEDING_THRESHOLD, EntropyRng::new()); b.iter(|| { for _ in 0..RAND_BENCH_N { diff --git a/src/lib.rs b/src/lib.rs index 8466a054c5..f32e22c8df 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -278,6 +278,7 @@ pub use jitter::JitterRng; #[cfg(feature="std")] pub use os::OsRng; // pseudo rngs +pub mod prng; pub use isaac::{IsaacRng, Isaac64Rng}; pub use chacha::ChaChaRng; pub use prng::XorShiftRng; @@ -317,7 +318,6 @@ pub mod isaac { mod le; #[cfg(feature="std")] mod entropy_rng; mod error; -mod prng; #[cfg(feature="std")] mod thread_rng; diff --git a/src/prng/mod.rs b/src/prng/mod.rs index 7eb1dac8e3..0cf0c420f2 100644 --- a/src/prng/mod.rs +++ b/src/prng/mod.rs @@ -40,8 +40,8 @@ //! same algorithm, it is possible that both will yield the same sequence of //! values (with some lag). -mod chacha; -mod hc128; +pub mod chacha; +pub mod hc128; mod isaac; mod isaac64; mod xorshift; @@ -53,4 +53,4 @@ pub use self::chacha::ChaChaRng; pub use self::hc128::Hc128Rng; pub use self::isaac::IsaacRng; pub use self::isaac64::Isaac64Rng; -pub use self::xorshift::XorShiftRng; \ No newline at end of file +pub use self::xorshift::XorShiftRng; diff --git a/src/reseeding.rs b/src/reseeding.rs index 637a0b4b99..74963068fc 100644 --- a/src/reseeding.rs +++ b/src/reseeding.rs @@ -11,7 +11,8 @@ //! A wrapper around another PRNG that reseeds it after it //! generates a certain number of random bytes. -use {RngCore, SeedableRng, Error, ErrorKind}; +use {RngCore, BlockRngCore, SeedableRng, Error, ErrorKind}; +use impls::BlockRng; /// A wrapper around any PRNG which reseeds the underlying PRNG after it has /// generated a certain number of random bytes. @@ -39,30 +40,26 @@ use {RngCore, SeedableRng, Error, ErrorKind}; /// `ReseedingRng` with the ISAAC RNG. That algorithm, although apparently /// strong and with no known attack, does not come with any proof of security /// and does not meet the current standards for a cryptographically secure -/// PRNG. By reseeding it frequently (every 32 MiB) it seems safe to assume +/// PRNG. By reseeding it frequently (every 32 kiB) it seems safe to assume /// there is no attack that can operate on the tiny window between reseeds. /// /// # Error handling /// /// If reseeding fails, `try_fill_bytes` is the only `Rng` method to report it. /// For all other `Rng` methods, `ReseedingRng` will not panic but try to -/// handle the error intelligently; if handling the source error fails these +/// handle the error intelligently through some combination of retrying and +/// delaying reseeding until later. If handling the source error fails these /// methods will continue generating data from the wrapped PRNG without /// reseeding. -/// -/// It is usually best to use the infallible methods `next_u32`, `next_u64` and -/// `fill_bytes` because they can make use of this error handling strategy. -/// Use `try_fill_bytes` and possibly `try_reseed` if you want to handle -/// reseeding errors explicitly. #[derive(Debug)] -pub struct ReseedingRng { - rng: R, - reseeder: Rsdr, - threshold: i64, - bytes_until_reseed: i64, -} +pub struct ReseedingRng(BlockRng>) +where R: BlockRngCore + SeedableRng, + Rsdr: RngCore; -impl ReseedingRng { +impl ReseedingRng +where R: BlockRngCore + SeedableRng, + Rsdr: RngCore +{ /// Create a new `ReseedingRng` with the given parameters. /// /// # Arguments @@ -70,42 +67,98 @@ impl ReseedingRng { /// * `rng`: the random number generator to use. /// * `threshold`: the number of generated bytes after which to reseed the RNG. /// * `reseeder`: the RNG to use for reseeding. - pub fn new(rng: R, threshold: u64, reseeder: Rsdr) -> ReseedingRng { + pub fn new(rng: R, threshold: u64, reseeder: Rsdr) + -> ReseedingRng + { assert!(threshold <= ::core::i64::MAX as u64); - ReseedingRng { - rng: rng, - reseeder: reseeder, - threshold: threshold as i64, - bytes_until_reseed: threshold as i64, + let results_empty = R::Results::default(); + ReseedingRng( + BlockRng { + core: Reseeder { + core: rng, + reseeder: reseeder, + threshold: threshold as i64, + bytes_until_reseed: threshold as i64, + }, + index: results_empty.as_ref().len(), // generate on first use + results: results_empty, + } + ) + } + + /// Reseed the internal PRNG. + pub fn reseed(&mut self) -> Result<(), Error> { + self.0.core.reseed() + } +} + +impl + SeedableRng, Rsdr: RngCore> RngCore for ReseedingRng { + #[inline] + fn next_u32(&mut self) -> u32 { + self.0.next_u32() + } + + #[inline] + fn next_u64(&mut self) -> u64 { + self.0.next_u64() + } + + #[inline] + fn fill_bytes(&mut self, dest: &mut [u8]) { + self.0.fill_bytes(dest) + } + + #[inline] + fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> { + self.0.try_fill_bytes(dest) + } +} + +#[derive(Debug)] +struct Reseeder { + core: R, + reseeder: Rsdr, + threshold: i64, + bytes_until_reseed: i64, +} + +impl BlockRngCore for Reseeder +where R: BlockRngCore + SeedableRng, + Rsdr: RngCore +{ + type Results = >::Results; + + fn generate(&mut self, results: &mut Self::Results) -> Result<(), Error> { + if self.bytes_until_reseed <= 0 { + // In the unlikely event the internal PRNG fails, we don't know + // whether this is resolvable; schedule to reseed on next use and + // return original error kind. + return self.reseed_and_generate(results); } + self.bytes_until_reseed -= results.as_ref().len() as i64 * 4; + self.core.generate(results) } +} +impl Reseeder +where R: BlockRngCore + SeedableRng, + Rsdr: RngCore +{ /// Reseed the internal PRNG. - /// - /// This will try to work around errors in the RNG used for reseeding - /// intelligently through some combination of retrying and delaying - /// reseeding until later. So long as the internal PRNG doesn't fail, this - /// method will not fail, i.e. failures from the reseeding source are not - /// fatal. - pub fn reseed(&mut self) { - // Behaviour is identical to `try_reseed`; we just squelch the error. - let _res = self.try_reseed(); + fn reseed(&mut self) -> Result<(), Error> { + R::from_rng(&mut self.reseeder).map(|result| self.core = result) } - /// Reseed the internal RNG if the number of bytes that have been - /// generated exceed the threshold. + /// Reseed the internal PRNG. /// - /// If reseeding fails, return an error with the original cause. Note that - /// in case of error we simply delay reseeding, allowing the generator to - /// continue its output of random data and try reseeding again later; - /// because of this we always return kind `ErrorKind::Transient`. - #[inline(never)] - pub fn try_reseed(&mut self) -> Result<(), Error> { + /// If reseeding fails, this will try to work around errors intelligently + /// through some combination of retrying and delaying reseeding until later. + /// It will also report the error with `ErrorKind::Transient` with the + /// original error as cause. + fn auto_reseed(&mut self) -> Result<(), Error> { trace!("Reseeding RNG after {} generated bytes", self.threshold - self.bytes_until_reseed); - if let Err(mut e) = R::from_rng(&mut self.reseeder) - .map(|result| self.rng = result) - { + if let Err(mut e) = self.reseed() { let delay = match e.kind { ErrorKind::Transient => 0, kind @ _ if kind.should_retry() => self.threshold >> 8, @@ -121,69 +174,36 @@ impl ReseedingRng { Ok(()) } } -} -impl RngCore for ReseedingRng { - fn next_u32(&mut self) -> u32 { - let value = self.rng.next_u32(); - self.bytes_until_reseed -= 4; - if self.bytes_until_reseed <= 0 { - self.reseed(); - } - value - } - - fn next_u64(&mut self) -> u64 { - let value = self.rng.next_u64(); - self.bytes_until_reseed -= 8; - if self.bytes_until_reseed <= 0 { - self.reseed(); - } - value - } - - fn fill_bytes(&mut self, dest: &mut [u8]) { - self.rng.fill_bytes(dest); - self.bytes_until_reseed -= dest.len() as i64; - if self.bytes_until_reseed <= 0 { - self.reseed(); - } - } - - fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> { - let res1 = self.rng.try_fill_bytes(dest); - self.bytes_until_reseed -= dest.len() as i64; - let res2 = if self.bytes_until_reseed <= 0 { - self.try_reseed() - } else { Ok(()) }; - - if let Err(e) = res1 { - // In the unlikely event the internal PRNG fails, we don't know - // whether this is resolvable; reseed immediately and return - // original error kind. - self.bytes_until_reseed = 0; - Err(e) - } else { - res2 - } + #[inline(never)] + fn reseed_and_generate(&mut self, + results: &mut >::Results) + -> Result<(), Error> + { + let res1 = self.auto_reseed(); + self.bytes_until_reseed -= results.as_ref().len() as i64 * 4; + let res2 = self.core.generate(results); + if res2.is_err() { res2 } else { res1 } } } #[cfg(test)] mod test { - use {Rng, SeedableRng, StdRng}; + use {Rng, SeedableRng}; + use prng::chacha::ChaChaCore; use mock::StepRng; use super::ReseedingRng; #[test] fn test_reseeding() { let mut zero = StepRng::new(0, 0); - let rng = StdRng::from_rng(&mut zero).unwrap(); - let mut reseeding = ReseedingRng::new(rng, 32, zero); + let rng = ChaChaCore::from_rng(&mut zero).unwrap(); + let mut reseeding = ReseedingRng::new(rng, 32*4, zero); // Currently we only support for arrays up to length 32. // TODO: cannot generate seq via Rng::gen because it uses different alg - let mut buf = [0u8; 32]; + let mut buf = [0u32; 32]; // Needs to be a multiple of the RNGs result + // size to test exactly. reseeding.fill(&mut buf); let seq = buf; for _ in 0..10 { diff --git a/src/thread_rng.rs b/src/thread_rng.rs index fc14cd7325..b95de87ab2 100644 --- a/src/thread_rng.rs +++ b/src/thread_rng.rs @@ -13,7 +13,8 @@ use std::cell::RefCell; use std::rc::Rc; -use {RngCore, CryptoRng, StdRng, SeedableRng, EntropyRng}; +use {RngCore, CryptoRng, SeedableRng, EntropyRng}; +use prng::hc128::Hc128Core; use {Distribution, Uniform, Rng, Error}; use reseeding::ReseedingRng; @@ -31,13 +32,13 @@ const THREAD_RNG_RESEED_THRESHOLD: u64 = 32*1024*1024; // 32 MiB /// [`thread_rng`]: fn.thread_rng.html #[derive(Clone, Debug)] pub struct ThreadRng { - rng: Rc>>, + rng: Rc>>, } thread_local!( - static THREAD_RNG_KEY: Rc>> = { + static THREAD_RNG_KEY: Rc>> = { let mut entropy_source = EntropyRng::new(); - let r = StdRng::from_rng(&mut entropy_source).unwrap_or_else(|err| + let r = Hc128Core::from_rng(&mut entropy_source).unwrap_or_else(|err| panic!("could not initialize thread_rng: {}", err)); let rng = ReseedingRng::new(r, THREAD_RNG_RESEED_THRESHOLD, @@ -51,11 +52,12 @@ thread_local!( /// chaining style, e.g. `thread_rng().gen::()`, or cached locally, e.g. /// `let mut rng = thread_rng();`. /// -/// `ThreadRng` uses [`ReseedingRng`] wrapping a [`StdRng`] which is reseeded -/// after generating 32 MiB of random data. A single instance is cached per -/// thread and the returned `ThreadRng` is a reference to this instance — hence -/// `ThreadRng` is neither `Send` nor `Sync` but is safe to use within a single -/// thread. This RNG is seeded and reseeded via [`EntropyRng`] as required. +/// `ThreadRng` uses [`ReseedingRng`] wrapping the same PRNG as [`StdRng`], +/// which is reseeded after generating 32 MiB of random data. A single instance +/// is cached per thread and the returned `ThreadRng` is a reference to this +/// instance — hence `ThreadRng` is neither `Send` nor `Sync` but is safe to use +/// within a single thread. This RNG is seeded and reseeded via [`EntropyRng`] +/// as required. /// /// Note that the reseeding is done as an extra precaution against entropy /// leaks and is in theory unnecessary — to predict `thread_rng`'s output, an