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
21const DATA: u8 = 0b000010;
23const OUT: u8 = 0b000100;
25
26#[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 let pos = 1 + header_len;
88
89 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 let level = (192 - RATE) / (2 * CAPACITY);
106 state[23] = u64::try_from(level * 2 + CAPACITY).expect("the value always fits into u64");
107
108 let word_pos = pos / 8;
111 let byte_pos = pos % 8;
112
113 const DATA_PAD: u8 = (DATA << 2) | 0x01;
115 state[word_pos] ^= u64::from(DATA_PAD) << (8 * byte_pos);
116
117 const { assert!(RATE % 8 == 0) }
119 state[RATE / 8] ^= 1u64 << 7;
120
121 bash_f(&mut state);
123
124 Ok(Self {
125 state,
126 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 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 const OUT_PAD: u8 = (OUT << 2) | 0x01;
153 self.state[word_pos] ^= u64::from(OUT_PAD) << (8 * byte_pos);
154
155 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#[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#[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 {}