mod crypto;
mod network;
use network::{AccessPoint, Station, MLKEM_PK_FRAG_COUNT, FRAG_PAYLOAD_MAX};
use crypto::SESSION_KEY_LEN;
use rand_core::RngCore;
use subtle::ConstantTimeEq;
fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("╔═══════════════════════════════════════════════════════════════╗");
println!("║ WPA-Next Hybrid Post-Quantum Handshake Demo ║");
println!("║ ML-KEM-768 (FIPS 203) + X25519 + HKDF-SHA384 Combiner ║");
println!("╚═══════════════════════════════════════════════════════════════╝\n");
let ap_mac: [u8; 6] = [0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF];
let station_mac: [u8; 6] = [0x11, 0x22, 0x33, 0x44, 0x55, 0x66];
let mut seq_id_bytes = [0u8; 4];
rand_core::OsRng.fill_bytes(&mut seq_id_bytes);
let sequence_id = u32::from_be_bytes(seq_id_bytes);
println!("[Init] Sequence ID for this association: {:#010X}\n", sequence_id);
println!("── Access Point Setup ───────────────────────────────────────────");
let mut ap = AccessPoint::new(ap_mac)?;
let ap_mlkem_pk = ap.mlkem_public_key_bytes();
let ap_x25519_pk = ap.x25519_public_key_bytes().expect("AP X25519 key present");
println!("[AP] ML-KEM-768 public key generated ({} bytes)", ap_mlkem_pk.len());
println!("[AP] X25519 public key generated ({} bytes)\n", ap_x25519_pk.len());
println!("── Station Setup ────────────────────────────────────────────────");
let station = Station::new(station_mac)?;
println!("[Station] Ephemeral X25519 key pair generated\n");
println!("════════════════════════════════════════════════════════════════");
println!(" STAGE 1 — DISCOVERY (FastLinkFrame)");
println!("════════════════════════════════════════════════════════════════");
let fast_link_frame = station.build_fast_link_frame()?;
println!(
"[Station] FastLinkFrame built — X25519 PK: {}...",
hex::encode(&fast_link_frame.x25519_public_key[..8])
);
let cookie = ap.process_fast_link_frame(&fast_link_frame, sequence_id)?;
println!("[AP] Cookie issued: {}...\n", hex::encode(&cookie[..8]));
println!("────────────────────────────────────────────────────────────────");
println!(
"[AP] Broadcasting ML-KEM-768 public key ({} bytes — needs fragmentation)",
ap_mlkem_pk.len()
);
println!("\n════════════════════════════════════════════════════════════════");
println!(" STAGE 2 — QUANTIZATION (FragmentedPQFrame × {})", MLKEM_PK_FRAG_COUNT);
println!("════════════════════════════════════════════════════════════════\n");
let station_x25519_pk = station.x25519_public_key_bytes()?;
let (pq_frames, station_pq_ss) =
station.build_pq_fragments(&ap_mlkem_pk, sequence_id, &cookie)?;
println!();
for frame in &pq_frames {
println!(
"[Station] → Fragment [{}/{}] seq={:#010X} payload={} bytes cookie_present={}",
frame.header.frag_index + 1,
frame.header.frag_total,
frame.header.sequence_id,
frame.header.payload_len,
frame.header.frag_index == 0,
);
}
println!();
let mut ap_session_key = None;
for frame in &pq_frames {
ap_session_key = ap.process_fragment(frame, &station_mac, &station_x25519_pk)?;
}
let ap_key = ap_session_key
.expect("[AP] Should have derived session key after the final fragment");
println!();
let station_key = station.complete_handshake(&ap_x25519_pk, station_pq_ss)?;
println!("\n════════════════════════════════════════════════════════════════");
println!(" VERIFICATION");
println!("════════════════════════════════════════════════════════════════\n");
let keys_match: bool = ap_key.0.ct_eq(&station_key.0).into();
if keys_match {
println!("✅ Session keys MATCH — handshake successful!\n");
println!(" Session key (hex): {}", hex::encode(&ap_key.0));
} else {
eprintln!("❌ Session keys DO NOT MATCH — protocol error!");
std::process::exit(1);
}
println!("\n════════════════════════════════════════════════════════════════");
println!(" PROTOCOL SUMMARY");
println!("════════════════════════════════════════════════════════════════");
println!(" Classical layer : X25519 ECDH (ring crate)");
println!(" PQ layer : ML-KEM-768 FIPS 203 (libcrux-ml-kem)");
println!(" Hybrid combiner : HKDF-SHA384 (IKM = classical_ss ∥ pq_ss)");
println!(" DoS mitigation : HMAC-SHA384 cookie (verified before state alloc)");
println!(
" Fragmentation : {} × ≤{} byte L2 frames per ML-KEM message",
MLKEM_PK_FRAG_COUNT, FRAG_PAYLOAD_MAX
);
println!(" Memory safety : All secrets wrapped in zeroize::ZeroizeOnDrop");
println!(" Timing safety : Cookie & key comparison via subtle::ConstantTimeEq");
println!("────────────────────────────────────────────────────────────────");
println!(" Security level : ~128-bit classical, ~180-bit post-quantum");
println!(" Session key size : {} bits", SESSION_KEY_LEN * 8);
println!("════════════════════════════════════════════════════════════════\n");
Ok(())
}