forked from rust-lang/rust
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
core: implement
DeterministicRandomSource
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
Showing
5 changed files
with
320 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
]); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters