Skip to content

Commit

Permalink
Merge pull request #7 from Bluefinger/full_partial_randomisation
Browse files Browse the repository at this point in the history
feat: Enable partial and full randomised states for WyHash
  • Loading branch information
Bluefinger authored Mar 26, 2024
2 parents 6d63aa9 + 58a920b commit acb1d74
Show file tree
Hide file tree
Showing 4 changed files with 53 additions and 26 deletions.
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);
}
}

0 comments on commit acb1d74

Please sign in to comment.