clock-hash 1.0.0

ClockHash-256: Consensus hash function for ClockinChain
Documentation
//! Comprehensive tests for domain separation edge cases
//!
//! Tests unusual domain tags, boundary conditions, and security properties
//! of domain separation in ClockHash-256.

use clock_hash::{clockhash256_domain, clockhash256_with_domain, clockhash256, DomainTag, tags};
use std::collections::HashSet;

/// Test domain separation with empty domains
#[test]
fn test_empty_domain_separation() {
    let data = b"test data";

    // Empty domain should produce different hash from no domain
    let empty_domain_hash = clockhash256_domain(b"", data);
    let plain_hash = clockhash256(data);

    assert_ne!(empty_domain_hash, plain_hash, "Empty domain should differ from no domain");

    // Empty domain should be deterministic
    let empty_domain_hash2 = clockhash256_domain(b"", data);
    assert_eq!(empty_domain_hash, empty_domain_hash2, "Empty domain should be deterministic");
}

/// Test domain separation with very long domains
#[test]
fn test_long_domain_separation() {
    let data = b"test data";

    // Test domains of various lengths
    let domain_lengths = [1, 10, 100, 1000, 10000];

    let mut hashes = HashSet::new();
    for &len in &domain_lengths {
        let domain: Vec<u8> = (0..len).map(|i| (i % 256) as u8).collect();
        let hash = clockhash256_domain(&domain, data);
        assert!(hashes.insert(hash), "Domain of length {} should produce unique hash", len);
    }

    // All should differ from plain hash
    let plain_hash = clockhash256(data);
    for &len in &domain_lengths {
        let domain: Vec<u8> = (0..len).map(|i| (i % 256) as u8).collect();
        let domain_hash = clockhash256_domain(&domain, data);
        assert_ne!(domain_hash, plain_hash, "Domain of length {} should differ from plain hash", len);
    }
}

/// Test domain separation with special byte values
#[test]
fn test_special_byte_domains() {
    let data = b"test data";

    // Test domains with special byte patterns
    let special_domains = vec![
        vec![0x00],                    // Null byte
        vec![0xFF],                    // All bits set
        vec![0x00, 0x00, 0x00],       // Multiple nulls
        vec![0xFF, 0xFF, 0xFF],       // Multiple max values
        vec![0x00, 0x01, 0x02, 0x03], // Sequential
        vec![0x80, 0x81, 0x82],       // High bits set
        vec![0xAA, 0x55, 0xAA, 0x55], // Alternating pattern
    ];

    let mut hashes = HashSet::new();
    for (i, domain) in special_domains.iter().enumerate() {
        let hash = clockhash256_domain(domain, data);
        assert!(hashes.insert(hash), "Special domain {} should produce unique hash", i);

        // Should differ from plain hash
        let plain_hash = clockhash256(data);
        assert_ne!(hash, plain_hash, "Special domain {} should differ from plain hash", i);
    }
}

/// Test domain separation with domains containing the separator byte
#[test]
fn test_domain_with_separator() {
    let data = b"test data";

    // Test domains that contain the separator byte (0x00)
    let domains_with_separator = vec![
        vec![0x00],                    // Just separator
        vec![0x42, 0x00, 0x43],       // Domain with separator in middle
        vec![0x00, 0x00],             // Multiple separators
        vec![0xFF, 0x00, 0x01],       // Separator with other bytes
    ];

    let mut hashes = HashSet::new();
    for (i, domain) in domains_with_separator.iter().enumerate() {
        let hash = clockhash256_domain(domain, data);
        assert!(hashes.insert(hash), "Domain with separator {} should produce unique hash", i);

        // Should differ from plain hash
        let plain_hash = clockhash256(data);
        assert_ne!(hash, plain_hash, "Domain with separator {} should differ from plain hash", i);
    }
}

/// Test that all standard domain tags are unique
#[test]
fn test_standard_domain_uniqueness() {
    let data = b"standard domain test data";

    let standard_domains = vec![
        tags::CLK_BLOCK,
        tags::CLK_TX,
        tags::CLK_MERKLE,
        tags::CLK_NONCE,
        tags::CLK_RNG,
    ];

    let mut hashes = HashSet::new();
    for domain in &standard_domains {
        let hash = clockhash256_domain(domain, data);
        assert!(hashes.insert(hash), "Standard domain {:?} should produce unique hash", std::str::from_utf8(domain));

        // Should differ from plain hash
        let plain_hash = clockhash256(data);
        assert_ne!(hash, plain_hash, "Standard domain {:?} should differ from plain hash", std::str::from_utf8(domain));
    }
}

/// Test domain separation with data that looks like domain tags
#[test]
fn test_data_looking_like_domains() {
    // Test when data itself looks like domain tags
    let test_cases = vec![
        (tags::CLK_BLOCK, &b"block data"[..]),
        (tags::CLK_TX, &b"tx data"[..]),
        (tags::CLK_MERKLE, &b"merkle data"[..]),
        (tags::CLK_NONCE, &b"nonce data"[..]),
        (tags::CLK_RNG, &b"rng data"[..]),
    ];

    let mut all_hashes = HashSet::new();
    for (domain, data) in test_cases {
        let hash = clockhash256_domain(domain, data);
        assert!(all_hashes.insert(hash), "Domain should produce unique hash");
    }
}

/// Test domain separation boundary conditions
#[test]
fn test_domain_boundary_conditions() {
    let data = b"boundary test";

    // Test domains at various boundary lengths
    let boundary_lengths = [0, 1, 254, 255, 256, 257, 511, 512, 513];

    let mut hashes = HashSet::new();
    for &len in &boundary_lengths {
        let domain: Vec<u8> = (0..len).map(|i| (i % 256) as u8).collect();
        let hash = clockhash256_domain(&domain, data);
        assert!(hashes.insert(hash), "Domain length {} should produce unique hash", len);

        // Should differ from plain hash
        let plain_hash = clockhash256(data);
        assert_ne!(hash, plain_hash, "Domain length {} should differ from plain hash", len);
    }
}

