use clock_hash::{clockhash256, ClockHasher, clockhash256_domain};
use clock_hash::tags;
#[test]
fn fuzzing_one_shot_vs_incremental_consistency() {
use proptest::prelude::*;
let config = ProptestConfig::with_cases(1000);
proptest!(config, |(input: Vec<u8>)| {
let hash1 = clockhash256(&input);
let mut hasher = ClockHasher::new();
hasher.update(&input);
let hash2 = hasher.finalize();
prop_assert_eq!(hash1, hash2, "One-shot and incremental hashing should be identical");
prop_assert_eq!(hash1.len(), 32, "Hash should always be 32 bytes");
prop_assert_eq!(hash2.len(), 32, "Hash should always be 32 bytes");
});
}
#[test]
fn fuzzing_chunked_incremental_hashing() {
use proptest::prelude::*;
let config = ProptestConfig::with_cases(1000);
proptest!(config, |(input: Vec<u8>, chunk_sizes: Vec<usize>)| {
prop_assume!(!input.is_empty());
let chunk_sizes = chunk_sizes.into_iter()
.filter(|&size| size > 0)
.collect::<Vec<_>>();
prop_assume!(!chunk_sizes.is_empty());
let mut hasher = ClockHasher::new();
let mut offset = 0;
for &chunk_size in &chunk_sizes {
if offset >= input.len() {
break;
}
let end = (offset + chunk_size).min(input.len());
hasher.update(&input[offset..end]);
offset = end;
}
if offset < input.len() {
hasher.update(&input[offset..]);
}
let chunked_hash = hasher.finalize();
let one_shot_hash = clockhash256(&input);
prop_assert_eq!(chunked_hash, one_shot_hash,
"Chunked incremental hashing should match one-shot");
prop_assert_eq!(chunked_hash.len(), 32, "Hash should always be 32 bytes");
});
}
#[test]
fn fuzzing_domain_separation_integration() {
use proptest::prelude::*;
let config = ProptestConfig::with_cases(1000);
proptest!(config, |(domain1: Vec<u8>, domain2: Vec<u8>, payload: Vec<u8>)| {
prop_assume!(domain1 != domain2);
let hash_domain1 = clockhash256_domain(&domain1, &payload);
let hash_domain2 = clockhash256_domain(&domain2, &payload);
let hash_no_domain = clockhash256(&payload);
prop_assert_ne!(hash_domain1, hash_domain2,
"Different domains should produce different hashes");
prop_assert_ne!(hash_domain1, hash_no_domain,
"Domain-separated hash should differ from plain hash");
prop_assert_ne!(hash_domain2, hash_no_domain,
"Domain-separated hash should differ from plain hash");
prop_assert_eq!(hash_domain1.len(), 32, "Domain hash should be 32 bytes");
prop_assert_eq!(hash_domain2.len(), 32, "Domain hash should be 32 bytes");
prop_assert_eq!(hash_no_domain.len(), 32, "Plain hash should be 32 bytes");
});
}
#[test]
fn fuzzing_streaming_api_edge_cases() {
use proptest::prelude::*;
let config = ProptestConfig::with_cases(1000);
proptest!(config, |(operations: Vec<(Vec<u8>, bool)>)| {
let mut current_hasher = ClockHasher::new();
let mut expected_combined = Vec::new();
let mut results = Vec::new();
for (chunk, finalize) in operations {
if finalize && !expected_combined.is_empty() {
let hash = current_hasher.finalize();
let expected_hash = clockhash256(&expected_combined);
prop_assert_eq!(hash, expected_hash,
"Finalized hash should match combined data");
results.push(hash);
current_hasher = ClockHasher::new();
expected_combined.clear();
}
current_hasher.update(&chunk);
expected_combined.extend_from_slice(&chunk);
}
if !expected_combined.is_empty() {
let hash = current_hasher.finalize();
let expected_hash = clockhash256(&expected_combined);
prop_assert_eq!(hash, expected_hash,
"Final hash should match remaining combined data");
results.push(hash);
}
for hash in results {
prop_assert_eq!(hash.len(), 32, "All hashes should be 32 bytes");
}
});
}
#[test]
fn fuzzing_hash_properties_by_size() {
use proptest::prelude::*;
let config = ProptestConfig::with_cases(1000);
proptest!(config, |(input: Vec<u8>)| {
let hash = clockhash256(&input);
prop_assert_eq!(hash.len(), 32, "Hash should always be 32 bytes");
prop_assert!(!hash.iter().all(|&b| b == 0),
"Hash should not be all zeros");
let hash2 = clockhash256(&input);
prop_assert_eq!(hash, hash2, "Hashing should be deterministic");
let mut hasher = ClockHasher::new();
hasher.update(&input);
let hash3 = hasher.finalize();
prop_assert_eq!(hash, hash3, "Incremental hashing should be deterministic");
if !input.is_empty() {
let mut modified_input = input.clone();
modified_input[0] ^= 1;
let modified_hash = clockhash256(&modified_input);
if hash == modified_hash {
let mut double_modified = modified_input.clone();
if double_modified.len() > 1 {
double_modified[1] ^= 1;
let double_modified_hash = clockhash256(&double_modified);
prop_assert_ne!(hash, double_modified_hash,
"Hash should change with multiple bit modifications");
}
}
let mut diff_bits = 0;
for i in 0..32 {
diff_bits += (hash[i] ^ modified_hash[i]).count_ones() as usize;
}
if hash != modified_hash {
prop_assert!(diff_bits >= 1,
"Avalanche effect: expected at least 1 differing bit, got {}", diff_bits);
}
}
});
}
#[test]
fn fuzzing_multiple_domain_operations() {
use proptest::prelude::*;
let config = ProptestConfig::with_cases(300);
proptest!(config, |(data: Vec<u8>, domains: Vec<Vec<u8>>)| {
let unique_domains: std::collections::HashSet<_> = domains.iter().collect();
prop_assume!(unique_domains.len() >= 2);
let mut domain_hashes = Vec::new();
for domain in &domains {
let hash = clockhash256_domain(domain, &data);
domain_hashes.push(hash);
}
for i in 0..domain_hashes.len() {
for j in (i + 1)..domain_hashes.len() {
if domains[i] != domains[j] {
prop_assert_ne!(domain_hashes[i], domain_hashes[j],
"Different domains should produce different hashes");
}
}
}
let plain_hash = clockhash256(&data);
for domain_hash in &domain_hashes {
prop_assert_ne!(*domain_hash, plain_hash,
"Domain hash should differ from plain hash");
}
for hash in &domain_hashes {
prop_assert_eq!(hash.len(), 32, "Domain hash should be 32 bytes");
}
prop_assert_eq!(plain_hash.len(), 32, "Plain hash should be 32 bytes");
});
}
#[test]
fn fuzzing_structured_input_hashing() {
use proptest::prelude::*;
let config = ProptestConfig::with_cases(500);
proptest!(config, |(header: Vec<u8>, body: Vec<u8>, footer: Vec<u8>)| {
let mut combined = Vec::with_capacity(header.len() + body.len() + footer.len());
combined.extend_from_slice(&header);
combined.extend_from_slice(&body);
combined.extend_from_slice(&footer);
let combined_hash = clockhash256(&combined);
let mut hasher = ClockHasher::new();
hasher.update(&header);
hasher.update(&body);
hasher.update(&footer);
let incremental_hash = hasher.finalize();
prop_assert_eq!(combined_hash, incremental_hash,
"Combined and incremental structured hashing should match");
let domain_hash = clockhash256_domain(b"structured", &combined);
prop_assert_ne!(domain_hash, combined_hash,
"Domain hash should differ from plain hash");
prop_assert_eq!(combined_hash.len(), 32, "Combined hash should be 32 bytes");
prop_assert_eq!(incremental_hash.len(), 32, "Incremental hash should be 32 bytes");
prop_assert_eq!(domain_hash.len(), 32, "Domain hash should be 32 bytes");
});
}
#[test]
fn fuzzing_collision_resistance_integration() {
use proptest::prelude::*;
use std::collections::HashSet;
let config = ProptestConfig::with_cases(300);
proptest!(config, |(mut inputs: Vec<Vec<u8>>)| {
let mut unique_inputs = HashSet::new();
inputs.retain(|input| !input.is_empty() && unique_inputs.insert(input.clone()));
prop_assume!(inputs.len() >= 2);
let mut hashes = Vec::new();
let mut hash_set = HashSet::new();
for input in &inputs {
let hash = clockhash256(input);
hashes.push(hash.clone());
prop_assert!(hash_set.insert(hash),
"Hash collision detected - different inputs produced same hash");
}
for input in &inputs {
let hash1 = clockhash256(input);
let hash2 = clockhash256(input);
prop_assert_eq!(hash1, hash2, "Hashing should be deterministic");
}
for hash in &hashes {
prop_assert_eq!(hash.len(), 32, "Hash should be 32 bytes");
prop_assert!(!hash.iter().all(|&b| b == 0), "Hash should not be all zeros");
}
});
}