Skip to content

Instantly share code, notes, and snippets.

@Gowee
Last active June 8, 2026 04:41
Show Gist options
  • Select an option

  • Save Gowee/2582990fcedbfd58e0e8ca6a6315ae02 to your computer and use it in GitHub Desktop.

Select an option

Save Gowee/2582990fcedbfd58e0e8ca6a6315ae02 to your computer and use it in GitHub Desktop.
// ./clatter/examples/hybrid_wire_format.rs
//! Wire format analysis for hybrid (X25519 + ML-KEM) Noise handshake patterns.
//!
//! This example captures the raw bytes of each handshake message to show
//! the on-wire field layout for `hybridNNpsk0`, demonstrating that the
//! responder's ML-KEM public key is omitted when the KEM ephemeral exchange
//! is already complete.
//!
//! Run with:
//! cargo run --example hybrid_wire_format
use clatter::bytearray::ByteArray;
use clatter::crypto::cipher::ChaChaPoly;
use clatter::crypto::dh::X25519;
use clatter::crypto::hash::Sha256;
use clatter::crypto::kem::rust_crypto_ml_kem::{MlKem768, MlKem1024};
use clatter::handshakepattern::noise_hybrid_nn_psk0;
use clatter::traits::{Cipher, Dh, Kem, Handshaker};
use clatter::{HybridHandshake, HybridHandshakeParams};
fn analyze<EKEM: Kem, SKEM: Kem>(label: &str) {
let psk = [0xAB_u8; 32];
let pattern = noise_hybrid_nn_psk0();
let alice_params = HybridHandshakeParams::<X25519, EKEM, SKEM>::new(pattern.clone(), true)
.with_prologue(b"test-prologue");
let mut alice =
HybridHandshake::<X25519, EKEM, SKEM, ChaChaPoly, Sha256>::new(alice_params).unwrap();
alice.push_psk(&psk);
let bob_params = HybridHandshakeParams::<X25519, EKEM, SKEM>::new(pattern, false)
.with_prologue(b"test-prologue");
let mut bob =
HybridHandshake::<X25519, EKEM, SKEM, ChaChaPoly, Sha256>::new(bob_params).unwrap();
bob.push_psk(&psk);
// Message 1: initiator -> responder
let overhead1 = alice.get_next_message_overhead().unwrap();
let mut msg1 = vec![0u8; 8192];
let n1 = alice.write_message(&[], &mut msg1).unwrap();
msg1.truncate(n1);
assert_eq!(n1, overhead1);
// Message 2: responder -> initiator
let mut payload_buf = [0u8; 256];
let p1 = bob.read_message(&msg1, &mut payload_buf).unwrap();
assert_eq!(p1, 0);
let overhead2 = bob.get_next_message_overhead().unwrap();
let mut msg2 = vec![0u8; 8192];
let n2 = bob.write_message(&[], &mut msg2).unwrap();
msg2.truncate(n2);
assert_eq!(n2, overhead2);
let p2 = alice.read_message(&msg2, &mut payload_buf).unwrap();
assert_eq!(p2, 0);
assert!(alice.is_finished() && bob.is_finished());
// Verify transport works
let mut alice = alice.finalize().unwrap();
let mut bob = bob.finalize().unwrap();
let mut send = vec![0u8; 4096];
let mut recv = [0u8; 4096];
let n = alice.send(b"hello", &mut send).unwrap();
let m = bob.receive(&send[..n], &mut recv).unwrap();
assert_eq!(&recv[..m], b"hello");
// Print wire layout
let dh_pub = <X25519 as Dh>::PubKey::len();
let kem_pub = <EKEM as Kem>::PubKey::len();
let kem_ct = <EKEM as Kem>::Ct::len();
let tag = <ChaChaPoly as Cipher>::tag_len();
println!("=== {label} ===");
println!("Pattern: -> psk, e / <- ekem, e, ee");
println!("Sizes: dh_pub={dh_pub} kem_pub={kem_pub} kem_ct={kem_ct} tag={tag}");
println!();
println!(
"Message 1 ({} bytes) — initiator -> responder:",
msg1.len()
);
println!(
" [0..{dh_pub}) X25519 ephemeral pubkey {dh_pub} B"
);
println!(
" [{dh_pub}..{}) ML-KEM ephemeral pubkey {kem_pub} B",
dh_pub + kem_pub
);
println!(
" [{}..{}) AEAD tag {tag} B",
dh_pub + kem_pub,
msg1.len()
);
println!();
println!(
"Message 2 ({} bytes) — responder -> initiator:",
msg2.len()
);
println!(
" [0..{kem_ct}) ML-KEM ciphertext {kem_ct} B"
);
println!(
" [{kem_ct}..{}) X25519 ephemeral pubkey {dh_pub} B",
kem_ct + dh_pub
);
println!(
" [{}..{}) AEAD tag {tag} B",
kem_ct + dh_pub,
msg2.len()
);
println!();
let saved = kem_pub;
let total = msg1.len() + msg2.len();
println!(
"Total on wire: {total} bytes (responder KEM pubkey omitted, {saved} bytes saved)"
);
println!();
}
fn main() {
analyze::<MlKem768, MlKem768>("ML-KEM-768");
analyze::<MlKem1024, MlKem1024>("ML-KEM-1024");
}
// ./clatter/examples/hybrid_wire_format.rs
//! Wire format analysis for hybrid (X25519 + ML-KEM) Noise handshake patterns.
//!
//! This example captures the raw bytes of each handshake message to show
//! the on-wire field layout for `hybridNNpsk0`, demonstrating that the
//! responder's ML-KEM public key is sent but never used.
//!
//! Run with:
//! cargo run --example hybrid_wire_format
use clatter::bytearray::ByteArray;
use clatter::crypto::cipher::ChaChaPoly;
use clatter::crypto::dh::X25519;
use clatter::crypto::hash::Sha256;
use clatter::crypto::kem::rust_crypto_ml_kem::{MlKem768, MlKem1024};
use clatter::handshakepattern::noise_hybrid_nn_psk0;
use clatter::traits::{Cipher, Dh, Kem, Handshaker};
use clatter::{HybridHandshake, HybridHandshakeParams};
fn analyze<EKEM: Kem, SKEM: Kem>(label: &str) {
let psk = [0xAB_u8; 32];
let pattern = noise_hybrid_nn_psk0();
let alice_params = HybridHandshakeParams::<X25519, EKEM, SKEM>::new(pattern.clone(), true)
.with_prologue(b"test-prologue");
let mut alice =
HybridHandshake::<X25519, EKEM, SKEM, ChaChaPoly, Sha256>::new(alice_params).unwrap();
alice.push_psk(&psk);
let bob_params = HybridHandshakeParams::<X25519, EKEM, SKEM>::new(pattern, false)
.with_prologue(b"test-prologue");
let mut bob =
HybridHandshake::<X25519, EKEM, SKEM, ChaChaPoly, Sha256>::new(bob_params).unwrap();
bob.push_psk(&psk);
// Message 1: initiator -> responder
let overhead1 = alice.get_next_message_overhead().unwrap();
let mut msg1 = vec![0u8; 8192];
let n1 = alice.write_message(&[], &mut msg1).unwrap();
msg1.truncate(n1);
assert_eq!(n1, overhead1);
// Message 2: responder -> initiator
let mut payload_buf = [0u8; 256];
let p1 = bob.read_message(&msg1, &mut payload_buf).unwrap();
assert_eq!(p1, 0);
let overhead2 = bob.get_next_message_overhead().unwrap();
let mut msg2 = vec![0u8; 8192];
let n2 = bob.write_message(&[], &mut msg2).unwrap();
msg2.truncate(n2);
assert_eq!(n2, overhead2);
let p2 = alice.read_message(&msg2, &mut payload_buf).unwrap();
assert_eq!(p2, 0);
assert!(alice.is_finished() && bob.is_finished());
// Verify transport works
let mut alice = alice.finalize().unwrap();
let mut bob = bob.finalize().unwrap();
let mut send = vec![0u8; 4096];
let mut recv = [0u8; 4096];
let n = alice.send(b"hello", &mut send).unwrap();
let m = bob.receive(&send[..n], &mut recv).unwrap();
assert_eq!(&recv[..m], b"hello");
// Print wire layout
let dh_pub = <X25519 as Dh>::PubKey::len();
let kem_pub = <EKEM as Kem>::PubKey::len();
let kem_ct = <EKEM as Kem>::Ct::len();
let tag = <ChaChaPoly as Cipher>::tag_len();
println!("=== {label} ===");
println!("Pattern: -> psk, e / <- ekem, e, ee");
println!("Sizes: dh_pub={dh_pub} kem_pub={kem_pub} kem_ct={kem_ct} tag={tag}");
println!();
println!(
"Message 1 ({} bytes) — initiator -> responder:",
msg1.len()
);
println!(
" [0..{dh_pub}) X25519 ephemeral pubkey {dh_pub} B"
);
println!(
" [{dh_pub}..{}) ML-KEM ephemeral pubkey {kem_pub} B",
dh_pub + kem_pub
);
println!(
" [{}..{}) AEAD tag {tag} B",
dh_pub + kem_pub,
msg1.len()
);
println!();
println!(
"Message 2 ({} bytes) — responder -> initiator:",
msg2.len()
);
println!(
" [0..{kem_ct}) ML-KEM ciphertext {kem_ct} B"
);
println!(
" [{kem_ct}..{}) X25519 ephemeral pubkey {dh_pub} B",
kem_ct + dh_pub
);
println!(
" [{}..{}) ML-KEM ephemeral pubkey {kem_pub} B <-- UNUSED",
kem_ct + dh_pub,
kem_ct + dh_pub + kem_pub
);
println!(
" [{}..{}) AEAD tag {tag} B",
kem_ct + dh_pub + kem_pub,
msg2.len()
);
println!();
let dead_weight = kem_pub;
let total = msg1.len() + msg2.len();
println!(
"Total on wire: {total} bytes ({dead_weight} bytes are unused responder KEM pubkey)"
);
println!();
}
fn main() {
analyze::<MlKem768, MlKem768>("ML-KEM-768");
analyze::<MlKem1024, MlKem1024>("ML-KEM-1024");
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment