Skip to main content

oxicrypto_mac/
lib.rs

1#![forbid(unsafe_code)]
2
3//! Pure Rust MAC implementations for the OxiCrypto stack.
4//!
5//! Provides [`Mac`] and [`StreamingMac`] trait wrappers for:
6//! - HMAC-SHA-256 / SHA-384 / SHA-512 (one-shot + streaming + truncated)
7//! - HMAC-SHA3-256 / SHA3-512
8//! - Poly1305 (one-time MAC)
9//! - CMAC-AES-128 / CMAC-AES-256
10//! - KMAC128 / KMAC256 (SP 800-185, via `tiny-keccak`)
11//!
12//! All MAC verifications use constant-time comparison via the `subtle` crate.
13
14extern crate alloc;
15
16use digest::KeyInit;
17use hmac::Mac as HmacMac;
18use oxicrypto_core::{CryptoError, Mac, StreamingMac};
19use subtle::ConstantTimeEq;
20
21// ── HMAC-SHA-256 ──────────────────────────────────────────────────────────────
22
23/// HMAC-SHA-256 message authentication code (32-byte tag).
24#[derive(Debug, Default, Clone, Copy)]
25pub struct HmacSha256;
26
27impl Mac for HmacSha256 {
28    fn name(&self) -> &'static str {
29        "HMAC-SHA-256"
30    }
31    fn key_len(&self) -> usize {
32        32
33    }
34    fn output_len(&self) -> usize {
35        32
36    }
37    fn mac(&self, key: &[u8], msg: &[u8], out: &mut [u8]) -> Result<(), CryptoError> {
38        if out.len() < 32 {
39            return Err(CryptoError::BufferTooSmall);
40        }
41        let mut mac =
42            hmac::Hmac::<sha2::Sha256>::new_from_slice(key).map_err(|_| CryptoError::InvalidKey)?;
43        mac.update(msg);
44        let result = mac.finalize().into_bytes();
45        out[..32].copy_from_slice(&result);
46        Ok(())
47    }
48    fn verify(&self, key: &[u8], msg: &[u8], tag: &[u8]) -> Result<(), CryptoError> {
49        if tag.len() != 32 {
50            return Err(CryptoError::InvalidTag);
51        }
52        let mut expected = [0u8; 32];
53        self.mac(key, msg, &mut expected)?;
54        if expected.ct_eq(tag).into() {
55            Ok(())
56        } else {
57            Err(CryptoError::InvalidTag)
58        }
59    }
60}
61
62impl HmacSha256 {
63    /// Compute a truncated HMAC-SHA-256 tag.
64    ///
65    /// Writes the first `out.len()` bytes of the full 32-byte HMAC into `out`.
66    /// Returns [`CryptoError::BadInput`] if `out.len() < 16` (minimum safe
67    /// truncation length per NIST SP 800-117).
68    pub fn mac_truncated(&self, key: &[u8], msg: &[u8], out: &mut [u8]) -> Result<(), CryptoError> {
69        let n = out.len();
70        if n < 16 {
71            return Err(CryptoError::BadInput);
72        }
73        let mut full = [0u8; 32];
74        self.mac(key, msg, &mut full)?;
75        out.copy_from_slice(&full[..n]);
76        Ok(())
77    }
78
79    /// Verify a truncated HMAC-SHA-256 tag in constant time.
80    ///
81    /// Returns [`CryptoError::BadInput`] if `tag.len() < 16`, or
82    /// [`CryptoError::InvalidTag`] on mismatch.
83    pub fn verify_truncated(&self, key: &[u8], msg: &[u8], tag: &[u8]) -> Result<(), CryptoError> {
84        let n = tag.len();
85        if n < 16 {
86            return Err(CryptoError::BadInput);
87        }
88        let mut buf = [0u8; 32];
89        self.mac(key, msg, &mut buf)?;
90        if buf[..n].ct_eq(tag).into() {
91            Ok(())
92        } else {
93            Err(CryptoError::InvalidTag)
94        }
95    }
96}
97
98// ── HMAC-SHA-512 ──────────────────────────────────────────────────────────────
99
100/// HMAC-SHA-512 message authentication code (64-byte tag).
101#[derive(Debug, Default, Clone, Copy)]
102pub struct HmacSha512;
103
104impl Mac for HmacSha512 {
105    fn name(&self) -> &'static str {
106        "HMAC-SHA-512"
107    }
108    fn key_len(&self) -> usize {
109        64
110    }
111    fn output_len(&self) -> usize {
112        64
113    }
114    fn mac(&self, key: &[u8], msg: &[u8], out: &mut [u8]) -> Result<(), CryptoError> {
115        if out.len() < 64 {
116            return Err(CryptoError::BufferTooSmall);
117        }
118        let mut mac =
119            hmac::Hmac::<sha2::Sha512>::new_from_slice(key).map_err(|_| CryptoError::InvalidKey)?;
120        mac.update(msg);
121        let result = mac.finalize().into_bytes();
122        out[..64].copy_from_slice(&result);
123        Ok(())
124    }
125    fn verify(&self, key: &[u8], msg: &[u8], tag: &[u8]) -> Result<(), CryptoError> {
126        if tag.len() != 64 {
127            return Err(CryptoError::InvalidTag);
128        }
129        let mut expected = [0u8; 64];
130        self.mac(key, msg, &mut expected)?;
131        if expected.ct_eq(tag).into() {
132            Ok(())
133        } else {
134            Err(CryptoError::InvalidTag)
135        }
136    }
137}
138
139impl HmacSha512 {
140    /// Compute a truncated HMAC-SHA-512 tag.
141    ///
142    /// Writes the first `out.len()` bytes of the full 64-byte HMAC into `out`.
143    /// Returns [`CryptoError::BadInput`] if `out.len() < 16`.
144    pub fn mac_truncated(&self, key: &[u8], msg: &[u8], out: &mut [u8]) -> Result<(), CryptoError> {
145        let n = out.len();
146        if n < 16 {
147            return Err(CryptoError::BadInput);
148        }
149        let mut full = [0u8; 64];
150        self.mac(key, msg, &mut full)?;
151        out.copy_from_slice(&full[..n]);
152        Ok(())
153    }
154
155    /// Verify a truncated HMAC-SHA-512 tag in constant time.
156    ///
157    /// Returns [`CryptoError::BadInput`] if `tag.len() < 16`, or
158    /// [`CryptoError::InvalidTag`] on mismatch.
159    pub fn verify_truncated(&self, key: &[u8], msg: &[u8], tag: &[u8]) -> Result<(), CryptoError> {
160        let n = tag.len();
161        if n < 16 {
162            return Err(CryptoError::BadInput);
163        }
164        let mut buf = [0u8; 64];
165        self.mac(key, msg, &mut buf)?;
166        if buf[..n].ct_eq(tag).into() {
167            Ok(())
168        } else {
169            Err(CryptoError::InvalidTag)
170        }
171    }
172}
173
174// ── HMAC-SHA-384 ──────────────────────────────────────────────────────────────
175
176/// HMAC-SHA-384 message authentication code (48-byte tag).
177#[derive(Debug, Default, Clone, Copy)]
178pub struct HmacSha384;
179
180impl Mac for HmacSha384 {
181    fn name(&self) -> &'static str {
182        "HMAC-SHA-384"
183    }
184    fn key_len(&self) -> usize {
185        48
186    }
187    fn output_len(&self) -> usize {
188        48
189    }
190    fn mac(&self, key: &[u8], msg: &[u8], out: &mut [u8]) -> Result<(), CryptoError> {
191        if out.len() < 48 {
192            return Err(CryptoError::BufferTooSmall);
193        }
194        let mut mac =
195            hmac::Hmac::<sha2::Sha384>::new_from_slice(key).map_err(|_| CryptoError::InvalidKey)?;
196        mac.update(msg);
197        let result = mac.finalize().into_bytes();
198        out[..48].copy_from_slice(&result);
199        Ok(())
200    }
201    fn verify(&self, key: &[u8], msg: &[u8], tag: &[u8]) -> Result<(), CryptoError> {
202        if tag.len() != 48 {
203            return Err(CryptoError::InvalidTag);
204        }
205        let mut expected = [0u8; 48];
206        self.mac(key, msg, &mut expected)?;
207        if expected.ct_eq(tag).into() {
208            Ok(())
209        } else {
210            Err(CryptoError::InvalidTag)
211        }
212    }
213}
214
215impl HmacSha384 {
216    /// Compute a truncated HMAC-SHA-384 tag.
217    ///
218    /// Writes the first `out.len()` bytes of the full 48-byte HMAC into `out`.
219    /// Returns [`CryptoError::BadInput`] if `out.len() < 16`.
220    pub fn mac_truncated(&self, key: &[u8], msg: &[u8], out: &mut [u8]) -> Result<(), CryptoError> {
221        let n = out.len();
222        if n < 16 {
223            return Err(CryptoError::BadInput);
224        }
225        let mut full = [0u8; 48];
226        self.mac(key, msg, &mut full)?;
227        out.copy_from_slice(&full[..n]);
228        Ok(())
229    }
230
231    /// Verify a truncated HMAC-SHA-384 tag in constant time.
232    ///
233    /// Returns [`CryptoError::BadInput`] if `tag.len() < 16`, or
234    /// [`CryptoError::InvalidTag`] on mismatch.
235    pub fn verify_truncated(&self, key: &[u8], msg: &[u8], tag: &[u8]) -> Result<(), CryptoError> {
236        let n = tag.len();
237        if n < 16 {
238            return Err(CryptoError::BadInput);
239        }
240        let mut buf = [0u8; 48];
241        self.mac(key, msg, &mut buf)?;
242        if buf[..n].ct_eq(tag).into() {
243            Ok(())
244        } else {
245            Err(CryptoError::InvalidTag)
246        }
247    }
248}
249
250// ── StreamingMac adapter (generic HMAC) ───────────────────────────────────────
251
252/// Generic streaming MAC adapter wrapping `hmac::Hmac<D>`.
253///
254/// Implements [`StreamingMac`]: feed chunks with `update`, then consume with
255/// `finalize` or `verify`.
256pub struct HmacStreamingAdapter<D: hmac::digest::block_api::EagerHash>
257where
258    hmac::Hmac<D>: HmacMac + KeyInit,
259{
260    inner: hmac::Hmac<D>,
261}
262
263impl<D: hmac::digest::block_api::EagerHash> HmacStreamingAdapter<D>
264where
265    hmac::Hmac<D>: HmacMac + KeyInit,
266{
267    /// Create a new streaming adapter with the given key.
268    ///
269    /// Returns [`CryptoError::InvalidKey`] if the key is rejected by HMAC.
270    pub fn new(key: &[u8]) -> Result<Self, CryptoError> {
271        let inner = hmac::Hmac::<D>::new_from_slice(key).map_err(|_| CryptoError::InvalidKey)?;
272        Ok(Self { inner })
273    }
274}
275
276impl<D: hmac::digest::block_api::EagerHash + Send> StreamingMac for HmacStreamingAdapter<D>
277where
278    hmac::Hmac<D>: HmacMac + KeyInit + Send,
279{
280    fn update(&mut self, data: &[u8]) {
281        self.inner.update(data);
282    }
283
284    fn finalize(self, out: &mut [u8]) -> Result<(), CryptoError> {
285        let tag = self.inner.finalize().into_bytes();
286        if out.len() < tag.len() {
287            return Err(CryptoError::BufferTooSmall);
288        }
289        out[..tag.len()].copy_from_slice(&tag);
290        Ok(())
291    }
292
293    fn verify(self, expected: &[u8]) -> Result<(), CryptoError> {
294        let tag = self.inner.finalize().into_bytes();
295        if tag.len() != expected.len() {
296            return Err(CryptoError::InvalidTag);
297        }
298        if tag.as_slice().ct_eq(expected).into() {
299            Ok(())
300        } else {
301            Err(CryptoError::InvalidTag)
302        }
303    }
304}
305
306/// Streaming HMAC-SHA-256 adapter.
307pub type HmacSha256Streaming = HmacStreamingAdapter<sha2::Sha256>;
308/// Streaming HMAC-SHA-384 adapter.
309pub type HmacSha384Streaming = HmacStreamingAdapter<sha2::Sha384>;
310/// Streaming HMAC-SHA-512 adapter.
311pub type HmacSha512Streaming = HmacStreamingAdapter<sha2::Sha512>;
312
313// ── HMAC-SHA3-256 ─────────────────────────────────────────────────────────────
314
315/// HMAC-SHA3-256 message authentication code (32-byte tag).
316///
317/// Uses `hmac::SimpleHmac` because sha3 0.12's types do not expose the
318/// block-level `CoreProxy` trait required by `hmac::Hmac<D>`.
319#[derive(Debug, Default, Clone, Copy)]
320pub struct HmacSha3_256;
321
322impl Mac for HmacSha3_256 {
323    fn name(&self) -> &'static str {
324        "HMAC-SHA3-256"
325    }
326    fn key_len(&self) -> usize {
327        32
328    }
329    fn output_len(&self) -> usize {
330        32
331    }
332    fn mac(&self, key: &[u8], msg: &[u8], out: &mut [u8]) -> Result<(), CryptoError> {
333        use digest::KeyInit as _;
334
335        if out.len() < 32 {
336            return Err(CryptoError::BufferTooSmall);
337        }
338        let mut mac = hmac::SimpleHmac::<sha3::Sha3_256>::new_from_slice(key)
339            .map_err(|_| CryptoError::InvalidKey)?;
340        HmacMac::update(&mut mac, msg);
341        let result = HmacMac::finalize(mac).into_bytes();
342        out[..32].copy_from_slice(&result);
343        Ok(())
344    }
345    fn verify(&self, key: &[u8], msg: &[u8], tag: &[u8]) -> Result<(), CryptoError> {
346        if tag.len() != 32 {
347            return Err(CryptoError::InvalidTag);
348        }
349        let mut expected = [0u8; 32];
350        self.mac(key, msg, &mut expected)?;
351        if expected.ct_eq(tag).into() {
352            Ok(())
353        } else {
354            Err(CryptoError::InvalidTag)
355        }
356    }
357}
358
359// ── HMAC-SHA3-512 ─────────────────────────────────────────────────────────────
360
361/// HMAC-SHA3-512 message authentication code (64-byte tag).
362///
363/// Uses `hmac::SimpleHmac` because sha3 0.12's types do not expose the
364/// block-level `CoreProxy` trait required by `hmac::Hmac<D>`.
365#[derive(Debug, Default, Clone, Copy)]
366pub struct HmacSha3_512;
367
368impl Mac for HmacSha3_512 {
369    fn name(&self) -> &'static str {
370        "HMAC-SHA3-512"
371    }
372    fn key_len(&self) -> usize {
373        64
374    }
375    fn output_len(&self) -> usize {
376        64
377    }
378    fn mac(&self, key: &[u8], msg: &[u8], out: &mut [u8]) -> Result<(), CryptoError> {
379        use digest::KeyInit as _;
380
381        if out.len() < 64 {
382            return Err(CryptoError::BufferTooSmall);
383        }
384        let mut mac = hmac::SimpleHmac::<sha3::Sha3_512>::new_from_slice(key)
385            .map_err(|_| CryptoError::InvalidKey)?;
386        HmacMac::update(&mut mac, msg);
387        let result = HmacMac::finalize(mac).into_bytes();
388        out[..64].copy_from_slice(&result);
389        Ok(())
390    }
391    fn verify(&self, key: &[u8], msg: &[u8], tag: &[u8]) -> Result<(), CryptoError> {
392        if tag.len() != 64 {
393            return Err(CryptoError::InvalidTag);
394        }
395        let mut expected = [0u8; 64];
396        self.mac(key, msg, &mut expected)?;
397        if expected.ct_eq(tag).into() {
398            Ok(())
399        } else {
400            Err(CryptoError::InvalidTag)
401        }
402    }
403}
404
405// ── Poly1305 ──────────────────────────────────────────────────────────────────
406
407/// Poly1305 one-time message authentication code (16-byte tag).
408///
409/// # Security warning
410///
411/// Poly1305 is a **one-time MAC**: the 32-byte key MUST NOT be reused for
412/// different messages.  Re-use of the same key across messages completely
413/// destroys the security guarantee.  In practice, derive a fresh per-message
414/// key from a stream cipher (e.g. ChaCha20) or a KDF.
415#[derive(Debug, Default, Clone, Copy)]
416pub struct Poly1305Mac;
417
418impl Mac for Poly1305Mac {
419    fn name(&self) -> &'static str {
420        "Poly1305"
421    }
422    fn key_len(&self) -> usize {
423        32
424    }
425    fn output_len(&self) -> usize {
426        16
427    }
428    fn mac(&self, key: &[u8], msg: &[u8], out: &mut [u8]) -> Result<(), CryptoError> {
429        use poly1305::universal_hash::KeyInit as _;
430
431        if key.len() != 32 {
432            return Err(CryptoError::InvalidKey);
433        }
434        if out.len() < 16 {
435            return Err(CryptoError::BufferTooSmall);
436        }
437        let key_arr = poly1305::Key::try_from(key).map_err(|_| CryptoError::InvalidKey)?;
438        let mac = poly1305::Poly1305::new(&key_arr);
439        // compute_unpadded is the standard Poly1305 MAC computation:
440        // it adds a 0x01 high-bit to partial final blocks (per RFC 8439 §2.5).
441        // update_padded would zero-pad partial blocks, which is incorrect for MAC.
442        let tag = mac.compute_unpadded(msg);
443        out[..16].copy_from_slice(tag.as_slice());
444        Ok(())
445    }
446    fn verify(&self, key: &[u8], msg: &[u8], tag: &[u8]) -> Result<(), CryptoError> {
447        if tag.len() != 16 {
448            return Err(CryptoError::InvalidTag);
449        }
450        let mut computed = [0u8; 16];
451        self.mac(key, msg, &mut computed)?;
452        if computed.ct_eq(tag).into() {
453            Ok(())
454        } else {
455            Err(CryptoError::InvalidTag)
456        }
457    }
458}
459
460// ── CMAC-AES-128 ──────────────────────────────────────────────────────────────
461
462/// CMAC-AES-128 message authentication code (16-byte tag).
463///
464/// Uses `cmac 0.8` with `aes 0.9` (cipher 0.5 trait chain).
465#[derive(Debug, Default, Clone, Copy)]
466pub struct CmacAes128;
467
468impl Mac for CmacAes128 {
469    fn name(&self) -> &'static str {
470        "CMAC-AES-128"
471    }
472    fn key_len(&self) -> usize {
473        16
474    }
475    fn output_len(&self) -> usize {
476        16
477    }
478    fn mac(&self, key: &[u8], msg: &[u8], out: &mut [u8]) -> Result<(), CryptoError> {
479        use cmac::Mac as _;
480        use digest::KeyInit as _;
481
482        if out.len() < 16 {
483            return Err(CryptoError::BufferTooSmall);
484        }
485        let mut mac = cmac::Cmac::<aes_cipher05::Aes128>::new_from_slice(key)
486            .map_err(|_| CryptoError::InvalidKey)?;
487        mac.update(msg);
488        let result = mac.finalize().into_bytes();
489        out[..16].copy_from_slice(&result);
490        Ok(())
491    }
492    fn verify(&self, key: &[u8], msg: &[u8], tag: &[u8]) -> Result<(), CryptoError> {
493        if tag.len() != 16 {
494            return Err(CryptoError::InvalidTag);
495        }
496        let mut expected = [0u8; 16];
497        self.mac(key, msg, &mut expected)?;
498        if expected.ct_eq(tag).into() {
499            Ok(())
500        } else {
501            Err(CryptoError::InvalidTag)
502        }
503    }
504}
505
506// ── CMAC-AES-256 ──────────────────────────────────────────────────────────────
507
508/// CMAC-AES-256 message authentication code (16-byte tag).
509///
510/// Uses `cmac 0.8` with `aes 0.9` (cipher 0.5 trait chain).
511#[derive(Debug, Default, Clone, Copy)]
512pub struct CmacAes256;
513
514impl Mac for CmacAes256 {
515    fn name(&self) -> &'static str {
516        "CMAC-AES-256"
517    }
518    fn key_len(&self) -> usize {
519        32
520    }
521    fn output_len(&self) -> usize {
522        16
523    }
524    fn mac(&self, key: &[u8], msg: &[u8], out: &mut [u8]) -> Result<(), CryptoError> {
525        use cmac::Mac as _;
526        use digest::KeyInit as _;
527
528        if out.len() < 16 {
529            return Err(CryptoError::BufferTooSmall);
530        }
531        let mut mac = cmac::Cmac::<aes_cipher05::Aes256>::new_from_slice(key)
532            .map_err(|_| CryptoError::InvalidKey)?;
533        mac.update(msg);
534        let result = mac.finalize().into_bytes();
535        out[..16].copy_from_slice(&result);
536        Ok(())
537    }
538    fn verify(&self, key: &[u8], msg: &[u8], tag: &[u8]) -> Result<(), CryptoError> {
539        if tag.len() != 16 {
540            return Err(CryptoError::InvalidTag);
541        }
542        let mut expected = [0u8; 16];
543        self.mac(key, msg, &mut expected)?;
544        if expected.ct_eq(tag).into() {
545            Ok(())
546        } else {
547            Err(CryptoError::InvalidTag)
548        }
549    }
550}
551
552// ── KMAC128 ───────────────────────────────────────────────────────────────────
553
554/// KMAC128 message authentication code (SP 800-185).
555///
556/// Variable-length output; the default output length is 32 bytes.
557/// Uses a customization string (may be empty) for domain separation.
558pub struct Kmac128 {
559    /// Customization string for domain separation (SP 800-185 §3.3).
560    custom: alloc::vec::Vec<u8>,
561    /// Output tag length in bytes (minimum 1, default 32).
562    output_len: usize,
563}
564
565impl Kmac128 {
566    /// Create a new KMAC128 with the given customization string and output length.
567    ///
568    /// Returns [`CryptoError::BadInput`] if `output_len` is 0.
569    pub fn new(custom: &[u8], output_len: usize) -> Result<Self, CryptoError> {
570        if output_len == 0 {
571            return Err(CryptoError::BadInput);
572        }
573        Ok(Self {
574            custom: custom.to_vec(),
575            output_len,
576        })
577    }
578}
579
580impl core::fmt::Debug for Kmac128 {
581    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
582        f.debug_struct("Kmac128")
583            .field("output_len", &self.output_len)
584            .finish()
585    }
586}
587
588impl Mac for Kmac128 {
589    fn name(&self) -> &'static str {
590        "KMAC128"
591    }
592    fn key_len(&self) -> usize {
593        // KMAC accepts variable-length keys; recommend >= 16 bytes.
594        16
595    }
596    fn output_len(&self) -> usize {
597        self.output_len
598    }
599    fn mac(&self, key: &[u8], msg: &[u8], out: &mut [u8]) -> Result<(), CryptoError> {
600        use tiny_keccak::Hasher as _;
601        if out.len() < self.output_len {
602            return Err(CryptoError::BufferTooSmall);
603        }
604        let mut kmac = tiny_keccak::Kmac::v128(key, &self.custom);
605        kmac.update(msg);
606        kmac.finalize(&mut out[..self.output_len]);
607        Ok(())
608    }
609    fn verify(&self, key: &[u8], msg: &[u8], tag: &[u8]) -> Result<(), CryptoError> {
610        if tag.len() != self.output_len {
611            return Err(CryptoError::InvalidTag);
612        }
613        let mut computed = alloc::vec![0u8; self.output_len];
614        self.mac(key, msg, &mut computed)?;
615        if computed.ct_eq(tag).into() {
616            Ok(())
617        } else {
618            Err(CryptoError::InvalidTag)
619        }
620    }
621}
622
623// ── KMAC256 ───────────────────────────────────────────────────────────────────
624
625/// KMAC256 message authentication code (SP 800-185).
626///
627/// Variable-length output; the default output length is 64 bytes.
628/// Uses a customization string (may be empty) for domain separation.
629pub struct Kmac256 {
630    /// Customization string for domain separation.
631    custom: alloc::vec::Vec<u8>,
632    /// Output tag length in bytes (minimum 1, default 64).
633    output_len: usize,
634}
635
636impl Kmac256 {
637    /// Create a new KMAC256 with the given customization string and output length.
638    ///
639    /// Returns [`CryptoError::BadInput`] if `output_len` is 0.
640    pub fn new(custom: &[u8], output_len: usize) -> Result<Self, CryptoError> {
641        if output_len == 0 {
642            return Err(CryptoError::BadInput);
643        }
644        Ok(Self {
645            custom: custom.to_vec(),
646            output_len,
647        })
648    }
649}
650
651impl core::fmt::Debug for Kmac256 {
652    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
653        f.debug_struct("Kmac256")
654            .field("output_len", &self.output_len)
655            .finish()
656    }
657}
658
659impl Mac for Kmac256 {
660    fn name(&self) -> &'static str {
661        "KMAC256"
662    }
663    fn key_len(&self) -> usize {
664        32
665    }
666    fn output_len(&self) -> usize {
667        self.output_len
668    }
669    fn mac(&self, key: &[u8], msg: &[u8], out: &mut [u8]) -> Result<(), CryptoError> {
670        use tiny_keccak::Hasher as _;
671        if out.len() < self.output_len {
672            return Err(CryptoError::BufferTooSmall);
673        }
674        let mut kmac = tiny_keccak::Kmac::v256(key, &self.custom);
675        kmac.update(msg);
676        kmac.finalize(&mut out[..self.output_len]);
677        Ok(())
678    }
679    fn verify(&self, key: &[u8], msg: &[u8], tag: &[u8]) -> Result<(), CryptoError> {
680        if tag.len() != self.output_len {
681            return Err(CryptoError::InvalidTag);
682        }
683        let mut computed = alloc::vec![0u8; self.output_len];
684        self.mac(key, msg, &mut computed)?;
685        if computed.ct_eq(tag).into() {
686            Ok(())
687        } else {
688            Err(CryptoError::InvalidTag)
689        }
690    }
691}
692
693// ── KMAC-XOF free functions ───────────────────────────────────────────────────
694
695/// KMAC128 with variable-length output (XOF mode, SP 800-185 §4.3.1).
696///
697/// Convenience free function returning an owned `Vec<u8>`.  For structured
698/// use with the [`Mac`] trait, see [`Kmac128`] which already accepts any
699/// output length.
700///
701/// - `key`: KMAC key (recommended ≥ 16 bytes for 128-bit security)
702/// - `custom`: customization string (e.g. `b"my-app-context"`), may be empty
703/// - `msg`: message data
704/// - `output_len`: desired output length in bytes
705///
706/// Returns [`CryptoError::BadInput`] if `output_len == 0`.
707pub fn kmac128_xof(
708    key: &[u8],
709    custom: &[u8],
710    msg: &[u8],
711    output_len: usize,
712) -> Result<alloc::vec::Vec<u8>, CryptoError> {
713    use tiny_keccak::Hasher as _;
714    if output_len == 0 {
715        return Err(CryptoError::BadInput);
716    }
717    let mut k = tiny_keccak::Kmac::v128(key, custom);
718    k.update(msg);
719    let mut out = alloc::vec![0u8; output_len];
720    k.finalize(&mut out);
721    Ok(out)
722}
723
724/// KMAC256 with variable-length output (XOF mode, SP 800-185 §4.3.1).
725///
726/// Convenience free function returning an owned `Vec<u8>`.  For structured
727/// use with the [`Mac`] trait, see [`Kmac256`] which already accepts any
728/// output length.
729///
730/// - `key`: KMAC key (recommended ≥ 32 bytes for 256-bit security)
731/// - `custom`: customization string (e.g. `b"my-app-context"`), may be empty
732/// - `msg`: message data
733/// - `output_len`: desired output length in bytes
734///
735/// Returns [`CryptoError::BadInput`] if `output_len == 0`.
736pub fn kmac256_xof(
737    key: &[u8],
738    custom: &[u8],
739    msg: &[u8],
740    output_len: usize,
741) -> Result<alloc::vec::Vec<u8>, CryptoError> {
742    use tiny_keccak::Hasher as _;
743    if output_len == 0 {
744        return Err(CryptoError::BadInput);
745    }
746    let mut k = tiny_keccak::Kmac::v256(key, custom);
747    k.update(msg);
748    let mut out = alloc::vec![0u8; output_len];
749    k.finalize(&mut out);
750    Ok(out)
751}
752
753// ── BLAKE3 keyed-hash MAC ─────────────────────────────────────────────────────
754
755/// BLAKE3 keyed-hash MAC (BLAKE3 spec §2.7).
756///
757/// This is BLAKE3's **native** authentication mode, **not** HMAC with BLAKE3.
758/// The key must be exactly 32 bytes.  Output is always 32 bytes.
759///
760/// This is faster than [`hmac_sha256_to_vec`] at an equivalent security level
761/// because BLAKE3 uses a single-pass tree construction rather than the
762/// double-compression of HMAC.
763///
764/// Use [`blake3_keyed_mac_verify`] for constant-time verification.
765pub fn blake3_keyed_mac(key: &[u8; 32], msg: &[u8]) -> [u8; 32] {
766    *blake3::Hasher::new_keyed(key)
767        .update(msg)
768        .finalize()
769        .as_bytes()
770}
771
772/// Verify a BLAKE3 keyed-hash MAC in constant time.
773///
774/// Returns `Ok(())` when `expected` matches the BLAKE3 keyed-hash of `msg`
775/// under `key`, or [`CryptoError::InvalidTag`] on mismatch.
776pub fn blake3_keyed_mac_verify(
777    key: &[u8; 32],
778    msg: &[u8],
779    expected: &[u8; 32],
780) -> Result<(), CryptoError> {
781    let actual = blake3_keyed_mac(key, msg);
782    if actual.ct_eq(expected).into() {
783        Ok(())
784    } else {
785        Err(CryptoError::InvalidTag)
786    }
787}
788
789// ── Standalone truncated-verify helper ───────────────────────────────────────
790
791/// Verify the first `truncated_tag.len()` bytes of an HMAC-SHA-256 MAC.
792///
793/// Useful for protocols that truncate MAC tags (e.g. to 16 bytes for bandwidth
794/// savings).  Always performs constant-time comparison over exactly
795/// `truncated_tag.len()` bytes.
796///
797/// Note: this helper accepts tags as short as 1 byte (permissive API for
798/// protocol use).  For production use, prefer [`HmacSha256::verify_truncated`]
799/// which enforces a 16-byte minimum per NIST SP 800-117.
800///
801/// Returns [`CryptoError::BadInput`] if `truncated_tag` is empty or longer
802/// than 32 bytes.
803pub fn hmac_sha256_verify_truncated(
804    key: &[u8],
805    msg: &[u8],
806    truncated_tag: &[u8],
807) -> Result<(), CryptoError> {
808    if truncated_tag.is_empty() || truncated_tag.len() > 32 {
809        return Err(CryptoError::BadInput);
810    }
811    let mut buf = [0u8; 32];
812    HmacSha256.mac(key, msg, &mut buf)?;
813    if buf[..truncated_tag.len()].ct_eq(truncated_tag).into() {
814        Ok(())
815    } else {
816        Err(CryptoError::InvalidTag)
817    }
818}
819
820// ── Convenience: mac_to_vec helpers ──────────────────────────────────────────
821
822/// Compute an HMAC-SHA-256 tag and return it as a 32-byte [`Vec<u8>`].
823///
824/// This is a convenience wrapper around [`HmacSha256::mac`] for callers that
825/// prefer an owned return value over a pre-allocated output buffer.
826///
827/// # Errors
828///
829/// Returns [`CryptoError::InvalidKey`] if the HMAC crate rejects the key.
830#[must_use = "MAC result must be used or verified"]
831pub fn hmac_sha256_to_vec(key: &[u8], msg: &[u8]) -> Result<alloc::vec::Vec<u8>, CryptoError> {
832    let mut out = alloc::vec![0u8; 32];
833    HmacSha256.mac(key, msg, &mut out)?;
834    Ok(out)
835}
836
837/// Compute an HMAC-SHA-384 tag and return it as a 48-byte [`Vec<u8>`].
838///
839/// This is a convenience wrapper around [`HmacSha384::mac`] for callers that
840/// prefer an owned return value over a pre-allocated output buffer.
841///
842/// # Errors
843///
844/// Returns [`CryptoError::InvalidKey`] if the HMAC crate rejects the key.
845#[must_use = "MAC result must be used or verified"]
846pub fn hmac_sha384_to_vec(key: &[u8], msg: &[u8]) -> Result<alloc::vec::Vec<u8>, CryptoError> {
847    let mut out = alloc::vec![0u8; 48];
848    HmacSha384.mac(key, msg, &mut out)?;
849    Ok(out)
850}
851
852/// Compute an HMAC-SHA-512 tag and return it as a 64-byte [`Vec<u8>`].
853///
854/// This is a convenience wrapper around [`HmacSha512::mac`] for callers that
855/// prefer an owned return value over a pre-allocated output buffer.
856///
857/// # Errors
858///
859/// Returns [`CryptoError::InvalidKey`] if the HMAC crate rejects the key.
860#[must_use = "MAC result must be used or verified"]
861pub fn hmac_sha512_to_vec(key: &[u8], msg: &[u8]) -> Result<alloc::vec::Vec<u8>, CryptoError> {
862    let mut out = alloc::vec![0u8; 64];
863    HmacSha512.mac(key, msg, &mut out)?;
864    Ok(out)
865}
866
867// ── Tests ─────────────────────────────────────────────────────────────────────
868
869#[cfg(test)]
870mod tests {
871    use super::*;
872
873    fn hex_decode(s: &str) -> alloc::vec::Vec<u8> {
874        (0..s.len())
875            .step_by(2)
876            .map(|i| u8::from_str_radix(&s[i..i + 2], 16).unwrap())
877            .collect()
878    }
879
880    // ── HMAC-SHA-256 ────────────────────────────────────────────────────────
881
882    // RFC 4231 Test Case 1
883    #[test]
884    fn hmac_sha256_rfc4231_tc1() {
885        let key = hex_decode("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b");
886        let data = b"Hi There";
887        let expected =
888            hex_decode("b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7");
889
890        let mac = HmacSha256;
891        let mut out = [0u8; 32];
892        mac.mac(&key, data, &mut out).unwrap();
893        assert_eq!(&out[..], expected.as_slice(), "HMAC-SHA-256 RFC4231 TC1");
894    }
895
896    #[test]
897    fn hmac_sha256_verify_ok() {
898        let key = b"secret-key";
899        let msg = b"the message";
900        let mac_impl = HmacSha256;
901        let mut tag = [0u8; 32];
902        mac_impl.mac(key, msg, &mut tag).unwrap();
903        mac_impl
904            .verify(key, msg, &tag)
905            .expect("verify should succeed");
906    }
907
908    #[test]
909    fn hmac_sha256_verify_fail() {
910        let key = b"secret-key";
911        let msg = b"the message";
912        let mac_impl = HmacSha256;
913        let mut tag = [0u8; 32];
914        mac_impl.mac(key, msg, &mut tag).unwrap();
915        tag[0] ^= 0xff;
916        let result = mac_impl.verify(key, msg, &tag);
917        assert_eq!(result, Err(CryptoError::InvalidTag));
918    }
919
920    // ── HMAC-SHA-512 ────────────────────────────────────────────────────────
921
922    #[test]
923    fn hmac_sha512_round_trip() {
924        let key = b"another-secret-key";
925        let msg = b"another message";
926        let mac_impl = HmacSha512;
927        let mut tag = [0u8; 64];
928        mac_impl.mac(key, msg, &mut tag).unwrap();
929        mac_impl
930            .verify(key, msg, &tag)
931            .expect("verify should succeed");
932    }
933
934    #[test]
935    fn hmac_sha512_verify_fail() {
936        let key = b"key";
937        let msg = b"msg";
938        let mac_impl = HmacSha512;
939        let mut tag = [0u8; 64];
940        mac_impl.mac(key, msg, &mut tag).unwrap();
941        tag[0] ^= 1;
942        assert_eq!(
943            mac_impl.verify(key, msg, &tag),
944            Err(CryptoError::InvalidTag)
945        );
946    }
947
948    // ── HMAC-SHA-384 ────────────────────────────────────────────────────────
949
950    // RFC 4231 Test Case 1 for HMAC-SHA-384
951    #[test]
952    fn hmac_sha384_rfc4231_tc1() {
953        let key = hex_decode("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b");
954        let data = b"Hi There";
955        let expected = hex_decode(
956            "afd03944d84895626b0825f4ab46907f15f9dadbe4101ec682aa034c7cebc59c\
957             faea9ea9076ede7f4af152e8b2fa9cb6",
958        );
959
960        let mac = HmacSha384;
961        let mut out = [0u8; 48];
962        mac.mac(&key, data, &mut out).unwrap();
963        assert_eq!(&out[..], expected.as_slice(), "HMAC-SHA-384 RFC4231 TC1");
964    }
965
966    #[test]
967    fn hmac_sha384_round_trip() {
968        let key = b"hmac-sha384-test-key";
969        let msg = b"test message for sha384";
970        let mac = HmacSha384;
971        let mut tag = [0u8; 48];
972        mac.mac(key, msg, &mut tag).unwrap();
973        mac.verify(key, msg, &tag).expect("verify should succeed");
974    }
975
976    #[test]
977    fn hmac_sha384_verify_fail() {
978        let key = b"key";
979        let msg = b"msg";
980        let mac = HmacSha384;
981        let mut tag = [0u8; 48];
982        mac.mac(key, msg, &mut tag).unwrap();
983        tag[0] ^= 1;
984        assert_eq!(mac.verify(key, msg, &tag), Err(CryptoError::InvalidTag));
985    }
986
987    // ── StreamingMac adapter ─────────────────────────────────────────────────
988
989    /// Verify that the streaming adapter produces the same tag as the one-shot
990    /// HmacSha256::mac method (fed the same message in two chunks).
991    #[test]
992    fn hmac_sha256_streaming_matches_oneshot() {
993        let key = b"streaming-key";
994        let msg_a = b"hello ";
995        let msg_b = b"world";
996        let full_msg = b"hello world";
997
998        // One-shot
999        let one_shot = HmacSha256;
1000        let mut expected = [0u8; 32];
1001        one_shot.mac(key, full_msg, &mut expected).unwrap();
1002
1003        // Streaming
1004        let mut streaming = HmacSha256Streaming::new(key).unwrap();
1005        streaming.update(msg_a);
1006        streaming.update(msg_b);
1007        let mut got = [0u8; 32];
1008        streaming.finalize(&mut got).unwrap();
1009
1010        assert_eq!(expected, got, "streaming must match one-shot");
1011    }
1012
1013    #[test]
1014    fn hmac_sha256_streaming_verify_ok() {
1015        let key = b"verify-key";
1016        let msg = b"verify message";
1017
1018        let mut one_shot_tag = [0u8; 32];
1019        HmacSha256.mac(key, msg, &mut one_shot_tag).unwrap();
1020
1021        let mut streaming = HmacSha256Streaming::new(key).unwrap();
1022        streaming.update(msg);
1023        streaming
1024            .verify(&one_shot_tag)
1025            .expect("streaming verify must succeed");
1026    }
1027
1028    #[test]
1029    fn hmac_sha256_streaming_verify_fail() {
1030        let key = b"k";
1031        let msg = b"m";
1032        let bad_tag = [0xffu8; 32];
1033
1034        let mut streaming = HmacSha256Streaming::new(key).unwrap();
1035        streaming.update(msg);
1036        assert_eq!(
1037            streaming.verify(&bad_tag),
1038            Err(CryptoError::InvalidTag),
1039            "streaming verify must fail on wrong tag"
1040        );
1041    }
1042
1043    // ── HMAC-SHA3-256 ────────────────────────────────────────────────────────
1044
1045    /// Basic KAT: HMAC-SHA3-256 of "Hi There" with RFC 4231 key
1046    /// (reference computed offline using SHA3-256 as the hash function).
1047    #[test]
1048    fn hmac_sha3_256_round_trip() {
1049        let key = b"hmac-sha3-256-key";
1050        let msg = b"test message";
1051        let mac = HmacSha3_256;
1052        let mut tag = [0u8; 32];
1053        mac.mac(key, msg, &mut tag).unwrap();
1054        mac.verify(key, msg, &tag)
1055            .expect("HMAC-SHA3-256 verify must succeed");
1056    }
1057
1058    #[test]
1059    fn hmac_sha3_256_verify_fail() {
1060        let key = b"k";
1061        let msg = b"m";
1062        let mac = HmacSha3_256;
1063        let mut tag = [0u8; 32];
1064        mac.mac(key, msg, &mut tag).unwrap();
1065        tag[0] ^= 1;
1066        assert_eq!(mac.verify(key, msg, &tag), Err(CryptoError::InvalidTag));
1067    }
1068
1069    // ── HMAC-SHA3-512 ────────────────────────────────────────────────────────
1070
1071    #[test]
1072    fn hmac_sha3_512_round_trip() {
1073        let key = b"hmac-sha3-512-test-key";
1074        let msg = b"test message for sha3-512";
1075        let mac = HmacSha3_512;
1076        let mut tag = [0u8; 64];
1077        mac.mac(key, msg, &mut tag).unwrap();
1078        mac.verify(key, msg, &tag)
1079            .expect("HMAC-SHA3-512 verify must succeed");
1080    }
1081
1082    // ── Poly1305 ─────────────────────────────────────────────────────────────
1083
1084    /// RFC 8439 §2.5.2 test vector.
1085    ///
1086    /// key  = 85d6be7857556d337f4452fe42d506a
1087    ///         80103808afb0db2fd4abff6af4149f51
1088    /// data = "Cryptographic Forum Research Group"
1089    /// tag  = a8061dc1305136c6c22b8baf0c0127a9
1090    #[test]
1091    fn poly1305_rfc8439_s2_5_2() {
1092        let key = hex_decode(
1093            "85d6be7857556d337f4452fe42d506a8\
1094             0103808afb0db2fd4abff6af4149f51b",
1095        );
1096        let msg = b"Cryptographic Forum Research Group";
1097        let expected = hex_decode("a8061dc1305136c6c22b8baf0c0127a9");
1098
1099        let mac = Poly1305Mac;
1100        let mut out = [0u8; 16];
1101        mac.mac(&key, msg, &mut out).unwrap();
1102        assert_eq!(&out[..], expected.as_slice(), "Poly1305 RFC8439 §2.5.2");
1103    }
1104
1105    #[test]
1106    fn poly1305_verify_ok() {
1107        let key = [0u8; 32];
1108        let msg = b"test";
1109        let mac = Poly1305Mac;
1110        let mut tag = [0u8; 16];
1111        mac.mac(&key, msg, &mut tag).unwrap();
1112        mac.verify(&key, msg, &tag)
1113            .expect("Poly1305 verify must succeed");
1114    }
1115
1116    #[test]
1117    fn poly1305_verify_fail() {
1118        let key = [1u8; 32];
1119        let msg = b"test";
1120        let mac = Poly1305Mac;
1121        let mut tag = [0u8; 16];
1122        mac.mac(&key, msg, &mut tag).unwrap();
1123        tag[0] ^= 0xff;
1124        assert_eq!(mac.verify(&key, msg, &tag), Err(CryptoError::InvalidTag));
1125    }
1126
1127    #[test]
1128    fn poly1305_bad_key_len() {
1129        let key = [0u8; 16]; // wrong length
1130        let mac = Poly1305Mac;
1131        let mut out = [0u8; 16];
1132        assert_eq!(
1133            mac.mac(&key, b"msg", &mut out),
1134            Err(CryptoError::InvalidKey)
1135        );
1136    }
1137
1138    // ── CMAC-AES-128 ─────────────────────────────────────────────────────────
1139
1140    /// NIST SP 800-38B Example 1: AES-128, empty message.
1141    ///
1142    /// K   = 2b7e151628aed2a6abf7158809cf4f3c
1143    /// M   = (empty)
1144    /// T16 = bb1d6929e9593728 7fa37d129b756746
1145    #[test]
1146    fn cmac_aes128_nist_sp800_38b_example1() {
1147        let key = hex_decode("2b7e151628aed2a6abf7158809cf4f3c");
1148        let expected = hex_decode("bb1d6929e95937287fa37d129b756746");
1149
1150        let mac = CmacAes128;
1151        let mut out = [0u8; 16];
1152        mac.mac(&key, b"", &mut out).unwrap();
1153        assert_eq!(&out[..], expected.as_slice(), "CMAC-AES-128 SP 800-38B Ex1");
1154    }
1155
1156    #[test]
1157    fn cmac_aes128_round_trip() {
1158        let key = [0x2b_u8; 16];
1159        let msg = b"hello cmac aes128";
1160        let mac = CmacAes128;
1161        let mut tag = [0u8; 16];
1162        mac.mac(&key, msg, &mut tag).unwrap();
1163        mac.verify(&key, msg, &tag)
1164            .expect("CMAC-AES-128 verify must succeed");
1165    }
1166
1167    #[test]
1168    fn cmac_aes128_verify_fail() {
1169        let key = [0u8; 16];
1170        let msg = b"msg";
1171        let mac = CmacAes128;
1172        let mut tag = [0u8; 16];
1173        mac.mac(&key, msg, &mut tag).unwrap();
1174        tag[0] ^= 1;
1175        assert_eq!(mac.verify(&key, msg, &tag), Err(CryptoError::InvalidTag));
1176    }
1177
1178    // ── CMAC-AES-256 ─────────────────────────────────────────────────────────
1179
1180    #[test]
1181    fn cmac_aes256_round_trip() {
1182        let key = [0x42_u8; 32];
1183        let msg = b"hello cmac aes256";
1184        let mac = CmacAes256;
1185        let mut tag = [0u8; 16];
1186        mac.mac(&key, msg, &mut tag).unwrap();
1187        mac.verify(&key, msg, &tag)
1188            .expect("CMAC-AES-256 verify must succeed");
1189    }
1190
1191    // ── KMAC128 ──────────────────────────────────────────────────────────────
1192
1193    /// NIST SP 800-185 Sample #1 (KMAC128, empty customization, 32-byte output)
1194    ///
1195    /// Key  = 404142...5e5f (32 bytes)
1196    /// Data = 00010203 (4 bytes)
1197    /// S    = "" (empty)
1198    /// L    = 256 bits
1199    ///
1200    /// Expected = e5780b0d3ea6f7d3a429c5706aa43a00 fadbd7d49628839e3187243f456ee14e
1201    ///
1202    /// Reference: NIST SP 800-185 §A.1 Sample #1, verified by tiny-keccak test suite.
1203    #[test]
1204    fn kmac128_nist_sp800_185_sample1() {
1205        let key = hex_decode(
1206            "404142434445464748494a4b4c4d4e4f\
1207             505152535455565758595a5b5c5d5e5f",
1208        );
1209        let data = hex_decode("00010203");
1210        let expected = hex_decode(
1211            "e5780b0d3ea6f7d3a429c5706aa43a00\
1212             fadbd7d49628839e3187243f456ee14e",
1213        );
1214
1215        let kmac = Kmac128::new(b"", 32).unwrap();
1216        let mut out = [0u8; 32];
1217        kmac.mac(&key, &data, &mut out).unwrap();
1218        assert_eq!(
1219            &out[..],
1220            expected.as_slice(),
1221            "KMAC128 SP 800-185 Sample #1"
1222        );
1223    }
1224
1225    #[test]
1226    fn kmac128_round_trip() {
1227        let kmac = Kmac128::new(b"test-domain", 32).unwrap();
1228        let key = [0xaa_u8; 16];
1229        let msg = b"hello kmac128";
1230        let mut tag = [0u8; 32];
1231        kmac.mac(&key, msg, &mut tag).unwrap();
1232        kmac.verify(&key, msg, &tag)
1233            .expect("KMAC128 verify must succeed");
1234    }
1235
1236    #[test]
1237    fn kmac128_verify_fail() {
1238        let kmac = Kmac128::new(b"", 32).unwrap();
1239        let key = [0u8; 16];
1240        let msg = b"test";
1241        let mut tag = [0u8; 32];
1242        kmac.mac(&key, msg, &mut tag).unwrap();
1243        tag[0] ^= 1;
1244        assert_eq!(kmac.verify(&key, msg, &tag), Err(CryptoError::InvalidTag));
1245    }
1246
1247    #[test]
1248    fn kmac128_zero_output_len_rejected() {
1249        assert_eq!(
1250            Kmac128::new(b"", 0).unwrap_err(),
1251            CryptoError::BadInput,
1252            "KMAC128 with output_len=0 must be rejected"
1253        );
1254    }
1255
1256    // ── KMAC256 ──────────────────────────────────────────────────────────────
1257
1258    /// NIST SP 800-185 §A.2 Sample #2 (KMAC256, empty customization, 64-byte output)
1259    ///
1260    /// Key    = 404142...5e5f (32 bytes)
1261    /// Data   = 00..c7 (200 bytes sequential)
1262    /// S      = "" (empty customization)
1263    /// L      = 512 bits (64 bytes)
1264    ///
1265    /// Expected:
1266    /// 75358cf39e41494e949707927cee0af2 0a3ff553904c86b08f21cc414bcfd691
1267    /// 589d27cf5e15369cbbff8b9a4c2eb178 00855d0235ff635da82533ec6b759b69
1268    ///
1269    /// Verified against tiny-keccak's test_kmac256_two.
1270    #[test]
1271    fn kmac256_nist_sp800_185_sample4() {
1272        let key = hex_decode(
1273            "404142434445464748494a4b4c4d4e4f\
1274             505152535455565758595a5b5c5d5e5f",
1275        );
1276        // 200-byte sequential data: 0x00..0xc7
1277        let data: alloc::vec::Vec<u8> = (0x00_u8..=0xc7_u8).collect();
1278        let expected = hex_decode(
1279            "75358cf39e41494e949707927cee0af2\
1280             0a3ff553904c86b08f21cc414bcfd691\
1281             589d27cf5e15369cbbff8b9a4c2eb178\
1282             00855d0235ff635da82533ec6b759b69",
1283        );
1284
1285        let kmac = Kmac256::new(b"", 64).unwrap();
1286        let mut out = [0u8; 64];
1287        kmac.mac(&key, &data, &mut out).unwrap();
1288        assert_eq!(
1289            &out[..],
1290            expected.as_slice(),
1291            "KMAC256 SP 800-185 §A.2 Sample #2 (200-byte data, empty S)"
1292        );
1293    }
1294
1295    #[test]
1296    fn kmac256_round_trip() {
1297        let kmac = Kmac256::new(b"domain", 64).unwrap();
1298        let key = [0xbb_u8; 32];
1299        let msg = b"hello kmac256";
1300        let mut tag = [0u8; 64];
1301        kmac.mac(&key, msg, &mut tag).unwrap();
1302        kmac.verify(&key, msg, &tag)
1303            .expect("KMAC256 verify must succeed");
1304    }
1305
1306    #[test]
1307    fn kmac256_zero_output_len_rejected() {
1308        assert_eq!(
1309            Kmac256::new(b"", 0).unwrap_err(),
1310            CryptoError::BadInput,
1311            "KMAC256 with output_len=0 must be rejected"
1312        );
1313    }
1314
1315    // ── Truncated HMAC ───────────────────────────────────────────────────────
1316
1317    /// mac_truncated produces the prefix of the full tag.
1318    #[test]
1319    fn hmac_sha256_truncated_is_prefix() {
1320        let key = b"trunc-key";
1321        let msg = b"truncated message";
1322
1323        let mac = HmacSha256;
1324        let mut full = [0u8; 32];
1325        mac.mac(key, msg, &mut full).unwrap();
1326
1327        let mut trunc = [0u8; 20];
1328        mac.mac_truncated(key, msg, &mut trunc).unwrap();
1329
1330        assert_eq!(
1331            &trunc[..],
1332            &full[..20],
1333            "truncated tag must be prefix of full tag"
1334        );
1335    }
1336
1337    #[test]
1338    fn hmac_sha256_truncated_verify_ok() {
1339        let key = b"k";
1340        let msg = b"m";
1341        let mac = HmacSha256;
1342
1343        let mut trunc = [0u8; 20];
1344        mac.mac_truncated(key, msg, &mut trunc).unwrap();
1345        mac.verify_truncated(key, msg, &trunc)
1346            .expect("truncated verify must succeed");
1347    }
1348
1349    #[test]
1350    fn hmac_sha256_truncated_too_short_rejected() {
1351        let mac = HmacSha256;
1352        let mut buf = [0u8; 15];
1353        assert_eq!(
1354            mac.mac_truncated(b"k", b"m", &mut buf),
1355            Err(CryptoError::BadInput),
1356            "truncation below 16 bytes must be rejected"
1357        );
1358        assert_eq!(
1359            mac.verify_truncated(b"k", b"m", &buf),
1360            Err(CryptoError::BadInput),
1361            "verify with tag < 16 bytes must be rejected"
1362        );
1363    }
1364
1365    #[test]
1366    fn hmac_sha512_truncated_is_prefix() {
1367        let key = b"key512";
1368        let msg = b"msg512";
1369
1370        let mac = HmacSha512;
1371        let mut full = [0u8; 64];
1372        mac.mac(key, msg, &mut full).unwrap();
1373
1374        let mut trunc = [0u8; 32];
1375        mac.mac_truncated(key, msg, &mut trunc).unwrap();
1376
1377        assert_eq!(&trunc[..], &full[..32]);
1378    }
1379
1380    #[test]
1381    fn hmac_sha384_truncated_is_prefix() {
1382        let key = b"key384";
1383        let msg = b"msg384";
1384
1385        let mac = HmacSha384;
1386        let mut full = [0u8; 48];
1387        mac.mac(key, msg, &mut full).unwrap();
1388
1389        let mut trunc = [0u8; 24];
1390        mac.mac_truncated(key, msg, &mut trunc).unwrap();
1391
1392        assert_eq!(&trunc[..], &full[..24]);
1393    }
1394
1395    // ── KMAC-XOF free functions ──────────────────────────────────────────────
1396
1397    /// kmac128_xof and kmac256_xof must match the trait-based Kmac128/Kmac256
1398    /// for the same key/custom/msg/output_len.
1399    #[test]
1400    fn kmac128_xof_matches_trait_impl() {
1401        let key = hex_decode(
1402            "404142434445464748494a4b4c4d4e4f\
1403             505152535455565758595a5b5c5d5e5f",
1404        );
1405        let data = hex_decode("00010203");
1406        // Known-good: NIST SP 800-185 §A.1 Sample #1
1407        let expected = hex_decode(
1408            "e5780b0d3ea6f7d3a429c5706aa43a00\
1409             fadbd7d49628839e3187243f456ee14e",
1410        );
1411
1412        let got = kmac128_xof(&key, b"", &data, 32).expect("kmac128_xof must not fail");
1413        assert_eq!(got, expected, "kmac128_xof NIST SP 800-185 Sample #1");
1414    }
1415
1416    #[test]
1417    fn kmac128_xof_variable_lengths() {
1418        let key = [0xaau8; 16];
1419        let msg = b"variable-length output test";
1420
1421        let out16 = kmac128_xof(&key, b"domain", msg, 16).unwrap();
1422        let out64 = kmac128_xof(&key, b"domain", msg, 64).unwrap();
1423
1424        // KMAC encodes the output length into the message padding (SP 800-185 §4.3.1),
1425        // so different requested lengths produce entirely different outputs.
1426        // Both must be the right length and non-zero.
1427        assert_eq!(
1428            out16.len(),
1429            16,
1430            "kmac128_xof must produce exactly output_len bytes"
1431        );
1432        assert_eq!(
1433            out64.len(),
1434            64,
1435            "kmac128_xof must produce exactly output_len bytes"
1436        );
1437        assert!(out16.iter().any(|&b| b != 0), "output must be non-zero");
1438        assert!(out64.iter().any(|&b| b != 0), "output must be non-zero");
1439        // Different lengths → different outputs (length-dependent padding).
1440        assert_ne!(
1441            &out64[..16],
1442            out16.as_slice(),
1443            "KMAC: different output_len must differ"
1444        );
1445    }
1446
1447    #[test]
1448    fn kmac128_xof_zero_len_rejected() {
1449        assert_eq!(
1450            kmac128_xof(b"key", b"", b"msg", 0).unwrap_err(),
1451            CryptoError::BadInput,
1452        );
1453    }
1454
1455    #[test]
1456    fn kmac256_xof_matches_trait_impl() {
1457        let key = hex_decode(
1458            "404142434445464748494a4b4c4d4e4f\
1459             505152535455565758595a5b5c5d5e5f",
1460        );
1461        let data: alloc::vec::Vec<u8> = (0x00_u8..=0xc7_u8).collect();
1462        let expected = hex_decode(
1463            "75358cf39e41494e949707927cee0af2\
1464             0a3ff553904c86b08f21cc414bcfd691\
1465             589d27cf5e15369cbbff8b9a4c2eb178\
1466             00855d0235ff635da82533ec6b759b69",
1467        );
1468
1469        let got = kmac256_xof(&key, b"", &data, 64).expect("kmac256_xof must not fail");
1470        assert_eq!(got, expected, "kmac256_xof NIST SP 800-185 §A.2 Sample #2");
1471    }
1472
1473    #[test]
1474    fn kmac256_xof_zero_len_rejected() {
1475        assert_eq!(
1476            kmac256_xof(b"key", b"", b"msg", 0).unwrap_err(),
1477            CryptoError::BadInput,
1478        );
1479    }
1480
1481    // ── BLAKE3 keyed-hash MAC ────────────────────────────────────────────────
1482
1483    /// BLAKE3 keyed-hash output is deterministic.
1484    #[test]
1485    fn blake3_keyed_mac_deterministic() {
1486        let key = [0x42u8; 32];
1487        let msg = b"hello blake3 keyed mac";
1488        let t1 = blake3_keyed_mac(&key, msg);
1489        let t2 = blake3_keyed_mac(&key, msg);
1490        assert_eq!(t1, t2, "BLAKE3 keyed mac must be deterministic");
1491    }
1492
1493    /// Different keys produce different tags.
1494    #[test]
1495    fn blake3_keyed_mac_key_dependent() {
1496        let k1 = [0x01u8; 32];
1497        let k2 = [0x02u8; 32];
1498        let msg = b"same msg";
1499        assert_ne!(
1500            blake3_keyed_mac(&k1, msg),
1501            blake3_keyed_mac(&k2, msg),
1502            "Different keys must produce different BLAKE3 MACs"
1503        );
1504    }
1505
1506    /// Verify round-trip.
1507    #[test]
1508    fn blake3_keyed_mac_verify_ok() {
1509        let key = [0xabu8; 32];
1510        let msg = b"verify me";
1511        let tag = blake3_keyed_mac(&key, msg);
1512        blake3_keyed_mac_verify(&key, msg, &tag).expect("BLAKE3 keyed verify must succeed");
1513    }
1514
1515    /// Verify detects corruption.
1516    #[test]
1517    fn blake3_keyed_mac_verify_fail() {
1518        let key = [0xcd_u8; 32];
1519        let msg = b"corrupt me";
1520        let mut tag = blake3_keyed_mac(&key, msg);
1521        tag[0] ^= 0xff;
1522        assert_eq!(
1523            blake3_keyed_mac_verify(&key, msg, &tag),
1524            Err(CryptoError::InvalidTag),
1525            "corrupted BLAKE3 MAC must be rejected"
1526        );
1527    }
1528
1529    // ── hmac_sha256_verify_truncated free function ───────────────────────────
1530
1531    #[test]
1532    fn free_fn_verify_truncated_ok() {
1533        let key = b"verify-trunc-key";
1534        let msg = b"verify-trunc-msg";
1535        let mut full = [0u8; 32];
1536        HmacSha256.mac(key, msg, &mut full).unwrap();
1537        hmac_sha256_verify_truncated(key, msg, &full[..16])
1538            .expect("free-fn verify_truncated must accept valid 16-byte tag");
1539    }
1540
1541    #[test]
1542    fn free_fn_verify_truncated_empty_rejected() {
1543        assert_eq!(
1544            hmac_sha256_verify_truncated(b"k", b"m", &[]),
1545            Err(CryptoError::BadInput),
1546        );
1547    }
1548
1549    #[test]
1550    fn free_fn_verify_truncated_too_long_rejected() {
1551        assert_eq!(
1552            hmac_sha256_verify_truncated(b"k", b"m", &[0u8; 33]),
1553            Err(CryptoError::BadInput),
1554        );
1555    }
1556
1557    #[test]
1558    fn free_fn_verify_truncated_mismatch() {
1559        let key = b"k";
1560        let msg = b"m";
1561        let mut full = [0u8; 32];
1562        HmacSha256.mac(key, msg, &mut full).unwrap();
1563        let mut bad = [0u8; 16];
1564        bad.copy_from_slice(&full[..16]);
1565        bad[0] ^= 0x01;
1566        assert_eq!(
1567            hmac_sha256_verify_truncated(key, msg, &bad),
1568            Err(CryptoError::InvalidTag),
1569        );
1570    }
1571}