clock_hash/
clockpermute.rs

1//! ClockPermute: 16-round permutation function for ClockHash-256
2//!
3//! Applies a series of mixing operations to the 8-word state to ensure
4//! strong diffusion and nonlinearity.
5
6use crate::constants::{P0, ROTATION_SCHEDULE};
7use crate::utils::rotl64;
8
9/// Number of permutation rounds
10const ROUNDS: usize = 16;
11
12/// Apply ClockPermute to the 8-word state (16 rounds).
13///
14/// Each round applies:
15/// - Step A: S[i] = (S[i] + S[(i+1) mod 8]) * P0 (wrapping arithmetic)
16/// - Step B: S[i] = ROTL(S[i] ⊕ S[(i+3) mod 8], R[(round+i) mod 16])
17/// - Step C: Swap pairs (S[1]↔S[5], S[2]↔S[6], S[3]↔S[7])
18///
19/// # Arguments
20///
21/// * `state` - Mutable reference to 8 u64 words representing the hash state
22///
23/// # Panics
24///
25/// This function will panic if `state.len() != 8`.
26#[inline]
27pub fn clock_permute(state: &mut [u64; 8]) {
28    for round in 0..ROUNDS {
29        // Step A: Add and multiply
30        for i in 0..8 {
31            let next_idx = (i + 1) % 8;
32            state[i] = state[i].wrapping_add(state[next_idx]).wrapping_mul(P0);
33        }
34
35        // Step B: XOR and rotate
36        for i in 0..8 {
37            let xor_idx = (i + 3) % 8;
38            let rotation_idx = (round + i) % 16;
39            let rotation = ROTATION_SCHEDULE[rotation_idx];
40            state[i] = rotl64(state[i] ^ state[xor_idx], rotation);
41        }
42
43        // Step C: Cross diffusion swaps
44        state.swap(1, 5);
45        state.swap(2, 6);
46        state.swap(3, 7);
47    }
48}
49
50#[cfg(test)]
51mod tests {
52    use super::*;
53
54    #[test]
55    fn test_clock_permute_zeros() {
56        let mut state = [0u64; 8];
57        let original = state;
58        clock_permute(&mut state);
59        // All-zeros should remain all-zeros
60        assert_eq!(state, original);
61    }
62
63    #[test]
64    fn test_clock_permute_basic() {
65        let mut state = [0u64; 8];
66        state[0] = 0x1234567890ABCDEF;
67
68        let original = state;
69        clock_permute(&mut state);
70
71        // Permutation should modify the state
72        assert_ne!(state, original);
73    }
74
75    #[test]
76    fn test_clock_permute_avalanche() {
77        let mut state1 = [0u64; 8];
78        let mut state2 = [0u64; 8];
79
80        state1[0] = 0x0000000000000000;
81        state2[0] = 0x0000000000000001; // Single bit difference
82
83        clock_permute(&mut state1);
84        clock_permute(&mut state2);
85
86        // Should produce different results (avalanche effect)
87        assert_ne!(state1, state2);
88    }
89
90    #[test]
91    fn test_clock_permute_deterministic() {
92        let mut state = [0u64; 8];
93        state[0] = 0xDEADBEEFCAFEBABE;
94
95        let mut state_copy = state;
96
97        clock_permute(&mut state);
98        clock_permute(&mut state_copy);
99
100        // Same input should produce same output
101        assert_eq!(state, state_copy);
102    }
103
104    #[test]
105    fn test_clock_permute_full_diffusion() {
106        // Test that changes in one word affect all words
107        let mut state1 = [0u64; 8];
108        let mut state2 = [0u64; 8];
109
110        state1[0] = 1;
111        state2[0] = 0;
112
113        clock_permute(&mut state1);
114        clock_permute(&mut state2);
115
116        // All words should be different (full diffusion)
117        for i in 0..8 {
118            assert_ne!(state1[i], state2[i], "Word {} should differ", i);
119        }
120    }
121}