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 ML-DSA *internal* interface (FIPS 204 Alg 6/7/8).
//!
//! These vectors feed a pre-formatted `M′` directly to `sign_internal` /
//! `verify_internal` (and `keygen_internal` for the `.rsp` `det_raw` set),
//! 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/ml_dsa_kat.rs` through the public API.

use crate::kat_json::{Value, hex_to_32, hex_to_bytes, parse_rsp, read_json, vector_path};
use crate::ml_dsa::{MlDsa44, MlDsa65, MlDsa87, Params, dsa};

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

fn run_siggen<P: Params>(param_set: &str) {
    let data = read_json(&vector_path("ML-DSA-sigGen-FIPS204/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;
        }
        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"));
            let rnd = if det { [0u8; 32] } else { hex_to_32(tc.get_str("rnd")) };

            let sig = dsa::sign_internal::<P>(&sk, &m_prime, &rnd)
                .unwrap_or_else(|_| panic!("{param_set} tc {}: sign_internal failed", tc.get_u64("tcId")));
            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("ML-DSA-sigVer-FIPS204/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 = dsa::verify_internal::<P>(&pk, &m_prime, &sig).unwrap_or(false);
            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");
}

// ===================== `.rsp` det_raw (message IS M′) =====================

fn run_det_raw<P: Params>(file: &str, label: &str) {
    let vectors = parse_rsp(&vector_path(file));
    let mut ok = 0u32;
    for v in &vectors {
        let count = v.get("count").map(String::as_str).unwrap_or("?");
        let xi = hex_to_32(v.get("xi").expect("missing xi"));
        let msg = hex_to_bytes(v.get("msg").expect("missing msg")); // already M′
        let sm = hex_to_bytes(v.get("sm").expect("missing sm"));

        let (_pk, sk) = dsa::keygen_internal::<P>(&xi);
        let sig = dsa::sign_internal::<P>(&sk, &msg, &[0u8; 32])
            .unwrap_or_else(|_| panic!("{label} count {count}: sign failed"));

        let expected_sig = &sm[..sm.len() - msg.len()]; // sm = sig || msg
        assert_eq!(sig.as_slice(), expected_sig, "{label} count {count}: sig mismatch");
        ok += 1;
    }
    eprintln!("{label}: {ok} det_raw vectors passed (sign_internal)");
    assert!(ok > 0, "{label}: no det_raw vectors exercised");
}

#[test]
fn siggen_internal_44() {
    run_siggen::<MlDsa44>("ML-DSA-44");
}
#[test]
fn siggen_internal_65() {
    run_siggen::<MlDsa65>("ML-DSA-65");
}
#[test]
fn siggen_internal_87() {
    run_siggen::<MlDsa87>("ML-DSA-87");
}

#[test]
fn sigver_internal_44() {
    run_sigver::<MlDsa44>("ML-DSA-44");
}
#[test]
fn sigver_internal_65() {
    run_sigver::<MlDsa65>("ML-DSA-65");
}
#[test]
fn sigver_internal_87() {
    run_sigver::<MlDsa87>("ML-DSA-87");
}

#[test]
fn det_raw_44() {
    run_det_raw::<MlDsa44>("kat/kat_MLDSA_44_det_raw.rsp", "ML-DSA-44-det-raw");
}
#[test]
fn det_raw_65() {
    run_det_raw::<MlDsa65>("kat/kat_MLDSA_65_det_raw.rsp", "ML-DSA-65-det-raw");
}
#[test]
fn det_raw_87() {
    run_det_raw::<MlDsa87>("kat/kat_MLDSA_87_det_raw.rsp", "ML-DSA-87-det-raw");
}