diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 908a6c1..56485a3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,9 +38,9 @@ jobs: - name: Run cargo check (without dev-dependencies to catch missing feature flags) if: startsWith(matrix.rust, 'nightly') run: cargo check -Z features=dev_dep - - name: Full feature + v4.2 testing + - name: Full feature testing run: cargo test --all-features - - name: v4 compatibility testing + - name: Default testing run: cargo test --no-default-features --features debug,rand_core,wyhash,randomised_wyhash msrv: diff --git a/Cargo.toml b/Cargo.toml index 9544916..58f7f8a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,13 +18,13 @@ rust-version = "1.70.0" debug = [] default = ["rand_core", "debug"] hash = [] +legacy_v4 = [] rand_core = ["dep:rand_core"] serde1 = ["dep:serde"] wyhash = [] randomised_wyhash = ["wyhash", "dep:getrandom"] fully_randomised_wyhash = ["randomised_wyhash"] threadrng_wyhash = ["dep:rand", "randomised_wyhash"] -v4_2 = [] [dependencies] getrandom = { version = "0.2", optional = true } diff --git a/README.md b/README.md index 7d96a51..144df31 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ A fast & portable non-cryptographic pseudorandom number generator written in Rus The implementations for both the PRNG and hasher are based on the C reference implementation [wyhash](https://github.com/wangyi-fudan/wyhash), a simple and fast hasher but **not** cryptographically secure. It's known to be extremely fast and performant while still having great statistical properties. -This crate provides both the v4.2 final implementation of the WyRand/WyHash algorithm, or the older final v4 implementation. The two versions have different outputs due to changes in the algorithm and also with the constants used. Currently by default, the older final v4 algorithm will be used. In the future, this will be changed to the newer algorithm to be the default, but the old implementation will remain for backwards compatibility reasons. +This crate provides both the v4.2 final implementation of the WyRand/WyHash algorithm, or the older final v4 implementation. The two versions have different outputs due to changes in the algorithm and also with the constants used. By default, the newer final v4.2 algorithm will be provided. If there's a need to use the older version for compatibility reasons, the legacy versions of the PRNG and hasher are available behind the `legacy_v4` feature flag. This crate can be used on its own or be integrated with `rand_core`/`rand`, and it is `no-std` compatible. Minimum compatible Rust version is 1.70. This crate is also implemented with no unsafe code via `#![forbid(unsafe_code)]`. @@ -34,11 +34,11 @@ The crate will always export `WyRand` and will do so when set as `default-featur - **`debug`** - Enables `core::fmt::Debug` implementation for `WyRand`/`WyHash`. - **`serde1`** - Enables `Serialize` and `Deserialize` derives on `WyRand`. - **`hash`** - Enables `core::hash::Hash` implementation for `WyRand`. -- **`wyhash`** - Enables `WyHash`, a fast & portable hashing algorithm. Based on the final v4 C implementation. +- **`wyhash`** - Enables `WyHash`, a fast & portable hashing algorithm. Based on the final v4.2 C implementation. - **`randomised_wyhash`** - Enables `RandomWyHashState`, a means to source a randomised state for `WyHash` for use in collections like `HashMap`/`HashSet`. Enables `wyhash` feature if it is not already enabled. - **`fully_randomised_wyhash`** - Randomises not just the seed for `RandomWyHashState`, but also the secret. The new secret is generated once per runtime, and then is used for every subsequent new `WyHash` (with each `WyHash` instance having its own unique seed). Enables `randomised_wyhash` if not already enabled, and requires `std` environments. - **`threadrng_wyhash`** - Enables sourcing entropy from `rand`'s `thread_rng()` method. Much quicker than `getrandom`. Enables `randomised_wyhash` if not already enabled. Requires `std` environments. -- **`v4_2`** - Switches the PRNG/Hashing algorithms to use the final v4.2 implementation. +- **`legacy_v4`** - Exposes the legacy PRNG/Hashing algorithms. ## Building for WASM/Web diff --git a/src/constants.rs b/src/constants.rs index ed4b61f..847f7fc 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -1,25 +1,6 @@ -#[cfg(not(feature = "v4_2"))] -mod v4 { - pub(crate) const WY0: u64 = 0xa076_1d64_78bd_642f; - pub(crate) const WY1: u64 = 0xe703_7ed1_a0b4_28db; - #[cfg(feature = "wyhash")] - pub(crate) const WY2: u64 = 0x8ebc_6af0_9c88_c6e3; - #[cfg(feature = "wyhash")] - pub(crate) const WY3: u64 = 0x5899_65cc_7537_4cc3; -} - -#[cfg(feature = "v4_2")] -mod v4_2 { - pub(crate) const WY0: u64 = 0x2d35_8dcc_aa6c_78a5; - pub(crate) const WY1: u64 = 0x8bb8_4b93_962e_acc9; - #[cfg(feature = "wyhash")] - pub(crate) const WY2: u64 = 0x4b33_a62e_d433_d4a3; - #[cfg(feature = "wyhash")] - pub(crate) const WY3: u64 = 0x4d5a_2da5_1de1_aa47; -} - -#[cfg(not(feature = "v4_2"))] -pub(crate) use v4::*; - -#[cfg(feature = "v4_2")] -pub(crate) use v4_2::*; +pub(crate )const C_VALUES: &[u8] = &[ + 15, 23, 27, 29, 30, 39, 43, 45, 46, 51, 53, 54, 57, 58, 60, 71, 75, 77, 78, 83, 85, 86, 89, 90, + 92, 99, 101, 102, 105, 106, 108, 113, 114, 116, 120, 135, 139, 141, 142, 147, 149, 150, 153, + 154, 156, 163, 165, 166, 169, 170, 172, 177, 178, 180, 184, 195, 197, 198, 201, 202, 204, 209, + 210, 212, 216, 225, 226, 228, 232, 240, +]; diff --git a/src/final_v4_2.rs b/src/final_v4_2.rs new file mode 100644 index 0000000..a9c31b9 --- /dev/null +++ b/src/final_v4_2.rs @@ -0,0 +1,24 @@ +#[cfg(feature = "randomised_wyhash")] +mod builder; +mod constants; +#[cfg(feature = "wyhash")] +mod hasher; +#[cfg(feature = "wyhash")] +mod primes; +#[cfg(feature = "wyhash")] +mod secret; +mod wyrand; + +#[cfg(feature = "randomised_wyhash")] +#[cfg_attr(docsrs, doc(cfg(feature = "randomised_wyhash")))] +pub use builder::RandomWyHashState; + +#[cfg(feature = "wyhash")] +#[cfg_attr(docsrs, doc(cfg(feature = "wyhash")))] +pub use hasher::WyHash; + +#[cfg(feature = "wyhash")] +#[cfg_attr(docsrs, doc(cfg(feature = "wyhash")))] +pub use secret::Secret; + +pub use wyrand::WyRand; diff --git a/src/hasher/builder.rs b/src/final_v4_2/builder.rs similarity index 85% rename from src/hasher/builder.rs rename to src/final_v4_2/builder.rs index ecb4f81..d77a117 100644 --- a/src/hasher/builder.rs +++ b/src/final_v4_2/builder.rs @@ -6,10 +6,10 @@ use core::fmt::Debug; #[cfg(feature = "fully_randomised_wyhash")] use std::sync::OnceLock; -use crate::WyHash; +use super::{secret::Secret, WyHash}; #[cfg(feature = "fully_randomised_wyhash")] -static SECRET: OnceLock<[u64; 4]> = OnceLock::new(); +static SECRET: OnceLock = OnceLock::new(); #[inline] fn get_random_u64() -> u64 { @@ -37,7 +37,6 @@ fn get_random_u64() -> u64 { } } -#[cfg_attr(docsrs, doc(cfg(feature = "randomised_wyhash")))] #[derive(Clone, Copy)] /// Randomised state constructor for [`WyHash`]. This builder will source entropy in order /// to provide random seeds for [`WyHash`]. If the `fully_randomised_wyhash` feature is enabled, @@ -45,13 +44,13 @@ fn get_random_u64() -> u64 { /// granting extra protection against DOS and prediction attacks. pub struct RandomWyHashState { state: u64, - secret: [u64; 4], + secret: Secret, } impl RandomWyHashState { /// Create a new [`RandomWyHashState`] instance. Calling this method will attempt to /// draw entropy from hardware/OS sources. If `fully_randomised_wyhash` feature is enabled, - /// then it will use a randomised `secret` as well, otherwise it uses the default wyhash constants. + /// then it will use a randomised `secret` as well, otherwise it uses the default wyhash constants. /// /// # Panics /// @@ -71,22 +70,21 @@ impl RandomWyHashState { #[inline] pub fn new() -> Self { #[cfg(feature = "fully_randomised_wyhash")] - use crate::hasher::secret::make_secret; - + use super::secret::make_secret; #[cfg(not(feature = "fully_randomised_wyhash"))] - use crate::constants::{WY0, WY1, WY2, WY3}; + use super::constants::{WY0, WY1, WY2, WY3}; #[cfg(feature = "fully_randomised_wyhash")] let secret = *SECRET.get_or_init(|| make_secret(get_random_u64())); #[cfg(not(feature = "fully_randomised_wyhash"))] - let secret = [WY0, WY1, WY2, WY3]; + let secret = Secret::new(WY0, WY1, WY2, WY3); Self::new_with_secret(secret) } /// Create a new [`RandomWyHashState`] instance with a provided secret. Calling this method /// will attempt to draw entropy from hardware/OS sources, and assumes the user provided the - /// secret via [`super::secret::make_secret`]. + /// secret via [`WyHash::make_secret`]. /// /// # Panics /// @@ -95,16 +93,16 @@ impl RandomWyHashState { /// # Examples /// /// ``` - /// use wyrand::{RandomWyHashState, make_secret}; /// use core::hash::BuildHasher; + /// use wyrand::{RandomWyHashState, WyHash}; /// - /// let s = RandomWyHashState::new_with_secret(make_secret(42)); + /// let s = RandomWyHashState::new_with_secret(WyHash::make_secret(42)); /// /// let mut hasher = s.build_hasher(); // Creates a WyHash instance with random state /// ``` #[must_use] #[inline] - pub fn new_with_secret(secret: [u64; 4]) -> Self { + pub fn new_with_secret(secret: Secret) -> Self { Self { state: get_random_u64(), secret, @@ -131,7 +129,7 @@ impl Default for RandomWyHashState { #[cfg(feature = "debug")] impl Debug for RandomWyHashState { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.debug_struct("RandomisedWyHashBuilder") + f.debug_struct("RandomisedWyHashState") .finish_non_exhaustive() } } @@ -151,7 +149,7 @@ mod tests { assert_eq!( format!("{builder:?}"), - "RandomisedWyHashBuilder { .. }", + "RandomisedWyHashState { .. }", "Debug should not be leaking internal state" ); } @@ -171,9 +169,9 @@ mod tests { // same as the default secret. #[cfg(feature = "fully_randomised_wyhash")] { - use crate::constants::{WY0, WY1, WY2, WY3}; + use super::super::constants::{WY0, WY1, WY2, WY3}; - let default_secret = [WY0, WY1, WY2, WY3]; + let default_secret = Secret::new(WY0, WY1, WY2, WY3); assert_ne!(&builder1.secret, &default_secret); assert_ne!(&builder2.secret, &default_secret); diff --git a/src/final_v4_2/constants.rs b/src/final_v4_2/constants.rs new file mode 100644 index 0000000..2c714fa --- /dev/null +++ b/src/final_v4_2/constants.rs @@ -0,0 +1,6 @@ +pub(super) const WY0: u64 = 0x2d35_8dcc_aa6c_78a5; +pub(super) const WY1: u64 = 0x8bb8_4b93_962e_acc9; +#[cfg(feature = "wyhash")] +pub(super) const WY2: u64 = 0x4b33_a62e_d433_d4a3; +#[cfg(feature = "wyhash")] +pub(super) const WY3: u64 = 0x4d5a_2da5_1de1_aa47; diff --git a/src/hasher.rs b/src/final_v4_2/hasher.rs similarity index 81% rename from src/hasher.rs rename to src/final_v4_2/hasher.rs index e3cca8b..0b177cb 100644 --- a/src/hasher.rs +++ b/src/final_v4_2/hasher.rs @@ -1,31 +1,20 @@ -#[cfg(feature = "randomised_wyhash")] -mod builder; -#[cfg(feature = "v4_2")] -mod primes; -mod read; -mod secret; - use core::hash::Hasher; -#[cfg(feature = "randomised_wyhash")] -#[cfg_attr(docsrs, doc(cfg(feature = "randomised_wyhash")))] -pub use builder::RandomWyHashState; - #[cfg(feature = "debug")] use core::fmt::Debug; use crate::{ - constants::{WY0, WY1, WY2, WY3}, + read::{read_4_bytes, read_8_bytes, read_upto_3_bytes}, utils::{wymix, wymul}, }; -use self::read::{is_over_48_bytes, read_4_bytes, read_8_bytes, read_upto_3_bytes}; - -pub use self::secret::make_secret; +use super::{ + constants::{WY0, WY1, WY2, WY3}, + secret::{make_secret, Secret}, +}; /// The WyHash hasher, a fast & portable hashing algorithm. This implementation is -/// based on the final v4/v4.2 C reference implementations (depending on whether the -/// `v4_2` feature flag is enabled or not). +/// based on the final v4.2 C reference implementation. /// /// ``` /// use wyrand::WyHash; @@ -46,17 +35,23 @@ pub use self::secret::make_secret; /// Any other sequence of events (including calls to `write_u32` or similar functions) are /// guaranteed to have consistent results between platforms and versions of this crate, but may not /// map well to the reference implementation. -#[cfg_attr(docsrs, doc(cfg(feature = "wyhash")))] #[derive(Clone)] pub struct WyHash { seed: u64, lo: u64, hi: u64, size: u64, - secret: [u64; 4], + secret: Secret, } impl WyHash { + /// thing + #[must_use] + #[inline] + pub fn make_secret(seed: u64) -> Secret { + make_secret(seed) + } + /// Create hasher with seeds for the state and secret (generates a new secret, expensive to compute). pub const fn new(seed: u64, secret_seed: u64) -> Self { Self::new_with_secret(seed, make_secret(secret_seed)) @@ -65,14 +60,14 @@ impl WyHash { /// Create hasher with a seed and default secrets #[inline] pub const fn new_with_default_secret(seed: u64) -> Self { - Self::new_with_secret(seed, [WY0, WY1, WY2, WY3]) + Self::new_with_secret(seed, Secret::new(WY0, WY1, WY2, WY3)) } /// Create hasher with a seed value and a secret. Assumes the user created the secret with [`make_secret`], /// else the hashing output will be weak/vulnerable. #[inline] - pub const fn new_with_secret(mut seed: u64, secret: [u64; 4]) -> Self { - seed ^= wymix(seed ^ secret[0], secret[1]); + pub const fn new_with_secret(mut seed: u64, secret: Secret) -> Self { + seed ^= wymix(seed ^ secret.first(), secret.second()); WyHash { seed, @@ -100,21 +95,21 @@ impl WyHash { let mut start = 0; let mut seed = self.seed; - if is_over_48_bytes(length) { + if length >= 48 { let mut seed1 = seed; let mut seed2 = seed; - while is_over_48_bytes(index) { + while index >= 48 { seed = wymix( - read_8_bytes(&bytes[start..]) ^ self.secret[1], + read_8_bytes(&bytes[start..]) ^ self.secret.second(), read_8_bytes(&bytes[start + 8..]) ^ seed, ); seed1 = wymix( - read_8_bytes(&bytes[start + 16..]) ^ self.secret[2], + read_8_bytes(&bytes[start + 16..]) ^ self.secret.third(), read_8_bytes(&bytes[start + 24..]) ^ seed1, ); seed2 = wymix( - read_8_bytes(&bytes[start + 32..]) ^ self.secret[3], + read_8_bytes(&bytes[start + 32..]) ^ self.secret.fourth(), read_8_bytes(&bytes[start + 40..]) ^ seed2, ); index -= 48; @@ -126,7 +121,7 @@ impl WyHash { while index > 16 { seed = wymix( - read_8_bytes(&bytes[start..]) ^ self.secret[1], + read_8_bytes(&bytes[start..]) ^ self.secret.second(), read_8_bytes(&bytes[start + 8..]) ^ seed, ); index -= 16; @@ -198,8 +193,11 @@ impl Hasher for WyHash { #[inline] fn finish(&self) -> u64 { - let (lo, hi) = wymul(self.lo ^ self.secret[1], self.hi ^ self.seed); - wymix(lo ^ self.secret[0] ^ self.size, hi ^ self.secret[1]) + let (lo, hi) = wymul(self.lo ^ self.secret.second(), self.hi ^ self.seed); + wymix( + lo ^ self.secret.first() ^ self.size, + hi ^ self.secret.second(), + ) } } @@ -242,20 +240,6 @@ mod tests { ); } - #[cfg(not(feature = "v4_2"))] - #[rustfmt::skip] - const TEST_VECTORS: [(u64, &str); 8] = [ - (0x0409_638e_e2bd_e459, ""), - (0xa841_2d09_1b5f_e0a9, "a"), - (0x32dd_92e4_b291_5153, "abc"), - (0x8619_1240_89a3_a16b, "message digest"), - (0x7a43_afb6_1d7f_5f40, "abcdefghijklmnopqrstuvwxyz"), - (0xff42_329b_90e5_0d58, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"), - (0xc39c_ab13_b115_aad3, "12345678901234567890123456789012345678901234567890123456789012345678901234567890"), - (0xe44a_846b_fc65_00cd, "123456789012345678901234567890123456789012345678"), - ]; - - #[cfg(feature = "v4_2")] #[rustfmt::skip] const TEST_VECTORS: [(u64, &str); 8] = [ (0x9322_8a4d_e0ee_c5a2, ""), diff --git a/src/hasher/primes.rs b/src/final_v4_2/primes.rs similarity index 100% rename from src/hasher/primes.rs rename to src/final_v4_2/primes.rs diff --git a/src/hasher/secret.rs b/src/final_v4_2/secret.rs similarity index 54% rename from src/hasher/secret.rs rename to src/final_v4_2/secret.rs index 3eaa2b0..4d46dc4 100644 --- a/src/hasher/secret.rs +++ b/src/final_v4_2/secret.rs @@ -1,18 +1,53 @@ -use crate::WyRand; +use crate::{constants::C_VALUES, WyRand}; -#[cfg(feature = "v4_2")] -use crate::hasher::primes::is_prime; +#[cfg(feature = "debug")] +use core::fmt::Debug; -/// Generate new secret for wyhash. Takes a seed value and outputs an array of 4 suitable `u64` constants -/// for use with the hasher. The PRNG will always use the default constants provided. -pub const fn make_secret(mut seed: u64) -> [u64; 4] { - const C_VALUES: &[u8] = &[ - 15, 23, 27, 29, 30, 39, 43, 45, 46, 51, 53, 54, 57, 58, 60, 71, 75, 77, 78, 83, 85, 86, 89, - 90, 92, 99, 101, 102, 105, 106, 108, 113, 114, 116, 120, 135, 139, 141, 142, 147, 149, 150, - 153, 154, 156, 163, 165, 166, 169, 170, 172, 177, 178, 180, 184, 195, 197, 198, 201, 202, - 204, 209, 210, 212, 216, 225, 226, 228, 232, 240, - ]; +use super::primes::is_prime; + +#[derive(Clone, Copy, PartialEq, Eq)] +/// A wrapper struct for containing generated secrets to be used by the wyhash algorithm. Ensures it can't be used +/// incorrectly, and can only be constructed by [`super::WyHash::make_secret`]. +pub struct Secret([u64; 4]); + +impl Secret { + #[must_use] + #[inline] + pub(super) const fn new(first: u64, second: u64, third: u64, fourth: u64) -> Self { + Self([first, second, third, fourth]) + } + + #[inline] + pub(super) const fn first(self) -> u64 { + self.0[0] + } + + #[inline] + pub(super) const fn second(self) -> u64 { + self.0[1] + } + + #[inline] + pub(super) const fn third(self) -> u64 { + self.0[2] + } + + #[inline] + pub(super) const fn fourth(self) -> u64 { + self.0[3] + } +} + +#[cfg(feature = "debug")] +impl Debug for Secret { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_tuple("Secret").finish() + } +} +/// Generate new secret for wyhash. Takes a seed value and outputs an array of 4 suitable `u64` constants +/// for use with the hasher. The PRNG will always use the default constants provided. +pub(super) const fn make_secret(mut seed: u64) -> Secret { let mut secret: [u64; 4] = [0; 4]; let mut i: usize = 0; @@ -48,7 +83,6 @@ pub const fn make_secret(mut seed: u64) -> [u64; 4] { j += 1; } - #[cfg(feature = "v4_2")] if ok && !is_prime(secret[i]) { ok = false; } @@ -57,69 +91,63 @@ pub const fn make_secret(mut seed: u64) -> [u64; 4] { i += 1; } - secret + Secret(secret) } #[cfg(test)] mod tests { + extern crate alloc; + use super::*; + #[cfg(feature = "debug")] + #[test] + fn no_leaking_debug() { + use alloc::format; + + let secret = Secret::new( + 0x39d43c5c4e3a724b, + 0x6596e14753cca38b, + 0xc68d954b2b339353, + 0x96b4a6e45c65aa55, + ); + + assert_eq!( + format!("{secret:?}"), + "Secret", + "Debug should not be leaking internal state" + ); + } + + #[cfg(feature = "debug")] #[test] fn generate_expected_secrets() { let test_cases: [u64; 4] = [0, 3, 6, 42]; - #[cfg(feature = "v4_2")] - let expected_results: [[u64; 4]; 4] = [ - [ + let expected_results: [Secret; 4] = [ + Secret::new( 0x39d43c5c4e3a724b, 0x6596e14753cca38b, 0xc68d954b2b339353, 0x96b4a6e45c65aa55, - ], - [ + ), + Secret::new( 0xa3743ca35956ac59, 0x65b1b8e8558b72c5, 0x78cad4b8c98ea535, 0x561d59965a4baa27, - ], - [ + ), + Secret::new( 0x993c394d599a9a2b, 0x535c4d3c9ae1a91d, 0x72b2356a3cc6f0a5, 0x5a6c8e1b6c2e4da9, - ], - [ + ), + Secret::new( 0x8b4be21b934dc6a3, 0x9a0f72f0e81b6969, 0x99746a47f066331b, 0xccb8b85a99aaa9b1, - ], - ]; - #[cfg(not(feature = "v4_2"))] - let expected_results: [[u64; 4]; 4] = [ - [ - 0x95d49a959ca5a395, - 0xb4a9716ac94da695, - 0x5635cc6355956559, - 0xe1e18e3a9c591da9, - ], - [ - 0xa9c64d71a6e2a3c9, - 0x5cac27591d9ad1e1, - 0x3574d14eb45987a5, - 0xd8b85963273c4d1d, - ], - [ - 0x4dc3d12e36b1272d, - 0xaa5a8b35b4781d1b, - 0xcc36354be4e24e4b, - 0x3c554da34d748787, - ], - [ - 0x4d781d729a998b95, - 0xa52e8ec66a3c5655, - 0xb4e89c6536272da3, - 0x6aacaaac8ee2c393, - ], + ), ]; test_cases diff --git a/src/wyrand.rs b/src/final_v4_2/wyrand.rs similarity index 92% rename from src/wyrand.rs rename to src/final_v4_2/wyrand.rs index c233cf8..f5a03e7 100644 --- a/src/wyrand.rs +++ b/src/final_v4_2/wyrand.rs @@ -1,7 +1,7 @@ #[cfg(feature = "debug")] use core::fmt::Debug; -use crate::constants::{WY0, WY1}; +use super::constants::{WY0, WY1}; #[cfg(feature = "rand_core")] use rand_core::{impls::fill_bytes_via_next, RngCore, SeedableRng}; @@ -9,7 +9,8 @@ use crate::utils::wymix; #[cfg(feature = "serde1")] use serde::{Deserialize, Serialize}; -/// A Pseudorandom Number generator, powered by the `wyrand` algorithm. +/// A Pseudorandom Number generator, powered by the `wyrand` algorithm. This generator +/// is based on the final v4.2 reference implementation. #[derive(Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] #[cfg_attr(feature = "hash", derive(Hash))] @@ -150,16 +151,8 @@ mod tests { let mut rng = WyRand::from_seed(Default::default()); - #[cfg(not(feature = "v4_2"))] - { - assert_eq!(rand_generic(&mut rng), 2_405_016_974); - assert_eq!(rand_dyn(&mut rng), 4_283_336_045); - } - #[cfg(feature = "v4_2")] - { - assert_eq!(rand_generic(&mut rng), 2_371_481_814); - assert_eq!(rand_dyn(&mut rng), 412_509_173); - } + assert_eq!(rand_generic(&mut rng), 2_371_481_814); + assert_eq!(rand_dyn(&mut rng), 412_509_173); } #[cfg(all(feature = "serde1", feature = "debug"))] diff --git a/src/legacy_final_v4.rs b/src/legacy_final_v4.rs new file mode 100644 index 0000000..ab13541 --- /dev/null +++ b/src/legacy_final_v4.rs @@ -0,0 +1,22 @@ +#[cfg(feature = "randomised_wyhash")] +mod builder; +mod constants; +#[cfg(feature = "wyhash")] +mod hasher; +#[cfg(feature = "wyhash")] +mod secret; +mod wyrand; + +#[cfg(feature = "randomised_wyhash")] +#[cfg_attr(docsrs, doc(cfg(feature = "randomised_wyhash")))] +pub use builder::RandomWyHashLegacyState; + +#[cfg(feature = "wyhash")] +#[cfg_attr(docsrs, doc(cfg(feature = "wyhash")))] +pub use hasher::WyHashLegacy; + +#[cfg(feature = "wyhash")] +#[cfg_attr(docsrs, doc(cfg(feature = "wyhash")))] +pub use secret::LegacySecret; + +pub use wyrand::WyRandLegacy; diff --git a/src/legacy_final_v4/builder.rs b/src/legacy_final_v4/builder.rs new file mode 100644 index 0000000..455eca3 --- /dev/null +++ b/src/legacy_final_v4/builder.rs @@ -0,0 +1,182 @@ +use core::hash::BuildHasher; + +#[cfg(feature = "debug")] +use core::fmt::Debug; + +#[cfg(feature = "fully_randomised_wyhash")] +use std::sync::OnceLock; + +use super::WyHashLegacy; + +use super::secret::LegacySecret; + +#[cfg(feature = "fully_randomised_wyhash")] +static SECRET: OnceLock = OnceLock::new(); + +#[inline] +fn get_random_u64() -> u64 { + #[cfg(not(feature = "threadrng_wyhash"))] + { + const SIZE: usize = core::mem::size_of::(); + + let mut state = [0; SIZE]; + + // Don't bother trying to handle the result. If we can't obtain + // entropy with getrandom, then there is no hope and we might as + // well panic. It is up to the user to ensure getrandom is configured + // correctly for their platform. + getrandom::getrandom(&mut state) + .expect("Failed to source entropy for WyHash randomised state"); + + u64::from_ne_bytes(state) + } + #[cfg(feature = "threadrng_wyhash")] + { + use rand::RngCore; + + // This is faster than doing `.fill_bytes()`. User-space entropy goes brrr. + rand::thread_rng().next_u64() + } +} + +#[derive(Clone, Copy)] +/// Randomised state constructor for [`WyHashLegacy`]. This builder will source entropy in order +/// to provide random seeds for [`WyHashLegacy`]. If the `fully_randomised_wyhash` feature is enabled, +/// this will yield a hasher with not just a random seed, but also a new random secret, +/// granting extra protection against DOS and prediction attacks. +pub struct RandomWyHashLegacyState { + state: u64, + secret: LegacySecret, +} + +impl RandomWyHashLegacyState { + /// Create a new [`RandomWyHashLegacyState`] instance. Calling this method will attempt to + /// draw entropy from hardware/OS sources. If `fully_randomised_wyhash` feature is enabled, + /// then it will use a randomised `secret` as well, otherwise it uses the default wyhash constants. + /// + /// # Panics + /// + /// This method will panic if it was unable to source enough entropy. + /// + /// # Examples + /// + /// ``` + /// use wyrand::legacy_final_v4::RandomWyHashLegacyState; + /// use core::hash::BuildHasher; + /// + /// let s = RandomWyHashLegacyState::new(); + /// + /// let mut hasher = s.build_hasher(); // Creates a WyHash instance with random state + /// ``` + #[must_use] + #[inline] + pub fn new() -> Self { + #[cfg(not(feature = "fully_randomised_wyhash"))] + use super::constants::{WY0, WY1, WY2, WY3}; + #[cfg(feature = "fully_randomised_wyhash")] + use super::secret::make_secret_legacy; + + #[cfg(feature = "fully_randomised_wyhash")] + let secret = *SECRET.get_or_init(|| make_secret_legacy(get_random_u64())); + #[cfg(not(feature = "fully_randomised_wyhash"))] + let secret = LegacySecret::new(WY0, WY1, WY2, WY3); + + Self::new_with_secret(secret) + } + + /// Create a new [`RandomWyHashLegacyState`] instance with a provided secret. Calling this method + /// will attempt to draw entropy from hardware/OS sources, and assumes the user provided the + /// secret via [`WyHashLegacy::make_secret`]. + /// + /// # Panics + /// + /// This method will panic if it was unable to source enough entropy. + /// + /// # Examples + /// + /// ``` + /// use core::hash::BuildHasher; + /// use wyrand::legacy_final_v4::{RandomWyHashLegacyState, WyHashLegacy}; + /// + /// let s = RandomWyHashLegacyState::new_with_secret(WyHashLegacy::make_secret(42)); + /// + /// let mut hasher = s.build_hasher(); // Creates a WyHash instance with random state + /// ``` + #[must_use] + #[inline] + pub fn new_with_secret(secret: LegacySecret) -> Self { + Self { + state: get_random_u64(), + secret, + } + } +} + +impl BuildHasher for RandomWyHashLegacyState { + type Hasher = WyHashLegacy; + + #[inline] + fn build_hasher(&self) -> Self::Hasher { + WyHashLegacy::new_with_secret(self.state, self.secret) + } +} + +impl Default for RandomWyHashLegacyState { + #[inline] + fn default() -> Self { + Self::new() + } +} + +#[cfg(feature = "debug")] +impl Debug for RandomWyHashLegacyState { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("RandomisedWyHashLegacyState") + .finish_non_exhaustive() + } +} + +#[cfg(test)] +mod tests { + extern crate alloc; + + use super::*; + + #[cfg(feature = "debug")] + #[test] + fn no_leaking_debug() { + use alloc::format; + + let builder = RandomWyHashLegacyState::default(); + + assert_eq!( + format!("{builder:?}"), + "RandomisedWyHashLegacyState { .. }", + "Debug should not be leaking internal state" + ); + } + + #[test] + fn randomised_builder_states() { + let builder1 = RandomWyHashLegacyState::new(); + let builder2 = RandomWyHashLegacyState::new(); + + // The two builders' internal states are different to each other + assert_ne!(&builder1.state, &builder2.state); + + // The two builders' internal secrets are the same to each other + assert_eq!(&builder1.secret, &builder2.secret); + + // When fully randomised, the generated secrets should not be the + // same as the default secret. + #[cfg(feature = "fully_randomised_wyhash")] + { + use super::super::constants::{WY0, WY1, WY2, WY3}; + + let default_secret = LegacySecret::new(WY0, WY1, WY2, WY3); + + assert_ne!(&builder1.secret, &default_secret); + assert_ne!(&builder2.secret, &default_secret); + } + } +} diff --git a/src/legacy_final_v4/constants.rs b/src/legacy_final_v4/constants.rs new file mode 100644 index 0000000..701bdd9 --- /dev/null +++ b/src/legacy_final_v4/constants.rs @@ -0,0 +1,6 @@ +pub(super) const WY0: u64 = 0xa076_1d64_78bd_642f; +pub(super) const WY1: u64 = 0xe703_7ed1_a0b4_28db; +#[cfg(feature = "wyhash")] +pub(super) const WY2: u64 = 0x8ebc_6af0_9c88_c6e3; +#[cfg(feature = "wyhash")] +pub(super) const WY3: u64 = 0x5899_65cc_7537_4cc3; diff --git a/src/legacy_final_v4/hasher.rs b/src/legacy_final_v4/hasher.rs new file mode 100644 index 0000000..ed0de23 --- /dev/null +++ b/src/legacy_final_v4/hasher.rs @@ -0,0 +1,310 @@ +// #[cfg(feature = "randomised_wyhash")] +// mod builder; + +use core::hash::Hasher; + +// #[cfg(feature = "randomised_wyhash")] +// #[cfg_attr(docsrs, doc(cfg(feature = "randomised_wyhash")))] +// pub use builder::RandomWyHashState; + +#[cfg(feature = "debug")] +use core::fmt::Debug; + +use crate::{ + read::{read_4_bytes, read_8_bytes, read_upto_3_bytes}, + utils::{wymix, wymul}, +}; + +use super::{ + constants::{WY0, WY1, WY2, WY3}, + secret::{make_secret_legacy, LegacySecret}, +}; + +/// The WyHash hasher, a fast & portable hashing algorithm. This implementation is +/// based on the legacy final v4 reference implementation. +/// +/// ``` +/// use wyrand::legacy_final_v4::WyHashLegacy; +/// use core::hash::Hasher; +/// +/// let mut hasher = WyHashLegacy::default(); +/// +/// hasher.write_u64(5); +/// +/// assert_ne!(hasher.finish(), 5); // Should not be represented by the same value any more +/// ``` +/// +/// # Stability +/// +/// The result is only guaranteed to match the result `wyhash` would naturally produce if `write` +/// is called a single time, followed by a call to `finish`. +/// +/// Any other sequence of events (including calls to `write_u32` or similar functions) are +/// guaranteed to have consistent results between platforms and versions of this crate, but may not +/// map well to the reference implementation. +#[derive(Clone)] +pub struct WyHashLegacy { + seed: u64, + lo: u64, + hi: u64, + size: u64, + secret: LegacySecret, +} + +impl WyHashLegacy { + /// thing + #[must_use] + #[inline] + pub fn make_secret(seed: u64) -> LegacySecret { + make_secret_legacy(seed) + } + + /// Create hasher with seeds for the state and secret (generates a new secret, expensive to compute). + pub const fn new(seed: u64, secret_seed: u64) -> Self { + Self::new_with_secret(seed, make_secret_legacy(secret_seed)) + } + + /// Create hasher with a seed and default secrets + #[inline] + pub const fn new_with_default_secret(seed: u64) -> Self { + Self::new_with_secret(seed, LegacySecret::new(WY0, WY1, WY2, WY3)) + } + + /// Create hasher with a seed value and a secret. Assumes the user created the secret with [`make_secret`], + /// else the hashing output will be weak/vulnerable. + #[inline] + pub(super) const fn new_with_secret(mut seed: u64, secret: LegacySecret) -> Self { + seed ^= wymix(seed ^ secret.first(), secret.second()); + + WyHashLegacy { + seed, + lo: 0, + hi: 0, + size: 0, + secret, + } + } + + #[inline] + fn consume_bytes(&self, bytes: &[u8]) -> (u64, u64, u64) { + let length = bytes.len(); + if length == 0 { + (0, 0, self.seed) + } else if length <= 3 { + (read_upto_3_bytes(bytes), 0, self.seed) + } else if length <= 16 { + let lo = (read_4_bytes(bytes) << 32) | read_4_bytes(&bytes[(length >> 3) << 2..]); + let hi = (read_4_bytes(&bytes[length - 4..]) << 32) + | read_4_bytes(&bytes[length - 4 - ((length >> 3) << 2)..]); + (lo, hi, self.seed) + } else { + let mut index = length; + let mut start = 0; + let mut seed = self.seed; + + if length > 48 { + let mut seed1 = seed; + let mut seed2 = seed; + + while index > 48 { + seed = wymix( + read_8_bytes(&bytes[start..]) ^ self.secret.second(), + read_8_bytes(&bytes[start + 8..]) ^ seed, + ); + seed1 = wymix( + read_8_bytes(&bytes[start + 16..]) ^ self.secret.third(), + read_8_bytes(&bytes[start + 24..]) ^ seed1, + ); + seed2 = wymix( + read_8_bytes(&bytes[start + 32..]) ^ self.secret.fourth(), + read_8_bytes(&bytes[start + 40..]) ^ seed2, + ); + index -= 48; + start += 48; + } + + seed ^= seed1 ^ seed2; + } + + while index > 16 { + seed = wymix( + read_8_bytes(&bytes[start..]) ^ self.secret.second(), + read_8_bytes(&bytes[start + 8..]) ^ seed, + ); + index -= 16; + start += 16 + } + + let lo = read_8_bytes(&bytes[length - 16..]); + let hi = read_8_bytes(&bytes[length - 8..]); + (lo, hi, seed) + } + } + + #[inline] + fn mix_current_seed(&mut self) { + if self.size != 0 { + self.seed = wymix(self.lo, self.hi ^ self.seed); + } + } +} + +impl Hasher for WyHashLegacy { + #[inline] + fn write(&mut self, bytes: &[u8]) { + self.mix_current_seed(); + + let (lo, hi, seed) = self.consume_bytes(bytes); + + self.lo = lo; + self.hi = hi; + self.seed = seed; + self.size += bytes.len() as u64; + } + + #[inline] + fn write_u8(&mut self, i: u8) { + self.write_u64(i as u64) + } + + #[inline] + fn write_u16(&mut self, i: u16) { + self.write_u64(i as u64) + } + + #[inline] + fn write_u32(&mut self, i: u32) { + self.write_u64(i as u64) + } + + #[inline] + fn write_u64(&mut self, i: u64) { + self.mix_current_seed(); + self.lo = i; + self.hi = 0; + self.size += 8; + } + + #[inline] + fn write_u128(&mut self, i: u128) { + self.mix_current_seed(); + self.lo = i as u64; + self.hi = (i >> 64) as u64; + self.size += 16; + } + + #[inline] + fn write_usize(&mut self, i: usize) { + self.write_u64(i as u64); + } + + #[inline] + fn finish(&self) -> u64 { + let (lo, hi) = wymul(self.lo ^ self.secret.second(), self.hi ^ self.seed); + wymix( + lo ^ self.secret.first() ^ self.size, + hi ^ self.secret.second(), + ) + } +} + +impl Default for WyHashLegacy { + #[inline] + fn default() -> Self { + WyHashLegacy::new_with_default_secret(0) + } +} + +#[cfg(feature = "debug")] +impl Debug for WyHashLegacy { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + // Do not expose the internal state of the Hasher + f.debug_struct("WyHashLegacy") + .field("size", &self.size) + .finish_non_exhaustive() + } +} + +#[cfg(test)] +mod tests { + extern crate alloc; + + use super::*; + + use core::hash::Hash; + + #[cfg(feature = "debug")] + #[test] + fn no_leaking_debug() { + use alloc::format; + + let rng = WyHashLegacy::default(); + + assert_eq!( + format!("{rng:?}"), + "WyHashLegacy { size: 0, .. }", + "Debug should not be leaking sensitive internal state" + ); + } + + #[rustfmt::skip] + const TEST_VECTORS: [(u64, &str); 8] = [ + (0x0409_638e_e2bd_e459, ""), + (0xa841_2d09_1b5f_e0a9, "a"), + (0x32dd_92e4_b291_5153, "abc"), + (0x8619_1240_89a3_a16b, "message digest"), + (0x7a43_afb6_1d7f_5f40, "abcdefghijklmnopqrstuvwxyz"), + (0xff42_329b_90e5_0d58, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"), + (0xc39c_ab13_b115_aad3, "12345678901234567890123456789012345678901234567890123456789012345678901234567890"), + (0xe44a_846b_fc65_00cd, "123456789012345678901234567890123456789012345678"), + ]; + + #[test] + fn expected_hasher_output() { + TEST_VECTORS + .into_iter() + .enumerate() + .map(|(seed, (expected, input))| { + let mut hasher = WyHashLegacy::new_with_default_secret(seed as u64); + + hasher.write(input.as_bytes()); + + (input, expected, hasher.finish()) + }) + .for_each(|(input, expected_hash, computed_hash)| { + assert_eq!( + expected_hash, computed_hash, + "Hashed output didn't match expected for \"{}\"", + input + ); + }); + } + + #[test] + fn multiple_writes_no_collision() { + let mut hasher = WyHashLegacy::new_with_default_secret(0); + hasher.write(b"abcdef"); + hasher.write(b"abcdef"); + let hash_a = hasher.finish(); + + let mut hasher = WyHashLegacy::new_with_default_secret(0); + hasher.write(b"abcdeF"); + hasher.write(b"abcdef"); + let hash_b = hasher.finish(); + + assert_ne!(hash_a, hash_b); + } + + #[test] + fn tuples_no_collision() { + let mut hasher = WyHashLegacy::new_with_default_secret(0); + (1000, 2000).hash(&mut hasher); + let hash_a = hasher.finish(); + + let mut hasher = WyHashLegacy::new_with_default_secret(0); + (1500, 2000).hash(&mut hasher); + let hash_b = hasher.finish(); + + assert_ne!(hash_a, hash_b); + } +} diff --git a/src/legacy_final_v4/secret.rs b/src/legacy_final_v4/secret.rs new file mode 100644 index 0000000..7a8169d --- /dev/null +++ b/src/legacy_final_v4/secret.rs @@ -0,0 +1,136 @@ +use crate::constants::C_VALUES; + +use super::WyRandLegacy; + +#[cfg(feature = "debug")] +use core::fmt::Debug; + +#[derive(Clone, Copy, PartialEq, Eq)] +/// A wrapper struct for containing generated secrets to be used by the wyhash algorithm. Ensures it can't be used +/// incorrectly, and can only be constructed by [`super::WyHashLegacy::make_secret`]. +pub struct LegacySecret([u64; 4]); + +impl LegacySecret { + #[must_use] + #[inline] + pub(super) const fn new(first: u64, second: u64, third: u64, fourth: u64) -> Self { + Self([first, second, third, fourth]) + } + + #[inline] + pub(super) const fn first(self) -> u64 { + self.0[0] + } + + #[inline] + pub(super) const fn second(self) -> u64 { + self.0[1] + } + + #[inline] + pub(super) const fn third(self) -> u64 { + self.0[2] + } + + #[inline] + pub(super) const fn fourth(self) -> u64 { + self.0[3] + } +} + +#[cfg(feature = "debug")] +impl Debug for LegacySecret { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_tuple("Secret").finish() + } +} + +/// Generate new secret for legacy final v4 wyhash. Takes a seed value and outputs 4 suitable `u64` constants +/// for use with the hasher. The PRNG will always use the default constants provided. +pub(super) const fn make_secret_legacy(mut seed: u64) -> LegacySecret { + let mut secret: [u64; 4] = [0; 4]; + let mut i: usize = 0; + + while i < secret.len() { + let mut ok: bool = false; + + while !ok { + ok = true; + secret[i] = 0; + let mut j: usize = 0; + + while j < 64 { + // WyRand... but const! + let (value, new_state) = WyRandLegacy::gen_u64(seed); + seed = new_state; + let random_index = (value as usize) % C_VALUES.len(); + secret[i] |= (C_VALUES[random_index] as u64) << j; + j += 8; + } + + if secret[i] % 2 == 0 { + ok = false; + continue; + } + + let mut j: usize = 0; + + while j < i { + if (secret[j] ^ secret[i]).count_ones() != 32 { + ok = false; + break; + } + j += 1; + } + } + + i += 1; + } + + LegacySecret(secret) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn generate_expected_secrets() { + let test_cases: [u64; 4] = [0, 3, 6, 42]; + let expected_results: [LegacySecret; 4] = [ + LegacySecret::new( + 0x95d49a959ca5a395, + 0xb4a9716ac94da695, + 0x5635cc6355956559, + 0xe1e18e3a9c591da9, + ), + LegacySecret::new( + 0xa9c64d71a6e2a3c9, + 0x5cac27591d9ad1e1, + 0x3574d14eb45987a5, + 0xd8b85963273c4d1d, + ), + LegacySecret::new( + 0x4dc3d12e36b1272d, + 0xaa5a8b35b4781d1b, + 0xcc36354be4e24e4b, + 0x3c554da34d748787, + ), + LegacySecret::new( + 0x4d781d729a998b95, + 0xa52e8ec66a3c5655, + 0xb4e89c6536272da3, + 0x6aacaaac8ee2c393, + ), + ]; + + test_cases + .into_iter() + .zip(expected_results) + .for_each(|(seed, expected)| { + let result = make_secret_legacy(seed); + + assert_eq!(&result, &expected, "Failed secret for seed: {}", seed); + }); + } +} diff --git a/src/legacy_final_v4/wyrand.rs b/src/legacy_final_v4/wyrand.rs new file mode 100644 index 0000000..0ab0a13 --- /dev/null +++ b/src/legacy_final_v4/wyrand.rs @@ -0,0 +1,199 @@ +#[cfg(feature = "debug")] +use core::fmt::Debug; + +use super::constants::{WY0, WY1}; +#[cfg(feature = "rand_core")] +use rand_core::{impls::fill_bytes_via_next, RngCore, SeedableRng}; + +use crate::utils::wymix; +#[cfg(feature = "serde1")] +use serde::{Deserialize, Serialize}; + +/// A Pseudorandom Number generator, powered by the `wyrand` algorithm. This generator +/// is based on the legacy final v4 reference implementation. +#[derive(Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "hash", derive(Hash))] +#[repr(transparent)] +pub struct WyRandLegacy { + state: u64, +} + +impl WyRandLegacy { + /// Creates a new [`WyRandLegacy`] instance with the provided seed. Be sure + /// to obtain the seed value from a good entropy source, either from + /// hardware, OS source, or from a suitable crate, like `getrandom`. + #[inline] + #[must_use] + pub const fn new(state: u64) -> Self { + Self { state } + } + + /// Generates a random [`u64`] value and advances the PRNG state. + #[inline] + pub fn rand(&mut self) -> u64 { + let (value, state) = Self::gen_u64(self.state); + self.state = state; + value + } + + /// Const [`WyRandLegacy`] generator. Generates and returns a random [`u64`] value first + /// and then the advanced state second. + /// ``` + /// use wyrand::legacy_final_v4::WyRandLegacy; + /// + /// let seed = 123; + /// + /// let (random_value, new_state) = WyRandLegacy::gen_u64(seed); + /// + /// assert_ne!(random_value, 0); + /// // The original seed now no longer matches the new state. + /// assert_ne!(new_state, seed); + /// ``` + #[inline(always)] + pub const fn gen_u64(mut seed: u64) -> (u64, u64) { + seed = seed.wrapping_add(WY0); + (wymix(seed, seed ^ WY1), seed) + } +} + +#[cfg(feature = "debug")] +impl Debug for WyRandLegacy { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("WyRandLegacy").finish() + } +} + +#[cfg(feature = "rand_core")] +impl RngCore for WyRandLegacy { + #[inline] + fn next_u32(&mut self) -> u32 { + self.rand() as u32 + } + + #[inline] + fn next_u64(&mut self) -> u64 { + self.rand() + } + + #[inline] + fn fill_bytes(&mut self, dest: &mut [u8]) { + fill_bytes_via_next(self, dest); + } + + #[inline] + fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core::Error> { + self.fill_bytes(dest); + Ok(()) + } +} + +#[cfg(feature = "rand_core")] +impl SeedableRng for WyRandLegacy { + type Seed = [u8; core::mem::size_of::()]; + + fn from_seed(seed: Self::Seed) -> Self { + Self::new(u64::from_ne_bytes(seed)) + } +} + +#[cfg(test)] +mod tests { + extern crate alloc; + + use super::*; + + #[cfg(feature = "debug")] + #[test] + fn no_leaking_debug() { + use alloc::format; + + let rng = WyRandLegacy::new(Default::default()); + + assert_eq!( + format!("{rng:?}"), + "WyRandLegacy", + "Debug should not be leaking internal state" + ); + } + + #[test] + fn clone_rng() { + let rng = WyRandLegacy::new(Default::default()); + + let mut cloned = rng.clone(); + + // Should be the same internal state after cloning + assert_eq!( + &rng.state, &cloned.state, + "the two RNG instances are not the same after cloning" + ); + + cloned.rand(); + + // Should no longer have the same internal state after generating a random number + assert_ne!( + &rng.state, &cloned.state, + "the two RNG instances are the same after one was used" + ); + } + + #[cfg(feature = "rand_core")] + #[test] + fn rand_core_integration() { + fn rand_generic(mut r: R) -> u32 { + r.next_u32() + } + + fn rand_dyn(r: &mut dyn RngCore) -> u32 { + r.next_u32() + } + + let mut rng = WyRandLegacy::from_seed(Default::default()); + + assert_eq!(rand_generic(&mut rng), 2_405_016_974); + assert_eq!(rand_dyn(&mut rng), 4_283_336_045); + } + + #[cfg(all(feature = "serde1", feature = "debug"))] + #[test] + fn serde_tokens() { + use serde_test::{assert_tokens, Token}; + + let seed = 12345; + let rng = WyRandLegacy::new(seed); + + assert_tokens( + &rng, + &[ + Token::Struct { + name: "WyRandLegacy", + len: 1, + }, + Token::BorrowedStr("state"), + Token::U64(seed), + Token::StructEnd, + ], + ); + } + + #[cfg(feature = "hash")] + #[allow(deprecated)] + #[test] + fn hash() { + use core::hash::{Hash, Hasher, SipHasher}; + + let rng = WyRandLegacy::new(123); + let state: u64 = 123; + + let mut hasher = SipHasher::default(); + rng.hash(&mut hasher); + let hashed_rng = hasher.finish(); + + let mut hasher = SipHasher::default(); + state.hash(&mut hasher); + let hashed_state = hasher.finish(); + + assert_eq!(hashed_rng, hashed_state); + } +} diff --git a/src/lib.rs b/src/lib.rs index a982960..f935311 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,12 +8,16 @@ #[cfg(feature = "fully_randomised_wyhash")] extern crate std; -mod constants; -#[cfg(feature = "wyhash")] -mod hasher; mod utils; -mod wyrand; - +mod read; +/// Legacy final v4 implementations of `WyRand` & `WyHash`. These are the older legacy version of the algorithms, +/// only use them if for whatever reason you favour stability with previous usages over to switching to +/// more secure versions of the algorithm. +#[cfg(feature = "legacy_v4")] +#[cfg_attr(docsrs, doc(cfg(feature = "legacy_v4")))] +pub mod legacy_final_v4; +mod final_v4_2; #[cfg(feature = "wyhash")] -pub use hasher::*; -pub use wyrand::WyRand; +mod constants; + +pub use final_v4_2::*; diff --git a/src/hasher/read.rs b/src/read.rs similarity index 56% rename from src/hasher/read.rs rename to src/read.rs index 30e55f5..20779f3 100644 --- a/src/hasher/read.rs +++ b/src/read.rs @@ -1,5 +1,5 @@ #[inline(always)] -pub(super) const fn read_8_bytes(bits: &[u8]) -> u64 { +pub(crate) const fn read_8_bytes(bits: &[u8]) -> u64 { (bits[7] as u64) << 56 | (bits[6] as u64) << 48 | (bits[5] as u64) << 40 @@ -11,23 +11,11 @@ pub(super) const fn read_8_bytes(bits: &[u8]) -> u64 { } #[inline(always)] -pub(super) const fn read_4_bytes(bits: &[u8]) -> u64 { +pub(crate) const fn read_4_bytes(bits: &[u8]) -> u64 { (bits[3] as u64) << 24 | (bits[2] as u64) << 16 | (bits[1] as u64) << 8 | (bits[0] as u64) } #[inline(always)] -pub(super) const fn read_upto_3_bytes(bits: &[u8]) -> u64 { +pub(crate) const fn read_upto_3_bytes(bits: &[u8]) -> u64 { (bits[0] as u64) << 16 | (bits[bits.len() >> 1] as u64) << 8 | (bits[bits.len() - 1] as u64) } - -#[inline(always)] -pub(super) const fn is_over_48_bytes(length: usize) -> bool { - #[cfg(feature = "v4_2")] - { - length >= 48 - } - #[cfg(not(feature = "v4_2"))] - { - length > 48 - } -}