Skip to main content

oxicrypto_hash/
lib.rs

1#![forbid(unsafe_code)]
2#![no_std]
3
4//! Pure Rust hash function implementations for the OxiCrypto stack.
5//!
6//! Provides [`Hash`]-trait wrappers and [`StreamingHash`] adapters for:
7//! - SHA-2: SHA-256, SHA-384, SHA-512, SHA-512/256 (FIPS 180-4)
8//! - SHA-3: SHA3-256, SHA3-384, SHA3-512 (FIPS 202)
9//! - BLAKE2: BLAKE2b-256, BLAKE2b-512, BLAKE2s-256 (RFC 7693)
10//! - BLAKE3: standard, keyed-hash, key-derivation, XOF (blake3 spec)
11//! - SHAKE128/256, cSHAKE128/256, TupleHash128/256 (NIST SP 800-185)
12//! - BLAKE2b keyed-hash mode (RFC 7693 MAC mode)
13//!
14//! All streaming adapters implement [`StreamingHash`] via the generic
15//! [`DigestStreamingAdapter`] wrapper which works with any `digest::Digest +
16//! Default` type.
17
18// `alloc` is required for `Vec`-returning functions such as `hash_to_vec`,
19// `blake3_xof`, and `parallel_hash*_xof`. When the `no_std` feature is
20// enabled, callers should prefer the alloc-free `hash_fixed` / `hash_to_array`
21// paths (see feature documentation in Cargo.toml).
22//
23// This crate always links `alloc` because the sub-modules (parallelhash, xof,
24// hash_builder) depend on it internally. The `no_std` feature flag serves as an
25// API-guidance signal rather than a link-time exclusion: it documents which
26// methods are suitable for embedded / alloc-free callers.
27extern crate alloc;
28
29#[cfg(feature = "std")]
30extern crate std;
31
32mod hash_builder;
33mod parallelhash;
34mod xof;
35pub use hash_builder::{DynStreamingHash, HashAlgorithm, HashBuilder, StreamingHashBuilder};
36pub use parallelhash::{
37    parallel_hash128, parallel_hash128_xof, parallel_hash256, parallel_hash256_xof,
38    ParallelHash128, ParallelHash256,
39};
40pub use xof::{
41    blake2b_keyed, cshake128, cshake256, shake128, shake128_start, shake256, shake256_start,
42    tuple_hash128, tuple_hash256, Blake2bKeyed, Shake128Reader, Shake256Reader,
43};
44
45#[cfg(feature = "std")]
46pub use xof::{hash_file_blake3, hash_file_sha256, hash_file_sha512};
47
48use alloc::vec::Vec;
49
50use digest::Digest;
51use oxicrypto_core::{CryptoError, Hash, StreamingHash};
52
53// ── Generic streaming adapter ────────────────────────────────────────────────
54
55/// Generic streaming adapter wrapping any `digest::Digest + Default` type.
56///
57/// Implements [`StreamingHash`] for all `digest 0.11`-compatible hash
58/// functions (SHA-2, SHA-3, BLAKE2). BLAKE3 uses a separate impl.
59pub struct DigestStreamingAdapter<D: Digest + Default> {
60    inner: D,
61}
62
63impl<D: Digest + Default> DigestStreamingAdapter<D> {
64    /// Create a new adapter in the initial (empty) state.
65    pub fn new() -> Self {
66        Self {
67            inner: D::default(),
68        }
69    }
70}
71
72impl<D: Digest + Default> Default for DigestStreamingAdapter<D> {
73    fn default() -> Self {
74        Self::new()
75    }
76}
77
78impl<D: Digest + Default + Clone> Clone for DigestStreamingAdapter<D> {
79    fn clone(&self) -> Self {
80        Self {
81            inner: self.inner.clone(),
82        }
83    }
84}
85
86impl<D: Digest + Default + Send> StreamingHash for DigestStreamingAdapter<D> {
87    fn update(&mut self, data: &[u8]) {
88        Digest::update(&mut self.inner, data);
89    }
90
91    fn finalize(self, out: &mut [u8]) -> Result<(), CryptoError> {
92        let result = Digest::finalize(self.inner);
93        if out.len() < result.len() {
94            return Err(CryptoError::BufferTooSmall);
95        }
96        out[..result.len()].copy_from_slice(&result);
97        Ok(())
98    }
99
100    fn reset(&mut self) {
101        self.inner = D::default();
102    }
103}
104
105// ── SHA-2 one-shot ───────────────────────────────────────────────────────────
106
107/// SHA-256 hash function (32-byte output, FIPS 180-4 §6.2).
108#[derive(Debug, Default, Clone, Copy)]
109pub struct Sha256;
110
111/// SHA-384 hash function (48-byte output, FIPS 180-4 §6.5).
112#[derive(Debug, Default, Clone, Copy)]
113pub struct Sha384;
114
115/// SHA-512 hash function (64-byte output, FIPS 180-4 §6.4).
116#[derive(Debug, Default, Clone, Copy)]
117pub struct Sha512;
118
119/// SHA-512/256 truncated hash function (32-byte output, FIPS 180-4 §6.7).
120#[derive(Debug, Default, Clone, Copy)]
121pub struct Sha512_256;
122
123impl Hash for Sha256 {
124    fn name(&self) -> &'static str {
125        "SHA-256"
126    }
127    fn output_len(&self) -> usize {
128        32
129    }
130    fn hash(&self, msg: &[u8], out: &mut [u8]) -> Result<(), CryptoError> {
131        if out.len() < 32 {
132            return Err(CryptoError::BufferTooSmall);
133        }
134        let digest = sha2::Sha256::digest(msg);
135        out[..32].copy_from_slice(&digest);
136        Ok(())
137    }
138}
139
140impl Hash for Sha384 {
141    fn name(&self) -> &'static str {
142        "SHA-384"
143    }
144    fn output_len(&self) -> usize {
145        48
146    }
147    fn hash(&self, msg: &[u8], out: &mut [u8]) -> Result<(), CryptoError> {
148        if out.len() < 48 {
149            return Err(CryptoError::BufferTooSmall);
150        }
151        let digest = sha2::Sha384::digest(msg);
152        out[..48].copy_from_slice(&digest);
153        Ok(())
154    }
155}
156
157impl Hash for Sha512 {
158    fn name(&self) -> &'static str {
159        "SHA-512"
160    }
161    fn output_len(&self) -> usize {
162        64
163    }
164    fn hash(&self, msg: &[u8], out: &mut [u8]) -> Result<(), CryptoError> {
165        if out.len() < 64 {
166            return Err(CryptoError::BufferTooSmall);
167        }
168        let digest = sha2::Sha512::digest(msg);
169        out[..64].copy_from_slice(&digest);
170        Ok(())
171    }
172}
173
174impl Hash for Sha512_256 {
175    fn name(&self) -> &'static str {
176        "SHA-512/256"
177    }
178    fn output_len(&self) -> usize {
179        32
180    }
181    fn hash(&self, msg: &[u8], out: &mut [u8]) -> Result<(), CryptoError> {
182        if out.len() < 32 {
183            return Err(CryptoError::BufferTooSmall);
184        }
185        let digest = sha2::Sha512_256::digest(msg);
186        out[..32].copy_from_slice(&digest);
187        Ok(())
188    }
189}
190
191// ── SHA-2 digest length constants ────────────────────────────────────────────
192
193impl Sha256 {
194    /// Byte length of the SHA-256 digest output.
195    pub const DIGEST_LEN: usize = 32;
196    /// Output length in bytes (alias for `DIGEST_LEN`; use for generic const contexts).
197    pub const OUTPUT_LEN: usize = 32;
198    /// SHA-256 block size in bytes (FIPS 180-4).
199    pub const BLOCK_SIZE: usize = 64;
200}
201
202impl Sha384 {
203    /// Byte length of the SHA-384 digest output.
204    pub const DIGEST_LEN: usize = 48;
205    /// Output length in bytes (alias for `DIGEST_LEN`; use for generic const contexts).
206    pub const OUTPUT_LEN: usize = 48;
207    /// SHA-384 block size in bytes (FIPS 180-4).
208    pub const BLOCK_SIZE: usize = 128;
209}
210
211impl Sha512 {
212    /// Byte length of the SHA-512 digest output.
213    pub const DIGEST_LEN: usize = 64;
214    /// Output length in bytes (alias for `DIGEST_LEN`; use for generic const contexts).
215    pub const OUTPUT_LEN: usize = 64;
216    /// SHA-512 block size in bytes (FIPS 180-4).
217    pub const BLOCK_SIZE: usize = 128;
218}
219
220impl Sha512_256 {
221    /// Byte length of the SHA-512/256 digest output.
222    pub const DIGEST_LEN: usize = 32;
223    /// Output length in bytes (alias for `DIGEST_LEN`; use for generic const contexts).
224    pub const OUTPUT_LEN: usize = 32;
225    /// SHA-512/256 block size in bytes (FIPS 180-4).
226    pub const BLOCK_SIZE: usize = 128;
227}
228
229// ── SHA-2 streaming ──────────────────────────────────────────────────────────
230
231/// Streaming SHA-256 hasher.
232pub type Sha256Streaming = DigestStreamingAdapter<sha2::Sha256>;
233/// Streaming SHA-384 hasher.
234pub type Sha384Streaming = DigestStreamingAdapter<sha2::Sha384>;
235/// Streaming SHA-512 hasher.
236pub type Sha512Streaming = DigestStreamingAdapter<sha2::Sha512>;
237/// Streaming SHA-512/256 hasher.
238pub type Sha512_256Streaming = DigestStreamingAdapter<sha2::Sha512_256>;
239
240// ── SHA-3 one-shot ───────────────────────────────────────────────────────────
241
242/// SHA3-256 hash function (32-byte output, FIPS 202).
243#[derive(Debug, Default, Clone, Copy)]
244pub struct Sha3_256;
245
246/// SHA3-384 hash function (48-byte output, FIPS 202).
247#[derive(Debug, Default, Clone, Copy)]
248pub struct Sha3_384;
249
250/// SHA3-512 hash function (64-byte output, FIPS 202).
251#[derive(Debug, Default, Clone, Copy)]
252pub struct Sha3_512;
253
254impl Hash for Sha3_256 {
255    fn name(&self) -> &'static str {
256        "SHA3-256"
257    }
258    fn output_len(&self) -> usize {
259        32
260    }
261    fn hash(&self, msg: &[u8], out: &mut [u8]) -> Result<(), CryptoError> {
262        if out.len() < 32 {
263            return Err(CryptoError::BufferTooSmall);
264        }
265        let digest = sha3::Sha3_256::digest(msg);
266        out[..32].copy_from_slice(&digest);
267        Ok(())
268    }
269}
270
271impl Hash for Sha3_384 {
272    fn name(&self) -> &'static str {
273        "SHA3-384"
274    }
275    fn output_len(&self) -> usize {
276        48
277    }
278    fn hash(&self, msg: &[u8], out: &mut [u8]) -> Result<(), CryptoError> {
279        if out.len() < 48 {
280            return Err(CryptoError::BufferTooSmall);
281        }
282        let digest = sha3::Sha3_384::digest(msg);
283        out[..48].copy_from_slice(&digest);
284        Ok(())
285    }
286}
287
288impl Hash for Sha3_512 {
289    fn name(&self) -> &'static str {
290        "SHA3-512"
291    }
292    fn output_len(&self) -> usize {
293        64
294    }
295    fn hash(&self, msg: &[u8], out: &mut [u8]) -> Result<(), CryptoError> {
296        if out.len() < 64 {
297            return Err(CryptoError::BufferTooSmall);
298        }
299        let digest = sha3::Sha3_512::digest(msg);
300        out[..64].copy_from_slice(&digest);
301        Ok(())
302    }
303}
304
305// ── SHA-3 digest length constants ────────────────────────────────────────────
306
307impl Sha3_256 {
308    /// Byte length of the SHA3-256 digest output.
309    pub const DIGEST_LEN: usize = 32;
310    /// Output length in bytes (alias for `DIGEST_LEN`; use for generic const contexts).
311    pub const OUTPUT_LEN: usize = 32;
312    /// SHA3-256 block (rate) size in bytes (FIPS 202).
313    pub const BLOCK_SIZE: usize = 136;
314}
315
316impl Sha3_384 {
317    /// Byte length of the SHA3-384 digest output.
318    pub const DIGEST_LEN: usize = 48;
319    /// Output length in bytes (alias for `DIGEST_LEN`; use for generic const contexts).
320    pub const OUTPUT_LEN: usize = 48;
321    /// SHA3-384 block (rate) size in bytes (FIPS 202).
322    pub const BLOCK_SIZE: usize = 104;
323}
324
325impl Sha3_512 {
326    /// Byte length of the SHA3-512 digest output.
327    pub const DIGEST_LEN: usize = 64;
328    /// Output length in bytes (alias for `DIGEST_LEN`; use for generic const contexts).
329    pub const OUTPUT_LEN: usize = 64;
330    /// SHA3-512 block (rate) size in bytes (FIPS 202).
331    pub const BLOCK_SIZE: usize = 72;
332}
333
334// ── SHA-3 streaming ──────────────────────────────────────────────────────────
335
336/// Streaming SHA3-256 hasher.
337pub type Sha3_256Streaming = DigestStreamingAdapter<sha3::Sha3_256>;
338/// Streaming SHA3-384 hasher.
339pub type Sha3_384Streaming = DigestStreamingAdapter<sha3::Sha3_384>;
340/// Streaming SHA3-512 hasher.
341pub type Sha3_512Streaming = DigestStreamingAdapter<sha3::Sha3_512>;
342
343// ── BLAKE2 one-shot ──────────────────────────────────────────────────────────
344
345/// BLAKE2b-256 hash function (32-byte output, RFC 7693).
346#[derive(Debug, Default, Clone, Copy)]
347pub struct Blake2b256;
348
349/// BLAKE2b-512 hash function (64-byte output, RFC 7693).
350#[derive(Debug, Default, Clone, Copy)]
351pub struct Blake2b512;
352
353/// BLAKE2s-256 hash function (32-byte output, RFC 7693).
354#[derive(Debug, Default, Clone, Copy)]
355pub struct Blake2s256;
356
357impl Hash for Blake2b256 {
358    fn name(&self) -> &'static str {
359        "BLAKE2b-256"
360    }
361    fn output_len(&self) -> usize {
362        32
363    }
364    fn hash(&self, msg: &[u8], out: &mut [u8]) -> Result<(), CryptoError> {
365        if out.len() < 32 {
366            return Err(CryptoError::BufferTooSmall);
367        }
368        let result = blake2::Blake2b256::digest(msg);
369        out[..32].copy_from_slice(&result);
370        Ok(())
371    }
372}
373
374impl Hash for Blake2b512 {
375    fn name(&self) -> &'static str {
376        "BLAKE2b-512"
377    }
378    fn output_len(&self) -> usize {
379        64
380    }
381    fn hash(&self, msg: &[u8], out: &mut [u8]) -> Result<(), CryptoError> {
382        if out.len() < 64 {
383            return Err(CryptoError::BufferTooSmall);
384        }
385        let result = blake2::Blake2b512::digest(msg);
386        out[..64].copy_from_slice(&result);
387        Ok(())
388    }
389}
390
391impl Hash for Blake2s256 {
392    fn name(&self) -> &'static str {
393        "BLAKE2s-256"
394    }
395    fn output_len(&self) -> usize {
396        32
397    }
398    fn hash(&self, msg: &[u8], out: &mut [u8]) -> Result<(), CryptoError> {
399        if out.len() < 32 {
400            return Err(CryptoError::BufferTooSmall);
401        }
402        let result = blake2::Blake2s256::digest(msg);
403        out[..32].copy_from_slice(&result);
404        Ok(())
405    }
406}
407
408// ── BLAKE2 digest length constants ───────────────────────────────────────────
409
410impl Blake2b256 {
411    /// Byte length of the BLAKE2b-256 digest output.
412    pub const DIGEST_LEN: usize = 32;
413    /// Output length in bytes (alias for `DIGEST_LEN`; use for generic const contexts).
414    pub const OUTPUT_LEN: usize = 32;
415    /// BLAKE2b block size in bytes (RFC 7693).
416    pub const BLOCK_SIZE: usize = 128;
417}
418
419impl Blake2b512 {
420    /// Byte length of the BLAKE2b-512 digest output.
421    pub const DIGEST_LEN: usize = 64;
422    /// Output length in bytes (alias for `DIGEST_LEN`; use for generic const contexts).
423    pub const OUTPUT_LEN: usize = 64;
424    /// BLAKE2b block size in bytes (RFC 7693).
425    pub const BLOCK_SIZE: usize = 128;
426}
427
428impl Blake2s256 {
429    /// Byte length of the BLAKE2s-256 digest output.
430    pub const DIGEST_LEN: usize = 32;
431    /// Output length in bytes (alias for `DIGEST_LEN`; use for generic const contexts).
432    pub const OUTPUT_LEN: usize = 32;
433    /// BLAKE2s block size in bytes (RFC 7693).
434    pub const BLOCK_SIZE: usize = 64;
435}
436
437// ── BLAKE2 streaming ─────────────────────────────────────────────────────────
438
439/// Streaming BLAKE2b-256 hasher (32-byte output).
440pub type Blake2b256Streaming = DigestStreamingAdapter<blake2::Blake2b256>;
441/// Streaming BLAKE2b-512 hasher (64-byte output).
442pub type Blake2b512Streaming = DigestStreamingAdapter<blake2::Blake2b512>;
443/// Streaming BLAKE2s-256 hasher (32-byte output).
444pub type Blake2s256Streaming = DigestStreamingAdapter<blake2::Blake2s256>;
445
446// ── BLAKE3 one-shot ───────────────────────────────────────────────────────────
447
448/// BLAKE3 hash function (32-byte output by default).
449#[derive(Debug, Default, Clone, Copy)]
450pub struct Blake3;
451
452impl Hash for Blake3 {
453    fn name(&self) -> &'static str {
454        "BLAKE3"
455    }
456    fn output_len(&self) -> usize {
457        32
458    }
459    fn hash(&self, msg: &[u8], out: &mut [u8]) -> Result<(), CryptoError> {
460        if out.len() < 32 {
461            return Err(CryptoError::BufferTooSmall);
462        }
463        let digest = blake3::hash(msg);
464        out[..32].copy_from_slice(digest.as_bytes());
465        Ok(())
466    }
467}
468
469impl Blake3 {
470    /// Byte length of the BLAKE3 default digest output.
471    pub const DIGEST_LEN: usize = 32;
472    /// Output length in bytes (alias for `DIGEST_LEN`; use for generic const contexts).
473    pub const OUTPUT_LEN: usize = 32;
474    /// BLAKE3 block size in bytes.
475    pub const BLOCK_SIZE: usize = 64;
476}
477
478// ── BLAKE3 streaming ─────────────────────────────────────────────────────────
479
480/// Streaming BLAKE3 hasher (32-byte output).
481///
482/// Uses `blake3::Hasher` directly since blake3 has its own incremental API.
483pub struct Blake3Streaming {
484    inner: blake3::Hasher,
485}
486
487impl Blake3Streaming {
488    /// Create a new BLAKE3 streaming hasher.
489    pub fn new() -> Self {
490        Self {
491            inner: blake3::Hasher::new(),
492        }
493    }
494}
495
496impl Default for Blake3Streaming {
497    fn default() -> Self {
498        Self::new()
499    }
500}
501
502impl Clone for Blake3Streaming {
503    fn clone(&self) -> Self {
504        Self {
505            inner: self.inner.clone(),
506        }
507    }
508}
509
510impl StreamingHash for Blake3Streaming {
511    fn update(&mut self, data: &[u8]) {
512        self.inner.update(data);
513    }
514
515    fn finalize(self, out: &mut [u8]) -> Result<(), CryptoError> {
516        if out.len() < 32 {
517            return Err(CryptoError::BufferTooSmall);
518        }
519        let result = self.inner.finalize();
520        out[..32].copy_from_slice(result.as_bytes());
521        Ok(())
522    }
523
524    fn reset(&mut self) {
525        self.inner.reset();
526    }
527}
528
529// ── std::io::Write for streaming hashers ─────────────────────────────────────
530
531/// Implement `std::io::Write` for [`DigestStreamingAdapter`] so that streaming
532/// hashers can be used as sinks in generic I/O pipelines.
533#[cfg(feature = "std")]
534impl<D: Digest + Default + Send> std::io::Write for DigestStreamingAdapter<D> {
535    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
536        self.update(buf);
537        Ok(buf.len())
538    }
539
540    fn flush(&mut self) -> std::io::Result<()> {
541        Ok(())
542    }
543}
544
545/// Implement `std::io::Write` for [`Blake3Streaming`] so that it can be used
546/// as a sink in generic I/O pipelines.
547#[cfg(feature = "std")]
548impl std::io::Write for Blake3Streaming {
549    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
550        self.update(buf);
551        Ok(buf.len())
552    }
553
554    fn flush(&mut self) -> std::io::Result<()> {
555        Ok(())
556    }
557}
558
559// ── Hex-digest convenience functions ─────────────────────────────────────────
560
561/// Hash `msg` with SHA-256 and return a lowercase hex string.
562///
563/// This function requires the `std` feature because it allocates a `String`.
564#[cfg(feature = "std")]
565pub fn sha256_hex(msg: &[u8]) -> std::string::String {
566    let digest = sha2::Sha256::digest(msg);
567    bytes_to_hex(digest.as_ref())
568}
569
570/// Hash `msg` with SHA-384 and return a lowercase hex string.
571#[cfg(feature = "std")]
572pub fn sha384_hex(msg: &[u8]) -> std::string::String {
573    let digest = sha2::Sha384::digest(msg);
574    bytes_to_hex(digest.as_ref())
575}
576
577/// Hash `msg` with SHA-512 and return a lowercase hex string.
578#[cfg(feature = "std")]
579pub fn sha512_hex(msg: &[u8]) -> std::string::String {
580    let digest = sha2::Sha512::digest(msg);
581    bytes_to_hex(digest.as_ref())
582}
583
584/// Hash `msg` with SHA3-256 and return a lowercase hex string.
585#[cfg(feature = "std")]
586pub fn sha3_256_hex(msg: &[u8]) -> std::string::String {
587    let digest = sha3::Sha3_256::digest(msg);
588    bytes_to_hex(digest.as_ref())
589}
590
591/// Hash `msg` with BLAKE3 and return a lowercase hex string.
592#[cfg(feature = "std")]
593pub fn blake3_hex(msg: &[u8]) -> std::string::String {
594    let digest = blake3::hash(msg);
595    bytes_to_hex(digest.as_bytes())
596}
597
598/// Convert a byte slice to a lowercase hexadecimal string.
599#[cfg(feature = "std")]
600fn bytes_to_hex(bytes: &[u8]) -> std::string::String {
601    bytes.iter().fold(
602        std::string::String::with_capacity(bytes.len() * 2),
603        |mut s, b| {
604            let _ = std::fmt::write(&mut s, format_args!("{b:02x}"));
605            s
606        },
607    )
608}
609
610// ── BLAKE3 keyed-hash mode ───────────────────────────────────────────────────
611
612/// BLAKE3 keyed-hash (MAC-like): deterministic 32-byte output under a 32-byte key.
613///
614/// Produces a unique output for each (key, message) pair. Keys are 32 bytes
615/// and must be kept secret for MAC use cases.
616pub struct Blake3Keyed {
617    key: [u8; 32],
618}
619
620impl Blake3Keyed {
621    /// Create a keyed hasher with the given 32-byte key.
622    pub fn new(key: [u8; 32]) -> Self {
623        Self { key }
624    }
625
626    /// Hash `msg` under this key; returns 32 bytes.
627    pub fn hash(&self, msg: &[u8]) -> [u8; 32] {
628        *blake3::keyed_hash(&self.key, msg).as_bytes()
629    }
630}
631
632/// Hash `msg` under `key` with BLAKE3 keyed-hash mode; returns 32 bytes.
633///
634/// Convenience free function — equivalent to `Blake3Keyed::new(key).hash(msg)`.
635pub fn blake3_keyed_hash(key: &[u8; 32], msg: &[u8]) -> [u8; 32] {
636    *blake3::keyed_hash(key, msg).as_bytes()
637}
638
639// ── BLAKE3 key-derivation mode ───────────────────────────────────────────────
640
641/// Derive a 32-byte key using BLAKE3 key-derivation mode.
642///
643/// `context` must be a globally unique, hardcoded string describing the
644/// purpose (e.g. `"MyApp 2024-01 file encryption key"`).  Key material
645/// may be any length, including zero.
646pub fn blake3_derive_key(context: &str, key_material: &[u8]) -> [u8; 32] {
647    blake3::derive_key(context, key_material)
648}
649
650// ── Alloc-free fixed-array hash helpers ────────────────────────────────────
651//
652// These inherent methods compute a hash and write into a stack-allocated
653// `[u8; N]` array without any heap allocation. They are the preferred API
654// when the `no_std` feature is enabled (or in any context where `alloc` is
655// undesirable).
656//
657// All concrete hash types implement `hash_fixed<N>()` which is equivalent
658// to `Hash::hash_to_array::<N>()` from `oxicrypto-core` but surfaced here
659// as an ergonomic shorthand directly on the type.
660//
661// Usage (alloc-free):
662//
663// ```rust
664// let digest: [u8; 32] = Sha256.hash_fixed(b"hello");
665// let digest: [u8; 64] = Sha512.hash_fixed(b"hello");
666// let digest: [u8; 32] = Blake3.hash_fixed(b"hello");
667// ```
668//
669// The `no_std` feature flag makes this distinction visible at the type level:
670// with `no_std` enabled, `Hash::hash_to_vec` (which requires `alloc`) is
671// documented as unavailable and callers should use `hash_fixed` or
672// `Hash::hash_to_array` instead.
673
674impl Sha256 {
675    /// Hash `msg` and return the 32-byte SHA-256 digest as a fixed-size array.
676    ///
677    /// Alloc-free alternative to [`Hash::hash_to_vec`].
678    ///
679    /// # `no_std` note
680    ///
681    /// When the `no_std` feature is enabled, prefer this method over
682    /// `hash_to_vec`, which requires heap allocation.
683    #[inline]
684    #[must_use]
685    pub fn hash_fixed(&self, msg: &[u8]) -> [u8; 32] {
686        sha2::Sha256::digest(msg).into()
687    }
688}
689
690impl Sha384 {
691    /// Hash `msg` and return the 48-byte SHA-384 digest as a fixed-size array.
692    ///
693    /// Alloc-free alternative to [`Hash::hash_to_vec`].
694    #[inline]
695    #[must_use]
696    pub fn hash_fixed(&self, msg: &[u8]) -> [u8; 48] {
697        sha2::Sha384::digest(msg).into()
698    }
699}
700
701impl Sha512 {
702    /// Hash `msg` and return the 64-byte SHA-512 digest as a fixed-size array.
703    ///
704    /// Alloc-free alternative to [`Hash::hash_to_vec`].
705    #[inline]
706    #[must_use]
707    pub fn hash_fixed(&self, msg: &[u8]) -> [u8; 64] {
708        sha2::Sha512::digest(msg).into()
709    }
710}
711
712impl Sha512_256 {
713    /// Hash `msg` and return the 32-byte SHA-512/256 digest as a fixed-size array.
714    ///
715    /// Alloc-free alternative to [`Hash::hash_to_vec`].
716    #[inline]
717    #[must_use]
718    pub fn hash_fixed(&self, msg: &[u8]) -> [u8; 32] {
719        sha2::Sha512_256::digest(msg).into()
720    }
721}
722
723impl Sha3_256 {
724    /// Hash `msg` and return the 32-byte SHA3-256 digest as a fixed-size array.
725    ///
726    /// Alloc-free alternative to [`Hash::hash_to_vec`].
727    #[inline]
728    #[must_use]
729    pub fn hash_fixed(&self, msg: &[u8]) -> [u8; 32] {
730        sha3::Sha3_256::digest(msg).into()
731    }
732}
733
734impl Sha3_384 {
735    /// Hash `msg` and return the 48-byte SHA3-384 digest as a fixed-size array.
736    ///
737    /// Alloc-free alternative to [`Hash::hash_to_vec`].
738    #[inline]
739    #[must_use]
740    pub fn hash_fixed(&self, msg: &[u8]) -> [u8; 48] {
741        sha3::Sha3_384::digest(msg).into()
742    }
743}
744
745impl Sha3_512 {
746    /// Hash `msg` and return the 64-byte SHA3-512 digest as a fixed-size array.
747    ///
748    /// Alloc-free alternative to [`Hash::hash_to_vec`].
749    #[inline]
750    #[must_use]
751    pub fn hash_fixed(&self, msg: &[u8]) -> [u8; 64] {
752        sha3::Sha3_512::digest(msg).into()
753    }
754}
755
756impl Blake2b256 {
757    /// Hash `msg` and return the 32-byte BLAKE2b-256 digest as a fixed-size array.
758    ///
759    /// Alloc-free alternative to [`Hash::hash_to_vec`].
760    #[inline]
761    #[must_use]
762    pub fn hash_fixed(&self, msg: &[u8]) -> [u8; 32] {
763        blake2::Blake2b256::digest(msg).into()
764    }
765}
766
767impl Blake2b512 {
768    /// Hash `msg` and return the 64-byte BLAKE2b-512 digest as a fixed-size array.
769    ///
770    /// Alloc-free alternative to [`Hash::hash_to_vec`].
771    #[inline]
772    #[must_use]
773    pub fn hash_fixed(&self, msg: &[u8]) -> [u8; 64] {
774        blake2::Blake2b512::digest(msg).into()
775    }
776}
777
778impl Blake2s256 {
779    /// Hash `msg` and return the 32-byte BLAKE2s-256 digest as a fixed-size array.
780    ///
781    /// Alloc-free alternative to [`Hash::hash_to_vec`].
782    #[inline]
783    #[must_use]
784    pub fn hash_fixed(&self, msg: &[u8]) -> [u8; 32] {
785        blake2::Blake2s256::digest(msg).into()
786    }
787}
788
789impl Blake3 {
790    /// Hash `msg` and return the 32-byte BLAKE3 digest as a fixed-size array.
791    ///
792    /// Alloc-free alternative to [`Hash::hash_to_vec`].
793    ///
794    /// # `no_std` note
795    ///
796    /// When the `no_std` feature is enabled, this method (along with
797    /// `blake3_keyed_hash` and `blake3_derive_key`) is the preferred API
798    /// since it avoids heap allocation entirely.
799    #[inline]
800    #[must_use]
801    pub fn hash_fixed(&self, msg: &[u8]) -> [u8; 32] {
802        *blake3::hash(msg).as_bytes()
803    }
804}
805
806// ── BLAKE3 XOF (extendable output) ───────────────────────────────────────────
807
808/// Hash `msg` with BLAKE3 and return `output_len` bytes.
809///
810/// The first 32 bytes are identical to the standard BLAKE3 hash of `msg`.
811/// Requesting more than 32 bytes extends the output using BLAKE3's XOF
812/// (extendable output function) mode.
813///
814/// # `no_std` note
815///
816/// This function allocates a `Vec<u8>`. When the `no_std` feature is enabled,
817/// use [`Blake3::hash_fixed`] for a 32-byte alloc-free alternative, or write
818/// the XOF output directly into a caller-provided `&mut [u8]` using
819/// `blake3::Hasher::finalize_xof().fill()`.
820pub fn blake3_xof(msg: &[u8], output_len: usize) -> Vec<u8> {
821    let mut out = alloc::vec![0u8; output_len];
822    let mut reader = blake3::Hasher::new().update(msg).finalize_xof();
823    reader.fill(&mut out);
824    out
825}
826
827// ── Tests ─────────────────────────────────────────────────────────────────────
828
829#[cfg(test)]
830mod tests {
831    use super::*;
832
833    fn hex_decode(s: &str) -> Vec<u8> {
834        hex::decode(s).unwrap_or_else(|e| panic!("invalid hex string {s:?}: {e}"))
835    }
836
837    // ── SHA-256 ──────────────────────────────────────────────────────────────
838
839    #[test]
840    fn sha256_empty() {
841        let hasher = Sha256;
842        let result = hasher.hash_to_vec(b"").unwrap();
843        let expected =
844            hex_decode("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855");
845        assert_eq!(result, expected, "SHA-256 of empty string mismatch");
846    }
847
848    #[test]
849    fn sha256_abc() {
850        let hasher = Sha256;
851        let result = hasher.hash_to_vec(b"abc").unwrap();
852        // NIST FIPS 180-4 test vector: SHA-256("abc")
853        let expected =
854            hex_decode("ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad");
855        assert_eq!(result, expected, "SHA-256 of 'abc' mismatch");
856    }
857
858    #[test]
859    fn sha384_abc() {
860        let hasher = Sha384;
861        let result = hasher.hash_to_vec(b"abc").unwrap();
862        assert_eq!(result.len(), 48);
863    }
864
865    #[test]
866    fn sha512_abc() {
867        let hasher = Sha512;
868        let result = hasher.hash_to_vec(b"abc").unwrap();
869        assert_eq!(result.len(), 64);
870    }
871
872    #[test]
873    fn sha3_256_output_len() {
874        let hasher = Sha3_256;
875        assert_eq!(hasher.output_len(), 32);
876        let result = hasher.hash_to_vec(b"abc").unwrap();
877        assert_eq!(result.len(), 32);
878    }
879
880    #[test]
881    fn sha3_384_output_len() {
882        let hasher = Sha3_384;
883        assert_eq!(hasher.output_len(), 48);
884        let result = hasher.hash_to_vec(b"abc").unwrap();
885        assert_eq!(result.len(), 48);
886    }
887
888    #[test]
889    fn sha3_512_output_len() {
890        let hasher = Sha3_512;
891        assert_eq!(hasher.output_len(), 64);
892        let result = hasher.hash_to_vec(b"abc").unwrap();
893        assert_eq!(result.len(), 64);
894    }
895
896    #[test]
897    fn blake3_abc() {
898        let hasher = Blake3;
899        let result = hasher.hash_to_vec(b"abc").unwrap();
900        // Official BLAKE3 test vector for "abc":
901        // https://github.com/BLAKE3-team/BLAKE3/blob/master/test_vectors/test_vectors.json
902        let expected =
903            hex_decode("6437b3ac38465133ffb63b75273a8db548c558465d79db03fd359c6cd5bd9d85");
904        assert_eq!(result, expected, "BLAKE3 of 'abc' mismatch");
905    }
906
907    #[test]
908    fn buffer_too_small_error() {
909        let hasher = Sha256;
910        let mut out = [0u8; 16];
911        let err = hasher.hash(b"test", &mut out).unwrap_err();
912        assert_eq!(err, CryptoError::BufferTooSmall);
913    }
914
915    // ── Streaming: SHA-256 equivalence ───────────────────────────────────────
916
917    #[test]
918    fn sha256_streaming_hello_world() {
919        // Streaming "hello" + " world" must equal one-shot "hello world"
920        let one_shot = Sha256.hash_to_vec(b"hello world").unwrap();
921
922        let mut streamer = Sha256Streaming::new();
923        StreamingHash::update(&mut streamer, b"hello");
924        StreamingHash::update(&mut streamer, b" world");
925        let mut buf = [0u8; 32];
926        StreamingHash::finalize(streamer, &mut buf).unwrap();
927
928        assert_eq!(buf.as_ref(), one_shot.as_slice());
929    }
930
931    #[test]
932    fn sha256_streaming_one_byte_chunks() {
933        // Feed "abc" one byte at a time; must equal one-shot SHA-256("abc")
934        let one_shot = Sha256.hash_to_vec(b"abc").unwrap();
935
936        let mut streamer = Sha256Streaming::new();
937        for byte in b"abc" {
938            StreamingHash::update(&mut streamer, core::slice::from_ref(byte));
939        }
940        let mut buf = [0u8; 32];
941        StreamingHash::finalize(streamer, &mut buf).unwrap();
942
943        assert_eq!(buf.as_ref(), one_shot.as_slice());
944    }
945
946    #[test]
947    fn sha256_streaming_reset() {
948        // After reset, streaming should produce a fresh hash
949        let expected = Sha256.hash_to_vec(b"world").unwrap();
950
951        let mut streamer = Sha256Streaming::new();
952        StreamingHash::update(&mut streamer, b"hello");
953        StreamingHash::reset(&mut streamer);
954        StreamingHash::update(&mut streamer, b"world");
955        let mut buf = [0u8; 32];
956        StreamingHash::finalize(streamer, &mut buf).unwrap();
957
958        assert_eq!(buf.as_ref(), expected.as_slice());
959    }
960
961    #[test]
962    fn sha256_streaming_buffer_too_small() {
963        let mut streamer = Sha256Streaming::new();
964        StreamingHash::update(&mut streamer, b"test");
965        let mut buf = [0u8; 16];
966        let err = StreamingHash::finalize(streamer, &mut buf).unwrap_err();
967        assert_eq!(err, CryptoError::BufferTooSmall);
968    }
969
970    // ── Streaming: BLAKE3 ────────────────────────────────────────────────────
971
972    #[test]
973    fn blake3_streaming_equivalence() {
974        let one_shot = Blake3.hash_to_vec(b"hello world").unwrap();
975
976        let mut streamer = Blake3Streaming::new();
977        StreamingHash::update(&mut streamer, b"hello");
978        StreamingHash::update(&mut streamer, b" world");
979        let mut buf = [0u8; 32];
980        StreamingHash::finalize(streamer, &mut buf).unwrap();
981
982        assert_eq!(buf.as_ref(), one_shot.as_slice());
983    }
984
985    #[test]
986    fn blake3_streaming_reset() {
987        let expected = Blake3.hash_to_vec(b"world").unwrap();
988
989        let mut streamer = Blake3Streaming::new();
990        StreamingHash::update(&mut streamer, b"hello");
991        StreamingHash::reset(&mut streamer);
992        StreamingHash::update(&mut streamer, b"world");
993        let mut buf = [0u8; 32];
994        StreamingHash::finalize(streamer, &mut buf).unwrap();
995
996        assert_eq!(buf.as_ref(), expected.as_slice());
997    }
998
999    // ── BLAKE2b-256 ──────────────────────────────────────────────────────────
1000
1001    #[test]
1002    fn blake2b256_output_len() {
1003        let hasher = Blake2b256;
1004        assert_eq!(hasher.output_len(), 32);
1005        let result = hasher.hash_to_vec(b"abc").unwrap();
1006        assert_eq!(result.len(), 32);
1007    }
1008
1009    #[test]
1010    fn blake2b256_empty_nonzero() {
1011        // BLAKE2b("")  -- just verify non-zero output of correct length
1012        let result = Blake2b256.hash_to_vec(b"").unwrap();
1013        assert_eq!(result.len(), 32);
1014        assert!(
1015            result.iter().any(|&b| b != 0),
1016            "BLAKE2b-256 of empty should be non-zero"
1017        );
1018    }
1019
1020    #[test]
1021    fn blake2b256_streaming_equivalence() {
1022        let one_shot = Blake2b256.hash_to_vec(b"hello world").unwrap();
1023
1024        let mut streamer = Blake2b256Streaming::new();
1025        StreamingHash::update(&mut streamer, b"hello");
1026        StreamingHash::update(&mut streamer, b" world");
1027        let mut buf = [0u8; 32];
1028        StreamingHash::finalize(streamer, &mut buf).unwrap();
1029
1030        assert_eq!(buf.as_ref(), one_shot.as_slice());
1031    }
1032
1033    // ── BLAKE2b-512 ──────────────────────────────────────────────────────────
1034
1035    #[test]
1036    fn blake2b512_output_len() {
1037        let hasher = Blake2b512;
1038        assert_eq!(hasher.output_len(), 64);
1039        let result = hasher.hash_to_vec(b"abc").unwrap();
1040        assert_eq!(result.len(), 64);
1041    }
1042
1043    #[test]
1044    fn blake2b512_streaming_equivalence() {
1045        let one_shot = Blake2b512.hash_to_vec(b"hello world").unwrap();
1046
1047        let mut streamer = Blake2b512Streaming::new();
1048        StreamingHash::update(&mut streamer, b"hello");
1049        StreamingHash::update(&mut streamer, b" world");
1050        let mut buf = [0u8; 64];
1051        StreamingHash::finalize(streamer, &mut buf).unwrap();
1052
1053        assert_eq!(buf.as_ref(), one_shot.as_slice());
1054    }
1055
1056    // ── BLAKE2s-256 ──────────────────────────────────────────────────────────
1057
1058    #[test]
1059    fn blake2s256_output_len() {
1060        let hasher = Blake2s256;
1061        assert_eq!(hasher.output_len(), 32);
1062        let result = hasher.hash_to_vec(b"abc").unwrap();
1063        assert_eq!(result.len(), 32);
1064    }
1065
1066    #[test]
1067    fn blake2s256_streaming_equivalence() {
1068        let one_shot = Blake2s256.hash_to_vec(b"hello world").unwrap();
1069
1070        let mut streamer = Blake2s256Streaming::new();
1071        StreamingHash::update(&mut streamer, b"hello");
1072        StreamingHash::update(&mut streamer, b" world");
1073        let mut buf = [0u8; 32];
1074        StreamingHash::finalize(streamer, &mut buf).unwrap();
1075
1076        assert_eq!(buf.as_ref(), one_shot.as_slice());
1077    }
1078
1079    // ── SHA-512/256 ──────────────────────────────────────────────────────────
1080
1081    #[test]
1082    fn sha512_256_output_len() {
1083        let hasher = Sha512_256;
1084        assert_eq!(hasher.output_len(), 32);
1085        let result = hasher.hash_to_vec(b"abc").unwrap();
1086        assert_eq!(result.len(), 32);
1087    }
1088
1089    #[test]
1090    fn sha512_256_known_vector() {
1091        // FIPS 180-4 known value: SHA-512/256("abc")
1092        let result = Sha512_256.hash_to_vec(b"abc").unwrap();
1093        let expected =
1094            hex_decode("53048e2681941ef99b2e29b76b4c7dabe4c2d0c634fc6d46e0e2f13107e7af23");
1095        assert_eq!(result, expected, "SHA-512/256 of 'abc' mismatch");
1096    }
1097
1098    #[test]
1099    fn sha512_256_streaming_equivalence() {
1100        let one_shot = Sha512_256.hash_to_vec(b"hello world").unwrap();
1101
1102        let mut streamer = Sha512_256Streaming::new();
1103        StreamingHash::update(&mut streamer, b"hello");
1104        StreamingHash::update(&mut streamer, b" world");
1105        let mut buf = [0u8; 32];
1106        StreamingHash::finalize(streamer, &mut buf).unwrap();
1107
1108        assert_eq!(buf.as_ref(), one_shot.as_slice());
1109    }
1110
1111    // ── Blake3Keyed ──────────────────────────────────────────────────────────
1112
1113    #[test]
1114    fn blake3_keyed_different_keys() {
1115        let key1 = [1u8; 32];
1116        let key2 = [2u8; 32];
1117        let msg = b"same message";
1118
1119        let out1 = Blake3Keyed::new(key1).hash(msg);
1120        let out2 = Blake3Keyed::new(key2).hash(msg);
1121
1122        assert_ne!(out1, out2, "Different keys must produce different outputs");
1123    }
1124
1125    #[test]
1126    fn blake3_keyed_different_messages() {
1127        let key = [42u8; 32];
1128        let out1 = Blake3Keyed::new(key).hash(b"message1");
1129        let out2 = Blake3Keyed::new(key).hash(b"message2");
1130
1131        assert_ne!(
1132            out1, out2,
1133            "Different messages must produce different outputs"
1134        );
1135    }
1136
1137    #[test]
1138    fn blake3_keyed_deterministic() {
1139        let key = [7u8; 32];
1140        let msg = b"deterministic";
1141        let out1 = Blake3Keyed::new(key).hash(msg);
1142        let out2 = blake3_keyed_hash(&key, msg);
1143
1144        assert_eq!(out1, out2, "Method and free function must agree");
1145    }
1146
1147    // ── blake3_derive_key ────────────────────────────────────────────────────
1148
1149    #[test]
1150    fn blake3_derive_key_different_contexts() {
1151        let material = b"shared key material";
1152        let out1 = blake3_derive_key("context A", material);
1153        let out2 = blake3_derive_key("context B", material);
1154
1155        assert_ne!(
1156            out1, out2,
1157            "Different contexts must produce different derived keys"
1158        );
1159    }
1160
1161    #[test]
1162    fn blake3_derive_key_deterministic() {
1163        let material = b"deterministic material";
1164        let out1 = blake3_derive_key("test context", material);
1165        let out2 = blake3_derive_key("test context", material);
1166
1167        assert_eq!(out1, out2, "derive_key must be deterministic");
1168    }
1169
1170    #[test]
1171    fn blake3_derive_key_output_len() {
1172        let out = blake3_derive_key("oxicrypto test", b"material");
1173        assert_eq!(out.len(), 32);
1174    }
1175
1176    // ── blake3_xof ───────────────────────────────────────────────────────────
1177
1178    #[test]
1179    fn blake3_xof_64_bytes() {
1180        let out = blake3_xof(b"hello", 64);
1181        assert_eq!(out.len(), 64);
1182    }
1183
1184    #[test]
1185    fn blake3_xof_first_32_match_standard_hash() {
1186        let msg = b"xof test";
1187        let standard = Blake3.hash_to_vec(msg).unwrap();
1188        let extended = blake3_xof(msg, 64);
1189
1190        assert_eq!(
1191            &extended[..32],
1192            standard.as_slice(),
1193            "First 32 bytes of XOF must match standard BLAKE3 hash"
1194        );
1195    }
1196
1197    #[test]
1198    fn blake3_xof_prefix_consistency() {
1199        let msg = b"prefix test";
1200        let out64 = blake3_xof(msg, 64);
1201        let out128 = blake3_xof(msg, 128);
1202
1203        assert_eq!(
1204            &out128[..64],
1205            out64.as_slice(),
1206            "128-byte XOF must be prefixed by 64-byte XOF"
1207        );
1208    }
1209
1210    #[test]
1211    fn blake3_xof_zero_len() {
1212        let out = blake3_xof(b"anything", 0);
1213        assert!(out.is_empty());
1214    }
1215
1216    // ── OUTPUT_LEN inherent constants (WI-A: const-assoc-consts) ────────────
1217
1218    #[test]
1219    fn test_output_len_consts() {
1220        assert_eq!(Sha256::OUTPUT_LEN, 32);
1221        assert_eq!(Sha384::OUTPUT_LEN, 48);
1222        assert_eq!(Sha512::OUTPUT_LEN, 64);
1223        assert_eq!(Sha512_256::OUTPUT_LEN, 32);
1224        assert_eq!(Sha3_256::OUTPUT_LEN, 32);
1225        assert_eq!(Sha3_384::OUTPUT_LEN, 48);
1226        assert_eq!(Sha3_512::OUTPUT_LEN, 64);
1227        assert_eq!(Blake2b256::OUTPUT_LEN, 32);
1228        assert_eq!(Blake2b512::OUTPUT_LEN, 64);
1229        assert_eq!(Blake2s256::OUTPUT_LEN, 32);
1230        assert_eq!(Blake3::OUTPUT_LEN, 32);
1231    }
1232
1233    #[test]
1234    fn test_output_len_matches_runtime_output_len() {
1235        assert_eq!(Sha256::OUTPUT_LEN, Sha256.output_len());
1236        assert_eq!(Sha384::OUTPUT_LEN, Sha384.output_len());
1237        assert_eq!(Sha512::OUTPUT_LEN, Sha512.output_len());
1238        assert_eq!(Sha512_256::OUTPUT_LEN, Sha512_256.output_len());
1239        assert_eq!(Sha3_256::OUTPUT_LEN, Sha3_256.output_len());
1240        assert_eq!(Sha3_384::OUTPUT_LEN, Sha3_384.output_len());
1241        assert_eq!(Sha3_512::OUTPUT_LEN, Sha3_512.output_len());
1242        assert_eq!(Blake2b256::OUTPUT_LEN, Blake2b256.output_len());
1243        assert_eq!(Blake2b512::OUTPUT_LEN, Blake2b512.output_len());
1244        assert_eq!(Blake2s256::OUTPUT_LEN, Blake2s256.output_len());
1245        assert_eq!(Blake3::OUTPUT_LEN, Blake3.output_len());
1246    }
1247
1248    // ── DIGEST_LEN / BLOCK_SIZE constants ────────────────────────────────────
1249
1250    #[test]
1251    fn sha256_digest_len_constant() {
1252        assert_eq!(Sha256::DIGEST_LEN, 32);
1253        assert_eq!(Sha256::BLOCK_SIZE, 64);
1254    }
1255
1256    #[test]
1257    fn sha384_digest_len_constant() {
1258        assert_eq!(Sha384::DIGEST_LEN, 48);
1259        assert_eq!(Sha384::BLOCK_SIZE, 128);
1260    }
1261
1262    #[test]
1263    fn sha512_digest_len_constant() {
1264        assert_eq!(Sha512::DIGEST_LEN, 64);
1265        assert_eq!(Sha512::BLOCK_SIZE, 128);
1266    }
1267
1268    #[test]
1269    fn sha3_256_digest_len_constant() {
1270        assert_eq!(Sha3_256::DIGEST_LEN, 32);
1271    }
1272
1273    #[test]
1274    fn blake3_digest_len_constant() {
1275        assert_eq!(Blake3::DIGEST_LEN, 32);
1276        assert_eq!(Blake3::BLOCK_SIZE, 64);
1277    }
1278
1279    #[test]
1280    fn blake2b256_digest_len_constant() {
1281        assert_eq!(Blake2b256::DIGEST_LEN, 32);
1282        assert_eq!(Blake2b256::BLOCK_SIZE, 128);
1283    }
1284
1285    #[test]
1286    fn blake2b512_digest_len_constant() {
1287        assert_eq!(Blake2b512::DIGEST_LEN, 64);
1288        assert_eq!(Blake2b512::BLOCK_SIZE, 128);
1289    }
1290
1291    #[test]
1292    fn blake2s256_digest_len_constant() {
1293        assert_eq!(Blake2s256::DIGEST_LEN, 32);
1294        assert_eq!(Blake2s256::BLOCK_SIZE, 64);
1295    }
1296
1297    // ── Constants match runtime output_len ───────────────────────────────────
1298
1299    #[test]
1300    fn constants_match_runtime_output_len() {
1301        assert_eq!(Sha256::DIGEST_LEN, Sha256.output_len());
1302        assert_eq!(Sha384::DIGEST_LEN, Sha384.output_len());
1303        assert_eq!(Sha512::DIGEST_LEN, Sha512.output_len());
1304        assert_eq!(Blake3::DIGEST_LEN, Blake3.output_len());
1305        assert_eq!(Blake2b256::DIGEST_LEN, Blake2b256.output_len());
1306        assert_eq!(Blake2b512::DIGEST_LEN, Blake2b512.output_len());
1307        assert_eq!(Blake2s256::DIGEST_LEN, Blake2s256.output_len());
1308    }
1309
1310    // ── Clone for streaming types ─────────────────────────────────────────────
1311
1312    #[test]
1313    fn sha256_streaming_clone_independent() {
1314        let mut streamer = Sha256Streaming::new();
1315        StreamingHash::update(&mut streamer, b"hello");
1316        let mut cloned = streamer.clone();
1317        // Feed different data to original vs clone
1318        StreamingHash::update(&mut streamer, b" world");
1319        StreamingHash::update(&mut cloned, b" clone");
1320
1321        let mut buf1 = [0u8; 32];
1322        let mut buf2 = [0u8; 32];
1323        StreamingHash::finalize(streamer, &mut buf1).expect("finalize original");
1324        StreamingHash::finalize(cloned, &mut buf2).expect("finalize clone");
1325
1326        // Different suffixes → different digests
1327        assert_ne!(
1328            buf1, buf2,
1329            "cloned streamer with different data must differ"
1330        );
1331    }
1332
1333    #[test]
1334    fn blake3_streaming_clone_independent() {
1335        let mut streamer = Blake3Streaming::new();
1336        StreamingHash::update(&mut streamer, b"hello");
1337        let mut cloned = streamer.clone();
1338        StreamingHash::update(&mut streamer, b" a");
1339        StreamingHash::update(&mut cloned, b" b");
1340
1341        let mut buf1 = [0u8; 32];
1342        let mut buf2 = [0u8; 32];
1343        StreamingHash::finalize(streamer, &mut buf1).expect("finalize original");
1344        StreamingHash::finalize(cloned, &mut buf2).expect("finalize clone");
1345
1346        assert_ne!(
1347            buf1, buf2,
1348            "cloned Blake3Streaming with different data must differ"
1349        );
1350    }
1351
1352    // ── hex_digest functions ──────────────────────────────────────────────────
1353
1354    #[cfg(feature = "std")]
1355    #[test]
1356    fn sha256_hex_known_vector() {
1357        // SHA-256("") = e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
1358        let hex = sha256_hex(b"");
1359        assert_eq!(
1360            hex,
1361            "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
1362        );
1363    }
1364
1365    #[cfg(feature = "std")]
1366    #[test]
1367    fn sha256_hex_length() {
1368        let hex = sha256_hex(b"abc");
1369        assert_eq!(hex.len(), 64, "SHA-256 hex string must be 64 characters");
1370        // Must be lowercase hex
1371        assert!(hex
1372            .chars()
1373            .all(|c| c.is_ascii_digit() || ('a'..='f').contains(&c)));
1374    }
1375
1376    #[cfg(feature = "std")]
1377    #[test]
1378    fn sha512_hex_length() {
1379        let hex = sha512_hex(b"abc");
1380        assert_eq!(hex.len(), 128, "SHA-512 hex string must be 128 characters");
1381    }
1382
1383    #[cfg(feature = "std")]
1384    #[test]
1385    fn blake3_hex_known_vector() {
1386        // BLAKE3("abc") = 6437b3ac38465133ffb63b75273a8db548c558465d79db03fd359c6cd5bd9d85
1387        let hex = blake3_hex(b"abc");
1388        assert_eq!(
1389            hex,
1390            "6437b3ac38465133ffb63b75273a8db548c558465d79db03fd359c6cd5bd9d85"
1391        );
1392    }
1393
1394    // ── io::Write for streaming hashers ──────────────────────────────────────
1395
1396    #[cfg(feature = "std")]
1397    #[test]
1398    fn sha256_streaming_io_write() {
1399        use std::io::Write;
1400        let mut streamer = Sha256Streaming::new();
1401        streamer.write_all(b"hello").expect("write_all hello");
1402        streamer.write_all(b" world").expect("write_all world");
1403        let mut buf = [0u8; 32];
1404        StreamingHash::finalize(streamer, &mut buf).expect("finalize");
1405
1406        let expected = Sha256.hash_to_vec(b"hello world").expect("one-shot");
1407        assert_eq!(
1408            &buf[..],
1409            expected.as_slice(),
1410            "io::Write result must match one-shot"
1411        );
1412    }
1413
1414    #[cfg(feature = "std")]
1415    #[test]
1416    fn blake3_streaming_io_write() {
1417        use std::io::Write;
1418        let mut streamer = Blake3Streaming::new();
1419        streamer.write_all(b"test data").expect("write_all");
1420        let mut buf = [0u8; 32];
1421        StreamingHash::finalize(streamer, &mut buf).expect("finalize");
1422
1423        let expected = Blake3.hash_to_vec(b"test data").expect("one-shot");
1424        assert_eq!(
1425            &buf[..],
1426            expected.as_slice(),
1427            "io::Write result must match one-shot"
1428        );
1429    }
1430
1431    // ── hash_fixed alloc-free path (no_std alternative to hash_to_vec) ────────
1432    //
1433    // These tests verify that the `hash_fixed` inherent methods (the alloc-free
1434    // alternative to `hash_to_vec`) produce identical digests to the `Hash`
1435    // trait path. When the `no_std` feature is enabled, callers use `hash_fixed`
1436    // or `Hash::hash_to_array` instead of `hash_to_vec`.
1437
1438    #[test]
1439    fn sha256_hash_fixed_matches_hash_trait() {
1440        // SHA-256 fixed-array path must match the trait-based one-shot hash.
1441        let msg = b"alloc-free sha256 test";
1442        let fixed: [u8; 32] = Sha256.hash_fixed(msg);
1443        let via_trait = Sha256.hash_to_vec(msg).expect("hash_to_vec");
1444        assert_eq!(&fixed[..], via_trait.as_slice());
1445    }
1446
1447    #[test]
1448    fn sha384_hash_fixed_matches_hash_trait() {
1449        let msg = b"alloc-free sha384 test";
1450        let fixed: [u8; 48] = Sha384.hash_fixed(msg);
1451        let via_trait = Sha384.hash_to_vec(msg).expect("hash_to_vec");
1452        assert_eq!(&fixed[..], via_trait.as_slice());
1453    }
1454
1455    #[test]
1456    fn sha512_hash_fixed_matches_hash_trait() {
1457        let msg = b"alloc-free sha512 test";
1458        let fixed: [u8; 64] = Sha512.hash_fixed(msg);
1459        let via_trait = Sha512.hash_to_vec(msg).expect("hash_to_vec");
1460        assert_eq!(&fixed[..], via_trait.as_slice());
1461    }
1462
1463    #[test]
1464    fn sha512_256_hash_fixed_matches_hash_trait() {
1465        let msg = b"alloc-free sha512/256 test";
1466        let fixed: [u8; 32] = Sha512_256.hash_fixed(msg);
1467        let via_trait = Sha512_256.hash_to_vec(msg).expect("hash_to_vec");
1468        assert_eq!(&fixed[..], via_trait.as_slice());
1469    }
1470
1471    #[test]
1472    fn sha3_256_hash_fixed_matches_hash_trait() {
1473        let msg = b"alloc-free sha3-256 test";
1474        let fixed: [u8; 32] = Sha3_256.hash_fixed(msg);
1475        let via_trait = Sha3_256.hash_to_vec(msg).expect("hash_to_vec");
1476        assert_eq!(&fixed[..], via_trait.as_slice());
1477    }
1478
1479    #[test]
1480    fn sha3_384_hash_fixed_matches_hash_trait() {
1481        let msg = b"alloc-free sha3-384 test";
1482        let fixed: [u8; 48] = Sha3_384.hash_fixed(msg);
1483        let via_trait = Sha3_384.hash_to_vec(msg).expect("hash_to_vec");
1484        assert_eq!(&fixed[..], via_trait.as_slice());
1485    }
1486
1487    #[test]
1488    fn sha3_512_hash_fixed_matches_hash_trait() {
1489        let msg = b"alloc-free sha3-512 test";
1490        let fixed: [u8; 64] = Sha3_512.hash_fixed(msg);
1491        let via_trait = Sha3_512.hash_to_vec(msg).expect("hash_to_vec");
1492        assert_eq!(&fixed[..], via_trait.as_slice());
1493    }
1494
1495    #[test]
1496    fn blake2b256_hash_fixed_matches_hash_trait() {
1497        let msg = b"alloc-free blake2b256 test";
1498        let fixed: [u8; 32] = Blake2b256.hash_fixed(msg);
1499        let via_trait = Blake2b256.hash_to_vec(msg).expect("hash_to_vec");
1500        assert_eq!(&fixed[..], via_trait.as_slice());
1501    }
1502
1503    #[test]
1504    fn blake2b512_hash_fixed_matches_hash_trait() {
1505        let msg = b"alloc-free blake2b512 test";
1506        let fixed: [u8; 64] = Blake2b512.hash_fixed(msg);
1507        let via_trait = Blake2b512.hash_to_vec(msg).expect("hash_to_vec");
1508        assert_eq!(&fixed[..], via_trait.as_slice());
1509    }
1510
1511    #[test]
1512    fn blake2s256_hash_fixed_matches_hash_trait() {
1513        let msg = b"alloc-free blake2s256 test";
1514        let fixed: [u8; 32] = Blake2s256.hash_fixed(msg);
1515        let via_trait = Blake2s256.hash_to_vec(msg).expect("hash_to_vec");
1516        assert_eq!(&fixed[..], via_trait.as_slice());
1517    }
1518
1519    #[test]
1520    fn blake3_hash_fixed_matches_hash_trait() {
1521        let msg = b"alloc-free blake3 test";
1522        let fixed: [u8; 32] = Blake3.hash_fixed(msg);
1523        let via_trait = Blake3.hash_to_vec(msg).expect("hash_to_vec");
1524        assert_eq!(&fixed[..], via_trait.as_slice());
1525    }
1526
1527    #[test]
1528    fn hash_fixed_known_vectors() {
1529        // Cross-verify hash_fixed against known-good RFC/NIST vectors.
1530        // SHA-256("abc") per FIPS 180-4 App B.1:
1531        let sha256_abc: [u8; 32] = Sha256.hash_fixed(b"abc");
1532        let expected_sha256 =
1533            hex_decode("ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad");
1534        assert_eq!(sha256_abc.as_ref(), expected_sha256.as_slice());
1535        // BLAKE3("abc") per official test vectors:
1536        let blake3_abc: [u8; 32] = Blake3.hash_fixed(b"abc");
1537        let expected_blake3 =
1538            hex_decode("6437b3ac38465133ffb63b75273a8db548c558465d79db03fd359c6cd5bd9d85");
1539        assert_eq!(blake3_abc.as_ref(), expected_blake3.as_slice());
1540    }
1541
1542    #[test]
1543    fn hash_fixed_empty_input() {
1544        // SHA-256("") per FIPS 180-4:
1545        let fixed: [u8; 32] = Sha256.hash_fixed(b"");
1546        let expected =
1547            hex_decode("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855");
1548        assert_eq!(fixed.as_ref(), expected.as_slice());
1549    }
1550}