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>

//! In-crate KATs for the SLH-DSA *internal* interface (FIPS 205 Alg 19/20).
//!
//! These vectors feed a pre-formatted `M′` directly to `slh_sign_internal` /
//! `slh_verify_internal`, which are `pub(crate)` and so cannot be driven from
//! the external `tests/` crate. The public / external-pure / pre-hash
//! conformance is validated in `tests/slh_dsa_kat.rs` through the public API.

use crate::kat_json::{Value, hex_to_bytes, read_json, vector_path};
use crate::slh_dsa::{
    Params, Sha2_128f, Sha2_128s, Sha2_192f, Sha2_192s, Sha2_256f, Sha2_256s, Shake128f, Shake128s, Shake192f,
    Shake192s, Shake256f, Shake256s, slh,
};

// ===================== ACVP internal-interface sigGen =====================

fn run_siggen<P: Params>(param_set: &str) {
    let data = read_json(&vector_path("SLH-DSA-sigGen-FIPS205/internalProjection.json"));
    let mut tested = 0;
    let n = P::N;
    for group in data.get_array("testGroups") {
        if group.get_str("parameterSet") != param_set {
            continue;
        }
        if group.get_str("signatureInterface") != "internal" {
            continue;
        }
        let det = matches!(group.get("deterministic"), Some(Value::Bool(true)));
        for tc in group.get_array("tests").iter() {
            if !tc.has("message") {
                continue;
            }
            // Internal interface: the `message` field IS M′.
            let sk = hex_to_bytes(tc.get_str("sk"));
            let m_prime = hex_to_bytes(tc.get_str("message"));
            let expected = hex_to_bytes(tc.get_str("signature"));
            // Deterministic: addrnd = PK.seed (sk[2n..3n]); else additionalRandomness.
            let addrnd = if det {
                sk[2 * n..3 * n].to_vec()
            } else {
                hex_to_bytes(tc.get_str("additionalRandomness"))
            };

            let sig = slh::slh_sign_internal::<P>(&m_prime, &sk, &addrnd)
                .expect("KAT runs without sca-fors-redundancy — sign_internal is infallible");
            assert_eq!(
                sig.as_slice(),
                expected.as_slice(),
                "{param_set} tc {}: sig mismatch",
                tc.get_u64("tcId")
            );
            tested += 1;
        }
    }
    eprintln!("{param_set} siggen (internal): {tested} vectors passed");
    assert!(tested > 0, "{param_set}: no internal sigGen vectors exercised");
}

// ===================== ACVP internal-interface sigVer =====================

fn run_sigver<P: Params>(param_set: &str) {
    let data = read_json(&vector_path("SLH-DSA-sigVer-FIPS205/internalProjection.json"));
    let mut tested = 0;
    for group in data.get_array("testGroups") {
        if group.get_str("parameterSet") != param_set {
            continue;
        }
        if group.get_str("signatureInterface") != "internal" {
            continue;
        }
        for tc in group.get_array("tests").iter() {
            if !tc.has("message") {
                continue;
            }
            let pk = hex_to_bytes(tc.get_str("pk"));
            let m_prime = hex_to_bytes(tc.get_str("message"));
            let sig = hex_to_bytes(tc.get_str("signature"));
            let test_passed = matches!(tc.get("testPassed"), Some(Value::Bool(true)) | None);

            let verified = slh::slh_verify_internal::<P>(&m_prime, &sig, &pk);
            assert_eq!(
                verified,
                test_passed,
                "{param_set} tc {}: verify expected {test_passed} got {verified}",
                tc.get_u64("tcId")
            );
            tested += 1;
        }
    }
    eprintln!("{param_set} sigver (internal): {tested} vectors tested");
    assert!(tested > 0, "{param_set}: no internal sigVer vectors exercised");
}

macro_rules! internal_kat {
    ($sig:ident, $ver:ident, $p:ty, $name:literal) => {
        #[test]
        fn $sig() {
            run_siggen::<$p>($name);
        }
        #[test]
        fn $ver() {
            run_sigver::<$p>($name);
        }
    };
}

internal_kat!(
    siggen_internal_shake_128s,
    sigver_internal_shake_128s,
    Shake128s,
    "SLH-DSA-SHAKE-128s"
);
internal_kat!(
    siggen_internal_shake_128f,
    sigver_internal_shake_128f,
    Shake128f,
    "SLH-DSA-SHAKE-128f"
);
internal_kat!(
    siggen_internal_shake_192s,
    sigver_internal_shake_192s,
    Shake192s,
    "SLH-DSA-SHAKE-192s"
);
internal_kat!(
    siggen_internal_shake_192f,
    sigver_internal_shake_192f,
    Shake192f,
    "SLH-DSA-SHAKE-192f"
);
internal_kat!(
    siggen_internal_shake_256s,
    sigver_internal_shake_256s,
    Shake256s,
    "SLH-DSA-SHAKE-256s"
);
internal_kat!(
    siggen_internal_shake_256f,
    sigver_internal_shake_256f,
    Shake256f,
    "SLH-DSA-SHAKE-256f"
);
internal_kat!(
    siggen_internal_sha2_128s,
    sigver_internal_sha2_128s,
    Sha2_128s,
    "SLH-DSA-SHA2-128s"
);
internal_kat!(
    siggen_internal_sha2_128f,
    sigver_internal_sha2_128f,
    Sha2_128f,
    "SLH-DSA-SHA2-128f"
);
internal_kat!(
    siggen_internal_sha2_192s,
    sigver_internal_sha2_192s,
    Sha2_192s,
    "SLH-DSA-SHA2-192s"
);
internal_kat!(
    siggen_internal_sha2_192f,
    sigver_internal_sha2_192f,
    Sha2_192f,
    "SLH-DSA-SHA2-192f"
);
internal_kat!(
    siggen_internal_sha2_256s,
    sigver_internal_sha2_256s,
    Sha2_256s,
    "SLH-DSA-SHA2-256s"
);
internal_kat!(
    siggen_internal_sha2_256f,
    sigver_internal_sha2_256f,
    Sha2_256f,
    "SLH-DSA-SHA2-256f"
);