Skip to content

Commit

Permalink
core: implement DeterministicRandomSource
Browse files Browse the repository at this point in the history
ACP: rust-lang/libs-team#394
Tracking issue: rust-lang#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`.
  • Loading branch information
joboet committed Oct 12, 2024
1 parent 484c8e7 commit 23d15b6
Show file tree
Hide file tree
Showing 5 changed files with 320 additions and 2 deletions.
264 changes: 264 additions & 0 deletions library/core/src/random/deterministic.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,264 @@
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,
}

/// Implement the ChaCha round function as defined by
/// [RFC 8439](https://datatracker.ietf.org/doc/html/rfc8439), but with reduced rounds.
#[doc(hidden)]
#[unstable(feature = "deterministic_random_internals", issue = "none")] // Used for testing only.
pub mod chacha {
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; 64] {
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; 64];
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 }
}

/// 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
}
}

#[unstable(feature = "deterministic_random_chacha8", issue = "131606")]
impl RandomSource for DeterministicRandomSource {
fn fill_bytes(&mut self, bytes: &mut [u8]) {
for block in bytes.chunks_mut(64) {
let data = chacha::block(&self.seed, self.counter_nonce, Self::ROUNDS);
block.copy_from_slice(&data[..block.len()]);
self.counter_nonce = self.counter_nonce.wrapping_add(1);
}
}
}

#[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()
}
}
13 changes: 12 additions & 1 deletion library/core/src/random.rs → library/core/src/random/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
}
}
};
Expand Down
4 changes: 4 additions & 0 deletions library/core/tests/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -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)]
Expand Down Expand Up @@ -147,6 +150,7 @@ mod pattern;
mod pin;
mod pin_macro;
mod ptr;
mod random;
mod result;
mod simd;
mod slice;
Expand Down
37 changes: 37 additions & 0 deletions library/core/tests/random.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
use core::random::chacha::*;

// 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,
]);
}
4 changes: 3 additions & 1 deletion library/std/src/random.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down

0 comments on commit 23d15b6

Please sign in to comment.