diff --git a/Cargo.toml b/Cargo.toml index 6442c18945..28e83158a1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -76,7 +76,8 @@ exclude = ["benches", "distr_test"] rand_core = { version = "0.10.0-rc-2", default-features = false } log = { version = "0.4.4", optional = true } serde = { version = "1.0.103", features = ["derive"], optional = true } -chacha20 = { version = "=0.10.0-rc.5", default-features = false, features = ["rng"], optional = true } +# chacha20 = { version = "=0.10.0-rc.5", default-features = false, features = ["rng"], optional = true } +chacha20 = { path = "rand_chacha", optional = true, package = "rand_chacha" } getrandom = { version = "0.3.0", optional = true } [dev-dependencies] @@ -85,3 +86,6 @@ rand_pcg = { path = "rand_pcg", version = "0.10.0-rc.1" } bincode = "1.2.1" rayon = "1.7" serde_json = "1.0.140" + +[patch.crates-io] +rand_core = { git = "https://github.com/rust-random/rand_core", branch = "block_buffer" } diff --git a/benches/Cargo.toml b/benches/Cargo.toml index 699186b26f..bde03de878 100644 --- a/benches/Cargo.toml +++ b/benches/Cargo.toml @@ -15,7 +15,6 @@ rand = { path = "..", features = ["small_rng", "nightly"] } rand_pcg = { path = "../rand_pcg" } rand_chacha = { path = "../rand_chacha" } criterion = "0.5" -criterion-cycles-per-byte = "0.6" [[bench]] name = "array" @@ -56,3 +55,6 @@ harness = false [[bench]] name = "weighted" harness = false + +[patch.crates-io] +rand_core = { git = "https://github.com/rust-random/rand_core", branch = "block_buffer" } diff --git a/benches/benches/generators.rs b/benches/benches/generators.rs index 1d22b1aa6c..1f1d427358 100644 --- a/benches/benches/generators.rs +++ b/benches/benches/generators.rs @@ -13,7 +13,7 @@ use rand::prelude::*; use rand::rngs::OsRng; use rand::rngs::ReseedingRng; use rand_chacha::rand_core::UnwrapErr; -use rand_chacha::{ChaCha8Rng, ChaCha12Rng, ChaCha20Core, ChaCha20Rng}; +use rand_chacha::{ChaCha8Rng, ChaCha12Rng, ChaCha20Rng}; use rand_pcg::{Pcg32, Pcg64, Pcg64Dxsm, Pcg64Mcg}; criterion_group!( @@ -198,7 +198,7 @@ pub fn reseeding_bytes(c: &mut Criterion) { fn bench(g: &mut BenchmarkGroup, thresh: u64) { let name = format!("chacha20_{thresh}k"); g.bench_function(name.as_str(), |b| { - let mut rng = ReseedingRng::::new(thresh * 1024, OsRng).unwrap(); + let mut rng = ReseedingRng::::new(thresh * 1024, OsRng).unwrap(); let mut buf = [0u8; 1024 * 1024]; b.iter(|| { rng.fill_bytes(&mut buf); diff --git a/rand_chacha/src/chacha.rs b/rand_chacha/src/chacha.rs index f42232f757..c692a279e0 100644 --- a/rand_chacha/src/chacha.rs +++ b/rand_chacha/src/chacha.rs @@ -10,8 +10,7 @@ use crate::guts::ChaCha; use core::fmt; -use rand_core::block::{BlockRng, BlockRngCore, CryptoBlockRng}; -use rand_core::{CryptoRng, RngCore, SeedableRng}; +use rand_core::{CryptoRng, RngCore, SeedableRng, le::BlockBuffer}; #[cfg(feature = "serde")] use serde::{Deserialize, Deserializer, Serialize, Serializer}; @@ -21,52 +20,6 @@ const BUF_BLOCKS: u8 = 4; // number of 32-bit words per ChaCha block (fixed by algorithm definition) const BLOCK_WORDS: u8 = 16; -#[repr(transparent)] -pub struct Array64([T; 64]); -impl Default for Array64 -where - T: Default, -{ - #[rustfmt::skip] - fn default() -> Self { - Self([ - T::default(), T::default(), T::default(), T::default(), T::default(), T::default(), T::default(), T::default(), - T::default(), T::default(), T::default(), T::default(), T::default(), T::default(), T::default(), T::default(), - T::default(), T::default(), T::default(), T::default(), T::default(), T::default(), T::default(), T::default(), - T::default(), T::default(), T::default(), T::default(), T::default(), T::default(), T::default(), T::default(), - T::default(), T::default(), T::default(), T::default(), T::default(), T::default(), T::default(), T::default(), - T::default(), T::default(), T::default(), T::default(), T::default(), T::default(), T::default(), T::default(), - T::default(), T::default(), T::default(), T::default(), T::default(), T::default(), T::default(), T::default(), - T::default(), T::default(), T::default(), T::default(), T::default(), T::default(), T::default(), T::default(), - ]) - } -} -impl AsRef<[T]> for Array64 { - fn as_ref(&self) -> &[T] { - &self.0 - } -} -impl AsMut<[T]> for Array64 { - fn as_mut(&mut self) -> &mut [T] { - &mut self.0 - } -} -impl Clone for Array64 -where - T: Copy + Default, -{ - fn clone(&self) -> Self { - let mut new = Self::default(); - new.0.copy_from_slice(&self.0); - new - } -} -impl fmt::Debug for Array64 { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "Array64 {{}}") - } -} - macro_rules! chacha_impl { ($ChaChaXCore:ident, $ChaChaXRng:ident, $rounds:expr, $doc:expr, $abst:ident,) => { #[doc=$doc] @@ -82,29 +35,28 @@ macro_rules! chacha_impl { } } - impl BlockRngCore for $ChaChaXCore { - type Item = u32; - type Results = Array64; - + impl $ChaChaXCore { + /// Create new core state from seed. #[inline] - fn generate(&mut self, r: &mut Self::Results) { - self.state.refill4($rounds, &mut r.0); + pub fn from_seed(seed: [u8; 32]) -> Self { + $ChaChaXCore { + state: ChaCha::new(&seed, &[0u8; 8]), + } } - } - impl SeedableRng for $ChaChaXCore { - type Seed = [u8; 32]; + /// Generate next block. + #[inline] + pub fn next_block(&mut self, r: &mut [u32; 64]) { + self.state.refill4($rounds, r); + } + /// Get current block position. #[inline] - fn from_seed(seed: Self::Seed) -> Self { - $ChaChaXCore { - state: ChaCha::new(&seed, &[0u8; 8]), - } + pub fn block_pos(&self) -> u64 { + self.state.get_block_pos() } } - impl CryptoBlockRng for $ChaChaXCore {} - /// A cryptographically secure random number generator that uses the ChaCha algorithm. /// /// ChaCha is a stream cipher designed by Daniel J. Bernstein[^1], that we use as an RNG. It is @@ -136,7 +88,7 @@ macro_rules! chacha_impl { /// ``` /// /// This implementation uses an output buffer of sixteen `u32` words, and uses - /// [`BlockRng`] to implement the [`RngCore`] methods. + /// them to implement the [`RngCore`] methods. /// /// [^1]: D. J. Bernstein, [*ChaCha, a variant of Salsa20*]( /// https://cr.yp.to/chacha.html) @@ -145,7 +97,25 @@ macro_rules! chacha_impl { /// http://www.ecrypt.eu.org/stream/) #[derive(Clone, Debug)] pub struct $ChaChaXRng { - rng: BlockRng<$ChaChaXCore>, + core: $ChaChaXCore, + buffer: BlockBuffer, + } + + impl $ChaChaXRng { + #[inline] + fn buffer_index(&self) -> u32 { + self.buffer.index() + } + + #[inline] + fn generate_and_set(&mut self, index: usize) { + if index == 0 { + self.buffer.reset(); + } else { + self.buffer + .generate_and_set(index, |block| self.core.next_block(block)); + } + } } impl SeedableRng for $ChaChaXRng { @@ -153,9 +123,9 @@ macro_rules! chacha_impl { #[inline] fn from_seed(seed: Self::Seed) -> Self { - let core = $ChaChaXCore::from_seed(seed); Self { - rng: BlockRng::new(core), + core: $ChaChaXCore::from_seed(seed), + buffer: BlockBuffer::default(), } } } @@ -163,17 +133,18 @@ macro_rules! chacha_impl { impl RngCore for $ChaChaXRng { #[inline] fn next_u32(&mut self) -> u32 { - self.rng.next_u32() + self.buffer.next_word(|block| self.core.next_block(block)) } #[inline] fn next_u64(&mut self) -> u64 { - self.rng.next_u64() + self.buffer.next_u64(|block| self.core.next_block(block)) } #[inline] - fn fill_bytes(&mut self, bytes: &mut [u8]) { - self.rng.fill_bytes(bytes) + fn fill_bytes(&mut self, dst: &mut [u8]) { + self.buffer + .fill_bytes(dst, |block| self.core.next_block(block)); } } @@ -190,11 +161,11 @@ macro_rules! chacha_impl { #[inline] pub fn get_word_pos(&self) -> u128 { let buf_start_block = { - let buf_end_block = self.rng.core.state.get_block_pos(); + let buf_end_block = self.core.state.get_block_pos(); u64::wrapping_sub(buf_end_block, BUF_BLOCKS.into()) }; let (buf_offset_blocks, block_offset_words) = { - let buf_offset_words = self.rng.index() as u64; + let buf_offset_words = self.buffer_index() as u64; let blocks_part = buf_offset_words / u64::from(BLOCK_WORDS); let words_part = buf_offset_words % u64::from(BLOCK_WORDS); (blocks_part, words_part) @@ -212,9 +183,8 @@ macro_rules! chacha_impl { #[inline] pub fn set_word_pos(&mut self, word_offset: u128) { let block = (word_offset / u128::from(BLOCK_WORDS)) as u64; - self.rng.core.state.set_block_pos(block); - self.rng - .generate_and_set((word_offset % u128::from(BLOCK_WORDS)) as usize); + self.core.state.set_block_pos(block); + self.generate_and_set((word_offset % u128::from(BLOCK_WORDS)) as usize); } /// Set the stream number. @@ -230,8 +200,8 @@ macro_rules! chacha_impl { /// indirectly via `set_word_pos`), but this is not directly supported. #[inline] pub fn set_stream(&mut self, stream: u64) { - self.rng.core.state.set_nonce(stream); - if self.rng.index() != 64 { + self.core.state.set_nonce(stream); + if self.buffer_index() != 64 { let wp = self.get_word_pos(); self.set_word_pos(wp); } @@ -240,13 +210,13 @@ macro_rules! chacha_impl { /// Get the stream number. #[inline] pub fn get_stream(&self) -> u64 { - self.rng.core.state.get_nonce() + self.core.state.get_nonce() } /// Get the seed. #[inline] pub fn get_seed(&self) -> [u8; 32] { - self.rng.core.state.get_seed() + self.core.state.get_seed() } } @@ -255,7 +225,8 @@ macro_rules! chacha_impl { impl From<$ChaChaXCore> for $ChaChaXRng { fn from(core: $ChaChaXCore) -> Self { $ChaChaXRng { - rng: BlockRng::new(core), + core, + buffer: BlockBuffer::default(), } } } diff --git a/rand_chacha/src/lib.rs b/rand_chacha/src/lib.rs index a06976e09f..2d3ceb82da 100644 --- a/rand_chacha/src/lib.rs +++ b/rand_chacha/src/lib.rs @@ -13,9 +13,8 @@ //! //! ## Generators //! -//! This crate provides 8-, 12- and 20-round variants of generators via a "core" -//! implementation (of [`BlockRngCore`]), each with an associated "RNG" type -//! (implementing [`RngCore`]). +//! This crate provides 8-, 12- and 20-round variants of generators +//! implementing [`RngCore`]. //! //! These generators are all deterministic and portable (see [Reproducibility] //! in the book), with testing against reference vectors. @@ -73,7 +72,6 @@ //! [Seeding RNGs]: https://rust-random.github.io/book/guide-seeding.html //! [Security]: https://rust-random.github.io/book/guide-rngs.html#security //! [Random Values]: https://rust-random.github.io/book/guide-values.html -//! [`BlockRngCore`]: rand_core::block::BlockRngCore //! [`RngCore`]: rand_core::RngCore //! [`SeedableRng`]: rand_core::SeedableRng //! [`OsRng`]: https://docs.rs/rand/latest/rand/rngs/struct.OsRng.html diff --git a/rand_pcg/src/pcg128.rs b/rand_pcg/src/pcg128.rs index f7dbada8cb..65767fd1de 100644 --- a/rand_pcg/src/pcg128.rs +++ b/rand_pcg/src/pcg128.rs @@ -126,10 +126,9 @@ impl SeedableRng for Lcg128Xsl64 { /// We use a single 255-bit seed to initialise the state and select a stream. /// One `seed` bit (lowest bit of `seed[8]`) is ignored. fn from_seed(seed: Self::Seed) -> Self { - let mut seed_u64 = [0u64; 4]; - le::read_u64_into(&seed, &mut seed_u64); - let state = u128::from(seed_u64[0]) | (u128::from(seed_u64[1]) << 64); - let incr = u128::from(seed_u64[2]) | (u128::from(seed_u64[3]) << 64); + let seed: [u64; 4] = le::read_words(&seed); + let state = u128::from(seed[0]) | (u128::from(seed[1]) << 64); + let incr = u128::from(seed[2]) | (u128::from(seed[3]) << 64); // The increment must be odd, hence we discard one bit: Lcg128Xsl64::from_state_incr(state, incr | 1) @@ -150,7 +149,7 @@ impl RngCore for Lcg128Xsl64 { #[inline] fn fill_bytes(&mut self, dest: &mut [u8]) { - le::fill_bytes_via_next(self, dest) + le::fill_bytes_via_next_word(dest, || self.next_u64()) } } @@ -232,9 +231,8 @@ impl SeedableRng for Mcg128Xsl64 { fn from_seed(seed: Self::Seed) -> Self { // Read as if a little-endian u128 value: - let mut seed_u64 = [0u64; 2]; - le::read_u64_into(&seed, &mut seed_u64); - let state = u128::from(seed_u64[0]) | (u128::from(seed_u64[1]) << 64); + let seed: [u64; 2] = le::read_words(&seed); + let state = u128::from(seed[0]) | (u128::from(seed[1]) << 64); Mcg128Xsl64::new(state) } } @@ -253,7 +251,7 @@ impl RngCore for Mcg128Xsl64 { #[inline] fn fill_bytes(&mut self, dest: &mut [u8]) { - le::fill_bytes_via_next(self, dest) + le::fill_bytes_via_next_word(dest, || self.next_u64()) } } diff --git a/rand_pcg/src/pcg128cm.rs b/rand_pcg/src/pcg128cm.rs index 681ca639b4..be8c7e700a 100644 --- a/rand_pcg/src/pcg128cm.rs +++ b/rand_pcg/src/pcg128cm.rs @@ -131,10 +131,9 @@ impl SeedableRng for Lcg128CmDxsm64 { /// We use a single 255-bit seed to initialise the state and select a stream. /// One `seed` bit (lowest bit of `seed[8]`) is ignored. fn from_seed(seed: Self::Seed) -> Self { - let mut seed_u64 = [0u64; 4]; - le::read_u64_into(&seed, &mut seed_u64); - let state = u128::from(seed_u64[0]) | (u128::from(seed_u64[1]) << 64); - let incr = u128::from(seed_u64[2]) | (u128::from(seed_u64[3]) << 64); + let seed: [u64; 4] = le::read_words(&seed); + let state = u128::from(seed[0]) | (u128::from(seed[1]) << 64); + let incr = u128::from(seed[2]) | (u128::from(seed[3]) << 64); // The increment must be odd, hence we discard one bit: Self::from_state_incr(state, incr | 1) @@ -156,7 +155,7 @@ impl RngCore for Lcg128CmDxsm64 { #[inline] fn fill_bytes(&mut self, dest: &mut [u8]) { - le::fill_bytes_via_next(self, dest) + le::fill_bytes_via_next_word(dest, || self.next_u64()) } } diff --git a/rand_pcg/src/pcg64.rs b/rand_pcg/src/pcg64.rs index 967299fdcb..571d2ef573 100644 --- a/rand_pcg/src/pcg64.rs +++ b/rand_pcg/src/pcg64.rs @@ -127,11 +127,9 @@ impl SeedableRng for Lcg64Xsh32 { /// We use a single 127-bit seed to initialise the state and select a stream. /// One `seed` bit (lowest bit of `seed[8]`) is ignored. fn from_seed(seed: Self::Seed) -> Self { - let mut seed_u64 = [0u64; 2]; - le::read_u64_into(&seed, &mut seed_u64); - + let [state, increment] = le::read_words(&seed); // The increment must be odd, hence we discard one bit: - Lcg64Xsh32::from_state_incr(seed_u64[0], seed_u64[1] | 1) + Lcg64Xsh32::from_state_incr(state, increment | 1) } } @@ -159,6 +157,6 @@ impl RngCore for Lcg64Xsh32 { #[inline] fn fill_bytes(&mut self, dest: &mut [u8]) { - le::fill_bytes_via_next(self, dest) + le::fill_bytes_via_next_word(dest, || self.next_u32()) } } diff --git a/src/distr/uniform_int.rs b/src/distr/uniform_int.rs index e4f4cfa3f8..53f3a6eb6f 100644 --- a/src/distr/uniform_int.rs +++ b/src/distr/uniform_int.rs @@ -882,7 +882,7 @@ mod tests { use serde_json; let serialized_on_32bit = r#"{"low":10,"range":91,"thresh":74}"#; let deserialized: UniformUsize = - serde_json::from_str(&serialized_on_32bit).expect("deserialization"); + serde_json::from_str(serialized_on_32bit).expect("deserialization"); assert_eq!( deserialized, UniformUsize::new_inclusive(10, 100).expect("creation") diff --git a/src/distr/utils.rs b/src/distr/utils.rs index 784534f48b..bb9e7006c1 100644 --- a/src/distr/utils.rs +++ b/src/distr/utils.rs @@ -179,6 +179,7 @@ mod simd_wmul { type Output = ($ty, $ty); #[inline(always)] + #[allow(clippy::undocumented_unsafe_blocks)] fn wmul(self, x: $ty) -> Self::Output { let hi = unsafe { $mulhi(self.into(), x.into()) }.into(); let lo = unsafe { $mullo(self.into(), x.into()) }.into(); diff --git a/src/lib.rs b/src/lib.rs index a42838d52d..dc5b241045 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -367,7 +367,7 @@ mod test { } fn fill_bytes(&mut self, dst: &mut [u8]) { - rand_core::le::fill_bytes_via_next(self, dst) + rand_core::le::fill_bytes_via_next_word(dst, || self.next_u64()) } } diff --git a/src/rngs/reseeding.rs b/src/rngs/reseeding.rs index 4a9f107edb..5e71ca6675 100644 --- a/src/rngs/reseeding.rs +++ b/src/rngs/reseeding.rs @@ -11,11 +11,9 @@ //! generates a certain number of random bytes. use core::mem::size_of_val; - -use rand_core::block::{BlockRng, BlockRngCore, CryptoBlockRng}; use rand_core::{CryptoRng, RngCore, SeedableRng, TryCryptoRng, TryRngCore}; -/// A wrapper around any PRNG that implements [`BlockRngCore`], that adds the +/// A wrapper around any PRNG that implements [`RngCore`], that adds the /// ability to reseed it. /// /// `ReseedingRng` reseeds the underlying PRNG in the following cases: @@ -52,29 +50,34 @@ use rand_core::{CryptoRng, RngCore, SeedableRng, TryCryptoRng, TryRngCore}; /// # Example /// /// ``` -/// use chacha20::ChaCha20Core; // Internal part of ChaChaRng that +/// use chacha20::ChaCha20Rng; // Internal part of ChaChaRng that /// // implements BlockRngCore /// use rand::prelude::*; /// use rand::rngs::OsRng; /// use rand::rngs::ReseedingRng; /// -/// let mut reseeding_rng = ReseedingRng::::new(0, OsRng).unwrap(); +/// let mut reseeding_rng = ReseedingRng::::new(0, OsRng).unwrap(); /// /// println!("{}", reseeding_rng.random::()); /// ``` /// -/// [`BlockRngCore`]: rand_core::block::BlockRngCore /// [`ReseedingRng::new`]: ReseedingRng::new /// [`reseed()`]: ReseedingRng::reseed #[derive(Debug)] -pub struct ReseedingRng(BlockRng>) +pub struct ReseedingRng where - R: BlockRngCore + SeedableRng, - Rsdr: TryRngCore; + R: RngCore + SeedableRng, + Rsdr: TryRngCore, +{ + prng: R, + reseed_rng: Rsdr, + threshold: i64, + bytes_until_reseed: i64, +} impl ReseedingRng where - R: BlockRngCore + SeedableRng, + R: RngCore + SeedableRng, Rsdr: TryRngCore, { /// Create a new `ReseedingRng` from an existing PRNG, combined with a RNG @@ -83,89 +86,8 @@ where /// `threshold` sets the number of generated bytes after which to reseed the /// PRNG. Set it to zero to never reseed based on the number of generated /// values. - pub fn new(threshold: u64, reseeder: Rsdr) -> Result { - Ok(ReseedingRng(BlockRng::new(ReseedingCore::new( - threshold, reseeder, - )?))) - } - - /// Immediately reseed the generator - /// - /// This discards any remaining random data in the cache. - pub fn reseed(&mut self) -> Result<(), Rsdr::Error> { - self.0.reset(); - self.0.core.reseed() - } -} - -// TODO: this should be implemented for any type where the inner type -// implements RngCore, but we can't specify that because ReseedingCore is private -impl RngCore for ReseedingRng -where - R: BlockRngCore + SeedableRng, - Rsdr: TryRngCore, -{ - #[inline(always)] - fn next_u32(&mut self) -> u32 { - self.0.next_u32() - } - - #[inline(always)] - fn next_u64(&mut self) -> u64 { - self.0.next_u64() - } - - fn fill_bytes(&mut self, dest: &mut [u8]) { - self.0.fill_bytes(dest) - } -} - -impl CryptoRng for ReseedingRng -where - R: BlockRngCore + SeedableRng + CryptoBlockRng, - Rsdr: TryCryptoRng, -{ -} - -#[derive(Debug)] -struct ReseedingCore { - inner: R, - reseeder: Rsdr, - threshold: i64, - bytes_until_reseed: i64, -} - -impl BlockRngCore for ReseedingCore -where - R: BlockRngCore + SeedableRng, - Rsdr: TryRngCore, -{ - type Item = ::Item; - type Results = ::Results; - - fn generate(&mut self, results: &mut Self::Results) { - if self.bytes_until_reseed <= 0 { - // We get better performance by not calling only `reseed` here - // and continuing with the rest of the function, but by directly - // returning from a non-inlined function. - return self.reseed_and_generate(results); - } - let num_bytes = size_of_val(results.as_ref()); - self.bytes_until_reseed -= num_bytes as i64; - self.inner.generate(results); - } -} - -impl ReseedingCore -where - R: BlockRngCore + SeedableRng, - Rsdr: TryRngCore, -{ - /// Create a new `ReseedingCore`. - /// - /// `threshold` is the maximum number of bytes produced by - /// [`BlockRngCore::generate`] before attempting reseeding. - fn new(threshold: u64, mut reseeder: Rsdr) -> Result { + #[inline] + pub fn new(threshold: u64, mut reseed_rng: Rsdr) -> Result { // Because generating more values than `i64::MAX` takes centuries on // current hardware, we just clamp to that value. // Also we set a threshold of 0, which indicates no limit, to that @@ -178,43 +100,68 @@ where i64::MAX }; - let inner = R::try_from_rng(&mut reseeder)?; - - Ok(ReseedingCore { - inner, - reseeder, + R::try_from_rng(&mut reseed_rng).map(|prng| Self { + prng, + reseed_rng, threshold, bytes_until_reseed: threshold, }) } - /// Reseed the internal PRNG. - fn reseed(&mut self) -> Result<(), Rsdr::Error> { - R::try_from_rng(&mut self.reseeder).map(|result| { + /// Immediately reseed the generator. + /// + /// This discards any remaining random data in the cache. + #[inline] + pub fn reseed(&mut self) -> Result<(), Rsdr::Error> { + self.prng = R::try_from_rng(&mut self.reseed_rng)?; + Ok(()) + } + + #[inline] + fn reseed_check(&mut self, bytes: usize) { + if self.bytes_until_reseed <= 0 { + let res = self.reseed(); + if let Err(_e) = res { + warn!("Reseeding RNG failed: {_e}"); + } self.bytes_until_reseed = self.threshold; - self.inner = result - }) + } + self.bytes_until_reseed -= bytes as i64; } +} - #[inline(never)] - fn reseed_and_generate(&mut self, results: &mut ::Results) { - trace!("Reseeding RNG (periodic reseed)"); +impl RngCore for ReseedingRng +where + R: RngCore + SeedableRng, + Rsdr: TryRngCore, +{ + #[inline(always)] + fn next_u32(&mut self) -> u32 { + self.reseed_check(size_of::()); + self.prng.next_u32() + } - let num_bytes = size_of_val(results.as_ref()); + #[inline(always)] + fn next_u64(&mut self) -> u64 { + self.reseed_check(size_of::()); + self.prng.next_u64() + } - if let Err(e) = self.reseed() { - warn!("Reseeding RNG failed: {}", e); - let _ = e; + #[inline(always)] + fn fill_bytes(&mut self, dst: &mut [u8]) { + // Do not perform `reseed_check` for an empty `dst` to allow the compiler + // to infer that we always generate PRNG data after reseeding. + if dst.is_empty() { + return; } - - self.bytes_until_reseed = self.threshold - num_bytes as i64; - self.inner.generate(results); + self.reseed_check(size_of_val(dst)); + self.prng.fill_bytes(dst) } } -impl CryptoBlockRng for ReseedingCore +impl CryptoRng for ReseedingRng where - R: BlockRngCore + SeedableRng + CryptoBlockRng, + R: CryptoRng + SeedableRng, Rsdr: TryCryptoRng, { } @@ -223,7 +170,7 @@ where #[cfg(test)] mod test { use crate::Rng; - use crate::rngs::std::Core; + use crate::rngs::std::StdRng; use crate::test::const_rng; use super::ReseedingRng; @@ -232,7 +179,7 @@ mod test { fn test_reseeding() { let zero = const_rng(0); let thresh = 1; // reseed every time the buffer is exhausted - let mut reseeding = ReseedingRng::::new(thresh, zero).unwrap(); + let mut reseeding = ReseedingRng::::new(thresh, zero).unwrap(); // RNG buffer size is [u32; 64] // Debug is only implemented up to length 32 so use two arrays diff --git a/src/rngs/std.rs b/src/rngs/std.rs index 3fc3e491e1..b62b62c0c8 100644 --- a/src/rngs/std.rs +++ b/src/rngs/std.rs @@ -10,9 +10,6 @@ use rand_core::{CryptoRng, RngCore, SeedableRng}; -#[cfg(any(test, feature = "os_rng"))] -pub(crate) use chacha20::ChaCha12Core as Core; - use chacha20::ChaCha12Rng as Rng; /// A strong, fast (amortized), non-portable RNG diff --git a/src/rngs/thread.rs b/src/rngs/thread.rs index 5acb90df0d..a2c423b785 100644 --- a/src/rngs/thread.rs +++ b/src/rngs/thread.rs @@ -13,8 +13,9 @@ use std::fmt; use std::rc::Rc; use std::thread_local; -use super::{OsError, OsRng, ReseedingRng, std::Core}; -use rand_core::{CryptoRng, RngCore}; +use super::{OsError, OsRng}; +use chacha20::ChaCha12Core; +use rand_core::{CryptoRng, RngCore, TryRngCore, le::BlockBuffer}; // Rationale for using `UnsafeCell` in `ThreadRng`: // @@ -30,10 +31,11 @@ use rand_core::{CryptoRng, RngCore}; // `ThreadRng` internally, which is nonsensical anyway. We should also never run // `ThreadRng` in destructors of its implementation, which is also nonsensical. -// Number of generated bytes after which to reseed `ThreadRng`. -// According to benchmarks, reseeding has a noticeable impact with thresholds -// of 32 kB and less. We choose 64 kB to avoid significant overhead. -const THREAD_RNG_RESEED_THRESHOLD: u64 = 1024 * 64; +/// Number of generated ChaCha blocks (64 bytes) after which we reseed `ThreadRng`. +/// +/// According to benchmarks, reseeding has a noticeable impact with thresholds +/// of 32 KiB and less. We choose 64 KiB (64 * 1024) to avoid significant overhead. +const THREAD_RNG_RESEED_THRESHOLD: u64 = 1024; /// A reference to the thread-local generator /// @@ -90,18 +92,21 @@ const THREAD_RNG_RESEED_THRESHOLD: u64 = 1024 * 64; #[derive(Clone)] pub struct ThreadRng { // Rc is explicitly !Send and !Sync - rng: Rc>>, + rng: Rc, ChaCha12Core)>>, } impl ThreadRng { /// Immediately reseed the generator /// /// This discards any remaining random data in the cache. + #[inline] pub fn reseed(&mut self) -> Result<(), OsError> { // SAFETY: We must make sure to stop using `rng` before anyone else // creates another mutable reference - let rng = unsafe { &mut *self.rng.get() }; - rng.reseed() + let (buffer, rng_core) = unsafe { &mut *self.rng.get() }; + reseed(rng_core)?; + buffer.reset(); + Ok(()) } } @@ -115,11 +120,15 @@ impl fmt::Debug for ThreadRng { thread_local!( // We require Rc<..> to avoid premature freeing when ThreadRng is used // within thread-local destructors. See #968. - static THREAD_RNG_KEY: Rc>> = { - let rng = ReseedingRng::new(THREAD_RNG_RESEED_THRESHOLD, - OsRng).unwrap_or_else(|err| - panic!("could not initialize ThreadRng: {}", err)); - Rc::new(UnsafeCell::new(rng)) + static THREAD_RNG_KEY: Rc, ChaCha12Core)>> = { + let mut seed = [0u8; 32]; + let res = OsRng.try_fill_bytes(&mut seed); + if let Err(err) = res { + panic!("could not initialize ThreadRng: {}", err) + } + let rng_core = ChaCha12Core::from_seed(seed); + let buffer = BlockBuffer::default(); + Rc::new(UnsafeCell::new((buffer, rng_core))) } ); @@ -151,45 +160,66 @@ thread_local!( /// # Security /// /// Refer to [`ThreadRng#Security`]. +#[inline] pub fn rng() -> ThreadRng { let rng = THREAD_RNG_KEY.with(|t| t.clone()); ThreadRng { rng } } impl Default for ThreadRng { + #[inline] fn default() -> ThreadRng { rng() } } impl RngCore for ThreadRng { - #[inline(always)] + #[inline] fn next_u32(&mut self) -> u32 { // SAFETY: We must make sure to stop using `rng` before anyone else // creates another mutable reference - let rng = unsafe { &mut *self.rng.get() }; - rng.next_u32() + let (buffer, rng_core) = unsafe { &mut *self.rng.get() }; + buffer.next_word(|block| next_block(rng_core, block)) } - #[inline(always)] + #[inline] fn next_u64(&mut self) -> u64 { // SAFETY: We must make sure to stop using `rng` before anyone else // creates another mutable reference - let rng = unsafe { &mut *self.rng.get() }; - rng.next_u64() + let (buffer, rng_core) = unsafe { &mut *self.rng.get() }; + buffer.next_u64(|block| next_block(rng_core, block)) } - #[inline(always)] - fn fill_bytes(&mut self, dest: &mut [u8]) { + #[inline] + fn fill_bytes(&mut self, dst: &mut [u8]) { // SAFETY: We must make sure to stop using `rng` before anyone else // creates another mutable reference - let rng = unsafe { &mut *self.rng.get() }; - rng.fill_bytes(dest) + let (buffer, rng_core) = unsafe { &mut *self.rng.get() }; + buffer.fill_bytes(dst, |block| next_block(rng_core, block)); } } impl CryptoRng for ThreadRng {} +#[inline(always)] +fn reseed(rng_core: &mut ChaCha12Core) -> Result<(), OsError> { + let mut seed = [0u8; 32]; + OsRng.try_fill_bytes(&mut seed)?; + *rng_core = ChaCha12Core::from_seed(seed); + Ok(()) +} + +#[inline(always)] +fn next_block(rng_core: &mut ChaCha12Core, block: &mut [u32; 64]) { + if rng_core.block_pos() >= THREAD_RNG_RESEED_THRESHOLD { + let res = reseed(rng_core); + if let Err(_e) = res { + warn!("Reseeding RNG failed: {_e}"); + } + } + rng_core.next_block(block); +} + #[cfg(test)] mod test { #[test] diff --git a/src/rngs/xoshiro128plusplus.rs b/src/rngs/xoshiro128plusplus.rs index 5d6b9318c5..58f15885db 100644 --- a/src/rngs/xoshiro128plusplus.rs +++ b/src/rngs/xoshiro128plusplus.rs @@ -31,8 +31,7 @@ impl SeedableRng for Xoshiro128PlusPlus { /// mapped to a different seed. #[inline] fn from_seed(seed: [u8; 16]) -> Xoshiro128PlusPlus { - let mut state = [0; 4]; - le::read_u32_into(&seed, &mut state); + let state: [u32; 4] = le::read_words(&seed); // Check for zero on aligned integers for better code generation. // Furtermore, seed_from_u64(0) will expand to a constant when optimized. if state.iter().all(|&x| x == 0) { @@ -93,7 +92,7 @@ impl RngCore for Xoshiro128PlusPlus { #[inline] fn fill_bytes(&mut self, dst: &mut [u8]) { - le::fill_bytes_via_next(self, dst) + le::fill_bytes_via_next_word(dst, || self.next_u32()) } } diff --git a/src/rngs/xoshiro256plusplus.rs b/src/rngs/xoshiro256plusplus.rs index 4ef433abe1..af0b9cb4e6 100644 --- a/src/rngs/xoshiro256plusplus.rs +++ b/src/rngs/xoshiro256plusplus.rs @@ -31,8 +31,7 @@ impl SeedableRng for Xoshiro256PlusPlus { /// mapped to a different seed. #[inline] fn from_seed(seed: [u8; 32]) -> Xoshiro256PlusPlus { - let mut state = [0; 4]; - le::read_u64_into(&seed, &mut state); + let state: [u64; 4] = le::read_words(&seed); // Check for zero on aligned integers for better code generation. // Furtermore, seed_from_u64(0) will expand to a constant when optimized. if state.iter().all(|&x| x == 0) { @@ -95,7 +94,7 @@ impl RngCore for Xoshiro256PlusPlus { #[inline] fn fill_bytes(&mut self, dst: &mut [u8]) { - le::fill_bytes_via_next(self, dst) + le::fill_bytes_via_next_word(dst, || self.next_u64()) } }