clock-hash 1.0.0

ClockHash-256: Consensus hash function for ClockinChain
Documentation
//! Fuzzing integration tests for ClockHash-256
//!
//! This module provides integration tests that incorporate fuzzing techniques
//! to test complex scenarios and edge cases in the complete hash function pipeline.

use clock_hash::{clockhash256, ClockHasher, clockhash256_domain};
use clock_hash::tags;

/// Fuzzing-based integration test for one-shot vs incremental hashing consistency
#[test]
fn fuzzing_one_shot_vs_incremental_consistency() {
    use proptest::prelude::*;

    let config = ProptestConfig::with_cases(1000);

    proptest!(config, |(input: Vec<u8>)| {
        // Test one-shot hashing
        let hash1 = clockhash256(&input);

        // Test incremental hashing
        let mut hasher = ClockHasher::new();
        hasher.update(&input);
        let hash2 = hasher.finalize();

        // One-shot and incremental should produce identical results
        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");
    });
}

/// Fuzzing-based integration test for chunked incremental hashing
#[test]
fn fuzzing_chunked_incremental_hashing() {
    use proptest::prelude::*;

    let config = ProptestConfig::with_cases(1000);

    proptest!(config, |(input: Vec<u8>, chunk_sizes: Vec<usize>)| {
        // Ensure we have at least one chunk and reasonable chunk sizes
        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;

        // Feed data in chunks
        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;
        }

        // Ensure all data was processed
        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");
    });
}

/// Fuzzing-based integration test for domain separation
#[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>)| {
        // Domains should be different for meaningful test
        prop_assume!(domain1 != domain2);

        let hash_domain1 = clockhash256_domain(&domain1, &payload);
        let hash_domain2 = clockhash256_domain(&domain2, &payload);
        let hash_no_domain = clockhash256(&payload);

        // Different domains should produce different hashes
        prop_assert_ne!(hash_domain1, hash_domain2,
            "Different domains should produce different hashes");

        // Domain-separated hashes should differ from plain hash
        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");

        // All hashes should be 32 bytes
        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");
    });
}

/// Fuzzing-based integration test for streaming API edge cases
#[test]
fn fuzzing_streaming_api_edge_cases() {
    use proptest::prelude::*;

    let config = ProptestConfig::with_cases(1000);

    proptest!(config, |(operations: Vec<(Vec<u8>, bool)>)| {
        // operations: (data_chunk, finalize_flag)
        // When finalize_flag is true, we finalize and start a new hasher

        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() {
                // Finalize current hasher and collect result
                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);

                // Start new hasher
                current_hasher = ClockHasher::new();
                expected_combined.clear();
            }

            // Update hasher with chunk
            current_hasher.update(&chunk);
            expected_combined.extend_from_slice(&chunk);
        }

        // Finalize any remaining data
        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);
        }

        // Ensure all results are valid hashes
        for hash in results {
            prop_assert_eq!(hash.len(), 32, "All hashes should be 32 bytes");
        }
    });
}

/// Fuzzing-based integration test for hash function properties across input sizes
#[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);

        // Basic properties
        prop_assert_eq!(hash.len(), 32, "Hash should always be 32 bytes");

        // Hash should not be all zeros (extremely unlikely for a good hash function)
        prop_assert!(!hash.iter().all(|&b| b == 0),
            "Hash should not be all zeros");

        // Test determinism
        let hash2 = clockhash256(&input);
        prop_assert_eq!(hash, hash2, "Hashing should be deterministic");

        // Test incremental determinism
        let mut hasher = ClockHasher::new();
        hasher.update(&input);
        let hash3 = hasher.finalize();
        prop_assert_eq!(hash, hash3, "Incremental hashing should be deterministic");

        // Test avalanche effect with single bit flip (only for non-empty inputs)
        if !input.is_empty() {
            let mut modified_input = input.clone();
            modified_input[0] ^= 1; // Flip one bit

            let modified_hash = clockhash256(&modified_input);

            // The hash should change (unless the input change doesn't affect the final hash,
            // which is extremely unlikely for a good hash function)
            // We allow this to potentially be equal only in very rare cases
            if hash == modified_hash {
                // If hashes are equal, this might indicate a weakness, but could be coincidental
                // Let's check if this happens with multiple bit flips
                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");
                }
            }

            // Count differing bits for avalanche effect analysis
            let mut diff_bits = 0;
            for i in 0..32 {
                diff_bits += (hash[i] ^ modified_hash[i]).count_ones() as usize;
            }

            // For a good hash function, we expect significant bit differences
            // Allow some flexibility - if hashes are identical, diff_bits will be 0
            if hash != modified_hash {
                // Should have at least some bit differences
                prop_assert!(diff_bits >= 1,
                    "Avalanche effect: expected at least 1 differing bit, got {}", diff_bits);
            }
        }
    });
}

/// Fuzzing-based integration test for multiple domain operations
#[test]
fn fuzzing_multiple_domain_operations() {
    use proptest::prelude::*;

    let config = ProptestConfig::with_cases(300);

    proptest!(config, |(data: Vec<u8>, domains: Vec<Vec<u8>>)| {
        // Filter to ensure we have at least 2 different domains
        let unique_domains: std::collections::HashSet<_> = domains.iter().collect();
        prop_assume!(unique_domains.len() >= 2);

        let mut domain_hashes = Vec::new();

        // Generate domain-separated hashes for each domain
        for domain in &domains {
            let hash = clockhash256_domain(domain, &data);
            domain_hashes.push(hash);
        }

        // Different domains should produce different hashes
        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");
                }
            }
        }

        // Domain hashes should differ from plain hash
        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");
        }

        // All hashes should be proper length
        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");
    });
}

/// Fuzzing-based integration test for hash function with structured inputs
#[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>)| {
        // Test hashing of concatenated structured data
        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);

        // Test incremental hashing of same structure
        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");

        // Test domain separation with structured data
        let domain_hash = clockhash256_domain(b"structured", &combined);
        prop_assert_ne!(domain_hash, combined_hash,
            "Domain hash should differ from plain hash");

        // All hashes should be proper length
        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");
    });
}

/// Fuzzing-based integration test for hash collision resistance
#[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>>)| {
        // Filter out empty inputs and ensure uniqueness for meaningful collision test
        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());

            // Check for collisions (same hash from different inputs)
            prop_assert!(hash_set.insert(hash),
                "Hash collision detected - different inputs produced same hash");
        }

        // Verify determinism: same inputs should produce same hashes
        for input in &inputs {
            let hash1 = clockhash256(input);
            let hash2 = clockhash256(input);
            prop_assert_eq!(hash1, hash2, "Hashing should be deterministic");
        }

        // All hashes should be valid
        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");
        }
    });
}