Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Enable partial and full randomised states for WyHash #7

Merged
merged 2 commits into from
Mar 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "wyrand"
version = "0.1.5"
version = "0.1.6"
edition = "2021"
authors = ["Gonçalo Rica Pais da Silva <[email protected]>"]
description = "A fast & portable non-cryptographic pseudorandom number generator and hashing algorithm"
Expand All @@ -22,6 +22,7 @@ rand_core = ["dep:rand_core"]
serde1 = ["dep:serde"]
wyhash = []
randomised_wyhash = ["wyhash", "dep:getrandom"]
fully_randomised_wyhash = ["randomised_wyhash"]
v4_2 = []

[dependencies]
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ The crate will always export `WyRand` and will do so when set as `default-featur
- **`hash`** - Enables `core::hash::Hash` implementation for `WyRand`.
- **`wyhash`** - Enables `WyHash`, a fast & portable hashing algorithm. Based on the final v4 C implementation.
- **`randomised_wyhash`** - Enables `RandomisedWyHashBuilder`, 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 `RandomisedWyHashBuilder`, but also the secret. Incurs a performance hit every time `WyHash` is initialised but it is more secure as a result. Enables `randomised_wyhash` if not already enabled.
- **`v4_2`** - Switches the PRNG/Hashing algorithms to use the final v4.2 implementation.

## Building for WASM/Web
Expand Down
18 changes: 18 additions & 0 deletions benches/rand_bench.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,24 @@ fn wyhash_benchmark(c: &mut Criterion) {
},
);
});

c.bench_function("Hash new with default secret", |b| {
b.iter(|| WyHash::new_with_default_secret(black_box(42)));
});

c.bench_function("Hash new with random secret", |b| {
b.iter(|| WyHash::new(black_box(42), black_box(256)));
});

#[cfg(feature = "randomised_wyhash")]
c.bench_function("Random Hash new", |b| {
use wyrand::RandomWyHashState;
use std::hash::BuildHasher;

b.iter(|| {
RandomWyHashState::new().build_hasher()
});
});
}

pub fn benches() {
Expand Down
57 changes: 32 additions & 25 deletions src/hasher/builder.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
use core::{hash::BuildHasher, mem::MaybeUninit};
use core::hash::BuildHasher;

#[cfg(feature = "debug")]
use core::fmt::Debug;

use getrandom::getrandom_uninit;

use crate::WyHash;

#[cfg_attr(docsrs, doc(cfg(feature = "randomised_wyhash")))]
#[derive(Clone, Copy)]
#[repr(align(8))]
/// Randomised state constructor for [`WyHash`]. This builder will source entropy in order
/// to provide random seeds for [`WyHash`]. 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(u64, u64);
pub struct RandomWyHashState {
#[cfg(feature = "fully_randomised_wyhash")]
state: [u8; 16],
#[cfg(not(feature = "fully_randomised_wyhash"))]
state: [u8; 8],
}

impl RandomWyHashState {
/// Create a new [`RandomWyHashState`] instance. Calling this method will attempt to
Expand All @@ -35,21 +39,17 @@ impl RandomWyHashState {
/// ```
#[must_use]
pub fn new() -> Self {
// Don't bother zeroing as we will initialise this with random data. If the initialisation fails
// for any reason, we will panic instead of trying to continue with a fully or partially
// uninitialised buffer. This ensures the whole process is safe without the need to use an
// unsafe block.
let mut bytes = [MaybeUninit::<u8>::uninit(); core::mem::size_of::<u64>() * 2];

let bytes = getrandom_uninit(&mut bytes)
.expect("Failed to source entropy for WyHash randomised state");
#[cfg(feature = "fully_randomised_wyhash")]
const SIZE: usize = core::mem::size_of::<u64>() * 2;
#[cfg(not(feature = "fully_randomised_wyhash"))]
const SIZE: usize = core::mem::size_of::<u64>();

let (first, second) = bytes.split_at(core::mem::size_of::<u64>());
let mut state = [0; SIZE];

let first = u64::from_ne_bytes(first.try_into().unwrap());
let second = u64::from_ne_bytes(second.try_into().unwrap());
getrandom::getrandom(&mut state)
.expect("Failed to source entropy for WyHash randomised state");

Self(first, second)
Self { state }
}
}

Expand All @@ -58,7 +58,21 @@ impl BuildHasher for RandomWyHashState {

#[inline]
fn build_hasher(&self) -> Self::Hasher {
WyHash::new(self.0, self.1)
#[cfg(feature = "fully_randomised_wyhash")]
{
let (first_seed, second_seed) = self.state.split_at(core::mem::size_of::<u64>());

let first_seed = u64::from_ne_bytes(first_seed.try_into().unwrap());
let second_seed = u64::from_ne_bytes(second_seed.try_into().unwrap());

WyHash::new(first_seed, second_seed)
}
#[cfg(not(feature = "fully_randomised_wyhash"))]
{
let seed = u64::from_ne_bytes(self.state);

WyHash::new_with_default_secret(seed)
}
}
}

Expand Down Expand Up @@ -103,13 +117,6 @@ mod tests {
let builder2 = RandomWyHashState::new();

// The two builders' internal states are different to each other
assert_ne!(&builder1.0, &builder2.0);
assert_ne!(&builder1.1, &builder2.1);

// Each builder's internal state should not be the same (hopefully).
// It is more likely that we have not initialised things correctly than
// to have the entropy source output the same bits for both fields.
assert_ne!(&builder1.0, &builder1.1);
assert_ne!(&builder2.0, &builder2.1);
assert_ne!(&builder1.state, &builder2.state);
}
}