From d38fd3e36510bc6915a8865bbc78b2464a450620 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Sat, 23 Jun 2018 17:25:01 +0100 Subject: [PATCH 1/3] Implement SeedableRng::seed_from_u64 --- rand_core/src/lib.rs | 82 +++++++++++++++++++++++++++++++++++++++++++- src/lib.rs | 16 +-------- 2 files changed, 82 insertions(+), 16 deletions(-) diff --git a/rand_core/src/lib.rs b/rand_core/src/lib.rs index 1b9c24c0fa4..715d6294016 100644 --- a/rand_core/src/lib.rs +++ b/rand_core/src/lib.rs @@ -52,6 +52,7 @@ use core::default::Default; use core::convert::AsMut; +use core::ptr::copy_nonoverlapping; #[cfg(all(feature="alloc", not(feature="std")))] use alloc::boxed::Box; @@ -297,7 +298,46 @@ pub trait SeedableRng: Sized { /// for example `0xBAD5EEDu32` or `0x0DDB1A5E5BAD5EEDu64` ("odd biases? bad /// seed"). This is assuming only a small number of values must be rejected. fn from_seed(seed: Self::Seed) -> Self; - + + /// Create a new PRNG using a `u64` seed. + /// + /// This is a convenience-wrapper around `from_seed` to allow construction + /// of any `SeedableRng` from a simple `u64` value. It is designed such that + /// low Hamming Weight numbers like 0 and 1 can be used and should still + /// result in good, independent seeds to the PRNG which is returned. + /// + /// This **is not suitable for cryptography**, as should be clear given that + /// the input size is only 64 bits. + /// + /// Implementations for PRNGs *may* provide their own implementations of + /// this function, but the default implementation should be good enough for + /// all purposes. *Changing* the implementation of this function should be + /// considered a value-breaking change. + fn seed_from_u64(mut state: u64) -> Self { + // We use PCG32 to generate a u32 sequence, and copy to the seed + const MUL: u64 = 6364136223846793005; + const INC: u64 = 11634580027462260723; + + let mut seed = Self::Seed::default(); + for chunk in seed.as_mut().chunks_mut(4) { + // We advance the state first (to get away from input, which may + // have low Hamming Weight). + state = state.wrapping_mul(MUL).wrapping_add(INC); + + // Use PCG output function with to_le to generate x: + let xorshifted = (((state >> 18) ^ state) >> 27) as u32; + let rot = (state >> 59) as u32; + let x = xorshifted.rotate_right(rot).to_le(); + + unsafe { + let p = &x as *const u32 as *const u8; + copy_nonoverlapping(p, chunk.as_mut_ptr(), chunk.len()); + } + } + + Self::from_seed(seed) + } + /// Create a new PRNG seeded from another `Rng`. /// /// This is the recommended way to initialize PRNGs with fresh entropy. The @@ -402,3 +442,43 @@ impl<'a, R: CryptoRng + ?Sized> CryptoRng for &'a mut R {} // Implement `CryptoRng` for boxed references to an `CryptoRng`. #[cfg(feature="alloc")] impl CryptoRng for Box {} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_seed_from_u64() { + struct SeedableNum(u64); + impl SeedableRng for SeedableNum { + type Seed = [u8; 8]; + fn from_seed(seed: Self::Seed) -> Self { + let mut x = [0u64; 1]; + le::read_u64_into(&seed, &mut x); + SeedableNum(x[0]) + } + } + + const N: usize = 8; + const SEEDS: [u64; N] = [0u64, 1, 2, 3, 4, 8, 16, -1i64 as u64]; + let mut results = [0u64; N]; + for (i, seed) in SEEDS.iter().enumerate() { + let SeedableNum(x) = SeedableNum::seed_from_u64(*seed); + results[i] = x; + } + + for (i1, r1) in results.iter().enumerate() { + let weight = r1.count_ones(); + assert!(weight >= 20); + + for (i2, r2) in results.iter().enumerate() { + if i1 == i2 { continue; } + let diff_weight = (r1 ^ r2).count_ones(); + assert!(diff_weight >= 20); + } + } + + // value-breakage test: + assert_eq!(results[0], 5029875928683246316); + } +} diff --git a/src/lib.rs b/src/lib.rs index c81ad28e4dd..e4488599ff4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1060,21 +1060,7 @@ mod test { } pub fn rng(seed: u64) -> TestRng { - // TODO: use from_hashable - let mut state = seed; - let mut seed = ::Seed::default(); - for x in seed.iter_mut() { - // PCG algorithm - const MUL: u64 = 6364136223846793005; - const INC: u64 = 11634580027462260723; - let oldstate = state; - state = oldstate.wrapping_mul(MUL).wrapping_add(INC); - - let xorshifted = (((oldstate >> 18) ^ oldstate) >> 27) as u32; - let rot = (oldstate >> 59) as u32; - *x = xorshifted.rotate_right(rot) as u8; - } - TestRng { inner: StdRng::from_seed(seed) } + TestRng { inner: StdRng::seed_from_u64(seed) } } #[test] From f2d813ec2fffd2ad90c170c5e3ea002dee4bb3a9 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Sat, 7 Jul 2018 09:56:14 +0100 Subject: [PATCH 2/3] Isaac*Rng: replace new_from_u64 with seed_from_u64 --- rand_core/src/block.rs | 8 +++++++ src/prng/isaac.rs | 48 ++++++++++++++++++++++++------------------ src/prng/isaac64.rs | 39 ++++++++++++++++++++++------------ 3 files changed, 62 insertions(+), 33 deletions(-) diff --git a/rand_core/src/block.rs b/rand_core/src/block.rs index f8209e57197..fc5e3b31a04 100644 --- a/rand_core/src/block.rs +++ b/rand_core/src/block.rs @@ -287,6 +287,10 @@ impl SeedableRng for BlockRng { Self::new(R::from_seed(seed)) } + fn seed_from_u64(seed: u64) -> Self { + Self::new(R::seed_from_u64(seed)) + } + fn from_rng(rng: S) -> Result { Ok(Self::new(R::from_rng(rng)?)) } @@ -494,6 +498,10 @@ impl SeedableRng for BlockRng64 { Self::new(R::from_seed(seed)) } + fn seed_from_u64(seed: u64) -> Self { + Self::new(R::seed_from_u64(seed)) + } + fn from_rng(rng: S) -> Result { Ok(Self::new(R::from_rng(rng)?)) } diff --git a/src/prng/isaac.rs b/src/prng/isaac.rs index d37856f4da7..99c80d6a065 100644 --- a/src/prng/isaac.rs +++ b/src/prng/isaac.rs @@ -120,6 +120,13 @@ impl SeedableRng for IsaacRng { fn from_seed(seed: Self::Seed) -> Self { IsaacRng(BlockRng::::from_seed(seed)) } + + /// Create an ISAAC random number generator using an `u64` as seed. + /// If `seed == 0` this will produce the same stream of random numbers as + /// the reference implementation when used unseeded. + fn seed_from_u64(seed: u64) -> Self { + IsaacRng(BlockRng::::seed_from_u64(seed)) + } fn from_rng(rng: S) -> Result { BlockRng::::from_rng(rng).map(|rng| IsaacRng(rng)) @@ -133,14 +140,15 @@ impl IsaacRng { /// DEPRECATED. `IsaacRng::new_from_u64(0)` will produce identical results. #[deprecated(since="0.5.0", note="use the FromEntropy or SeedableRng trait")] pub fn new_unseeded() -> Self { - Self::new_from_u64(0) + Self::seed_from_u64(0) } /// Create an ISAAC random number generator using an `u64` as seed. /// If `seed == 0` this will produce the same stream of random numbers as /// the reference implementation when used unseeded. + #[deprecated(since="0.6.0", note="use SeedableRng::seed_from_u64 instead")] pub fn new_from_u64(seed: u64) -> Self { - IsaacRng(BlockRng::new(IsaacCore::new_from_u64(seed))) + Self::seed_from_u64(seed) } } @@ -311,22 +319,6 @@ impl IsaacCore { Self { mem, a: w(0), b: w(0), c: w(0) } } - - /// Create an ISAAC random number generator using an `u64` as seed. - /// If `seed == 0` this will produce the same stream of random numbers as - /// the reference implementation when used unseeded. - fn new_from_u64(seed: u64) -> Self { - let mut key = [w(0); RAND_SIZE]; - key[0] = w(seed as u32); - key[1] = w((seed >> 32) as u32); - // Initialize with only one pass. - // A second pass does not improve the quality here, because all of the - // seed was already available in the first round. - // Not doing the second pass has the small advantage that if - // `seed == 0` this method produces exactly the same state as the - // reference implementation when used unseeded. - Self::init(key, 1) - } } impl SeedableRng for IsaacCore { @@ -342,6 +334,22 @@ impl SeedableRng for IsaacCore { } Self::init(seed_extended, 2) } + + /// Create an ISAAC random number generator using an `u64` as seed. + /// If `seed == 0` this will produce the same stream of random numbers as + /// the reference implementation when used unseeded. + fn seed_from_u64(seed: u64) -> Self { + let mut key = [w(0); RAND_SIZE]; + key[0] = w(seed as u32); + key[1] = w((seed >> 32) as u32); + // Initialize with only one pass. + // A second pass does not improve the quality here, because all of the + // seed was already available in the first round. + // Not doing the second pass has the small advantage that if + // `seed == 0` this method produces exactly the same state as the + // reference implementation when used unseeded. + Self::init(key, 1) + } fn from_rng(mut rng: R) -> Result { // Custom `from_rng` implementation that fills a seed with the same size @@ -435,11 +443,11 @@ mod test { #[test] fn test_isaac_new_uninitialized() { // Compare the results from initializing `IsaacRng` with - // `new_from_u64(0)`, to make sure it is the same as the reference + // `seed_from_u64(0)`, to make sure it is the same as the reference // implementation when used uninitialized. // Note: We only test the first 16 integers, not the full 256 of the // first block. - let mut rng = IsaacRng::new_from_u64(0); + let mut rng = IsaacRng::seed_from_u64(0); let mut results = [0u32; 16]; for i in results.iter_mut() { *i = rng.next_u32(); } let expected: [u32; 16] = [ diff --git a/src/prng/isaac64.rs b/src/prng/isaac64.rs index 1a7c69dc115..fb3156df94c 100644 --- a/src/prng/isaac64.rs +++ b/src/prng/isaac64.rs @@ -111,6 +111,13 @@ impl SeedableRng for Isaac64Rng { Isaac64Rng(BlockRng64::::from_seed(seed)) } + /// Create an ISAAC random number generator using an `u64` as seed. + /// If `seed == 0` this will produce the same stream of random numbers as + /// the reference implementation when used unseeded. + fn seed_from_u64(seed: u64) -> Self { + Isaac64Rng(BlockRng64::::seed_from_u64(seed)) + } + fn from_rng(rng: S) -> Result { BlockRng64::::from_rng(rng).map(|rng| Isaac64Rng(rng)) } @@ -123,14 +130,15 @@ impl Isaac64Rng { /// DEPRECATED. `Isaac64Rng::new_from_u64(0)` will produce identical results. #[deprecated(since="0.5.0", note="use the FromEntropy or SeedableRng trait")] pub fn new_unseeded() -> Self { - Self::new_from_u64(0) + Self::seed_from_u64(0) } /// Create an ISAAC-64 random number generator using an `u64` as seed. /// If `seed == 0` this will produce the same stream of random numbers as /// the reference implementation when used unseeded. + #[deprecated(since="0.6.0", note="use SeedableRng::seed_from_u64 instead")] pub fn new_from_u64(seed: u64) -> Self { - Isaac64Rng(BlockRng64::new(Isaac64Core::new_from_u64(seed))) + Self::seed_from_u64(seed) } } @@ -280,16 +288,9 @@ impl Isaac64Core { /// Create an ISAAC-64 random number generator using an `u64` as seed. /// If `seed == 0` this will produce the same stream of random numbers as /// the reference implementation when used unseeded. + #[deprecated(since="0.6.0", note="use SeedableRng::seed_from_u64 instead")] pub fn new_from_u64(seed: u64) -> Self { - let mut key = [w(0); RAND_SIZE]; - key[0] = w(seed); - // Initialize with only one pass. - // A second pass does not improve the quality here, because all of the - // seed was already available in the first round. - // Not doing the second pass has the small advantage that if - // `seed == 0` this method produces exactly the same state as the - // reference implementation when used unseeded. - Self::init(key, 1) + Self::seed_from_u64(seed) } } @@ -306,6 +307,18 @@ impl SeedableRng for Isaac64Core { } Self::init(seed_extended, 2) } + + fn seed_from_u64(seed: u64) -> Self { + let mut key = [w(0); RAND_SIZE]; + key[0] = w(seed); + // Initialize with only one pass. + // A second pass does not improve the quality here, because all of the + // seed was already available in the first round. + // Not doing the second pass has the small advantage that if + // `seed == 0` this method produces exactly the same state as the + // reference implementation when used unseeded. + Self::init(key, 1) + } fn from_rng(mut rng: R) -> Result { // Custom `from_rng` implementation that fills a seed with the same size @@ -425,11 +438,11 @@ mod test { #[test] fn test_isaac64_new_uninitialized() { // Compare the results from initializing `IsaacRng` with - // `new_from_u64(0)`, to make sure it is the same as the reference + // `seed_from_u64(0)`, to make sure it is the same as the reference // implementation when used uninitialized. // Note: We only test the first 16 integers, not the full 256 of the // first block. - let mut rng = Isaac64Rng::new_from_u64(0); + let mut rng = Isaac64Rng::seed_from_u64(0); let mut results = [0u64; 16]; for i in results.iter_mut() { *i = rng.next_u64(); } let expected: [u64; 16] = [ From 16291b67ecb6888887adee642f9f10a97a5a49fa Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Mon, 3 Sep 2018 17:14:51 +0100 Subject: [PATCH 3/3] seed_from_u64: tweak documentation and test --- rand_core/src/lib.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/rand_core/src/lib.rs b/rand_core/src/lib.rs index 715d6294016..2b1a7b17e79 100644 --- a/rand_core/src/lib.rs +++ b/rand_core/src/lib.rs @@ -320,8 +320,8 @@ pub trait SeedableRng: Sized { let mut seed = Self::Seed::default(); for chunk in seed.as_mut().chunks_mut(4) { - // We advance the state first (to get away from input, which may - // have low Hamming Weight). + // We advance the state first (to get away from the input value, + // in case it has low Hamming Weight). state = state.wrapping_mul(MUL).wrapping_add(INC); // Use PCG output function with to_le to generate x: @@ -469,7 +469,10 @@ mod test { for (i1, r1) in results.iter().enumerate() { let weight = r1.count_ones(); - assert!(weight >= 20); + // This is the binomial distribution B(64, 0.5), so chance of + // weight < 20 is binocdf(19, 64, 0.5) = 7.8e-4, and same for + // weight > 44. + assert!(weight >= 20 && weight <= 44); for (i2, r2) in results.iter().enumerate() { if i1 == i2 { continue; }