From f9727ce0a0464f1484248bec7515701b6f4c5026 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Thu, 26 Feb 2026 11:40:17 +0000 Subject: [PATCH] clippy and formatting issues --- Cargo.lock | 5 +- .../mixnet/query/query_all_gateways.rs | 2 +- .../mixnet/query/query_all_mixnodes.rs | 2 +- common/nym-kkt/Cargo.toml | 5 - common/nym-kkt/benches/benches.rs | 413 ------------------ common/nym-kkt/src/lib.rs | 8 +- common/nym-kkt/src/rekey.rs | 47 +- common/nym-lp/src/lib.rs | 2 +- common/nym-lp/src/peer.rs | 10 +- common/registration/src/lib.rs | 3 +- gateway/src/node/lp_listener/data_handler.rs | 2 +- gateway/src/node/lp_listener/error.rs | 7 +- gateway/src/node/lp_listener/handler.rs | 380 +++------------- integration-tests/src/lp_registration.rs | 338 +++++++------- nym-api/nym-api-requests/Cargo.toml | 1 - nym-api/nym-api-requests/src/ecash/models.rs | 10 +- .../src/models/described/type_translation.rs | 46 +- .../src/models/described/v2.rs | 52 ++- nym-gateway-probe/Cargo.toml | 1 + nym-gateway-probe/src/common/helpers.rs | 10 - nym-gateway-probe/src/common/nodes.rs | 70 +-- nym-gateway-probe/src/common/probe_tests.rs | 64 +-- nym-node/Cargo.toml | 1 + nym-node/nym-node-requests/src/api/mod.rs | 7 +- nym-node/src/node/key_rotation/key.rs | 7 +- tools/nym-lp-client/Cargo.toml | 1 + tools/nym-lp-client/src/client.rs | 142 +++--- tools/nym-lp-client/src/topology.rs | 10 +- 28 files changed, 535 insertions(+), 1111 deletions(-) delete mode 100644 common/nym-kkt/benches/benches.rs diff --git a/Cargo.lock b/Cargo.lock index cd3bf09db8..032cf8372b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5454,7 +5454,6 @@ dependencies = [ "nym-serde-helpers", "nym-test-utils", "nym-ticketbooks-merkle", - "rand_chacha 0.3.1", "schemars 0.8.22", "serde", "serde_json", @@ -6521,6 +6520,7 @@ dependencies = [ "nym-validator-client", "pnet_packet", "rand 0.8.5", + "rand 0.9.2", "reqwest 0.13.1", "serde", "serde_json", @@ -6825,7 +6825,6 @@ name = "nym-kkt" version = "0.1.0" dependencies = [ "anyhow", - "criterion", "libcrux-chacha20poly1305", "libcrux-ecdh", "libcrux-kem", @@ -6923,6 +6922,7 @@ dependencies = [ "nym-topology", "nym-validator-client", "rand 0.8.5", + "rand 0.9.2", "rand_chacha 0.3.1", "serde", "serde_json", @@ -7216,6 +7216,7 @@ dependencies = [ "nym-sphinx-types", "nym-statistics-common", "nym-task", + "nym-test-utils", "nym-topology", "nym-types", "nym-validator-client", diff --git a/common/commands/src/validator/mixnet/query/query_all_gateways.rs b/common/commands/src/validator/mixnet/query/query_all_gateways.rs index f03fb61d28..516a533315 100644 --- a/common/commands/src/validator/mixnet/query/query_all_gateways.rs +++ b/common/commands/src/validator/mixnet/query/query_all_gateways.rs @@ -14,7 +14,7 @@ pub struct Args { } pub async fn query(args: Args, client: &QueryClientWithNyxd) { - match client.get_all_cached_described_nodes().await { + match client.get_all_cached_described_nodes_v2().await { Ok(res) => match args.identity_key { Some(identity_key) => { let node = res.iter().find(|node| { diff --git a/common/commands/src/validator/mixnet/query/query_all_mixnodes.rs b/common/commands/src/validator/mixnet/query/query_all_mixnodes.rs index ef5b4a9548..b1a81780b9 100644 --- a/common/commands/src/validator/mixnet/query/query_all_mixnodes.rs +++ b/common/commands/src/validator/mixnet/query/query_all_mixnodes.rs @@ -14,7 +14,7 @@ pub struct Args { } pub async fn query(args: Args, client: &QueryClientWithNyxd) { - match client.get_all_cached_described_nodes().await { + match client.get_all_cached_described_nodes_v2().await { Ok(res) => match args.identity_key { Some(identity_key) => { let node = res.iter().find(|node| { diff --git a/common/nym-kkt/Cargo.toml b/common/nym-kkt/Cargo.toml index 6ed830dad0..207b1f00ed 100644 --- a/common/nym-kkt/Cargo.toml +++ b/common/nym-kkt/Cargo.toml @@ -30,12 +30,7 @@ libcrux-ml-kem = { workspace = true } [dev-dependencies] rand_chacha = "0.9.0" anyhow = { workspace = true } -criterion = { workspace = true } -[[bench]] -name = "benches" -harness = false - [lints] workspace = true diff --git a/common/nym-kkt/benches/benches.rs b/common/nym-kkt/benches/benches.rs deleted file mode 100644 index 16820b192b..0000000000 --- a/common/nym-kkt/benches/benches.rs +++ /dev/null @@ -1,413 +0,0 @@ -// Copyright 2025 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -// fine in benchmarking code -#![allow(clippy::expect_used)] -#![allow(clippy::unwrap_used)] - -use criterion::{Criterion, criterion_group, criterion_main}; - -use nym_kkt::{ - ciphersuite::{Ciphersuite, EncapsulationKey, HashFunction, KEM, SignatureScheme}, - context::KKTMode, - frame::KKTFrame, - key_utils::{ - generate_keypair_libcrux, generate_keypair_mceliece, generate_keypair_mlkem, - hash_encapsulation_key, - }, - session::{ - initiator_ingest_response, initiator_process, responder_ingest_message, responder_process, - }, -}; -use rand09::prelude::*; - -pub fn gen_mlkem768_keypair(c: &mut Criterion) { - c.bench_function("Generate MlKem768 Keypair", |b| { - b.iter(|| { - libcrux_kem::key_gen(libcrux_kem::Algorithm::MlKem768, &mut rand09::rng()).unwrap() - }); - }); -} - -pub fn kkt_benchmark(c: &mut Criterion) { - let mut rng = rand09::rng(); - - let mut secret_initiator: [u8; 32] = [0u8; 32]; - rng.fill_bytes(&mut secret_initiator); - - let mut secret_responder: [u8; 32] = [0u8; 32]; - rng.fill_bytes(&mut secret_responder); - - for kem in [KEM::MlKem768, KEM::XWing, KEM::X25519, KEM::McEliece] { - for hash_function in [ - HashFunction::Blake3, - HashFunction::SHA256, - HashFunction::Shake128, - HashFunction::Shake256, - ] { - let ciphersuite = Ciphersuite::resolve_ciphersuite( - kem, - hash_function, - SignatureScheme::Ed25519, - None, - ) - .unwrap(); - - // generate kem public keys - - let (responder_kem_public_key, initiator_kem_public_key) = match kem { - KEM::MlKem768 => ( - EncapsulationKey::MlKem768(generate_keypair_mlkem(&mut rng).1), - EncapsulationKey::MlKem768(generate_keypair_mlkem(&mut rng).1), - ), - KEM::XWing => ( - EncapsulationKey::XWing(generate_keypair_libcrux(&mut rng, kem).unwrap().1), - EncapsulationKey::XWing(generate_keypair_libcrux(&mut rng, kem).unwrap().1), - ), - KEM::X25519 => ( - EncapsulationKey::X25519(generate_keypair_libcrux(&mut rng, kem).unwrap().1), - EncapsulationKey::X25519(generate_keypair_libcrux(&mut rng, kem).unwrap().1), - ), - KEM::McEliece => ( - EncapsulationKey::McEliece(generate_keypair_mceliece(&mut rng).1), - EncapsulationKey::McEliece(generate_keypair_mceliece(&mut rng).1), - ), - }; - - let i_kem_key_bytes = initiator_kem_public_key.encode(); - - let r_kem_key_bytes = responder_kem_public_key.encode(); - - let i_dir_hash = hash_encapsulation_key( - &ciphersuite.hash_function(), - ciphersuite.hash_len(), - &i_kem_key_bytes, - ); - - let r_dir_hash = hash_encapsulation_key( - &ciphersuite.hash_function(), - ciphersuite.hash_len(), - &r_kem_key_bytes, - ); - - // Anonymous Initiator, OneWay - { - c.bench_function( - &format!("{kem}, {hash_function} | Anonymous Initiator: Generate Request",), - |b| { - b.iter(|| { - initiator_process(&mut rng, KKTMode::OneWay, ciphersuite, None).unwrap() - }); - }, - ); - - let (mut i_context, i_frame) = - initiator_process(&mut rng, KKTMode::OneWay, ciphersuite, None).unwrap(); - - c.bench_function( - &format!( - "{kem}, {hash_function} | Anonymous Initiator: Encode Frame - Request", - ), - |b| b.iter(|| i_frame.to_bytes()), - ); - - let i_frame_bytes = i_frame.to_bytes(); - - c.bench_function( - &format!( - "{kem}, {hash_function} | Anonymous Initiator: Decode Frame - Request", - ), - |b| b.iter(|| KKTFrame::from_bytes(&i_frame_bytes).unwrap()), - ); - - let (i_frame_r, r_context) = KKTFrame::from_bytes(&i_frame_bytes).unwrap(); - - c.bench_function( - &format!( - "{kem}, {hash_function} | Anonymous Initiator: Responder Ingest Frame", - ), - |b| { - b.iter(|| responder_ingest_message(&r_context, None, &i_frame_r).unwrap()); - }, - ); - - let (mut r_context, _) = - responder_ingest_message(&r_context, None, &i_frame_r).unwrap(); - - c.bench_function( - &format!( - "{kem}, {hash_function} | Anonymous Initiator: Responder Generate Response", - ), - |b| { - b.iter(|| { - responder_process( - &mut r_context, - i_frame_r.session_id(), - &responder_kem_public_key, - ) - .unwrap() - }); - }, - ); - let r_frame = responder_process( - &mut r_context, - i_frame_r.session_id(), - &responder_kem_public_key, - ) - .unwrap(); - - c.bench_function( - &format!( - "{kem}, {hash_function} | Anonymous Initiator: Responder Encode Frame", - ), - |b| b.iter(|| r_frame.to_bytes()), - ); - - c.bench_function( - &format!( - "{kem}, {hash_function} | Anonymous Initiator: Initiator Ingest Response", - ), - |b| { - b.iter(|| { - initiator_ingest_response( - &mut i_context, - &r_frame, - &r_frame.context().unwrap(), - &r_dir_hash, - ) - .unwrap() - }); - }, - ); - - let obtained_key = initiator_ingest_response( - &mut i_context, - &r_frame, - &r_frame.context().unwrap(), - &r_dir_hash, - ) - .unwrap(); - - assert_eq!(obtained_key.encode(), r_kem_key_bytes) - } - // Initiator, OneWay - { - let (mut i_context, i_frame) = - initiator_process(&mut rng, KKTMode::OneWay, ciphersuite, None).unwrap(); - - c.bench_function( - &format!("{kem}, {hash_function} | Initiator OneWay: Generate Request",), - |b| { - b.iter(|| { - initiator_process(&mut rng, KKTMode::OneWay, ciphersuite, None).unwrap() - }); - }, - ); - - c.bench_function( - &format!("{kem}, {hash_function} | Initiator OneWay: Encode Frame - Request",), - |b| b.iter(|| i_frame.to_bytes()), - ); - - let i_frame_bytes = i_frame.to_bytes(); - - c.bench_function( - &format!("{kem}, {hash_function} | Initiator OneWay: Decode Frame - Request",), - |b| b.iter(|| KKTFrame::from_bytes(&i_frame_bytes).unwrap()), - ); - - let (i_frame_r, r_context) = KKTFrame::from_bytes(&i_frame_bytes).unwrap(); - - c.bench_function( - &format!("{kem}, {hash_function} | Initiator OneWay: Responder Ingest Frame",), - |b| { - b.iter(|| responder_ingest_message(&r_context, None, &i_frame_r).unwrap()); - }, - ); - - let (mut r_context, r_obtained_key) = - responder_ingest_message(&r_context, None, &i_frame_r).unwrap(); - - assert!(r_obtained_key.is_none()); - - c.bench_function( - &format!( - "{kem}, {hash_function} | Initiator OneWay: Responder Generate Response", - ), - |b| { - b.iter(|| { - responder_process( - &mut r_context, - i_frame_r.session_id(), - &responder_kem_public_key, - ) - .unwrap() - }); - }, - ); - - let r_frame = responder_process( - &mut r_context, - i_frame_r.session_id(), - &responder_kem_public_key, - ) - .unwrap(); - - c.bench_function( - &format!("{kem}, {hash_function} | Initiator OneWay: Responder Encode Frame",), - |b| { - b.iter(|| r_frame.to_bytes()); - }, - ); - - c.bench_function( - &format!( - "{kem}, {hash_function} | Initiator OneWay: Initiator Ingest Response", - ), - |b| { - b.iter(|| { - initiator_ingest_response( - &mut i_context, - &r_frame, - &r_frame.context().unwrap(), - &r_dir_hash, - ) - .unwrap() - }); - }, - ); - - let i_obtained_key = initiator_ingest_response( - &mut i_context, - &r_frame, - &r_frame.context().unwrap(), - &r_dir_hash, - ) - .unwrap(); - - assert_eq!(i_obtained_key.encode(), r_kem_key_bytes) - } - - // Initiator, Mutual - { - c.bench_function( - &format!("{kem}, {hash_function} | Initiator Mutual: Generate Request",), - |b| { - b.iter(|| { - initiator_process( - &mut rng, - KKTMode::Mutual, - ciphersuite, - Some(&initiator_kem_public_key), - ) - .unwrap() - }); - }, - ); - - let (mut i_context, i_frame) = initiator_process( - &mut rng, - KKTMode::Mutual, - ciphersuite, - Some(&initiator_kem_public_key), - ) - .unwrap(); - - c.bench_function( - &format!("{kem}, {hash_function} | Initiator Mutual: Encode Frame - Request",), - |b| { - b.iter(|| i_frame.to_bytes()); - }, - ); - - let i_frame_bytes = i_frame.to_bytes(); - - c.bench_function( - &format!("{kem}, {hash_function} | Initiator Mutual: Decode Frame - Request",), - |b| { - b.iter(|| KKTFrame::from_bytes(&i_frame_bytes).unwrap()); - }, - ); - - let (i_frame_r, r_context) = KKTFrame::from_bytes(&i_frame_bytes).unwrap(); - - c.bench_function( - &format!("{kem}, {hash_function} | Initiator Mutual: Responder Ingest Frame",), - |b| { - b.iter(|| { - responder_ingest_message(&r_context, Some(&i_dir_hash), &i_frame_r) - .unwrap() - }); - }, - ); - - let (mut r_context, r_obtained_key) = - responder_ingest_message(&r_context, Some(&i_dir_hash), &i_frame_r).unwrap(); - - assert_eq!(r_obtained_key.unwrap().encode(), i_kem_key_bytes); - - c.bench_function( - &format!( - "{kem}, {hash_function} | Initiator Mutual: Responder Generate Response", - ), - |b| { - b.iter(|| { - responder_process( - &mut r_context, - i_frame_r.session_id(), - &responder_kem_public_key, - ) - .unwrap() - }); - }, - ); - - let r_frame = responder_process( - &mut r_context, - i_frame_r.session_id(), - &responder_kem_public_key, - ) - .unwrap(); - - c.bench_function( - &format!("{kem}, {hash_function} | Initiator Mutual: Responder Encode Frame",), - |b| { - b.iter(|| { - r_frame.to_bytes(); - }); - }, - ); - - c.bench_function( - &format!( - "{kem}, {hash_function} | Initiator Mutual: Initiator Ingest Response", - ), - |b| { - b.iter(|| { - initiator_ingest_response( - &mut i_context, - &r_frame, - &r_frame.context().unwrap(), - &r_dir_hash, - ) - .unwrap() - }); - }, - ); - - let obtained_key = initiator_ingest_response( - &mut i_context, - &r_frame, - &r_frame.context().unwrap(), - &r_dir_hash, - ) - .unwrap(); - - assert_eq!(obtained_key.encode(), r_kem_key_bytes) - } - } - } -} - -criterion_group!(benches, gen_mlkem768_keypair, kkt_benchmark); -criterion_main!(benches); diff --git a/common/nym-kkt/src/lib.rs b/common/nym-kkt/src/lib.rs index ddef8fdb3e..1b8d00080e 100644 --- a/common/nym-kkt/src/lib.rs +++ b/common/nym-kkt/src/lib.rs @@ -106,7 +106,7 @@ mod test { ) .unwrap(); - let response = responder.process_request(request).unwrap(); + let response = responder.process_request(request.request).unwrap(); let result = initiator.process_response(response.response).unwrap(); @@ -133,7 +133,7 @@ mod test { ) .unwrap(); - let processed_request = responder.process_request(request).unwrap(); + let processed_request = responder.process_request(request.request).unwrap(); // if we keep unverified keys, this should change assert!(processed_request.remote_encapsulation_key.is_none()); @@ -166,7 +166,7 @@ mod test { ) .unwrap(); - let processed_request = responder.process_request(request).unwrap(); + let processed_request = responder.process_request(request.request).unwrap(); let processed_response = initiator .process_response(processed_request.response) @@ -195,7 +195,7 @@ mod test { ) .unwrap(); - let processed_request = responder.process_request(request).unwrap(); + let processed_request = responder.process_request(request.request).unwrap(); // if we keep unverified keys, this should change assert!(processed_request.remote_encapsulation_key.is_none()); diff --git a/common/nym-kkt/src/rekey.rs b/common/nym-kkt/src/rekey.rs index c62d37790f..dc8fb273df 100644 --- a/common/nym-kkt/src/rekey.rs +++ b/common/nym-kkt/src/rekey.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 //! Post-Quantum Re-Key Protocol + /// This module implements a stateless post-quantum re-keying protocol in one round-trip. /// We currently support MlKem768 and XWing. /// @@ -16,6 +17,7 @@ use libcrux_kem::*; use nym_crypto::hkdf::blake3::derive_key_blake3; use nym_kkt_ciphersuite::{KEM, mceliece, ml_kem768, x25519, xwing}; use rand09::{CryptoRng, RngCore}; +use std::fmt::{Debug, Formatter}; use zeroize::Zeroize; use crate::error::KKTError; @@ -29,6 +31,26 @@ pub struct RekeyInitiator { salt: [u8; 32], } +impl Debug for RekeyInitiator { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let key_typ = match self.decapsulation_key { + PrivateKey::X25519(_) => "x25519", + PrivateKey::P256(_) => "p256", + PrivateKey::MlKem512(_) => "ml512", + PrivateKey::MlKem768(_) => "mlkem768", + PrivateKey::X25519MlKem768Draft00(_) => "x25519-mlkem768", + PrivateKey::XWingKemDraft06(_) => "xwing", + PrivateKey::MlKem1024(_) => "ml1024", + }; + + f.debug_struct("RekeyInitiator") + .field("algorithm", &self.algorithm) + .field("decapsulation_key", &key_typ) + .field("salt", &self.salt) + .finish() + } +} + impl RekeyInitiator { /// The Initiator generates an ephemeral KEM keypair and a 32-byte salt. /// The Initiator keeps the decapsulation key and generates a request message. @@ -204,6 +226,7 @@ where #[cfg(test)] mod tests { + use crate::error::KKTError; use crate::rekey::{RekeyInitiator, responder_process}; use nym_kkt_ciphersuite::KEM; @@ -211,16 +234,24 @@ mod tests { fn rekey_test() { let mut rng = rand09::rng(); - for kem in [KEM::MlKem768] { - let (rekey_state, request_message) = - RekeyInitiator::generate_request(&mut rng, kem).unwrap(); + let (rekey_state, request_message) = + RekeyInitiator::generate_request(&mut rng, KEM::MlKem768).unwrap(); - let (responder_secret, response_message) = - responder_process(&mut rng, request_message).unwrap(); + let (responder_secret, response_message) = + responder_process(&mut rng, request_message).unwrap(); - let initiator_secret = rekey_state.finalize(&response_message).unwrap(); + let initiator_secret = rekey_state.finalize(&response_message).unwrap(); - assert_eq!(initiator_secret, responder_secret); - } + assert_eq!(initiator_secret, responder_secret); + + // mceliece should fail + let err = RekeyInitiator::generate_request(&mut rng, KEM::McEliece).unwrap_err(); + assert_eq!( + err.to_string(), + KKTError::UnsupportedAlgorithm { + info: "McEliece is not supported for re-keying", + } + .to_string() + ) } } diff --git a/common/nym-lp/src/lib.rs b/common/nym-lp/src/lib.rs index 8015d521e8..06e4458ca2 100644 --- a/common/nym-lp/src/lib.rs +++ b/common/nym-lp/src/lib.rs @@ -18,7 +18,7 @@ pub use nym_kkt_ciphersuite::{ Ciphersuite, HashFunction, HashLength, KEM, KEMKeyDigests, SignatureScheme, }; pub use nym_lp_packet::{ - EncryptedLpPacket, LpMessage, LpPacket, OuterHeader, + EncryptedLpPacket, InnerHeader, LpHeader, LpMessage, LpPacket, MessageType, OuterHeader, error::MalformedLpPacketError, message::{ApplicationData, ExpectedResponseSize, ForwardPacketData}, }; diff --git a/common/nym-lp/src/peer.rs b/common/nym-lp/src/peer.rs index 888d908100..d68d1caf9f 100644 --- a/common/nym-lp/src/peer.rs +++ b/common/nym-lp/src/peer.rs @@ -2,13 +2,15 @@ // SPDX-License-Identifier: Apache-2.0 use crate::LpError; -use nym_crypto::asymmetric::x25519; use nym_kkt_ciphersuite::{Ciphersuite, KEM, KEMKeyDigests}; use std::collections::BTreeMap; use std::fmt::Debug; use std::sync::Arc; pub use libcrux_psq::handshake::types::{DHKeyPair, DHPublicKey}; +pub use nym_kkt::key_utils::{ + generate_keypair_mceliece, generate_keypair_mlkem, generate_lp_keypair_x25519, +}; pub use nym_kkt::keys::KEMKeys; /// Representation of a local Lewes Protocol peer @@ -85,11 +87,9 @@ pub struct LpRemotePeer { } impl LpRemotePeer { - pub fn new(x25519_public: x25519::PublicKey) -> Self { - // TODO: make nicer conversion (without cloning) + error handling - let responder_x25519_public_key = DHPublicKey::from_bytes(x25519_public.as_bytes()); + pub fn new(x25519_public: DHPublicKey) -> Self { LpRemotePeer { - x25519_public: responder_x25519_public_key, + x25519_public, expected_kem_key_digests: Default::default(), } } diff --git a/common/registration/src/lib.rs b/common/registration/src/lib.rs index d11337f687..8e271ab1ea 100644 --- a/common/registration/src/lib.rs +++ b/common/registration/src/lib.rs @@ -12,6 +12,7 @@ use std::collections::BTreeMap; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; pub use lp_messages::*; +use nym_crypto::asymmetric::x25519::DHPublicKey; pub use serialisation::BincodeError; mod lp_messages; @@ -57,7 +58,7 @@ pub struct WireguardConfiguration { pub struct NymNodeLPInformation { pub address: SocketAddr, pub expected_kem_key_hashes: BTreeMap, - pub x25519: x25519::PublicKey, + pub x25519: DHPublicKey, // to be inferred from node's version pub ciphersuite: Ciphersuite, diff --git a/gateway/src/node/lp_listener/data_handler.rs b/gateway/src/node/lp_listener/data_handler.rs index 576fb9a053..7436027d8c 100644 --- a/gateway/src/node/lp_listener/data_handler.rs +++ b/gateway/src/node/lp_listener/data_handler.rs @@ -122,7 +122,7 @@ impl LpDataHandler { ) -> Result<(), LpHandlerError> { inc!("lp_data_packets_received"); - let _ = OuterHeader::parse(&packet)?; + let _ = OuterHeader::parse(packet)?; trace!( "received {} bytes from {src_addr} on the unimplemented LP Data endpoint", packet.len() diff --git a/gateway/src/node/lp_listener/error.rs b/gateway/src/node/lp_listener/error.rs index cf17fad44d..e98fbcf916 100644 --- a/gateway/src/node/lp_listener/error.rs +++ b/gateway/src/node/lp_listener/error.rs @@ -59,10 +59,9 @@ pub enum LpHandlerError { impl LpHandlerError { pub fn is_connection_closed(&self) -> bool { match self { - LpHandlerError::LpTransportError(transport_err) => match transport_err { - LpTransportError::ConnectionClosed => true, - _ => false, - }, + LpHandlerError::LpTransportError(transport_err) => { + matches!(transport_err, LpTransportError::ConnectionClosed) + } _ => false, } } diff --git a/gateway/src/node/lp_listener/handler.rs b/gateway/src/node/lp_listener/handler.rs index 9292ce7029..c5b0d523b9 100644 --- a/gateway/src/node/lp_listener/handler.rs +++ b/gateway/src/node/lp_listener/handler.rs @@ -624,35 +624,6 @@ where Ok(()) } - // only used in tests - #[cfg(test)] - async fn send_lp_packet(&mut self, packet: LpPacket) -> Result<(), LpHandlerError> { - let receiver_index = self.bound_receiver_index()?; - - let mut session_entry = self - .state - .session_states - .get_mut(&receiver_index) - .ok_or_else(|| LpHandlerError::MissingLpSession { receiver_index })?; - - // Access session via state machine for subsession support - let session = session_entry.value_mut().state.session_mut()?; - - let mut packet_buf = BytesMut::new(); - serialize_lp_packet(&packet, &mut packet_buf, Some(outer_key)).map_err(|e| { - LpHandlerError::LpProtocolError(format!("Failed to serialize packet: {e}",)) - })?; - - self.stream - .send_length_prefixed_transport_packet(&packet_buf) - .await?; - - // Track bytes sent (4 byte header + packet data) - self.stats.record_bytes_sent(4 + packet_buf.len()); - - Ok(()) - } - /// Emit connection lifecycle metrics fn emit_lifecycle_metrics(&self, graceful: bool) { use nym_metrics::inc_by; @@ -689,17 +660,18 @@ mod tests { use super::*; use crate::node::lp_listener::{LpConfig, LpDebug}; use crate::node::ActiveClientsStore; - use bytes::BytesMut; - use nym_lp::peer::LpLocalPeer; - use nym_lp::SessionsMock; + use nym_lp::peer::{generate_keypair_mceliece, generate_keypair_mlkem, KEMKeys, LpLocalPeer}; + use nym_lp::{sessions_for_tests, Ciphersuite, SessionManager}; + use nym_test_utils::helpers::{deterministic_rng, deterministic_rng_09}; use std::sync::Arc; - use tokio::io::{AsyncRead, AsyncReadExt, AsyncWriteExt}; // ==================== Test Helpers ==================== /// Create a minimal test state for handler tests async fn create_minimal_test_state() -> LpHandlerState { use nym_crypto::asymmetric::ed25519; - use rand::rngs::OsRng; + + let mut rng = deterministic_rng(); + let mut rng09 = deterministic_rng_09(); // Create in-memory storage for testing let storage = nym_gateway_storage::GatewayStorage::init(":memory:", 100) @@ -723,10 +695,14 @@ mod tests { // Create mix forwarding channel (unused in tests but required by struct) let (mix_sender, _mix_receiver) = nym_mixnet_client::forwarder::mix_forwarding_channels(); - let id_keys = Arc::new(ed25519::KeyPair::new(&mut OsRng)); - let x_keys = Arc::new(id_keys.to_x25519()); + let id_keys = Arc::new(ed25519::KeyPair::new(&mut rng)); + let x_keys = Arc::new(id_keys.to_x25519().try_into().unwrap()); - let lp_peer = LpLocalPeer::new(id_keys, x_keys.clone()).with_kem_psq_key(x_keys); + let kem_keys = KEMKeys::new( + generate_keypair_mceliece(&mut rng09), + generate_keypair_mlkem(&mut rng09), + ); + let lp_peer = LpLocalPeer::new(Ciphersuite::default(), x_keys).with_kem_keys(kem_keys); LpHandlerState { lp_config, @@ -739,173 +715,24 @@ mod tests { outbound_mix_sender: mix_sender, session_states: Arc::new(dashmap::DashMap::new()), peer_registrator: None, + forward_semaphore, } } - fn add_dummy_lp_state(handler: &mut LpConnectionHandler, session: LpSession) { - let id = session.receiver_index(); - let state_machine = LpStateMachine::new(session); - handler.bound_receiver_idx = Some(id); - - handler - .state - .session_states - .insert(id, TimestampedState::new(state_machine)); - } - - /// Helper to write an LP packet to a stream with proper framing - async fn write_lp_packet_to_stream( - stream: &mut W, - packet: &LpPacket, - ) -> Result<(), std::io::Error> { - let mut packet_buf = BytesMut::new(); - serialize_lp_packet(packet, &mut packet_buf, None) - .map_err(|e| std::io::Error::other(e.to_string()))?; - - // Write length prefix - let len = packet_buf.len() as u32; - stream.write_all(&len.to_be_bytes()).await?; - - // Write packet data - stream.write_all(&packet_buf).await?; - stream.flush().await?; - - Ok(()) - } - - /// Helper to read an LP packet from a stream with proper framing - async fn read_lp_packet_from_stream( - stream: &mut R, - outer_aead_key: &OuterAeadKey, - ) -> Result { - // Read length prefix - let mut len_buf = [0u8; 4]; - stream.read_exact(&mut len_buf).await?; - let packet_len = u32::from_be_bytes(len_buf) as usize; - - // Read packet data - let mut packet_buf = vec![0u8; packet_len]; - stream.read_exact(&mut packet_buf).await?; - - // Parse packet - parse_lp_packet(&packet_buf, Some(outer_aead_key)) - .map_err(|e| std::io::Error::other(e.to_string())) - } - // ==================== Existing Tests ==================== - #[test] - fn test_validate_timestamp_current() { - use std::time::{SystemTime, UNIX_EPOCH}; - - let now = SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap() - .as_secs(); - - // Current timestamp should always pass - assert!( - LpConnectionHandler::::validate_timestamp(now, Duration::from_secs(30)) - .is_ok() - ); - } - - #[test] - fn test_validate_timestamp_within_tolerance() { - use std::time::{SystemTime, UNIX_EPOCH}; - - let now = SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap() - .as_secs(); - - // 10 seconds old, tolerance 30s -> should pass - let old_timestamp = now - 10; - assert!(LpConnectionHandler::::validate_timestamp( - old_timestamp, - Duration::from_secs(30) - ) - .is_ok()); - - // 10 seconds in future, tolerance 30s -> should pass - let future_timestamp = now + 10; - assert!(LpConnectionHandler::::validate_timestamp( - future_timestamp, - Duration::from_secs(30) - ) - .is_ok()); - } - - #[test] - fn test_validate_timestamp_too_old() { - use std::time::{SystemTime, UNIX_EPOCH}; - - let now = SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap() - .as_secs(); - - // 60 seconds old, tolerance 30s -> should fail - let old_timestamp = now - 60; - let result = LpConnectionHandler::::validate_timestamp( - old_timestamp, - Duration::from_secs(30), - ); - assert!(result.is_err()); - assert!(format!("{:?}", result).contains("too old")); - } - - #[test] - fn test_validate_timestamp_too_far_future() { - use std::time::{SystemTime, UNIX_EPOCH}; - - let now = SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap() - .as_secs(); - - // 60 seconds in future, tolerance 30s -> should fail - let future_timestamp = now + 60; - let result = LpConnectionHandler::::validate_timestamp( - future_timestamp, - Duration::from_secs(30), - ); - assert!(result.is_err()); - assert!(format!("{:?}", result).contains("too future")); - } - - #[test] - fn test_validate_timestamp_boundary() { - use std::time::{SystemTime, UNIX_EPOCH}; - - let now = SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap() - .as_secs(); - - // Exactly at tolerance boundary -> should pass - let boundary_timestamp = now - 30; - assert!(LpConnectionHandler::::validate_timestamp( - boundary_timestamp, - Duration::from_secs(30) - ) - .is_ok()); - - // Just beyond boundary -> should fail - let beyond_timestamp = now - 31; - assert!(LpConnectionHandler::::validate_timestamp( - beyond_timestamp, - Duration::from_secs(30) - ) - .is_err()); - } - // ==================== Packet I/O Tests ==================== #[tokio::test] async fn test_receive_raw_packet_valid() { use tokio::net::{TcpListener, TcpStream}; + let (init, resp) = sessions_for_tests(); + let mut init_sm = SessionManager::new(); + let mut resp_sm = SessionManager::new(); + resp_sm.create_session_state_machine(resp).unwrap(); + let id = init_sm.create_session_state_machine(init).unwrap(); + // Bind to localhost let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); let addr = listener.local_addr().unwrap(); @@ -916,69 +743,38 @@ mod tests { let state = create_minimal_test_state().await; let mut handler = LpConnectionHandler::new(stream, remote_addr, state); // Two-phase: receive raw bytes + header, then parse full packet - let (raw_bytes, header) = handler.receive_raw_packet().await?; - let packet = parse_lp_packet(&raw_bytes, None).map_err(|e| { - LpHandlerError::LpProtocolError(format!("Failed to parse packet: {}", e)) - })?; - Ok::<_, LpHandlerError>((header, packet)) + let packet = handler.receive_raw_packet().await?; + let header = packet.outer_header(); + assert_eq!(packet.outer_header().receiver_idx, id); + let Some(LpAction::DeliverData(data)) = resp_sm.receive_packet(id, packet).unwrap() + else { + panic!("illegal state") + }; + Ok::<_, LpHandlerError>((header, data)) }); // Connect as client let mut client_stream = TcpStream::connect(addr).await.unwrap(); // Send a valid packet from client side - let packet = LpPacket::new( - LpHeader { - protocol_version: 1, - reserved: [0u8; 3], - receiver_idx: 42, - counter: 0, - }, - LpMessage::Busy, - ); - write_lp_packet_to_stream(&mut client_stream, &packet) + let LpAction::SendPacket(packet) = init_sm + .send_data(id, LpData::new_opaque(b"foomp".to_vec())) + .unwrap() + else { + panic!("illegal state") + }; + + client_stream + .send_length_prefixed_transport_packet(&packet) .await .unwrap(); // Handler should receive and parse it correctly // Note: header is OuterHeader (receiver_idx + counter only), not LpHeader let (header, received) = server_task.await.unwrap().unwrap(); - assert_eq!(header.receiver_idx, 42); + assert_eq!(header.receiver_idx, id); assert_eq!(header.counter, 0); - assert_eq!(received.header().protocol_version, 1); - assert_eq!(received.header().receiver_idx, 42); - assert_eq!(received.header().counter, 0); - } - - #[tokio::test] - async fn test_receive_raw_packet_exceeds_max_size() { - use tokio::net::{TcpListener, TcpStream}; - - let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); - let addr = listener.local_addr().unwrap(); - - let server_task = tokio::spawn(async move { - let (stream, remote_addr) = listener.accept().await.unwrap(); - let state = create_minimal_test_state().await; - let mut handler = LpConnectionHandler::new(stream, remote_addr, state); - handler.receive_raw_packet().await - }); - - let mut client_stream = TcpStream::connect(addr).await.unwrap(); - - // Send a packet size that exceeds MAX_PACKET_SIZE (64KB) - let oversized_len: u32 = 70000; // > 65536 - client_stream - .write_all(&oversized_len.to_be_bytes()) - .await - .unwrap(); - client_stream.flush().await.unwrap(); - - // Handler should reject it - let result = server_task.await.unwrap(); - assert!(result.is_err()); - let err_msg = format!("{:?}", result.unwrap_err()); - assert!(err_msg.contains("exceeds maximum")); + assert_eq!(received.content.as_ref(), b"foomp"); } #[tokio::test] @@ -988,88 +784,46 @@ mod tests { let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); let addr = listener.local_addr().unwrap(); - let receiver_idx = 99; - let sessions = SessionsMock::mock_post_handshake(receiver_idx); - let init = sessions.initiator; - let resp = sessions.responder; + let (init, resp) = sessions_for_tests(); + let mut init_sm = SessionManager::new(); + let mut resp_sm = SessionManager::new(); + resp_sm.create_session_state_machine(resp).unwrap(); + let id = init_sm.create_session_state_machine(init).unwrap(); let server_task = tokio::spawn(async move { - let (stream, remote_addr) = listener.accept().await.unwrap(); - let state = create_minimal_test_state().await; - let mut handler = LpConnectionHandler::new(stream, remote_addr, state); - add_dummy_lp_state(&mut handler, resp); + let (mut stream, _) = listener.accept().await.unwrap(); - let packet = LpPacket::new( - LpHeader { - protocol_version: 1, - reserved: [0u8; 3], - receiver_idx, - counter: 5, - }, - LpMessage::Busy, - ); - handler.send_lp_packet(packet).await + let LpAction::SendPacket(packet) = resp_sm + .send_data(id, LpData::new_opaque(b"foomp".to_vec())) + .unwrap() + else { + panic!("illegal state") + }; + + stream + .send_length_prefixed_transport_packet(&packet) + .await + .unwrap(); }); let mut client_stream = TcpStream::connect(addr).await.unwrap(); // Wait for server to send - server_task.await.unwrap().unwrap(); + server_task.await.unwrap(); // Client should receive it correctly - let received = read_lp_packet_from_stream(&mut client_stream, init.outer_aead_key()) + let received = client_stream + .receive_length_prefixed_transport_packet() .await .unwrap(); - assert_eq!(received.header().receiver_idx, receiver_idx); - assert_eq!(received.header().counter, 5); - } + let header = received.outer_header(); + let Some(LpAction::DeliverData(data)) = init_sm.receive_packet(id, received).unwrap() + else { + panic!("illegal state") + }; - #[tokio::test] - async fn test_send_receive_encrypted_data_message() { - use tokio::net::{TcpListener, TcpStream}; - - let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); - let addr = listener.local_addr().unwrap(); - - let receiver_idx = 200; - let sessions = SessionsMock::mock_post_handshake(receiver_idx); - let init = sessions.initiator; - let resp = sessions.responder; - - let encrypted_payload = vec![42u8; 256]; - let expected_payload = encrypted_payload.clone(); - - let server_task = tokio::spawn(async move { - let (stream, remote_addr) = listener.accept().await.unwrap(); - let state = create_minimal_test_state().await; - let mut handler = LpConnectionHandler::new(stream, remote_addr, state); - add_dummy_lp_state(&mut handler, resp); - - let packet = LpPacket::new( - LpHeader { - protocol_version: 1, - reserved: [0u8; 3], - receiver_idx, - counter: 20, - }, - LpMessage::ApplicationData(ApplicationData(encrypted_payload)), - ); - handler.send_lp_packet(packet).await - }); - - let mut client_stream = TcpStream::connect(addr).await.unwrap(); - server_task.await.unwrap().unwrap(); - - let received = read_lp_packet_from_stream(&mut client_stream, init.outer_aead_key()) - .await - .unwrap(); - assert_eq!(received.header().receiver_idx, 200); - assert_eq!(received.header().counter, 20); - match received.message() { - LpMessage::ApplicationData(data) => { - assert_eq!(data, &ApplicationData(expected_payload)) - } - _ => panic!("Expected EncryptedData message"), - } + assert_eq!(header.receiver_idx, id); + assert_eq!(header.counter, 0); + assert_eq!(data.content.as_ref(), b"foomp"); } } diff --git a/integration-tests/src/lp_registration.rs b/integration-tests/src/lp_registration.rs index 5b47b4f6ce..8120d1c0e6 100644 --- a/integration-tests/src/lp_registration.rs +++ b/integration-tests/src/lp_registration.rs @@ -9,12 +9,11 @@ mod tests { use nym_credential_verification::upgrade_mode::testing::mock_dummy_upgrade_mode_details; use nym_credentials_interface::TicketType; use nym_crypto::asymmetric::{ed25519, x25519}; - use nym_gateway::GatewayError; use nym_gateway::node::lp_listener::error::LpHandlerError; use nym_gateway::node::lp_listener::handler::LpConnectionHandler; use nym_gateway::node::lp_listener::{ - DHKeyPair, KEMKeys, LpDebug, LpHandlerState, LpLocalPeer, MixForwardingReceiver, - PeerControlRequest, WireguardGatewayData, mix_forwarding_channels, + KEMKeys, LpDebug, LpHandlerState, LpLocalPeer, MixForwardingReceiver, PeerControlRequest, + WireguardGatewayData, mix_forwarding_channels, }; use nym_gateway::node::wireguard::{PeerManager, PeerRegistrator}; use nym_gateway::node::{ActiveClientsStore, GatewayStorage, LpConfig}; @@ -23,7 +22,7 @@ mod tests { }; use nym_kkt_ciphersuite::Ciphersuite; use nym_registration_client::{LpClientError, LpRegistrationClient}; - use nym_test_utils::helpers::{CryptoRng09, RngCore09, seeded_rng}; + use nym_test_utils::helpers::{CryptoRng09, seeded_rng}; use nym_test_utils::mocks::async_read_write::MockIOStream; use nym_test_utils::traits::Timeboxed; use nym_wireguard::peer_controller::IpPair; @@ -35,7 +34,6 @@ mod tests { use std::mem; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; use std::sync::Arc; - use std::time::Duration; use tokio::sync::Semaphore; use tokio::sync::mpsc::Receiver; use tokio::task::JoinHandle; @@ -61,7 +59,7 @@ mod tests { } impl Party { - fn generate(rng: &mut (impl RngCore09 + CryptoRng09)) -> Self { + fn generate(rng: &mut impl CryptoRng09) -> Self { let mut ip = [0u8; 4]; let mut port = [0u8; 2]; @@ -100,7 +98,7 @@ mod tests { } impl Client { - fn mock(rng: &mut (impl RngCore09 + CryptoRng09)) -> Self { + fn mock(rng: &mut impl CryptoRng09) -> Self { Client { base: Party::generate(rng), ticket_provider: Default::default(), @@ -194,7 +192,7 @@ mod tests { Ok(GatewayStorage::from_connection_pool(conn_pool, 100).await?) } - async fn mock(rng: &mut (impl RngCore09 + CryptoRng09)) -> anyhow::Result { + async fn mock(rng: &mut impl CryptoRng09) -> anyhow::Result { let base = Party::generate(rng); // 1. create in-memory gateway storage @@ -574,199 +572,203 @@ mod tests { async fn test_basic_lp_exit_registration() -> anyhow::Result<()> { // nym_test_utils::helpers::setup_test_logger(); - let TODO = "test with different kems"; - let ciphersuite = Ciphersuite::default(); + todo!("doesn't work with mceliece"); - // initialise random, but deterministic, keys, addresses, etc. for the parties - let mut client_rng = u64_seeded_rng_09(0); - let mut entry_rng = u64_seeded_rng_09(1); - let mut exit_rng = u64_seeded_rng_09(2); + for kem in KEM::iter() { + let ciphersuite = Ciphersuite::default().with_kem(kem); - let client_data = Client::mock(&mut client_rng); - let client_key = *client_data.base.x25519_wg_keys.public_key(); - let mut entry = Gateway::mock(&mut entry_rng).await?; - let mut exit = Gateway::mock(&mut exit_rng).await?; + // initialise random, but deterministic, keys, addresses, etc. for the parties + let mut client_rng = u64_seeded_rng_09(0); + let mut entry_rng = u64_seeded_rng_09(1); + let mut exit_rng = u64_seeded_rng_09(2); - let mut entry_client = LpRegistrationClient::::new_with_default_config( - client_data.base.peer.x25519().clone(), - entry.base.peer.as_remote(), - entry.base.socket_addr, - ciphersuite, - entry.base.lp_version, - ); + let client_data = Client::mock(&mut client_rng); + let client_key = *client_data.base.x25519_wg_keys.public_key(); + let mut entry = Gateway::mock(&mut entry_rng).await?; + let mut exit = Gateway::mock(&mut exit_rng).await?; - // START: ENTRY SETUP - // - // 1. establish mock connection between client and gateway and retrieve gateway's handle - entry_client.ensure_connected().await?; - let entry_conn = entry_client - .connection() - .as_ref() - .context("mock connection has failed!")? - .try_get_remote_handle(); - entry_conn.set_id(1); + let mut entry_client = + LpRegistrationClient::::new_with_default_config( + client_data.base.peer.x25519().clone(), + entry.base.peer.as_remote(), + entry.base.socket_addr, + ciphersuite, + entry.base.lp_version, + ); - // 2. create handler for the client connection (entry) - entry.create_lp_handler(entry_conn, client_data.base.socket_addr); + // START: ENTRY SETUP + // + // 1. establish mock connection between client and gateway and retrieve gateway's handle + entry_client.ensure_connected().await?; + let entry_conn = entry_client + .connection() + .as_ref() + .context("mock connection has failed!")? + .try_get_remote_handle(); + entry_conn.set_id(1); - // 3. pre-establish connection between entry and exit - let exit_conn = entry - .establish_forwarding_channel(exit.base.socket_addr) - .await?; - exit_conn.set_id(255); + // 2. create handler for the client connection (entry) + entry.create_lp_handler(entry_conn, client_data.base.socket_addr); - // 4. register all needed responses for the dvpn registration that will reach the peer controller - // 1) peer registration - ip pair allocation - let entry_ip_pair = entry.pre_allocate_ip_pair(); - let reg_res = Ok::<_, nym_wireguard::Error>(entry_ip_pair); + // 3. pre-establish connection between entry and exit + let exit_conn = entry + .establish_forwarding_channel(exit.base.socket_addr) + .await?; + exit_conn.set_id(255); - entry - .register_peer_controller_response( + // 4. register all needed responses for the dvpn registration that will reach the peer controller + // 1) peer registration - ip pair allocation + let entry_ip_pair = entry.pre_allocate_ip_pair(); + let reg_res = Ok::<_, nym_wireguard::Error>(entry_ip_pair); + + entry + .register_peer_controller_response( + PeerControlRequestType::AllocatePeerIpPair {}, + reg_res, + ) + .await; + + // 2) new peer inclusion - in non-mock system it would spawn handlers, + // here we'll just set a flag and say it's all fine + let public_key = client_key.to_wg_key(); + let add_res = Ok::<_, nym_wireguard::Error>(()); + entry + .register_peer_controller_response( + PeerControlRequestType::AddPeer { public_key }, + add_res, + ) + .await; + + // 3) peer query - check for prior registrations + let query_res = Ok::<_, nym_wireguard::Error>(Option::::None); + let key = client_key.to_wg_key(); + entry + .register_peer_controller_response( + PeerControlRequestType::QueryPeer { key }, + query_res, + ) + .await; + + // 5. spawn peer controller to be able to handle dvpn registration requests + entry.spawn_peer_controller(); + + // 6. finally spawn the handler + entry.spawn_lp_handler(); + + // 7. perform client handshake (with the entry) + entry_client.perform_handshake().timeboxed().await??; + + // END: ENTRY SETUP + // + // START: EXIT SETUP: + // 8. create handler for the forwarding channel (exit) + exit.create_lp_handler(exit_conn, client_data.base.socket_addr); + + // 9. spawn the handler + exit.spawn_lp_handler(); + + // 10. register all needed responses for the dvpn registration that will reach the peer controller + // 1) peer registration - ip pair allocation + let exit_ip_pair = exit.pre_allocate_ip_pair(); + let reg_res = Ok::<_, nym_wireguard::Error>(exit_ip_pair); + + exit.register_peer_controller_response( PeerControlRequestType::AllocatePeerIpPair {}, reg_res, ) .await; - // 2) new peer inclusion - in non-mock system it would spawn handlers, - // here we'll just set a flag and say it's all fine - let public_key = client_key.to_wg_key(); - let add_res = Ok::<_, nym_wireguard::Error>(()); - entry - .register_peer_controller_response( + // 2) new peer inclusion - in non-mock system it would spawn handlers, + // here we'll just set a flag and say it's all fine + let public_key = client_key.to_wg_key(); + let add_res = Ok::<_, nym_wireguard::Error>(()); + exit.register_peer_controller_response( PeerControlRequestType::AddPeer { public_key }, add_res, ) .await; - // 3) peer query - check for prior registrations - let query_res = Ok::<_, nym_wireguard::Error>(Option::::None); - let key = client_key.to_wg_key(); - entry - .register_peer_controller_response( + // 3) peer query - check for prior registrations + let query_res = Ok::<_, nym_wireguard::Error>(Option::::None); + let key = client_key.to_wg_key(); + exit.register_peer_controller_response( PeerControlRequestType::QueryPeer { key }, query_res, ) .await; - // 5. spawn peer controller to be able to handle dvpn registration requests - entry.spawn_peer_controller(); + // 11. spawn peer controller to be able to handle dvpn registration requests + exit.spawn_peer_controller(); - // 6. finally spawn the handler - entry.spawn_lp_handler(); + // END: EXIT SETUP - // 7. perform client handshake (with the entry) - entry_client.perform_handshake().timeboxed().await??; + // 12. create nested session to register with exit via forwarding + // technically we should use different ephemeral keys than we had for the entry + // but crypto is going to work the same + let mut nested_session = NestedLpSession::new( + exit.base.socket_addr, + client_data.base.peer.x25519().clone(), + exit.base.peer.as_remote(), + ciphersuite, + exit.base.lp_version, + ); - // END: ENTRY SETUP - // - // START: EXIT SETUP: - // 8. create handler for the forwarding channel (exit) - exit.create_lp_handler(exit_conn, client_data.base.socket_addr); + // 13. Perform handshake and registration with exit gateway (all via entry forwarding) + nested_session.perform_handshake(&mut entry_client).await?; - // 9. spawn the handler - exit.spawn_lp_handler(); + let exit_registration_result = nested_session + .register_dvpn( + &mut entry_client, + &mut client_rng, + &client_data.base.x25519_wg_keys, + exit.base.identity.public_key(), + &client_data.ticket_provider, + TicketType::V1WireguardExit, + ) + .timeboxed() + .await??; - // 10. register all needed responses for the dvpn registration that will reach the peer controller - // 1) peer registration - ip pair allocation - let exit_ip_pair = exit.pre_allocate_ip_pair(); - let reg_res = Ok::<_, nym_wireguard::Error>(exit_ip_pair); + // 14. complete registration with the entry + let entry_registration_result = entry_client + .register_dvpn( + &mut client_rng, + &client_data.base.x25519_wg_keys, + entry.base.identity.public_key(), + &client_data.ticket_provider, + TicketType::V1WireguardEntry, + ) + .timeboxed() + .await??; - exit.register_peer_controller_response( - PeerControlRequestType::AllocatePeerIpPair {}, - reg_res, - ) - .await; + // 15. verify all registration results + let peers_guard = entry.mock_peer_controller_state.peers.read().await; + let entry_peer = peers_guard.get_by_x25519_key(&client_key).unwrap().clone(); + drop(peers_guard); + assert!(entry_peer.add_success); - // 2) new peer inclusion - in non-mock system it would spawn handlers, - // here we'll just set a flag and say it's all fine - let public_key = client_key.to_wg_key(); - let add_res = Ok::<_, nym_wireguard::Error>(()); - exit.register_peer_controller_response( - PeerControlRequestType::AddPeer { public_key }, - add_res, - ) - .await; + let peers_guard = exit.mock_peer_controller_state.peers.read().await; + let exit_peer = peers_guard.get_by_x25519_key(&client_key).unwrap().clone(); + drop(peers_guard); + assert!(exit_peer.add_success); - // 3) peer query - check for prior registrations - let query_res = Ok::<_, nym_wireguard::Error>(Option::::None); - let key = client_key.to_wg_key(); - exit.register_peer_controller_response( - PeerControlRequestType::QueryPeer { key }, - query_res, - ) - .await; + assert_eq!(entry_registration_result.private_ipv4, entry_ip_pair.ipv4); + assert_eq!(entry_registration_result.private_ipv6, entry_ip_pair.ipv6); + assert_eq!( + entry_registration_result.public_key, + *entry.base.x25519_wg_keys.public_key() + ); - // 11. spawn peer controller to be able to handle dvpn registration requests - exit.spawn_peer_controller(); + assert_eq!(exit_registration_result.private_ipv4, exit_ip_pair.ipv4); + assert_eq!(exit_registration_result.private_ipv6, exit_ip_pair.ipv6); + assert_eq!( + exit_registration_result.public_key, + *exit.base.x25519_wg_keys.public_key() + ); - // END: EXIT SETUP - - // 12. create nested session to register with exit via forwarding - // technically we should use different ephemeral keys than we had for the entry - // but crypto is going to work the same - let mut nested_session = NestedLpSession::new( - exit.base.socket_addr, - client_data.base.peer.x25519().clone(), - exit.base.peer.as_remote(), - ciphersuite, - exit.base.lp_version, - ); - - // 13. Perform handshake and registration with exit gateway (all via entry forwarding) - nested_session.perform_handshake(&mut entry_client).await?; - - let exit_registration_result = nested_session - .register_dvpn( - &mut entry_client, - &mut client_rng, - &client_data.base.x25519_wg_keys, - exit.base.identity.public_key(), - &client_data.ticket_provider, - TicketType::V1WireguardExit, - ) - .timeboxed() - .await??; - - // 14. complete registration with the entry - let entry_registration_result = entry_client - .register_dvpn( - &mut client_rng, - &client_data.base.x25519_wg_keys, - entry.base.identity.public_key(), - &client_data.ticket_provider, - TicketType::V1WireguardEntry, - ) - .timeboxed() - .await??; - - // 15. verify all registration results - let peers_guard = entry.mock_peer_controller_state.peers.read().await; - let entry_peer = peers_guard.get_by_x25519_key(&client_key).unwrap().clone(); - drop(peers_guard); - assert!(entry_peer.add_success); - - let peers_guard = exit.mock_peer_controller_state.peers.read().await; - let exit_peer = peers_guard.get_by_x25519_key(&client_key).unwrap().clone(); - drop(peers_guard); - assert!(exit_peer.add_success); - - assert_eq!(entry_registration_result.private_ipv4, entry_ip_pair.ipv4); - assert_eq!(entry_registration_result.private_ipv6, entry_ip_pair.ipv6); - assert_eq!( - entry_registration_result.public_key, - *entry.base.x25519_wg_keys.public_key() - ); - - assert_eq!(exit_registration_result.private_ipv4, exit_ip_pair.ipv4); - assert_eq!(exit_registration_result.private_ipv6, exit_ip_pair.ipv6); - assert_eq!( - exit_registration_result.public_key, - *exit.base.x25519_wg_keys.public_key() - ); - - // 16. stop the gateway task and finish the test - entry.stop_tasks().await?; - exit.stop_tasks().await?; + // 16. stop the gateway task and finish the test + entry.stop_tasks().await?; + exit.stop_tasks().await?; + } Ok(()) } diff --git a/nym-api/nym-api-requests/Cargo.toml b/nym-api/nym-api-requests/Cargo.toml index edb25c2972..176eb0920f 100644 --- a/nym-api/nym-api-requests/Cargo.toml +++ b/nym-api/nym-api-requests/Cargo.toml @@ -52,7 +52,6 @@ nym-ecash-signer-check-types = { workspace = true } nym-kkt-ciphersuite = { workspace = true } [dev-dependencies] -rand_chacha = { workspace = true } nym-crypto = { workspace = true, features = ["rand"] } nym-test-utils = { workspace = true } diff --git a/nym-api/nym-api-requests/src/ecash/models.rs b/nym-api/nym-api-requests/src/ecash/models.rs index 7b4898518c..6db039abdd 100644 --- a/nym-api/nym-api-requests/src/ecash/models.rs +++ b/nym-api/nym-api-requests/src/ecash/models.rs @@ -790,13 +790,7 @@ mod tests { use nym_compact_ecash::scheme::keygen::KeyPairUser; use nym_compact_ecash::withdrawal_request; use nym_ecash_time::{ecash_today_date, EcashTime}; - use rand_chacha::rand_core::SeedableRng; - use rand_chacha::ChaCha20Rng; - - pub fn test_rng() -> ChaCha20Rng { - let dummy_seed = [42u8; 32]; - ChaCha20Rng::from_seed(dummy_seed) - } + use nym_test_utils::helpers::deterministic_rng; // had some issues with `Date` and serde... // so might as well leave this unit test in case we do something to the helper @@ -821,7 +815,7 @@ mod tests { #[test] fn decoding_attribute_commitments() { - let mut rng = test_rng(); + let mut rng = deterministic_rng(); let keys = ed25519::KeyPair::new(&mut rng); let dummy_sig = keys.private_key().sign("foomp"); let dummy_keypair = KeyPairUser::new(); diff --git a/nym-api/nym-api-requests/src/models/described/type_translation.rs b/nym-api/nym-api-requests/src/models/described/type_translation.rs index 8fe02bdd03..37653a2d14 100644 --- a/nym-api/nym-api-requests/src/models/described/type_translation.rs +++ b/nym-api/nym-api-requests/src/models/described/type_translation.rs @@ -209,6 +209,16 @@ pub struct LewesProtocolDetailsV1 { pub signature: ed25519::Signature, } +impl LewesProtocolDetailsV1 { + pub fn verify(&self, key: &ed25519::PublicKey) -> bool { + let Ok(plaintext) = serde_json::to_string(&self.content) else { + return false; + }; + + key.verify(plaintext, &self.signature).is_ok() + } +} + #[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema, PartialEq)] pub struct LewesProtocolDetailsDataV1 { /// Helper field that specifies whether the LP listener(s) is enabled on this node. @@ -572,10 +582,44 @@ impl TryFrom for SignatureScheme { #[cfg(test)] mod tests { use super::*; + use nym_node_requests::api::SignedLewesProtocol; #[test] fn signature_validity_after_conversion() { + use nym_node_requests::api::v1::lewes_protocol::models::{LPHashFunction, LPKEM}; + + let signing_key = ed25519::PrivateKey::from_bytes(&[42u8; 32]).unwrap(); + let verification_key = signing_key.public_key(); + + let x25519_key = x25519::DHPublicKey::from_bytes(&[42u8; 32]); + + let mut dummy_kems = BTreeMap::new(); + for kem in [LPKEM::McEliece, LPKEM::McEliece] { + let mut kem_digests = BTreeMap::new(); + for sf in [ + LPHashFunction::Blake3, + LPHashFunction::Shake128, + LPHashFunction::Shake256, + LPHashFunction::Sha256, + ] { + kem_digests.insert(sf, "0xdeadbeef".to_string()); + } + dummy_kems.insert(kem, kem_digests); + } + // make sure the serialisation stays the same and signature is still valid - todo!() + let dummy_lp = nym_node_requests::api::v1::lewes_protocol::models::LewesProtocol { + enabled: false, + control_port: 123, + data_port: 345, + x25519: x25519_key, + kem_keys: dummy_kems, + }; + let dummy_signed_lp = SignedLewesProtocol::new(dummy_lp, &signing_key).unwrap(); + // sanity check + assert!(dummy_signed_lp.verify(&verification_key)); + + let converted = LewesProtocolDetailsV1::from(dummy_signed_lp); + assert!(converted.verify(&verification_key)); } } diff --git a/nym-api/nym-api-requests/src/models/described/v2.rs b/nym-api/nym-api-requests/src/models/described/v2.rs index 429fb2ed88..73f1356dbc 100644 --- a/nym-api/nym-api-requests/src/models/described/v2.rs +++ b/nym-api/nym-api-requests/src/models/described/v2.rs @@ -247,7 +247,7 @@ impl From for NymNodeDescriptionV2 { #[cfg(test)] pub fn mock_nym_node_description(seed: u64) -> NymNodeDescriptionV2 { - use crate::models::{LPHashFunction, LPSignatureScheme, LPKEM}; + use nym_node_requests::api::v1::lewes_protocol::models::{LPHashFunction, LPKEM}; use nym_test_utils::helpers::{u64_seeded_rng, RngCore}; let mut rng = u64_seeded_rng(seed); @@ -257,22 +257,33 @@ pub fn mock_nym_node_description(seed: u64) -> NymNodeDescriptionV2 { // just reuse the same x25519 key for everything - this is just a data mock let x25519 = x25519::KeyPair::new(&mut rng); - let mut kem_hashes_wrapper = std::collections::HashMap::new(); - let mut signing_keys_hashes_wrapper = std::collections::HashMap::new(); - let mut kem_hashes = std::collections::HashMap::new(); - let mut signing_keys_hashes = std::collections::HashMap::new(); + let mut dummy_kems = std::collections::BTreeMap::new(); + for kem in [LPKEM::McEliece, LPKEM::McEliece] { + let mut kem_digests = std::collections::BTreeMap::new(); + for (i, sf) in [ + LPHashFunction::Blake3, + LPHashFunction::Shake128, + LPHashFunction::Shake256, + LPHashFunction::Sha256, + ] + .iter() + .enumerate() + { + kem_digests.insert(*sf, hex::encode([((seed + i as u64) % 256) as u8; 32])); + } + dummy_kems.insert(kem, kem_digests); + } - kem_hashes.insert( - LPHashFunction::Sha256, - hex::encode([(seed % 256) as u8; 32]), - ); - kem_hashes_wrapper.insert(LPKEM::X25519, kem_hashes); - - signing_keys_hashes.insert( - LPHashFunction::Sha256, - hex::encode([(seed % 256) as u8; 32]), - ); - signing_keys_hashes_wrapper.insert(LPSignatureScheme::Ed25519, signing_keys_hashes); + // make sure the serialisation stays the same and signature is still valid + let dummy_lp = nym_node_requests::api::v1::lewes_protocol::models::LewesProtocol { + enabled: false, + control_port: 123, + data_port: 345, + x25519: (*x25519.public_key()).into(), + kem_keys: dummy_kems, + }; + let dummy_signed_lp = + nym_node_requests::api::SignedLewesProtocol::new(dummy_lp, ed25519.private_key()).unwrap(); NymNodeDescriptionV2 { node_id: rng.next_u32(), @@ -339,14 +350,7 @@ pub fn mock_nym_node_description(seed: u64) -> NymNodeDescriptionV2 { metadata_port: 456, public_key: x25519.public_key().to_base58_string(), }), - lewes_protocol: Some(LewesProtocolDetailsV1 { - enabled: true, - control_port: 1234, - data_port: 2345, - x25519: *x25519.public_key(), - kem_keys: kem_hashes_wrapper, - signing_keys: signing_keys_hashes_wrapper, - }), + lewes_protocol: Some(dummy_signed_lp.into()), mixnet_websockets: WebSocketsV2 { ws_port: 9000, wss_port: None, diff --git a/nym-gateway-probe/Cargo.toml b/nym-gateway-probe/Cargo.toml index bf8c621e59..b18927be15 100644 --- a/nym-gateway-probe/Cargo.toml +++ b/nym-gateway-probe/Cargo.toml @@ -22,6 +22,7 @@ hex.workspace = true tracing.workspace = true pnet_packet.workspace = true rand.workspace = true +rand09.workspace = true reqwest = { workspace = true, features = ["socks"] } serde.workspace = true serde_json.workspace = true diff --git a/nym-gateway-probe/src/common/helpers.rs b/nym-gateway-probe/src/common/helpers.rs index 5693b8a7a9..7f90fca60a 100644 --- a/nym-gateway-probe/src/common/helpers.rs +++ b/nym-gateway-probe/src/common/helpers.rs @@ -1,12 +1,9 @@ // Copyright 2026 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::common::nodes::TestedNodeLpDetails; -use nym_crypto::asymmetric::ed25519; use nym_ip_packet_requests::v8::response::{ ControlResponse, DataResponse, InfoLevel, IpPacketResponse, IpPacketResponseData, }; -use nym_lp::peer::LpRemotePeer; use nym_sdk::{ DebugConfig, NymApiTopologyProvider, NymApiTopologyProviderConfig, NymNetworkDetails, TopologyProvider, mixnet::ReconstructedMessage, @@ -15,13 +12,6 @@ use nym_topology::NymTopology; use tracing::*; use url::Url; -pub fn to_lp_remote_peer(identity: ed25519::PublicKey, data: TestedNodeLpDetails) -> LpRemotePeer { - LpRemotePeer::new(identity, data.x25519).with_key_digests( - data.expected_kem_key_hashes, - data.expected_signing_key_hashes, - ) -} - pub fn mixnet_debug_config( min_gateway_performance: Option, ignore_egress_epoch_role: bool, diff --git a/nym-gateway-probe/src/common/nodes.rs b/nym-gateway-probe/src/common/nodes.rs index 4e79e84ce6..04b2b10cac 100644 --- a/nym-gateway-probe/src/common/nodes.rs +++ b/nym-gateway-probe/src/common/nodes.rs @@ -3,25 +3,25 @@ use anyhow::{Context, anyhow, bail}; use nym_api_requests::models::{ - AuthenticatorDetailsV1, DeclaredRolesV1, DescribedNodeTypeV1, HostInformationV1, - IpPacketRouterDetailsV1, NetworkRequesterDetailsV1, NymNodeDataV1, - OffsetDateTimeJsonSchemaWrapper, WebSocketsV1, WireguardDetailsV1, + AuthenticatorDetailsV2, DeclaredRolesV2, DescribedNodeTypeV2, HostInformationV2, + IpPacketRouterDetailsV2, NetworkRequesterDetailsV2, NymNodeDataV2, + OffsetDateTimeJsonSchemaWrapper, WebSocketsV2, WireguardDetailsV2, }; use nym_authenticator_requests::AuthenticatorVersion; use nym_bin_common::build_information::BinaryBuildInformationOwned; -use nym_crypto::asymmetric::x25519; use nym_http_api_client::UserAgent; -use nym_kkt_ciphersuite::SignatureScheme; +use nym_kkt_ciphersuite::Ciphersuite; use nym_kkt_ciphersuite::{KEM, KEMKeyDigests}; +use nym_lp::peer::{DHPublicKey, LpRemotePeer}; use nym_network_defaults::DEFAULT_NYM_NODE_HTTP_PORT; use nym_node_requests::api::client::NymNodeApiClientExt; use nym_node_requests::api::v1::node::models::AuxiliaryDetails as NodeAuxiliaryDetails; use nym_sdk::mixnet::NodeIdentity; use nym_sdk::mixnet::Recipient; use nym_validator_client::client::NymApiClientExt; -use nym_validator_client::models::NymNodeDescriptionV1; +use nym_validator_client::models::NymNodeDescriptionV2; use rand::prelude::IteratorRandom; -use std::collections::HashMap; +use std::collections::{BTreeMap, HashMap}; use std::net::{IpAddr, SocketAddr}; use std::time::Duration; use time::OffsetDateTime; @@ -90,7 +90,7 @@ use url::Url; #[derive(Clone)] pub struct DirectoryNode { - described: NymNodeDescriptionV1, + described: NymNodeDescriptionV2, } impl DirectoryNode { @@ -237,11 +237,11 @@ pub async fn query_gateway_by_ip(address: String) -> anyhow::Result anyhow::Result = - nr_result.map(|nr| NetworkRequesterDetailsV1 { + let network_requester: Option = + nr_result.map(|nr| NetworkRequesterDetailsV2 { address: nr.address, uses_exit_policy: false, // Field not availabe, to change if it becomes useful here }); - let ip_packet_router: Option = - ipr_result.map(|ipr| IpPacketRouterDetailsV1 { + let ip_packet_router: Option = + ipr_result.map(|ipr| IpPacketRouterDetailsV2 { address: ipr.address, }); - let authenticator: Option = - authenticator_result.map(|auth| AuthenticatorDetailsV1 { + let authenticator: Option = + authenticator_result.map(|auth| AuthenticatorDetailsV2 { address: auth.address, }); #[allow(deprecated)] - let wireguard: Option = - wireguard_result.map(|wg| WireguardDetailsV1 { + let wireguard: Option = + wireguard_result.map(|wg| WireguardDetailsV2 { port: wg.tunnel_port, // Use tunnel_port for deprecated port field tunnel_port: wg.tunnel_port, metadata_port: wg.metadata_port, @@ -284,14 +284,14 @@ pub async fn query_gateway_by_ip(address: String) -> anyhow::Result anyhow::Result, - pub expected_signing_key_hashes: HashMap, - pub x25519: x25519::PublicKey, + pub expected_kem_key_hashes: BTreeMap, + pub x25519: DHPublicKey, pub lp_version: u8, + pub ciphersuite: Ciphersuite, +} + +impl TestedNodeLpDetails { + pub fn into_remote_peer(self) -> LpRemotePeer { + LpRemotePeer::new(self.x25519).with_key_digests(self.expected_kem_key_hashes) + } } diff --git a/nym-gateway-probe/src/common/probe_tests.rs b/nym-gateway-probe/src/common/probe_tests.rs index 0ee8140f77..158cbfc011 100644 --- a/nym-gateway-probe/src/common/probe_tests.rs +++ b/nym-gateway-probe/src/common/probe_tests.rs @@ -28,10 +28,12 @@ use nym_credentials_interface::{CredentialSpendingData, TicketType}; use nym_crypto::asymmetric::{ed25519, x25519}; use nym_ip_packet_client::IprClientConnect; use nym_ip_packet_requests::{IpPair, codec::MultiIpPacketCodec}; +use nym_lp::peer::DHKeyPair; use nym_registration_client::{LpRegistrationClient, NestedLpSession}; use nym_sdk::NymNetworkDetails; use nym_sdk::mixnet::{MixnetClient, MixnetClientBuilder, NodeIdentity, Recipient, Socks5}; use nym_topology::{HardcodedTopologyProvider, NymTopology}; +use rand09::SeedableRng; use std::{ net::{IpAddr, Ipv4Addr, Ipv6Addr}, sync::Arc, @@ -163,23 +165,25 @@ pub async fn lp_registration_probe( ) -> anyhow::Result { let lp_address = gateway_lp_data.address; let lp_version = gateway_lp_data.lp_version; - let peer = helpers::to_lp_remote_peer(gateway_identity, gateway_lp_data); + let lp_ciphersuite = gateway_lp_data.ciphersuite; + let peer = gateway_lp_data.into_remote_peer(); info!("Starting LP registration probe for gateway at {lp_address}"); let mut lp_outcome = LpProbeResults::default(); // Generate Ed25519 keypair for this connection (X25519 will be derived internally by LP) - let mut rng = rand::thread_rng(); - let client_ed25519_keypair = std::sync::Arc::new(ed25519::KeyPair::new(&mut rng)); + let mut rng09 = rand09::rngs::StdRng::from_os_rng(); + let client_x25519_keypair = Arc::new(DHKeyPair::new(&mut rng09)); // Step 0: Derive X25519 keys from Ed25519 for the gateways // Create LP registration client (uses Ed25519 keys directly, derives X25519 internally) let mut client = LpRegistrationClient::::new_with_default_config( - client_ed25519_keypair, + client_x25519_keypair, peer, lp_address, + lp_ciphersuite, lp_version, ); @@ -209,23 +213,13 @@ pub async fn lp_registration_probe( let wg_keypair = nym_crypto::asymmetric::x25519::KeyPair::new(&mut rng); // Convert gateway identity to ed25519 public key - let gateway_ed25519_pubkey = match nym_crypto::asymmetric::ed25519::PublicKey::from_bytes( - &gateway_identity.to_bytes(), - ) { - Ok(key) => key, - Err(e) => { - let error_msg = format!("Failed to convert gateway identity: {}", e); - error!("{}", error_msg); - lp_outcome.error = Some(error_msg); - return Ok(lp_outcome); - } - }; + let gateway_ed25519_pubkey = gateway_identity; // Register using the new packet-per-connection API (returns GatewayData directly) let ticket_type = TicketType::V1WireguardEntry; let gateway_data = match client .register_dvpn( - &mut rng, + &mut rng09, &wg_keypair, &gateway_ed25519_pubkey, bandwidth_controller, @@ -299,21 +293,26 @@ pub async fn wg_probe_lp( let entry_lp_version = entry_lp_data.lp_version; let exit_lp_version = exit_lp_data.lp_version; + let entry_lp_ciphersuite = entry_lp_data.ciphersuite; + let exit_lp_ciphersuite = exit_lp_data.ciphersuite; + info!("Starting LP-based WireGuard probe (entry→exit via forwarding)"); let mut wg_outcome = WgProbeResults::default(); - // Generate Ed25519 keypairs for LP protocol - let mut rng = rand::thread_rng(); - let entry_lp_keypair = Arc::new(ed25519::KeyPair::new(&mut rng)); - let exit_lp_keypair = Arc::new(ed25519::KeyPair::new(&mut rng)); + // Generate x25519 keypairs for LP protocol + let mut rng09 = rand09::rngs::StdRng::from_os_rng(); + + let entry_lp_keypair = Arc::new(DHKeyPair::new(&mut rng09)); + let exit_lp_keypair = Arc::new(DHKeyPair::new(&mut rng09)); // Generate WireGuard keypairs for VPN registration + let mut rng = rand::rngs::OsRng; let entry_wg_keypair = x25519::KeyPair::new(&mut rng); let exit_wg_keypair = x25519::KeyPair::new(&mut rng); - let entry_peer = helpers::to_lp_remote_peer(entry_gateway.identity, entry_lp_data); - let exit_peer = helpers::to_lp_remote_peer(exit_gateway.identity, exit_lp_data); + let entry_peer = entry_lp_data.into_remote_peer(); + let exit_peer = exit_lp_data.into_remote_peer(); // STEP 1: Establish outer LP session with entry gateway // LpRegistrationClient uses packet-per-connection model - connect() is gone, @@ -323,6 +322,7 @@ pub async fn wg_probe_lp( entry_lp_keypair, entry_peer, entry_address, + entry_lp_ciphersuite, entry_lp_version, ); @@ -335,16 +335,26 @@ pub async fn wg_probe_lp( // STEP 2: Use nested session to register with exit gateway via forwarding info!("Registering with exit gateway via entry forwarding..."); - let mut nested_session = - NestedLpSession::new(exit_address, exit_lp_keypair, exit_peer, exit_lp_version); + let mut nested_session = NestedLpSession::new( + exit_address, + exit_lp_keypair, + exit_peer, + exit_lp_ciphersuite, + exit_lp_version, + ); let exit_gateway_pubkey = exit_gateway.identity; // Perform handshake and registration with exit gateway via forwarding + if let Err(err) = nested_session.perform_handshake(&mut entry_client).await { + error!("Failed to perform handshake with exit gateway: {err}"); + return Ok(wg_outcome); + }; + let exit_gateway_data = match nested_session - .handshake_and_register_dvpn( + .register_dvpn( &mut entry_client, - &mut rng, + &mut rng09, &exit_wg_keypair, &exit_gateway_pubkey, bandwidth_controller, @@ -370,7 +380,7 @@ pub async fn wg_probe_lp( // Use packet-per-connection register() which returns GatewayData directly let entry_gateway_data = match entry_client .register_dvpn( - &mut rng, + &mut rng09, &entry_wg_keypair, &entry_gateway_pubkey, bandwidth_controller, diff --git a/nym-node/Cargo.toml b/nym-node/Cargo.toml index 275b4fda55..3fc765e1fa 100644 --- a/nym-node/Cargo.toml +++ b/nym-node/Cargo.toml @@ -134,6 +134,7 @@ cargo_metadata = { workspace = true } [dev-dependencies] criterion = { workspace = true, features = ["async_tokio"] } +nym-test-utils = { workspace = true } [features] tokio-console = ["console-subscriber", "nym-task/tokio-tracing"] diff --git a/nym-node/nym-node-requests/src/api/mod.rs b/nym-node/nym-node-requests/src/api/mod.rs index b884773edc..d9c9835af0 100644 --- a/nym-node/nym-node-requests/src/api/mod.rs +++ b/nym-node/nym-node-requests/src/api/mod.rs @@ -134,7 +134,6 @@ impl Display for ErrorResponse { #[allow(deprecated)] #[cfg(test)] mod tests { - use super::*; use crate::api::v1::node::models::{HostKeys, SphinxKey}; use nym_crypto::asymmetric::{ed25519, x25519}; @@ -243,7 +242,7 @@ mod tests { #[test] fn dummy_legacy_v3_signed_host_verification() { - let mut rng = rand_chacha::ChaCha20Rng::from_seed([0u8; 32]); + let mut rng = deterministic_rng(); let ed22519 = ed25519::KeyPair::new(&mut rng); let x25519_sphinx = x25519::KeyPair::new(&mut rng); let x25519_noise = x25519::KeyPair::new(&mut rng); @@ -337,7 +336,7 @@ mod tests { #[test] fn dummy_legacy_v2_signed_host_verification() { - let mut rng = rand_chacha::ChaCha20Rng::from_seed([0u8; 32]); + let mut rng = deterministic_rng(); let ed22519 = ed25519::KeyPair::new(&mut rng); let x25519_sphinx = x25519::KeyPair::new(&mut rng); let x25519_noise = x25519::KeyPair::new(&mut rng); @@ -426,7 +425,7 @@ mod tests { #[test] fn dummy_legacy_v1_signed_host_verification() { - let mut rng = rand_chacha::ChaCha20Rng::from_seed([0u8; 32]); + let mut rng = deterministic_rng(); let ed22519 = ed25519::KeyPair::new(&mut rng); let x25519_sphinx = x25519::KeyPair::new(&mut rng); diff --git a/nym-node/src/node/key_rotation/key.rs b/nym-node/src/node/key_rotation/key.rs index 7d31455ae5..350cae8a7f 100644 --- a/nym-node/src/node/key_rotation/key.rs +++ b/nym-node/src/node/key_rotation/key.rs @@ -113,14 +113,11 @@ impl PemStorableKey for SphinxPrivateKey { #[cfg(test)] mod tests { use super::*; - use rand::SeedableRng; - use rand_chacha::ChaCha20Rng; + use nym_test_utils::helpers::deterministic_rng; #[test] fn private_key_bytes_convertion() { - // Set up a deterministic RNG. - let seed = [42u8; 32]; - let mut rng = ChaCha20Rng::from_seed(seed); + let mut rng = deterministic_rng(); let key = SphinxPrivateKey { rotation_id: 42, diff --git a/tools/nym-lp-client/Cargo.toml b/tools/nym-lp-client/Cargo.toml index d3e6b08bd5..e91e052662 100644 --- a/tools/nym-lp-client/Cargo.toml +++ b/tools/nym-lp-client/Cargo.toml @@ -15,6 +15,7 @@ anyhow = { workspace = true } bytes = { workspace = true } clap = { workspace = true, features = ["derive"] } rand = { workspace = true } +rand09 = { workspace = true } rand_chacha = { workspace = true } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } diff --git a/tools/nym-lp-client/src/client.rs b/tools/nym-lp-client/src/client.rs index 3be0bc18b2..881a781392 100644 --- a/tools/nym-lp-client/src/client.rs +++ b/tools/nym-lp-client/src/client.rs @@ -20,7 +20,8 @@ use nym_sphinx_anonymous_replies::requests::{AnonymousSenderTag, RepliableMessag use nym_sphinx_anonymous_replies::{ReplySurb, SurbEncryptionKey}; use nym_sphinx_framing::codec::NymCodec; use nym_sphinx_framing::packet::FramedNymPacket; -use rand_chacha::rand_core::SeedableRng; +use rand::SeedableRng; +use rand09::SeedableRng as SeedableRng09; use rand_chacha::ChaCha8Rng; use std::net::SocketAddr; use std::sync::Arc; @@ -33,7 +34,7 @@ use tracing::{debug, info, trace}; use crate::topology::{GatewayInfo, SpeedtestTopology}; use nym_ip_packet_requests::v8::request::IpPacketRequest; use nym_lp::packet::version; -use nym_lp::peer::LpRemotePeer; +use nym_lp::peer::{DHKeyPair, LpRemotePeer}; use nym_sphinx::forwarding::packet::MixPacket; /// Conv ID for KCP - hash of source and destination addresses @@ -51,6 +52,8 @@ pub struct SpeedtestClient { identity_keypair: Arc, /// Client's x25519 encryption keypair (for SURBs) encryption_keypair: Arc, + /// Client's LP keypair + lp_keypair: Arc, /// Target gateway gateway: GatewayInfo, /// Network topology for routing @@ -86,11 +89,14 @@ impl SpeedtestClient { pub fn new(gateway: GatewayInfo, topology: Arc) -> Self { let identity_keypair = Arc::new(ed25519::KeyPair::new(&mut rand::rngs::OsRng)); let encryption_keypair = Arc::new(x25519::KeyPair::new(&mut rand::rngs::OsRng)); + let mut rng09 = rand09::rngs::StdRng::from_os_rng(); + let lp_keypair = DHKeyPair::new(&mut rng09); let rng = ChaCha8Rng::from_entropy(); Self { identity_keypair, encryption_keypair, + lp_keypair: Arc::new(lp_keypair), gateway, topology, socket: None, @@ -118,16 +124,14 @@ impl SpeedtestClient { self.gateway.lp_address ); - let gw_peer = LpRemotePeer::new(self.gateway.identity, self.gateway.identity.to_x25519()?) - .with_key_digests( - self.gateway.kem_key_hashes.clone(), - self.gateway.signing_key_hashes.clone(), - ); + let gw_peer = LpRemotePeer::new(self.gateway.lp_key) + .with_key_digests(self.gateway.kem_key_hashes.clone()); let mut lp_client = LpRegistrationClient::::new_with_default_config( - self.identity_keypair.clone(), + self.lp_keypair.clone(), gw_peer, self.gateway.lp_address, + self.gateway.ciphersuite, self.gateway.lp_version, ); @@ -165,16 +169,14 @@ impl SpeedtestClient { self.gateway.lp_address ); - let gw_peer = LpRemotePeer::new(self.gateway.identity, self.gateway.identity.to_x25519()?) - .with_key_digests( - self.gateway.kem_key_hashes.clone(), - self.gateway.signing_key_hashes.clone(), - ); + let gw_peer = LpRemotePeer::new(self.gateway.lp_key) + .with_key_digests(self.gateway.kem_key_hashes.clone()); let mut lp_client = LpRegistrationClient::new_with_default_config( - self.identity_keypair.clone(), + self.lp_keypair.clone(), gw_peer, self.gateway.lp_address, + self.gateway.ciphersuite, self.gateway.lp_version, ); @@ -440,61 +442,65 @@ impl SpeedtestClient { if !self.has_lp_session() { bail!("LP session not initialized - call init_lp_session() first"); } + let _ = payload; + let _ = num_surbs; + bail!("lp transport channel is not yet implemented"); - let prepared = self.prepare_sphinx_fragments(payload, num_surbs).await?; - - // Now get mutable references after prepare_sphinx_fragments is done - let lp_client = self.lp_client.as_mut().unwrap(); // safe: checked above - let socket = self.socket.as_ref().context("socket not initialized")?; - let lp_data_address = self.gateway.lp_data_address; - - let mut total_sent = 0usize; - let fragment_count = prepared.fragments.len(); - - for fragment in prepared.fragments { - let nym_packet = NymPacket::sphinx_build( - false, - PacketSize::RegularPacket.payload_size(), - fragment.into_bytes(), - &prepared.route, - &prepared.destination, - &prepared.delays, - )?; - - // Wrap in MixPacket v2: packet_type || key_rotation || next_hop || sphinx_data - let mix_packet = MixPacket::new( - prepared.first_hop_addr, - nym_packet, - PacketType::Mix, - SphinxKeyRotation::Unknown, - ); - - let mix_bytes = mix_packet - .into_v2_bytes() - .context("failed to serialize MixPacket")?; - - // Wrap in LP for UDP data plane - let lp_packet = lp_client - .wrap_data(&mix_bytes) - .context("failed to wrap in LP")?; - - // Send to gateway's LP data port (51264) with timeout - tokio::time::timeout( - Duration::from_secs(5), - socket.send_to(&lp_packet, lp_data_address), - ) - .await - .context("UDP send timed out")? - .context("UDP send failed")?; - total_sent += lp_packet.len(); - } - - info!( - "Sent {} bytes via LP ({} fragments, {} SURBs) to {}", - total_sent, fragment_count, num_surbs, lp_data_address - ); - - Ok(prepared.encryption_keys) + // leave the code for future reference + // let prepared = self.prepare_sphinx_fragments(payload, num_surbs).await?; + // + // // Now get mutable references after prepare_sphinx_fragments is done + // let lp_client = self.lp_client.as_mut().unwrap(); // safe: checked above + // let socket = self.socket.as_ref().context("socket not initialized")?; + // let lp_data_address = self.gateway.lp_data_address; + // + // let mut total_sent = 0usize; + // let fragment_count = prepared.fragments.len(); + // + // for fragment in prepared.fragments { + // let nym_packet = NymPacket::sphinx_build( + // false, + // PacketSize::RegularPacket.payload_size(), + // fragment.into_bytes(), + // &prepared.route, + // &prepared.destination, + // &prepared.delays, + // )?; + // + // // Wrap in MixPacket v2: packet_type || key_rotation || next_hop || sphinx_data + // let mix_packet = MixPacket::new( + // prepared.first_hop_addr, + // nym_packet, + // PacketType::Mix, + // SphinxKeyRotation::Unknown, + // ); + // + // let mix_bytes = mix_packet + // .into_v2_bytes() + // .context("failed to serialize MixPacket")?; + // + // // Wrap in LP for UDP data plane + // let lp_packet = lp_client + // .wrap_data(&mix_bytes) + // .context("failed to wrap in LP")?; + // + // // Send to gateway's LP data port (51264) with timeout + // tokio::time::timeout( + // Duration::from_secs(5), + // socket.send_to(&lp_packet, lp_data_address), + // ) + // .await + // .context("UDP send timed out")? + // .context("UDP send failed")?; + // total_sent += lp_packet.len(); + // } + // + // info!( + // "Sent {} bytes via LP ({} fragments, {} SURBs) to {}", + // total_sent, fragment_count, num_surbs, lp_data_address + // ); + // + // Ok(prepared.encryption_keys) } /// Receive UDP data with timeout diff --git a/tools/nym-lp-client/src/topology.rs b/tools/nym-lp-client/src/topology.rs index 3e0f76b6ff..91fccc9b8c 100644 --- a/tools/nym-lp-client/src/topology.rs +++ b/tools/nym-lp-client/src/topology.rs @@ -10,13 +10,14 @@ use nym_api_requests::models::{LPHashFunction, LPKEM}; use nym_api_requests::nym_nodes::SkimmedNode; use nym_crypto::asymmetric::ed25519; use nym_http_api_client::UserAgent; -use nym_kkt_ciphersuite::{KEMKeyDigests, SignatureScheme, SigningKeyDigests, KEM}; +use nym_kkt_ciphersuite::{Ciphersuite, KEMKeyDigests, SignatureScheme, KEM}; +use nym_lp::peer::DHPublicKey; use nym_sphinx_types::Node as SphinxNode; use nym_topology::{NymRouteProvider, NymTopology, NymTopologyMetadata}; use nym_validator_client::nym_api::NymApiClientExt; use rand::prelude::IteratorRandom; use rand::{CryptoRng, Rng}; -use std::collections::HashMap; +use std::collections::{BTreeMap, HashMap}; use std::net::SocketAddr; use tracing::{debug, info}; use url::Url; @@ -30,8 +31,9 @@ const LP_DATA_PORT: u16 = 51264; #[derive(Debug, Clone)] pub struct GatewayInfo { pub identity: ed25519::PublicKey, - pub kem_key_hashes: HashMap, - pub signing_key_hashes: HashMap, + pub lp_key: DHPublicKey, + pub kem_key_hashes: BTreeMap, + pub ciphersuite: Ciphersuite, pub sphinx_key: nym_crypto::asymmetric::x25519::PublicKey, /// Mix host (IP:port for Sphinx mixing)