pub const DEFAULT_TVLA_ABS_T: f64 = 4.5;
pub const SUGGESTED_TRACES_PER_CLASS: u64 = 1_000_000;
#[must_use]
pub fn first_order_screen_passes(t_statistic: f64) -> bool {
t_statistic.abs() < DEFAULT_TVLA_ABS_T
}
#[must_use]
pub fn screen_fixed_vs_random(fixed: &[f64], random: &[f64]) -> Option<bool> {
crate::welch_t_statistic(fixed, random).map(first_order_screen_passes)
}
#[cfg(feature = "mldsa")]
use lib_q_ml_dsa::ml_dsa_44::portable;
#[cfg(feature = "mlkem")]
use lib_q_ml_kem::{
Decapsulate,
Encapsulate,
KemCore,
MlKem768,
};
#[cfg(feature = "mlkem")]
pub fn mlkem_decaps_tvla_timings(samples: usize) -> (Vec<f64>, Vec<f64>) {
let mut rng = lib_q_random::LibQRng::new_secure().expect("secure rng");
let (fixed_dk, fixed_ek) = MlKem768::generate(&mut rng);
let (fixed_ct, _fixed_ss) = fixed_ek.encapsulate(&mut rng).expect("encap");
let mut random_pairs = Vec::with_capacity(samples);
for _ in 0..samples {
let (dk, ek) = MlKem768::generate(&mut rng);
let (ct, _ss) = ek.encapsulate(&mut rng).expect("encap");
random_pairs.push((dk, ct));
}
let fixed = crate::sample_wall_times(
|| {
let ss = fixed_dk.decapsulate(&fixed_ct).expect("decap");
std::hint::black_box(ss);
},
samples,
);
let mut random_idx = 0usize;
let random = crate::sample_wall_times(
|| {
let (dk, ct) = &random_pairs[random_idx];
let ss = dk.decapsulate(ct).expect("decap");
std::hint::black_box(ss);
random_idx = (random_idx + 1) % random_pairs.len();
},
samples,
);
(fixed, random)
}
#[cfg(feature = "mldsa")]
pub fn mldsa_sign_tvla_timings(samples: usize) -> (Vec<f64>, Vec<f64>) {
let msg = b"lib-q-sca-tvla";
let ctx = b"";
let rnd = [0x42u8; 32];
let fixed_kp = portable::generate_key_pair([0x11u8; 32]);
let random_kps: Vec<_> = (0..samples)
.map(|i| portable::generate_key_pair([i as u8; 32]))
.collect();
let fixed = crate::sample_wall_times(
|| {
let sig = portable::sign(&fixed_kp.signing_key, msg, ctx, rnd).expect("sign");
std::hint::black_box(sig);
},
samples,
);
let mut random_idx = 0usize;
let random = crate::sample_wall_times(
|| {
let sig =
portable::sign(&random_kps[random_idx].signing_key, msg, ctx, rnd).expect("sign");
std::hint::black_box(sig);
random_idx = (random_idx + 1) % random_kps.len();
},
samples,
);
(fixed, random)
}
#[cfg(feature = "mlkem")]
pub fn mlkem_decaps_tvla_screen(samples: usize) -> Option<bool> {
let (fixed, random) = mlkem_decaps_tvla_timings(samples);
screen_fixed_vs_random(&fixed, &random)
}
#[cfg(feature = "mldsa")]
pub fn mldsa_sign_tvla_screen(samples: usize) -> Option<bool> {
let (fixed, random) = mldsa_sign_tvla_timings(samples);
screen_fixed_vs_random(&fixed, &random)
}
#[cfg(feature = "mlkem")]
pub fn mlkem_decaps_dudect_screen(samples: usize, threshold: f64) -> bool {
let (fixed, random) = mlkem_decaps_tvla_timings(samples);
let mut joined = fixed;
joined.extend(random);
crate::dudect::timing_passes_loose(threshold, &joined)
}
#[cfg(feature = "mldsa")]
pub fn mldsa_sign_dudect_screen(samples: usize, threshold: f64) -> bool {
let (fixed, random) = mldsa_sign_tvla_timings(samples);
let mut joined = fixed;
joined.extend(random);
crate::dudect::timing_passes_loose(threshold, &joined)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn threshold_sanity() {
assert!(first_order_screen_passes(0.1));
assert!(!first_order_screen_passes(10.0));
}
#[cfg(feature = "mlkem")]
#[test]
fn mlkem_tvla_and_dudect_smoke() {
let _ = mlkem_decaps_tvla_screen(32).expect("t-stat");
let _ = mlkem_decaps_dudect_screen(32, DEFAULT_TVLA_ABS_T);
}
#[cfg(feature = "mldsa")]
#[test]
fn mldsa_tvla_and_dudect_smoke() {
let _ = mldsa_sign_tvla_screen(32).expect("t-stat");
let _ = mldsa_sign_dudect_screen(32, DEFAULT_TVLA_ABS_T);
}
#[cfg(feature = "mldsa")]
#[test]
#[ignore = "slow: ~10k sign timings per class; for harness scale only"]
fn mldsa_sign_tvla_screen_10k_welch_report() {
const SAMPLES: usize = 10_000;
let (fixed, random) = mldsa_sign_tvla_timings(SAMPLES);
let t = crate::welch_t_statistic(&fixed, &random).expect("welch t-statistic");
eprintln!(
"ML-DSA Sign wall-clock Welch t (n={SAMPLES} per class): {t:.6} (first-order EM/TVLA gate is |t| < {DEFAULT_TVLA_ABS_T})"
);
assert!(t.is_finite(), "t-statistic must be finite");
}
}