Skip to main content

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