Skip to main content

spongefish/instantiations/
hash.rs

1//! XXX. we do two things: define hash, and instantiate the duplexspongeinterface
2//!
3//! This code is inspired from libsignal's poksho:
4//! <https://github.com/signalapp/libsignal/blob/main/rust/poksho/src/shosha256.rs>.
5//! With the following generalizations:
6//! - squeeze satisfies streaming
7//!     ```text
8//!     squeeze(1); squeeze(1); squeeze(1) = squeeze(3);
9//!     ```
10//! - the implementation is for any Digest.
11
12use alloc::vec::Vec;
13
14use digest::{
15    block_api::{Block, BlockSizeUser},
16    typenum::Unsigned,
17    Digest, FixedOutputReset, Output, Reset,
18};
19#[cfg(feature = "zeroize")]
20use zeroize::Zeroize;
21
22use crate::DuplexSpongeInterface;
23
24/// A Bridge to our sponge interface for legacy `Digest` implementations.
25#[derive(Clone)]
26pub struct Hash<D: Digest + Clone + Reset + BlockSizeUser> {
27    /// The underlying hasher.
28    hasher: D,
29    /// Cached digest
30    cv: Output<D>,
31    /// Current operation, keeping state between absorb and squeeze
32    /// across multiple calls when streaming.
33    mode: Mode,
34    /// Digest bytes left over from a previous squeeze.
35    leftovers: Vec<u8>,
36}
37
38impl<D: BlockSizeUser + Digest + Clone + FixedOutputReset> DuplexSpongeInterface for Hash<D> {
39    type U = u8;
40
41    fn absorb(&mut self, input: &[u8]) -> &mut Self {
42        self.squeeze_end();
43
44        if self.mode == Mode::Start {
45            self.mode = Mode::Absorb;
46            Digest::update(&mut self.hasher, Self::mask_absorb());
47            Digest::update(&mut self.hasher, &self.cv);
48        }
49
50        Digest::update(&mut self.hasher, input);
51        self
52    }
53
54    fn ratchet(&mut self) -> &mut Self {
55        self.squeeze_end();
56        // Double hash
57        self.cv = <D as Digest>::digest(self.hasher.finalize_reset());
58        // Restart the rest of the data
59        #[cfg(feature = "zeroize")]
60        self.leftovers.zeroize();
61        self.leftovers.clear();
62        self.mode = Mode::Start;
63        self
64    }
65
66    fn squeeze(&mut self, output: &mut [u8]) -> &mut Self {
67        if self.mode == Mode::Start {
68            self.mode = Mode::Squeeze(0);
69            // create the prefix hash
70            Digest::update(&mut self.hasher, Self::mask_squeeze());
71            Digest::update(&mut self.hasher, &self.cv);
72            self.squeeze(output)
73        // If Absorbing, ratchet
74        } else if self.mode == Mode::Absorb {
75            self.ratchet();
76            self.squeeze(output)
77        // If we have no more data to squeeze, return
78        } else if output.is_empty() {
79            self
80        // If we still have some digest not yet squeezed
81        // from previous invocations, write it to the output.
82        } else if !self.leftovers.is_empty() {
83            let len = usize::min(output.len(), self.leftovers.len());
84            output[..len].copy_from_slice(&self.leftovers[..len]);
85            self.leftovers.drain(..len);
86            self.squeeze(&mut output[len..])
87        // Squeeze another digest
88        } else if let Mode::Squeeze(i) = self.mode {
89            // Add the squeeze mask, current digest, and index
90            let mut output_hasher_prefix = self.hasher.clone();
91            Digest::update(&mut output_hasher_prefix, i.to_be_bytes());
92            let digest = output_hasher_prefix.finalize();
93            // Copy the digest into the output, and store the rest for later
94            let chunk_len = usize::min(output.len(), Self::DIGEST_SIZE);
95            output[..chunk_len].copy_from_slice(&digest[..chunk_len]);
96            self.leftovers.extend_from_slice(&digest[chunk_len..]);
97            // Update the state
98            self.mode = Mode::Squeeze(i + 1);
99            self.squeeze(&mut output[chunk_len..])
100        } else {
101            unreachable!()
102        }
103    }
104}
105
106#[derive(Clone, PartialEq, Eq)]
107enum Mode {
108    Start,
109    Absorb,
110    Squeeze(usize),
111}
112
113impl<D: BlockSizeUser + Digest + Clone + Reset> Hash<D> {
114    const BLOCK_SIZE: usize = D::BlockSize::USIZE;
115    const DIGEST_SIZE: usize = D::OutputSize::USIZE;
116
117    /// Create a block
118    /// | start | 0000 0000 | end |
119    fn pad_block(start: &[u8], end: &[u8]) -> Block<D> {
120        debug_assert!(start.len() + end.len() < Self::BLOCK_SIZE);
121        let mut mask = Block::<D>::default();
122        mask[..start.len()].copy_from_slice(start);
123        mask[Self::BLOCK_SIZE - end.len()..].copy_from_slice(end);
124        mask
125    }
126
127    fn mask_absorb() -> Block<D> {
128        Self::pad_block(&[], &[0x00])
129    }
130
131    fn mask_squeeze() -> Block<D> {
132        Self::pad_block(&[], &[0x01])
133    }
134
135    fn mask_squeeze_end() -> Block<D> {
136        Self::pad_block(&[], &[0x02])
137    }
138
139    fn squeeze_end(&mut self) {
140        if let Mode::Squeeze(count) = self.mode {
141            Digest::reset(&mut self.hasher);
142
143            // append to the state the squeeze mask
144            // with the length of the data read so far
145            // and the current digest
146            let byte_count = count * Self::DIGEST_SIZE - self.leftovers.len();
147            let mut squeeze_hasher = D::new();
148            Digest::update(&mut squeeze_hasher, Self::mask_squeeze_end());
149            Digest::update(&mut squeeze_hasher, &self.cv);
150            Digest::update(&mut squeeze_hasher, byte_count.to_be_bytes());
151            self.cv = Digest::finalize(squeeze_hasher);
152
153            // set the sponge state in absorb mode
154            self.mode = Mode::Start;
155            self.leftovers.clear();
156        }
157    }
158}
159
160#[cfg(feature = "zeroize")]
161impl<D: Clone + Digest + Reset + BlockSizeUser> Zeroize for Hash<D> {
162    fn zeroize(&mut self) {
163        self.cv.zeroize();
164        Digest::reset(&mut self.hasher);
165    }
166}
167
168#[cfg(feature = "zeroize")]
169impl<D: Clone + Digest + Reset + BlockSizeUser> Drop for Hash<D> {
170    fn drop(&mut self) {
171        self.zeroize();
172    }
173}
174
175impl<D: BlockSizeUser + Digest + Clone + FixedOutputReset> Default for Hash<D> {
176    fn default() -> Self {
177        Self {
178            hasher: D::new(),
179            cv: Output::<D>::default(),
180            mode: Mode::Start,
181            leftovers: Vec::new(),
182        }
183    }
184}
185
186#[cfg(all(test, feature = "sha2"))]
187#[test]
188fn test_shosha() {
189    let expected = b"\xEB\xE4\xEF\x29\xE1\x8A\xA5\x41\x37\xED\xD8\x9C\x23\xF8\
190    \xBF\xEA\xC2\x73\x1C\x9F\x67\x5D\xA2\x0E\x7C\x67\xD5\xAD\
191    \x68\xD7\xEE\x2D\x40\xA4\x52\x32\xB5\x99\x55\x2D\x46\xB5\
192    \x20\x08\x2F\xB2\x70\x59\x71\xF0\x7B\x31\x58\xB0\x72\xB6\
193    \x3A\xB0\x93\x4A\x05\xE6\xAF\x64";
194    let mut sho = Hash::<sha2::Sha256>::default();
195    let mut got = [0u8; 64];
196    sho.absorb(b"asd");
197    sho.ratchet();
198    // streaming absorb
199    sho.absorb(b"asd");
200    sho.absorb(b"asd");
201    // streaming squeeze
202    sho.squeeze(&mut got[..32]);
203    sho.squeeze(&mut got[32..]);
204    assert_eq!(&got, expected);
205
206    let expected = b"\xEB\xE4\xEF\x29\xE1\x8A\xA5\x41\x37\xED\xD8\x9C\x23\xF8\
207    \xBF\xEA\xC2\x73\x1C\x9F\x67\x5D\xA2\x0E\x7C\x67\xD5\xAD\
208    \x68\xD7\xEE\x2D\x40\xA4\x52\x32\xB5\x99\x55\x2D\x46\xB5\
209    \x20\x08\x2F\xB2\x70\x59\x71\xF0\x7B\x31\x58\xB0\x72\xB6\
210    \x3A\xB0\x93\x4A\x05\xE6\xAF\x64\x48";
211    let mut sho = Hash::<sha2::Sha256>::default();
212    let mut got = [0u8; 65];
213    sho.absorb(b"asd");
214    sho.ratchet();
215    sho.absorb(b"asdasd");
216    sho.squeeze(&mut got);
217    assert_eq!(&got, expected);
218
219    let expected = b"\x0D\xDE\xEA\x97\x3F\x32\x10\xF7\x72\x5A\x3C\xDB\x24\x73\
220    \xF8\x73\xAE\xAB\x8F\xEB\x32\xB8\x0D\xEE\x67\xF0\xCD\xE7\
221    \x95\x4E\x92\x9A\x4E\x78\x7A\xEF\xEE\x6D\xBE\x91\xD3\xFF\
222    \xF1\x62\x1A\xAB\x8D\x0D\x29\x19\x4F\x8A\xF9\x86\xD6\xF3\
223    \x57\xAD\xD0\x15\x0D\xF7\xD9";
224
225    let mut sho = Hash::<sha2::Sha256>::default();
226    let mut got = [0u8; 150];
227    sho.absorb(b"");
228    sho.ratchet();
229    sho.absorb(b"abc");
230    sho.ratchet();
231    sho.absorb(&[0u8; 63]);
232    sho.ratchet();
233    sho.absorb(&[0u8; 64]);
234    sho.ratchet();
235    sho.absorb(&[0u8; 65]);
236    sho.ratchet();
237    sho.absorb(&[0u8; 127]);
238    sho.ratchet();
239    sho.absorb(&[0u8; 128]);
240    sho.ratchet();
241    sho.absorb(&[0u8; 129]);
242    sho.ratchet();
243    sho.squeeze(&mut got[..63]);
244    // assert_eq!(&got[..63], &hex::decode("5bddc29ac27fd88bf682b07dd5c496b065f6ce11fd7aa77d1e13c609d77b9b2fed21b470f71a7f1fdfbfa895060c51302e782f440305d12ec85a492635dd3a").unwrap()[..]);
245    sho.squeeze_end();
246    sho.squeeze(&mut got[..64]);
247    // assert_eq!(&got[..64], &hex::decode("0ad17fc123d823548447b16ebebc8c21243dc4c59dd95525b7321c3b92a58e30156ec8c8e70987ed1483d2be84e89d2be5813fb1b8ab82119608120a2694a425").unwrap()[..]);
248    sho.squeeze_end();
249    sho.squeeze(&mut got[..65]);
250    sho.squeeze_end();
251    sho.squeeze(&mut got[..127]);
252    sho.squeeze_end();
253    sho.squeeze(&mut got[..128]);
254    sho.squeeze_end();
255    sho.squeeze(&mut got[..129]);
256    assert_eq!(got[0], 0xd0);
257    sho.absorb(b"def");
258    sho.ratchet();
259    sho.squeeze(&mut got[..63]);
260    assert_eq!(&got[..63], expected);
261}