tiny_keccak/
parallel_hash.rs

1use crate::{left_encode, right_encode, CShake, Hasher, IntoXof, Xof};
2
3#[derive(Clone)]
4struct UnfinishedState {
5    state: CShake,
6    absorbed: usize,
7}
8
9struct Suboutout {
10    state: [u8; 64],
11    size: usize,
12}
13
14impl Suboutout {
15    fn security(bits: usize) -> Suboutout {
16        Suboutout {
17            state: [0u8; 64],
18            // 128 => 32, 256 => 64
19            size: bits / 4,
20        }
21    }
22
23    #[inline]
24    fn as_bytes(&self) -> &[u8] {
25        &self.state[..self.size]
26    }
27
28    #[inline]
29    fn as_bytes_mut(&mut self) -> &mut [u8] {
30        &mut self.state[..self.size]
31    }
32}
33
34/// The `ParallelHash` hash functions defined in [`SP800-185`].
35///
36/// The purpose of `ParallelHash` is to support the efficient hashing of very long strings, by
37/// taking advantage of the parallelism available in modern processors. `ParallelHash` supports the
38/// [`128-bit`] and [`256-bit`] security strengths, and also provides variable-length output.
39///
40/// # Usage
41///
42/// ```toml
43/// [dependencies]
44/// tiny-keccak = { version = "2.0.0", features = ["parallel_hash"] }
45/// ```
46///
47/// [`SP800-185`]: https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-185.pdf
48/// [`128-bit`]: struct.ParallelHash.html#method.v128
49/// [`256-bit`]: struct.ParallelHash.html#method.v256
50#[derive(Clone)]
51pub struct ParallelHash {
52    state: CShake,
53    block_size: usize,
54    bits: usize,
55    blocks: usize,
56    unfinished: Option<UnfinishedState>,
57}
58
59impl ParallelHash {
60    /// Creates  new [`ParallelHash`] hasher with a security level of 128 bits.
61    ///
62    /// [`ParallelHash`]: struct.ParallelHash.html
63    pub fn v128(custom_string: &[u8], block_size: usize) -> ParallelHash {
64        ParallelHash::new(custom_string, block_size, 128)
65    }
66
67    /// Creates  new [`ParallelHash`] hasher with a security level of 256 bits.
68    ///
69    /// [`ParallelHash`]: struct.ParallelHash.html
70    pub fn v256(custom_string: &[u8], block_size: usize) -> ParallelHash {
71        ParallelHash::new(custom_string, block_size, 256)
72    }
73
74    fn new(custom_string: &[u8], block_size: usize, bits: usize) -> ParallelHash {
75        let mut state = CShake::new(b"ParallelHash", custom_string, bits);
76        state.update(left_encode(block_size).value());
77        ParallelHash {
78            state,
79            block_size,
80            bits,
81            blocks: 0,
82            unfinished: None,
83        }
84    }
85}
86
87impl Hasher for ParallelHash {
88    fn update(&mut self, mut input: &[u8]) {
89        if let Some(mut unfinished) = self.unfinished.take() {
90            let to_absorb = self.block_size - unfinished.absorbed;
91            if input.len() >= to_absorb {
92                unfinished.state.update(&input[..to_absorb]);
93                input = &input[to_absorb..];
94
95                let mut suboutput = Suboutout::security(self.bits);
96                unfinished.state.finalize(suboutput.as_bytes_mut());
97                self.state.update(suboutput.as_bytes());
98                self.blocks += 1;
99            } else {
100                unfinished.state.update(input);
101                unfinished.absorbed += input.len();
102                self.unfinished = Some(unfinished);
103                return;
104            }
105        }
106
107        let bits = self.bits;
108        let input_blocks_end = input.len() / self.block_size * self.block_size;
109        let input_blocks = &input[..input_blocks_end];
110        let input_end = &input[input_blocks_end..];
111        let parts = input_blocks.chunks(self.block_size).map(|chunk| {
112            let mut state = CShake::new(b"", b"", bits);
113            state.update(chunk);
114            let mut suboutput = Suboutout::security(bits);
115            state.finalize(suboutput.as_bytes_mut());
116            suboutput
117        });
118
119        for part in parts {
120            self.state.update(part.as_bytes());
121            self.blocks += 1;
122        }
123
124        if !input_end.is_empty() {
125            assert!(self.unfinished.is_none());
126            let mut state = CShake::new(b"", b"", bits);
127            state.update(input_end);
128            self.unfinished = Some(UnfinishedState {
129                state,
130                absorbed: input_end.len(),
131            });
132        }
133    }
134
135    fn finalize(mut self, output: &mut [u8]) {
136        if let Some(unfinished) = self.unfinished.take() {
137            let mut suboutput = Suboutout::security(self.bits);
138            unfinished.state.finalize(suboutput.as_bytes_mut());
139            self.state.update(suboutput.as_bytes());
140            self.blocks += 1;
141        }
142
143        self.state.update(right_encode(self.blocks).value());
144        self.state.update(right_encode(output.len() * 8).value());
145        self.state.finalize(output);
146    }
147}
148
149/// The `ParallelHashXOF` extendable-output functions defined in [`SP800-185`].
150///
151/// # Usage
152///
153/// ```toml
154/// [dependencies]
155/// tiny-keccak = { version = "2.0.0", features = ["parallel_hash"] }
156/// ```
157///
158/// # Example
159///
160/// ```
161/// # use tiny_keccak::{ParallelHash, Xof, IntoXof, Hasher};
162/// let input = b"hello world";
163/// let mut output = [0u8; 64];
164/// let mut hasher = ParallelHash::v256(b"", 8);
165/// hasher.update(input);
166/// let mut xof = hasher.into_xof();
167/// xof.squeeze(&mut output[..32]);
168/// xof.squeeze(&mut output[32..]);
169/// ```
170///
171/// ---
172///
173/// [`ParallelHashXof`] can be created only by using [`ParallelHash::IntoXof`] interface.
174///
175///
176/// [`SP800-185`]: https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-185.pdf
177/// [`ParallelHashXof`]: struct.ParallelHashXof.html
178/// [`ParallelHash::IntoXof`]: struct.ParallelHash.html#impl-IntoXof
179#[derive(Clone)]
180pub struct ParallelHashXof {
181    state: CShake,
182}
183
184impl IntoXof for ParallelHash {
185    type Xof = ParallelHashXof;
186
187    fn into_xof(mut self) -> Self::Xof {
188        if let Some(unfinished) = self.unfinished.take() {
189            let mut suboutput = Suboutout::security(self.bits);
190            unfinished.state.finalize(suboutput.as_bytes_mut());
191            self.state.update(suboutput.as_bytes());
192            self.blocks += 1;
193        }
194
195        self.state.update(right_encode(self.blocks).value());
196        self.state.update(right_encode(0).value());
197
198        ParallelHashXof { state: self.state }
199    }
200}
201
202impl Xof for ParallelHashXof {
203    fn squeeze(&mut self, output: &mut [u8]) {
204        self.state.squeeze(output);
205    }
206}