Skip to content

Commit

Permalink
Use getrandom for obtaining random state if std is enabled.
Browse files Browse the repository at this point in the history
(This requires an API change.)
  • Loading branch information
tkaitchuck committed Oct 7, 2020
1 parent c5ac439 commit 980159d
Show file tree
Hide file tree
Showing 8 changed files with 178 additions and 190 deletions.
21 changes: 10 additions & 11 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "ahash"
version = "0.4.5"
version = "0.5.0"
authors = ["Tom Kaitchuck <Tom.Kaitchuck@gmail.com>"]
license = "MIT OR Apache-2.0"
description = "A non-cryptographic hash function using AES-NI for high performance"
Expand All @@ -19,18 +19,15 @@ bench = true
doc = true

[features]
default = ["compile-time-rng", "std"]
default = ["std"]

# Enabling this will enable `AHashMap` and `AHashSet`
std = []
std = ["getrandom", "lazy_static"]
compile-time-rng = ["const-random"]

# Enables specilization (reuqires nightly)
# Enables specilization (requires nightly)
specialize = []

# Disabling this feature will make the `ABuildHasher::new` use a preset seed
# and disable the `Default` impl for `AHasher`
compile-time-rng = ["const-random"]

[[bench]]
name = "ahash"
path = "tests/bench.rs"
Expand Down Expand Up @@ -60,16 +57,18 @@ debug-assertions = false
codegen-units = 1

[dependencies]
lazy_static = { version = "1.4.0", optional = true }
getrandom = { version = "0.2.0", optional = true }
const-random = { version = "0.1.6", optional = true }

[dev-dependencies]
no-panic = "0.1.10"
criterion = {version = "0.3.2"}
seahash = "3.0.5"
seahash = "4.0"
fnv = "1.0.5"
fxhash = "0.2.1"
hex = "0.3.2"
rand = "0.6.5"
hex = "0.4.2"
rand = "0.7.3"

[package.metadata.docs.rs]
rustc-args = ["-C", "target-feature=+aes"]
Expand Down
23 changes: 13 additions & 10 deletions src/aes_hash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use crate::operations::*;
#[cfg(feature = "specialize")]
use crate::HasherExt;
use core::hash::Hasher;
use crate::RandomState;

/// A `Hasher` for hashing an arbitrary stream of bytes.
///
Expand Down Expand Up @@ -56,14 +57,14 @@ impl AHasher {
}
}

