use highway::{HighwayHash, HighwayHasher, Key};
pub const RIEGELI_HASH_KEY: [u64; 4] = [
0x2f696c6567656952,
0x0a7364726f636572,
0x2f696c6567656952,
0x0a7364726f636572,
];
pub fn highway_hash_64(data: &[u8]) -> u64 {
HighwayHasher::new(Key(RIEGELI_HASH_KEY)).hash64(data)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_hash_empty_is_deterministic() {
let h1 = highway_hash_64(b"");
let h2 = highway_hash_64(b"");
assert_eq!(h1, h2, "hash must be deterministic");
}
#[test]
fn test_hash_empty_is_nonzero() {
let h = highway_hash_64(b"");
assert_ne!(h, 0, "hash of empty string should be non-zero");
}
#[test]
fn test_hash_differs_for_different_inputs() {
let h_empty = highway_hash_64(b"");
let h_hello = highway_hash_64(b"hello");
assert_ne!(
h_empty, h_hello,
"different inputs must produce different hashes"
);
}
#[test]
fn test_hash_known_block_header_body() {
let mut body = [0u8; 16];
body[0..8].copy_from_slice(&0u64.to_le_bytes());
body[8..16].copy_from_slice(&24u64.to_le_bytes());
let computed_hash = highway_hash_64(&body);
let mut full_header = [0u8; 24];
full_header[0..8].copy_from_slice(&computed_hash.to_le_bytes());
full_header[8..24].copy_from_slice(&body);
let stored = u64::from_le_bytes(full_header[0..8].try_into().unwrap());
let recomputed = highway_hash_64(&full_header[8..24]);
assert_eq!(stored, recomputed, "block header hash round-trip failed");
}
#[test]
fn test_cpp_reference_hash_empty() {
assert_eq!(highway_hash_64(b""), 0x72c3b1e9c0139fe1);
}
#[test]
fn test_hash_bit_flip_detection() {
let mut body = [0u8; 16];
body[0..8].copy_from_slice(&0u64.to_le_bytes());
body[8..16].copy_from_slice(&24u64.to_le_bytes());
let original = highway_hash_64(&body);
for byte_idx in 0..16 {
for bit in 0..8 {
let mut mutated = body;
mutated[byte_idx] ^= 1 << bit;
assert_ne!(
original,
highway_hash_64(&mutated),
"flipping bit {bit} of byte {byte_idx} should change the hash"
);
}
}
}
}