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}