M1a: deterministischer Rust-Kern #3
@@ -0,0 +1,17 @@
|
||||
//! FNV-1a hashing for state checksums.
|
||||
//!
|
||||
//! The hash only has to reveal accidental divergence between peers. It is
|
||||
//! not meant to resist tampering.
|
||||
|
||||
const FNV_OFFSET_BASIS: u64 = 0xcbf2_9ce4_8422_2325;
|
||||
const FNV_PRIME: u64 = 0x0000_0100_0000_01b3;
|
||||
|
||||
/// Returns the 64-bit FNV-1a hash of `bytes`.
|
||||
pub fn fnv1a64(bytes: &[u8]) -> u64 {
|
||||
let mut hash = FNV_OFFSET_BASIS;
|
||||
for &byte in bytes {
|
||||
hash ^= u64::from(byte);
|
||||
hash = hash.wrapping_mul(FNV_PRIME);
|
||||
}
|
||||
hash
|
||||
}
|
||||
@@ -7,3 +7,5 @@
|
||||
//! world and exchange nothing but commands.
|
||||
|
||||
pub mod codec;
|
||||
pub mod hash;
|
||||
pub mod rng;
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
//! Repeatable pseudo random numbers (SplitMix64).
|
||||
//!
|
||||
//! The core never uses any other source of randomness. The same seed always
|
||||
//! gives the same sequence, on every machine.
|
||||
|
||||
/// Pseudo random number generator with 64 bits of state.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Rng {
|
||||
state: u64,
|
||||
}
|
||||
|
||||
impl Rng {
|
||||
pub fn new(seed: u64) -> Self {
|
||||
Self { state: seed }
|
||||
}
|
||||
|
||||
/// Returns the next value of the sequence.
|
||||
pub fn next_u64(&mut self) -> u64 {
|
||||
self.state = self.state.wrapping_add(0x9E37_79B9_7F4A_7C15);
|
||||
let mut z = self.state;
|
||||
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
|
||||
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
|
||||
z ^ (z >> 31)
|
||||
}
|
||||
|
||||
/// Returns a value in `0..bound`.
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if `bound` is zero.
|
||||
pub fn below(&mut self, bound: u32) -> u32 {
|
||||
assert!(bound > 0, "bound must be greater than zero");
|
||||
((u128::from(self.next_u64()) * u128::from(bound)) >> 64) as u32
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
use grovewright_core::hash::fnv1a64;
|
||||
|
||||
#[test]
|
||||
fn matches_the_published_fnv1a_test_vectors() {
|
||||
assert_eq!(fnv1a64(b""), 0xcbf2_9ce4_8422_2325);
|
||||
assert_eq!(fnv1a64(b"a"), 0xaf63_dc4c_8601_ec8c);
|
||||
assert_eq!(fnv1a64(b"foobar"), 0x8594_4171_f739_67e8);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_order_of_bytes_matters() {
|
||||
assert_ne!(fnv1a64(&[0, 1]), fnv1a64(&[1, 0]));
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
use grovewright_core::rng::Rng;
|
||||
|
||||
#[test]
|
||||
fn matches_the_splitmix64_reference_values() {
|
||||
let mut rng = Rng::new(0);
|
||||
assert_eq!(rng.next_u64(), 0xE220_A839_7B1D_CDAF);
|
||||
assert_eq!(rng.next_u64(), 0x6E78_9E6A_A1B9_65F4);
|
||||
assert_eq!(rng.next_u64(), 0x06C4_5D18_8009_454F);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_same_seed_gives_the_same_sequence() {
|
||||
let mut a = Rng::new(2026);
|
||||
let mut b = Rng::new(2026);
|
||||
for _ in 0..1_000 {
|
||||
assert_eq!(a.next_u64(), b.next_u64());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn different_seeds_give_different_sequences() {
|
||||
assert_ne!(Rng::new(1).next_u64(), Rng::new(2).next_u64());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn below_stays_inside_the_bound() {
|
||||
let mut rng = Rng::new(7);
|
||||
for bound in [1u32, 2, 3, 10, 100, 1_000_000, u32::MAX] {
|
||||
for _ in 0..1_000 {
|
||||
assert!(rng.below(bound) < bound);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn below_reaches_every_value_of_a_small_bound() {
|
||||
let mut rng = Rng::new(99);
|
||||
let mut seen = [false; 6];
|
||||
for _ in 0..1_000 {
|
||||
seen[rng.below(6) as usize] = true;
|
||||
}
|
||||
assert_eq!(seen, [true; 6]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "bound must be greater than zero")]
|
||||
fn below_refuses_a_bound_of_zero() {
|
||||
Rng::new(1).below(0);
|
||||
}
|
||||
Reference in New Issue
Block a user