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            // Encode the index as a fixed-width `u64` (not `usize`) so the absorbed
91            // bytes are identical on 32- and 64-bit targets; otherwise a 64-bit
92            // prover and a 32-bit verifier (e.g. wasm32) derive different outputs.
93            let mut output_hasher_prefix = self.hasher.clone();
94            Digest::update(&mut output_hasher_prefix, (i as u64).to_be_bytes());
95            let digest = output_hasher_prefix.finalize();
96            // Copy the digest into the output, and store the rest for later
97            let chunk_len = usize::min(output.len(), Self::DIGEST_SIZE);
98            output[..chunk_len].copy_from_slice(&digest[..chunk_len]);
99            self.leftovers.extend_from_slice(&digest[chunk_len..]);
100            // Update the state
101            self.mode = Mode::Squeeze(i + 1);
102            self.squeeze(&mut output[chunk_len..])
103        } else {
104            unreachable!()
105        }
106    }
107}
108
109#[derive(Clone, PartialEq, Eq)]
110enum Mode {
111    Start,
112    Absorb,
113    Squeeze(usize),
114}
115
116impl<D: BlockSizeUser + Digest + Clone + Reset> Hash<D> {
117    const BLOCK_SIZE: usize = D::BlockSize::USIZE;
118    const DIGEST_SIZE: usize = D::OutputSize::USIZE;
119
120    /// Create a block
121    /// | start | 0000 0000 | end |
122    fn pad_block(start: &[u8], end: &[u8]) -> Block<D> {
123        debug_assert!(start.len() + end.len() < Self::BLOCK_SIZE);
124        let mut mask = Block::<D>::default();
125        mask[..start.len()].copy_from_slice(start);
126        mask[Self::BLOCK_SIZE - end.len()..].copy_from_slice(end);
127        mask
128    }
129
130    fn mask_absorb() -> Block<D> {
131        Self::pad_block(&[], &[0x00])
132    }
133
134    fn mask_squeeze() -> Block<D> {
135        Self::pad_block(&[], &[0x01])
136    }
137
138    fn mask_squeeze_end() -> Block<D> {
139        Self::pad_block(&[], &[0x02])
140    }
141
142    fn squeeze_end(&mut self) {
143        if let Mode::Squeeze(count) = self.mode {
144            Digest::reset(&mut self.hasher);
145
146            // append to the state the squeeze mask
147            // with the length of the data read so far
148            // and the current digest
149            let byte_count = count * Self::DIGEST_SIZE - self.leftovers.len();
150            let mut squeeze_hasher = D::new();
151            Digest::update(&mut squeeze_hasher, Self::mask_squeeze_end());
152            Digest::update(&mut squeeze_hasher, &self.cv);
153            // Fixed-width `u64` encoding for cross-architecture portability; see the
154            // matching note in `squeeze`.
155            Digest::update(&mut squeeze_hasher, (byte_count as u64).to_be_bytes());
156            self.cv = Digest::finalize(squeeze_hasher);
157
158            // set the sponge state in absorb mode
159            self.mode = Mode::Start;
160            self.leftovers.clear();
161        }
162    }
163}
164
165#[cfg(feature = "zeroize")]
166impl<D: Clone + Digest + Reset + BlockSizeUser> Zeroize for Hash<D> {
167    fn zeroize(&mut self) {
168        self.cv.zeroize();
169        Digest::reset(&mut self.hasher);
170    }
171}
172
173#[cfg(feature = "zeroize")]
174impl<D: Clone + Digest + Reset + BlockSizeUser> Drop for Hash<D> {
175    fn drop(&mut self) {
176        self.zeroize();
177    }
178}
179
180impl<D: BlockSizeUser + Digest + Clone + FixedOutputReset> Default for Hash<D> {
181    fn default() -> Self {
182        Self {
183            hasher: D::new(),
184            cv: Output::<D>::default(),
185            mode: Mode::Start,
186            leftovers: Vec::new(),
187        }
188    }
189}
190
191#[cfg(all(test, feature = "sha2"))]
192#[test]
193fn test_shosha() {
194    let expected = b"\xEB\xE4\xEF\x29\xE1\x8A\xA5\x41\x37\xED\xD8\x9C\x23\xF8\
195    \xBF\xEA\xC2\x73\x1C\x9F\x67\x5D\xA2\x0E\x7C\x67\xD5\xAD\
196    \x68\xD7\xEE\x2D\x40\xA4\x52\x32\xB5\x99\x55\x2D\x46\xB5\
197    \x20\x08\x2F\xB2\x70\x59\x71\xF0\x7B\x31\x58\xB0\x72\xB6\
198    \x3A\xB0\x93\x4A\x05\xE6\xAF\x64";
199    let mut sho = Hash::<sha2::Sha256>::default();
200    let mut got = [0u8; 64];
201    sho.absorb(b"asd");
202    sho.ratchet();
203    // streaming absorb
204    sho.absorb(b"asd");
205    sho.absorb(b"asd");
206    // streaming squeeze
207    sho.squeeze(&mut got[..32]);
208    sho.squeeze(&mut got[32..]);
209    assert_eq!(&got, expected);
210
211    let expected = b"\xEB\xE4\xEF\x29\xE1\x8A\xA5\x41\x37\xED\xD8\x9C\x23\xF8\
212    \xBF\xEA\xC2\x73\x1C\x9F\x67\x5D\xA2\x0E\x7C\x67\xD5\xAD\
213    \x68\xD7\xEE\x2D\x40\xA4\x52\x32\xB5\x99\x55\x2D\x46\xB5\
214    \x20\x08\x2F\xB2\x70\x59\x71\xF0\x7B\x31\x58\xB0\x72\xB6\
215    \x3A\xB0\x93\x4A\x05\xE6\xAF\x64\x48";
216    let mut sho = Hash::<sha2::Sha256>::default();
217    let mut got = [0u8; 65];
218    sho.absorb(b"asd");
219    sho.ratchet();
220    sho.absorb(b"asdasd");
221    sho.squeeze(&mut got);
222    assert_eq!(&got, expected);
223
224    let expected = b"\x0D\xDE\xEA\x97\x3F\x32\x10\xF7\x72\x5A\x3C\xDB\x24\x73\
225    \xF8\x73\xAE\xAB\x8F\xEB\x32\xB8\x0D\xEE\x67\xF0\xCD\xE7\
226    \x95\x4E\x92\x9A\x4E\x78\x7A\xEF\xEE\x6D\xBE\x91\xD3\xFF\
227    \xF1\x62\x1A\xAB\x8D\x0D\x29\x19\x4F\x8A\xF9\x86\xD6\xF3\
228    \x57\xAD\xD0\x15\x0D\xF7\xD9";
229
230    let mut sho = Hash::<sha2::Sha256>::default();
231    let mut got = [0u8; 150];
232    sho.absorb(b"");
233    sho.ratchet();
234    sho.absorb(b"abc");
235    sho.ratchet();
236    sho.absorb(&[0u8; 63]);
237    sho.ratchet();
238    sho.absorb(&[0u8; 64]);
239    sho.ratchet();
240    sho.absorb(&[0u8; 65]);
241    sho.ratchet();
242    sho.absorb(&[0u8; 127]);
243    sho.ratchet();
244    sho.absorb(&[0u8; 128]);
245    sho.ratchet();
246    sho.absorb(&[0u8; 129]);
247    sho.ratchet();
248    sho.squeeze(&mut got[..63]);
249    // assert_eq!(&got[..63], &hex::decode("5bddc29ac27fd88bf682b07dd5c496b065f6ce11fd7aa77d1e13c609d77b9b2fed21b470f71a7f1fdfbfa895060c51302e782f440305d12ec85a492635dd3a").unwrap()[..]);
250    sho.squeeze_end();
251    sho.squeeze(&mut got[..64]);
252    // assert_eq!(&got[..64], &hex::decode("0ad17fc123d823548447b16ebebc8c21243dc4c59dd95525b7321c3b92a58e30156ec8c8e70987ed1483d2be84e89d2be5813fb1b8ab82119608120a2694a425").unwrap()[..]);
253    sho.squeeze_end();
254    sho.squeeze(&mut got[..65]);
255    sho.squeeze_end();
256    sho.squeeze(&mut got[..127]);
257    sho.squeeze_end();
258    sho.squeeze(&mut got[..128]);
259    sho.squeeze_end();
260    sho.squeeze(&mut got[..129]);
261    assert_eq!(got[0], 0xd0);
262    sho.absorb(b"def");
263    sho.ratchet();
264    sho.squeeze(&mut got[..63]);
265    assert_eq!(&got[..63], expected);
266}