diff --git a/Cargo.lock b/Cargo.lock index f433bd2986..ea0f41a1ea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5443,6 +5443,7 @@ dependencies = [ "nym-crypto", "nym-ecash-signer-check-types", "nym-ecash-time", + "nym-kkt-ciphersuite", "nym-mixnet-contract-common", "nym-network-defaults", "nym-node-requests", @@ -6500,6 +6501,7 @@ dependencies = [ "nym-http-api-client-macro", "nym-ip-packet-client", "nym-ip-packet-requests", + "nym-kkt-ciphersuite", "nym-lp", "nym-mixnet-contract-common", "nym-network-defaults", @@ -7551,8 +7553,6 @@ dependencies = [ "nym-test-utils", "nym-wireguard-types", "serde", - "time", - "tokio-util", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index e195477930..b52bcac5df 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -432,6 +432,7 @@ nym-http-api-client = { version = "1.20.1", path = "common/http-api-client" } nym-http-api-client-macro = { version = "1.20.1", path = "common/http-api-client-macro" } nym-http-api-common = { version = "1.20.1", path = "common/http-api-common", default-features = false } nym-id = { version = "1.20.1", path = "common/nym-id" } +nym-kkt-ciphersuite = { path = "common/nym-kkt-ciphersuite" } nym-ip-packet-client = { version = "1.20.1", path = "nym-ip-packet-client" } nym-ip-packet-requests = { version = "1.20.1", path = "common/ip-packet-requests" } nym-metrics = { version = "1.20.1", path = "common/nym-metrics" } @@ -482,9 +483,9 @@ nym-vesting-contract-common = { version = "1.20.1", path = "common/cosmwasm-smar nym-verloc = { version = "1.20.1", path = "common/verloc" } nym-wireguard = { version = "1.20.1", path = "common/wireguard" } nym-wireguard-types = { version = "1.20.1", path = "common/wireguard-types" } -nym-wireguard-private-metadata-shared = { version = "1.20.1", path = "common/wireguard-private-metadata/shared" } -nym-wireguard-private-metadata-client = { version = "1.20.1", path = "common/wireguard-private-metadata/client" } -nym-wireguard-private-metadata-server = { version = "1.20.1", path = "common/wireguard-private-metadata/server" } +nym-wireguard-private-metadata-shared = { version = "1.20.1", path = "common/wireguard-private-metadata/shared" } +nym-wireguard-private-metadata-client = { version = "1.20.1", path = "common/wireguard-private-metadata/client" } +nym-wireguard-private-metadata-server = { version = "1.20.1", path = "common/wireguard-private-metadata/server" } nym-sqlx-pool-guard = { version = "1.2.0", path = "nym-sqlx-pool-guard" } nym-wasm-client-core = { version = "1.20.1", path = "common/wasm/client-core" } nym-wasm-storage = { version = "1.20.1", path = "common/wasm/storage" } diff --git a/common/nym-kkt-ciphersuite/src/lib.rs b/common/nym-kkt-ciphersuite/src/lib.rs index c34d40f0bc..6be109febd 100644 --- a/common/nym-kkt-ciphersuite/src/lib.rs +++ b/common/nym-kkt-ciphersuite/src/lib.rs @@ -5,7 +5,7 @@ use crate::error::KKTCiphersuiteError; use num_enum::{IntoPrimitive, TryFromPrimitive}; use std::collections::HashMap; use std::fmt::Display; -use strum_macros::EnumIter; +use strum_macros::{EnumIter, EnumString}; pub mod error; @@ -47,12 +47,15 @@ pub mod xwing { pub type KEMKeyDigests = HashMap>; -#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, IntoPrimitive, TryFromPrimitive, EnumIter)] +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, IntoPrimitive, TryFromPrimitive, EnumIter, EnumString, +)] +#[strum(ascii_case_insensitive)] #[repr(u8)] pub enum HashFunction { Blake3 = 0, - SHAKE256 = 1, - SHAKE128 = 2, + Shake256 = 1, + Shake128 = 2, SHA256 = 3, } @@ -67,8 +70,8 @@ impl HashFunction { hasher.finalize_xof().fill(&mut out); hasher.reset(); } - HashFunction::SHAKE256 => libcrux_sha3::shake256_ema(&mut out, data.as_ref()), - HashFunction::SHAKE128 => libcrux_sha3::shake128_ema(&mut out, data.as_ref()), + HashFunction::Shake256 => libcrux_sha3::shake256_ema(&mut out, data.as_ref()), + HashFunction::Shake128 => libcrux_sha3::shake128_ema(&mut out, data.as_ref()), HashFunction::SHA256 => libcrux_sha3::sha256_ema(&mut out, data.as_ref()), } @@ -80,8 +83,8 @@ impl Display for HashFunction { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str(match self { HashFunction::Blake3 => "blake3", - HashFunction::SHAKE128 => "shake128", - HashFunction::SHAKE256 => "shake256", + HashFunction::Shake128 => "shake128", + HashFunction::Shake256 => "shake256", HashFunction::SHA256 => "sha256", }) } @@ -180,7 +183,10 @@ impl Display for SignatureScheme { } } -#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, IntoPrimitive, TryFromPrimitive)] +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, IntoPrimitive, TryFromPrimitive, EnumIter, EnumString, +)] +#[strum(ascii_case_insensitive)] #[repr(u8)] pub enum KEM { XWing = 0, @@ -335,3 +341,26 @@ impl Display for Ciphersuite { ) } } + +#[cfg(test)] +mod tests { + use super::*; + use std::str::FromStr; + use strum::IntoEnumIterator; + + #[test] + fn kem_display_consistency() { + for kem in KEM::iter() { + let display = format!("{kem}"); + assert_eq!(kem, KEM::from_str(&display).unwrap()); + } + } + + #[test] + fn hash_function_display_consistency() { + for hash_fn in HashFunction::iter() { + let display = format!("{hash_fn}"); + assert_eq!(hash_fn, HashFunction::from_str(&display).unwrap()); + } + } +} diff --git a/common/nym-kkt/Cargo.toml b/common/nym-kkt/Cargo.toml index 1c37c4c36e..a018a9adac 100644 --- a/common/nym-kkt/Cargo.toml +++ b/common/nym-kkt/Cargo.toml @@ -15,7 +15,7 @@ strum = { workspace = true } # internal nym-crypto = { path = "../crypto", features = ["asymmetric", "serde"] } -nym-kkt-ciphersuite = { path = "../nym-kkt-ciphersuite", features = ["digests"] } +nym-kkt-ciphersuite = { workspace = true, features = ["digests"] } libcrux-kem = { git = "https://github.com/cryspen/libcrux" } libcrux-ecdh = { git = "https://github.com/cryspen/libcrux", features = ["codec"] } diff --git a/common/nym-kkt/benches/benches.rs b/common/nym-kkt/benches/benches.rs index e6774542a2..bbcda5ddad 100644 --- a/common/nym-kkt/benches/benches.rs +++ b/common/nym-kkt/benches/benches.rs @@ -57,8 +57,8 @@ pub fn kkt_benchmark(c: &mut Criterion) { for hash_function in [ HashFunction::Blake3, HashFunction::SHA256, - HashFunction::SHAKE128, - HashFunction::SHAKE256, + HashFunction::Shake128, + HashFunction::Shake256, ] { let ciphersuite = Ciphersuite::resolve_ciphersuite( kem, diff --git a/common/nym-kkt/src/lib.rs b/common/nym-kkt/src/lib.rs index c8a033fc69..a9a4fcdaa3 100644 --- a/common/nym-kkt/src/lib.rs +++ b/common/nym-kkt/src/lib.rs @@ -48,8 +48,8 @@ mod test { for hash_function in [ HashFunction::Blake3, HashFunction::SHA256, - HashFunction::SHAKE128, - HashFunction::SHAKE256, + HashFunction::Shake128, + HashFunction::Shake256, ] { let ciphersuite = Ciphersuite::resolve_ciphersuite( kem, @@ -250,8 +250,8 @@ mod test { for hash_function in [ HashFunction::Blake3, HashFunction::SHA256, - HashFunction::SHAKE128, - HashFunction::SHAKE256, + HashFunction::Shake128, + HashFunction::Shake256, ] { let ciphersuite = Ciphersuite::resolve_ciphersuite( kem, diff --git a/common/registration/Cargo.toml b/common/registration/Cargo.toml index 68dd0ad00c..230833559c 100644 --- a/common/registration/Cargo.toml +++ b/common/registration/Cargo.toml @@ -15,7 +15,6 @@ workspace = true [dependencies] bincode = { workspace = true } serde = { workspace = true, features = ["derive"] } -tokio-util.workspace = true nym-authenticator-requests = { workspace = true } nym-credentials-interface = { workspace = true } @@ -23,9 +22,8 @@ nym-crypto = { workspace = true } nym-ip-packet-requests = { workspace = true } nym-sphinx = { workspace = true } nym-wireguard-types = { workspace = true } -nym-kkt-ciphersuite = { path = "../nym-kkt-ciphersuite" } +nym-kkt-ciphersuite = { workspace = true } [dev-dependencies] bincode.workspace = true -time.workspace = true nym-test-utils = { workspace = true } diff --git a/common/registration/src/lib.rs b/common/registration/src/lib.rs index 665b5a7d68..56a3cee0bb 100644 --- a/common/registration/src/lib.rs +++ b/common/registration/src/lib.rs @@ -1,47 +1,49 @@ // Copyright 2025 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use nym_authenticator_requests::AuthenticatorVersion; +use nym_crypto::asymmetric::x25519::serde_helpers::bs58_x25519_pubkey; +use nym_crypto::asymmetric::{ed25519, x25519}; +use nym_ip_packet_requests::IpPair; +use nym_kkt_ciphersuite::{KEM, KEMKeyDigests}; +use nym_sphinx::addressing::Recipient; +use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; -use nym_authenticator_requests::AuthenticatorVersion; -use nym_crypto::asymmetric::x25519::{PublicKey, serde_helpers::bs58_x25519_pubkey}; -use nym_ip_packet_requests::IpPair; -use nym_sphinx::addressing::{NodeIdentity, Recipient}; -use serde::{Deserialize, Serialize}; - -mod lp_messages; -mod serialisation; - pub use lp_messages::{ LpDvpnRegistrationRequest, LpMixnetGatewayData, LpMixnetRegistrationRequest, LpRegistrationData, LpRegistrationRequest, LpRegistrationResponse, RegistrationMode, }; -use nym_kkt_ciphersuite::{KEM, KEMKeyDigests}; pub use serialisation::BincodeError; +mod lp_messages; +mod serialisation; + #[derive(Debug, Clone)] -pub struct NymNode { - pub identity: NodeIdentity, +pub struct NymNodeInformation { + pub identity: ed25519::PublicKey, pub ip_address: IpAddr, pub ipr_address: Option, pub authenticator_address: Option, - pub lp_data: Option, + pub lp_data: Option, pub version: AuthenticatorVersion, } + #[derive(Clone, Debug, Serialize, Deserialize)] -pub struct GatewayData { +pub struct WireguardConfiguration { #[serde(with = "bs58_x25519_pubkey")] - pub public_key: PublicKey, + pub public_key: x25519::PublicKey, pub endpoint: SocketAddr, pub private_ipv4: Ipv4Addr, pub private_ipv6: Ipv6Addr, } #[derive(Clone, Debug)] -pub struct LpData { +pub struct NymNodeLPInformation { pub address: SocketAddr, pub expected_kem_key_hashes: HashMap, + pub x25519: x25519::PublicKey, } #[derive(Clone, Copy, Debug)] diff --git a/common/registration/src/lp_messages.rs b/common/registration/src/lp_messages.rs index 3d62ea2180..1f20710f8a 100644 --- a/common/registration/src/lp_messages.rs +++ b/common/registration/src/lp_messages.rs @@ -3,7 +3,7 @@ //! LP (Lewes Protocol) registration message types shared between client and gateway. -use crate::GatewayData; +use crate::WireguardConfiguration; use crate::serialisation::{BincodeError, BincodeOptions, lp_bincode_serializer}; use nym_credentials_interface::{CredentialSpendingData, TicketType}; use nym_crypto::asymmetric::ed25519; @@ -94,7 +94,7 @@ pub struct LpRegistrationResponse { /// Gateway configuration data for dVPN mode (WireGuard) /// This matches what WireguardRegistrationResult expects - pub gateway_data: Option, + pub gateway_data: Option, /// Gateway data for mixnet mode /// @@ -152,7 +152,7 @@ impl LpRegistrationRequest { impl LpRegistrationResponse { /// Create a success response with GatewayData (for dVPN mode) - pub fn success(allocated_bandwidth: i64, gateway_data: GatewayData) -> Self { + pub fn success(allocated_bandwidth: i64, gateway_data: WireguardConfiguration) -> Self { Self { success: true, error: None, @@ -202,10 +202,10 @@ mod tests { use std::net::Ipv4Addr; // ==================== Helper Functions ==================== - fn create_test_gateway_data() -> GatewayData { + fn create_test_gateway_data() -> WireguardConfiguration { use std::net::Ipv6Addr; - GatewayData { + WireguardConfiguration { public_key: nym_crypto::asymmetric::x25519::PublicKey::from( nym_sphinx::PublicKey::from([1u8; 32]), ), diff --git a/gateway/src/node/lp_listener/registration.rs b/gateway/src/node/lp_listener/registration.rs index 0be9925d21..850f4651a1 100644 --- a/gateway/src/node/lp_listener/registration.rs +++ b/gateway/src/node/lp_listener/registration.rs @@ -18,8 +18,8 @@ use nym_gateway_storage::models::PersistedBandwidth; use nym_gateway_storage::traits::BandwidthGatewayStorage; use nym_metrics::{add_histogram_obs, inc, inc_by}; use nym_registration_common::{ - GatewayData, LpDvpnRegistrationRequest, LpMixnetGatewayData, LpMixnetRegistrationRequest, - LpRegistrationData, LpRegistrationRequest, LpRegistrationResponse, + LpDvpnRegistrationRequest, LpMixnetGatewayData, LpMixnetRegistrationRequest, + LpRegistrationData, LpRegistrationRequest, LpRegistrationResponse, WireguardConfiguration, }; use nym_wireguard::PeerControlRequest; use std::sync::Arc; @@ -185,7 +185,7 @@ async fn check_existing_registration( Some(LpRegistrationResponse::success( bandwidth, - GatewayData { + WireguardConfiguration { public_key: *wg_data.keypair().public_key(), endpoint: wg_data.config().bind_address, private_ipv4, @@ -350,7 +350,7 @@ async fn register_wg_peer( public_key_bytes: &[u8], ticket_type: nym_credentials_interface::TicketType, state: &LpHandlerState, -) -> Result<(GatewayData, i64), GatewayError> { +) -> Result<(WireguardConfiguration, i64), GatewayError> { let Some(wg_controller) = &state.wg_peer_controller else { return Err(GatewayError::ServiceProviderNotRunning { service: "WireGuard".to_string(), @@ -467,7 +467,7 @@ async fn register_wg_peer( // Create GatewayData response (matching authenticator response format) Ok(( - GatewayData { + WireguardConfiguration { public_key: gateway_pubkey, endpoint: gateway_endpoint, private_ipv4: client_ipv4, diff --git a/gateway/src/node/mod.rs b/gateway/src/node/mod.rs index 153b8ba59f..b904143ac6 100644 --- a/gateway/src/node/mod.rs +++ b/gateway/src/node/mod.rs @@ -93,6 +93,9 @@ pub struct GatewayTasksBuilder { /// ed25519 keypair used to assert one's identity. identity_keypair: Arc, + /// x25519 keypair used within KTT exchange + x25519_keypair: Arc, + /// x25519 (for now, to be changed into MlKem) keypair used for the PSQ derivation kem_psq_keys: Arc, @@ -124,6 +127,7 @@ impl GatewayTasksBuilder { pub fn new( config: Config, identity: Arc, + x25519: Arc, kem_psq_keys: Arc, storage: GatewayStorage, mix_packet_sender: MixForwardingSender, @@ -142,6 +146,7 @@ impl GatewayTasksBuilder { wireguard_data: None, user_agent, identity_keypair: identity, + x25519_keypair: x25519, kem_psq_keys, storage, mix_packet_sender, @@ -331,21 +336,14 @@ impl GatewayTasksBuilder { .as_ref() .map(|wg_data| wg_data.inner.peer_tx().clone()); - // We use standard RFC 7748 conversion to derive X25519 keys from Ed25519 identity keys. - // This allows callers to provide only Ed25519 keys (which they already have for signing/identity) - // without needing to manage separate X25519 keypairs. - // - // Security: Ed25519→X25519 conversion is cryptographically sound (RFC 7748). - // The derived X25519 keys are used for: - // - Noise protocol ephemeral DH - // - PSQ ECDH baseline security (pre-quantum) - let x25519_keys = Arc::new(self.identity_keypair.to_x25519()); - let handler_state = lp_listener::LpHandlerState { ecash_verifier: self.ecash_manager().await?, storage: self.storage.clone(), - local_lp_peer: LpLocalPeer::new(self.identity_keypair.clone(), x25519_keys) - .with_kem_psq_key(self.kem_psq_keys.clone()), + local_lp_peer: LpLocalPeer::new( + self.identity_keypair.clone(), + self.x25519_keypair.clone(), + ) + .with_kem_psq_key(self.kem_psq_keys.clone()), metrics: self.metrics.clone(), active_clients_store, wg_peer_controller, diff --git a/integration-tests/src/lp_registration.rs b/integration-tests/src/lp_registration.rs index 1eee3a3b42..6fed38ee7e 100644 --- a/integration-tests/src/lp_registration.rs +++ b/integration-tests/src/lp_registration.rs @@ -392,7 +392,6 @@ mod tests { client_data.base.peer.ed25519().clone(), entry.base.peer.as_remote(), entry.base.socket_addr, - client_data.base.socket_addr.ip(), ); // 1. establish mock connection between client and gateway and retrieve gateway's handle @@ -483,7 +482,6 @@ mod tests { client_data.base.peer.ed25519().clone(), entry.base.peer.as_remote(), entry.base.socket_addr, - client_data.base.socket_addr.ip(), ); // 1. establish mock connection between client and gateway and retrieve gateway's handle @@ -544,7 +542,6 @@ mod tests { client_data.base.peer.ed25519().clone(), entry.base.peer.as_remote(), entry.base.socket_addr, - client_data.base.socket_addr.ip(), ); // START: ENTRY SETUP diff --git a/nym-api/nym-api-requests/Cargo.toml b/nym-api/nym-api-requests/Cargo.toml index 25279ba1cb..edb25c2972 100644 --- a/nym-api/nym-api-requests/Cargo.toml +++ b/nym-api/nym-api-requests/Cargo.toml @@ -49,7 +49,7 @@ nym-noise-keys = { workspace = true } nym-network-defaults = { workspace = true } nym-ticketbooks-merkle = { workspace = true } nym-ecash-signer-check-types = { workspace = true } - +nym-kkt-ciphersuite = { workspace = true } [dev-dependencies] rand_chacha = { workspace = true } 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 54c751ea46..5f1b078cb8 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 @@ -8,6 +8,7 @@ use celes::Country; use nym_crypto::asymmetric::ed25519::serde_helpers::bs58_ed25519_pubkey; use nym_crypto::asymmetric::x25519::serde_helpers::bs58_x25519_pubkey; use nym_crypto::asymmetric::{ed25519, x25519}; +use nym_kkt_ciphersuite::{HashFunction, KEM}; use nym_network_defaults::{WG_METADATA_PORT, WG_TUNNEL_PORT}; use nym_noise_keys::VersionedNoiseKeyV1; use schemars::JsonSchema; @@ -424,3 +425,25 @@ impl From fo } } } + +impl From for KEM { + fn from(value: LPKEM) -> Self { + match value { + LPKEM::MlKem768 => KEM::MlKem768, + LPKEM::XWing => KEM::XWing, + LPKEM::X25519 => KEM::X25519, + LPKEM::McEliece => KEM::McEliece, + } + } +} + +impl From for HashFunction { + fn from(value: LPHashFunction) -> Self { + match value { + LPHashFunction::Blake3 => HashFunction::Blake3, + LPHashFunction::Shake128 => HashFunction::Shake128, + LPHashFunction::Shake256 => HashFunction::Shake256, + LPHashFunction::Sha256 => HashFunction::SHA256, + } + } +} diff --git a/nym-authenticator-client/src/lib.rs b/nym-authenticator-client/src/lib.rs index c09a766696..b214264cd4 100644 --- a/nym-authenticator-client/src/lib.rs +++ b/nym-authenticator-client/src/lib.rs @@ -4,7 +4,7 @@ use nym_authenticator_requests::client_message::QueryMessageImpl; use nym_bandwidth_controller::{BandwidthTicketProvider, DEFAULT_TICKETS_TO_SPEND}; use nym_crypto::asymmetric::x25519::KeyPair; -use nym_registration_common::GatewayData; +use nym_registration_common::WireguardConfiguration; use std::net::{IpAddr, SocketAddr}; use std::sync::Arc; use std::time::Duration; @@ -261,7 +261,7 @@ impl AuthenticatorClient { &mut self, controller: &dyn BandwidthTicketProvider, ticketbook_type: TicketType, - ) -> std::result::Result { + ) -> std::result::Result { debug!("Registering with the wg gateway..."); let pub_key = self.peer_public_key(); @@ -348,7 +348,7 @@ impl AuthenticatorClient { &self.ip_addr, ®istered_data ); - let gateway_data = GatewayData { + let gateway_data = WireguardConfiguration { public_key: registered_data.pub_key().inner().into(), endpoint: SocketAddr::new(self.ip_addr, registered_data.wg_port()), private_ipv4: registered_data.private_ips().ipv4, diff --git a/nym-gateway-probe/Cargo.toml b/nym-gateway-probe/Cargo.toml index 3f8ab3753b..16fe2c08e9 100644 --- a/nym-gateway-probe/Cargo.toml +++ b/nym-gateway-probe/Cargo.toml @@ -65,6 +65,8 @@ nym-node-status-client = { path = "../nym-node-status-api/nym-node-status-client nym-node-requests = { path = "../nym-node/nym-node-requests" } nym-registration-client = { path = "../nym-registration-client" } nym-lp = { path = "../common/nym-lp" } +nym-kkt-ciphersuite = { workspace = true } + nym-mixnet-contract-common = { path = "../common/cosmwasm-smart-contracts/mixnet-contract" } nym-network-defaults = { path = "../common/network-defaults" } nym-registration-common = { path = "../common/registration" } diff --git a/nym-gateway-probe/src/lib.rs b/nym-gateway-probe/src/lib.rs index f3be8e64b9..f6cdb0a7fc 100644 --- a/nym-gateway-probe/src/lib.rs +++ b/nym-gateway-probe/src/lib.rs @@ -1,14 +1,8 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use std::{ - net::{IpAddr, Ipv4Addr, Ipv6Addr}, - sync::Arc, - time::Duration, -}; - use crate::types::Entry; -use anyhow::{Context, bail}; +use anyhow::bail; use base64::{Engine as _, engine::general_purpose}; use bytes::BytesMut; use clap::Args; @@ -39,7 +33,14 @@ use nym_sdk::mixnet::{ NodeIdentity, Recipient, ReconstructedMessage, StoragePaths, }; use rand::rngs::OsRng; +use std::collections::HashMap; +use std::net::SocketAddr; use std::path::PathBuf; +use std::{ + net::{IpAddr, Ipv4Addr, Ipv6Addr}, + sync::Arc, + time::Duration, +}; use tokio::net::TcpStream; use tokio_util::{codec::Decoder, sync::CancellationToken}; use tracing::*; @@ -62,8 +63,11 @@ mod types; use crate::bandwidth_helpers::{acquire_bandwidth, import_bandwidth}; use crate::nodes::{DirectoryNode, NymApiDirectory}; pub use mode::TestMode; +use nym_crypto::asymmetric::{ed25519, x25519}; +use nym_kkt_ciphersuite::{KEM, KEMKeyDigests}; use nym_lp::peer::LpRemotePeer; use nym_node_status_client::models::AttachedTicketMaterials; +use nym_registration_client::{LpRegistrationClient, NestedLpSession}; pub use types::{IpPingReplies, ProbeOutcome, ProbeResult}; #[derive(Args, Clone)] @@ -156,17 +160,23 @@ pub struct TestedNodeDetails { authenticator_address: Option, authenticator_version: AuthenticatorVersion, ip_address: Option, - lp_address: Option, + lp_data: Option, +} + +#[derive(Debug, Clone)] +pub struct TestedNodeLpDetails { + pub address: SocketAddr, + pub expected_kem_key_hashes: HashMap, + pub x25519: x25519::PublicKey, } impl TestedNodeDetails { /// Create from CLI args (localnet mode - no HTTP query needed) - /// Only identity and LP address are required; other fields are None/default. - pub fn from_cli(identity: NodeIdentity, lp_address: std::net::SocketAddr) -> Self { + pub fn from_cli(identity: NodeIdentity, lp_data: TestedNodeLpDetails) -> Self { Self { identity, - ip_address: Some(lp_address.ip()), - lp_address: Some(lp_address), + ip_address: Some(lp_data.address.ip()), + lp_data: Some(lp_data), // These are None in localnet mode - only needed for mixnet/authenticator exit_router_address: None, authenticator_address: None, @@ -176,7 +186,7 @@ impl TestedNodeDetails { /// Check if this node has sufficient info for LP testing pub fn can_test_lp(&self) -> bool { - self.lp_address.is_some() + self.lp_data.is_some() } /// Check if this node has sufficient info for mixnet testing @@ -577,11 +587,8 @@ impl Probe { } // Check if node has LP address - let (lp_address, ip_address) = match (node_info.lp_address, node_info.ip_address) { - (Some(lp_addr), Some(ip_addr)) => (lp_addr, ip_addr), - _ => { - bail!("Gateway does not have LP address configured"); - } + let Some(lp_data) = node_info.lp_data else { + bail!("Gateway does not have LP data configured"); }; info!("Testing LP registration for gateway {}", node_info.identity); @@ -597,15 +604,10 @@ impl Probe { ); // Run LP registration probe - let lp_outcome = lp_registration_probe( - node_info.identity, - lp_address, - ip_address, - &bw_controller, - use_mock_ecash, - ) - .await - .unwrap_or_default(); + let lp_outcome = + lp_registration_probe(node_info.identity, lp_data, &bw_controller, use_mock_ecash) + .await + .unwrap_or_default(); // Return result with only LP outcome Ok(ProbeResult { @@ -925,10 +927,8 @@ impl Probe { }; // Test LP registration if node has LP address - let lp_outcome = if let (Some(lp_address), Some(ip_address)) = - (node_info.lp_address, node_info.ip_address) - { - info!("Node has LP address, testing LP registration..."); + let lp_outcome = if let Some(lp_data) = node_info.lp_data { + info!("Node has LP data, testing LP registration..."); // Prepare bandwidth credential for LP registration let config = nym_validator_client::nyxd::Config::try_from_nym_network_details( @@ -941,15 +941,10 @@ impl Probe { client, ); - let outcome = lp_registration_probe( - node_info.identity, - lp_address, - ip_address, - &bw_controller, - use_mock_ecash, - ) - .await - .unwrap_or_default(); + let outcome = + lp_registration_probe(node_info.identity, lp_data, &bw_controller, use_mock_ecash) + .await + .unwrap_or_default(); Some(outcome) } else { @@ -1081,10 +1076,13 @@ async fn wg_probe( Ok(wg_outcome) } +fn to_lp_remote_peer(identity: ed25519::PublicKey, data: TestedNodeLpDetails) -> LpRemotePeer { + LpRemotePeer::new(identity, data.x25519).with_kem_key_digests(data.expected_kem_key_hashes) +} + async fn lp_registration_probe( gateway_identity: NodeIdentity, - gateway_lp_address: std::net::SocketAddr, - gateway_ip: IpAddr, + gateway_lp_data: TestedNodeLpDetails, bandwidth_controller: &nym_bandwidth_controller::BandwidthController< nym_validator_client::nyxd::NyxdClient, St, @@ -1098,10 +1096,10 @@ where use nym_crypto::asymmetric::ed25519; use nym_registration_client::LpRegistrationClient; - info!( - "Starting LP registration probe for gateway at {}", - gateway_lp_address - ); + let lp_address = gateway_lp_data.address; + let peer = to_lp_remote_peer(gateway_identity, gateway_lp_data); + + info!("Starting LP registration probe for gateway at {lp_address}",); let mut lp_outcome = types::LpProbeResults::default(); @@ -1110,23 +1108,18 @@ where let client_ed25519_keypair = std::sync::Arc::new(ed25519::KeyPair::new(&mut rng)); // Step 0: Derive X25519 keys from Ed25519 for the gateways - let gateway_x25519_key = gateway_identity - .to_x25519() - .context("failed to convert entry ed25519 key to x25519")?; - let peer = LpRemotePeer::new(gateway_identity, gateway_x25519_key); // Create LP registration client (uses Ed25519 keys directly, derives X25519 internally) let mut client = LpRegistrationClient::::new_with_default_config( client_ed25519_keypair, peer, - gateway_lp_address, - gateway_ip, + lp_address, ); // Step 1: Perform handshake (connection is implicit in packet-per-connection model) // LpRegistrationClient uses packet-per-connection model - connect() is gone, // connection is established during handshake and registration automatically. - info!("Performing LP handshake at {}...", gateway_lp_address); + info!("Performing LP handshake at {lp_address}..."); match client.perform_handshake().await { Ok(_) => { info!("LP handshake completed successfully"); @@ -1245,49 +1238,38 @@ where St: nym_sdk::mixnet::CredentialStorage + Clone + Send + Sync + 'static, ::StorageError: Send + Sync, { - use nym_crypto::asymmetric::{ed25519, x25519}; - use nym_registration_client::{LpRegistrationClient, NestedLpSession}; + // Validate that both gateways have required information + let entry_lp_data = entry_gateway + .lp_data + .clone() + .ok_or_else(|| anyhow::anyhow!("Entry gateway missing LP data"))?; + + let exit_lp_data = exit_gateway + .lp_data + .clone() + .ok_or_else(|| anyhow::anyhow!("Exit gateway missing LP data"))?; + + let entry_address = entry_lp_data.address; + let exit_address = exit_lp_data.address; + + let entry_ip = entry_address.ip(); + let exit_ip = exit_address.ip(); info!("Starting LP-based WireGuard probe (entry→exit via forwarding)"); let mut wg_outcome = WgProbeResults::default(); - // Validate that both gateways have required information - let entry_lp_address = entry_gateway - .lp_address - .ok_or_else(|| anyhow::anyhow!("Entry gateway missing LP address"))?; - let exit_lp_address = exit_gateway - .lp_address - .ok_or_else(|| anyhow::anyhow!("Exit gateway missing LP address"))?; - let entry_ip = entry_gateway - .ip_address - .ok_or_else(|| anyhow::anyhow!("Entry gateway missing IP address"))?; - let exit_ip = exit_gateway - .ip_address - .ok_or_else(|| anyhow::anyhow!("Exit gateway missing IP address"))?; - // 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)); - // Step 0: Derive X25519 keys from Ed25519 for the gateways - let entry_x25519_public = entry_gateway - .identity - .to_x25519() - .context("failed to convert entry ed25519 key to x25519")?; - - let exit_x25519_public = exit_gateway - .identity - .to_x25519() - .context("failed to convert exit ed25519 key to x25519")?; - // Generate WireGuard keypairs for VPN registration let entry_wg_keypair = x25519::KeyPair::new(&mut rng); let exit_wg_keypair = x25519::KeyPair::new(&mut rng); - let entry_peer = LpRemotePeer::new(entry_gateway.identity, entry_x25519_public); - let exit_peer = LpRemotePeer::new(exit_gateway.identity, exit_x25519_public); + let entry_peer = to_lp_remote_peer(entry_gateway.identity, entry_lp_data); + let exit_peer = to_lp_remote_peer(exit_gateway.identity, exit_lp_data); // STEP 1: Establish outer LP session with entry gateway // LpRegistrationClient uses packet-per-connection model - connect() is gone, @@ -1296,8 +1278,7 @@ where let mut entry_client = LpRegistrationClient::::new_with_default_config( entry_lp_keypair, entry_peer, - entry_lp_address, - entry_ip, + entry_address, ); // Perform handshake with entry gateway (connection is implicit) @@ -1310,7 +1291,7 @@ where // 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_lp_address.to_string(), exit_lp_keypair, exit_peer); + NestedLpSession::new(exit_address.to_string(), exit_lp_keypair, exit_peer); // Convert exit gateway identity to ed25519 public key for registration let exit_gateway_pubkey = ed25519::PublicKey::from_bytes(&exit_gateway.identity.to_bytes()) diff --git a/nym-gateway-probe/src/nodes.rs b/nym-gateway-probe/src/nodes.rs index bc44e8c035..1eeee1657c 100644 --- a/nym-gateway-probe/src/nodes.rs +++ b/nym-gateway-probe/src/nodes.rs @@ -1,7 +1,7 @@ // Copyright 2025 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::TestedNodeDetails; +use crate::{TestedNodeDetails, TestedNodeLpDetails}; use anyhow::{Context, anyhow, bail}; use nym_api_requests::models::{ AuthenticatorDetailsV2, DeclaredRolesV2, DescribedNodeTypeV2, HostInformationV2, @@ -19,6 +19,7 @@ use nym_validator_client::client::NymApiClientExt; use nym_validator_client::models::NymNodeDescriptionV2; use rand::prelude::IteratorRandom; use std::collections::HashMap; +use std::net::SocketAddr; use std::time::Duration; use time::OffsetDateTime; use tracing::{debug, info, warn}; @@ -96,16 +97,14 @@ impl DirectoryNode { } pub fn to_testable_node(&self) -> anyhow::Result { - let exit_router_address = self - .described - .description + let description = &self.described.description; + + let exit_router_address = description .ip_packet_router .as_ref() .map(|ipr| ipr.address.parse().context("malformed ipr address")) .transpose()?; - let authenticator_address = self - .described - .description + let authenticator_address = description .authenticator .as_ref() .map(|ipr| { @@ -114,32 +113,46 @@ impl DirectoryNode { .context("malformed authenticator address") }) .transpose()?; - let authenticator_version = AuthenticatorVersion::from( - self.described - .description - .build_information - .build_version - .as_str(), - ); - let ip_address = self - .described - .description + let authenticator_version = + AuthenticatorVersion::from(description.build_information.build_version.as_str()); + let ip_address = description .host_information .ip_address .first() - .copied(); + .copied() + .ok_or_else(|| anyhow!("no ip address known"))?; - // Derive LP address from gateway IP + default LP control port (41264) - // TODO: Update this when LP address is exposed in node description API - let lp_address = ip_address.map(|ip| std::net::SocketAddr::new(ip, 41264)); + let lp_data = match ( + description.lewes_protocol.clone(), + description.host_information.keys.x25519_versioned_noise, + ) { + (Some(lp_data), Some(noise_key)) => Some(TestedNodeLpDetails { + address: SocketAddr::new(ip_address, lp_data.control_port), + expected_kem_key_hashes: lp_data + .kem_keys + .into_iter() + .map(|(kem, digests)| { + ( + kem.into(), + digests + .into_iter() + .map(|(hash_fn, digest)| (hash_fn.into(), digest)) + .collect(), + ) + }) + .collect(), + x25519: noise_key.x25519_pubkey, + }), + _ => None, + }; Ok(TestedNodeDetails { identity: self.identity(), exit_router_address, authenticator_address, authenticator_version, - ip_address, - lp_address, + ip_address: Some(ip_address), + lp_data, }) } } diff --git a/nym-gateway-probe/src/run.rs b/nym-gateway-probe/src/run.rs index 6fc3d7083b..085aaae2b1 100644 --- a/nym-gateway-probe/src/run.rs +++ b/nym-gateway-probe/src/run.rs @@ -5,11 +5,15 @@ use anyhow::bail; use clap::{Parser, Subcommand}; use nym_bin_common::bin_info; use nym_config::defaults::setup_env; +use nym_crypto::asymmetric::{ed25519, x25519}; use nym_gateway_probe::nodes::{NymApiDirectory, query_gateway_by_ip}; use nym_gateway_probe::{ CredentialArgs, NetstackArgs, ProbeResult, TestMode, TestedNode, TestedNodeDetails, + TestedNodeLpDetails, }; +use nym_kkt_ciphersuite::{HashFunction, KEM}; use nym_sdk::mixnet::NodeIdentity; +use std::collections::HashMap; use std::net::SocketAddr; use std::path::Path; use std::{path::PathBuf, sync::OnceLock}; @@ -46,32 +50,70 @@ struct CliArgs { #[arg(long, global = true)] gateway_ip: Option, + // ########## + // ENTRY + // ########## + /// Ed25519 identity of the entry gateway (base58 encoded) + /// When provided, skips HTTP API query - use for localnet testing + #[arg(long, global = true)] + entry_gateway_identity: Option, + + /// x25519 key of the entry gateway used for KKT exchange (base58 encoded) + #[arg(long, global = true, requires = "entry_gateway_identity")] + entry_gateway_x25519_key: Option, + + /// expected kem type of the entry gateway used during KKT exchange + #[arg(long, global = true, requires = "entry_gateway_x25519_key")] + entry_gateway_kem_key_type: Option, + + /// expected hash function used for the entry gateway kem key digest + #[arg(long, global = true, requires = "entry_gateway_kem_key_type")] + entry_gateway_kem_key_hash_function: Option, + + /// expected entry gateway kem key digest (base58 encoded) + #[arg(long, global = true, requires = "entry_gateway_kem_key_hash_function")] + entry_gateway_kem_hey_hash_bs58: Option, + + /// LP listener address for entry gateway (e.g., "192.168.66.6:41264") + /// Used with --entry-gateway-identity for localnet mode + #[arg(long, global = true)] + entry_lp_address: Option, + + // ########## + // EXIT + // ########## /// The address of the exit gateway for LP forwarding tests (used with --test-lp-wg) /// When specified, --gateway-ip becomes the entry gateway and this becomes the exit gateway /// Supports formats: IP (192.168.66.5), IP:PORT (192.168.66.5:8080), HOST:PORT (localhost:30004) #[arg(long, global = true)] exit_gateway_ip: Option, - /// Ed25519 identity of the entry gateway (base58 encoded) - /// When provided, skips HTTP API query - use for localnet testing - #[arg(long, global = true)] - entry_gateway_identity: Option, - /// Ed25519 identity of the exit gateway (base58 encoded) /// When provided, skips HTTP API query - use for localnet testing #[arg(long, global = true)] exit_gateway_identity: Option, - /// LP listener address for entry gateway (e.g., "192.168.66.6:41264") - /// Used with --entry-gateway-identity for localnet mode - #[arg(long, global = true)] - entry_lp_address: Option, + /// x25519 key of the exit gateway used for KKT exchange (base58 encoded) + #[arg(long, global = true, requires = "exit_gateway_identity")] + exit_gateway_x25519_key: Option, + + /// expected kem type of the exit gateway used during KKT exchange + #[arg(long, global = true, requires = "exit_gateway_x25519_key")] + exit_gateway_kem_key_type: Option, + + /// expected hash function used for the exit gateway kem key digest + #[arg(long, global = true, requires = "exit_gateway_kem_key_type")] + exit_gateway_kem_key_hash_function: Option, + + /// expected exit gateway kem key digest (base58 encoded) + #[arg(long, global = true, requires = "exit_gateway_kem_key_hash_function")] + exit_gateway_kem_hey_hash_bs58: Option, /// LP listener address for exit gateway (e.g., "172.18.0.5:41264") /// This is the address the entry gateway uses to reach exit (for forwarding) /// Used with --exit-gateway-identity for localnet mode #[arg(long, global = true)] - exit_lp_address: Option, + exit_lp_address: Option, /// Default LP control port when deriving LP address from gateway IP #[arg(long, global = true, default_value = "41264")] @@ -202,6 +244,7 @@ fn mode_to_flags(mode: TestMode) -> (bool, bool, bool) { } } +#[allow(clippy::unwrap_used)] pub(crate) async fn run() -> anyhow::Result { let args = CliArgs::parse(); if !args.no_log { @@ -224,21 +267,35 @@ pub(crate) async fn run() -> anyhow::Result { // 3. Directory mode: uses nym-api directory service // Localnet mode: identity provided via CLI, skip HTTP queries entirely - if let Some(entry_identity_str) = &args.entry_gateway_identity { + if let Some(kem_key_digest) = &args.entry_gateway_kem_hey_hash_bs58 { info!("Using localnet mode with CLI-provided gateway identity"); - let entry_identity = NodeIdentity::from_base58_string(entry_identity_str)?; + // SAFETY: if kem key digest is provided, all other LP data must also be present + // (enforced by clap) + let hash_fn: HashFunction = args + .entry_gateway_kem_key_hash_function + .as_ref() + .unwrap() + .parse()?; + let kem_type: KEM = args.entry_gateway_kem_key_type.as_ref().unwrap().parse()?; + let x25519_key: x25519::PublicKey = + args.entry_gateway_x25519_key.as_ref().unwrap().parse()?; + let identity: ed25519::PublicKey = args.entry_gateway_identity.as_ref().unwrap().parse()?; + let digest = bs58::decode(&kem_key_digest).into_vec()?; + + let mut expected_kem_key_hashes = HashMap::new(); + let mut digests = HashMap::new(); + digests.insert(hash_fn, digest); + expected_kem_key_hashes.insert(kem_type, digests); // Entry LP address: explicit or derived from gateway_ip + lp_port - let entry_lp_addr: SocketAddr = if let Some(lp_addr) = &args.entry_lp_address { + let entry_lp_addr: SocketAddr = if let Some(lp_addr) = args.entry_lp_address { lp_addr - .parse() - .map_err(|e| anyhow::anyhow!("Invalid entry-lp-address '{}': {}", lp_addr, e))? } else if let Some(gw_ip) = &args.gateway_ip { // Derive LP address from gateway IP let ip: std::net::IpAddr = gw_ip .parse() - .map_err(|e| anyhow::anyhow!("Invalid gateway-ip '{}': {}", gw_ip, e))?; + .map_err(|e| anyhow::anyhow!("Invalid gateway-ip '{gw_ip}': {e}"))?; SocketAddr::new(ip, args.lp_port) } else { anyhow::bail!( @@ -246,20 +303,45 @@ pub(crate) async fn run() -> anyhow::Result { ); }; - let entry_details = TestedNodeDetails::from_cli(entry_identity, entry_lp_addr); + let entry_lp_node = TestedNodeLpDetails { + address: entry_lp_addr, + expected_kem_key_hashes, + x25519: x25519_key, + }; + let entry_details = TestedNodeDetails::from_cli(identity, entry_lp_node); // Parse exit gateway if provided - let exit_details = if let Some(exit_identity_str) = &args.exit_gateway_identity { - let exit_identity = NodeIdentity::from_base58_string(exit_identity_str)?; - let exit_lp_addr: SocketAddr = args - .exit_lp_address + let exit_details = if let Some(kem_key_digest) = &args.exit_gateway_kem_hey_hash_bs58 { + let exit_lp_addr = *args.exit_lp_address.as_ref().ok_or_else(|| { + anyhow::anyhow!("--exit-lp-address required with --exit-gateway-identity") + })?; + + // SAFETY: if kem key digest is provided, all other LP data must also be present + // (enforced by clap) + let hash_fn: HashFunction = args + .exit_gateway_kem_key_hash_function .as_ref() - .ok_or_else(|| { - anyhow::anyhow!("--exit-lp-address required with --exit-gateway-identity") - })? - .parse() - .map_err(|e| anyhow::anyhow!("Invalid exit-lp-address: {}", e))?; - Some(TestedNodeDetails::from_cli(exit_identity, exit_lp_addr)) + .unwrap() + .parse()?; + let kem_type: KEM = args.exit_gateway_kem_key_type.as_ref().unwrap().parse()?; + let x25519_key: x25519::PublicKey = + args.exit_gateway_x25519_key.as_ref().unwrap().parse()?; + let identity: ed25519::PublicKey = + args.exit_gateway_identity.as_ref().unwrap().parse()?; + let digest = bs58::decode(&kem_key_digest).into_vec()?; + + let mut expected_kem_key_hashes = HashMap::new(); + let mut digests = HashMap::new(); + digests.insert(hash_fn, digest); + expected_kem_key_hashes.insert(kem_type, digests); + + let exit_lp_node = TestedNodeLpDetails { + address: exit_lp_addr, + expected_kem_key_hashes, + x25519: x25519_key, + }; + + Some(TestedNodeDetails::from_cli(identity, exit_lp_node)) } else { None }; diff --git a/nym-node/nym-node-requests/Cargo.toml b/nym-node/nym-node-requests/Cargo.toml index ec7b6822c8..6c311152ab 100644 --- a/nym-node/nym-node-requests/Cargo.toml +++ b/nym-node/nym-node-requests/Cargo.toml @@ -31,7 +31,7 @@ nym-exit-policy = { workspace = true } nym-noise-keys = { workspace = true } nym-wireguard-types = { workspace = true } nym-upgrade-mode-check = { workspace = true, features = ["openapi"] } -nym-kkt-ciphersuite = { path = "../../common/nym-kkt-ciphersuite" } +nym-kkt-ciphersuite = { workspace = true } # feature-specific dependencies: diff --git a/nym-node/nym-node-requests/src/api/v1/lewes_protocol/models.rs b/nym-node/nym-node-requests/src/api/v1/lewes_protocol/models.rs index 7f41cb9a42..24848610f7 100644 --- a/nym-node/nym-node-requests/src/api/v1/lewes_protocol/models.rs +++ b/nym-node/nym-node-requests/src/api/v1/lewes_protocol/models.rs @@ -122,8 +122,8 @@ impl From for nym_kkt_ciphersuite::HashFunction { fn from(lp_hash_fnction: LPHashFunction) -> Self { match lp_hash_fnction { LPHashFunction::Blake3 => nym_kkt_ciphersuite::HashFunction::Blake3, - LPHashFunction::Shake128 => nym_kkt_ciphersuite::HashFunction::SHAKE128, - LPHashFunction::Shake256 => nym_kkt_ciphersuite::HashFunction::SHAKE256, + LPHashFunction::Shake128 => nym_kkt_ciphersuite::HashFunction::Shake128, + LPHashFunction::Shake256 => nym_kkt_ciphersuite::HashFunction::Shake256, LPHashFunction::Sha256 => nym_kkt_ciphersuite::HashFunction::SHA256, } } @@ -133,8 +133,8 @@ impl From for LPHashFunction { fn from(kem: nym_kkt_ciphersuite::HashFunction) -> Self { match kem { nym_kkt_ciphersuite::HashFunction::Blake3 => LPHashFunction::Blake3, - nym_kkt_ciphersuite::HashFunction::SHAKE128 => LPHashFunction::Shake128, - nym_kkt_ciphersuite::HashFunction::SHAKE256 => LPHashFunction::Shake256, + nym_kkt_ciphersuite::HashFunction::Shake128 => LPHashFunction::Shake128, + nym_kkt_ciphersuite::HashFunction::Shake256 => LPHashFunction::Shake256, nym_kkt_ciphersuite::HashFunction::SHA256 => LPHashFunction::Sha256, } } diff --git a/nym-node/src/node/mod.rs b/nym-node/src/node/mod.rs index 0167b0a253..80eff44f06 100644 --- a/nym-node/src/node/mod.rs +++ b/nym-node/src/node/mod.rs @@ -400,7 +400,6 @@ pub(crate) struct NymNode { sphinx_key_manager: Option, // to be used when noise is integrated - #[allow(dead_code)] x25519_noise_keys: Arc, } @@ -644,6 +643,7 @@ impl NymNode { let mut gateway_tasks_builder = GatewayTasksBuilder::new( config.gateway, self.ed25519_identity_keys.clone(), + self.x25519_noise_keys.clone(), self.entry_gateway.psq_kem_key.clone(), self.entry_gateway.client_storage.clone(), mix_packet_sender, diff --git a/nym-registration-client/src/builder/config.rs b/nym-registration-client/src/builder/config.rs index bf3dff774c..8efedd0847 100644 --- a/nym-registration-client/src/builder/config.rs +++ b/nym-registration-client/src/builder/config.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: GPL-3.0-only use nym_credential_storage::persistent_storage::PersistentStorage; -use nym_registration_common::NymNode; +use nym_registration_common::NymNodeInformation; use nym_sdk::{ DebugConfig, NymNetworkDetails, RememberMe, TopologyProvider, UserAgent, mixnet::{ @@ -25,7 +25,7 @@ const MIXNET_CLIENT_STARTUP_TIMEOUT: Duration = Duration::from_secs(30); #[derive(Clone)] pub struct NymNodeWithKeys { - pub node: NymNode, + pub node: NymNodeInformation, pub keys: Arc, } diff --git a/nym-registration-client/src/error.rs b/nym-registration-client/src/error.rs index 932eb1bf54..449d6d4608 100644 --- a/nym-registration-client/src/error.rs +++ b/nym-registration-client/src/error.rs @@ -93,9 +93,6 @@ pub enum RegistrationClientError { #[source] source: Box, }, - - #[error("failed to convert ed25519 pubkey into x25519 pubkey")] - X25519PubkeyConversionFailure, } impl RegistrationClientError { diff --git a/nym-registration-client/src/lib.rs b/nym-registration-client/src/lib.rs index abf40816f5..8b31d92658 100644 --- a/nym-registration-client/src/lib.rs +++ b/nym-registration-client/src/lib.rs @@ -1,9 +1,8 @@ // Copyright 2025 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use tokio_util::sync::CancellationToken; - use crate::config::RegistrationClientConfig; +use crate::lp_client::helpers::to_lp_remote_peer; use nym_authenticator_client::{AuthClientMixnetListener, AuthenticatorClient}; use nym_bandwidth_controller::BandwidthTicketProvider; use nym_credentials_interface::TicketType; @@ -14,12 +13,7 @@ use nym_sdk::mixnet::{EventReceiver, MixnetClient, Recipient}; use rand::rngs::OsRng; use std::sync::Arc; use tokio::net::TcpStream; - -mod builder; -mod config; -mod error; -mod lp_client; -mod types; +use tokio_util::sync::CancellationToken; pub use builder::RegistrationClientBuilder; pub use builder::config::{ @@ -29,11 +23,16 @@ pub use builder::config::{ pub use config::RegistrationMode; pub use error::RegistrationClientError; pub use lp_client::{LpConfig, LpRegistrationClient, NestedLpSession, error::LpClientError}; -use nym_lp::peer::LpRemotePeer; pub use types::{ LpRegistrationResult, MixnetRegistrationResult, RegistrationResult, WireguardRegistrationResult, }; +mod builder; +mod config; +mod error; +mod lp_client; +mod types; + pub struct RegistrationClient { mixnet_client: MixnetClient, config: RegistrationClientConfig, @@ -168,64 +167,46 @@ impl RegistrationClient { }, )?; - tracing::debug!("Entry gateway LP address: {}", entry_lp_data.address); - tracing::debug!("Exit gateway LP address: {}", exit_lp_data.address); + let entry_address = entry_lp_data.address; + let exit_address = exit_lp_data.address; + + tracing::debug!("Entry gateway LP address: {entry_address}"); + tracing::debug!("Exit gateway LP address: {exit_address}"); // Generate fresh Ed25519 keypairs for LP registration // These are ephemeral and used only for the LP handshake protocol let entry_lp_keypair = Arc::new(ed25519::KeyPair::new(&mut OsRng)); let exit_lp_keypair = Arc::new(ed25519::KeyPair::new(&mut OsRng)); - // Step 1: Derive X25519 keys from Ed25519 for the gateways - let entry_x25519_public = self - .config - .entry - .node - .identity - .to_x25519() - .map_err(|_| RegistrationClientError::X25519PubkeyConversionFailure)?; + let entry_peer = to_lp_remote_peer(self.config.entry.node.identity, entry_lp_data); + let exit_peer = to_lp_remote_peer(self.config.exit.node.identity, exit_lp_data); - let exit_x25519_public = self - .config - .exit - .node - .identity - .to_x25519() - .map_err(|_| RegistrationClientError::X25519PubkeyConversionFailure)?; - - let entry_peer = LpRemotePeer::new(self.config.entry.node.identity, entry_x25519_public) - .with_kem_key_digests(entry_lp_data.expected_kem_key_hashes); - - let exit_peer = LpRemotePeer::new(self.config.exit.node.identity, exit_x25519_public) - .with_kem_key_digests(exit_lp_data.expected_kem_key_hashes); - - // STEP 2: Establish outer session with entry gateway + // STEP 1: Establish outer session with entry gateway // This creates the LP session that will be used to forward packets to exit. // Uses packet-per-connection model: each handshake packet on new TCP connection. tracing::info!("Establishing outer session with entry gateway"); let mut entry_client = LpRegistrationClient::new_with_default_config( entry_lp_keypair.clone(), entry_peer, - entry_lp_data.address, - self.config.entry.node.ip_address, + entry_address, ); // Perform handshake with entry gateway (outer session now established) entry_client.perform_handshake().await.map_err(|source| { RegistrationClientError::EntryGatewayRegisterLp { gateway_id: self.config.entry.node.identity.to_base58_string(), - lp_address: entry_lp_data.address, + lp_address: entry_address, source: Box::new(source), } })?; tracing::info!("Outer session with entry gateway established"); - // STEP 3: Use nested session to register with exit gateway via forwarding + // STEP 2: Use nested session to register with exit gateway via forwarding // This hides the client's IP address from the exit gateway tracing::info!("Registering with exit gateway via entry forwarding"); let mut nested_session = - NestedLpSession::new(exit_lp_data.address.to_string(), exit_lp_keypair, exit_peer); + NestedLpSession::new(exit_address.to_string(), exit_lp_keypair, exit_peer); // Perform handshake and registration with exit gateway (all via entry forwarding) let exit_gateway_data = nested_session @@ -239,13 +220,13 @@ impl RegistrationClient { .await .map_err(|source| RegistrationClientError::ExitGatewayRegisterLp { gateway_id: self.config.exit.node.identity.to_base58_string(), - lp_address: exit_lp_data.address, + lp_address: exit_address, source: Box::new(source), })?; tracing::info!("Exit gateway registration completed via forwarding"); - // STEP 4: Register with entry gateway (packet-per-connection) + // STEP 3: Register with entry gateway (packet-per-connection) tracing::info!("Registering with entry gateway"); let entry_gateway_data = entry_client .register( @@ -257,7 +238,7 @@ impl RegistrationClient { .await .map_err(|source| RegistrationClientError::EntryGatewayRegisterLp { gateway_id: self.config.entry.node.identity.to_base58_string(), - lp_address: entry_lp_data.address, + lp_address: entry_address, source: Box::new(source), })?; diff --git a/nym-registration-client/src/lp_client/client.rs b/nym-registration-client/src/lp_client/client.rs index b5264000d4..42c2489974 100644 --- a/nym-registration-client/src/lp_client/client.rs +++ b/nym-registration-client/src/lp_client/client.rs @@ -19,9 +19,9 @@ use nym_lp::message::ForwardPacketData; use nym_lp::peer::{LpLocalPeer, LpRemotePeer}; use nym_lp::state_machine::{LpAction, LpData, LpInput, LpStateMachine}; use nym_lp_transport::traits::LpTransport; -use nym_registration_common::{GatewayData, LpRegistrationRequest}; +use nym_registration_common::{LpRegistrationRequest, WireguardConfiguration}; use nym_wireguard_types::PeerPublicKey; -use std::net::{IpAddr, SocketAddr}; +use std::net::SocketAddr; use std::sync::Arc; use std::time::{SystemTime, UNIX_EPOCH}; use tokio::io::{AsyncReadExt, AsyncWriteExt}; @@ -54,9 +54,6 @@ pub struct LpRegistrationClient { /// Created during handshake initiation. Persists across packet-per-connection calls. state_machine: Option, - /// Client's IP address for registration metadata. - client_ip: IpAddr, - /// Configuration for timeouts and TCP parameters. config: LpConfig, @@ -75,7 +72,6 @@ where /// * `local_ed25519_keypair` - Client's Ed25519 identity keypair /// * `gateway_lp_peer` - Encapsulates all the gateway keys needed for the Lewes Protocol /// * `gateway_lp_address` - Gateway's LP listener socket address - /// * `client_ip` - Client IP address for registration /// * `config` - Configuration for timeouts and TCP parameters (use `LpConfig::default()`) /// /// # Note @@ -84,7 +80,6 @@ where local_ed25519_keypair: Arc, gateway_lp_peer: LpRemotePeer, gateway_lp_address: SocketAddr, - client_ip: IpAddr, config: LpConfig, ) -> Self { let local_x25519_keypair = local_ed25519_keypair.to_x25519(); @@ -94,7 +89,6 @@ where gateway_lp_peer, gateway_lp_address, state_machine: None, - client_ip, config, stream: None, } @@ -115,13 +109,11 @@ where local_ed25519_keypair: Arc, gateway_lp_peer: LpRemotePeer, gateway_lp_address: SocketAddr, - client_ip: IpAddr, ) -> Self { Self::new( local_ed25519_keypair, gateway_lp_peer, gateway_lp_address, - client_ip, LpConfig::default(), ) } @@ -140,11 +132,6 @@ where self.gateway_lp_address } - /// Returns the client's IP address. - pub fn client_ip(&self) -> IpAddr { - self.client_ip - } - /// Returns reference to the established connection between the client and the gateway. pub fn connection(&self) -> &Option { &self.stream @@ -697,7 +684,7 @@ where gateway_identity: &ed25519::PublicKey, bandwidth_controller: &dyn BandwidthTicketProvider, ticket_type: TicketType, - ) -> Result { + ) -> Result { tracing::debug!("Acquiring bandwidth credential for registration"); // Get bandwidth credential from controller @@ -739,7 +726,7 @@ where wg_keypair: &x25519::KeyPair, credential: CredentialSpendingData, ticket_type: TicketType, - ) -> Result { + ) -> Result { tracing::debug!("Sending registration request (persistent connection)"); // 1. Build registration request @@ -885,7 +872,7 @@ where bandwidth_controller: &dyn BandwidthTicketProvider, ticket_type: TicketType, max_retries: u32, - ) -> Result { + ) -> Result { tracing::debug!("Starting resilient registration (max_retries={max_retries})",); // Acquire credential ONCE before any attempts @@ -1179,17 +1166,14 @@ mod tests { let gateway_peer = LpRemotePeer::new(*gateway_ed_keys.public_key(), *gateway_x_keys.public_key()); let address = "127.0.0.1:41264".parse().unwrap(); - let client_ip = "192.168.1.100".parse().unwrap(); let client = LpRegistrationClient::::new_with_default_config( keypair, gateway_peer, address, - client_ip, ); assert!(!client.is_handshake_complete()); assert_eq!(client.gateway_address(), address); - assert_eq!(client.client_ip(), client_ip); } } diff --git a/nym-registration-client/src/lp_client/helpers.rs b/nym-registration-client/src/lp_client/helpers.rs index 6eb1a0f487..2637ea0110 100644 --- a/nym-registration-client/src/lp_client/helpers.rs +++ b/nym-registration-client/src/lp_client/helpers.rs @@ -2,9 +2,13 @@ // SPDX-License-Identifier: Apache-2.0 use crate::LpClientError; +use nym_crypto::asymmetric::ed25519; use nym_lp::message::ForwardPacketData; +use nym_lp::peer::LpRemotePeer; use nym_lp::state_machine::{LpAction, LpData, LpDataKind, LpInput}; -use nym_registration_common::{LpRegistrationRequest, LpRegistrationResponse}; +use nym_registration_common::{ + LpRegistrationRequest, LpRegistrationResponse, NymNodeLPInformation, +}; pub(crate) fn convert_registration_request( request: LpRegistrationRequest, @@ -81,3 +85,10 @@ pub(crate) fn try_convert_forward_response(action: LpAction) -> Result, Ok(response_data.content.into()) } + +pub(crate) fn to_lp_remote_peer( + identity: ed25519::PublicKey, + data: NymNodeLPInformation, +) -> LpRemotePeer { + LpRemotePeer::new(identity, data.x25519).with_kem_key_digests(data.expected_kem_key_hashes) +} diff --git a/nym-registration-client/src/lp_client/nested_session.rs b/nym-registration-client/src/lp_client/nested_session.rs index df1394a5cc..4d74105999 100644 --- a/nym-registration-client/src/lp_client/nested_session.rs +++ b/nym-registration-client/src/lp_client/nested_session.rs @@ -30,7 +30,7 @@ use nym_lp::peer::{LpLocalPeer, LpRemotePeer}; use nym_lp::state_machine::{LpAction, LpInput, LpStateMachine}; use nym_lp::{LpMessage, LpPacket}; use nym_lp_transport::traits::LpTransport; -use nym_registration_common::{GatewayData, LpRegistrationRequest}; +use nym_registration_common::{LpRegistrationRequest, WireguardConfiguration}; use nym_wireguard_types::PeerPublicKey; use std::sync::Arc; use std::time::{SystemTime, UNIX_EPOCH}; @@ -293,7 +293,7 @@ impl NestedLpSession { wg_keypair: &x25519::KeyPair, credential: nym_credentials_interface::CredentialSpendingData, ticket_type: TicketType, - ) -> Result + ) -> Result where S: LpTransport + Unpin, { @@ -432,7 +432,7 @@ impl NestedLpSession { gateway_identity: &ed25519::PublicKey, bandwidth_controller: &dyn BandwidthTicketProvider, ticket_type: TicketType, - ) -> Result + ) -> Result where S: LpTransport + Unpin, { @@ -585,7 +585,7 @@ impl NestedLpSession { bandwidth_controller: &dyn BandwidthTicketProvider, ticket_type: TicketType, max_retries: u32, - ) -> Result + ) -> Result where S: LpTransport + Unpin, { diff --git a/nym-registration-client/src/types.rs b/nym-registration-client/src/types.rs index ad387d7b5b..9d38bebc59 100644 --- a/nym-registration-client/src/types.rs +++ b/nym-registration-client/src/types.rs @@ -3,7 +3,7 @@ use nym_authenticator_client::{AuthClientMixnetListenerHandle, AuthenticatorClient}; use nym_bandwidth_controller::BandwidthTicketProvider; -use nym_registration_common::{AssignedAddresses, GatewayData}; +use nym_registration_common::{AssignedAddresses, WireguardConfiguration}; use nym_sdk::mixnet::{EventReceiver, MixnetClient}; pub enum RegistrationResult { @@ -21,8 +21,8 @@ pub struct MixnetRegistrationResult { pub struct WireguardRegistrationResult { pub entry_gateway_client: AuthenticatorClient, pub exit_gateway_client: AuthenticatorClient, - pub entry_gateway_data: GatewayData, - pub exit_gateway_data: GatewayData, + pub entry_gateway_data: WireguardConfiguration, + pub exit_gateway_data: WireguardConfiguration, pub authenticator_listener_handle: AuthClientMixnetListenerHandle, pub bw_controller: Box, } @@ -39,10 +39,10 @@ pub struct WireguardRegistrationResult { /// * `bw_controller` - Bandwidth ticket provider for credential management pub struct LpRegistrationResult { /// Gateway configuration data from entry gateway - pub entry_gateway_data: GatewayData, + pub entry_gateway_data: WireguardConfiguration, /// Gateway configuration data from exit gateway - pub exit_gateway_data: GatewayData, + pub exit_gateway_data: WireguardConfiguration, /// Bandwidth controller for credential management pub bw_controller: Box, diff --git a/nym-wallet/Cargo.lock b/nym-wallet/Cargo.lock index bc4614b5ef..6f4ebfbc81 100644 --- a/nym-wallet/Cargo.lock +++ b/nym-wallet/Cargo.lock @@ -4229,6 +4229,7 @@ dependencies = [ "nym-crypto", "nym-ecash-signer-check-types", "nym-ecash-time", + "nym-kkt-ciphersuite", "nym-mixnet-contract-common", "nym-network-defaults", "nym-node-requests", diff --git a/tools/nym-lp-client/Cargo.toml b/tools/nym-lp-client/Cargo.toml index 1c6b18fc43..d3e6b08bd5 100644 --- a/tools/nym-lp-client/Cargo.toml +++ b/tools/nym-lp-client/Cargo.toml @@ -28,7 +28,7 @@ url = { workspace = true } # Nym crates nym-api-requests = { path = "../../nym-api/nym-api-requests" } nym-crypto = { path = "../../common/crypto" } -nym-kkt-ciphersuite = { path = "../../common/nym-kkt-ciphersuite" } +nym-kkt-ciphersuite = { workspace = true } nym-http-api-client = { path = "../../common/http-api-client" } nym-kcp = { path = "../../common/nym-kcp" } nym-lp = { path = "../../common/nym-lp" } diff --git a/tools/nym-lp-client/src/client.rs b/tools/nym-lp-client/src/client.rs index 744bc14dcb..7dfd2122ee 100644 --- a/tools/nym-lp-client/src/client.rs +++ b/tools/nym-lp-client/src/client.rs @@ -117,8 +117,6 @@ impl SpeedtestClient { self.gateway.lp_address ); - let client_ip = "0.0.0.0".parse()?; - let gw_peer = LpRemotePeer::new(self.gateway.identity, self.gateway.identity.to_x25519()?) .with_kem_key_digests(self.gateway.kem_key_hashes.clone()); @@ -126,7 +124,6 @@ impl SpeedtestClient { self.identity_keypair.clone(), gw_peer, self.gateway.lp_address, - client_ip, ); let start = Instant::now(); @@ -163,8 +160,6 @@ impl SpeedtestClient { self.gateway.lp_address ); - let client_ip = "0.0.0.0".parse()?; - let gw_peer = LpRemotePeer::new(self.gateway.identity, self.gateway.identity.to_x25519()?) .with_kem_key_digests(self.gateway.kem_key_hashes.clone()); @@ -172,7 +167,6 @@ impl SpeedtestClient { self.identity_keypair.clone(), gw_peer, self.gateway.lp_address, - client_ip, ); let start = Instant::now();