Merge remote-tracking branch 'origin' into georgio/lp-psqv2-integration

This commit is contained in:
Georgio Nicolas
2026-01-27 01:52:33 +01:00
32 changed files with 389 additions and 295 deletions
Generated
+2 -2
View File
@@ -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]]
+4 -3
View File
@@ -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" }
+38 -9
View File
@@ -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<HashFunction, Vec<u8>>;
#[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());
}
}
}
+1 -1
View File
@@ -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"] }
+2 -2
View File
@@ -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,
+4 -4
View File
@@ -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,
+1 -3
View File
@@ -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 }
+18 -16
View File
@@ -1,47 +1,49 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// 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<Recipient>,
pub authenticator_address: Option<Recipient>,
pub lp_data: Option<LpData>,
pub lp_data: Option<NymNodeLPInformation>,
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<KEM, KEMKeyDigests>,
pub x25519: x25519::PublicKey,
}
#[derive(Clone, Copy, Debug)]
+5 -5
View File
@@ -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<GatewayData>,
pub gateway_data: Option<WireguardConfiguration>,
/// 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]),
),
+5 -5
View File
@@ -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,
+10 -12
View File
@@ -93,6 +93,9 @@ pub struct GatewayTasksBuilder {
/// ed25519 keypair used to assert one's identity.
identity_keypair: Arc<ed25519::KeyPair>,
/// x25519 keypair used within KTT exchange
x25519_keypair: Arc<x25519::KeyPair>,
/// x25519 (for now, to be changed into MlKem) keypair used for the PSQ derivation
kem_psq_keys: Arc<x25519::KeyPair>,
@@ -124,6 +127,7 @@ impl GatewayTasksBuilder {
pub fn new(
config: Config,
identity: Arc<ed25519::KeyPair>,
x25519: Arc<x25519::KeyPair>,
kem_psq_keys: Arc<x25519::KeyPair>,
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,
-3
View File
@@ -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
+1 -1
View File
@@ -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 }
@@ -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<nym_node_requests::api::v1::lewes_protocol::models::LPHashFunction> fo
}
}
}
impl From<LPKEM> 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<LPHashFunction> 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,
}
}
}
+3 -3
View File
@@ -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<GatewayData, RegistrationError> {
) -> std::result::Result<WireguardConfiguration, RegistrationError> {
debug!("Registering with the wg gateway...");
let pub_key = self.peer_public_key();
@@ -348,7 +348,7 @@ impl AuthenticatorClient {
&self.ip_addr, &registered_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,
+2
View File
@@ -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" }
+66 -85
View File
@@ -1,14 +1,8 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// 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<Recipient>,
authenticator_version: AuthenticatorVersion,
ip_address: Option<IpAddr>,
lp_address: Option<std::net::SocketAddr>,
lp_data: Option<TestedNodeLpDetails>,
}
#[derive(Debug, Clone)]
pub struct TestedNodeLpDetails {
pub address: SocketAddr,
pub expected_kem_key_hashes: HashMap<KEM, KEMKeyDigests>,
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<St>(
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<nym_validator_client::HttpRpcClient>,
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::<TcpStream>::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,
<St as nym_sdk::mixnet::CredentialStorage>::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::<TcpStream>::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())
+36 -23
View File
@@ -1,7 +1,7 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// 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<TestedNodeDetails> {
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,
})
}
}
+109 -27
View File
@@ -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<String>,
// ##########
// 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<String>,
/// 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<String>,
/// 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<String>,
/// 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<String>,
/// 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<String>,
/// 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<SocketAddr>,
// ##########
// 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<String>,
/// 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<String>,
/// 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<String>,
/// 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<String>,
/// 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<String>,
/// 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<String>,
/// 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<String>,
/// 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<String>,
/// 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<String>,
exit_lp_address: Option<SocketAddr>,
/// 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<ProbeResult> {
let args = CliArgs::parse();
if !args.no_log {
@@ -224,21 +267,35 @@ pub(crate) async fn run() -> anyhow::Result<ProbeResult> {
// 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<ProbeResult> {
);
};
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
};
+1 -1
View File
@@ -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:
@@ -122,8 +122,8 @@ impl From<LPHashFunction> 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<nym_kkt_ciphersuite::HashFunction> 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,
}
}
+1 -1
View File
@@ -400,7 +400,6 @@ pub(crate) struct NymNode {
sphinx_key_manager: Option<SphinxKeyManager>,
// to be used when noise is integrated
#[allow(dead_code)]
x25519_noise_keys: Arc<x25519::KeyPair>,
}
@@ -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,
@@ -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<x25519::KeyPair>,
}
-3
View File
@@ -93,9 +93,6 @@ pub enum RegistrationClientError {
#[source]
source: Box<crate::lp_client::LpClientError>,
},
#[error("failed to convert ed25519 pubkey into x25519 pubkey")]
X25519PubkeyConversionFailure,
}
impl RegistrationClientError {
+23 -42
View File
@@ -1,9 +1,8 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// 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),
})?;
@@ -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<S = TcpStream> {
/// Created during handshake initiation. Persists across packet-per-connection calls.
state_machine: Option<LpStateMachine>,
/// 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<ed25519::KeyPair>,
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<ed25519::KeyPair>,
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<S> {
&self.stream
@@ -697,7 +684,7 @@ where
gateway_identity: &ed25519::PublicKey,
bandwidth_controller: &dyn BandwidthTicketProvider,
ticket_type: TicketType,
) -> Result<GatewayData> {
) -> Result<WireguardConfiguration> {
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<GatewayData> {
) -> Result<WireguardConfiguration> {
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<GatewayData> {
) -> Result<WireguardConfiguration> {
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::<TcpStream>::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);
}
}
@@ -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<Vec<u8>,
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)
}
@@ -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<GatewayData>
) -> Result<WireguardConfiguration>
where
S: LpTransport + Unpin,
{
@@ -432,7 +432,7 @@ impl NestedLpSession {
gateway_identity: &ed25519::PublicKey,
bandwidth_controller: &dyn BandwidthTicketProvider,
ticket_type: TicketType,
) -> Result<GatewayData>
) -> Result<WireguardConfiguration>
where
S: LpTransport + Unpin,
{
@@ -585,7 +585,7 @@ impl NestedLpSession {
bandwidth_controller: &dyn BandwidthTicketProvider,
ticket_type: TicketType,
max_retries: u32,
) -> Result<GatewayData>
) -> Result<WireguardConfiguration>
where
S: LpTransport + Unpin,
{
+5 -5
View File
@@ -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<dyn BandwidthTicketProvider>,
}
@@ -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<dyn BandwidthTicketProvider>,
+1
View File
@@ -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",
+1 -1
View File
@@ -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" }
-6
View File
@@ -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();