diff --git a/library/core/src/random/deterministic.rs b/library/core/src/random/deterministic.rs new file mode 100644 index 0000000000000..9b7731e5dd370 --- /dev/null +++ b/library/core/src/random/deterministic.rs @@ -0,0 +1,293 @@ +use super::{Random, RandomSource}; +use crate::fmt::{self, Debug}; + +/// A seeded, insecure random number generator. +/// +/// **DO NOT USE THIS FOR CRYPTOGRAPHY PURPOSES! EVER! NO, YOUR USECASE IS NOT +/// SPECIAL! IF YOU USE THIS IN SECURITY-SENSITIVE CONTEXTS, FERRIS WILL BE +/// ZOMBIFIED AND EAT YOU ALIVE!** +/// +/// If you require secure randomness, use `DefaultRandomSource` instead. In +/// particular, this source: +/// * Does *not* provide forward secrecy, so key compromise will result in *all* +/// output being predictable. +/// * Is *vulnerable* to side-channel attacks such as timing based attacks. +/// * Does *not* reseed on `fork`, VM fork, or in similar scenarios, meaning the +/// generated bytes will be the same. +/// +/// That said, if you do *not* need security, this ChaCha8-based [`RandomSource`] +/// can be used to quickly generate good-quality, deterministic random data +/// usable for purposes such as Monte-Carlo integration, video game RNG, etc. +/// +/// # Stability +/// +/// This random source is guaranteed to always produce the same bytes from a +/// given seed, irrespective of the platform or Rust version. +/// +/// # Examples +/// +/// Test if a coin flip is fair by simulating it 100 times: +/// ```rust +/// #![feature(random, deterministic_random_chacha8)] +/// +/// use std::random::{DeterministicRandomSource, Random}; +/// +/// // Seed chosen by fair dice roll. Guaranteed to be random. +/// let mut rng = DeterministicRandomSource::from_seed([4; 32]); +/// +/// let mut heads = 0usize; +/// for _ in 0..100 { +/// if bool::random(&mut rng) { +/// heads += 1; +/// } +/// } +/// +/// // With a confidence of one standard deviation, the number of heads of +/// // will be within this range: +/// assert!(heads.abs_diff(50) < 20); +/// ``` +/// +/// A Monty-Hall-problem-inspired game: +/// ```rust,no_run +/// #![feature(random, deterministic_random_chacha8)] +/// +/// use std::io::stdin; +/// use std::random::{DefaultRandomSource, DeterministicRandomSource, Random}; +/// +/// // Use a random seed so that the generated numbers will be different every +/// // time the program is run. +/// let mut rng = DeterministicRandomSource::random(&mut DefaultRandomSource); +/// +/// // Pick a random door, avoiding bias. +/// let door = loop { +/// let num = u8::random(&mut rng); +/// if num < 255 { +/// break num % 3; +/// } +/// }; +/// +/// let mut input = stdin().lines().map(Result::unwrap); +/// let guess = loop { +/// println!("Pick a door from 1, 2 or 3:"); +/// match input.next().as_deref() { +/// Some("1") => break 0, +/// Some("2") => break 1, +/// Some("3") => break 2, +/// _ => println!("That's not a valid door"), +/// } +/// }; +/// +/// let reveal = match (guess, door) { +/// // Choose which door the moderator must open. +/// // Since both unpicked doors contain a goat, we decide by fair coin flip. +/// (0, 0) | (1, 1) | (2, 2) => { +/// let diceroll = bool::random(&mut rng) as u8; +/// (door + diceroll) % 3 +/// } +/// (0, 1) | (1, 0) => 2, +/// (0, 2) | (2, 0) => 1, +/// (1, 2) | (2, 1) => 0, +/// _ => unreachable!(), +/// }; +/// println!("Door {} contains a goat. Do you want to change your guess (y/n)?", reveal + 1); +/// +/// let guess = loop { +/// match input.next().as_deref() { +/// Some("y") => break match (guess, reveal) { +/// (0, 1) | (1, 0) => 2, +/// (0, 2) | (2, 0) => 1, +/// (1, 2) | (2, 1) => 0, +/// _ => unreachable!(), +/// }, +/// Some("n") => break guess, +/// _ => println!("Well, what? Answer with either yes (y) or no (n)."), +/// } +/// }; +/// +/// if guess == door { +/// println!("Congratulations, you won a bike for Ferris (also known as a Ferris-wheel)!"); +/// } else { +/// println!("Congratulations, you won a goat! You did not want a goat? Well, better luck next time ;-)."); +/// } +/// ``` +#[unstable(feature = "deterministic_random_chacha8", issue = "131606")] +pub struct DeterministicRandomSource { + seed: [u8; 32], + // We use both the 32-bit counter and the 96-bit nonce from the RFC as + // block counter, resulting in a 128-bit counter that will realistically + // never roll over. + counter_nonce: u128, + block: [u8; chacha::BLOCK_SIZE], + // The amount of bytes in block that were already used. + used: usize, +} + +/// Implements the ChaCha block function as defined by +/// [RFC 8439](https://datatracker.ietf.org/doc/html/rfc8439). +#[doc(hidden)] +#[unstable(feature = "deterministic_random_internals", issue = "none")] // Used for testing only. +pub mod chacha { + pub const BLOCK_SIZE: usize = 64; + + pub const fn quarter_round( + mut a: u32, + mut b: u32, + mut c: u32, + mut d: u32, + ) -> (u32, u32, u32, u32) { + a = a.wrapping_add(b); + d ^= a; + d = d.rotate_left(16); + + c = c.wrapping_add(d); + b ^= c; + b = b.rotate_left(12); + + a = a.wrapping_add(b); + d ^= a; + d = d.rotate_left(8); + + c = c.wrapping_add(d); + b ^= c; + b = b.rotate_left(7); + + (a, b, c, d) + } + + pub fn block(key: &[u8; 32], counter_nonce: u128, rounds: u32) -> [u8; BLOCK_SIZE] { + assert!(rounds % 2 == 0); + + let mut state = [0; 16]; + state[0] = 0x61707865; + state[1] = 0x3320646e; + state[2] = 0x79622d32; + state[3] = 0x6b206574; + + for (i, word) in key.array_chunks().enumerate() { + state[4 + i] = u32::from_le_bytes(*word); + } + + state[12] = (counter_nonce >> 0) as u32; + state[13] = (counter_nonce >> 32) as u32; + state[14] = (counter_nonce >> 64) as u32; + state[15] = (counter_nonce >> 96) as u32; + + let mut block = state; + let mut qr = |a, b, c, d| { + let res = quarter_round(block[a], block[b], block[c], block[d]); + block[a] = res.0; + block[b] = res.1; + block[c] = res.2; + block[d] = res.3; + }; + + for _ in 0..rounds / 2 { + qr(0, 4, 8, 12); + qr(1, 5, 9, 13); + qr(2, 6, 10, 14); + qr(3, 7, 11, 15); + + qr(0, 5, 10, 15); + qr(1, 6, 11, 12); + qr(2, 7, 8, 13); + qr(3, 4, 9, 14); + } + + let mut out = [0; BLOCK_SIZE]; + for i in 0..16 { + out[4 * i..][..4].copy_from_slice(&block[i].wrapping_add(state[i]).to_le_bytes()); + } + + out + } +} + +impl DeterministicRandomSource { + const ROUNDS: u32 = 8; + + /// Creates a new random source with the given seed. + /// + /// # Example + /// + /// ```rust + /// #![feature(deterministic_random_chacha8, random)] + /// + /// use std::random::{DeterministicRandomSource, Random}; + /// + /// let mut rng = DeterministicRandomSource::from_seed([42; 32]); + /// let num = i32::random(&mut rng); + /// assert_eq!(num, 1325358262); + /// ``` + #[unstable(feature = "deterministic_random_chacha8", issue = "131606")] + pub const fn from_seed(seed: [u8; 32]) -> DeterministicRandomSource { + DeterministicRandomSource { + seed, + counter_nonce: 0, + block: [0; chacha::BLOCK_SIZE], + used: chacha::BLOCK_SIZE, + } + } + + /// Returns the seed this random source was initialized with. + /// + /// # Example + /// + /// ```rust + /// #![feature(deterministic_random_chacha8)] + /// + /// use std::random::DeterministicRandomSource; + /// + /// let rng = DeterministicRandomSource::from_seed([4; 32]); + /// assert_eq!(rng.seed(), &[4; 32]); + /// ``` + #[unstable(feature = "deterministic_random_chacha8", issue = "131606")] + pub const fn seed(&self) -> &[u8; 32] { + &self.seed + } + + fn next_block(&mut self) -> [u8; chacha::BLOCK_SIZE] { + let block = chacha::block(&self.seed, self.counter_nonce, Self::ROUNDS); + self.counter_nonce = self.counter_nonce.wrapping_add(1); + block + } +} + +#[unstable(feature = "deterministic_random_chacha8", issue = "131606")] +impl RandomSource for DeterministicRandomSource { + fn fill_bytes(&mut self, mut bytes: &mut [u8]) { + if self.used != self.block.len() { + let len = usize::min(self.block.len() - self.used, bytes.len()); + bytes[..len].copy_from_slice(&self.block[self.used..][..len]); + bytes = &mut bytes[len..]; + self.used += len; + } + + let mut blocks = bytes.array_chunks_mut::<{ chacha::BLOCK_SIZE }>(); + for block in &mut blocks { + block.copy_from_slice(&self.next_block()); + } + + let bytes = blocks.into_remainder(); + if !bytes.is_empty() { + self.block = self.next_block(); + bytes.copy_from_slice(&self.block[..bytes.len()]); + self.used = bytes.len(); + } + } +} + +#[unstable(feature = "deterministic_random_chacha8", issue = "131606")] +impl Random for DeterministicRandomSource { + fn random(source: &mut (impl RandomSource + ?Sized)) -> DeterministicRandomSource { + let mut seed = [0; 32]; + source.fill_bytes(&mut seed); + DeterministicRandomSource::from_seed(seed) + } +} + +#[unstable(feature = "deterministic_random_chacha8", issue = "131606")] +impl Debug for DeterministicRandomSource { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("DeterministcRandomSource").finish_non_exhaustive() + } +} diff --git a/library/core/src/random.rs b/library/core/src/random/mod.rs similarity index 81% rename from library/core/src/random.rs rename to library/core/src/random/mod.rs index 051fe26086389..f8c85b6eababc 100644 --- a/library/core/src/random.rs +++ b/library/core/src/random/mod.rs @@ -3,6 +3,15 @@ //! The [`Random`] trait allows generating a random value for a type using a //! given [`RandomSource`]. +mod deterministic; + +#[unstable(feature = "deterministic_random_chacha8", issue = "131606")] +pub use deterministic::DeterministicRandomSource; +#[doc(hidden)] +#[unstable(feature = "deterministic_random_internals", issue = "none")] +// Used for testing only. +pub use deterministic::chacha; + /// A source of randomness. #[unstable(feature = "random", issue = "130703")] pub trait RandomSource { @@ -42,7 +51,9 @@ macro_rules! impl_primitive { fn random(source: &mut (impl RandomSource + ?Sized)) -> Self { let mut bytes = (0 as Self).to_ne_bytes(); source.fill_bytes(&mut bytes); - Self::from_ne_bytes(bytes) + // Use LE-ordering to guarantee that the number is the same, + // irrespective of the platform. + Self::from_le_bytes(bytes) } } }; diff --git a/library/core/tests/lib.rs b/library/core/tests/lib.rs index 0d4ec96f8426f..0a40f52ff7049 100644 --- a/library/core/tests/lib.rs +++ b/library/core/tests/lib.rs @@ -38,6 +38,8 @@ #![feature(core_private_diy_float)] #![feature(debug_more_non_exhaustive)] #![feature(dec2flt)] +#![feature(deterministic_random_chacha8)] +#![feature(deterministic_random_internals)] #![feature(duration_constants)] #![feature(duration_constructors)] #![feature(duration_consts_float)] @@ -83,6 +85,7 @@ #![feature(pointer_is_aligned_to)] #![feature(portable_simd)] #![feature(ptr_metadata)] +#![feature(random)] #![feature(slice_from_ptr_range)] #![feature(slice_internals)] #![feature(slice_partition_dedup)] @@ -147,6 +150,7 @@ mod pattern; mod pin; mod pin_macro; mod ptr; +mod random; mod result; mod simd; mod slice; diff --git a/library/core/tests/random.rs b/library/core/tests/random.rs new file mode 100644 index 0000000000000..8cf716684252f --- /dev/null +++ b/library/core/tests/random.rs @@ -0,0 +1,84 @@ +use core::random::chacha::*; +use core::random::{DeterministicRandomSource, RandomSource}; + +// Test the quarter-round function. +#[test] +fn test_round() { + let a = 0x11111111; + let b = 0x01020304; + let c = 0x9b8d6f43; + let d = 0x01234567; + let (a, b, c, d) = quarter_round(a, b, c, d); + assert_eq!(a, 0xea2a92f4); + assert_eq!(b, 0xcb1cf8ce); + assert_eq!(c, 0x4581472e); + assert_eq!(d, 0x5881c4bb); +} + +// Test the block function. +// RFC 8439 only gives a test vector for 20 rounds, so we use that here. +#[test] +fn test_block() { + let key = [ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, + 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, + 0x1e, 0x1f, + ]; + let counter_nonce = 0x00_00_00_00_4a_00_00_00_09_00_00_00_00_00_00_01; + + let block = block(&key, counter_nonce, 20); + + #[rustfmt::skip] // Preserve the formatting from the RFC. + assert_eq!(block, [ + 0x10, 0xf1, 0xe7, 0xe4, 0xd1, 0x3b, 0x59, 0x15, 0x50, 0x0f, 0xdd, 0x1f, 0xa3, 0x20, 0x71, 0xc4, + 0xc7, 0xd1, 0xf4, 0xc7, 0x33, 0xc0, 0x68, 0x03, 0x04, 0x22, 0xaa, 0x9a, 0xc3, 0xd4, 0x6c, 0x4e, + 0xd2, 0x82, 0x64, 0x46, 0x07, 0x9f, 0xaa, 0x09, 0x14, 0xc2, 0xd7, 0x05, 0xd9, 0x8b, 0x02, 0xa2, + 0xb5, 0x12, 0x9c, 0xd1, 0xde, 0x16, 0x4e, 0xb9, 0xcb, 0xd0, 0x83, 0xe8, 0xa2, 0x50, 0x3c, 0x4e, + ]); +} + +#[test] +fn test_source() { + // Test that two sources produce the same output after a zero-sized request. + let mut rng1 = DeterministicRandomSource::from_seed([42; 32]); + let mut rng2 = DeterministicRandomSource::from_seed([42; 32]); + rng1.fill_bytes(&mut []); + let mut b1 = [0; 64]; + let mut b2 = [0; 64]; + rng1.fill_bytes(&mut b1); + rng2.fill_bytes(&mut b2); + assert_eq!(b1, b2); + + // Test that two sources with different seeds produce different data. + let mut rng1 = DeterministicRandomSource::from_seed([b'A'; 32]); + let mut rng2 = DeterministicRandomSource::from_seed([b'B'; 32]); + let mut b1 = [0; 64]; // This size should be large enough to guarantee uniqueness. + let mut b2 = [0; 64]; + rng1.fill_bytes(&mut b1); + rng2.fill_bytes(&mut b2); + assert_ne!(b1, b2); + + // Test that the source always generates the same bytes, irrespective of the + // size of the individual requests. + let mut rng1 = DeterministicRandomSource::from_seed([4; 32]); + let mut rng2 = DeterministicRandomSource::from_seed([4; 32]); + let mut b1 = [0; 128]; + let mut b2 = [0; 128]; + rng1.fill_bytes(&mut b1[0..63]); + rng1.fill_bytes(&mut b1[63..128]); + rng2.fill_bytes(&mut b2); + assert_eq!(b1, b2); + + // Test the output of the RNG. + // If this test fails, this is a bug in the RNG, not the test. + // Do not change the generated bytes below. + let mut rng = DeterministicRandomSource::from_seed([42; 32]); + let mut bytes = [0; 64]; + rng.fill_bytes(&mut bytes); + assert_eq!(bytes, [ + 182, 92, 255, 78, 146, 170, 167, 19, 201, 210, 224, 219, 84, 107, 104, 196, 224, 111, 107, + 198, 98, 121, 52, 115, 177, 219, 124, 69, 52, 111, 194, 63, 248, 202, 181, 174, 85, 116, + 46, 203, 37, 238, 86, 5, 123, 158, 53, 234, 229, 76, 64, 169, 181, 145, 115, 128, 64, 187, + 25, 75, 60, 217, 10, 169 + ]); +} diff --git a/library/std/src/random.rs b/library/std/src/random.rs index 604fa4df11066..fc27cbc83c55a 100644 --- a/library/std/src/random.rs +++ b/library/std/src/random.rs @@ -3,8 +3,10 @@ //! The [`Random`] trait allows generating a random value for a type using a //! given [`RandomSource`]. +#[unstable(feature = "deterministic_random_chacha8", issue = "131606")] +pub use core::random::DeterministicRandomSource; #[unstable(feature = "random", issue = "130703")] -pub use core::random::*; +pub use core::random::{Random, RandomSource}; use crate::sys::random as sys;