use super::params::*;
#[test]
fn invariant_l_is_multiple_of_4() {
assert_eq!(
Params44::L % 4,
0,
"Params44::L={} not divisible by 4",
Params44::L
);
assert_eq!(
Params65::L % 4,
0,
"Params65::L={} not divisible by 4",
Params65::L
);
assert_eq!(
Params87::L % 4,
0,
"Params87::L={} not divisible by 4",
Params87::L
);
}
#[test]
fn invariant_k_times_l_is_multiple_of_4() {
assert_eq!(
(Params44::K * Params44::L) % 4,
0,
"Params44: K×L={} not divisible by 4",
Params44::K * Params44::L
);
assert_eq!(
(Params65::K * Params65::L) % 4,
0,
"Params65: K×L={} not divisible by 4",
Params65::K * Params65::L
);
assert_eq!(
(Params87::K * Params87::L) % 4,
0,
"Params87: K×L={} not divisible by 4",
Params87::K * Params87::L
);
}
#[test]
fn invariant_dimension_meets_security_level() {
assert!(
Params44::DIMENSION >= Params44::ML_DSA_EQUIVALENT_DIM,
"Params44: dimension {} < ML-DSA equivalent {}",
Params44::DIMENSION,
Params44::ML_DSA_EQUIVALENT_DIM
);
assert!(
Params65::DIMENSION >= Params65::ML_DSA_EQUIVALENT_DIM,
"Params65: dimension {} < ML-DSA equivalent {}",
Params65::DIMENSION,
Params65::ML_DSA_EQUIVALENT_DIM
);
assert!(
Params87::DIMENSION >= Params87::ML_DSA_EQUIVALENT_DIM,
"Params87: dimension {} < ML-DSA equivalent {}",
Params87::DIMENSION,
Params87::ML_DSA_EQUIVALENT_DIM
);
}
#[test]
fn invariant_beta_equals_tau_times_eta() {
assert_eq!(
Params44::BETA,
(Params44::TAU * Params44::ETA) as u32,
"Params44: BETA={} != TAU×ETA={}",
Params44::BETA,
Params44::TAU * Params44::ETA
);
assert_eq!(
Params65::BETA,
(Params65::TAU * Params65::ETA) as u32,
"Params65: BETA={} != TAU×ETA={}",
Params65::BETA,
Params65::TAU * Params65::ETA
);
assert_eq!(
Params87::BETA,
(Params87::TAU * Params87::ETA) as u32,
"Params87: BETA={} != TAU×ETA={}",
Params87::BETA,
Params87::TAU * Params87::ETA
);
}
#[test]
fn invariant_gamma1_is_power_of_2() {
assert!(
Params44::GAMMA1.is_power_of_two(),
"Params44::GAMMA1={} not power of 2",
Params44::GAMMA1
);
assert!(
Params65::GAMMA1.is_power_of_two(),
"Params65::GAMMA1={} not power of 2",
Params65::GAMMA1
);
assert!(
Params87::GAMMA1.is_power_of_two(),
"Params87::GAMMA1={} not power of 2",
Params87::GAMMA1
);
}
#[test]
fn invariant_gamma2_divides_q_minus_1() {
assert_eq!(
(Q as u32 - 1) % Params44::GAMMA2,
0,
"Params44: (Q-1) % GAMMA2 != 0"
);
assert_eq!(
(Q as u32 - 1) % Params65::GAMMA2,
0,
"Params65: (Q-1) % GAMMA2 != 0"
);
assert_eq!(
(Q as u32 - 1) % Params87::GAMMA2,
0,
"Params87: (Q-1) % GAMMA2 != 0"
);
}
#[test]
fn simd_expand_mask_has_no_leftover() {
let leftovers_44 = Params44::L % 4;
let leftovers_65 = Params65::L % 4;
let leftovers_87 = Params87::L % 4;
assert_eq!(
leftovers_44, 0,
"Params44: {} leftover elements in expand_mask",
leftovers_44
);
assert_eq!(
leftovers_65, 0,
"Params65: {} leftover elements in expand_mask",
leftovers_65
);
assert_eq!(
leftovers_87, 0,
"Params87: {} leftover elements in expand_mask",
leftovers_87
);
}
#[test]
fn simd_expand_a_has_no_leftover() {
let total_44 = Params44::K * Params44::L;
let total_65 = Params65::K * Params65::L;
let total_87 = Params87::K * Params87::L;
assert_eq!(
total_44 % 4,
0,
"Params44: K×L={} has {} leftovers",
total_44,
total_44 % 4
);
assert_eq!(
total_65 % 4,
0,
"Params65: K×L={} has {} leftovers",
total_65,
total_65 % 4
);
assert_eq!(
total_87 % 4,
0,
"Params87: K×L={} has {} leftovers",
total_87,
total_87 % 4
);
}
#[test]
fn simd_avx512_potential() {
let avx512_ready_87 = Params87::L % 8 == 0;
assert!(
avx512_ready_87,
"Params87::L={} should be AVX-512 ready",
Params87::L
);
assert_eq!(Params65::L, 4, "Params65 uses L=4 for performance");
}
#[test]
fn security_margin_params44_non_negative() {
let margin = (Params44::DIMENSION * 100 / Params44::ML_DSA_EQUIVALENT_DIM) as i32 - 100;
assert!(
margin >= 0,
"Params44: security margin {}% is negative",
margin
);
println!("Params44 security margin: {}%", margin);
}
#[test]
fn security_margin_params65_non_negative() {
let margin = (Params65::DIMENSION * 100 / Params65::ML_DSA_EQUIVALENT_DIM) as i32 - 100;
assert!(
margin >= 0,
"Params65: security margin {}% is negative",
margin
);
println!(
"Params65 security margin: {}% (same dimension as ML-DSA-65)",
margin
);
}
#[test]
fn security_margin_params87_positive() {
let margin = (Params87::DIMENSION * 100 / Params87::ML_DSA_EQUIVALENT_DIM) as i32 - 100;
assert!(
margin > 0,
"Params87: expected positive security margin, got {}%",
margin
);
println!("Params87 security margin: {}%", margin);
}
#[test]
fn security_dimensions_strictly_increasing() {
assert!(
Params65::DIMENSION > Params44::DIMENSION,
"Params65 dimension {} should exceed Params44 dimension {}",
Params65::DIMENSION,
Params44::DIMENSION
);
assert!(
Params87::DIMENSION > Params65::DIMENSION,
"Params87 dimension {} should exceed Params65 dimension {}",
Params87::DIMENSION,
Params65::DIMENSION
);
}
#[test]
fn size_document_public_key_impact() {
let ml_dsa_65_pk = 32 + 6 * 320;
let arcanum_65_pk = 32 + Params65::K * 320;
println!("ML-DSA-65 public key: {} bytes", ml_dsa_65_pk);
println!("Arcanum-65 public key: {} bytes", arcanum_65_pk);
println!(
"Difference: {} bytes",
arcanum_65_pk as i32 - ml_dsa_65_pk as i32
);
let ml_dsa_87_pk = 32 + 8 * 320;
let arcanum_87_pk = 32 + Params87::K * 320;
println!("ML-DSA-87 public key: {} bytes", ml_dsa_87_pk);
println!("Arcanum-87 public key: {} bytes", arcanum_87_pk);
}
#[test]
fn size_document_secret_key_impact() {
let ml_dsa_65_s1_size = 5 * 96;
let arcanum_65_s1_size = Params65::L * 96;
println!("ML-DSA-65 s₁ size: {} bytes", ml_dsa_65_s1_size);
println!("Arcanum-65 s₁ size: {} bytes", arcanum_65_s1_size);
println!(
"s₁ increase: {} bytes",
arcanum_65_s1_size as i32 - ml_dsa_65_s1_size as i32
);
let ml_dsa_87_s1_size = 7 * 64; let arcanum_87_s1_size = Params87::L * 64;
println!("ML-DSA-87 s₁ size: {} bytes", ml_dsa_87_s1_size);
println!("Arcanum-87 s₁ size: {} bytes", arcanum_87_s1_size);
}
#[test]
fn size_document_signature_impact() {
let bytes_per_poly_gamma19 = 640;
let ml_dsa_65_z_size = 5 * bytes_per_poly_gamma19;
let arcanum_65_z_size = Params65::L * bytes_per_poly_gamma19;
println!("ML-DSA-65 z size: {} bytes", ml_dsa_65_z_size);
println!("Arcanum-65 z size: {} bytes", arcanum_65_z_size);
println!(
"z increase: {} bytes",
arcanum_65_z_size as i32 - ml_dsa_65_z_size as i32
);
let ml_dsa_87_z_size = 7 * bytes_per_poly_gamma19;
let arcanum_87_z_size = Params87::L * bytes_per_poly_gamma19;
println!("ML-DSA-87 z size: {} bytes", ml_dsa_87_z_size);
println!("Arcanum-87 z size: {} bytes", arcanum_87_z_size);
}
use super::api::{ArcanumDsa, ArcanumDsa44, ArcanumDsa65, ArcanumDsa87};
#[test]
fn functional_sign_verify_roundtrip_44() {
let (sk, vk) = ArcanumDsa44::generate_keypair();
let msg = b"test message for Arcanum-DSA-44";
let sig = ArcanumDsa44::sign(&sk, msg);
assert!(
ArcanumDsa44::verify(&vk, msg, &sig).is_ok(),
"Arcanum-DSA-44 signature verification failed"
);
}
#[test]
fn functional_sign_verify_roundtrip_65() {
let (sk, vk) = ArcanumDsa65::generate_keypair();
let msg = b"test message for Arcanum-DSA-65 with SIMD-optimized L=8";
let sig = ArcanumDsa65::sign(&sk, msg);
assert!(
ArcanumDsa65::verify(&vk, msg, &sig).is_ok(),
"Arcanum-DSA-65 signature verification failed"
);
}
#[test]
fn functional_sign_verify_roundtrip_87() {
let (sk, vk) = ArcanumDsa87::generate_keypair();
let msg = b"test message for Arcanum-DSA-87 with maximum security";
let sig = ArcanumDsa87::sign(&sk, msg);
assert!(
ArcanumDsa87::verify(&vk, msg, &sig).is_ok(),
"Arcanum-DSA-87 signature verification failed"
);
}
#[test]
fn functional_invalid_signature_rejected() {
let (sk, vk) = ArcanumDsa65::generate_keypair();
let sig = ArcanumDsa65::sign(&sk, b"message A");
let result = ArcanumDsa65::verify(&vk, b"message B", &sig);
assert!(
result.is_err(),
"Verification should fail for wrong message"
);
}
#[test]
fn functional_wrong_key_rejected() {
let (sk1, _vk1) = ArcanumDsa65::generate_keypair();
let (_sk2, vk2) = ArcanumDsa65::generate_keypair();
let msg = b"test message";
let sig = ArcanumDsa65::sign(&sk1, msg);
let result = ArcanumDsa65::verify(&vk2, msg, &sig);
assert!(result.is_err(), "Verification should fail with wrong key");
}
#[test]
fn functional_empty_message() {
let (sk, vk) = ArcanumDsa44::generate_keypair();
let msg = b"";
let sig = ArcanumDsa44::sign(&sk, msg);
assert!(
ArcanumDsa44::verify(&vk, msg, &sig).is_ok(),
"Empty message signature verification failed"
);
}
#[test]
fn functional_large_message() {
let (sk, vk) = ArcanumDsa87::generate_keypair();
let msg = vec![0x42u8; 10000]; let sig = ArcanumDsa87::sign(&sk, &msg);
assert!(
ArcanumDsa87::verify(&vk, &msg, &sig).is_ok(),
"Large message signature verification failed"
);
}
#[test]
fn functional_key_sizes_match_params() {
use super::api::{sizes_44, sizes_65, sizes_87};
let (sk44, vk44) = ArcanumDsa44::generate_keypair();
assert_eq!(sk44.to_bytes().len(), sizes_44::SK_SIZE);
assert_eq!(vk44.to_bytes().len(), sizes_44::PK_SIZE);
let (sk65, vk65) = ArcanumDsa65::generate_keypair();
assert_eq!(sk65.to_bytes().len(), sizes_65::SK_SIZE);
assert_eq!(vk65.to_bytes().len(), sizes_65::PK_SIZE);
let (sk87, vk87) = ArcanumDsa87::generate_keypair();
assert_eq!(sk87.to_bytes().len(), sizes_87::SK_SIZE);
assert_eq!(vk87.to_bytes().len(), sizes_87::PK_SIZE);
}
#[test]
fn functional_signature_sizes_match_params() {
use super::api::{sizes_44, sizes_65, sizes_87};
let msg = b"size test";
let (sk44, _) = ArcanumDsa44::generate_keypair();
let sig44 = ArcanumDsa44::sign(&sk44, msg);
assert_eq!(sig44.to_bytes().len(), sizes_44::SIG_SIZE);
let (sk65, _) = ArcanumDsa65::generate_keypair();
let sig65 = ArcanumDsa65::sign(&sk65, msg);
assert_eq!(sig65.to_bytes().len(), sizes_65::SIG_SIZE);
let (sk87, _) = ArcanumDsa87::generate_keypair();
let sig87 = ArcanumDsa87::sign(&sk87, msg);
assert_eq!(sig87.to_bytes().len(), sizes_87::SIG_SIZE);
}
#[test]
#[ignore = "Benchmark - run manually with: cargo test --release benchmark"]
fn benchmark_sign_performance() {
use crate::ml_dsa::{MlDsa, MlDsa44, MlDsa65, MlDsa87};
use std::time::Instant;
const ITERATIONS: u32 = 1000;
const WARMUP: u32 = 100;
fn bench_sign<F: Fn()>(name: &str, iterations: u32, warmup: u32, f: F) -> std::time::Duration {
for _ in 0..warmup {
f();
}
let start = Instant::now();
for _ in 0..iterations {
f();
}
let elapsed = start.elapsed();
let avg = elapsed / iterations;
println!("{}: {:?} avg over {} iterations", name, avg, iterations);
avg
}
println!("\n╔════════════════════════════════════════════════════════════╗");
println!(
"║ SIGN PERFORMANCE BENCHMARK ({} iterations) ║",
ITERATIONS
);
println!("╚════════════════════════════════════════════════════════════╝\n");
let (ml_sk44, _) = MlDsa44::generate_keypair();
let (ml_sk65, _) = MlDsa65::generate_keypair();
let (ml_sk87, _) = MlDsa87::generate_keypair();
let (ar_sk44, _) = ArcanumDsa44::generate_keypair();
let (ar_sk65, _) = ArcanumDsa65::generate_keypair();
let (ar_sk87, _) = ArcanumDsa87::generate_keypair();
let msg = b"benchmark message for sign performance testing";
println!("─── Level 2 (L=4 for both) ───");
let ml44 = bench_sign("ML-DSA-44 sign", ITERATIONS, WARMUP, || {
let _ = MlDsa44::sign(&ml_sk44, msg);
});
let ar44 = bench_sign("Arcanum-44 sign", ITERATIONS, WARMUP, || {
let _ = ArcanumDsa44::sign(&ar_sk44, msg);
});
println!(
" Ratio: {:.2}x ({})\n",
ar44.as_nanos() as f64 / ml44.as_nanos() as f64,
if ar44 < ml44 {
"Arcanum faster"
} else {
"ML-DSA faster"
}
);
println!("─── Level 3 (ML-DSA K=6,L=5 vs Arcanum K=7,L=4 SIMD) ───");
let ml65 = bench_sign("ML-DSA-65 sign", ITERATIONS, WARMUP, || {
let _ = MlDsa65::sign(&ml_sk65, msg);
});
let ar65 = bench_sign("Arcanum-65 sign", ITERATIONS, WARMUP, || {
let _ = ArcanumDsa65::sign(&ar_sk65, msg);
});
println!(
" Ratio: {:.2}x ({})\n",
ar65.as_nanos() as f64 / ml65.as_nanos() as f64,
if ar65 < ml65 {
"Arcanum faster"
} else {
"ML-DSA faster"
}
);
println!("─── Level 5 (ML-DSA L=7 scalar vs Arcanum L=8 SIMD) ───");
let ml87 = bench_sign("ML-DSA-87 sign", ITERATIONS, WARMUP, || {
let _ = MlDsa87::sign(&ml_sk87, msg);
});
let ar87 = bench_sign("Arcanum-87 sign", ITERATIONS, WARMUP, || {
let _ = ArcanumDsa87::sign(&ar_sk87, msg);
});
println!(
" Ratio: {:.2}x ({})\n",
ar87.as_nanos() as f64 / ml87.as_nanos() as f64,
if ar87 < ml87 {
"Arcanum faster"
} else {
"ML-DSA faster"
}
);
}