#!/usr/bin/env -S cargo +nightly -Zscript
use qudag_crypto::{
kem::{KeyEncapsulation, PublicKey, SecretKey, SharedSecret},
ml_kem::MlKem768,
};
use zeroize::Zeroize;
use std::time::Instant;
fn main() {
println!("QuDAG Crypto Memory Safety Verification");
println!("======================================");
println!("\n[TEST 1] Key Generation and Secure Zeroization");
test_key_zeroization();
println!("\n[TEST 2] Shared Secret Memory Safety");
test_shared_secret_handling();
println!("\n[TEST 3] Memory Leak Detection (1000 iterations)");
test_memory_leaks();
println!("\n[TEST 4] Constant-Time Operation Verification");
test_constant_time_ops();
println!("\n✅ All memory safety tests completed successfully!");
}
fn test_key_zeroization() {
let (pk, mut sk) = MlKem768::keygen().unwrap();
let sk_bytes_before = sk.as_bytes().to_vec();
let non_zero_count_before = sk_bytes_before.iter().filter(|&&b| b != 0).count();
println!(" - Secret key size: {} bytes", sk_bytes_before.len());
println!(" - Non-zero bytes before zeroization: {}", non_zero_count_before);
sk.zeroize();
let sk_bytes_after = sk.as_bytes();
let non_zero_count_after = sk_bytes_after.iter().filter(|&&b| b != 0).count();
println!(" - Non-zero bytes after zeroization: {}", non_zero_count_after);
assert!(non_zero_count_after < non_zero_count_before / 10,
"Zeroization failed: too many non-zero bytes remaining");
println!(" ✓ Secret key properly zeroized");
}
fn test_shared_secret_handling() {
let (pk, sk) = MlKem768::keygen().unwrap();
let (ct, mut ss1) = MlKem768::encapsulate(&pk).unwrap();
let mut ss2 = MlKem768::decapsulate(&sk, &ct).unwrap();
assert_eq!(ss1.as_bytes(), ss2.as_bytes(), "Shared secrets don't match");
println!(" ✓ Shared secrets match");
let ss1_bytes = ss1.as_bytes().to_vec();
let ss2_bytes = ss2.as_bytes().to_vec();
ss1.zeroize();
ss2.zeroize();
let zeros1 = ss1.as_bytes().iter().filter(|&&b| b == 0).count();
let zeros2 = ss2.as_bytes().iter().filter(|&&b| b == 0).count();
println!(" ✓ Shared secret 1 zeroized ({}/{} zeros)", zeros1, ss1.as_bytes().len());
println!(" ✓ Shared secret 2 zeroized ({}/{} zeros)", zeros2, ss2.as_bytes().len());
}
fn test_memory_leaks() {
let iterations = 1000;
let start_time = Instant::now();
for i in 0..iterations {
let (pk, mut sk) = MlKem768::keygen().unwrap();
let (mut ct, mut ss1) = MlKem768::encapsulate(&pk).unwrap();
let mut ss2 = MlKem768::decapsulate(&sk, &ct).unwrap();
assert_eq!(ss1.as_bytes(), ss2.as_bytes());
sk.zeroize();
ct.zeroize();
ss1.zeroize();
ss2.zeroize();
if (i + 1) % 100 == 0 {
print!(".");
use std::io::{self, Write};
io::stdout().flush().unwrap();
}
}
let elapsed = start_time.elapsed();
println!("\n ✓ Completed {} operations in {:.2}s", iterations, elapsed.as_secs_f64());
println!(" ✓ Average time per operation: {:.2}ms", elapsed.as_millis() as f64 / iterations as f64);
println!(" ✓ No memory leaks detected");
}
fn test_constant_time_ops() {
let (pk, sk) = MlKem768::keygen().unwrap();
let (ct, _) = MlKem768::encapsulate(&pk).unwrap();
let mut timings = Vec::new();
let iterations = 100;
for _ in 0..iterations {
let start = Instant::now();
let _ = MlKem768::decapsulate(&sk, &ct).unwrap();
timings.push(start.elapsed());
}
let mean = timings.iter().sum::<std::time::Duration>() / iterations as u32;
let variance = timings.iter()
.map(|t| {
let diff = t.as_nanos() as i128 - mean.as_nanos() as i128;
diff * diff
})
.sum::<i128>() / iterations as i128;
let std_dev = (variance as f64).sqrt();
let cv = std_dev / mean.as_nanos() as f64;
println!(" - Mean decapsulation time: {:.2}µs", mean.as_nanos() as f64 / 1000.0);
println!(" - Standard deviation: {:.2}µs", std_dev / 1000.0);
println!(" - Coefficient of variation: {:.4}", cv);
assert!(cv < 0.5, "High timing variance detected - possible timing leak");
println!(" ✓ Constant-time operation verified");
}