krypteia-quantica 0.2.0

Pure-Rust post-quantum cryptography: FIPS 203 ML-KEM, FIPS 204 ML-DSA, and FIPS 205 SLH-DSA. First-order arithmetic masking, shuffled NTT, FORS recompute-and-compare redundancy, constant-time rejection sampling. Targets embedded (no_std), STM32 M0/M4/M33, ESP32-C3 RISC-V. Zero runtime dependencies.
Documentation
// SPDX-License-Identifier: Apache-2.0
// Copyright 2026 Cédric Mesnil <cslashm@pm.me>

//! Shared `Keccak-f[1600]` sponge core (FIPS 202) — re-exported from
//! [`krypteia-tessera`](tessera).
//!
//! Historically quantica carried its own Keccak permutation here. As of
//! v0.2 the single workspace-wide implementation lives in the shared
//! `krypteia-tessera` crate ([`tessera::sha3::KeccakState`]); this module
//! re-exports it plus the FIPS 202 rate constants, so the per-algorithm
//! `sha3` wrappers (`h`/`g`/`j`/`prf`/`Xof` for ML-KEM; `sha3_256`/
//! `sha3_512`/`shake128`/`shake256` for ML-DSA; `Shake256`/
//! `shake256_into` for SLH-DSA) and every call site keep compiling
//! unchanged. The two implementations are byte-identical plain
//! Keccak-f[1600] (verified by KAT/ACVP) and benchmarked at parity, so
//! this is a pure deduplication with no behavioural or performance
//! change. `KeccakState` is naturally data-oblivious; it carries no
//! secret-dependent branch, index, or table lookup on either side.

pub use tessera::sha3::KeccakState;

// ============================================================
// Standard FIPS 202 rates (in bytes). `krypteia-tessera` keeps these as
// literals inside its macros and does not export them, so quantica owns
// the named constants its wrappers import.
// ============================================================

/// SHA3-256: rate = (1600 − 2·256) / 8 = 136 bytes, suffix = 0x06.
pub const SHA3_256_RATE: usize = 136;
/// SHA3-512: rate = (1600 − 2·512) / 8 = 72 bytes, suffix = 0x06.
pub const SHA3_512_RATE: usize = 72;
/// SHAKE128: rate = (1600 − 2·128) / 8 = 168 bytes, suffix = 0x1f.
pub const SHAKE128_RATE: usize = 168;
/// SHAKE256: rate = (1600 − 2·256) / 8 = 136 bytes, suffix = 0x1f.
pub const SHAKE256_RATE: usize = 136;

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

    fn sha3_256(input: &[u8]) -> [u8; 32] {
        let mut s = KeccakState::new(SHA3_256_RATE, 0x06);
        s.absorb(input);
        let mut out = [0u8; 32];
        s.squeeze(&mut out);
        out
    }

    fn sha3_512(input: &[u8]) -> [u8; 64] {
        let mut s = KeccakState::new(SHA3_512_RATE, 0x06);
        s.absorb(input);
        let mut out = [0u8; 64];
        s.squeeze(&mut out);
        out
    }

    // Smoke KATs proving the re-exported core is wired correctly through
    // quantica's rate constants (full SHA-3/SHAKE/XOF coverage lives in
    // krypteia-tessera; ML-KEM/ML-DSA/SLH-DSA conformance is covered by the
    // ACVP/KAT suites).
    #[test]
    fn sha3_256_empty_kat() {
        // SHA3-256("") = a7ffc6f8bf1ed76651c14756a061d662f580ff4de43b49fa82d80a4b80f8434a
        let out = sha3_256(b"");
        let expected = [
            0xa7, 0xff, 0xc6, 0xf8, 0xbf, 0x1e, 0xd7, 0x66, 0x51, 0xc1, 0x47, 0x56, 0xa0, 0x61, 0xd6, 0x62, 0xf5, 0x80,
            0xff, 0x4d, 0xe4, 0x3b, 0x49, 0xfa, 0x82, 0xd8, 0x0a, 0x4b, 0x80, 0xf8, 0x43, 0x4a,
        ];
        assert_eq!(out, expected);
    }

    #[test]
    fn sha3_512_empty_kat_first_32() {
        let out = sha3_512(b"");
        let expected_first_32 = [
            0xa6, 0x9f, 0x73, 0xcc, 0xa2, 0x3a, 0x9a, 0xc5, 0xc8, 0xb5, 0x67, 0xdc, 0x18, 0x5a, 0x75, 0x6e, 0x97, 0xc9,
            0x82, 0x16, 0x4f, 0xe2, 0x58, 0x59, 0xe0, 0xd1, 0xdc, 0xc1, 0x47, 0x5c, 0x80, 0xa6,
        ];
        assert_eq!(&out[..32], &expected_first_32);
    }
}