use quantica::ml_dsa;
use quantica::ml_kem;
fn parse_rsp(path: &str) -> Vec<std::collections::HashMap<String, String>> {
let content = std::fs::read_to_string(path).unwrap_or_else(|_| panic!("Cannot read {}", path));
let mut results = Vec::new();
let mut current: std::collections::HashMap<String, String> = std::collections::HashMap::new();
for line in content.lines() {
let line = line.trim();
if line.is_empty() || line.starts_with('#') {
if !current.is_empty() {
results.push(current.clone());
current.clear();
}
continue;
}
if let Some(pos) = line.find('=') {
let key = line[..pos].trim().to_string();
let val = line[pos + 1..].trim().to_string();
if key == "count" && !current.is_empty() {
results.push(current.clone());
current.clear();
}
current.insert(key, val);
}
}
if !current.is_empty() {
results.push(current);
}
results
}
fn hex_to_bytes(hex: &str) -> Vec<u8> {
(0..hex.len())
.step_by(2)
.map(|i| u8::from_str_radix(&hex[i..i + 2], 16).unwrap())
.collect()
}
fn hex_to_32(hex: &str) -> [u8; 32] {
let v = hex_to_bytes(hex);
let mut a = [0u8; 32];
a.copy_from_slice(&v);
a
}
mod mlkem_kat_rsp {
use super::*;
fn run_mlkem_rsp<P: ml_kem::Params>(file: &str, label: &str) {
let vectors = parse_rsp(file);
let mut ok = 0u32;
for v in &vectors {
let d = hex_to_32(v.get("d").expect("missing d"));
let z = hex_to_32(v.get("z").expect("missing z"));
let expected_pk = hex_to_bytes(v.get("pk").expect("missing pk"));
let expected_sk = hex_to_bytes(v.get("sk").expect("missing sk"));
let expected_ct = hex_to_bytes(v.get("ct").expect("missing ct"));
let expected_ss = hex_to_bytes(v.get("ss").expect("missing ss"));
let msg = hex_to_32(v.get("msg").expect("missing msg"));
let (ek, dk) = ml_kem::MlKem::<P>::keygen_internal(&d, &z);
assert_eq!(
ek,
expected_pk,
"{} count {}: ek mismatch",
label,
v.get("count").unwrap_or(&"?".to_string())
);
assert_eq!(
dk,
expected_sk,
"{} count {}: dk mismatch",
label,
v.get("count").unwrap_or(&"?".to_string())
);
let (ss, ct) = ml_kem::MlKem::<P>::encaps_internal(&ek, &msg);
assert_eq!(
ct,
expected_ct,
"{} count {}: ct mismatch",
label,
v.get("count").unwrap_or(&"?".to_string())
);
assert_eq!(
ss.to_vec(),
expected_ss,
"{} count {}: ss mismatch",
label,
v.get("count").unwrap_or(&"?".to_string())
);
let ss2 = ml_kem::MlKem::<P>::decaps_internal(&dk, &ct);
assert_eq!(
ss2.to_vec(),
expected_ss,
"{} count {}: decaps ss mismatch",
label,
v.get("count").unwrap_or(&"?".to_string())
);
ok += 1;
}
eprintln!("{}: {} KAT vectors passed (keygen+encaps+decaps)", label, ok);
}
#[test]
#[ignore] fn mlkem_512_rsp() {
run_mlkem_rsp::<ml_kem::MlKem512>("tests/vectors/kat_MLKEM_512.rsp", "ML-KEM-512-rsp");
}
#[test]
#[ignore]
fn mlkem_768_rsp() {
run_mlkem_rsp::<ml_kem::MlKem768>("tests/vectors/kat_MLKEM_768.rsp", "ML-KEM-768-rsp");
}
#[test]
#[ignore]
fn mlkem_1024_rsp() {
run_mlkem_rsp::<ml_kem::MlKem1024>("tests/vectors/kat_MLKEM_1024.rsp", "ML-KEM-1024-rsp");
}
}
mod mldsa_kat_rsp {
use super::*;
fn run_mldsa_det_pure<P: ml_dsa::Params>(file: &str, label: &str) {
let vectors = parse_rsp(file);
let mut ok = 0u32;
for v in &vectors {
let xi = hex_to_32(v.get("xi").expect("missing xi"));
let expected_pk = hex_to_bytes(v.get("pk").expect("missing pk"));
let expected_sk = hex_to_bytes(v.get("sk").expect("missing sk"));
let msg = hex_to_bytes(v.get("msg").expect("missing msg"));
let sm = hex_to_bytes(v.get("sm").expect("missing sm"));
let ctx_hex = v.get("ctx").map(|s| s.as_str()).unwrap_or("");
let ctx = hex_to_bytes(ctx_hex);
let (pk, sk) = ml_dsa::dsa::keygen_internal::<P>(&xi);
assert_eq!(
pk,
expected_pk,
"{} count {}: pk mismatch",
label,
v.get("count").unwrap_or(&"?".to_string())
);
assert_eq!(
sk,
expected_sk,
"{} count {}: sk mismatch",
label,
v.get("count").unwrap_or(&"?".to_string())
);
let mut m_prime = Vec::new();
m_prime.push(0x00);
m_prime.push(ctx.len() as u8);
m_prime.extend_from_slice(&ctx);
m_prime.extend_from_slice(&msg);
let rnd = [0u8; 32];
let sig = ml_dsa::dsa::sign_internal::<P>(&sk, &m_prime, &rnd).expect(&format!(
"{} count {}: sign failed",
label,
v.get("count").unwrap_or(&"?".to_string())
));
let expected_sig = &sm[..sm.len() - msg.len()];
assert_eq!(
sig,
expected_sig,
"{} count {}: sig mismatch",
label,
v.get("count").unwrap_or(&"?".to_string())
);
ok += 1;
}
eprintln!("{}: {} det_pure vectors passed (keygen+sign)", label, ok);
}
fn run_mldsa_det_raw<P: ml_dsa::Params>(file: &str, label: &str) {
let vectors = parse_rsp(file);
let mut ok = 0u32;
for v in &vectors {
let xi = hex_to_32(v.get("xi").expect("missing xi"));
let msg = hex_to_bytes(v.get("msg").expect("missing msg"));
let sm = hex_to_bytes(v.get("sm").expect("missing sm"));
let (_pk, sk) = ml_dsa::dsa::keygen_internal::<P>(&xi);
let rnd = [0u8; 32];
let sig = ml_dsa::dsa::sign_internal::<P>(&sk, &msg, &rnd).expect(&format!(
"{} count {}: sign failed",
label,
v.get("count").unwrap_or(&"?".to_string())
));
let expected_sig = &sm[..sm.len() - msg.len()];
assert_eq!(
sig,
expected_sig,
"{} count {}: sig mismatch",
label,
v.get("count").unwrap_or(&"?".to_string())
);
ok += 1;
}
eprintln!("{}: {} det_raw vectors passed (sign_internal)", label, ok);
}
#[test]
fn mldsa_44_det_pure() {
run_mldsa_det_pure::<ml_dsa::MlDsa44>("tests/vectors/kat/kat_MLDSA_44_det_pure.rsp", "ML-DSA-44-det-pure");
}
#[test]
fn mldsa_65_det_pure() {
run_mldsa_det_pure::<ml_dsa::MlDsa65>("tests/vectors/kat/kat_MLDSA_65_det_pure.rsp", "ML-DSA-65-det-pure");
}
#[test]
fn mldsa_87_det_pure() {
run_mldsa_det_pure::<ml_dsa::MlDsa87>("tests/vectors/kat/kat_MLDSA_87_det_pure.rsp", "ML-DSA-87-det-pure");
}
#[test]
fn mldsa_44_det_raw() {
run_mldsa_det_raw::<ml_dsa::MlDsa44>("tests/vectors/kat/kat_MLDSA_44_det_raw.rsp", "ML-DSA-44-det-raw");
}
#[test]
fn mldsa_65_det_raw() {
run_mldsa_det_raw::<ml_dsa::MlDsa65>("tests/vectors/kat/kat_MLDSA_65_det_raw.rsp", "ML-DSA-65-det-raw");
}
#[test]
fn mldsa_87_det_raw() {
run_mldsa_det_raw::<ml_dsa::MlDsa87>("tests/vectors/kat/kat_MLDSA_87_det_raw.rsp", "ML-DSA-87-det-raw");
}
}