From e9f4e841c292f7e81d38710de31dc7973b3d3037 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D1=91=D0=BC=20=D0=9F=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=BE=D0=B2=20=5BArtyom=20Pavlov=5D?= Date: Wed, 19 Nov 2025 19:27:39 +0300 Subject: [PATCH 01/17] Migrate to new `rand_core` utility functions --- Cargo.toml | 17 ++-- rand_pcg/src/pcg128.rs | 16 ++-- rand_pcg/src/pcg128cm.rs | 9 +- rand_pcg/src/pcg64.rs | 8 +- src/lib.rs | 2 +- src/rngs/reseeding.rs | 167 +++++++++++---------------------- src/rngs/xoshiro128plusplus.rs | 5 +- src/rngs/xoshiro256plusplus.rs | 5 +- 8 files changed, 85 insertions(+), 144 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 13aea84ed7..f5522b5211 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,9 +28,9 @@ features = ["small_rng", "serde"] [features] # Meta-features: -default = ["std", "std_rng", "os_rng", "small_rng", "thread_rng"] +# default = ["std", "std_rng", "os_rng", "small_rng", "thread_rng"] nightly = [] # some additions requiring nightly Rust -serde = ["dep:serde", "rand_core/serde"] +serde = ["dep:serde"] # Option (enabled by default): without "std" rand uses libcore; this option # enables functionality expected to be available on a standard platform. @@ -46,16 +46,16 @@ os_rng = ["dep:getrandom"] simd_support = [] # Option (enabled by default): enable StdRng -std_rng = ["dep:chacha20"] +# std_rng = ["dep:chacha20"] # Option: enable SmallRng small_rng = [] # Option: enable ThreadRng and rng() -thread_rng = ["std", "std_rng", "os_rng"] +# thread_rng = ["std", "std_rng", "os_rng"] # Option: enable rand::rngs::ChaCha*Rng -chacha = ["dep:chacha20"] +# chacha = ["dep:chacha20"] # Option: use unbiased sampling for algorithms supporting this option: Uniform distribution. # By default, bias affecting no more than one in 2^48 samples is accepted. @@ -67,7 +67,7 @@ log = ["dep:log"] [workspace] members = [ - "rand_chacha", + # "rand_chacha", "rand_pcg", ] exclude = ["benches", "distr_test"] @@ -76,7 +76,7 @@ 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 } getrandom = { version = "0.3.0", optional = true } [dev-dependencies] @@ -85,3 +85,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/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/lib.rs b/src/lib.rs index 3ae859eed4..28278173b5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -368,7 +368,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..390da39abc 100644 --- a/src/rngs/reseeding.rs +++ b/src/rngs/reseeding.rs @@ -11,8 +11,6 @@ //! 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 @@ -67,14 +65,20 @@ use rand_core::{CryptoRng, RngCore, SeedableRng, TryCryptoRng, TryRngCore}; /// [`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 +87,7 @@ 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 { + 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,66 @@ 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. + pub fn reseed(&mut self) -> Result<(), Rsdr::Error> { + self.prng = R::try_from_rng(&mut self.reseed_rng)?; + Ok(()) + } + + 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] + 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, { } 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()) } } From f5cce31f1b0c2bda47e66e19a48c5157f071921a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D1=91=D0=BC=20=D0=9F=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=BE=D0=B2=20=5BArtyom=20Pavlov=5D?= Date: Wed, 19 Nov 2025 20:25:38 +0300 Subject: [PATCH 02/17] port rand_chacha --- Cargo.toml | 2 +- rand_chacha/src/chacha.rs | 80 ++++++++++++++++++++++----------------- 2 files changed, 46 insertions(+), 36 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f5522b5211..8d2edd312d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -67,7 +67,7 @@ log = ["dep:log"] [workspace] members = [ - # "rand_chacha", + "rand_chacha", "rand_pcg", ] exclude = ["benches", "distr_test"] diff --git a/rand_chacha/src/chacha.rs b/rand_chacha/src/chacha.rs index f42232f757..889ab5fa04 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}; #[cfg(feature = "serde")] use serde::{Deserialize, Deserializer, Serialize, Serializer}; @@ -82,28 +81,19 @@ macro_rules! chacha_impl { } } - impl BlockRngCore for $ChaChaXCore { - type Item = u32; - type Results = Array64; - - #[inline] - fn generate(&mut self, r: &mut Self::Results) { - self.state.refill4($rounds, &mut r.0); - } - } - - impl SeedableRng for $ChaChaXCore { - type Seed = [u8; 32]; - + impl $ChaChaXCore { #[inline] - fn from_seed(seed: Self::Seed) -> Self { + fn from_seed(seed: [u8; 32]) -> Self { $ChaChaXCore { state: ChaCha::new(&seed, &[0u8; 8]), } } - } - impl CryptoBlockRng for $ChaChaXCore {} + #[inline] + fn next_block(&mut self, r: &mut [u32; 64]) { + self.state.refill4($rounds, r); + } + } /// A cryptographically secure random number generator that uses the ChaCha algorithm. /// @@ -145,7 +135,24 @@ macro_rules! chacha_impl { /// http://www.ecrypt.eu.org/stream/) #[derive(Clone, Debug)] pub struct $ChaChaXRng { - rng: BlockRng<$ChaChaXCore>, + core: $ChaChaXCore, + buffer: [u32; 64], + } + + impl $ChaChaXRng { + fn buffer_index(&self) -> u32 { + self.buffer[0] + } + + fn generate_and_set(&mut self, index: usize) { + assert!(index < self.buffer.len()); + self.buffer[0] = if index != 0 { + self.core.next_block(&mut self.buffer); + index as u32 + } else { + self.buffer.len() as u32 + } + } } impl SeedableRng for $ChaChaXRng { @@ -153,9 +160,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: le::new_buffer(), } } } @@ -163,17 +170,20 @@ macro_rules! chacha_impl { impl RngCore for $ChaChaXRng { #[inline] fn next_u32(&mut self) -> u32 { - self.rng.next_u32() + let Self { core, buffer } = self; + le::next_word_via_gen_block(buffer, |block| core.next_block(block)) } #[inline] fn next_u64(&mut self) -> u64 { - self.rng.next_u64() + let Self { core, buffer } = self; + le::next_u64_via_gen_block(buffer, |block| 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]) { + let Self { core, buffer } = self; + le::fill_bytes_via_gen_block(dst, buffer, |block| core.next_block(block)); } } @@ -190,11 +200,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 +222,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 +239,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 +249,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 +264,8 @@ macro_rules! chacha_impl { impl From<$ChaChaXCore> for $ChaChaXRng { fn from(core: $ChaChaXCore) -> Self { $ChaChaXRng { - rng: BlockRng::new(core), + core, + buffer: le::new_buffer(), } } } From 90b9fee9a1b1bb0e7760f1a63ae1bb29200f82b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D1=91=D0=BC=20=D0=9F=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=BE=D0=B2=20=5BArtyom=20Pavlov=5D?= Date: Wed, 19 Nov 2025 20:29:24 +0300 Subject: [PATCH 03/17] patch rand_core for benches --- benches/Cargo.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/benches/Cargo.toml b/benches/Cargo.toml index 699186b26f..54d2b6b989 100644 --- a/benches/Cargo.toml +++ b/benches/Cargo.toml @@ -56,3 +56,6 @@ harness = false [[bench]] name = "weighted" harness = false + +[patch.crates-io] +rand_core = { git = "https://github.com/rust-random/rand_core", branch = "block_buffer" } From 0baca571e904dbabc4ad7f1db5fa43bc9a6716e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D1=91=D0=BC=20=D0=9F=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=BE=D0=B2=20=5BArtyom=20Pavlov=5D?= Date: Wed, 19 Nov 2025 20:29:31 +0300 Subject: [PATCH 04/17] fix some warnings --- rand_chacha/src/chacha.rs | 46 --------------------------------------- src/distr/uniform_int.rs | 2 +- 2 files changed, 1 insertion(+), 47 deletions(-) diff --git a/rand_chacha/src/chacha.rs b/rand_chacha/src/chacha.rs index 889ab5fa04..b18b6822f3 100644 --- a/rand_chacha/src/chacha.rs +++ b/rand_chacha/src/chacha.rs @@ -20,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] 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") From 9ba4cadcfe65ef6d1510216e0cbab58578e52083 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D1=91=D0=BC=20=D0=9F=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=BE=D0=B2=20=5BArtyom=20Pavlov=5D?= Date: Wed, 19 Nov 2025 20:36:19 +0300 Subject: [PATCH 05/17] use rand_chacha instead of chacha20 --- Cargo.toml | 7 ++++--- src/rngs/reseeding.rs | 8 ++++---- src/rngs/std.rs | 3 --- src/rngs/thread.rs | 6 +++--- 4 files changed, 11 insertions(+), 13 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8d2edd312d..73e54ad84c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,16 +46,16 @@ os_rng = ["dep:getrandom"] simd_support = [] # Option (enabled by default): enable StdRng -# std_rng = ["dep:chacha20"] +std_rng = ["dep:chacha20"] # Option: enable SmallRng small_rng = [] # Option: enable ThreadRng and rng() -# thread_rng = ["std", "std_rng", "os_rng"] +thread_rng = ["std", "std_rng", "os_rng"] # Option: enable rand::rngs::ChaCha*Rng -# chacha = ["dep:chacha20"] +chacha = ["dep:chacha20"] # Option: use unbiased sampling for algorithms supporting this option: Uniform distribution. # By default, bias affecting no more than one in 2^48 samples is accepted. @@ -77,6 +77,7 @@ 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 = { path = "rand_chacha", optional = true, package = "rand_chacha" } getrandom = { version = "0.3.0", optional = true } [dev-dependencies] diff --git a/src/rngs/reseeding.rs b/src/rngs/reseeding.rs index 390da39abc..2c1f64da31 100644 --- a/src/rngs/reseeding.rs +++ b/src/rngs/reseeding.rs @@ -50,13 +50,13 @@ 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::()); /// ``` @@ -168,7 +168,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; @@ -177,7 +177,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..55914eca47 100644 --- a/src/rngs/thread.rs +++ b/src/rngs/thread.rs @@ -13,7 +13,7 @@ use std::fmt; use std::rc::Rc; use std::thread_local; -use super::{OsError, OsRng, ReseedingRng, std::Core}; +use super::{OsError, OsRng, ReseedingRng, std::StdRng}; use rand_core::{CryptoRng, RngCore}; // Rationale for using `UnsafeCell` in `ThreadRng`: @@ -90,7 +90,7 @@ const THREAD_RNG_RESEED_THRESHOLD: u64 = 1024 * 64; #[derive(Clone)] pub struct ThreadRng { // Rc is explicitly !Send and !Sync - rng: Rc>>, + rng: Rc>>, } impl ThreadRng { @@ -115,7 +115,7 @@ 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>> = { + static THREAD_RNG_KEY: Rc>> = { let rng = ReseedingRng::new(THREAD_RNG_RESEED_THRESHOLD, OsRng).unwrap_or_else(|err| panic!("could not initialize ThreadRng: {}", err)); From 569262ae22e09c474586fb22dc0b57f48efede66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D1=91=D0=BC=20=D0=9F=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=BE=D0=B2=20=5BArtyom=20Pavlov=5D?= Date: Wed, 19 Nov 2025 20:39:42 +0300 Subject: [PATCH 06/17] fix benches --- benches/Cargo.toml | 2 +- benches/benches/generators.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/benches/Cargo.toml b/benches/Cargo.toml index 54d2b6b989..724207708a 100644 --- a/benches/Cargo.toml +++ b/benches/Cargo.toml @@ -11,7 +11,7 @@ simd_support = ["rand/simd_support"] [dependencies] [dev-dependencies] -rand = { path = "..", features = ["small_rng", "nightly"] } +rand = { path = "..", features = ["small_rng", "nightly", "thread_rng"] } rand_pcg = { path = "../rand_pcg" } rand_chacha = { path = "../rand_chacha" } criterion = "0.5" 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); From 3f4c4c869ceffaa5d247ca1d543c8d93bd312449 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D1=91=D0=BC=20=D0=9F=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=BE=D0=B2=20=5BArtyom=20Pavlov=5D?= Date: Wed, 19 Nov 2025 20:50:32 +0300 Subject: [PATCH 07/17] fix warning --- src/rngs/reseeding.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rngs/reseeding.rs b/src/rngs/reseeding.rs index 2c1f64da31..82c90eaea1 100644 --- a/src/rngs/reseeding.rs +++ b/src/rngs/reseeding.rs @@ -119,8 +119,8 @@ where 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}"); + if let Err(_e) = res { + warn!("Reseeding RNG failed: {_e}"); } self.bytes_until_reseed = self.threshold; } From 47676619b9f5e8d2e423ef78b331e1527f3da7a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D1=91=D0=BC=20=D0=9F=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=BE=D0=B2=20=5BArtyom=20Pavlov=5D?= Date: Wed, 19 Nov 2025 20:56:00 +0300 Subject: [PATCH 08/17] fix doc --- src/rngs/reseeding.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/rngs/reseeding.rs b/src/rngs/reseeding.rs index 82c90eaea1..d0a237cb86 100644 --- a/src/rngs/reseeding.rs +++ b/src/rngs/reseeding.rs @@ -13,7 +13,7 @@ use core::mem::size_of_val; 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: @@ -61,7 +61,6 @@ use rand_core::{CryptoRng, RngCore, SeedableRng, TryCryptoRng, TryRngCore}; /// println!("{}", reseeding_rng.random::()); /// ``` /// -/// [`BlockRngCore`]: rand_core::block::BlockRngCore /// [`ReseedingRng::new`]: ReseedingRng::new /// [`reseed()`]: ReseedingRng::reseed #[derive(Debug)] From 60baaba3484878257e9dec918992cadbc13ef6f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D1=91=D0=BC=20=D0=9F=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=BE=D0=B2=20=5BArtyom=20Pavlov=5D?= Date: Wed, 19 Nov 2025 21:07:23 +0300 Subject: [PATCH 09/17] uncomment default features --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 73e54ad84c..28e83158a1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,7 @@ features = ["small_rng", "serde"] [features] # Meta-features: -# default = ["std", "std_rng", "os_rng", "small_rng", "thread_rng"] +default = ["std", "std_rng", "os_rng", "small_rng", "thread_rng"] nightly = [] # some additions requiring nightly Rust serde = ["dep:serde"] From 2154e9fd74baf0de485f07342e40f57c8a4d586e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D1=91=D0=BC=20=D0=9F=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=BE=D0=B2=20=5BArtyom=20Pavlov=5D?= Date: Wed, 19 Nov 2025 21:10:19 +0300 Subject: [PATCH 10/17] fix rand_chacha docs --- rand_chacha/src/chacha.rs | 2 +- rand_chacha/src/lib.rs | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/rand_chacha/src/chacha.rs b/rand_chacha/src/chacha.rs index b18b6822f3..4a2b04aa44 100644 --- a/rand_chacha/src/chacha.rs +++ b/rand_chacha/src/chacha.rs @@ -80,7 +80,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) diff --git a/rand_chacha/src/lib.rs b/rand_chacha/src/lib.rs index 112ffe6f40..ce1ad76e7e 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 From 911038772dc5108de849067005aa10e07ae80e07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D1=91=D0=BC=20=D0=9F=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=BE=D0=B2=20=5BArtyom=20Pavlov=5D?= Date: Wed, 19 Nov 2025 21:23:19 +0300 Subject: [PATCH 11/17] add inline attributes --- src/rngs/reseeding.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/rngs/reseeding.rs b/src/rngs/reseeding.rs index d0a237cb86..5e71ca6675 100644 --- a/src/rngs/reseeding.rs +++ b/src/rngs/reseeding.rs @@ -86,6 +86,7 @@ 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. + #[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. @@ -110,11 +111,13 @@ where /// 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(); @@ -144,7 +147,7 @@ where self.prng.next_u64() } - #[inline] + #[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. From e338cd2661f6f1b6daabca2980d9c2c1e083806a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D1=91=D0=BC=20=D0=9F=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=BE=D0=B2=20=5BArtyom=20Pavlov=5D?= Date: Fri, 21 Nov 2025 16:00:48 +0300 Subject: [PATCH 12/17] remove `criterion-cycles-per-byte` dependency --- benches/Cargo.toml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/benches/Cargo.toml b/benches/Cargo.toml index 724207708a..bde03de878 100644 --- a/benches/Cargo.toml +++ b/benches/Cargo.toml @@ -11,11 +11,10 @@ simd_support = ["rand/simd_support"] [dependencies] [dev-dependencies] -rand = { path = "..", features = ["small_rng", "nightly", "thread_rng"] } +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" From 288ae1851caf0ad401ef429c54e9a035f08245f7 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Mon, 24 Nov 2025 07:48:54 +0000 Subject: [PATCH 13/17] Use branch push-wmrnlsxrmtrl --- Cargo.toml | 2 +- benches/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 28e83158a1..2fa72f9fe5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -88,4 +88,4 @@ rayon = "1.7" serde_json = "1.0.140" [patch.crates-io] -rand_core = { git = "https://github.com/rust-random/rand_core", branch = "block_buffer" } +rand_core = { git = "https://github.com/rust-random/rand_core", branch = "push-wmrnlsxrmtrl" } diff --git a/benches/Cargo.toml b/benches/Cargo.toml index bde03de878..603a3cc82a 100644 --- a/benches/Cargo.toml +++ b/benches/Cargo.toml @@ -57,4 +57,4 @@ name = "weighted" harness = false [patch.crates-io] -rand_core = { git = "https://github.com/rust-random/rand_core", branch = "block_buffer" } +rand_core = { git = "https://github.com/rust-random/rand_core", branch = "push-wmrnlsxrmtrl" } From d53a946667bb45f99f6737dc36df21e93827e5b4 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Mon, 24 Nov 2025 07:54:00 +0000 Subject: [PATCH 14/17] rand_chacha: use trait Generator --- rand_chacha/src/chacha.rs | 37 +++++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/rand_chacha/src/chacha.rs b/rand_chacha/src/chacha.rs index 4a2b04aa44..d81c040ba1 100644 --- a/rand_chacha/src/chacha.rs +++ b/rand_chacha/src/chacha.rs @@ -10,7 +10,7 @@ use crate::guts::ChaCha; use core::fmt; -use rand_core::{CryptoRng, RngCore, SeedableRng, le}; +use rand_core::{le, CryptoRng, RngCore, SeedableRng, Generator, CryptoGenerator}; #[cfg(feature = "serde")] use serde::{Deserialize, Deserializer, Serialize, Serializer}; @@ -35,20 +35,28 @@ macro_rules! chacha_impl { } } - impl $ChaChaXCore { + impl Generator for $ChaChaXCore { + type Result = [u32; 64]; + #[inline] - fn from_seed(seed: [u8; 32]) -> Self { - $ChaChaXCore { - state: ChaCha::new(&seed, &[0u8; 8]), - } + fn generate(&mut self, r: &mut Self::Result) { + self.state.refill4($rounds, r); } + } + + impl SeedableRng for $ChaChaXCore { + type Seed = [u8; 32]; #[inline] - fn next_block(&mut self, r: &mut [u32; 64]) { - self.state.refill4($rounds, r); + fn from_seed(seed: Self::Seed) -> Self { + $ChaChaXCore { + state: ChaCha::new(&seed, &[0u8; 8]), + } } } + impl CryptoGenerator 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 @@ -80,7 +88,7 @@ macro_rules! chacha_impl { /// ``` /// /// This implementation uses an output buffer of sixteen `u32` words, and uses - /// them to implement the [`RngCore`] methods. + /// [`BlockRng`] to implement the [`RngCore`] methods. /// /// [^1]: D. J. Bernstein, [*ChaCha, a variant of Salsa20*]( /// https://cr.yp.to/chacha.html) @@ -101,7 +109,7 @@ macro_rules! chacha_impl { fn generate_and_set(&mut self, index: usize) { assert!(index < self.buffer.len()); self.buffer[0] = if index != 0 { - self.core.next_block(&mut self.buffer); + self.core.generate(&mut self.buffer); index as u32 } else { self.buffer.len() as u32 @@ -125,19 +133,19 @@ macro_rules! chacha_impl { #[inline] fn next_u32(&mut self) -> u32 { let Self { core, buffer } = self; - le::next_word_via_gen_block(buffer, |block| core.next_block(block)) + le::next_word_via_gen_block(buffer, |block| core.generate(block)) } #[inline] fn next_u64(&mut self) -> u64 { let Self { core, buffer } = self; - le::next_u64_via_gen_block(buffer, |block| core.next_block(block)) + le::next_u64_via_gen_block(buffer, |block| core.generate(block)) } #[inline] fn fill_bytes(&mut self, dst: &mut [u8]) { let Self { core, buffer } = self; - le::fill_bytes_via_gen_block(dst, buffer, |block| core.next_block(block)); + le::fill_bytes_via_gen_block(dst, buffer, |block| core.generate(block)); } } @@ -177,7 +185,8 @@ macro_rules! chacha_impl { pub fn set_word_pos(&mut self, word_offset: u128) { let block = (word_offset / u128::from(BLOCK_WORDS)) as u64; self.core.state.set_block_pos(block); - self.generate_and_set((word_offset % u128::from(BLOCK_WORDS)) as usize); + self + .generate_and_set((word_offset % u128::from(BLOCK_WORDS)) as usize); } /// Set the stream number. From 31d28f74dbeb8533adcb5fd584607196f23fa807 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Mon, 24 Nov 2025 08:11:47 +0000 Subject: [PATCH 15/17] ReseedingRng: use trait Generator --- rand_chacha/src/chacha.rs | 5 +- src/rngs/reseeding.rs | 203 ++++++++++++++++++++++++++------------ src/rngs/std.rs | 3 + src/rngs/thread.rs | 6 +- 4 files changed, 148 insertions(+), 69 deletions(-) diff --git a/rand_chacha/src/chacha.rs b/rand_chacha/src/chacha.rs index d81c040ba1..bb2d37ef79 100644 --- a/rand_chacha/src/chacha.rs +++ b/rand_chacha/src/chacha.rs @@ -10,7 +10,7 @@ use crate::guts::ChaCha; use core::fmt; -use rand_core::{le, CryptoRng, RngCore, SeedableRng, Generator, CryptoGenerator}; +use rand_core::{CryptoGenerator, CryptoRng, Generator, RngCore, SeedableRng, le}; #[cfg(feature = "serde")] use serde::{Deserialize, Deserializer, Serialize, Serializer}; @@ -185,8 +185,7 @@ macro_rules! chacha_impl { pub fn set_word_pos(&mut self, word_offset: u128) { let block = (word_offset / u128::from(BLOCK_WORDS)) as u64; self.core.state.set_block_pos(block); - self - .generate_and_set((word_offset % u128::from(BLOCK_WORDS)) as usize); + self.generate_and_set((word_offset % u128::from(BLOCK_WORDS)) as usize); } /// Set the stream number. diff --git a/src/rngs/reseeding.rs b/src/rngs/reseeding.rs index 5e71ca6675..4a6f85f3aa 100644 --- a/src/rngs/reseeding.rs +++ b/src/rngs/reseeding.rs @@ -10,10 +10,15 @@ //! A wrapper around another PRNG that reseeds it after it //! generates a certain number of random bytes. +use core::fmt; use core::mem::size_of_val; -use rand_core::{CryptoRng, RngCore, SeedableRng, TryCryptoRng, TryRngCore}; -/// A wrapper around any PRNG that implements [`RngCore`], that adds the +use rand_core::le::{self, Word}; +use rand_core::{ + CryptoGenerator, CryptoRng, Generator, RngCore, SeedableRng, TryCryptoRng, TryRngCore, +}; + +/// A wrapper around any PRNG that implements [`Generator`], that adds the /// ability to reseed it. /// /// `ReseedingRng` reseeds the underlying PRNG in the following cases: @@ -50,34 +55,46 @@ use rand_core::{CryptoRng, RngCore, SeedableRng, TryCryptoRng, TryRngCore}; /// # Example /// /// ``` -/// use chacha20::ChaCha20Rng; // Internal part of ChaChaRng that -/// // implements BlockRngCore +/// use chacha20::ChaCha20Core; // Internal part of ChaChaRng that +/// // implements Generator /// 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::()); /// ``` /// +/// [`Generator`]: rand_core::Generator /// [`ReseedingRng::new`]: ReseedingRng::new /// [`reseed()`]: ReseedingRng::reseed -#[derive(Debug)] pub struct ReseedingRng where - R: RngCore + SeedableRng, + R: Generator + SeedableRng, Rsdr: TryRngCore, { - prng: R, - reseed_rng: Rsdr, - threshold: i64, - bytes_until_reseed: i64, + core: ReseedingCore, + buffer: ::Result, +} + +impl fmt::Debug for ReseedingRng +where + R: Generator + SeedableRng, + R::Result: fmt::Debug, + Rsdr: TryRngCore, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("ReseedingRng") + .field("core", &self.core) + .field("buffer", &self.buffer) + .finish() + } } -impl ReseedingRng +impl ReseedingRng where - R: RngCore + SeedableRng, + R: Generator + SeedableRng, Rsdr: TryRngCore, { /// Create a new `ReseedingRng` from an existing PRNG, combined with a RNG @@ -86,8 +103,93 @@ 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 { + core: ReseedingCore::new(threshold, reseeder)?, + buffer: le::new_buffer(), + }) + } + + /// Immediately reseed the generator + /// + /// This discards any remaining random data in the cache. + pub fn reseed(&mut self) -> Result<(), Rsdr::Error> { + self.buffer = le::new_buffer(); + self.core.reseed() + } +} + +impl RngCore for ReseedingRng +where + R: Generator + SeedableRng, + Rsdr: TryRngCore, +{ + #[inline] + fn next_u32(&mut self) -> u32 { + let Self { core, buffer } = self; + // NOTE: this fn always returns one word, thus cannot return u32 for any W: Word + le::next_word_via_gen_block(buffer, |block| core.generate(block)) + } + + #[inline] + fn next_u64(&mut self) -> u64 { + let Self { core, buffer } = self; + // NOTE: this fn is specific to u32 buffers + le::next_u64_via_gen_block(buffer, |block| core.generate(block)) + } + #[inline] - pub fn new(threshold: u64, mut reseed_rng: Rsdr) -> Result { + fn fill_bytes(&mut self, dst: &mut [u8]) { + let Self { core, buffer } = self; + le::fill_bytes_via_gen_block(dst, buffer, |block| core.generate(block)); + } +} + +impl CryptoRng for ReseedingRng +where + R: Generator + SeedableRng + CryptoGenerator, + Rsdr: TryCryptoRng, +{ +} + +#[derive(Debug)] +struct ReseedingCore { + inner: R, + reseeder: Rsdr, + threshold: i64, + bytes_until_reseed: i64, +} + +impl Generator for ReseedingCore +where + R: Generator + SeedableRng, + Rsdr: TryRngCore, +{ + type Result = ::Result; + + fn generate(&mut self, results: &mut Self::Result) { + 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); + self.bytes_until_reseed -= num_bytes as i64; + self.inner.generate(results); + } +} + +impl ReseedingCore +where + R: Generator + SeedableRng, + Rsdr: TryRngCore, +{ + /// Create a new `ReseedingCore`. + /// + /// `threshold` is the maximum number of bytes produced by + /// [`Generator::generate`] before attempting reseeding. + fn new(threshold: u64, mut reseeder: 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 @@ -100,68 +202,43 @@ where i64::MAX }; - R::try_from_rng(&mut reseed_rng).map(|prng| Self { - prng, - reseed_rng, + let inner = R::try_from_rng(&mut reseeder)?; + + Ok(ReseedingCore { + inner, + reseeder, threshold, bytes_until_reseed: threshold, }) } - /// 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}"); - } + /// Reseed the internal PRNG. + fn reseed(&mut self) -> Result<(), Rsdr::Error> { + R::try_from_rng(&mut self.reseeder).map(|result| { self.bytes_until_reseed = self.threshold; - } - self.bytes_until_reseed -= bytes as i64; + self.inner = result + }) } -} -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() - } + #[inline(never)] + fn reseed_and_generate(&mut self, results: &mut ::Result) { + trace!("Reseeding RNG (periodic reseed)"); - #[inline(always)] - fn next_u64(&mut self) -> u64 { - self.reseed_check(size_of::()); - self.prng.next_u64() - } + let num_bytes = size_of_val(results); - #[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; + if let Err(e) = self.reseed() { + warn!("Reseeding RNG failed: {}", e); + let _ = e; } - self.reseed_check(size_of_val(dst)); - self.prng.fill_bytes(dst) + + self.bytes_until_reseed = self.threshold - num_bytes as i64; + self.inner.generate(results); } } -impl CryptoRng for ReseedingRng +impl CryptoGenerator for ReseedingCore where - R: CryptoRng + SeedableRng, + R: Generator + SeedableRng + CryptoGenerator, Rsdr: TryCryptoRng, { } @@ -170,7 +247,7 @@ where #[cfg(test)] mod test { use crate::Rng; - use crate::rngs::std::StdRng; + use crate::rngs::std::Core; use crate::test::const_rng; use super::ReseedingRng; @@ -179,7 +256,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 b62b62c0c8..3fc3e491e1 100644 --- a/src/rngs/std.rs +++ b/src/rngs/std.rs @@ -10,6 +10,9 @@ 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 55914eca47..5acb90df0d 100644 --- a/src/rngs/thread.rs +++ b/src/rngs/thread.rs @@ -13,7 +13,7 @@ use std::fmt; use std::rc::Rc; use std::thread_local; -use super::{OsError, OsRng, ReseedingRng, std::StdRng}; +use super::{OsError, OsRng, ReseedingRng, std::Core}; use rand_core::{CryptoRng, RngCore}; // Rationale for using `UnsafeCell` in `ThreadRng`: @@ -90,7 +90,7 @@ const THREAD_RNG_RESEED_THRESHOLD: u64 = 1024 * 64; #[derive(Clone)] pub struct ThreadRng { // Rc is explicitly !Send and !Sync - rng: Rc>>, + rng: Rc>>, } impl ThreadRng { @@ -115,7 +115,7 @@ 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>> = { + static THREAD_RNG_KEY: Rc>> = { let rng = ReseedingRng::new(THREAD_RNG_RESEED_THRESHOLD, OsRng).unwrap_or_else(|err| panic!("could not initialize ThreadRng: {}", err)); From 068ef04f9ad86738fc8c197b05b2f3499594c744 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Mon, 24 Nov 2025 08:59:27 +0000 Subject: [PATCH 16/17] Fix benches --- benches/benches/generators.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/benches/benches/generators.rs b/benches/benches/generators.rs index 1f1d427358..1d22b1aa6c 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, ChaCha20Rng}; +use rand_chacha::{ChaCha8Rng, ChaCha12Rng, ChaCha20Core, 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); From 36215497fb24cee0f328b2c2d048c7d6157ae1b8 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Fri, 28 Nov 2025 09:43:20 +0000 Subject: [PATCH 17/17] Inline fn ReseedingCore::reseed_and_generate The noted perf. advantage does not hold in current benchmarking --- src/rngs/reseeding.rs | 27 ++++++++------------------- 1 file changed, 8 insertions(+), 19 deletions(-) diff --git a/src/rngs/reseeding.rs b/src/rngs/reseeding.rs index 4a6f85f3aa..70dadf0625 100644 --- a/src/rngs/reseeding.rs +++ b/src/rngs/reseeding.rs @@ -169,10 +169,14 @@ where fn generate(&mut self, results: &mut Self::Result) { 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); + trace!("Reseeding RNG (periodic reseed)"); + + if let Err(e) = self.reseed() { + warn!("Reseeding RNG failed: {}", e); + let _ = e; + } + + self.bytes_until_reseed = self.threshold; } let num_bytes = size_of_val(results); self.bytes_until_reseed -= num_bytes as i64; @@ -219,21 +223,6 @@ where self.inner = result }) } - - #[inline(never)] - fn reseed_and_generate(&mut self, results: &mut ::Result) { - trace!("Reseeding RNG (periodic reseed)"); - - let num_bytes = size_of_val(results); - - if let Err(e) = self.reseed() { - warn!("Reseeding RNG failed: {}", e); - let _ = e; - } - - self.bytes_until_reseed = self.threshold - num_bytes as i64; - self.inner.generate(results); - } } impl CryptoGenerator for ReseedingCore