Last active
June 8, 2026 04:41
-
-
Save Gowee/2582990fcedbfd58e0e8ca6a6315ae02 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ./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"); | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ./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