bee_crypto/ternary/sponge/curlp/batched/mod.rs
1// Copyright 2020-2021 IOTA Stiftung
2// SPDX-License-Identifier: Apache-2.0
3
4//! A batched version of the `CurlP` hash.
5
6mod bct;
7mod bct_curlp;
8
9use crate::ternary::sponge::{CurlP, CurlPRounds, Sponge, HASH_LENGTH};
10
11use bct::{BcTrit, BcTritArr, BcTritBuf};
12use bct_curlp::BctCurlP;
13
14use bee_ternary::{
15 raw::{RawEncoding, RawEncodingBuf},
16 Btrit, T1B1Buf, TritBuf,
17};
18
19/// The number of inputs that can be processed in a single batch.
20pub const BATCH_SIZE: usize = 8 * std::mem::size_of::<usize>();
21const HIGH_BITS: usize = usize::max_value();
22
23/// A hasher that can process several inputs at the same time in batches.
24///
25/// This hasher works by interleaving the trits of the inputs in each batch and hashing this
26/// interleaved representation. It is also able to fall back to the regular CurlP algorithm if
27/// required.
28pub struct BatchHasher<B: RawEncodingBuf> {
29 /// The trits of the inputs before being interleaved.
30 trit_inputs: Vec<TritBuf<B>>,
31 /// An interleaved representation of the input trits.
32 bct_inputs: BcTritBuf,
33 /// An interleaved representation of the output trits.
34 bct_hashes: BcTritArr<HASH_LENGTH>,
35 /// A buffer for demultiplexing.
36 buf_demux: TritBuf,
37 /// The CurlP hasher for binary coded trits.
38 bct_curlp: BctCurlP,
39 /// The regular CurlP hasher.
40 curlp: CurlP,
41}
42
43impl<B> BatchHasher<B>
44where
45 B: RawEncodingBuf,
46 B::Slice: RawEncoding<Trit = Btrit>,
47{
48 /// Create a new hasher.
49 ///
50 /// It requires the length of the input, the length of the output hash and the number of
51 /// rounds.
52 pub fn new(input_length: usize, rounds: CurlPRounds) -> Self {
53 Self {
54 trit_inputs: Vec::with_capacity(BATCH_SIZE),
55 bct_inputs: BcTritBuf::zeros(input_length),
56 bct_hashes: BcTritArr::<HASH_LENGTH>::zeros(),
57 buf_demux: TritBuf::zeros(HASH_LENGTH),
58 bct_curlp: BctCurlP::new(rounds),
59 curlp: CurlP::new(rounds),
60 }
61 }
62
63 /// Add a new input to the batch.
64 ///
65 /// It panics if the size of the batch exceeds `BATCH_SIZE` or if `input.len()` is not equal to
66 /// the `input_length` parameter of the constructor.
67 pub fn add(&mut self, input: TritBuf<B>) {
68 assert!(self.trit_inputs.len() < BATCH_SIZE, "Batch is full.");
69 assert_eq!(input.len(), self.bct_inputs.len(), "Input has an incorrect size.");
70 self.trit_inputs.push(input);
71 }
72
73 /// Return the length of the current batch.
74 pub fn len(&self) -> usize {
75 self.trit_inputs.len()
76 }
77
78 /// Check if the current batch is empty.
79 pub fn is_empty(&self) -> bool {
80 self.len() == 0
81 }
82
83 /// Multiplex or interleave the input trits in the bash.
84 ///
85 /// Before doing the actual interleaving, each trit is encoded as two bits which are usually
86 /// refered as the low and high bit.
87 ///
88 /// | Trit | Low bit | High bit |
89 /// |------|---------|----------|
90 /// | -1 | 1 | 0 |
91 /// | 0 | 0 | 1 |
92 /// | 1 | 1 | 1 |
93 ///
94 /// Then the low and high bits are interleaved into two vectors of integers. Each integer has
95 /// size `BATCH_SIZE` and there are `input_length` integers in each vector. This means that
96 /// the low and high bits of the transaction number `N` in the batch are stored in the position
97 /// `N` of each integer.
98 ///
99 /// This step works correctly even if there are less than `BATCH_SIZE` inputs.
100 fn mux(&mut self) {
101 let count = self.trit_inputs.len();
102 for i in 0..self.bct_inputs.len() {
103 // This is safe because `i < self.bct_inputs.len()`.
104 let BcTrit(lo, hi) = unsafe { self.bct_inputs.get_unchecked_mut(i) };
105
106 for j in 0..count {
107 // this is safe because `j < self.trit_inputs.len()` and
108 // `i < self.trit_inputs[j].len()` (the `add` method guarantees that all the inputs
109 // have the same length as `self.trit_inputs`).
110 match unsafe { self.trit_inputs.get_unchecked(j).get_unchecked(i) } {
111 Btrit::NegOne => *lo |= 1 << j,
112 Btrit::PlusOne => *hi |= 1 << j,
113 Btrit::Zero => {
114 *lo |= 1 << j;
115 *hi |= 1 << j;
116 }
117 }
118 }
119 }
120 }
121
122 /// Demultiplex the bits of the output to obtain the hash of the input with a specific index.
123 ///
124 /// This is the inverse of the `mux` function, but it is applied over the vector with the
125 /// binary encoding of the output hashes. Each pair of low and high bits in the `bct_hashes`
126 /// field is decoded into a trit using the same convention as the `mux` step with an additional
127 /// rule for the `(0, 0)` pair of bits which is mapped to the `0` trit.
128 fn demux(&mut self, index: usize) -> TritBuf {
129 for (bc_trit, btrit) in self.bct_hashes.iter().zip(self.buf_demux.iter_mut()) {
130 let lo = (bc_trit.lo() >> index) & 1;
131 let hi = (bc_trit.hi() >> index) & 1;
132
133 *btrit = match (lo, hi) {
134 (1, 0) => Btrit::NegOne,
135 (0, 1) => Btrit::PlusOne,
136 // This can only be `(0, 0)` or `(1, 1)`.
137 _ => Btrit::Zero,
138 };
139 }
140
141 self.buf_demux.clone()
142 }
143
144 /// Hash the received inputs using the batched version of CurlP.
145 ///
146 /// This function also takes care of cleaning the buffers of the struct and resetting the
147 /// batched CurlP hasher so it can be called at any time.
148 pub fn hash_batched(&mut self) -> impl Iterator<Item = TritBuf> + '_ {
149 let total = self.trit_inputs.len();
150 // Reset batched CurlP hasher.
151 self.bct_curlp.reset();
152 // Multiplex the trits in `trits` and dump them into `inputs`
153 self.mux();
154 // Do the regular sponge steps.
155 self.bct_curlp.absorb(&self.bct_inputs);
156 self.bct_curlp.squeeze_into(&mut self.bct_hashes);
157 // Clear the `trits` buffer to allow receiving a new batch.
158 self.trit_inputs.clear();
159 // Fill the `inputs` buffer with zeros.
160 self.bct_inputs.fill(0);
161 // Return an iterator for the output hashes.
162 BatchedHashes {
163 hasher: self,
164 range: 0..total,
165 }
166 }
167
168 /// Hash the received inputs using the regular version of CurlP.
169 ///
170 /// In particular this function does not use the `bct_inputs` and `bct_hashes` buffers, takes
171 /// care of cleaning the `trit_inputs` buffer and resets the regular CurlP hasher so it can be
172 /// called at any time.
173 pub fn hash_unbatched(&mut self) -> impl Iterator<Item = TritBuf> + '_ {
174 self.curlp.reset();
175 UnbatchedHashes {
176 curl: &mut self.curlp,
177 trit_inputs: self.trit_inputs.drain(..),
178 }
179 }
180}
181
182/// A helper iterator type for the output of the `hash_batched` method.
183struct BatchedHashes<'a, B: RawEncodingBuf> {
184 hasher: &'a mut BatchHasher<B>,
185 range: std::ops::Range<usize>,
186}
187
188impl<'a, B> Iterator for BatchedHashes<'a, B>
189where
190 B: RawEncodingBuf,
191 B::Slice: RawEncoding<Trit = Btrit>,
192{
193 type Item = TritBuf;
194
195 fn next(&mut self) -> Option<Self::Item> {
196 let index = self.range.next()?;
197 Some(self.hasher.demux(index))
198 }
199}
200
201/// A helper iterator type for the output of the `hash_unbatched` method.
202struct UnbatchedHashes<'a, B: RawEncodingBuf> {
203 curl: &'a mut CurlP,
204 trit_inputs: std::vec::Drain<'a, TritBuf<B>>,
205}
206
207impl<'a, B> Iterator for UnbatchedHashes<'a, B>
208where
209 B: RawEncodingBuf,
210 B::Slice: RawEncoding<Trit = Btrit>,
211{
212 type Item = TritBuf;
213
214 fn next(&mut self) -> Option<Self::Item> {
215 let buf = self.trit_inputs.next()?;
216 // FIXME: Could we make regular `CurlP` generic too?`
217 Some(self.curl.digest(&buf.encode::<T1B1Buf>()).unwrap())
218 }
219}