libsoliton 0.1.3

Core cryptographic library for the LO protocol — hybrid post-quantum key exchange, signatures, ratchet, and storage encryption
Documentation
//! HMAC-SHA3-256 via RustCrypto (`hmac`/`sha3` crates).
//!
//! MAC computation and constant-time comparison are pure Rust,
//! using the `hmac` and `sha3` crates.

use hmac::{Hmac, KeyInit, Mac};
use sha3::Sha3_256;
use subtle::ConstantTimeEq;

type HmacSha3_256 = Hmac<Sha3_256>;

/// Compute HMAC-SHA3-256(key, data).
///
/// Key can be any length (hashed to block size per RFC 2104 if needed).
/// The `hmac` crate's `zeroize` feature is enabled — the internal HMAC key
/// schedule (ipad/opad) is zeroized on drop via `ZeroizeOnDrop`.
///
/// # Security
///
/// The returned `[u8; 32]` is a plain array. If the caller uses it as
/// secret key material (e.g. chain key derivation), the caller is
/// responsible for zeroizing the value when it is no longer needed.
#[must_use]
pub fn hmac_sha3_256(key: &[u8], data: &[u8]) -> [u8; 32] {
    let mut mac = HmacSha3_256::new_from_slice(key).expect("HMAC accepts any key length");
    mac.update(data);
    mac.finalize().into_bytes().into()
}

/// Compare two 32-byte HMAC values in constant time.
///
/// # Security
///
/// Uses `subtle::ConstantTimeEq` — execution time does not depend on which
/// bytes differ, preventing timing side-channels on HMAC token comparison.
#[must_use]
pub fn hmac_sha3_256_verify_raw(a: &[u8; 32], b: &[u8; 32]) -> bool {
    a.ct_eq(b).into()
}

#[cfg(test)]
mod tests {
    use super::*;
    use hex_literal::hex;

    // Test vectors: RFC 4231 input patterns computed with SHA3-256 via Python 3
    // hashlib (provenance: `hmac.new(key, msg, 'sha3_256').hexdigest()`).

    #[test]
    fn case1() {
        let key = [0x0b; 20];
        let data = b"Hi There";
        let expected = hex!("ba85192310dffa96e2a3a40e69774351140bb7185e1202cdcc917589f95e16bb");
        assert_eq!(hmac_sha3_256(&key, data), expected);
    }

    #[test]
    fn case2() {
        let key = b"Jefe";
        let data = b"what do ya want for nothing?";
        let expected = hex!("c7d4072e788877ae3596bbb0da73b887c9171f93095b294ae857fbe2645e1ba5");
        assert_eq!(hmac_sha3_256(key, data), expected);
    }

    #[test]
    fn case3() {
        let key = [0xaa; 20];
        let data = [0xdd; 50];
        let expected = hex!("84ec79124a27107865cedd8bd82da9965e5ed8c37b0ac98005a7f39ed58a4207");
        assert_eq!(hmac_sha3_256(&key, &data), expected);
    }

    #[test]
    fn case4() {
        let key = hex!("0102030405060708090a0b0c0d0e0f10111213141516171819");
        let data = [0xcd; 50];
        let expected = hex!("57366a45e2305321a4bc5aa5fe2ef8a921f6af8273d7fe7be6cfedb3f0aea6d7");
        assert_eq!(hmac_sha3_256(&key, &data), expected);
    }

    #[test]
    fn case6() {
        let key = [0xaa; 131];
        let data = b"Test Using Larger Than Block-Size Key - Hash Key First";
        let expected = hex!("ed73a374b96c005235f948032f09674a58c0ce555cfc1f223b02356560312c3b");
        assert_eq!(hmac_sha3_256(&key, data), expected);
    }

    #[test]
    fn case7() {
        let key = [0xaa; 131];
        let data = b"This is a test using a larger than block-size key and a larger than block-size data. The key needs to be hashed before being used by the HMAC algorithm.";
        let expected = hex!("65c5b06d4c3de32a7aef8763261e49adb6e2293ec8e7c61e8de61701fc63e123");
        assert_eq!(hmac_sha3_256(&key, data), expected);
    }

    #[test]
    fn verify_raw_correct() {
        let mac = hmac_sha3_256(b"key", b"data");
        assert!(hmac_sha3_256_verify_raw(&mac, &mac));
    }

    #[test]
    fn verify_raw_wrong() {
        let mac_a = hmac_sha3_256(b"key", b"data");
        let mac_b = hmac_sha3_256(b"key", b"other");
        assert!(!hmac_sha3_256_verify_raw(&mac_a, &mac_b));
    }

    #[test]
    fn verify_raw_single_bit_diff() {
        let mac = hmac_sha3_256(b"key", b"data");
        let mut flipped = mac;
        flipped[0] ^= 0x01;
        assert!(!hmac_sha3_256_verify_raw(&mac, &flipped));
    }

    #[test]
    fn empty_key() {
        let mac = hmac_sha3_256(b"", b"data");
        assert!(mac.iter().any(|&b| b != 0));
    }

    #[test]
    fn empty_data() {
        let mac = hmac_sha3_256(b"key", b"");
        assert!(mac.iter().any(|&b| b != 0));
    }

    #[test]
    fn empty_key_and_empty_data() {
        let mac = hmac_sha3_256(b"", b"");
        assert!(mac.iter().any(|&b| b != 0));
    }
}