From 24b4ec70358e533ed16d290b5db0fbde91af611d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Rica=20Pais=20da=20Silva?= Date: Wed, 17 Apr 2024 16:02:53 +0200 Subject: [PATCH 1/9] refactor: Rework module and feature flag for better v4/v4.2 split --- .github/workflows/ci.yml | 6 +- Cargo.toml | 2 +- README.md | 4 +- src/constants.rs | 31 +-- src/final_v4_2.rs | 24 ++ src/{hasher => final_v4_2}/builder.rs | 32 ++- src/final_v4_2/constants.rs | 6 + src/{ => final_v4_2}/hasher.rs | 72 +++--- src/{hasher => final_v4_2}/primes.rs | 0 src/{hasher => final_v4_2}/secret.rs | 130 ++++++----- src/{ => final_v4_2}/wyrand.rs | 17 +- src/legacy_final_v4.rs | 22 ++ src/legacy_final_v4/builder.rs | 182 +++++++++++++++ src/legacy_final_v4/constants.rs | 6 + src/legacy_final_v4/hasher.rs | 310 ++++++++++++++++++++++++++ src/legacy_final_v4/secret.rs | 136 +++++++++++ src/legacy_final_v4/wyrand.rs | 199 +++++++++++++++++ src/lib.rs | 19 +- src/{hasher => }/read.rs | 18 +- 19 files changed, 1039 insertions(+), 177 deletions(-) create mode 100644 src/final_v4_2.rs rename src/{hasher => final_v4_2}/builder.rs (85%) create mode 100644 src/final_v4_2/constants.rs rename src/{ => final_v4_2}/hasher.rs (81%) rename src/{hasher => final_v4_2}/primes.rs (100%) rename src/{hasher => final_v4_2}/secret.rs (54%) rename src/{ => final_v4_2}/wyrand.rs (92%) create mode 100644 src/legacy_final_v4.rs create mode 100644 src/legacy_final_v4/builder.rs create mode 100644 src/legacy_final_v4/constants.rs create mode 100644 src/legacy_final_v4/hasher.rs create mode 100644 src/legacy_final_v4/secret.rs create mode 100644 src/legacy_final_v4/wyrand.rs rename src/{hasher => }/read.rs (56%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 908a6c1..f351ecd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,10 +35,10 @@ jobs: - name: Run cargo check run: cargo check --all --all-features --all-targets if: startsWith(matrix.rust, 'nightly') - - name: Run cargo check (without dev-dependencies to catch missing feature flags) + - name: Run cargo check if: startsWith(matrix.rust, 'nightly') - run: cargo check -Z features=dev_dep - - name: Full feature + v4.2 testing + run: cargo check + - name: Full feature testing run: cargo test --all-features - name: v4 compatibility testing run: cargo test --no-default-features --features debug,rand_core,wyhash,randomised_wyhash diff --git a/Cargo.toml b/Cargo.toml index 9544916..f754dd7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,7 @@ wyhash = [] randomised_wyhash = ["wyhash", "dep:getrandom"] fully_randomised_wyhash = ["randomised_wyhash"] threadrng_wyhash = ["dep:rand", "randomised_wyhash"] -v4_2 = [] +legacy_v4 = [] [dependencies] getrandom = { version = "0.2", optional = true } diff --git a/README.md b/README.md index 7d96a51..e3ff394 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 final v4.2 algorithm will be used. If one needs to use the older, legacy v4 implementation for compatibility/stability reasons, the legacy hasher and PRNG can be exposed by enabling 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)]`. @@ -38,7 +38,7 @@ The crate will always export `WyRand` and will do so when set as `default-featur - **`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 that use the final v4 implementation. ## 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..5ac5b29 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,12 +8,17 @@ #[cfg(feature = "fully_randomised_wyhash")] extern crate std; -mod constants; -#[cfg(feature = "wyhash")] -mod hasher; mod utils; -mod wyrand; - #[cfg(feature = "wyhash")] -pub use hasher::*; -pub use wyrand::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")] +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 - } -} From 501750a32f53f63af126f59d630bacff8542c29e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Rica=20Pais=20da=20Silva?= Date: Wed, 17 Apr 2024 16:24:56 +0200 Subject: [PATCH 2/9] doc: Don't forget to document methods correctly --- src/final_v4_2/hasher.rs | 2 +- src/legacy_final_v4/hasher.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/final_v4_2/hasher.rs b/src/final_v4_2/hasher.rs index 0b177cb..14c0edb 100644 --- a/src/final_v4_2/hasher.rs +++ b/src/final_v4_2/hasher.rs @@ -45,7 +45,7 @@ pub struct WyHash { } impl WyHash { - /// thing + /// Create suitable secret values to be used by the hasher. #[must_use] #[inline] pub fn make_secret(seed: u64) -> Secret { diff --git a/src/legacy_final_v4/hasher.rs b/src/legacy_final_v4/hasher.rs index ed0de23..df34bd8 100644 --- a/src/legacy_final_v4/hasher.rs +++ b/src/legacy_final_v4/hasher.rs @@ -52,7 +52,7 @@ pub struct WyHashLegacy { } impl WyHashLegacy { - /// thing + /// Create suitable secret values to be used by the hasher. #[must_use] #[inline] pub fn make_secret(seed: u64) -> LegacySecret { From a4440038b10568f40d1345a71089f1d458a7f996 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Rica=20Pais=20da=20Silva?= Date: Wed, 17 Apr 2024 17:21:30 +0200 Subject: [PATCH 3/9] perf: Ensure &self on secrets methods for less spurious copying --- src/final_v4_2/secret.rs | 8 ++++---- src/legacy_final_v4/secret.rs | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/final_v4_2/secret.rs b/src/final_v4_2/secret.rs index 4d46dc4..14d44b1 100644 --- a/src/final_v4_2/secret.rs +++ b/src/final_v4_2/secret.rs @@ -18,22 +18,22 @@ impl Secret { } #[inline] - pub(super) const fn first(self) -> u64 { + pub(super) const fn first(&self) -> u64 { self.0[0] } #[inline] - pub(super) const fn second(self) -> u64 { + pub(super) const fn second(&self) -> u64 { self.0[1] } #[inline] - pub(super) const fn third(self) -> u64 { + pub(super) const fn third(&self) -> u64 { self.0[2] } #[inline] - pub(super) const fn fourth(self) -> u64 { + pub(super) const fn fourth(&self) -> u64 { self.0[3] } } diff --git a/src/legacy_final_v4/secret.rs b/src/legacy_final_v4/secret.rs index 7a8169d..f46c845 100644 --- a/src/legacy_final_v4/secret.rs +++ b/src/legacy_final_v4/secret.rs @@ -18,22 +18,22 @@ impl LegacySecret { } #[inline] - pub(super) const fn first(self) -> u64 { + pub(super) const fn first(&self) -> u64 { self.0[0] } #[inline] - pub(super) const fn second(self) -> u64 { + pub(super) const fn second(&self) -> u64 { self.0[1] } #[inline] - pub(super) const fn third(self) -> u64 { + pub(super) const fn third(&self) -> u64 { self.0[2] } #[inline] - pub(super) const fn fourth(self) -> u64 { + pub(super) const fn fourth(&self) -> u64 { self.0[3] } } From 9b57128952302a9575a7ac0ec059789a3e0a414b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Rica=20Pais=20da=20Silva?= Date: Wed, 17 Apr 2024 17:35:06 +0200 Subject: [PATCH 4/9] perf: Ensure inlining of default secret --- src/final_v4_2/secret.rs | 2 +- src/legacy_final_v4/secret.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/final_v4_2/secret.rs b/src/final_v4_2/secret.rs index 14d44b1..a438558 100644 --- a/src/final_v4_2/secret.rs +++ b/src/final_v4_2/secret.rs @@ -12,7 +12,7 @@ pub struct Secret([u64; 4]); impl Secret { #[must_use] - #[inline] + #[inline(always)] pub(super) const fn new(first: u64, second: u64, third: u64, fourth: u64) -> Self { Self([first, second, third, fourth]) } diff --git a/src/legacy_final_v4/secret.rs b/src/legacy_final_v4/secret.rs index f46c845..8ae6912 100644 --- a/src/legacy_final_v4/secret.rs +++ b/src/legacy_final_v4/secret.rs @@ -12,7 +12,7 @@ pub struct LegacySecret([u64; 4]); impl LegacySecret { #[must_use] - #[inline] + #[inline(always)] pub(super) const fn new(first: u64, second: u64, third: u64, fourth: u64) -> Self { Self([first, second, third, fourth]) } From c48588ec5778f80978f12277fae19cf98748c1d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Rica=20Pais=20da=20Silva?= Date: Wed, 17 Apr 2024 17:50:34 +0200 Subject: [PATCH 5/9] perf: Add extra benchmarks, always inline secrets methods --- benches/rand_bench.rs | 37 ++++++++++++++++++++++++++++++++++- src/final_v4_2/secret.rs | 8 ++++---- src/legacy_final_v4/secret.rs | 8 ++++---- 3 files changed, 44 insertions(+), 9 deletions(-) diff --git a/benches/rand_bench.rs b/benches/rand_bench.rs index a290746..4a5cf81 100644 --- a/benches/rand_bench.rs +++ b/benches/rand_bench.rs @@ -33,7 +33,7 @@ fn wyrand_benchmark(c: &mut Criterion) { #[cfg(feature = "wyhash")] fn wyhash_benchmark(c: &mut Criterion) { - use std::hash::Hasher; + use std::hash::{Hash, Hasher}; use criterion::BenchmarkId; use wyrand::WyHash; @@ -67,6 +67,41 @@ fn wyhash_benchmark(c: &mut Criterion) { ); }); + c.bench_function("Hash integer single", |b| { + b.iter(|| { + let mut hasher = WyHash::new_with_default_secret(black_box(42)); + + hasher.write_u64(black_box(256)); + + hasher.finish() + }); + }); + + c.bench_function("Hash integer multiple", |b| { + let big_value = u64::MAX as u128 + 125; + + b.iter(|| { + let mut hasher = WyHash::new_with_default_secret(black_box(42)); + + hasher.write_u64(black_box(256)); + hasher.write_u128(black_box(big_value)); + + hasher.finish() + }); + }); + + c.bench_function("Hash integer tuple", |b| { + let big_value = u64::MAX as u128 + 125; + + b.iter(|| { + let mut hasher = WyHash::new_with_default_secret(black_box(42)); + + (black_box(42), black_box(big_value)).hash(&mut hasher); + + hasher.finish() + }); + }); + c.bench_function("Hash new with default secret", |b| { b.iter(|| WyHash::new_with_default_secret(black_box(42))); }); diff --git a/src/final_v4_2/secret.rs b/src/final_v4_2/secret.rs index a438558..5613f71 100644 --- a/src/final_v4_2/secret.rs +++ b/src/final_v4_2/secret.rs @@ -17,22 +17,22 @@ impl Secret { Self([first, second, third, fourth]) } - #[inline] + #[inline(always)] pub(super) const fn first(&self) -> u64 { self.0[0] } - #[inline] + #[inline(always)] pub(super) const fn second(&self) -> u64 { self.0[1] } - #[inline] + #[inline(always)] pub(super) const fn third(&self) -> u64 { self.0[2] } - #[inline] + #[inline(always)] pub(super) const fn fourth(&self) -> u64 { self.0[3] } diff --git a/src/legacy_final_v4/secret.rs b/src/legacy_final_v4/secret.rs index 8ae6912..8c1353a 100644 --- a/src/legacy_final_v4/secret.rs +++ b/src/legacy_final_v4/secret.rs @@ -17,22 +17,22 @@ impl LegacySecret { Self([first, second, third, fourth]) } - #[inline] + #[inline(always)] pub(super) const fn first(&self) -> u64 { self.0[0] } - #[inline] + #[inline(always)] pub(super) const fn second(&self) -> u64 { self.0[1] } - #[inline] + #[inline(always)] pub(super) const fn third(&self) -> u64 { self.0[2] } - #[inline] + #[inline(always)] pub(super) const fn fourth(&self) -> u64 { self.0[3] } From 1e08a8446e41e55dd32e4bccb5c2132a3335b46c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Rica=20Pais=20da=20Silva?= Date: Thu, 18 Apr 2024 10:25:08 +0200 Subject: [PATCH 6/9] perf: Optimise secrets layout, deduplicate make_secrets check --- src/constants.rs | 2 +- src/final_v4_2/builder.rs | 6 +++--- src/final_v4_2/hasher.rs | 2 +- src/final_v4_2/secret.rs | 21 ++++----------------- src/legacy_final_v4/builder.rs | 8 +++++--- src/legacy_final_v4/secret.rs | 21 ++++----------------- src/lib.rs | 8 ++++---- src/utils.rs | 19 +++++++++++++++++++ 8 files changed, 41 insertions(+), 46 deletions(-) diff --git a/src/constants.rs b/src/constants.rs index 847f7fc..bf2d5f4 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -1,4 +1,4 @@ -pub(crate )const C_VALUES: &[u8] = &[ +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, diff --git a/src/final_v4_2/builder.rs b/src/final_v4_2/builder.rs index d77a117..fed75c8 100644 --- a/src/final_v4_2/builder.rs +++ b/src/final_v4_2/builder.rs @@ -37,7 +37,7 @@ fn get_random_u64() -> u64 { } } -#[derive(Clone, Copy)] +#[derive(Clone)] /// 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, /// this will yield a hasher with not just a random seed, but also a new random secret, @@ -75,7 +75,7 @@ impl RandomWyHashState { use super::constants::{WY0, WY1, WY2, WY3}; #[cfg(feature = "fully_randomised_wyhash")] - let secret = *SECRET.get_or_init(|| make_secret(get_random_u64())); + let secret = SECRET.get_or_init(|| make_secret(get_random_u64())).clone(); #[cfg(not(feature = "fully_randomised_wyhash"))] let secret = Secret::new(WY0, WY1, WY2, WY3); @@ -115,7 +115,7 @@ impl BuildHasher for RandomWyHashState { #[inline] fn build_hasher(&self) -> Self::Hasher { - WyHash::new_with_secret(self.state, self.secret) + WyHash::new_with_secret(self.state, self.secret.clone()) } } diff --git a/src/final_v4_2/hasher.rs b/src/final_v4_2/hasher.rs index 14c0edb..89ba9fa 100644 --- a/src/final_v4_2/hasher.rs +++ b/src/final_v4_2/hasher.rs @@ -47,7 +47,7 @@ pub struct WyHash { impl WyHash { /// Create suitable secret values to be used by the hasher. #[must_use] - #[inline] + #[inline(always)] pub fn make_secret(seed: u64) -> Secret { make_secret(seed) } diff --git a/src/final_v4_2/secret.rs b/src/final_v4_2/secret.rs index 5613f71..a79d9cf 100644 --- a/src/final_v4_2/secret.rs +++ b/src/final_v4_2/secret.rs @@ -1,11 +1,12 @@ -use crate::{constants::C_VALUES, WyRand}; +use crate::{constants::C_VALUES, utils::check_for_valid_secret_value, WyRand}; #[cfg(feature = "debug")] use core::fmt::Debug; use super::primes::is_prime; -#[derive(Clone, Copy, PartialEq, Eq)] +#[derive(Clone, PartialEq, Eq)] +#[repr(align(32))] /// 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]); @@ -55,7 +56,6 @@ pub(super) const fn make_secret(mut seed: u64) -> Secret { let mut ok: bool = false; while !ok { - ok = true; secret[i] = 0; let mut j: usize = 0; @@ -68,20 +68,7 @@ pub(super) const fn make_secret(mut seed: u64) -> Secret { 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; - } + ok = check_for_valid_secret_value(i, &secret); if ok && !is_prime(secret[i]) { ok = false; diff --git a/src/legacy_final_v4/builder.rs b/src/legacy_final_v4/builder.rs index 455eca3..4e3ce46 100644 --- a/src/legacy_final_v4/builder.rs +++ b/src/legacy_final_v4/builder.rs @@ -39,7 +39,7 @@ fn get_random_u64() -> u64 { } } -#[derive(Clone, Copy)] +#[derive(Clone)] /// 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, @@ -77,7 +77,9 @@ impl RandomWyHashLegacyState { use super::secret::make_secret_legacy; #[cfg(feature = "fully_randomised_wyhash")] - let secret = *SECRET.get_or_init(|| make_secret_legacy(get_random_u64())); + let secret = SECRET + .get_or_init(|| make_secret_legacy(get_random_u64())) + .clone(); #[cfg(not(feature = "fully_randomised_wyhash"))] let secret = LegacySecret::new(WY0, WY1, WY2, WY3); @@ -117,7 +119,7 @@ impl BuildHasher for RandomWyHashLegacyState { #[inline] fn build_hasher(&self) -> Self::Hasher { - WyHashLegacy::new_with_secret(self.state, self.secret) + WyHashLegacy::new_with_secret(self.state, self.secret.clone()) } } diff --git a/src/legacy_final_v4/secret.rs b/src/legacy_final_v4/secret.rs index 8c1353a..3dd60e7 100644 --- a/src/legacy_final_v4/secret.rs +++ b/src/legacy_final_v4/secret.rs @@ -1,11 +1,12 @@ -use crate::constants::C_VALUES; +use crate::{constants::C_VALUES, utils::check_for_valid_secret_value}; use super::WyRandLegacy; #[cfg(feature = "debug")] use core::fmt::Debug; -#[derive(Clone, Copy, PartialEq, Eq)] +#[derive(Clone, PartialEq, Eq)] +#[repr(align(32))] /// 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]); @@ -55,7 +56,6 @@ pub(super) const fn make_secret_legacy(mut seed: u64) -> LegacySecret { let mut ok: bool = false; while !ok { - ok = true; secret[i] = 0; let mut j: usize = 0; @@ -68,20 +68,7 @@ pub(super) const fn make_secret_legacy(mut seed: u64) -> LegacySecret { 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; - } + ok = check_for_valid_secret_value(i, &secret); } i += 1; diff --git a/src/lib.rs b/src/lib.rs index 5ac5b29..a170c41 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,17 +8,17 @@ #[cfg(feature = "fully_randomised_wyhash")] extern crate std; -mod utils; #[cfg(feature = "wyhash")] -mod read; +mod constants; +mod final_v4_2; /// 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")] -mod constants; +mod read; +mod utils; pub use final_v4_2::*; diff --git a/src/utils.rs b/src/utils.rs index 4aa92d6..76d5946 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -13,3 +13,22 @@ pub(crate) const fn wymul(first: u64, second: u64) -> (u64, u64) { let total = (first as u128).wrapping_mul(second as u128); (total as u64, total.wrapping_shr(64) as u64) } + +#[cfg(feature = "wyhash")] +#[inline(always)] +pub(crate) const fn check_for_valid_secret_value(current_value: usize, secret: &[u64; 4]) -> bool { + if secret[current_value] % 2 == 0 { + return false; + } + + let mut prev_value: usize = 0; + + while prev_value < current_value { + if (secret[prev_value] ^ secret[current_value]).count_ones() != 32 { + return false; + } + prev_value += 1; + } + + true +} From 9464e428113fd24470054a3c9efd44724de79803 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Rica=20Pais=20da=20Silva?= Date: Thu, 18 Apr 2024 14:10:26 +0200 Subject: [PATCH 7/9] fix: Make legacy new_with_secret method pub, doc link fix --- src/final_v4_2/hasher.rs | 2 +- src/legacy_final_v4/hasher.rs | 8 ++------ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/final_v4_2/hasher.rs b/src/final_v4_2/hasher.rs index 89ba9fa..50f36f5 100644 --- a/src/final_v4_2/hasher.rs +++ b/src/final_v4_2/hasher.rs @@ -63,7 +63,7 @@ impl WyHash { 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`], + /// Create hasher with a seed value and a secret. Assumes the user created the secret with [`WyHash::make_secret`], /// else the hashing output will be weak/vulnerable. #[inline] pub const fn new_with_secret(mut seed: u64, secret: Secret) -> Self { diff --git a/src/legacy_final_v4/hasher.rs b/src/legacy_final_v4/hasher.rs index df34bd8..39fe4c6 100644 --- a/src/legacy_final_v4/hasher.rs +++ b/src/legacy_final_v4/hasher.rs @@ -3,10 +3,6 @@ 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; @@ -70,10 +66,10 @@ impl WyHashLegacy { 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`], + /// Create hasher with a seed value and a secret. Assumes the user created the secret with [`WyHashLegacy::make_secret`], /// else the hashing output will be weak/vulnerable. #[inline] - pub(super) const fn new_with_secret(mut seed: u64, secret: LegacySecret) -> Self { + pub const fn new_with_secret(mut seed: u64, secret: LegacySecret) -> Self { seed ^= wymix(seed ^ secret.first(), secret.second()); WyHashLegacy { From c672021c00c8a71f6b78a4eb5c531ed0fa05f42e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Rica=20Pais=20da=20Silva?= Date: Thu, 18 Apr 2024 14:57:41 +0200 Subject: [PATCH 8/9] chore: Cleanup old code and formatting fixes --- src/final_v4_2/builder.rs | 4 ++-- src/legacy_final_v4/hasher.rs | 3 --- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/final_v4_2/builder.rs b/src/final_v4_2/builder.rs index fed75c8..dfb14e6 100644 --- a/src/final_v4_2/builder.rs +++ b/src/final_v4_2/builder.rs @@ -69,10 +69,10 @@ impl RandomWyHashState { #[must_use] #[inline] pub fn new() -> Self { - #[cfg(feature = "fully_randomised_wyhash")] - use super::secret::make_secret; #[cfg(not(feature = "fully_randomised_wyhash"))] use super::constants::{WY0, WY1, WY2, WY3}; + #[cfg(feature = "fully_randomised_wyhash")] + use super::secret::make_secret; #[cfg(feature = "fully_randomised_wyhash")] let secret = SECRET.get_or_init(|| make_secret(get_random_u64())).clone(); diff --git a/src/legacy_final_v4/hasher.rs b/src/legacy_final_v4/hasher.rs index 39fe4c6..195766d 100644 --- a/src/legacy_final_v4/hasher.rs +++ b/src/legacy_final_v4/hasher.rs @@ -1,6 +1,3 @@ -// #[cfg(feature = "randomised_wyhash")] -// mod builder; - use core::hash::Hasher; #[cfg(feature = "debug")] From d5a50fb520033d1dce8ec1ebd5344b149c427414 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Rica=20Pais=20da=20Silva?= Date: Thu, 18 Apr 2024 15:11:11 +0200 Subject: [PATCH 9/9] chore: Simplify CI runs --- .github/workflows/ci.yml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f351ecd..ed514b5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,13 +35,10 @@ jobs: - name: Run cargo check run: cargo check --all --all-features --all-targets if: startsWith(matrix.rust, 'nightly') - - name: Run cargo check - if: startsWith(matrix.rust, 'nightly') - run: cargo check - name: Full feature testing run: cargo test --all-features - - name: v4 compatibility testing - run: cargo test --no-default-features --features debug,rand_core,wyhash,randomised_wyhash + - name: No default compatibility testing + run: cargo test --no-default-features --features debug,rand_core,wyhash msrv: runs-on: ubuntu-latest