lighthouse-pulse/lighthouse/state/transition/shuffling/rng.rs
2018-08-24 16:01:24 +10:00

133 lines
3.6 KiB
Rust

use super::blake2_rfc::blake2s::{ Blake2s, Blake2sResult };
const SEED_SIZE_BYTES: usize = 32;
const RAND_BYTES: usize = 3; // 24 / 8
const RAND_MAX: u32 = 16777216; // 2**24
/// A pseudo-random number generator which given a seed
/// uses successive blake2s hashing to generate "entropy".
pub struct ShuffleRng {
seed: Blake2sResult,
idx: usize,
pub rand_max: u32,
}
impl ShuffleRng {
/// Create a new instance given some "seed" bytes.
pub fn new(initial_seed: &[u8]) -> Self {
Self {
seed: hash(initial_seed),
idx: 0,
rand_max: RAND_MAX,
}
}
/// "Regenerates" the seed by hashing it.
fn rehash_seed(&mut self) {
self.seed = hash(self.seed.as_bytes());
self.idx = 0;
}
/// Extracts 3 bytes from the `seed`. Rehashes seed if required.
fn rand(&mut self) -> u32 {
self.idx += RAND_BYTES;
match self.idx >= SEED_SIZE_BYTES {
true => {
self.rehash_seed();
self.rand()
}
false => {
int_from_byte_slice(
self.seed.as_bytes(),
self.idx - RAND_BYTES,
)
}
}
}
/// Generate a random u32 below the specified maximum `n`.
///
/// Provides a filtered result from a higher-level rng, by discarding
/// results which may bias the output. Because of this, execution time is
/// not linear and may potentially be infinite.
pub fn rand_range(&mut self, n: u32) -> u32 {
assert!(n < RAND_MAX, "RAND_MAX exceed");
let mut x = self.rand();
while x >= self.rand_max - (self.rand_max % n) {
x = self.rand();
}
x % n
}
}
/// Reads the next three bytes of `source`, starting from `offset` and
/// interprets those bytes as a 24 bit big-endian integer.
/// Returns that integer.
fn int_from_byte_slice(source: &[u8], offset: usize) -> u32 {
(
source[offset + 2] as u32) |
((source[offset + 1] as u32) << 8) |
((source[offset ] as u32) << 16
)
}
/// Peform a blake2s hash on the given bytes.
fn hash(bytes: &[u8]) -> Blake2sResult {
let mut hasher = Blake2s::new(SEED_SIZE_BYTES);
hasher.update(bytes);
hasher.finalize()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_shuffling_int_from_slice() {
let mut x = int_from_byte_slice(
&[0, 0, 1],
0);
assert_eq!((x as u32), 1);
x = int_from_byte_slice(
&[0, 1, 1],
0);
assert_eq!(x, 257);
x = int_from_byte_slice(
&[1, 1, 1],
0);
assert_eq!(x, 65793);
x = int_from_byte_slice(
&[255, 1, 1],
0);
assert_eq!(x, 16711937);
x = int_from_byte_slice(
&[255, 255, 255],
0);
assert_eq!(x, 16777215);
x = int_from_byte_slice(
&[0x8f, 0xbb, 0xc7],
0);
assert_eq!(x, 9419719);
}
#[test]
fn test_shuffling_hash_fn() {
let digest = hash(hash(b"4kn4driuctg8").as_bytes()); // double-hash is intentional
let digest_bytes = digest.as_bytes();
let expected = [
0xff, 0xff, 0xff, 0x8f, 0xbb, 0xc7, 0xab, 0x64, 0x43, 0x9a,
0xe5, 0x12, 0x44, 0xd8, 0x70, 0xcf, 0xe5, 0x79, 0xf6, 0x55,
0x6b, 0xbd, 0x81, 0x43, 0xc5, 0xcd, 0x70, 0x2b, 0xbe, 0xe3,
0x87, 0xc7,
];
assert_eq!(digest_bytes.len(), expected.len());
assert_eq!(digest_bytes, expected)
}
}