Skip to content

Commit d45cf03

Browse files
committed
Use BlockRng in ReseedingRng
1 parent 60ffa44 commit d45cf03

File tree

5 files changed

+132
-106
lines changed

5 files changed

+132
-106
lines changed

benches/generators.rs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use test::{black_box, Bencher};
1212
use rand::{RngCore, Rng, SeedableRng, NewRng, StdRng, OsRng, JitterRng, EntropyRng};
1313
use rand::{XorShiftRng, Hc128Rng, IsaacRng, Isaac64Rng, ChaChaRng};
1414
use rand::reseeding::ReseedingRng;
15+
use rand::prng::hc128::Hc128Core;
1516
use rand::thread_rng;
1617

1718
macro_rules! gen_bytes {
@@ -151,10 +152,13 @@ chacha_rounds!(gen_bytes_chacha12, gen_u32_chacha12, gen_u64_chacha12, 12);
151152
chacha_rounds!(gen_bytes_chacha20, gen_u32_chacha20, gen_u64_chacha20, 20);
152153

153154

155+
const RESEEDING_THRESHOLD: u64 = 1024*1024*1024; // something high enough to get
156+
// deterministic measurements
157+
154158
#[bench]
155159
fn reseeding_hc128_bytes(b: &mut Bencher) {
156-
let mut rng = ReseedingRng::new(Hc128Rng::new().unwrap(),
157-
128*1024*1024,
160+
let mut rng = ReseedingRng::new(Hc128Core::new().unwrap(),
161+
RESEEDING_THRESHOLD,
158162
EntropyRng::new());
159163
let mut buf = [0u8; BYTES_LEN];
160164
b.iter(|| {
@@ -170,8 +174,8 @@ macro_rules! reseeding_uint {
170174
($fnn:ident, $ty:ty) => {
171175
#[bench]
172176
fn $fnn(b: &mut Bencher) {
173-
let mut rng = ReseedingRng::new(Hc128Rng::new().unwrap(),
174-
128*1024*1024,
177+
let mut rng = ReseedingRng::new(Hc128Core::new().unwrap(),
178+
RESEEDING_THRESHOLD,
175179
EntropyRng::new());
176180
b.iter(|| {
177181
for _ in 0..RAND_BENCH_N {

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,7 @@ pub use jitter::JitterRng;
278278
#[cfg(feature="std")] pub use os::OsRng;
279279

280280
// pseudo rngs
281+
pub mod prng;
281282
pub use isaac::{IsaacRng, Isaac64Rng};
282283
pub use chacha::ChaChaRng;
283284
pub use prng::XorShiftRng;
@@ -317,7 +318,6 @@ pub mod isaac {
317318
mod le;
318319
#[cfg(feature="std")] mod entropy_rng;
319320
mod error;
320-
mod prng;
321321
#[cfg(feature="std")] mod thread_rng;
322322

323323

src/prng/mod.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@
4040
//! same algorithm, it is possible that both will yield the same sequence of
4141
//! values (with some lag).
4242
43-
mod chacha;
44-
mod hc128;
43+
pub mod chacha;
44+
pub mod hc128;
4545
mod isaac;
4646
mod isaac64;
4747
mod xorshift;
@@ -53,4 +53,4 @@ pub use self::chacha::ChaChaRng;
5353
pub use self::hc128::Hc128Rng;
5454
pub use self::isaac::IsaacRng;
5555
pub use self::isaac64::Isaac64Rng;
56-
pub use self::xorshift::XorShiftRng;
56+
pub use self::xorshift::XorShiftRng;

src/reseeding.rs

Lines changed: 109 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
//! A wrapper around another PRNG that reseeds it after it
1212
//! generates a certain number of random bytes.
1313
14-
use {RngCore, SeedableRng, Error, ErrorKind};
14+
use {RngCore, BlockRngCore, SeedableRng, Error, ErrorKind};
15+
use impls::BlockRng;
1516

1617
/// A wrapper around any PRNG which reseeds the underlying PRNG after it has
1718
/// generated a certain number of random bytes.
@@ -39,73 +40,125 @@ use {RngCore, SeedableRng, Error, ErrorKind};
3940
/// `ReseedingRng` with the ISAAC RNG. That algorithm, although apparently
4041
/// strong and with no known attack, does not come with any proof of security
4142
/// and does not meet the current standards for a cryptographically secure
42-
/// PRNG. By reseeding it frequently (every 32 MiB) it seems safe to assume
43+
/// PRNG. By reseeding it frequently (every 32 kiB) it seems safe to assume
4344
/// there is no attack that can operate on the tiny window between reseeds.
4445
///
4546
/// # Error handling
4647
///
4748
/// If reseeding fails, `try_fill_bytes` is the only `Rng` method to report it.
4849
/// For all other `Rng` methods, `ReseedingRng` will not panic but try to
49-
/// handle the error intelligently; if handling the source error fails these
50+
/// handle the error intelligently through some combination of retrying and
51+
/// delaying reseeding until later. If handling the source error fails these
5052
/// methods will continue generating data from the wrapped PRNG without
5153
/// reseeding.
52-
///
53-
/// It is usually best to use the infallible methods `next_u32`, `next_u64` and
54-
/// `fill_bytes` because they can make use of this error handling strategy.
55-
/// Use `try_fill_bytes` and possibly `try_reseed` if you want to handle
56-
/// reseeding errors explicitly.
5754
#[derive(Debug)]
58-
pub struct ReseedingRng<R, Rsdr> {
59-
rng: R,
60-
reseeder: Rsdr,
61-
threshold: i64,
62-
bytes_until_reseed: i64,
63-
}
55+
pub struct ReseedingRng<R, Rsdr>(BlockRng<Reseeder<R, Rsdr>>)
56+
where R: BlockRngCore<u32> + SeedableRng,
57+
Rsdr: RngCore;
6458

65-
impl<R: RngCore + SeedableRng, Rsdr: RngCore> ReseedingRng<R, Rsdr> {
59+
impl<R, Rsdr> ReseedingRng<R, Rsdr>
60+
where R: BlockRngCore<u32> + SeedableRng,
61+
Rsdr: RngCore
62+
{
6663
/// Create a new `ReseedingRng` with the given parameters.
6764
///
6865
/// # Arguments
6966
///
7067
/// * `rng`: the random number generator to use.
7168
/// * `threshold`: the number of generated bytes after which to reseed the RNG.
7269
/// * `reseeder`: the RNG to use for reseeding.
73-
pub fn new(rng: R, threshold: u64, reseeder: Rsdr) -> ReseedingRng<R,Rsdr> {
70+
pub fn new(rng: R, threshold: u64, reseeder: Rsdr)
71+
-> ReseedingRng<R, Rsdr>
72+
{
7473
assert!(threshold <= ::core::i64::MAX as u64);
75-
ReseedingRng {
76-
rng: rng,
77-
reseeder: reseeder,
78-
threshold: threshold as i64,
79-
bytes_until_reseed: threshold as i64,
74+
let results_empty = R::Results::default();
75+
ReseedingRng(
76+
BlockRng {
77+
core: Reseeder {
78+
core: rng,
79+
reseeder: reseeder,
80+
threshold: threshold as i64,
81+
bytes_until_reseed: threshold as i64,
82+
},
83+
index: results_empty.as_ref().len(), // generate on first use
84+
results: results_empty,
85+
}
86+
)
87+
}
88+
89+
/// Reseed the internal PRNG.
90+
pub fn reseed(&mut self) -> Result<(), Error> {
91+
self.0.core.reseed()
92+
}
93+
}
94+
95+
impl<R: BlockRngCore<u32> + SeedableRng, Rsdr: RngCore> RngCore for ReseedingRng<R, Rsdr> {
96+
#[inline]
97+
fn next_u32(&mut self) -> u32 {
98+
self.0.next_u32()
99+
}
100+
101+
#[inline]
102+
fn next_u64(&mut self) -> u64 {
103+
self.0.next_u64()
104+
}
105+
106+
#[inline]
107+
fn fill_bytes(&mut self, dest: &mut [u8]) {
108+
self.0.fill_bytes(dest)
109+
}
110+
111+
#[inline]
112+
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> {
113+
self.0.try_fill_bytes(dest)
114+
}
115+
}
116+
117+
#[derive(Debug)]
118+
struct Reseeder<R, Rsdr> {
119+
core: R,
120+
reseeder: Rsdr,
121+
threshold: i64,
122+
bytes_until_reseed: i64,
123+
}
124+
125+
impl<R, Rsdr> BlockRngCore<u32> for Reseeder<R, Rsdr>
126+
where R: BlockRngCore<u32> + SeedableRng,
127+
Rsdr: RngCore
128+
{
129+
type Results = <R as BlockRngCore<u32>>::Results;
130+
131+
fn generate(&mut self, results: &mut Self::Results) -> Result<(), Error> {
132+
if self.bytes_until_reseed <= 0 {
133+
// In the unlikely event the internal PRNG fails, we don't know
134+
// whether this is resolvable; schedule to reseed on next use and
135+
// return original error kind.
136+
return self.reseed_and_generate(results);
80137
}
138+
self.bytes_until_reseed -= results.as_ref().len() as i64 * 4;
139+
self.core.generate(results)
81140
}
141+
}
82142

143+
impl<R, Rsdr> Reseeder<R, Rsdr>
144+
where R: BlockRngCore<u32> + SeedableRng,
145+
Rsdr: RngCore
146+
{
83147
/// Reseed the internal PRNG.
84-
///
85-
/// This will try to work around errors in the RNG used for reseeding
86-
/// intelligently through some combination of retrying and delaying
87-
/// reseeding until later. So long as the internal PRNG doesn't fail, this
88-
/// method will not fail, i.e. failures from the reseeding source are not
89-
/// fatal.
90-
pub fn reseed(&mut self) {
91-
// Behaviour is identical to `try_reseed`; we just squelch the error.
92-
let _res = self.try_reseed();
148+
fn reseed(&mut self) -> Result<(), Error> {
149+
R::from_rng(&mut self.reseeder).map(|result| self.core = result)
93150
}
94151

95-
/// Reseed the internal RNG if the number of bytes that have been
96-
/// generated exceed the threshold.
152+
/// Reseed the internal PRNG.
97153
///
98-
/// If reseeding fails, return an error with the original cause. Note that
99-
/// in case of error we simply delay reseeding, allowing the generator to
100-
/// continue its output of random data and try reseeding again later;
101-
/// because of this we always return kind `ErrorKind::Transient`.
102-
#[inline(never)]
103-
pub fn try_reseed(&mut self) -> Result<(), Error> {
154+
/// If reseeding fails, this will try to work around errors intelligently
155+
/// through some combination of retrying and delaying reseeding until later.
156+
/// It will also report the error with `ErrorKind::Transient` with the
157+
/// original error as cause.
158+
fn auto_reseed(&mut self) -> Result<(), Error> {
104159
trace!("Reseeding RNG after {} generated bytes",
105160
self.threshold - self.bytes_until_reseed);
106-
if let Err(mut e) = R::from_rng(&mut self.reseeder)
107-
.map(|result| self.rng = result)
108-
{
161+
if let Err(mut e) = self.reseed() {
109162
let delay = match e.kind {
110163
ErrorKind::Transient => 0,
111164
kind @ _ if kind.should_retry() => self.threshold >> 8,
@@ -121,69 +174,36 @@ impl<R: RngCore + SeedableRng, Rsdr: RngCore> ReseedingRng<R, Rsdr> {
121174
Ok(())
122175
}
123176
}
124-
}
125177

126-
impl<R: RngCore + SeedableRng, Rsdr: RngCore> RngCore for ReseedingRng<R, Rsdr> {
127-
fn next_u32(&mut self) -> u32 {
128-
let value = self.rng.next_u32();
129-
self.bytes_until_reseed -= 4;
130-
if self.bytes_until_reseed <= 0 {
131-
self.reseed();
132-
}
133-
value
134-
}
135-
136-
fn next_u64(&mut self) -> u64 {
137-
let value = self.rng.next_u64();
138-
self.bytes_until_reseed -= 8;
139-
if self.bytes_until_reseed <= 0 {
140-
self.reseed();
141-
}
142-
value
143-
}
144-
145-
fn fill_bytes(&mut self, dest: &mut [u8]) {
146-
self.rng.fill_bytes(dest);
147-
self.bytes_until_reseed -= dest.len() as i64;
148-
if self.bytes_until_reseed <= 0 {
149-
self.reseed();
150-
}
151-
}
152-
153-
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> {
154-
let res1 = self.rng.try_fill_bytes(dest);
155-
self.bytes_until_reseed -= dest.len() as i64;
156-
let res2 = if self.bytes_until_reseed <= 0 {
157-
self.try_reseed()
158-
} else { Ok(()) };
159-
160-
if let Err(e) = res1 {
161-
// In the unlikely event the internal PRNG fails, we don't know
162-
// whether this is resolvable; reseed immediately and return
163-
// original error kind.
164-
self.bytes_until_reseed = 0;
165-
Err(e)
166-
} else {
167-
res2
168-
}
178+
#[inline(never)]
179+
fn reseed_and_generate(&mut self,
180+
results: &mut <Self as BlockRngCore<u32>>::Results)
181+
-> Result<(), Error>
182+
{
183+
let res1 = self.auto_reseed();
184+
self.bytes_until_reseed -= results.as_ref().len() as i64 * 4;
185+
let res2 = self.core.generate(results);
186+
if res2.is_err() { res2 } else { res1 }
169187
}
170188
}
171189

172190
#[cfg(test)]
173191
mod test {
174-
use {Rng, SeedableRng, StdRng};
192+
use {Rng, SeedableRng};
193+
use prng::chacha::ChaChaCore;
175194
use mock::StepRng;
176195
use super::ReseedingRng;
177196

178197
#[test]
179198
fn test_reseeding() {
180199
let mut zero = StepRng::new(0, 0);
181-
let rng = StdRng::from_rng(&mut zero).unwrap();
182-
let mut reseeding = ReseedingRng::new(rng, 32, zero);
200+
let rng = ChaChaCore::from_rng(&mut zero).unwrap();
201+
let mut reseeding = ReseedingRng::new(rng, 32*4, zero);
183202

184203
// Currently we only support for arrays up to length 32.
185204
// TODO: cannot generate seq via Rng::gen because it uses different alg
186-
let mut buf = [0u8; 32];
205+
let mut buf = [0u32; 32]; // Needs to be a multiple of the RNGs result
206+
// size to test exactly.
187207
reseeding.fill(&mut buf);
188208
let seq = buf;
189209
for _ in 0..10 {

src/thread_rng.rs

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
use std::cell::RefCell;
1414
use std::rc::Rc;
1515

16-
use {RngCore, CryptoRng, StdRng, SeedableRng, EntropyRng};
16+
use {RngCore, CryptoRng, SeedableRng, EntropyRng};
17+
use prng::hc128::Hc128Core;
1718
use {Distribution, Uniform, Rng, Error};
1819
use reseeding::ReseedingRng;
1920

@@ -31,13 +32,13 @@ const THREAD_RNG_RESEED_THRESHOLD: u64 = 32*1024*1024; // 32 MiB
3132
/// [`thread_rng`]: fn.thread_rng.html
3233
#[derive(Clone, Debug)]
3334
pub struct ThreadRng {
34-
rng: Rc<RefCell<ReseedingRng<StdRng, EntropyRng>>>,
35+
rng: Rc<RefCell<ReseedingRng<Hc128Core, EntropyRng>>>,
3536
}
3637

3738
thread_local!(
38-
static THREAD_RNG_KEY: Rc<RefCell<ReseedingRng<StdRng, EntropyRng>>> = {
39+
static THREAD_RNG_KEY: Rc<RefCell<ReseedingRng<Hc128Core, EntropyRng>>> = {
3940
let mut entropy_source = EntropyRng::new();
40-
let r = StdRng::from_rng(&mut entropy_source).unwrap_or_else(|err|
41+
let r = Hc128Core::from_rng(&mut entropy_source).unwrap_or_else(|err|
4142
panic!("could not initialize thread_rng: {}", err));
4243
let rng = ReseedingRng::new(r,
4344
THREAD_RNG_RESEED_THRESHOLD,
@@ -51,11 +52,12 @@ thread_local!(
5152
/// chaining style, e.g. `thread_rng().gen::<i32>()`, or cached locally, e.g.
5253
/// `let mut rng = thread_rng();`.
5354
///
54-
/// `ThreadRng` uses [`ReseedingRng`] wrapping a [`StdRng`] which is reseeded
55-
/// after generating 32 MiB of random data. A single instance is cached per
56-
/// thread and the returned `ThreadRng` is a reference to this instance — hence
57-
/// `ThreadRng` is neither `Send` nor `Sync` but is safe to use within a single
58-
/// thread. This RNG is seeded and reseeded via [`EntropyRng`] as required.
55+
/// `ThreadRng` uses [`ReseedingRng`] wrapping the same PRNG as [`StdRng`],
56+
/// which is reseeded after generating 32 MiB of random data. A single instance
57+
/// is cached per thread and the returned `ThreadRng` is a reference to this
58+
/// instance — hence `ThreadRng` is neither `Send` nor `Sync` but is safe to use
59+
/// within a single thread. This RNG is seeded and reseeded via [`EntropyRng`]
60+
/// as required.
5961
///
6062
/// Note that the reseeding is done as an extra precaution against entropy
6163
/// leaks and is in theory unnecessary — to predict `thread_rng`'s output, an

0 commit comments

Comments
 (0)