#![allow(clippy::disallowed_types)]
use std::collections::HashSet;
use lib_q_k12::Kt128;
use lib_q_k12::digest::{
ExtendableOutput,
Reset,
Update,
};
#[test]
fn test_determinism() {
let test_data = b"determinism test data";
let customization = b"test_custom";
let mut results = Vec::new();
for _ in 0..10 {
let mut hasher = Kt128::new(customization);
hasher.update(test_data);
let result = hasher.finalize_boxed(64);
results.push(result);
}
for i in 1..results.len() {
assert_eq!(
results[0], results[i],
"Determinism failed: result {} differs from result 0",
i
);
}
}
#[test]
fn test_collision_resistance() {
let mut results = HashSet::new();
let output_size = 32;
let test_inputs = [
b"".as_slice(),
b"a".as_slice(),
b"ab".as_slice(),
b"abc".as_slice(),
b"abcd".as_slice(),
b"The quick brown fox jumps over the lazy dog".as_slice(),
b"The quick brown fox jumps over the lazy dog.".as_slice(),
&[0x00u8; 100],
&[0x01u8; 100],
&[0xFFu8; 100],
];
for input in &test_inputs {
let mut hasher = Kt128::default();
hasher.update(input);
let result = hasher.finalize_boxed(output_size);
assert!(
results.insert(result.clone()),
"Collision detected for input: {:?}",
input
);
}
}
#[test]
fn test_customization_separation() {
let test_data = b"same input data";
let customizations = [
b"".as_slice(),
b"custom1".as_slice(),
b"custom2".as_slice(),
b"a_very_long_customization_string_that_exceeds_normal_length".as_slice(),
&[0x00u8; 50],
&[0x01u8; 50],
];
let mut results = HashSet::new();
for custom in &customizations {
let mut hasher = Kt128::new(custom);
hasher.update(test_data);
let result = hasher.finalize_boxed(32);
assert!(
results.insert(result.clone()),
"Customization collision detected for: {:?}",
custom
);
}
}
#[test]
fn test_avalanche_effect() {
let base_input = vec![0x42u8; 100];
let mut base_hasher = Kt128::default();
base_hasher.update(&base_input);
let base_result = base_hasher.finalize_boxed(64);
for byte_pos in [0, 50, 99] {
for bit_pos in 0..8 {
let mut modified_input = base_input.clone();
modified_input[byte_pos] ^= 1 << bit_pos;
let mut hasher = Kt128::default();
hasher.update(&modified_input);
let result = hasher.finalize_boxed(64);
let mut diff_bits = 0;
for i in 0..64 {
diff_bits += (base_result[i] ^ result[i]).count_ones();
}
assert!(
(200..=312).contains(&diff_bits),
"Insufficient avalanche effect: only {} bits differ for bit flip at byte {} bit {}",
diff_bits,
byte_pos,
bit_pos
);
}
}
}
#[test]
fn test_output_distribution() {
let mut bit_counts = [0u32; 8]; let num_samples = 256;
for i in 0..num_samples {
let input = vec![i as u8; 10];
let mut hasher = Kt128::default();
hasher.update(&input);
let result = hasher.finalize_boxed(1);
for (bit_pos, count) in bit_counts.iter_mut().enumerate() {
if (result[0] >> bit_pos) & 1 == 1 {
*count += 1;
}
}
}
let expected = num_samples / 2;
let tolerance = expected / 4;
for (bit_pos, &count) in bit_counts.iter().enumerate() {
assert!(
(expected - tolerance..=expected + tolerance).contains(&count),
"Bit {} distribution skewed: {} out of {} (expected ~{})",
bit_pos,
count,
num_samples,
expected
);
}
}
#[test]
fn test_xof_consistency() {
let test_data = b"XOF consistency test";
let customization = b"xof_test";
let mut hasher1 = Kt128::new(customization);
hasher1.update(test_data);
let short_output = hasher1.finalize_boxed(32);
let mut hasher2 = Kt128::new(customization);
hasher2.update(test_data);
let long_output = hasher2.finalize_boxed(64);
assert_eq!(
short_output[..],
long_output[..32],
"XOF consistency failed: short output doesn't match prefix of long output"
);
let mut hasher3 = Kt128::new(customization);
hasher3.update(test_data);
let very_long_output = hasher3.finalize_boxed(128);
assert_eq!(
long_output[..],
very_long_output[..64],
"XOF consistency failed: medium output doesn't match prefix of long output"
);
}
#[test]
fn test_reset_security() {
let data1 = b"first data";
let data2 = b"second data";
let mut hasher = Kt128::default();
hasher.update(data1);
hasher.reset();
hasher.update(data2);
let result1 = hasher.finalize_boxed(32);
let mut hasher2 = Kt128::default();
hasher2.update(data2);
let result2 = hasher2.finalize_boxed(32);
assert_eq!(result1, result2, "Reset failed to clear state properly");
}
#[test]
fn test_input_size_edge_cases() {
let sizes = [0, 1, 127, 128, 129, 8191, 8192, 8193, 16383, 16384, 16385];
let mut results = HashSet::new();
for &size in &sizes {
let input: Vec<u8> = (0..size).map(|i| (i % 256) as u8).collect();
let mut hasher = Kt128::default();
hasher.update(&input);
let result = hasher.finalize_boxed(32);
assert!(
results.insert((size, result.clone())),
"Duplicate result for input size {}",
size
);
}
}
#[test]
fn test_large_customization() {
let test_data = b"test data";
let large_custom = vec![0x55u8; 10000];
let mut hasher = Kt128::new(&large_custom);
hasher.update(test_data);
let result = hasher.finalize_boxed(32);
assert_ne!(
result[..],
vec![0u8; 32][..],
"Large customization produced all-zero output"
);
let mut hasher2 = Kt128::default();
hasher2.update(test_data);
let result2 = hasher2.finalize_boxed(32);
assert_ne!(result, result2, "Large customization had no effect");
}
#[test]
fn test_incremental_updates() {
let data = vec![0x77u8; 1000];
let customization = b"incremental_test";
let mut hasher1 = Kt128::new(customization);
hasher1.update(&data);
let result1 = hasher1.finalize_boxed(32);
let mut hasher2 = Kt128::new(customization);
for chunk in data.chunks(100) {
hasher2.update(chunk);
}
let result2 = hasher2.finalize_boxed(32);
assert_eq!(
result1, result2,
"Incremental updates produced different result"
);
}
#[test]
fn test_zero_length_output() {
let test_data = b"test for zero output";
let mut hasher = Kt128::default();
hasher.update(test_data);
let result = hasher.finalize_boxed(0);
assert_eq!(result.len(), 0, "Zero-length output should be empty");
}
#[test]
fn test_large_output() {
let test_data = b"test for large output";
let output_size = 10000;
let mut hasher = Kt128::default();
hasher.update(test_data);
let result = hasher.finalize_boxed(output_size);
assert_eq!(result.len(), output_size, "Large output size mismatch");
let all_zeros = result.iter().all(|&b| b == 0);
let all_ones = result.iter().all(|&b| b == 0xFF);
assert!(!all_zeros, "Large output is all zeros");
assert!(!all_ones, "Large output is all ones");
let unique_bytes: HashSet<_> = result.iter().collect();
assert!(unique_bytes.len() > 10, "Large output lacks diversity");
}
#[test]
fn test_cloning() {
let test_data = b"cloning test data";
let mut hasher1 = Kt128::default();
hasher1.update(test_data);
let hasher2 = hasher1.clone();
let result1 = hasher1.finalize_boxed(32);
let result2 = hasher2.finalize_boxed(32);
assert_eq!(result1, result2, "Cloned hasher produced different result");
}
#[test]
fn test_chunk_independence() {
let chunk_size = 8192;
let data1 = vec![0x11u8; chunk_size * 2];
let data2 = vec![0x22u8; chunk_size * 2];
let mut hasher1 = Kt128::default();
hasher1.update(&data1[..chunk_size]);
hasher1.update(&data1[chunk_size..]);
let result1 = hasher1.finalize_boxed(32);
let mut hasher2 = Kt128::default();
hasher2.update(&data2[..chunk_size]);
hasher2.update(&data2[chunk_size..]);
let result2 = hasher2.finalize_boxed(32);
assert_ne!(
result1, result2,
"Different chunk patterns produced same result"
);
}