Skip to main content

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}