clock_hash/
security.rs

1//! Security verification utilities for ClockHash-256
2//!
3//! This module provides tools for verifying the security properties of ClockHash-256,
4//! including constant-time operation verification and side-channel resistance checks.
5
6/// Verify that ClockHash-256 operations are constant-time
7///
8/// This function performs basic constant-time verification by ensuring that
9/// operations complete in predictable time regardless of input data.
10/// Note: This is a basic verification; for full constant-time guarantees,
11/// use formal verification tools like ctgrind or valgrind.
12///
13/// # Arguments
14///
15/// * `iterations` - Number of iterations to test for timing consistency
16///
17/// # Returns
18///
19/// Returns `true` if the operations appear constant-time within the test parameters
20#[cfg(feature = "std")]
21pub fn verify_constant_time(iterations: usize) -> bool {
22    use crate::clockhash256;
23    use std::time::Instant;
24
25    let mut times = Vec::with_capacity(iterations);
26
27    // Measure timing for different inputs
28    for i in 0..iterations {
29        let input = if i % 2 == 0 { [0u8; 64] } else { [0xFFu8; 64] };
30        let start = Instant::now();
31        let _hash = clockhash256(&input);
32        let elapsed = start.elapsed();
33        times.push(elapsed.as_nanos());
34    }
35
36    // Check that timing variation is within acceptable bounds
37    // Allow 10% variation to account for system noise
38    if let (Some(&min), Some(&max)) = (times.iter().min(), times.iter().max()) {
39        let variation = (max - min) as f64 / min as f64;
40        variation < 0.1 // Less than 10% variation
41    } else {
42        false
43    }
44}
45
46/// Verify avalanche effect for security
47///
48/// Ensures that small input changes result in large output changes.
49/// This is a fundamental property of secure hash functions.
50///
51/// # Arguments
52///
53/// * `input1` - First input data
54/// * `input2` - Second input data (differing by 1 bit)
55///
56/// # Returns
57///
58/// Returns the avalanche coefficient (0.0 to 1.0), where 0.5 is ideal
59pub fn verify_avalanche(input1: &[u8], input2: &[u8]) -> f64 {
60    use crate::clockhash256;
61
62    let hash1 = clockhash256(input1);
63    let hash2 = clockhash256(input2);
64
65    // Count differing bits
66    let mut diff_bits = 0;
67    for i in 0..32 {
68        diff_bits += (hash1[i] ^ hash2[i]).count_ones() as usize;
69    }
70
71    // Avalanche coefficient: fraction of output bits that changed
72    diff_bits as f64 / 256.0
73}
74
75/// Verify collision resistance properties
76///
77/// Performs basic collision resistance testing by hashing different inputs
78/// and ensuring no accidental collisions occur.
79///
80/// # Arguments
81///
82/// * `test_cases` - Array of test input data
83///
84/// # Returns
85///
86/// Returns `true` if no collisions were found among the test cases
87pub fn verify_collision_resistance(test_cases: &[&[u8]]) -> bool {
88    use crate::clockhash256;
89
90    // Simple collision check without HashSet (works in no_std)
91    for i in 0..test_cases.len() {
92        let hash_i = clockhash256(test_cases[i]);
93        for j in (i + 1)..test_cases.len() {
94            let hash_j = clockhash256(test_cases[j]);
95            if hash_i == hash_j {
96                return false; // Collision found
97            }
98        }
99    }
100
101    true // No collisions found
102}
103
104/// Verify domain separation properties
105///
106/// Ensures that domain-separated hashes are different even when the
107/// underlying data is identical.
108///
109/// # Arguments
110///
111/// * `data` - Test data to hash
112/// * `domains` - Array of domain identifiers to test
113///
114/// # Returns
115///
116/// Returns `true` if all domain-separated hashes are unique
117pub fn verify_domain_separation(data: &[u8], domains: &[&[u8]]) -> bool {
118    use crate::clockhash256_domain;
119
120    // Simple uniqueness check without HashSet (works in no_std)
121    for i in 0..domains.len() {
122        let hash_i = clockhash256_domain(domains[i], data);
123        for j in (i + 1)..domains.len() {
124            let hash_j = clockhash256_domain(domains[j], data);
125            if hash_i == hash_j {
126                return false; // Domain separation failed
127            }
128        }
129    }
130
131    true // All domains produce unique hashes
132}
133
134#[cfg(test)]
135mod tests {
136    use super::*;
137    use crate::{clockhash256, tags};
138
139    #[test]
140    fn test_avalanche_effect() {
141        let input1 = [0u8; 64];
142        let mut input2 = input1;
143        input2[0] ^= 1; // Flip one bit
144
145        let avalanche = verify_avalanche(&input1, &input2);
146
147        // Avalanche coefficient should be close to 0.5 (50% of bits change)
148        assert!(
149            avalanche > 0.3 && avalanche < 0.7,
150            "Avalanche coefficient {} should be close to 0.5",
151            avalanche
152        );
153    }
154
155    #[test]
156    fn test_collision_resistance() {
157        let test_cases: &[&[u8]] = &[
158            b"",
159            b"a",
160            b"abc",
161            b"message digest",
162            b"abcdefghijklmnopqrstuvwxyz",
163            &[0u8; 64],
164            &[0xFFu8; 64],
165        ];
166
167        assert!(
168            verify_collision_resistance(test_cases),
169            "Collision resistance test failed"
170        );
171    }
172
173    #[test]
174    fn test_domain_separation() {
175        let data = b"test data";
176        let domains = [
177            tags::CLK_BLOCK,
178            tags::CLK_TX,
179            tags::CLK_MERKLE,
180            tags::CLK_NONCE,
181            tags::CLK_RNG,
182        ];
183
184        assert!(
185            verify_domain_separation(data, &domains),
186            "Domain separation test failed"
187        );
188    }
189
190    #[test]
191    #[cfg(feature = "std")]
192    fn test_constant_time() {
193        // Note: This is a basic timing test and may be affected by system noise
194        // For proper constant-time verification, use specialized tools like ctgrind
195        let is_constant_time = verify_constant_time(100);
196
197        // In a noise-free environment, this should pass
198        // In practice, system noise may cause false failures
199        if !is_constant_time {
200            println!("Warning: Constant-time test failed - may be due to system noise");
201            println!("For proper constant-time verification, use ctgrind or valgrind");
202        }
203    }
204
205    #[test]
206    fn test_preimage_resistance() {
207        // Basic preimage resistance test
208        let target_hash = clockhash256(b"known input");
209        let different_input = b"different input";
210        let different_hash = clockhash256(different_input);
211
212        assert_ne!(
213            target_hash, different_hash,
214            "Different inputs should produce different hashes"
215        );
216    }
217
218    #[test]
219    fn test_second_preimage_resistance() {
220        // Basic second preimage resistance test
221        let original_data = b"original message";
222        let original_hash = clockhash256(original_data);
223
224        // Try to find different data with same hash (should fail)
225        let test_cases: &[&[u8]] = &[
226            b"different message",
227            b"another message",
228            b"modified message",
229        ];
230
231        for &test_case in test_cases {
232            let test_hash = clockhash256(test_case);
233            assert_ne!(
234                original_hash, test_hash,
235                "Second preimage resistance: different data should not collide"
236            );
237        }
238    }
239}