clock-hash 1.0.0

ClockHash-256: Consensus hash function for ClockinChain
Documentation
//! Property-based tests for SIMD operations
//!
//! Uses proptest to generate random test cases and verify SIMD correctness
//! across a wide range of inputs.

#[cfg(all(test, feature = "std"))]
mod property_tests {
    use crate::simd::dispatch::*;
    use crate::simd::scalar::*;
    use proptest::prelude::*;
    use std::panic::AssertUnwindSafe;

    // Strategy for generating random u64 arrays
    fn u64_array_strategy() -> impl Strategy<Value = [u64; 16]> {
        [any::<u64>(); 16]
    }

    // Strategy for generating block data
    fn block_strategy() -> impl Strategy<Value = [u8; 128]> {
        [any::<u8>(); 128]
    }

    // Strategy for generating state data
    fn state_strategy() -> impl Strategy<Value = [u64; 8]> {
        [any::<u64>(); 8]
    }

    proptest! {
        #[test]
        fn test_simd_scalar_equivalence_prop(data in u64_array_strategy()) {
            let mut msg_simd = data;
            let mut msg_scalar = data;

            // Apply both implementations
            prop_assume!(std::panic::catch_unwind(AssertUnwindSafe(|| {
                clock_mix_avx2(&mut msg_simd);
            })).is_ok());

            scalar_clock_mix(&mut msg_scalar);

            // Results should be identical
            prop_assert_eq!(msg_simd, msg_scalar,
                "SIMD and scalar implementations should produce identical results");
        }

        #[test]
        fn test_clock_mix_deterministic_prop(data in u64_array_strategy()) {
            let mut msg1 = data;
            let mut msg2 = data;

            clock_mix_avx2(&mut msg1);
            clock_mix_avx2(&mut msg2);

            prop_assert_eq!(msg1, msg2,
                "ClockMix should be deterministic for same input");
        }

        #[test]
        fn test_clock_mix_not_identity_prop(data in u64_array_strategy()) {
            let original = data;
            let mut modified = data;

            clock_mix_avx2(&mut modified);

            // Should not be unchanged unless input was all zeros
            let is_all_zeros = original.iter().all(|&x| x == 0);
            if !is_all_zeros {
                prop_assert_ne!(modified, original,
                    "ClockMix should modify input data (unless input is all zeros)");
            }
        }

        #[test]
        fn test_process_block_consistency_prop(
            block in block_strategy(),
            mut state1 in state_strategy(),
            mut state2 in state_strategy()
        ) {
            // Ensure states start the same
            state2 = state1;

            // Process with both implementations
            process_block_simd_scalar(&block, &mut state1);
            process_block_simd(&block, &mut state2);

            prop_assert_eq!(state1, state2,
                "Block processing should be consistent between SIMD and scalar");
        }

        #[test]
        fn test_simd_preserves_length_prop(data in u64_array_strategy()) {
            let original = data;
            let mut modified = data;

            clock_mix_avx2(&mut modified);

            // Length should be preserved
            prop_assert_eq!(modified.len(), original.len());

            // Should not produce all zeros unless input was all zeros
            let is_all_zeros_input = original.iter().all(|&x| x == 0);
            let is_all_zeros_output = modified.iter().all(|&x| x == 0);
            if !is_all_zeros_input {
                prop_assert!(!is_all_zeros_output,
                    "ClockMix should not produce all zeros from non-zero input");
            }
        }

        #[test]
        fn test_simd_bit_distribution_prop(data in u64_array_strategy()) {
            let mut modified = data;
            clock_mix_avx2(&mut modified);

            // Count set bits in input and output
            let input_bits: u32 = data.iter().map(|&x| x.count_ones()).sum();
            let output_bits: u32 = modified.iter().map(|&x| x.count_ones()).sum();

            // Bit distribution should be reasonably preserved (within 20% of input)
            let input_bits_f = input_bits as f64;
            let output_bits_f = output_bits as f64;
            let ratio = if input_bits_f > 0.0 {
                output_bits_f / input_bits_f
            } else {
                1.0
            };

            prop_assert!((0.8..=1.2).contains(&ratio),
                "Bit distribution should be reasonably preserved (ratio: {})", ratio);
        }

        #[test]
        fn test_simd_diffusion_prop(base_data in u64_array_strategy()) {
            // Test that ClockMix actually modifies the input data
            // (avalanche testing is done at the full hash function level)
            let mut modified = base_data;
            let original = base_data;

            clock_mix_avx2(&mut modified);

            // ClockMix should modify the data (unless input is all zeros, which we handle specially)
            let is_all_zeros = original.iter().all(|&x| x == 0);
            if !is_all_zeros {
                prop_assert_ne!(modified, original, "ClockMix should modify non-zero input");
            }

            // Should not produce all zeros from non-zero input
            let output_all_zeros = modified.iter().all(|&x| x == 0);
            if !is_all_zeros {
                prop_assert!(!output_all_zeros, "ClockMix should not produce all zeros from non-zero input");
            }
        }

        #[test]
        fn test_simd_no_fixed_points_prop(data in u64_array_strategy()) {
            let mut modified = data;
            clock_mix_avx2(&mut modified);

            // No element should remain unchanged unless the whole array satisfies
            // some specific mathematical property (which is unlikely for random data)
            let unchanged_count = modified.iter().zip(data.iter())
                .filter(|(a, b)| a == b).count();

            // Allow up to 2 unchanged elements (statistical fluke), but not more
            prop_assert!(unchanged_count <= 2,
                "Too many elements unchanged: {} out of {}", unchanged_count, data.len());
        }

        #[test]
        fn test_simd_state_injection_prop(
            block in block_strategy(),
            base_state in state_strategy()
        ) {
            let mut state_simd = base_state;
            let mut state_scalar = base_state;

            process_block_simd(&block, &mut state_simd);
            process_block_simd_scalar(&block, &mut state_scalar);

            // States should be modified
            prop_assert_ne!(state_simd, base_state,
                "SIMD state should be modified by block processing");
            prop_assert_ne!(state_scalar, base_state,
                "Scalar state should be modified by block processing");

            // And should match each other
            prop_assert_eq!(state_simd, state_scalar,
                "SIMD and scalar state injection should match");
        }

        #[test]
        fn test_simd_endianness_robustness_prop(
            mut block in block_strategy()
        ) {
            let mut state1 = [0u64; 8];
            let mut state2 = [0u64; 8];

            // Process block normally
            process_block_simd(&block, &mut state1);

            // Reverse endianness of block (simulate endianness issues)
            for chunk in block.chunks_exact_mut(8) {
                chunk.reverse();
            }

            process_block_simd(&block, &mut state2);

            // States should be different (endianness matters)
            prop_assert_ne!(state1, state2,
                "Block processing should be sensitive to endianness changes");
        }
    }

    /// Scalar version of process_block_simd for testing
    fn process_block_simd_scalar(block: &[u8; 128], state: &mut [u64; 8]) {
        // Parse block to 16 u64 words (little-endian)
        let mut words = [0u64; 16];
        for i in 0..16 {
            let offset = i * 8;
            words[i] = u64::from_le_bytes([
                block[offset],
                block[offset + 1],
                block[offset + 2],
                block[offset + 3],
                block[offset + 4],
                block[offset + 5],
                block[offset + 6],
                block[offset + 7],
            ]);
        }

        // Apply ClockMix
        scalar_clock_mix(&mut words);

        // Inject into state
        for i in 0..8 {
            state[i] = state[i].wrapping_add(words[i]);
            let rot_idx = (i + 4) % 8;
            state[i] ^= crate::utils::rotl64(state[rot_idx], 17);
        }

        crate::clockpermute::clock_permute(state);
    }
}