From bf2437ec2c10d9b15c45bb33f237b656bbc565ec Mon Sep 17 00:00:00 2001 From: parazyd Date: Wed, 19 Jul 2023 13:17:46 +0200 Subject: [PATCH] util: Implement PCG for a deterministic PRNG used for testing and sim. --- Cargo.toml | 1 + src/util/mod.rs | 4 ++ src/util/pcg.rs | 104 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 109 insertions(+) create mode 100644 src/util/pcg.rs diff --git a/Cargo.toml b/Cargo.toml index e319e6281..fe957d9d0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -249,6 +249,7 @@ tx = [ ] util = [ + "rand", "simplelog", "serde", "serde_json", diff --git a/src/util/mod.rs b/src/util/mod.rs index d96ebd274..a58b68390 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -40,3 +40,7 @@ pub mod time; /// Ring Buffer implementation pub mod ringbuffer; + +/// Permuted Congruential Generator (PCG) +/// This is an insecure PRNG used for simulations and tests. +pub mod pcg; diff --git a/src/util/pcg.rs b/src/util/pcg.rs new file mode 100644 index 000000000..c414e1b1f --- /dev/null +++ b/src/util/pcg.rs @@ -0,0 +1,104 @@ +/* This file is part of DarkFi (https://dark.fi) + * + * Copyright (C) 2020-2023 Dyne.org foundation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use rand::{CryptoRng, Error, RngCore}; + +pub struct Pcg32 { + state: u64, + increment: u64, +} + +impl Pcg32 { + const MULTIPLIER: u64 = 6364136223846793005; + const INCREMENT: u64 = 1442695040888963407; + + pub fn new(seed: u64) -> Self { + let mut rng = Self { state: 0, increment: Self::INCREMENT | 1 }; + rng.state = rng.state.wrapping_add(seed); + rng.state = rng.state.wrapping_mul(Self::MULTIPLIER).wrapping_add(rng.increment); + rng + } + + fn next_u32(&mut self) -> u32 { + let old_state = self.state; + self.state = old_state.wrapping_mul(Self::MULTIPLIER).wrapping_add(self.increment); + let xorshifted = ((old_state >> 18) ^ old_state) >> 27; + let rot = old_state >> 59; + (xorshifted >> rot | xorshifted << ((!rot).wrapping_add(1) & 31)) as u32 + } +} + +impl CryptoRng for Pcg32 {} + +impl RngCore for Pcg32 { + fn next_u32(&mut self) -> u32 { + self.next_u32() + } + + fn next_u64(&mut self) -> u64 { + (self.next_u32() as u64) << 32 | (self.next_u32() as u64) + } + + fn fill_bytes(&mut self, dest: &mut [u8]) { + let mut i = 0; + while i + 4 <= dest.len() { + let bytes = self.next_u32().to_le_bytes(); + dest[i..i + 4].copy_from_slice(&bytes); + i += 4; + } + if i < dest.len() { + let bytes = self.next_u32().to_le_bytes(); + for (j, dest_byte) in dest[i..].iter_mut().enumerate() { + *dest_byte = bytes[j]; + } + } + } + + fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> { + Ok(self.fill_bytes(dest)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_pcg() { + const ITERS: usize = 10000; + + let mut rng0 = Pcg32::new(42); + let mut rng1 = Pcg32::new(42); + + for i in 0..ITERS { + let a = rng0.next_u32(); + let b = rng1.next_u32(); + assert!(a == b); + + let a = rng0.next_u64(); + let b = rng1.next_u64(); + assert!(a == b); + + let mut buf0 = vec![0u8; i]; + let mut buf1 = vec![0u8; i]; + rng0.fill_bytes(&mut buf0); + rng1.fill_bytes(&mut buf1); + assert!(buf0 == buf1); + } + } +}