#![deny(unsafe_code)]
#![allow(
clippy::unwrap_used,
clippy::expect_used,
clippy::cast_precision_loss,
clippy::cast_lossless,
unused_qualifications
)]
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::thread;
use std::time::{Duration, Instant};
use latticearc::perf::{Histogram, MetricsCollector, Timer, benchmark, time_operation};
const MAX_SINGLE_OP_TIME: Duration = Duration::from_secs(10);
const MAX_BULK_OP_TIME: Duration = Duration::from_secs(30);
const MAX_LARGE_MSG_TIME: Duration = Duration::from_secs(60);
fn timed_operation<F, R>(name: &str, max_duration: Duration, operation: F) -> R
where
F: FnOnce() -> R,
{
let start = Instant::now();
let result = operation();
let elapsed = start.elapsed();
assert!(
elapsed < max_duration,
"{} took {:?}, expected less than {:?}",
name,
elapsed,
max_duration
);
result
}
#[test]
fn test_mlkem_512_keygen_timing_bound_succeeds() {
use latticearc::primitives::kem::ml_kem::{MlKem, MlKemSecurityLevel};
timed_operation("ML-KEM-512 keygen", MAX_SINGLE_OP_TIME, || {
let result = MlKem::generate_keypair(MlKemSecurityLevel::MlKem512);
assert!(result.is_ok(), "ML-KEM-512 keygen should succeed");
});
}
#[test]
fn test_mlkem_768_keygen_timing_bound_succeeds() {
use latticearc::primitives::kem::ml_kem::{MlKem, MlKemSecurityLevel};
timed_operation("ML-KEM-768 keygen", MAX_SINGLE_OP_TIME, || {
let result = MlKem::generate_keypair(MlKemSecurityLevel::MlKem768);
assert!(result.is_ok(), "ML-KEM-768 keygen should succeed");
});
}
#[test]
fn test_mlkem_1024_keygen_timing_bound_succeeds() {
use latticearc::primitives::kem::ml_kem::{MlKem, MlKemSecurityLevel};
timed_operation("ML-KEM-1024 keygen", MAX_SINGLE_OP_TIME, || {
let result = MlKem::generate_keypair(MlKemSecurityLevel::MlKem1024);
assert!(result.is_ok(), "ML-KEM-1024 keygen should succeed");
});
}
#[test]
fn test_mlkem_encapsulation_timing_bound_succeeds() {
use latticearc::primitives::kem::ml_kem::{MlKem, MlKemSecurityLevel};
let (pk, _sk) =
MlKem::generate_keypair(MlKemSecurityLevel::MlKem768).expect("keygen should succeed");
timed_operation("ML-KEM encapsulation", MAX_SINGLE_OP_TIME, || {
let result = MlKem::encapsulate(&pk);
assert!(result.is_ok(), "ML-KEM encapsulation should succeed");
});
}
#[test]
fn test_mldsa_44_keygen_timing_bound_succeeds() {
use latticearc::primitives::sig::ml_dsa::{MlDsaParameterSet, generate_keypair};
timed_operation("ML-DSA-44 keygen", MAX_SINGLE_OP_TIME, || {
let result = generate_keypair(MlDsaParameterSet::MlDsa44);
assert!(result.is_ok(), "ML-DSA-44 keygen should succeed");
});
}
#[test]
fn test_mldsa_65_keygen_timing_bound_succeeds() {
use latticearc::primitives::sig::ml_dsa::{MlDsaParameterSet, generate_keypair};
timed_operation("ML-DSA-65 keygen", MAX_SINGLE_OP_TIME, || {
let result = generate_keypair(MlDsaParameterSet::MlDsa65);
assert!(result.is_ok(), "ML-DSA-65 keygen should succeed");
});
}
#[test]
fn test_mldsa_87_keygen_timing_bound_succeeds() {
use latticearc::primitives::sig::ml_dsa::{MlDsaParameterSet, generate_keypair};
timed_operation("ML-DSA-87 keygen", MAX_SINGLE_OP_TIME, || {
let result = generate_keypair(MlDsaParameterSet::MlDsa87);
assert!(result.is_ok(), "ML-DSA-87 keygen should succeed");
});
}
#[test]
fn test_mldsa_sign_timing_bound_succeeds() {
use latticearc::primitives::sig::ml_dsa::{MlDsaParameterSet, generate_keypair};
let (_pk, sk) = generate_keypair(MlDsaParameterSet::MlDsa65).expect("keygen should succeed");
let message = b"Test message for signing performance";
timed_operation("ML-DSA signing", MAX_SINGLE_OP_TIME, || {
let result = sk.sign(message, &[]);
assert!(result.is_ok(), "ML-DSA signing should succeed");
});
}
#[test]
fn test_mldsa_verify_timing_bound_succeeds() {
use latticearc::primitives::sig::ml_dsa::{MlDsaParameterSet, generate_keypair};
let (pk, sk) = generate_keypair(MlDsaParameterSet::MlDsa65).expect("keygen should succeed");
let message = b"Test message for verification performance";
let signature = sk.sign(message, &[]).expect("signing should succeed");
timed_operation("ML-DSA verification", MAX_SINGLE_OP_TIME, || {
let result = pk.verify(message, &signature, &[]);
assert!(result.is_ok(), "ML-DSA verification should succeed");
assert!(result.unwrap(), "Signature should be valid");
});
}
#[test]
fn test_aes_gcm_256_encrypt_timing_bound_succeeds() {
use latticearc::primitives::aead::AeadCipher;
use latticearc::primitives::aead::aes_gcm::AesGcm256;
let key = AesGcm256::generate_key();
let cipher = AesGcm256::new(&*key).expect("cipher creation should succeed");
let nonce = AesGcm256::generate_nonce();
let plaintext = vec![0xAB; 1024];
timed_operation("AES-GCM-256 encryption", MAX_SINGLE_OP_TIME, || {
let result = cipher.encrypt(&nonce, &plaintext, None);
assert!(result.is_ok(), "AES-GCM encryption should succeed");
});
}
#[test]
fn test_aes_gcm_256_decrypt_timing_bound_succeeds() {
use latticearc::primitives::aead::AeadCipher;
use latticearc::primitives::aead::aes_gcm::AesGcm256;
let key = AesGcm256::generate_key();
let cipher = AesGcm256::new(&*key).expect("cipher creation should succeed");
let nonce = AesGcm256::generate_nonce();
let plaintext = vec![0xAB; 1024]; let (ciphertext, tag) = cipher.encrypt(&nonce, &plaintext, None).unwrap();
timed_operation("AES-GCM-256 decryption", MAX_SINGLE_OP_TIME, || {
let result = cipher.decrypt(&nonce, &ciphertext, &tag, None);
assert!(result.is_ok(), "AES-GCM decryption should succeed");
});
}
#[test]
#[cfg(not(feature = "fips"))]
fn test_chacha20_poly1305_encrypt_timing_bound_succeeds() {
use latticearc::primitives::aead::AeadCipher;
use latticearc::primitives::aead::chacha20poly1305::ChaCha20Poly1305Cipher;
let key = ChaCha20Poly1305Cipher::generate_key();
let cipher = ChaCha20Poly1305Cipher::new(&*key).expect("cipher creation should succeed");
let nonce = ChaCha20Poly1305Cipher::generate_nonce();
let plaintext = vec![0xAB; 1024];
timed_operation("ChaCha20-Poly1305 encryption", MAX_SINGLE_OP_TIME, || {
let result = cipher.encrypt(&nonce, &plaintext, None);
assert!(result.is_ok(), "ChaCha20-Poly1305 encryption should succeed");
});
}
#[test]
fn test_sha256_hash_timing_bound_succeeds() {
use latticearc::primitives::hash::sha2::sha256;
let data = vec![0xAB; 1024];
timed_operation("SHA-256 hashing", MAX_SINGLE_OP_TIME, || {
let result = sha256(&data);
assert!(result.is_ok(), "SHA-256 hashing should succeed");
});
}
#[test]
fn test_sha512_hash_timing_bound_succeeds() {
use latticearc::primitives::hash::sha2::sha512;
let data = vec![0xAB; 1024];
timed_operation("SHA-512 hashing", MAX_SINGLE_OP_TIME, || {
let result = sha512(&data);
assert!(result.is_ok(), "SHA-512 hashing should succeed");
});
}
#[test]
fn test_hkdf_derivation_timing_bound_succeeds() {
use latticearc::primitives::kdf::hkdf::hkdf;
let ikm = vec![0xAB; 32];
let salt = vec![0xCD; 16];
let info = b"test info";
timed_operation("HKDF derivation", MAX_SINGLE_OP_TIME, || {
let result = hkdf(&ikm, Some(&salt), Some(info), 64);
assert!(result.is_ok(), "HKDF derivation should succeed");
});
}
#[test]
fn test_aes_gcm_128_encrypt_timing_bound_succeeds() {
use latticearc::primitives::aead::AeadCipher;
use latticearc::primitives::aead::aes_gcm::AesGcm128;
let key = AesGcm128::generate_key();
let cipher = AesGcm128::new(&*key).expect("cipher creation should succeed");
let nonce = AesGcm128::generate_nonce();
let plaintext = vec![0xAB; 1024];
timed_operation("AES-GCM-128 encryption", MAX_SINGLE_OP_TIME, || {
let result = cipher.encrypt(&nonce, &plaintext, None);
assert!(result.is_ok(), "AES-GCM-128 encryption should succeed");
});
}
#[test]
#[ignore = "perf-floor: throughput_mbps > 10.0; opt in via --include-ignored on a quiescent release build"]
fn test_aes_gcm_256_bulk_encryption_throughput_succeeds() {
use latticearc::primitives::aead::AeadCipher;
use latticearc::primitives::aead::aes_gcm::AesGcm256;
let key = AesGcm256::generate_key();
let cipher = AesGcm256::new(&*key).expect("cipher creation should succeed");
let plaintext = vec![0xAB; 1024 * 1024];
let iterations = 10;
let start = Instant::now();
for _ in 0..iterations {
let nonce = AesGcm256::generate_nonce();
let _result = cipher.encrypt(&nonce, &plaintext, None).unwrap();
}
let elapsed = start.elapsed();
let total_mb = (iterations * plaintext.len()) as f64 / (1024.0 * 1024.0);
let throughput_mbps = total_mb / elapsed.as_secs_f64();
assert!(
throughput_mbps > 10.0,
"AES-GCM throughput {:.2} MB/s is too low (expected > 10 MB/s)",
throughput_mbps
);
}
#[test]
#[cfg(not(feature = "fips"))]
#[ignore = "perf-floor: throughput_mbps > 10.0; opt in via --include-ignored on a quiescent release build"]
fn test_chacha20_poly1305_bulk_encryption_throughput_succeeds() {
use latticearc::primitives::aead::AeadCipher;
use latticearc::primitives::aead::chacha20poly1305::ChaCha20Poly1305Cipher;
let key = ChaCha20Poly1305Cipher::generate_key();
let cipher = ChaCha20Poly1305Cipher::new(&*key).expect("cipher creation should succeed");
let plaintext = vec![0xAB; 1024 * 1024];
let iterations = 10;
let start = Instant::now();
for _ in 0..iterations {
let nonce = ChaCha20Poly1305Cipher::generate_nonce();
let _result = cipher.encrypt(&nonce, &plaintext, None).unwrap();
}
let elapsed = start.elapsed();
let total_mb = (iterations * plaintext.len()) as f64 / (1024.0 * 1024.0);
let throughput_mbps = total_mb / elapsed.as_secs_f64();
assert!(
throughput_mbps > 10.0,
"ChaCha20-Poly1305 throughput {:.2} MB/s is too low (expected > 10 MB/s)",
throughput_mbps
);
}
#[test]
#[ignore = "perf-floor: rate > 10/s; opt in via --include-ignored on a quiescent release build"]
fn test_mlkem_keygen_rate_succeeds() {
use latticearc::primitives::kem::ml_kem::{MlKem, MlKemSecurityLevel};
let iterations = 100;
let start = Instant::now();
for _ in 0..iterations {
let _result = MlKem::generate_keypair(MlKemSecurityLevel::MlKem768).unwrap();
}
let elapsed = start.elapsed();
let rate = iterations as f64 / elapsed.as_secs_f64();
assert!(rate > 10.0, "ML-KEM keygen rate {:.2}/s is too low (expected > 10/s)", rate);
}
#[test]
#[ignore = "perf-floor: rate > 10/s; opt in via --include-ignored on a quiescent release build"]
fn test_mldsa_sign_rate_succeeds() {
use latticearc::primitives::sig::ml_dsa::{MlDsaParameterSet, generate_keypair};
let (_pk, sk) = generate_keypair(MlDsaParameterSet::MlDsa44).expect("keygen should succeed");
let message = b"Test message for signing rate measurement";
let iterations = 100;
let start = Instant::now();
for _ in 0..iterations {
let _result = sk.sign(message, &[]).unwrap();
}
let elapsed = start.elapsed();
let rate = iterations as f64 / elapsed.as_secs_f64();
assert!(rate > 10.0, "ML-DSA signing rate {:.2}/s is too low (expected > 10/s)", rate);
}
#[test]
#[ignore = "perf-floor: rate > 10/s; opt in via --include-ignored on a quiescent release build"]
fn test_mldsa_verify_rate_succeeds() {
use latticearc::primitives::sig::ml_dsa::{MlDsaParameterSet, generate_keypair};
let (pk, sk) = generate_keypair(MlDsaParameterSet::MlDsa44).expect("keygen should succeed");
let message = b"Test message for verification rate measurement";
let signature = sk.sign(message, &[]).expect("signing should succeed");
let iterations = 100;
let start = Instant::now();
for _ in 0..iterations {
let _result = pk.verify(message, &signature, &[]).unwrap();
}
let elapsed = start.elapsed();
let rate = iterations as f64 / elapsed.as_secs_f64();
assert!(rate > 10.0, "ML-DSA verification rate {:.2}/s is too low (expected > 10/s)", rate);
}
#[test]
#[ignore = "perf-floor: throughput_mbps > 50.0; opt in via --include-ignored on a quiescent release build"]
fn test_sha256_throughput_succeeds() {
use latticearc::primitives::hash::sha2::sha256;
let data = vec![0xAB; 1024 * 1024]; let iterations = 10;
let start = Instant::now();
for _ in 0..iterations {
let _result = sha256(&data).unwrap();
}
let elapsed = start.elapsed();
let total_mb = (iterations * data.len()) as f64 / (1024.0 * 1024.0);
let throughput_mbps = total_mb / elapsed.as_secs_f64();
assert!(
throughput_mbps > 50.0,
"SHA-256 throughput {:.2} MB/s is too low (expected > 50 MB/s)",
throughput_mbps
);
}
#[test]
#[ignore = "perf-floor: throughput_mbps > 50.0; opt in via --include-ignored on a quiescent release build"]
fn test_sha512_throughput_succeeds() {
use latticearc::primitives::hash::sha2::sha512;
let data = vec![0xAB; 1024 * 1024]; let iterations = 10;
let start = Instant::now();
for _ in 0..iterations {
let _result = sha512(&data).unwrap();
}
let elapsed = start.elapsed();
let total_mb = (iterations * data.len()) as f64 / (1024.0 * 1024.0);
let throughput_mbps = total_mb / elapsed.as_secs_f64();
assert!(
throughput_mbps > 50.0,
"SHA-512 throughput {:.2} MB/s is too low (expected > 50 MB/s)",
throughput_mbps
);
}
#[test]
#[ignore = "perf-floor: rate > 1000/s; opt in via --include-ignored on a quiescent release build"]
fn test_hkdf_derivation_rate_succeeds() {
use latticearc::primitives::kdf::hkdf::hkdf;
let ikm = vec![0xAB; 32];
let salt = vec![0xCD; 16];
let info = b"test info";
let iterations = 1000;
let start = Instant::now();
for _ in 0..iterations {
let _result = hkdf(&ikm, Some(&salt), Some(info), 32).unwrap();
}
let elapsed = start.elapsed();
let rate = iterations as f64 / elapsed.as_secs_f64();
assert!(rate > 1000.0, "HKDF derivation rate {:.2}/s is too low (expected > 1000/s)", rate);
}
#[test]
fn test_batch_signature_processing_succeeds() {
use latticearc::primitives::sig::ml_dsa::{MlDsaParameterSet, generate_keypair};
let (_pk, sk) = generate_keypair(MlDsaParameterSet::MlDsa44).expect("keygen should succeed");
let batch_size = 50;
let messages: Vec<Vec<u8>> =
(0..batch_size).map(|i| format!("Message number {}", i).into_bytes()).collect();
timed_operation("Batch signature processing", MAX_BULK_OP_TIME, || {
for message in &messages {
let _result = sk.sign(message, &[]).unwrap();
}
});
}
#[test]
#[ignore = "perf-floor: rate > 50/s; opt in via --include-ignored on a quiescent release build"]
fn test_mlkem_encapsulation_rate_succeeds() {
use latticearc::primitives::kem::ml_kem::{MlKem, MlKemSecurityLevel};
let (pk, _sk) =
MlKem::generate_keypair(MlKemSecurityLevel::MlKem768).expect("keygen should succeed");
let iterations = 100;
let start = Instant::now();
for _ in 0..iterations {
let _result = MlKem::encapsulate(&pk).unwrap();
}
let elapsed = start.elapsed();
let rate = iterations as f64 / elapsed.as_secs_f64();
assert!(rate > 50.0, "ML-KEM encapsulation rate {:.2}/s is too low (expected > 50/s)", rate);
}
#[test]
fn test_mlkem_512_key_sizes_has_correct_size() {
use latticearc::primitives::kem::ml_kem::{MlKem, MlKemSecurityLevel};
let (pk, sk) =
MlKem::generate_keypair(MlKemSecurityLevel::MlKem512).expect("keygen should succeed");
assert_eq!(pk.as_bytes().len(), 800, "ML-KEM-512 public key should be 800 bytes");
assert_eq!(sk.expose_secret().len(), 1632, "ML-KEM-512 secret key should be 1632 bytes");
}
#[test]
fn test_mlkem_768_key_sizes_has_correct_size() {
use latticearc::primitives::kem::ml_kem::{MlKem, MlKemSecurityLevel};
let (pk, sk) =
MlKem::generate_keypair(MlKemSecurityLevel::MlKem768).expect("keygen should succeed");
assert_eq!(pk.as_bytes().len(), 1184, "ML-KEM-768 public key should be 1184 bytes");
assert_eq!(sk.expose_secret().len(), 2400, "ML-KEM-768 secret key should be 2400 bytes");
}
#[test]
fn test_mlkem_1024_key_sizes_has_correct_size() {
use latticearc::primitives::kem::ml_kem::{MlKem, MlKemSecurityLevel};
let (pk, sk) =
MlKem::generate_keypair(MlKemSecurityLevel::MlKem1024).expect("keygen should succeed");
assert_eq!(pk.as_bytes().len(), 1568, "ML-KEM-1024 public key should be 1568 bytes");
assert_eq!(sk.expose_secret().len(), 3168, "ML-KEM-1024 secret key should be 3168 bytes");
}
#[test]
fn test_mlkem_ciphertext_sizes_has_correct_size() {
use latticearc::primitives::kem::ml_kem::{MlKem, MlKemSecurityLevel};
let (pk512, _sk) = MlKem::generate_keypair(MlKemSecurityLevel::MlKem512).unwrap();
let (_ss, ct512) = MlKem::encapsulate(&pk512).unwrap();
assert_eq!(ct512.as_bytes().len(), 768, "ML-KEM-512 ciphertext should be 768 bytes");
let (pk768, _sk) = MlKem::generate_keypair(MlKemSecurityLevel::MlKem768).unwrap();
let (_ss, ct768) = MlKem::encapsulate(&pk768).unwrap();
assert_eq!(ct768.as_bytes().len(), 1088, "ML-KEM-768 ciphertext should be 1088 bytes");
let (pk1024, _sk) = MlKem::generate_keypair(MlKemSecurityLevel::MlKem1024).unwrap();
let (_ss, ct1024) = MlKem::encapsulate(&pk1024).unwrap();
assert_eq!(ct1024.as_bytes().len(), 1568, "ML-KEM-1024 ciphertext should be 1568 bytes");
}
#[test]
fn test_mldsa_key_sizes_has_correct_size() {
use latticearc::primitives::sig::ml_dsa::{MlDsaParameterSet, generate_keypair};
let (pk44, sk44) = generate_keypair(MlDsaParameterSet::MlDsa44).unwrap();
assert_eq!(pk44.len(), 1312, "ML-DSA-44 public key should be 1312 bytes");
assert_eq!(sk44.len(), 2560, "ML-DSA-44 secret key should be 2560 bytes");
let (pk65, sk65) = generate_keypair(MlDsaParameterSet::MlDsa65).unwrap();
assert_eq!(pk65.len(), 1952, "ML-DSA-65 public key should be 1952 bytes");
assert_eq!(sk65.len(), 4032, "ML-DSA-65 secret key should be 4032 bytes");
let (pk87, sk87) = generate_keypair(MlDsaParameterSet::MlDsa87).unwrap();
assert_eq!(pk87.len(), 2592, "ML-DSA-87 public key should be 2592 bytes");
assert_eq!(sk87.len(), 4896, "ML-DSA-87 secret key should be 4896 bytes");
}
#[test]
fn test_mldsa_signature_sizes_has_correct_size() {
use latticearc::primitives::sig::ml_dsa::{MlDsaParameterSet, generate_keypair};
let message = b"Test message for signature size check";
let (_pk44, sk44) = generate_keypair(MlDsaParameterSet::MlDsa44).unwrap();
let sig44 = sk44.sign(message, &[]).unwrap();
assert_eq!(sig44.len(), 2420, "ML-DSA-44 signature should be 2420 bytes");
let (_pk65, sk65) = generate_keypair(MlDsaParameterSet::MlDsa65).unwrap();
let sig65 = sk65.sign(message, &[]).unwrap();
assert_eq!(sig65.len(), 3309, "ML-DSA-65 signature should be 3309 bytes");
let (_pk87, sk87) = generate_keypair(MlDsaParameterSet::MlDsa87).unwrap();
let sig87 = sk87.sign(message, &[]).unwrap();
assert_eq!(sig87.len(), 4627, "ML-DSA-87 signature should be 4627 bytes");
}
#[test]
fn test_aes_gcm_ciphertext_expansion_succeeds() {
use latticearc::primitives::aead::AeadCipher;
use latticearc::primitives::aead::aes_gcm::AesGcm256;
let key = AesGcm256::generate_key();
let cipher = AesGcm256::new(&*key).expect("cipher creation should succeed");
let nonce = AesGcm256::generate_nonce();
for size in [0, 1, 16, 64, 256, 1024, 4096] {
let plaintext = vec![0xAB; size];
let (ciphertext, tag) = cipher.encrypt(&nonce, &plaintext, None).unwrap();
assert_eq!(
ciphertext.len(),
plaintext.len(),
"AES-GCM ciphertext should have same length as plaintext for size {}",
size
);
assert_eq!(tag.len(), 16, "AES-GCM tag should always be 16 bytes");
}
}
#[test]
#[cfg(not(feature = "fips"))]
fn test_chacha20_poly1305_ciphertext_expansion_succeeds() {
use latticearc::primitives::aead::AeadCipher;
use latticearc::primitives::aead::chacha20poly1305::ChaCha20Poly1305Cipher;
let key = ChaCha20Poly1305Cipher::generate_key();
let cipher = ChaCha20Poly1305Cipher::new(&*key).expect("cipher creation should succeed");
let nonce = ChaCha20Poly1305Cipher::generate_nonce();
for size in [0, 1, 16, 64, 256, 1024, 4096] {
let plaintext = vec![0xAB; size];
let (ciphertext, tag) = cipher.encrypt(&nonce, &plaintext, None).unwrap();
assert_eq!(
ciphertext.len(),
plaintext.len(),
"ChaCha20-Poly1305 ciphertext should have same length as plaintext for size {}",
size
);
assert_eq!(tag.len(), 16, "ChaCha20-Poly1305 tag should always be 16 bytes");
}
}
#[test]
fn test_sha_hash_output_sizes_has_correct_size() {
use latticearc::primitives::hash::sha2::{sha256, sha384, sha512};
let data = b"Test data for hash output size verification";
let hash256 = sha256(data).unwrap();
assert_eq!(hash256.len(), 32, "SHA-256 output should be 32 bytes");
let hash384 = sha384(data).unwrap();
assert_eq!(hash384.len(), 48, "SHA-384 output should be 48 bytes");
let hash512 = sha512(data).unwrap();
assert_eq!(hash512.len(), 64, "SHA-512 output should be 64 bytes");
}
#[test]
fn test_no_memory_leak_keygen_succeeds() {
use latticearc::primitives::kem::ml_kem::{MlKem, MlKemSecurityLevel};
let iterations = 100;
timed_operation("Repeated keygen (leak test)", MAX_BULK_OP_TIME, || {
for _ in 0..iterations {
let _keypair = MlKem::generate_keypair(MlKemSecurityLevel::MlKem768);
}
});
}
#[test]
fn test_no_memory_leak_encryption_succeeds() {
use latticearc::primitives::aead::AeadCipher;
use latticearc::primitives::aead::aes_gcm::AesGcm256;
let key = AesGcm256::generate_key();
let cipher = AesGcm256::new(&*key).expect("cipher creation should succeed");
let plaintext = vec![0xAB; 1024]; let iterations = 1000;
timed_operation("Repeated encryption (leak test)", MAX_BULK_OP_TIME, || {
for _ in 0..iterations {
let nonce = AesGcm256::generate_nonce();
let _result = cipher.encrypt(&nonce, &plaintext, None);
}
});
}
#[test]
fn test_aes_gcm_scaling_with_data_size_has_correct_size() {
use latticearc::primitives::aead::AeadCipher;
use latticearc::primitives::aead::aes_gcm::AesGcm256;
let key = AesGcm256::generate_key();
let cipher = AesGcm256::new(&*key).expect("cipher creation should succeed");
let sizes = [1024, 4096, 16384, 65536, 262144]; let mut durations = Vec::with_capacity(sizes.len());
for &size in &sizes {
let plaintext = vec![0xAB; size];
let nonce = AesGcm256::generate_nonce();
let start = Instant::now();
let _result = cipher.encrypt(&nonce, &plaintext, None).unwrap();
durations.push((size, start.elapsed()));
}
for (size, duration) in &durations {
assert!(
duration.as_secs() < 5,
"AES-GCM encryption of {} bytes took too long: {:?}",
size,
duration
);
}
}
#[test]
fn test_sha256_scaling_with_data_size_has_correct_size() {
use latticearc::primitives::hash::sha2::sha256;
let sizes = [1024, 4096, 16384, 65536, 262144];
for &size in &sizes {
let data = vec![0xAB; size];
let start = Instant::now();
let _result = sha256(&data).unwrap();
let duration = start.elapsed();
assert!(
duration.as_secs() < 5,
"SHA-256 hashing of {} bytes took too long: {:?}",
size,
duration
);
}
}
#[test]
fn test_concurrent_mlkem_operations_succeeds() {
use latticearc::primitives::kem::ml_kem::{MlKem, MlKemSecurityLevel};
let thread_count = 4;
let operations_per_thread = 10;
let success_count = Arc::new(AtomicUsize::new(0));
let start = Instant::now();
let mut handles = vec![];
for _ in 0..thread_count {
let success_count_clone = Arc::clone(&success_count);
let handle = thread::spawn(move || {
for _ in 0..operations_per_thread {
if MlKem::generate_keypair(MlKemSecurityLevel::MlKem768).is_ok() {
success_count_clone.fetch_add(1, Ordering::SeqCst);
}
}
});
handles.push(handle);
}
for handle in handles {
handle.join().expect("Thread should not panic");
}
let elapsed = start.elapsed();
let total_ops = success_count.load(Ordering::SeqCst);
assert_eq!(
total_ops,
thread_count * operations_per_thread,
"All concurrent operations should succeed"
);
assert!(
elapsed < MAX_BULK_OP_TIME,
"Concurrent ML-KEM operations took {:?}, expected less than {:?}",
elapsed,
MAX_BULK_OP_TIME
);
}
#[test]
fn test_concurrent_aes_gcm_operations_succeeds() {
use latticearc::primitives::aead::AeadCipher;
use latticearc::primitives::aead::aes_gcm::AesGcm256;
let thread_count = 4;
let operations_per_thread = 100;
let success_count = Arc::new(AtomicUsize::new(0));
let key = AesGcm256::generate_key();
let plaintext = vec![0xAB; 1024];
let start = Instant::now();
let mut handles = vec![];
for _ in 0..thread_count {
let success_count_clone = Arc::clone(&success_count);
let key_clone = key.clone();
let plaintext_clone = plaintext.clone();
let handle = thread::spawn(move || {
let cipher = AesGcm256::new(&*key_clone).expect("cipher creation should succeed");
for _ in 0..operations_per_thread {
let nonce = AesGcm256::generate_nonce();
if cipher.encrypt(&nonce, &plaintext_clone, None).is_ok() {
success_count_clone.fetch_add(1, Ordering::SeqCst);
}
}
});
handles.push(handle);
}
for handle in handles {
handle.join().expect("Thread should not panic");
}
let elapsed = start.elapsed();
let total_ops = success_count.load(Ordering::SeqCst);
assert_eq!(
total_ops,
thread_count * operations_per_thread,
"All concurrent operations should succeed"
);
assert!(elapsed < MAX_BULK_OP_TIME, "Concurrent AES-GCM operations took {:?}", elapsed);
}
#[test]
fn test_large_message_1mb_succeeds() {
use latticearc::primitives::aead::AeadCipher;
use latticearc::primitives::aead::aes_gcm::AesGcm256;
let key = AesGcm256::generate_key();
let cipher = AesGcm256::new(&*key).expect("cipher creation should succeed");
let nonce = AesGcm256::generate_nonce();
let plaintext = vec![0xAB; 1024 * 1024];
timed_operation("1MB encryption", MAX_LARGE_MSG_TIME, || {
let (ciphertext, tag) = cipher.encrypt(&nonce, &plaintext, None).unwrap();
let decrypted = cipher.decrypt(&nonce, &ciphertext, &tag, None).unwrap();
assert_eq!(
plaintext.as_slice(),
decrypted.as_slice(),
"Decrypted data should match original"
);
});
}
#[test]
fn test_large_message_10mb_succeeds() {
use latticearc::primitives::aead::AeadCipher;
use latticearc::primitives::aead::aes_gcm::AesGcm256;
let key = AesGcm256::generate_key();
let cipher = AesGcm256::new(&*key).expect("cipher creation should succeed");
let nonce = AesGcm256::generate_nonce();
let plaintext = vec![0xAB; 10 * 1024 * 1024];
timed_operation("10MB encryption", MAX_LARGE_MSG_TIME, || {
let (ciphertext, tag) = cipher.encrypt(&nonce, &plaintext, None).unwrap();
let decrypted = cipher.decrypt(&nonce, &ciphertext, &tag, None).unwrap();
assert_eq!(plaintext.len(), decrypted.len(), "Decrypted length should match");
});
}
#[test]
fn test_large_message_hashing_10mb_succeeds() {
use latticearc::primitives::hash::sha2::sha256;
let data = vec![0xAB; 10 * 1024 * 1024];
timed_operation("10MB SHA-256 hashing", MAX_LARGE_MSG_TIME, || {
let hash = sha256(&data).unwrap();
assert_eq!(hash.len(), 32, "SHA-256 output should be 32 bytes");
});
}
#[test]
fn test_performance_histogram_crypto_ops_succeeds() {
use latticearc::primitives::kem::ml_kem::{MlKem, MlKemSecurityLevel};
let mut histogram = Histogram::new(100);
for _ in 0..100 {
let mut timer = Timer::start();
let _result = MlKem::generate_keypair(MlKemSecurityLevel::MlKem768);
histogram.record(timer.stop());
}
let stats = histogram.calculate_statistics();
assert_eq!(stats.count, 100, "Should have 100 samples");
assert!(stats.min <= stats.median, "Min should be <= median");
assert!(stats.median <= stats.max, "Median should be <= max");
assert!(stats.percentile_99 >= stats.percentile_90, "P99 should be >= P90");
}
#[test]
fn test_metrics_collector_crypto_tracking_succeeds() {
use latticearc::primitives::kem::ml_kem::{MlKem, MlKemSecurityLevel};
let collector = MetricsCollector::new();
for _ in 0..10 {
let start = Instant::now();
let _result = MlKem::generate_keypair(MlKemSecurityLevel::MlKem512);
collector.record_operation("mlkem512_keygen", start.elapsed());
}
let (pk, _sk) = MlKem::generate_keypair(MlKemSecurityLevel::MlKem512).unwrap();
for _ in 0..10 {
let start = Instant::now();
let _result = MlKem::encapsulate(&pk);
collector.record_operation("mlkem512_encaps", start.elapsed());
}
assert_eq!(collector.get_count("mlkem512_keygen"), 10);
assert_eq!(collector.get_count("mlkem512_encaps"), 10);
let keygen_stats = collector.get_statistics("mlkem512_keygen");
assert_eq!(keygen_stats.count, 10);
assert!(keygen_stats.average > Duration::ZERO);
}
#[test]
fn test_hkdf_scaling_with_output_length_has_correct_size() {
use latticearc::primitives::kdf::hkdf::hkdf;
let ikm = vec![0xAB; 32];
let salt = vec![0xCD; 16];
let info = b"test info";
let output_lengths = [32, 64, 128, 256, 512, 1024, 2048, 4096];
for &length in &output_lengths {
let start = Instant::now();
let result = hkdf(&ikm, Some(&salt), Some(info), length).unwrap();
let duration = start.elapsed();
assert_eq!(result.expose_secret().len(), length, "HKDF output should be {} bytes", length);
assert!(
duration.as_secs() < 1,
"HKDF with {} byte output took too long: {:?}",
length,
duration
);
}
}
#[test]
fn test_mlkem_shared_secret_size_has_correct_size() {
use latticearc::primitives::kem::ml_kem::{MlKem, MlKemSecurityLevel};
for level in
[MlKemSecurityLevel::MlKem512, MlKemSecurityLevel::MlKem768, MlKemSecurityLevel::MlKem1024]
{
let (pk, _sk) = MlKem::generate_keypair(level).unwrap();
let (shared_secret, _ct) = MlKem::encapsulate(&pk).unwrap();
assert_eq!(
shared_secret.expose_secret().len(),
32,
"{:?} shared secret should be 32 bytes",
level
);
}
}
#[test]
fn test_concurrent_signature_operations_succeeds() {
use latticearc::primitives::sig::ml_dsa::{MlDsaParameterSet, generate_keypair};
let thread_count = 4;
let operations_per_thread = 20;
let success_count = Arc::new(AtomicUsize::new(0));
let (_pk, sk) = generate_keypair(MlDsaParameterSet::MlDsa44).unwrap();
let sk_bytes = sk.expose_secret().to_vec();
let start = Instant::now();
let mut handles = vec![];
for thread_id in 0..thread_count {
let success_count_clone = Arc::clone(&success_count);
let sk_bytes_clone = sk_bytes.clone();
let handle = thread::spawn(move || {
let sk = latticearc::primitives::sig::ml_dsa::MlDsaSecretKey::new(
MlDsaParameterSet::MlDsa44,
sk_bytes_clone,
)
.expect("SK reconstruction should succeed");
for i in 0..operations_per_thread {
let message = format!("Thread {} message {}", thread_id, i);
if sk.sign(message.as_bytes(), &[]).is_ok() {
success_count_clone.fetch_add(1, Ordering::SeqCst);
}
}
});
handles.push(handle);
}
for handle in handles {
handle.join().expect("Thread should not panic");
}
let elapsed = start.elapsed();
let total_ops = success_count.load(Ordering::SeqCst);
assert_eq!(
total_ops,
thread_count * operations_per_thread,
"All concurrent signature operations should succeed"
);
assert!(elapsed < MAX_BULK_OP_TIME, "Concurrent signature operations took {:?}", elapsed);
}
#[test]
fn test_performance_measurement_baseline_succeeds() {
let mut timer = Timer::start();
let mut sum: u64 = 0;
for i in 0..100000 {
sum = sum.wrapping_add(std::hint::black_box(i));
}
let elapsed = timer.stop();
assert!(elapsed > Duration::ZERO, "Timer should measure non-zero time");
std::hint::black_box(sum);
let stats = benchmark(100, || {
let mut x: u64 = 0;
for i in 0..1000 {
x = x.wrapping_add(std::hint::black_box(i));
}
std::hint::black_box(x);
});
assert_eq!(stats.count, 100, "Should have 100 samples");
assert!(stats.average > Duration::ZERO, "Average should be > 0");
assert!(stats.min <= stats.average, "Min should be <= average");
assert!(stats.max >= stats.average, "Max should be >= average");
let duration = time_operation(|| {
std::thread::sleep(Duration::from_millis(1));
});
assert!(duration >= Duration::from_millis(1), "time_operation should measure at least 1ms");
}