From aea48bd30ab5bd3f69799ea4449e17f19fa9c49a Mon Sep 17 00:00:00 2001 From: Georgio Nicolas Date: Fri, 13 Feb 2026 06:33:30 +0100 Subject: [PATCH] Add test --- Cargo.lock | 31 +++- Cargo.toml | 1 + common/nym-lp-sandbox/Cargo.toml | 42 +++++ common/nym-lp-sandbox/src/lib.rs | 262 +++++++++++++++++++++++++++++++ 4 files changed, 335 insertions(+), 1 deletion(-) create mode 100644 common/nym-lp-sandbox/Cargo.toml create mode 100644 common/nym-lp-sandbox/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index d72ea6d2e7..b86e409cb1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6682,7 +6682,6 @@ name = "nym-kkt" version = "0.1.0" dependencies = [ "anyhow", - "blake3", "criterion", "libcrux-chacha20poly1305", "libcrux-ecdh", @@ -6791,6 +6790,36 @@ dependencies = [ name = "nym-lp-common" version = "0.1.0" +[[package]] +name = "nym-lp-sandbox" +version = "0.1.0" +dependencies = [ + "bs58", + "bytes", + "chacha20poly1305", + "criterion", + "dashmap", + "libcrux-kem", + "libcrux-psq", + "num_enum", + "nym-crypto", + "nym-kkt", + "nym-kkt-ciphersuite", + "nym-lp-common", + "nym-test-utils", + "parking_lot", + "rand 0.8.5", + "rand 0.9.2", + "rand_chacha 0.3.1", + "serde", + "sha2 0.10.9", + "snow", + "thiserror 2.0.18", + "tls_codec", + "tracing", + "zeroize", +] + [[package]] name = "nym-lp-transport" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index b67f1da34f..581a37ea15 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -175,6 +175,7 @@ members = [ "wasm/zknym-lib", # "nym-gateway-probe", "integration-tests", "common/nym-lp-transport", "common/nym-kkt-ciphersuite", + "common/nym-lp-sandbox" ] default-members = [ diff --git a/common/nym-lp-sandbox/Cargo.toml b/common/nym-lp-sandbox/Cargo.toml new file mode 100644 index 0000000000..eb04b78f51 --- /dev/null +++ b/common/nym-lp-sandbox/Cargo.toml @@ -0,0 +1,42 @@ +[package] +name = "nym-lp-sandbox" +version = "0.1.0" +edition = { workspace = true } +license = { workspace = true } +publish = false + +[dependencies] +thiserror = { workspace = true } +parking_lot = { workspace = true } +snow = { workspace = true } +bs58 = { workspace = true } +serde = { workspace = true } +bytes = { workspace = true } +dashmap = { workspace = true } +sha2 = { workspace = true } +tracing = { workspace = true } +rand = { workspace = true } +rand09 = { workspace = true } + + +nym-crypto = { path = "../crypto", features = ["hashing", "asymmetric"] } +nym-kkt = { path = "../nym-kkt" } +nym-lp-common = { path = "../nym-lp-common" } + +nym-kkt-ciphersuite = { path ="../nym-kkt-ciphersuite" } + +# libcrux dependencies for PSQ (Post-Quantum PSK derivation) +libcrux-psq = { workspace = true, features = ["test-utils"] } +libcrux-kem = { workspace = true } +tls_codec = { workspace = true } +num_enum = { workspace = true } +chacha20poly1305 = { workspace = true } +zeroize = { workspace = true, features = ["zeroize_derive"] } + +[dev-dependencies] +criterion = { version = "0.5", features = ["html_reports"] } +rand_chacha = "0.3" +nym-crypto = { path = "../crypto", features = ["rand"] } +nym-test-utils = { workspace = true } + + diff --git a/common/nym-lp-sandbox/src/lib.rs b/common/nym-lp-sandbox/src/lib.rs new file mode 100644 index 0000000000..888e01653d --- /dev/null +++ b/common/nym-lp-sandbox/src/lib.rs @@ -0,0 +1,262 @@ +#[cfg(test)] +mod tests { + + use libcrux_psq::{ + Channel, IntoSession, + handshake::{ + builders::{CiphersuiteBuilder, PrincipalBuilder}, + ciphersuites::CiphersuiteName, + types::{Authenticator, PQEncapsulationKey}, + }, + session::{Session, SessionBinding}, + }; + use nym_kkt::{ + initiator::KKTInitiator, + key_utils::{ + generate_keypair_mceliece, generate_keypair_mlkem, generate_keypair_x25519, + hash_encapsulation_key, + }, + responder::KKTResponder, + }; + use nym_kkt_ciphersuite::{Ciphersuite, HashFunction, HashLength, KEM, SignatureScheme}; + + #[test] + fn test_e2e_client_node() { + let mut rng = rand09::rng(); + + // we should add these as consts + let aad_initiator_outer = b"Test Data I Outer"; + let aad_initiator_inner = b"Test Data I Inner"; + let aad_responder = b"Test Data R"; + let ctx = b"Test Context"; + + // generate responder x25519 keys + let responder_x25519_keypair = generate_keypair_x25519(&mut rng); + let hash_function = HashFunction::Blake3; + // generate kem public keys + + let responder_mlkem_keypair = generate_keypair_mlkem(&mut rng); + let responder_mceliece_keypair = generate_keypair_mceliece(&mut rng); + + let r_dir_hash_mlkem = hash_encapsulation_key( + // &ciphersuite.hash_function(), + &hash_function, + // ciphersuite.hash_len(), + HashLength::Default.value(), + responder_mlkem_keypair.1.as_slice().as_slice(), + ); + + let r_dir_hash_mceliece = hash_encapsulation_key( + // &ciphersuite.hash_function(), + &hash_function, + // ciphersuite.hash_len(), + HashLength::Default.value(), + responder_mceliece_keypair.1.as_ref(), + ); + + let kkt_responder = KKTResponder::new( + &responder_x25519_keypair, + Some(&responder_mlkem_keypair.1), + Some(&responder_mceliece_keypair.1), + &[ + HashFunction::Blake3, + HashFunction::SHA256, + HashFunction::Shake128, + HashFunction::Shake256, + ], + &[1], + &[SignatureScheme::Ed25519], + ) + .unwrap(); + + // OneWay - MlKem + let psq_ciphersuite = CiphersuiteName::X25519_MLKEM768_X25519_AESGCM128_HKDFSHA256; + + let responder_ciphersuite = CiphersuiteBuilder::new(psq_ciphersuite) + .longterm_x25519_keys(&responder_x25519_keypair) + .longterm_mlkem_encapsulation_key(&responder_mlkem_keypair.1) + .longterm_mlkem_decapsulation_key(&responder_mlkem_keypair.0) + .build_responder_ciphersuite() + .unwrap(); + + let mut responder = PrincipalBuilder::new(rand09::rng()) + .context(ctx) + .outer_aad(aad_responder) + .recent_keys_upper_bound(30) + .build_responder(responder_ciphersuite) + .unwrap(); + + let ciphersuite = Ciphersuite::resolve_ciphersuite( + KEM::MlKem768, + hash_function, + SignatureScheme::Ed25519, + None, + ) + .unwrap(); + + let (mut initiator, request_bytes) = KKTInitiator::generate_one_way_request( + &mut rng, + &ciphersuite, + &responder_x25519_keypair.pk, + &r_dir_hash_mlkem, + 1u8, + ) + .unwrap(); + + let (response_bytes, _) = kkt_responder.process_request(&request_bytes).unwrap(); + + let (i_obtained_key, _) = initiator.process_response(&response_bytes).unwrap(); + + assert_eq!( + i_obtained_key, + responder_mlkem_keypair.1.as_slice().as_slice(), + ); + + let mlkem_key = + libcrux_kem::MlKem768PublicKey::try_from(i_obtained_key.as_slice()).unwrap(); + + let initiator_psq_keys = generate_keypair_x25519(&mut rng); + let initiator_cbuilder = CiphersuiteBuilder::new(psq_ciphersuite) + .longterm_x25519_keys(&initiator_psq_keys) + .peer_longterm_x25519_pk(&responder_x25519_keypair.pk) + .peer_longterm_mlkem_pk(&mlkem_key); + + let initiator_ciphersuite = initiator_cbuilder.build_initiator_ciphersuite().unwrap(); + + let mut msg_channel = vec![0u8; 8192]; + let mut payload_buf_responder = vec![0u8; 4096]; + let mut payload_buf_initiator = vec![0u8; 4096]; + + let mut initiator = PrincipalBuilder::new(rand09::rng()) + .outer_aad(aad_initiator_outer) + .inner_aad(aad_initiator_inner) + .context(ctx) + .build_registration_initiator(initiator_ciphersuite) + .unwrap(); + + // Send first message + let registration_payload_initiator = b"Registration_init"; + let len_i = initiator + .write_message(registration_payload_initiator, &mut msg_channel) + .unwrap(); + + // Read first message + let (len_r_deserialized, len_r_payload) = responder + .read_message(&msg_channel, &mut payload_buf_responder) + .unwrap(); + + // We read the same amount of data. + assert_eq!(len_r_deserialized, len_i); + assert_eq!(len_r_payload, registration_payload_initiator.len()); + assert_eq!( + &payload_buf_responder[0..len_r_payload], + registration_payload_initiator + ); + + // Get the authenticator out here, so we can deserialize the session later. + let Some(initiator_authenticator) = responder.initiator_authenticator() else { + panic!("No initiator authenticator found") + }; + + // Respond + let registration_payload_responder = b"Registration_respond"; + let len_r = responder + .write_message(registration_payload_responder, &mut msg_channel) + .unwrap(); + + // Finalize on registration initiator + let (len_i_deserialized, len_i_payload) = initiator + .read_message(&msg_channel, &mut payload_buf_initiator) + .unwrap(); + + // We read the same amount of data. + assert_eq!(len_r, len_i_deserialized); + assert_eq!(registration_payload_responder.len(), len_i_payload); + assert_eq!( + &payload_buf_initiator[0..len_i_payload], + registration_payload_responder + ); + + // Ready for transport mode + assert!(initiator.is_handshake_finished()); + assert!(responder.is_handshake_finished()); + + let i_transport = initiator.into_session().unwrap(); + let r_transport = responder.into_session().unwrap(); + + // test serialization, deserialization + let mut session_storage = vec![0u8; 4096]; + i_transport + .serialize( + &mut session_storage, + SessionBinding { + initiator_authenticator: &Authenticator::Dh(initiator_psq_keys.pk), + responder_ecdh_pk: &responder_x25519_keypair.pk, + responder_pq_pk: Some(PQEncapsulationKey::MlKem(&mlkem_key)), + }, + ) + .unwrap(); + let mut i_transport = Session::deserialize( + &session_storage, + SessionBinding { + initiator_authenticator: &Authenticator::Dh(initiator_psq_keys.pk), + responder_ecdh_pk: &responder_x25519_keypair.pk, + responder_pq_pk: Some(PQEncapsulationKey::MlKem(&mlkem_key)), + }, + ) + .unwrap(); + + r_transport + .serialize( + &mut session_storage, + SessionBinding { + initiator_authenticator: &Authenticator::Dh(initiator_psq_keys.pk), + responder_ecdh_pk: &responder_x25519_keypair.pk, + responder_pq_pk: Some(PQEncapsulationKey::MlKem(&mlkem_key)), + }, + ) + .unwrap(); + let mut r_transport = Session::deserialize( + &session_storage, + SessionBinding { + initiator_authenticator: &Authenticator::Dh(initiator_psq_keys.pk), + responder_ecdh_pk: &responder_x25519_keypair.pk, + responder_pq_pk: Some(PQEncapsulationKey::MlKem(&mlkem_key)), + }, + ) + .unwrap(); + + let mut channel_i = i_transport.transport_channel().unwrap(); + let mut channel_r = r_transport.transport_channel().unwrap(); + + assert_eq!(channel_i.identifier(), channel_r.identifier()); + + let app_data_i = b"Derived session hey".as_slice(); + let app_data_r = b"Derived session ho".as_slice(); + + let len_i = channel_i + .write_message(app_data_i, &mut msg_channel) + .unwrap(); + + let (len_r_deserialized, len_r_payload) = channel_r + .read_message(&msg_channel, &mut payload_buf_responder) + .unwrap(); + + // We read the same amount of data. + assert_eq!(len_r_deserialized, len_i); + assert_eq!(len_r_payload, app_data_i.len()); + assert_eq!(&payload_buf_responder[0..len_r_payload], app_data_i); + + let len_r = channel_r + .write_message(app_data_r, &mut msg_channel) + .unwrap(); + + let (len_i_deserialized, len_i_payload) = channel_i + .read_message(&msg_channel, &mut payload_buf_initiator) + .unwrap(); + + assert_eq!(len_r, len_i_deserialized); + assert_eq!(app_data_r.len(), len_i_payload); + assert_eq!(&payload_buf_initiator[0..len_i_payload], app_data_r); + } +}