|
| 1 | +use super::{Random, RandomSource}; |
| 2 | +use crate::fmt::{self, Debug}; |
| 3 | + |
| 4 | +/// A seeded, insecure random number generator. |
| 5 | +/// |
| 6 | +/// **DO NOT USE THIS FOR CRYPTOGRAPHY PURPOSES! EVER! NO, YOUR USECASE IS NOT |
| 7 | +/// SPECIAL! IF YOU USE THIS IN SECURITY-SENSITIVE CONTEXTS, FERRIS WILL BE |
| 8 | +/// ZOMBIFIED AND EAT YOU ALIVE!** |
| 9 | +/// |
| 10 | +/// If you require secure randomness, use `DefaultRandomSource` instead. In |
| 11 | +/// particular, this source: |
| 12 | +/// * Does *not* provide forward secrecy, so key compromise will result in *all* |
| 13 | +/// output being predictable. |
| 14 | +/// * Is *vulnerable* to side-channel attacks such as timing based attacks. |
| 15 | +/// * Does *not* reseed on `fork`, VM fork or in similar scenarios, meaning the |
| 16 | +/// generated bytes will be the same. |
| 17 | +/// |
| 18 | +/// That said, if you do *not* need security, this ChaCha8-based [`RandomSource`] |
| 19 | +/// can be used to quickly generate good-quality, deterministic random data |
| 20 | +/// usable for purposes such as Monte-Carlo integration, video game RNG, etc. |
| 21 | +/// |
| 22 | +/// # Stability |
| 23 | +/// |
| 24 | +/// This random source is guaranteed to always produce the same bytes from a |
| 25 | +/// given seed, irrespective of the platform or Rust version. |
| 26 | +/// |
| 27 | +/// # Examples |
| 28 | +/// |
| 29 | +/// Test if a coin flip is fair by simulating it 100 times: |
| 30 | +/// ```rust |
| 31 | +/// #![feature(random, deterministic_random_chacha8)] |
| 32 | +/// |
| 33 | +/// use std::random::{DeterministicRandomSource, Random}; |
| 34 | +/// |
| 35 | +/// // Seed chosen by fair dice roll. Guaranteed to be random. |
| 36 | +/// let mut rng = DeterministicRandomSource::from_seed([4; 32]); |
| 37 | +/// |
| 38 | +/// let mut heads = 0usize; |
| 39 | +/// for _ in 0..100 { |
| 40 | +/// if bool::random(&mut rng) { |
| 41 | +/// heads += 1; |
| 42 | +/// } |
| 43 | +/// } |
| 44 | +/// |
| 45 | +/// // With a confidence of one standard deviation, the number of heads of |
| 46 | +/// // will be within this range: |
| 47 | +/// assert!(heads.abs_diff(50) < 20); |
| 48 | +/// ``` |
| 49 | +/// |
| 50 | +/// A Monty-Hall-problem-inspired game: |
| 51 | +/// ```rust,no_run |
| 52 | +/// #![feature(random, deterministic_random_chacha8)] |
| 53 | +/// |
| 54 | +/// use std::io::stdin; |
| 55 | +/// use std::random::{DefaultRandomSource, DeterministicRandomSource, Random}; |
| 56 | +/// |
| 57 | +/// // Use a random seed so that the generated numbers will be different every |
| 58 | +/// // time the program is run. |
| 59 | +/// let mut rng = DeterministicRandomSource::random(&mut DefaultRandomSource); |
| 60 | +/// |
| 61 | +/// // Pick a random door, avoiding bias. |
| 62 | +/// let door = loop { |
| 63 | +/// let num = u8::random(&mut rng); |
| 64 | +/// if num < 255 { |
| 65 | +/// break num % 3; |
| 66 | +/// } |
| 67 | +/// }; |
| 68 | +/// |
| 69 | +/// let mut input = stdin().lines().map(Result::unwrap); |
| 70 | +/// let guess = loop { |
| 71 | +/// println!("Pick a door from 1, 2 or 3:"); |
| 72 | +/// match input.next().as_deref() { |
| 73 | +/// Some("1") => break 0, |
| 74 | +/// Some("2") => break 1, |
| 75 | +/// Some("3") => break 2, |
| 76 | +/// _ => println!("That's not a valid door"), |
| 77 | +/// } |
| 78 | +/// }; |
| 79 | +/// |
| 80 | +/// let reveal = match (guess, door) { |
| 81 | +/// // Choose which door the moderator must open. |
| 82 | +/// // Since both unpicked doors contain a goat, we decide by fair coin flip. |
| 83 | +/// (0, 0) | (1, 1) | (2, 2) => { |
| 84 | +/// let diceroll = bool::random(&mut rng) as u8; |
| 85 | +/// (door + diceroll) % 3 |
| 86 | +/// } |
| 87 | +/// (0, 1) | (1, 0) => 2, |
| 88 | +/// (0, 2) | (2, 0) => 1, |
| 89 | +/// (1, 2) | (2, 1) => 0, |
| 90 | +/// _ => unreachable!(), |
| 91 | +/// }; |
| 92 | +/// println!("Door {} contains a goat. Do you want to change your guess (y/n)?", reveal + 1); |
| 93 | +/// |
| 94 | +/// let guess = loop { |
| 95 | +/// match input.next().as_deref() { |
| 96 | +/// Some("y") => break match (guess, reveal) { |
| 97 | +/// (0, 1) | (1, 0) => 2, |
| 98 | +/// (0, 2) | (2, 0) => 1, |
| 99 | +/// (1, 2) | (2, 1) => 0, |
| 100 | +/// _ => unreachable!(), |
| 101 | +/// }, |
| 102 | +/// Some("n") => break guess, |
| 103 | +/// _ => println!("Well, what? Answer with either yes (y) or no (n)."), |
| 104 | +/// } |
| 105 | +/// }; |
| 106 | +/// |
| 107 | +/// if guess == door { |
| 108 | +/// println!("Congratulations, you won a bike for Ferris (also known as a Ferris-wheel)!"); |
| 109 | +/// } else { |
| 110 | +/// println!("Congratulations, you won a goat! You did not want a goat? Well, better luck next time ;-)."); |
| 111 | +/// } |
| 112 | +/// ``` |
| 113 | +#[unstable(feature = "deterministic_random_chacha8", issue = "131606")] |
| 114 | +pub struct DeterministicRandomSource { |
| 115 | + seed: [u8; 32], |
| 116 | + // We use both the 32-bit counter and the 96-bit nonce from the RFC as |
| 117 | + // block counter, resulting in a 128-bit counter that will realistically |
| 118 | + // never roll over. |
| 119 | + counter_nonce: u128, |
| 120 | +} |
| 121 | + |
| 122 | +/// Implement the ChaCha round function as defined by |
| 123 | +/// [RFC 8439](https://datatracker.ietf.org/doc/html/rfc8439), but with reduced rounds. |
| 124 | +#[doc(hidden)] |
| 125 | +#[unstable(feature = "deterministic_random_internals", issue = "none")] // Used for testing only. |
| 126 | +pub mod chacha { |
| 127 | + pub const fn quarter_round( |
| 128 | + mut a: u32, |
| 129 | + mut b: u32, |
| 130 | + mut c: u32, |
| 131 | + mut d: u32, |
| 132 | + ) -> (u32, u32, u32, u32) { |
| 133 | + a = a.wrapping_add(b); |
| 134 | + d ^= a; |
| 135 | + d = d.rotate_left(16); |
| 136 | + |
| 137 | + c = c.wrapping_add(d); |
| 138 | + b ^= c; |
| 139 | + b = b.rotate_left(12); |
| 140 | + |
| 141 | + a = a.wrapping_add(b); |
| 142 | + d ^= a; |
| 143 | + d = d.rotate_left(8); |
| 144 | + |
| 145 | + c = c.wrapping_add(d); |
| 146 | + b ^= c; |
| 147 | + b = b.rotate_left(7); |
| 148 | + |
| 149 | + (a, b, c, d) |
| 150 | + } |
| 151 | + |
| 152 | + pub fn block(key: &[u8; 32], counter_nonce: u128, rounds: u32) -> [u8; 64] { |
| 153 | + assert!(rounds % 2 == 0); |
| 154 | + |
| 155 | + let mut state = [0; 16]; |
| 156 | + state[0] = 0x61707865; |
| 157 | + state[1] = 0x3320646e; |
| 158 | + state[2] = 0x79622d32; |
| 159 | + state[3] = 0x6b206574; |
| 160 | + |
| 161 | + for (i, word) in key.array_chunks().enumerate() { |
| 162 | + state[4 + i] = u32::from_le_bytes(*word); |
| 163 | + } |
| 164 | + |
| 165 | + state[12] = (counter_nonce >> 0) as u32; |
| 166 | + state[13] = (counter_nonce >> 32) as u32; |
| 167 | + state[14] = (counter_nonce >> 64) as u32; |
| 168 | + state[15] = (counter_nonce >> 96) as u32; |
| 169 | + |
| 170 | + let mut block = state; |
| 171 | + let mut qr = |a, b, c, d| { |
| 172 | + let res = quarter_round(block[a], block[b], block[c], block[d]); |
| 173 | + block[a] = res.0; |
| 174 | + block[b] = res.1; |
| 175 | + block[c] = res.2; |
| 176 | + block[d] = res.3; |
| 177 | + }; |
| 178 | + |
| 179 | + for _ in 0..rounds / 2 { |
| 180 | + qr(0, 4, 8, 12); |
| 181 | + qr(1, 5, 9, 13); |
| 182 | + qr(2, 6, 10, 14); |
| 183 | + qr(3, 7, 11, 15); |
| 184 | + |
| 185 | + qr(0, 5, 10, 15); |
| 186 | + qr(1, 6, 11, 12); |
| 187 | + qr(2, 7, 8, 13); |
| 188 | + qr(3, 4, 9, 14); |
| 189 | + } |
| 190 | + |
| 191 | + let mut out = [0; 64]; |
| 192 | + for i in 0..16 { |
| 193 | + out[4 * i..][..4].copy_from_slice(&block[i].wrapping_add(state[i]).to_le_bytes()); |
| 194 | + } |
| 195 | + |
| 196 | + out |
| 197 | + } |
| 198 | +} |
| 199 | + |
| 200 | +impl DeterministicRandomSource { |
| 201 | + const ROUNDS: u32 = 8; |
| 202 | + |
| 203 | + /// Creates a new random source with the given seed. |
| 204 | + /// |
| 205 | + /// # Example |
| 206 | + /// |
| 207 | + /// ```rust |
| 208 | + /// #![feature(deterministic_random_chacha8, random)] |
| 209 | + /// |
| 210 | + /// use std::random::{DeterministicRandomSource, Random}; |
| 211 | + /// |
| 212 | + /// let mut rng = DeterministicRandomSource::from_seed([42; 32]); |
| 213 | + /// let num = i32::random(&mut rng); |
| 214 | + /// assert_eq!(num, 1325358262); |
| 215 | + /// ``` |
| 216 | + #[unstable(feature = "deterministic_random_chacha8", issue = "131606")] |
| 217 | + pub const fn from_seed(seed: [u8; 32]) -> DeterministicRandomSource { |
| 218 | + DeterministicRandomSource { seed, counter_nonce: 0 } |
| 219 | + } |
| 220 | + |
| 221 | + /// Returns the seed this random source was initialized with. |
| 222 | + /// |
| 223 | + /// # Example |
| 224 | + /// |
| 225 | + /// ```rust |
| 226 | + /// #![feature(deterministic_random_chacha8)] |
| 227 | + /// |
| 228 | + /// use std::random::DeterministicRandomSource; |
| 229 | + /// |
| 230 | + /// let rng = DeterministicRandomSource::from_seed([4; 32]); |
| 231 | + /// assert_eq!(rng.seed(), &[4; 32]); |
| 232 | + /// ``` |
| 233 | + #[unstable(feature = "deterministic_random_chacha8", issue = "131606")] |
| 234 | + pub const fn seed(&self) -> &[u8; 32] { |
| 235 | + &self.seed |
| 236 | + } |
| 237 | +} |
| 238 | + |
| 239 | +#[unstable(feature = "deterministic_random_chacha8", issue = "131606")] |
| 240 | +impl RandomSource for DeterministicRandomSource { |
| 241 | + fn fill_bytes(&mut self, bytes: &mut [u8]) { |
| 242 | + for block in bytes.chunks_mut(64) { |
| 243 | + let data = chacha::block(&self.seed, self.counter_nonce, Self::ROUNDS); |
| 244 | + block.copy_from_slice(&data[..block.len()]); |
| 245 | + self.counter_nonce = self.counter_nonce.wrapping_add(1); |
| 246 | + } |
| 247 | + } |
| 248 | +} |
| 249 | + |
| 250 | +#[unstable(feature = "deterministic_random_chacha8", issue = "131606")] |
| 251 | +impl Random for DeterministicRandomSource { |
| 252 | + fn random(source: &mut (impl RandomSource + ?Sized)) -> DeterministicRandomSource { |
| 253 | + let mut seed = [0; 32]; |
| 254 | + source.fill_bytes(&mut seed); |
| 255 | + DeterministicRandomSource::from_seed(seed) |
| 256 | + } |
| 257 | +} |
| 258 | + |
| 259 | +#[unstable(feature = "deterministic_random_chacha8", issue = "131606")] |
| 260 | +impl Debug for DeterministicRandomSource { |
| 261 | + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 262 | + f.debug_struct("DeterministcRandomSource").finish_non_exhaustive() |
| 263 | + } |
| 264 | +} |
0 commit comments