Skip to main content

bash_prg_hash/
lib.rs

1#![no_std]
2#![doc = include_str!("../README.md")]
3#![doc(
4    html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/6ee8e381/logo.svg",
5    html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/6ee8e381/logo.svg"
6)]
7#![cfg_attr(docsrs, feature(doc_cfg))]
8#![warn(missing_docs, rust_2018_idioms)]
9#![forbid(unsafe_code)]
10
11pub use digest::{self, Digest};
12
13mod variants;
14pub use variants::*;
15
16use bash_f::{STATE_WORDS, bash_f};
17use core::fmt;
18use digest::{ExtendableOutput, TryCustomizedInit, Update, XofReader, common::AlgorithmName};
19use sponge_cursor::SpongeCursor;
20
21/// Data type codes from Table 3 of STB 34.101.77-2020
22const DATA: u8 = 0b000010;
23/// Data type codes from Table 3 of STB 34.101.77-2020
24const OUT: u8 = 0b000100;
25
26/// `bash-prg-hash` hasher generic over rate and capacity.
27///
28/// # Generic Parameters
29///
30/// Only the following combinations of rate and capacity with
31/// the resulting security level are supported:
32///
33/// | Rate, bytes | Capacity | Security level, bits |       Variant       |
34/// |:-----------:|:--------:|:--------------------:|:-------------------:|
35/// |     160     |     1    |          128         | [`BashPrgHash1281`] |
36/// |     128     |     2    |          128         | [`BashPrgHash1282`] |
37/// |     144     |     1    |          192         | [`BashPrgHash1921`] |
38/// |      96     |     2    |          192         | [`BashPrgHash1922`] |
39/// |     128     |     1    |          256         | [`BashPrgHash2561`] |
40/// |      64     |     2    |          256         | [`BashPrgHash2562`] |
41///
42/// Trying to initialize hasher state with a different pair of parameters will
43/// result in a compilation error.
44///
45/// Users are recommended to use type aliases referenced in the last column instead of using
46/// this type directly.
47// Note: Ideally, we would use `LEVEL` instead of `RATE` and define the `cursor` field as
48// `SpongeCursor<{192-2*CAPACITY*LEVEL}>`, but it requires stabilized `generic_const_exprs`.
49#[derive(Clone)]
50pub struct BashPrgHash<const RATE: usize, const CAPACITY: usize> {
51    state: [u64; STATE_WORDS],
52    cursor: SpongeCursor<RATE>,
53}
54
55impl<const RATE: usize, const CAPACITY: usize> Default for BashPrgHash<RATE, CAPACITY> {
56    #[inline]
57    fn default() -> Self {
58        Self::try_new_customized(&[]).expect("Always correct")
59    }
60}
61
62impl<const RATE: usize, const CAPACITY: usize> TryCustomizedInit for BashPrgHash<RATE, CAPACITY> {
63    type Error = InvalidHeaderError;
64
65    #[inline]
66    fn try_new_customized(header: &[u8]) -> Result<Self, Self::Error> {
67        const {
68            assert!(
69                matches!(
70                    (RATE, CAPACITY),
71                    (160, 1) | (128, 2) | (144, 1) | (96, 2) | (128, 1) | (64, 2)
72                ),
73                "invalid combination of RATE and CAPACITY"
74            )
75        }
76
77        const MAX_HEADER_LEN: usize = 60;
78
79        let header_len = header.len();
80        if header_len > MAX_HEADER_LEN || header_len % 4 != 0 {
81            return Err(InvalidHeaderError);
82        }
83
84        // `start[ℓ, 𝑑](𝐴, 𝐾)` (Section 8.3.2)
85
86        // Step 3: pos <- 8 + |A| + |K| (in bits), i.e. 1 + |A| + |K| (in bytes).
87        let pos = 1 + header_len;
88
89        // Step 4: S[...pos) <- <|A|/2 + |K|/32>_8 || A || K.
90        // Step 5: S[pos...1472) <- 0^{1472-pos}
91
92        let mut buf = [0u8; 8 * STATE_WORDS];
93        buf[0] = u8::try_from(header_len * 4).expect("header_len fits into u8");
94        buf[1..][..header_len].copy_from_slice(header);
95
96        let mut state = [0u64; STATE_WORDS];
97        let chunks = buf.chunks_exact(8);
98        assert!(chunks.remainder().is_empty());
99        for (dst, chunk) in state.iter_mut().zip(chunks) {
100            let chunk = chunk.try_into().expect("chunk has correct size");
101            *dst = u64::from_le_bytes(chunk);
102        }
103
104        // Step 6: S[1472...) <- <ℓ/4 + d>_64
105        let level = (192 - RATE) / (2 * CAPACITY);
106        state[23] = u64::try_from(level * 2 + CAPACITY).expect("the value always fits into u64");
107
108        // `commit[ℓ, d](DATA)` (Section 8.4.2)
109
110        let word_pos = pos / 8;
111        let byte_pos = pos % 8;
112
113        // Step 1: S[pos...pos+8) <- S[pos...pos+8) ⊕ (t || 01).
114        const DATA_PAD: u8 = (DATA << 2) | 0x01;
115        state[word_pos] ^= u64::from(DATA_PAD) << (8 * byte_pos);
116
117        // Step 2: S[r] <- S[r] ⊕ 1, where r = 1536 - 2 d ℓ (bit index).
118        const { assert!(RATE % 8 == 0) }
119        state[RATE / 8] ^= 1u64 << 7;
120
121        // Step 3: S <- bash-f(S).
122        bash_f(&mut state);
123
124        Ok(Self {
125            state,
126            // Step 4: pos <- 0.
127            cursor: Default::default(),
128        })
129    }
130}
131
132impl<const RATE: usize, const CAPACITY: usize> BashPrgHash<RATE, CAPACITY> {}
133
134impl<const RATE: usize, const CAPACITY: usize> Update for BashPrgHash<RATE, CAPACITY> {
135    #[inline]
136    fn update(&mut self, data: &[u8]) {
137        // `absorb[ℓ, d](X)` (Section 8.6.2)
138        self.cursor.absorb_u64_le(&mut self.state, bash_f, data);
139    }
140}
141
142impl<const RATE: usize, const CAPACITY: usize> ExtendableOutput for BashPrgHash<RATE, CAPACITY> {
143    type Reader = BashPrgHashReader<RATE, CAPACITY>;
144
145    #[inline]
146    fn finalize_xof(mut self) -> Self::Reader {
147        let pos = self.cursor.pos();
148        let word_pos = pos / 8;
149        let byte_pos = pos % 8;
150
151        // Step 1: S[pos...pos+8) <- S[pos...pos+8) ⊕ (t || 01).
152        const OUT_PAD: u8 = (OUT << 2) | 0x01;
153        self.state[word_pos] ^= u64::from(OUT_PAD) << (8 * byte_pos);
154
155        // Step 2: S[r] <- S[r] ⊕ 1, where r = 1536 - 2 d ℓ (bit index).
156        const { assert!(RATE % 8 == 0) }
157        self.state[RATE / 8] ^= 1u64 << 7;
158
159        BashPrgHashReader {
160            state: self.state,
161            cursor: Default::default(),
162        }
163    }
164}
165
166impl<const RATE: usize, const CAPACITY: usize> fmt::Debug for BashPrgHash<RATE, CAPACITY> {
167    #[inline]
168    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
169        let level = 8 * (192 - RATE) / (2 * CAPACITY);
170        write!(f, "BashPrgHash{level}{CAPACITY} {{ ... }}")
171    }
172}
173
174impl<const RATE: usize, const CAPACITY: usize> AlgorithmName for BashPrgHash<RATE, CAPACITY> {
175    #[inline]
176    fn write_alg_name(f: &mut fmt::Formatter<'_>) -> fmt::Result {
177        let level = 8 * (192 - RATE) / (2 * CAPACITY);
178        write!(f, "BashPrgHash{level}{CAPACITY}")
179    }
180}
181
182impl<const RATE: usize, const CAPACITY: usize> Drop for BashPrgHash<RATE, CAPACITY> {
183    #[inline]
184    fn drop(&mut self) {
185        #[cfg(feature = "zeroize")]
186        {
187            use digest::zeroize::Zeroize;
188            self.state.zeroize();
189            self.cursor.zeroize();
190        }
191    }
192}
193
194#[cfg(feature = "zeroize")]
195impl<const RATE: usize, const CAPACITY: usize> digest::zeroize::ZeroizeOnDrop
196    for BashPrgHash<RATE, CAPACITY>
197{
198}
199
200/// Reader for bash-prg-hash XOF output.
201#[derive(Clone)]
202pub struct BashPrgHashReader<const RATE: usize, const CAPACITY: usize> {
203    state: [u64; STATE_WORDS],
204    cursor: SpongeCursor<RATE>,
205}
206
207impl<const RATE: usize, const CAPACITY: usize> XofReader for BashPrgHashReader<RATE, CAPACITY> {
208    #[inline]
209    fn read(&mut self, buf: &mut [u8]) {
210        self.cursor
211            .squeeze_read_u64_le(&mut self.state, bash_f, buf);
212    }
213}
214
215impl<const RATE: usize, const CAPACITY: usize> Drop for BashPrgHashReader<RATE, CAPACITY> {
216    #[inline]
217    fn drop(&mut self) {
218        #[cfg(feature = "zeroize")]
219        {
220            use digest::zeroize::Zeroize;
221            self.state.zeroize();
222            self.cursor.zeroize();
223        }
224    }
225}
226
227#[cfg(feature = "zeroize")]
228impl<const RATE: usize, const CAPACITY: usize> digest::zeroize::ZeroizeOnDrop
229    for BashPrgHashReader<RATE, CAPACITY>
230{
231}
232
233/// Invalid `bash-prg-hash` header error.
234#[derive(Debug)]
235pub struct InvalidHeaderError;
236
237impl fmt::Display for InvalidHeaderError {
238    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
239        f.write_str(
240            "Invalid `bash-prg-hash` header. \
241            Header length must be a multiple of 4 bytes and not greater than 60 bytes.",
242        )
243    }
244}
245
246impl core::error::Error for InvalidHeaderError {}