Skip to content

Commit

Permalink
Strengthen short finish (#229)
Browse files Browse the repository at this point in the history
* Add more tests
* Fix two problems with the short_finish:
   * If on ARM where the xor happens first the same value cannot be used for both parameters.
   * XOR the two halfs of the output to avoid the possibility that two bit diffs can cancel
* Fix test error
* Add aarch64 nightly to github actions

Signed-off-by: Tom Kaitchuck <Tom.Kaitchuck@gmail.com>
  • Loading branch information
tkaitchuck authored Mar 29, 2024
1 parent cd5000a commit d1532cc
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 15 deletions.
20 changes: 20 additions & 0 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,26 @@ jobs:
toolchain: 1.72.0
targets: aarch64-apple-darwin
- run: cargo +1.72.0 check --target aarch64-apple-darwin
nightly:
name: Aarch64 nightly
runs-on: macos-14
env:
RUSTFLAGS: -C target-cpu=native
steps:
- uses: actions/checkout@v4
- name: Install latest nightly
uses: dtolnay/rust-toolchain@master
with:
toolchain: nightly
components: clippy
- name: check nightly
run: cargo check -Z msrv-policy
- name: test nightly
run: cargo test
- name: check serde
run: cargo check --features serde
- name: test serde
run: cargo test --features serde
# aarch64-debug:
# name: Debug Apple
# runs-on: macos-14
Expand Down
6 changes: 3 additions & 3 deletions src/aes_hash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,9 @@ impl AHasher {
#[inline]
#[cfg(feature = "specialize")]
fn short_finish(&self) -> u64 {
let combined = aesenc(self.sum, self.enc);
let result: [u64; 2] = aesdec(combined, combined).convert();
result[0]
let combined = aesdec(self.enc, self.sum);
let result: [u64; 2] = aesdec(combined, self.key).convert();
result[0] ^ result[1]
}

#[inline]
Expand Down
104 changes: 95 additions & 9 deletions src/hash_quality_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ fn test_keys_change_output<T: Hasher>(constructor: impl Fn(u128, u128) -> T) {
}

fn test_input_affect_every_byte<T: Hasher>(constructor: impl Fn(u128, u128) -> T) {
let base = hash_with(&0, constructor(0, 0));
let base = hash_with(&0_u128, constructor(0, 0));
for shift in 0..16 {
let mut alternatives = vec![];
for v in 0..256 {
Expand Down Expand Up @@ -257,7 +257,6 @@ fn test_single_bit_flip<T: Hasher>(hasher: impl Fn() -> T) {
let compare_value = hash(&0u128, &hasher);
for pos in 0..size {
let test_value = hash(&(1u128 << pos), &hasher);
dbg!(compare_value, test_value);
assert_sufficiently_different(compare_value, test_value, 2);
}
}
Expand Down Expand Up @@ -330,10 +329,14 @@ fn test_padding_doesnot_collide<T: Hasher>(hasher: impl Fn() -> T) {
fn test_length_extension<T: Hasher>(hasher: impl Fn(u128, u128) -> T) {
for key in 0..256 {
let h1 = hasher(key, key);
let v1 = hash_with(&[0_u8, 0, 0, 0, 0, 0, 0, 0], h1);
let h2 = hasher(key, key);
let v2 = hash_with(&[1_u8, 0, 0, 0, 0, 0, 0, 0, 0], h2);
assert_ne!(v1, v2);
let v1 = vec![0_u8; 1];
let o1 = hash_with(&v1, h1);
for len in 2..256 {
let h2 = hasher(key, key);
let v2 = vec![0; len];
let o2 = hash_with(&v2, h2);
assert_ne!(o1, o2);
}
}
}

Expand Down Expand Up @@ -371,6 +374,8 @@ mod fallback_tests {

#[test]
fn fallback_single_bit_flip() {
#[cfg(feature = "specialize")]
test_single_bit_flip(|| AHasherFixed(AHasher::new_with_keys(0, 0)));
test_single_bit_flip(|| AHasher::new_with_keys(0, 0))
}

Expand All @@ -381,16 +386,22 @@ mod fallback_tests {

#[test]
fn fallback_all_bytes_matter() {
#[cfg(feature = "specialize")]
test_all_bytes_matter(|| AHasherStr(AHasher::new_with_keys(0, 0)));
test_all_bytes_matter(|| AHasher::new_with_keys(0, 0));
}

#[test]
fn fallback_test_no_pair_collisions() {
#[cfg(feature = "specialize")]
test_no_pair_collisions(|| AHasherFixed(AHasher::new_with_keys(0, 0)));
test_no_pair_collisions(|| AHasher::new_with_keys(0, 0));
}

#[test]
fn fallback_test_no_full_collisions() {
#[cfg(feature = "specialize")]
test_no_full_collisions(|| AHasherStr(AHasher::new_with_keys(0, 0)));
test_no_full_collisions(|| AHasher::new_with_keys(0, 0));
}

Expand All @@ -401,6 +412,8 @@ mod fallback_tests {

#[test]
fn fallback_input_affect_every_byte() {
#[cfg(feature = "specialize")]
test_input_affect_every_byte(|a, b| AHasherFixed(AHasher::new_with_keys(a, b)));
test_input_affect_every_byte(AHasher::new_with_keys);
}

Expand All @@ -415,6 +428,8 @@ mod fallback_tests {

#[test]
fn fallback_finish_is_consistant() {
#[cfg(feature = "specialize")]
test_finish_is_consistent(|a, b| AHasherStr(AHasher::test_with_keys(a, b)));
test_finish_is_consistent(AHasher::test_with_keys)
}

Expand All @@ -424,17 +439,31 @@ mod fallback_tests {
test_padding_doesnot_collide(|| AHasher::new_with_keys(0, 2));
test_padding_doesnot_collide(|| AHasher::new_with_keys(2, 0));
test_padding_doesnot_collide(|| AHasher::new_with_keys(2, 2));
#[cfg(feature = "specialize")]
{
test_padding_doesnot_collide(|| AHasherStr(AHasher::new_with_keys(0, 0)));
test_padding_doesnot_collide(|| AHasherStr(AHasher::new_with_keys(0, 2)));
test_padding_doesnot_collide(|| AHasherStr(AHasher::new_with_keys(2, 0)));
test_padding_doesnot_collide(|| AHasherStr(AHasher::new_with_keys(2, 2)));
}
}

#[test]
fn fallback_length_extension() {
#[cfg(feature = "specialize")]
test_length_extension(|a, b| AHasherStr(AHasher::new_with_keys(a, b)));
test_length_extension(|a, b| AHasher::new_with_keys(a, b));
}

#[test]
fn test_no_sparse_collisions() {
test_sparse(|| AHasher::new_with_keys(0, 0));
test_sparse(|| AHasher::new_with_keys(1, 2));
#[cfg(feature = "specialize")]
{
test_sparse(|| AHasherStr(AHasher::new_with_keys(0, 0)));
test_sparse(|| AHasherStr(AHasher::new_with_keys(1, 2)));
}
}
}

Expand All @@ -449,7 +478,7 @@ mod aes_tests {
use crate::aes_hash::*;
use crate::hash_quality_test::*;
use std::hash::{Hash, Hasher};

//This encrypts to 0.
const BAD_KEY2: u128 = 0x6363_6363_6363_6363_6363_6363_6363_6363;
//This decrypts to 0.
Expand All @@ -466,40 +495,77 @@ mod aes_tests {

#[test]
fn aes_single_bit_flip() {
test_single_bit_flip(|| AHasher::test_with_keys(0, 0));
test_single_bit_flip(|| AHasher::test_with_keys(BAD_KEY, BAD_KEY));
test_single_bit_flip(|| AHasher::test_with_keys(BAD_KEY2, BAD_KEY2));
#[cfg(feature = "specialize")]
{
test_single_bit_flip(|| AHasherFixed(AHasher::test_with_keys(0, 0)));
test_single_bit_flip(|| AHasherFixed(AHasher::test_with_keys(BAD_KEY, BAD_KEY)));
test_single_bit_flip(|| AHasherFixed(AHasher::test_with_keys(BAD_KEY2, BAD_KEY2)));
}
}

#[test]
fn aes_single_key_bit_flip() {
test_single_key_bit_flip(AHasher::test_with_keys)
test_single_key_bit_flip(AHasher::test_with_keys);
#[cfg(feature = "specialize")]
{
test_single_key_bit_flip(|a, b| AHasherStr(AHasher::test_with_keys(a, b)));
}
}

#[test]
fn aes_all_bytes_matter() {
test_all_bytes_matter(|| AHasher::test_with_keys(0, 0));
test_all_bytes_matter(|| AHasher::test_with_keys(BAD_KEY, BAD_KEY));
test_all_bytes_matter(|| AHasher::test_with_keys(BAD_KEY2, BAD_KEY2));
#[cfg(feature = "specialize")]
{
test_all_bytes_matter(|| AHasherStr(AHasher::test_with_keys(0, 0)));
test_all_bytes_matter(|| AHasherStr(AHasher::test_with_keys(BAD_KEY, BAD_KEY)));
test_all_bytes_matter(|| AHasherStr(AHasher::test_with_keys(BAD_KEY2, BAD_KEY2)));
}
}

#[test]
fn aes_test_no_pair_collisions() {
test_no_pair_collisions(|| AHasher::test_with_keys(0, 0));
test_no_pair_collisions(|| AHasher::test_with_keys(BAD_KEY, BAD_KEY));
test_no_pair_collisions(|| AHasher::test_with_keys(BAD_KEY2, BAD_KEY2));
#[cfg(feature = "specialize")]
{
test_no_pair_collisions(|| AHasherFixed(AHasher::test_with_keys(0, 0)));
test_no_pair_collisions(|| AHasherFixed(AHasher::test_with_keys(BAD_KEY, BAD_KEY)));
test_no_pair_collisions(|| AHasherFixed(AHasher::test_with_keys(BAD_KEY2, BAD_KEY2)));
}
}

#[test]
fn ase_test_no_full_collisions() {
test_no_full_collisions(|| AHasher::test_with_keys(12345, 67890));
#[cfg(feature = "specialize")]
{
test_no_full_collisions(|| AHasherStr(AHasher::test_with_keys(12345, 67890)));
}
}

#[test]
fn aes_keys_change_output() {
test_keys_change_output(AHasher::test_with_keys);
#[cfg(feature = "specialize")]
{
test_keys_change_output(|a, b| AHasherStr(AHasher::test_with_keys(a, b)));
}
}

#[test]
fn aes_input_affect_every_byte() {
test_input_affect_every_byte(AHasher::test_with_keys);
#[cfg(feature = "specialize")]
{
test_input_affect_every_byte(|a, b| AHasherFixed(AHasher::test_with_keys(a, b)));
}
}

#[test]
Expand All @@ -512,23 +578,43 @@ mod aes_tests {

#[test]
fn aes_finish_is_consistant() {
test_finish_is_consistent(AHasher::test_with_keys)
test_finish_is_consistent(AHasher::test_with_keys);
#[cfg(feature = "specialize")]
{
test_finish_is_consistent(|a, b| AHasherStr(AHasher::test_with_keys(a, b)));
}
}

#[test]
fn aes_padding_doesnot_collide() {
test_padding_doesnot_collide(|| AHasher::test_with_keys(0, 0));
test_padding_doesnot_collide(|| AHasher::test_with_keys(BAD_KEY, BAD_KEY));
test_padding_doesnot_collide(|| AHasher::test_with_keys(BAD_KEY2, BAD_KEY2));
#[cfg(feature = "specialize")]
{
test_padding_doesnot_collide(|| AHasherStr(AHasher::test_with_keys(0, 0)));
test_padding_doesnot_collide(|| AHasherStr(AHasher::test_with_keys(BAD_KEY, BAD_KEY)));
test_padding_doesnot_collide(|| AHasherStr(AHasher::test_with_keys(BAD_KEY2, BAD_KEY2)));
}
}

#[test]
fn aes_length_extension() {
test_length_extension(|a, b| AHasher::test_with_keys(a, b));
#[cfg(feature = "specialize")]
{
test_length_extension(|a, b| AHasherStr(AHasher::test_with_keys(a, b)));
}
}

#[test]
fn aes_no_sparse_collisions() {
test_sparse(|| AHasher::test_with_keys(0, 0));
test_sparse(|| AHasher::test_with_keys(1, 2));
#[cfg(feature = "specialize")]
{
test_sparse(|| AHasherStr(AHasher::test_with_keys(0, 0)));
test_sparse(|| AHasherStr(AHasher::test_with_keys(1, 2)));
}
}
}
5 changes: 2 additions & 3 deletions tests/map_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -280,9 +280,8 @@ fn ahash_vec(b: &Vec<String>) -> u64 {
let mut total: u64 = 0;
let state = RandomState::<String>::with_seeds(0, 0, 0, 0);
for item in b {
let mut hasher = state.build_hasher();
item.hash(&mut hasher);
total = total.wrapping_add(hasher.finish());
let value = state.hash_one(item);
total = total.wrapping_add(value);
}
total
}
Expand Down

0 comments on commit d1532cc

Please sign in to comment.