/// Test domain separation with incremental hasher
#[test]
fn test_domain_separation_incremental() {
    use clock_hash::ClockHasher;

    let domain = b"INCREMENTAL_DOMAIN";
    let data = b"test data for incremental domain hashing";

    // Hash using clockhash256_domain
    let reference_hash = clockhash256_domain(domain, data);

    // Hash using incremental hasher
    let mut hasher = ClockHasher::new();
    hasher.update(domain);
    hasher.update(&[0x00]); // Domain separator
    hasher.update(data);
    let incremental_hash = hasher.finalize();

    assert_eq!(reference_hash, incremental_hash,
        "Domain separation should work identically with incremental hasher");
}

/// Test domain separation with empty data
#[test]
fn test_domain_separation_empty_data() {
    let domains = vec![
        &b""[..],
        &b"DOMAIN1"[..],
        &b"DOMAIN2"[..],
        tags::CLK_BLOCK,
        tags::CLK_TX,
    ];

    let empty_data = b"";

    let mut hashes = HashSet::new();
    for domain in &domains {
        let hash = clockhash256_domain(domain, empty_data);
        assert!(hashes.insert(hash), "Domain {:?} with empty data should produce unique hash",
            std::str::from_utf8(domain));
    }

    // Should differ from plain empty hash
    let plain_empty_hash = clockhash256(empty_data);
    for domain in &domains {
        let domain_hash = clockhash256_domain(domain, empty_data);
        assert_ne!(domain_hash, plain_empty_hash,
            "Domain {:?} with empty data should differ from plain empty hash",
            std::str::from_utf8(domain));
    }
}

/// Test domain tag enum completeness
#[test]
fn test_domain_tag_enum_completeness() {
    let data = b"enum completeness test";

    // Test that all enum variants work
    let enum_domains = vec![
        (DomainTag::Block, "Block"),
        (DomainTag::Transaction, "Transaction"),
        (DomainTag::Merkle, "Merkle"),
        (DomainTag::Nonce, "Nonce"),
        (DomainTag::Rng, "Rng"),
    ];

    let mut enum_hashes = HashSet::new();
    let mut tag_hashes = HashSet::new();

    for (enum_domain, name) in &enum_domains {
        let enum_hash = clockhash256_with_domain(*enum_domain, data);
        let tag_hash = clockhash256_domain(enum_domain.as_bytes(), data);

        assert_eq!(enum_hash, tag_hash,
            "Enum domain {} should match tag-based domain", name);

        assert!(enum_hashes.insert(enum_hash),
            "Enum domain {} should produce unique hash", name);
        assert!(tag_hashes.insert(tag_hash),
            "Tag domain {} should produce unique hash", name);
    }
}

/// Test domain separation collision resistance
#[test]
fn test_domain_separation_collision_resistance() {
    let data = b"collision resistance test";

    // Test many different domains
    let mut hashes = HashSet::new();
    for i in 0..1000 {
        let domain = format!("DOMAIN_{:04x}", i);
        let hash = clockhash256_domain(domain.as_bytes(), data);
        assert!(hashes.insert(hash), "Domain {} should produce unique hash", i);
    }

    // Should all differ from plain hash
    let plain_hash = clockhash256(data);
    for i in 0..100 {
        let domain = format!("DOMAIN_{:04x}", i);
        let domain_hash = clockhash256_domain(domain.as_bytes(), data);
        assert_ne!(domain_hash, plain_hash, "Domain {} should differ from plain hash", i);
    }
}

/// Test domain separation with binary domains
#[test]
fn test_binary_domain_separation() {
    let data = b"binary domain test";

    // Test domains with binary data (non-UTF8)
    let binary_domains = vec![
        vec![0x00, 0x01, 0x02, 0xFF],
        vec![0x80, 0x81, 0x82, 0x83],
        vec![0x00, 0xFF, 0x00, 0xFF],
        (0..16).map(|i| i as u8).collect::<Vec<_>>(),
        (0..16).map(|i| (255 - i) as u8).collect::<Vec<_>>(),
    ];

    let mut hashes = HashSet::new();
    for (i, domain) in binary_domains.iter().enumerate() {
        let hash = clockhash256_domain(domain, data);
        assert!(hashes.insert(hash), "Binary domain {} should produce unique hash", i);

        // Should differ from plain hash
        let plain_hash = clockhash256(data);
        assert_ne!(hash, plain_hash, "Binary domain {} should differ from plain hash", i);
    }
}

/// Test domain separation security - no domain should allow finding preimages
#[test]
fn test_domain_separation_preimage_resistance() {
    let target_data = b"target data";
    let target_domain = b"TARGET_DOMAIN";

    let target_hash = clockhash256_domain(target_domain, target_data);

    // Try to find data that hashes to target_hash in different domains
    // This should be extremely unlikely
    let test_domains = [&b"DIFF_DOMAIN"[..], &b"OTHER_DOMAIN"[..], &b"RANDOM_DOMAIN"[..]];

    for domain in &test_domains {
        // Try a few candidate data values
        for i in 0..100 {
            let candidate_data = format!("candidate_data_{}", i);
            let candidate_hash = clockhash256_domain(domain, candidate_data.as_bytes());

            assert_ne!(candidate_hash, target_hash,
                "Should not find preimage in different domain {} with candidate {}", 
                std::str::from_utf8(domain).unwrap_or("<?>"), i);
        }
    }
}