use std::time::Instant;
use kk_crypto::kk_mix::{kk_hash, kk_mac, kk_mac_verify};
const THRESHOLD: f64 = 4.5;
const SAMPLES: usize = 100_000;
struct WelchT {
n0: f64,
n1: f64,
mean0: f64,
mean1: f64,
m2_0: f64,
m2_1: f64,
}
impl WelchT {
fn new() -> Self {
Self {
n0: 0.0,
n1: 0.0,
mean0: 0.0,
mean1: 0.0,
m2_0: 0.0,
m2_1: 0.0,
}
}
fn push(&mut self, class: u8, value: f64) {
if class == 0 {
self.n0 += 1.0;
let delta = value - self.mean0;
self.mean0 += delta / self.n0;
let delta2 = value - self.mean0;
self.m2_0 += delta * delta2;
} else {
self.n1 += 1.0;
let delta = value - self.mean1;
self.mean1 += delta / self.n1;
let delta2 = value - self.mean1;
self.m2_1 += delta * delta2;
}
}
fn t_statistic(&self) -> Option<f64> {
if self.n0 < 2.0 || self.n1 < 2.0 {
return None;
}
let var0 = self.m2_0 / (self.n0 - 1.0);
let var1 = self.m2_1 / (self.n1 - 1.0);
let denom = (var0 / self.n0 + var1 / self.n1).sqrt();
if denom < 1e-15 {
return None;
}
Some((self.mean0 - self.mean1) / denom)
}
}
struct Xorshift64(u64);
impl Xorshift64 {
fn new(seed: u64) -> Self {
Self(seed)
}
fn next(&mut self) -> u64 {
let mut x = self.0;
x ^= x << 13;
x ^= x >> 7;
x ^= x << 17;
self.0 = x;
x
}
fn next_byte(&mut self) -> u8 {
self.next() as u8
}
fn fill(&mut self, buf: &mut [u8]) {
for b in buf.iter_mut() {
*b = self.next_byte();
}
}
}
#[inline(never)]
fn measure_ns<F: FnMut()>(mut f: F) -> f64 {
let start = Instant::now();
f();
start.elapsed().as_nanos() as f64
}
fn percentile(times: &mut [f64], pct: f64) -> f64 {
times.sort_by(|a, b| a.partial_cmp(b).unwrap());
let idx = ((times.len() as f64) * pct) as usize;
times[idx.min(times.len() - 1)]
}
fn test_mac_verify_ct() -> (f64, &'static str) {
let key = b"dudect-test-key-0123456789abcdef";
let message = b"the quick brown fox jumps over the lazy dog";
let correct_tag = kk_mac(key, message);
let mut wrong_tag = correct_tag;
for b in wrong_tag.iter_mut() {
*b ^= 0xFF;
}
let mut rng = Xorshift64::new(0xDEAD_BEEF_CAFE_BABE);
let mut tester = WelchT::new();
let mut classes = vec![0u8; SAMPLES * 2];
for i in 0..SAMPLES {
classes[i * 2] = 0;
classes[i * 2 + 1] = 1;
}
for i in (1..classes.len()).rev() {
let j = (rng.next() as usize) % (i + 1);
classes.swap(i, j);
}
let mut timings: Vec<(u8, f64)> = Vec::with_capacity(classes.len());
for &class in &classes {
let t = if class == 0 {
measure_ns(|| {
let _ = kk_mac_verify(key, message, &correct_tag);
})
} else {
measure_ns(|| {
let _ = kk_mac_verify(key, message, &wrong_tag);
})
};
timings.push((class, t));
}
let mut all_times: Vec<f64> = timings.iter().map(|(_, t)| *t).collect();
let crop_threshold = percentile(&mut all_times, 0.95);
for &(class, t) in &timings {
if t <= crop_threshold {
tester.push(class, t);
}
}
let t = tester.t_statistic().unwrap_or(0.0);
(t, "kk_mac_verify (correct vs wrong tag)")
}
fn test_mac_key_independence() -> (f64, &'static str) {
let fixed_key = [0u8; 32];
let message = b"the quick brown fox jumps over the lazy dog";
let mut rng = Xorshift64::new(0x1234_5678_9ABC_DEF0);
let mut classes = vec![0u8; SAMPLES * 2];
for i in 0..SAMPLES {
classes[i * 2] = 0;
classes[i * 2 + 1] = 1;
}
for i in (1..classes.len()).rev() {
let j = (rng.next() as usize) % (i + 1);
classes.swap(i, j);
}
let mut random_keys: Vec<[u8; 32]> = Vec::with_capacity(SAMPLES);
for _ in 0..SAMPLES {
let mut k = [0u8; 32];
rng.fill(&mut k);
random_keys.push(k);
}
let mut tester = WelchT::new();
let mut rand_idx = 0usize;
let mut timings: Vec<(u8, f64)> = Vec::with_capacity(classes.len());
for &class in &classes {
let t = if class == 0 {
measure_ns(|| {
let _ = kk_mac(&fixed_key, message);
})
} else {
let k = &random_keys[rand_idx % random_keys.len()];
rand_idx += 1;
measure_ns(|| {
let _ = kk_mac(k, message);
})
};
timings.push((class, t));
}
let mut all_times: Vec<f64> = timings.iter().map(|(_, t)| *t).collect();
let crop_threshold = percentile(&mut all_times, 0.95);
for &(class, t) in &timings {
if t <= crop_threshold {
tester.push(class, t);
}
}
let t = tester.t_statistic().unwrap_or(0.0);
(t, "kk_mac (fixed vs random key)")
}
fn test_mac_message_independence() -> (f64, &'static str) {
let key = b"dudect-test-key-0123456789abcdef";
let msg_zero = [0u8; 64];
let msg_ones = [0xFFu8; 64];
let mut rng = Xorshift64::new(0xAAAA_BBBB_CCCC_DDDD);
let mut classes = vec![0u8; SAMPLES * 2];
for i in 0..SAMPLES {
classes[i * 2] = 0;
classes[i * 2 + 1] = 1;
}
for i in (1..classes.len()).rev() {
let j = (rng.next() as usize) % (i + 1);
classes.swap(i, j);
}
let mut tester = WelchT::new();
let mut timings: Vec<(u8, f64)> = Vec::with_capacity(classes.len());
for &class in &classes {
let t = if class == 0 {
measure_ns(|| {
let _ = kk_mac(key, &msg_zero);
})
} else {
measure_ns(|| {
let _ = kk_mac(key, &msg_ones);
})
};
timings.push((class, t));
}
let mut all_times: Vec<f64> = timings.iter().map(|(_, t)| *t).collect();
let crop_threshold = percentile(&mut all_times, 0.95);
for &(class, t) in &timings {
if t <= crop_threshold {
tester.push(class, t);
}
}
let t = tester.t_statistic().unwrap_or(0.0);
(t, "kk_mac (zero vs 0xFF message)")
}
fn test_mac_verify_position() -> (f64, &'static str) {
let key = b"dudect-test-key-0123456789abcdef";
let message = b"the quick brown fox jumps over the lazy dog";
let correct_tag = kk_mac(key, message);
let mut wrong_first = correct_tag;
wrong_first[0] ^= 1;
let mut wrong_last = correct_tag;
wrong_last[31] ^= 1;
let mut rng = Xorshift64::new(0xFEED_FACE_DEAD_C0DE);
let mut classes = vec![0u8; SAMPLES * 2];
for i in 0..SAMPLES {
classes[i * 2] = 0;
classes[i * 2 + 1] = 1;
}
for i in (1..classes.len()).rev() {
let j = (rng.next() as usize) % (i + 1);
classes.swap(i, j);
}
let mut tester = WelchT::new();
let mut timings: Vec<(u8, f64)> = Vec::with_capacity(classes.len());
for &class in &classes {
let t = if class == 0 {
measure_ns(|| {
let _ = kk_mac_verify(key, message, &wrong_first);
})
} else {
measure_ns(|| {
let _ = kk_mac_verify(key, message, &wrong_last);
})
};
timings.push((class, t));
}
let mut all_times: Vec<f64> = timings.iter().map(|(_, t)| *t).collect();
let crop_threshold = percentile(&mut all_times, 0.95);
for &(class, t) in &timings {
if t <= crop_threshold {
tester.push(class, t);
}
}
let t = tester.t_statistic().unwrap_or(0.0);
(t, "kk_mac_verify (first-byte vs last-byte wrong)")
}
fn test_permute_data_independence() -> (f64, &'static str) {
let input_zero = [0u8; 152]; let input_ones = [0xFFu8; 152];
let mut rng = Xorshift64::new(0x0BAD_F00D_1337_BEEF);
let mut classes = vec![0u8; SAMPLES * 2];
for i in 0..SAMPLES {
classes[i * 2] = 0;
classes[i * 2 + 1] = 1;
}
for i in (1..classes.len()).rev() {
let j = (rng.next() as usize) % (i + 1);
classes.swap(i, j);
}
let mut tester = WelchT::new();
let mut timings: Vec<(u8, f64)> = Vec::with_capacity(classes.len());
for &class in &classes {
let t = if class == 0 {
measure_ns(|| {
let _ = kk_hash(&input_zero);
})
} else {
measure_ns(|| {
let _ = kk_hash(&input_ones);
})
};
timings.push((class, t));
}
let mut all_times: Vec<f64> = timings.iter().map(|(_, t)| *t).collect();
let crop_threshold = percentile(&mut all_times, 0.95);
for &(class, t) in &timings {
if t <= crop_threshold {
tester.push(class, t);
}
}
let t = tester.t_statistic().unwrap_or(0.0);
(t, "kk_hash/permute (zero vs 0xFF state)")
}
fn main() {
println!("╔══════════════════════════════════════════════════════════════════╗");
println!("║ KK-Crypto Constant-Time Verification (dudect methodology) ║");
println!(
"║ Samples per class: {:>7} ║",
SAMPLES
);
println!(
"║ Threshold: |t| < {:.1} → no timing leak detected ║",
THRESHOLD
);
println!("╚══════════════════════════════════════════════════════════════════╝");
println!();
let tests: Vec<fn() -> (f64, &'static str)> = vec![
test_mac_verify_ct,
test_mac_key_independence,
test_mac_message_independence,
test_mac_verify_position,
test_permute_data_independence,
];
let mut all_pass = true;
for (i, test_fn) in tests.iter().enumerate() {
let (t, name) = test_fn();
let abs_t = t.abs();
let status = if abs_t < THRESHOLD { "PASS" } else { "FAIL" };
let marker = if abs_t < THRESHOLD { " " } else { "!!" };
println!(
" Test {}: {} |t| = {:.2} {} {}",
i + 1,
status,
abs_t,
marker,
name,
);
if abs_t >= THRESHOLD {
all_pass = false;
}
}
println!();
if all_pass {
println!(" Result: ALL TESTS PASSED, no timing leaks detected.");
println!(
" (with {} samples per class, threshold |t| < {:.1})",
SAMPLES, THRESHOLD
);
} else {
println!(" Result: TIMING LEAK DETECTED in one or more tests.");
println!(" Functions marked !! may have data-dependent timing.");
}
println!();
println!(" Note: dudect is statistical. Re-run to confirm any failures.");
println!(" False positives are possible (~1/million at threshold 4.5).");
}