krypteia-quantica 0.1.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
//! SHAKE-based tweakable hash function wrappers for SLH-DSA (FIPS 205, Section 11.1).
//!
//! All hash functions in the SHAKE instantiation of SLH-DSA are built on top of SHAKE256.
//! The address structure (`Adrs`) is included in each hash call to provide domain separation.
//!
//! The `_into` variants write output to a caller-provided buffer (zero heap allocation).
//! The standard variants return `Vec<u8>` for convenience.

use super::address::Adrs;
use super::params::Params;
use super::sha3::{KeccakState, SHAKE256_RATE};
use alloc::vec::Vec;

/// Internal: create a SHAKE256 context, absorb pk_seed and ADRS, return it.
/// This prefix is common to PRF, T_l, H, and F.
#[inline(always)]
fn shake256_pk_adrs(pk_seed: &[u8], adrs: &Adrs) -> KeccakState {
    let mut ctx = KeccakState::new(SHAKE256_RATE, 0x1f);
    ctx.absorb(pk_seed);
    ctx.absorb(adrs.as_bytes());
    ctx
}

/// `H_msg(R, PK.seed, PK.root, M)` — message hash. Output written to `out` (m bytes).
pub fn h_msg_into<P: Params>(r: &[u8], pk_seed: &[u8], pk_root: &[u8], m: &[u8], out: &mut [u8]) {
    let mut ctx = KeccakState::new(SHAKE256_RATE, 0x1f);
    ctx.absorb(r);
    ctx.absorb(pk_seed);
    ctx.absorb(pk_root);
    ctx.absorb(m);
    ctx.squeeze(out);
}

/// `H_msg` — convenience wrapper returning Vec.
pub fn h_msg<P: Params>(r: &[u8], pk_seed: &[u8], pk_root: &[u8], m: &[u8]) -> Vec<u8> {
    let mut out = vec![0u8; P::M];
    h_msg_into::<P>(r, pk_seed, pk_root, m, &mut out);
    out
}

/// `PRF(PK.seed, SK.seed, ADRS)` — secret value derivation. Output to `out` (n bytes).
pub fn prf_into<P: Params>(pk_seed: &[u8], sk_seed: &[u8], adrs: &Adrs, out: &mut [u8]) {
    let mut ctx = shake256_pk_adrs(pk_seed, adrs);
    ctx.absorb(sk_seed);
    ctx.squeeze(out);
}

/// `PRF` — convenience wrapper returning Vec.
pub fn prf<P: Params>(pk_seed: &[u8], sk_seed: &[u8], adrs: &Adrs) -> Vec<u8> {
    let mut out = vec![0u8; P::N];
    prf_into::<P>(pk_seed, sk_seed, adrs, &mut out);
    out
}

/// `PRF_msg(SK.prf, opt_rand, M)` — message randomizer. Output to `out` (n bytes).
pub fn prf_msg_into<P: Params>(sk_prf: &[u8], opt_rand: &[u8], m: &[u8], out: &mut [u8]) {
    let mut ctx = KeccakState::new(SHAKE256_RATE, 0x1f);
    ctx.absorb(sk_prf);
    ctx.absorb(opt_rand);
    ctx.absorb(m);
    ctx.squeeze(out);
}

/// `PRF_msg` — convenience wrapper returning Vec.
pub fn prf_msg<P: Params>(sk_prf: &[u8], opt_rand: &[u8], m: &[u8]) -> Vec<u8> {
    let mut out = vec![0u8; P::N];
    prf_msg_into::<P>(sk_prf, opt_rand, m, &mut out);
    out
}

/// `T_l(PK.seed, ADRS, M)` — multi-input compression. Output to `out` (n bytes).
pub fn t_l_into<P: Params>(pk_seed: &[u8], adrs: &Adrs, m: &[u8], out: &mut [u8]) {
    let mut ctx = shake256_pk_adrs(pk_seed, adrs);
    ctx.absorb(m);
    ctx.squeeze(out);
}

/// `T_l` — convenience wrapper returning Vec.
pub fn t_l<P: Params>(pk_seed: &[u8], adrs: &Adrs, m: &[u8]) -> Vec<u8> {
    let mut out = vec![0u8; P::N];
    t_l_into::<P>(pk_seed, adrs, m, &mut out);
    out
}

/// `H(PK.seed, ADRS, M1 || M2)` — two-input Merkle node hash. Output to `out` (n bytes).
pub fn hash_h_into<P: Params>(pk_seed: &[u8], adrs: &Adrs, m1: &[u8], m2: &[u8], out: &mut [u8]) {
    let mut ctx = shake256_pk_adrs(pk_seed, adrs);
    ctx.absorb(m1);
    ctx.absorb(m2);
    ctx.squeeze(out);
}

/// `H` — convenience wrapper returning Vec.
pub fn hash_h<P: Params>(pk_seed: &[u8], adrs: &Adrs, m1: &[u8], m2: &[u8]) -> Vec<u8> {
    let mut out = vec![0u8; P::N];
    hash_h_into::<P>(pk_seed, adrs, m1, m2, &mut out);
    out
}

/// `F(PK.seed, ADRS, M1)` — single-input chain hash. Output to `out` (n bytes).
///
/// This is the most frequently called hash function in SLH-DSA (WOTS+ chains).
pub fn f_hash_into<P: Params>(pk_seed: &[u8], adrs: &Adrs, m1: &[u8], out: &mut [u8]) {
    let mut ctx = shake256_pk_adrs(pk_seed, adrs);
    ctx.absorb(m1);
    ctx.squeeze(out);
}

/// `F` — convenience wrapper returning Vec.
pub fn f_hash<P: Params>(pk_seed: &[u8], adrs: &Adrs, m1: &[u8]) -> Vec<u8> {
    let mut out = vec![0u8; P::N];
    f_hash_into::<P>(pk_seed, adrs, m1, &mut out);
    out
}