#[cfg(test)]
pub(crate) fn test_with_keys(key1: u64, key2: u64) -> AHasher {
use crate::random_state::scramble_keys;
let (k1, k2, k3, k4) = scramble_keys(key1, key2);
AHasher {
enc: [k1, k2].convert(),
sum: [k3, k4].convert(),
key: add_by_64s([k1, k2], [k3, k4]).convert(),
#[inline]
pub(crate) fn from_random_state(rand_state: &RandomState) -> Self {
let key1 = [rand_state.k0, rand_state.k1].convert();
let key2 = [rand_state.k2, rand_state.k3].convert();
Self {
enc: key1,
sum: key2,
key: key1 ^ key2,
}
}

Expand Down Expand Up @@ -108,7 +109,9 @@ impl HasherExt for AHasher {
}
}

/// Provides methods to hash all of the primitive types.
/// Provides [Hasher] methods to hash all of the primitive types.
///
/// [Hasher]: core::hash::Hasher
impl Hasher for AHasher {
#[inline]
fn write_u8(&mut self, i: u8) {
Expand Down Expand Up @@ -228,7 +231,7 @@ mod tests {
use std::hash::{BuildHasher, Hasher};
#[test]
fn test_sanity() {
let mut hasher = RandomState::with_seeds(192837465, 1234567890).build_hasher();
let mut hasher = RandomState::with_seeds(1, 2, 3,4).build_hasher();
hasher.write_u64(0);
let h1 = hasher.finish();
hasher.write(&[1, 0, 0, 0, 0, 0, 0, 0]);
Expand Down
6 changes: 6 additions & 0 deletions src/convert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@ convert!(u128, [u64; 2]);
convert!(u128, [u32; 4]);
convert!(u128, [u16; 8]);
convert!(u128, [u8; 16]);
convert!([u64; 8], [u32; 16]);
convert!([u64; 8], [u16; 32]);
convert!([u64; 8], [u8; 64]);
convert!([u64; 4], [u32; 8]);
convert!([u64; 4], [u16; 16]);
convert!([u64; 4], [u8; 32]);
convert!([u64; 2], [u32; 4]);
convert!([u64; 2], [u16; 8]);
convert!([u64; 2], [u8; 16]);
Expand Down
35 changes: 19 additions & 16 deletions src/fallback_hash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ use crate::operations::folded_multiply;
#[cfg(feature = "specialize")]
use crate::HasherExt;
use core::hash::Hasher;
use crate::RandomState;
use crate::random_state::PI;

///This constant come from Kunth's prng (Empirically it works better than those from splitmix32).
const MULTIPLE: u64 = crate::random_state::MULTIPLE;
const MULTIPLE: u64 = 6364136223846793005;
const ROT: u32 = 23; //17

/// A `Hasher` for hashing an arbitrary stream of bytes.
Expand All @@ -31,22 +33,23 @@ impl AHasher {
#[inline]
#[allow(dead_code)] // Is not called if non-fallback hash is used.
pub fn new_with_keys(key1: u128, key2: u128) -> AHasher {
let pi: [u128; 2] = PI.convert();
let key1: [u64; 2] = (key1 ^ pi[0]).convert();
let key2: [u64; 2] = (key2 ^ pi[1]).convert();
AHasher {
buffer: key1 as u64,
pad: key2 as u64,
extra_keys: (key1 ^ key2).convert(),
buffer: key1[0],
pad: key1[1],
extra_keys: key2,
}
}

#[cfg(test)]
#[inline]
#[allow(dead_code)] // Is not called if non-fallback hash is used.
pub(crate) fn test_with_keys(key1: u64, key2: u64) -> AHasher {
use crate::random_state::scramble_keys;
let (k1, k2, k3, k4) = scramble_keys(key1, key2);
pub(crate) fn from_random_state(rand_state: &RandomState) -> AHasher {
AHasher {
buffer: k1,
pad: k2,
extra_keys: [k3, k4],
buffer: rand_state.k0,
pad: rand_state.k1,
extra_keys: [rand_state.k2, rand_state.k3],
}
}

Expand Down Expand Up @@ -117,7 +120,9 @@ impl HasherExt for AHasher {
}
}

/// Provides methods to hash all of the primitive types.
/// Provides [Hasher] methods to hash all of the primitive types.
///
/// [Hasher]: core::hash::Hasher
impl Hasher for AHasher {
#[inline]
fn write_u8(&mut self, i: u8) {
Expand All @@ -141,9 +146,7 @@ impl Hasher for AHasher {

#[inline]
fn write_u128(&mut self, i: u128) {
let data: [u64; 2] = i.convert();
self.update(data[0]);
self.update(data[1]);
self.large_update(i);
}

#[inline]
Expand Down
83 changes: 42 additions & 41 deletions src/hash_quality_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ fn test_no_full_collisions<T: Hasher>(gen_hash: impl Fn() -> T) {
assert_eq!(2396744, map.len());
}

fn test_keys_change_output<T: HasherExt>(constructor: impl Fn(u64, u64) -> T) {
fn test_keys_change_output<T: HasherExt>(constructor: impl Fn(u128, u128) -> T) {
let mut a = constructor(1, 1);
let mut b = constructor(1, 2);
let mut c = constructor(2, 1);
Expand All @@ -110,7 +110,7 @@ fn test_keys_change_output<T: HasherExt>(constructor: impl Fn(u64, u64) -> T) {
assert_sufficiently_different(c.finish(), d.finish(), 1);
}

fn test_input_affect_every_byte<T: HasherExt>(constructor: impl Fn(u64, u64) -> T) {
fn test_input_affect_every_byte<T: HasherExt>(constructor: impl Fn(u128, u128) -> T) {
let base = 0.get_hash(constructor(0, 0));
for shift in 0..16 {
let mut alternitives = vec![];
Expand All @@ -124,13 +124,13 @@ fn test_input_affect_every_byte<T: HasherExt>(constructor: impl Fn(u64, u64) ->
}

///Ensures that for every bit in the output there is some value for each byte in the key that flips it.
fn test_keys_affect_every_byte<H: Hash, T: HasherExt>(item: H, constructor: impl Fn(u64, u64) -> T) {
fn test_keys_affect_every_byte<H: Hash, T: HasherExt>(item: H, constructor: impl Fn(u128, u128) -> T) {
let base = item.get_hash(constructor(0, 0));
for shift in 0..8 {
for shift in 0..16 {
let mut alternitives1 = vec![];
let mut alternitives2 = vec![];
for v in 0..256 {
let input = (v as u64) << (shift * 8);
let input = (v as u128) << (shift * 8);
let hasher1 = constructor(input, 0);
let hasher2 = constructor(0, input);
let h1 = item.get_hash(hasher1);
Expand All @@ -151,16 +151,16 @@ fn assert_each_byte_differs(base: u64, alternitives: Vec<u64>) {
assert_eq!(core::u64::MAX, changed_bits, "Bits changed: {:x}", changed_bits);
}

fn test_finish_is_consistent<T: Hasher>(constructor: impl Fn(u64, u64) -> T) {
fn test_finish_is_consistent<T: Hasher>(constructor: impl Fn(u128, u128) -> T) {
let mut hasher = constructor(1, 2);
"Foo".hash(&mut hasher);
let a = hasher.finish();
let b = hasher.finish();
assert_eq!(a, b);
}

fn test_single_key_bit_flip<T: Hasher>(constructor: impl Fn(u64, u64) -> T) {
for bit in 0..64 {
fn test_single_key_bit_flip<T: Hasher>(constructor: impl Fn(u128, u128) -> T) {
for bit in 0..128 {
let mut a = constructor(0, 0);
let mut b = constructor(0, 1 << bit);
let mut c = constructor(1 << bit, 0);
Expand Down Expand Up @@ -320,57 +320,58 @@ mod fallback_tests {

#[test]
fn fallback_single_bit_flip() {
test_single_bit_flip(|| AHasher::test_with_keys(0, 0))
test_single_bit_flip(|| AHasher::new_with_keys(0, 0))
}

#[test]
fn fallback_single_key_bit_flip() {
test_single_key_bit_flip(AHasher::test_with_keys)
test_single_key_bit_flip(AHasher::new_with_keys)
}

#[test]
fn fallback_all_bytes_matter() {
test_all_bytes_matter(|| AHasher::test_with_keys(0, 0));
test_all_bytes_matter(|| AHasher::new_with_keys(0, 0));
}

#[test]
fn fallback_test_no_pair_collisions() {
test_no_pair_collisions(|| AHasher::test_with_keys(0, 0));
test_no_pair_collisions(|| AHasher::new_with_keys(0, 0));
}

#[test]
fn fallback_test_no_full_collisions() {
test_no_full_collisions(|| AHasher::test_with_keys(12345, 67890));
test_no_full_collisions(|| AHasher::new_with_keys(12345, 67890));
}

#[test]
fn fallback_keys_change_output() {
test_keys_change_output(AHasher::test_with_keys);
test_keys_change_output(AHasher::new_with_keys);
}

#[test]
fn fallback_input_affect_every_byte() {
test_input_affect_every_byte(AHasher::test_with_keys);
test_input_affect_every_byte(AHasher::new_with_keys);
}

#[test]
fn fallback_keys_affect_every_byte() {
test_keys_affect_every_byte(0, AHasher::test_with_keys);
test_keys_affect_every_byte("", AHasher::test_with_keys);
test_keys_affect_every_byte((0, 0), AHasher::test_with_keys);
//For fallback second key is not used in every hash.
test_keys_affect_every_byte(0, |a, b| AHasher::new_with_keys(a ^ b, a));
test_keys_affect_every_byte("", |a, b| AHasher::new_with_keys(a ^ b, a));
test_keys_affect_every_byte((0, 0), |a, b| AHasher::new_with_keys(a ^ b, a));
}

#[test]
fn fallback_finish_is_consistant() {
test_finish_is_consistent(AHasher::test_with_keys)
test_finish_is_consistent(AHasher::new_with_keys)
}

#[test]
fn fallback_padding_doesnot_collide() {
test_padding_doesnot_collide(|| AHasher::test_with_keys(0, 0));
test_padding_doesnot_collide(|| AHasher::test_with_keys(0, 1));
test_padding_doesnot_collide(|| AHasher::test_with_keys(1, 0));
test_padding_doesnot_collide(|| AHasher::test_with_keys(1, 1));
test_padding_doesnot_collide(|| AHasher::new_with_keys(0, 0));
test_padding_doesnot_collide(|| AHasher::new_with_keys(0, 1));
test_padding_doesnot_collide(|| AHasher::new_with_keys(1, 0));
test_padding_doesnot_collide(|| AHasher::new_with_keys(1, 1));
}
}

Expand All @@ -382,8 +383,8 @@ mod aes_tests {
use crate::hash_quality_test::*;
use std::hash::{Hash, Hasher};

const BAD_KEY: u64 = 0x5252_5252_5252_5252; //This encrypts to 0.
const BAD_KEY2: u64 = 0x6363_6363_6363_6363; //This decrypts to 0.
const BAD_KEY: u128 = 0x5252_5252_5252_5252_5252_5252_5252_5252; //This encrypts to 0.
const BAD_KEY2: u128 = 0x6363_6363_6363_6363_6363_6363_6363_6363; //This decrypts to 0.

#[test]
fn test_single_bit_in_byte() {
Expand All @@ -396,56 +397,56 @@ mod aes_tests {

#[test]
fn aes_single_bit_flip() {
test_single_bit_flip(|| AHasher::test_with_keys(BAD_KEY, BAD_KEY));
test_single_bit_flip(|| AHasher::test_with_keys(BAD_KEY2, BAD_KEY2));
test_single_bit_flip(|| AHasher::new_with_keys(BAD_KEY, BAD_KEY));
test_single_bit_flip(|| AHasher::new_with_keys(BAD_KEY2, BAD_KEY2));
}

#[test]
fn aes_single_key_bit_flip() {
test_single_key_bit_flip(|k1, k2| AHasher::test_with_keys(k1, k2))
test_single_key_bit_flip(AHasher::new_with_keys)
}

#[test]
fn aes_all_bytes_matter() {
test_all_bytes_matter(|| AHasher::test_with_keys(BAD_KEY, BAD_KEY));
test_all_bytes_matter(|| AHasher::test_with_keys(BAD_KEY2, BAD_KEY2));
test_all_bytes_matter(|| AHasher::new_with_keys(BAD_KEY, BAD_KEY));
test_all_bytes_matter(|| AHasher::new_with_keys(BAD_KEY2, BAD_KEY2));
}

#[test]
fn aes_test_no_pair_collisions() {
test_no_pair_collisions(|| AHasher::test_with_keys(BAD_KEY, BAD_KEY));
test_no_pair_collisions(|| AHasher::test_with_keys(BAD_KEY2, BAD_KEY2));
test_no_pair_collisions(|| AHasher::new_with_keys(BAD_KEY, BAD_KEY));
test_no_pair_collisions(|| AHasher::new_with_keys(BAD_KEY2, BAD_KEY2));
}

#[test]
fn ase_test_no_full_collisions() {
test_no_full_collisions(|| AHasher::test_with_keys(12345, 67890));
test_no_full_collisions(|| AHasher::new_with_keys(12345, 67890));
}

#[test]
fn aes_keys_change_output() {
test_keys_change_output(AHasher::test_with_keys);
test_keys_change_output(AHasher::new_with_keys);
}

#[test]
fn aes_input_affect_every_byte() {
test_input_affect_every_byte(AHasher::test_with_keys);
test_input_affect_every_byte(AHasher::new_with_keys);
}

#[test]
fn aes_keys_affect_every_byte() {
test_keys_affect_every_byte(0, AHasher::test_with_keys);
test_keys_affect_every_byte("", AHasher::test_with_keys);
test_keys_affect_every_byte((0, 0), AHasher::test_with_keys);
test_keys_affect_every_byte(0, AHasher::new_with_keys);
test_keys_affect_every_byte("", AHasher::new_with_keys);
test_keys_affect_every_byte((0, 0), AHasher::new_with_keys);
}
#[test]
fn aes_finish_is_consistant() {
test_finish_is_consistent(AHasher::test_with_keys)
test_finish_is_consistent(AHasher::new_with_keys)
}

#[test]
fn aes_padding_doesnot_collide() {
test_padding_doesnot_collide(|| AHasher::test_with_keys(BAD_KEY, BAD_KEY));
test_padding_doesnot_collide(|| AHasher::test_with_keys(BAD_KEY2, BAD_KEY2));
test_padding_doesnot_collide(|| AHasher::new_with_keys(BAD_KEY, BAD_KEY));
test_padding_doesnot_collide(|| AHasher::new_with_keys(BAD_KEY2, BAD_KEY2));
}
}
Loading

0 comments on commit 980159d

Please sign in to comment.