Skip to content

Commit

Permalink
Revert "refactor: Redo module and feature structure for v4/v4.2 split"
Browse files Browse the repository at this point in the history
This reverts commit d46a84c.
  • Loading branch information
Bluefinger committed Apr 17, 2024
1 parent d46a84c commit a15429d
Show file tree
Hide file tree
Showing 19 changed files with 177 additions and 1,038 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 testing
- name: Full feature + v4.2 testing
run: cargo test --all-features
- name: Default testing
- name: v4 compatibility testing
run: cargo test --no-default-features --features debug,rand_core,wyhash,randomised_wyhash

msrv:
Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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. 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 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 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)]`.

Expand All @@ -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.2 C implementation.
- **`wyhash`** - Enables `WyHash`, a fast & portable hashing algorithm. Based on the final v4 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.
- **`legacy_v4`** - Exposes the legacy PRNG/Hashing algorithms.
- **`v4_2`** - Switches the PRNG/Hashing algorithms to use the final v4.2 implementation.

## Building for WASM/Web

Expand Down
31 changes: 25 additions & 6 deletions src/constants.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,25 @@
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,
];
#[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::*;
24 changes: 0 additions & 24 deletions src/final_v4_2.rs

This file was deleted.

6 changes: 0 additions & 6 deletions src/final_v4_2/constants.rs

This file was deleted.

72 changes: 44 additions & 28 deletions src/final_v4_2/hasher.rs → src/hasher.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,31 @@
#[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::{
read::{read_4_bytes, read_8_bytes, read_upto_3_bytes},
constants::{WY0, WY1, WY2, WY3},
utils::{wymix, wymul},
};

use super::{
constants::{WY0, WY1, WY2, WY3},
secret::{make_secret, Secret},
};
use self::read::{is_over_48_bytes, read_4_bytes, read_8_bytes, read_upto_3_bytes};

pub use self::secret::make_secret;

/// The WyHash hasher, a fast & portable hashing algorithm. This implementation is
/// based on the final v4.2 C reference implementation.
/// based on the final v4/v4.2 C reference implementations (depending on whether the
/// `v4_2` feature flag is enabled or not).
///
/// ```
/// use wyrand::WyHash;
Expand All @@ -35,23 +46,17 @@ use super::{
/// 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: Secret,
secret: [u64; 4],
}

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))
Expand All @@ -60,14 +65,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, Secret::new(WY0, WY1, WY2, WY3))
Self::new_with_secret(seed, [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: Secret) -> Self {
seed ^= wymix(seed ^ secret.first(), secret.second());
pub const fn new_with_secret(mut seed: u64, secret: [u64; 4]) -> Self {
seed ^= wymix(seed ^ secret[0], secret[1]);

WyHash {
seed,
Expand Down Expand Up @@ -95,21 +100,21 @@ impl WyHash {
let mut start = 0;
let mut seed = self.seed;

if length >= 48 {
if is_over_48_bytes(length) {
let mut seed1 = seed;
let mut seed2 = seed;

while index >= 48 {
while is_over_48_bytes(index) {
seed = wymix(
read_8_bytes(&bytes[start..]) ^ self.secret.second(),
read_8_bytes(&bytes[start..]) ^ self.secret[1],
read_8_bytes(&bytes[start + 8..]) ^ seed,
);
seed1 = wymix(
read_8_bytes(&bytes[start + 16..]) ^ self.secret.third(),
read_8_bytes(&bytes[start + 16..]) ^ self.secret[2],
read_8_bytes(&bytes[start + 24..]) ^ seed1,
);
seed2 = wymix(
read_8_bytes(&bytes[start + 32..]) ^ self.secret.fourth(),
read_8_bytes(&bytes[start + 32..]) ^ self.secret[3],
read_8_bytes(&bytes[start + 40..]) ^ seed2,
);
index -= 48;
Expand All @@ -121,7 +126,7 @@ impl WyHash {

while index > 16 {
seed = wymix(
read_8_bytes(&bytes[start..]) ^ self.secret.second(),
read_8_bytes(&bytes[start..]) ^ self.secret[1],
read_8_bytes(&bytes[start + 8..]) ^ seed,
);
index -= 16;
Expand Down Expand Up @@ -193,11 +198,8 @@ impl Hasher for WyHash {

#[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(),
)
let (lo, hi) = wymul(self.lo ^ self.secret[1], self.hi ^ self.seed);
wymix(lo ^ self.secret[0] ^ self.size, hi ^ self.secret[1])
}
}

Expand Down Expand Up @@ -240,6 +242,20 @@ 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, ""),
Expand Down
32 changes: 17 additions & 15 deletions src/final_v4_2/builder.rs → src/hasher/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ use core::fmt::Debug;
#[cfg(feature = "fully_randomised_wyhash")]
use std::sync::OnceLock;

use super::{secret::Secret, WyHash};
use crate::WyHash;

#[cfg(feature = "fully_randomised_wyhash")]
static SECRET: OnceLock<Secret> = OnceLock::new();
static SECRET: OnceLock<[u64; 4]> = OnceLock::new();

#[inline]
fn get_random_u64() -> u64 {
Expand Down Expand Up @@ -37,20 +37,21 @@ 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,
/// 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 RandomWyHashState {
state: u64,
secret: Secret,
secret: [u64; 4],
}

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
///
Expand All @@ -70,21 +71,22 @@ impl RandomWyHashState {
#[inline]
pub fn new() -> Self {
#[cfg(feature = "fully_randomised_wyhash")]
use super::secret::make_secret;
use crate::hasher::secret::make_secret;

#[cfg(not(feature = "fully_randomised_wyhash"))]
use super::constants::{WY0, WY1, WY2, WY3};
use crate::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 = Secret::new(WY0, WY1, WY2, WY3);
let secret = [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 [`WyHash::make_secret`].
/// secret via [`super::secret::make_secret`].
///
/// # Panics
///
Expand All @@ -93,16 +95,16 @@ impl RandomWyHashState {
/// # Examples
///
/// ```
/// use wyrand::{RandomWyHashState, make_secret};
/// use core::hash::BuildHasher;
/// use wyrand::{RandomWyHashState, WyHash};
///
/// let s = RandomWyHashState::new_with_secret(WyHash::make_secret(42));
/// let s = RandomWyHashState::new_with_secret(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: Secret) -> Self {
pub fn new_with_secret(secret: [u64; 4]) -> Self {
Self {
state: get_random_u64(),
secret,
Expand All @@ -129,7 +131,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("RandomisedWyHashState")
f.debug_struct("RandomisedWyHashBuilder")
.finish_non_exhaustive()
}
}
Expand All @@ -149,7 +151,7 @@ mod tests {

assert_eq!(
format!("{builder:?}"),
"RandomisedWyHashState { .. }",
"RandomisedWyHashBuilder { .. }",
"Debug should not be leaking internal state"
);
}
Expand All @@ -169,9 +171,9 @@ mod tests {
// same as the default secret.
#[cfg(feature = "fully_randomised_wyhash")]
{
use super::super::constants::{WY0, WY1, WY2, WY3};
use crate::constants::{WY0, WY1, WY2, WY3};

let default_secret = Secret::new(WY0, WY1, WY2, WY3);
let default_secret = [WY0, WY1, WY2, WY3];

assert_ne!(&builder1.secret, &default_secret);
assert_ne!(&builder2.secret, &default_secret);
Expand Down
File renamed without changes.
Loading

0 comments on commit a15429d

Please sign in to comment.