1use alloc::boxed::Box;
32
33use oxicrypto_core::{Hash, StreamingHash};
34
35use crate::{
36 Blake3, Blake3Streaming, Sha256, Sha256Streaming, Sha384, Sha384Streaming, Sha3_256,
37 Sha3_256Streaming, Sha3_384, Sha3_384Streaming, Sha3_512, Sha3_512Streaming, Sha512,
38 Sha512Streaming, Sha512_256, Sha512_256Streaming,
39};
40
41#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45pub enum HashAlgorithm {
46 Sha256,
48 Sha384,
50 Sha512,
52 Sha512_256,
54 Sha3_256,
56 Sha3_384,
58 Sha3_512,
60 Blake3,
62}
63
64impl HashAlgorithm {
65 #[must_use]
67 pub const fn output_len(self) -> usize {
68 match self {
69 HashAlgorithm::Sha256
70 | HashAlgorithm::Sha512_256
71 | HashAlgorithm::Sha3_256
72 | HashAlgorithm::Blake3 => 32,
73 HashAlgorithm::Sha384 | HashAlgorithm::Sha3_384 => 48,
74 HashAlgorithm::Sha512 | HashAlgorithm::Sha3_512 => 64,
75 }
76 }
77}
78
79#[derive(Debug, Clone, Copy, PartialEq, Eq)]
85pub struct HashBuilder {
86 algorithm: HashAlgorithm,
87}
88
89impl HashBuilder {
90 #[must_use]
92 pub const fn new(algorithm: HashAlgorithm) -> Self {
93 Self { algorithm }
94 }
95
96 #[must_use]
98 pub const fn sha256() -> Self {
99 Self::new(HashAlgorithm::Sha256)
100 }
101
102 #[must_use]
104 pub const fn sha384() -> Self {
105 Self::new(HashAlgorithm::Sha384)
106 }
107
108 #[must_use]
110 pub const fn sha512() -> Self {
111 Self::new(HashAlgorithm::Sha512)
112 }
113
114 #[must_use]
116 pub const fn sha512_256() -> Self {
117 Self::new(HashAlgorithm::Sha512_256)
118 }
119
120 #[must_use]
122 pub const fn sha3_256() -> Self {
123 Self::new(HashAlgorithm::Sha3_256)
124 }
125
126 #[must_use]
128 pub const fn sha3_384() -> Self {
129 Self::new(HashAlgorithm::Sha3_384)
130 }
131
132 #[must_use]
134 pub const fn sha3_512() -> Self {
135 Self::new(HashAlgorithm::Sha3_512)
136 }
137
138 #[must_use]
140 pub const fn blake3() -> Self {
141 Self::new(HashAlgorithm::Blake3)
142 }
143
144 #[must_use]
146 pub const fn algorithm(&self) -> HashAlgorithm {
147 self.algorithm
148 }
149
150 #[must_use]
152 pub const fn streaming(self) -> StreamingHashBuilder {
153 StreamingHashBuilder {
154 algorithm: self.algorithm,
155 }
156 }
157
158 #[must_use]
160 pub fn build(self) -> Box<dyn Hash> {
161 match self.algorithm {
162 HashAlgorithm::Sha256 => Box::new(Sha256),
163 HashAlgorithm::Sha384 => Box::new(Sha384),
164 HashAlgorithm::Sha512 => Box::new(Sha512),
165 HashAlgorithm::Sha512_256 => Box::new(Sha512_256),
166 HashAlgorithm::Sha3_256 => Box::new(Sha3_256),
167 HashAlgorithm::Sha3_384 => Box::new(Sha3_384),
168 HashAlgorithm::Sha3_512 => Box::new(Sha3_512),
169 HashAlgorithm::Blake3 => Box::new(Blake3),
170 }
171 }
172}
173
174#[derive(Debug, Clone, Copy, PartialEq, Eq)]
179pub struct StreamingHashBuilder {
180 algorithm: HashAlgorithm,
181}
182
183impl StreamingHashBuilder {
184 #[must_use]
186 pub const fn new(algorithm: HashAlgorithm) -> Self {
187 Self { algorithm }
188 }
189
190 #[must_use]
192 pub const fn algorithm(&self) -> HashAlgorithm {
193 self.algorithm
194 }
195
196 #[must_use]
198 pub fn build(self) -> DynStreamingHash {
199 match self.algorithm {
200 HashAlgorithm::Sha256 => DynStreamingHash::Sha256(Sha256Streaming::new()),
201 HashAlgorithm::Sha384 => DynStreamingHash::Sha384(Sha384Streaming::new()),
202 HashAlgorithm::Sha512 => DynStreamingHash::Sha512(Sha512Streaming::new()),
203 HashAlgorithm::Sha512_256 => DynStreamingHash::Sha512_256(Sha512_256Streaming::new()),
204 HashAlgorithm::Sha3_256 => DynStreamingHash::Sha3_256(Sha3_256Streaming::new()),
205 HashAlgorithm::Sha3_384 => DynStreamingHash::Sha3_384(Sha3_384Streaming::new()),
206 HashAlgorithm::Sha3_512 => DynStreamingHash::Sha3_512(Sha3_512Streaming::new()),
207 HashAlgorithm::Blake3 => DynStreamingHash::Blake3(Box::default()),
208 }
209 }
210}
211
212pub enum DynStreamingHash {
219 Sha256(Sha256Streaming),
221 Sha384(Sha384Streaming),
223 Sha512(Sha512Streaming),
225 Sha512_256(Sha512_256Streaming),
227 Sha3_256(Sha3_256Streaming),
229 Sha3_384(Sha3_384Streaming),
231 Sha3_512(Sha3_512Streaming),
233 Blake3(Box<Blake3Streaming>),
238}
239
240impl StreamingHash for DynStreamingHash {
241 fn update(&mut self, data: &[u8]) {
242 match self {
243 DynStreamingHash::Sha256(h) => h.update(data),
244 DynStreamingHash::Sha384(h) => h.update(data),
245 DynStreamingHash::Sha512(h) => h.update(data),
246 DynStreamingHash::Sha512_256(h) => h.update(data),
247 DynStreamingHash::Sha3_256(h) => h.update(data),
248 DynStreamingHash::Sha3_384(h) => h.update(data),
249 DynStreamingHash::Sha3_512(h) => h.update(data),
250 DynStreamingHash::Blake3(h) => h.update(data),
251 }
252 }
253
254 fn finalize(self, out: &mut [u8]) -> Result<(), oxicrypto_core::CryptoError> {
255 match self {
256 DynStreamingHash::Sha256(h) => h.finalize(out),
257 DynStreamingHash::Sha384(h) => h.finalize(out),
258 DynStreamingHash::Sha512(h) => h.finalize(out),
259 DynStreamingHash::Sha512_256(h) => h.finalize(out),
260 DynStreamingHash::Sha3_256(h) => h.finalize(out),
261 DynStreamingHash::Sha3_384(h) => h.finalize(out),
262 DynStreamingHash::Sha3_512(h) => h.finalize(out),
263 DynStreamingHash::Blake3(h) => (*h).finalize(out),
264 }
265 }
266
267 fn reset(&mut self) {
268 match self {
269 DynStreamingHash::Sha256(h) => h.reset(),
270 DynStreamingHash::Sha384(h) => h.reset(),
271 DynStreamingHash::Sha512(h) => h.reset(),
272 DynStreamingHash::Sha512_256(h) => h.reset(),
273 DynStreamingHash::Sha3_256(h) => h.reset(),
274 DynStreamingHash::Sha3_384(h) => h.reset(),
275 DynStreamingHash::Sha3_512(h) => h.reset(),
276 DynStreamingHash::Blake3(h) => h.reset(),
277 }
278 }
279}
280
281impl DynStreamingHash {
282 #[must_use]
284 pub const fn algorithm(&self) -> HashAlgorithm {
285 match self {
286 DynStreamingHash::Sha256(_) => HashAlgorithm::Sha256,
287 DynStreamingHash::Sha384(_) => HashAlgorithm::Sha384,
288 DynStreamingHash::Sha512(_) => HashAlgorithm::Sha512,
289 DynStreamingHash::Sha512_256(_) => HashAlgorithm::Sha512_256,
290 DynStreamingHash::Sha3_256(_) => HashAlgorithm::Sha3_256,
291 DynStreamingHash::Sha3_384(_) => HashAlgorithm::Sha3_384,
292 DynStreamingHash::Sha3_512(_) => HashAlgorithm::Sha3_512,
293 DynStreamingHash::Blake3(_) => HashAlgorithm::Blake3,
294 }
295 }
296}
297
298impl core::fmt::Debug for DynStreamingHash {
299 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
300 f.debug_tuple("DynStreamingHash")
301 .field(&self.algorithm())
302 .finish()
303 }
304}
305
306#[cfg(test)]
307mod tests {
308 use super::*;
309 use crate::{Blake3, Sha256, Sha384, Sha3_256, Sha3_384, Sha3_512, Sha512, Sha512_256};
310
311 fn direct_hash(algo: HashAlgorithm, msg: &[u8]) -> alloc::vec::Vec<u8> {
313 match algo {
314 HashAlgorithm::Sha256 => Sha256.hash_to_vec(msg).unwrap(),
315 HashAlgorithm::Sha384 => Sha384.hash_to_vec(msg).unwrap(),
316 HashAlgorithm::Sha512 => Sha512.hash_to_vec(msg).unwrap(),
317 HashAlgorithm::Sha512_256 => Sha512_256.hash_to_vec(msg).unwrap(),
318 HashAlgorithm::Sha3_256 => Sha3_256.hash_to_vec(msg).unwrap(),
319 HashAlgorithm::Sha3_384 => Sha3_384.hash_to_vec(msg).unwrap(),
320 HashAlgorithm::Sha3_512 => Sha3_512.hash_to_vec(msg).unwrap(),
321 HashAlgorithm::Blake3 => Blake3.hash_to_vec(msg).unwrap(),
322 }
323 }
324
325 const ALL: [HashAlgorithm; 8] = [
326 HashAlgorithm::Sha256,
327 HashAlgorithm::Sha384,
328 HashAlgorithm::Sha512,
329 HashAlgorithm::Sha512_256,
330 HashAlgorithm::Sha3_256,
331 HashAlgorithm::Sha3_384,
332 HashAlgorithm::Sha3_512,
333 HashAlgorithm::Blake3,
334 ];
335
336 #[test]
337 fn builder_one_shot_matches_direct_api() {
338 let msg = b"The quick brown fox jumps over the lazy dog";
339 for algo in ALL {
340 let built = HashBuilder::new(algo).build();
341 let via_builder = built.hash_to_vec(msg).unwrap();
342 let direct = direct_hash(algo, msg);
343 assert_eq!(
344 via_builder, direct,
345 "builder one-shot must equal direct API for {algo:?}"
346 );
347 }
348 }
349
350 #[test]
351 fn builder_output_len_matches_trait() {
352 for algo in ALL {
353 let built = HashBuilder::new(algo).build();
354 assert_eq!(
355 built.output_len(),
356 algo.output_len(),
357 "HashAlgorithm::output_len must match trait output_len for {algo:?}"
358 );
359 }
360 }
361
362 #[test]
363 fn builder_streaming_matches_one_shot() {
364 let msg = b"streaming-vs-one-shot equivalence payload";
365 for algo in ALL {
366 let direct = direct_hash(algo, msg);
367
368 let mut streaming = HashBuilder::new(algo).streaming().build();
369 streaming.update(&msg[..7]);
371 streaming.update(&msg[7..20]);
372 streaming.update(&msg[20..]);
373
374 let mut out = alloc::vec![0u8; algo.output_len()];
375 streaming.finalize(out.as_mut_slice()).unwrap();
376
377 assert_eq!(
378 out, direct,
379 "builder streaming must equal one-shot for {algo:?}"
380 );
381 }
382 }
383
384 #[test]
385 fn builder_streaming_byte_at_a_time() {
386 let msg = b"abc";
387 for algo in ALL {
388 let direct = direct_hash(algo, msg);
389
390 let mut streaming = HashBuilder::new(algo).streaming().build();
391 for byte in msg {
392 streaming.update(core::slice::from_ref(byte));
393 }
394 let mut out = alloc::vec![0u8; algo.output_len()];
395 streaming.finalize(out.as_mut_slice()).unwrap();
396
397 assert_eq!(
398 out, direct,
399 "byte-at-a-time streaming must equal one-shot for {algo:?}"
400 );
401 }
402 }
403
404 #[test]
405 fn named_constructors_select_expected_algorithm() {
406 assert_eq!(HashBuilder::sha256().algorithm(), HashAlgorithm::Sha256);
407 assert_eq!(HashBuilder::sha384().algorithm(), HashAlgorithm::Sha384);
408 assert_eq!(HashBuilder::sha512().algorithm(), HashAlgorithm::Sha512);
409 assert_eq!(
410 HashBuilder::sha512_256().algorithm(),
411 HashAlgorithm::Sha512_256
412 );
413 assert_eq!(HashBuilder::sha3_256().algorithm(), HashAlgorithm::Sha3_256);
414 assert_eq!(HashBuilder::sha3_384().algorithm(), HashAlgorithm::Sha3_384);
415 assert_eq!(HashBuilder::sha3_512().algorithm(), HashAlgorithm::Sha3_512);
416 assert_eq!(HashBuilder::blake3().algorithm(), HashAlgorithm::Blake3);
417 }
418
419 #[test]
420 fn streaming_preserves_algorithm() {
421 for algo in ALL {
422 let b = HashBuilder::new(algo).streaming();
423 assert_eq!(b.algorithm(), algo);
424 }
425 }
426
427 #[test]
428 fn fluent_sha256_example_round_trips() {
429 let hasher = HashBuilder::sha256().build();
431 let digest = hasher.hash_to_vec(b"abc").unwrap();
432
433 let mut streaming = HashBuilder::sha256().streaming().build();
434 streaming.update(b"a");
435 streaming.update(b"bc");
436 let mut out = [0u8; 32];
437 streaming.finalize(out.as_mut_slice()).unwrap();
438
439 assert_eq!(&out[..], digest.as_slice());
440 }
441}