sigma_rs/duplex_sponge/
keccak.rs

1//! Keccak-based duplex sponge implementation
2//!
3//! This module implements a duplex sponge construction using the Keccak-f[1600] permutation.
4//! It is designed to match test vectors from the original Sage implementation.
5
6use crate::duplex_sponge::DuplexSpongeInterface;
7use zerocopy::IntoBytes;
8
9const RATE: usize = 136;
10const LENGTH: usize = 136 + 64;
11
12/// Low-level Keccak-f[1600] state representation.
13#[derive(Clone, Default)]
14pub struct KeccakPermutationState([u64; LENGTH / 8]);
15
16impl KeccakPermutationState {
17    pub fn new(iv: [u8; 32]) -> Self {
18        let mut state = Self::default();
19        state.as_mut()[RATE..RATE + 32].copy_from_slice(&iv);
20        state
21    }
22
23    pub fn permute(&mut self) {
24        keccak::f1600(&mut self.0);
25    }
26}
27
28impl AsRef<[u8]> for KeccakPermutationState {
29    fn as_ref(&self) -> &[u8] {
30        self.0.as_bytes()
31    }
32}
33
34impl AsMut<[u8]> for KeccakPermutationState {
35    fn as_mut(&mut self) -> &mut [u8] {
36        self.0.as_mut_bytes()
37    }
38}
39
40/// Duplex sponge construction using Keccak-f[1600].
41#[derive(Clone)]
42pub struct KeccakDuplexSponge {
43    state: KeccakPermutationState,
44    absorb_index: usize,
45    squeeze_index: usize,
46}
47
48impl KeccakDuplexSponge {
49    pub fn new(iv: [u8; 32]) -> Self {
50        let state = KeccakPermutationState::new(iv);
51        KeccakDuplexSponge {
52            state,
53            absorb_index: 0,
54            squeeze_index: RATE,
55        }
56    }
57}
58
59impl DuplexSpongeInterface for KeccakDuplexSponge {
60    fn new(iv: [u8; 32]) -> Self {
61        KeccakDuplexSponge::new(iv)
62    }
63
64    fn absorb(&mut self, mut input: &[u8]) {
65        self.squeeze_index = RATE;
66
67        while !input.is_empty() {
68            if self.absorb_index == RATE {
69                self.state.permute();
70                self.absorb_index = 0;
71            }
72
73            let chunk_size = usize::min(RATE - self.absorb_index, input.len());
74            let dest = &mut self.state.as_mut()[self.absorb_index..self.absorb_index + chunk_size];
75            dest.copy_from_slice(&input[..chunk_size]);
76            self.absorb_index += chunk_size;
77            input = &input[chunk_size..];
78        }
79    }
80
81    fn squeeze(&mut self, mut length: usize) -> Vec<u8> {
82        let mut output = Vec::new();
83        while length != 0 {
84            if self.squeeze_index == RATE {
85                self.state.permute();
86                self.squeeze_index = 0;
87                self.absorb_index = 0;
88            }
89
90            let chunk_size = usize::min(RATE - self.squeeze_index, length);
91            output.extend_from_slice(
92                &self.state.as_mut()[self.squeeze_index..self.squeeze_index + chunk_size],
93            );
94            self.squeeze_index += chunk_size;
95            length -= chunk_size;
96        }
97        output
98    }
99
100    fn ratchet(&mut self) {
101        self.state.permute();
102        self.absorb_index = 0;
103        self.squeeze_index = RATE;
104    }
105}
106
107#[cfg(test)]
108mod tests {
109    use super::*;
110    use crate::duplex_sponge::DuplexSpongeInterface;
111    use hex_literal::hex;
112
113    #[test]
114    fn test_associativity_of_absorb() {
115        let expected_output =
116            hex!("7dfada182d6191e106ce287c2262a443ce2fb695c7cc5037a46626e88889af58");
117        let tag = *b"absorb-associativity-domain-----";
118
119        // Absorb all at once
120        let mut sponge1 = KeccakDuplexSponge::new(tag);
121        sponge1.absorb(b"hello world");
122        let out1 = sponge1.squeeze(32);
123
124        // Absorb in two parts
125        let mut sponge2 = KeccakDuplexSponge::new(tag);
126        sponge2.absorb(b"hello");
127        sponge2.absorb(b" world");
128        let out2 = sponge2.squeeze(32);
129
130        assert_eq!(out1, expected_output);
131        assert_eq!(out2, expected_output);
132    }
133}