use quantica::ml_dsa;
use quantica::ml_kem;
use quantica::slh_dsa;
mod mlkem_negative {
use super::*;
fn make_keypair() -> (
ml_kem::EncapsulationKey<ml_kem::MlKem768>,
ml_kem::DecapsulationKey<ml_kem::MlKem768>,
) {
let mut rng = ml_kem::OsRng;
ml_kem::MlKem::<ml_kem::MlKem768>::keygen(&mut rng).unwrap()
}
fn make_ciphertext() -> (
ml_kem::EncapsulationKey<ml_kem::MlKem768>,
ml_kem::DecapsulationKey<ml_kem::MlKem768>,
ml_kem::Ciphertext<ml_kem::MlKem768>,
ml_kem::SharedSecret,
) {
let mut rng = ml_kem::OsRng;
let (ek, dk) = ml_kem::MlKem::<ml_kem::MlKem768>::keygen(&mut rng).unwrap();
let (ss, ct) = ml_kem::MlKem::<ml_kem::MlKem768>::encaps(&ek, &mut rng).unwrap();
(ek, dk, ct, ss)
}
#[test]
fn ek_from_bytes_wrong_length() {
let short = vec![0u8; 100];
assert!(ml_kem::EncapsulationKey::<ml_kem::MlKem768>::from_bytes(&short).is_err());
let long = vec![0u8; <ml_kem::MlKem768 as ml_kem::Params>::EK_LEN + 1];
assert!(ml_kem::EncapsulationKey::<ml_kem::MlKem768>::from_bytes(&long).is_err());
}
#[test]
fn dk_from_bytes_wrong_length() {
let short = vec![0u8; 10];
assert!(ml_kem::DecapsulationKey::<ml_kem::MlKem768>::from_bytes(&short).is_err());
}
#[test]
fn ct_from_bytes_wrong_length() {
let short = vec![0u8; 100];
assert!(ml_kem::Ciphertext::<ml_kem::MlKem768>::from_bytes(&short).is_err());
let long = vec![0u8; <ml_kem::MlKem768 as ml_kem::Params>::CT_LEN + 1];
assert!(ml_kem::Ciphertext::<ml_kem::MlKem768>::from_bytes(&long).is_err());
}
#[test]
fn encaps_ek_all_zeros() {
let zeros = vec![0u8; <ml_kem::MlKem768 as ml_kem::Params>::EK_LEN];
let ek = ml_kem::EncapsulationKey::<ml_kem::MlKem768>::from_bytes(&zeros).unwrap();
let mut rng = ml_kem::OsRng;
let _ = ml_kem::MlKem::<ml_kem::MlKem768>::encaps(&ek, &mut rng);
}
#[test]
fn decaps_ct_bitflip_returns_implicit_rejection() {
let (_, dk, ct, ss_good) = make_ciphertext();
let mut ct_bytes = ct.as_bytes().to_vec();
ct_bytes[0] ^= 0x01;
let ct_bad = ml_kem::Ciphertext::<ml_kem::MlKem768>::from_bytes(&ct_bytes).unwrap();
let mut rng = ml_kem::OsRng;
let ss_bad = ml_kem::MlKem::<ml_kem::MlKem768>::decaps(&dk, &ct_bad, &mut rng).unwrap();
assert_ne!(
ss_good.as_bytes(),
ss_bad.as_bytes(),
"corrupted ciphertext must produce a different shared secret (implicit rejection)"
);
}
#[test]
fn decaps_ct_random_returns_implicit_rejection() {
let (_, dk, _, _) = make_ciphertext();
let mut rng = ml_kem::OsRng;
let mut random_ct = vec![0u8; <ml_kem::MlKem768 as ml_kem::Params>::CT_LEN];
ml_kem::CryptoRng::fill_bytes(&mut rng, &mut random_ct).unwrap();
let ct_rand = ml_kem::Ciphertext::<ml_kem::MlKem768>::from_bytes(&random_ct).unwrap();
let _ = ml_kem::MlKem::<ml_kem::MlKem768>::decaps(&dk, &ct_rand, &mut rng);
}
#[test]
fn decaps_dk_corrupted_integrity_check() {
let (ek, dk, _, _) = make_ciphertext();
let h_ek_offset = 384 * 3 + <ml_kem::MlKem768 as ml_kem::Params>::EK_LEN;
let mut dk_bytes = dk.as_bytes().to_vec();
dk_bytes[h_ek_offset] ^= 0xFF;
let dk_bad = ml_kem::DecapsulationKey::<ml_kem::MlKem768>::from_bytes(&dk_bytes).unwrap();
let mut rng = ml_kem::OsRng;
let (ss_good, ct) = ml_kem::MlKem::<ml_kem::MlKem768>::encaps(&ek, &mut rng).unwrap();
match ml_kem::MlKem::<ml_kem::MlKem768>::decaps(&dk_bad, &ct, &mut rng) {
Err(_) => { }
Ok(ss_bad) => {
assert_ne!(
ss_good.as_bytes(),
ss_bad.as_bytes(),
"corrupted dk must not silently produce the correct shared secret"
);
}
}
}
#[test]
fn shared_secret_debug_is_redacted() {
let (_, dk, ct, _) = make_ciphertext();
let mut rng = ml_kem::OsRng;
let ss = ml_kem::MlKem::<ml_kem::MlKem768>::decaps(&dk, &ct, &mut rng).unwrap();
let dbg = format!("{:?}", ss);
assert!(dbg.contains("redacted"), "Debug must not leak secret bytes: {}", dbg);
assert!(!dbg.contains("0x"), "Debug must not contain hex bytes");
}
}
mod mldsa_negative {
use super::*;
fn make_keypair() -> (
ml_dsa::VerifyingKey<ml_dsa::MlDsa65>,
ml_dsa::SigningKey<ml_dsa::MlDsa65>,
) {
let mut rng = ml_dsa::OsRng;
ml_dsa::MlDsa::<ml_dsa::MlDsa65>::keygen(&mut rng).unwrap()
}
#[test]
fn vk_from_bytes_wrong_length() {
assert!(ml_dsa::VerifyingKey::<ml_dsa::MlDsa65>::from_bytes(&[0u8; 10]).is_err());
assert!(
ml_dsa::VerifyingKey::<ml_dsa::MlDsa65>::from_bytes(&vec![
0u8;
ml_dsa::MlDsa::<ml_dsa::MlDsa65>::PK_LEN + 1
])
.is_err()
);
}
#[test]
fn sk_from_bytes_wrong_length() {
assert!(ml_dsa::SigningKey::<ml_dsa::MlDsa65>::from_bytes(&[0u8; 10]).is_err());
}
#[test]
fn sig_from_bytes_wrong_length() {
assert!(ml_dsa::Signature::<ml_dsa::MlDsa65>::from_bytes(&[0u8; 10]).is_err());
assert!(
ml_dsa::Signature::<ml_dsa::MlDsa65>::from_bytes(&vec![0u8; ml_dsa::MlDsa::<ml_dsa::MlDsa65>::SIG_LEN + 1])
.is_err()
);
}
#[test]
fn sign_context_too_long() {
let (_, sk) = make_keypair();
let mut rng = ml_dsa::OsRng;
let long_ctx = vec![0xAA; 256]; let result = ml_dsa::MlDsa::<ml_dsa::MlDsa65>::sign(&sk, b"msg", &long_ctx, &mut rng);
assert!(result.is_err(), "context > 255 bytes must be rejected");
}
#[test]
fn verify_context_too_long() {
let (pk, sk) = make_keypair();
let mut rng = ml_dsa::OsRng;
let sig = ml_dsa::MlDsa::<ml_dsa::MlDsa65>::sign(&sk, b"msg", b"", &mut rng).unwrap();
let long_ctx = vec![0xAA; 256];
let result = ml_dsa::MlDsa::<ml_dsa::MlDsa65>::verify(&pk, b"msg", &long_ctx, &sig);
assert!(result.is_err(), "context > 255 bytes must be rejected in verify");
}
#[test]
fn verify_sig_bitflip_rejected() {
let (pk, sk) = make_keypair();
let mut rng = ml_dsa::OsRng;
let sig = ml_dsa::MlDsa::<ml_dsa::MlDsa65>::sign(&sk, b"hello", b"", &mut rng).unwrap();
let mut sig_bytes = sig.as_bytes().to_vec();
sig_bytes[0] ^= 0x01;
let sig_bad = ml_dsa::Signature::<ml_dsa::MlDsa65>::from_bytes(&sig_bytes).unwrap();
let result = ml_dsa::MlDsa::<ml_dsa::MlDsa65>::verify(&pk, b"hello", b"", &sig_bad);
assert!(!result.unwrap_or(true), "corrupted signature must be rejected");
}
#[test]
fn verify_wrong_message_rejected() {
let (pk, sk) = make_keypair();
let mut rng = ml_dsa::OsRng;
let sig = ml_dsa::MlDsa::<ml_dsa::MlDsa65>::sign(&sk, b"hello", b"", &mut rng).unwrap();
let result = ml_dsa::MlDsa::<ml_dsa::MlDsa65>::verify(&pk, b"world", b"", &sig);
assert!(!result.unwrap_or(true), "signature on wrong message must be rejected");
}
#[test]
fn verify_all_zero_sig_rejected() {
let (pk, _) = make_keypair();
let zero_sig = vec![0u8; ml_dsa::MlDsa::<ml_dsa::MlDsa65>::SIG_LEN];
let sig = ml_dsa::Signature::<ml_dsa::MlDsa65>::from_bytes(&zero_sig).unwrap();
let result = ml_dsa::MlDsa::<ml_dsa::MlDsa65>::verify(&pk, b"msg", b"", &sig);
assert!(!result.unwrap_or(true), "all-zero signature must be rejected");
}
#[test]
fn verify_random_sig_rejected() {
let (pk, _) = make_keypair();
let mut rng = ml_dsa::OsRng;
let mut random_sig = vec![0u8; ml_dsa::MlDsa::<ml_dsa::MlDsa65>::SIG_LEN];
{
use ml_dsa::CryptoRng as _;
rng.fill_bytes(&mut random_sig).unwrap();
}
let sig = ml_dsa::Signature::<ml_dsa::MlDsa65>::from_bytes(&random_sig).unwrap();
let result = ml_dsa::MlDsa::<ml_dsa::MlDsa65>::verify(&pk, b"msg", b"", &sig);
assert!(!result.unwrap_or(true), "random garbage signature must be rejected");
}
#[test]
fn signing_key_debug_is_redacted() {
let (_, sk) = make_keypair();
let dbg = format!("{:?}", sk.as_bytes());
assert!(sk.len() > 0);
}
}
mod slhdsa_negative {
use super::*;
fn make_keypair() -> (
slh_dsa::SigningKey<slh_dsa::Shake128f>,
slh_dsa::VerifyingKey<slh_dsa::Shake128f>,
) {
let mut rng = slh_dsa::OsRng;
slh_dsa::SlhDsa::<slh_dsa::Shake128f>::keygen(&mut rng).unwrap()
}
#[test]
fn vk_from_bytes_wrong_length() {
assert!(slh_dsa::VerifyingKey::<slh_dsa::Shake128f>::from_bytes(&[0u8; 10]).is_err());
}
#[test]
fn sk_from_bytes_wrong_length() {
assert!(slh_dsa::SigningKey::<slh_dsa::Shake128f>::from_bytes(&[0u8; 10]).is_err());
}
#[test]
fn sig_from_bytes_wrong_length() {
assert!(slh_dsa::Signature::<slh_dsa::Shake128f>::from_bytes(&[0u8; 10]).is_err());
}
#[test]
fn verify_sig_bitflip_rejected() {
let (sk, pk) = make_keypair();
let mut rng = slh_dsa::OsRng;
let sig = slh_dsa::SlhDsa::<slh_dsa::Shake128f>::sign(b"hello", &sk, &mut rng).unwrap();
let mut sig_bytes = sig.as_bytes().to_vec();
sig_bytes[100] ^= 0x01;
let sig_bad = slh_dsa::Signature::<slh_dsa::Shake128f>::from_bytes(&sig_bytes).unwrap();
let result = slh_dsa::SlhDsa::<slh_dsa::Shake128f>::verify(b"hello", &sig_bad, &pk);
assert!(!result.unwrap_or(true), "corrupted signature must be rejected");
}
#[test]
fn verify_wrong_message_rejected() {
let (sk, pk) = make_keypair();
let mut rng = slh_dsa::OsRng;
let sig = slh_dsa::SlhDsa::<slh_dsa::Shake128f>::sign(b"hello", &sk, &mut rng).unwrap();
let result = slh_dsa::SlhDsa::<slh_dsa::Shake128f>::verify(b"world", &sig, &pk);
assert!(!result.unwrap_or(true), "signature on wrong message must be rejected");
}
#[test]
fn verify_random_sig_rejected() {
let (_, pk) = make_keypair();
let sig_size = slh_dsa::SlhDsa::<slh_dsa::Shake128f>::signature_size();
let mut rng = slh_dsa::OsRng;
let mut random_sig = vec![0u8; sig_size];
{
use slh_dsa::CryptoRng as _;
rng.fill_bytes(&mut random_sig).unwrap();
}
let sig = slh_dsa::Signature::<slh_dsa::Shake128f>::from_bytes(&random_sig).unwrap();
let result = slh_dsa::SlhDsa::<slh_dsa::Shake128f>::verify(b"msg", &sig, &pk);
assert!(!result.unwrap_or(true), "random garbage signature must be rejected");
}
#[test]
fn verify_all_zero_sig_rejected() {
let (_, pk) = make_keypair();
let sig_size = slh_dsa::SlhDsa::<slh_dsa::Shake128f>::signature_size();
let zero_sig = vec![0u8; sig_size];
let sig = slh_dsa::Signature::<slh_dsa::Shake128f>::from_bytes(&zero_sig).unwrap();
let result = slh_dsa::SlhDsa::<slh_dsa::Shake128f>::verify(b"msg", &sig, &pk);
assert!(!result.unwrap_or(true), "all-zero signature must be rejected");
}
}