Skip to content

Commit 23d15b6

Browse files
committed
core: implement DeterministicRandomSource
ACP: rust-lang/libs-team#394 Tracking issue: #131606 The version implemented here uses ChaCha8 as RNG. Whether this is the right choice is still open for debate, so I've included the RNG name in the feature gate to make sure people need to recheck their code if we change the RNG. Also, I've made one minor change to the API proposed in the ACP: in accordance with [C-GETTER](https://rust-lang.github.io/api-guidelines/naming.html#getter-names-follow-rust-convention-c-getter), `get_seed` is now named `seed`.
1 parent 484c8e7 commit 23d15b6

File tree

5 files changed

+320
-2
lines changed

5 files changed

+320
-2
lines changed
+264
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
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+
}

library/core/src/random.rs renamed to library/core/src/random/mod.rs

+12-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,15 @@
33
//! The [`Random`] trait allows generating a random value for a type using a
44
//! given [`RandomSource`].
55
6+
mod deterministic;
7+
8+
#[unstable(feature = "deterministic_random_chacha8", issue = "131606")]
9+
pub use deterministic::DeterministicRandomSource;
10+
#[doc(hidden)]
11+
#[unstable(feature = "deterministic_random_internals", issue = "none")]
12+
// Used for testing only.
13+
pub use deterministic::chacha;
14+
615
/// A source of randomness.
716
#[unstable(feature = "random", issue = "130703")]
817
pub trait RandomSource {
@@ -42,7 +51,9 @@ macro_rules! impl_primitive {
4251
fn random(source: &mut (impl RandomSource + ?Sized)) -> Self {
4352
let mut bytes = (0 as Self).to_ne_bytes();
4453
source.fill_bytes(&mut bytes);
45-
Self::from_ne_bytes(bytes)
54+
// Use LE-ordering to guarantee that the number is the same,
55+
// irrespective of the platform.
56+
Self::from_le_bytes(bytes)
4657
}
4758
}
4859
};

library/core/tests/lib.rs

+4
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@
3838
#![feature(core_private_diy_float)]
3939
#![feature(debug_more_non_exhaustive)]
4040
#![feature(dec2flt)]
41+
#![feature(deterministic_random_chacha8)]
42+
#![feature(deterministic_random_internals)]
4143
#![feature(duration_constants)]
4244
#![feature(duration_constructors)]
4345
#![feature(duration_consts_float)]
@@ -83,6 +85,7 @@
8385
#![feature(pointer_is_aligned_to)]
8486
#![feature(portable_simd)]
8587
#![feature(ptr_metadata)]
88+
#![feature(random)]
8689
#![feature(slice_from_ptr_range)]
8790
#![feature(slice_internals)]
8891
#![feature(slice_partition_dedup)]
@@ -147,6 +150,7 @@ mod pattern;
147150
mod pin;
148151
mod pin_macro;
149152
mod ptr;
153+
mod random;
150154
mod result;
151155
mod simd;
152156
mod slice;

library/core/tests/random.rs

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
use core::random::chacha::*;
2+
3+
// Test the quarter-round function.
4+
#[test]
5+
fn test_round() {
6+
let a = 0x11111111;
7+
let b = 0x01020304;
8+
let c = 0x9b8d6f43;
9+
let d = 0x01234567;
10+
let (a, b, c, d) = quarter_round(a, b, c, d);
11+
assert_eq!(a, 0xea2a92f4);
12+
assert_eq!(b, 0xcb1cf8ce);
13+
assert_eq!(c, 0x4581472e);
14+
assert_eq!(d, 0x5881c4bb);
15+
}
16+
17+
// Test the block function.
18+
// RFC 8439 only gives a test vector for 20 rounds, so we use that here.
19+
#[test]
20+
fn test_block() {
21+
let key = [
22+
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
23+
0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d,
24+
0x1e, 0x1f,
25+
];
26+
let counter_nonce = 0x00_00_00_00_4a_00_00_00_09_00_00_00_00_00_00_01;
27+
28+
let block = block(&key, counter_nonce, 20);
29+
30+
#[rustfmt::skip] // Preserve the formatting from the RFC.
31+
assert_eq!(block, [
32+
0x10, 0xf1, 0xe7, 0xe4, 0xd1, 0x3b, 0x59, 0x15, 0x50, 0x0f, 0xdd, 0x1f, 0xa3, 0x20, 0x71, 0xc4,
33+
0xc7, 0xd1, 0xf4, 0xc7, 0x33, 0xc0, 0x68, 0x03, 0x04, 0x22, 0xaa, 0x9a, 0xc3, 0xd4, 0x6c, 0x4e,
34+
0xd2, 0x82, 0x64, 0x46, 0x07, 0x9f, 0xaa, 0x09, 0x14, 0xc2, 0xd7, 0x05, 0xd9, 0x8b, 0x02, 0xa2,
35+
0xb5, 0x12, 0x9c, 0xd1, 0xde, 0x16, 0x4e, 0xb9, 0xcb, 0xd0, 0x83, 0xe8, 0xa2, 0x50, 0x3c, 0x4e,
36+
]);
37+
}

library/std/src/random.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@
33
//! The [`Random`] trait allows generating a random value for a type using a
44
//! given [`RandomSource`].
55
6+
#[unstable(feature = "deterministic_random_chacha8", issue = "131606")]
7+
pub use core::random::DeterministicRandomSource;
68
#[unstable(feature = "random", issue = "130703")]
7-
pub use core::random::*;
9+
pub use core::random::{Random, RandomSource};
810

911
use crate::sys::random as sys;
1012

0 commit comments

Comments
 (0)