From 1a3c1fa46689917b7dbbc27d91868477e32f2666 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Fri, 16 Jan 2026 08:58:10 +0000 Subject: [PATCH 01/51] standarise lp serialisation: (#6324) * standarise lp serialisation: - stop using bincode within `LpMessage` in favour of predictable bytes concatenation - use consistent encode/decode interface for every `LpMessage` inner variant - hide usage of bincode within `LpRegistrationResponse` / `LpRegistrationResponse` behind `serialise` / `try_deserialise` interface * reduced 'target_lp_address' len encoding space from u32 to u16 --- Cargo.lock | 1 - common/nym-lp/Cargo.toml | 1 - common/nym-lp/src/codec.rs | 89 +---- common/nym-lp/src/lib.rs | 6 +- common/nym-lp/src/message.rs | 354 ++++++++++++++---- common/registration/Cargo.toml | 1 + common/registration/src/lib.rs | 14 +- common/registration/src/lp_messages.rs | 21 ++ .../src/serialisation.rs | 0 gateway/src/node/lp_listener/handler.rs | 37 +- .../src/lp_client/client.rs | 21 +- .../src/lp_client/error.rs | 2 +- .../src/lp_client/nested_session.rs | 33 +- 13 files changed, 360 insertions(+), 220 deletions(-) rename common/{nym-lp => registration}/src/serialisation.rs (100%) diff --git a/Cargo.lock b/Cargo.lock index 35d8872605..682381c7b8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6686,7 +6686,6 @@ name = "nym-lp" version = "0.1.0" dependencies = [ "ansi_term", - "bincode", "bs58", "bytes", "chacha20poly1305", diff --git a/common/nym-lp/Cargo.toml b/common/nym-lp/Cargo.toml index 50fec3ac36..c7ea8fc19a 100644 --- a/common/nym-lp/Cargo.toml +++ b/common/nym-lp/Cargo.toml @@ -5,7 +5,6 @@ edition = { workspace = true } license = { workspace = true } [dependencies] -bincode = { workspace = true } thiserror = { workspace = true } parking_lot = { workspace = true } snow = { workspace = true } diff --git a/common/nym-lp/src/codec.rs b/common/nym-lp/src/codec.rs index 08c98b1221..1a6b0e05ad 100644 --- a/common/nym-lp/src/codec.rs +++ b/common/nym-lp/src/codec.rs @@ -2,13 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 use crate::LpError; -use crate::message::{ - ClientHelloData, EncryptedDataPayload, ForwardPacketData, HandshakeData, KKTRequestData, - KKTResponseData, LpMessage, MessageType, SubsessionKK1Data, SubsessionKK2Data, - SubsessionReadyData, -}; +use crate::message::{LpMessage, MessageType}; use crate::packet::{LpHeader, LpPacket, OuterHeader, TRAILER_LEN}; -use crate::serialisation::{BincodeOptions, lp_bincode_serializer}; use bytes::{BufMut, BytesMut}; use chacha20poly1305::{ ChaCha20Poly1305, Key, Nonce, Tag, @@ -96,87 +91,7 @@ fn parse_message_from_type_and_content( let message_type = MessageType::from_u32(msg_type_raw) .ok_or_else(|| LpError::invalid_message_type(msg_type_raw))?; - match message_type { - MessageType::Busy => { - if !content.is_empty() { - return Err(LpError::InvalidPayloadSize { - expected: 0, - actual: content.len(), - }); - } - Ok(LpMessage::Busy) - } - MessageType::Handshake => Ok(LpMessage::Handshake(HandshakeData(content.to_vec()))), - MessageType::EncryptedData => Ok(LpMessage::EncryptedData(EncryptedDataPayload( - content.to_vec(), - ))), - MessageType::ClientHello => { - let data = ClientHelloData::decode(content)?; - Ok(LpMessage::ClientHello(data)) - } - MessageType::KKTRequest => Ok(LpMessage::KKTRequest(KKTRequestData(content.to_vec()))), - MessageType::KKTResponse => Ok(LpMessage::KKTResponse(KKTResponseData(content.to_vec()))), - MessageType::ForwardPacket => { - let data: ForwardPacketData = lp_bincode_serializer() - .deserialize(content) - .map_err(|e| LpError::DeserializationError(e.to_string()))?; - Ok(LpMessage::ForwardPacket(data)) - } - MessageType::Collision => { - if !content.is_empty() { - return Err(LpError::InvalidPayloadSize { - expected: 0, - actual: content.len(), - }); - } - Ok(LpMessage::Collision) - } - MessageType::Ack => { - if !content.is_empty() { - return Err(LpError::InvalidPayloadSize { - expected: 0, - actual: content.len(), - }); - } - Ok(LpMessage::Ack) - } - MessageType::SubsessionRequest => { - if !content.is_empty() { - return Err(LpError::InvalidPayloadSize { - expected: 0, - actual: content.len(), - }); - } - Ok(LpMessage::SubsessionRequest) - } - MessageType::SubsessionKK1 => { - let data: SubsessionKK1Data = lp_bincode_serializer() - .deserialize(content) - .map_err(|e| LpError::DeserializationError(e.to_string()))?; - Ok(LpMessage::SubsessionKK1(data)) - } - MessageType::SubsessionKK2 => { - let data: SubsessionKK2Data = lp_bincode_serializer() - .deserialize(content) - .map_err(|e| LpError::DeserializationError(e.to_string()))?; - Ok(LpMessage::SubsessionKK2(data)) - } - MessageType::SubsessionReady => { - let data: SubsessionReadyData = lp_bincode_serializer() - .deserialize(content) - .map_err(|e| LpError::DeserializationError(e.to_string()))?; - Ok(LpMessage::SubsessionReady(data)) - } - MessageType::SubsessionAbort => { - // Empty signal message - no content to deserialize - if !content.is_empty() { - return Err(LpError::DeserializationError( - "SubsessionAbort should have no payload".to_string(), - )); - } - Ok(LpMessage::SubsessionAbort) - } - } + LpMessage::decode_content(content, message_type) } /// Parse only the outer header from raw packet bytes. diff --git a/common/nym-lp/src/lib.rs b/common/nym-lp/src/lib.rs index 9cedea941a..d33082cd80 100644 --- a/common/nym-lp/src/lib.rs +++ b/common/nym-lp/src/lib.rs @@ -14,6 +14,7 @@ pub mod replay; pub mod session; mod session_integration; pub mod session_manager; +pub mod state_machine; pub use config::LpConfig; pub use error::LpError; @@ -22,11 +23,6 @@ pub use packet::{BOOTSTRAP_RECEIVER_IDX, LpPacket, OuterHeader}; pub use replay::{ReceivingKeyCounterValidator, ReplayError}; pub use session::{LpSession, generate_fresh_salt}; pub use session_manager::SessionManager; - -// Add the new state machine module -pub mod serialisation; -pub mod state_machine; - pub use state_machine::LpStateMachine; pub const NOISE_PATTERN: &str = "Noise_XKpsk3_25519_ChaChaPoly_SHA256"; diff --git a/common/nym-lp/src/message.rs b/common/nym-lp/src/message.rs index 09e31c99e0..44606d95d9 100644 --- a/common/nym-lp/src/message.rs +++ b/common/nym-lp/src/message.rs @@ -1,7 +1,6 @@ // Copyright 2025 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::serialisation::{BincodeOptions, lp_bincode_serializer}; use crate::{BOOTSTRAP_RECEIVER_IDX, LpError}; use bytes::{BufMut, BytesMut}; use num_enum::{IntoPrimitive, TryFromPrimitive}; @@ -23,8 +22,13 @@ pub struct ClientHelloData { } impl ClientHelloData { + // 4 bytes for receiver index + 32 bytes for client lp key, 32 bytes for client ed25519 key + 32 bytes for salt pub const LEN: usize = 100; + fn len(&self) -> usize { + Self::LEN + } + fn generate_receiver_index() -> u32 { loop { let candidate = rand::random(); @@ -74,14 +78,13 @@ impl ClientHelloData { u64::from_le_bytes(timestamp_bytes) } - pub fn encode(&self) -> Vec { - let mut out = Vec::with_capacity(Self::LEN); - out.put_u32_le(self.receiver_index); - out.put_slice(&self.client_lp_public_key); - out.put_slice(&self.client_ed25519_public_key); - out.put_slice(&self.salt); - out + pub fn encode(&self, dst: &mut BytesMut) { + dst.put_u32_le(self.receiver_index); + dst.put_slice(&self.client_lp_public_key); + dst.put_slice(&self.client_ed25519_public_key); + dst.put_slice(&self.salt); } + pub fn decode(b: &[u8]) -> Result { if b.len() != Self::LEN { return Err(LpError::DeserializationError(format!( @@ -141,23 +144,80 @@ impl MessageType { #[derive(Debug, Clone, PartialEq, Eq)] pub struct HandshakeData(pub Vec); +impl HandshakeData { + fn len(&self) -> usize { + self.0.len() + } + + fn encode(&self, dst: &mut BytesMut) { + dst.put_slice(&self.0); + } + + fn decode(bytes: &[u8]) -> Result { + Ok(HandshakeData(bytes.to_vec())) + } +} + #[derive(Debug, Clone, PartialEq, Eq)] pub struct EncryptedDataPayload(pub Vec); +impl EncryptedDataPayload { + fn len(&self) -> usize { + self.0.len() + } + + fn encode(&self, dst: &mut BytesMut) { + dst.put_slice(&self.0); + } + + fn decode(bytes: &[u8]) -> Result { + Ok(EncryptedDataPayload(bytes.to_vec())) + } +} + /// KKT request frame data (serialized KKTFrame bytes) #[derive(Debug, Clone, PartialEq, Eq)] pub struct KKTRequestData(pub Vec); +impl KKTRequestData { + fn len(&self) -> usize { + self.0.len() + } + + fn encode(&self, dst: &mut BytesMut) { + dst.put_slice(&self.0); + } + + fn decode(bytes: &[u8]) -> Result { + Ok(KKTRequestData(bytes.to_vec())) + } +} + /// KKT response frame data (serialized KKTFrame bytes) #[derive(Debug, Clone, PartialEq, Eq)] pub struct KKTResponseData(pub Vec); +impl KKTResponseData { + fn len(&self) -> usize { + self.0.len() + } + + fn encode(&self, dst: &mut BytesMut) { + dst.put_slice(&self.0); + } + + fn decode(bytes: &[u8]) -> Result { + Ok(KKTResponseData(bytes.to_vec())) + } +} + /// Packet forwarding request with embedded inner LP packet -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone)] pub struct ForwardPacketData { /// Target gateway's Ed25519 identity (32 bytes) pub target_gateway_identity: [u8; 32], + // TODO: replace it with `SocketAddr` /// Target gateway's LP address (IP:port string) pub target_lp_address: String, @@ -166,27 +226,157 @@ pub struct ForwardPacketData { pub inner_packet_bytes: Vec, } +impl ForwardPacketData { + fn len(&self) -> usize { + // 32 bytes target gateway identity + // + + // 4 bytes length of target lp address + // + + // target_lp_address.len() + // + + // 4 bytes of length of inner packet bytes + // + + // inner_packet_bytes.len() + 32 + 4 + self.target_lp_address.len() + 4 + self.inner_packet_bytes.len() + } + + fn encode(&self, dst: &mut BytesMut) { + dst.put_slice(&self.target_gateway_identity); + dst.put_u16_le(self.target_lp_address.len() as u16); + dst.put_slice(self.target_lp_address.as_bytes()); + dst.put_u32_le(self.inner_packet_bytes.len() as u32); + dst.put_slice(&self.inner_packet_bytes); + } + + pub fn to_bytes(&self) -> Vec { + let mut buf = BytesMut::new(); + self.encode(&mut buf); + buf.into() + } + + pub fn decode(bytes: &[u8]) -> Result { + // smallest possible packet with empty address and empty data + if bytes.len() < 38 { + return Err(LpError::DeserializationError(format!( + "Too few bytes to deserialise ForwardPacketData[1]. got {}", + bytes.len() + ))); + } + // SAFETY: we ensured we have sufficient data + #[allow(clippy::unwrap_used)] + let target_gateway_identity = bytes[0..32].try_into().unwrap(); + let target_lp_address_len = u16::from_le_bytes([bytes[32], bytes[33]]); + + // smallest possible packet with empty data + if bytes[34..].len() < 4 + target_lp_address_len as usize { + return Err(LpError::DeserializationError(format!( + "Too few bytes to deserialise ForwardPacketData[2]. got {}", + bytes.len() + ))); + } + + let target_lp_address = + String::from_utf8_lossy(&bytes[34..34 + target_lp_address_len as usize]).to_string(); + let inner_packet_bytes_len = u32::from_le_bytes([ + bytes[34 + target_lp_address_len as usize], + bytes[34 + target_lp_address_len as usize + 1], + bytes[34 + target_lp_address_len as usize + 2], + bytes[34 + target_lp_address_len as usize + 3], + ]); + if bytes[34 + target_lp_address_len as usize + 4..].len() != inner_packet_bytes_len as usize + { + return Err(LpError::DeserializationError(format!( + "Expected {inner_packet_bytes_len} bytes to deserialise inner packet bytes of ForwardPacketData. got {}", + bytes[34 + target_lp_address_len as usize + 4..].len() + ))); + } + let inner_packet_bytes = bytes[34 + target_lp_address_len as usize + 4..].to_vec(); + + Ok(ForwardPacketData { + target_gateway_identity, + target_lp_address, + inner_packet_bytes, + }) + } +} + /// Subsession KK1 message - first message of Noise KK handshake -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct SubsessionKK1Data { /// Noise KK first message payload (ephemeral key + encrypted static) pub payload: Vec, } +impl SubsessionKK1Data { + fn len(&self) -> usize { + self.payload.len() + } + + fn encode(&self, dst: &mut BytesMut) { + dst.put_slice(&self.payload); + } + + fn decode(bytes: &[u8]) -> Result { + Ok(SubsessionKK1Data { + payload: bytes.to_vec(), + }) + } +} + /// Subsession KK2 message - second message of Noise KK handshake -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct SubsessionKK2Data { /// Noise KK second message payload (ephemeral key + encrypted response) pub payload: Vec, } +impl SubsessionKK2Data { + fn len(&self) -> usize { + self.payload.len() + } + + fn encode(&self, dst: &mut BytesMut) { + dst.put_slice(&self.payload); + } + + fn decode(bytes: &[u8]) -> Result { + Ok(SubsessionKK2Data { + payload: bytes.to_vec(), + }) + } +} + /// Subsession ready confirmation with new session index -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct SubsessionReadyData { /// New subsession's receiver index for routing pub receiver_index: u32, } +impl SubsessionReadyData { + pub const LEN: usize = 4; + + fn len(&self) -> usize { + Self::LEN + } + + fn encode(&self, dst: &mut BytesMut) { + dst.put_u32_le(self.receiver_index); + } + + fn decode(bytes: &[u8]) -> Result { + if bytes.len() != 4 { + return Err(LpError::DeserializationError(format!( + "Expected 4 bytes to deserialise SubsessionReadyData. got {}", + bytes.len() + ))); + } + Ok(SubsessionReadyData { + receiver_index: u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]), + }) + } +} + #[derive(Debug, Clone)] pub enum LpMessage { Busy, @@ -275,23 +465,18 @@ impl LpMessage { pub fn len(&self) -> usize { match self { LpMessage::Busy => 0, - LpMessage::Handshake(payload) => payload.0.len(), - LpMessage::EncryptedData(payload) => payload.0.len(), - // 4 bytes receiver_index + 32 bytes x25519 key + 32 bytes ed25519 key + 32 bytes salt - LpMessage::ClientHello(_) => ClientHelloData::LEN, - LpMessage::KKTRequest(payload) => payload.0.len(), - LpMessage::KKTResponse(payload) => payload.0.len(), - LpMessage::ForwardPacket(data) => { - 32 + data.target_lp_address.len() + data.inner_packet_bytes.len() + 10 - } + LpMessage::Handshake(payload) => payload.len(), + LpMessage::EncryptedData(payload) => payload.len(), + LpMessage::ClientHello(payload) => payload.len(), + LpMessage::KKTRequest(payload) => payload.len(), + LpMessage::KKTResponse(payload) => payload.len(), + LpMessage::ForwardPacket(payload) => payload.len(), LpMessage::Collision => 0, LpMessage::Ack => 0, LpMessage::SubsessionRequest => 0, - // Variable length: bincode overhead (~8 bytes for Vec length) + payload - LpMessage::SubsessionKK1(data) => 8 + data.payload.len(), - LpMessage::SubsessionKK2(data) => 8 + data.payload.len(), - // 4 bytes u32 + bincode overhead (~4 bytes) - LpMessage::SubsessionReady(_) => 8, + LpMessage::SubsessionKK1(payload) => payload.len(), + LpMessage::SubsessionKK2(payload) => payload.len(), + LpMessage::SubsessionReady(payload) => payload.len(), LpMessage::SubsessionAbort => 0, } } @@ -318,51 +503,90 @@ impl LpMessage { pub fn encode_content(&self, dst: &mut BytesMut) { match self { LpMessage::Busy => { /* No content */ } - LpMessage::Handshake(payload) => { - dst.put_slice(&payload.0); - } - LpMessage::EncryptedData(payload) => { - dst.put_slice(&payload.0); - } - LpMessage::ClientHello(data) => { - dst.put_slice(&data.encode()); - } - LpMessage::KKTRequest(payload) => { - dst.put_slice(&payload.0); - } - LpMessage::KKTResponse(payload) => { - dst.put_slice(&payload.0); - } - LpMessage::ForwardPacket(data) => { - let serialized = lp_bincode_serializer() - .serialize(data) - .expect("Failed to serialize ForwardPacketData"); - dst.put_slice(&serialized); - } + LpMessage::Handshake(payload) => payload.encode(dst), + LpMessage::EncryptedData(payload) => payload.encode(dst), + LpMessage::ClientHello(data) => data.encode(dst), + LpMessage::KKTRequest(payload) => payload.encode(dst), + LpMessage::KKTResponse(payload) => payload.encode(dst), + LpMessage::ForwardPacket(data) => data.encode(dst), LpMessage::Collision => { /* No content */ } LpMessage::Ack => { /* No content */ } LpMessage::SubsessionRequest => { /* No content - signal only */ } - LpMessage::SubsessionKK1(data) => { - let serialized = lp_bincode_serializer() - .serialize(data) - .expect("Failed to serialize SubsessionKK1Data"); - dst.put_slice(&serialized); - } - LpMessage::SubsessionKK2(data) => { - let serialized = lp_bincode_serializer() - .serialize(data) - .expect("Failed to serialize SubsessionKK2Data"); - dst.put_slice(&serialized); - } - LpMessage::SubsessionReady(data) => { - let serialized = lp_bincode_serializer() - .serialize(data) - .expect("Failed to serialize SubsessionReadyData"); - dst.put_slice(&serialized); - } + LpMessage::SubsessionKK1(data) => data.encode(dst), + LpMessage::SubsessionKK2(data) => data.encode(dst), + LpMessage::SubsessionReady(data) => data.encode(dst), LpMessage::SubsessionAbort => { /* No content - signal only */ } } } + + /// Parse message from its type and content bytes. + /// + /// Used when decrypting outer-encrypted packets where the message type + /// was encrypted along with the content. + pub fn decode_content(content: &[u8], message_type: MessageType) -> Result { + match message_type { + MessageType::Busy => { + content.ensure_empty()?; + Ok(LpMessage::Busy) + } + MessageType::Handshake => Ok(LpMessage::Handshake(HandshakeData::decode(content)?)), + MessageType::EncryptedData => Ok(LpMessage::EncryptedData( + EncryptedDataPayload::decode(content)?, + )), + MessageType::ClientHello => { + Ok(LpMessage::ClientHello(ClientHelloData::decode(content)?)) + } + MessageType::KKTRequest => Ok(LpMessage::KKTRequest(KKTRequestData::decode(content)?)), + MessageType::KKTResponse => { + Ok(LpMessage::KKTResponse(KKTResponseData::decode(content)?)) + } + MessageType::ForwardPacket => Ok(LpMessage::ForwardPacket(ForwardPacketData::decode( + content, + )?)), + MessageType::Collision => { + content.ensure_empty()?; + Ok(LpMessage::Collision) + } + MessageType::Ack => { + content.ensure_empty()?; + Ok(LpMessage::Ack) + } + MessageType::SubsessionRequest => { + content.ensure_empty()?; + Ok(LpMessage::SubsessionRequest) + } + MessageType::SubsessionKK1 => Ok(LpMessage::SubsessionKK1(SubsessionKK1Data::decode( + content, + )?)), + MessageType::SubsessionKK2 => Ok(LpMessage::SubsessionKK2(SubsessionKK2Data::decode( + content, + )?)), + MessageType::SubsessionReady => Ok(LpMessage::SubsessionReady( + SubsessionReadyData::decode(content)?, + )), + MessageType::SubsessionAbort => { + content.ensure_empty()?; + Ok(LpMessage::SubsessionAbort) + } + } + } +} + +/// Helper trait for improving readability to return error if bytes content is not empty +trait EnsureEmptyContent { + fn ensure_empty(&self) -> Result<(), LpError>; +} + +impl EnsureEmptyContent for &[u8] { + fn ensure_empty(&self) -> Result<(), LpError> { + if !self.is_empty() { + return Err(LpError::InvalidPayloadSize { + expected: 0, + actual: self.len(), + }); + } + Ok(()) + } } #[cfg(test)] diff --git a/common/registration/Cargo.toml b/common/registration/Cargo.toml index 6d4c56e022..93104bfbbf 100644 --- a/common/registration/Cargo.toml +++ b/common/registration/Cargo.toml @@ -12,6 +12,7 @@ license.workspace = true workspace = true [dependencies] +bincode = { workspace = true } serde = { workspace = true, features = ["derive"] } tokio-util.workspace = true diff --git a/common/registration/src/lib.rs b/common/registration/src/lib.rs index 7ac2e20e9d..801daa9525 100644 --- a/common/registration/src/lib.rs +++ b/common/registration/src/lib.rs @@ -1,12 +1,6 @@ // Copyright 2025 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -mod lp_messages; - -pub use lp_messages::{ - LpGatewayData, LpRegistrationRequest, LpRegistrationResponse, RegistrationMode, -}; - use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; use nym_authenticator_requests::AuthenticatorVersion; @@ -15,6 +9,14 @@ 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::{ + LpGatewayData, LpRegistrationRequest, LpRegistrationResponse, RegistrationMode, +}; +pub use serialisation::BincodeError; + #[derive(Debug, Clone, Copy, PartialEq)] pub struct NymNode { pub identity: NodeIdentity, diff --git a/common/registration/src/lp_messages.rs b/common/registration/src/lp_messages.rs index 94c3889f5f..6b54532c6f 100644 --- a/common/registration/src/lp_messages.rs +++ b/common/registration/src/lp_messages.rs @@ -8,6 +8,7 @@ use serde::{Deserialize, Serialize}; use std::net::IpAddr; use crate::GatewayData; +use crate::serialisation::{BincodeError, BincodeOptions, lp_bincode_serializer}; /// Registration request sent by client after LP handshake /// Aligned with existing authenticator registration flow @@ -128,6 +129,16 @@ impl LpRegistrationRequest { (now as i64 - self.timestamp as i64).abs() <= max_skew_secs as i64 } + + /// Attempt to serialise this `LpRegistrationRequest` into bytes. + pub fn serialise(&self) -> Result, BincodeError> { + lp_bincode_serializer().serialize(self) + } + + /// Attempt to deserialise a `LpRegistrationRequest` from bytes. + pub fn try_deserialise(b: &[u8]) -> Result { + lp_bincode_serializer().deserialize(b) + } } impl LpRegistrationResponse { @@ -163,6 +174,16 @@ impl LpRegistrationResponse { allocated_bandwidth: 0, } } + + /// Attempt to serialise this `LpRegistrationResponse` into bytes. + pub fn serialise(&self) -> Result, BincodeError> { + lp_bincode_serializer().serialize(self) + } + + /// Attempt to deserialise a `LpRegistrationResponse` from bytes. + pub fn try_deserialise(b: &[u8]) -> Result { + lp_bincode_serializer().deserialize(b) + } } #[cfg(test)] diff --git a/common/nym-lp/src/serialisation.rs b/common/registration/src/serialisation.rs similarity index 100% rename from common/nym-lp/src/serialisation.rs rename to common/registration/src/serialisation.rs diff --git a/gateway/src/node/lp_listener/handler.rs b/gateway/src/node/lp_listener/handler.rs index 4fa5daad76..d30eb3600e 100644 --- a/gateway/src/node/lp_listener/handler.rs +++ b/gateway/src/node/lp_listener/handler.rs @@ -5,7 +5,6 @@ use super::messages::LpRegistrationRequest; use super::registration::process_registration; use super::LpHandlerState; use crate::error::GatewayError; -use nym_lp::serialisation::{lp_bincode_serializer, BincodeOptions}; use nym_lp::{ codec::OuterAeadKey, keypair::PublicKey, message::ForwardPacketData, packet::LpHeader, LpMessage, LpPacket, OuterHeader, @@ -344,7 +343,7 @@ where // Note: Do NOT set binding on collision - client may retry with new receiver_index let collision_packet = LpPacket::new(LpHeader::new(receiver_index, 0), LpMessage::Collision); - self.send_lp_packet(&collision_packet, None).await?; + self.send_lp_packet(collision_packet, None).await?; return Ok(()); } @@ -404,7 +403,7 @@ where // Send Ack to confirm ClientHello received // No outer key - this is before PSK derivation let ack_packet = LpPacket::new(LpHeader::new(receiver_index, 0), LpMessage::Ack); - self.send_lp_packet(&ack_packet, None).await?; + self.send_lp_packet(ack_packet, None).await?; Ok(()) } @@ -508,7 +507,7 @@ where // Send response packet if needed if let Some((packet, outer_key)) = should_send { - self.send_lp_packet(&packet, outer_key.as_ref()).await?; + self.send_lp_packet(packet, outer_key.as_ref()).await?; trace!( "Sent handshake response to {} (encrypted={})", self.remote_addr, @@ -586,7 +585,7 @@ where self.remote_addr, receiver_idx ); inc!("lp_subsession_kk2_sent"); - self.send_lp_packet(&response_packet, outer_key.as_ref()) + self.send_lp_packet(response_packet, outer_key.as_ref()) .await?; Ok(()) } @@ -632,9 +631,7 @@ where let remote = self.remote_addr; // Try to deserialize as LpRegistrationRequest first (most common case after handshake) - if let Ok(request) = - lp_bincode_serializer().deserialize::(&decrypted_bytes) - { + if let Ok(request) = LpRegistrationRequest::try_deserialise(&decrypted_bytes) { debug!( "LP registration request from {remote} (receiver_idx={receiver_idx}): mode={:?}", request.mode @@ -645,9 +642,7 @@ where } // Try to deserialize as ForwardPacketData (entry gateway forwarding to exit) - if let Ok(forward_data) = - lp_bincode_serializer().deserialize::(&decrypted_bytes) - { + if let Ok(forward_data) = ForwardPacketData::decode(&decrypted_bytes) { debug!( "LP forward request from {remote} (receiver_idx={receiver_idx}) to {}", forward_data.target_lp_address @@ -689,7 +684,7 @@ where // Send SubsessionReady packet if present (for initiator - gateway is responder, so typically None) if let Some(packet) = ready_packet { - self.send_lp_packet(&packet, outer_key.as_ref()).await?; + self.send_lp_packet(packet, outer_key.as_ref()).await?; } // Create new state machine from completed subsession @@ -759,7 +754,7 @@ where .map_err(|e| GatewayError::LpProtocolError(format!("Session error: {}", e)))?; // Serialize and encrypt response - let response_bytes = lp_bincode_serializer().serialize(&response).map_err(|e| { + let response_bytes = response.serialise().map_err(|e| { GatewayError::LpProtocolError(format!("Failed to serialize response: {}", e)) })?; @@ -777,7 +772,7 @@ where }; // Send response (encrypted with outer AEAD) - self.send_lp_packet(&response_packet, outer_key.as_ref()) + self.send_lp_packet(response_packet, outer_key.as_ref()) .await?; if response.success { @@ -834,7 +829,7 @@ where }; // Send encrypted response to client (encrypted with outer AEAD) - self.send_lp_packet(&response_packet, outer_key.as_ref()) + self.send_lp_packet(response_packet, outer_key.as_ref()) .await?; debug!( @@ -1223,7 +1218,7 @@ where /// * `outer_key` - Optional outer AEAD key for encryption (None for cleartext, Some for encrypted) async fn send_lp_packet( &mut self, - packet: &LpPacket, + packet: LpPacket, outer_key: Option<&OuterAeadKey>, ) -> Result<(), GatewayError> { use bytes::BytesMut; @@ -1231,7 +1226,7 @@ where // Serialize the packet (encrypted if outer_key provided) let mut packet_buf = BytesMut::new(); - serialize_lp_packet(packet, &mut packet_buf, outer_key).map_err(|e| { + serialize_lp_packet(&packet, &mut packet_buf, outer_key).map_err(|e| { GatewayError::LpProtocolError(format!("Failed to serialize packet: {}", e)) })?; @@ -1597,7 +1592,7 @@ mod tests { }, LpMessage::Busy, ); - handler.send_lp_packet(&packet, None).await + handler.send_lp_packet(packet, None).await }); let mut client_stream = TcpStream::connect(addr).await.unwrap(); @@ -1637,7 +1632,7 @@ mod tests { }, LpMessage::Handshake(HandshakeData(handshake_data)), ); - handler.send_lp_packet(&packet, None).await + handler.send_lp_packet(packet, None).await }); let mut client_stream = TcpStream::connect(addr).await.unwrap(); @@ -1678,7 +1673,7 @@ mod tests { }, LpMessage::EncryptedData(EncryptedDataPayload(encrypted_payload)), ); - handler.send_lp_packet(&packet, None).await + handler.send_lp_packet(packet, None).await }); let mut client_stream = TcpStream::connect(addr).await.unwrap(); @@ -1730,7 +1725,7 @@ mod tests { }, LpMessage::ClientHello(hello_data), ); - handler.send_lp_packet(&packet, None).await + handler.send_lp_packet(packet, None).await }); let mut client_stream = TcpStream::connect(addr).await.unwrap(); diff --git a/nym-registration-client/src/lp_client/client.rs b/nym-registration-client/src/lp_client/client.rs index a7b482e4c5..2cb6a3ed0f 100644 --- a/nym-registration-client/src/lp_client/client.rs +++ b/nym-registration-client/src/lp_client/client.rs @@ -12,7 +12,6 @@ use nym_crypto::asymmetric::{ed25519, x25519}; use nym_lp::LpPacket; use nym_lp::codec::{OuterAeadKey, parse_lp_packet, serialize_lp_packet}; use nym_lp::message::ForwardPacketData; -use nym_lp::serialisation::{BincodeOptions, lp_bincode_serializer}; use nym_lp::state_machine::{LpAction, LpInput, LpStateMachine}; use nym_lp_transport::traits::LpTransport; use nym_registration_common::{GatewayData, LpRegistrationRequest, LpRegistrationResponse}; @@ -757,7 +756,7 @@ where tracing::trace!("Built registration request: {:?}", request); // 2. Serialize the request - let request_bytes = lp_bincode_serializer().serialize(&request).map_err(|e| { + let request_bytes = request.serialise().map_err(|e| { LpClientError::SendRegistrationRequest(format!("Failed to serialize request: {e}")) })?; @@ -843,13 +842,11 @@ where }; // 8. Deserialize the response - let response: LpRegistrationResponse = lp_bincode_serializer() - .deserialize(&response_data) - .map_err(|e| { - LpClientError::ReceiveRegistrationResponse(format!( - "Failed to deserialize registration response: {e}", - )) - })?; + let response = LpRegistrationResponse::try_deserialise(&response_data).map_err(|e| { + LpClientError::ReceiveRegistrationResponse(format!( + "Failed to deserialize registration response: {e}", + )) + })?; tracing::debug!( "Received registration response: success={}", @@ -1037,11 +1034,7 @@ where }; // 2. Serialize the ForwardPacketData - let forward_data_bytes = lp_bincode_serializer() - .serialize(&forward_data) - .map_err(|e| { - LpClientError::Transport(format!("Failed to serialize ForwardPacketData: {e}")) - })?; + let forward_data_bytes = forward_data.to_bytes(); tracing::trace!( "Serialized ForwardPacketData ({} bytes)", diff --git a/nym-registration-client/src/lp_client/error.rs b/nym-registration-client/src/lp_client/error.rs index bbc0a6ef91..2fc74e3fd1 100644 --- a/nym-registration-client/src/lp_client/error.rs +++ b/nym-registration-client/src/lp_client/error.rs @@ -4,7 +4,7 @@ //! Error types for LP (Lewes Protocol) client operations. use nym_lp::LpError; -use nym_lp::serialisation::BincodeError; +use nym_registration_common::BincodeError; use std::io; use thiserror::Error; diff --git a/nym-registration-client/src/lp_client/nested_session.rs b/nym-registration-client/src/lp_client/nested_session.rs index d4f35898ee..2f011e9b15 100644 --- a/nym-registration-client/src/lp_client/nested_session.rs +++ b/nym-registration-client/src/lp_client/nested_session.rs @@ -25,7 +25,6 @@ use nym_bandwidth_controller::BandwidthTicketProvider; use nym_credentials_interface::TicketType; use nym_crypto::asymmetric::{ed25519, x25519}; use nym_lp::codec::{OuterAeadKey, parse_lp_packet, serialize_lp_packet}; -use nym_lp::serialisation::{BincodeOptions, lp_bincode_serializer}; use nym_lp::state_machine::{LpAction, LpInput, LpStateMachine}; use nym_lp::{LpMessage, LpPacket}; use nym_lp_transport::traits::LpTransport; @@ -329,7 +328,7 @@ impl NestedLpSession { tracing::trace!("Built registration request: {:?}", request); // Step 4: Serialize the request - let request_bytes = lp_bincode_serializer().serialize(&request).map_err(|e| { + let request_bytes = request.serialise().map_err(|e| { LpClientError::Transport(format!("Failed to serialize registration request: {}", e)) })?; @@ -397,14 +396,12 @@ impl NestedLpSession { }; // Step 10: Deserialize the response - let response: LpRegistrationResponse = lp_bincode_serializer() - .deserialize(&response_data) - .map_err(|e| { - LpClientError::Transport(format!( - "Failed to deserialize registration response: {}", - e - )) - })?; + let response = LpRegistrationResponse::try_deserialise(&response_data).map_err(|e| { + LpClientError::Transport(format!( + "Failed to deserialize registration response: {}", + e + )) + })?; tracing::debug!( "Received registration response from exit: success={}", @@ -505,7 +502,7 @@ impl NestedLpSession { tracing::trace!("Built registration request: {:?}", request); // Step 5: Serialize the request - let request_bytes = lp_bincode_serializer().serialize(&request).map_err(|e| { + let request_bytes = request.serialise().map_err(|e| { LpClientError::Transport(format!("Failed to serialize registration request: {}", e)) })?; @@ -573,14 +570,12 @@ impl NestedLpSession { }; // Step 11: Deserialize the response - let response: LpRegistrationResponse = lp_bincode_serializer() - .deserialize(&response_data) - .map_err(|e| { - LpClientError::Transport(format!( - "Failed to deserialize registration response: {}", - e - )) - })?; + let response = LpRegistrationResponse::try_deserialise(&response_data).map_err(|e| { + LpClientError::Transport(format!( + "Failed to deserialize registration response: {}", + e + )) + })?; tracing::debug!( "Received registration response from exit: success={}", From 3b75af34e82b6a968e899fcb1e11bef7e1bb286e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Fri, 16 Jan 2026 08:58:20 +0000 Subject: [PATCH 02/51] ensure packets with incompatible versions are rejected (#6326) --- common/nym-lp/src/codec.rs | 46 ++++++++++---------- common/nym-lp/src/error.rs | 8 ++++ common/nym-lp/src/lib.rs | 12 ++--- common/nym-lp/src/message.rs | 2 +- common/nym-lp/src/packet.rs | 39 ++++++++++++++--- common/nym-lp/src/session_integration/mod.rs | 2 +- gateway/src/node/lp_listener/handler.rs | 14 +++--- 7 files changed, 79 insertions(+), 44 deletions(-) diff --git a/common/nym-lp/src/codec.rs b/common/nym-lp/src/codec.rs index 1a6b0e05ad..8d6e0ccdd9 100644 --- a/common/nym-lp/src/codec.rs +++ b/common/nym-lp/src/codec.rs @@ -164,7 +164,7 @@ pub fn parse_lp_packet(src: &[u8], outer_key: Option<&OuterAeadKey>) -> Result) -> Result Self { Self { - protocol_version: 1, - reserved: 0, + protocol_version: version::CURRENT, + reserved: [0u8; 3], receiver_idx, counter, } @@ -211,7 +217,7 @@ impl LpHeader { dst.put_u8(self.protocol_version); // reserved - dst.put_slice(&[0, 0, 0]); + dst.put_slice(&self.reserved); // sender index dst.put_slice(&self.receiver_idx.to_le_bytes()); @@ -226,7 +232,28 @@ impl LpHeader { } let protocol_version = src[0]; - // Skip reserved bytes [1..4] + + // Ensure we are using compatible protocol + // right now only support a single version + if protocol_version > version::CURRENT { + return Err(LpError::IncompatibleFuturePacketVersion { + got: protocol_version, + highest_supported: version::CURRENT, + }); + } + + if protocol_version < version::CURRENT { + return Err(LpError::IncompatibleLegacyPacketVersion { + got: protocol_version, + lowest_supported: version::CURRENT, + }); + } + + // skip reserved bytes, but log if they're different from the expected zeroes + let reserved = [src[1], src[2], src[3]]; + if reserved != [0u8; 3] { + warn!("received non-zero reserved bytes. got: {reserved:?}"); + } let mut receiver_idx_bytes = [0u8; 4]; receiver_idx_bytes.copy_from_slice(&src[4..8]); @@ -238,7 +265,7 @@ impl LpHeader { Ok(LpHeader { protocol_version, - reserved: 0, + reserved: [0u8; 3], receiver_idx, counter, }) diff --git a/common/nym-lp/src/session_integration/mod.rs b/common/nym-lp/src/session_integration/mod.rs index ceafc3f81f..a3b58f2575 100644 --- a/common/nym-lp/src/session_integration/mod.rs +++ b/common/nym-lp/src/session_integration/mod.rs @@ -21,7 +21,7 @@ mod tests { // Create the header let header = LpHeader { protocol_version, - reserved: 0u16, // reserved + reserved: [0u8; 3], // reserved receiver_idx, counter, }; diff --git a/gateway/src/node/lp_listener/handler.rs b/gateway/src/node/lp_listener/handler.rs index d30eb3600e..7960d4f026 100644 --- a/gateway/src/node/lp_listener/handler.rs +++ b/gateway/src/node/lp_listener/handler.rs @@ -1520,7 +1520,7 @@ mod tests { let packet = LpPacket::new( LpHeader { protocol_version: 1, - reserved: 0, + reserved: [0u8; 3], receiver_idx: 42, counter: 0, }, @@ -1586,7 +1586,7 @@ mod tests { let packet = LpPacket::new( LpHeader { protocol_version: 1, - reserved: 0, + reserved: [0u8; 3], receiver_idx: 99, counter: 5, }, @@ -1626,7 +1626,7 @@ mod tests { let packet = LpPacket::new( LpHeader { protocol_version: 1, - reserved: 0, + reserved: [0u8; 3], receiver_idx: 100, counter: 10, }, @@ -1667,7 +1667,7 @@ mod tests { let packet = LpPacket::new( LpHeader { protocol_version: 1, - reserved: 0, + reserved: [0u8; 3], receiver_idx: 200, counter: 20, }, @@ -1719,7 +1719,7 @@ mod tests { let packet = LpPacket::new( LpHeader { protocol_version: 1, - reserved: 0, + reserved: [0u8; 3], receiver_idx: 300, counter: 30, }, @@ -1784,7 +1784,7 @@ mod tests { let packet = LpPacket::new( LpHeader { protocol_version: 1, - reserved: 0, + reserved: [0u8; 3], receiver_idx: 0, counter: 0, }, @@ -1854,7 +1854,7 @@ mod tests { let packet = LpPacket::new( LpHeader { protocol_version: 1, - reserved: 0, + reserved: [0u8; 3], receiver_idx: 0, counter: 0, }, From 7d8d1e9d6d03164a185c4ceda877980e3e2d6fa6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Fri, 16 Jan 2026 10:11:49 +0000 Subject: [PATCH 03/51] Lp/encrypted kkt (#6331) * enable encryption - kkt * integrate encrypted kkt into nym-lp * chore: remove unused imports * chore: remove magic constants from KKTContext * fixed KKT exchange * use more strict typing for KKTFrame fields * removed recursive error conversion * removed needless borrow * restored kkt tests * fixed KKT benchmarks compilation --------- Co-authored-by: Georgio Nicolas --- Cargo.lock | 9 +- common/nym-kkt/Cargo.toml | 12 +- common/nym-kkt/benches/benches.rs | 122 +++----- common/nym-kkt/src/ciphersuite.rs | 100 +++---- common/nym-kkt/src/context.rs | 221 +++++++------- common/nym-kkt/src/encryption.rs | 304 ++++++++++++++----- common/nym-kkt/src/error.rs | 28 ++ common/nym-kkt/src/frame.rs | 132 +++++---- common/nym-kkt/src/key_utils.rs | 15 + common/nym-kkt/src/kkt.rs | 173 ++++++++--- common/nym-kkt/src/lib.rs | 299 +++++++++++++++++-- common/nym-kkt/src/session.rs | 32 +- common/nym-lp/src/kkt_orchestrator.rs | 233 ++++++++------- common/nym-lp/src/session.rs | 55 ++-- common/nym-lp/src/state_machine.rs | 410 +++++++++++++------------- 15 files changed, 1345 insertions(+), 800 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 682381c7b8..9c9710eca4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6656,18 +6656,21 @@ dependencies = [ name = "nym-kkt" version = "0.1.0" dependencies = [ + "anyhow", "blake3", "classic-mceliece-rust", "criterion", + "libcrux-chacha20poly1305", "libcrux-ecdh", "libcrux-kem", - "libcrux-ml-kem", - "libcrux-psq", "libcrux-sha3", - "libcrux-traits", + "num_enum", "nym-crypto", + "nym-sphinx", "rand 0.9.2", + "rand_chacha 0.9.0", "thiserror 2.0.12", + "zeroize", ] [[package]] diff --git a/common/nym-kkt/Cargo.toml b/common/nym-kkt/Cargo.toml index 30d008ee52..e01fd953ae 100644 --- a/common/nym-kkt/Cargo.toml +++ b/common/nym-kkt/Cargo.toml @@ -8,24 +8,30 @@ license.workspace = true [dependencies] blake3 = { workspace = true } thiserror = { workspace = true } +num_enum = { workspace = true } + # internal nym-crypto = { path = "../crypto", features = ["asymmetric", "serde"] } +nym-sphinx = { path = "../nymsphinx" } -libcrux-traits = { git = "https://github.com/cryspen/libcrux" } libcrux-kem = { git = "https://github.com/cryspen/libcrux" } -libcrux-psq = { git = "https://github.com/cryspen/libcrux", features = ["test-utils"] } libcrux-sha3 = { git = "https://github.com/cryspen/libcrux" } -libcrux-ml-kem = { git = "https://github.com/cryspen/libcrux" } libcrux-ecdh = { git = "https://github.com/cryspen/libcrux", features = ["codec"] } +libcrux-chacha20poly1305 = { git = "https://github.com/cryspen/libcrux" } +#rand = "0.9.2" rand = "0.9.2" +zeroize = { workspace = true, features = ["zeroize_derive"] } classic-mceliece-rust = { git = "https://github.com/georgio/classic-mceliece-rust", features = ["mceliece460896f", "zeroize"] } [dev-dependencies] +rand_chacha = "0.9.0" +anyhow = { workspace = true } criterion = { workspace = true } + [[bench]] name = "benches" harness = false diff --git a/common/nym-kkt/benches/benches.rs b/common/nym-kkt/benches/benches.rs index 62038d6c36..4e76fc452f 100644 --- a/common/nym-kkt/benches/benches.rs +++ b/common/nym-kkt/benches/benches.rs @@ -48,6 +48,7 @@ pub fn kkt_benchmark(c: &mut Criterion) { let mut secret_responder: [u8; 32] = [0u8; 32]; rng.fill_bytes(&mut secret_responder); + let responder_ed25519_keypair = ed25519::KeyPair::from_secret(secret_responder, 1); for kem in [KEM::MlKem768, KEM::XWing, KEM::X25519, KEM::McEliece] { for hash_function in [ @@ -104,10 +105,7 @@ pub fn kkt_benchmark(c: &mut Criterion) { // Anonymous Initiator, OneWay { c.bench_function( - &format!( - "{}, {} | Anonymous Initiator: Generate Request", - kem, hash_function - ), + &format!("{kem}, {hash_function} | Anonymous Initiator: Generate Request",), |b| { b.iter(|| anonymous_initiator_process(&mut rng, ciphersuite).unwrap()); }, @@ -118,8 +116,7 @@ pub fn kkt_benchmark(c: &mut Criterion) { c.bench_function( &format!( - "{}, {} | Anonymous Initiator: Encode Frame - Request", - kem, hash_function + "{kem}, {hash_function} | Anonymous Initiator: Encode Frame - Request", ), |b| b.iter(|| i_frame.to_bytes()), ); @@ -128,8 +125,7 @@ pub fn kkt_benchmark(c: &mut Criterion) { c.bench_function( &format!( - "{}, {} | Anonymous Initiator: Decode Frame - Request", - kem, hash_function + "{kem}, {hash_function} | Anonymous Initiator: Decode Frame - Request", ), |b| b.iter(|| KKTFrame::from_bytes(&i_frame_bytes).unwrap()), ); @@ -138,8 +134,7 @@ pub fn kkt_benchmark(c: &mut Criterion) { c.bench_function( &format!( - "{}, {} | Anonymous Initiator: Responder Ingest Frame", - kem, hash_function + "{kem}, {hash_function} | Anonymous Initiator: Responder Ingest Frame", ), |b| { b.iter(|| { @@ -153,14 +148,13 @@ pub fn kkt_benchmark(c: &mut Criterion) { c.bench_function( &format!( - "{}, {} | Anonymous Initiator: Responder Generate Response", - kem, hash_function + "{kem}, {hash_function} | Anonymous Initiator: Responder Generate Response", ), |b| { b.iter(|| { responder_process( &mut r_context, - i_frame_r.session_id_ref(), + i_frame_r.session_id(), responder_ed25519_keypair.private_key(), &responder_kem_public_key, ) @@ -170,7 +164,7 @@ pub fn kkt_benchmark(c: &mut Criterion) { ); let r_frame = responder_process( &mut r_context, - i_frame_r.session_id_ref(), + i_frame_r.session_id(), responder_ed25519_keypair.private_key(), &responder_kem_public_key, ) @@ -178,26 +172,23 @@ pub fn kkt_benchmark(c: &mut Criterion) { c.bench_function( &format!( - "{}, {} | Anonymous Initiator: Responder Encode Frame", - kem, hash_function + "{kem}, {hash_function} | Anonymous Initiator: Responder Encode Frame", ), |b| b.iter(|| r_frame.to_bytes()), ); - let r_bytes = r_frame.to_bytes(); - c.bench_function( &format!( - "{}, {} | Anonymous Initiator: Initiator Ingest Response", - kem, hash_function + "{kem}, {hash_function} | Anonymous Initiator: Initiator Ingest Response", ), |b| { b.iter(|| { initiator_ingest_response( &mut i_context, + &r_frame, + &r_frame.context().unwrap(), responder_ed25519_keypair.public_key(), &r_dir_hash, - &r_bytes, ) .unwrap() }); @@ -206,9 +197,10 @@ pub fn kkt_benchmark(c: &mut Criterion) { let obtained_key = initiator_ingest_response( &mut i_context, + &r_frame, + &r_frame.context().unwrap(), responder_ed25519_keypair.public_key(), &r_dir_hash, - &r_bytes, ) .unwrap(); @@ -226,10 +218,7 @@ pub fn kkt_benchmark(c: &mut Criterion) { .unwrap(); c.bench_function( - &format!( - "{}, {} | Initiator OneWay: Generate Request", - kem, hash_function - ), + &format!("{kem}, {hash_function} | Initiator OneWay: Generate Request",), |b| { b.iter(|| { initiator_process( @@ -245,30 +234,21 @@ pub fn kkt_benchmark(c: &mut Criterion) { ); c.bench_function( - &format!( - "{}, {} | Initiator OneWay: Encode Frame - Request", - kem, hash_function - ), + &format!("{kem}, {hash_function} | Initiator OneWay: Encode Frame - Request",), |b| b.iter(|| i_frame.to_bytes()), ); let i_frame_bytes = i_frame.to_bytes(); c.bench_function( - &format!( - "{}, {} | Initiator OneWay: Decode Frame - Request", - kem, hash_function - ), + &format!("{kem}, {hash_function} | Initiator OneWay: Decode Frame - Request",), |b| b.iter(|| KKTFrame::from_bytes(&i_frame_bytes).unwrap()), ); let (i_frame_r, r_context) = KKTFrame::from_bytes(&i_frame_bytes).unwrap(); c.bench_function( - &format!( - "{}, {} | Initiator OneWay: Responder Ingest Frame", - kem, hash_function - ), + &format!("{kem}, {hash_function} | Initiator OneWay: Responder Ingest Frame",), |b| { b.iter(|| { responder_ingest_message( @@ -294,14 +274,13 @@ pub fn kkt_benchmark(c: &mut Criterion) { c.bench_function( &format!( - "{}, {} | Initiator OneWay: Responder Generate Response", - kem, hash_function + "{kem}, {hash_function} | Initiator OneWay: Responder Generate Response", ), |b| { b.iter(|| { responder_process( &mut r_context, - i_frame_r.session_id_ref(), + i_frame_r.session_id(), responder_ed25519_keypair.private_key(), &responder_kem_public_key, ) @@ -312,36 +291,31 @@ pub fn kkt_benchmark(c: &mut Criterion) { let r_frame = responder_process( &mut r_context, - i_frame_r.session_id_ref(), + i_frame_r.session_id(), responder_ed25519_keypair.private_key(), &responder_kem_public_key, ) .unwrap(); c.bench_function( - &format!( - "{}, {} | Initiator OneWay: Responder Encode Frame", - kem, hash_function - ), + &format!("{kem}, {hash_function} | Initiator OneWay: Responder Encode Frame",), |b| { b.iter(|| r_frame.to_bytes()); }, ); - let r_bytes = r_frame.to_bytes(); - c.bench_function( &format!( - "{}, {} | Initiator OneWay: Initiator Ingest Response", - kem, hash_function + "{kem}, {hash_function} | Initiator OneWay: Initiator Ingest Response", ), |b| { b.iter(|| { initiator_ingest_response( &mut i_context, + &r_frame, + &r_frame.context().unwrap(), responder_ed25519_keypair.public_key(), &r_dir_hash, - &r_bytes, ) .unwrap() }); @@ -350,9 +324,10 @@ pub fn kkt_benchmark(c: &mut Criterion) { let i_obtained_key = initiator_ingest_response( &mut i_context, + &r_frame, + &r_frame.context().unwrap(), responder_ed25519_keypair.public_key(), &r_dir_hash, - &r_bytes, ) .unwrap(); @@ -362,10 +337,7 @@ pub fn kkt_benchmark(c: &mut Criterion) { // Initiator, Mutual { c.bench_function( - &format!( - "{}, {} | Initiator Mutual: Generate Request", - kem, hash_function - ), + &format!("{kem}, {hash_function} | Initiator Mutual: Generate Request",), |b| { b.iter(|| { initiator_process( @@ -390,10 +362,7 @@ pub fn kkt_benchmark(c: &mut Criterion) { .unwrap(); c.bench_function( - &format!( - "{}, {} | Initiator Mutual: Encode Frame - Request", - kem, hash_function - ), + &format!("{kem}, {hash_function} | Initiator Mutual: Encode Frame - Request",), |b| { b.iter(|| i_frame.to_bytes()); }, @@ -402,10 +371,7 @@ pub fn kkt_benchmark(c: &mut Criterion) { let i_frame_bytes = i_frame.to_bytes(); c.bench_function( - &format!( - "{}, {} | Initiator Mutual: Decode Frame - Request", - kem, hash_function - ), + &format!("{kem}, {hash_function} | Initiator Mutual: Decode Frame - Request",), |b| { b.iter(|| KKTFrame::from_bytes(&i_frame_bytes).unwrap()); }, @@ -414,10 +380,7 @@ pub fn kkt_benchmark(c: &mut Criterion) { let (i_frame_r, r_context) = KKTFrame::from_bytes(&i_frame_bytes).unwrap(); c.bench_function( - &format!( - "{}, {} | Initiator Mutual: Responder Ingest Frame", - kem, hash_function - ), + &format!("{kem}, {hash_function} | Initiator Mutual: Responder Ingest Frame",), |b| { b.iter(|| { responder_ingest_message( @@ -443,14 +406,13 @@ pub fn kkt_benchmark(c: &mut Criterion) { c.bench_function( &format!( - "{}, {} | Initiator Mutual: Responder Generate Response", - kem, hash_function + "{kem}, {hash_function} | Initiator Mutual: Responder Generate Response", ), |b| { b.iter(|| { responder_process( &mut r_context, - i_frame_r.session_id_ref(), + i_frame_r.session_id(), responder_ed25519_keypair.private_key(), &responder_kem_public_key, ) @@ -461,17 +423,14 @@ pub fn kkt_benchmark(c: &mut Criterion) { let r_frame = responder_process( &mut r_context, - i_frame_r.session_id_ref(), + i_frame_r.session_id(), responder_ed25519_keypair.private_key(), &responder_kem_public_key, ) .unwrap(); c.bench_function( - &format!( - "{}, {} | Initiator Mutual: Responder Encode Frame", - kem, hash_function - ), + &format!("{kem}, {hash_function} | Initiator Mutual: Responder Encode Frame",), |b| { b.iter(|| { r_frame.to_bytes(); @@ -479,20 +438,18 @@ pub fn kkt_benchmark(c: &mut Criterion) { }, ); - let r_bytes = r_frame.to_bytes(); - c.bench_function( &format!( - "{}, {} | Initiator Mutual: Initiator Ingest Response", - kem, hash_function + "{kem}, {hash_function} | Initiator Mutual: Initiator Ingest Response", ), |b| { b.iter(|| { initiator_ingest_response( &mut i_context, + &r_frame, + &r_frame.context().unwrap(), responder_ed25519_keypair.public_key(), &r_dir_hash, - &r_bytes, ) .unwrap() }); @@ -501,9 +458,10 @@ pub fn kkt_benchmark(c: &mut Criterion) { let obtained_key = initiator_ingest_response( &mut i_context, + &r_frame, + &r_frame.context().unwrap(), responder_ed25519_keypair.public_key(), &r_dir_hash, - &r_bytes, ) .unwrap(); diff --git a/common/nym-kkt/src/ciphersuite.rs b/common/nym-kkt/src/ciphersuite.rs index 0ada7ffb4c..7dd5a85ae3 100644 --- a/common/nym-kkt/src/ciphersuite.rs +++ b/common/nym-kkt/src/ciphersuite.rs @@ -8,12 +8,12 @@ use nym_crypto::asymmetric::ed25519; use crate::error::KKTError; -pub const HASH_LEN_256: u8 = 32; +pub const HASH_LEN_256: usize = 32; pub const CIPHERSUITE_ENCODING_LEN: usize = 4; pub const CURVE25519_KEY_LEN: usize = 32; -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] pub enum HashFunction { Blake3, SHAKE128, @@ -87,7 +87,7 @@ impl<'a> EncapsulationKey<'a> { } } -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] pub enum SignatureScheme { Ed25519, } @@ -99,7 +99,7 @@ impl Display for SignatureScheme { } } -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] pub enum KEM { MlKem768, XWing, @@ -118,7 +118,7 @@ impl Display for KEM { } } -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] pub struct Ciphersuite { hash_function: HashFunction, signature_scheme: SignatureScheme, @@ -172,7 +172,7 @@ impl Ciphersuite { l } } - None => HASH_LEN_256, + None => HASH_LEN_256 as u8, }; Ok(Self { hash_function, @@ -203,7 +203,7 @@ impl Ciphersuite { }, }) } - pub fn encode(&self) -> [u8; 4] { + pub fn encode(&self) -> [u8; CIPHERSUITE_ENCODING_LEN] { // [kem, hash, hashlen, sig] [ match self.kem { @@ -218,8 +218,8 @@ impl Ciphersuite { HashFunction::SHAKE128 => 2, HashFunction::SHA256 => 3, }, - match self.hash_length { - HASH_LEN_256 => 0, + match self.hash_length as usize { + HASH_LEN_256 => 0u8, _ => self.hash_length, }, match self.signature_scheme { @@ -227,55 +227,45 @@ impl Ciphersuite { }, ] } - pub fn decode(encoding: &[u8]) -> Result { - if encoding.len() == 4 { - let kem = match encoding[0] { - 0 => KEM::XWing, - 1 => KEM::MlKem768, - 2 => KEM::McEliece, - 255 => KEM::X25519, - _ => { - return Err(KKTError::CiphersuiteDecodingError { - info: format!("Undefined KEM: {}", encoding[0]), - }); - } - }; - let hash_function = match encoding[1] { - 0 => HashFunction::Blake3, - 1 => HashFunction::SHAKE256, - 2 => HashFunction::SHAKE128, - 3 => HashFunction::SHA256, - _ => { - return Err(KKTError::CiphersuiteDecodingError { - info: format!("Undefined Hash Function: {}", encoding[1]), - }); - } - }; + pub fn decode(encoding: [u8; CIPHERSUITE_ENCODING_LEN]) -> Result { + let kem = match encoding[0] { + 0 => KEM::XWing, + 1 => KEM::MlKem768, + 2 => KEM::McEliece, + 255 => KEM::X25519, + _ => { + return Err(KKTError::CiphersuiteDecodingError { + info: format!("Undefined KEM: {}", encoding[0]), + }); + } + }; + let hash_function = match encoding[1] { + 0 => HashFunction::Blake3, + 1 => HashFunction::SHAKE256, + 2 => HashFunction::SHAKE128, + 3 => HashFunction::SHA256, + _ => { + return Err(KKTError::CiphersuiteDecodingError { + info: format!("Undefined Hash Function: {}", encoding[1]), + }); + } + }; - let custom_hash_length = match encoding[2] { - 0 => None, - _ => Some(encoding[2]), - }; + let custom_hash_length = match encoding[2] { + 0 => None, + _ => Some(encoding[2]), + }; - let signature_scheme = match encoding[3] { - 0 => SignatureScheme::Ed25519, - _ => { - return Err(KKTError::CiphersuiteDecodingError { - info: format!("Undefined Signature Scheme: {}", encoding[3]), - }); - } - }; + let signature_scheme = match encoding[3] { + 0 => SignatureScheme::Ed25519, + _ => { + return Err(KKTError::CiphersuiteDecodingError { + info: format!("Undefined Signature Scheme: {}", encoding[3]), + }); + } + }; - Self::resolve_ciphersuite(kem, hash_function, signature_scheme, custom_hash_length) - } else { - Err(KKTError::CiphersuiteDecodingError { - info: format!( - "Incorrect Encoding Length: actual: {} != expected: {}", - encoding.len(), - CIPHERSUITE_ENCODING_LEN - ), - }) - } + Self::resolve_ciphersuite(kem, hash_function, signature_scheme, custom_hash_length) } } diff --git a/common/nym-kkt/src/context.rs b/common/nym-kkt/src/context.rs index da66bd3ae6..8ca1b351c7 100644 --- a/common/nym-kkt/src/context.rs +++ b/common/nym-kkt/src/context.rs @@ -1,22 +1,25 @@ // Copyright 2025 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use std::fmt::Display; - +use crate::ciphersuite::CIPHERSUITE_ENCODING_LEN; use crate::{KKT_VERSION, ciphersuite::Ciphersuite, error::KKTError, frame::KKT_SESSION_ID_LEN}; +use num_enum::{IntoPrimitive, TryFromPrimitive}; +use std::fmt::Display; pub const KKT_CONTEXT_LEN: usize = 7; -#[derive(Clone, Copy, PartialEq, Debug)] +// bitmask used: 0b1110_0000 +#[derive(Clone, Copy, PartialEq, Debug, IntoPrimitive, TryFromPrimitive)] +#[repr(u8)] pub enum KKTStatus { - Ok, - InvalidRequestFormat, - InvalidResponseFormat, - InvalidSignature, - UnsupportedCiphersuite, - UnsupportedKKTVersion, - InvalidKey, - Timeout, + Ok = 0b0000_0000, + InvalidRequestFormat = 0b0010_0000, + InvalidResponseFormat = 0b0100_0000, + InvalidSignature = 0b0110_0000, + UnsupportedCiphersuite = 0b1000_0000, + UnsupportedKKTVersion = 0b1010_0000, + InvalidKey = 0b1100_0000, + Timeout = 0b1110_0000, } impl Display for KKTStatus { @@ -33,20 +36,25 @@ impl Display for KKTStatus { }) } } -#[derive(Clone, Copy, PartialEq, Debug)] + +// bitmask used: 0b0000_0011 +#[derive(Clone, Copy, PartialEq, Debug, IntoPrimitive, TryFromPrimitive)] +#[repr(u8)] pub enum KKTRole { - Initiator, - AnonymousInitiator, - Responder, + Initiator = 0b0000_0000, + Responder = 0b0000_0001, + AnonymousInitiator = 0b0000_0010, } -#[derive(Clone, Copy, PartialEq, Debug)] +// bitmask used: 0b0001_1100 +#[derive(Clone, Copy, PartialEq, Debug, IntoPrimitive, TryFromPrimitive)] +#[repr(u8)] pub enum KKTMode { - OneWay, - Mutual, + OneWay = 0b0000_0000, + Mutual = 0b0000_0100, } -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, PartialEq)] pub struct KKTContext { version: u8, message_sequence: u8, @@ -127,11 +135,14 @@ impl KKTContext { } } - pub fn header_len(&self) -> usize { + pub const fn header_len(&self) -> usize { KKT_CONTEXT_LEN } - pub fn session_id_len(&self) -> usize { + pub const fn session_id_len(&self) -> usize { + // note: if anyone decides to update this function and changes the constant value, + // you will have to adjust encoding/decoding functions + // match self.role { // KKTRole::Initiator | KKTRole::Responder => SESSION_ID_LENGTH, // It doesn't make sense to send a session_id if we send messages in the clear @@ -144,115 +155,87 @@ impl KKTContext { self.body_len() + self.signature_len() + self.header_len() + self.session_id_len() } - pub fn encode(&self) -> Result, KKTError> { - let mut header_bytes: Vec = Vec::with_capacity(KKT_CONTEXT_LEN); + pub fn encode(&self) -> Result<[u8; KKT_CONTEXT_LEN], KKTError> { + let mut header_bytes = [0u8; KKT_CONTEXT_LEN]; if self.message_sequence >= 1 << 4 { return Err(KKTError::MessageCountLimitReached); } - header_bytes.push((KKT_VERSION << 4) + self.message_sequence); + let ciphersuite_bytes = self.ciphersuite.encode(); - header_bytes.push( - match self.status { - KKTStatus::Ok => 0, - KKTStatus::InvalidRequestFormat => 0b0010_0000, - KKTStatus::InvalidResponseFormat => 0b0100_0000, - KKTStatus::InvalidSignature => 0b0110_0000, - KKTStatus::UnsupportedCiphersuite => 0b1000_0000, - KKTStatus::UnsupportedKKTVersion => 0b1010_0000, - KKTStatus::InvalidKey => 0b1100_0000, - KKTStatus::Timeout => 0b1110_0000, - } + match self.mode { - KKTMode::OneWay => 0, - KKTMode::Mutual => 0b0000_0100, - } + match self.role { - KKTRole::Initiator => 0, - KKTRole::Responder => 1, - KKTRole::AnonymousInitiator => 2, - }, - ); + header_bytes[0] = (KKT_VERSION << 4) + self.message_sequence; + header_bytes[1] = u8::from(self.status) + u8::from(self.mode) + u8::from(self.role); - header_bytes.extend_from_slice(&self.ciphersuite.encode()); - header_bytes.push(0); + let mut i = 2; + for b in ciphersuite_bytes.into_iter() { + header_bytes[i] = b; + i += 1; + } + header_bytes[i] = 0; Ok(header_bytes) } - pub fn try_decode(header_bytes: &[u8]) -> Result { - if header_bytes.len() == KKT_CONTEXT_LEN { - let kkt_version = header_bytes[0] & 0b1111_0000; + pub fn try_decode(header_bytes: [u8; KKT_CONTEXT_LEN]) -> Result { + let kkt_version = (header_bytes[0] & 0b1111_0000) >> 4; + let message_sequence_counter = header_bytes[0] & 0b0000_1111; - let message_sequence_counter = header_bytes[0] & 0b0000_1111; + // We only check if stuff is valid here, not necessarily if it's compatible - // We only check if stuff is valid here, not necessarily if it's compatible - - if (kkt_version >> 4) > KKT_VERSION { - return Err(KKTError::FrameDecodingError { - info: format!("Header - Invalid KKT Version: {}", kkt_version >> 4), - }); - } - - let status = match header_bytes[1] & 0b1110_0000 { - 0 => KKTStatus::Ok, - 0b0010_0000 => KKTStatus::InvalidRequestFormat, - 0b0100_0000 => KKTStatus::InvalidResponseFormat, - 0b0110_0000 => KKTStatus::InvalidSignature, - 0b1000_0000 => KKTStatus::UnsupportedCiphersuite, - 0b1010_0000 => KKTStatus::UnsupportedKKTVersion, - 0b1100_0000 => KKTStatus::InvalidKey, - 0b1110_0000 => KKTStatus::Timeout, - _ => { - return Err(KKTError::FrameDecodingError { - info: format!( - "Header - Invalid KKT Status: {}", - header_bytes[1] & 0b1110_0000 - ), - }); - } - }; - - let role = match header_bytes[1] & 0b0000_0011 { - 0 => KKTRole::Initiator, - 1 => KKTRole::Responder, - 2 => KKTRole::AnonymousInitiator, - _ => { - return Err(KKTError::FrameDecodingError { - info: format!( - "Header - Invalid KKT Role: {}", - header_bytes[1] & 0b0000_0011 - ), - }); - } - }; - - let mode = match (header_bytes[1] & 0b0001_1100) >> 2 { - 0 => KKTMode::OneWay, - 1 => KKTMode::Mutual, - _ => { - return Err(KKTError::FrameDecodingError { - info: format!( - "Header - Invalid KKT Mode: {}", - (header_bytes[1] & 0b0001_1100) >> 2 - ), - }); - } - }; - - Ok(KKTContext { - version: kkt_version, - status, - mode, - role, - ciphersuite: Ciphersuite::decode(&header_bytes[2..6])?, - message_sequence: message_sequence_counter, - }) - } else { - Err(KKTError::FrameDecodingError { - info: format!( - "Header - Invalid Header Length: actual: {} != expected: {}", - header_bytes.len(), - KKT_CONTEXT_LEN - ), - }) + if kkt_version > KKT_VERSION { + return Err(KKTError::FrameDecodingError { + info: format!("Header - Invalid KKT Version: {kkt_version}"), + }); } + + let raw_kkt_status = header_bytes[1] & 0b1110_0000; + let raw_kkt_role = header_bytes[1] & 0b0000_0011; + let raw_kkt_mode = header_bytes[1] & 0b0001_1100; + + let status = + KKTStatus::try_from(raw_kkt_status).map_err(|_| KKTError::FrameDecodingError { + info: format!("Header - Invalid KKT Status: {raw_kkt_status}"), + })?; + let role = KKTRole::try_from(raw_kkt_role).map_err(|_| KKTError::FrameDecodingError { + info: format!("Header - Invalid KKT Role: {raw_kkt_role}"), + })?; + let mode = KKTMode::try_from(raw_kkt_mode).map_err(|_| KKTError::FrameDecodingError { + info: format!("Header - Invalid KKT Mode: {raw_kkt_mode}"), + })?; + + let ciphersuite_bytes = header_bytes[2..6].try_into().map_err(|_| { + KKTError::CiphersuiteDecodingError { + info: format!( + "Incorrect Encoding Length: actual: 4 != expected: {CIPHERSUITE_ENCODING_LEN}", + ), + } + })?; + + Ok(KKTContext { + version: kkt_version, + status, + mode, + role, + ciphersuite: Ciphersuite::decode(ciphersuite_bytes)?, + message_sequence: message_sequence_counter, + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn kkt_context_encoding() { + let valid_context = KKTContext::new( + KKTRole::Initiator, + KKTMode::Mutual, + Ciphersuite::decode([255, 1, 0, 0]).unwrap(), + ) + .unwrap(); + let encoded = valid_context.encode().unwrap(); + let decoded = KKTContext::try_decode(encoded).unwrap(); + + assert_eq!(decoded, valid_context); } } diff --git a/common/nym-kkt/src/encryption.rs b/common/nym-kkt/src/encryption.rs index 65ac46f0ac..df6c9cd4e6 100644 --- a/common/nym-kkt/src/encryption.rs +++ b/common/nym-kkt/src/encryption.rs @@ -1,95 +1,251 @@ -use core::hash; +use blake3::Hasher; -use blake3::{Hash, Hasher}; -use curve25519_dalek::digest::DynDigest; -use libcrux_psq::traits::Ciphertext; -use nym_crypto::symmetric::aead::{AeadKey, Nonce}; -use nym_crypto::{ - aes::Aes256, - asymmetric::x25519::{self, PrivateKey, PublicKey}, - generic_array::GenericArray, - Aes256GcmSiv, -}; -// use rand::{CryptoRng, RngCore}; +use libcrux_chacha20poly1305::{NONCE_LEN, TAG_LEN}; + +use nym_sphinx::{PrivateKey, PublicKey}; + +use rand::{CryptoRng, RngCore}; use zeroize::Zeroize; -use nym_crypto::aes::cipher::crypto_common::rand_core::{CryptoRng, RngCore}; +use crate::kkt::KKT_INITIAL_FRAME_AAD; +use crate::{ + ciphersuite::CURVE25519_KEY_LEN, context::KKTContext, error::KKTError, frame::KKTFrame, +}; -use crate::error::KKTError; +#[derive(Clone, Copy, Zeroize)] +pub struct KKTSessionSecret([u8; 32]); -fn generate_round_trip_symmetric_key( - rng: &mut R, - remote_public_key: &PublicKey, -) -> ([u8; 64], [u8; 32]) -where - R: CryptoRng + RngCore, -{ - let mut s = x25519::PrivateKey::new(rng); - let gs = s.public_key(); +impl KKTSessionSecret { + pub fn new(remote_public_key: &PublicKey) -> (Self, PublicKey) { + // this doesn't use the newer rand crate + let ephemeral_private_key = PrivateKey::random(); + let ephemeral_public_key = PublicKey::from(&ephemeral_private_key); - let mut gbs = s.diffie_hellman(remote_public_key); - s.zeroize(); + ( + Self::derive(&ephemeral_private_key, remote_public_key), + ephemeral_public_key, + ) + } + pub fn from_bytes(secret: [u8; 32]) -> Self { + Self(secret) + } + pub fn try_derive(private_key: &PrivateKey, public_key: &[u8]) -> Result { + let mut pub_key: [u8; 32] = [0u8; 32]; + pub_key.copy_from_slice(&public_key[0..CURVE25519_KEY_LEN]); - let mut message: [u8; 64] = [0u8; 64]; - message[0..32].clone_from_slice(gs.as_bytes()); + // Todo: check validity of pk... + let pk = PublicKey::from(pub_key); + Ok(Self::derive(private_key, &pk)) + } - let mut hasher = Hasher::new(); + pub fn derive(private_key: &PrivateKey, public_key: &PublicKey) -> Self { + let mut shared_secret = private_key.diffie_hellman(public_key); - hasher.update(&gbs); - gbs.zeroize(); - let key: [u8; 32] = hasher.finalize().as_bytes().to_owned(); + let mut hasher = Hasher::new(); - hasher.update(remote_public_key.as_bytes()); - hasher.update(gs.as_bytes()); + hasher.update(shared_secret.as_bytes()); + shared_secret.zeroize(); - hasher.finalize_into_reset(&mut message[32..64]); - - (message, key) -} - -fn extract_shared_secret(b: &PrivateKey, message: &[u8; 64]) -> Result<[u8; 32], KKTError> { - let gs = PublicKey::from_bytes(&message[0..32])?; - - let mut gsb = b.diffie_hellman(&gs); - - let mut hasher = Hasher::new(); - hasher.update(&gsb); - gsb.zeroize(); - let key: [u8; 32] = hasher.finalize().as_bytes().to_owned(); - - hasher.update(b.public_key().as_bytes()); - hasher.update(gs.as_bytes()); - - // This runs in constant time - if hasher.finalize() == message[32..64] { - Ok(key) - } else { - Err(KKTError::X25519Error { - info: format!("Symmetric Key Hash Validation Error"), - }) + Self(hasher.finalize().as_bytes().to_owned()) + } + pub fn as_bytes(&self) -> &[u8; 32] { + &self.0 } } -fn encrypt(mut key: [u8; 32], message: &[u8]) -> Result, KKTError> { - // The empty nonce is fine since we use the key once. - let nonce = Nonce::::from_slice(&[]); +pub fn encrypt_initial_kkt_frame( + rng: &mut R, + remote_public_key: &PublicKey, + kkt_frame: &KKTFrame, +) -> Result<(KKTSessionSecret, Vec), KKTError> +where + R: CryptoRng + RngCore, +{ + let (session_secret_key, ephemeral_public_key) = KKTSessionSecret::new(remote_public_key); - let ciphertext = - nym_crypto::symmetric::aead::encrypt::(&key.into(), nonce, message)?; + let mut encrypted_frame = + encrypt_kkt_frame(rng, &session_secret_key, kkt_frame, KKT_INITIAL_FRAME_AAD)?; - key.zeroize(); + let mut output_buffer = Vec::with_capacity(encrypted_frame.len() + CURVE25519_KEY_LEN); + output_buffer.extend_from_slice(ephemeral_public_key.as_bytes()); + output_buffer.append(&mut encrypted_frame); - Ok(ciphertext) + // [ 32 | 12 | ciphertext | 16]; + // [eph_pub_key | nonce | ciphertext | tag]; + Ok((session_secret_key, output_buffer)) } -fn decrypt(key: [u8; 32], ciphertext: Vec) -> Vec { - // The empty nonce is fine since we use the key once. - let nonce = Nonce::::from_slice(&[]); +pub fn decrypt_initial_kkt_frame( + responder_private_key: &PrivateKey, + encrypted_frame_bytes: &[u8], +) -> Result<(KKTSessionSecret, KKTFrame, KKTContext), KKTError> { + if encrypted_frame_bytes.len() < CURVE25519_KEY_LEN + TAG_LEN + NONCE_LEN { + Err(KKTError::AEADError { + info: "Encrypted KKT Frame is too short.", + }) + } else { + let shared_secret = KKTSessionSecret::try_derive( + responder_private_key, + &encrypted_frame_bytes[0..CURVE25519_KEY_LEN], + )?; - let ciphertext = - nym_crypto::symmetric::aead::encrypt::(&key.into(), nonce, message)?; - - key.zeroize(); - - Ok(ciphertext) + let (kkt_frame, kkt_context) = decrypt_kkt_frame( + &shared_secret, + &encrypted_frame_bytes[CURVE25519_KEY_LEN..], + KKT_INITIAL_FRAME_AAD, + )?; + Ok((shared_secret, kkt_frame, kkt_context)) + } +} + +pub fn encrypt_kkt_frame( + rng: &mut R, + secret_key: &KKTSessionSecret, + kkt_frame: &KKTFrame, + aad: &[u8], +) -> Result, KKTError> +where + R: CryptoRng + RngCore, +{ + let kkt_frame_bytes = kkt_frame.to_bytes(); + + // generate nonce + let mut nonce: [u8; NONCE_LEN] = [0u8; NONCE_LEN]; + rng.fill_bytes(&mut nonce); + + let mut ciphertext = encrypt(secret_key.as_bytes(), &kkt_frame_bytes, aad, &nonce)?; + + // [ 12 | ciphertext | 16]; + // [nonce | ciphertext | tag]; + let mut output_buffer: Vec = + Vec::with_capacity(NONCE_LEN + kkt_frame_bytes.len() + TAG_LEN); + + output_buffer.extend_from_slice(&nonce); + output_buffer.append(&mut ciphertext); + + Ok(output_buffer) +} + +// kkt_frame_bytes should look like this +// [ 12 | ciphertext | 16]; +// [nonce | ciphertext | tag]; +pub fn decrypt_kkt_frame( + secret_key: &KKTSessionSecret, + kkt_frame_bytes: &[u8], + aad: &[u8], +) -> Result<(KKTFrame, KKTContext), KKTError> { + let mut nonce: [u8; NONCE_LEN] = [0u8; NONCE_LEN]; + nonce.copy_from_slice(&kkt_frame_bytes[0..NONCE_LEN]); + + let plaintext = decrypt( + secret_key.as_bytes(), + &kkt_frame_bytes[NONCE_LEN..], + aad, + &nonce, + )?; + + KKTFrame::from_bytes(&plaintext) +} + +fn encrypt( + secret_key: &[u8; 32], + plaintext: &[u8], + aad: &[u8], + nonce: &[u8; NONCE_LEN], +) -> Result, KKTError> { + let mut output_buffer = vec![0; plaintext.len() + TAG_LEN]; + libcrux_chacha20poly1305::encrypt(secret_key, plaintext, &mut output_buffer, aad, nonce)?; + Ok(output_buffer) +} + +fn decrypt( + secret_key: &[u8; 32], + ciphertext: &[u8], + aad: &[u8], + nonce: &[u8; NONCE_LEN], +) -> Result, KKTError> { + let mut output_buffer = vec![0; ciphertext.len() - TAG_LEN]; + libcrux_chacha20poly1305::decrypt(secret_key, &mut output_buffer, ciphertext, aad, nonce)?; + Ok(output_buffer) +} + +#[cfg(test)] +mod test { + use crate::ciphersuite::Ciphersuite; + use crate::context::{KKTContext, KKTMode, KKTRole}; + use crate::encryption::{decrypt_kkt_frame, encrypt_kkt_frame}; + use crate::frame::{KKT_SESSION_ID_LEN, KKTFrame}; + use crate::{ + ciphersuite::HASH_LEN_256, + encryption::{KKTSessionSecret, decrypt, encrypt}, + key_utils::generate_keypair_x25519, + }; + use rand::{RngCore, SeedableRng, rng}; + use rand_chacha::ChaCha20Rng; + + #[test] + fn test_keygen() { + let responder_x25519_keypair = generate_keypair_x25519(); + + let (session_secret_key, ephemeral_public_key) = + KKTSessionSecret::new(&responder_x25519_keypair.1); + + let shared_secret = KKTSessionSecret::try_derive( + &responder_x25519_keypair.0, + ephemeral_public_key.as_bytes().as_slice(), + ) + .unwrap(); + + assert_eq!(shared_secret.as_bytes(), session_secret_key.as_bytes()) + } + + #[test] + fn test_encryption() { + let mut rng = rng(); + + let mut secret_key = [0u8; HASH_LEN_256]; + rng.fill_bytes(&mut secret_key); + + let mut plaintext = vec![0; 100]; + rng.fill_bytes(&mut plaintext); + + let mut nonce = [0; 12]; + rng.fill_bytes(&mut nonce); + + let mut aad = vec![0; 124]; + rng.fill_bytes(&mut aad); + + let ciphertext = encrypt(&secret_key, &plaintext, &aad, &nonce).unwrap(); + + let o_plaintext = decrypt(&secret_key, &ciphertext, &aad, &nonce).unwrap(); + + assert_eq!(o_plaintext, plaintext) + } + + #[test] + fn kkt_frame_encryption() -> anyhow::Result<()> { + let mut rng = ChaCha20Rng::seed_from_u64(42); + let session_key = KKTSessionSecret::from_bytes([42u8; 32]); + let aad = b"my-amazing-aad"; + + let valid_context = KKTContext::new( + KKTRole::Initiator, + KKTMode::Mutual, + Ciphersuite::decode([255, 1, 0, 0])?, + )?; + let dummy_frame = KKTFrame::new( + valid_context.encode()?, + &[2u8; 32], + [3u8; KKT_SESSION_ID_LEN], + &[4u8; 64], + ); + + let ciphertext = encrypt_kkt_frame(&mut rng, &session_key, &dummy_frame, aad.as_slice())?; + + let (frame, context) = decrypt_kkt_frame(&session_key, &ciphertext, aad.as_slice())?; + + assert_eq!(dummy_frame, frame); + assert_eq!(context, valid_context); + Ok(()) + } } diff --git a/common/nym-kkt/src/error.rs b/common/nym-kkt/src/error.rs index 0144fef6ac..61744154a1 100644 --- a/common/nym-kkt/src/error.rs +++ b/common/nym-kkt/src/error.rs @@ -1,6 +1,7 @@ // Copyright 2025 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use std::fmt::Debug; use thiserror::Error; use crate::context::KKTStatus; @@ -44,6 +45,9 @@ pub enum KKTError { #[error("{}", info)] X25519Error { info: &'static str }, + #[error("{}", info)] + AEADError { info: &'static str }, + #[error("Generic libcrux error")] LibcruxError, } @@ -87,3 +91,27 @@ impl From for KKTError { } } } +impl From for KKTError { + fn from(err: libcrux_chacha20poly1305::AeadError) -> Self { + KKTError::KEMError { + info: match err { + libcrux_chacha20poly1305::AeadError::PlaintextTooLarge => { + "Plaintext is longer than u32::MAX" + } + libcrux_chacha20poly1305::AeadError::CiphertextTooLarge => { + "Ciphertext is longer than u32::MAX" + } + libcrux_chacha20poly1305::AeadError::AadTooLarge => "Aad is longer than u32::MAX", + libcrux_chacha20poly1305::AeadError::CiphertextTooShort => { + "The provided destination ciphertext does not fit the ciphertext and tag" + } + libcrux_chacha20poly1305::AeadError::PlaintextTooShort => { + "The provided destination plaintext is too short to fit the decrypted plaintext" + } + libcrux_chacha20poly1305::AeadError::InvalidCiphertext => { + "The ciphertext is not a valid encryption under the given key and nonce." + } + }, + } + } +} diff --git a/common/nym-kkt/src/frame.rs b/common/nym-kkt/src/frame.rs index 1745b997f1..6502af882c 100644 --- a/common/nym-kkt/src/frame.rs +++ b/common/nym-kkt/src/frame.rs @@ -14,9 +14,12 @@ use crate::{ pub const KKT_SESSION_ID_LEN: usize = 16; +pub type KKTSessionId = [u8; KKT_SESSION_ID_LEN]; + +#[derive(Debug, PartialEq, Clone)] pub struct KKTFrame { - context: Vec, - session_id: Vec, + context: [u8; KKT_CONTEXT_LEN], + session_id: KKTSessionId, body: Vec, signature: Vec, } @@ -27,20 +30,31 @@ pub struct KKTFrame { // if coming from responder => body has the responder's kem public key and the signature is over the context + body + session_id. impl KKTFrame { - pub fn new(context: &[u8], body: &[u8], session_id: &[u8], signature: &[u8]) -> Self { + pub fn new( + context: [u8; KKT_CONTEXT_LEN], + body: &[u8], + session_id: [u8; KKT_SESSION_ID_LEN], + signature: &[u8], + ) -> Self { Self { - context: Vec::from(context), + context, body: Vec::from(body), - session_id: Vec::from(session_id), + session_id, signature: Vec::from(signature), } } pub fn context_ref(&self) -> &[u8] { &self.context } + + pub fn context(&self) -> Result { + KKTContext::try_decode(self.context) + } + pub fn signature_ref(&self) -> &[u8] { &self.signature } + pub fn body_ref(&self) -> &[u8] { &self.body } @@ -48,6 +62,10 @@ impl KKTFrame { pub fn session_id_ref(&self) -> &[u8] { &self.session_id } + pub fn session_id(&self) -> [u8; KKT_SESSION_ID_LEN] { + self.session_id + } + pub fn signature_mut(&mut self) -> &mut [u8] { &mut self.signature } @@ -73,57 +91,65 @@ impl KKTFrame { } pub fn from_bytes(bytes: &[u8]) -> Result<(Self, KKTContext), KKTError> { + let len = bytes.len(); if bytes.len() < KKT_CONTEXT_LEN { - Err(KKTError::FrameDecodingError { + return Err(KKTError::FrameDecodingError { info: format!( - "Frame is shorter than expected context length: actual {} != expected {}", - bytes.len(), - KKT_CONTEXT_LEN + "Frame is shorter than expected context length: actual {len} != expected {KKT_CONTEXT_LEN}", ), - }) - } else { - let context_bytes = Vec::from(&bytes[0..KKT_CONTEXT_LEN]); - - let context = KKTContext::try_decode(&context_bytes)?; - - let (mut session_id, mut body, mut signature): (Vec, Vec, Vec) = - (vec![], vec![], vec![]); - - if bytes.len() == context.full_message_len() { - if context.body_len() > 0 { - body.extend_from_slice( - &bytes[KKT_CONTEXT_LEN..KKT_CONTEXT_LEN + context.body_len()], - ); - } - if context.session_id_len() > 0 { - session_id.extend_from_slice( - &bytes[KKT_CONTEXT_LEN + context.body_len() - ..KKT_CONTEXT_LEN + context.body_len() + context.session_id_len()], - ); - } - if context.signature_len() > 0 { - signature.extend_from_slice( - &bytes[KKT_CONTEXT_LEN + context.body_len() + context.session_id_len() - ..KKT_CONTEXT_LEN - + context.body_len() - + context.session_id_len() - + context.signature_len()], - ); - } - - Ok(( - KKTFrame::new(&context_bytes, &body, &session_id, &signature), - context, - )) - } else { - Err(KKTError::FrameDecodingError { - info: format!( - "Frame is shorter than expected: actual {} != expected {}", - bytes.len(), - context.full_message_len() - ), - }) - } + }); } + + // SAFETY: we're using exactly KKT_CONTEXT_LEN bytes + #[allow(clippy::unwrap_used)] + let context_bytes = bytes[0..KKT_CONTEXT_LEN].try_into().unwrap(); + let context = KKTContext::try_decode(context_bytes)?; + + if bytes.len() != context.full_message_len() { + return Err(KKTError::FrameDecodingError { + info: format!( + "Frame is shorter than expected: actual {len} != expected {}", + context.full_message_len() + ), + }); + } + + let mut body = Vec::new(); + let mut signature = Vec::new(); + + // decode body + if context.body_len() > 0 { + let body_bytes = &bytes[KKT_CONTEXT_LEN..KKT_CONTEXT_LEN + context.body_len()]; + body.extend_from_slice(body_bytes); + } + + let session_bytes = &bytes[KKT_CONTEXT_LEN + context.body_len() + ..KKT_CONTEXT_LEN + context.body_len() + KKT_SESSION_ID_LEN]; + // SAFETY: we're using exactly KKT_SESSION_ID_LEN bytes and we checked for sufficient bytes + #[allow(clippy::unwrap_used)] + let session_id = session_bytes.try_into().unwrap(); + + // // old code left for reference if session id becomes variable in length: + // if context.session_id_len() > 0 { + // session_id.extend_from_slice( + // &bytes[KKT_CONTEXT_LEN + context.body_len() + // ..KKT_CONTEXT_LEN + context.body_len() + context.session_id_len()], + // ); + // } + + // decode signature + if context.signature_len() > 0 { + let signature_bytes = &bytes[KKT_CONTEXT_LEN + context.body_len() + KKT_SESSION_ID_LEN + ..KKT_CONTEXT_LEN + + context.body_len() + + KKT_SESSION_ID_LEN + + context.signature_len()]; + signature.extend_from_slice(signature_bytes); + } + + Ok(( + KKTFrame::new(context_bytes, &body, session_id, &signature), + context, + )) } } diff --git a/common/nym-kkt/src/key_utils.rs b/common/nym-kkt/src/key_utils.rs index 1ab18934e0..302f7a125f 100644 --- a/common/nym-kkt/src/key_utils.rs +++ b/common/nym-kkt/src/key_utils.rs @@ -7,7 +7,22 @@ use classic_mceliece_rust::keypair_boxed; use libcrux_kem::{Algorithm, key_gen}; use libcrux_sha3; +use nym_crypto::asymmetric::ed25519; use rand::{CryptoRng, RngCore}; +pub fn generate_keypair_ed25519(rng: &mut R, index: Option) -> ed25519::KeyPair +where + R: RngCore + CryptoRng, +{ + let mut secret_initiator: [u8; 32] = [0u8; 32]; + rng.fill_bytes(&mut secret_initiator); + ed25519::KeyPair::from_secret(secret_initiator, index.unwrap_or(0)) +} + +pub fn generate_keypair_x25519() -> (nym_sphinx::PrivateKey, nym_sphinx::PublicKey) { + let private_key = nym_sphinx::PrivateKey::random(); + let public_key = nym_sphinx::PublicKey::from(&private_key); + (private_key, public_key) +} // (decapsulation_key, encapsulation_key) pub fn generate_keypair_libcrux( diff --git a/common/nym-kkt/src/kkt.rs b/common/nym-kkt/src/kkt.rs index 7fcef8d3e3..f2fba1ace5 100644 --- a/common/nym-kkt/src/kkt.rs +++ b/common/nym-kkt/src/kkt.rs @@ -14,8 +14,8 @@ use rand::{CryptoRng, RngCore}; use crate::{ ciphersuite::{Ciphersuite, EncapsulationKey}, context::{KKTContext, KKTMode}, + encryption::{decrypt_initial_kkt_frame, decrypt_kkt_frame, encrypt_kkt_frame}, error::KKTError, - frame::KKTFrame, }; // Re-export core session functions for advanced use cases @@ -24,7 +24,13 @@ pub use crate::session::{ responder_ingest_message, responder_process, }; -/// Request a KEM public key from a responder (OneWay mode). +use crate::encryption::{KKTSessionSecret, encrypt_initial_kkt_frame}; +use crate::frame::KKTFrame; + +pub(crate) const KKT_RESPONSE_AAD: &[u8] = b"KKT_Response"; +pub(crate) const KKT_INITIAL_FRAME_AAD: &[u8] = b"KKT_INITIAL_FRAME"; + +/// Perform an *Encrypted* request for a KEM public key from a responder (OneWay mode). /// /// This is the client-side operation that initiates a KKT exchange. /// The request will be signed with the provided signing key. @@ -33,17 +39,20 @@ pub use crate::session::{ /// * `rng` - Random number generator /// * `ciphersuite` - Negotiated ciphersuite (KEM, hash, signature algorithms) /// * `signing_key` - Client's Ed25519 signing key for authentication +/// * `responder_dh_public_key` - Responder's long-term x25519 Diffie-Hellman public key /// /// # Returns +/// * `KKTSessionSecret` - Session Secret Key to use when decrypting responses /// * `KKTContext` - Context to use when validating the response -/// * `KKTFrame` - Signed request frame to send to responder +/// * `Vec` - Contains the client's ephemeral public key and encrypted and signed bytes to send to responder /// /// # Example /// ```ignore -/// let (context, request_frame) = request_kem_key( +/// let (session_secret, context, request_frame) = request_kem_key( /// &mut rng, /// ciphersuite, /// client_signing_key, +/// responder_dh_public_key, /// )?; /// // Send request_frame to gateway /// ``` @@ -51,13 +60,21 @@ pub fn request_kem_key( rng: &mut R, ciphersuite: Ciphersuite, signing_key: &ed25519::PrivateKey, -) -> Result<(KKTContext, KKTFrame), KKTError> { + responder_dh_public_key: &nym_sphinx::PublicKey, +) -> Result<(KKTSessionSecret, KKTContext, Vec), KKTError> { // OneWay mode: client only wants responder's KEM key // None: client doesn't send their own KEM key - initiator_process(rng, KKTMode::OneWay, ciphersuite, signing_key, None) + let (initiator_context, initiator_frame) = + initiator_process(rng, KKTMode::OneWay, ciphersuite, signing_key, None)?; + + // Generate the session's shared secret and encrypt the Initiator's request + let (session_secret, encrypted_request_bytes) = + encrypt_initial_kkt_frame(rng, responder_dh_public_key, &initiator_frame)?; + + Ok((session_secret, initiator_context, encrypted_request_bytes)) } -/// Validate a KKT response and extract the responder's KEM public key. +/// Decrypt, validate an *Encrypted* KKT response and extract the responder's KEM public key. /// /// This is the client-side operation that processes the gateway's response. /// It verifies the signature and validates the key hash against the expected value @@ -65,6 +82,7 @@ pub fn request_kem_key( /// /// # Arguments /// * `context` - Context from the initial request +/// * `session_secret` - Session Secret Key (generated with request) /// * `responder_vk` - Responder's Ed25519 verification key (from directory) /// * `expected_key_hash` - Expected hash of responder's KEM key (from directory) /// * `response_bytes` - Serialized response frame from responder @@ -76,7 +94,8 @@ pub fn request_kem_key( /// ```ignore /// let gateway_kem_key = validate_kem_response( /// &mut context, -/// gateway_verification_key, +/// &session_secret, +/// &gateway_verification_key, /// &expected_hash_from_directory, /// &response_bytes, /// )?; @@ -84,23 +103,44 @@ pub fn request_kem_key( /// ``` pub fn validate_kem_response<'a>( context: &mut KKTContext, + session_secret: &KKTSessionSecret, responder_vk: &ed25519::PublicKey, expected_key_hash: &[u8], - response_bytes: &[u8], + encrypted_response_bytes: &[u8], ) -> Result, KKTError> { - initiator_ingest_response(context, responder_vk, expected_key_hash, response_bytes) + let (responder_frame, responder_context) = + decrypt_kkt_response_frame(session_secret, encrypted_response_bytes)?; + + initiator_ingest_response( + context, + &responder_frame, + &responder_context, + responder_vk, + expected_key_hash, + ) } -/// Handle a KKT request and generate a signed response with the responder's KEM key. +/// Decrypts and validates an *Encrypted* KKT response +/// +/// This is the client-side operation that processes the gateway's response. +pub fn decrypt_kkt_response_frame( + session_secret: &KKTSessionSecret, + frame_ciphertext: &[u8], +) -> Result<(KKTFrame, KKTContext), KKTError> { + decrypt_kkt_frame(session_secret, frame_ciphertext, KKT_RESPONSE_AAD) +} + +/// Handle an *Encrypted* KKT request and generate a signed response with the responder's KEM key. /// /// This is the gateway-side operation that processes a client's KKT request. /// It validates the request signature (if authenticated) and responds with /// the gateway's KEM public key, signed for authenticity. /// /// # Arguments -/// * `request_frame` - Request frame received from initiator +/// * `encrypted_request_bytes` - encrypted KEM request /// * `initiator_vk` - Initiator's Ed25519 verification key (None for anonymous) /// * `responder_signing_key` - Gateway's Ed25519 signing key +/// * `responder_dh_public_key` - Gateway's long-term x25519 Diffie-Hellman private key /// * `responder_kem_key` - Gateway's KEM public key to send /// /// # Returns @@ -116,31 +156,40 @@ pub fn validate_kem_response<'a>( /// )?; /// // Send response_frame back to client /// ``` -pub fn handle_kem_request<'a>( - request_frame: &KKTFrame, +pub fn handle_kem_request<'a, R>( + rng: &mut R, + encrypted_request_bytes: &[u8], initiator_vk: Option<&ed25519::PublicKey>, responder_signing_key: &ed25519::PrivateKey, + responder_dh_private_key: &nym_sphinx::PrivateKey, responder_kem_key: &EncapsulationKey<'a>, -) -> Result { - // Parse context from the request frame - let request_bytes = request_frame.to_bytes(); - let (_, request_context) = KKTFrame::from_bytes(&request_bytes)?; +) -> Result, KKTError> +where + R: RngCore + CryptoRng, +{ + // Compute the session's shared secret, decrypt and parse context from the request frame + + let (session_secret, request_frame, initiator_context) = + decrypt_initial_kkt_frame(responder_dh_private_key, encrypted_request_bytes)?; // Validate the request (verifies signature if initiator_vk provided) let (mut response_context, _) = responder_ingest_message( - &request_context, + &initiator_context, initiator_vk, None, // Not checking initiator's KEM key in OneWay mode - request_frame, + &request_frame, )?; // Generate signed response with our KEM public key - responder_process( + let responder_frame = responder_process( &mut response_context, - request_frame.session_id_ref(), + request_frame.session_id(), responder_signing_key, responder_kem_key, - ) + )?; + + // Encrypt the responder's response with the session's shared secret + encrypt_kkt_frame(rng, &session_secret, &responder_frame, KKT_RESPONSE_AAD) } #[cfg(test)] @@ -158,11 +207,14 @@ mod tests { // Generate Ed25519 keypairs for both parties let mut initiator_secret = [0u8; 32]; rng.fill_bytes(&mut initiator_secret); - let initiator_keypair = ed25519::KeyPair::from_secret(initiator_secret, 0); + let ed25519_init = ed25519::KeyPair::from_secret(initiator_secret, 0); let mut responder_secret = [0u8; 32]; rng.fill_bytes(&mut responder_secret); - let responder_keypair = ed25519::KeyPair::from_secret(responder_secret, 1); + let ed25519_resp = ed25519::KeyPair::from_secret(responder_secret, 1); + + let x25519_resp_priv = nym_sphinx::PrivateKey::random(); + let x25519_resp_pub = nym_sphinx::PublicKey::from(&x25519_resp_priv); // Generate responder's KEM keypair (X25519 for testing) let (_, responder_kem_pk) = generate_keypair_libcrux(&mut rng, KEM::X25519).unwrap(); @@ -185,14 +237,21 @@ mod tests { ); // Client: Request KEM key - let (mut context, request_frame) = - request_kem_key(&mut rng, ciphersuite, initiator_keypair.private_key()).unwrap(); + let (session_key, mut context, request_frame_ciphertext) = request_kem_key( + &mut rng, + ciphersuite, + ed25519_init.private_key(), + &x25519_resp_pub, + ) + .unwrap(); // Gateway: Handle request - let response_frame = handle_kem_request( - &request_frame, - Some(initiator_keypair.public_key()), // Authenticated - responder_keypair.private_key(), + let response_frame_ciphertext = handle_kem_request( + &mut rng, + &request_frame_ciphertext, + Some(ed25519_init.public_key()), // Authenticated + ed25519_resp.private_key(), + &x25519_resp_priv, &responder_kem_key, ) .unwrap(); @@ -200,9 +259,10 @@ mod tests { // Client: Validate response let obtained_key = validate_kem_response( &mut context, - responder_keypair.public_key(), + &session_key, + ed25519_resp.public_key(), &key_hash, - &response_frame.to_bytes(), + &response_frame_ciphertext, ) .unwrap(); @@ -222,6 +282,9 @@ mod tests { let (_, responder_kem_pk) = generate_keypair_libcrux(&mut rng, KEM::X25519).unwrap(); let responder_kem_key = EncapsulationKey::X25519(responder_kem_pk); + let x25519_resp_priv = nym_sphinx::PrivateKey::random(); + let x25519_resp_pub = nym_sphinx::PublicKey::from(&x25519_resp_priv); + let ciphersuite = Ciphersuite::resolve_ciphersuite( KEM::X25519, HashFunction::Blake3, @@ -240,11 +303,17 @@ mod tests { let (mut context, request_frame) = anonymous_initiator_process(&mut rng, ciphersuite).unwrap(); + // Generate the session's shared secret and encrypt the Initiator's request + let (session_secret, encrypted_request_bytes) = + encrypt_initial_kkt_frame(&mut rng, &x25519_resp_pub, &request_frame).unwrap(); + // Gateway: Handle anonymous request let response_frame = handle_kem_request( - &request_frame, + &mut rng, + &encrypted_request_bytes, None, // Anonymous - no verification key responder_keypair.private_key(), + &x25519_resp_priv, &responder_kem_key, ) .unwrap(); @@ -252,9 +321,10 @@ mod tests { // Initiator: Validate response let obtained_key = validate_kem_response( &mut context, + &session_secret, responder_keypair.public_key(), &key_hash, - &response_frame.to_bytes(), + &response_frame, ) .unwrap(); @@ -273,6 +343,9 @@ mod tests { rng.fill_bytes(&mut responder_secret); let responder_keypair = ed25519::KeyPair::from_secret(responder_secret, 1); + let x25519_resp_priv = nym_sphinx::PrivateKey::random(); + let x25519_resp_pub = nym_sphinx::PublicKey::from(&x25519_resp_priv); + // Different keypair for wrong signature let mut wrong_secret = [0u8; 32]; rng.fill_bytes(&mut wrong_secret); @@ -289,14 +362,21 @@ mod tests { ) .unwrap(); - let (_context, request_frame) = - request_kem_key(&mut rng, ciphersuite, initiator_keypair.private_key()).unwrap(); + let (_session_key, _context, request_frame_ciphertext) = request_kem_key( + &mut rng, + ciphersuite, + initiator_keypair.private_key(), + &x25519_resp_pub, + ) + .unwrap(); // Gateway handles request but we provide WRONG verification key let result = handle_kem_request( - &request_frame, + &mut rng, + &request_frame_ciphertext, Some(wrong_keypair.public_key()), // Wrong key! responder_keypair.private_key(), + &x25519_resp_priv, &responder_kem_key, ); @@ -316,6 +396,9 @@ mod tests { rng.fill_bytes(&mut responder_secret); let responder_keypair = ed25519::KeyPair::from_secret(responder_secret, 1); + let x25519_resp_priv = nym_sphinx::PrivateKey::random(); + let x25519_resp_pub = nym_sphinx::PublicKey::from(&x25519_resp_priv); + let (_, responder_kem_pk) = generate_keypair_libcrux(&mut rng, KEM::X25519).unwrap(); let responder_kem_key = EncapsulationKey::X25519(responder_kem_pk); @@ -330,13 +413,20 @@ mod tests { // Use WRONG hash let wrong_hash = [0u8; 32]; - let (mut context, request_frame) = - request_kem_key(&mut rng, ciphersuite, initiator_keypair.private_key()).unwrap(); + let (session_key, mut context, request_frame) = request_kem_key( + &mut rng, + ciphersuite, + initiator_keypair.private_key(), + &x25519_resp_pub, + ) + .unwrap(); let response_frame = handle_kem_request( + &mut rng, &request_frame, Some(initiator_keypair.public_key()), responder_keypair.private_key(), + &x25519_resp_priv, &responder_kem_key, ) .unwrap(); @@ -344,9 +434,10 @@ mod tests { // Client validates with WRONG hash let result = validate_kem_response( &mut context, + &session_key, responder_keypair.public_key(), &wrong_hash, // Wrong! - &response_frame.to_bytes(), + &response_frame, ); // Should fail hash validation diff --git a/common/nym-kkt/src/lib.rs b/common/nym-kkt/src/lib.rs index 348e8fb01c..ee8e336c60 100644 --- a/common/nym-kkt/src/lib.rs +++ b/common/nym-kkt/src/lib.rs @@ -3,28 +3,31 @@ pub mod ciphersuite; pub mod context; -// pub mod encryption; +pub mod encryption; pub mod error; pub mod frame; pub mod key_utils; pub mod kkt; pub mod session; -// pub mod psq; - // This must be less than 4 bits pub const KKT_VERSION: u8 = 1; const _: () = assert!(KKT_VERSION < 1 << 4); #[cfg(test)] mod test { - use nym_crypto::asymmetric::ed25519; - use rand::prelude::*; - + use crate::kkt::KKT_RESPONSE_AAD; use crate::{ ciphersuite::{Ciphersuite, EncapsulationKey, HashFunction, KEM}, + encryption::{ + decrypt_initial_kkt_frame, decrypt_kkt_frame, encrypt_initial_kkt_frame, + encrypt_kkt_frame, + }, frame::KKTFrame, - key_utils::{generate_keypair_libcrux, generate_keypair_mceliece, hash_encapsulation_key}, + key_utils::{ + generate_keypair_ed25519, generate_keypair_libcrux, generate_keypair_mceliece, + generate_keypair_x25519, hash_encapsulation_key, + }, session::{ anonymous_initiator_process, initiator_ingest_response, initiator_process, responder_ingest_message, responder_process, @@ -36,13 +39,9 @@ mod test { let mut rng = rand::rng(); // generate ed25519 keys - let mut secret_initiator: [u8; 32] = [0u8; 32]; - rng.fill_bytes(&mut secret_initiator); - let initiator_ed25519_keypair = ed25519::KeyPair::from_secret(secret_initiator, 0); + let initiator_ed25519_keypair = generate_keypair_ed25519(&mut rng, Some(0)); + let responder_ed25519_keypair = generate_keypair_ed25519(&mut rng, Some(1)); - let mut secret_responder: [u8; 32] = [0u8; 32]; - rng.fill_bytes(&mut secret_responder); - let responder_ed25519_keypair = ed25519::KeyPair::from_secret(secret_responder, 1); for kem in [KEM::MlKem768, KEM::XWing, KEM::X25519, KEM::McEliece] { for hash_function in [ HashFunction::Blake3, @@ -117,7 +116,7 @@ mod test { let r_frame = responder_process( &mut r_context, - i_frame_r.session_id_ref(), + i_frame_r.session_id(), responder_ed25519_keypair.private_key(), &responder_kem_public_key, ) @@ -125,15 +124,18 @@ mod test { let r_bytes = r_frame.to_bytes(); - let obtained_key = initiator_ingest_response( + let (i_frame_r, i_context_r) = KKTFrame::from_bytes(&r_bytes).unwrap(); + + let i_obtained_key = initiator_ingest_response( &mut i_context, + &i_frame_r, + &i_context_r, responder_ed25519_keypair.public_key(), &r_dir_hash, - &r_bytes, ) .unwrap(); - assert_eq!(obtained_key.encode(), r_kem_key_bytes) + assert_eq!(i_obtained_key.encode(), r_kem_key_bytes) } // Initiator, OneWay { @@ -162,7 +164,7 @@ mod test { let r_frame = responder_process( &mut r_context, - i_frame_r.session_id_ref(), + i_frame_r.session_id(), responder_ed25519_keypair.private_key(), &responder_kem_public_key, ) @@ -170,11 +172,14 @@ mod test { let r_bytes = r_frame.to_bytes(); + let (i_frame_r, i_context_r) = KKTFrame::from_bytes(&r_bytes).unwrap(); + let i_obtained_key = initiator_ingest_response( &mut i_context, + &i_frame_r, + &i_context_r, responder_ed25519_keypair.public_key(), &r_dir_hash, - &r_bytes, ) .unwrap(); @@ -208,7 +213,7 @@ mod test { let r_frame = responder_process( &mut r_context, - i_frame_r.session_id_ref(), + i_frame_r.session_id(), responder_ed25519_keypair.private_key(), &responder_kem_public_key, ) @@ -216,15 +221,263 @@ mod test { let r_bytes = r_frame.to_bytes(); - let obtained_key = initiator_ingest_response( + let (i_frame_r, i_context_r) = KKTFrame::from_bytes(&r_bytes).unwrap(); + + let i_obtained_key = initiator_ingest_response( &mut i_context, + &i_frame_r, + &i_context_r, responder_ed25519_keypair.public_key(), &r_dir_hash, - &r_bytes, ) .unwrap(); - assert_eq!(obtained_key.encode(), r_kem_key_bytes) + assert_eq!(i_obtained_key.encode(), r_kem_key_bytes) + } + } + } + } + #[test] + fn test_kkt_psq_e2e_encrypted() { + let mut rng = rand::rng(); + + // generate ed25519 keys + let initiator_ed25519_keypair = generate_keypair_ed25519(&mut rng, Some(0)); + let responder_ed25519_keypair = generate_keypair_ed25519(&mut rng, Some(1)); + + // generate responder x25519 keys + let responder_x25519_keypair = generate_keypair_x25519(); + + for kem in [KEM::MlKem768, KEM::XWing, KEM::X25519, KEM::McEliece] { + for hash_function in [ + HashFunction::Blake3, + HashFunction::SHA256, + HashFunction::SHAKE128, + HashFunction::SHAKE256, + ] { + let ciphersuite = Ciphersuite::resolve_ciphersuite( + kem, + hash_function, + crate::ciphersuite::SignatureScheme::Ed25519, + None, + ) + .unwrap(); + + // generate kem public keys + + let (responder_kem_public_key, initiator_kem_public_key) = match kem { + KEM::MlKem768 => ( + EncapsulationKey::MlKem768( + generate_keypair_libcrux(&mut rng, kem).unwrap().1, + ), + EncapsulationKey::MlKem768( + generate_keypair_libcrux(&mut rng, kem).unwrap().1, + ), + ), + KEM::XWing => ( + EncapsulationKey::XWing(generate_keypair_libcrux(&mut rng, kem).unwrap().1), + EncapsulationKey::XWing(generate_keypair_libcrux(&mut rng, kem).unwrap().1), + ), + KEM::X25519 => ( + EncapsulationKey::X25519( + generate_keypair_libcrux(&mut rng, kem).unwrap().1, + ), + EncapsulationKey::X25519( + generate_keypair_libcrux(&mut rng, kem).unwrap().1, + ), + ), + KEM::McEliece => ( + EncapsulationKey::McEliece(generate_keypair_mceliece(&mut rng).1), + EncapsulationKey::McEliece(generate_keypair_mceliece(&mut rng).1), + ), + }; + + let i_kem_key_bytes = initiator_kem_public_key.encode(); + + let r_kem_key_bytes = responder_kem_public_key.encode(); + + let i_dir_hash = hash_encapsulation_key( + &ciphersuite.hash_function(), + ciphersuite.hash_len(), + &i_kem_key_bytes, + ); + + let r_dir_hash = hash_encapsulation_key( + &ciphersuite.hash_function(), + ciphersuite.hash_len(), + &r_kem_key_bytes, + ); + + // Anonymous Initiator, OneWay + { + let (mut i_context, i_frame) = + anonymous_initiator_process(&mut rng, ciphersuite).unwrap(); + + // encryption - initiator frame + + let (i_session_secret, i_bytes) = + encrypt_initial_kkt_frame(&mut rng, &responder_x25519_keypair.1, &i_frame) + .unwrap(); + + // decryption - initiator frame + + let (r_session_secret, i_frame_r, i_context_r) = + decrypt_initial_kkt_frame(&responder_x25519_keypair.0, &i_bytes).unwrap(); + + let (mut r_context, _) = + responder_ingest_message(&i_context_r, None, None, &i_frame_r).unwrap(); + + let r_frame = responder_process( + &mut r_context, + i_frame_r.session_id(), + responder_ed25519_keypair.private_key(), + &responder_kem_public_key, + ) + .unwrap(); + + // encryption - responder frame + let r_bytes = + encrypt_kkt_frame(&mut rng, &r_session_secret, &r_frame, KKT_RESPONSE_AAD) + .unwrap(); + + // decryption - responder frame + + let (i_frame_r, i_context_r) = + decrypt_kkt_frame(&i_session_secret, &r_bytes, KKT_RESPONSE_AAD).unwrap(); + + let i_obtained_key = initiator_ingest_response( + &mut i_context, + &i_frame_r, + &i_context_r, + responder_ed25519_keypair.public_key(), + &r_dir_hash, + ) + .unwrap(); + + assert_eq!(i_obtained_key.encode(), r_kem_key_bytes) + } + // Initiator, OneWay + { + let (mut i_context, i_frame) = initiator_process( + &mut rng, + crate::context::KKTMode::OneWay, + ciphersuite, + initiator_ed25519_keypair.private_key(), + None, + ) + .unwrap(); + + // encryption - initiator frame + + let (i_session_secret, i_bytes) = + encrypt_initial_kkt_frame(&mut rng, &responder_x25519_keypair.1, &i_frame) + .unwrap(); + + // decryption - initiator frame + + let (r_session_secret, i_frame_r, r_context) = + decrypt_initial_kkt_frame(&responder_x25519_keypair.0, &i_bytes).unwrap(); + + let (mut r_context, r_obtained_key) = responder_ingest_message( + &r_context, + Some(initiator_ed25519_keypair.public_key()), + None, + &i_frame_r, + ) + .unwrap(); + + assert!(r_obtained_key.is_none()); + + let r_frame = responder_process( + &mut r_context, + i_frame_r.session_id(), + responder_ed25519_keypair.private_key(), + &responder_kem_public_key, + ) + .unwrap(); + + // encryption - responder frame + let r_bytes = + encrypt_kkt_frame(&mut rng, &r_session_secret, &r_frame, KKT_RESPONSE_AAD) + .unwrap(); + + // decryption - responder frame + + let (i_frame_r, i_context_r) = + decrypt_kkt_frame(&i_session_secret, &r_bytes, KKT_RESPONSE_AAD).unwrap(); + + let i_obtained_key = initiator_ingest_response( + &mut i_context, + &i_frame_r, + &i_context_r, + responder_ed25519_keypair.public_key(), + &r_dir_hash, + ) + .unwrap(); + + assert_eq!(i_obtained_key.encode(), r_kem_key_bytes) + } + + // Initiator, Mutual + { + let (mut i_context, i_frame) = initiator_process( + &mut rng, + crate::context::KKTMode::Mutual, + ciphersuite, + initiator_ed25519_keypair.private_key(), + Some(&initiator_kem_public_key), + ) + .unwrap(); + + // encryption - initiator frame + + let (i_session_secret, i_bytes) = + encrypt_initial_kkt_frame(&mut rng, &responder_x25519_keypair.1, &i_frame) + .unwrap(); + + // decryption - initiator frame + + let (r_session_secret, i_frame_r, i_context_r) = + decrypt_initial_kkt_frame(&responder_x25519_keypair.0, &i_bytes).unwrap(); + + let (mut r_context, r_obtained_key) = responder_ingest_message( + &i_context_r, + Some(initiator_ed25519_keypair.public_key()), + Some(&i_dir_hash), + &i_frame_r, + ) + .unwrap(); + + assert_eq!(r_obtained_key.unwrap().encode(), i_kem_key_bytes); + + let r_frame = responder_process( + &mut r_context, + i_frame_r.session_id(), + responder_ed25519_keypair.private_key(), + &responder_kem_public_key, + ) + .unwrap(); + + // encryption - responder frame + let r_bytes = + encrypt_kkt_frame(&mut rng, &r_session_secret, &r_frame, KKT_RESPONSE_AAD) + .unwrap(); + + // decryption - responder frame + + let (i_frame_r, i_context_r) = + decrypt_kkt_frame(&i_session_secret, &r_bytes, KKT_RESPONSE_AAD).unwrap(); + + let i_obtained_key = initiator_ingest_response( + &mut i_context, + &i_frame_r, + &i_context_r, + responder_ed25519_keypair.public_key(), + &r_dir_hash, + ) + .unwrap(); + + assert_eq!(i_obtained_key.encode(), r_kem_key_bytes) } } } diff --git a/common/nym-kkt/src/session.rs b/common/nym-kkt/src/session.rs index 75492a6170..88a59500eb 100644 --- a/common/nym-kkt/src/session.rs +++ b/common/nym-kkt/src/session.rs @@ -1,6 +1,7 @@ use nym_crypto::asymmetric::ed25519::{self, Signature}; use rand::{CryptoRng, RngCore}; +use crate::frame::KKTSessionId; use crate::{ ciphersuite::{Ciphersuite, EncapsulationKey}, context::{KKTContext, KKTMode, KKTRole, KKTStatus}, @@ -51,7 +52,7 @@ where Ok(( context, - KKTFrame::new(&context_bytes, body, &session_id, &signature), + KKTFrame::new(context_bytes, body, session_id, &signature), )) } @@ -68,43 +69,38 @@ where let mut session_id = [0u8; KKT_SESSION_ID_LEN]; rng.fill_bytes(&mut session_id); - Ok(( - context, - KKTFrame::new(&context_bytes, &[], &session_id, &[]), - )) + Ok((context, KKTFrame::new(context_bytes, &[], session_id, &[]))) } pub fn initiator_ingest_response<'a>( own_context: &mut KKTContext, + remote_frame: &KKTFrame, + remote_context: &KKTContext, remote_verification_key: &ed25519::PublicKey, expected_hash: &[u8], - message_bytes: &[u8], ) -> Result, KKTError> { - // sizes have to be correct - let (frame, remote_context) = KKTFrame::from_bytes(message_bytes)?; - - check_compatibility(own_context, &remote_context)?; + check_compatibility(own_context, remote_context)?; match remote_context.status() { KKTStatus::Ok => { let mut bytes_to_verify: Vec = Vec::with_capacity( remote_context.full_message_len() - remote_context.signature_len(), ); bytes_to_verify.extend_from_slice(&remote_context.encode()?); - bytes_to_verify.extend_from_slice(frame.body_ref()); - bytes_to_verify.extend_from_slice(frame.session_id_ref()); + bytes_to_verify.extend_from_slice(remote_frame.body_ref()); + bytes_to_verify.extend_from_slice(remote_frame.session_id_ref()); - match Signature::from_bytes(frame.signature_ref()) { + match Signature::from_bytes(remote_frame.signature_ref()) { Ok(sig) => match remote_verification_key.verify(bytes_to_verify, &sig) { Ok(()) => { let received_encapsulation_key = EncapsulationKey::decode( own_context.ciphersuite().kem(), - frame.body_ref(), + remote_frame.body_ref(), )?; match validate_encapsulation_key( &own_context.ciphersuite().hash_function(), own_context.ciphersuite().hash_len(), - frame.body_ref(), + remote_frame.body_ref(), expected_hash, ) { true => Ok(received_encapsulation_key), @@ -206,7 +202,7 @@ pub fn responder_ingest_message<'a>( pub fn responder_process<'a>( own_context: &mut KKTContext, - session_id: &[u8], + session_id: KKTSessionId, signing_key: &ed25519::PrivateKey, encapsulation_key: &EncapsulationKey<'a>, ) -> Result { @@ -218,11 +214,11 @@ pub fn responder_process<'a>( Vec::with_capacity(own_context.full_message_len() - own_context.signature_len()); bytes_to_sign.extend_from_slice(&own_context.encode()?); bytes_to_sign.extend_from_slice(&body); - bytes_to_sign.extend_from_slice(session_id); + bytes_to_sign.extend_from_slice(&session_id); let signature = signing_key.sign(bytes_to_sign).to_bytes(); - Ok(KKTFrame::new(&context_bytes, &body, session_id, &signature)) + Ok(KKTFrame::new(context_bytes, &body, session_id, &signature)) } fn check_compatibility( diff --git a/common/nym-lp/src/kkt_orchestrator.rs b/common/nym-lp/src/kkt_orchestrator.rs index e6b20df373..efb9e2e9d4 100644 --- a/common/nym-lp/src/kkt_orchestrator.rs +++ b/common/nym-lp/src/kkt_orchestrator.rs @@ -37,9 +37,10 @@ //! ).unwrap(); //! //! // Client: Create request -//! let (client_context, request_data) = create_request( +//! let (session_secret, client_context, request_data) = create_request( //! ciphersuite, //! &client_signing_key, +//! &responder_dh_public_key //! ).unwrap(); //! //! // Gateway: Handle request @@ -47,12 +48,14 @@ //! &request_data, //! Some(&client_verification_key), //! &gateway_signing_key, +//! &gateway_dh_private_key, //! &gateway_kem_public_key, //! ).unwrap(); //! //! // Client: Process response //! let gateway_kem_key = process_response( //! client_context, +//! &session_secret, //! &gateway_verification_key, //! &expected_key_hash, //! &response_data, @@ -64,7 +67,7 @@ use crate::message::{KKTRequestData, KKTResponseData}; use nym_crypto::asymmetric::ed25519; use nym_kkt::ciphersuite::{Ciphersuite, EncapsulationKey}; use nym_kkt::context::KKTContext; -use nym_kkt::frame::KKTFrame; +use nym_kkt::encryption::KKTSessionSecret; use nym_kkt::kkt::{handle_kem_request, request_kem_key, validate_kem_response}; /// Creates a KKT request to obtain the responder's KEM public key. @@ -75,8 +78,10 @@ use nym_kkt::kkt::{handle_kem_request, request_kem_key, validate_kem_response}; /// # Arguments /// * `ciphersuite` - Negotiated ciphersuite (KEM, hash, signature algorithms) /// * `signing_key` - Client's Ed25519 signing key for authentication +/// * `responder_dh_public_key` - Gateway's x25519 public key (from directory) /// /// # Returns +/// * `KKTSessionSecret` - Session secret key to encrypt/decrypt KKT messages for this session /// * `KKTContext` - Context to use when validating the response /// * `KKTRequestData` - Serialized KKT request frame to send to gateway /// @@ -85,14 +90,15 @@ use nym_kkt::kkt::{handle_kem_request, request_kem_key, validate_kem_response}; pub fn create_request( ciphersuite: Ciphersuite, signing_key: &ed25519::PrivateKey, -) -> Result<(KKTContext, KKTRequestData), LpError> { + responder_dh_public_key: &nym_sphinx::PublicKey, +) -> Result<(KKTSessionSecret, KKTContext, KKTRequestData), LpError> { // Note: Uses rand 0.9's thread_rng() to match nym-kkt's rand version let mut rng = rand09::rng(); - let (context, frame) = request_kem_key(&mut rng, ciphersuite, signing_key) - .map_err(|e| LpError::KKTError(e.to_string()))?; + let (session_secret, context, request_bytes) = + request_kem_key(&mut rng, ciphersuite, signing_key, responder_dh_public_key) + .map_err(|e| LpError::KKTError(e.to_string()))?; - let request_bytes = frame.to_bytes(); - Ok((context, KKTRequestData(request_bytes))) + Ok((session_secret, context, KKTRequestData(request_bytes))) } /// Processes a KKT response and extracts the responder's KEM public key. @@ -102,6 +108,7 @@ pub fn create_request( /// /// # Arguments /// * `context` - Context from the initial `create_request()` call +/// * `session_secret` - The KKT session secret key from the initial `create_request()` call /// * `responder_vk` - Responder's Ed25519 verification key (from directory) /// * `expected_key_hash` - Expected hash of responder's KEM key (from directory) /// * `response_data` - Serialized KKT response frame from responder @@ -116,12 +123,14 @@ pub fn create_request( /// - Key hash doesn't match expected value pub fn process_response<'a>( mut context: KKTContext, + session_secret: &KKTSessionSecret, responder_vk: &ed25519::PublicKey, expected_key_hash: &[u8], response_data: &KKTResponseData, ) -> Result, LpError> { validate_kem_response( &mut context, + session_secret, responder_vk, expected_key_hash, &response_data.0, @@ -139,6 +148,7 @@ pub fn process_response<'a>( /// * `request_data` - Serialized KKT request frame from initiator /// * `initiator_vk` - Initiator's Ed25519 verification key (None for anonymous) /// * `responder_signing_key` - Gateway's Ed25519 signing key +/// * `responder_dh_private_key` - Gateway's x25519 private key /// * `responder_kem_key` - Gateway's KEM public key to send /// /// # Returns @@ -153,22 +163,21 @@ pub fn handle_request<'a>( request_data: &KKTRequestData, initiator_vk: Option<&ed25519::PublicKey>, responder_signing_key: &ed25519::PrivateKey, + responder_dh_private_key: &nym_sphinx::PrivateKey, responder_kem_key: &EncapsulationKey<'a>, ) -> Result { - // Deserialize request frame - let (request_frame, _) = KKTFrame::from_bytes(&request_data.0) - .map_err(|e| LpError::KKTError(format!("Failed to parse KKT request: {}", e)))?; - + let mut rng = rand09::rng(); // Handle the request and generate response - let response_frame = handle_kem_request( - &request_frame, + let response_bytes = handle_kem_request( + &mut rng, + &request_data.0, initiator_vk, responder_signing_key, + responder_dh_private_key, responder_kem_key, ) .map_err(|e| LpError::KKTError(e.to_string()))?; - let response_bytes = response_frame.to_bytes(); Ok(KKTResponseData(response_bytes)) } @@ -176,7 +185,10 @@ pub fn handle_request<'a>( mod tests { use super::*; use nym_kkt::ciphersuite::{HashFunction, KEM, SignatureScheme}; - use nym_kkt::key_utils::{generate_keypair_libcrux, hash_encapsulation_key}; + use nym_kkt::key_utils::{ + generate_keypair_ed25519, generate_keypair_libcrux, generate_keypair_x25519, + hash_encapsulation_key, + }; use rand09::RngCore; #[test] @@ -184,13 +196,10 @@ mod tests { let mut rng = rand09::rng(); // Generate Ed25519 keypairs for both parties - let mut initiator_secret = [0u8; 32]; - rng.fill_bytes(&mut initiator_secret); - let initiator_keypair = ed25519::KeyPair::from_secret(initiator_secret, 0); + let initiator_ed25519_keypair = generate_keypair_ed25519(&mut rng, Some(0)); + let responder_ed25519_keypair = generate_keypair_ed25519(&mut rng, Some(1)); - let mut responder_secret = [0u8; 32]; - rng.fill_bytes(&mut responder_secret); - let responder_keypair = ed25519::KeyPair::from_secret(responder_secret, 1); + let (responder_x25519_sk, responder_x25519_pk) = generate_keypair_x25519(); // Generate responder's KEM keypair (X25519 for testing) let (_, responder_kem_pk) = generate_keypair_libcrux(&mut rng, KEM::X25519).unwrap(); @@ -213,14 +222,19 @@ mod tests { ); // Client: Create request - let (context, request_data) = - create_request(ciphersuite, initiator_keypair.private_key()).unwrap(); + let (session_secret, context, request_data) = create_request( + ciphersuite, + initiator_ed25519_keypair.private_key(), + &responder_x25519_pk, + ) + .unwrap(); // Gateway: Handle request let response_data = handle_request( &request_data, - Some(initiator_keypair.public_key()), - responder_keypair.private_key(), + Some(initiator_ed25519_keypair.public_key()), + responder_ed25519_keypair.private_key(), + &responder_x25519_sk, &responder_kem_key, ) .unwrap(); @@ -228,7 +242,8 @@ mod tests { // Client: Process response let obtained_key = process_response( context, - responder_keypair.public_key(), + &session_secret, + responder_ed25519_keypair.public_key(), &key_hash, &response_data, ) @@ -238,70 +253,71 @@ mod tests { assert_eq!(obtained_key.encode(), responder_kem_key.encode()); } - #[test] - fn test_kkt_roundtrip_anonymous() { - let mut rng = rand09::rng(); + // #[test] + // fn test_kkt_roundtrip_anonymous() { + // let mut rng = rand09::rng(); - // Only responder has keys (anonymous initiator) - let mut responder_secret = [0u8; 32]; - rng.fill_bytes(&mut responder_secret); - let responder_keypair = ed25519::KeyPair::from_secret(responder_secret, 1); + // // Only responder has keys (anonymous initiator) + // // Generate Ed25519 keypairs for both parties - let (_, responder_kem_pk) = generate_keypair_libcrux(&mut rng, KEM::X25519).unwrap(); - let responder_kem_key = EncapsulationKey::X25519(responder_kem_pk); + // let responder_ed25519_keypair = generate_keypair_ed25519(&mut rng, Some(1)); - let ciphersuite = Ciphersuite::resolve_ciphersuite( - KEM::X25519, - HashFunction::Blake3, - SignatureScheme::Ed25519, - None, - ) - .unwrap(); + // let (responder_x25519_sk, responder_x25519_pk) = generate_keypair_x25519(); - let key_hash = hash_encapsulation_key( - &ciphersuite.hash_function(), - ciphersuite.hash_len(), - &responder_kem_key.encode(), - ); + // let (_, responder_kem_pk) = generate_keypair_libcrux(&mut rng, KEM::X25519).unwrap(); + // let responder_kem_key = EncapsulationKey::X25519(responder_kem_pk); - // Anonymous initiator - use anonymous_initiator_process directly - use nym_kkt::kkt::anonymous_initiator_process; - let (mut context, request_frame) = - anonymous_initiator_process(&mut rng, ciphersuite).unwrap(); - let request_data = KKTRequestData(request_frame.to_bytes()); + // let ciphersuite = Ciphersuite::resolve_ciphersuite( + // KEM::X25519, + // HashFunction::Blake3, + // SignatureScheme::Ed25519, + // None, + // ) + // .unwrap(); - // Gateway: Handle anonymous request - let response_data = handle_request( - &request_data, - None, // Anonymous - no verification key - responder_keypair.private_key(), - &responder_kem_key, - ) - .unwrap(); + // let key_hash = hash_encapsulation_key( + // &ciphersuite.hash_function(), + // ciphersuite.hash_len(), + // &responder_kem_key.encode(), + // ); - // Initiator: Validate response - let obtained_key = validate_kem_response( - &mut context, - responder_keypair.public_key(), - &key_hash, - &response_data.0, - ) - .unwrap(); + // // Anonymous initiator - use anonymous_initiator_process directly + // use nym_kkt::kkt::anonymous_initiator_process; + // let (mut context, request_frame) = + // anonymous_initiator_process(&mut rng, ciphersuite).unwrap(); + // let request_data = KKTRequestData(request_frame.to_bytes()); - assert_eq!(obtained_key.encode(), responder_kem_key.encode()); - } + // // Gateway: Handle anonymous request + // let response_data = handle_request( + // &request_data, + // None, + // responder_ed25519_keypair.private_key(), + // &responder_x25519_sk, + // &responder_kem_key, + // ) + // .unwrap(); + + // // Initiator: Validate response + // let obtained_key = initiator_ingest_response( + // &mut context, + // responder_ed25519_keypair.public_key(), + // &key_hash, + // &response_data.0, + // ) + // .unwrap(); + + // assert_eq!(obtained_key.encode(), responder_kem_key.encode()); + // } #[test] fn test_invalid_signature_rejected() { let mut rng = rand09::rng(); - let mut initiator_secret = [0u8; 32]; - rng.fill_bytes(&mut initiator_secret); - let initiator_keypair = ed25519::KeyPair::from_secret(initiator_secret, 0); + // Generate Ed25519 keypairs for both parties + let initiator_ed25519_keypair = generate_keypair_ed25519(&mut rng, Some(0)); + let responder_ed25519_keypair = generate_keypair_ed25519(&mut rng, Some(1)); - let mut responder_secret = [0u8; 32]; - rng.fill_bytes(&mut responder_secret); - let responder_keypair = ed25519::KeyPair::from_secret(responder_secret, 1); + let (responder_x25519_sk, responder_x25519_pk) = generate_keypair_x25519(); // Different keypair for wrong signature let mut wrong_secret = [0u8; 32]; @@ -319,14 +335,19 @@ mod tests { ) .unwrap(); - let (_context, request_data) = - create_request(ciphersuite, initiator_keypair.private_key()).unwrap(); + let (_session_secret, _context, request_data) = create_request( + ciphersuite, + initiator_ed25519_keypair.private_key(), + &responder_x25519_pk, + ) + .unwrap(); // Gateway handles request but we provide WRONG verification key let result = handle_request( &request_data, Some(wrong_keypair.public_key()), // Wrong key! - responder_keypair.private_key(), + responder_ed25519_keypair.private_key(), + &responder_x25519_sk, &responder_kem_key, ); @@ -343,13 +364,11 @@ mod tests { fn test_hash_mismatch_rejected() { let mut rng = rand09::rng(); - let mut initiator_secret = [0u8; 32]; - rng.fill_bytes(&mut initiator_secret); - let initiator_keypair = ed25519::KeyPair::from_secret(initiator_secret, 0); + // Generate Ed25519 keypairs for both parties + let initiator_ed25519_keypair = generate_keypair_ed25519(&mut rng, Some(0)); + let responder_ed25519_keypair = generate_keypair_ed25519(&mut rng, Some(1)); - let mut responder_secret = [0u8; 32]; - rng.fill_bytes(&mut responder_secret); - let responder_keypair = ed25519::KeyPair::from_secret(responder_secret, 1); + let (responder_x25519_sk, responder_x25519_pk) = generate_keypair_x25519(); let (_, responder_kem_pk) = generate_keypair_libcrux(&mut rng, KEM::X25519).unwrap(); let responder_kem_key = EncapsulationKey::X25519(responder_kem_pk); @@ -365,13 +384,18 @@ mod tests { // Use WRONG hash let wrong_hash = [0u8; 32]; - let (context, request_data) = - create_request(ciphersuite, initiator_keypair.private_key()).unwrap(); + let (session_secret, context, request_data) = create_request( + ciphersuite, + initiator_ed25519_keypair.private_key(), + &responder_x25519_pk, + ) + .unwrap(); let response_data = handle_request( &request_data, - Some(initiator_keypair.public_key()), - responder_keypair.private_key(), + Some(initiator_ed25519_keypair.public_key()), + responder_ed25519_keypair.private_key(), + &responder_x25519_sk, &responder_kem_key, ) .unwrap(); @@ -379,7 +403,8 @@ mod tests { // Client validates with WRONG hash let result = process_response( context, - responder_keypair.public_key(), + &session_secret, + responder_ed25519_keypair.public_key(), &wrong_hash, // Wrong! &response_data, ); @@ -399,7 +424,9 @@ mod tests { let mut responder_secret = [0u8; 32]; rng.fill_bytes(&mut responder_secret); - let responder_keypair = ed25519::KeyPair::from_secret(responder_secret, 1); + let responder_ed25519_keypair = generate_keypair_ed25519(&mut rng, Some(1)); + + let (responder_x25519_sk, _responder_x25519_pk) = generate_keypair_x25519(); let (_, responder_kem_pk) = generate_keypair_libcrux(&mut rng, KEM::X25519).unwrap(); let responder_kem_key = EncapsulationKey::X25519(responder_kem_pk); @@ -410,7 +437,8 @@ mod tests { let result = handle_request( &malformed_request, None, - responder_keypair.private_key(), + responder_ed25519_keypair.private_key(), + &responder_x25519_sk, &responder_kem_key, ); @@ -427,13 +455,11 @@ mod tests { fn test_malformed_response_rejected() { let mut rng = rand09::rng(); - let mut initiator_secret = [0u8; 32]; - rng.fill_bytes(&mut initiator_secret); - let initiator_keypair = ed25519::KeyPair::from_secret(initiator_secret, 0); + // Generate Ed25519 keypairs for both parties + let initiator_ed25519_keypair = generate_keypair_ed25519(&mut rng, Some(0)); + let responder_ed25519_keypair = generate_keypair_ed25519(&mut rng, Some(1)); - let mut responder_secret = [0u8; 32]; - rng.fill_bytes(&mut responder_secret); - let responder_keypair = ed25519::KeyPair::from_secret(responder_secret, 1); + let (_responder_x25519_sk, responder_x25519_pk) = generate_keypair_x25519(); let ciphersuite = Ciphersuite::resolve_ciphersuite( KEM::X25519, @@ -443,8 +469,12 @@ mod tests { ) .unwrap(); - let (context, _request_data) = - create_request(ciphersuite, initiator_keypair.private_key()).unwrap(); + let (session_secret, context, _request_data) = create_request( + ciphersuite, + initiator_ed25519_keypair.private_key(), + &responder_x25519_pk, + ) + .unwrap(); // Create malformed response data let malformed_response = KKTResponseData(vec![0xFF; 100]); @@ -452,7 +482,8 @@ mod tests { let result = process_response( context, - responder_keypair.public_key(), + &session_secret, + responder_ed25519_keypair.public_key(), &key_hash, &malformed_response, ); diff --git a/common/nym-lp/src/session.rs b/common/nym-lp/src/session.rs index 9275e9be95..52e62a706a 100644 --- a/common/nym-lp/src/session.rs +++ b/common/nym-lp/src/session.rs @@ -18,6 +18,8 @@ use crate::replay::ReceivingKeyCounterValidator; use crate::{LpError, LpMessage, LpPacket}; use nym_crypto::asymmetric::ed25519; use nym_kkt::ciphersuite::{DecapsulationKey, EncapsulationKey}; +use nym_kkt::encryption::KKTSessionSecret; +use nym_kkt::kkt::decrypt_kkt_response_frame; use parking_lot::Mutex; use snow::Builder; use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; @@ -71,6 +73,7 @@ pub enum KKTState { InitiatorWaiting { /// KKT context for verifying the response context: nym_kkt::context::KKTContext, + session_secret: KKTSessionSecret, }, /// KKT exchange completed (initiator received and validated KEM key). @@ -88,7 +91,7 @@ impl std::fmt::Debug for KKTState { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::NotStarted => write!(f, "KKTState::NotStarted"), - Self::InitiatorWaiting { context } => f + Self::InitiatorWaiting { context, .. } => f .debug_struct("KKTState::InitiatorWaiting") .field("context", context) .finish(), @@ -561,13 +564,20 @@ impl LpSession { }; let mut rng = rand09::rng(); - match request_kem_key(&mut rng, ciphersuite, &self.local_ed25519_private) { - Ok((context, request_frame)) => { + match request_kem_key( + &mut rng, + ciphersuite, + &self.local_ed25519_private, + &self.remote_x25519_public, + ) { + Ok((session_secret, context, request_bytes)) => { // Store context for response validation - *kkt_state = KKTState::InitiatorWaiting { context }; + *kkt_state = KKTState::InitiatorWaiting { + context, + session_secret, + }; // Serialize KKT frame to bytes - let request_bytes = request_frame.to_bytes(); Some(Ok(LpMessage::KKTRequest(crate::message::KKTRequestData( request_bytes, )))) @@ -613,8 +623,11 @@ impl LpSession { let mut kkt_state = self.kkt_state.lock(); // Extract context from waiting state - let mut context = match &*kkt_state { - KKTState::InitiatorWaiting { context } => *context, + let (mut context, session_secret) = match &*kkt_state { + KKTState::InitiatorWaiting { + context, + session_secret, + } => (*context, *session_secret), _ => { return Err(LpError::Internal( "KKT response received in invalid state".to_string(), @@ -629,11 +642,10 @@ impl LpSession { None => { // Signature-only mode: extract key from response and compute its hash // This effectively bypasses hash validation while keeping signature validation - use nym_kkt::frame::KKTFrame; - - let (frame, _) = KKTFrame::from_bytes(response_bytes).map_err(|e| { - LpError::Internal(format!("Failed to parse KKT response: {:?}", e)) - })?; + let (frame, _) = decrypt_kkt_response_frame(&session_secret, response_bytes) + .map_err(|e| { + LpError::Internal(format!("Failed to decrypt KKT response: {:?}", e)) + })?; hash_for_validation = hash_encapsulation_key( &context.ciphersuite().hash_function(), @@ -647,6 +659,7 @@ impl LpSession { // Validate response and extract KEM key let kem_pk = validate_kem_response( &mut context, + &session_secret, &self.remote_ed25519_public, hash_ref, response_bytes, @@ -680,20 +693,19 @@ impl LpSession { request_bytes: &[u8], responder_kem_pk: &EncapsulationKey, ) -> Result { - use nym_kkt::{frame::KKTFrame, kkt::handle_kem_request}; + use nym_kkt::kkt::handle_kem_request; + + let mut rng = rand09::rng(); let mut kkt_state = self.kkt_state.lock(); - // Deserialize request frame - let (request_frame, _) = KKTFrame::from_bytes(request_bytes).map_err(|e| { - LpError::Internal(format!("KKT request deserialization failed: {:?}", e)) - })?; - // Handle request and create signed response - let response_frame = handle_kem_request( - &request_frame, + let response_bytes = handle_kem_request( + &mut rng, + request_bytes, Some(&self.remote_ed25519_public), // Verify initiator signature &self.local_ed25519_private, // Sign response + &self.local_x25519_private, responder_kem_pk, ) .map_err(|e| LpError::Internal(format!("KKT request handling failed: {:?}", e)))?; @@ -702,9 +714,6 @@ impl LpSession { // Responder doesn't store the kem_pk since they already have their own KEM keypair *kkt_state = KKTState::ResponderProcessed; - // Serialize response frame - let response_bytes = response_frame.to_bytes(); - Ok(LpMessage::KKTResponse(crate::message::KKTResponseData( response_bytes, ))) diff --git a/common/nym-lp/src/state_machine.rs b/common/nym-lp/src/state_machine.rs index ddd63e8d13..37a4ee72f8 100644 --- a/common/nym-lp/src/state_machine.rs +++ b/common/nym-lp/src/state_machine.rs @@ -439,75 +439,75 @@ impl LpStateMachine { // --- Inline handle_handshake_packet logic --- // 1. Check replay protection *before* processing if let Err(e) = session.receiving_counter_quick_check(packet.header.counter) { - let _reason = e.to_string(); - result_action = Some(Err(e)); - LpState::Handshaking { session } + let _reason = e.to_string(); + result_action = Some(Err(e)); + LpState::Handshaking { session } // LpState::Closed { reason } } else { - // 2. Process the handshake message - match session.process_handshake_message(&packet.message) { - Ok(_) => { - // 3. Mark counter as received *after* successful processing - if let Err(e) = session.receiving_counter_mark(packet.header.counter) { - let _reason = e.to_string(); - result_action = Some(Err(e)); + // 2. Process the handshake message + match session.process_handshake_message(&packet.message) { + Ok(_) => { + // 3. Mark counter as received *after* successful processing + if let Err(e) = session.receiving_counter_mark(packet.header.counter) { + let _reason = e.to_string(); + result_action = Some(Err(e)); // LpState::Closed { reason } LpState::Handshaking { session } - } else { - // 4. First check if we need to send a handshake message (before checking completion) - match session.prepare_handshake_message() { - Some(Ok(message)) => { - match session.next_packet(message) { - Ok(response_packet) => { - result_action = Some(Ok(LpAction::SendPacket(response_packet))); - // Check if handshake became complete after preparing message - if session.is_handshake_complete() { - LpState::Transport { session } // Transition to Transport - } else { - LpState::Handshaking { session } // Remain Handshaking - } - } - Err(e) => { - let reason = e.to_string(); - result_action = Some(Err(e)); - LpState::Closed { reason } - } - } - } - Some(Err(e)) => { - let reason = e.to_string(); - result_action = Some(Err(e)); - LpState::Closed { reason } - } - None => { - // 5. No message to send - check if handshake is complete - if session.is_handshake_complete() { - result_action = Some(Ok(LpAction::HandshakeComplete)); - LpState::Transport { session } // Transition to Transport - } else { - // Handshake stalled unexpectedly - let err = LpError::NoiseError(NoiseError::Other( - "Handshake stalled unexpectedly".to_string(), - )); - let reason = err.to_string(); - result_action = Some(Err(err)); - LpState::Closed { reason } - } - } - } - } - } - Err(e) => { // Error from process_handshake_message - let reason = e.to_string(); - result_action = Some(Err(e)); - LpState::Closed { reason } - } - } + } else { + // 4. First check if we need to send a handshake message (before checking completion) + match session.prepare_handshake_message() { + Some(Ok(message)) => { + match session.next_packet(message) { + Ok(response_packet) => { + result_action = Some(Ok(LpAction::SendPacket(response_packet))); + // Check if handshake became complete after preparing message + if session.is_handshake_complete() { + LpState::Transport { session } // Transition to Transport + } else { + LpState::Handshaking { session } // Remain Handshaking + } + } + Err(e) => { + let reason = e.to_string(); + result_action = Some(Err(e)); + LpState::Closed { reason } + } + } + } + Some(Err(e)) => { + let reason = e.to_string(); + result_action = Some(Err(e)); + LpState::Closed { reason } + } + None => { + // 5. No message to send - check if handshake is complete + if session.is_handshake_complete() { + result_action = Some(Ok(LpAction::HandshakeComplete)); + LpState::Transport { session } // Transition to Transport + } else { + // Handshake stalled unexpectedly + let err = LpError::NoiseError(NoiseError::Other( + "Handshake stalled unexpectedly".to_string(), + )); + let reason = err.to_string(); + result_action = Some(Err(err)); + LpState::Closed { reason } + } + } + } + } + } + Err(e) => { // Error from process_handshake_message + let reason = e.to_string(); + result_action = Some(Err(e)); + LpState::Closed { reason } + } + } } // --- End inline handle_handshake_packet logic --- } } - // Reject SendData during handshake + // Reject SendData during handshake (LpState::Handshaking { session }, LpInput::SendData(_)) => { // Keep session if returning to this state result_action = Some(Err(LpError::InvalidStateTransition { state: "Handshaking".to_string(), @@ -522,114 +522,114 @@ impl LpStateMachine { state: "Handshaking".to_string(), input: "StartHandshake".to_string(), })); - // Invalid input, remain in Handshaking state - LpState::Handshaking { session } + // Invalid input, remain in Handshaking state + LpState::Handshaking { session } } // --- Transport State --- (LpState::Transport { session }, LpInput::ReceivePacket(packet)) => { - // Check if packet lp_id matches our session - if packet.header.receiver_idx() != session.id() { + // Check if packet lp_id matches our session + if packet.header.receiver_idx() != session.id() { result_action = Some(Err(LpError::UnknownSessionId(packet.header.receiver_idx()))); LpState::Transport { session } - } else { - // Check message type - handle subsession initiation from peer - match &packet.message { - // Peer initiated subsession - we become responder - LpMessage::SubsessionKK1(kk1_data) => { - // Create subsession as responder - let subsession_index = session.next_subsession_index(); - match session.create_subsession(subsession_index, false) { - Ok(subsession) => { - // Process KK1 - match subsession.process_message(&kk1_data.payload) { - Ok(_) => { - // Prepare KK2 response - match subsession.prepare_message() { - Ok(kk2_payload) => { - let kk2_msg = LpMessage::SubsessionKK2(SubsessionKK2Data { payload: kk2_payload }); - match session.next_packet(kk2_msg) { - Ok(response_packet) => { - result_action = Some(Ok(LpAction::SendPacket(response_packet))); - // Stay in SubsessionHandshaking, wait for SubsessionReady - LpState::SubsessionHandshaking { session, subsession: Box::new(subsession) } - } - Err(e) => { - let reason = e.to_string(); - result_action = Some(Err(e)); - LpState::Closed { reason } - } - } - } - Err(e) => { - let reason = e.to_string(); - result_action = Some(Err(e)); - LpState::Closed { reason } - } - } - } - Err(e) => { - let reason = e.to_string(); - result_action = Some(Err(e)); - LpState::Closed { reason } - } - } - } - Err(e) => { - let reason = e.to_string(); - result_action = Some(Err(e)); - LpState::Closed { reason } - } - } - } - // Normal encrypted data - LpMessage::EncryptedData(_) => { - // 1. Check replay protection - if let Err(e) = session.receiving_counter_quick_check(packet.header.counter) { - result_action = Some(Err(e)); - LpState::Transport { session } - } else { - // 2. Decrypt data - match session.decrypt_data(&packet.message) { - Ok(plaintext) => { - // 3. Mark counter as received - if let Err(e) = session.receiving_counter_mark(packet.header.counter) { - result_action = Some(Err(e)); - LpState::Transport { session } - } else { - // 4. Deliver data - result_action = Some(Ok(LpAction::DeliverData(BytesMut::from(plaintext.as_slice())))); - LpState::Transport { session } - } - } - Err(e) => { - let reason = e.to_string(); - result_action = Some(Err(e.into())); - LpState::Closed { reason } - } - } - } - } - // Stale abort in Transport state - race already resolved. - // This can happen if abort arrives after loser already returned to Transport - // via KK1 processing (loser detected local < remote and became responder). - // The winner's abort message arrived late. Silently ignore. - LpMessage::SubsessionAbort => { - debug!("Ignoring stale SubsessionAbort in Transport state"); - result_action = None; - LpState::Transport { session } - } - _ => { - // Unexpected message type in Transport state - let err = LpError::InvalidStateTransition { - state: "Transport".to_string(), - input: format!("Unexpected message type: {}", packet.message), - }; - result_action = Some(Err(err)); - LpState::Transport { session } - } - } - } + } else { + // Check message type - handle subsession initiation from peer + match &packet.message { + // Peer initiated subsession - we become responder + LpMessage::SubsessionKK1(kk1_data) => { + // Create subsession as responder + let subsession_index = session.next_subsession_index(); + match session.create_subsession(subsession_index, false) { + Ok(subsession) => { + // Process KK1 + match subsession.process_message(&kk1_data.payload) { + Ok(_) => { + // Prepare KK2 response + match subsession.prepare_message() { + Ok(kk2_payload) => { + let kk2_msg = LpMessage::SubsessionKK2(SubsessionKK2Data { payload: kk2_payload }); + match session.next_packet(kk2_msg) { + Ok(response_packet) => { + result_action = Some(Ok(LpAction::SendPacket(response_packet))); + // Stay in SubsessionHandshaking, wait for SubsessionReady + LpState::SubsessionHandshaking { session, subsession: Box::new(subsession) } + } + Err(e) => { + let reason = e.to_string(); + result_action = Some(Err(e)); + LpState::Closed { reason } + } + } + } + Err(e) => { + let reason = e.to_string(); + result_action = Some(Err(e)); + LpState::Closed { reason } + } + } + } + Err(e) => { + let reason = e.to_string(); + result_action = Some(Err(e)); + LpState::Closed { reason } + } + } + } + Err(e) => { + let reason = e.to_string(); + result_action = Some(Err(e)); + LpState::Closed { reason } + } + } + } + // Normal encrypted data + LpMessage::EncryptedData(_) => { + // 1. Check replay protection + if let Err(e) = session.receiving_counter_quick_check(packet.header.counter) { + result_action = Some(Err(e)); + LpState::Transport { session } + } else { + // 2. Decrypt data + match session.decrypt_data(&packet.message) { + Ok(plaintext) => { + // 3. Mark counter as received + if let Err(e) = session.receiving_counter_mark(packet.header.counter) { + result_action = Some(Err(e)); + LpState::Transport { session } + } else { + // 4. Deliver data + result_action = Some(Ok(LpAction::DeliverData(BytesMut::from(plaintext.as_slice())))); + LpState::Transport { session } + } + } + Err(e) => { + let reason = e.to_string(); + result_action = Some(Err(e.into())); + LpState::Closed { reason } + } + } + } + } + // Stale abort in Transport state - race already resolved. + // This can happen if abort arrives after loser already returned to Transport + // via KK1 processing (loser detected local < remote and became responder). + // The winner's abort message arrived late. Silently ignore. + LpMessage::SubsessionAbort => { + debug!("Ignoring stale SubsessionAbort in Transport state"); + result_action = None; + LpState::Transport { session } + } + _ => { + // Unexpected message type in Transport state + let err = LpError::InvalidStateTransition { + state: "Transport".to_string(), + input: format!("Unexpected message type: {}", packet.message), + }; + result_action = Some(Err(err)); + LpState::Transport { session } + } + } + } } (LpState::Transport { session }, LpInput::SendData(data)) => { // Encrypt and send application data @@ -641,17 +641,17 @@ impl LpStateMachine { result_action = Some(Err(e.into())); } } - // Remain in transport state - LpState::Transport { session } + // Remain in transport state + LpState::Transport { session } } - // Reject StartHandshake if already in transport + // Reject StartHandshake if already in transport (LpState::Transport { session }, LpInput::StartHandshake) => { // Keep session result_action = Some(Err(LpError::InvalidStateTransition { state: "Transport".to_string(), input: "StartHandshake".to_string(), })); - // Invalid input, remain in Transport state - LpState::Transport { session } + // Invalid input, remain in Transport state + LpState::Transport { session } } // --- Transport + InitiateSubsession → SubsessionHandshaking --- @@ -970,25 +970,25 @@ impl LpStateMachine { result_action = Some(Err(LpError::UnknownSessionId(packet.header.receiver_idx()))); LpState::ReadOnlyTransport { session } } else if let Err(e) = session.receiving_counter_quick_check(packet.header.counter) { - result_action = Some(Err(e)); - LpState::ReadOnlyTransport { session } - } else { - match session.decrypt_data(&packet.message) { - Ok(plaintext) => { - if let Err(e) = session.receiving_counter_mark(packet.header.counter) { - result_action = Some(Err(e)); - LpState::ReadOnlyTransport { session } - } else { - result_action = Some(Ok(LpAction::DeliverData(BytesMut::from(plaintext.as_slice())))); - LpState::ReadOnlyTransport { session } - } - } - Err(e) => { - let reason = e.to_string(); - result_action = Some(Err(e.into())); - LpState::Closed { reason } + result_action = Some(Err(e)); + LpState::ReadOnlyTransport { session } + } else { + match session.decrypt_data(&packet.message) { + Ok(plaintext) => { + if let Err(e) = session.receiving_counter_mark(packet.header.counter) { + result_action = Some(Err(e)); + LpState::ReadOnlyTransport { session } + } else { + result_action = Some(Ok(LpAction::DeliverData(BytesMut::from(plaintext.as_slice())))); + LpState::ReadOnlyTransport { session } } } + Err(e) => { + let reason = e.to_string(); + result_action = Some(Err(e.into())); + LpState::Closed { reason } + } + } } } @@ -1026,8 +1026,8 @@ impl LpStateMachine { LpInput::Close, ) => { result_action = Some(Ok(LpAction::ConnectionClosed)); - // Transition to Closed state - LpState::Closed { reason: "Closed by user".to_string() } + // Transition to Closed state + LpState::Closed { reason: "Closed by user".to_string() } } // Ignore Close if already Closed (closed_state @ LpState::Closed { .. }, LpInput::Close) => { @@ -1040,36 +1040,36 @@ impl LpStateMachine { // result_action = Some(Err(LpError::LpSessionClosed)); // closed_state // } - // Ignore ReceivePacket if Closed + // Ignore ReceivePacket if Closed (closed_state @ LpState::Closed { .. }, LpInput::ReceivePacket(_)) => { - result_action = Some(Err(LpError::LpSessionClosed)); - closed_state + result_action = Some(Err(LpError::LpSessionClosed)); + closed_state } - // Ignore SendData if Closed + // Ignore SendData if Closed (closed_state @ LpState::Closed { .. }, LpInput::SendData(_)) => { - result_action = Some(Err(LpError::LpSessionClosed)); - closed_state + result_action = Some(Err(LpError::LpSessionClosed)); + closed_state } // Processing state should not be matched directly if using replace (LpState::Processing, _) => { - // This case should ideally be unreachable if placeholder logic is correct - let err = LpError::Internal("Reached Processing state unexpectedly".to_string()); - let reason = err.to_string(); - result_action = Some(Err(err)); - LpState::Closed { reason } + // This case should ideally be unreachable if placeholder logic is correct + let err = LpError::Internal("Reached Processing state unexpectedly".to_string()); + let reason = err.to_string(); + result_action = Some(Err(err)); + LpState::Closed { reason } } // --- Default: Invalid input for current state (if any combinations missed) --- // Consider if this should transition to Closed state. For now, just report error // and transition to Closed as a safety measure. (invalid_state, input) => { - let err = LpError::InvalidStateTransition { - state: format!("{:?}", invalid_state), // Use owned state for debug info - input: format!("{:?}", input), - }; - let reason = err.to_string(); - result_action = Some(Err(err)); - LpState::Closed { reason } + let err = LpError::InvalidStateTransition { + state: format!("{:?}", invalid_state), // Use owned state for debug info + input: format!("{:?}", input), + }; + let reason = err.to_string(); + result_action = Some(Err(err)); + LpState::Closed { reason } } }; From b6f234259cd33cfef82c63f966f4f6cbe2933ec9 Mon Sep 17 00:00:00 2001 From: Andrej Mihajlov Date: Fri, 16 Jan 2026 12:29:21 +0100 Subject: [PATCH 04/51] Upgrade to def_guard_wireguard v0.8.0 (#6315) * Upgrade to def_guard_wireguard v0.8.0 * Update nix, netlink-packet-wireguard * Adapt linux code for defguard_wireguard * rustfmt * Revert nix to 0.27.1 * clippy: fix * fix from rebase * Restore userspace imp on condition * Add send+sync on boxed wgapi * Use error to indicate when userspace/kernel imps are unavailable; userspace is not available on all platforms * Remove duplicate import --------- Co-authored-by: mfahampshire --- Cargo.lock | 1175 +++++++++++------ Cargo.toml | 3 +- common/wireguard/src/lib.rs | 169 ++- common/wireguard/src/peer_controller/mod.rs | 23 +- .../authenticator/mixnet_listener.rs | 4 +- gateway/src/node/lp_listener/registration.rs | 2 +- gateway/src/node/mod.rs | 2 +- 7 files changed, 888 insertions(+), 490 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9c9710eca4..c76ac98c4e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,7 +11,7 @@ dependencies = [ "macroific", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.104", ] [[package]] @@ -24,15 +24,6 @@ dependencies = [ "psl-types", ] -[[package]] -name = "addr2line" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" -dependencies = [ - "gimli", -] - [[package]] name = "adler2" version = "2.0.1" @@ -133,9 +124,9 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "ammonia" -version = "4.1.2" +version = "4.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17e913097e1a2124b46746c980134e8c954bc17a6a59bb3fde96f088d126dde6" +checksum = "d6b346764dd0814805de8abf899fe03065bcee69bb1a4771c785817e39f3978f" dependencies = [ "cssparser", "html5ever", @@ -226,9 +217,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.98" +version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" [[package]] name = "arbitrary" @@ -417,7 +408,7 @@ dependencies = [ "rustc-hash", "serde", "serde_derive", - "syn 2.0.106", + "syn 2.0.104", ] [[package]] @@ -499,7 +490,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.104", ] [[package]] @@ -510,7 +501,7 @@ checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.104", ] [[package]] @@ -664,7 +655,7 @@ checksum = "57d123550fa8d071b7255cb0cc04dc302baa6c8c4a79f55701552684d8399bce" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.104", ] [[package]] @@ -697,21 +688,6 @@ dependencies = [ "url", ] -[[package]] -name = "backtrace" -version = "0.3.75" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" -dependencies = [ - "addr2line", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", - "windows-targets 0.52.6", -] - [[package]] name = "base16ct" version = "0.2.0" @@ -1074,7 +1050,7 @@ dependencies = [ "semver 1.0.26", "serde", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.17", ] [[package]] @@ -1168,7 +1144,7 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-link", + "windows-link 0.1.3", ] [[package]] @@ -1259,7 +1235,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.104", ] [[package]] @@ -1271,7 +1247,7 @@ checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" [[package]] name = "classic-mceliece-rust" version = "3.2.0" -source = "git+https://github.com/georgio/classic-mceliece-rust#f2f27048b621df103bbe64369a18174ffec04ae1" +source = "git+https://github.com/georgio/classic-mceliece-rust#7bdf0c3c8c727cbc4784e05b0972641ad0791ab9" dependencies = [ "rand 0.9.2", "sha3", @@ -1454,10 +1430,10 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "core-models" version = "0.0.4" -source = "git+https://github.com/cryspen/libcrux#f63bb67ead59297560edf523a3b29b21489c17ea" +source = "git+https://github.com/cryspen/libcrux#21cf9cae922f55ca2394641d5ee7bbabfd36ace0" dependencies = [ "hax-lib", - "pastey", + "pastey 0.2.1", "rand 0.9.2", ] @@ -1533,7 +1509,7 @@ checksum = "a782b93fae93e57ca8ad3e9e994e784583f5933aeaaa5c80a545c4b437be2047" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.104", ] [[package]] @@ -1557,7 +1533,7 @@ checksum = "e01c9214319017f6ebd8e299036e1f717fa9bb6724e758f7d6fb2477599d1a29" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.104", ] [[package]] @@ -1801,7 +1777,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" dependencies = [ "quote", - "syn 2.0.106", + "syn 2.0.104", ] [[package]] @@ -1905,7 +1881,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.104", ] [[package]] @@ -1953,7 +1929,7 @@ dependencies = [ "schemars 0.8.22", "serde", "sha2 0.10.9", - "thiserror 2.0.12", + "thiserror 2.0.17", ] [[package]] @@ -2057,7 +2033,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.106", + "syn 2.0.104", ] [[package]] @@ -2068,7 +2044,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote", - "syn 2.0.106", + "syn 2.0.104", ] [[package]] @@ -2091,12 +2067,41 @@ version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" +[[package]] +name = "defguard_boringtun" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81823fe5700844a2b68c3344f5c414dd1316a69590aea0b3c71b32cbb0e27d45" +dependencies = [ + "aead", + "base64 0.22.1", + "blake2 0.10.6", + "chacha20poly1305", + "hex", + "hmac", + "ip_network", + "ip_network_table", + "libc", + "nix 0.30.1", + "parking_lot", + "ring", + "socket2 0.6.0", + "thiserror 2.0.17", + "tracing", + "uniffi 0.30.0", + "untrusted", + "x25519-dalek", +] + [[package]] name = "defguard_wireguard_rs" -version = "0.4.7" -source = "git+https://github.com/DefGuard/wireguard-rs.git?rev=v0.4.7#ef1cf3714629bf5016fb38cbb7320451dc69fb09" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf853e6ffe9d6ab91aa1f706cbbf84a446c002cdf469ae0eb085fbcca07648e4" dependencies = [ "base64 0.22.1", + "defguard_boringtun", + "ipnet", "libc", "log", "netlink-packet-core", @@ -2105,9 +2110,13 @@ dependencies = [ "netlink-packet-utils", "netlink-packet-wireguard", "netlink-sys", - "nix 0.29.0", + "nix 0.30.1", + "regex", "serde", - "thiserror 1.0.69", + "thiserror 2.0.17", + "windows 0.62.2", + "wireguard-nt", + "x25519-dalek", ] [[package]] @@ -2121,7 +2130,7 @@ dependencies = [ "macroific", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.104", ] [[package]] @@ -2164,7 +2173,7 @@ checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.104", ] [[package]] @@ -2185,7 +2194,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.104", ] [[package]] @@ -2195,7 +2204,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" dependencies = [ "derive_builder_core", - "syn 2.0.106", + "syn 2.0.104", ] [[package]] @@ -2224,7 +2233,7 @@ checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.104", "unicode-xid", ] @@ -2236,7 +2245,7 @@ checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.104", "unicode-xid", ] @@ -2294,7 +2303,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -2305,7 +2314,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.104", ] [[package]] @@ -2358,7 +2367,7 @@ version = "0.1.0" dependencies = [ "cosmwasm-std", "quote", - "syn 2.0.106", + "syn 2.0.104", ] [[package]] @@ -2520,7 +2529,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.104", ] [[package]] @@ -2617,7 +2626,7 @@ dependencies = [ "console_error_panic_hook", "js-sys", "serde-wasm-bindgen 0.6.5", - "thiserror 2.0.12", + "thiserror 2.0.17", "wasm-bindgen", "wasm-bindgen-futures", "wasm-storage", @@ -2650,7 +2659,7 @@ dependencies = [ "macroific", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.104", ] [[package]] @@ -2880,7 +2889,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.104", ] [[package]] @@ -2930,7 +2939,7 @@ dependencies = [ "libc", "log", "rustversion", - "windows", + "windows 0.61.3", ] [[package]] @@ -2991,17 +3000,11 @@ dependencies = [ "polyval", ] -[[package]] -name = "gimli" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" - [[package]] name = "glob" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" [[package]] name = "gloo-net" @@ -3212,7 +3215,7 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.104", ] [[package]] @@ -3323,7 +3326,7 @@ dependencies = [ "rand 0.9.2", "ring", "rustls 0.23.29", - "thiserror 2.0.12", + "thiserror 2.0.17", "tinyvec", "tokio", "tokio-rustls 0.26.2", @@ -3349,7 +3352,7 @@ dependencies = [ "resolv-conf", "rustls 0.23.29", "smallvec", - "thiserror 2.0.12", + "thiserror 2.0.17", "tokio", "tokio-rustls 0.26.2", "tracing", @@ -3627,9 +3630,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.15" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f66d5bd4c6f02bf0542fad85d626775bab9258cf795a4256dcaf3161114d1df" +checksum = "8d9b05277c7e8da2c93a568989bb6207bef0112e8d17df7a6eda4a3cf143bc5e" dependencies = [ "base64 0.22.1", "bytes", @@ -3643,7 +3646,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2 0.5.10", + "socket2 0.6.0", "tokio", "tower-service", "tracing", @@ -3661,7 +3664,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core", + "windows-core 0.61.2", ] [[package]] @@ -3827,7 +3830,7 @@ checksum = "0ab604ee7085efba6efc65e4ebca0e9533e3aff6cb501d7d77b211e3a781c6d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.104", ] [[package]] @@ -3880,7 +3883,7 @@ dependencies = [ "js-sys", "sealed", "smallvec", - "thiserror 2.0.12", + "thiserror 2.0.17", "tokio", "wasm-bindgen", "wasm-bindgen-futures", @@ -3896,7 +3899,7 @@ dependencies = [ "macroific", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.104", ] [[package]] @@ -4035,23 +4038,28 @@ dependencies = [ "rustversion", ] -[[package]] -name = "io-uring" -version = "0.7.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b86e202f00093dcba4275d4636b93ef9dd75d025ae560d2521b45ea28ab49013" -dependencies = [ - "bitflags 2.9.1", - "cfg-if", - "libc", -] - [[package]] name = "ip_network" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa2f047c0a98b2f299aa5d6d7088443570faae494e9ae1305e48be000c9e0eb1" +[[package]] +name = "ip_network_table" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4099b7cfc5c5e2fe8c5edf3f6f7adf7a714c9cc697534f63a5a5da30397cb2c0" +dependencies = [ + "ip_network", + "ip_network_table-deps-treebitmap", +] + +[[package]] +name = "ip_network_table-deps-treebitmap" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e537132deb99c0eb4b752f0346b6a836200eaaa3516dd7e5514b63930a09e5d" + [[package]] name = "ipconfig" version = "0.3.2" @@ -4059,7 +4067,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" dependencies = [ "socket2 0.5.10", - "widestring", + "widestring 1.2.0", "windows-sys 0.48.0", "winreg", ] @@ -4176,7 +4184,7 @@ checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.104", ] [[package]] @@ -4201,9 +4209,9 @@ dependencies = [ [[package]] name = "jwt-simple" -version = "0.12.12" +version = "0.12.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "731011e9647a71ff4f8474176ff6ce6e0d2de87a0173f15613af3a84c3e3401a" +checksum = "6ad8761f175784dfbb83709f322fc4daf6b27afd5bf375492f2876f9e925ef5a" dependencies = [ "anyhow", "binstring", @@ -4221,7 +4229,7 @@ dependencies = [ "serde", "serde_json", "superboring", - "thiserror 2.0.12", + "thiserror 2.0.17", "zeroize", ] @@ -4326,10 +4334,21 @@ version = "0.2.174" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" +[[package]] +name = "libcrux-aesgcm" +version = "0.0.5" +source = "git+https://github.com/cryspen/libcrux#21cf9cae922f55ca2394641d5ee7bbabfd36ace0" +dependencies = [ + "libcrux-intrinsics", + "libcrux-platform", + "libcrux-secrets", + "libcrux-traits", +] + [[package]] name = "libcrux-chacha20poly1305" version = "0.0.4" -source = "git+https://github.com/cryspen/libcrux#f63bb67ead59297560edf523a3b29b21489c17ea" +source = "git+https://github.com/cryspen/libcrux#21cf9cae922f55ca2394641d5ee7bbabfd36ace0" dependencies = [ "libcrux-hacl-rs", "libcrux-macros", @@ -4341,7 +4360,7 @@ dependencies = [ [[package]] name = "libcrux-curve25519" version = "0.0.4" -source = "git+https://github.com/cryspen/libcrux#f63bb67ead59297560edf523a3b29b21489c17ea" +source = "git+https://github.com/cryspen/libcrux#21cf9cae922f55ca2394641d5ee7bbabfd36ace0" dependencies = [ "libcrux-hacl-rs", "libcrux-macros", @@ -4352,7 +4371,7 @@ dependencies = [ [[package]] name = "libcrux-ecdh" version = "0.0.4" -source = "git+https://github.com/cryspen/libcrux#f63bb67ead59297560edf523a3b29b21489c17ea" +source = "git+https://github.com/cryspen/libcrux#21cf9cae922f55ca2394641d5ee7bbabfd36ace0" dependencies = [ "libcrux-curve25519", "libcrux-p256", @@ -4363,7 +4382,7 @@ dependencies = [ [[package]] name = "libcrux-ed25519" version = "0.0.4" -source = "git+https://github.com/cryspen/libcrux#f63bb67ead59297560edf523a3b29b21489c17ea" +source = "git+https://github.com/cryspen/libcrux#21cf9cae922f55ca2394641d5ee7bbabfd36ace0" dependencies = [ "libcrux-hacl-rs", "libcrux-macros", @@ -4375,7 +4394,7 @@ dependencies = [ [[package]] name = "libcrux-hacl-rs" version = "0.0.4" -source = "git+https://github.com/cryspen/libcrux#f63bb67ead59297560edf523a3b29b21489c17ea" +source = "git+https://github.com/cryspen/libcrux#21cf9cae922f55ca2394641d5ee7bbabfd36ace0" dependencies = [ "libcrux-macros", ] @@ -4383,7 +4402,7 @@ dependencies = [ [[package]] name = "libcrux-hkdf" version = "0.0.4" -source = "git+https://github.com/cryspen/libcrux#f63bb67ead59297560edf523a3b29b21489c17ea" +source = "git+https://github.com/cryspen/libcrux#21cf9cae922f55ca2394641d5ee7bbabfd36ace0" dependencies = [ "libcrux-hacl-rs", "libcrux-hmac", @@ -4393,7 +4412,7 @@ dependencies = [ [[package]] name = "libcrux-hmac" version = "0.0.4" -source = "git+https://github.com/cryspen/libcrux#f63bb67ead59297560edf523a3b29b21489c17ea" +source = "git+https://github.com/cryspen/libcrux#21cf9cae922f55ca2394641d5ee7bbabfd36ace0" dependencies = [ "libcrux-hacl-rs", "libcrux-macros", @@ -4403,7 +4422,7 @@ dependencies = [ [[package]] name = "libcrux-intrinsics" version = "0.0.4" -source = "git+https://github.com/cryspen/libcrux#f63bb67ead59297560edf523a3b29b21489c17ea" +source = "git+https://github.com/cryspen/libcrux#21cf9cae922f55ca2394641d5ee7bbabfd36ace0" dependencies = [ "core-models", "hax-lib", @@ -4412,7 +4431,7 @@ dependencies = [ [[package]] name = "libcrux-kem" version = "0.0.4" -source = "git+https://github.com/cryspen/libcrux#f63bb67ead59297560edf523a3b29b21489c17ea" +source = "git+https://github.com/cryspen/libcrux#21cf9cae922f55ca2394641d5ee7bbabfd36ace0" dependencies = [ "libcrux-curve25519", "libcrux-ecdh", @@ -4427,16 +4446,30 @@ dependencies = [ [[package]] name = "libcrux-macros" version = "0.0.3" -source = "git+https://github.com/cryspen/libcrux#f63bb67ead59297560edf523a3b29b21489c17ea" +source = "git+https://github.com/cryspen/libcrux#21cf9cae922f55ca2394641d5ee7bbabfd36ace0" dependencies = [ "quote", - "syn 2.0.106", + "syn 2.0.104", +] + +[[package]] +name = "libcrux-ml-dsa" +version = "0.0.5" +source = "git+https://github.com/cryspen/libcrux#21cf9cae922f55ca2394641d5ee7bbabfd36ace0" +dependencies = [ + "core-models", + "hax-lib", + "libcrux-intrinsics", + "libcrux-macros", + "libcrux-platform", + "libcrux-sha3", + "tls_codec", ] [[package]] name = "libcrux-ml-kem" -version = "0.0.4" -source = "git+https://github.com/cryspen/libcrux#f63bb67ead59297560edf523a3b29b21489c17ea" +version = "0.0.5" +source = "git+https://github.com/cryspen/libcrux#21cf9cae922f55ca2394641d5ee7bbabfd36ace0" dependencies = [ "hax-lib", "libcrux-intrinsics", @@ -4451,7 +4484,7 @@ dependencies = [ [[package]] name = "libcrux-p256" version = "0.0.4" -source = "git+https://github.com/cryspen/libcrux#f63bb67ead59297560edf523a3b29b21489c17ea" +source = "git+https://github.com/cryspen/libcrux#21cf9cae922f55ca2394641d5ee7bbabfd36ace0" dependencies = [ "libcrux-hacl-rs", "libcrux-macros", @@ -4462,8 +4495,8 @@ dependencies = [ [[package]] name = "libcrux-platform" -version = "0.0.2" -source = "git+https://github.com/cryspen/libcrux#f63bb67ead59297560edf523a3b29b21489c17ea" +version = "0.0.3" +source = "git+https://github.com/cryspen/libcrux#21cf9cae922f55ca2394641d5ee7bbabfd36ace0" dependencies = [ "libc", ] @@ -4471,7 +4504,7 @@ dependencies = [ [[package]] name = "libcrux-poly1305" version = "0.0.4" -source = "git+https://github.com/cryspen/libcrux#f63bb67ead59297560edf523a3b29b21489c17ea" +source = "git+https://github.com/cryspen/libcrux#21cf9cae922f55ca2394641d5ee7bbabfd36ace0" dependencies = [ "libcrux-hacl-rs", "libcrux-macros", @@ -4480,14 +4513,16 @@ dependencies = [ [[package]] name = "libcrux-psq" version = "0.0.5" -source = "git+https://github.com/cryspen/libcrux#f63bb67ead59297560edf523a3b29b21489c17ea" +source = "git+https://github.com/cryspen/libcrux#21cf9cae922f55ca2394641d5ee7bbabfd36ace0" dependencies = [ + "libcrux-aesgcm", "libcrux-chacha20poly1305", "libcrux-ecdh", "libcrux-ed25519", "libcrux-hkdf", "libcrux-hmac", "libcrux-kem", + "libcrux-ml-dsa", "libcrux-ml-kem", "libcrux-sha2", "libcrux-traits", @@ -4498,7 +4533,7 @@ dependencies = [ [[package]] name = "libcrux-secrets" version = "0.0.4" -source = "git+https://github.com/cryspen/libcrux#f63bb67ead59297560edf523a3b29b21489c17ea" +source = "git+https://github.com/cryspen/libcrux#21cf9cae922f55ca2394641d5ee7bbabfd36ace0" dependencies = [ "hax-lib", ] @@ -4506,7 +4541,7 @@ dependencies = [ [[package]] name = "libcrux-sha2" version = "0.0.4" -source = "git+https://github.com/cryspen/libcrux#f63bb67ead59297560edf523a3b29b21489c17ea" +source = "git+https://github.com/cryspen/libcrux#21cf9cae922f55ca2394641d5ee7bbabfd36ace0" dependencies = [ "libcrux-hacl-rs", "libcrux-macros", @@ -4515,8 +4550,8 @@ dependencies = [ [[package]] name = "libcrux-sha3" -version = "0.0.4" -source = "git+https://github.com/cryspen/libcrux#f63bb67ead59297560edf523a3b29b21489c17ea" +version = "0.0.5" +source = "git+https://github.com/cryspen/libcrux#21cf9cae922f55ca2394641d5ee7bbabfd36ace0" dependencies = [ "hax-lib", "libcrux-intrinsics", @@ -4527,12 +4562,22 @@ dependencies = [ [[package]] name = "libcrux-traits" version = "0.0.4" -source = "git+https://github.com/cryspen/libcrux#f63bb67ead59297560edf523a3b29b21489c17ea" +source = "git+https://github.com/cryspen/libcrux#21cf9cae922f55ca2394641d5ee7bbabfd36ace0" dependencies = [ "libcrux-secrets", "rand 0.9.2", ] +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link 0.2.1", +] + [[package]] name = "libm" version = "0.2.15" @@ -4664,7 +4709,7 @@ dependencies = [ "proc-macro2", "quote", "sealed", - "syn 2.0.106", + "syn 2.0.104", ] [[package]] @@ -4676,7 +4721,7 @@ dependencies = [ "proc-macro2", "quote", "sealed", - "syn 2.0.106", + "syn 2.0.104", ] [[package]] @@ -4689,7 +4734,7 @@ dependencies = [ "macroific_core", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.104", ] [[package]] @@ -4717,7 +4762,7 @@ checksum = "ac84fd3f360fcc43dc5f5d186f02a94192761a080e8bc58621ad4d12296a58cf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.104", ] [[package]] @@ -4840,7 +4885,7 @@ dependencies = [ "rand 0.8.5", "serde", "serde-wasm-bindgen 0.6.5", - "thiserror 2.0.12", + "thiserror 2.0.17", "tokio", "tsify", "url", @@ -4912,66 +4957,55 @@ dependencies = [ [[package]] name = "netlink-packet-core" -version = "0.7.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72724faf704479d67b388da142b186f916188505e7e0b26719019c525882eda4" +checksum = "3463cbb78394cb0141e2c926b93fc2197e473394b761986eca3b9da2c63ae0f4" dependencies = [ - "anyhow", - "byteorder", - "netlink-packet-utils", + "paste", ] [[package]] name = "netlink-packet-generic" -version = "0.3.3" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd7eb8ad331c84c6b8cb7f685b448133e5ad82e1ffd5acafac374af4a5a308b" +checksum = "2f891b2e0054cac5a684a06628f59568f841c93da4e551239da6e518f539e775" dependencies = [ - "anyhow", - "byteorder", "netlink-packet-core", - "netlink-packet-utils", ] [[package]] name = "netlink-packet-route" -version = "0.20.1" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55e5bda7ca0f9ac5e75b5debac3b75e29a8ac8e2171106a2c3bb466389a8dd83" +checksum = "3ec2f5b6839be2a19d7fa5aab5bc444380f6311c2b693551cb80f45caaa7b5ef" dependencies = [ - "anyhow", "bitflags 2.9.1", - "byteorder", "libc", "log", "netlink-packet-core", - "netlink-packet-utils", ] [[package]] name = "netlink-packet-utils" -version = "0.5.2" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ede8a08c71ad5a95cdd0e4e52facd37190977039a4704eb82a283f713747d34" +checksum = "3176f18d11a1ae46053e59ec89d46ba318ae1343615bd3f8c908bfc84edae35c" dependencies = [ - "anyhow", "byteorder", - "paste", - "thiserror 1.0.69", + "pastey 0.1.1", + "thiserror 2.0.17", ] [[package]] name = "netlink-packet-wireguard" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b25b050ff1f6a1e23c6777b72db22790fe5b6b5ccfd3858672587a79876c8f" +checksum = "598962d9067d3153a00106da10e7b8276cea68f396f4a22f5b4a079270d92e29" dependencies = [ - "anyhow", - "byteorder", "libc", "log", + "netlink-packet-core", "netlink-packet-generic", - "netlink-packet-utils", ] [[package]] @@ -5010,7 +5044,6 @@ dependencies = [ "cfg-if", "cfg_aliases", "libc", - "memoffset", ] [[package]] @@ -5023,6 +5056,7 @@ dependencies = [ "cfg-if", "cfg_aliases", "libc", + "memoffset", ] [[package]] @@ -5092,11 +5126,11 @@ dependencies = [ [[package]] name = "nu-ansi-term" -version = "0.50.3" +version = "0.50.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +checksum = "d4a28e057d01f97e61255210fcff094d74ed0466038633e95017f5beb68e4399" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.52.0", ] [[package]] @@ -5191,7 +5225,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.104", ] [[package]] @@ -5271,7 +5305,7 @@ dependencies = [ "tempfile", "tendermint", "test-with", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "tokio-stream", @@ -5317,7 +5351,7 @@ dependencies = [ "sha2 0.10.9", "tendermint", "tendermint-rpc", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tracing", "ts-rs", @@ -5350,7 +5384,7 @@ dependencies = [ "nym-validator-client", "nym-wireguard-types", "semver 1.0.26", - "thiserror 2.0.12", + "thiserror 2.0.17", "tokio", "tokio-util", "tracing", @@ -5375,7 +5409,7 @@ dependencies = [ "serde", "sha2 0.10.9", "strum_macros", - "thiserror 2.0.12", + "thiserror 2.0.17", "tracing", "x25519-dalek", ] @@ -5394,7 +5428,7 @@ dependencies = [ "nym-task", "nym-validator-client", "rand 0.8.5", - "thiserror 2.0.12", + "thiserror 2.0.17", ] [[package]] @@ -5501,7 +5535,7 @@ dependencies = [ "serde_json", "tap", "tempfile", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "toml 0.8.23", @@ -5538,7 +5572,7 @@ dependencies = [ "serde", "serde_json", "tap", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "tokio-tungstenite", @@ -5591,7 +5625,7 @@ dependencies = [ "sha2 0.10.9", "si-scale", "tempfile", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "tokio-stream", @@ -5618,7 +5652,7 @@ dependencies = [ "nym-sphinx-params", "nym-statistics-common", "serde", - "thiserror 2.0.12", + "thiserror 2.0.17", "url", ] @@ -5633,7 +5667,7 @@ dependencies = [ "nym-gateway-requests", "serde", "sqlx", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "tracing", @@ -5653,7 +5687,7 @@ dependencies = [ "nym-task", "sqlx", "sqlx-pool-guard", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "tracing", @@ -5676,7 +5710,7 @@ dependencies = [ "serde", "serde-wasm-bindgen 0.6.5", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.17", "tokio_with_wasm", "tsify", "wasm-bindgen", @@ -5737,7 +5771,7 @@ dependencies = [ "serde", "sha2 0.10.9", "subtle 2.6.1", - "thiserror 2.0.12", + "thiserror 2.0.17", "zeroize", ] @@ -5750,7 +5784,7 @@ dependencies = [ "log", "nym-network-defaults", "serde", - "thiserror 2.0.12", + "thiserror 2.0.17", "toml 0.8.23", "url", ] @@ -5767,7 +5801,7 @@ dependencies = [ "nym-ip-packet-requests", "nym-sdk", "pnet_packet", - "thiserror 2.0.12", + "thiserror 2.0.17", "tokio", "tokio-util", "tracing", @@ -5785,7 +5819,7 @@ dependencies = [ "schemars 0.8.22", "serde", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.17", "utoipa", "vergen 8.3.1", ] @@ -5854,7 +5888,7 @@ dependencies = [ "strum", "strum_macros", "tempfile", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "tokio-util", @@ -5895,7 +5929,7 @@ dependencies = [ "strum", "strum_macros", "tempfile", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "tokio-util", @@ -5946,7 +5980,7 @@ dependencies = [ "serde", "sqlx", "sqlx-pool-guard", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "zeroize", @@ -5965,7 +5999,7 @@ dependencies = [ "nym-credentials-interface", "nym-ecash-time", "nym-validator-client", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", ] @@ -5992,7 +6026,7 @@ dependencies = [ "nym-upgrade-mode-check", "nym-validator-client", "si-scale", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "tracing", @@ -6017,7 +6051,7 @@ dependencies = [ "nym-validator-client", "rand 0.8.5", "serde", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "zeroize", ] @@ -6035,7 +6069,7 @@ dependencies = [ "serde", "strum", "strum_macros", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "utoipa", ] @@ -6070,7 +6104,7 @@ dependencies = [ "serde_json", "sha2 0.10.9", "subtle-encoding", - "thiserror 2.0.12", + "thiserror 2.0.17", "x25519-dalek", "zeroize", ] @@ -6099,7 +6133,7 @@ dependencies = [ "serde", "serde_json", "sqlx", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "tokio-util", @@ -6130,7 +6164,7 @@ dependencies = [ "serde", "serde_derive", "sha2 0.10.9", - "thiserror 2.0.12", + "thiserror 2.0.17", "zeroize", ] @@ -6145,7 +6179,7 @@ dependencies = [ "cw-utils", "cw2", "nym-multisig-contract-common", - "thiserror 2.0.12", + "thiserror 2.0.17", ] [[package]] @@ -6158,7 +6192,7 @@ dependencies = [ "nym-network-defaults", "nym-validator-client", "semver 1.0.26", - "thiserror 2.0.12", + "thiserror 2.0.17", "tokio", "tracing", "url", @@ -6172,7 +6206,7 @@ dependencies = [ "nym-crypto", "semver 1.0.26", "serde", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tracing", "url", @@ -6194,7 +6228,7 @@ dependencies = [ "reqwest 0.12.22", "serde", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.17", "tracing", "utoipa", ] @@ -6211,8 +6245,8 @@ dependencies = [ "nym-sdk", "nym-sphinx-anonymous-replies", "tokio", - "uniffi", - "uniffi_build", + "uniffi 0.29.3", + "uniffi_build 0.29.3", ] [[package]] @@ -6265,7 +6299,7 @@ dependencies = [ "nym-wireguard-types", "rand 0.8.5", "serde", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "tokio-stream", @@ -6298,7 +6332,7 @@ dependencies = [ "rand 0.8.5", "serde", "si-scale", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "tokio-stream", @@ -6355,7 +6389,7 @@ dependencies = [ "rand 0.8.5", "serde", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "tokio-util", @@ -6389,7 +6423,7 @@ dependencies = [ "serde_json", "strum", "subtle 2.6.1", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "tracing", @@ -6408,7 +6442,7 @@ dependencies = [ "nym-statistics-common", "sqlx", "strum", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "tracing", @@ -6427,7 +6461,7 @@ dependencies = [ "nym-gateway-requests", "nym-sphinx", "sqlx", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "tracing", @@ -6444,10 +6478,10 @@ dependencies = [ "nym-ffi-shared", "nym-sdk", "nym-sphinx-anonymous-replies", - "thiserror 2.0.12", + "thiserror 2.0.17", "tokio", - "uniffi", - "uniffi_build", + "uniffi 0.29.3", + "uniffi_build 0.29.3", ] [[package]] @@ -6485,7 +6519,7 @@ dependencies = [ "serde_json", "serde_plain", "serde_yaml", - "thiserror 2.0.12", + "thiserror 2.0.17", "tokio", "tracing", "tracing-subscriber", @@ -6502,7 +6536,7 @@ dependencies = [ "proc-macro2", "quote", "reqwest 0.12.22", - "syn 2.0.106", + "syn 2.0.104", "uuid", ] @@ -6534,7 +6568,7 @@ version = "0.1.0" dependencies = [ "nym-credential-storage", "nym-credentials", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tracing", "zeroize", @@ -6560,7 +6594,7 @@ version = "0.1.0" dependencies = [ "log", "rand 0.8.5", - "thiserror 2.0.12", + "thiserror 2.0.17", ] [[package]] @@ -6572,7 +6606,7 @@ dependencies = [ "futures", "nym-ip-packet-requests", "nym-sdk", - "thiserror 2.0.12", + "thiserror 2.0.17", "tokio", "tokio-util", "tracing", @@ -6590,7 +6624,7 @@ dependencies = [ "nym-sphinx", "rand 0.8.5", "serde", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "tokio-util", @@ -6632,7 +6666,7 @@ dependencies = [ "reqwest 0.12.22", "serde", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "tokio-tun", @@ -6648,7 +6682,7 @@ dependencies = [ "bytes", "env_logger", "log", - "thiserror 2.0.12", + "thiserror 2.0.17", "tokio-util", ] @@ -6668,8 +6702,8 @@ dependencies = [ "nym-crypto", "nym-sphinx", "rand 0.9.2", + "thiserror 2.0.17", "rand_chacha 0.9.0", - "thiserror 2.0.12", "zeroize", ] @@ -6681,7 +6715,7 @@ dependencies = [ "k256", "ledger-transport", "ledger-transport-hid", - "thiserror 2.0.12", + "thiserror 2.0.17", ] [[package]] @@ -6709,7 +6743,7 @@ dependencies = [ "serde", "sha2 0.10.9", "snow", - "thiserror 2.0.12", + "thiserror 2.0.17", "tls_codec", "tracing", "utoipa", @@ -6806,7 +6840,7 @@ dependencies = [ "semver 1.0.26", "serde", "serde_repr", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "ts-rs", "utoipa", @@ -6832,7 +6866,7 @@ dependencies = [ "nym-task", "rand 0.8.5", "serde", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "tokio-util", @@ -6851,7 +6885,7 @@ dependencies = [ "cw4", "schemars 0.8.22", "serde", - "thiserror 2.0.12", + "thiserror 2.0.17", ] [[package]] @@ -6945,7 +6979,7 @@ dependencies = [ "sqlx", "tap", "tempfile", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "tokio-tungstenite", @@ -7023,7 +7057,7 @@ dependencies = [ "serde_json", "sha2 0.10.9", "sysinfo", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "tokio-stream", @@ -7073,7 +7107,7 @@ dependencies = [ "serde_json", "strum", "strum_macros", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "url", @@ -7142,7 +7176,7 @@ dependencies = [ "sqlx", "strum", "strum_macros", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "tokio-stream", @@ -7188,7 +7222,7 @@ dependencies = [ "rand_chacha 0.3.1", "serde", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.17", "tokio", "wasm-utils", ] @@ -7203,7 +7237,7 @@ dependencies = [ "rand 0.8.5", "serde", "serde-wasm-bindgen 0.6.5", - "thiserror 2.0.12", + "thiserror 2.0.17", "tokio", "tsify", "wasm-bindgen", @@ -7230,7 +7264,7 @@ dependencies = [ "snow", "strum", "strum_macros", - "thiserror 2.0.12", + "thiserror 2.0.17", "tokio", "tokio-util", "tracing", @@ -7277,7 +7311,7 @@ name = "nym-ordered-buffer" version = "0.1.0" dependencies = [ "log", - "thiserror 2.0.12", + "thiserror 2.0.17", ] [[package]] @@ -7294,7 +7328,7 @@ dependencies = [ "rand 0.8.5", "rayon", "sphinx-packet", - "thiserror 2.0.12", + "thiserror 2.0.17", "x25519-dalek", "zeroize", ] @@ -7318,7 +7352,7 @@ dependencies = [ "nym-contracts-common", "schemars 0.8.22", "serde", - "thiserror 2.0.12", + "thiserror 2.0.17", ] [[package]] @@ -7330,7 +7364,7 @@ dependencies = [ "cw-controllers", "schemars 0.8.22", "serde", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", ] @@ -7353,7 +7387,7 @@ dependencies = [ "nym-validator-client", "nym-wireguard-types", "rand 0.8.5", - "thiserror 2.0.12", + "thiserror 2.0.17", "tokio", "tokio-util", "tracing", @@ -7423,7 +7457,7 @@ dependencies = [ "serde", "tap", "tempfile", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "tokio-stream", @@ -7453,7 +7487,7 @@ version = "0.1.0" dependencies = [ "bincode", "serde", - "thiserror 2.0.12", + "thiserror 2.0.17", ] [[package]] @@ -7469,7 +7503,7 @@ dependencies = [ "nym-sphinx-anonymous-replies", "serde", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.17", "tokio", ] @@ -7519,7 +7553,7 @@ dependencies = [ "serde", "serde_json", "tap", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "url", @@ -7553,7 +7587,7 @@ dependencies = [ "schemars 0.8.22", "serde", "tap", - "thiserror 2.0.12", + "thiserror 2.0.17", "tokio", "url", ] @@ -7585,7 +7619,7 @@ dependencies = [ "serde", "serde_json", "tap", - "thiserror 2.0.12", + "thiserror 2.0.17", ] [[package]] @@ -7609,7 +7643,7 @@ dependencies = [ "rand 0.8.5", "rand_chacha 0.3.1", "rand_distr", - "thiserror 2.0.12", + "thiserror 2.0.17", "tokio", "tracing", ] @@ -7628,7 +7662,7 @@ dependencies = [ "nym-topology", "rand 0.8.5", "serde", - "thiserror 2.0.12", + "thiserror 2.0.17", "zeroize", ] @@ -7642,7 +7676,7 @@ dependencies = [ "rand 0.8.5", "serde", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.17", ] [[package]] @@ -7658,7 +7692,7 @@ dependencies = [ "nym-topology", "rand 0.8.5", "rand_chacha 0.3.1", - "thiserror 2.0.12", + "thiserror 2.0.17", "tracing", "wasm-bindgen", ] @@ -7676,7 +7710,7 @@ dependencies = [ "nym-sphinx-types", "rand 0.8.5", "serde", - "thiserror 2.0.12", + "thiserror 2.0.17", "utoipa", "wasmtimer", ] @@ -7695,7 +7729,7 @@ dependencies = [ "nym-sphinx-types", "nym-topology", "rand 0.8.5", - "thiserror 2.0.12", + "thiserror 2.0.17", ] [[package]] @@ -7706,7 +7740,7 @@ dependencies = [ "nym-sphinx-anonymous-replies", "nym-sphinx-params", "nym-sphinx-types", - "thiserror 2.0.12", + "thiserror 2.0.17", ] [[package]] @@ -7720,7 +7754,7 @@ dependencies = [ "nym-sphinx-forwarding", "nym-sphinx-params", "nym-sphinx-types", - "thiserror 2.0.12", + "thiserror 2.0.17", "tokio", "tokio-util", "tracing", @@ -7733,7 +7767,7 @@ dependencies = [ "nym-crypto", "nym-sphinx-types", "serde", - "thiserror 2.0.12", + "thiserror 2.0.17", ] [[package]] @@ -7742,7 +7776,7 @@ version = "0.1.0" dependencies = [ "nym-sphinx-addressing", "nym-sphinx-types", - "thiserror 2.0.12", + "thiserror 2.0.17", ] [[package]] @@ -7751,7 +7785,7 @@ version = "0.2.0" dependencies = [ "nym-outfox", "sphinx-packet", - "thiserror 2.0.12", + "thiserror 2.0.17", ] [[package]] @@ -7802,7 +7836,7 @@ dependencies = [ "strum", "strum_macros", "sysinfo", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "utoipa", @@ -7820,7 +7854,7 @@ dependencies = [ "rand 0.8.5", "serde", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.17", "zeroize", ] @@ -7833,7 +7867,7 @@ dependencies = [ "futures", "log", "nym-test-utils", - "thiserror 2.0.12", + "thiserror 2.0.17", "tokio", "tokio-util", "tracing", @@ -7885,7 +7919,7 @@ dependencies = [ "reqwest 0.12.22", "serde", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tracing", "tsify", @@ -7900,7 +7934,7 @@ dependencies = [ "etherparse", "log", "nym-wireguard-types", - "thiserror 2.0.12", + "thiserror 2.0.17", "tokio", "tokio-tun", ] @@ -7929,7 +7963,7 @@ dependencies = [ "strum", "strum_macros", "tempfile", - "thiserror 2.0.12", + "thiserror 2.0.17", "ts-rs", "url", "utoipa", @@ -7948,7 +7982,7 @@ dependencies = [ "reqwest 0.12.22", "serde", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tracing", "utoipa", @@ -7995,7 +8029,7 @@ dependencies = [ "serde_json", "sha2 0.10.9", "tendermint-rpc", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "tracing", @@ -8040,7 +8074,7 @@ dependencies = [ "serde_with", "sha2 0.10.9", "sqlx", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "tracing", @@ -8061,7 +8095,7 @@ dependencies = [ "nym-task", "nym-validator-client", "rand 0.8.5", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "tokio-util", @@ -8079,7 +8113,7 @@ dependencies = [ "nym-contracts-common", "nym-mixnet-contract-common", "serde", - "thiserror 2.0.12", + "thiserror 2.0.17", "ts-rs", ] @@ -8100,7 +8134,7 @@ dependencies = [ "serde", "serde-wasm-bindgen 0.6.5", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tsify", "wasm-bindgen", @@ -8149,7 +8183,7 @@ dependencies = [ "nym-task", "nym-wireguard-types", "rand 0.8.5", - "thiserror 2.0.12", + "thiserror 2.0.17", "tokio", "tokio-stream", "tracing", @@ -8194,7 +8228,7 @@ dependencies = [ "nym-credentials-interface", "schemars 0.8.22", "serde", - "thiserror 2.0.12", + "thiserror 2.0.17", "utoipa", ] @@ -8230,7 +8264,7 @@ dependencies = [ "nym-crypto", "rand 0.8.5", "serde", - "thiserror 2.0.12", + "thiserror 2.0.17", "x25519-dalek", ] @@ -8257,7 +8291,7 @@ dependencies = [ "serde_json", "sha2 0.10.9", "tar", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "tracing", @@ -8283,7 +8317,7 @@ dependencies = [ "schemars 0.8.22", "serde", "sqlx", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "tokio-util", @@ -8307,7 +8341,7 @@ dependencies = [ "serde", "serde_json", "sqlx", - "thiserror 2.0.12", + "thiserror 2.0.17", "tokio", "tracing", ] @@ -8331,7 +8365,7 @@ dependencies = [ "sha2 0.10.9", "tendermint", "tendermint-rpc", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "tokio-stream", @@ -8348,39 +8382,30 @@ dependencies = [ "async-trait", "nyxd-scraper-shared", "sqlx", - "thiserror 2.0.12", + "thiserror 2.0.17", "tokio", "tracing", ] [[package]] name = "objc2-core-foundation" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ "bitflags 2.9.1", ] [[package]] name = "objc2-io-kit" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71c1c64d6120e51cd86033f67176b1cb66780c2efe34dec55176f77befd93c0a" +checksum = "33fafba39597d6dc1fb709123dfa8289d39406734be322956a69f0931c73bb15" dependencies = [ "libc", "objc2-core-foundation", ] -[[package]] -name = "object" -version = "0.36.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" -dependencies = [ - "memchr", -] - [[package]] name = "once_cell" version = "1.21.3" @@ -8629,6 +8654,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec" +[[package]] +name = "pastey" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b867cad97c0791bbd3aaa6472142568c6c9e8f71937e98379f584cfb0cf35bec" + [[package]] name = "peg" version = "0.8.5" @@ -8689,7 +8720,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1db05f56d34358a8b1066f67cbb203ee3e7ed2ba674a6263a1d5ec6db2204323" dependencies = [ "memchr", - "thiserror 2.0.12", + "thiserror 2.0.17", "ucd-trie", ] @@ -8713,7 +8744,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.104", ] [[package]] @@ -8776,7 +8807,7 @@ dependencies = [ "phf_shared", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.104", ] [[package]] @@ -8805,7 +8836,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.104", ] [[package]] @@ -8899,7 +8930,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.106", + "syn 2.0.104", ] [[package]] @@ -9057,11 +9088,11 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "3.3.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" +checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" dependencies = [ - "toml_edit", + "toml_edit 0.23.5", ] [[package]] @@ -9083,7 +9114,7 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.104", ] [[package]] @@ -9116,7 +9147,7 @@ dependencies = [ "memchr", "parking_lot", "protobuf", - "thiserror 2.0.12", + "thiserror 2.0.17", ] [[package]] @@ -9139,7 +9170,7 @@ dependencies = [ "itertools 0.14.0", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.104", ] [[package]] @@ -9216,7 +9247,7 @@ dependencies = [ "rustc-hash", "rustls 0.23.29", "socket2 0.5.10", - "thiserror 2.0.12", + "thiserror 2.0.17", "tokio", "tracing", "web-time", @@ -9237,7 +9268,7 @@ dependencies = [ "rustls 0.23.29", "rustls-pki-types", "slab", - "thiserror 2.0.12", + "thiserror 2.0.17", "tinyvec", "tracing", "web-time", @@ -9384,7 +9415,7 @@ checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ "getrandom 0.2.16", "libredox", - "thiserror 2.0.12", + "thiserror 2.0.17", ] [[package]] @@ -9404,14 +9435,14 @@ checksum = "1165225c21bff1f3bbce98f5a1f889949bc902d3575308cc7b0de30b4f6d27c7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.104", ] [[package]] name = "regex" -version = "1.11.1" +version = "1.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" dependencies = [ "aho-corasick", "memchr", @@ -9421,9 +9452,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.9" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" dependencies = [ "aho-corasick", "memchr", @@ -9525,7 +9556,7 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21918d6644020c6f6ef1993242989bf6d4952d2e025617744f184c02df51c356" dependencies = [ - "thiserror 2.0.12", + "thiserror 2.0.17", ] [[package]] @@ -9639,7 +9670,7 @@ dependencies = [ "proc-macro2", "quote", "rust-embed-utils", - "syn 2.0.106", + "syn 2.0.104", "walkdir", ] @@ -9669,12 +9700,6 @@ dependencies = [ "thiserror 1.0.69", ] -[[package]] -name = "rustc-demangle" -version = "0.1.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" - [[package]] name = "rustc-hash" version = "2.1.1" @@ -9928,7 +9953,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals 0.29.1", - "syn 2.0.106", + "syn 2.0.104", ] [[package]] @@ -9960,7 +9985,7 @@ checksum = "1783eabc414609e28a5ba76aee5ddd52199f7107a0b24c2e9746a1ecc34a683d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.104", ] [[package]] @@ -9981,7 +10006,7 @@ checksum = "22f968c5ea23d555e670b449c1c5e7b2fc399fdaec1d304a17cd48e288abc107" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.104", ] [[package]] @@ -10066,10 +10091,11 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ + "serde_core", "serde_derive", ] @@ -10114,14 +10140,23 @@ dependencies = [ ] [[package]] -name = "serde_derive" -version = "1.0.219" +name = "serde_core" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.104", ] [[package]] @@ -10132,7 +10167,7 @@ checksum = "e578a843d40b4189a4d66bba51d7684f57da5bd7c304c64e14bd63efbef49509" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.104", ] [[package]] @@ -10143,19 +10178,20 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.104", ] [[package]] name = "serde_json" -version = "1.0.141" +version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30b9eff21ebe718216c6ec64e1d9ac57087aad11efc64e32002bce4a0d4c03d3" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" dependencies = [ "itoa", "memchr", "ryu", "serde", + "serde_core", ] [[package]] @@ -10171,7 +10207,7 @@ dependencies = [ "serde_json", "serde_json_path_core", "serde_json_path_macros", - "thiserror 2.0.12", + "thiserror 2.0.17", ] [[package]] @@ -10183,7 +10219,7 @@ dependencies = [ "inventory", "serde", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.17", ] [[package]] @@ -10205,7 +10241,7 @@ checksum = "aafbefbe175fa9bf03ca83ef89beecff7d2a95aaacd5732325b90ac8c3bd7b90" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.104", ] [[package]] @@ -10235,7 +10271,7 @@ checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.104", ] [[package]] @@ -10288,7 +10324,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.104", ] [[package]] @@ -10450,9 +10486,9 @@ checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" [[package]] name = "slab" -version = "0.4.11" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" +checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" [[package]] name = "sluice" @@ -10625,7 +10661,7 @@ dependencies = [ "serde_json", "sha2 0.10.9", "smallvec", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "tokio-stream", @@ -10644,7 +10680,7 @@ dependencies = [ "quote", "sqlx-core", "sqlx-macros-core", - "syn 2.0.106", + "syn 2.0.104", ] [[package]] @@ -10667,7 +10703,7 @@ dependencies = [ "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", - "syn 2.0.106", + "syn 2.0.104", "tokio", "url", ] @@ -10710,7 +10746,7 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tracing", "whoami", @@ -10726,7 +10762,7 @@ dependencies = [ "tokio", "tracing", "tracing-subscriber", - "windows", + "windows 0.61.3", ] [[package]] @@ -10762,7 +10798,7 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tracing", "whoami", @@ -10788,7 +10824,7 @@ dependencies = [ "serde", "serde_urlencoded", "sqlx-core", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tracing", "url", @@ -10876,7 +10912,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.104", ] [[package]] @@ -10932,9 +10968,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.106" +version = "2.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" dependencies = [ "proc-macro2", "quote", @@ -10964,21 +11000,21 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.104", ] [[package]] name = "sysinfo" -version = "0.37.0" +version = "0.37.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07cec4dc2d2e357ca1e610cfb07de2fa7a10fc3e9fe89f72545f3d244ea87753" +checksum = "16607d5caffd1c07ce073528f9ed972d88db15dd44023fa57142963be3feb11f" dependencies = [ "libc", "memchr", "ntapi", "objc2-core-foundation", "objc2-io-kit", - "windows", + "windows 0.61.3", ] [[package]] @@ -11161,7 +11197,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.106", + "syn 2.0.104", ] [[package]] @@ -11197,7 +11233,7 @@ dependencies = [ "serde_json", "sqlx", "tempfile", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "toml 0.8.23", @@ -11226,11 +11262,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.12" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" dependencies = [ - "thiserror-impl 2.0.12", + "thiserror-impl 2.0.17", ] [[package]] @@ -11241,18 +11277,18 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.104", ] [[package]] name = "thiserror-impl" -version = "2.0.12" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.104", ] [[package]] @@ -11373,39 +11409,36 @@ checksum = "2d2e76690929402faae40aebdda620a2c0e25dd6d3b9afe48867dfd95991f4bd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.104", ] [[package]] name = "tokio" -version = "1.47.1" +version = "1.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" +checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" dependencies = [ - "backtrace", "bytes", - "io-uring", "libc", "mio 1.0.4", "parking_lot", "pin-project-lite", "signal-hook-registry", - "slab", "socket2 0.6.0", "tokio-macros", "tracing", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "tokio-macros" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.104", ] [[package]] @@ -11554,7 +11587,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37e04c1865c281139e5ccf633cb9f76ffdaabeebfe53b703984cf82878e2aabb" dependencies = [ "quote", - "syn 2.0.106", + "syn 2.0.104", ] [[package]] @@ -11574,8 +11607,8 @@ checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" dependencies = [ "serde", "serde_spanned", - "toml_datetime", - "toml_edit", + "toml_datetime 0.6.11", + "toml_edit 0.22.27", ] [[package]] @@ -11587,6 +11620,15 @@ dependencies = [ "serde", ] +[[package]] +name = "toml_datetime" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" +dependencies = [ + "serde_core", +] + [[package]] name = "toml_edit" version = "0.22.27" @@ -11596,11 +11638,32 @@ dependencies = [ "indexmap 2.10.0", "serde", "serde_spanned", - "toml_datetime", + "toml_datetime 0.6.11", "toml_write", "winnow", ] +[[package]] +name = "toml_edit" +version = "0.23.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2ad0b7ae9cfeef5605163839cb9221f453399f15cfb5c10be9885fcf56611f9" +dependencies = [ + "indexmap 2.10.0", + "toml_datetime 0.7.3", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b551886f449aa90d4fe2bdaa9f4a2577ad2dde302c61ecf262d80b116db95c10" +dependencies = [ + "winnow", +] + [[package]] name = "toml_write" version = "0.1.2" @@ -11747,9 +11810,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.44" +version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "log", "pin-project-lite", @@ -11759,20 +11822,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.31" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.104", ] [[package]] name = "tracing-core" -version = "0.1.36" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" dependencies = [ "once_cell", "valuable", @@ -11838,12 +11901,12 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.22" +version = "0.3.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" +checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5" dependencies = [ "matchers", - "nu-ansi-term 0.50.3", + "nu-ansi-term 0.50.1", "once_cell", "regex-automata", "sharded-slab", @@ -11872,7 +11935,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04659ddb06c87d233c566112c1c9c5b9e98256d9af50ec3bc9c8327f873a7568" dependencies = [ "quote", - "syn 2.0.106", + "syn 2.0.104", ] [[package]] @@ -11929,7 +11992,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e640d9b0964e9d39df633548591090ab92f7a4567bc31d3891af23471a3365c6" dependencies = [ "lazy_static", - "thiserror 2.0.12", + "thiserror 2.0.17", "ts-rs-macros", ] @@ -11956,7 +12019,7 @@ checksum = "0e9d8656589772eeec2cf7a8264d9cda40fb28b9bc53118ceb9e8c07f8f38730" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.104", "termcolor", ] @@ -11983,7 +12046,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals 0.28.0", - "syn 2.0.106", + "syn 2.0.104", ] [[package]] @@ -12045,7 +12108,7 @@ checksum = "016c26257f448222014296978b2c8456e2cad4de308c35bdb1e383acd569ef5b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.104", ] [[package]] @@ -12127,11 +12190,28 @@ dependencies = [ "camino", "cargo_metadata 0.19.2", "clap", - "uniffi_bindgen", - "uniffi_build", - "uniffi_core", - "uniffi_macros", - "uniffi_pipeline", + "uniffi_bindgen 0.29.3", + "uniffi_build 0.29.3", + "uniffi_core 0.29.3", + "uniffi_macros 0.29.3", + "uniffi_pipeline 0.29.3", +] + +[[package]] +name = "uniffi" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c866f627c3f04c3df068b68bb2d725492caaa539dd313e2a9d26bb85b1a32f4e" +dependencies = [ + "anyhow", + "camino", + "cargo_metadata 0.19.2", + "clap", + "uniffi_bindgen 0.30.0", + "uniffi_build 0.30.0", + "uniffi_core 0.30.0", + "uniffi_macros 0.30.0", + "uniffi_pipeline 0.30.0", ] [[package]] @@ -12154,10 +12234,36 @@ dependencies = [ "tempfile", "textwrap", "toml 0.5.11", - "uniffi_internal_macros", - "uniffi_meta", - "uniffi_pipeline", - "uniffi_udl", + "uniffi_internal_macros 0.29.3", + "uniffi_meta 0.29.3", + "uniffi_pipeline 0.29.3", + "uniffi_udl 0.29.3", +] + +[[package]] +name = "uniffi_bindgen" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c8ca600167641ebe7c8ba9254af40492dda3397c528cc3b2f511bd23e8541a5" +dependencies = [ + "anyhow", + "askama", + "camino", + "cargo_metadata 0.19.2", + "fs-err", + "glob", + "goblin", + "heck 0.5.0", + "indexmap 2.10.0", + "once_cell", + "serde", + "tempfile", + "textwrap", + "toml 0.8.23", + "uniffi_internal_macros 0.30.0", + "uniffi_meta 0.30.0", + "uniffi_pipeline 0.30.0", + "uniffi_udl 0.30.0", ] [[package]] @@ -12168,7 +12274,18 @@ checksum = "0d84d607076008df3c32dd2100ee4e727269f11d3faa35691af70d144598f666" dependencies = [ "anyhow", "camino", - "uniffi_bindgen", + "uniffi_bindgen 0.29.3", +] + +[[package]] +name = "uniffi_build" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e55c05228f4858bb258f651d21d743fcc1fe5a2ec20d3c0f9daefddb105ee4d" +dependencies = [ + "anyhow", + "camino", + "uniffi_bindgen 0.30.0", ] [[package]] @@ -12183,6 +12300,18 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "uniffi_core" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e7a5a038ebffe8f4cf91416b154ef3c2468b18e828b7009e01b1b99938089f9" +dependencies = [ + "anyhow", + "bytes", + "once_cell", + "static_assertions", +] + [[package]] name = "uniffi_internal_macros" version = "0.29.3" @@ -12193,7 +12322,20 @@ dependencies = [ "indexmap 2.10.0", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.104", +] + +[[package]] +name = "uniffi_internal_macros" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c2a6f93e7b73726e2015696ece25ca0ac5a5f1cf8d6a7ab5214dd0a01d2edf" +dependencies = [ + "anyhow", + "indexmap 2.10.0", + "proc-macro2", + "quote", + "syn 2.0.104", ] [[package]] @@ -12208,9 +12350,26 @@ dependencies = [ "proc-macro2", "quote", "serde", - "syn 2.0.106", + "syn 2.0.104", "toml 0.5.11", - "uniffi_meta", + "uniffi_meta 0.29.3", +] + +[[package]] +name = "uniffi_macros" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64c6309fc36c7992afc03bc0c5b059c656bccbef3f2a4bc362980017f8936141" +dependencies = [ + "camino", + "fs-err", + "once_cell", + "proc-macro2", + "quote", + "serde", + "syn 2.0.104", + "toml 0.8.23", + "uniffi_meta 0.30.0", ] [[package]] @@ -12221,8 +12380,20 @@ checksum = "3d226fc167754ce548c5ece9828c8a06f03bf1eea525d2659ba6bd648bd8e2f3" dependencies = [ "anyhow", "siphasher 0.3.11", - "uniffi_internal_macros", - "uniffi_pipeline", + "uniffi_internal_macros 0.29.3", + "uniffi_pipeline 0.29.3", +] + +[[package]] +name = "uniffi_meta" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a138823392dba19b0aa494872689f97d0ee157de5852e2bec157ce6de9cdc22" +dependencies = [ + "anyhow", + "siphasher 0.3.11", + "uniffi_internal_macros 0.30.0", + "uniffi_pipeline 0.30.0", ] [[package]] @@ -12235,7 +12406,20 @@ dependencies = [ "heck 0.5.0", "indexmap 2.10.0", "tempfile", - "uniffi_internal_macros", + "uniffi_internal_macros 0.29.3", +] + +[[package]] +name = "uniffi_pipeline" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c27c4b515d25f8e53cc918e238c39a79c3144a40eaf2e51c4a7958973422c29" +dependencies = [ + "anyhow", + "heck 0.5.0", + "indexmap 2.10.0", + "tempfile", + "uniffi_internal_macros 0.30.0", ] [[package]] @@ -12246,7 +12430,19 @@ checksum = "9c42649b721df759d9d4692a376b82b62ce3028ec9fc466f4780fb8cdf728996" dependencies = [ "anyhow", "textwrap", - "uniffi_meta", + "uniffi_meta 0.29.3", + "weedle2", +] + +[[package]] +name = "uniffi_udl" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0adacdd848aeed7af4f5af7d2f621d5e82531325d405e29463482becfdeafca" +dependencies = [ + "anyhow", + "textwrap", + "uniffi_meta 0.30.0", "weedle2", ] @@ -12335,7 +12531,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.106", + "syn 2.0.104", "uuid", ] @@ -12374,7 +12570,7 @@ checksum = "268d76aaebb80eba79240b805972e52d7d410d4bcc52321b951318b0f440cd60" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.104", ] [[package]] @@ -12385,7 +12581,7 @@ checksum = "382673bda1d05c85b4550d32fd4192ccd4cffe9a908543a0795d1e7682b36246" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.104", "utoipauto-core", ] @@ -12604,7 +12800,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.104", "wasm-bindgen-shared", ] @@ -12639,7 +12835,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.104", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -12674,7 +12870,7 @@ checksum = "17d5042cc5fa009658f9a7333ef24291b1291a25b6382dd68862a7f3b969f69b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.104", ] [[package]] @@ -12700,7 +12896,7 @@ dependencies = [ "rand 0.8.5", "serde", "serde-wasm-bindgen 0.6.5", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tsify", "url", @@ -12722,7 +12918,7 @@ dependencies = [ "nym-store-cipher", "serde", "serde-wasm-bindgen 0.6.5", - "thiserror 2.0.12", + "thiserror 2.0.17", "wasm-bindgen", "wasm-utils", ] @@ -12855,6 +13051,12 @@ dependencies = [ "web-sys", ] +[[package]] +name = "widestring" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c168940144dd21fd8046987c16a46a33d5fc84eec29ef9dcddc2ac9e31526b7c" + [[package]] name = "widestring" version = "1.2.0" @@ -12898,11 +13100,23 @@ version = "0.61.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" dependencies = [ - "windows-collections", - "windows-core", - "windows-future", - "windows-link", - "windows-numerics", + "windows-collections 0.2.0", + "windows-core 0.61.2", + "windows-future 0.2.1", + "windows-link 0.1.3", + "windows-numerics 0.2.0", +] + +[[package]] +name = "windows" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" +dependencies = [ + "windows-collections 0.3.2", + "windows-core 0.62.2", + "windows-future 0.3.2", + "windows-numerics 0.3.1", ] [[package]] @@ -12911,7 +13125,16 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" dependencies = [ - "windows-core", + "windows-core 0.61.2", +] + +[[package]] +name = "windows-collections" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" +dependencies = [ + "windows-core 0.62.2", ] [[package]] @@ -12922,9 +13145,22 @@ checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" dependencies = [ "windows-implement", "windows-interface", - "windows-link", - "windows-result", - "windows-strings", + "windows-link 0.1.3", + "windows-result 0.3.4", + "windows-strings 0.4.2", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", ] [[package]] @@ -12933,31 +13169,42 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" dependencies = [ - "windows-core", - "windows-link", - "windows-threading", + "windows-core 0.61.2", + "windows-link 0.1.3", + "windows-threading 0.1.0", +] + +[[package]] +name = "windows-future" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" +dependencies = [ + "windows-core 0.62.2", + "windows-link 0.2.1", + "windows-threading 0.2.1", ] [[package]] name = "windows-implement" -version = "0.60.0" +version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.104", ] [[package]] name = "windows-interface" -version = "0.59.1" +version = "0.59.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.104", ] [[package]] @@ -12966,14 +13213,30 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + [[package]] name = "windows-numerics" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" dependencies = [ - "windows-core", - "windows-link", + "windows-core 0.61.2", + "windows-link 0.1.3", +] + +[[package]] +name = "windows-numerics" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" +dependencies = [ + "windows-core 0.62.2", + "windows-link 0.2.1", ] [[package]] @@ -12982,7 +13245,16 @@ version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" dependencies = [ - "windows-link", + "windows-link 0.1.3", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link 0.2.1", ] [[package]] @@ -12991,7 +13263,16 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" dependencies = [ - "windows-link", + "windows-link 0.1.3", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link 0.2.1", ] [[package]] @@ -13039,6 +13320,15 @@ dependencies = [ "windows-targets 0.53.2", ] +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link 0.2.1", +] + [[package]] name = "windows-targets" version = "0.42.2" @@ -13107,7 +13397,16 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" dependencies = [ - "windows-link", + "windows-link 0.1.3", +] + +[[package]] +name = "windows-threading" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" +dependencies = [ + "windows-link 0.2.1", ] [[package]] @@ -13309,6 +13608,22 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "wireguard-nt" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22b4dbcc6c93786cf22e420ef96e8976bfb92a455070282302b74de5848191f4" +dependencies = [ + "bitflags 2.9.1", + "getrandom 0.2.16", + "ipnet", + "libloading", + "log", + "thiserror 1.0.69", + "widestring 0.4.3", + "windows-sys 0.59.0", +] + [[package]] name = "wit-bindgen-rt" version = "0.39.0" @@ -13381,7 +13696,7 @@ checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.104", "synstructure", ] @@ -13402,7 +13717,7 @@ checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.104", ] [[package]] @@ -13422,15 +13737,15 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.104", "synstructure", ] [[package]] name = "zeroize" -version = "1.8.1" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" dependencies = [ "zeroize_derive", ] @@ -13443,7 +13758,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.104", ] [[package]] @@ -13476,7 +13791,7 @@ checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.104", ] [[package]] @@ -13492,7 +13807,7 @@ dependencies = [ "flate2", "indexmap 2.10.0", "memchr", - "thiserror 2.0.12", + "thiserror 2.0.17", "zopfli", ] @@ -13513,7 +13828,7 @@ dependencies = [ "rand 0.8.5", "reqwest 0.12.22", "serde", - "thiserror 2.0.12", + "thiserror 2.0.17", "tokio", "tsify", "uuid", @@ -13573,7 +13888,7 @@ dependencies = [ "reqwest 0.12.22", "serde", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.17", "tokio", "tracing", "url", diff --git a/Cargo.toml b/Cargo.toml index dfc6650e8f..d9239e959d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -256,8 +256,7 @@ ctr = "0.9.1" cupid = "0.6.1" curve25519-dalek = "4.1.3" dashmap = "5.5.3" -# We want https://github.com/DefGuard/wireguard-rs/pull/64 , but there's no crates.io release being pushed out anymore -defguard_wireguard_rs = { git = "https://github.com/DefGuard/wireguard-rs.git", rev = "v0.4.7" } +defguard_wireguard_rs = "0.8.0" digest = "0.10.7" dirs = "6.0" dotenvy = "0.15.6" diff --git a/common/wireguard/src/lib.rs b/common/wireguard/src/lib.rs index 978443cefd..1bf229db03 100644 --- a/common/wireguard/src/lib.rs +++ b/common/wireguard/src/lib.rs @@ -6,16 +6,18 @@ // #![warn(clippy::expect_used)] // #![warn(clippy::unwrap_used)] -use defguard_wireguard_rs::{WGApi, WireguardInterfaceApi, host::Peer, key::Key, net::IpAddrMask}; +use defguard_wireguard_rs::{ + WGApi, WireguardInterfaceApi, error::WireguardInterfaceError, host::Peer, key::Key, + net::IpAddrMask, +}; use nym_crypto::asymmetric::x25519::KeyPair; +use std::net::IpAddr; use std::sync::Arc; use tokio::sync::mpsc::{self, Receiver, Sender}; use tracing::error; #[cfg(target_os = "linux")] use nym_ip_packet_requests::IpPair; -#[cfg(target_os = "linux")] -use std::net::IpAddr; #[cfg(target_os = "linux")] use nym_network_defaults::constants::WG_TUN_BASE_NAME; @@ -33,13 +35,104 @@ pub use peer_controller::{PeerControlRequest, PeerRegistrationData}; pub const CONTROL_CHANNEL_SIZE: usize = 256; +#[derive(Debug, thiserror::Error)] +pub enum WgApiWrapperError { + #[error("WireGuard kernel implementation is not available on this platform")] + KernelUnavailable, + + #[error("WireGuard userspace implementation is not available on this platform")] + UserspaceUnavailable, + + #[error("WireGuard interface error: {0}")] + Interface(#[from] WireguardInterfaceError), +} + pub struct WgApiWrapper { - inner: WGApi, + inner: Box, +} + +impl WgApiWrapper { + /// Create new instance of `WgApiWrapper` choosing internal implementation based on `use_userspace` flag and platform availability. + /// + /// Falls back to userspace implementation when kernel implementation is requested but not available. + pub fn new(ifname: &str, use_userspace: bool) -> Result { + if use_userspace { + Self::userspace(ifname) + } else { + Self::kernel(ifname).or_else(|err| { + if matches!(err, WgApiWrapperError::KernelUnavailable) { + Self::userspace(ifname) + } else { + Err(err) + } + }) + } + } + + /// Create userspace implementation + fn userspace(_ifname: &str) -> Result { + #[cfg(any( + target_os = "linux", + target_os = "macos", + target_os = "freebsd", + target_os = "netbsd" + ))] + { + let api = WGApi::::new(_ifname)?; + Ok(Self { + inner: Box::new(api), + }) + } + + #[cfg(not(any( + target_os = "linux", + target_os = "macos", + target_os = "freebsd", + target_os = "netbsd" + )))] + { + Err(WgApiWrapperError::UserspaceUnavailable) + } + } + + /// Create kernel implementation if available. + fn kernel(_ifname: &str) -> Result { + #[cfg(any( + target_os = "linux", + target_os = "windows", + target_os = "freebsd", + target_os = "netbsd" + ))] + { + let api = WGApi::::new(_ifname)?; + Ok(Self { + inner: Box::new(api), + }) + } + + #[cfg(not(any( + target_os = "linux", + target_os = "windows", + target_os = "freebsd", + target_os = "netbsd" + )))] + { + Err(WgApiWrapperError::KernelUnavailable) + } + } +} + +impl Drop for WgApiWrapper { + fn drop(&mut self) { + if let Err(e) = self.inner.remove_interface() { + error!("Could not remove the wireguard interface: {e:?}"); + } + } } impl WireguardInterfaceApi for WgApiWrapper { fn create_interface( - &self, + &mut self, ) -> Result<(), defguard_wireguard_rs::error::WireguardInterfaceError> { self.inner.create_interface() } @@ -58,7 +151,6 @@ impl WireguardInterfaceApi for WgApiWrapper { self.inner.configure_peer_routing(peers) } - #[cfg(not(target_os = "windows"))] fn configure_interface( &self, config: &defguard_wireguard_rs::InterfaceConfiguration, @@ -66,21 +158,20 @@ impl WireguardInterfaceApi for WgApiWrapper { self.inner.configure_interface(config) } - #[cfg(target_os = "windows")] - fn configure_interface( - &self, - config: &defguard_wireguard_rs::InterfaceConfiguration, - dns: &[std::net::IpAddr], - ) -> Result<(), defguard_wireguard_rs::error::WireguardInterfaceError> { - self.inner.configure_interface(config, dns) - } - + #[cfg(not(windows))] fn remove_interface( &self, ) -> Result<(), defguard_wireguard_rs::error::WireguardInterfaceError> { self.inner.remove_interface() } + #[cfg(windows)] + fn remove_interface( + &mut self, + ) -> Result<(), defguard_wireguard_rs::error::WireguardInterfaceError> { + self.inner.remove_interface() + } + fn configure_peer( &self, peer: &Peer, @@ -106,24 +197,10 @@ impl WireguardInterfaceApi for WgApiWrapper { fn configure_dns( &self, - dns: &[std::net::IpAddr], + dns: &[IpAddr], + search_domains: &[&str], ) -> Result<(), defguard_wireguard_rs::error::WireguardInterfaceError> { - self.inner.configure_dns(dns) - } -} - -impl WgApiWrapper { - pub fn new(wg_api: WGApi) -> Self { - WgApiWrapper { inner: wg_api } - } -} - -impl Drop for WgApiWrapper { - fn drop(&mut self) { - if let Err(e) = defguard_wireguard_rs::WireguardInterfaceApi::remove_interface(&self.inner) - { - error!("Could not remove the wireguard interface: {e:?}"); - } + self.inner.configure_dns(dns, search_domains) } } @@ -181,7 +258,7 @@ pub async fn start_wireguard( use_userspace: bool, ) -> Result, Box> { use base64::{Engine, prelude::BASE64_STANDARD}; - use defguard_wireguard_rs::{InterfaceConfiguration, WireguardInterfaceApi}; + use defguard_wireguard_rs::InterfaceConfiguration; use ip_network::IpNetwork; use peer_controller::PeerController; use std::collections::HashMap; @@ -193,7 +270,7 @@ pub async fn start_wireguard( "Initializing WireGuard interface '{}' with use_userspace={}", ifname, use_userspace ); - let wg_api = defguard_wireguard_rs::WGApi::new(ifname.clone(), use_userspace)?; + let mut wg_api = WgApiWrapper::new(&ifname, use_userspace)?; let mut peer_bandwidth_managers = HashMap::with_capacity(peers.len()); for peer in peers.iter() { @@ -214,14 +291,22 @@ pub async fn start_wireguard( let interface_config = InterfaceConfiguration { name: ifname.clone(), prvkey: BASE64_STANDARD.encode(wireguard_data.inner.keypair().private_key().to_bytes()), - address: wireguard_data.inner.config().private_ipv4.to_string(), - port: wireguard_data.inner.config().announced_tunnel_port as u32, + addresses: vec![IpAddrMask::host(IpAddr::from( + wireguard_data.inner.config().private_ipv4, + ))], + port: wireguard_data.inner.config().announced_tunnel_port, peers: peers.clone(), // Clone since we need to use peers later to mark IPs as used mtu: None, }; info!( - "attempting to configure wireguard interface '{ifname}': address={}, port={}", - interface_config.address, interface_config.port + "attempting to configure wireguard interface '{ifname}': addresses=[{}], port={}", + interface_config + .addresses + .iter() + .map(|s| s.to_string()) + .collect::>() + .join(", "), + interface_config.port ); info!("Configuring WireGuard interface..."); @@ -263,7 +348,6 @@ pub async fn start_wireguard( wg_api.configure_peer_routing(&[catch_all_peer])?; let host = wg_api.read_interface_data()?; - let wg_api = std::sync::Arc::new(WgApiWrapper::new(wg_api)); // Initialize IP pool from configuration info!("Initializing IP pool for WireGuard peer allocation"); @@ -278,13 +362,13 @@ pub async fn start_wireguard( for peer in &peers { for allowed_ip in &peer.allowed_ips { // Extract IPv4 and IPv6 from peer's allowed_ips - if let IpAddr::V4(ipv4) = allowed_ip.ip { + if let IpAddr::V4(ipv4) = allowed_ip.address { // Find corresponding IPv6 if let Some(ipv6_mask) = peer .allowed_ips .iter() - .find(|ip| matches!(ip.ip, IpAddr::V6(_))) - && let IpAddr::V6(ipv6) = ipv6_mask.ip + .find(|ip| matches!(ip.address, IpAddr::V6(_))) + && let IpAddr::V6(ipv6) = ipv6_mask.address { ip_pool.mark_used(IpPair::new(ipv4, ipv6)).await; } @@ -292,6 +376,7 @@ pub async fn start_wireguard( } } + let wg_api = std::sync::Arc::new(wg_api); let mut controller = PeerController::new( ecash_manager, metrics, diff --git a/common/wireguard/src/peer_controller/mod.rs b/common/wireguard/src/peer_controller/mod.rs index 50db3677a3..7848409b75 100644 --- a/common/wireguard/src/peer_controller/mod.rs +++ b/common/wireguard/src/peer_controller/mod.rs @@ -321,7 +321,7 @@ impl PeerController { bw_manager .allowed_ips() .iter() - .find(|ip_mask| ip_mask.ip == ip) + .find(|ip_mask| ip_mask.address == ip) .and(Some(key.clone())) })) } @@ -551,7 +551,7 @@ struct MockWgApi { #[allow(clippy::todo)] impl WireguardInterfaceApi for MockWgApi { fn create_interface( - &self, + &mut self, ) -> std::result::Result<(), defguard_wireguard_rs::error::WireguardInterfaceError> { todo!() } @@ -570,7 +570,6 @@ impl WireguardInterfaceApi for MockWgApi { todo!() } - #[cfg(not(target_os = "windows"))] fn configure_interface( &self, _config: &defguard_wireguard_rs::InterfaceConfiguration, @@ -578,21 +577,20 @@ impl WireguardInterfaceApi for MockWgApi { todo!() } - #[cfg(target_os = "windows")] - fn configure_interface( - &self, - _config: &defguard_wireguard_rs::InterfaceConfiguration, - _dns: &[std::net::IpAddr], - ) -> std::result::Result<(), defguard_wireguard_rs::error::WireguardInterfaceError> { - todo!() - } - + #[cfg(not(windows))] fn remove_interface( &self, ) -> std::result::Result<(), defguard_wireguard_rs::error::WireguardInterfaceError> { todo!() } + #[cfg(windows)] + fn remove_interface( + &mut self, + ) -> std::result::Result<(), defguard_wireguard_rs::error::WireguardInterfaceError> { + todo!() + } + fn configure_peer( &self, peer: &Peer, @@ -623,6 +621,7 @@ impl WireguardInterfaceApi for MockWgApi { fn configure_dns( &self, _dns: &[std::net::IpAddr], + _search_domains: &[&str], ) -> std::result::Result<(), defguard_wireguard_rs::error::WireguardInterfaceError> { todo!() } diff --git a/gateway/src/node/internal_service_providers/authenticator/mixnet_listener.rs b/gateway/src/node/internal_service_providers/authenticator/mixnet_listener.rs index c05d9d8cd6..d5f14c70e9 100644 --- a/gateway/src/node/internal_service_providers/authenticator/mixnet_listener.rs +++ b/gateway/src/node/internal_service_providers/authenticator/mixnet_listener.rs @@ -297,7 +297,7 @@ impl MixnetListener { let allowed_ipv4 = peer .allowed_ips .iter() - .find_map(|ip_mask| match ip_mask.ip { + .find_map(|ip_mask| match ip_mask.address { IpAddr::V4(ipv4_addr) => Some(ipv4_addr), _ => None, }) @@ -307,7 +307,7 @@ impl MixnetListener { let allowed_ipv6 = peer .allowed_ips .iter() - .find_map(|ip_mask| match ip_mask.ip { + .find_map(|ip_mask| match ip_mask.address { IpAddr::V6(ipv6_addr) => Some(ipv6_addr), _ => None, }) diff --git a/gateway/src/node/lp_listener/registration.rs b/gateway/src/node/lp_listener/registration.rs index 92ae86516e..7ff41acc4a 100644 --- a/gateway/src/node/lp_listener/registration.rs +++ b/gateway/src/node/lp_listener/registration.rs @@ -155,7 +155,7 @@ async fn check_existing_registration( let mut ipv4 = None; let mut ipv6 = None; for ip_mask in &defguard_peer.allowed_ips { - match ip_mask.ip { + match ip_mask.address { std::net::IpAddr::V4(v4) => ipv4 = Some(v4), std::net::IpAddr::V6(v6) => ipv6 = Some(v6), } diff --git a/gateway/src/node/mod.rs b/gateway/src/node/mod.rs index 6b376133f7..418a12673d 100644 --- a/gateway/src/node/mod.rs +++ b/gateway/src/node/mod.rs @@ -447,7 +447,7 @@ impl GatewayTasksBuilder { .await?; continue; }; - used_private_network_ips.push(allowed_ip.ip); + used_private_network_ips.push(allowed_ip.address); all_peers.push(peer); } From 7c0babf35a051ce0717705cc13b28209ebbe8cc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Fri, 16 Jan 2026 16:37:53 +0000 Subject: [PATCH 05/51] LP: x25519/ed22519 cleanup round (#6335) * removed dependency on nymsphinx::* key types and removed needless copies of ed25519 keys * use more strongly types in ClientHelloData * explicitly use provided client's x25519 from ClientHelloData this requires adjusting LpSession constructor to take an additional key argument * allow large LpInput enum * clippy within tests * removed redundant type aliases for x25519 keys --- Cargo.lock | 4 - common/crypto/src/asymmetric/ed25519/mod.rs | 40 ++- common/crypto/src/asymmetric/x25519/mod.rs | 46 +++- common/nym-kkt/Cargo.toml | 1 - common/nym-kkt/src/encryption.rs | 50 ++-- common/nym-kkt/src/key_utils.rs | 47 ++-- common/nym-kkt/src/kkt.rs | 29 ++- common/nym-kkt/src/lib.rs | 38 ++- common/nym-lp/Cargo.toml | 4 +- common/nym-lp/src/codec.rs | 52 +--- common/nym-lp/src/keypair.rs | 200 --------------- common/nym-lp/src/kkt_orchestrator.rs | 34 +-- common/nym-lp/src/lib.rs | 48 ++-- common/nym-lp/src/message.rs | 47 ++-- common/nym-lp/src/psk.rs | 67 ++--- common/nym-lp/src/session.rs | 228 ++++++++---------- common/nym-lp/src/session_integration/mod.rs | 94 ++++---- common/nym-lp/src/session_manager.rs | 67 +++-- common/nym-lp/src/state_machine.rs | 141 +++++------ gateway/src/node/lp_listener/handler.rs | 122 ++++------ .../src/lp_client/client.rs | 14 +- .../src/lp_client/nested_session.rs | 14 +- 22 files changed, 618 insertions(+), 769 deletions(-) delete mode 100644 common/nym-lp/src/keypair.rs diff --git a/Cargo.lock b/Cargo.lock index c76ac98c4e..3674983671 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6700,7 +6700,6 @@ dependencies = [ "libcrux-sha3", "num_enum", "nym-crypto", - "nym-sphinx", "rand 0.9.2", "thiserror 2.0.17", "rand_chacha 0.9.0", @@ -6722,7 +6721,6 @@ dependencies = [ name = "nym-lp" version = "0.1.0" dependencies = [ - "ansi_term", "bs58", "bytes", "chacha20poly1305", @@ -6735,7 +6733,6 @@ dependencies = [ "nym-crypto", "nym-kkt", "nym-lp-common", - "nym-sphinx", "parking_lot", "rand 0.8.5", "rand 0.9.2", @@ -6746,7 +6743,6 @@ dependencies = [ "thiserror 2.0.17", "tls_codec", "tracing", - "utoipa", "zeroize", ] diff --git a/common/crypto/src/asymmetric/ed25519/mod.rs b/common/crypto/src/asymmetric/ed25519/mod.rs index 313fb7e5d1..9706697b67 100644 --- a/common/crypto/src/asymmetric/ed25519/mod.rs +++ b/common/crypto/src/asymmetric/ed25519/mod.rs @@ -20,6 +20,7 @@ pub use serde_helpers::*; #[cfg(feature = "sphinx")] use nym_sphinx_types::{DESTINATION_ADDRESS_LENGTH, DestinationAddressBytes}; +use crate::asymmetric::x25519; #[cfg(feature = "rand")] use rand::{CryptoRng, Rng, RngCore}; #[cfg(feature = "serde")] @@ -110,6 +111,18 @@ impl KeyPair { index: fake_index(pub_bytes), }) } + + /// Converts this Ed25519 keypair to an X25519 keypair for ECDH. + /// + /// Uses the standard ed25519→x25519 conversion via SHA-512 hash and clamping. + /// This is the same approach as libsodium's `crypto_sign_ed25519_sk_to_curve25519`. + /// + /// # Returns + /// The converted X25519 keypair + pub fn to_x25519(&self) -> x25519::KeyPair { + let private_key = self.private_key.to_x25519(); + x25519::KeyPair::from(private_key) + } } /// Reduces a byte slice into a u32 value by XOR-ing all its bytes into a 4-byte accumulator. @@ -136,6 +149,16 @@ impl From for KeyPair { } } +impl From<(PrivateKey, PublicKey)> for KeyPair { + fn from((private_key, public_key): (PrivateKey, PublicKey)) -> Self { + KeyPair { + private_key, + public_key, + index: fake_index(public_key.to_bytes().as_ref()), + } + } +} + impl PemStorableKeyPair for KeyPair { type PrivatePemKey = PrivateKey; type PublicPemKey = PublicKey; @@ -185,14 +208,25 @@ impl PublicKey { } /// Convert this public key to a byte array. + #[inline] pub fn to_bytes(self) -> [u8; PUBLIC_KEY_LENGTH] { self.0.to_bytes() } + /// View this public key as a byte array. + #[inline] + pub fn as_bytes(&self) -> &[u8; PUBLIC_KEY_LENGTH] { + self.0.as_bytes() + } + + #[inline] pub fn from_bytes(b: &[u8]) -> Result { - Ok(PublicKey(ed25519_dalek::VerifyingKey::from_bytes( - b.try_into()?, - )?)) + Self::from_byte_array(b.try_into()?) + } + + #[inline] + pub fn from_byte_array(b: &[u8; PUBLIC_KEY_LENGTH]) -> Result { + Ok(PublicKey(ed25519_dalek::VerifyingKey::from_bytes(b)?)) } pub fn to_base58_string(self) -> String { diff --git a/common/crypto/src/asymmetric/x25519/mod.rs b/common/crypto/src/asymmetric/x25519/mod.rs index c01e437ef4..e717df3d92 100644 --- a/common/crypto/src/asymmetric/x25519/mod.rs +++ b/common/crypto/src/asymmetric/x25519/mod.rs @@ -4,6 +4,7 @@ use base64::Engine; use nym_pemstore::traits::{PemStorableKey, PemStorableKeyPair}; use std::fmt::{self, Debug, Display, Formatter}; +use std::ops::Deref; use std::str::FromStr; use thiserror::Error; use zeroize::{Zeroize, ZeroizeOnDrop}; @@ -56,6 +57,15 @@ pub struct KeyPair { pub(crate) public_key: PublicKey, } +impl Debug for KeyPair { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.debug_struct("KeyPair") + .field("private_key", &"") + .field("public_key", &self.public_key.to_base58_string()) + .finish() + } +} + impl KeyPair { #[cfg(feature = "rand")] pub fn new(rng: &mut R) -> Self { @@ -93,6 +103,15 @@ impl From for KeyPair { } } +impl From<(PrivateKey, PublicKey)> for KeyPair { + fn from((private_key, public_key): (PrivateKey, PublicKey)) -> Self { + KeyPair { + private_key, + public_key, + } + } +} + impl PemStorableKeyPair for KeyPair { type PrivatePemKey = PrivateKey; type PublicPemKey = PublicKey; @@ -116,6 +135,13 @@ impl PemStorableKeyPair for KeyPair { #[derive(PartialEq, Eq, Hash, Copy, Clone)] pub struct PublicKey(x25519_dalek::PublicKey); +impl Deref for PublicKey { + type Target = x25519_dalek::PublicKey; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + impl Display for PublicKey { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { Display::fmt(&self.to_base58_string(), f) @@ -129,14 +155,17 @@ impl Debug for PublicKey { } impl PublicKey { + #[inline] pub fn to_bytes(self) -> [u8; PUBLIC_KEY_SIZE] { *self.0.as_bytes() } + #[inline] pub fn as_bytes(&self) -> &[u8; PUBLIC_KEY_SIZE] { self.0.as_bytes() } + #[inline] pub fn from_bytes(b: &[u8]) -> Result { if b.len() != PUBLIC_KEY_SIZE { return Err(KeyRecoveryError::InvalidSizePublicKey { @@ -146,7 +175,12 @@ impl PublicKey { } let mut bytes = [0; PUBLIC_KEY_SIZE]; bytes.copy_from_slice(&b[..PUBLIC_KEY_SIZE]); - Ok(Self(x25519_dalek::PublicKey::from(bytes))) + Ok(Self::from_byte_array(&bytes)) + } + + #[inline] + pub fn from_byte_array(b: &[u8; PUBLIC_KEY_SIZE]) -> Self { + Self(x25519_dalek::PublicKey::from(*b)) } pub fn to_base58_string(self) -> String { @@ -174,6 +208,12 @@ impl PublicKey { } } +impl From<[u8; PUBLIC_KEY_SIZE]> for PublicKey { + fn from(bytes: [u8; PUBLIC_KEY_SIZE]) -> Self { + PublicKey(x25519_dalek::PublicKey::from(bytes)) + } +} + impl FromStr for PublicKey { type Err = KeyRecoveryError; @@ -296,6 +336,10 @@ impl PrivateKey { Ok(Self(x25519_dalek::StaticSecret::from(bytes))) } + pub fn from_secret(secret: [u8; PRIVATE_KEY_SIZE]) -> Self { + Self(x25519_dalek::StaticSecret::from(secret)) + } + pub fn to_base58_string(&self) -> String { bs58::encode(&self.to_bytes()).into_string() } diff --git a/common/nym-kkt/Cargo.toml b/common/nym-kkt/Cargo.toml index e01fd953ae..1cabf14fb2 100644 --- a/common/nym-kkt/Cargo.toml +++ b/common/nym-kkt/Cargo.toml @@ -13,7 +13,6 @@ num_enum = { workspace = true } # internal nym-crypto = { path = "../crypto", features = ["asymmetric", "serde"] } -nym-sphinx = { path = "../nymsphinx" } libcrux-kem = { git = "https://github.com/cryspen/libcrux" } libcrux-sha3 = { git = "https://github.com/cryspen/libcrux" } diff --git a/common/nym-kkt/src/encryption.rs b/common/nym-kkt/src/encryption.rs index df6c9cd4e6..7bb7610622 100644 --- a/common/nym-kkt/src/encryption.rs +++ b/common/nym-kkt/src/encryption.rs @@ -1,25 +1,29 @@ -use blake3::Hasher; - -use libcrux_chacha20poly1305::{NONCE_LEN, TAG_LEN}; - -use nym_sphinx::{PrivateKey, PublicKey}; - -use rand::{CryptoRng, RngCore}; -use zeroize::Zeroize; +// Copyright 2025-2026 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 use crate::kkt::KKT_INITIAL_FRAME_AAD; use crate::{ ciphersuite::CURVE25519_KEY_LEN, context::KKTContext, error::KKTError, frame::KKTFrame, }; +use blake3::Hasher; +use libcrux_chacha20poly1305::{NONCE_LEN, TAG_LEN}; +use nym_crypto::asymmetric::x25519; +use rand::{CryptoRng, RngCore}; +use zeroize::Zeroize; #[derive(Clone, Copy, Zeroize)] pub struct KKTSessionSecret([u8; 32]); impl KKTSessionSecret { - pub fn new(remote_public_key: &PublicKey) -> (Self, PublicKey) { - // this doesn't use the newer rand crate - let ephemeral_private_key = PrivateKey::random(); - let ephemeral_public_key = PublicKey::from(&ephemeral_private_key); + pub fn new(rng: &mut R, remote_public_key: &x25519::PublicKey) -> (Self, x25519::PublicKey) + where + R: RngCore + CryptoRng, + { + let mut private_key_bytes = [0u8; x25519::PRIVATE_KEY_SIZE]; + rng.fill_bytes(&mut private_key_bytes); + + let ephemeral_private_key = x25519::PrivateKey::from_secret(private_key_bytes); + let ephemeral_public_key = x25519::PublicKey::from(&ephemeral_private_key); ( Self::derive(&ephemeral_private_key, remote_public_key), @@ -29,21 +33,22 @@ impl KKTSessionSecret { pub fn from_bytes(secret: [u8; 32]) -> Self { Self(secret) } - pub fn try_derive(private_key: &PrivateKey, public_key: &[u8]) -> Result { + + fn try_derive(private_key: &x25519::PrivateKey, public_key: &[u8]) -> Result { let mut pub_key: [u8; 32] = [0u8; 32]; pub_key.copy_from_slice(&public_key[0..CURVE25519_KEY_LEN]); // Todo: check validity of pk... - let pk = PublicKey::from(pub_key); + let pk = x25519::PublicKey::from(pub_key); Ok(Self::derive(private_key, &pk)) } - pub fn derive(private_key: &PrivateKey, public_key: &PublicKey) -> Self { + pub fn derive(private_key: &x25519::PrivateKey, public_key: &x25519::PublicKey) -> Self { let mut shared_secret = private_key.diffie_hellman(public_key); let mut hasher = Hasher::new(); - hasher.update(shared_secret.as_bytes()); + hasher.update(&shared_secret); shared_secret.zeroize(); Self(hasher.finalize().as_bytes().to_owned()) @@ -55,13 +60,13 @@ impl KKTSessionSecret { pub fn encrypt_initial_kkt_frame( rng: &mut R, - remote_public_key: &PublicKey, + remote_public_key: &x25519::PublicKey, kkt_frame: &KKTFrame, ) -> Result<(KKTSessionSecret, Vec), KKTError> where R: CryptoRng + RngCore, { - let (session_secret_key, ephemeral_public_key) = KKTSessionSecret::new(remote_public_key); + let (session_secret_key, ephemeral_public_key) = KKTSessionSecret::new(rng, remote_public_key); let mut encrypted_frame = encrypt_kkt_frame(rng, &session_secret_key, kkt_frame, KKT_INITIAL_FRAME_AAD)?; @@ -76,7 +81,7 @@ where } pub fn decrypt_initial_kkt_frame( - responder_private_key: &PrivateKey, + responder_private_key: &x25519::PrivateKey, encrypted_frame_bytes: &[u8], ) -> Result<(KKTSessionSecret, KKTFrame, KKTContext), KKTError> { if encrypted_frame_bytes.len() < CURVE25519_KEY_LEN + TAG_LEN + NONCE_LEN { @@ -185,13 +190,14 @@ mod test { #[test] fn test_keygen() { - let responder_x25519_keypair = generate_keypair_x25519(); + let mut rng = rng(); + let responder_x25519_keypair = generate_keypair_x25519(&mut rng); let (session_secret_key, ephemeral_public_key) = - KKTSessionSecret::new(&responder_x25519_keypair.1); + KKTSessionSecret::new(&mut rng, responder_x25519_keypair.public_key()); let shared_secret = KKTSessionSecret::try_derive( - &responder_x25519_keypair.0, + responder_x25519_keypair.private_key(), ephemeral_public_key.as_bytes().as_slice(), ) .unwrap(); diff --git a/common/nym-kkt/src/key_utils.rs b/common/nym-kkt/src/key_utils.rs index 302f7a125f..2845e7a907 100644 --- a/common/nym-kkt/src/key_utils.rs +++ b/common/nym-kkt/src/key_utils.rs @@ -1,42 +1,53 @@ -use crate::{ - ciphersuite::{HashFunction, KEM}, - error::KKTError, -}; +use crate::ciphersuite::HashFunction; use classic_mceliece_rust::keypair_boxed; -use libcrux_kem::{Algorithm, key_gen}; use libcrux_sha3; -use nym_crypto::asymmetric::ed25519; use rand::{CryptoRng, RngCore}; -pub fn generate_keypair_ed25519(rng: &mut R, index: Option) -> ed25519::KeyPair + +pub fn generate_keypair_ed25519( + rng: &mut R, + index: Option, +) -> nym_crypto::asymmetric::ed25519::KeyPair where R: RngCore + CryptoRng, { let mut secret_initiator: [u8; 32] = [0u8; 32]; rng.fill_bytes(&mut secret_initiator); - ed25519::KeyPair::from_secret(secret_initiator, index.unwrap_or(0)) + nym_crypto::asymmetric::ed25519::KeyPair::from_secret(secret_initiator, index.unwrap_or(0)) } -pub fn generate_keypair_x25519() -> (nym_sphinx::PrivateKey, nym_sphinx::PublicKey) { - let private_key = nym_sphinx::PrivateKey::random(); - let public_key = nym_sphinx::PublicKey::from(&private_key); - (private_key, public_key) +pub fn generate_keypair_x25519(rng: &mut R) -> nym_crypto::asymmetric::x25519::KeyPair +where + R: RngCore + CryptoRng, +{ + let mut secret_initiator: [u8; 32] = [0u8; 32]; + rng.fill_bytes(&mut secret_initiator); + + let private_key = nym_crypto::asymmetric::x25519::PrivateKey::from_secret(secret_initiator); + private_key.into() } // (decapsulation_key, encapsulation_key) pub fn generate_keypair_libcrux( rng: &mut R, - kem: KEM, -) -> Result<(libcrux_kem::PrivateKey, libcrux_kem::PublicKey), KKTError> + kem: crate::ciphersuite::KEM, +) -> Result<(libcrux_kem::PrivateKey, libcrux_kem::PublicKey), crate::error::KKTError> where R: RngCore + CryptoRng, { match kem { - KEM::MlKem768 => Ok(key_gen(Algorithm::MlKem768, rng)?), - KEM::XWing => Ok(key_gen(Algorithm::XWingKemDraft06, rng)?), - KEM::X25519 => Ok(key_gen(Algorithm::X25519, rng)?), - _ => Err(KKTError::KEMError { + crate::ciphersuite::KEM::MlKem768 => { + Ok(libcrux_kem::key_gen(libcrux_kem::Algorithm::MlKem768, rng)?) + } + crate::ciphersuite::KEM::XWing => Ok(libcrux_kem::key_gen( + libcrux_kem::Algorithm::XWingKemDraft06, + rng, + )?), + crate::ciphersuite::KEM::X25519 => { + Ok(libcrux_kem::key_gen(libcrux_kem::Algorithm::X25519, rng)?) + } + _ => Err(crate::error::KKTError::KEMError { info: "Key Generation Error: Unsupported Libcrux Algorithm", }), } diff --git a/common/nym-kkt/src/kkt.rs b/common/nym-kkt/src/kkt.rs index f2fba1ace5..c83454a52a 100644 --- a/common/nym-kkt/src/kkt.rs +++ b/common/nym-kkt/src/kkt.rs @@ -8,7 +8,7 @@ //! //! The underlying KKT protocol is implemented in the `session` module. -use nym_crypto::asymmetric::ed25519; +use nym_crypto::asymmetric::{ed25519, x25519}; use rand::{CryptoRng, RngCore}; use crate::{ @@ -60,7 +60,7 @@ pub fn request_kem_key( rng: &mut R, ciphersuite: Ciphersuite, signing_key: &ed25519::PrivateKey, - responder_dh_public_key: &nym_sphinx::PublicKey, + responder_dh_public_key: &x25519::PublicKey, ) -> Result<(KKTSessionSecret, KKTContext, Vec), KKTError> { // OneWay mode: client only wants responder's KEM key // None: client doesn't send their own KEM key @@ -161,7 +161,7 @@ pub fn handle_kem_request<'a, R>( encrypted_request_bytes: &[u8], initiator_vk: Option<&ed25519::PublicKey>, responder_signing_key: &ed25519::PrivateKey, - responder_dh_private_key: &nym_sphinx::PrivateKey, + responder_dh_private_key: &x25519::PrivateKey, responder_kem_key: &EncapsulationKey<'a>, ) -> Result, KKTError> where @@ -200,6 +200,13 @@ mod tests { key_utils::{generate_keypair_libcrux, hash_encapsulation_key}, }; + fn random_x25519_key() -> x25519::PrivateKey { + let mut bytes = [0u8; 32]; + let mut rng = rand::rng(); + rng.fill_bytes(&mut bytes); + x25519::PrivateKey::from_secret(bytes) + } + #[test] fn test_kkt_wrappers_oneway_authenticated() { let mut rng = rand::rng(); @@ -213,8 +220,8 @@ mod tests { rng.fill_bytes(&mut responder_secret); let ed25519_resp = ed25519::KeyPair::from_secret(responder_secret, 1); - let x25519_resp_priv = nym_sphinx::PrivateKey::random(); - let x25519_resp_pub = nym_sphinx::PublicKey::from(&x25519_resp_priv); + let x25519_resp_priv = random_x25519_key(); + let x25519_resp_pub = x25519::PublicKey::from(&x25519_resp_priv); // Generate responder's KEM keypair (X25519 for testing) let (_, responder_kem_pk) = generate_keypair_libcrux(&mut rng, KEM::X25519).unwrap(); @@ -282,8 +289,8 @@ mod tests { let (_, responder_kem_pk) = generate_keypair_libcrux(&mut rng, KEM::X25519).unwrap(); let responder_kem_key = EncapsulationKey::X25519(responder_kem_pk); - let x25519_resp_priv = nym_sphinx::PrivateKey::random(); - let x25519_resp_pub = nym_sphinx::PublicKey::from(&x25519_resp_priv); + let x25519_resp_priv = random_x25519_key(); + let x25519_resp_pub = x25519::PublicKey::from(&x25519_resp_priv); let ciphersuite = Ciphersuite::resolve_ciphersuite( KEM::X25519, @@ -343,8 +350,8 @@ mod tests { rng.fill_bytes(&mut responder_secret); let responder_keypair = ed25519::KeyPair::from_secret(responder_secret, 1); - let x25519_resp_priv = nym_sphinx::PrivateKey::random(); - let x25519_resp_pub = nym_sphinx::PublicKey::from(&x25519_resp_priv); + let x25519_resp_priv = random_x25519_key(); + let x25519_resp_pub = x25519::PublicKey::from(&x25519_resp_priv); // Different keypair for wrong signature let mut wrong_secret = [0u8; 32]; @@ -396,8 +403,8 @@ mod tests { rng.fill_bytes(&mut responder_secret); let responder_keypair = ed25519::KeyPair::from_secret(responder_secret, 1); - let x25519_resp_priv = nym_sphinx::PrivateKey::random(); - let x25519_resp_pub = nym_sphinx::PublicKey::from(&x25519_resp_priv); + let x25519_resp_priv = random_x25519_key(); + let x25519_resp_pub = x25519::PublicKey::from(&x25519_resp_priv); let (_, responder_kem_pk) = generate_keypair_libcrux(&mut rng, KEM::X25519).unwrap(); let responder_kem_key = EncapsulationKey::X25519(responder_kem_pk); diff --git a/common/nym-kkt/src/lib.rs b/common/nym-kkt/src/lib.rs index ee8e336c60..bdfd643cb7 100644 --- a/common/nym-kkt/src/lib.rs +++ b/common/nym-kkt/src/lib.rs @@ -246,7 +246,7 @@ mod test { let responder_ed25519_keypair = generate_keypair_ed25519(&mut rng, Some(1)); // generate responder x25519 keys - let responder_x25519_keypair = generate_keypair_x25519(); + let responder_x25519_keypair = generate_keypair_x25519(&mut rng); for kem in [KEM::MlKem768, KEM::XWing, KEM::X25519, KEM::McEliece] { for hash_function in [ @@ -315,14 +315,18 @@ mod test { // encryption - initiator frame - let (i_session_secret, i_bytes) = - encrypt_initial_kkt_frame(&mut rng, &responder_x25519_keypair.1, &i_frame) - .unwrap(); + let (i_session_secret, i_bytes) = encrypt_initial_kkt_frame( + &mut rng, + responder_x25519_keypair.public_key(), + &i_frame, + ) + .unwrap(); // decryption - initiator frame let (r_session_secret, i_frame_r, i_context_r) = - decrypt_initial_kkt_frame(&responder_x25519_keypair.0, &i_bytes).unwrap(); + decrypt_initial_kkt_frame(responder_x25519_keypair.private_key(), &i_bytes) + .unwrap(); let (mut r_context, _) = responder_ingest_message(&i_context_r, None, None, &i_frame_r).unwrap(); @@ -369,14 +373,18 @@ mod test { // encryption - initiator frame - let (i_session_secret, i_bytes) = - encrypt_initial_kkt_frame(&mut rng, &responder_x25519_keypair.1, &i_frame) - .unwrap(); + let (i_session_secret, i_bytes) = encrypt_initial_kkt_frame( + &mut rng, + responder_x25519_keypair.public_key(), + &i_frame, + ) + .unwrap(); // decryption - initiator frame let (r_session_secret, i_frame_r, r_context) = - decrypt_initial_kkt_frame(&responder_x25519_keypair.0, &i_bytes).unwrap(); + decrypt_initial_kkt_frame(responder_x25519_keypair.private_key(), &i_bytes) + .unwrap(); let (mut r_context, r_obtained_key) = responder_ingest_message( &r_context, @@ -431,14 +439,18 @@ mod test { // encryption - initiator frame - let (i_session_secret, i_bytes) = - encrypt_initial_kkt_frame(&mut rng, &responder_x25519_keypair.1, &i_frame) - .unwrap(); + let (i_session_secret, i_bytes) = encrypt_initial_kkt_frame( + &mut rng, + responder_x25519_keypair.public_key(), + &i_frame, + ) + .unwrap(); // decryption - initiator frame let (r_session_secret, i_frame_r, i_context_r) = - decrypt_initial_kkt_frame(&responder_x25519_keypair.0, &i_bytes).unwrap(); + decrypt_initial_kkt_frame(responder_x25519_keypair.private_key(), &i_bytes) + .unwrap(); let (mut r_context, r_obtained_key) = responder_ingest_message( &i_context_r, diff --git a/common/nym-lp/Cargo.toml b/common/nym-lp/Cargo.toml index c7ea8fc19a..08bdef92f2 100644 --- a/common/nym-lp/Cargo.toml +++ b/common/nym-lp/Cargo.toml @@ -13,9 +13,7 @@ serde = { workspace = true } bytes = { workspace = true } dashmap = { workspace = true } sha2 = { workspace = true } -ansi_term = { workspace = true } tracing = { workspace = true } -utoipa = { workspace = true, features = ["macros", "non_strict_integers"] } rand = { workspace = true } # rand 0.9 for KKT integration (nym-kkt uses rand 0.9) rand09 = { package = "rand", version = "0.9.2" } @@ -23,7 +21,6 @@ rand09 = { package = "rand", version = "0.9.2" } nym-crypto = { path = "../crypto", features = ["hashing", "asymmetric"] } nym-kkt = { path = "../nym-kkt" } nym-lp-common = { path = "../nym-lp-common" } -nym-sphinx = { path = "../nymsphinx" } # libcrux dependencies for PSQ (Post-Quantum PSK derivation) libcrux-psq = { git = "https://github.com/cryspen/libcrux", features = [ @@ -39,6 +36,7 @@ zeroize = { workspace = true, features = ["zeroize_derive"] } [dev-dependencies] criterion = { version = "0.5", features = ["html_reports"] } rand_chacha = "0.3" +nym-crypto = { path = "../crypto", features = ["rand"] } [[bench]] diff --git a/common/nym-lp/src/codec.rs b/common/nym-lp/src/codec.rs index 8d6e0ccdd9..2067bb37a5 100644 --- a/common/nym-lp/src/codec.rs +++ b/common/nym-lp/src/codec.rs @@ -321,6 +321,8 @@ mod tests { use crate::message::{EncryptedDataPayload, HandshakeData, LpMessage, MessageType}; use crate::packet::{LpHeader, LpPacket, TRAILER_LEN}; use bytes::BytesMut; + use nym_crypto::asymmetric::{ed25519, x25519}; + use rand::thread_rng; // With unified format, outer header (receiver_idx + counter) is always first // and is the only cleartext portion for encrypted packets @@ -603,11 +605,13 @@ mod tests { fn test_serialize_parse_client_hello() { use crate::message::ClientHelloData; + let mut rng = thread_rng(); + let valid_ed25519 = ed25519::KeyPair::new(&mut rng); let mut dst = BytesMut::new(); // Create ClientHelloData - let client_key = [42u8; 32]; - let client_ed25519_key = [43u8; 32]; + let client_key = x25519::PublicKey::from_byte_array(&[42u8; 32]); + let client_ed25519_key = *valid_ed25519.public_key(); let salt = [99u8; 32]; let hello_data = ClientHelloData { receiver_index: 12345, @@ -661,8 +665,11 @@ mod tests { .as_secs(); // Create ClientHelloData with fresh salt - let client_key = [7u8; 32]; - let client_ed25519_key = [8u8; 32]; + let mut rng = thread_rng(); + let valid_ed25519 = ed25519::KeyPair::new(&mut rng); + + let client_key = x25519::PublicKey::from_byte_array(&[7u8; 32]); + let client_ed25519_key = *valid_ed25519.public_key(); let hello_data = ClientHelloData::new_with_fresh_salt(client_key, client_ed25519_key, timestamp); @@ -749,43 +756,6 @@ mod tests { } } - #[test] - fn test_client_hello_different_protocol_versions() { - use crate::message::ClientHelloData; - - for version in [0u8, 1, 2, 255] { - let mut dst = BytesMut::new(); - - let hello_data = ClientHelloData { - receiver_index: version as u32, - client_lp_public_key: [version; 32], - client_ed25519_public_key: [version.wrapping_add(2); 32], - salt: [version.wrapping_add(1); 32], - }; - - let packet = LpPacket { - header: LpHeader { - protocol_version: 1, - reserved: [0u8; 3], - receiver_idx: version as u32, - counter: version as u64, - }, - message: LpMessage::ClientHello(hello_data.clone()), - trailer: [version; TRAILER_LEN], - }; - - serialize_lp_packet(&packet, &mut dst, None).unwrap(); - let decoded = parse_lp_packet(&dst, None).unwrap(); - - match decoded.message { - LpMessage::ClientHello(decoded_data) => { - assert_eq!(decoded_data.client_lp_public_key, [version; 32]); - } - _ => panic!("Expected ClientHello message for version {}", version), - } - } - } - #[test] fn test_forward_packet_encode_decode_roundtrip() { let mut dst = BytesMut::new(); diff --git a/common/nym-lp/src/keypair.rs b/common/nym-lp/src/keypair.rs deleted file mode 100644 index 6f9546ba52..0000000000 --- a/common/nym-lp/src/keypair.rs +++ /dev/null @@ -1,200 +0,0 @@ -use std::fmt::{self, Display, Formatter}; -use std::ops::Deref; -use std::str::FromStr; - -use nym_sphinx::{PrivateKey as SphinxPrivateKey, PublicKey as SphinxPublicKey}; -use serde::Serialize; -use utoipa::ToSchema; - -use crate::LpError; - -#[derive(Clone)] -pub struct PrivateKey(SphinxPrivateKey); - -impl fmt::Debug for PrivateKey { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - f.debug_tuple("PrivateKey").field(&"[REDACTED]").finish() - } -} - -impl Deref for PrivateKey { - type Target = SphinxPrivateKey; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl Default for PrivateKey { - fn default() -> Self { - Self::new() - } -} - -impl PrivateKey { - pub fn new() -> Self { - let private_key = SphinxPrivateKey::random(); - Self(private_key) - } - - pub fn to_base58_string(&self) -> String { - bs58::encode(self.0.to_bytes()).into_string() - } - - pub fn from_base58_string(s: &str) -> Result { - let bytes: [u8; 32] = bs58::decode(s).into_vec()?.try_into().unwrap(); - Ok(PrivateKey(SphinxPrivateKey::from(bytes))) - } - - pub fn from_bytes(bytes: &[u8; 32]) -> Self { - PrivateKey(SphinxPrivateKey::from(*bytes)) - } - - pub fn public_key(&self) -> PublicKey { - let public_key = SphinxPublicKey::from(&self.0); - PublicKey(public_key) - } -} - -#[derive(Clone)] -pub struct PublicKey(SphinxPublicKey); - -impl fmt::Debug for PublicKey { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - f.debug_tuple("PublicKey") - .field(&self.to_base58_string()) - .finish() - } -} - -impl Deref for PublicKey { - type Target = SphinxPublicKey; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl PublicKey { - pub fn to_base58_string(&self) -> String { - bs58::encode(self.0.as_bytes()).into_string() - } - - pub fn from_base58_string(s: &str) -> Result { - let bytes: [u8; 32] = bs58::decode(s).into_vec()?.try_into().unwrap(); - Ok(PublicKey(SphinxPublicKey::from(bytes))) - } - - pub fn from_bytes(bytes: &[u8; 32]) -> Result { - Ok(PublicKey(SphinxPublicKey::from(*bytes))) - } - - pub fn as_bytes(&self) -> &[u8; 32] { - self.0.as_bytes() - } -} - -impl Default for PublicKey { - fn default() -> Self { - let private_key = PrivateKey::default(); - private_key.public_key() - } -} - -pub struct Keypair { - private_key: PrivateKey, - public_key: PublicKey, -} - -impl Default for Keypair { - fn default() -> Self { - Self::new() - } -} - -impl Keypair { - pub fn new() -> Self { - let private_key = PrivateKey::default(); - let public_key = private_key.public_key(); - Self { - private_key, - public_key, - } - } - - pub fn from_private_key(private_key: PrivateKey) -> Self { - let public_key = private_key.public_key(); - Self { - private_key, - public_key, - } - } - - pub fn from_keys(private_key: PrivateKey, public_key: PublicKey) -> Self { - Self { - private_key, - public_key, - } - } - - pub fn private_key(&self) -> &PrivateKey { - &self.private_key - } - - pub fn public_key(&self) -> &PublicKey { - &self.public_key - } -} - -impl From for Keypair { - fn from(keypair: KeypairReadable) -> Self { - Self { - private_key: PrivateKey::from_base58_string(&keypair.private).unwrap(), - public_key: PublicKey::from_base58_string(&keypair.public).unwrap(), - } - } -} - -impl From<&Keypair> for KeypairReadable { - fn from(keypair: &Keypair) -> Self { - Self { - private: keypair.private_key.to_base58_string(), - public: keypair.public_key.to_base58_string(), - } - } -} -impl FromStr for PrivateKey { - type Err = LpError; - - fn from_str(s: &str) -> Result { - PrivateKey::from_base58_string(s) - } -} - -impl Display for PrivateKey { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.to_base58_string()) - } -} - -impl Display for PublicKey { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.to_base58_string()) - } -} - -#[derive(Serialize, serde::Deserialize, Clone, ToSchema, Debug)] -pub struct KeypairReadable { - private: String, - public: String, -} - -impl KeypairReadable { - pub fn private_key(&self) -> Result { - PrivateKey::from_base58_string(&self.private) - } - - pub fn public_key(&self) -> Result { - PublicKey::from_base58_string(&self.public) - } -} diff --git a/common/nym-lp/src/kkt_orchestrator.rs b/common/nym-lp/src/kkt_orchestrator.rs index efb9e2e9d4..f07168a933 100644 --- a/common/nym-lp/src/kkt_orchestrator.rs +++ b/common/nym-lp/src/kkt_orchestrator.rs @@ -64,7 +64,7 @@ use crate::LpError; use crate::message::{KKTRequestData, KKTResponseData}; -use nym_crypto::asymmetric::ed25519; +use nym_crypto::asymmetric::{ed25519, x25519}; use nym_kkt::ciphersuite::{Ciphersuite, EncapsulationKey}; use nym_kkt::context::KKTContext; use nym_kkt::encryption::KKTSessionSecret; @@ -90,7 +90,7 @@ use nym_kkt::kkt::{handle_kem_request, request_kem_key, validate_kem_response}; pub fn create_request( ciphersuite: Ciphersuite, signing_key: &ed25519::PrivateKey, - responder_dh_public_key: &nym_sphinx::PublicKey, + responder_dh_public_key: &x25519::PublicKey, ) -> Result<(KKTSessionSecret, KKTContext, KKTRequestData), LpError> { // Note: Uses rand 0.9's thread_rng() to match nym-kkt's rand version let mut rng = rand09::rng(); @@ -163,7 +163,7 @@ pub fn handle_request<'a>( request_data: &KKTRequestData, initiator_vk: Option<&ed25519::PublicKey>, responder_signing_key: &ed25519::PrivateKey, - responder_dh_private_key: &nym_sphinx::PrivateKey, + responder_dh_private_key: &x25519::PrivateKey, responder_kem_key: &EncapsulationKey<'a>, ) -> Result { let mut rng = rand09::rng(); @@ -199,7 +199,7 @@ mod tests { let initiator_ed25519_keypair = generate_keypair_ed25519(&mut rng, Some(0)); let responder_ed25519_keypair = generate_keypair_ed25519(&mut rng, Some(1)); - let (responder_x25519_sk, responder_x25519_pk) = generate_keypair_x25519(); + let responder_x25519 = generate_keypair_x25519(&mut rng); // Generate responder's KEM keypair (X25519 for testing) let (_, responder_kem_pk) = generate_keypair_libcrux(&mut rng, KEM::X25519).unwrap(); @@ -225,7 +225,7 @@ mod tests { let (session_secret, context, request_data) = create_request( ciphersuite, initiator_ed25519_keypair.private_key(), - &responder_x25519_pk, + responder_x25519.public_key(), ) .unwrap(); @@ -234,7 +234,7 @@ mod tests { &request_data, Some(initiator_ed25519_keypair.public_key()), responder_ed25519_keypair.private_key(), - &responder_x25519_sk, + responder_x25519.private_key(), &responder_kem_key, ) .unwrap(); @@ -262,7 +262,7 @@ mod tests { // let responder_ed25519_keypair = generate_keypair_ed25519(&mut rng, Some(1)); - // let (responder_x25519_sk, responder_x25519_pk) = generate_keypair_x25519(); + // let responder_x25519 = generate_keypair_x25519(&mut rng); // let (_, responder_kem_pk) = generate_keypair_libcrux(&mut rng, KEM::X25519).unwrap(); // let responder_kem_key = EncapsulationKey::X25519(responder_kem_pk); @@ -317,7 +317,7 @@ mod tests { let initiator_ed25519_keypair = generate_keypair_ed25519(&mut rng, Some(0)); let responder_ed25519_keypair = generate_keypair_ed25519(&mut rng, Some(1)); - let (responder_x25519_sk, responder_x25519_pk) = generate_keypair_x25519(); + let responder_x25519 = generate_keypair_x25519(&mut rng); // Different keypair for wrong signature let mut wrong_secret = [0u8; 32]; @@ -338,7 +338,7 @@ mod tests { let (_session_secret, _context, request_data) = create_request( ciphersuite, initiator_ed25519_keypair.private_key(), - &responder_x25519_pk, + responder_x25519.public_key(), ) .unwrap(); @@ -347,7 +347,7 @@ mod tests { &request_data, Some(wrong_keypair.public_key()), // Wrong key! responder_ed25519_keypair.private_key(), - &responder_x25519_sk, + responder_x25519.private_key(), &responder_kem_key, ); @@ -368,7 +368,7 @@ mod tests { let initiator_ed25519_keypair = generate_keypair_ed25519(&mut rng, Some(0)); let responder_ed25519_keypair = generate_keypair_ed25519(&mut rng, Some(1)); - let (responder_x25519_sk, responder_x25519_pk) = generate_keypair_x25519(); + let responder_x25519 = generate_keypair_x25519(&mut rng); let (_, responder_kem_pk) = generate_keypair_libcrux(&mut rng, KEM::X25519).unwrap(); let responder_kem_key = EncapsulationKey::X25519(responder_kem_pk); @@ -387,7 +387,7 @@ mod tests { let (session_secret, context, request_data) = create_request( ciphersuite, initiator_ed25519_keypair.private_key(), - &responder_x25519_pk, + responder_x25519.public_key(), ) .unwrap(); @@ -395,7 +395,7 @@ mod tests { &request_data, Some(initiator_ed25519_keypair.public_key()), responder_ed25519_keypair.private_key(), - &responder_x25519_sk, + responder_x25519.private_key(), &responder_kem_key, ) .unwrap(); @@ -426,7 +426,7 @@ mod tests { rng.fill_bytes(&mut responder_secret); let responder_ed25519_keypair = generate_keypair_ed25519(&mut rng, Some(1)); - let (responder_x25519_sk, _responder_x25519_pk) = generate_keypair_x25519(); + let responder_x25519 = generate_keypair_x25519(&mut rng); let (_, responder_kem_pk) = generate_keypair_libcrux(&mut rng, KEM::X25519).unwrap(); let responder_kem_key = EncapsulationKey::X25519(responder_kem_pk); @@ -438,7 +438,7 @@ mod tests { &malformed_request, None, responder_ed25519_keypair.private_key(), - &responder_x25519_sk, + responder_x25519.private_key(), &responder_kem_key, ); @@ -459,7 +459,7 @@ mod tests { let initiator_ed25519_keypair = generate_keypair_ed25519(&mut rng, Some(0)); let responder_ed25519_keypair = generate_keypair_ed25519(&mut rng, Some(1)); - let (_responder_x25519_sk, responder_x25519_pk) = generate_keypair_x25519(); + let responder_x25519 = generate_keypair_x25519(&mut rng); let ciphersuite = Ciphersuite::resolve_ciphersuite( KEM::X25519, @@ -472,7 +472,7 @@ mod tests { let (session_secret, context, _request_data) = create_request( ciphersuite, initiator_ed25519_keypair.private_key(), - &responder_x25519_pk, + responder_x25519.public_key(), ) .unwrap(); diff --git a/common/nym-lp/src/lib.rs b/common/nym-lp/src/lib.rs index f85570ae78..7e8eae8bd1 100644 --- a/common/nym-lp/src/lib.rs +++ b/common/nym-lp/src/lib.rs @@ -4,7 +4,6 @@ pub mod codec; pub mod config; pub mod error; -pub mod keypair; pub mod kkt_orchestrator; pub mod message; pub mod noise_protocol; @@ -30,12 +29,14 @@ pub const NOISE_PSK_INDEX: u8 = 3; #[cfg(test)] pub fn sessions_for_tests() -> (LpSession, LpSession) { - use crate::keypair::Keypair; - use nym_crypto::asymmetric::ed25519; + use nym_crypto::asymmetric::{ed25519, x25519}; + use std::sync::Arc; + + let mut rng = rand::thread_rng(); // X25519 keypairs for Noise protocol - let keypair_1 = Keypair::default(); - let keypair_2 = Keypair::default(); + let keypair_1 = Arc::new(x25519::KeyPair::new(&mut rng)); + let keypair_2 = Arc::new(x25519::KeyPair::new(&mut rng)); // Use a fixed receiver_index for deterministic tests let receiver_index: u32 = 12345; @@ -44,6 +45,8 @@ pub fn sessions_for_tests() -> (LpSession, LpSession) { let ed25519_keypair_1 = ed25519::KeyPair::from_secret([1u8; 32], 0); let ed25519_keypair_2 = ed25519::KeyPair::from_secret([2u8; 32], 1); + let ed25519_keypair1_pubkey = *ed25519_keypair_1.public_key(); + // Use consistent salt for deterministic tests let salt = [1u8; 32]; @@ -52,11 +55,8 @@ pub fn sessions_for_tests() -> (LpSession, LpSession) { let initiator_session = LpSession::new( receiver_index, true, - ( - ed25519_keypair_1.private_key(), - ed25519_keypair_1.public_key(), - ), - keypair_1.private_key(), + Arc::new(ed25519_keypair_1), + keypair_1.clone(), ed25519_keypair_2.public_key(), keypair_2.public_key(), &salt, @@ -66,12 +66,9 @@ pub fn sessions_for_tests() -> (LpSession, LpSession) { let responder_session = LpSession::new( receiver_index, false, - ( - ed25519_keypair_2.private_key(), - ed25519_keypair_2.public_key(), - ), - keypair_2.private_key(), - ed25519_keypair_1.public_key(), + Arc::new(ed25519_keypair_2), + keypair_2.clone(), + &ed25519_keypair1_pubkey, keypair_1.public_key(), &salt, ) @@ -87,6 +84,7 @@ mod tests { use crate::session_manager::SessionManager; use crate::{LpError, sessions_for_tests}; use bytes::BytesMut; + use std::sync::Arc; // Import the new standalone functions use crate::codec::{parse_lp_packet, serialize_lp_packet}; @@ -202,6 +200,10 @@ mod tests { let ed25519_keypair_local = ed25519::KeyPair::from_secret([8u8; 32], 0); let ed25519_keypair_remote = ed25519::KeyPair::from_secret([9u8; 32], 1); + let ed25519_keypair_local_pubkey = *ed25519_keypair_local.public_key(); + let x25519_keypair_local_pubkey = ed25519_keypair_local_pubkey.to_x25519().unwrap(); + let x25519_keypair_remote_pubkey = ed25519_keypair_remote.public_key().to_x25519().unwrap(); + // Use fixed receiver_index for deterministic test let receiver_index: u32 = 54321; @@ -212,11 +214,9 @@ mod tests { let _ = local_manager .create_session_state_machine( receiver_index, - ( - ed25519_keypair_local.private_key(), - ed25519_keypair_local.public_key(), - ), + Arc::new(ed25519_keypair_local), ed25519_keypair_remote.public_key(), + &x25519_keypair_remote_pubkey, true, &salt, ) @@ -225,11 +225,9 @@ mod tests { let _ = remote_manager .create_session_state_machine( receiver_index, - ( - ed25519_keypair_remote.private_key(), - ed25519_keypair_remote.public_key(), - ), - ed25519_keypair_local.public_key(), + Arc::new(ed25519_keypair_remote), + &ed25519_keypair_local_pubkey, + &x25519_keypair_local_pubkey, false, &salt, ) diff --git a/common/nym-lp/src/message.rs b/common/nym-lp/src/message.rs index 60e46d4580..578395a957 100644 --- a/common/nym-lp/src/message.rs +++ b/common/nym-lp/src/message.rs @@ -4,6 +4,7 @@ use crate::{BOOTSTRAP_RECEIVER_IDX, LpError}; use bytes::{BufMut, BytesMut}; use num_enum::{IntoPrimitive, TryFromPrimitive}; +use nym_crypto::asymmetric::{ed25519, x25519}; use serde::{Deserialize, Serialize}; use std::fmt::{self, Display}; @@ -14,9 +15,9 @@ pub struct ClientHelloData { /// Auto-generated randomly by the client pub receiver_index: u32, /// Client's LP x25519 public key (32 bytes) - derived from Ed25519 key - pub client_lp_public_key: [u8; 32], + pub client_lp_public_key: x25519::PublicKey, /// Client's Ed25519 public key (32 bytes) - for PSQ authentication - pub client_ed25519_public_key: [u8; 32], + pub client_ed25519_public_key: ed25519::PublicKey, /// Salt for PSK derivation (32 bytes: 8-byte timestamp + 24-byte nonce) pub salt: [u8; 32], } @@ -46,8 +47,8 @@ impl ClientHelloData { /// * `client_lp_public_key` - Client's x25519 public key (derived from Ed25519) /// * `client_ed25519_public_key` - Client's Ed25519 public key (for PSQ authentication) pub fn new_with_fresh_salt( - client_lp_public_key: [u8; 32], - client_ed25519_public_key: [u8; 32], + client_lp_public_key: x25519::PublicKey, + client_ed25519_public_key: ed25519::PublicKey, timestamp: u64, ) -> Self { // Generate salt: timestamp + nonce @@ -80,8 +81,8 @@ impl ClientHelloData { pub fn encode(&self, dst: &mut BytesMut) { dst.put_u32_le(self.receiver_index); - dst.put_slice(&self.client_lp_public_key); - dst.put_slice(&self.client_ed25519_public_key); + dst.put_slice(self.client_lp_public_key.as_bytes()); + dst.put_slice(self.client_ed25519_public_key.as_bytes()); dst.put_slice(&self.salt); } @@ -96,10 +97,15 @@ impl ClientHelloData { // SAFETY: we checked for valid byte lengths #[allow(clippy::unwrap_used)] + let client_lp_public_key_bytes = b[4..36].try_into().unwrap(); + let client_ed25519_public_key_bytes = b[36..68].try_into().unwrap(); + Ok(ClientHelloData { receiver_index: u32::from_le_bytes([b[0], b[1], b[2], b[3]]), - client_lp_public_key: b[4..36].try_into().unwrap(), - client_ed25519_public_key: b[36..68].try_into().unwrap(), + client_lp_public_key: x25519::PublicKey::from_byte_array(client_lp_public_key_bytes), + client_ed25519_public_key: ed25519::PublicKey::from_byte_array( + client_ed25519_public_key_bytes, + )?, salt: b[68..].try_into().unwrap(), }) } @@ -635,8 +641,13 @@ mod tests { .duration_since(UNIX_EPOCH) .expect("System time before UNIX epoch") .as_secs(); - let client_key = [1u8; 32]; - let client_ed25519_key = [2u8; 32]; + + let mut rng = rand::thread_rng(); + let ed25519 = ed25519::KeyPair::new(&mut rng); + let x25519 = ed25519.to_x25519(); + + let client_key = *x25519.public_key(); + let client_ed25519_key = *ed25519.public_key(); let hello1 = ClientHelloData::new_with_fresh_salt(client_key, client_ed25519_key, timestamp); let hello2 = @@ -657,8 +668,12 @@ mod tests { .duration_since(UNIX_EPOCH) .expect("System time before UNIX epoch") .as_secs(); - let client_key = [2u8; 32]; - let client_ed25519_key = [3u8; 32]; + let mut rng = rand::thread_rng(); + let ed25519 = ed25519::KeyPair::new(&mut rng); + let x25519 = ed25519.to_x25519(); + + let client_key = *x25519.public_key(); + let client_ed25519_key = *ed25519.public_key(); let hello = ClientHelloData::new_with_fresh_salt(client_key, client_ed25519_key, timestamp); let timestamp = hello.extract_timestamp(); @@ -677,8 +692,12 @@ mod tests { .duration_since(UNIX_EPOCH) .expect("System time before UNIX epoch") .as_secs(); - let client_key = [3u8; 32]; - let client_ed25519_key = [4u8; 32]; + let mut rng = rand::thread_rng(); + let ed25519 = ed25519::KeyPair::new(&mut rng); + let x25519 = ed25519.to_x25519(); + + let client_key = *x25519.public_key(); + let client_ed25519_key = *ed25519.public_key(); let hello = ClientHelloData::new_with_fresh_salt(client_key, client_ed25519_key, timestamp); // First 8 bytes should be non-zero timestamp diff --git a/common/nym-lp/src/psk.rs b/common/nym-lp/src/psk.rs index ea48db9415..45c352f490 100644 --- a/common/nym-lp/src/psk.rs +++ b/common/nym-lp/src/psk.rs @@ -47,12 +47,11 @@ //! - **No cleanup needed**: No state was mutated use crate::LpError; -use crate::keypair::{PrivateKey, PublicKey}; use libcrux_psq::v1::cred::{Authenticator, Ed25519}; use libcrux_psq::v1::impls::X25519 as PsqX25519; use libcrux_psq::v1::psk_registration::{Initiator, InitiatorMsg, Responder}; use libcrux_psq::v1::traits::{Ciphertext as PsqCiphertext, PSQ}; -use nym_crypto::asymmetric::ed25519; +use nym_crypto::asymmetric::{ed25519, x25519}; use nym_kkt::ciphersuite::{DecapsulationKey, EncapsulationKey}; use std::time::Duration; use tls_codec::{Deserialize as TlsDeserializeTrait, Serialize as TlsSerializeTrait}; @@ -137,8 +136,8 @@ pub struct PsqResponderResult { /// // Send ciphertext to gateway /// ``` pub fn derive_psk_with_psq_initiator( - local_x25519_private: &PrivateKey, - remote_x25519_public: &PublicKey, + local_x25519_private: &x25519::PrivateKey, + remote_x25519_public: &x25519::PublicKey, remote_kem_public: &EncapsulationKey, salt: &[u8; 32], ) -> Result<([u8; 32], Vec), LpError> { @@ -168,7 +167,7 @@ pub fn derive_psk_with_psq_initiator( // Step 3: Combine ECDH + PSQ via Blake3 KDF let mut combined = Vec::with_capacity(64 + psq_psk.len()); - combined.extend_from_slice(ecdh_secret.as_bytes()); + combined.extend_from_slice(&ecdh_secret); combined.extend_from_slice(&psq_psk); // psq_psk is [u8; 32], need & combined.extend_from_slice(salt); @@ -220,8 +219,8 @@ pub fn derive_psk_with_psq_initiator( /// )?; /// ``` pub fn derive_psk_with_psq_responder( - local_x25519_private: &PrivateKey, - remote_x25519_public: &PublicKey, + local_x25519_private: &x25519::PrivateKey, + remote_x25519_public: &x25519::PublicKey, local_kem_keypair: (&DecapsulationKey, &EncapsulationKey), ciphertext: &[u8], salt: &[u8; 32], @@ -249,7 +248,7 @@ pub fn derive_psk_with_psq_responder( // Step 5: Combine ECDH + PSQ via Blake3 KDF (same formula as initiator) let mut combined = Vec::with_capacity(64 + psq_psk.len()); - combined.extend_from_slice(ecdh_secret.as_bytes()); + combined.extend_from_slice(&ecdh_secret); combined.extend_from_slice(&psq_psk); // psq_psk is [u8; 32], need & combined.extend_from_slice(salt); @@ -280,8 +279,8 @@ pub fn derive_psk_with_psq_responder( /// # Returns /// `PsqInitiatorResult` containing PSK, payload, and raw PQ shared secret pub fn psq_initiator_create_message( - local_x25519_private: &PrivateKey, - remote_x25519_public: &PublicKey, + local_x25519_private: &x25519::PrivateKey, + remote_x25519_public: &x25519::PublicKey, remote_kem_public: &EncapsulationKey, client_ed25519_sk: &ed25519::PrivateKey, client_ed25519_pk: &ed25519::PublicKey, @@ -335,7 +334,7 @@ pub fn psq_initiator_create_message( // Step 3: Combine ECDH + PSQ via Blake3 KDF let mut combined = Vec::with_capacity(64 + psq_psk.len()); - combined.extend_from_slice(ecdh_secret.as_bytes()); + combined.extend_from_slice(&ecdh_secret); combined.extend_from_slice(psq_psk); // psq_psk is already a &[u8; 32] combined.extend_from_slice(salt); @@ -375,8 +374,8 @@ pub fn psq_initiator_create_message( /// # Returns /// `PsqResponderResult` containing PSK, PSK handle, and raw PQ shared secret pub fn psq_responder_process_message( - local_x25519_private: &PrivateKey, - remote_x25519_public: &PublicKey, + local_x25519_private: &x25519::PrivateKey, + remote_x25519_public: &x25519::PublicKey, local_kem_keypair: (&DecapsulationKey, &EncapsulationKey), initiator_ed25519_pk: &ed25519::PublicKey, psq_payload: &[u8], @@ -444,7 +443,7 @@ pub fn psq_responder_process_message( // Step 6: Combine ECDH + PSQ via Blake3 KDF (same formula as initiator) let mut combined = Vec::with_capacity(64 + psq_psk.len()); - combined.extend_from_slice(ecdh_secret.as_bytes()); + combined.extend_from_slice(&ecdh_secret); combined.extend_from_slice(&psq_psk); // psq_psk is [u8; 32], need & combined.extend_from_slice(salt); @@ -493,12 +492,16 @@ pub fn derive_subsession_psk(pq_shared_secret: &[u8; 32], subsession_index: u64) #[cfg(test)] mod tests { use super::*; - use crate::keypair::Keypair; + use rand::thread_rng; + + fn generate_x25519_keypair() -> x25519::KeyPair { + x25519::KeyPair::new(&mut thread_rng()) + } #[test] fn test_psk_derivation_is_symmetric() { - let keypair_1 = Keypair::default(); - let keypair_2 = Keypair::default(); + let keypair_1 = generate_x25519_keypair(); + let keypair_2 = generate_x25519_keypair(); let salt = [2u8; 32]; let mut rng = &mut rand09::rng(); @@ -533,8 +536,8 @@ mod tests { #[test] fn test_different_salts_produce_different_psks() { - let keypair_1 = Keypair::default(); - let keypair_2 = Keypair::default(); + let keypair_1 = generate_x25519_keypair(); + let keypair_2 = generate_x25519_keypair(); let salt1 = [1u8; 32]; let salt2 = [2u8; 32]; @@ -562,9 +565,9 @@ mod tests { #[test] fn test_different_keys_produce_different_psks() { - let keypair_1 = Keypair::default(); - let keypair_2 = Keypair::default(); - let keypair_3 = Keypair::default(); + let keypair_1 = generate_x25519_keypair(); + let keypair_2 = generate_x25519_keypair(); + let keypair_3 = generate_x25519_keypair(); let salt = [3u8; 32]; let mut rng = &mut rand09::rng(); @@ -601,8 +604,8 @@ mod tests { let mut rng = rand09::rng(); // Generate X25519 keypairs for Noise - let client_keypair = Keypair::default(); - let gateway_keypair = Keypair::default(); + let client_keypair = generate_x25519_keypair(); + let gateway_keypair = generate_x25519_keypair(); // Generate KEM keypair for PSQ let (kem_sk, kem_pk) = generate_keypair_libcrux(&mut rng, KEM::X25519).unwrap(); @@ -659,8 +662,8 @@ mod tests { let mut rng = rand09::rng(); // Generate X25519 keypairs for Noise - let client_keypair = Keypair::default(); - let gateway_keypair = Keypair::default(); + let client_keypair = generate_x25519_keypair(); + let gateway_keypair = generate_x25519_keypair(); // Generate KEM keypair for PSQ let (kem_sk, kem_pk) = generate_keypair_libcrux(&mut rng, KEM::X25519).unwrap(); @@ -698,8 +701,8 @@ mod tests { fn test_different_kem_keys_different_psk() { let mut rng = rand09::rng(); - let client_keypair = Keypair::default(); - let gateway_keypair = Keypair::default(); + let client_keypair = generate_x25519_keypair(); + let gateway_keypair = generate_x25519_keypair(); // Two different KEM keypairs let (_, kem_pk1) = generate_keypair_libcrux(&mut rng, KEM::X25519).unwrap(); @@ -736,8 +739,8 @@ mod tests { fn test_psq_psk_output_length() { let mut rng = rand09::rng(); - let client_keypair = Keypair::default(); - let gateway_keypair = Keypair::default(); + let client_keypair = generate_x25519_keypair(); + let gateway_keypair = generate_x25519_keypair(); let (_, kem_pk) = generate_keypair_libcrux(&mut rng, KEM::X25519).unwrap(); let enc_key = EncapsulationKey::X25519(kem_pk); @@ -759,8 +762,8 @@ mod tests { fn test_psq_different_salts_different_psks() { let mut rng = rand09::rng(); - let client_keypair = Keypair::default(); - let gateway_keypair = Keypair::default(); + let client_keypair = generate_x25519_keypair(); + let gateway_keypair = generate_x25519_keypair(); let (_, kem_pk) = generate_keypair_libcrux(&mut rng, KEM::X25519).unwrap(); let enc_key = EncapsulationKey::X25519(kem_pk); diff --git a/common/nym-lp/src/session.rs b/common/nym-lp/src/session.rs index 52e62a706a..3a26b6b3e9 100644 --- a/common/nym-lp/src/session.rs +++ b/common/nym-lp/src/session.rs @@ -7,7 +7,6 @@ //! and Noise protocol state handling. use crate::codec::OuterAeadKey; -use crate::keypair::{PrivateKey, PublicKey}; use crate::message::{EncryptedDataPayload, HandshakeData}; use crate::noise_protocol::{NoiseError, NoiseProtocol, ReadResult}; use crate::packet::LpHeader; @@ -16,12 +15,13 @@ use crate::psk::{ }; use crate::replay::ReceivingKeyCounterValidator; use crate::{LpError, LpMessage, LpPacket}; -use nym_crypto::asymmetric::ed25519; +use nym_crypto::asymmetric::{ed25519, x25519}; use nym_kkt::ciphersuite::{DecapsulationKey, EncapsulationKey}; use nym_kkt::encryption::KKTSessionSecret; use nym_kkt::kkt::decrypt_kkt_response_frame; use parking_lot::Mutex; use snow::Builder; +use std::sync::Arc; use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; use zeroize::{Zeroize, ZeroizeOnDrop}; @@ -182,20 +182,17 @@ pub struct LpSession { psk_injected: AtomicBool, // PSQ-related keys stored for handshake - /// Local Ed25519 private key for PSQ authentication - local_ed25519_private: ed25519::PrivateKey, - - /// Local Ed25519 public key for PSQ authentication - local_ed25519_public: ed25519::PublicKey, + /// Local Ed25519 keys for PSQ authentication + local_ed25519: Arc, /// Remote Ed25519 public key for PSQ authentication remote_ed25519_public: ed25519::PublicKey, - /// Local X25519 private key (Noise static key) - local_x25519_private: PrivateKey, + /// Local x25519 keys (Noise static key) + local_x25519: Arc, /// Remote X25519 public key (Noise static key) - remote_x25519_public: PublicKey, + remote_x25519_public: x25519::PublicKey, /// Salt for PSK derivation salt: [u8; 32], @@ -276,8 +273,7 @@ impl LpSession { /// Defaults to 1 (current LP version). Set during handshake via /// `set_negotiated_version()` when ClientHello/ServerHello is processed. pub fn negotiated_version(&self) -> u8 { - self.negotiated_version - .load(std::sync::atomic::Ordering::Acquire) + self.negotiated_version.load(Ordering::Acquire) } /// Sets the negotiated protocol version from handshake packet header. @@ -285,23 +281,22 @@ impl LpSession { /// Should be called during handshake when processing ClientHello (responder) /// or ServerHello (initiator) to record the agreed protocol version. pub fn set_negotiated_version(&self, version: u8) { - self.negotiated_version - .store(version, std::sync::atomic::Ordering::Release); + self.negotiated_version.store(version, Ordering::Release); } - /// Returns the local X25519 public key derived from the private key. + /// Returns the local X25519 public key. /// /// This is used for KKT protocol when the responder needs to send their /// KEM public key in the KKT response. - pub fn local_x25519_public(&self) -> PublicKey { - self.local_x25519_private.public_key() + pub fn local_x25519_public(&self) -> x25519::PublicKey { + *self.local_x25519.public_key() } /// Returns the remote X25519 public key. /// /// Used for tie-breaking in simultaneous subsession initiation. /// Lower key loses and becomes responder. - pub fn remote_x25519_public(&self) -> &PublicKey { + pub fn remote_x25519_public(&self) -> &x25519::PublicKey { &self.remote_x25519_public } @@ -356,17 +351,17 @@ impl LpSession { /// * `id` - Session identifier /// * `is_initiator` - True if this side initiates the Noise handshake. /// * `local_ed25519_keypair` - This side's Ed25519 keypair for PSQ authentication - /// * `local_x25519_key` - This side's X25519 private key for Noise protocol and DHKEM + /// * `local_x25519_keypair` - This side's X25519 keypair for Noise protocol and DHKEM /// * `remote_ed25519_key` - Peer's Ed25519 public key for PSQ authentication /// * `remote_x25519_key` - Peer's X25519 public key for Noise protocol and DHKEM /// * `salt` - Salt for PSK derivation pub fn new( id: u32, is_initiator: bool, - local_ed25519_keypair: (&ed25519::PrivateKey, &ed25519::PublicKey), - local_x25519_key: &PrivateKey, + local_ed25519_keypair: Arc, + local_x25519_keypair: Arc, remote_ed25519_key: &ed25519::PublicKey, - remote_x25519_key: &PublicKey, + remote_x25519_key: &x25519::PublicKey, salt: &[u8; 32], ) -> Result { // XKpsk3 pattern requires remote static key known upfront (XK) @@ -377,8 +372,8 @@ impl LpSession { let params = pattern_name.parse()?; let builder = Builder::new(params); - let local_key_bytes = local_x25519_key.to_bytes(); - let builder = builder.local_private_key(&local_key_bytes); + let local_key_bytes = local_x25519_keypair.private_key().as_bytes(); + let builder = builder.local_private_key(local_key_bytes); let remote_key_bytes = remote_x25519_key.to_bytes(); let builder = builder.remote_public_key(&remote_key_bytes); @@ -416,19 +411,10 @@ impl LpSession { sending_counter: AtomicU64::new(0), receiving_counter: Mutex::new(ReceivingKeyCounterValidator::default()), psk_injected: AtomicBool::new(false), - // Ed25519 keys don't impl Clone, so convert to bytes and reconstruct - local_ed25519_private: ed25519::PrivateKey::from_bytes( - &local_ed25519_keypair.0.to_bytes(), - ) - .expect("Valid ed25519 private key"), - local_ed25519_public: ed25519::PublicKey::from_bytes( - &local_ed25519_keypair.1.to_bytes(), - ) - .expect("Valid ed25519 public key"), - remote_ed25519_public: ed25519::PublicKey::from_bytes(&remote_ed25519_key.to_bytes()) - .expect("Valid ed25519 public key"), - local_x25519_private: local_x25519_key.clone(), - remote_x25519_public: remote_x25519_key.clone(), + local_ed25519: local_ed25519_keypair.clone(), + remote_ed25519_public: *remote_ed25519_key, + local_x25519: local_x25519_keypair, + remote_x25519_public: *remote_x25519_key, salt: *salt, outer_aead_key: Mutex::new(None), pq_shared_secret: Mutex::new(None), @@ -567,7 +553,7 @@ impl LpSession { match request_kem_key( &mut rng, ciphersuite, - &self.local_ed25519_private, + self.local_ed25519.private_key(), &self.remote_x25519_public, ) { Ok((session_secret, context, request_bytes)) => { @@ -704,8 +690,8 @@ impl LpSession { &mut rng, request_bytes, Some(&self.remote_ed25519_public), // Verify initiator signature - &self.local_ed25519_private, // Sign response - &self.local_x25519_private, + self.local_ed25519.private_key(), // Sign response + self.local_x25519.private_key(), responder_kem_pk, ) .map_err(|e| LpError::Internal(format!("KKT request handling failed: {:?}", e)))?; @@ -757,11 +743,11 @@ impl LpSession { let session_context = self.id.to_le_bytes(); let psq_result = match psq_initiator_create_message( - &self.local_x25519_private, + self.local_x25519.private_key(), &self.remote_x25519_public, remote_kem, - &self.local_ed25519_private, - &self.local_ed25519_public, + self.local_ed25519.private_key(), + self.local_ed25519.public_key(), &self.salt, &session_context, ) { @@ -895,7 +881,7 @@ impl LpSession { let noise_payload = &payload[2 + psq_len..]; // Convert X25519 local keys to DecapsulationKey/EncapsulationKey (DHKEM) - let local_private_bytes = &self.local_x25519_private.to_bytes(); + let local_private_bytes = &self.local_x25519.private_key().to_bytes(); let libcrux_private_key = libcrux_kem::PrivateKey::decode( libcrux_kem::Algorithm::X25519, local_private_bytes, @@ -908,7 +894,7 @@ impl LpSession { })?; let dec_key = DecapsulationKey::X25519(libcrux_private_key); - let local_public_key = self.local_x25519_private.public_key(); + let local_public_key = self.local_x25519_public(); let local_public_bytes = local_public_key.as_bytes(); let libcrux_public_key = libcrux_kem::PublicKey::decode( libcrux_kem::Algorithm::X25519, @@ -926,7 +912,7 @@ impl LpSession { let session_context = self.id.to_le_bytes(); let psq_result = match psq_responder_process_message( - &self.local_x25519_private, + self.local_x25519.private_key(), &self.remote_x25519_public, (&dec_key, &enc_key), &self.remote_ed25519_public, @@ -1132,7 +1118,7 @@ impl LpSession { /// Test-only method to set KKT state to Completed with a mock KEM key. /// This allows tests to bypass KKT exchange and directly test PSQ handshake. #[cfg(test)] - pub(crate) fn set_kkt_completed_for_test(&self, remote_x25519_pub: &PublicKey) { + pub(crate) fn set_kkt_completed_for_test(&self, remote_x25519_pub: &x25519::PublicKey) { // Convert remote X25519 public key to EncapsulationKey for testing let remote_kem_bytes = remote_x25519_pub.as_bytes(); let libcrux_public_key = @@ -1185,7 +1171,7 @@ impl LpSession { let pattern_name = "Noise_KKpsk0_25519_ChaChaPoly_SHA256"; let params = pattern_name.parse()?; - let local_key_bytes = self.local_x25519_private.to_bytes(); + let local_key_bytes = self.local_x25519.private_key().to_bytes(); let remote_key_bytes = self.remote_x25519_public.to_bytes(); let builder = Builder::new(params) @@ -1204,22 +1190,12 @@ impl LpSession { noise_state: Mutex::new(NoiseProtocol::new(handshake_state)), is_initiator, // Copy key material from parent for into_session() conversion - local_ed25519_private: ed25519::PrivateKey::from_bytes( - &self.local_ed25519_private.to_bytes(), - ) - .expect("Valid Ed25519 private key from parent"), - local_ed25519_public: ed25519::PublicKey::from_bytes( - &self.local_ed25519_public.to_bytes(), - ) - .expect("Valid Ed25519 public key from parent"), - remote_ed25519_public: ed25519::PublicKey::from_bytes( - &self.remote_ed25519_public.to_bytes(), - ) - .expect("Valid Ed25519 public key from parent"), - local_x25519_private: self.local_x25519_private.clone(), - remote_x25519_public: self.remote_x25519_public.clone(), + local_ed25519: self.local_ed25519.clone(), + remote_ed25519_public: self.remote_ed25519_public, + remote_x25519_public: self.remote_x25519_public, pq_shared_secret: PqSharedSecret::new(pq_secret), subsession_psk, + local_x25519: self.local_x25519.clone(), }) } } @@ -1249,16 +1225,17 @@ pub struct SubsessionHandshake { is_initiator: bool, // Key material inherited from parent session for into_session() conversion - /// Local Ed25519 private key (for PSQ auth if needed) - local_ed25519_private: ed25519::PrivateKey, - /// Local Ed25519 public key - local_ed25519_public: ed25519::PublicKey, + /// Local Ed25519 keys (for PSQ auth if needed) + local_ed25519: Arc, + + /// Local x25519 keys (Noise static key) + local_x25519: Arc, + /// Remote Ed25519 public key remote_ed25519_public: ed25519::PublicKey, - /// Local X25519 private key (Noise static key) - local_x25519_private: PrivateKey, + /// Remote X25519 public key (Noise static key) - remote_x25519_public: PublicKey, + remote_x25519_public: x25519::PublicKey, /// PQ shared secret inherited from parent (for creating further subsessions) pq_shared_secret: PqSharedSecret, /// Subsession PSK (for deriving outer AEAD key) @@ -1351,10 +1328,9 @@ impl SubsessionHandshake { sending_counter: AtomicU64::new(0), receiving_counter: Mutex::new(ReceivingKeyCounterValidator::new(0)), psk_injected: AtomicBool::new(true), // PSK was in KKpsk0 - local_ed25519_private: self.local_ed25519_private, - local_ed25519_public: self.local_ed25519_public, + local_ed25519: self.local_ed25519, remote_ed25519_public: self.remote_ed25519_public, - local_x25519_private: self.local_x25519_private, + local_x25519: self.local_x25519, remote_x25519_public: self.remote_x25519_public, salt, outer_aead_key: Mutex::new(Some(outer_key)), @@ -1372,18 +1348,19 @@ impl SubsessionHandshake { mod tests { use super::*; use crate::{replay::ReplayError, sessions_for_tests}; + use rand::thread_rng; // Helper function to generate keypairs for tests - fn generate_keypair() -> crate::keypair::Keypair { - crate::keypair::Keypair::default() + fn generate_keypair() -> x25519::KeyPair { + x25519::KeyPair::new(&mut thread_rng()) } // Helper function to create a session with real keys for handshake tests fn create_handshake_test_session( receiver_index: u32, is_initiator: bool, - local_keys: &crate::keypair::Keypair, - remote_pub_key: &crate::keypair::PublicKey, + local_keys: &x25519::KeyPair, + remote_pub_key: &x25519::PublicKey, ) -> LpSession { use nym_crypto::asymmetric::ed25519; @@ -1396,6 +1373,9 @@ mod tests { }; let local_ed25519 = ed25519::KeyPair::from_secret(local_ed25519_seed, 0); + let local_x25519 = x25519::PrivateKey::from_bytes(local_keys.private_key().as_bytes()) + .unwrap() + .into(); let remote_ed25519 = ed25519::KeyPair::from_secret(remote_ed25519_seed, 1); let salt = [0u8; 32]; // Test salt @@ -1404,8 +1384,8 @@ mod tests { let session = LpSession::new( receiver_index, is_initiator, - (local_ed25519.private_key(), local_ed25519.public_key()), - local_keys.private_key(), + Arc::new(local_ed25519), + Arc::new(local_x25519), remote_ed25519.public_key(), remote_pub_key, &salt, @@ -1510,8 +1490,8 @@ mod tests { #[test] fn test_prepare_handshake_message_initial_state() { - let initiator_keys = generate_keypair(); - let responder_keys = generate_keypair(); + let initiator_keys = Arc::new(generate_keypair()); + let responder_keys = Arc::new(generate_keypair()); let receiver_index = 12345u32; let initiator_session = create_handshake_test_session( @@ -1542,8 +1522,8 @@ mod tests { #[test] fn test_process_handshake_message_first_step() { - let initiator_keys = generate_keypair(); - let responder_keys = generate_keypair(); + let initiator_keys = Arc::new(generate_keypair()); + let responder_keys = Arc::new(generate_keypair()); let receiver_index = 12345u32; let initiator_session = create_handshake_test_session( @@ -1588,8 +1568,8 @@ mod tests { #[test] fn test_handshake_driver_simulation() { - let initiator_keys = generate_keypair(); - let responder_keys = generate_keypair(); + let initiator_keys = Arc::new(generate_keypair()); + let responder_keys = Arc::new(generate_keypair()); let initiator_session = create_handshake_test_session( 12345u32, @@ -1683,8 +1663,8 @@ mod tests { #[test] fn test_encrypt_decrypt_after_handshake() { // --- Setup Handshake --- - let initiator_keys = generate_keypair(); - let responder_keys = generate_keypair(); + let initiator_keys = Arc::new(generate_keypair()); + let responder_keys = Arc::new(generate_keypair()); let initiator_session = create_handshake_test_session( 12345u32, @@ -1752,8 +1732,8 @@ mod tests { #[test] fn test_encrypt_decrypt_before_handshake() { - let initiator_keys = generate_keypair(); - let responder_keys = generate_keypair(); + let initiator_keys = Arc::new(generate_keypair()); + let responder_keys = Arc::new(generate_keypair()); let initiator_session = create_handshake_test_session( 12345u32, @@ -1828,8 +1808,8 @@ mod tests { /// Test that PSQ runs during handshake and derives a PSK #[test] fn test_psq_handshake_runs_with_psk_injection() { - let initiator_keys = generate_keypair(); - let responder_keys = generate_keypair(); + let initiator_keys = Arc::new(generate_keypair()); + let responder_keys = Arc::new(generate_keypair()); let initiator_session = create_handshake_test_session( 12345u32, @@ -1905,8 +1885,8 @@ mod tests { fn test_x25519_to_kem_conversion() { use nym_kkt::ciphersuite::EncapsulationKey; - let initiator_keys = generate_keypair(); - let responder_keys = generate_keypair(); + let initiator_keys = Arc::new(generate_keypair()); + let responder_keys = Arc::new(generate_keypair()); // Verify we can convert X25519 public key to KEM format (as done in session.rs) let x25519_public_bytes = responder_keys.public_key().as_bytes(); @@ -1929,8 +1909,8 @@ mod tests { /// Test that PSQ actually derives a different PSK (not using dummy) #[test] fn test_psq_derived_psk_differs_from_dummy() { - let initiator_keys = generate_keypair(); - let responder_keys = generate_keypair(); + let initiator_keys = Arc::new(generate_keypair()); + let responder_keys = Arc::new(generate_keypair()); // Create sessions - they start with dummy PSK [0u8; 32] let initiator_session = create_handshake_test_session( @@ -2003,8 +1983,8 @@ mod tests { /// Test full end-to-end handshake with PSQ integration #[test] fn test_handshake_with_psq_end_to_end() { - let initiator_keys = generate_keypair(); - let responder_keys = generate_keypair(); + let initiator_keys = Arc::new(generate_keypair()); + let responder_keys = Arc::new(generate_keypair()); let initiator_session = create_handshake_test_session( 12345u32, @@ -2089,8 +2069,8 @@ mod tests { /// Test that Ed25519 keys are used in PSQ authentication #[test] fn test_psq_handshake_uses_ed25519_authentication() { - let initiator_keys = generate_keypair(); - let responder_keys = generate_keypair(); + let initiator_keys = Arc::new(generate_keypair()); + let responder_keys = Arc::new(generate_keypair()); // Create sessions with explicit Ed25519 keys let initiator_session = create_handshake_test_session( @@ -2172,8 +2152,8 @@ mod tests { #[test] fn test_handshake_abort_on_psq_failure() { // Test that Ed25519 auth failure causes handshake abort - let initiator_keys = generate_keypair(); - let responder_keys = generate_keypair(); + let initiator_keys = Arc::new(generate_keypair()); + let responder_keys = Arc::new(generate_keypair()); // Create sessions with MISMATCHED Ed25519 keys // This simulates authentication failure @@ -2186,11 +2166,8 @@ mod tests { let initiator_session = LpSession::new( receiver_index, true, - ( - initiator_ed25519.private_key(), - initiator_ed25519.public_key(), - ), - initiator_keys.private_key(), + Arc::new(initiator_ed25519), + initiator_keys.clone(), wrong_ed25519.public_key(), // Responder expects THIS key responder_keys.public_key(), &salt, @@ -2204,11 +2181,8 @@ mod tests { let responder_session = LpSession::new( receiver_index, false, - ( - responder_ed25519.private_key(), - responder_ed25519.public_key(), - ), - responder_keys.private_key(), + Arc::new(responder_ed25519), + responder_keys.clone(), wrong_ed25519.public_key(), // Expects WRONG key (not initiator's) initiator_keys.public_key(), &salt, @@ -2241,8 +2215,8 @@ mod tests { fn test_psq_invalid_signature() { // Test Ed25519 signature validation specifically // Setup with matching X25519 keys but mismatched Ed25519 keys - let initiator_keys = generate_keypair(); - let responder_keys = generate_keypair(); + let initiator_keys = Arc::new(generate_keypair()); + let responder_keys = Arc::new(generate_keypair()); // Initiator uses Ed25519 key [1u8] let initiator_ed25519 = ed25519::KeyPair::from_secret([1u8; 32], 0); @@ -2257,11 +2231,8 @@ mod tests { let initiator_session = LpSession::new( receiver_index, true, - ( - initiator_ed25519.private_key(), - initiator_ed25519.public_key(), - ), - initiator_keys.private_key(), + Arc::new(initiator_ed25519), + initiator_keys.clone(), wrong_ed25519_public, // This doesn't matter for initiator responder_keys.public_key(), &salt, @@ -2275,11 +2246,8 @@ mod tests { let responder_session = LpSession::new( receiver_index, false, - ( - responder_ed25519.private_key(), - responder_ed25519.public_key(), - ), - responder_keys.private_key(), + Arc::new(responder_ed25519), + responder_keys.clone(), wrong_ed25519_public, // Responder expects WRONG key initiator_keys.public_key(), &salt, @@ -2364,8 +2332,8 @@ mod tests { // This test verifies the safety mechanism that prevents transport mode operations // from running with the dummy PSK if PSQ injection fails or is skipped. - let initiator_keys = generate_keypair(); - let responder_keys = generate_keypair(); + let initiator_keys = Arc::new(generate_keypair()); + let responder_keys = Arc::new(generate_keypair()); // Create session but don't complete handshake (no PSK injection will occur) let session = create_handshake_test_session( @@ -2413,8 +2381,8 @@ mod tests { #[test] fn test_demote_sets_read_only() { - let initiator_keys = generate_keypair(); - let responder_keys = generate_keypair(); + let initiator_keys = Arc::new(generate_keypair()); + let responder_keys = Arc::new(generate_keypair()); let session = create_handshake_test_session( 12345u32, @@ -2438,8 +2406,8 @@ mod tests { #[test] fn test_encrypt_fails_after_demotion() { // --- Setup Handshake --- - let initiator_keys = generate_keypair(); - let responder_keys = generate_keypair(); + let initiator_keys = Arc::new(generate_keypair()); + let responder_keys = Arc::new(generate_keypair()); let initiator_session = create_handshake_test_session( 12345u32, @@ -2494,8 +2462,8 @@ mod tests { #[test] fn test_decrypt_works_after_demotion() { // --- Setup Handshake --- - let initiator_keys = generate_keypair(); - let responder_keys = generate_keypair(); + let initiator_keys = Arc::new(generate_keypair()); + let responder_keys = Arc::new(generate_keypair()); let initiator_session = create_handshake_test_session( 12345u32, diff --git a/common/nym-lp/src/session_integration/mod.rs b/common/nym-lp/src/session_integration/mod.rs index a3b58f2575..74f4dfdfb4 100644 --- a/common/nym-lp/src/session_integration/mod.rs +++ b/common/nym-lp/src/session_integration/mod.rs @@ -1,7 +1,6 @@ #[cfg(test)] mod tests { use crate::codec::{parse_lp_packet, serialize_lp_packet}; - use crate::keypair::PublicKey; use crate::{ LpError, message::LpMessage, @@ -9,7 +8,8 @@ mod tests { session_manager::SessionManager, }; use bytes::BytesMut; - use nym_crypto::asymmetric::ed25519; + use nym_crypto::asymmetric::{ed25519, x25519}; + use std::sync::Arc; // Function to create a test packet - similar to how it's done in codec.rs tests fn create_test_packet( @@ -53,6 +53,11 @@ mod tests { let ed25519_keypair_a = ed25519::KeyPair::from_secret([1u8; 32], 0); let ed25519_keypair_b = ed25519::KeyPair::from_secret([2u8; 32], 1); + let x25519_keypair_a = ed25519_keypair_a.to_x25519(); + let x25519_keypair_b = ed25519_keypair_b.to_x25519(); + + let ed25519_pubkey_a = *ed25519_keypair_a.public_key(); + // Derive X25519 keys from Ed25519 (needed for KKT init test) let x25519_pub_a = ed25519_keypair_a .public_key() @@ -64,9 +69,9 @@ mod tests { .expect("Failed to derive X25519 from Ed25519"); // Convert to LP keypair types - let lp_pub_a = PublicKey::from_bytes(x25519_pub_a.as_bytes()) + let lp_pub_a = x25519::PublicKey::from_bytes(x25519_pub_a.as_bytes()) .expect("Failed to create PublicKey from bytes"); - let lp_pub_b = PublicKey::from_bytes(x25519_pub_b.as_bytes()) + let lp_pub_b = x25519::PublicKey::from_bytes(x25519_pub_b.as_bytes()) .expect("Failed to create PublicKey from bytes"); // Use fixed receiver_index for deterministic test @@ -79,11 +84,9 @@ mod tests { let peer_a_sm = session_manager_1 .create_session_state_machine( receiver_index, - ( - ed25519_keypair_a.private_key(), - ed25519_keypair_a.public_key(), - ), + Arc::new(ed25519_keypair_a), ed25519_keypair_b.public_key(), + x25519_keypair_b.public_key(), true, &salt, ) @@ -92,11 +95,9 @@ mod tests { let peer_b_sm = session_manager_2 .create_session_state_machine( receiver_index, - ( - ed25519_keypair_b.private_key(), - ed25519_keypair_b.public_key(), - ), - ed25519_keypair_a.public_key(), + Arc::new(ed25519_keypair_b), + &ed25519_pubkey_a, + x25519_keypair_a.public_key(), false, &salt, ) @@ -512,6 +513,11 @@ mod tests { let ed25519_keypair_a = ed25519::KeyPair::from_secret([3u8; 32], 0); let ed25519_keypair_b = ed25519::KeyPair::from_secret([4u8; 32], 1); + let x25519_keypair_a = ed25519_keypair_a.to_x25519(); + let x25519_keypair_b = ed25519_keypair_b.to_x25519(); + + let ed25519_pubkey_a = *ed25519_keypair_a.public_key(); + // Derive X25519 keys from Ed25519 (same as state machine does internally) let x25519_pub_a = ed25519_keypair_a .public_key() @@ -523,9 +529,9 @@ mod tests { .expect("Failed to derive X25519 from Ed25519"); // Convert to LP keypair types - let lp_pub_a = PublicKey::from_bytes(x25519_pub_a.as_bytes()) + let lp_pub_a = x25519::PublicKey::from_bytes(x25519_pub_a.as_bytes()) .expect("Failed to create PublicKey from bytes"); - let lp_pub_b = PublicKey::from_bytes(x25519_pub_b.as_bytes()) + let lp_pub_b = x25519::PublicKey::from_bytes(x25519_pub_b.as_bytes()) .expect("Failed to create PublicKey from bytes"); // Use fixed receiver_index for test @@ -537,11 +543,9 @@ mod tests { let peer_a_sm = session_manager_1 .create_session_state_machine( receiver_index, - ( - ed25519_keypair_a.private_key(), - ed25519_keypair_a.public_key(), - ), + Arc::new(ed25519_keypair_a), ed25519_keypair_b.public_key(), + x25519_keypair_b.public_key(), true, &salt, ) @@ -549,11 +553,9 @@ mod tests { let peer_b_sm = session_manager_2 .create_session_state_machine( receiver_index, - ( - ed25519_keypair_b.private_key(), - ed25519_keypair_b.public_key(), - ), - ed25519_keypair_a.public_key(), + Arc::new(ed25519_keypair_b), + &ed25519_pubkey_a, + x25519_keypair_a.public_key(), false, &salt, ) @@ -720,16 +722,22 @@ mod tests { let session_manager = SessionManager::new(); // Generate Ed25519 keypair for PSQ authentication - let ed25519_keypair = ed25519::KeyPair::from_secret([5u8; 32], 0); + let ed25519_keypair_a = ed25519::KeyPair::from_secret([5u8; 32], 0); + let ed25519_keypair_b = ed25519::KeyPair::from_secret([6u8; 32], 0); + + let x25519_keypair_a = ed25519_keypair_a.to_x25519(); + let x25519_keypair_b = ed25519_keypair_b.to_x25519(); // Derive X25519 key from Ed25519 (same as state machine does internally) - let x25519_pub = ed25519_keypair + let x25519_pub = ed25519_keypair_a .public_key() .to_x25519() .expect("Failed to derive X25519 from Ed25519"); + let keypair_a = Arc::new(ed25519_keypair_a); + // Convert to LP keypair type (still needed for init_kkt_for_test below if used) - let _lp_pub = PublicKey::from_bytes(x25519_pub.as_bytes()) + let _lp_pub = x25519::PublicKey::from_bytes(x25519_pub.as_bytes()) .expect("Failed to create PublicKey from bytes"); // Use fixed receiver_index for test @@ -742,8 +750,9 @@ mod tests { let _session = session_manager .create_session_state_machine( receiver_index, - (ed25519_keypair.private_key(), ed25519_keypair.public_key()), - ed25519_keypair.public_key(), + keypair_a.clone(), + ed25519_keypair_b.public_key(), + x25519_keypair_b.public_key(), true, &salt, ) @@ -765,8 +774,9 @@ mod tests { let _temp_session = session_manager .create_session_state_machine( receiver_index_temp, - (ed25519_keypair.private_key(), ed25519_keypair.public_key()), - ed25519_keypair.public_key(), + keypair_a.clone(), + ed25519_keypair_b.public_key(), + x25519_keypair_a.public_key(), true, &salt, ) @@ -849,6 +859,12 @@ mod tests { let ed25519_keypair_a = ed25519::KeyPair::from_secret([6u8; 32], 0); let ed25519_keypair_b = ed25519::KeyPair::from_secret([7u8; 32], 1); + let x25519_keypair_a = ed25519_keypair_a.to_x25519(); + let x25519_keypair_b = ed25519_keypair_b.to_x25519(); + + let pubkey_a = *ed25519_keypair_a.public_key(); + let pubkey_b = *ed25519_keypair_b.public_key(); + // Use fixed receiver_index for test let receiver_index: u32 = 100005; @@ -860,11 +876,9 @@ mod tests { session_manager_1 .create_session_state_machine( receiver_index, - ( - ed25519_keypair_a.private_key(), - ed25519_keypair_a.public_key() - ), - ed25519_keypair_b.public_key(), + Arc::new(ed25519_keypair_a), + &pubkey_b, + x25519_keypair_b.public_key(), true, &salt, ) // Initiator @@ -874,11 +888,9 @@ mod tests { session_manager_2 .create_session_state_machine( receiver_index, - ( - ed25519_keypair_b.private_key(), - ed25519_keypair_b.public_key() - ), - ed25519_keypair_a.public_key(), + Arc::new(ed25519_keypair_b), + &pubkey_a, + x25519_keypair_a.public_key(), false, &salt, ) // Responder diff --git a/common/nym-lp/src/session_manager.rs b/common/nym-lp/src/session_manager.rs index 8eb1bbcb3d..c669a9df41 100644 --- a/common/nym-lp/src/session_manager.rs +++ b/common/nym-lp/src/session_manager.rs @@ -7,7 +7,8 @@ //! creation, retrieval, and storage of sessions. use dashmap::DashMap; -use nym_crypto::asymmetric::ed25519; +use nym_crypto::asymmetric::{ed25519, x25519}; +use std::sync::Arc; use crate::noise_protocol::ReadResult; use crate::state_machine::{LpAction, LpInput, LpState, LpStateBare}; @@ -169,8 +170,9 @@ impl SessionManager { pub fn create_session_state_machine( &self, receiver_index: u32, - local_ed25519_keypair: (&ed25519::PrivateKey, &ed25519::PublicKey), + local_ed25519_keypair: Arc, remote_ed25519_key: &ed25519::PublicKey, + remote_x25519_key: &x25519::PublicKey, is_initiator: bool, salt: &[u8; 32], ) -> Result { @@ -179,6 +181,7 @@ impl SessionManager { is_initiator, local_ed25519_keypair, remote_ed25519_key, + remote_x25519_key, salt, )?; @@ -199,7 +202,7 @@ impl SessionManager { pub fn init_kkt_for_test( &self, lp_id: u32, - remote_x25519_pub: &crate::keypair::PublicKey, + remote_x25519_pub: &x25519::PublicKey, ) -> Result<(), LpError> { self.with_state_machine(lp_id, |sm| { sm.session()?.set_kkt_completed_for_test(remote_x25519_pub); @@ -217,14 +220,19 @@ mod tests { fn test_session_manager_get() { let manager = SessionManager::new(); let ed25519_keypair = ed25519::KeyPair::from_secret([10u8; 32], 0); + let ed25519_keypair2 = ed25519::KeyPair::from_secret([16u8; 32], 0); + + let x25519_keypair2 = ed25519_keypair2.to_x25519(); + let salt = [47u8; 32]; let receiver_index: u32 = 1001; let sm_1_id = manager .create_session_state_machine( receiver_index, - (ed25519_keypair.private_key(), ed25519_keypair.public_key()), - ed25519_keypair.public_key(), + Arc::new(ed25519_keypair), + ed25519_keypair2.public_key(), + x25519_keypair2.public_key(), true, &salt, ) @@ -241,14 +249,19 @@ mod tests { fn test_session_manager_remove() { let manager = SessionManager::new(); let ed25519_keypair = ed25519::KeyPair::from_secret([11u8; 32], 0); + let ed25519_keypair2 = ed25519::KeyPair::from_secret([16u8; 32], 0); + + let x25519_keypair2 = ed25519_keypair2.to_x25519(); + let salt = [48u8; 32]; let receiver_index: u32 = 2002; let sm_1_id = manager .create_session_state_machine( receiver_index, - (ed25519_keypair.private_key(), ed25519_keypair.public_key()), - ed25519_keypair.public_key(), + Arc::new(ed25519_keypair), + ed25519_keypair2.public_key(), + x25519_keypair2.public_key(), true, &salt, ) @@ -270,14 +283,20 @@ mod tests { let ed25519_keypair_3 = ed25519::KeyPair::from_secret([14u8; 32], 2); let salt = [49u8; 32]; + let pubkey1 = *ed25519_keypair_1.public_key(); + let pubkey2 = *ed25519_keypair_2.public_key(); + let pubkey3 = *ed25519_keypair_3.public_key(); + + let xpubkey1 = *ed25519_keypair_1.to_x25519().public_key(); + let xpubkey2 = *ed25519_keypair_2.to_x25519().public_key(); + let xpubkey3 = *ed25519_keypair_3.to_x25519().public_key(); + let sm_1 = manager .create_session_state_machine( 3001, - ( - ed25519_keypair_1.private_key(), - ed25519_keypair_1.public_key(), - ), - ed25519_keypair_1.public_key(), + Arc::new(ed25519_keypair_1), + &pubkey2, + &xpubkey2, true, &salt, ) @@ -286,11 +305,9 @@ mod tests { let sm_2 = manager .create_session_state_machine( 3002, - ( - ed25519_keypair_2.private_key(), - ed25519_keypair_2.public_key(), - ), - ed25519_keypair_2.public_key(), + Arc::new(ed25519_keypair_2), + &pubkey3, + &xpubkey3, true, &salt, ) @@ -299,11 +316,9 @@ mod tests { let sm_3 = manager .create_session_state_machine( 3003, - ( - ed25519_keypair_3.private_key(), - ed25519_keypair_3.public_key(), - ), - ed25519_keypair_3.public_key(), + Arc::new(ed25519_keypair_3), + &pubkey1, + &xpubkey1, true, &salt, ) @@ -324,13 +339,17 @@ mod tests { fn test_session_manager_create_session() { let manager = SessionManager::new(); let ed25519_keypair = ed25519::KeyPair::from_secret([15u8; 32], 0); + let ed25519_keypair2 = ed25519::KeyPair::from_secret([16u8; 32], 0); let salt = [50u8; 32]; let receiver_index: u32 = 4004; + let x25519_keypair2 = ed25519_keypair2.to_x25519(); + let sm = manager.create_session_state_machine( receiver_index, - (ed25519_keypair.private_key(), ed25519_keypair.public_key()), - ed25519_keypair.public_key(), + Arc::new(ed25519_keypair), + ed25519_keypair2.public_key(), + x25519_keypair2.public_key(), true, &salt, ); diff --git a/common/nym-lp/src/state_machine.rs b/common/nym-lp/src/state_machine.rs index 37a4ee72f8..fb467d213e 100644 --- a/common/nym-lp/src/state_machine.rs +++ b/common/nym-lp/src/state_machine.rs @@ -15,15 +15,15 @@ use crate::{ LpError, - keypair::{Keypair, PrivateKey as LpPrivateKey, PublicKey as LpPublicKey}, message::{LpMessage, SubsessionKK1Data, SubsessionKK2Data, SubsessionReadyData}, noise_protocol::NoiseError, packet::LpPacket, session::{LpSession, SubsessionHandshake}, }; use bytes::BytesMut; -use nym_crypto::asymmetric::ed25519; +use nym_crypto::asymmetric::{ed25519, x25519}; use std::mem; +use std::sync::Arc; use tracing::debug; /// Represents the possible states of the Lewes Protocol connection. @@ -90,6 +90,7 @@ impl From<&LpState> for LpStateBare { } /// Represents inputs that drive the state machine transitions. +#[allow(clippy::large_enum_variant)] #[derive(Debug)] pub enum LpInput { /// Explicitly trigger the start of the handshake (optional, could be implicit on creation) @@ -189,7 +190,8 @@ impl LpStateMachine { /// * `is_initiator` - Whether this side initiates the handshake /// * `local_ed25519_keypair` - Ed25519 keypair for PSQ authentication and X25519 derivation /// (from client identity key or gateway signing key) - /// * `remote_ed25519_key` - Peer's Ed25519 public key for PSQ authentication and X25519 derivation + /// * `remote_ed25519_key` - Peer's Ed25519 public key for PSQ authentication + /// * `remote_x25519_key` - Peer's x25519 public key for Noise protocol and DHKEM /// * `salt` - Fresh salt for PSK derivation (must be unique per session) /// /// # Errors @@ -199,8 +201,9 @@ impl LpStateMachine { pub fn new( receiver_index: u32, is_initiator: bool, - local_ed25519_keypair: (&ed25519::PrivateKey, &ed25519::PublicKey), + local_ed25519_keypair: Arc, remote_ed25519_key: &ed25519::PublicKey, + remote_x25519_key: &x25519::PublicKey, salt: &[u8; 32], ) -> Result { // We use standard RFC 7748 conversion to derive X25519 keys from Ed25519 identity keys. @@ -213,23 +216,7 @@ impl LpStateMachine { // - PSQ ECDH baseline security (pre-quantum) // Convert Ed25519 keys to X25519 for Noise protocol - let local_x25519_private = local_ed25519_keypair.0.to_x25519(); - let local_x25519_public = local_ed25519_keypair - .1 - .to_x25519() - .map_err(LpError::Ed25519RecoveryError)?; - - let remote_x25519_public = remote_ed25519_key - .to_x25519() - .map_err(LpError::Ed25519RecoveryError)?; - - // Convert nym_crypto X25519 types to nym_lp keypair types - let lp_private = LpPrivateKey::from_bytes(local_x25519_private.as_bytes()); - let lp_public = LpPublicKey::from_bytes(local_x25519_public.as_bytes())?; - let lp_remote_public = LpPublicKey::from_bytes(remote_x25519_public.as_bytes())?; - - // Create X25519 keypair for Noise - let local_x25519_keypair = Keypair::from_keys(lp_private, lp_public); + let local_x25519 = Arc::new(local_ed25519_keypair.to_x25519()); // Create the session with both Ed25519 (for PSQ auth) and derived X25519 keys (for Noise) // receiver_index is client-proposed, passed through directly @@ -237,9 +224,9 @@ impl LpStateMachine { receiver_index, is_initiator, local_ed25519_keypair, - local_x25519_keypair.private_key(), + local_x25519, remote_ed25519_key, - &lp_remote_public, + remote_x25519_key, salt, )?; @@ -1105,6 +1092,10 @@ mod tests { let ed25519_keypair_init = ed25519::KeyPair::from_secret([16u8; 32], 0); let ed25519_keypair_resp = ed25519::KeyPair::from_secret([17u8; 32], 1); + let ed25519_pubkey_init = *ed25519_keypair_init.public_key(); + let x25519_pubkey_init = ed25519_pubkey_init.to_x25519().unwrap(); + let x25519_pubkey_resp = *ed25519_keypair_resp.to_x25519().public_key(); + // Test salt let salt = [51u8; 32]; @@ -1113,11 +1104,9 @@ mod tests { let initiator_sm = LpStateMachine::new( receiver_index, true, - ( - ed25519_keypair_init.private_key(), - ed25519_keypair_init.public_key(), - ), + Arc::new(ed25519_keypair_init), ed25519_keypair_resp.public_key(), + &x25519_pubkey_resp, &salt, ); assert!(initiator_sm.is_ok()); @@ -1132,11 +1121,9 @@ mod tests { let responder_sm = LpStateMachine::new( receiver_index, false, - ( - ed25519_keypair_resp.private_key(), - ed25519_keypair_resp.public_key(), - ), - ed25519_keypair_init.public_key(), + Arc::new(ed25519_keypair_resp), + &ed25519_pubkey_init, + &x25519_pubkey_init, &salt, ); assert!(responder_sm.is_ok()); @@ -1158,6 +1145,10 @@ mod tests { let ed25519_keypair_init = ed25519::KeyPair::from_secret([18u8; 32], 0); let ed25519_keypair_resp = ed25519::KeyPair::from_secret([19u8; 32], 1); + let ed25519_pubkey_init = *ed25519_keypair_init.public_key(); + let x25519_pubkey_init = ed25519_pubkey_init.to_x25519().unwrap(); + let x25519_pubkey_resp = *ed25519_keypair_resp.to_x25519().public_key(); + // Test salt let salt = [52u8; 32]; let receiver_index: u32 = 88888; @@ -1166,11 +1157,9 @@ mod tests { let mut initiator = LpStateMachine::new( receiver_index, true, // is_initiator - ( - ed25519_keypair_init.private_key(), - ed25519_keypair_init.public_key(), - ), + Arc::new(ed25519_keypair_init), ed25519_keypair_resp.public_key(), + &x25519_pubkey_resp, &salt, ) .unwrap(); @@ -1178,11 +1167,9 @@ mod tests { let mut responder = LpStateMachine::new( receiver_index, false, // is_initiator - ( - ed25519_keypair_resp.private_key(), - ed25519_keypair_resp.public_key(), - ), - ed25519_keypair_init.public_key(), + Arc::new(ed25519_keypair_resp), + &ed25519_pubkey_init, + &x25519_pubkey_init, &salt, ) .unwrap(); @@ -1366,6 +1353,8 @@ mod tests { let ed25519_keypair_init = ed25519::KeyPair::from_secret([20u8; 32], 0); let ed25519_keypair_resp = ed25519::KeyPair::from_secret([21u8; 32], 1); + let x25519_pubkey_init = *ed25519_keypair_init.to_x25519().public_key(); + let salt = [53u8; 32]; let receiver_index: u32 = 99901; @@ -1373,11 +1362,9 @@ mod tests { let mut initiator = LpStateMachine::new( receiver_index, true, - ( - ed25519_keypair_init.private_key(), - ed25519_keypair_init.public_key(), - ), + Arc::new(ed25519_keypair_init), ed25519_keypair_resp.public_key(), + &x25519_pubkey_init, &salt, ) .unwrap(); @@ -1397,6 +1384,9 @@ mod tests { let ed25519_keypair_init = ed25519::KeyPair::from_secret([22u8; 32], 0); let ed25519_keypair_resp = ed25519::KeyPair::from_secret([23u8; 32], 1); + let ed25519_pubkey_init = *ed25519_keypair_init.public_key(); + let x25519_pubkey_init = ed25519_pubkey_init.to_x25519().unwrap(); + let salt = [54u8; 32]; let receiver_index: u32 = 99902; @@ -1404,11 +1394,9 @@ mod tests { let mut responder = LpStateMachine::new( receiver_index, false, - ( - ed25519_keypair_resp.private_key(), - ed25519_keypair_resp.public_key(), - ), - ed25519_keypair_init.public_key(), + Arc::new(ed25519_keypair_resp), + &ed25519_pubkey_init, + &x25519_pubkey_init, &salt, ) .unwrap(); @@ -1428,6 +1416,10 @@ mod tests { let ed25519_keypair_init = ed25519::KeyPair::from_secret([24u8; 32], 0); let ed25519_keypair_resp = ed25519::KeyPair::from_secret([25u8; 32], 1); + let ed25519_pubkey_init = *ed25519_keypair_init.public_key(); + let x25519_pubkey_init = ed25519_pubkey_init.to_x25519().unwrap(); + let x25519_pubkey_resp = *ed25519_keypair_resp.to_x25519().public_key(); + let salt = [55u8; 32]; let receiver_index: u32 = 99903; @@ -1435,11 +1427,9 @@ mod tests { let mut initiator = LpStateMachine::new( receiver_index, true, - ( - ed25519_keypair_init.private_key(), - ed25519_keypair_init.public_key(), - ), + Arc::new(ed25519_keypair_init), ed25519_keypair_resp.public_key(), + &x25519_pubkey_resp, &salt, ) .unwrap(); @@ -1447,11 +1437,9 @@ mod tests { let mut responder = LpStateMachine::new( receiver_index, false, - ( - ed25519_keypair_resp.private_key(), - ed25519_keypair_resp.public_key(), - ), - ed25519_keypair_init.public_key(), + Arc::new(ed25519_keypair_resp), + &ed25519_pubkey_init, + &x25519_pubkey_init, &salt, ) .unwrap(); @@ -1493,6 +1481,8 @@ mod tests { let ed25519_keypair_init = ed25519::KeyPair::from_secret([26u8; 32], 0); let ed25519_keypair_resp = ed25519::KeyPair::from_secret([27u8; 32], 1); + let x25519_pubkey_resp = *ed25519_keypair_resp.to_x25519().public_key(); + let salt = [56u8; 32]; let receiver_index: u32 = 99904; @@ -1500,11 +1490,9 @@ mod tests { let mut initiator = LpStateMachine::new( receiver_index, true, - ( - ed25519_keypair_init.private_key(), - ed25519_keypair_init.public_key(), - ), + Arc::new(ed25519_keypair_init), ed25519_keypair_resp.public_key(), + &x25519_pubkey_resp, &salt, ) .unwrap(); @@ -1525,6 +1513,8 @@ mod tests { let ed25519_keypair_init = ed25519::KeyPair::from_secret([28u8; 32], 0); let ed25519_keypair_resp = ed25519::KeyPair::from_secret([29u8; 32], 1); + let x25519_pubkey_resp = *ed25519_keypair_resp.to_x25519().public_key(); + let salt = [57u8; 32]; let receiver_index: u32 = 99905; @@ -1532,11 +1522,9 @@ mod tests { let mut initiator = LpStateMachine::new( receiver_index, true, - ( - ed25519_keypair_init.private_key(), - ed25519_keypair_init.public_key(), - ), + Arc::new(ed25519_keypair_init), ed25519_keypair_resp.public_key(), + &x25519_pubkey_resp, &salt, ) .unwrap(); @@ -1570,6 +1558,11 @@ mod tests { let ed25519_keypair_a = ed25519::KeyPair::from_secret([30u8; 32], 0); let ed25519_keypair_b = ed25519::KeyPair::from_secret([31u8; 32], 1); + let ed25519_pubkey_a = *ed25519_keypair_a.public_key(); + + let x25519_pubkey_a = *ed25519_keypair_a.to_x25519().public_key(); + let x25519_pubkey_b = *ed25519_keypair_b.to_x25519().public_key(); + let salt = [60u8; 32]; let receiver_index: u32 = 111111; @@ -1577,11 +1570,9 @@ mod tests { let mut alice = LpStateMachine::new( receiver_index, true, - ( - ed25519_keypair_a.private_key(), - ed25519_keypair_a.public_key(), - ), + Arc::new(ed25519_keypair_a), ed25519_keypair_b.public_key(), + &x25519_pubkey_b, &salt, ) .unwrap(); @@ -1589,11 +1580,9 @@ mod tests { let mut bob = LpStateMachine::new( receiver_index, false, - ( - ed25519_keypair_b.private_key(), - ed25519_keypair_b.public_key(), - ), - ed25519_keypair_a.public_key(), + Arc::new(ed25519_keypair_b), + &ed25519_pubkey_a, + &x25519_pubkey_a, &salt, ) .unwrap(); diff --git a/gateway/src/node/lp_listener/handler.rs b/gateway/src/node/lp_listener/handler.rs index 7960d4f026..5f70199637 100644 --- a/gateway/src/node/lp_listener/handler.rs +++ b/gateway/src/node/lp_listener/handler.rs @@ -5,9 +5,10 @@ use super::messages::LpRegistrationRequest; use super::registration::process_registration; use super::LpHandlerState; use crate::error::GatewayError; +use nym_crypto::asymmetric::{ed25519, x25519}; use nym_lp::{ - codec::OuterAeadKey, keypair::PublicKey, message::ForwardPacketData, packet::LpHeader, - LpMessage, LpPacket, OuterHeader, + codec::OuterAeadKey, message::ForwardPacketData, packet::LpHeader, LpMessage, LpPacket, + OuterHeader, }; use nym_lp_transport::traits::LpTransport; use nym_metrics::{add_histogram_obs, inc}; @@ -289,31 +290,11 @@ where async fn handle_client_hello(&mut self, packet: LpPacket) -> Result<(), GatewayError> { use nym_lp::packet::LpHeader; use nym_lp::state_machine::{LpInput, LpStateMachine}; + let remote = self.remote_addr; // Extract ClientHello data - let (receiver_index, client_ed25519_pubkey, salt) = match packet.message() { - LpMessage::ClientHello(hello_data) => { - // Validate timestamp - let timestamp = hello_data.extract_timestamp(); - Self::validate_timestamp( - timestamp, - self.state.lp_config.debug.timestamp_tolerance, - )?; - - // Extract client-proposed receiver_index - let receiver_index = hello_data.receiver_index; - - let client_ed25519_pubkey = nym_crypto::asymmetric::ed25519::PublicKey::from_bytes( - &hello_data.client_ed25519_public_key, - ) - .map_err(|e| { - GatewayError::LpProtocolError( - format!("Invalid client Ed25519 public key: {e}",), - ) - })?; - - (receiver_index, client_ed25519_pubkey, hello_data.salt) - } + let hello_data = match packet.message() { + LpMessage::ClientHello(hello_data) => hello_data, other => { inc!("lp_client_hello_failed"); return Err(GatewayError::LpProtocolError(format!( @@ -322,20 +303,23 @@ where } }; - debug!( - "Processing ClientHello from {} (proposed receiver_index={})", - self.remote_addr, receiver_index - ); + // Validate timestamp + let timestamp = hello_data.extract_timestamp(); + Self::validate_timestamp(timestamp, self.state.lp_config.debug.timestamp_tolerance)?; + + // Extract client-proposed receiver_index + let receiver_index = hello_data.receiver_index; + + // let client_ed25519_pubkey = hello_data.client_ed25519_public_key; + + debug!("Processing ClientHello from {remote} (proposed receiver_index={receiver_index})",); // Collision check for client-proposed receiver_index // Check both handshake_states (in-progress) and session_states (established) if self.state.handshake_states.contains_key(&receiver_index) || self.state.session_states.contains_key(&receiver_index) { - warn!( - "Receiver index collision: {} from {}", - receiver_index, self.remote_addr - ); + warn!("Receiver index collision: {receiver_index} from {remote}",); inc!("lp_receiver_index_collision"); // Send Collision response to tell client to retry with new receiver_index @@ -351,32 +335,23 @@ where // Collision check passed - bind this connection to the receiver_index // All subsequent packets on this connection must use this receiver_index self.bound_receiver_idx = Some(receiver_index); - trace!( - "Bound connection from {} to receiver_idx={} (via ClientHello)", - self.remote_addr, - receiver_index - ); + trace!("Bound connection from {remote} to receiver_idx={receiver_index} (via ClientHello)",); // Create state machine for this handshake using client-proposed receiver_index let mut state_machine = LpStateMachine::new( receiver_index, false, // responder - ( - self.state.local_identity.private_key(), - self.state.local_identity.public_key(), - ), - &client_ed25519_pubkey, - &salt, + self.state.local_identity.clone(), + &hello_data.client_ed25519_public_key, + &hello_data.client_lp_public_key, + &hello_data.salt, ) .map_err(|e| { inc!("lp_client_hello_failed"); GatewayError::LpHandshakeError(format!("Failed to create state machine: {}", e)) })?; - debug!( - "Created handshake state for {} (receiver_index={})", - self.remote_addr, receiver_index - ); + debug!("Created handshake state for {remote} (receiver_index={receiver_index})",); // Transition state machine to KKTExchange (responder waits for client's KKT request) // For responder, StartHandshake returns None (just transitions state) @@ -384,8 +359,7 @@ where if let Some(Err(e)) = state_machine.process_input(LpInput::StartHandshake) { inc!("lp_client_hello_failed"); return Err(GatewayError::LpHandshakeError(format!( - "StartHandshake failed: {}", - e + "StartHandshake failed: {e}", ))); // Responder (gateway) gets Ok but no packet to send - we just wait for client's next packet } @@ -396,8 +370,7 @@ where .insert(receiver_index, super::TimestampedState::new(state_machine)); debug!( - "Stored handshake state for {} (receiver_index={}) - waiting for KKT request", - self.remote_addr, receiver_index + "Stored handshake state for {remote} (receiver_index={receiver_index}) - waiting for KKT request", ); // Send Ack to confirm ClientHello received @@ -897,14 +870,7 @@ where #[allow(dead_code)] async fn receive_client_hello( &mut self, - ) -> Result< - ( - PublicKey, - nym_crypto::asymmetric::ed25519::PublicKey, - [u8; 32], - ), - GatewayError, - > { + ) -> Result<(x25519::PublicKey, ed25519::PublicKey, [u8; 32]), GatewayError> { // Receive first packet which should be ClientHello (no outer encryption) let (raw_bytes, _header) = self.receive_raw_packet().await?; let packet = nym_lp::codec::parse_lp_packet(&raw_bytes, None) @@ -931,22 +897,11 @@ where self.state.lp_config.debug.timestamp_tolerance.as_secs() ); - // Convert bytes to X25519 PublicKey (for Noise protocol) - let client_pubkey = PublicKey::from_bytes(&hello_data.client_lp_public_key) - .map_err(|e| { - GatewayError::LpProtocolError(format!("Invalid client public key: {}", e)) - })?; + // Retrieve X25519 PublicKey (for Noise protocol) + let client_pubkey = hello_data.client_lp_public_key; - // Convert bytes to Ed25519 PublicKey (for PSQ authentication) - let client_ed25519_pubkey = nym_crypto::asymmetric::ed25519::PublicKey::from_bytes( - &hello_data.client_ed25519_public_key, - ) - .map_err(|e| { - GatewayError::LpProtocolError(format!( - "Invalid client Ed25519 public key: {}", - e - )) - })?; + // Retrieve Ed25519 PublicKey (for PSQ authentication) + let client_ed25519_pubkey = hello_data.client_ed25519_public_key; // Extract salt for PSK derivation let salt = hello_data.salt; @@ -1705,8 +1660,13 @@ mod tests { let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); let addr = listener.local_addr().unwrap(); - let client_key = [7u8; 32]; - let client_ed25519_key = [8u8; 32]; + let mut rng = rand::thread_rng(); + let ed25519 = ed25519::KeyPair::new(&mut rng); + let x25519 = ed25519.to_x25519(); + + let client_key = *x25519.public_key(); + let client_ed25519_key = *ed25519.public_key(); + let hello_data = ClientHelloData::new_with_fresh_salt(client_key, client_ed25519_key, timestamp); let expected_salt = hello_data.salt; // Clone salt before moving hello_data @@ -1777,8 +1737,8 @@ mod tests { let client_x25519_public = client_ed25519_keypair.public_key().to_x25519().unwrap(); let hello_data = ClientHelloData::new_with_fresh_salt( - client_x25519_public.to_bytes(), - client_ed25519_keypair.public_key().to_bytes(), + client_x25519_public, + *client_ed25519_keypair.public_key(), timestamp, ); let packet = LpPacket::new( @@ -1838,8 +1798,8 @@ mod tests { let client_x25519_public = client_ed25519_keypair.public_key().to_x25519().unwrap(); let mut hello_data = ClientHelloData::new_with_fresh_salt( - client_x25519_public.to_bytes(), - client_ed25519_keypair.public_key().to_bytes(), + client_x25519_public, + *client_ed25519_keypair.public_key(), timestamp, ); diff --git a/nym-registration-client/src/lp_client/client.rs b/nym-registration-client/src/lp_client/client.rs index 2cb6a3ed0f..6e350eeb50 100644 --- a/nym-registration-client/src/lp_client/client.rs +++ b/nym-registration-client/src/lp_client/client.rs @@ -328,6 +328,10 @@ where LpClientError::Crypto(format!("Failed to derive X25519 public key: {e}")) })?; + let gateway_x25519_public = self.gateway_ed25519_public_key.to_x25519().map_err(|e| { + LpClientError::Crypto(format!("Failed to derive X25519 public key: {e}")) + })?; + let timestamp = SystemTime::now() .duration_since(UNIX_EPOCH) .map_err(|_| LpClientError::Other("System time before UNIX epoch".into()))? @@ -335,8 +339,8 @@ where // Step 2: Generate ClientHelloData with fresh salt and both public keys let client_hello_data = nym_lp::ClientHelloData::new_with_fresh_salt( - client_x25519_public.to_bytes(), - self.local_ed25519_keypair.public_key().to_bytes(), + client_x25519_public, + *self.local_ed25519_keypair.public_key(), timestamp, ); let salt = client_hello_data.salt; @@ -381,11 +385,9 @@ where let mut state_machine = LpStateMachine::new( receiver_index, true, // is_initiator - ( - self.local_ed25519_keypair.private_key(), - self.local_ed25519_keypair.public_key(), - ), + self.local_ed25519_keypair.clone(), &self.gateway_ed25519_public_key, + &gateway_x25519_public, &salt, )?; diff --git a/nym-registration-client/src/lp_client/nested_session.rs b/nym-registration-client/src/lp_client/nested_session.rs index 2f011e9b15..7fbdbbc11e 100644 --- a/nym-registration-client/src/lp_client/nested_session.rs +++ b/nym-registration-client/src/lp_client/nested_session.rs @@ -129,6 +129,10 @@ impl NestedLpSession { LpClientError::Crypto(format!("Failed to derive X25519 public key: {}", e)) })?; + let gateway_x25519_public = self.exit_public_key.to_x25519().map_err(|e| { + LpClientError::Crypto(format!("Failed to derive X25519 public key: {e}")) + })?; + let timestamp = SystemTime::now() .duration_since(UNIX_EPOCH) .map_err(|_| LpClientError::Other("System time before UNIX epoch".into()))? @@ -136,8 +140,8 @@ impl NestedLpSession { // Step 2: Generate ClientHello for exit gateway let client_hello_data = nym_lp::ClientHelloData::new_with_fresh_salt( - client_x25519_public.to_bytes(), - self.client_keypair.public_key().to_bytes(), + client_x25519_public, + *self.client_keypair.public_key(), timestamp, ); let salt = client_hello_data.salt; @@ -191,11 +195,9 @@ impl NestedLpSession { let mut state_machine = LpStateMachine::new( receiver_index, true, // is_initiator - ( - self.client_keypair.private_key(), - self.client_keypair.public_key(), - ), + self.client_keypair.clone(), &self.exit_public_key, + &gateway_x25519_public, &salt, )?; From 18a44209db5eff9b2d660a26d8c7cf53232e8621 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Mon, 19 Jan 2026 09:11:12 +0000 Subject: [PATCH 06/51] lp chore: make sure to take reserved bytes straight from the header (#6336) --- Cargo.lock | 2 +- common/nym-lp/src/codec.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3674983671..1f86a54a74 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6701,8 +6701,8 @@ dependencies = [ "num_enum", "nym-crypto", "rand 0.9.2", - "thiserror 2.0.17", "rand_chacha 0.9.0", + "thiserror 2.0.17", "zeroize", ] diff --git a/common/nym-lp/src/codec.rs b/common/nym-lp/src/codec.rs index 2067bb37a5..9c41cacc69 100644 --- a/common/nym-lp/src/codec.rs +++ b/common/nym-lp/src/codec.rs @@ -255,7 +255,7 @@ pub fn serialize_lp_packet( // Cleartext mode // 2. Write inner prefix: proto(1) + reserved(3) dst.put_u8(item.header.protocol_version); - dst.put_slice(&[0, 0, 0]); // reserved + dst.put_slice(&item.header.reserved); // reserved // 3. Write message type (4B) + content dst.put_slice(&(item.message.typ() as u32).to_le_bytes()); @@ -274,7 +274,7 @@ pub fn serialize_lp_packet( // 2. Build plaintext: proto(1) + reserved(3) + msg_type(4) + content let mut plaintext = BytesMut::new(); plaintext.put_u8(item.header.protocol_version); - plaintext.put_slice(&[0, 0, 0]); // reserved + plaintext.put_slice(&item.header.reserved); // reserved plaintext.put_slice(&(item.message.typ() as u32).to_le_bytes()); item.message.encode_content(&mut plaintext); From 38316690f0046755bce07915235c843738430296 Mon Sep 17 00:00:00 2001 From: import this <97586125+serinko@users.noreply.github.com> Date: Mon, 19 Jan 2026 13:07:46 +0000 Subject: [PATCH 07/51] Bugfix: NTM sync of policies to nymtun and nymwg (#6334) --- .../nym-node-setup/network-tunnel-manager.sh | 110 ++++++++++++------ 1 file changed, 76 insertions(+), 34 deletions(-) diff --git a/scripts/nym-node-setup/network-tunnel-manager.sh b/scripts/nym-node-setup/network-tunnel-manager.sh index a901d5be3c..8306cb512c 100755 --- a/scripts/nym-node-setup/network-tunnel-manager.sh +++ b/scripts/nym-node-setup/network-tunnel-manager.sh @@ -350,8 +350,11 @@ apply_iptables_rules() { iptables -t nat -C POSTROUTING -o "$NETWORK_DEVICE" -j MASQUERADE 2>/dev/null || \ iptables -t nat -A POSTROUTING -o "$NETWORK_DEVICE" -j MASQUERADE - iptables -C FORWARD -i "$interface" -o "$NETWORK_DEVICE" -j ACCEPT 2>/dev/null || \ - iptables -I FORWARD 1 -i "$interface" -o "$NETWORK_DEVICE" -j ACCEPT + # governed by NYM-EXIT, do not add a broad FORWARD ACCEPT + if ! iptables -C FORWARD -i "$interface" -o "$NETWORK_DEVICE" -j "$NYM_CHAIN" 2>/dev/null; then + iptables -C FORWARD -i "$interface" -o "$NETWORK_DEVICE" -j ACCEPT 2>/dev/null || \ + iptables -I FORWARD 1 -i "$interface" -o "$NETWORK_DEVICE" -j ACCEPT + fi iptables -C FORWARD -i "$NETWORK_DEVICE" -o "$interface" -m state --state RELATED,ESTABLISHED -j ACCEPT 2>/dev/null || \ iptables -I FORWARD 2 -i "$NETWORK_DEVICE" -o "$interface" -m state --state RELATED,ESTABLISHED -j ACCEPT @@ -360,8 +363,11 @@ apply_iptables_rules() { ip6tables -t nat -C POSTROUTING -o "$NETWORK_DEVICE" -j MASQUERADE 2>/dev/null || \ ip6tables -t nat -A POSTROUTING -o "$NETWORK_DEVICE" -j MASQUERADE - ip6tables -C FORWARD -i "$interface" -o "$NETWORK_DEVICE" -j ACCEPT 2>/dev/null || \ - ip6tables -I FORWARD 1 -i "$interface" -o "$NETWORK_DEVICE" -j ACCEPT + # governed by NYM-EXIT, do not add a broad FORWARD ACCEPT + if ! ip6tables -C FORWARD -i "$interface" -o "$NETWORK_DEVICE" -j "$NYM_CHAIN" 2>/dev/null; then + ip6tables -C FORWARD -i "$interface" -o "$NETWORK_DEVICE" -j ACCEPT 2>/dev/null || \ + ip6tables -I FORWARD 1 -i "$interface" -o "$NETWORK_DEVICE" -j ACCEPT + fi ip6tables -C FORWARD -i "$NETWORK_DEVICE" -o "$interface" -m state --state RELATED,ESTABLISHED -j ACCEPT 2>/dev/null || \ ip6tables -I FORWARD 2 -i "$NETWORK_DEVICE" -o "$interface" -m state --state RELATED,ESTABLISHED -j ACCEPT @@ -559,7 +565,7 @@ create_nym_chain() { ip6tables -N "$NYM_CHAIN" fi - # remove *all* FORWARD -> NYM-EXIT jumps + # remove *all* FORWARD -> NYM-EXIT jumps while read -r rule; do spec="${rule#-A FORWARD }" iptables -D FORWARD $spec 2>/dev/null || true @@ -570,11 +576,20 @@ create_nym_chain() { ip6tables -D FORWARD $spec 2>/dev/null || true done < <(ip6tables -S FORWARD | grep -F " -j $NYM_CHAIN" || true) - # add the single correct hook - iptables -I FORWARD 1 -i "$WG_INTERFACE" -o "$NETWORK_DEVICE" -j "$NYM_CHAIN" - ip6tables -I FORWARD 1 -i "$WG_INTERFACE" -o "$NETWORK_DEVICE" -j "$NYM_CHAIN" + # remove broad ACCEPT rules for wg + tun outbound so NYM-EXIT is authoritative + iptables -D FORWARD -i "$WG_INTERFACE" -o "$NETWORK_DEVICE" -j ACCEPT 2>/dev/null || true + iptables -D FORWARD -i "$TUNNEL_INTERFACE" -o "$NETWORK_DEVICE" -j ACCEPT 2>/dev/null || true + ip6tables -D FORWARD -i "$WG_INTERFACE" -o "$NETWORK_DEVICE" -j ACCEPT 2>/dev/null || true + ip6tables -D FORWARD -i "$TUNNEL_INTERFACE" -o "$NETWORK_DEVICE" -j ACCEPT 2>/dev/null || true - ok "NYM-EXIT chain ready + single FORWARD hook installed" + # install the correct hook for both wg + tun + iptables -I FORWARD 1 -i "$WG_INTERFACE" -o "$NETWORK_DEVICE" -j "$NYM_CHAIN" + iptables -I FORWARD 1 -i "$TUNNEL_INTERFACE" -o "$NETWORK_DEVICE" -j "$NYM_CHAIN" + + ip6tables -I FORWARD 1 -i "$WG_INTERFACE" -o "$NETWORK_DEVICE" -j "$NYM_CHAIN" + ip6tables -I FORWARD 1 -i "$TUNNEL_INTERFACE" -o "$NETWORK_DEVICE" -j "$NYM_CHAIN" + + ok "NYM-EXIT chain ready + FORWARD hooks installed for $WG_INTERFACE and $TUNNEL_INTERFACE" } @@ -588,16 +603,10 @@ setup_nat_rules() { ip6tables -t nat -A POSTROUTING -o "$NETWORK_DEVICE" -j MASQUERADE fi - if ! iptables -C FORWARD -i "$WG_INTERFACE" -o "$NETWORK_DEVICE" -j ACCEPT 2>/dev/null; then - iptables -I FORWARD 1 -i "$WG_INTERFACE" -o "$NETWORK_DEVICE" -j ACCEPT - fi + # keep reverse RELATED,ESTABLISHED in FORWARD for return traffic. if ! iptables -C FORWARD -i "$NETWORK_DEVICE" -o "$WG_INTERFACE" -m state --state RELATED,ESTABLISHED -j ACCEPT 2>/dev/null; then iptables -I FORWARD 2 -i "$NETWORK_DEVICE" -o "$WG_INTERFACE" -m state --state RELATED,ESTABLISHED -j ACCEPT fi - - if ! ip6tables -C FORWARD -i "$WG_INTERFACE" -o "$NETWORK_DEVICE" -j ACCEPT 2>/dev/null; then - ip6tables -I FORWARD 1 -i "$WG_INTERFACE" -o "$NETWORK_DEVICE" -j ACCEPT - fi if ! ip6tables -C FORWARD -i "$NETWORK_DEVICE" -o "$WG_INTERFACE" -m state --state RELATED,ESTABLISHED -j ACCEPT 2>/dev/null; then ip6tables -I FORWARD 2 -i "$NETWORK_DEVICE" -o "$WG_INTERFACE" -m state --state RELATED,ESTABLISHED -j ACCEPT fi @@ -606,7 +615,7 @@ setup_nat_rules() { configure_exit_dns_and_icmp() { info "ensuring dns and icmp are allowed inside nym exit chain" - # Remove any existing DNS/ICMP rules first to avoid duplicates + # remove any existing DNS/ICMP rules first to avoid duplicates iptables -D "$NYM_CHAIN" -p udp --dport 53 -j ACCEPT 2>/dev/null || true iptables -D "$NYM_CHAIN" -p tcp --dport 53 -j ACCEPT 2>/dev/null || true iptables -D "$NYM_CHAIN" -p icmp --icmp-type echo-request -j ACCEPT 2>/dev/null || true @@ -615,7 +624,7 @@ configure_exit_dns_and_icmp() { ip6tables -D "$NYM_CHAIN" -p tcp --dport 53 -j ACCEPT 2>/dev/null || true ip6tables -D "$NYM_CHAIN" -p ipv6-icmp -j ACCEPT 2>/dev/null || true - # Insert rules at the beginning in correct order: DNS first, then ICMP + # insert rules at the beginning in correct order: DNS first, then ICMP iptables -I "$NYM_CHAIN" 1 -p udp --dport 53 -j ACCEPT iptables -I "$NYM_CHAIN" 2 -p tcp --dport 53 -j ACCEPT iptables -I "$NYM_CHAIN" 3 -p icmp --icmp-type echo-request -j ACCEPT @@ -647,7 +656,7 @@ apply_port_allowlist() { ["SMBWindowsFileShare"]="445" ["Kpasswd"]="464" # this port is opened and rate limited in apply_smtps_465_rate_limit - # ["SMTP"]="465" + # ["SMTP"]="465" ["RTSP"]="554" ["SMTPSubmission"]="587" ["LDAPS"]="636" @@ -722,7 +731,7 @@ apply_port_allowlist() { ["MOSH"]="60000-61000" ["Mumble"]="64738" ["Metadata"]="51830" - + ) local port @@ -768,16 +777,17 @@ EOF if [[ -n "$ip_range" ]]; then - # ipv4 reject - if ! iptables -C "$NYM_CHAIN" -d "$ip_range" -j REJECT 2>/dev/null; then - iptables -A "$NYM_CHAIN" -d "$ip_range" -j REJECT --reject-with icmp-port-unreachable \ + # insert blocklist BEFORE the allowlist (after DNS/ICMP bootstrap rules) + # ipv4 reject (DNS/ICMP occupy positions 1-4) + if ! iptables -C "$NYM_CHAIN" -d "$ip_range" -j REJECT --reject-with icmp-port-unreachable 2>/dev/null; then + iptables -I "$NYM_CHAIN" 5 -d "$ip_range" -j REJECT --reject-with icmp-port-unreachable \ || error "warning: failed adding ipv4 reject for $ip_range" fi - # ipv6 reject + # ipv6 reject (DNS/ICMP occupy positions 1-3) if [[ "$ip_range" == *":"* ]]; then - if ! ip6tables -C "$NYM_CHAIN" -d "$ip_range" -j REJECT 2>/dev/null; then - ip6tables -A "$NYM_CHAIN" -d "$ip_range" -j REJECT \ + if ! ip6tables -C "$NYM_CHAIN" -d "$ip_range" -j REJECT --reject-with icmp6-port-unreachable 2>/dev/null; then + ip6tables -I "$NYM_CHAIN" 4 -d "$ip_range" -j REJECT --reject-with icmp6-port-unreachable \ || error "warning: failed adding ipv6 reject for $ip_range" fi fi @@ -808,8 +818,11 @@ clear_exit_policy_rules() { iptables -F "$NYM_CHAIN" 2>/dev/null || true ip6tables -F "$NYM_CHAIN" 2>/dev/null || true + # remove hooks for BOTH wg + tun iptables -D FORWARD -i "$WG_INTERFACE" -o "$NETWORK_DEVICE" -j "$NYM_CHAIN" 2>/dev/null || true + iptables -D FORWARD -i "$TUNNEL_INTERFACE" -o "$NETWORK_DEVICE" -j "$NYM_CHAIN" 2>/dev/null || true ip6tables -D FORWARD -i "$WG_INTERFACE" -o "$NETWORK_DEVICE" -j "$NYM_CHAIN" 2>/dev/null || true + ip6tables -D FORWARD -i "$TUNNEL_INTERFACE" -o "$NETWORK_DEVICE" -j "$NYM_CHAIN" 2>/dev/null || true iptables -X "$NYM_CHAIN" 2>/dev/null || true ip6tables -X "$NYM_CHAIN" 2>/dev/null || true @@ -819,21 +832,36 @@ show_exit_policy_status() { info "nym exit policy status" info "network device: $NETWORK_DEVICE" info "wireguard interface: $WG_INTERFACE" + info "tunnel interface: $TUNNEL_INTERFACE" echo if ! ip link show "$WG_INTERFACE" >/dev/null 2>&1; then error "warning: wireguard interface $WG_INTERFACE not found" else - info "interface details:" + info "wireguard interface details:" ip link show "$WG_INTERFACE" echo - info "ipv4 addresses:" + info "wireguard ipv4 addresses:" ip -4 addr show dev "$WG_INTERFACE" echo - info "ipv6 addresses:" + info "wireguard ipv6 addresses:" ip -6 addr show dev "$WG_INTERFACE" fi + echo + if ! ip link show "$TUNNEL_INTERFACE" >/dev/null 2>&1; then + error "warning: tunnel interface $TUNNEL_INTERFACE not found" + else + info "tunnel interface details:" + ip link show "$TUNNEL_INTERFACE" + echo + info "tunnel ipv4 addresses:" + ip -4 addr show dev "$TUNNEL_INTERFACE" + echo + info "tunnel ipv6 addresses:" + ip -6 addr show dev "$TUNNEL_INTERFACE" + fi + echo info "iptables chains for ${NYM_CHAIN}:" iptables -L "$NYM_CHAIN" -n -v 2>/dev/null || echo "ipv4 chain not found" @@ -1100,17 +1128,32 @@ test_forward_chain_hook() { local failures=0 + # verify BOTH interfaces are hooked to NYM-EXIT (IPv4 + IPv6) if iptables -C FORWARD -i "$WG_INTERFACE" -o "$NETWORK_DEVICE" -j "$NYM_CHAIN" 2>/dev/null; then - ok "ipv4 forward hook ok: -i $WG_INTERFACE -o $NETWORK_DEVICE -> $NYM_CHAIN" + ok "ipv4 forward hook ok (wg): -i $WG_INTERFACE -o $NETWORK_DEVICE -> $NYM_CHAIN" else - error "ipv4 forward hook missing or wrong" + error "ipv4 forward hook missing or wrong (wg)" + ((failures++)) + fi + + if iptables -C FORWARD -i "$TUNNEL_INTERFACE" -o "$NETWORK_DEVICE" -j "$NYM_CHAIN" 2>/dev/null; then + ok "ipv4 forward hook ok (tun): -i $TUNNEL_INTERFACE -o $NETWORK_DEVICE -> $NYM_CHAIN" + else + error "ipv4 forward hook missing or wrong (tun)" ((failures++)) fi if ip6tables -C FORWARD -i "$WG_INTERFACE" -o "$NETWORK_DEVICE" -j "$NYM_CHAIN" 2>/dev/null; then - ok "ipv6 forward hook ok: -i $WG_INTERFACE -o $NETWORK_DEVICE -> $NYM_CHAIN" + ok "ipv6 forward hook ok (wg): -i $WG_INTERFACE -o $NETWORK_DEVICE -> $NYM_CHAIN" else - error "ipv6 forward hook missing or wrong" + error "ipv6 forward hook missing or wrong (wg)" + ((failures++)) + fi + + if ip6tables -C FORWARD -i "$TUNNEL_INTERFACE" -o "$NETWORK_DEVICE" -j "$NYM_CHAIN" 2>/dev/null; then + ok "ipv6 forward hook ok (tun): -i $TUNNEL_INTERFACE -o $NETWORK_DEVICE -> $NYM_CHAIN" + else + error "ipv6 forward hook missing or wrong (tun)" ((failures++)) fi @@ -1178,7 +1221,6 @@ exit_policy_run_tests() { ############################################################################### nym_tunnel_setup() { - # this mirrors your previous chain of calls but inside one script info "running full tunnel setup for ${TUNNEL_INTERFACE} and ${WG_INTERFACE}" check_tunnel_iptables "$TUNNEL_INTERFACE" From cf3fd00350cec1c59cd56491aab8300d5ee72906 Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Mon, 19 Jan 2026 13:19:45 +0000 Subject: [PATCH 08/51] Max/crates io prep v2 (#6270) * - standardise versions for all nym-sdk workspace dependencies - prepend sqlx-pool-guard with 'nym-' * Test remove nym-api from deps * Add oneliner to client_pool doc comments * Add note to commented out docs.rs link in sdk * remove nym-api from script * add publishing file * bring non-binary / contract / tools into workspace version * added more info to publishing.md * make deps workspace version * remove uploaded sphinx-types crate from script * remove erroueously included ignore-defaults * add zeroise to feature * chore: Release * add topology to batch * more cargo versioning * more cargo versioning - wasm utils * more cargo versioning - wasm utils * Add publish=false to manifest for cargo workspaces / crates.io publishing exclusion * remove script now switched to manifest based exclusion * rename import based on rename of contracts-common dep * Making workspace versions for publication + removing unnecessary crates from publication * Remove OOD info from publishing sdk guide * rename contract imports + remove package * temp commit: continuing with removal of path from cargo manifest and replacing with workspace version import for publication * continuing with cargo.toml updates * dryrun only erroring on known version problem crates * remove old published-crates file * Minor comment change * remove default features warning * Additional info on workspace dep comment re publish list * Add missing description to cargo.toml * Fix missing feature flags * Add missing descriptions * Fix remaining path import * Add workspace repo / homepage / documentation links to cargo.toml files * remove workspace version from excluded crate * Remove todo descriptions * Minor comment change * add homepage etc * move from bls git import to nym_bls_fork crate * Modify rest of imports from path to workspace import, excluding binaries * add directory/homepage info * fix cargo fmt * add notes to gitignore * better solution to contracts/ experiment * wasm -> nym_wasm crate renaming * fix fatfinger * add metadata to ecash cargo.toml * stub publishing guide * fix misrevolved netlink- version * Fixes and block publication of rebase re: LP * first pass @ workflows --- .../workflows/publish-crates-io-dry-run.yml | 43 + .github/workflows/publish-crates-io.yml | 47 + .gitignore | 2 + Cargo.lock | 2406 +++++++++-------- Cargo.toml | 110 +- clients/native/Cargo.toml | 33 +- clients/native/websocket-requests/Cargo.toml | 8 +- clients/socks5/Cargo.toml | 31 +- common/async-file-watcher/Cargo.toml | 6 +- common/authenticator-requests/Cargo.toml | 19 +- common/bandwidth-controller/Cargo.toml | 22 +- common/bin-common/Cargo.toml | 2 +- common/client-core/Cargo.toml | 54 +- common/client-core/config-types/Cargo.toml | 16 +- .../client-core/gateways-storage/Cargo.toml | 12 +- .../client-core/src/client/base_client/mod.rs | 2 +- .../real_traffic_stream.rs | 2 +- common/client-core/src/init/helpers.rs | 4 +- common/client-core/surb-storage/Cargo.toml | 16 +- .../src/backend/fs_backend/manager.rs | 2 +- common/client-libs/gateway-client/Cargo.toml | 38 +- .../gateway-client/src/client/mod.rs | 4 +- .../gateway-client/src/socket_state.rs | 2 +- common/client-libs/mixnet-client/Cargo.toml | 14 +- .../client-libs/validator-client/Cargo.toml | 34 +- common/commands/Cargo.toml | 54 +- common/config/Cargo.toml | 6 +- .../coconut-dkg/Cargo.toml | 10 +- .../coconut-dkg/src/dealing.rs | 2 +- .../coconut-dkg/src/msg.rs | 2 +- .../coconut-dkg/src/types.rs | 2 +- .../contracts-common-testing/Cargo.toml | 5 +- .../contracts-common/Cargo.toml | 2 +- .../easy_addr/Cargo.toml | 4 +- .../ecash-contract/Cargo.toml | 8 +- .../group-contract/Cargo.toml | 6 +- .../mixnet-contract/Cargo.toml | 6 +- .../mixnet-contract/src/error.rs | 4 +- .../mixnet-contract/src/events.rs | 2 +- .../mixnet-contract/src/helpers.rs | 2 +- .../mixnet-contract/src/lib.rs | 2 +- .../mixnet-contract/src/msg.rs | 6 +- .../mixnet-contract/src/nym_node.rs | 2 +- .../mixnet-contract/src/rewarding/helpers.rs | 2 +- .../mixnet-contract/src/signing_types.rs | 4 +- .../mixnet-contract/src/types.rs | 2 +- .../multisig-contract/Cargo.toml | 4 +- .../nym-performance-contract/Cargo.toml | 5 +- .../nym-pool-contract/Cargo.toml | 2 +- .../vesting-contract/Cargo.toml | 6 +- .../vesting-contract/src/error.rs | 2 +- .../vesting-contract/src/lib.rs | 4 +- .../vesting-contract/src/messages.rs | 8 +- .../vesting-contract/src/types.rs | 4 +- common/credential-proxy/Cargo.toml | 23 +- common/credential-storage/Cargo.toml | 24 +- .../credential-storage/src/backends/sqlite.rs | 2 +- .../src/persistent_storage/mod.rs | 2 +- common/credential-utils/Cargo.toml | 23 +- common/credential-verification/Cargo.toml | 25 +- common/credentials-interface/Cargo.toml | 14 +- common/credentials/Cargo.toml | 27 +- common/crypto/Cargo.toml | 12 +- common/dkg/Cargo.toml | 14 +- common/dkg/benches/benchmarks.rs | 2 +- common/dkg/src/bte/encryption.rs | 6 +- common/dkg/src/bte/keys.rs | 2 +- common/dkg/src/bte/mod.rs | 4 +- common/dkg/src/bte/proof_chunking.rs | 2 +- common/dkg/src/bte/proof_discrete_log.rs | 2 +- common/dkg/src/bte/proof_sharing.rs | 2 +- common/dkg/src/dealing.rs | 2 +- common/dkg/src/interpolation/mod.rs | 2 +- common/dkg/src/interpolation/polynomial.rs | 2 +- common/dkg/src/lib.rs | 4 +- common/dkg/src/share.rs | 2 +- common/dkg/src/utils.rs | 8 +- common/dkg/tests/integration.rs | 2 +- common/ecash-signer-check-types/Cargo.toml | 7 +- common/ecash-signer-check/Cargo.toml | 11 +- common/ecash-time/Cargo.toml | 7 +- common/exit-policy/Cargo.toml | 3 +- common/gateway-requests/Cargo.toml | 28 +- common/gateway-stats-storage/Cargo.toml | 9 +- common/gateway-storage/Cargo.toml | 11 +- common/http-api-client-macro/Cargo.toml | 5 +- common/http-api-client/Cargo.toml | 11 +- common/http-api-common/Cargo.toml | 5 +- common/inclusion-probability/Cargo.toml | 2 +- common/ip-packet-requests/Cargo.toml | 12 +- common/ledger/Cargo.toml | 3 +- common/mixnode-common/Cargo.toml | 27 +- common/network-defaults/Cargo.toml | 2 +- common/node-tester-utils/Cargo.toml | 20 +- common/node-tester-utils/src/lib.rs | 6 +- common/nonexhaustive-delayqueue/Cargo.toml | 7 +- common/nym-cache/Cargo.toml | 3 +- common/nym-common/Cargo.toml | 3 +- common/nym-connection-monitor/Cargo.toml | 9 +- common/nym-id/Cargo.toml | 7 +- common/nym-kcp/Cargo.toml | 3 + common/nym-kkt/Cargo.toml | 1 + common/nym-lp-common/Cargo.toml | 1 + common/nym-lp-transport/Cargo.toml | 1 + common/nym-lp/Cargo.toml | 1 + common/nym-metrics/Cargo.toml | 3 +- common/nym_offline_compact_ecash/Cargo.toml | 12 +- .../benches/benchmarks_group_operations.rs | 14 +- .../src/common_types.rs | 2 +- .../src/constants.rs | 2 +- .../nym_offline_compact_ecash/src/helpers.rs | 2 +- common/nym_offline_compact_ecash/src/lib.rs | 4 +- .../src/proofs/mod.rs | 2 +- .../src/proofs/proof_spend.rs | 2 +- .../src/proofs/proof_withdrawal.rs | 2 +- .../src/scheme/aggregation.rs | 2 +- .../src/scheme/coin_indices_signatures.rs | 2 +- .../src/scheme/expiration_date_signatures.rs | 2 +- .../src/scheme/keygen.rs | 2 +- .../src/scheme/mod.rs | 2 +- .../src/scheme/setup.rs | 2 +- .../src/scheme/withdrawal.rs | 4 +- .../nym_offline_compact_ecash/src/traits.rs | 2 +- common/nym_offline_compact_ecash/src/utils.rs | 8 +- common/nymnoise/Cargo.toml | 14 +- common/nymnoise/keys/Cargo.toml | 10 +- common/nymsphinx/Cargo.toml | 32 +- common/nymsphinx/acknowledgements/Cargo.toml | 16 +- common/nymsphinx/addressing/Cargo.toml | 10 +- common/nymsphinx/anonymous-replies/Cargo.toml | 14 +- common/nymsphinx/chunking/Cargo.toml | 12 +- common/nymsphinx/cover/Cargo.toml | 20 +- common/nymsphinx/forwarding/Cargo.toml | 10 +- common/nymsphinx/framing/Cargo.toml | 12 +- common/nymsphinx/params/Cargo.toml | 6 +- common/nymsphinx/routing/Cargo.toml | 8 +- common/nymsphinx/types/Cargo.toml | 4 +- common/nyxd-scraper-psql/Cargo.toml | 3 +- common/nyxd-scraper-shared/Cargo.toml | 3 +- common/nyxd-scraper-sqlite/Cargo.toml | 5 +- common/pemstore/Cargo.toml | 4 +- common/published-crates.txt | 8 - common/registration/Cargo.toml | 15 +- common/serde-helpers/Cargo.toml | 5 +- .../Cargo.toml | 3 +- common/socks5-client-core/Cargo.toml | 32 +- common/socks5/ordered-buffer/Cargo.toml | 6 +- common/socks5/proxy-helpers/Cargo.toml | 12 +- common/socks5/requests/Cargo.toml | 12 +- common/statistics/Cargo.toml | 16 +- common/store-cipher/Cargo.toml | 6 +- common/task/Cargo.toml | 6 +- common/test-utils/Cargo.toml | 5 +- common/ticketbooks-merkle/Cargo.toml | 7 +- common/topology/Cargo.toml | 17 +- common/topology/src/wasm_helpers.rs | 2 +- common/tun/Cargo.toml | 5 +- common/types/Cargo.toml | 15 +- common/upgrade-mode-check/Cargo.toml | 11 +- common/verloc/Cargo.toml | 13 +- common/wasm/client-core/Cargo.toml | 35 +- common/wasm/client-core/src/error.rs | 4 +- common/wasm/client-core/src/helpers.rs | 4 +- common/wasm/client-core/src/lib.rs | 2 +- .../src/storage/core_client_traits.rs | 2 +- common/wasm/client-core/src/storage/mod.rs | 10 +- .../src/storage/wasm_client_traits.rs | 2 +- common/wasm/storage/Cargo.toml | 12 +- common/wasm/storage/src/error.rs | 2 +- common/wasm/storage/src/lib.rs | 2 +- common/wasm/utils/Cargo.toml | 10 +- .../client/Cargo.toml | 7 +- .../server/Cargo.toml | 13 +- .../shared/Cargo.toml | 5 +- .../tests/Cargo.toml | 24 +- common/wireguard-types/Cargo.toml | 7 +- common/wireguard/Cargo.toml | 27 +- common/zulip-client/Cargo.toml | 7 +- contracts/Cargo.lock | 260 +- contracts/Cargo.toml | 35 + contracts/coconut-dkg/Cargo.toml | 18 +- contracts/ecash/Cargo.toml | 10 +- .../Cargo.toml | 12 +- contracts/mixnet/Cargo.toml | 18 +- .../multisig/cw3-flex-multisig/Cargo.toml | 14 +- contracts/multisig/cw4-group/Cargo.toml | 10 +- contracts/nym-pool/Cargo.toml | 6 +- contracts/performance/Cargo.toml | 14 +- contracts/vesting/Cargo.toml | 6 +- crate-publishing.md | 22 + documentation/autodoc/Cargo.toml | 1 + gateway/Cargo.toml | 59 +- integration-tests/Cargo.toml | 1 + nym-api/Cargo.toml | 65 +- nym-api/nym-api-requests/Cargo.toml | 36 +- nym-authenticator-client/Cargo.toml | 21 +- nym-browser-extension/storage/Cargo.toml | 19 +- nym-browser-extension/storage/src/error.rs | 4 +- nym-browser-extension/storage/src/lib.rs | 2 +- nym-browser-extension/storage/src/storage.rs | 8 +- .../nym-credential-proxy-requests/Cargo.toml | 17 +- .../nym-credential-proxy/Cargo.toml | 33 +- .../vpn-api-lib-wasm/Cargo.toml | 20 +- .../vpn-api-lib-wasm/src/error.rs | 2 +- .../vpn-api-lib-wasm/src/lib.rs | 10 +- nym-data-observatory/Cargo.toml | 15 +- nym-gateway-probe/Cargo.toml | 32 +- nym-ip-packet-client/Cargo.toml | 7 +- nym-network-monitor/Cargo.toml | 25 +- .../nym-node-status-agent/Cargo.toml | 5 +- .../nym-node-status-api/Cargo.toml | 33 +- .../nym-node-status-client/Cargo.toml | 5 +- nym-node/Cargo.toml | 63 +- nym-node/nym-node-metrics/Cargo.toml | 7 +- nym-node/nym-node-requests/Cargo.toml | 19 +- nym-outfox/Cargo.toml | 2 +- nym-registration-client/Cargo.toml | 23 +- nym-signers-monitor/Cargo.toml | 11 +- .../Cargo.toml | 8 +- .../src/apple.rs | 0 .../src/lib.rs | 0 .../src/linux.rs | 0 .../src/windows.rs | 0 nym-statistics-api/Cargo.toml | 14 +- nym-validator-rewarder/Cargo.toml | 33 +- nyx-chain-watcher/Cargo.toml | 11 +- sdk/ffi/cpp/Cargo.toml | 14 +- sdk/ffi/go/Cargo.toml | 16 +- sdk/ffi/shared/Cargo.toml | 14 +- sdk/rust/nym-sdk/Cargo.toml | 49 +- sdk/rust/nym-sdk/src/client_pool.rs | 3 + service-providers/common/Cargo.toml | 15 +- service-providers/ip-packet-router/Cargo.toml | 40 +- .../network-requester/Cargo.toml | 39 +- tools/echo-server/Cargo.toml | 8 +- .../importer-cli/Cargo.toml | 11 +- .../importer-contract/Cargo.toml | 4 +- .../mixnet-connectivity-check/Cargo.toml | 9 +- tools/internal/ssl-inject/Cargo.toml | 1 + tools/internal/testnet-manager/Cargo.toml | 32 +- .../dkg-bypass-contract/Cargo.toml | 7 +- .../validator-status-check/Cargo.toml | 9 +- tools/nym-cli/Cargo.toml | 7 +- tools/nym-id-cli/Cargo.toml | 7 +- tools/nym-lp-client/Cargo.toml | 1 + tools/nym-nr-query/Cargo.toml | 11 +- tools/nymvisor/Cargo.toml | 11 +- tools/ts-rs-cli/Cargo.toml | 11 +- wasm/client/Cargo.toml | 12 +- wasm/client/src/client.rs | 36 +- wasm/client/src/config.rs | 4 +- wasm/client/src/encoded_payload_helper.rs | 8 +- wasm/client/src/error.rs | 8 +- wasm/client/src/helpers.rs | 14 +- wasm/client/src/lib.rs | 10 +- wasm/client/src/response_pusher.rs | 10 +- wasm/full-nym-wasm/Cargo.toml | 7 +- wasm/mix-fetch/Cargo.toml | 17 +- wasm/mix-fetch/src/active_requests.rs | 2 +- wasm/mix-fetch/src/client.rs | 24 +- wasm/mix-fetch/src/config.rs | 8 +- wasm/mix-fetch/src/error.rs | 6 +- wasm/mix-fetch/src/fetch.rs | 6 +- wasm/mix-fetch/src/go_bridge.rs | 2 +- wasm/mix-fetch/src/helpers.rs | 2 +- wasm/mix-fetch/src/lib.rs | 8 +- wasm/mix-fetch/src/request_writer.rs | 10 +- wasm/mix-fetch/src/socks_helpers.rs | 2 +- wasm/node-tester/Cargo.toml | 7 +- wasm/node-tester/src/ephemeral_receiver.rs | 2 +- wasm/node-tester/src/error.rs | 8 +- wasm/node-tester/src/helpers.rs | 2 +- wasm/node-tester/src/lib.rs | 2 +- wasm/node-tester/src/tester.rs | 30 +- wasm/node-tester/src/types.rs | 2 +- wasm/zknym-lib/Cargo.toml | 17 +- wasm/zknym-lib/src/bandwidth_voucher.rs | 2 +- wasm/zknym-lib/src/error.rs | 2 +- wasm/zknym-lib/src/lib.rs | 4 +- 279 files changed, 3272 insertions(+), 2492 deletions(-) create mode 100644 .github/workflows/publish-crates-io-dry-run.yml create mode 100644 .github/workflows/publish-crates-io.yml delete mode 100644 common/published-crates.txt create mode 100644 crate-publishing.md rename {sqlx-pool-guard => nym-sqlx-pool-guard}/Cargo.toml (80%) rename {sqlx-pool-guard => nym-sqlx-pool-guard}/src/apple.rs (100%) rename {sqlx-pool-guard => nym-sqlx-pool-guard}/src/lib.rs (100%) rename {sqlx-pool-guard => nym-sqlx-pool-guard}/src/linux.rs (100%) rename {sqlx-pool-guard => nym-sqlx-pool-guard}/src/windows.rs (100%) diff --git a/.github/workflows/publish-crates-io-dry-run.yml b/.github/workflows/publish-crates-io-dry-run.yml new file mode 100644 index 0000000000..2025df7bc4 --- /dev/null +++ b/.github/workflows/publish-crates-io-dry-run.yml @@ -0,0 +1,43 @@ +name: Publish to crates.io (dry run) + +on: + workflow_dispatch: + inputs: + version: + description: "Version to publish (e.g. 1.21.0)" + required: true + type: string + +jobs: + publish-dry-run: + runs-on: arc-ubuntu-22.04-dind + steps: + - name: Checkout repo + uses: actions/checkout@v6 + + - name: Install rust toolchain + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + + - name: Install cargo-workspaces + run: cargo install cargo-workspaces + + - name: Bump versions (local only) + run: | + cargo workspaces version ${{ inputs.version }} \ + --no-git-commit \ + --no-git-tag \ + --no-git-push \ + --yes + + # Note: Dry run may show cascading dependency errors because packages + # aren't actually uploaded. Check if the missing dependency has an + # "aborting upload due to dry run" message earlier in the output - if so, + # it would succeed in a real publish since cargo-workspaces publishes in + # dependency order. cargo-workspaces doesn't fail on err, so there isn't + # a good way to check this at the moment. + - name: Publish (dry run) + run: cargo workspaces publish --from-git --dry-run --allow-dirty diff --git a/.github/workflows/publish-crates-io.yml b/.github/workflows/publish-crates-io.yml new file mode 100644 index 0000000000..68a0cd064c --- /dev/null +++ b/.github/workflows/publish-crates-io.yml @@ -0,0 +1,47 @@ +name: Publish to crates.io + +on: + workflow_dispatch: + inputs: + version: + description: "Version to publish (e.g. 1.21.0)" + required: true + type: string + +jobs: + publish: + runs-on: arc-ubuntu-22.04-dind + steps: + - name: Checkout repo + uses: actions/checkout@v6 + + - name: Install rust toolchain + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + + - name: Install cargo-workspaces + run: cargo install cargo-workspaces + + # - name: Configure git + # run: | + # git config user.name "github-actions[bot]" + # git config user.email "github-actions[bot]@users.noreply.github.com" + + - name: Bump versions + run: | + cargo workspaces version ${{ inputs.version }} \ + --no-git-push \ + --no-git-tag \ + --yes + + - name: Publish + env: + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} + run: cargo workspaces publish --from-git --no-git-commit + + # - name: Push version commit + # run: | + # git push origin HEAD diff --git a/.gitignore b/.gitignore index 5f3826e012..fa4df4495d 100644 --- a/.gitignore +++ b/.gitignore @@ -75,3 +75,5 @@ docs .superego/ .claude/hooks/superego/ .claude/settings.json + +/notes diff --git a/Cargo.lock b/Cargo.lock index 1f86a54a74..30db56699b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,7 +11,7 @@ dependencies = [ "macroific", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] @@ -36,7 +36,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" dependencies = [ - "crypto-common", + "crypto-common 0.1.7", "generic-array 0.14.7", ] @@ -47,7 +47,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ "cfg-if", - "cipher", + "cipher 0.4.4", + "cpufeatures", +] + +[[package]] +name = "aes" +version = "0.9.0-rc.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd9e1c818b25efb32214df89b0ec22f01aa397aaeb718d1022bf0635a3bfd1a8" +dependencies = [ + "cfg-if", + "cipher 0.5.0-rc.3", "cpufeatures", ] @@ -58,8 +69,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" dependencies = [ "aead", - "aes", - "cipher", + "aes 0.8.4", + "cipher 0.4.4", "ctr", "ghash", "subtle 2.6.1", @@ -72,14 +83,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae0784134ba9375416d469ec31e7c5f9fa94405049cf08c5ce5b4698be673e0d" dependencies = [ "aead", - "aes", - "cipher", + "aes 0.8.4", + "cipher 0.4.4", "ctr", "polyval", "subtle 2.6.1", "zeroize", ] +[[package]] +name = "aes-keywrap" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10b6f24a1f796bc46415a1d0d18dc0a8203ccba088acf5def3291c4f61225522" +dependencies = [ + "aes 0.9.0-rc.2", + "byteorder", +] + [[package]] name = "ahash" version = "0.8.12" @@ -94,9 +115,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] @@ -124,9 +145,9 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "ammonia" -version = "4.1.1" +version = "4.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6b346764dd0814805de8abf899fe03065bcee69bb1a4771c785817e39f3978f" +checksum = "17e913097e1a2124b46746c980134e8c954bc17a6a59bb3fde96f088d126dde6" dependencies = [ "cssparser", "html5ever", @@ -135,12 +156,6 @@ dependencies = [ "url", ] -[[package]] -name = "android-tzdata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - [[package]] name = "android_system_properties" version = "0.1.5" @@ -167,9 +182,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.19" +version = "0.6.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" dependencies = [ "anstyle", "anstyle-parse", @@ -182,9 +197,9 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.11" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" [[package]] name = "anstyle-parse" @@ -197,22 +212,22 @@ dependencies = [ [[package]] name = "anstyle-query" -version = "1.1.3" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "anstyle-wincon" -version = "3.0.9" +version = "3.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -223,18 +238,21 @@ checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" [[package]] name = "arbitrary" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" dependencies = [ "derive_arbitrary", ] [[package]] name = "arc-swap" -version = "1.7.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" +checksum = "51d03449bb8ca2cc2ef70869af31463d1ae5ccc8fa3e334b307203fbf815207e" +dependencies = [ + "rustversion", +] [[package]] name = "argon2" @@ -408,7 +426,7 @@ dependencies = [ "rustc-hash", "serde", "serde_derive", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] @@ -446,27 +464,24 @@ dependencies = [ [[package]] name = "async-compression" -version = "0.4.27" +version = "0.4.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddb939d66e4ae03cee6091612804ba446b12878410cfa17f785f4dd67d4014e8" +checksum = "98ec5f6c2f8bc326c994cb9e241cc257ddaba9afa8555a43cffbb5dd86efaa37" dependencies = [ - "brotli", - "flate2", + "compression-codecs", + "compression-core", "futures-core", - "memchr", "pin-project-lite", "tokio", - "zstd", - "zstd-safe", ] [[package]] name = "async-lock" -version = "3.4.0" +version = "3.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" +checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" dependencies = [ - "event-listener 5.4.0", + "event-listener 5.4.1", "event-listener-strategy", "pin-project-lite", ] @@ -490,18 +505,18 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] name = "async-trait" -version = "0.1.88" +version = "0.1.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] @@ -567,10 +582,10 @@ dependencies = [ "axum-macros", "bytes", "futures-util", - "http 1.3.1", + "http 1.4.0", "http-body 1.0.1", "http-body-util", - "hyper 1.6.0", + "hyper 1.8.1", "hyper-util", "itoa", "matchit", @@ -611,7 +626,7 @@ dependencies = [ "async-trait", "bytes", "futures-util", - "http 1.3.1", + "http 1.4.0", "http-body 1.0.1", "http-body-util", "mime", @@ -635,7 +650,7 @@ dependencies = [ "fastrand 2.3.0", "futures-util", "headers", - "http 1.3.1", + "http 1.4.0", "http-body 1.0.1", "http-body-util", "mime", @@ -655,7 +670,7 @@ checksum = "57d123550fa8d071b7255cb0cc04dc302baa6c8c4a79f55701552684d8399bce" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] @@ -671,9 +686,9 @@ dependencies = [ "bytes", "bytesize", "cookie", - "http 1.3.1", + "http 1.4.0", "http-body-util", - "hyper 1.6.0", + "hyper 1.8.1", "hyper-util", "mime", "pretty_assertions", @@ -714,9 +729,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" -version = "1.8.0" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" +checksum = "7d809780667f4410e7c41b07f52439b94d2bdf8528eeedc287fa38d3b7f95d82" [[package]] name = "base85rs" @@ -735,9 +750,9 @@ dependencies = [ [[package]] name = "bech32" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d" +checksum = "32637268377fc7b10a8c6d51de3e7fba1ce5dd371a96e342b34e6078db558e7f" [[package]] name = "bincode" @@ -773,9 +788,9 @@ dependencies = [ [[package]] name = "bip39" -version = "2.2.0" +version = "2.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43d193de1f7487df1914d3a568b772458861d33f9c54249612cc2893d6915054" +checksum = "90dbd31c98227229239363921e60fcf5e558e43ec69094d46fc4996f08d1d5bc" dependencies = [ "bitcoin_hashes", "rand 0.8.5", @@ -785,19 +800,12 @@ dependencies = [ "zeroize", ] -[[package]] -name = "bitcoin-internals" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9425c3bf7089c983facbae04de54513cce73b41c7f9ff8c845b54e7bc64ebbfb" - [[package]] name = "bitcoin_hashes" -version = "0.13.0" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1930a4dabfebb8d7d9992db18ebe3ae2876f0a305fab206fd168df931ede293b" +checksum = "26ec84b80c482df901772e931a9a681e26a1b9ee2302edeff23cb30328745c8b" dependencies = [ - "bitcoin-internals", "hex-conservative", ] @@ -809,11 +817,11 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.1" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" dependencies = [ - "serde", + "serde_core", ] [[package]] @@ -911,22 +919,6 @@ dependencies = [ "siphasher 1.0.1", ] -[[package]] -name = "bls12_381" -version = "0.8.0" -source = "git+https://github.com/jstuczyn/bls12_381?branch=temp%2Fexperimental-serdect-updated#9bf520059cb28323fc51469cae86868ef4fa6fbd" -dependencies = [ - "digest 0.10.7", - "ff", - "group", - "pairing", - "rand_core 0.6.4", - "serde", - "serdect 0.3.0", - "subtle 2.6.1", - "zeroize", -] - [[package]] name = "bnum" version = "0.11.0" @@ -935,9 +927,9 @@ checksum = "3e31ea183f6ee62ac8b8a8cf7feddd766317adfb13ff469de57ce033efd6a790" [[package]] name = "brotli" -version = "8.0.1" +version = "8.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9991eea70ea4f293524138648e41ee89b0b2b12ddef3b255effa43c8056e0e0d" +checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -966,9 +958,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.19.0" +version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" [[package]] name = "byte-tools" @@ -994,9 +986,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.10.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" dependencies = [ "serde", ] @@ -1009,11 +1001,11 @@ checksum = "2e93abca9e28e0a1b9877922aacb20576e05d4679ffa78c3d6dc22a26a216659" [[package]] name = "camino" -version = "1.1.10" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0da45bc31171d8d6960122e222a67740df867c1dd53b4d51caa297084c185cab" +checksum = "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48" dependencies = [ - "serde", + "serde_core", ] [[package]] @@ -1033,7 +1025,7 @@ checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037" dependencies = [ "camino", "cargo-platform", - "semver 1.0.26", + "semver 1.0.27", "serde", "serde_json", "thiserror 1.0.69", @@ -1047,7 +1039,7 @@ checksum = "dd5eb614ed4c27c5d706420e4320fbe3216ab31fa1c33cd8246ac36dae4479ba" dependencies = [ "camino", "cargo-platform", - "semver 1.0.26", + "semver 1.0.27", "serde", "serde_json", "thiserror 2.0.17", @@ -1067,10 +1059,11 @@ checksum = "a2698f953def977c68f935bb0dfa959375ad4638570e969e2f1e9f433cbf1af6" [[package]] name = "cc" -version = "1.2.30" +version = "1.2.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "deec109607ca693028562ed836a5f1c4b8bd77755c4e132fc5ce11b0b6211ae7" +checksum = "7a0aeaff4ff1a90589618835a598e545176939b97874f7abc7851caa0618f203" dependencies = [ + "find-msvc-tools", "jobserver", "libc", "shlex", @@ -1082,15 +1075,15 @@ version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc3ba3c5408fae183329e0d1ac8f8eed3cb7b647590fd93e6d6288f6b09db0be" dependencies = [ - "phf", + "phf 0.11.3", "serde", ] [[package]] name = "cfg-if" -version = "1.0.1" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "cfg_aliases" @@ -1115,7 +1108,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" dependencies = [ "cfg-if", - "cipher", + "cipher 0.4.4", "cpufeatures", ] @@ -1127,24 +1120,23 @@ checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" dependencies = [ "aead", "chacha20", - "cipher", + "cipher 0.4.4", "poly1305", "zeroize", ] [[package]] name = "chrono" -version = "0.4.41" +version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" +checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" dependencies = [ - "android-tzdata", "iana-time-zone", "js-sys", "num-traits", "serde", "wasm-bindgen", - "windows-link 0.1.3", + "windows-link 0.2.1", ] [[package]] @@ -1180,16 +1172,26 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ - "crypto-common", - "inout", + "crypto-common 0.1.7", + "inout 0.1.4", "zeroize", ] [[package]] -name = "clap" -version = "4.5.41" +name = "cipher" +version = "0.5.0-rc.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be92d32e80243a54711e5d7ce823c35c41c9d929dc4ab58e1276f625841aadf9" +checksum = "98d708bac5451350d56398433b19a7889022fa9187df1a769c0edbc3b2c03167" +dependencies = [ + "crypto-common 0.2.0-rc.9", + "inout 0.2.2", +] + +[[package]] +name = "clap" +version = "4.5.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394" dependencies = [ "clap_builder", "clap_derive", @@ -1197,9 +1199,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.41" +version = "4.5.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707eab41e9622f9139419d573eca0900137718000c517d47da73045f54331c3d" +checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00" dependencies = [ "anstream", "anstyle", @@ -1209,9 +1211,9 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.5.55" +version = "4.5.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5abde44486daf70c5be8b8f8f1b66c49f86236edf6fa2abadb4d961c4c6229a" +checksum = "430b4dc2b5e3861848de79627b2bedc9f3342c7da5173a14eaa5d0f8dc18ae5d" dependencies = [ "clap", ] @@ -1228,21 +1230,21 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.41" +version = "4.5.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef4f52386a59ca4c860f7393bcf8abd8dfd91ecccc0f774635ff68e92eeef491" +checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] name = "clap_lex" -version = "0.7.5" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" +checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" [[package]] name = "classic-mceliece-rust" @@ -1283,15 +1285,35 @@ dependencies = [ [[package]] name = "comfy-table" -version = "7.1.4" +version = "7.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a65ebfec4fb190b6f90e944a817d60499ee0744e582530e2c9900a22e591d9a" +checksum = "b03b7db8e0b4b2fdad6c551e634134e99ec000e5c8c3b6856c65e8bbaded7a3b" dependencies = [ - "crossterm 0.28.1", + "crossterm 0.29.0", "unicode-segmentation", - "unicode-width 0.2.1", + "unicode-width 0.2.2", ] +[[package]] +name = "compression-codecs" +version = "0.4.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0f7ac3e5b97fdce45e8922fb05cae2c37f7bbd63d30dd94821dacfd8f3f2bf2" +dependencies = [ + "brotli", + "compression-core", + "flate2", + "memchr", + "zstd", + "zstd-safe", +] + +[[package]] +name = "compression-core" +version = "0.4.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75984efb6ed102a0d42db99afb6c1948f0380d1d91808d5529916e6c08b49d8d" + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -1303,15 +1325,15 @@ dependencies = [ [[package]] name = "console" -version = "0.16.0" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e09ced7ebbccb63b4c65413d821f2e00ce54c5ca4514ddc6b3c892fdbcbc69d" +checksum = "03e45a4a8926227e4197636ba97a9fc9b00477e9f4bd711395687c5f0734bec4" dependencies = [ "encode_unicode", "libc", "once_cell", - "unicode-width 0.2.1", - "windows-sys 0.60.2", + "unicode-width 0.2.2", + "windows-sys 0.61.2", ] [[package]] @@ -1377,9 +1399,9 @@ checksum = "3618cccc083bb987a415d85c02ca6c9994ea5b44731ec28b9ecf09658655fba9" [[package]] name = "const_format" -version = "0.2.34" +version = "0.2.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "126f97965c8ad46d6d9163268ff28432e8f6a1196a55578867832e3049df63dd" +checksum = "7faa7469a93a566e9ccc1c73fe783b4a65c274c5ace346038dca9c39fe0030ad" dependencies = [ "const_format_proc_macros", ] @@ -1473,15 +1495,15 @@ dependencies = [ [[package]] name = "cosmwasm-core" -version = "2.2.2" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35b6dc17e7fd89d0a0a58f12ef33f0bbdf09a6a14c3dfb383eae665e5889250e" +checksum = "63efd882086668c3bad621b5b897a0e058d0b5fae027c984e1a6230bf284ab85" [[package]] name = "cosmwasm-crypto" -version = "2.2.2" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa2f53285517db3e33d825b3e46301efe845135778527e1295154413b2f0469e" +checksum = "0969e6f569f867725bf89e672058ae336e130c1fcf914def4f7c6c0380b2bc83" dependencies = [ "ark-bls12-381", "ark-ec", @@ -1503,13 +1525,13 @@ dependencies = [ [[package]] name = "cosmwasm-derive" -version = "2.2.2" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a782b93fae93e57ca8ad3e9e994e784583f5933aeaaa5c80a545c4b437be2047" +checksum = "d0da0db99715fc86e6a5ad7be9c19d0d9e5a92179862b17cae7406b729a402c2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] @@ -1533,7 +1555,7 @@ checksum = "e01c9214319017f6ebd8e299036e1f717fa9bb6724e758f7d6fb2477599d1a29" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] @@ -1571,9 +1593,9 @@ dependencies = [ [[package]] name = "crc" -version = "3.3.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9710d3b3739c2e349eb44fe848ad0b7c8cb1e42bd87ee49371df2f7acaf3e675" +checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" dependencies = [ "crc-catalog", ] @@ -1698,14 +1720,15 @@ dependencies = [ [[package]] name = "crossterm" -version = "0.28.1" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" +checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.10.0", "crossterm_winapi", + "document-features", "parking_lot", - "rustix 0.38.44", + "rustix", "winapi", ] @@ -1738,15 +1761,24 @@ dependencies = [ [[package]] name = "crypto-common" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ "generic-array 0.14.7", "rand_core 0.6.4", "typenum", ] +[[package]] +name = "crypto-common" +version = "0.2.0-rc.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41b8986f836d4aeb30ccf4c9d3bd562fd716074cfd7fc4a2948359fbd21ed809" +dependencies = [ + "hybrid-array", +] + [[package]] name = "crypto-mac" version = "0.7.0" @@ -1766,7 +1798,7 @@ dependencies = [ "cssparser-macros", "dtoa-short", "itoa", - "phf", + "phf 0.11.3", "smallvec", ] @@ -1777,26 +1809,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" dependencies = [ "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] name = "csv" -version = "1.3.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acdc4883a9c96732e4733212c01447ebd805833b7275a73ca3ee080fd77afdaf" +checksum = "52cd9d68cf7efc6ddfaaee42e7288d3a99d613d4b50f76ce9827ae0c6e14f938" dependencies = [ "csv-core", "itoa", "ryu", - "serde", + "serde_core", ] [[package]] name = "csv-core" -version = "0.1.12" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d02f3b0da4c6504f86e9cd789d8dbafab48c2321be74e9987593de5a894d93d" +checksum = "704a3c26996a80471189265814dbc2c257598b96b8a7feae2d31ace646bb9782" dependencies = [ "memchr", ] @@ -1813,7 +1845,7 @@ version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" dependencies = [ - "cipher", + "cipher 0.4.4", ] [[package]] @@ -1828,24 +1860,24 @@ dependencies = [ [[package]] name = "curl" -version = "0.4.48" +version = "0.4.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e2d5c8f48d9c0c23250e52b55e82a6ab4fdba6650c931f5a0a57a43abda812b" +checksum = "79fc3b6dd0b87ba36e565715bf9a2ced221311db47bd18011676f24a6066edbc" dependencies = [ "curl-sys", "libc", "openssl-probe", "openssl-sys", "schannel", - "socket2 0.5.10", + "socket2 0.6.1", "windows-sys 0.59.0", ] [[package]] name = "curl-sys" -version = "0.4.82+curl-8.14.1" +version = "0.4.84+curl-8.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4d63638b5ec65f1a4ae945287b3fd035be4554bbaf211901159c9a2a74fb5be" +checksum = "abc4294dc41b882eaff37973c2ec3ae203d0091341ee68fbadd1d06e0c18a73b" dependencies = [ "cc", "libc", @@ -1881,7 +1913,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] @@ -1966,7 +1998,7 @@ dependencies = [ "cosmwasm-std", "cw-storage-plus", "schemars 0.8.22", - "semver 1.0.26", + "semver 1.0.27", "serde", "thiserror 1.0.69", ] @@ -2018,8 +2050,18 @@ version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" dependencies = [ - "darling_core", - "darling_macro", + "darling_core 0.20.11", + "darling_macro 0.20.11", +] + +[[package]] +name = "darling" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" +dependencies = [ + "darling_core 0.21.3", + "darling_macro 0.21.3", ] [[package]] @@ -2033,7 +2075,21 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.104", + "syn 2.0.114", +] + +[[package]] +name = "darling_core" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.114", ] [[package]] @@ -2042,9 +2098,20 @@ version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ - "darling_core", + "darling_core 0.20.11", "quote", - "syn 2.0.104", + "syn 2.0.114", +] + +[[package]] +name = "darling_macro" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" +dependencies = [ + "darling_core 0.21.3", + "quote", + "syn 2.0.114", ] [[package]] @@ -2069,9 +2136,9 @@ checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" [[package]] name = "defguard_boringtun" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81823fe5700844a2b68c3344f5c414dd1316a69590aea0b3c71b32cbb0e27d45" +checksum = "e226ae51c414f475137460063382d0bb6b5a1b1b1a2b135d4b3b830ab43f06ec" dependencies = [ "aead", "base64 0.22.1", @@ -2085,7 +2152,7 @@ dependencies = [ "nix 0.30.1", "parking_lot", "ring", - "socket2 0.6.0", + "socket2 0.6.1", "thiserror 2.0.17", "tracing", "uniffi 0.30.0", @@ -2130,7 +2197,7 @@ dependencies = [ "macroific", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] @@ -2146,12 +2213,12 @@ dependencies = [ [[package]] name = "deranged" -version = "0.4.0" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" +checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" dependencies = [ "powerfmt", - "serde", + "serde_core", ] [[package]] @@ -2167,13 +2234,13 @@ dependencies = [ [[package]] name = "derive_arbitrary" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" +checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] @@ -2191,10 +2258,10 @@ version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" dependencies = [ - "darling", + "darling 0.20.11", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] @@ -2204,7 +2271,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" dependencies = [ "derive_builder_core", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] @@ -2218,11 +2285,11 @@ dependencies = [ [[package]] name = "derive_more" -version = "2.0.1" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" dependencies = [ - "derive_more-impl 2.0.1", + "derive_more-impl 2.1.1", ] [[package]] @@ -2233,19 +2300,20 @@ checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", "unicode-xid", ] [[package]] name = "derive_more-impl" -version = "2.0.1" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "rustc_version 0.4.1", + "syn 2.0.114", "unicode-xid", ] @@ -2281,7 +2349,7 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer 0.10.4", "const-oid", - "crypto-common", + "crypto-common 0.1.7", "subtle 2.6.1", ] @@ -2314,7 +2382,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] @@ -2330,9 +2398,18 @@ dependencies = [ [[package]] name = "doc-comment" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" +checksum = "780955b8b195a21ab8e4ac6b60dd1dbdcec1dc6c51c0617964b08c81785e12c9" + +[[package]] +name = "document-features" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +dependencies = [ + "litrs", +] [[package]] name = "dotenvy" @@ -2342,9 +2419,9 @@ checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" [[package]] name = "dtoa" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6add3b8cff394282be81f3fc1a0605db594ed69890078ca6e2cab1c408bcf04" +checksum = "4c3cf4824e2d5f025c7b531afcb2325364084a16806f6d47fbc1f5fbd9960590" [[package]] name = "dtoa-short" @@ -2357,17 +2434,17 @@ dependencies = [ [[package]] name = "dyn-clone" -version = "1.0.19" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c7a8fb8a9fbf66c1f703fe16184d10ca0ee9d23be5b4436400408ba54a95005" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" [[package]] name = "easy-addr" -version = "0.1.0" +version = "1.20.1" dependencies = [ "cosmwasm-std", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] @@ -2423,12 +2500,12 @@ dependencies = [ [[package]] name = "ed25519-compact" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9b3460f44bea8cd47f45a0c70892f1eff856d97cd55358b2f73f663789f6190" +checksum = "33ce99a9e19c84beb4cc35ece85374335ccc398240712114c85038319ed709bd" dependencies = [ "ct-codecs", - "getrandom 0.2.16", + "getrandom 0.3.4", ] [[package]] @@ -2529,14 +2606,14 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] name = "env_filter" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" +checksum = "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2" dependencies = [ "log", "regex", @@ -2563,12 +2640,12 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.13" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -2599,9 +2676,9 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "event-listener" -version = "5.4.0" +version = "5.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" dependencies = [ "concurrent-queue", "parking", @@ -2614,7 +2691,7 @@ version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" dependencies = [ - "event-listener 5.4.0", + "event-listener 5.4.1", "pin-project-lite", ] @@ -2625,12 +2702,12 @@ dependencies = [ "bip39", "console_error_panic_hook", "js-sys", + "nym-wasm-storage", + "nym-wasm-utils", "serde-wasm-bindgen 0.6.5", "thiserror 2.0.17", "wasm-bindgen", "wasm-bindgen-futures", - "wasm-storage", - "wasm-utils", "zeroize", ] @@ -2659,7 +2736,7 @@ dependencies = [ "macroific", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] @@ -2695,16 +2772,22 @@ checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" [[package]] name = "filetime" -version = "0.2.25" +version = "0.2.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" +checksum = "bc0505cd1b6fa6580283f6bdf70a73fcf4aba1184038c90902b92b3dd0df63ed" dependencies = [ "cfg-if", "libc", "libredox", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] +[[package]] +name = "find-msvc-tools" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645cbb3a84e60b7531617d5ae4e57f7e27308f6445f5abf653209ea76dec8dff" + [[package]] name = "fixedbitset" version = "0.4.2" @@ -2713,9 +2796,9 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "flate2" -version = "1.1.2" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" +checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" dependencies = [ "crc32fast", "miniz_oxide", @@ -2756,9 +2839,9 @@ checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" [[package]] name = "form_urlencoded" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" dependencies = [ "percent-encoding", ] @@ -2889,7 +2972,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] @@ -2928,20 +3011,6 @@ version = "0.3.55" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" -[[package]] -name = "generator" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d18470a76cb7f8ff746cf1f7470914f900252ec36bbc40b569d74b1258446827" -dependencies = [ - "cc", - "cfg-if", - "libc", - "log", - "rustversion", - "windows 0.61.3", -] - [[package]] name = "generic-array" version = "0.12.4" @@ -2972,21 +3041,21 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "wasi 0.11.1+wasi-snapshot-preview1", + "wasi", "wasm-bindgen", ] [[package]] name = "getrandom" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "js-sys", "libc", "r-efi", - "wasi 0.14.2+wasi-0.2.4", + "wasip2", "wasm-bindgen", ] @@ -3016,7 +3085,7 @@ dependencies = [ "futures-core", "futures-sink", "gloo-utils 0.2.0", - "http 1.3.1", + "http 1.4.0", "js-sys", "pin-project", "serde", @@ -3099,7 +3168,7 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.12", - "indexmap 2.10.0", + "indexmap 2.13.0", "slab", "tokio", "tokio-util", @@ -3108,17 +3177,17 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.11" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17da50a276f1e01e0ba6c029e47b7100754904ee8a278f886546e98575380785" +checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" dependencies = [ "atomic-waker", "bytes", "fnv", "futures-core", "futures-sink", - "http 1.3.1", - "indexmap 2.10.0", + "http 1.4.0", + "indexmap 2.13.0", "slab", "tokio", "tokio-util", @@ -3127,12 +3196,13 @@ dependencies = [ [[package]] name = "half" -version = "2.6.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" dependencies = [ "cfg-if", "crunchy", + "zerocopy", ] [[package]] @@ -3176,22 +3246,28 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.4" +version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ "allocator-api2", "equivalent", "foldhash", ] +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + [[package]] name = "hashlink" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" dependencies = [ - "hashbrown 0.15.4", + "hashbrown 0.15.5", ] [[package]] @@ -3215,7 +3291,7 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] @@ -3253,7 +3329,7 @@ dependencies = [ "base64 0.22.1", "bytes", "headers-core", - "http 1.3.1", + "http 1.4.0", "httpdate", "mime", "sha1", @@ -3265,7 +3341,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4" dependencies = [ - "http 1.3.1", + "http 1.4.0", ] [[package]] @@ -3294,9 +3370,12 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hex-conservative" -version = "0.1.2" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "212ab92002354b4819390025006c897e8140934349e8635c9b077f47b4dcbd20" +checksum = "fda06d18ac606267c40c04e41b9947729bf8b9efe74bd4e82b61a5f26a510b9f" +dependencies = [ + "arrayvec", +] [[package]] name = "hex-literal" @@ -3318,18 +3397,18 @@ dependencies = [ "futures-channel", "futures-io", "futures-util", - "h2 0.4.11", - "http 1.3.1", + "h2 0.4.13", + "http 1.4.0", "idna", "ipnet", "once_cell", "rand 0.9.2", "ring", - "rustls 0.23.29", + "rustls 0.23.36", "thiserror 2.0.17", "tinyvec", "tokio", - "tokio-rustls 0.26.2", + "tokio-rustls 0.26.4", "tracing", "url", "webpki-roots 0.26.11", @@ -3350,11 +3429,11 @@ dependencies = [ "parking_lot", "rand 0.9.2", "resolv-conf", - "rustls 0.23.29", + "rustls 0.23.36", "smallvec", "thiserror 2.0.17", "tokio", - "tokio-rustls 0.26.2", + "tokio-rustls 0.26.4", "tracing", "webpki-roots 0.26.11", ] @@ -3391,9 +3470,9 @@ dependencies = [ [[package]] name = "hmac-sha1-compact" -version = "1.1.5" +version = "1.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18492c9f6f9a560e0d346369b665ad2bdbc89fa9bceca75796584e79042694c3" +checksum = "d1a4e188bd5d537801721276d1771f1cea235cd94961ddb2828eb76e16688356" [[package]] name = "hmac-sha256" @@ -3415,11 +3494,11 @@ dependencies = [ [[package]] name = "home" -version = "0.5.11" +version = "0.5.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -3446,12 +3525,11 @@ dependencies = [ [[package]] name = "http" -version = "1.3.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" dependencies = [ "bytes", - "fnv", "itoa", ] @@ -3473,7 +3551,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http 1.3.1", + "http 1.4.0", ] [[package]] @@ -3484,7 +3562,7 @@ checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", "futures-core", - "http 1.3.1", + "http 1.4.0", "http-body 1.0.1", "pin-project-lite", ] @@ -3525,9 +3603,9 @@ checksum = "f58b778a5761513caf593693f8951c97a5b610841e754788400f32102eefdff1" [[package]] name = "humantime" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b112acc8b3adf4b107a8ec20977da0273a8c386765a3ec0229bd500a1443f9f" +checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424" [[package]] name = "humantime-serde" @@ -3539,6 +3617,15 @@ dependencies = [ "serde", ] +[[package]] +name = "hybrid-array" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f471e0a81b2f90ffc0cb2f951ae04da57de8baa46fa99112b062a5173a5088d0" +dependencies = [ + "typenum", +] + [[package]] name = "hyper" version = "0.14.32" @@ -3565,20 +3652,22 @@ dependencies = [ [[package]] name = "hyper" -version = "1.6.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" +checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" dependencies = [ + "atomic-waker", "bytes", "futures-channel", - "futures-util", - "h2 0.4.11", - "http 1.3.1", + "futures-core", + "h2 0.4.13", + "http 1.4.0", "http-body 1.0.1", "httparse", "httpdate", "itoa", "pin-project-lite", + "pin-utils", "smallvec", "tokio", "want", @@ -3604,15 +3693,15 @@ version = "0.27.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" dependencies = [ - "http 1.3.1", - "hyper 1.6.0", + "http 1.4.0", + "hyper 1.8.1", "hyper-util", - "rustls 0.23.29", + "rustls 0.23.36", "rustls-pki-types", "tokio", - "tokio-rustls 0.26.2", + "tokio-rustls 0.26.4", "tower-service", - "webpki-roots 1.0.2", + "webpki-roots 1.0.5", ] [[package]] @@ -3621,7 +3710,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" dependencies = [ - "hyper 1.6.0", + "hyper 1.8.1", "hyper-util", "pin-project-lite", "tokio", @@ -3630,23 +3719,23 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.16" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d9b05277c7e8da2c93a568989bb6207bef0112e8d17df7a6eda4a3cf143bc5e" +checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f" dependencies = [ "base64 0.22.1", "bytes", "futures-channel", "futures-core", "futures-util", - "http 1.3.1", + "http 1.4.0", "http-body 1.0.1", - "hyper 1.6.0", + "hyper 1.8.1", "ipnet", "libc", "percent-encoding", "pin-project-lite", - "socket2 0.6.0", + "socket2 0.6.1", "tokio", "tower-service", "tracing", @@ -3654,9 +3743,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.63" +version = "0.1.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -3664,7 +3753,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core 0.61.2", + "windows-core 0.62.2", ] [[package]] @@ -3711,9 +3800,9 @@ dependencies = [ [[package]] name = "icu_collections" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" dependencies = [ "displaydoc", "potential_utf", @@ -3724,9 +3813,9 @@ dependencies = [ [[package]] name = "icu_locale_core" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" dependencies = [ "displaydoc", "litemap", @@ -3737,11 +3826,10 @@ dependencies = [ [[package]] name = "icu_normalizer" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" dependencies = [ - "displaydoc", "icu_collections", "icu_normalizer_data", "icu_properties", @@ -3752,42 +3840,38 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" [[package]] name = "icu_properties" -version = "2.0.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" dependencies = [ - "displaydoc", "icu_collections", "icu_locale_core", "icu_properties_data", "icu_provider", - "potential_utf", "zerotrie", "zerovec", ] [[package]] name = "icu_properties_data" -version = "2.0.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" [[package]] name = "icu_provider" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" dependencies = [ "displaydoc", "icu_locale_core", - "stable_deref_trait", - "tinystr", "writeable", "yoke", "zerofrom", @@ -3803,9 +3887,9 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" -version = "1.0.3" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" dependencies = [ "idna_adapter", "smallvec", @@ -3830,7 +3914,7 @@ checksum = "0ab604ee7085efba6efc65e4ebca0e9533e3aff6cb501d7d77b211e3a781c6d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] @@ -3864,9 +3948,9 @@ dependencies = [ [[package]] name = "indenter" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" +checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5" [[package]] name = "indexed_db_futures" @@ -3877,7 +3961,7 @@ dependencies = [ "accessory", "cfg-if", "delegate-display", - "derive_more 2.0.1", + "derive_more 2.1.1", "fancy_constructor", "indexed_db_futures_macros_internal", "js-sys", @@ -3899,7 +3983,7 @@ dependencies = [ "macroific", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] @@ -3915,24 +3999,25 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.10.0" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" dependencies = [ "equivalent", - "hashbrown 0.15.4", + "hashbrown 0.16.1", "serde", + "serde_core", ] [[package]] name = "indicatif" -version = "0.18.0" +version = "0.18.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70a646d946d06bedbbc4cac4c218acf4bbf2d87757a784857025f4d447e4e1cd" +checksum = "9375e112e4b463ec1b1c6c011953545c65a30164fbab5b581df32b3abf0dcb88" dependencies = [ "console", "portable-atomic", - "unicode-width 0.2.1", + "unicode-width 0.2.2", "unit-prefix", "vt100", "web-time", @@ -3978,6 +4063,15 @@ dependencies = [ "generic-array 0.14.7", ] +[[package]] +name = "inout" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4250ce6452e92010fdf7268ccc5d14faa80bb12fc741938534c58f16804e03c7" +dependencies = [ + "hybrid-array", +] + [[package]] name = "inquire" version = "0.6.2" @@ -4067,7 +4161,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" dependencies = [ "socket2 0.5.10", - "widestring 1.2.0", + "widestring 1.2.1", "windows-sys 0.48.0", "winreg", ] @@ -4089,9 +4183,9 @@ dependencies = [ [[package]] name = "iri-string" -version = "0.7.8" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" +checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" dependencies = [ "memchr", "serde", @@ -4099,20 +4193,20 @@ dependencies = [ [[package]] name = "is-terminal" -version = "0.4.16" +version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" +checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "is_terminal_polyfill" -version = "1.70.1" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] name = "isahc" @@ -4159,49 +4253,49 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.15" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "jiff" -version = "0.2.15" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49" +checksum = "e67e8da4c49d6d9909fe03361f9b620f58898859f5c7aded68351e85e71ecf50" dependencies = [ "jiff-static", "log", "portable-atomic", "portable-atomic-util", - "serde", + "serde_core", ] [[package]] name = "jiff-static" -version = "0.2.15" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4" +checksum = "e0c84ee7f197eca9a86c6fd6cb771e55eb991632f15f2bc3ca6ec838929e6e78" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] name = "jobserver" -version = "0.1.33" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" dependencies = [ - "getrandom 0.3.3", + "getrandom 0.3.4", "libc", ] [[package]] name = "js-sys" -version = "0.3.77" +version = "0.3.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" dependencies = [ "once_cell", "wasm-bindgen", @@ -4330,9 +4424,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.174" +version = "0.2.180" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" [[package]] name = "libcrux-aesgcm" @@ -4449,7 +4543,7 @@ version = "0.0.3" source = "git+https://github.com/cryspen/libcrux#21cf9cae922f55ca2394641d5ee7bbabfd36ace0" dependencies = [ "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] @@ -4586,13 +4680,13 @@ checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "libredox" -version = "0.1.6" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4488594b9328dee448adb906d8b126d9b7deb7cf5c22161ee591610bb1be83c0" +checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.10.0", "libc", - "redox_syscall", + "redox_syscall 0.7.0", ] [[package]] @@ -4608,9 +4702,9 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.22" +version = "1.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b70e7a7df205e92a1a4cd9aaae7898dac0aa555503cc0a649494d0d60e7651d" +checksum = "15d118bbf3771060e7311cc7bb0545b01d08a8b4a7de949198dec1fa0ca1c0f7" dependencies = [ "cc", "libc", @@ -4620,15 +4714,9 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.4.15" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" - -[[package]] -name = "linux-raw-sys" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" [[package]] name = "lioness" @@ -4644,38 +4732,30 @@ dependencies = [ [[package]] name = "litemap" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" + +[[package]] +name = "litrs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" [[package]] name = "lock_api" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" dependencies = [ - "autocfg", "scopeguard", ] [[package]] name = "log" -version = "0.4.27" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" - -[[package]] -name = "loom" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" -dependencies = [ - "cfg-if", - "generator", - "scoped-tls", - "tracing", - "tracing-subscriber", -] +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "lru-slab" @@ -4709,7 +4789,7 @@ dependencies = [ "proc-macro2", "quote", "sealed", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] @@ -4721,7 +4801,7 @@ dependencies = [ "proc-macro2", "quote", "sealed", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] @@ -4734,7 +4814,7 @@ dependencies = [ "macroific_core", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] @@ -4762,7 +4842,7 @@ checksum = "ac84fd3f360fcc43dc5f5d186f02a94192761a080e8bc58621ad4d12296a58cf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] @@ -4792,9 +4872,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.5" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "memoffset" @@ -4823,9 +4903,9 @@ dependencies = [ [[package]] name = "minicov" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f27fe9f1cc3c22e1687f9446c2083c4c5fc7f0bcf1c7a86bdbded14985895b4b" +checksum = "4869b6a491569605d66d3952bcdf03df789e5b536e5f0cf7758a7f08a55ae24d" dependencies = [ "cc", "walkdir", @@ -4844,6 +4924,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", + "simd-adler32", ] [[package]] @@ -4854,19 +4935,19 @@ checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", "log", - "wasi 0.11.1+wasi-snapshot-preview1", + "wasi", "windows-sys 0.48.0", ] [[package]] name = "mio" -version = "1.0.4" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" dependencies = [ "libc", - "wasi 0.11.1+wasi-snapshot-preview1", - "windows-sys 0.59.0", + "wasi", + "windows-sys 0.61.2", ] [[package]] @@ -4882,6 +4963,8 @@ dependencies = [ "nym-service-providers-common", "nym-socks5-requests", "nym-validator-client", + "nym-wasm-client-core", + "nym-wasm-utils", "rand 0.8.5", "serde", "serde-wasm-bindgen 0.6.5", @@ -4891,8 +4974,6 @@ dependencies = [ "url", "wasm-bindgen", "wasm-bindgen-futures", - "wasm-client-core", - "wasm-utils", ] [[package]] @@ -4918,23 +4999,21 @@ checksum = "dce6dd36094cac388f119d2e9dc82dc730ef91c32a6222170d630e5414b956e6" [[package]] name = "moka" -version = "0.12.10" +version = "0.12.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9321642ca94a4282428e6ea4af8cc2ca4eac48ac7a6a4ea8f33f76d0ce70926" +checksum = "a3dec6bd31b08944e08b58fd99373893a6c17054d6f3ea5006cc894f4f4eee2a" dependencies = [ "async-lock", "crossbeam-channel", "crossbeam-epoch", "crossbeam-utils", - "event-listener 5.4.0", + "equivalent", + "event-listener 5.4.1", "futures-util", - "loom", "parking_lot", "portable-atomic", - "rustc_version 0.4.1", "smallvec", "tagptr", - "thiserror 1.0.69", "uuid", ] @@ -4947,7 +5026,7 @@ dependencies = [ "bytes", "encoding_rs", "futures-util", - "http 1.3.1", + "http 1.4.0", "httparse", "memchr", "mime", @@ -4979,7 +5058,7 @@ version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ec2f5b6839be2a19d7fa5aab5bc444380f6311c2b693551cb80f45caaa7b5ef" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.10.0", "libc", "log", "netlink-packet-core", @@ -5040,7 +5119,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.10.0", "cfg-if", "cfg_aliases", "libc", @@ -5052,7 +5131,7 @@ version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.10.0", "cfg-if", "cfg_aliases", "libc", @@ -5107,9 +5186,9 @@ dependencies = [ [[package]] name = "ntapi" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" +checksum = "c70f219e21142367c70c0b30c6a9e3a14d55b4d12a204d897fbec83a0363f081" dependencies = [ "winapi", ] @@ -5126,11 +5205,11 @@ dependencies = [ [[package]] name = "nu-ansi-term" -version = "0.50.1" +version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4a28e057d01f97e61255210fcff094d74ed0466038633e95017f5beb68e4399" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -5145,11 +5224,10 @@ dependencies = [ [[package]] name = "num-bigint-dig" -version = "0.8.4" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" dependencies = [ - "byteorder", "lazy_static", "libm", "num-integer", @@ -5225,7 +5303,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] @@ -5239,7 +5317,7 @@ dependencies = [ [[package]] name = "nym-api" -version = "1.1.71" +version = "1.20.1" dependencies = [ "anyhow", "async-trait", @@ -5295,9 +5373,9 @@ dependencies = [ "pin-project", "rand 0.8.5", "rand_chacha 0.3.1", - "reqwest 0.12.22", + "reqwest 0.12.28", "schemars 0.8.22", - "semver 1.0.26", + "semver 1.0.27", "serde", "serde_json", "sha2 0.10.9", @@ -5322,7 +5400,7 @@ dependencies = [ [[package]] name = "nym-api-requests" -version = "0.1.0" +version = "1.20.1" dependencies = [ "bs58", "cosmrs", @@ -5360,7 +5438,7 @@ dependencies = [ [[package]] name = "nym-async-file-watcher" -version = "0.1.0" +version = "1.20.1" dependencies = [ "futures", "log", @@ -5370,7 +5448,7 @@ dependencies = [ [[package]] name = "nym-authenticator-client" -version = "0.1.0" +version = "1.20.1" dependencies = [ "bincode", "futures", @@ -5383,7 +5461,7 @@ dependencies = [ "nym-service-provider-requests-common", "nym-validator-client", "nym-wireguard-types", - "semver 1.0.26", + "semver 1.0.27", "thiserror 2.0.17", "tokio", "tokio-util", @@ -5392,7 +5470,7 @@ dependencies = [ [[package]] name = "nym-authenticator-requests" -version = "0.1.0" +version = "1.20.1" dependencies = [ "base64 0.22.1", "bincode", @@ -5405,7 +5483,7 @@ dependencies = [ "nym-test-utils", "nym-wireguard-types", "rand 0.8.5", - "semver 1.0.26", + "semver 1.0.27", "serde", "sha2 0.10.9", "strum_macros", @@ -5416,7 +5494,7 @@ dependencies = [ [[package]] name = "nym-bandwidth-controller" -version = "0.1.0" +version = "1.20.1" dependencies = [ "async-trait", "log", @@ -5433,7 +5511,7 @@ dependencies = [ [[package]] name = "nym-bin-common" -version = "0.6.0" +version = "1.20.1" dependencies = [ "clap", "clap_complete", @@ -5453,9 +5531,26 @@ dependencies = [ "vergen 8.3.1", ] +[[package]] +name = "nym-bls12_381-fork" +version = "0.8.0-forked" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce84633751030f960a2fd167b5270ec21da4c40d9b6400e1b56676a682fe6f3d" +dependencies = [ + "digest 0.10.7", + "ff", + "group", + "pairing", + "rand_core 0.6.4", + "serde", + "serdect 0.3.0", + "subtle 2.6.1", + "zeroize", +] + [[package]] name = "nym-cache" -version = "0.1.0" +version = "1.20.1" dependencies = [ "tokio", ] @@ -5486,7 +5581,7 @@ dependencies = [ [[package]] name = "nym-cli-commands" -version = "1.0.0" +version = "1.20.1" dependencies = [ "anyhow", "base64 0.22.1", @@ -5582,7 +5677,7 @@ dependencies = [ [[package]] name = "nym-client-core" -version = "1.1.15" +version = "1.20.1" dependencies = [ "async-trait", "base64 0.22.1", @@ -5594,7 +5689,7 @@ dependencies = [ "gloo-timers", "http-body-util", "humantime", - "hyper 1.6.0", + "hyper 1.8.1", "hyper-util", "nym-bandwidth-controller", "nym-client-core-config-types", @@ -5618,6 +5713,7 @@ dependencies = [ "nym-task", "nym-topology", "nym-validator-client", + "nym-wasm-utils", "rand 0.8.5", "rand_chacha 0.3.1", "serde", @@ -5636,14 +5732,13 @@ dependencies = [ "url", "wasm-bindgen", "wasm-bindgen-futures", - "wasm-utils", "wasmtimer", "zeroize", ] [[package]] name = "nym-client-core-config-types" -version = "0.1.0" +version = "1.20.1" dependencies = [ "humantime-serde", "nym-config", @@ -5658,7 +5753,7 @@ dependencies = [ [[package]] name = "nym-client-core-gateways-storage" -version = "0.1.0" +version = "1.20.1" dependencies = [ "anyhow", "async-trait", @@ -5677,16 +5772,16 @@ dependencies = [ [[package]] name = "nym-client-core-surb-storage" -version = "0.1.0" +version = "1.20.1" dependencies = [ "anyhow", "async-trait", "dashmap", "nym-crypto", "nym-sphinx", + "nym-sqlx-pool-guard", "nym-task", "sqlx", - "sqlx-pool-guard", "thiserror 2.0.17", "time", "tokio", @@ -5705,6 +5800,8 @@ dependencies = [ "nym-gateway-requests", "nym-node-tester-utils", "nym-node-tester-wasm", + "nym-wasm-client-core", + "nym-wasm-utils", "once_cell", "rand 0.8.5", "serde", @@ -5716,14 +5813,12 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "wasm-bindgen-test", - "wasm-client-core", - "wasm-utils", "web-sys", ] [[package]] name = "nym-client-websocket-requests" -version = "0.1.0" +version = "1.20.1" dependencies = [ "nym-sphinx", "serde", @@ -5732,7 +5827,7 @@ dependencies = [ [[package]] name = "nym-coconut-dkg-common" -version = "0.1.0" +version = "1.20.1" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -5745,7 +5840,7 @@ dependencies = [ [[package]] name = "nym-common" -version = "1.18.0" +version = "1.20.1" dependencies = [ "tracing", "tracing-test", @@ -5753,10 +5848,9 @@ dependencies = [ [[package]] name = "nym-compact-ecash" -version = "0.1.0" +version = "1.20.1" dependencies = [ "bincode", - "bls12_381", "bs58", "cfg-if", "criterion", @@ -5764,6 +5858,7 @@ dependencies = [ "ff", "group", "itertools 0.14.0", + "nym-bls12_381-fork", "nym-network-defaults", "nym-pemstore", "rand 0.8.5", @@ -5777,7 +5872,7 @@ dependencies = [ [[package]] name = "nym-config" -version = "0.1.0" +version = "1.20.1" dependencies = [ "dirs", "handlebars", @@ -5809,7 +5904,7 @@ dependencies = [ [[package]] name = "nym-contracts-common" -version = "0.5.0" +version = "1.20.1" dependencies = [ "anyhow", "bs58", @@ -5826,7 +5921,7 @@ dependencies = [ [[package]] name = "nym-contracts-common-testing" -version = "0.1.0" +version = "1.20.1" dependencies = [ "anyhow", "cosmwasm-std", @@ -5840,7 +5935,7 @@ dependencies = [ [[package]] name = "nym-cpp-ffi" -version = "0.1.2" +version = "1.20.1" dependencies = [ "anyhow", "bs58", @@ -5881,7 +5976,7 @@ dependencies = [ "nym-upgrade-mode-check", "nym-validator-client", "rand 0.8.5", - "reqwest 0.12.22", + "reqwest 0.12.28", "serde", "serde_json", "sqlx", @@ -5903,7 +5998,7 @@ dependencies = [ [[package]] name = "nym-credential-proxy-lib" -version = "0.1.0" +version = "1.20.1" dependencies = [ "anyhow", "axum", @@ -5922,7 +6017,7 @@ dependencies = [ "nym-network-defaults", "nym-validator-client", "rand 0.8.5", - "reqwest 0.12.22", + "reqwest 0.12.28", "serde", "serde_json", "sqlx", @@ -5941,7 +6036,7 @@ dependencies = [ [[package]] name = "nym-credential-proxy-requests" -version = "0.1.0" +version = "1.20.1" dependencies = [ "async-trait", "nym-credentials", @@ -5950,7 +6045,7 @@ dependencies = [ "nym-http-api-common", "nym-serde-helpers", "nym-upgrade-mode-check", - "reqwest 0.12.22", + "reqwest 0.12.28", "schemars 0.8.22", "serde", "serde_json", @@ -5965,7 +6060,7 @@ dependencies = [ [[package]] name = "nym-credential-storage" -version = "0.1.0" +version = "1.20.1" dependencies = [ "anyhow", "async-trait", @@ -5976,10 +6071,10 @@ dependencies = [ "nym-credentials-interface", "nym-crypto", "nym-ecash-time", + "nym-sqlx-pool-guard", "nym-test-utils", "serde", "sqlx", - "sqlx-pool-guard", "thiserror 2.0.17", "time", "tokio", @@ -5988,7 +6083,7 @@ dependencies = [ [[package]] name = "nym-credential-utils" -version = "0.1.0" +version = "1.20.1" dependencies = [ "log", "nym-bandwidth-controller", @@ -6006,7 +6101,7 @@ dependencies = [ [[package]] name = "nym-credential-verification" -version = "0.1.0" +version = "1.20.1" dependencies = [ "async-trait", "bs58", @@ -6034,13 +6129,13 @@ dependencies = [ [[package]] name = "nym-credentials" -version = "0.1.0" +version = "1.20.1" dependencies = [ "bincode", - "bls12_381", "cosmrs", "log", "nym-api-requests", + "nym-bls12_381-fork", "nym-credentials-interface", "nym-crypto", "nym-ecash-contract-common", @@ -6058,9 +6153,9 @@ dependencies = [ [[package]] name = "nym-credentials-interface" -version = "0.1.0" +version = "1.20.1" dependencies = [ - "bls12_381", + "nym-bls12_381-fork", "nym-compact-ecash", "nym-ecash-time", "nym-network-defaults", @@ -6076,16 +6171,16 @@ dependencies = [ [[package]] name = "nym-crypto" -version = "0.4.0" +version = "1.20.1" dependencies = [ "aead", - "aes", + "aes 0.8.4", "aes-gcm-siv", "anyhow", "base64 0.22.1", "blake3", "bs58", - "cipher", + "cipher 0.4.4", "ctr", "curve25519-dalek", "digest 0.10.7", @@ -6128,7 +6223,7 @@ dependencies = [ "nym-validator-client", "nyxd-scraper-psql", "nyxd-scraper-shared", - "reqwest 0.12.22", + "reqwest 0.12.28", "schemars 0.8.22", "serde", "serde_json", @@ -6148,14 +6243,14 @@ dependencies = [ [[package]] name = "nym-dkg" -version = "0.1.0" +version = "1.20.1" dependencies = [ "bitvec", - "bls12_381", "bs58", "criterion", "ff", "group", + "nym-bls12_381-fork", "nym-contracts-common", "nym-pemstore", "rand 0.8.5", @@ -6170,7 +6265,7 @@ dependencies = [ [[package]] name = "nym-ecash-contract-common" -version = "0.1.0" +version = "1.20.1" dependencies = [ "bs58", "cosmwasm-schema", @@ -6184,14 +6279,14 @@ dependencies = [ [[package]] name = "nym-ecash-signer-check" -version = "0.1.0" +version = "1.20.1" dependencies = [ "futures", "nym-ecash-signer-check-types", "nym-http-api-client", "nym-network-defaults", "nym-validator-client", - "semver 1.0.26", + "semver 1.0.27", "thiserror 2.0.17", "tokio", "tracing", @@ -6200,11 +6295,11 @@ dependencies = [ [[package]] name = "nym-ecash-signer-check-types" -version = "0.1.0" +version = "1.20.1" dependencies = [ "nym-coconut-dkg-common", "nym-crypto", - "semver 1.0.26", + "semver 1.0.27", "serde", "thiserror 2.0.17", "time", @@ -6215,7 +6310,7 @@ dependencies = [ [[package]] name = "nym-ecash-time" -version = "0.1.0" +version = "1.20.1" dependencies = [ "nym-compact-ecash", "time", @@ -6223,9 +6318,9 @@ dependencies = [ [[package]] name = "nym-exit-policy" -version = "0.1.0" +version = "1.20.1" dependencies = [ - "reqwest 0.12.22", + "reqwest 0.12.28", "serde", "serde_json", "thiserror 2.0.17", @@ -6235,7 +6330,7 @@ dependencies = [ [[package]] name = "nym-ffi-shared" -version = "0.2.1" +version = "1.20.1" dependencies = [ "anyhow", "bs58", @@ -6245,8 +6340,8 @@ dependencies = [ "nym-sdk", "nym-sphinx-anonymous-replies", "tokio", - "uniffi 0.29.3", - "uniffi_build 0.29.3", + "uniffi 0.29.5", + "uniffi_build 0.29.5", ] [[package]] @@ -6311,7 +6406,7 @@ dependencies = [ [[package]] name = "nym-gateway-client" -version = "0.1.0" +version = "1.20.1" dependencies = [ "futures", "getrandom 0.2.16", @@ -6329,6 +6424,7 @@ dependencies = [ "nym-statistics-common", "nym-task", "nym-validator-client", + "nym-wasm-utils", "rand 0.8.5", "serde", "si-scale", @@ -6342,7 +6438,6 @@ dependencies = [ "url", "wasm-bindgen", "wasm-bindgen-futures", - "wasm-utils", "wasmtimer", "zeroize", ] @@ -6402,7 +6497,7 @@ dependencies = [ [[package]] name = "nym-gateway-requests" -version = "0.1.0" +version = "1.20.1" dependencies = [ "anyhow", "bs58", @@ -6434,7 +6529,7 @@ dependencies = [ [[package]] name = "nym-gateway-stats-storage" -version = "0.1.0" +version = "1.20.1" dependencies = [ "anyhow", "nym-node-metrics", @@ -6450,7 +6545,7 @@ dependencies = [ [[package]] name = "nym-gateway-storage" -version = "0.1.0" +version = "1.20.1" dependencies = [ "anyhow", "async-trait", @@ -6469,7 +6564,7 @@ dependencies = [ [[package]] name = "nym-go-ffi" -version = "0.2.2" +version = "1.20.1" dependencies = [ "anyhow", "lazy_static", @@ -6480,13 +6575,13 @@ dependencies = [ "nym-sphinx-anonymous-replies", "thiserror 2.0.17", "tokio", - "uniffi 0.29.3", - "uniffi_build 0.29.3", + "uniffi 0.29.5", + "uniffi_build 0.29.5", ] [[package]] name = "nym-group-contract-common" -version = "0.1.0" +version = "1.20.1" dependencies = [ "cosmwasm-schema", "cw-controllers", @@ -6497,7 +6592,7 @@ dependencies = [ [[package]] name = "nym-http-api-client" -version = "0.1.0" +version = "1.20.1" dependencies = [ "async-trait", "bincode", @@ -6505,7 +6600,7 @@ dependencies = [ "cfg-if", "encoding_rs", "hickory-resolver", - "http 1.3.1", + "http 1.4.0", "inventory", "itertools 0.14.0", "mime", @@ -6514,7 +6609,7 @@ dependencies = [ "nym-http-api-common", "nym-network-defaults", "once_cell", - "reqwest 0.12.22", + "reqwest 0.12.28", "serde", "serde_json", "serde_plain", @@ -6529,20 +6624,20 @@ dependencies = [ [[package]] name = "nym-http-api-client-macro" -version = "0.1.0" +version = "1.20.1" dependencies = [ "nym-http-api-client", "proc-macro-crate", "proc-macro2", "quote", - "reqwest 0.12.22", - "syn 2.0.104", + "reqwest 0.12.28", + "syn 2.0.114", "uuid", ] [[package]] name = "nym-http-api-common" -version = "0.1.0" +version = "1.20.1" dependencies = [ "axum", "axum-client-ip", @@ -6564,7 +6659,7 @@ dependencies = [ [[package]] name = "nym-id" -version = "0.1.0" +version = "1.20.1" dependencies = [ "nym-credential-storage", "nym-credentials", @@ -6590,7 +6685,7 @@ dependencies = [ [[package]] name = "nym-inclusion-probability" -version = "0.1.0" +version = "1.20.1" dependencies = [ "log", "rand 0.8.5", @@ -6599,7 +6694,7 @@ dependencies = [ [[package]] name = "nym-ip-packet-client" -version = "0.1.0" +version = "1.20.1" dependencies = [ "bincode", "bytes", @@ -6614,7 +6709,7 @@ dependencies = [ [[package]] name = "nym-ip-packet-requests" -version = "0.1.0" +version = "1.20.1" dependencies = [ "bincode", "bytes", @@ -6632,7 +6727,7 @@ dependencies = [ [[package]] name = "nym-ip-packet-router" -version = "0.1.0" +version = "1.20.1" dependencies = [ "anyhow", "async-trait", @@ -6663,7 +6758,7 @@ dependencies = [ "nym-wireguard", "nym-wireguard-types", "rand 0.8.5", - "reqwest 0.12.22", + "reqwest 0.12.28", "serde", "serde_json", "thiserror 2.0.17", @@ -6708,7 +6803,7 @@ dependencies = [ [[package]] name = "nym-ledger" -version = "0.1.0" +version = "1.20.1" dependencies = [ "bip32", "k256", @@ -6794,7 +6889,7 @@ dependencies = [ [[package]] name = "nym-metrics" -version = "0.1.0" +version = "1.20.1" dependencies = [ "dashmap", "lazy_static", @@ -6804,7 +6899,7 @@ dependencies = [ [[package]] name = "nym-mixnet-client" -version = "0.1.0" +version = "1.20.1" dependencies = [ "dashmap", "futures", @@ -6821,7 +6916,7 @@ dependencies = [ [[package]] name = "nym-mixnet-contract-common" -version = "0.6.0" +version = "1.20.1" dependencies = [ "bs58", "cosmwasm-schema", @@ -6833,7 +6928,7 @@ dependencies = [ "nym-contracts-common", "rand_chacha 0.3.1", "schemars 0.8.22", - "semver 1.0.26", + "semver 1.0.27", "serde", "serde_repr", "thiserror 2.0.17", @@ -6844,7 +6939,7 @@ dependencies = [ [[package]] name = "nym-mixnode-common" -version = "0.1.0" +version = "1.20.1" dependencies = [ "bytes", "futures", @@ -6871,7 +6966,7 @@ dependencies = [ [[package]] name = "nym-multisig-contract-common" -version = "0.1.0" +version = "1.20.1" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -6886,7 +6981,7 @@ dependencies = [ [[package]] name = "nym-network-defaults" -version = "0.1.0" +version = "1.20.1" dependencies = [ "cargo_metadata 0.19.2", "dotenvy", @@ -6923,7 +7018,7 @@ dependencies = [ "petgraph", "rand 0.8.5", "rand_chacha 0.3.1", - "reqwest 0.12.22", + "reqwest 0.12.28", "serde", "serde_json", "tokio", @@ -6969,7 +7064,7 @@ dependencies = [ "publicsuffix", "rand 0.8.5", "regex", - "reqwest 0.12.22", + "reqwest 0.12.28", "serde", "serde_json", "sqlx", @@ -7071,7 +7166,7 @@ dependencies = [ [[package]] name = "nym-node-metrics" -version = "0.1.0" +version = "1.20.1" dependencies = [ "dashmap", "futures", @@ -7085,7 +7180,7 @@ dependencies = [ [[package]] name = "nym-node-requests" -version = "0.1.0" +version = "1.20.1" dependencies = [ "async-trait", "celes", @@ -7164,8 +7259,8 @@ dependencies = [ "rand 0.8.5", "rand_chacha 0.3.1", "regex", - "reqwest 0.12.22", - "semver 1.0.26", + "reqwest 0.12.28", + "semver 1.0.27", "serde", "serde_json", "serde_json_path", @@ -7196,7 +7291,7 @@ dependencies = [ "bs58", "nym-credentials", "nym-crypto", - "reqwest 0.12.22", + "reqwest 0.12.28", "serde", "serde_json", "time", @@ -7205,7 +7300,7 @@ dependencies = [ [[package]] name = "nym-node-tester-utils" -version = "0.1.0" +version = "1.20.1" dependencies = [ "futures", "log", @@ -7214,13 +7309,13 @@ dependencies = [ "nym-sphinx-params", "nym-task", "nym-topology", + "nym-wasm-utils", "rand 0.8.5", "rand_chacha 0.3.1", "serde", "serde_json", "thiserror 2.0.17", "tokio", - "wasm-utils", ] [[package]] @@ -7230,6 +7325,8 @@ dependencies = [ "futures", "js-sys", "nym-node-tester-utils", + "nym-wasm-client-core", + "nym-wasm-utils", "rand 0.8.5", "serde", "serde-wasm-bindgen 0.6.5", @@ -7238,14 +7335,12 @@ dependencies = [ "tsify", "wasm-bindgen", "wasm-bindgen-futures", - "wasm-client-core", - "wasm-utils", "wasmtimer", ] [[package]] name = "nym-noise" -version = "0.1.0" +version = "1.20.1" dependencies = [ "anyhow", "arc-swap", @@ -7268,7 +7363,7 @@ dependencies = [ [[package]] name = "nym-noise-keys" -version = "0.1.0" +version = "1.20.1" dependencies = [ "nym-crypto", "schemars 0.8.22", @@ -7278,7 +7373,7 @@ dependencies = [ [[package]] name = "nym-nonexhaustive-delayqueue" -version = "0.1.0" +version = "1.20.1" dependencies = [ "tokio", "tokio-stream", @@ -7304,7 +7399,7 @@ dependencies = [ [[package]] name = "nym-ordered-buffer" -version = "0.1.0" +version = "1.20.1" dependencies = [ "log", "thiserror 2.0.17", @@ -7312,7 +7407,7 @@ dependencies = [ [[package]] name = "nym-outfox" -version = "0.1.0" +version = "1.20.1" dependencies = [ "blake3", "chacha20", @@ -7331,7 +7426,7 @@ dependencies = [ [[package]] name = "nym-pemstore" -version = "0.3.0" +version = "1.20.1" dependencies = [ "pem", "tracing", @@ -7340,7 +7435,7 @@ dependencies = [ [[package]] name = "nym-performance-contract-common" -version = "0.1.0" +version = "1.20.1" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -7353,7 +7448,7 @@ dependencies = [ [[package]] name = "nym-pool-contract-common" -version = "0.1.0" +version = "1.20.1" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -7366,7 +7461,7 @@ dependencies = [ [[package]] name = "nym-registration-client" -version = "0.1.0" +version = "1.20.1" dependencies = [ "bytes", "futures", @@ -7393,7 +7488,7 @@ dependencies = [ [[package]] name = "nym-registration-common" -version = "0.1.0" +version = "1.20.1" dependencies = [ "bincode", "nym-authenticator-requests", @@ -7409,7 +7504,7 @@ dependencies = [ [[package]] name = "nym-sdk" -version = "0.1.0" +version = "1.20.1" dependencies = [ "anyhow", "async-trait", @@ -7423,7 +7518,7 @@ dependencies = [ "dotenvy", "futures", "hex", - "http 1.3.1", + "http 1.4.0", "httpcodec", "log", "nym-bandwidth-controller", @@ -7449,7 +7544,7 @@ dependencies = [ "nym-validator-client", "parking_lot", "rand 0.8.5", - "reqwest 0.12.22", + "reqwest 0.12.28", "serde", "tap", "tempfile", @@ -7468,7 +7563,7 @@ dependencies = [ [[package]] name = "nym-serde-helpers" -version = "0.1.0" +version = "1.20.1" dependencies = [ "base64 0.22.1", "bs58", @@ -7479,7 +7574,7 @@ dependencies = [ [[package]] name = "nym-service-provider-requests-common" -version = "0.1.0" +version = "1.20.1" dependencies = [ "bincode", "serde", @@ -7488,7 +7583,7 @@ dependencies = [ [[package]] name = "nym-service-providers-common" -version = "0.1.0" +version = "1.20.1" dependencies = [ "anyhow", "async-trait", @@ -7558,7 +7653,7 @@ dependencies = [ [[package]] name = "nym-socks5-client-core" -version = "0.1.0" +version = "1.20.1" dependencies = [ "anyhow", "dirs", @@ -7579,7 +7674,7 @@ dependencies = [ "nym-validator-client", "pin-project", "rand 0.8.5", - "reqwest 0.12.22", + "reqwest 0.12.28", "schemars 0.8.22", "serde", "tap", @@ -7590,7 +7685,7 @@ dependencies = [ [[package]] name = "nym-socks5-proxy-helpers" -version = "0.1.0" +version = "1.20.1" dependencies = [ "bytes", "futures", @@ -7605,7 +7700,7 @@ dependencies = [ [[package]] name = "nym-socks5-requests" -version = "0.1.0" +version = "1.20.1" dependencies = [ "bincode", "log", @@ -7620,7 +7715,7 @@ dependencies = [ [[package]] name = "nym-sphinx" -version = "0.1.0" +version = "1.20.1" dependencies = [ "nym-crypto", "nym-metrics", @@ -7646,7 +7741,7 @@ dependencies = [ [[package]] name = "nym-sphinx-acknowledgements" -version = "0.1.0" +version = "1.20.1" dependencies = [ "generic-array 0.14.7", "nym-crypto", @@ -7664,7 +7759,7 @@ dependencies = [ [[package]] name = "nym-sphinx-addressing" -version = "0.1.0" +version = "1.20.1" dependencies = [ "bincode", "nym-crypto", @@ -7677,7 +7772,7 @@ dependencies = [ [[package]] name = "nym-sphinx-anonymous-replies" -version = "0.1.0" +version = "1.20.1" dependencies = [ "bs58", "nym-crypto", @@ -7695,7 +7790,7 @@ dependencies = [ [[package]] name = "nym-sphinx-chunking" -version = "0.1.0" +version = "1.20.1" dependencies = [ "dashmap", "log", @@ -7713,7 +7808,7 @@ dependencies = [ [[package]] name = "nym-sphinx-cover" -version = "0.1.0" +version = "1.20.1" dependencies = [ "nym-crypto", "nym-sphinx-acknowledgements", @@ -7730,7 +7825,7 @@ dependencies = [ [[package]] name = "nym-sphinx-forwarding" -version = "0.1.0" +version = "1.20.1" dependencies = [ "nym-sphinx-addressing", "nym-sphinx-anonymous-replies", @@ -7741,7 +7836,7 @@ dependencies = [ [[package]] name = "nym-sphinx-framing" -version = "0.1.0" +version = "1.20.1" dependencies = [ "bytes", "cfg-if", @@ -7758,7 +7853,7 @@ dependencies = [ [[package]] name = "nym-sphinx-params" -version = "0.1.0" +version = "1.20.1" dependencies = [ "nym-crypto", "nym-sphinx-types", @@ -7768,7 +7863,7 @@ dependencies = [ [[package]] name = "nym-sphinx-routing" -version = "0.1.0" +version = "1.20.1" dependencies = [ "nym-sphinx-addressing", "nym-sphinx-types", @@ -7777,13 +7872,26 @@ dependencies = [ [[package]] name = "nym-sphinx-types" -version = "0.2.0" +version = "1.20.1" dependencies = [ "nym-outfox", "sphinx-packet", "thiserror 2.0.17", ] +[[package]] +name = "nym-sqlx-pool-guard" +version = "1.20.1" +dependencies = [ + "proc_pidinfo", + "sqlx", + "tempfile", + "tokio", + "tracing", + "tracing-subscriber", + "windows 0.61.3", +] + [[package]] name = "nym-statistics-api" version = "0.3.1" @@ -7816,7 +7924,7 @@ dependencies = [ [[package]] name = "nym-statistics-common" -version = "0.1.0" +version = "1.20.1" dependencies = [ "futures", "log", @@ -7841,7 +7949,7 @@ dependencies = [ [[package]] name = "nym-store-cipher" -version = "0.1.0" +version = "1.20.1" dependencies = [ "aes-gcm", "argon2", @@ -7856,7 +7964,7 @@ dependencies = [ [[package]] name = "nym-task" -version = "0.1.0" +version = "1.20.1" dependencies = [ "anyhow", "cfg-if", @@ -7874,7 +7982,7 @@ dependencies = [ [[package]] name = "nym-test-utils" -version = "0.1.0" +version = "1.20.1" dependencies = [ "anyhow", "futures", @@ -7886,7 +7994,7 @@ dependencies = [ [[package]] name = "nym-ticketbooks-merkle" -version = "0.1.0" +version = "1.20.1" dependencies = [ "nym-credentials-interface", "nym-serde-helpers", @@ -7903,7 +8011,7 @@ dependencies = [ [[package]] name = "nym-topology" -version = "0.1.0" +version = "1.20.1" dependencies = [ "async-trait", "nym-api-requests", @@ -7911,8 +8019,9 @@ dependencies = [ "nym-mixnet-contract-common", "nym-sphinx-addressing", "nym-sphinx-types", + "nym-wasm-utils", "rand 0.8.5", - "reqwest 0.12.22", + "reqwest 0.12.28", "serde", "serde_json", "thiserror 2.0.17", @@ -7920,12 +8029,11 @@ dependencies = [ "tracing", "tsify", "wasm-bindgen", - "wasm-utils", ] [[package]] name = "nym-tun" -version = "0.1.0" +version = "1.20.1" dependencies = [ "etherparse", "log", @@ -7937,7 +8045,7 @@ dependencies = [ [[package]] name = "nym-types" -version = "1.0.0" +version = "1.20.1" dependencies = [ "base64 0.22.1", "cosmrs", @@ -7951,7 +8059,7 @@ dependencies = [ "nym-mixnet-contract-common", "nym-validator-client", "nym-vesting-contract-common", - "reqwest 0.12.22", + "reqwest 0.12.28", "schemars 0.8.22", "serde", "serde_json", @@ -7968,14 +8076,14 @@ dependencies = [ [[package]] name = "nym-upgrade-mode-check" -version = "0.1.0" +version = "1.20.1" dependencies = [ "anyhow", "jwt-simple", "nym-crypto", "nym-http-api-client", "nym-test-utils", - "reqwest 0.12.22", + "reqwest 0.12.28", "serde", "serde_json", "thiserror 2.0.17", @@ -7986,7 +8094,7 @@ dependencies = [ [[package]] name = "nym-validator-client" -version = "0.1.0" +version = "1.20.1" dependencies = [ "anyhow", "async-trait", @@ -8020,7 +8128,7 @@ dependencies = [ "nym-serde-helpers", "nym-vesting-contract-common", "prost", - "reqwest 0.12.22", + "reqwest 0.12.28", "serde", "serde_json", "sha2 0.10.9", @@ -8080,7 +8188,7 @@ dependencies = [ [[package]] name = "nym-verloc" -version = "0.1.0" +version = "1.20.1" dependencies = [ "bytes", "futures", @@ -8101,7 +8209,7 @@ dependencies = [ [[package]] name = "nym-vesting-contract-common" -version = "0.7.0" +version = "1.20.1" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -8115,7 +8223,7 @@ dependencies = [ [[package]] name = "nym-vpn-api-lib-wasm" -version = "0.1.0" +version = "1.20.1" dependencies = [ "bs58", "getrandom 0.2.16", @@ -8127,6 +8235,7 @@ dependencies = [ "nym-credentials-interface", "nym-crypto", "nym-ecash-time", + "nym-wasm-utils", "serde", "serde-wasm-bindgen 0.6.5", "serde_json", @@ -8134,7 +8243,6 @@ dependencies = [ "time", "tsify", "wasm-bindgen", - "wasm-utils", "zeroize", ] @@ -8158,9 +8266,75 @@ dependencies = [ "ts-rs", ] +[[package]] +name = "nym-wasm-client-core" +version = "1.20.1" +dependencies = [ + "async-trait", + "console_error_panic_hook", + "js-sys", + "nym-bandwidth-controller", + "nym-client-core", + "nym-config", + "nym-credential-storage", + "nym-crypto", + "nym-gateway-client", + "nym-http-api-client", + "nym-sphinx", + "nym-sphinx-acknowledgements", + "nym-statistics-common", + "nym-task", + "nym-topology", + "nym-validator-client", + "nym-wasm-storage", + "nym-wasm-utils", + "rand 0.8.5", + "serde", + "serde-wasm-bindgen 0.6.5", + "thiserror 2.0.17", + "time", + "tsify", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "zeroize", +] + +[[package]] +name = "nym-wasm-storage" +version = "1.20.1" +dependencies = [ + "async-trait", + "getrandom 0.2.16", + "indexed_db_futures", + "js-sys", + "nym-store-cipher", + "nym-wasm-utils", + "serde", + "serde-wasm-bindgen 0.6.5", + "thiserror 2.0.17", + "wasm-bindgen", +] + +[[package]] +name = "nym-wasm-utils" +version = "1.20.1" +dependencies = [ + "console_error_panic_hook", + "futures", + "getrandom 0.2.16", + "gloo-net", + "gloo-utils 0.2.0", + "js-sys", + "tungstenite 0.20.1", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "nym-wireguard" -version = "0.1.0" +version = "1.20.1" dependencies = [ "base64 0.22.1", "defguard_wireguard_rs", @@ -8187,7 +8361,7 @@ dependencies = [ [[package]] name = "nym-wireguard-private-metadata-client" -version = "1.0.0" +version = "1.20.1" dependencies = [ "async-trait", "nym-http-api-client", @@ -8197,7 +8371,7 @@ dependencies = [ [[package]] name = "nym-wireguard-private-metadata-server" -version = "1.0.0" +version = "1.20.1" dependencies = [ "anyhow", "async-trait", @@ -8217,7 +8391,7 @@ dependencies = [ [[package]] name = "nym-wireguard-private-metadata-shared" -version = "1.0.0" +version = "1.20.1" dependencies = [ "axum", "bincode", @@ -8230,7 +8404,7 @@ dependencies = [ [[package]] name = "nym-wireguard-private-metadata-tests" -version = "1.0.0" +version = "1.20.1" dependencies = [ "async-trait", "axum", @@ -8254,7 +8428,7 @@ dependencies = [ [[package]] name = "nym-wireguard-types" -version = "0.1.0" +version = "1.20.1" dependencies = [ "base64 0.22.1", "nym-crypto", @@ -8266,7 +8440,7 @@ dependencies = [ [[package]] name = "nymvisor" -version = "0.1.33" +version = "1.20.1" dependencies = [ "anyhow", "bytes", @@ -8282,7 +8456,7 @@ dependencies = [ "nym-bin-common", "nym-config", "nym-task", - "reqwest 0.12.22", + "reqwest 0.12.28", "serde", "serde_json", "sha2 0.10.9", @@ -8309,7 +8483,7 @@ dependencies = [ "nym-task", "nym-validator-client", "nyxd-scraper-sqlite", - "reqwest 0.12.22", + "reqwest 0.12.28", "schemars 0.8.22", "serde", "sqlx", @@ -8344,7 +8518,7 @@ dependencies = [ [[package]] name = "nyxd-scraper-shared" -version = "0.1.0" +version = "1.20.1" dependencies = [ "async-trait", "base64 0.22.1", @@ -8389,7 +8563,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.10.0", ] [[package]] @@ -8414,9 +8588,9 @@ dependencies = [ [[package]] name = "once_cell_polyfill" -version = "1.70.1" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "oorandom" @@ -8444,9 +8618,9 @@ checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-sys" -version = "0.9.110" +version = "0.9.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a9f0075ba3c21b09f8e8b2026584b1d18d49388648f2fbbf3c97ea8deced8e2" +checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" dependencies = [ "cc", "libc", @@ -8606,9 +8780,9 @@ checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" [[package]] name = "parking_lot" -version = "0.12.4" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" dependencies = [ "lock_api", "parking_lot_core", @@ -8616,15 +8790,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.11" +version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.5.18", "smallvec", - "windows-targets 0.52.6", + "windows-link 0.2.1", ] [[package]] @@ -8705,26 +8879,25 @@ dependencies = [ [[package]] name = "percent-encoding" -version = "2.3.1" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pest" -version = "2.8.1" +version = "2.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1db05f56d34358a8b1066f67cbb203ee3e7ed2ba674a6263a1d5ec6db2204323" +checksum = "2c9eb05c21a464ea704b53158d358a31e6425db2f63a1a7312268b05fe2b75f7" dependencies = [ "memchr", - "thiserror 2.0.17", "ucd-trie", ] [[package]] name = "pest_derive" -version = "2.8.1" +version = "2.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb056d9e8ea77922845ec74a1c4e8fb17e7c218cc4fc11a15c5d25e189aa40bc" +checksum = "68f9dbced329c441fa79d80472764b1a2c7e57123553b8519b36663a2fb234ed" dependencies = [ "pest", "pest_generator", @@ -8732,22 +8905,22 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.8.1" +version = "2.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87e404e638f781eb3202dc82db6760c8ae8a1eeef7fb3fa8264b2ef280504966" +checksum = "3bb96d5051a78f44f43c8f712d8e810adb0ebf923fc9ed2655a7f66f63ba8ee5" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] name = "pest_meta" -version = "2.8.1" +version = "2.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edd1101f170f5903fde0914f899bb503d9ff5271d7ba76bbb70bea63690cc0d5" +checksum = "602113b5b5e8621770cfd490cfd90b9f84ab29bd2b0e49ad83eb6d186cef2365" dependencies = [ "pest", "sha2 0.10.9", @@ -8760,7 +8933,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset", - "indexmap 2.10.0", + "indexmap 2.13.0", ] [[package]] @@ -8770,7 +8943,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" dependencies = [ "phf_macros", - "phf_shared", + "phf_shared 0.11.3", +] + +[[package]] +name = "phf" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" +dependencies = [ + "phf_shared 0.13.1", + "serde", ] [[package]] @@ -8780,7 +8963,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" dependencies = [ "phf_generator", - "phf_shared", + "phf_shared 0.11.3", ] [[package]] @@ -8789,7 +8972,7 @@ version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" dependencies = [ - "phf_shared", + "phf_shared 0.11.3", "rand 0.8.5", ] @@ -8800,10 +8983,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" dependencies = [ "phf_generator", - "phf_shared", + "phf_shared 0.11.3", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] @@ -8815,6 +8998,15 @@ dependencies = [ "siphasher 1.0.1", ] +[[package]] +name = "phf_shared" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" +dependencies = [ + "siphasher 1.0.1", +] + [[package]] name = "pin-project" version = "1.1.10" @@ -8832,7 +9024,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] @@ -8926,7 +9118,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] @@ -8991,9 +9183,9 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.11.1" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" +checksum = "f89776e4d69bb58bc6993e99ffa1d11f228b839984854c7daeb5d37f87cbe950" [[package]] name = "portable-atomic-util" @@ -9006,9 +9198,9 @@ dependencies = [ [[package]] name = "postgres-protocol" -version = "0.6.8" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76ff0abab4a9b844b93ef7b81f1efc0a366062aaef2cd702c76256b5dc075c54" +checksum = "fbef655056b916eb868048276cfd5d6a7dea4f81560dfd047f97c8c6fe3fcfd4" dependencies = [ "base64 0.22.1", "byteorder", @@ -9024,9 +9216,9 @@ dependencies = [ [[package]] name = "postgres-types" -version = "0.2.9" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613283563cd90e1dfc3518d548caee47e0e725455ed619881f5cf21f36de4b48" +checksum = "ef4605b7c057056dd35baeb6ac0c0338e4975b1f2bef0f65da953285eb007095" dependencies = [ "bytes", "fallible-iterator", @@ -9035,9 +9227,9 @@ dependencies = [ [[package]] name = "potential_utf" -version = "0.1.2" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" dependencies = [ "zerovec", ] @@ -9088,7 +9280,7 @@ version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" dependencies = [ - "toml_edit 0.23.5", + "toml_edit 0.23.10+spec-1.0.0", ] [[package]] @@ -9110,14 +9302,14 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] name = "proc-macro2" -version = "1.0.95" +version = "1.0.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7" dependencies = [ "unicode-ident", ] @@ -9166,7 +9358,7 @@ dependencies = [ "itertools 0.14.0", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] @@ -9200,9 +9392,9 @@ dependencies = [ [[package]] name = "psl" -version = "2.1.126" +version = "2.1.176" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45f621acfbd2ca5670eee9a95270747dfa9a29e63e1937d7e6a1ac6994331966" +checksum = "5f06bb516febf92008f7e4ebdc1129a75e07905eadc4601af27093cb83928c78" dependencies = [ "psl-types", ] @@ -9231,9 +9423,9 @@ checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" [[package]] name = "quinn" -version = "0.11.8" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "626214629cda6781b6dc1d316ba307189c85ba657213ce642d9c77670f8202c8" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" dependencies = [ "bytes", "cfg_aliases", @@ -9241,8 +9433,8 @@ dependencies = [ "quinn-proto", "quinn-udp", "rustc-hash", - "rustls 0.23.29", - "socket2 0.5.10", + "rustls 0.23.36", + "socket2 0.6.1", "thiserror 2.0.17", "tokio", "tracing", @@ -9251,17 +9443,17 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.11.12" +version = "0.11.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49df843a9161c85bb8aae55f101bc0bac8bcafd637a620d9122fd7e0b2f7422e" +checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" dependencies = [ "bytes", - "getrandom 0.3.3", + "getrandom 0.3.4", "lru-slab", "rand 0.9.2", "ring", "rustc-hash", - "rustls 0.23.29", + "rustls 0.23.36", "rustls-pki-types", "slab", "thiserror 2.0.17", @@ -9272,23 +9464,23 @@ dependencies = [ [[package]] name = "quinn-udp" -version = "0.5.13" +version = "0.5.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcebb1209ee276352ef14ff8732e24cc2b02bbac986cd74a4c81bcb2f9881970" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.5.10", + "socket2 0.6.1", "tracing", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] name = "quote" -version = "1.0.40" +version = "1.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" dependencies = [ "proc-macro2", ] @@ -9361,7 +9553,7 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "getrandom 0.3.3", + "getrandom 0.3.4", ] [[package]] @@ -9376,9 +9568,9 @@ dependencies = [ [[package]] name = "rayon" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" dependencies = [ "either", "rayon-core", @@ -9386,9 +9578,9 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.12.1" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" dependencies = [ "crossbeam-deque", "crossbeam-utils", @@ -9396,11 +9588,20 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.15" +version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e8af0dde094006011e6a740d4879319439489813bd0bcdc7d821beaeeff48ec" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.10.0", +] + +[[package]] +name = "redox_syscall" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f3fe0889e69e2ae9e41f4d6c4c0181701d00e4697b356fb1f74173a5e0ee27" +dependencies = [ + "bitflags 2.10.0", ] [[package]] @@ -9416,22 +9617,22 @@ dependencies = [ [[package]] name = "ref-cast" -version = "1.0.24" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a0ae411dbe946a674d89546582cea4ba2bb8defac896622d6496f14c23ba5cf" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" dependencies = [ "ref-cast-impl", ] [[package]] name = "ref-cast-impl" -version = "1.0.24" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1165225c21bff1f3bbce98f5a1f889949bc902d3575308cc7b0de30b4f6d27c7" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] @@ -9459,9 +9660,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.5" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" [[package]] name = "reqwest" @@ -9506,19 +9707,18 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.12.22" +version = "0.12.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbc931937e6ca3a06e3b6c0aa7841849b160a90351d6ab467a8b9b9959767531" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" dependencies = [ - "async-compression", "base64 0.22.1", "bytes", "futures-core", "futures-util", - "http 1.3.1", + "http 1.4.0", "http-body 1.0.1", "http-body-util", - "hyper 1.6.0", + "hyper 1.8.1", "hyper-rustls 0.27.7", "hyper-util", "js-sys", @@ -9526,14 +9726,14 @@ dependencies = [ "percent-encoding", "pin-project-lite", "quinn", - "rustls 0.23.29", + "rustls 0.23.36", "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", "sync_wrapper 1.0.2", "tokio", - "tokio-rustls 0.26.2", + "tokio-rustls 0.26.4", "tokio-util", "tower 0.5.2", "tower-http", @@ -9543,7 +9743,7 @@ dependencies = [ "wasm-bindgen-futures", "wasm-streams", "web-sys", - "webpki-roots 1.0.2", + "webpki-roots 1.0.5", ] [[package]] @@ -9557,9 +9757,9 @@ dependencies = [ [[package]] name = "resolv-conf" -version = "0.7.4" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95325155c684b1c89f7765e30bc1c42e4a6da51ca513615660cb8a62ef9a88e3" +checksum = "1e061d1b48cb8d38042de4ae0a7a6401009d6143dc80d2e2d6f31f0bdd6470c7" [[package]] name = "rfc6979" @@ -9596,22 +9796,19 @@ dependencies = [ [[package]] name = "rmp" -version = "0.8.14" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "228ed7c16fa39782c3b3468e974aec2795e9089153cd08ee2e9aefb3613334c4" +checksum = "4ba8be72d372b2c9b35542551678538b562e7cf86c3315773cae48dfbfe7790c" dependencies = [ - "byteorder", "num-traits", - "paste", ] [[package]] name = "rmp-serde" -version = "1.3.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52e599a477cf9840e92f2cde9a7189e67b42c57532749bf90aea6ec10facd4db" +checksum = "72f81bee8c8ef9b577d1681a70ebbc962c232461e397b22c208c43c04b67a155" dependencies = [ - "byteorder", "rmp", "serde", ] @@ -9627,9 +9824,9 @@ dependencies = [ [[package]] name = "rsa" -version = "0.9.8" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78928ac1ed176a5ca1d17e578a1825f3d81ca54cf41053a592584b020cfd691b" +checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" dependencies = [ "const-oid", "digest 0.10.7", @@ -9648,9 +9845,9 @@ dependencies = [ [[package]] name = "rust-embed" -version = "8.7.2" +version = "8.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "025908b8682a26ba8d12f6f2d66b987584a4a87bc024abc5bbc12553a8cd178a" +checksum = "947d7f3fad52b283d261c4c99a084937e2fe492248cb9a68a8435a861b8798ca" dependencies = [ "rust-embed-impl", "rust-embed-utils", @@ -9659,22 +9856,22 @@ dependencies = [ [[package]] name = "rust-embed-impl" -version = "8.7.2" +version = "8.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6065f1a4392b71819ec1ea1df1120673418bf386f50de1d6f54204d836d4349c" +checksum = "5fa2c8c9e8711e10f9c4fd2d64317ef13feaab820a4c51541f1a8c8e2e851ab2" dependencies = [ "proc-macro2", "quote", "rust-embed-utils", - "syn 2.0.104", + "syn 2.0.114", "walkdir", ] [[package]] name = "rust-embed-utils" -version = "8.7.2" +version = "8.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6cc0c81648b20b70c491ff8cce00c1c3b223bb8ed2b5d41f0e54c6c4c0a3594" +checksum = "60b161f275cb337fe0a44d924a5f4df0ed69c2c39519858f931ce61c779d3475" dependencies = [ "sha2 0.10.9", "walkdir", @@ -9717,33 +9914,20 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ - "semver 1.0.26", + "semver 1.0.27", ] [[package]] name = "rustix" -version = "0.38.44" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.10.0", "errno", "libc", - "linux-raw-sys 0.4.15", - "windows-sys 0.59.0", -] - -[[package]] -name = "rustix" -version = "1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" -dependencies = [ - "bitflags 2.9.1", - "errno", - "libc", - "linux-raw-sys 0.9.4", - "windows-sys 0.60.2", + "linux-raw-sys", + "windows-sys 0.61.2", ] [[package]] @@ -9774,15 +9958,15 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.29" +version = "0.23.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2491382039b29b9b11ff08b76ff6c97cf287671dbb74f0be44bda389fffe9bd1" +checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" dependencies = [ "log", "once_cell", "ring", "rustls-pki-types", - "rustls-webpki 0.103.4", + "rustls-webpki 0.103.8", "subtle 2.6.1", "zeroize", ] @@ -9832,9 +10016,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.12.0" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" +checksum = "21e6f2ab2928ca4291b86736a8bd920a277a399bba1589409d72154ff87c1282" dependencies = [ "web-time", "zeroize", @@ -9863,9 +10047,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.4" +version = "0.103.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc" +checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" dependencies = [ "ring", "rustls-pki-types", @@ -9874,15 +10058,15 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.21" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" -version = "1.0.20" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" [[package]] name = "same-file" @@ -9895,11 +10079,11 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.27" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" +checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -9930,9 +10114,9 @@ dependencies = [ [[package]] name = "schemars" -version = "1.0.4" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82d20c4491bc164fa2f6c5d44565947a52ad80b9505d8e36f8d54c27c739fcd0" +checksum = "54e910108742c57a770f492731f99be216a52fadd361b06c8fb59d74ccc267d2" dependencies = [ "dyn-clone", "ref-cast", @@ -9949,15 +10133,9 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals 0.29.1", - "syn 2.0.104", + "syn 2.0.114", ] -[[package]] -name = "scoped-tls" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" - [[package]] name = "scopeguard" version = "1.2.0" @@ -9981,7 +10159,7 @@ checksum = "1783eabc414609e28a5ba76aee5ddd52199f7107a0b24c2e9746a1ecc34a683d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] @@ -10002,7 +10180,7 @@ checksum = "22f968c5ea23d555e670b449c1c5e7b2fc399fdaec1d304a17cd48e288abc107" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] @@ -10044,7 +10222,7 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.10.0", "core-foundation", "core-foundation-sys", "libc", @@ -10053,9 +10231,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.14.0" +version = "2.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" +checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" dependencies = [ "core-foundation-sys", "libc", @@ -10072,11 +10250,12 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.26" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" dependencies = [ "serde", + "serde_core", ] [[package]] @@ -10128,11 +10307,12 @@ dependencies = [ [[package]] name = "serde_bytes" -version = "0.11.17" +version = "0.11.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8437fd221bde2d4ca316d61b90e337e9e702b3820b87d63caa9ba6c02bd06d96" +checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8" dependencies = [ "serde", + "serde_core", ] [[package]] @@ -10152,7 +10332,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] @@ -10163,7 +10343,7 @@ checksum = "e578a843d40b4189a4d66bba51d7684f57da5bd7c304c64e14bd63efbef49509" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] @@ -10174,20 +10354,20 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] name = "serde_json" -version = "1.0.145" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ "itoa", "memchr", - "ryu", "serde", "serde_core", + "zmij", ] [[package]] @@ -10237,17 +10417,18 @@ checksum = "aafbefbe175fa9bf03ca83ef89beecff7d2a95aaacd5732325b90ac8c3bd7b90" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] name = "serde_path_to_error" -version = "0.1.17" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59fab13f937fa393d08645bf3a84bdfe86e296747b506ada67bb15f10f218b2a" +checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" dependencies = [ "itoa", "serde", + "serde_core", ] [[package]] @@ -10267,7 +10448,7 @@ checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] @@ -10293,19 +10474,18 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.14.0" +version = "3.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2c45cd61fefa9db6f254525d46e392b852e0e61d9a1fd36e5bd183450a556d5" +checksum = "4fa237f2807440d238e0364a218270b98f767a00d3dada77b1c53ae88940e2e7" dependencies = [ "base64 0.22.1", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.10.0", + "indexmap 2.13.0", "schemars 0.9.0", - "schemars 1.0.4", - "serde", - "serde_derive", + "schemars 1.2.0", + "serde_core", "serde_json", "serde_with_macros", "time", @@ -10313,14 +10493,14 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.14.0" +version = "3.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de90945e6565ce0d9a25098082ed4ee4002e047cb59892c318d66821e14bb30f" +checksum = "52a8e3ca0ca629121f70ab50f95249e5a6f925cc0f6ffe8256c45b728875706c" dependencies = [ - "darling", + "darling 0.21.3", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] @@ -10329,7 +10509,7 @@ version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap 2.10.0", + "indexmap 2.13.0", "itoa", "ryu", "serde", @@ -10434,9 +10614,9 @@ dependencies = [ [[package]] name = "signal-hook-mio" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" +checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc" dependencies = [ "libc", "mio 0.8.11", @@ -10445,10 +10625,11 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.5" +version = "1.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" dependencies = [ + "errno", "libc", ] @@ -10464,9 +10645,9 @@ dependencies = [ [[package]] name = "simd-adler32" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" [[package]] name = "siphasher" @@ -10482,9 +10663,9 @@ checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" [[package]] name = "slab" -version = "0.4.10" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" [[package]] name = "sluice" @@ -10562,12 +10743,12 @@ dependencies = [ [[package]] name = "socket2" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" +checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -10576,7 +10757,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c26f0c20d909fdda1c5d0ece3973127ca421984d55b000215df365e93722fc6e" dependencies = [ - "aes", + "aes 0.8.4", "arrayref", "blake2 0.8.1", "bs58", @@ -10640,19 +10821,19 @@ dependencies = [ "crc", "crossbeam-queue", "either", - "event-listener 5.4.0", + "event-listener 5.4.1", "futures-core", "futures-intrusive", "futures-io", "futures-util", - "hashbrown 0.15.4", + "hashbrown 0.15.5", "hashlink", - "indexmap 2.10.0", + "indexmap 2.13.0", "log", "memchr", "once_cell", "percent-encoding", - "rustls 0.23.29", + "rustls 0.23.36", "serde", "serde_json", "sha2 0.10.9", @@ -10676,7 +10857,7 @@ dependencies = [ "quote", "sqlx-core", "sqlx-macros-core", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] @@ -10699,7 +10880,7 @@ dependencies = [ "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", - "syn 2.0.104", + "syn 2.0.114", "tokio", "url", ] @@ -10712,7 +10893,7 @@ checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" dependencies = [ "atoi", "base64 0.22.1", - "bitflags 2.9.1", + "bitflags 2.10.0", "byteorder", "bytes", "chrono", @@ -10748,19 +10929,6 @@ dependencies = [ "whoami", ] -[[package]] -name = "sqlx-pool-guard" -version = "0.1.0" -dependencies = [ - "proc_pidinfo", - "sqlx", - "tempfile", - "tokio", - "tracing", - "tracing-subscriber", - "windows 0.61.3", -] - [[package]] name = "sqlx-postgres" version = "0.8.6" @@ -10769,7 +10937,7 @@ checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" dependencies = [ "atoi", "base64 0.22.1", - "bitflags 2.9.1", + "bitflags 2.10.0", "byteorder", "chrono", "crc", @@ -10838,9 +11006,9 @@ dependencies = [ [[package]] name = "stable_deref_trait" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] name = "static_assertions" @@ -10856,7 +11024,7 @@ checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" dependencies = [ "new_debug_unreachable", "parking_lot", - "phf_shared", + "phf_shared 0.11.3", "precomputed-hash", "serde", ] @@ -10868,7 +11036,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0" dependencies = [ "phf_generator", - "phf_shared", + "phf_shared 0.11.3", "proc-macro2", "quote", ] @@ -10908,7 +11076,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] @@ -10940,10 +11108,12 @@ checksum = "734676eb262c623cec13c3155096e08d1f8f29adce39ba17948b18dad1e54142" [[package]] name = "superboring" -version = "0.1.4" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "515cce34a781d7250b8a65706e0f2a5b99236ea605cb235d4baed6685820478f" +checksum = "10d8c985e81c88f5694d5dfc232691b2aa34f3e1f66e860db9cd1ddf2bb6dc64" dependencies = [ + "aes-gcm", + "aes-keywrap", "getrandom 0.2.16", "hmac-sha256", "hmac-sha512", @@ -10964,9 +11134,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.104" +version = "2.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" dependencies = [ "proc-macro2", "quote", @@ -10996,7 +11166,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] @@ -11059,15 +11229,15 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.20.0" +version = "3.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" +checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" dependencies = [ "fastrand 2.3.0", - "getrandom 0.3.3", + "getrandom 0.3.4", "once_cell", - "rustix 1.0.8", - "windows-sys 0.59.0", + "rustix", + "windows-sys 0.61.2", ] [[package]] @@ -11145,7 +11315,7 @@ dependencies = [ "pin-project", "rand 0.8.5", "reqwest 0.11.27", - "semver 1.0.26", + "semver 1.0.27", "serde", "serde_bytes", "serde_json", @@ -11185,15 +11355,15 @@ dependencies = [ [[package]] name = "test-with" -version = "0.15.4" +version = "0.15.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0f370b9efbfbbc5f057cbce9888373eaeb146a3095bb8cc869b199c94d15559" +checksum = "57ec38bac967f54b11756d3b8b42069f5b30c3ff449972c516683b98340a4e8e" dependencies = [ "proc-macro-error2", "proc-macro2", "quote", "regex", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] @@ -11273,7 +11443,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] @@ -11284,7 +11454,7 @@ checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] @@ -11320,9 +11490,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.41" +version = "0.3.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" +checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" dependencies = [ "deranged", "itoa", @@ -11338,15 +11508,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.4" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" +checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" [[package]] name = "time-macros" -version = "0.2.22" +version = "0.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" +checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" dependencies = [ "num-conv", "time-core", @@ -11354,9 +11524,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" dependencies = [ "displaydoc", "zerovec", @@ -11374,9 +11544,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" dependencies = [ "tinyvec_macros", ] @@ -11405,22 +11575,22 @@ checksum = "2d2e76690929402faae40aebdda620a2c0e25dd6d3b9afe48867dfd95991f4bd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] name = "tokio" -version = "1.48.0" +version = "1.49.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" +checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" dependencies = [ "bytes", "libc", - "mio 1.0.4", + "mio 1.1.1", "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2 0.6.0", + "socket2 0.6.1", "tokio-macros", "tracing", "windows-sys 0.61.2", @@ -11434,14 +11604,14 @@ checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] name = "tokio-postgres" -version = "0.7.13" +version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c95d533c83082bb6490e0189acaa0bbeef9084e60471b696ca6988cd0541fb0" +checksum = "2b40d66d9b2cfe04b628173409368e58247e8eddbbd3b0e6c6ba1d09f20f6c9e" dependencies = [ "async-trait", "byteorder", @@ -11452,12 +11622,12 @@ dependencies = [ "log", "parking_lot", "percent-encoding", - "phf", + "phf 0.13.1", "pin-project-lite", "postgres-protocol", "postgres-types", "rand 0.9.2", - "socket2 0.5.10", + "socket2 0.6.1", "tokio", "tokio-util", "whoami", @@ -11486,19 +11656,19 @@ dependencies = [ [[package]] name = "tokio-rustls" -version = "0.26.2" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" dependencies = [ - "rustls 0.23.29", + "rustls 0.23.36", "tokio", ] [[package]] name = "tokio-stream" -version = "0.1.17" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" dependencies = [ "futures-core", "pin-project-lite", @@ -11508,12 +11678,10 @@ dependencies = [ [[package]] name = "tokio-test" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2468baabc3311435b55dd935f702f42cd1b8abb7e754fb7dfb16bd36aa88f9f7" +checksum = "3f6d24790a10a7af737693a3e8f1d03faef7e6ca0cc99aae5066f533766de545" dependencies = [ - "async-stream", - "bytes", "futures-core", "tokio", "tokio-stream", @@ -11548,15 +11716,14 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.15" +version = "0.7.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" dependencies = [ "bytes", "futures-core", "futures-sink", "futures-util", - "hashbrown 0.15.4", "pin-project-lite", "slab", "tokio", @@ -11583,7 +11750,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37e04c1865c281139e5ccf633cb9f76ffdaabeebfe53b703984cf82878e2aabb" dependencies = [ "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] @@ -11618,9 +11785,9 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.7.3" +version = "0.7.5+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" dependencies = [ "serde_core", ] @@ -11631,7 +11798,7 @@ version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ - "indexmap 2.10.0", + "indexmap 2.13.0", "serde", "serde_spanned", "toml_datetime 0.6.11", @@ -11641,21 +11808,21 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.23.5" +version = "0.23.10+spec-1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2ad0b7ae9cfeef5605163839cb9221f453399f15cfb5c10be9885fcf56611f9" +checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" dependencies = [ - "indexmap 2.10.0", - "toml_datetime 0.7.3", + "indexmap 2.13.0", + "toml_datetime 0.7.5+spec-1.1.0", "toml_parser", "winnow", ] [[package]] name = "toml_parser" -version = "1.0.2" +version = "1.0.6+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b551886f449aa90d4fe2bdaa9f4a2577ad2dde302c61ecf262d80b116db95c10" +checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" dependencies = [ "winnow", ] @@ -11677,11 +11844,11 @@ dependencies = [ "axum", "base64 0.22.1", "bytes", - "h2 0.4.11", - "http 1.3.1", + "h2 0.4.13", + "http 1.4.0", "http-body 1.0.1", "http-body-util", - "hyper 1.6.0", + "hyper 1.8.1", "hyper-timeout", "hyper-util", "percent-encoding", @@ -11705,11 +11872,11 @@ dependencies = [ "async-trait", "base64 0.22.1", "bytes", - "h2 0.4.11", - "http 1.3.1", + "h2 0.4.13", + "http 1.4.0", "http-body 1.0.1", "http-body-util", - "hyper 1.6.0", + "hyper 1.8.1", "hyper-timeout", "hyper-util", "percent-encoding", @@ -11752,7 +11919,7 @@ checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" dependencies = [ "futures-core", "futures-util", - "indexmap 2.10.0", + "indexmap 2.13.0", "pin-project-lite", "slab", "sync_wrapper 1.0.2", @@ -11765,16 +11932,16 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.6.6" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" dependencies = [ "async-compression", - "bitflags 2.9.1", + "bitflags 2.10.0", "bytes", "futures-core", "futures-util", - "http 1.3.1", + "http 1.4.0", "http-body 1.0.1", "http-body-util", "http-range-header", @@ -11806,9 +11973,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.41" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "log", "pin-project-lite", @@ -11818,20 +11985,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.30" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] name = "tracing-core" -version = "0.1.34" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", "valuable", @@ -11849,9 +12016,9 @@ dependencies = [ [[package]] name = "tracing-indicatif" -version = "0.3.11" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c714cc8fc46db04fcfddbd274c6ef59bebb1b435155984e7c6e89c3ce66f200" +checksum = "e1ef6990e0438749f0080573248e96631171a0b5ddfddde119aa5ba8c3a9c47e" dependencies = [ "indicatif", "tracing", @@ -11897,12 +12064,12 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.20" +version = "0.3.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5" +checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" dependencies = [ "matchers", - "nu-ansi-term 0.50.1", + "nu-ansi-term 0.50.3", "once_cell", "regex-automata", "sharded-slab", @@ -11931,7 +12098,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04659ddb06c87d233c566112c1c9c5b9e98256d9af50ec3bc9c8327f873a7568" dependencies = [ "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] @@ -12015,7 +12182,7 @@ checksum = "0e9d8656589772eeec2cf7a8264d9cda40fb28b9bc53118ceb9e8c07f8f38730" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", "termcolor", ] @@ -12042,7 +12209,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals 0.28.0", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] @@ -12075,7 +12242,7 @@ dependencies = [ "byteorder", "bytes", "data-encoding", - "http 1.3.1", + "http 1.4.0", "httparse", "log", "rand 0.8.5", @@ -12089,29 +12256,29 @@ dependencies = [ [[package]] name = "typed-builder" -version = "0.23.0" +version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d0dd654273fc253fde1df4172c31fb6615cf8b041d3a4008a028ef8b1119e66" +checksum = "31aa81521b70f94402501d848ccc0ecaa8f93c8eb6999eb9747e72287757ffda" dependencies = [ "typed-builder-macro", ] [[package]] name = "typed-builder-macro" -version = "0.23.0" +version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "016c26257f448222014296978b2c8456e2cad4de308c35bdb1e383acd569ef5b" +checksum = "076a02dc54dd46795c2e9c8282ed40bcfb1e22747e955de9389a1de28190fb26" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] name = "typenum" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" [[package]] name = "ucd-trie" @@ -12121,9 +12288,9 @@ checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" [[package]] name = "unicase" -version = "2.8.1" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" +checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" [[package]] name = "unicode-bidi" @@ -12133,24 +12300,24 @@ checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" [[package]] name = "unicode-ident" -version = "1.0.18" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "unicode-normalization" -version = "0.1.24" +version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" +checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" dependencies = [ "tinyvec", ] [[package]] name = "unicode-properties" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" +checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" [[package]] name = "unicode-segmentation" @@ -12166,9 +12333,9 @@ checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] name = "unicode-width" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" [[package]] name = "unicode-xid" @@ -12178,19 +12345,19 @@ checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "uniffi" -version = "0.29.3" +version = "0.29.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b334fd69b3cf198b63616c096aabf9820ab21ed9b2aa1367ddd4b411068bf520" +checksum = "3291800a6b06569f7d3e15bdb6dc235e0f0c8bd3eb07177f430057feb076415f" dependencies = [ "anyhow", "camino", "cargo_metadata 0.19.2", "clap", - "uniffi_bindgen 0.29.3", - "uniffi_build 0.29.3", - "uniffi_core 0.29.3", - "uniffi_macros 0.29.3", - "uniffi_pipeline 0.29.3", + "uniffi_bindgen 0.29.5", + "uniffi_build 0.29.5", + "uniffi_core 0.29.5", + "uniffi_macros 0.29.5", + "uniffi_pipeline 0.29.5", ] [[package]] @@ -12212,9 +12379,9 @@ dependencies = [ [[package]] name = "uniffi_bindgen" -version = "0.29.3" +version = "0.29.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ff0132b533483cf19abb30bba5c72c24d9f3e4d9a2ff71cb3e22e73899fd46e" +checksum = "a04b99fa7796eaaa7b87976a0dbdd1178dc1ee702ea00aca2642003aef9b669e" dependencies = [ "anyhow", "askama", @@ -12224,16 +12391,16 @@ dependencies = [ "glob", "goblin", "heck 0.5.0", - "indexmap 2.10.0", + "indexmap 2.13.0", "once_cell", "serde", "tempfile", "textwrap", "toml 0.5.11", - "uniffi_internal_macros 0.29.3", - "uniffi_meta 0.29.3", - "uniffi_pipeline 0.29.3", - "uniffi_udl 0.29.3", + "uniffi_internal_macros 0.29.5", + "uniffi_meta 0.29.5", + "uniffi_pipeline 0.29.5", + "uniffi_udl 0.29.5", ] [[package]] @@ -12250,7 +12417,7 @@ dependencies = [ "glob", "goblin", "heck 0.5.0", - "indexmap 2.10.0", + "indexmap 2.13.0", "once_cell", "serde", "tempfile", @@ -12264,13 +12431,13 @@ dependencies = [ [[package]] name = "uniffi_build" -version = "0.29.3" +version = "0.29.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d84d607076008df3c32dd2100ee4e727269f11d3faa35691af70d144598f666" +checksum = "025a05cba02ee22b6624ac3d257e59c7395319ea8fe1aae33a7cdb4e2a3016cc" dependencies = [ "anyhow", "camino", - "uniffi_bindgen 0.29.3", + "uniffi_bindgen 0.29.5", ] [[package]] @@ -12286,9 +12453,9 @@ dependencies = [ [[package]] name = "uniffi_core" -version = "0.29.3" +version = "0.29.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53e3b997192dc15ef1778c842001811ec7f241a093a693ac864e1fc938e64fa9" +checksum = "f38a9a27529ccff732f8efddb831b65b1e07f7dea3fd4cacd4a35a8c4b253b98" dependencies = [ "anyhow", "bytes", @@ -12310,15 +12477,15 @@ dependencies = [ [[package]] name = "uniffi_internal_macros" -version = "0.29.3" +version = "0.29.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f64bec2f3a33f2f08df8150e67fa45ba59a2ca740bf20c1beb010d4d791f9a1b" +checksum = "09acd2ce09c777dd65ee97c251d33c8a972afc04873f1e3b21eb3492ade16933" dependencies = [ "anyhow", - "indexmap 2.10.0", + "indexmap 2.13.0", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] @@ -12328,17 +12495,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3c2a6f93e7b73726e2015696ece25ca0ac5a5f1cf8d6a7ab5214dd0a01d2edf" dependencies = [ "anyhow", - "indexmap 2.10.0", + "indexmap 2.13.0", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] name = "uniffi_macros" -version = "0.29.3" +version = "0.29.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d8708716d2582e4f3d7e9f320290b5966eb951ca421d7630571183615453efc" +checksum = "5596f178c4f7aafa1a501c4e0b96236a96bc2ef92bdb453d83e609dad0040152" dependencies = [ "camino", "fs-err", @@ -12346,9 +12513,9 @@ dependencies = [ "proc-macro2", "quote", "serde", - "syn 2.0.104", + "syn 2.0.114", "toml 0.5.11", - "uniffi_meta 0.29.3", + "uniffi_meta 0.29.5", ] [[package]] @@ -12363,21 +12530,21 @@ dependencies = [ "proc-macro2", "quote", "serde", - "syn 2.0.104", + "syn 2.0.114", "toml 0.8.23", "uniffi_meta 0.30.0", ] [[package]] name = "uniffi_meta" -version = "0.29.3" +version = "0.29.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d226fc167754ce548c5ece9828c8a06f03bf1eea525d2659ba6bd648bd8e2f3" +checksum = "beadc1f460eb2e209263c49c4f5b19e9a02e00a3b2b393f78ad10d766346ecff" dependencies = [ "anyhow", "siphasher 0.3.11", - "uniffi_internal_macros 0.29.3", - "uniffi_pipeline 0.29.3", + "uniffi_internal_macros 0.29.5", + "uniffi_pipeline 0.29.5", ] [[package]] @@ -12394,15 +12561,15 @@ dependencies = [ [[package]] name = "uniffi_pipeline" -version = "0.29.3" +version = "0.29.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b925b6421df15cf4bedee27714022cd9626fb4d7eee0923522a608b274ba4371" +checksum = "dd76b3ac8a2d964ca9fce7df21c755afb4c77b054a85ad7a029ad179cc5abb8a" dependencies = [ "anyhow", "heck 0.5.0", - "indexmap 2.10.0", + "indexmap 2.13.0", "tempfile", - "uniffi_internal_macros 0.29.3", + "uniffi_internal_macros 0.29.5", ] [[package]] @@ -12413,20 +12580,20 @@ checksum = "8c27c4b515d25f8e53cc918e238c39a79c3144a40eaf2e51c4a7958973422c29" dependencies = [ "anyhow", "heck 0.5.0", - "indexmap 2.10.0", + "indexmap 2.13.0", "tempfile", "uniffi_internal_macros 0.30.0", ] [[package]] name = "uniffi_udl" -version = "0.29.3" +version = "0.29.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c42649b721df759d9d4692a376b82b62ce3028ec9fc466f4780fb8cdf728996" +checksum = "4319cf905911d70d5b97ce0f46f101619a22e9a189c8c46d797a9955e9233716" dependencies = [ "anyhow", "textwrap", - "uniffi_meta 0.29.3", + "uniffi_meta 0.29.5", "weedle2", ] @@ -12444,9 +12611,9 @@ dependencies = [ [[package]] name = "unit-prefix" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "323402cff2dd658f39ca17c789b502021b3f18707c91cdf22e3838e1b4023817" +checksum = "81e544489bf3d8ef66c953931f56617f423cd4b5494be343d9b9d3dda037b9a3" [[package]] name = "universal-hash" @@ -12454,7 +12621,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" dependencies = [ - "crypto-common", + "crypto-common 0.1.7", "subtle 2.6.1", ] @@ -12472,14 +12639,15 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.4" +version = "2.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" dependencies = [ "form_urlencoded", "idna", "percent-encoding", "serde", + "serde_derive", ] [[package]] @@ -12512,7 +12680,7 @@ version = "5.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2fcc29c80c21c31608227e0912b2d7fddba57ad76b606890627ba8ee7964e993" dependencies = [ - "indexmap 2.10.0", + "indexmap 2.13.0", "serde", "serde_json", "utoipa-gen", @@ -12527,7 +12695,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.104", + "syn 2.0.114", "uuid", ] @@ -12566,7 +12734,7 @@ checksum = "268d76aaebb80eba79240b805972e52d7d410d4bcc52321b951318b0f440cd60" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] @@ -12577,19 +12745,19 @@ checksum = "382673bda1d05c85b4550d32fd4192ccd4cffe9a908543a0795d1e7682b36246" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", "utoipauto-core", ] [[package]] name = "uuid" -version = "1.17.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" +checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a" dependencies = [ - "getrandom 0.3.3", + "getrandom 0.3.4", "js-sys", - "serde", + "serde_core", "wasm-bindgen", ] @@ -12688,35 +12856,23 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "vt100" -version = "0.15.2" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84cd863bf0db7e392ba3bd04994be3473491b31e66340672af5d11943c6274de" +checksum = "054ff75fb8fa83e609e685106df4faeffdf3a735d3c74ebce97ec557d5d36fd9" dependencies = [ "itoa", - "log", - "unicode-width 0.1.14", + "unicode-width 0.2.2", "vte", ] [[package]] name = "vte" -version = "0.11.1" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5022b5fbf9407086c180e9557be968742d839e68346af7792b8592489732197" +checksum = "a5924018406ce0063cd67f8e008104968b74b563ee1b85dde3ed1f7cb87d3dbd" dependencies = [ "arrayvec", - "utf8parse", - "vte_generate_state_changes", -] - -[[package]] -name = "vte_generate_state_changes" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e369bee1b05d510a7b4ed645f5faa90619e05437111783ea5848f28d97d3c2e" -dependencies = [ - "proc-macro2", - "quote", + "memchr", ] [[package]] @@ -12751,12 +12907,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] -name = "wasi" -version = "0.14.2+wasi-0.2.4" +name = "wasip2" +version = "1.0.1+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" dependencies = [ - "wit-bindgen-rt", + "wit-bindgen", ] [[package]] @@ -12771,40 +12927,27 @@ version = "0.12.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1fbb4ef9bbca0c1170e0b00dd28abc9e3b68669821600cad1caaed606583c6d" dependencies = [ - "wasi 0.11.1+wasi-snapshot-preview1", + "wasi", ] [[package]] name = "wasm-bindgen" -version = "0.2.100" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" dependencies = [ "cfg-if", "once_cell", "rustversion", "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" -dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn 2.0.104", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.50" +version = "0.4.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +checksum = "836d9622d604feee9e5de25ac10e3ea5f2d65b41eac0d9ce72eb5deae707ce7c" dependencies = [ "cfg-if", "js-sys", @@ -12815,9 +12958,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.100" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -12825,34 +12968,42 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.100" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" dependencies = [ + "bumpalo", "proc-macro2", "quote", - "syn 2.0.104", - "wasm-bindgen-backend", + "syn 2.0.114", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.100" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" dependencies = [ "unicode-ident", ] [[package]] name = "wasm-bindgen-test" -version = "0.3.50" +version = "0.3.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66c8d5e33ca3b6d9fa3b4676d774c5778031d27a578c2b007f905acf816152c3" +checksum = "25e90e66d265d3a1efc0e72a54809ab90b9c0c515915c67cdf658689d2c22c6c" dependencies = [ + "async-trait", + "cast", "js-sys", + "libm", "minicov", + "nu-ansi-term 0.50.3", + "num-traits", + "oorandom", + "serde", + "serde_json", "wasm-bindgen", "wasm-bindgen-futures", "wasm-bindgen-test-macro", @@ -12860,63 +13011,13 @@ dependencies = [ [[package]] name = "wasm-bindgen-test-macro" -version = "0.3.50" +version = "0.3.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17d5042cc5fa009658f9a7333ef24291b1291a25b6382dd68862a7f3b969f69b" +checksum = "7150335716dce6028bead2b848e72f47b45e7b9422f64cccdc23bedca89affc1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", -] - -[[package]] -name = "wasm-client-core" -version = "0.1.0" -dependencies = [ - "async-trait", - "console_error_panic_hook", - "js-sys", - "nym-bandwidth-controller", - "nym-client-core", - "nym-config", - "nym-credential-storage", - "nym-crypto", - "nym-gateway-client", - "nym-http-api-client", - "nym-sphinx", - "nym-sphinx-acknowledgements", - "nym-statistics-common", - "nym-task", - "nym-topology", - "nym-validator-client", - "rand 0.8.5", - "serde", - "serde-wasm-bindgen 0.6.5", - "thiserror 2.0.17", - "time", - "tsify", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "wasm-storage", - "wasm-utils", - "zeroize", -] - -[[package]] -name = "wasm-storage" -version = "0.1.0" -dependencies = [ - "async-trait", - "getrandom 0.2.16", - "indexed_db_futures", - "js-sys", - "nym-store-cipher", - "serde", - "serde-wasm-bindgen 0.6.5", - "thiserror 2.0.17", - "wasm-bindgen", - "wasm-utils", + "syn 2.0.114", ] [[package]] @@ -12932,27 +13033,11 @@ dependencies = [ "web-sys", ] -[[package]] -name = "wasm-utils" -version = "0.1.0" -dependencies = [ - "console_error_panic_hook", - "futures", - "getrandom 0.2.16", - "gloo-net", - "gloo-utils 0.2.0", - "js-sys", - "tungstenite 0.20.1", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - [[package]] name = "wasmtimer" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8d49b5d6c64e8558d9b1b065014426f35c18de636895d24893dbbd329743446" +checksum = "1c598d6b99ea013e35844697fc4670d08339d5cda15588f193c6beedd12f644b" dependencies = [ "futures", "js-sys", @@ -12964,9 +13049,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.77" +version = "0.3.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac" dependencies = [ "js-sys", "wasm-bindgen", @@ -12988,7 +13073,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57ffde1dc01240bdf9992e3205668b235e59421fd085e8a317ed98da0178d414" dependencies = [ - "phf", + "phf 0.11.3", "phf_codegen", "string_cache", "string_cache_codegen", @@ -13015,14 +13100,14 @@ version = "0.26.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" dependencies = [ - "webpki-roots 1.0.2", + "webpki-roots 1.0.5", ] [[package]] name = "webpki-roots" -version = "1.0.2" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e8983c3ab33d6fb807cfcdad2491c4ea8cbc8ed839181c7dfd9c67c83e261b2" +checksum = "12bed680863276c63889429bfd6cab3b99943659923822de1c8a39c49e4d722c" dependencies = [ "rustls-pki-types", ] @@ -13038,11 +13123,11 @@ dependencies = [ [[package]] name = "whoami" -version = "1.6.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6994d13118ab492c3c80c1f81928718159254c53c472bf9ce36f8dae4add02a7" +checksum = "5d4a4db5077702ca3015d3d02d74974948aba2ad9e12ab7df718ee64ccd7e97d" dependencies = [ - "redox_syscall", + "libredox", "wasite", "web-sys", ] @@ -13055,9 +13140,9 @@ checksum = "c168940144dd21fd8046987c16a46a33d5fc84eec29ef9dcddc2ac9e31526b7c" [[package]] name = "widestring" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd7cf3379ca1aac9eea11fba24fd7e315d621f8dfe35c8d7d2be8b793726e07d" +checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" [[package]] name = "winapi" @@ -13077,11 +13162,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.9" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -13189,7 +13274,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] @@ -13200,7 +13285,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] @@ -13313,7 +13398,7 @@ version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets 0.53.2", + "windows-targets 0.53.5", ] [[package]] @@ -13373,18 +13458,19 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.2" +version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ - "windows_aarch64_gnullvm 0.53.0", - "windows_aarch64_msvc 0.53.0", - "windows_i686_gnu 0.53.0", - "windows_i686_gnullvm 0.53.0", - "windows_i686_msvc 0.53.0", - "windows_x86_64_gnu 0.53.0", - "windows_x86_64_gnullvm 0.53.0", - "windows_x86_64_msvc 0.53.0", + "windows-link 0.2.1", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", ] [[package]] @@ -13425,9 +13511,9 @@ checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" [[package]] name = "windows_aarch64_msvc" @@ -13449,9 +13535,9 @@ checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_aarch64_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" [[package]] name = "windows_i686_gnu" @@ -13473,9 +13559,9 @@ checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnu" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" [[package]] name = "windows_i686_gnullvm" @@ -13485,9 +13571,9 @@ checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" [[package]] name = "windows_i686_msvc" @@ -13509,9 +13595,9 @@ checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_i686_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" [[package]] name = "windows_x86_64_gnu" @@ -13533,9 +13619,9 @@ checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnu" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" [[package]] name = "windows_x86_64_gnullvm" @@ -13557,9 +13643,9 @@ checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" [[package]] name = "windows_x86_64_msvc" @@ -13581,15 +13667,15 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "windows_x86_64_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winnow" -version = "0.7.12" +version = "0.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" +checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" dependencies = [ "memchr", ] @@ -13610,7 +13696,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22b4dbcc6c93786cf22e420ef96e8976bfb92a455070282302b74de5848191f4" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.10.0", "getrandom 0.2.16", "ipnet", "libloading", @@ -13621,19 +13707,16 @@ dependencies = [ ] [[package]] -name = "wit-bindgen-rt" -version = "0.39.0" +name = "wit-bindgen" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" -dependencies = [ - "bitflags 2.9.1", -] +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" [[package]] name = "writeable" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" [[package]] name = "wyz" @@ -13658,12 +13741,12 @@ dependencies = [ [[package]] name = "xattr" -version = "1.5.1" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af3a19837351dc82ba89f8a125e22a3c475f05aba604acc023d62b2739ae2909" +checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" dependencies = [ "libc", - "rustix 1.0.8", + "rustix", ] [[package]] @@ -13674,11 +13757,10 @@ checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" [[package]] name = "yoke" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" dependencies = [ - "serde", "stable_deref_trait", "yoke-derive", "zerofrom", @@ -13686,34 +13768,34 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", "synstructure", ] [[package]] name = "zerocopy" -version = "0.8.26" +version = "0.8.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" +checksum = "668f5168d10b9ee831de31933dc111a459c97ec93225beb307aed970d1372dfd" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.26" +version = "0.8.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" +checksum = "2c7962b26b0a8685668b671ee4b54d007a67d4eaf05fda79ac0ecf41e32270f1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] @@ -13733,7 +13815,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", "synstructure", ] @@ -13748,20 +13830,20 @@ dependencies = [ [[package]] name = "zeroize_derive" -version = "1.4.2" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] name = "zerotrie" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" dependencies = [ "displaydoc", "yoke", @@ -13770,9 +13852,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.2" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" dependencies = [ "yoke", "zerofrom", @@ -13781,13 +13863,13 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.114", ] [[package]] @@ -13801,7 +13883,7 @@ dependencies = [ "crossbeam-utils", "displaydoc", "flate2", - "indexmap 2.10.0", + "indexmap 2.13.0", "memchr", "thiserror 2.0.17", "zopfli", @@ -13809,7 +13891,7 @@ dependencies = [ [[package]] name = "zknym-lib" -version = "0.1.0" +version = "1.20.1" dependencies = [ "anyhow", "async-trait", @@ -13821,24 +13903,30 @@ dependencies = [ "nym-credentials", "nym-crypto", "nym-http-api-client", + "nym-wasm-utils", "rand 0.8.5", - "reqwest 0.12.22", + "reqwest 0.12.28", "serde", "thiserror 2.0.17", "tokio", "tsify", "uuid", "wasm-bindgen", - "wasm-utils", "wasmtimer", "zeroize", ] [[package]] -name = "zopfli" -version = "0.8.2" +name = "zmij" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edfc5ee405f504cd4984ecc6f14d02d55cfda60fa4b689434ef4102aae150cd7" +checksum = "2fc5a66a20078bf1251bde995aa2fdcc4b800c70b5d92dd2c62abc5c60f679f8" + +[[package]] +name = "zopfli" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249" dependencies = [ "bumpalo", "crc32fast", @@ -13866,9 +13954,9 @@ dependencies = [ [[package]] name = "zstd-sys" -version = "2.0.15+zstd.1.5.7" +version = "2.0.16+zstd.1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" dependencies = [ "cc", "pkg-config", @@ -13876,12 +13964,12 @@ dependencies = [ [[package]] name = "zulip-client" -version = "0.1.0" +version = "1.20.1" dependencies = [ "itertools 0.14.0", "nym-bin-common", "nym-http-api-client", - "reqwest 0.12.22", + "reqwest 0.12.28", "serde", "serde_json", "thiserror 2.0.17", diff --git a/Cargo.toml b/Cargo.toml index d9239e959d..7cf7fbe6d2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -152,7 +152,7 @@ members = [ "service-providers/common", "service-providers/ip-packet-router", "service-providers/network-requester", - "sqlx-pool-guard", + "nym-sqlx-pool-guard", "tools/echo-server", "tools/internal/contract-state-importer/importer-cli", "tools/internal/contract-state-importer/importer-contract", @@ -206,6 +206,7 @@ edition = "2024" license = "Apache-2.0" rust-version = "1.85" readme = "README.md" +version = "1.20.1" [workspace.dependencies] addr = "0.15.6" @@ -389,11 +390,112 @@ zeroize = "1.7.0" prometheus = { version = "0.14.0" } +# Workspace dep definitions required by crates.io publication - we need a workspace version since `cargo workspaces` doesn't work with path imports from crate manifests +nym-api-requests = { version = "1.20.1", path = "nym-api/nym-api-requests" } +nym-authenticator-requests = { version = "1.20.1", path = "common/authenticator-requests" } +nym-async-file-watcher = { version = "1.20.1", path = "common/async-file-watcher" } +nym-authenticator-client = { version = "1.20.1", path = "nym-authenticator-client" } +nym-bandwidth-controller = { version = "1.20.1", path = "common/bandwidth-controller" } +nym-bin-common = { version = "1.20.1", path = "common/bin-common" } +nym-cache = { version = "1.20.1", path = "common/nym-cache" } +nym-client-core = { version = "1.20.1", path = "common/client-core", default-features = false } +nym-client-core-config-types = { version = "1.20.1", path = "common/client-core/config-types" } +nym-client-core-gateways-storage = { version = "1.20.1", path = "common/client-core/gateways-storage" } +nym-client-core-surb-storage = { version = "1.20.1", path = "common/client-core/surb-storage" } +nym-client-websocket-requests = { version = "1.20.1", path = "clients/native/websocket-requests" } +nym-common = { version = "1.20.1", path = "common/nym-common" } +nym-compact-ecash = { version = "1.20.1", path = "common/nym_offline_compact_ecash" } +nym-config = { version = "1.20.1", path = "common/config" } +nym-contracts-common = { version = "1.20.1", path = "common/cosmwasm-smart-contracts/contracts-common" } +nym-coconut-dkg-common = { version = "1.20.1", path = "common/cosmwasm-smart-contracts/coconut-dkg" } +nym-credential-storage = { version = "1.20.1", path = "common/credential-storage" } +nym-credential-utils = { version = "1.20.1", path = "common/credential-utils" } +nym-credential-proxy-lib = { version = "1.20.1", path = "common/credential-proxy" } +nym-credentials = { version = "1.20.1", path = "common/credentials", default-features = false } +nym-credentials-interface = { version = "1.20.1", path = "common/credentials-interface" } +nym-credential-proxy-requests = { version = "1.20.1", path = "nym-credential-proxy/nym-credential-proxy-requests", default-features = false } +nym-credential-verification = { version = "1.20.1", path = "common/credential-verification" } +nym-crypto = { version = "1.20.1", path = "common/crypto", default-features = false } +nym-dkg = { version = "1.20.1", path = "common/dkg" } +nym-ecash-contract-common = { version = "1.20.1", path = "common/cosmwasm-smart-contracts/ecash-contract" } +nym-ecash-signer-check = { version = "1.20.1", path = "common/ecash-signer-check" } +nym-ecash-signer-check-types = { version = "1.20.1", path = "common/ecash-signer-check-types" } +nym-ecash-time = { version = "1.20.1", path = "common/ecash-time" } +nym-exit-policy = { version = "1.20.1", path = "common/exit-policy" } +nym-ffi-shared = { version = "1.20.1", path = "sdk/ffi/shared" } +nym-gateway-client = { version = "1.20.1", path = "common/client-libs/gateway-client", default-features = false } +nym-gateway-requests = { version = "1.20.1", path = "common/gateway-requests" } +nym-gateway-storage = { version = "1.20.1", path = "common/gateway-storage" } +nym-gateway-stats-storage = { version = "1.20.1", path = "common/gateway-stats-storage" } +nym-group-contract-common = { version = "1.20.1", path = "common/cosmwasm-smart-contracts/group-contract" } +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-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" } +nym-mixnet-client = { version = "1.20.1", path = "common/client-libs/mixnet-client" } +nym-mixnet-contract-common = { version = "1.20.1", path = "common/cosmwasm-smart-contracts/mixnet-contract" } +nym-multisig-contract-common = { version = "1.20.1", path = "common/cosmwasm-smart-contracts/multisig-contract" } +nym-network-defaults = { version = "1.20.1", path = "common/network-defaults" } +nym-node-tester-utils = { version = "1.20.1", path = "common/node-tester-utils" } +nym-noise = { version = "1.20.1", path = "common/nymnoise" } +nym-noise-keys = { version = "1.20.1", path = "common/nymnoise/keys" } +nym-nonexhaustive-delayqueue = { version = "1.20.1", path = "common/nonexhaustive-delayqueue" } +nym-node-requests = { version = "1.20.1", path = "nym-node/nym-node-requests", default-features = false } +nym-node-metrics = { version = "1.20.1", path = "nym-node/nym-node-metrics" } +nym-ordered-buffer = { version = "1.20.1", path = "common/socks5/ordered-buffer" } +nym-outfox = { version = "1.20.1", path = "nym-outfox" } +nym-registration-common = { version = "1.20.1", path = "common/registration" } +nym-pemstore = { version = "1.20.1", path = "common/pemstore" } +nym-performance-contract-common = { version = "1.20.1", path = "common/cosmwasm-smart-contracts/nym-performance-contract" } +nym-sdk = { version = "1.20.1", path = "sdk/rust/nym-sdk" } +nym-serde-helpers = { version = "1.20.1", path = "common/serde-helpers" } +nym-service-providers-common = { version = "1.20.1", path = "service-providers/common" } +nym-service-provider-requests-common = { version = "1.20.1", path = "common/service-provider-requests-common" } +nym-socks5-client-core = { version = "1.20.1", path = "common/socks5-client-core" } +nym-socks5-proxy-helpers = { version = "1.20.1", path = "common/socks5/proxy-helpers" } +nym-socks5-requests = { version = "1.20.1", path = "common/socks5/requests" } +nym-sphinx = { version = "1.20.1", path = "common/nymsphinx" } +nym-sphinx-acknowledgements = { version = "1.20.1", path = "common/nymsphinx/acknowledgements" } +nym-sphinx-addressing = { version = "1.20.1", path = "common/nymsphinx/addressing" } +nym-sphinx-anonymous-replies = { version = "1.20.1", path = "common/nymsphinx/anonymous-replies" } +nym-sphinx-chunking = { version = "1.20.1", path = "common/nymsphinx/chunking" } +nym-sphinx-cover = { version = "1.20.1", path = "common/nymsphinx/cover" } +nym-sphinx-forwarding = { version = "1.20.1", path = "common/nymsphinx/forwarding" } +nym-sphinx-framing = { version = "1.20.1", path = "common/nymsphinx/framing" } +nym-sphinx-params = { version = "1.20.1", path = "common/nymsphinx/params" } +nym-sphinx-routing = { version = "1.20.1", path = "common/nymsphinx/routing" } +nym-sphinx-types = { version = "1.20.1", path = "common/nymsphinx/types" } +nym-statistics-common = { version = "1.20.1", path = "common/statistics" } +nym-store-cipher = { version = "1.20.1", path = "common/store-cipher" } +nym-task = { version = "1.20.1", path = "common/task" } +nym-tun = { version = "1.20.1", path = "common/tun" } +nym-test-utils = { version = "1.20.1", path = "common/test-utils" } +nym-ticketbooks-merkle = { version = "1.20.1", path = "common/ticketbooks-merkle" } +nym-topology = { version = "1.20.1", path = "common/topology" } +nym-types = { version = "1.20.1", path = "common/types" } +nym-upgrade-mode-check = { version = "1.20.1", path = "common/upgrade-mode-check" } +nym-validator-client = { version = "1.20.1", path = "common/client-libs/validator-client", default-features = false } +nym-vesting-contract-common = { version = "1.20.1", path = "common/cosmwasm-smart-contracts/vesting-contract" } +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-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" } +nym-wasm-utils = { version = "1.20.1", path = "common/wasm/utils", default-features = false } +nyxd-scraper-shared = { version = "1.20.1", path = "common/nyxd-scraper-shared" } + # coconut/DKG related -# unfortunately until https://github.com/zkcrypto/bls12_381/issues/10 is resolved, we have to rely on the fork +# unfortunately until https://github.com/zkcrypto/nym-bls12_381-fork/issues/10 is resolved, we have to rely on the fork # as we need to be able to serialize Gt so that we could create the lookup table for baby-step-giant-step algorithm -# plus to make our live easier we need serde support from https://github.com/zkcrypto/bls12_381/pull/125 -bls12_381 = { git = "https://github.com/jstuczyn/bls12_381", default-features = false, branch = "temp/experimental-serdect-updated" } +# plus to make our live easier we need serde support from https://github.com/zkcrypto/nym-bls12_381-fork/pull/125 +nym-bls12_381-fork = { version = "0.8.0-forked", default-features = false } group = { version = "0.13.0", default-features = false } ff = { version = "0.13.1", default-features = false } subtle = "2.5.0" diff --git a/clients/native/Cargo.toml b/clients/native/Cargo.toml index 026f483626..07c8d66608 100644 --- a/clients/native/Cargo.toml +++ b/clients/native/Cargo.toml @@ -6,6 +6,7 @@ description = "Implementation of the Nym Client" edition = "2021" rust-version = "1.85" license.workspace = true +publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -42,32 +43,32 @@ tokio-tungstenite = { workspace = true } zeroize = { workspace = true } ## internal -nym-bandwidth-controller = { path = "../../common/bandwidth-controller" } -nym-bin-common = { path = "../../common/bin-common", features = [ +nym-bandwidth-controller = { workspace = true } +nym-bin-common = { workspace = true, features = [ "output_format", "clap", "basic_tracing", ] } -nym-client-core = { path = "../../common/client-core", features = [ +nym-client-core = { workspace = true, features = [ "fs-credentials-storage", "fs-surb-storage", "fs-gateways-storage", "cli", ] } -nym-config = { path = "../../common/config" } -nym-credential-storage = { path = "../../common/credential-storage" } -nym-credentials = { path = "../../common/credentials" } -nym-crypto = { path = "../../common/crypto" } -nym-gateway-requests = { path = "../../common/gateway-requests" } -nym-network-defaults = { path = "../../common/network-defaults" } -nym-sphinx = { path = "../../common/nymsphinx" } -nym-pemstore = { path = "../../common/pemstore" } -nym-task = { path = "../../common/task" } -nym-topology = { path = "../../common/topology" } -nym-validator-client = { path = "../../common/client-libs/validator-client", features = [ +nym-config = { workspace = true } +nym-credential-storage = { workspace = true } +nym-credentials = { workspace = true } +nym-crypto = { workspace = true } +nym-gateway-requests = { workspace = true } +nym-network-defaults = { workspace = true } +nym-sphinx = { workspace = true } +nym-pemstore = { workspace = true } +nym-task = { workspace = true } +nym-topology = { workspace = true } +nym-validator-client = { workspace = true, features = [ "http-client", ] } -nym-client-websocket-requests = { path = "websocket-requests" } -nym-id = { path = "../../common/nym-id" } +nym-client-websocket-requests = { workspace = true } +nym-id = { workspace = true } [dev-dependencies] diff --git a/clients/native/websocket-requests/Cargo.toml b/clients/native/websocket-requests/Cargo.toml index a3ef6a1f28..25defd82ff 100644 --- a/clients/native/websocket-requests/Cargo.toml +++ b/clients/native/websocket-requests/Cargo.toml @@ -1,9 +1,13 @@ [package] name = "nym-client-websocket-requests" -version = "0.1.0" +version.workspace = true authors = ["Jędrzej Stuczyński "] edition = "2021" license.workspace = true +description = "Request and response definitions for Nym client websocket connections" +repository.workspace = true +homepage.workspace = true +documentation.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -11,4 +15,4 @@ license.workspace = true serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } -nym-sphinx = { path = "../../../common/nymsphinx" } +nym-sphinx = { workspace = true } diff --git a/clients/socks5/Cargo.toml b/clients/socks5/Cargo.toml index 500dd6cc6a..c9ebf8fc44 100644 --- a/clients/socks5/Cargo.toml +++ b/clients/socks5/Cargo.toml @@ -6,6 +6,7 @@ description = "A SOCKS5 localhost proxy that converts incoming messages to Sphin edition = "2021" rust-version = "1.85" license.workspace = true +publish = false [dependencies] bs58 = { workspace = true } @@ -24,30 +25,30 @@ url = { workspace = true } zeroize = { workspace = true } # internal -nym-bin-common = { path = "../../common/bin-common", features = [ +nym-bin-common = { workspace = true, features = [ "output_format", "clap", "basic_tracing", ] } -nym-client-core = { path = "../../common/client-core", features = [ +nym-client-core = { workspace = true, features = [ "fs-credentials-storage", "fs-surb-storage", "fs-gateways-storage", "cli", ] } -nym-config = { path = "../../common/config" } -nym-credential-storage = { path = "../../common/credential-storage" } -nym-credentials = { path = "../../common/credentials" } -nym-crypto = { path = "../../common/crypto" } -nym-gateway-requests = { path = "../../common/gateway-requests" } -nym-id = { path = "../../common/nym-id" } -nym-network-defaults = { path = "../../common/network-defaults" } -nym-ordered-buffer = { path = "../../common/socks5/ordered-buffer" } -nym-pemstore = { path = "../../common/pemstore" } -nym-socks5-client-core = { path = "../../common/socks5-client-core" } -nym-sphinx = { path = "../../common/nymsphinx" } -nym-topology = { path = "../../common/topology" } -nym-validator-client = { path = "../../common/client-libs/validator-client", features = [ +nym-config = { workspace = true } +nym-credential-storage = { workspace = true } +nym-credentials = { workspace = true } +nym-crypto = { workspace = true } +nym-gateway-requests = { workspace = true } +nym-id = { workspace = true } +nym-network-defaults = { workspace = true } +nym-ordered-buffer = { workspace = true } +nym-pemstore = { workspace = true } +nym-socks5-client-core = { workspace = true } +nym-sphinx = { workspace = true } +nym-topology = { workspace = true } +nym-validator-client = { workspace = true, features = [ "http-client", ] } diff --git a/common/async-file-watcher/Cargo.toml b/common/async-file-watcher/Cargo.toml index cde165eaab..4c4af2ab4c 100644 --- a/common/async-file-watcher/Cargo.toml +++ b/common/async-file-watcher/Cargo.toml @@ -1,8 +1,12 @@ [package] name = "nym-async-file-watcher" -version = "0.1.0" +version.workspace = true edition.workspace = true license.workspace = true +description = "Simple file watcher that sends a notification whenever there was any change in the watched file" +repository.workspace = true +homepage.workspace = true +documentation.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/common/authenticator-requests/Cargo.toml b/common/authenticator-requests/Cargo.toml index 6126a18f80..89873c5c20 100644 --- a/common/authenticator-requests/Cargo.toml +++ b/common/authenticator-requests/Cargo.toml @@ -1,12 +1,13 @@ [package] name = "nym-authenticator-requests" -version = "0.1.0" +version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true documentation.workspace = true edition.workspace = true license.workspace = true +description = "Crate defining requests and responses for the Nym authenticator client" [dependencies] base64 = { workspace = true } @@ -18,12 +19,12 @@ strum_macros = { workspace = true } thiserror = { workspace = true } tracing = { workspace = true } -nym-credentials-interface = { path = "../credentials-interface" } -nym-crypto = { path = "../crypto", features = ["asymmetric"] } -nym-network-defaults = { path = "../network-defaults" } -nym-service-provider-requests-common = { path = "../service-provider-requests-common" } -nym-sphinx = { path = "../nymsphinx" } -nym-wireguard-types = { path = "../wireguard-types" } +nym-credentials-interface = { workspace = true } +nym-crypto = { workspace = true, features = ["asymmetric"] } +nym-network-defaults = { workspace = true } +nym-service-provider-requests-common = { workspace = true } +nym-sphinx = { workspace = true } +nym-wireguard-types = { workspace = true } ## verify: hmac = { workspace = true, optional = true } @@ -31,7 +32,7 @@ sha2 = { workspace = true, optional = true } x25519-dalek = { workspace = true, features = ["static_secrets"] } [dev-dependencies] -nym-test-utils = { path = "../test-utils" } +nym-test-utils = { workspace = true } [features] default = ["verify"] @@ -39,4 +40,4 @@ default = ["verify"] verify = ["hmac", "sha2"] [lints] -workspace = true \ No newline at end of file +workspace = true diff --git a/common/bandwidth-controller/Cargo.toml b/common/bandwidth-controller/Cargo.toml index a94abcc1cb..d93ddbab85 100644 --- a/common/bandwidth-controller/Cargo.toml +++ b/common/bandwidth-controller/Cargo.toml @@ -1,8 +1,12 @@ [package] name = "nym-bandwidth-controller" -version = "0.1.0" +version.workspace = true edition = "2021" license.workspace = true +description = "Crate for controlling the use of zknym credentials to ensure constant bandwidth availability for NymVPN app" +repository.workspace = true +homepage.workspace = true +documentation.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -12,14 +16,14 @@ log = { workspace = true } rand = { workspace = true } thiserror = { workspace = true } -nym-credential-storage = { path = "../credential-storage" } -nym-credentials = { path = "../credentials" } -nym-credentials-interface = { path = "../credentials-interface" } -nym-crypto = { path = "../crypto", features = ["rand", "asymmetric", "stream_cipher", "aes", "hashing"] } -nym-ecash-time = { path = "../ecash-time" } -nym-task = { path = "../task" } -nym-validator-client = { path = "../client-libs/validator-client", default-features = false } +nym-credential-storage = { workspace = true } +nym-credentials = { workspace = true } +nym-credentials-interface = { workspace = true } +nym-crypto = { workspace = true, features = ["rand", "asymmetric", "stream_cipher", "aes", "hashing"] } +nym-ecash-time = { workspace = true } +nym-task = { workspace = true } +nym-validator-client = { workspace = true } [target."cfg(not(target_arch = \"wasm32\"))".dependencies.nym-validator-client] -path = "../client-libs/validator-client" +workspace = true features = ["http-client"] diff --git a/common/bin-common/Cargo.toml b/common/bin-common/Cargo.toml index aa75898ec4..8220494794 100644 --- a/common/bin-common/Cargo.toml +++ b/common/bin-common/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-bin-common" -version = "0.6.0" +version.workspace = true description = "Common code for nym binaries" edition = { workspace = true } authors = { workspace = true } diff --git a/common/client-core/Cargo.toml b/common/client-core/Cargo.toml index 2f6d87dbc4..77cdf19c98 100644 --- a/common/client-core/Cargo.toml +++ b/common/client-core/Cargo.toml @@ -1,10 +1,14 @@ [package] name = "nym-client-core" -version = "1.1.15" +version.workspace = true authors = ["Dave Hrycyszyn "] edition = "2021" rust-version = "1.85" license.workspace = true +description = "Crate containing core client functionality and configs, used by all other Nym client implentations" +repository.workspace = true +homepage.workspace = true +documentation.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -31,32 +35,32 @@ tracing = { workspace = true } zeroize = { workspace = true } # internal -nym-id = { path = "../nym-id" } -nym-bandwidth-controller = { path = "../bandwidth-controller" } -nym-crypto = { path = "../crypto" } -nym-gateway-client = { path = "../client-libs/gateway-client" } -nym-gateway-requests = { path = "../gateway-requests" } -nym-http-api-client = { path = "../http-api-client", features = ["network-defaults"] } -nym-nonexhaustive-delayqueue = { path = "../nonexhaustive-delayqueue" } -nym-sphinx = { path = "../nymsphinx" } -nym-statistics-common = { path = "../statistics" } -nym-pemstore = { path = "../pemstore" } -nym-topology = { path = "../topology", features = ["persistence"] } -nym-validator-client = { path = "../client-libs/validator-client", default-features = false } -nym-task = { path = "../task" } -nym-credentials-interface = { path = "../credentials-interface" } -nym-credential-storage = { path = "../credential-storage" } -nym-network-defaults = { path = "../network-defaults" } -nym-client-core-config-types = { path = "./config-types", features = [ +nym-id = { workspace = true } +nym-bandwidth-controller = { workspace = true } +nym-crypto = { workspace = true } +nym-gateway-client = { workspace = true } +nym-gateway-requests = { workspace = true } +nym-http-api-client = { workspace = true, features = ["network-defaults"] } +nym-nonexhaustive-delayqueue = { workspace = true } +nym-sphinx = { workspace = true } +nym-statistics-common = { workspace = true } +nym-pemstore = { workspace = true } +nym-topology = { workspace = true, features = ["persistence"] } +nym-validator-client = { workspace = true } +nym-task = { workspace = true } +nym-credentials-interface = { workspace = true } +nym-credential-storage = { workspace = true } +nym-network-defaults = { workspace = true } +nym-client-core-config-types = { workspace = true, features = [ "disk-persistence", ] } -nym-client-core-surb-storage = { path = "./surb-storage" } -nym-client-core-gateways-storage = { path = "./gateways-storage" } -nym-ecash-time = { path = "../ecash-time" } -nym-mixnet-contract-common = { path = "../cosmwasm-smart-contracts/mixnet-contract" } +nym-client-core-surb-storage = { workspace = true } +nym-client-core-gateways-storage = { workspace = true } +nym-ecash-time = { workspace = true } +nym-mixnet-contract-common = { workspace = true } [target."cfg(not(target_arch = \"wasm32\"))".dependencies] -nym-mixnet-client = { path = "../client-libs/mixnet-client", default-features = false } +nym-mixnet-client = { workspace = true } ### For serving prometheus metrics [target."cfg(not(target_arch = \"wasm32\"))".dependencies.hyper] @@ -105,8 +109,8 @@ features = ["tokio"] workspace = true features = ["futures"] -[target."cfg(target_arch = \"wasm32\")".dependencies.wasm-utils] -path = "../wasm/utils" +[target."cfg(target_arch = \"wasm32\")".dependencies.nym-wasm-utils] +workspace = true features = ["websocket"] [target."cfg(target_arch = \"wasm32\")".dependencies.time] diff --git a/common/client-core/config-types/Cargo.toml b/common/client-core/config-types/Cargo.toml index 31184905f5..4c586ec1c3 100644 --- a/common/client-core/config-types/Cargo.toml +++ b/common/client-core/config-types/Cargo.toml @@ -1,8 +1,12 @@ [package] name = "nym-client-core-config-types" -version = "0.1.0" +version.workspace = true edition = "2021" license.workspace = true +description = "Low level configs and constants used by Nym clients and nodes" +repository.workspace = true +homepage.workspace = true +documentation.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -12,14 +16,14 @@ serde = { workspace = true, features = ["derive"] } thiserror.workspace = true url = { workspace = true, features = ["serde"] } -nym-config = { path = "../../config" } +nym-config = { workspace = true } -nym-pemstore = { path = "../../pemstore", optional = true } +nym-pemstore = { workspace = true , optional = true } # those are pulling so many deps T.T -nym-sphinx-params = { path = "../../nymsphinx/params" } -nym-sphinx-addressing = { path = "../../nymsphinx/addressing" } -nym-statistics-common = { path = "../../statistics" } +nym-sphinx-params = { workspace = true } +nym-sphinx-addressing = { workspace = true } +nym-statistics-common = { workspace = true } [features] diff --git a/common/client-core/gateways-storage/Cargo.toml b/common/client-core/gateways-storage/Cargo.toml index dc25a2b748..1a1e9f7c2d 100644 --- a/common/client-core/gateways-storage/Cargo.toml +++ b/common/client-core/gateways-storage/Cargo.toml @@ -1,9 +1,13 @@ [package] name = "nym-client-core-gateways-storage" -version = "0.1.0" +version.workspace = true edition = "2021" license.workspace = true rust-version.workspace = true +description = "Functionality for Nym clients to store and retrive Gateway connections" +repository.workspace = true +homepage.workspace = true +documentation.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -17,9 +21,9 @@ tracing.workspace = true url.workspace = true zeroize = { workspace = true, features = ["zeroize_derive"] } -nym-crypto = { path = "../../crypto", features = ["asymmetric"] } -nym-gateway-requests = { path = "../../gateway-requests" } -nym-gateway-client = { path = "../../client-libs/gateway-client" } +nym-crypto = { workspace = true, features = ["asymmetric"] } +nym-gateway-requests = { workspace = true} +nym-gateway-client = { workspace = true} [target."cfg(not(target_arch = \"wasm32\"))".dependencies.sqlx] workspace = true diff --git a/common/client-core/src/client/base_client/mod.rs b/common/client-core/src/client/base_client/mod.rs index 600085b4cf..92565c144b 100644 --- a/common/client-core/src/client/base_client/mod.rs +++ b/common/client-core/src/client/base_client/mod.rs @@ -71,7 +71,7 @@ use url::Url; #[cfg(target_arch = "wasm32")] #[cfg(debug_assertions)] -use wasm_utils::console_log; +use nym_wasm_utils::console_log; /// Default number of retries for Nym API requests when using network details with domain fronting. /// This allows the client to try alternative URLs if the primary endpoint is unavailable. diff --git a/common/client-core/src/client/real_messages_control/real_traffic_stream.rs b/common/client-core/src/client/real_messages_control/real_traffic_stream.rs index 1b90208b6d..12fa62e3ad 100644 --- a/common/client-core/src/client/real_messages_control/real_traffic_stream.rs +++ b/common/client-core/src/client/real_messages_control/real_traffic_stream.rs @@ -31,7 +31,7 @@ use tracing::*; #[cfg(not(target_arch = "wasm32"))] use tokio::time::{sleep, Sleep}; -// use wasm_utils::console_log; +// use nym_wasm_utils::console_log; #[cfg(target_arch = "wasm32")] use wasmtimer::tokio::{sleep, Sleep}; mod sending_delay_controller; diff --git a/common/client-core/src/init/helpers.rs b/common/client-core/src/init/helpers.rs index 8cb926fe55..3400981309 100644 --- a/common/client-core/src/init/helpers.rs +++ b/common/client-core/src/init/helpers.rs @@ -23,6 +23,8 @@ use url::Url; use crate::init::websockets::connect_async; use nym_topology::NodeId; +#[cfg(target_arch = "wasm32")] +use nym_wasm_utils::websocket::JSWebsocket; #[cfg(not(target_arch = "wasm32"))] use tokio::net::TcpStream; #[cfg(not(target_arch = "wasm32"))] @@ -32,8 +34,6 @@ use tokio::time::Instant; #[cfg(not(target_arch = "wasm32"))] use tokio_tungstenite::{MaybeTlsStream, WebSocketStream}; #[cfg(target_arch = "wasm32")] -use wasm_utils::websocket::JSWebsocket; -#[cfg(target_arch = "wasm32")] use wasmtimer::std::Instant; #[cfg(target_arch = "wasm32")] use wasmtimer::tokio::sleep; diff --git a/common/client-core/surb-storage/Cargo.toml b/common/client-core/surb-storage/Cargo.toml index be03487d95..04168ec19e 100644 --- a/common/client-core/surb-storage/Cargo.toml +++ b/common/client-core/surb-storage/Cargo.toml @@ -1,8 +1,12 @@ [package] name = "nym-client-core-surb-storage" -version = "0.1.0" +version.workspace = true edition = "2021" license.workspace = true +description = "Functionality for Nym clients to generate and use Single Use Reply Blocks (SURBs)" +repository.workspace = true +homepage.workspace = true +documentation.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -13,9 +17,9 @@ tracing.workspace = true thiserror.workspace = true time.workspace = true -nym-crypto = { path = "../../crypto", optional = true, default-features = false } -nym-sphinx = { path = "../../nymsphinx" } -nym-task = { path = "../../task" } +nym-crypto = { workspace = true, optional = true, default-features = false } +nym-sphinx = { workspace = true } +nym-task = { workspace = true } [target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio] workspace = true @@ -26,8 +30,8 @@ workspace = true features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate", "time"] optional = true -[target."cfg(not(target_arch = \"wasm32\"))".dependencies.sqlx-pool-guard] -path = "../../../sqlx-pool-guard" +[target."cfg(not(target_arch = \"wasm32\"))".dependencies.nym-sqlx-pool-guard] +workspace = true [build-dependencies] anyhow = { workspace = true } diff --git a/common/client-core/surb-storage/src/backend/fs_backend/manager.rs b/common/client-core/surb-storage/src/backend/fs_backend/manager.rs index 6edde091ce..b0a6ec68f7 100644 --- a/common/client-core/surb-storage/src/backend/fs_backend/manager.rs +++ b/common/client-core/surb-storage/src/backend/fs_backend/manager.rs @@ -13,7 +13,7 @@ use std::path::Path; use time::OffsetDateTime; use tracing::{error, info}; -use sqlx_pool_guard::SqlitePoolGuard; +use nym_sqlx_pool_guard::SqlitePoolGuard; #[derive(Debug, Clone)] pub struct StorageManager { diff --git a/common/client-libs/gateway-client/Cargo.toml b/common/client-libs/gateway-client/Cargo.toml index 969d94807e..914c23a45e 100644 --- a/common/client-libs/gateway-client/Cargo.toml +++ b/common/client-libs/gateway-client/Cargo.toml @@ -1,9 +1,13 @@ [package] name = "nym-gateway-client" -version = "0.1.0" +version.workspace = true authors = ["Jędrzej Stuczyński "] edition = "2021" license.workspace = true +description = "Functions and types for Nym client <> Gateway connections" +repository.workspace = true +homepage.workspace = true +documentation.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -21,19 +25,19 @@ time.workspace = true zeroize.workspace = true # internal -nym-bandwidth-controller = { path = "../../bandwidth-controller" } -nym-credentials = { path = "../../credentials" } -nym-credential-storage = { path = "../../credential-storage" } -nym-credentials-interface = { path = "../../credentials-interface" } -nym-crypto = { path = "../../crypto" } -nym-gateway-requests = { path = "../../gateway-requests" } -nym-http-api-client = { path = "../../http-api-client" } -nym-network-defaults = { path = "../../network-defaults" } -nym-sphinx = { path = "../../nymsphinx" } -nym-statistics-common = { path = "../../statistics" } -nym-pemstore = { path = "../../pemstore" } -nym-validator-client = { path = "../validator-client", default-features = false } -nym-task = { path = "../../task" } +nym-bandwidth-controller = { workspace = true } +nym-credentials = { workspace = true } +nym-credential-storage = { workspace = true } +nym-credentials-interface = { workspace = true } +nym-crypto = { workspace = true } +nym-gateway-requests = { workspace = true } +nym-http-api-client = { workspace = true } +nym-network-defaults = { workspace = true } +nym-sphinx = { workspace = true } +nym-statistics-common = { workspace = true } +nym-pemstore = { workspace = true } +nym-validator-client = { workspace = true, default-features = false } +nym-task = { workspace = true } serde = { workspace = true, features = ["derive"] } @@ -61,8 +65,8 @@ workspace = true [target."cfg(target_arch = \"wasm32\")".dependencies.wasm-bindgen-futures] workspace = true -[target."cfg(target_arch = \"wasm32\")".dependencies.wasm-utils] -path = "../../wasm/utils" +[target."cfg(target_arch = \"wasm32\")".dependencies.nym-wasm-utils] +workspace = true features = ["websocket"] [target."cfg(target_arch = \"wasm32\")".dependencies.gloo-utils] @@ -90,4 +94,4 @@ features = ["js"] wasm = [] [lints] -workspace = true \ No newline at end of file +workspace = true diff --git a/common/client-libs/gateway-client/src/client/mod.rs b/common/client-libs/gateway-client/src/client/mod.rs index 1d75b31835..a657617a8c 100644 --- a/common/client-libs/gateway-client/src/client/mod.rs +++ b/common/client-libs/gateway-client/src/client/mod.rs @@ -41,11 +41,11 @@ use std::os::fd::RawFd; #[cfg(not(target_arch = "wasm32"))] use tokio::time::sleep; +#[cfg(target_arch = "wasm32")] +use nym_wasm_utils::websocket::JSWebsocket; #[cfg(not(unix))] use std::os::raw::c_int as RawFd; #[cfg(target_arch = "wasm32")] -use wasm_utils::websocket::JSWebsocket; -#[cfg(target_arch = "wasm32")] use wasmtimer::tokio::sleep; pub mod config; diff --git a/common/client-libs/gateway-client/src/socket_state.rs b/common/client-libs/gateway-client/src/socket_state.rs index 1a464aa304..c07b360be8 100644 --- a/common/client-libs/gateway-client/src/socket_state.rs +++ b/common/client-libs/gateway-client/src/socket_state.rs @@ -29,7 +29,7 @@ use tokio::net::TcpStream; use tokio_tungstenite::{MaybeTlsStream, WebSocketStream}; #[cfg(target_arch = "wasm32")] -use wasm_utils::websocket::JSWebsocket; +use nym_wasm_utils::websocket::JSWebsocket; // type alias for not having to type the whole thing every single time (and now it makes it easier // to use different types based on compilation target) diff --git a/common/client-libs/mixnet-client/Cargo.toml b/common/client-libs/mixnet-client/Cargo.toml index 2a8a39383b..72f838e50b 100644 --- a/common/client-libs/mixnet-client/Cargo.toml +++ b/common/client-libs/mixnet-client/Cargo.toml @@ -1,9 +1,13 @@ [package] name = "nym-mixnet-client" -version = "0.1.0" +version.workspace = true authors = ["Jedrzej Stuczynski "] edition = "2021" license.workspace = true +description = "Client for Mix Node <> Mix Node & Mix Node <> Gateway communication" +repository.workspace = true +homepage.workspace = true +documentation.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -16,14 +20,14 @@ tokio-util = { workspace = true, features = ["codec"], optional = true } tokio-stream = { workspace = true } # internal -nym-noise = { path = "../../nymnoise" } -nym-sphinx = { path = "../../nymsphinx" } -nym-task = { path = "../../task", optional = true } +nym-noise = { workspace = true } +nym-sphinx = { workspace = true } +nym-task = { workspace = true, optional = true } [features] default = ["client"] client = ["tokio-util", "nym-task", "tokio/net", "tokio/rt"] [dev-dependencies] -nym-crypto = { path = "../../crypto" } +nym-crypto = { workspace = true } rand = { workspace = true } diff --git a/common/client-libs/validator-client/Cargo.toml b/common/client-libs/validator-client/Cargo.toml index ec4eb62bec..4faa1a0943 100644 --- a/common/client-libs/validator-client/Cargo.toml +++ b/common/client-libs/validator-client/Cargo.toml @@ -1,10 +1,14 @@ [package] name = "nym-validator-client" -version = "0.1.0" +version.workspace = true authors = ["Jędrzej Stuczyński "] edition = "2021" rust-version = "1.85" license.workspace = true +description = "Client for interacting with Nyx Cosmos SDK blockchain" +repository.workspace = true +homepage.workspace = true +documentation.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -12,18 +16,18 @@ license.workspace = true base64 = { workspace = true } colored = { workspace = true } -nym-coconut-dkg-common = { path = "../../cosmwasm-smart-contracts/coconut-dkg" } -nym-contracts-common = { path = "../../cosmwasm-smart-contracts/contracts-common" } -nym-mixnet-contract-common = { path = "../../cosmwasm-smart-contracts/mixnet-contract" } -nym-vesting-contract-common = { path = "../../cosmwasm-smart-contracts/vesting-contract" } -nym-ecash-contract-common = { path = "../../cosmwasm-smart-contracts/ecash-contract" } -nym-multisig-contract-common = { path = "../../cosmwasm-smart-contracts/multisig-contract" } -nym-group-contract-common = { path = "../../cosmwasm-smart-contracts/group-contract" } -nym-performance-contract-common = { path = "../../cosmwasm-smart-contracts/nym-performance-contract" } -nym-serde-helpers = { path = "../../serde-helpers", features = ["hex", "base64"] } +nym-coconut-dkg-common = { workspace = true } +nym-contracts-common = { workspace = true } +nym-mixnet-contract-common = { workspace = true } +nym-vesting-contract-common = { workspace = true } +nym-ecash-contract-common = { workspace = true } +nym-multisig-contract-common = { workspace = true } +nym-group-contract-common = { workspace = true } +nym-performance-contract-common = { workspace = true } +nym-serde-helpers = { workspace = true, features = ["hex", "base64"] } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } -nym-http-api-client = { path = "../../../common/http-api-client" } +nym-http-api-client = { workspace = true } thiserror = { workspace = true } tracing = { workspace = true } url = { workspace = true, features = ["serde"] } @@ -31,13 +35,13 @@ tokio = { workspace = true, features = ["sync", "time"] } time = { workspace = true, features = ["formatting"] } futures = { workspace = true } -nym-compact-ecash = { path = "../../nym_offline_compact_ecash" } -nym-network-defaults = { path = "../../network-defaults" } -nym-api-requests = { path = "../../../nym-api/nym-api-requests" } +nym-compact-ecash = { workspace = true } +nym-network-defaults = { workspace = true } +nym-api-requests = { workspace = true } async-trait = { workspace = true } bip39 = { workspace = true, features = ["rand"] } -nym-config = { path = "../../config" } +nym-config = { workspace = true } cosmrs = { workspace = true, features = ["bip32", "cosmwasm"] } # note that this has the same version as used by cosmrs diff --git a/common/commands/Cargo.toml b/common/commands/Cargo.toml index 4398342d4c..1c7d1f8da8 100644 --- a/common/commands/Cargo.toml +++ b/common/commands/Cargo.toml @@ -1,9 +1,13 @@ [package] name = "nym-cli-commands" -version = "1.0.0" +version.workspace = true authors.workspace = true edition = "2021" license.workspace = true +description = "Common commands crate used by the nym-cli tool for interacting with the Nyx Cosmos SDK blockchain and Mixnet endpoints" +repository.workspace = true +homepage.workspace = true +documentation.workspace = true [dependencies] anyhow = { workspace = true } @@ -37,28 +41,28 @@ zeroize = { workspace = true } cosmrs = { workspace = true } cosmwasm-std = { workspace = true } -nym-validator-client = { path = "../client-libs/validator-client" } -nym-http-api-client = { path = "../http-api-client" } -nym-bin-common = { path = "../../common/bin-common", features = ["output_format"] } -nym-crypto = { path = "../../common/crypto", features = ["asymmetric"] } -nym-network-defaults = { path = "../network-defaults" } -nym-contracts-common = { path = "../cosmwasm-smart-contracts/contracts-common" } -nym-bandwidth-controller = { path = "../../common/bandwidth-controller" } -nym-mixnet-contract-common = { path = "../cosmwasm-smart-contracts/mixnet-contract" } -nym-vesting-contract-common = { path = "../cosmwasm-smart-contracts/vesting-contract" } -nym-coconut-dkg-common = { path = "../cosmwasm-smart-contracts/coconut-dkg" } -nym-multisig-contract-common = { path = "../cosmwasm-smart-contracts/multisig-contract" } -nym-ecash-contract-common = { path = "../cosmwasm-smart-contracts/ecash-contract" } -nym-ecash-time = { path = "../../common/ecash-time" } -nym-sphinx = { path = "../../common/nymsphinx" } -nym-client-core = { path = "../../common/client-core" } -nym-config = { path = "../../common/config" } -nym-credentials = { path = "../../common/credentials" } -nym-credentials-interface = { path = "../../common/credentials-interface" } -nym-credential-storage = { path = "../../common/credential-storage" } -nym-credential-utils = { path = "../../common/credential-utils" } -nym-id = { path = "../nym-id" } -nym-credential-proxy-requests = { path = "../../nym-credential-proxy/nym-credential-proxy-requests" } +nym-validator-client = { workspace = true} +nym-http-api-client = { workspace = true} +nym-bin-common = { workspace = true, features = ["output_format"] } +nym-crypto = { workspace = true, features = ["asymmetric"] } +nym-network-defaults = { workspace = true } +nym-contracts-common = { workspace = true } +nym-bandwidth-controller = { workspace = true } +nym-mixnet-contract-common = { workspace = true } +nym-vesting-contract-common = { workspace = true } +nym-coconut-dkg-common = { workspace = true } +nym-multisig-contract-common = { workspace = true } +nym-ecash-contract-common = { workspace = true } +nym-ecash-time = { workspace = true } +nym-sphinx = { workspace = true } +nym-client-core = { workspace = true } +nym-config = { workspace = true } +nym-credentials = { workspace = true } +nym-credentials-interface = { workspace = true } +nym-credential-storage = { workspace = true } +nym-credential-utils = { workspace = true } +nym-id = { workspace = true } +nym-credential-proxy-requests = { workspace = true } -nym-pemstore = { path = "../../common/pemstore", version = "0.3.0" } -nym-types = { path = "../../common/types" } +nym-pemstore = { workspace = true } +nym-types = { workspace = true } diff --git a/common/config/Cargo.toml b/common/config/Cargo.toml index d978fe4aee..4c364f9292 100644 --- a/common/config/Cargo.toml +++ b/common/config/Cargo.toml @@ -1,9 +1,11 @@ [package] name = "nym-config" -version = "0.1.0" +version.workspace = true authors = ["Jedrzej Stuczynski "] edition = "2021" license.workspace = true +homepage.workspace = true +description = "Config related helpers and functions" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -16,7 +18,7 @@ thiserror = { workspace = true } toml = { workspace = true, features = ["display"] } url = { workspace = true } -nym-network-defaults = { path = "../network-defaults", features = ["utoipa"] } +nym-network-defaults = { workspace = true, features = ["utoipa"] } [features] default = ["dirs"] diff --git a/common/cosmwasm-smart-contracts/coconut-dkg/Cargo.toml b/common/cosmwasm-smart-contracts/coconut-dkg/Cargo.toml index d2a9cbd50f..158b920e24 100644 --- a/common/cosmwasm-smart-contracts/coconut-dkg/Cargo.toml +++ b/common/cosmwasm-smart-contracts/coconut-dkg/Cargo.toml @@ -1,8 +1,12 @@ [package] name = "nym-coconut-dkg-common" -version = "0.1.0" +version.workspace = true edition = "2021" license.workspace = true +description = "Common crate for Nym's DKG cosmwasm contract" +repository.workspace = true +homepage.workspace = true +documentation.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -13,8 +17,8 @@ cw-utils = { workspace = true } cw2 = { workspace = true } cw4 = { workspace = true } -contracts-common = { path = "../contracts-common", package = "nym-contracts-common" } -nym-multisig-contract-common = { path = "../multisig-contract" } +nym-contracts-common = { workspace = true } +nym-multisig-contract-common = { workspace = true } [features] schema = [] diff --git a/common/cosmwasm-smart-contracts/coconut-dkg/src/dealing.rs b/common/cosmwasm-smart-contracts/coconut-dkg/src/dealing.rs index 347a158a55..b65e4109e1 100644 --- a/common/cosmwasm-smart-contracts/coconut-dkg/src/dealing.rs +++ b/common/cosmwasm-smart-contracts/coconut-dkg/src/dealing.rs @@ -2,9 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 use crate::types::{ChunkIndex, DealingIndex, EpochId, PartialContractDealingData}; -use contracts_common::dealings::ContractSafeBytes; use cosmwasm_schema::cw_serde; use cosmwasm_std::Addr; +use nym_contracts_common::dealings::ContractSafeBytes; use std::collections::{BTreeMap, HashMap}; /// Defines the maximum size of a dealing chunk. Currently set to 2kB diff --git a/common/cosmwasm-smart-contracts/coconut-dkg/src/msg.rs b/common/cosmwasm-smart-contracts/coconut-dkg/src/msg.rs index d0c8972117..1783dfaf78 100644 --- a/common/cosmwasm-smart-contracts/coconut-dkg/src/msg.rs +++ b/common/cosmwasm-smart-contracts/coconut-dkg/src/msg.rs @@ -6,8 +6,8 @@ use crate::types::{ ChunkIndex, DealingIndex, EncodedBTEPublicKeyWithProof, EpochId, TimeConfiguration, }; use crate::verification_key::VerificationKeyShare; -use contracts_common::IdentityKey; use cosmwasm_schema::cw_serde; +use nym_contracts_common::IdentityKey; #[cfg(feature = "schema")] use crate::{ diff --git a/common/cosmwasm-smart-contracts/coconut-dkg/src/types.rs b/common/cosmwasm-smart-contracts/coconut-dkg/src/types.rs index 5cf5fd780e..6e4fdef06a 100644 --- a/common/cosmwasm-smart-contracts/coconut-dkg/src/types.rs +++ b/common/cosmwasm-smart-contracts/coconut-dkg/src/types.rs @@ -6,9 +6,9 @@ use std::fmt::{Display, Formatter}; use std::str::FromStr; pub use crate::dealer::{DealerDetails, DealerRegistrationDetails, PagedDealerResponse}; -pub use contracts_common::dealings::ContractSafeBytes; pub use cosmwasm_std::{Addr, Coin, Timestamp}; pub use cw4::Cw4Contract; +pub use nym_contracts_common::dealings::ContractSafeBytes; pub type EncodedBTEPublicKeyWithProof = String; pub type EncodedBTEPublicKeyWithProofRef<'a> = &'a str; diff --git a/common/cosmwasm-smart-contracts/contracts-common-testing/Cargo.toml b/common/cosmwasm-smart-contracts/contracts-common-testing/Cargo.toml index 0bb81f1792..69f429b24d 100644 --- a/common/cosmwasm-smart-contracts/contracts-common-testing/Cargo.toml +++ b/common/cosmwasm-smart-contracts/contracts-common-testing/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-contracts-common-testing" -version = "0.1.0" +version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true @@ -9,6 +9,7 @@ edition.workspace = true license.workspace = true rust-version.workspace = true readme.workspace = true +description = "Common crate for cosmwasm contract tests" [dependencies] anyhow = { workspace = true } @@ -20,7 +21,7 @@ rand_chacha = { workspace = true } rand = { workspace = true } cw-multi-test = { workspace = true } -nym-contracts-common = { path = "../contracts-common" } +nym-contracts-common = { workspace = true } [lints] workspace = true diff --git a/common/cosmwasm-smart-contracts/contracts-common/Cargo.toml b/common/cosmwasm-smart-contracts/contracts-common/Cargo.toml index c13d29a7ec..277351d5d0 100644 --- a/common/cosmwasm-smart-contracts/contracts-common/Cargo.toml +++ b/common/cosmwasm-smart-contracts/contracts-common/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-contracts-common" -version = "0.5.0" +version.workspace = true description = "Common library for Nym cosmwasm contracts" edition = { workspace = true } authors = { workspace = true } diff --git a/common/cosmwasm-smart-contracts/easy_addr/Cargo.toml b/common/cosmwasm-smart-contracts/easy_addr/Cargo.toml index 992378fbe0..b8a48b40a7 100644 --- a/common/cosmwasm-smart-contracts/easy_addr/Cargo.toml +++ b/common/cosmwasm-smart-contracts/easy_addr/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "easy-addr" -version = "0.1.0" +version.workspace = true edition = "2021" publish = false license.workspace = true @@ -11,4 +11,4 @@ proc-macro = true [dependencies] cosmwasm-std = { workspace = true } quote = { workspace = true } -syn = { workspace = true, features = ["full", "printing", "extra-traits"] } \ No newline at end of file +syn = { workspace = true, features = ["full", "printing", "extra-traits"] } diff --git a/common/cosmwasm-smart-contracts/ecash-contract/Cargo.toml b/common/cosmwasm-smart-contracts/ecash-contract/Cargo.toml index 54bc53fe18..dce59308ce 100644 --- a/common/cosmwasm-smart-contracts/ecash-contract/Cargo.toml +++ b/common/cosmwasm-smart-contracts/ecash-contract/Cargo.toml @@ -1,8 +1,12 @@ [package] name = "nym-ecash-contract-common" -version = "0.1.0" +version.workspace = true edition = "2021" license.workspace = true +description = "Common crate for Nym's ecash/zknym cosmwasm contract" +repository.workspace = true +homepage.workspace = true +documentation.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -11,7 +15,7 @@ bs58.workspace = true cosmwasm-std = { workspace = true } cosmwasm-schema = { workspace = true } cw2 = { workspace = true, optional = true } -nym-multisig-contract-common = { path = "../multisig-contract" } +nym-multisig-contract-common = { workspace = true } thiserror.workspace = true cw-utils = { workspace = true } cw-controllers = { workspace = true } diff --git a/common/cosmwasm-smart-contracts/group-contract/Cargo.toml b/common/cosmwasm-smart-contracts/group-contract/Cargo.toml index e9a942139f..17cb3e83db 100644 --- a/common/cosmwasm-smart-contracts/group-contract/Cargo.toml +++ b/common/cosmwasm-smart-contracts/group-contract/Cargo.toml @@ -1,8 +1,12 @@ [package] name = "nym-group-contract-common" -version = "0.1.0" +version.workspace = true edition = "2021" license.workspace = true +description = "Common crate for Nym's group cosmwasm contract" +repository.workspace = true +homepage.workspace = true +documentation.workspace = true [dependencies] cosmwasm-schema = { workspace = true } diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/Cargo.toml b/common/cosmwasm-smart-contracts/mixnet-contract/Cargo.toml index ed9b7ac125..fb7143723d 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/Cargo.toml +++ b/common/cosmwasm-smart-contracts/mixnet-contract/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-mixnet-contract-common" -version = "0.6.0" +version.workspace = true description = "Common library for the Nym mixnet contract" rust-version = "1.85" edition = { workspace = true } @@ -22,7 +22,7 @@ semver = { workspace = true, features = ["serde"] } # we still have to preserve that import for `JsonSchema` for `Layer` type (since we can't use cw_serde macro due to custom serde impl) schemars = { workspace = true } thiserror = { workspace = true } -contracts-common = { path = "../contracts-common", package = "nym-contracts-common", version = "0.5.0" } +nym-contracts-common = { workspace = true } humantime-serde = { workspace = true } utoipa = { workspace = true, optional = true } @@ -41,4 +41,4 @@ schema = ["cw2"] generate-ts = ['ts-rs'] [lints] -workspace = true \ No newline at end of file +workspace = true diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/error.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/error.rs index 9298b38523..1d27cd69d0 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/error.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/error.rs @@ -5,10 +5,10 @@ use crate::nym_node::Role; use crate::{ EpochEventId, EpochState, IntervalEventId, NodeId, OperatingCostRange, ProfitMarginRange, }; -use contracts_common::Percent; -use contracts_common::signing::verifier::ApiVerifierError; use cosmwasm_std::{Addr, Coin, Decimal, Uint128}; use cw_controllers::AdminError; +use nym_contracts_common::Percent; +use nym_contracts_common::signing::verifier::ApiVerifierError; use thiserror::Error; #[derive(Error, Debug, PartialEq)] diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/events.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/events.rs index a53c5dc1a9..572b8131a6 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/events.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/events.rs @@ -8,8 +8,8 @@ use crate::nym_node::Role; use crate::reward_params::{ActiveSetUpdate, IntervalRewardParams, IntervalRewardingParamsUpdate}; use crate::rewarding::RewardDistribution; use crate::{BlockHeight, ContractStateParamsUpdate, EpochId, IdentityKeyRef, Interval, NodeId}; -pub use contracts_common::events::*; use cosmwasm_std::{Addr, Coin, Decimal, Event, attr}; +pub use nym_contracts_common::events::*; use std::fmt::Display; pub const EVENT_VERSION_PREFIX: &str = "v2_"; diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/helpers.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/helpers.rs index b5032e64be..dc56386238 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/helpers.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/helpers.rs @@ -7,8 +7,8 @@ use crate::{ EpochEventId, IntervalEventId, MixNodeBond, MixNodeDetails, NodeId, NodeRewarding, NymNodeBond, NymNodeDetails, PendingNodeChanges, }; -use contracts_common::IdentityKeyRef; use cosmwasm_std::{Coin, Decimal, StdError, StdResult, Uint128}; +use nym_contracts_common::IdentityKeyRef; #[track_caller] pub fn compare_decimals(a: Decimal, b: Decimal, epsilon: Option) { diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/lib.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/lib.rs index eb1d533270..3c6f4b6b08 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/lib.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/lib.rs @@ -21,7 +21,6 @@ pub mod types; pub use config_score::*; pub use constants::*; -pub use contracts_common::types::*; pub use cosmwasm_std::{Addr, Coin, Decimal, Fraction}; pub use delegation::{ Delegation, PagedAllDelegationsResponse, PagedDelegatorDelegationsResponse, @@ -41,6 +40,7 @@ pub use mixnode::{ NodeRewarding, PagedMixnodeBondsResponse, UnbondedMixnode, }; pub use msg::*; +pub use nym_contracts_common::types::*; pub use nym_node::{NymNode, NymNodeBond, NymNodeDetails, PendingNodeChanges}; pub use pending_events::{ EpochEventId, IntervalEventId, NumberOfPendingEventsResponse, PendingEpochEvent, diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/msg.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/msg.rs index a85d86fedf..4b0d31016d 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/msg.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/msg.rs @@ -18,9 +18,9 @@ use crate::{ VersionScoreFormulaParams, }; use crate::{OperatingCostRange, ProfitMarginRange}; -use contracts_common::{IdentityKey, Percent, signing::MessageSignature}; use cosmwasm_schema::cw_serde; use cosmwasm_std::{Coin, Decimal}; +use nym_contracts_common::{IdentityKey, Percent, signing::MessageSignature}; use std::time::Duration; #[cfg(feature = "schema")] @@ -55,9 +55,9 @@ use crate::{ types::{ContractState, ContractStateParams}, }; #[cfg(feature = "schema")] -use contracts_common::{ContractBuildInformation, signing::Nonce}; -#[cfg(feature = "schema")] use cosmwasm_schema::QueryResponses; +#[cfg(feature = "schema")] +use nym_contracts_common::{ContractBuildInformation, signing::Nonce}; #[cw_serde] pub struct InstantiateMsg { diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/nym_node.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/nym_node.rs index 6fc172db09..d82ca38907 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/nym_node.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/nym_node.rs @@ -3,10 +3,10 @@ use crate::error::MixnetContractError; use crate::{EpochEventId, EpochId, Gateway, IntervalEventId, MixNode, NodeId, NodeRewarding}; -use contracts_common::IdentityKey; use cosmwasm_schema::cw_serde; use cosmwasm_std::{Addr, Coin, Decimal, StdError, StdResult}; use cw_storage_plus::{IntKey, Key, KeyDeserialize, PrimaryKey}; +use nym_contracts_common::IdentityKey; use std::fmt::{Display, Formatter}; #[cw_serde] diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/rewarding/helpers.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/rewarding/helpers.rs index 4d9ca75459..f128a9c2c6 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/rewarding/helpers.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/rewarding/helpers.rs @@ -1,8 +1,8 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use contracts_common::truncate_decimal; use cosmwasm_std::{Coin, Decimal, Uint128}; +use nym_contracts_common::truncate_decimal; /// Truncates all decimal points so that the reward would fit in a `Coin` and so that we would /// never attempt to reward more than the owner is due diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/signing_types.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/signing_types.rs index 00130d52d8..6cd247d18c 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/signing_types.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/signing_types.rs @@ -3,11 +3,11 @@ use crate::nym_node::NymNode; use crate::{Gateway, MixNode, NodeCostParams}; -use contracts_common::signing::{ +use cosmwasm_std::{Addr, Coin}; +use nym_contracts_common::signing::{ ContractMessageContent, LegacyContractMessageContent, MessageType, Nonce, SignableMessage, SigningPurpose, }; -use cosmwasm_std::{Addr, Coin}; use serde::Serialize; pub type SignableMixNodeBondingMsg = SignableMessage>; diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/types.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/types.rs index ebe198186d..1b0590df1f 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/types.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/types.rs @@ -5,10 +5,10 @@ use crate::EpochId; use crate::config_score::{ConfigScoreParams, OutdatedVersionWeights, VersionScoreFormulaParams}; use crate::nym_node::Role; use crate::reward_params::RewardedSetParams; -use contracts_common::Percent; use cosmwasm_schema::cw_serde; use cosmwasm_std::Coin; use cosmwasm_std::{Addr, Uint128}; +use nym_contracts_common::Percent; use std::fmt::{Display, Formatter}; // type aliases for better reasoning about available data diff --git a/common/cosmwasm-smart-contracts/multisig-contract/Cargo.toml b/common/cosmwasm-smart-contracts/multisig-contract/Cargo.toml index 89e039cd1f..31aef542af 100644 --- a/common/cosmwasm-smart-contracts/multisig-contract/Cargo.toml +++ b/common/cosmwasm-smart-contracts/multisig-contract/Cargo.toml @@ -1,8 +1,10 @@ [package] name = "nym-multisig-contract-common" -version = "0.1.0" +version.workspace = true edition = "2021" license.workspace = true +description = "Common code for the Nym multisig CosmWasm smart contract" +homepage.workspace = true [dependencies] cosmwasm-schema = { workspace = true } diff --git a/common/cosmwasm-smart-contracts/nym-performance-contract/Cargo.toml b/common/cosmwasm-smart-contracts/nym-performance-contract/Cargo.toml index 078351a348..8fd19ff838 100644 --- a/common/cosmwasm-smart-contracts/nym-performance-contract/Cargo.toml +++ b/common/cosmwasm-smart-contracts/nym-performance-contract/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-performance-contract-common" -version = "0.1.0" +version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true @@ -9,6 +9,7 @@ edition.workspace = true license.workspace = true rust-version.workspace = true readme.workspace = true +description = "Common crate for Nym's group performance contract" [dependencies] thiserror = { workspace = true } @@ -19,7 +20,7 @@ cosmwasm-std = { workspace = true } cosmwasm-schema = { workspace = true } cw-controllers = { workspace = true } -nym-contracts-common = { path = "../contracts-common" } +nym-contracts-common = { workspace = true } [features] diff --git a/common/cosmwasm-smart-contracts/nym-pool-contract/Cargo.toml b/common/cosmwasm-smart-contracts/nym-pool-contract/Cargo.toml index 38d7659ece..a4567fb0c2 100644 --- a/common/cosmwasm-smart-contracts/nym-pool-contract/Cargo.toml +++ b/common/cosmwasm-smart-contracts/nym-pool-contract/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-pool-contract-common" -version = "0.1.0" +version.workspace = true description = "Common library for the Nym Pool contract" authors.workspace = true repository.workspace = true diff --git a/common/cosmwasm-smart-contracts/vesting-contract/Cargo.toml b/common/cosmwasm-smart-contracts/vesting-contract/Cargo.toml index 9edf0ce49c..cc29aebe12 100644 --- a/common/cosmwasm-smart-contracts/vesting-contract/Cargo.toml +++ b/common/cosmwasm-smart-contracts/vesting-contract/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-vesting-contract-common" -version = "0.7.0" +version.workspace = true description = "Common library for the Nym vesting contract" edition = { workspace = true } authors = { workspace = true } @@ -11,8 +11,8 @@ repository = { workspace = true } cosmwasm-std = { workspace = true } cosmwasm-schema = { workspace = true } cw2 = { workspace = true, optional = true } -mixnet-contract-common = { path = "../mixnet-contract", package = "nym-mixnet-contract-common", version = "0.6.0" } -contracts-common = { path = "../contracts-common", package = "nym-contracts-common", version = "0.5.0" } +nym-mixnet-contract-common = { workspace = true } +nym-contracts-common = { workspace = true } serde = { workspace = true, features = ["derive"] } thiserror = { workspace = true } # without this feature, cargo clippy emits a ton of incompatibility warnings diff --git a/common/cosmwasm-smart-contracts/vesting-contract/src/error.rs b/common/cosmwasm-smart-contracts/vesting-contract/src/error.rs index c8b1c03594..996a666f37 100644 --- a/common/cosmwasm-smart-contracts/vesting-contract/src/error.rs +++ b/common/cosmwasm-smart-contracts/vesting-contract/src/error.rs @@ -3,7 +3,7 @@ use crate::account::VestingAccountStorageKey; use cosmwasm_std::{Addr, Coin, OverflowError, StdError, Uint128}; -use mixnet_contract_common::NodeId; +use nym_mixnet_contract_common::NodeId; use thiserror::Error; #[derive(Error, Debug, PartialEq)] diff --git a/common/cosmwasm-smart-contracts/vesting-contract/src/lib.rs b/common/cosmwasm-smart-contracts/vesting-contract/src/lib.rs index 741fde4f7b..8e462fb4bc 100644 --- a/common/cosmwasm-smart-contracts/vesting-contract/src/lib.rs +++ b/common/cosmwasm-smart-contracts/vesting-contract/src/lib.rs @@ -6,7 +6,7 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::{Addr, Coin}; -use mixnet_contract_common::NodeId; +use nym_mixnet_contract_common::NodeId; pub mod account; pub mod error; @@ -126,8 +126,8 @@ pub struct AccountsResponse { #[cfg(test)] mod test { - use contracts_common::Percent; use cosmwasm_std::Uint128; + use nym_contracts_common::Percent; use std::str::FromStr; use crate::PledgeCap; diff --git a/common/cosmwasm-smart-contracts/vesting-contract/src/messages.rs b/common/cosmwasm-smart-contracts/vesting-contract/src/messages.rs index 9b2d4aa6e9..cade5dec81 100644 --- a/common/cosmwasm-smart-contracts/vesting-contract/src/messages.rs +++ b/common/cosmwasm-smart-contracts/vesting-contract/src/messages.rs @@ -2,19 +2,19 @@ // SPDX-License-Identifier: Apache-2.0 use crate::{PledgeCap, VestingSpecification}; -use contracts_common::signing::MessageSignature; use cosmwasm_schema::cw_serde; use cosmwasm_std::{Coin, Timestamp}; -use mixnet_contract_common::{ +use nym_contracts_common::signing::MessageSignature; +use nym_mixnet_contract_common::{ Gateway, MixNode, NodeId, gateway::GatewayConfigUpdate, mixnode::{MixNodeConfigUpdate, NodeCostParams}, }; -#[cfg(feature = "schema")] -use contracts_common::ContractBuildInformation; #[cfg(feature = "schema")] use cosmwasm_schema::QueryResponses; +#[cfg(feature = "schema")] +use nym_contracts_common::ContractBuildInformation; #[cfg(feature = "schema")] use crate::{ diff --git a/common/cosmwasm-smart-contracts/vesting-contract/src/types.rs b/common/cosmwasm-smart-contracts/vesting-contract/src/types.rs index 0b15346804..35ba50a97f 100644 --- a/common/cosmwasm-smart-contracts/vesting-contract/src/types.rs +++ b/common/cosmwasm-smart-contracts/vesting-contract/src/types.rs @@ -1,10 +1,10 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use contracts_common::Percent; use cosmwasm_schema::cw_serde; use cosmwasm_std::{Coin, Timestamp, Uint128}; -use mixnet_contract_common::NodeId; +use nym_contracts_common::Percent; +use nym_mixnet_contract_common::NodeId; use std::str::FromStr; #[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))] diff --git a/common/credential-proxy/Cargo.toml b/common/credential-proxy/Cargo.toml index e06fd612e3..2141e37c42 100644 --- a/common/credential-proxy/Cargo.toml +++ b/common/credential-proxy/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-credential-proxy-lib" -version = "0.1.0" +version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true @@ -9,6 +9,7 @@ edition.workspace = true license.workspace = true rust-version.workspace = true readme.workspace = true +description = "Build script and core functionality of the Nym Credential Proxy" [dependencies] anyhow = { workspace = true } @@ -33,16 +34,16 @@ uuid = { workspace = true, features = ["serde"] } url = { workspace = true } zeroize = { workspace = true } -nym-credentials = { path = "../credentials" } -nym-crypto = { path = "../crypto", features = ["asymmetric", "rand", "serde"] } -nym-credentials-interface = { path = "../credentials-interface" } -nym-credential-proxy-requests = { path = "../../nym-credential-proxy/nym-credential-proxy-requests" } -nym-ecash-signer-check = { path = "../ecash-signer-check" } -nym-ecash-contract-common = { path = "../cosmwasm-smart-contracts/ecash-contract" } -nym-compact-ecash = { path = "../nym_offline_compact_ecash" } -nym-validator-client = { path = "../client-libs/validator-client" } -nym-network-defaults = { path = "../network-defaults" } -nym-cache = { path = "../nym-cache" } +nym-credentials = { workspace = true } +nym-crypto = { workspace = true, features = ["asymmetric", "rand", "serde"] } +nym-credentials-interface = { workspace = true } +nym-credential-proxy-requests = { workspace = true, features = ["query-types"] } +nym-ecash-signer-check = { workspace = true } +nym-ecash-contract-common = { workspace = true } +nym-compact-ecash = { workspace = true } +nym-validator-client = { workspace = true } +nym-network-defaults = { workspace = true } +nym-cache = { workspace = true } [dev-dependencies] tempfile = { workspace = true } diff --git a/common/credential-storage/Cargo.toml b/common/credential-storage/Cargo.toml index 9ca0eab6f3..42da92cedc 100644 --- a/common/credential-storage/Cargo.toml +++ b/common/credential-storage/Cargo.toml @@ -1,9 +1,13 @@ [package] name = "nym-credential-storage" -version = "0.1.0" +version.workspace = true edition = "2021" license.workspace = true rust-version.workspace = true +description = "Crate for handling and storing spent and unspent zknym ticketbooks" +repository.workspace = true +homepage.workspace = true +documentation.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -18,12 +22,12 @@ time = { workspace = true } tokio = { workspace = true, features = ["sync"] } zeroize = { workspace = true, features = ["zeroize_derive"] } -nym-credentials = { path = "../credentials" } -nym-compact-ecash = { path = "../nym_offline_compact_ecash" } -nym-ecash-time = { path = "../ecash-time" } +nym-credentials = { workspace = true } +nym-compact-ecash = { workspace = true } +nym-ecash-time = { workspace = true } -[target."cfg(not(target_arch = \"wasm32\"))".dependencies.sqlx-pool-guard] -path = "../../sqlx-pool-guard" +[target."cfg(not(target_arch = \"wasm32\"))".dependencies.nym-sqlx-pool-guard] +workspace = true [target."cfg(not(target_arch = \"wasm32\"))".dependencies.sqlx] workspace = true @@ -36,10 +40,10 @@ features = ["rt-multi-thread", "net", "signal", "fs"] [dev-dependencies] anyhow = { workspace = true } -nym-crypto = { path = "../crypto", features = ["asymmetric", "rand"] } -nym-test-utils = { path = "../test-utils" } -nym-credentials-interface = { path = "../credentials-interface" } -nym-compact-ecash = { path = "../nym_offline_compact_ecash" } +nym-crypto = { workspace = true, features = ["asymmetric", "rand"] } +nym-test-utils = { workspace = true } +nym-credentials-interface = { workspace = true } +nym-compact-ecash = { workspace = true } [build-dependencies] diff --git a/common/credential-storage/src/backends/sqlite.rs b/common/credential-storage/src/backends/sqlite.rs index 960e555e1b..25c7512a9b 100644 --- a/common/credential-storage/src/backends/sqlite.rs +++ b/common/credential-storage/src/backends/sqlite.rs @@ -7,8 +7,8 @@ use crate::models::{ StoredIssuedTicketbook, StoredPendingTicketbook, }; use nym_ecash_time::Date; +use nym_sqlx_pool_guard::SqlitePoolGuard; use sqlx::{Executor, Sqlite, Transaction}; -use sqlx_pool_guard::SqlitePoolGuard; #[derive(Clone)] pub struct SqliteEcashTicketbookManager { diff --git a/common/credential-storage/src/persistent_storage/mod.rs b/common/credential-storage/src/persistent_storage/mod.rs index 4596bea255..6f6665dedc 100644 --- a/common/credential-storage/src/persistent_storage/mod.rs +++ b/common/credential-storage/src/persistent_storage/mod.rs @@ -34,11 +34,11 @@ use nym_credentials::{ IssuanceTicketBook, IssuedTicketBook, }; use nym_ecash_time::{ecash_today, Date, EcashTime}; +use nym_sqlx_pool_guard::SqlitePoolGuard; use sqlx::{ sqlite::{SqliteAutoVacuum, SqliteSynchronous}, ConnectOptions, }; -use sqlx_pool_guard::SqlitePoolGuard; use std::path::Path; use zeroize::Zeroizing; diff --git a/common/credential-utils/Cargo.toml b/common/credential-utils/Cargo.toml index 81ba19a85b..f3d6986d1d 100644 --- a/common/credential-utils/Cargo.toml +++ b/common/credential-utils/Cargo.toml @@ -1,9 +1,12 @@ [package] name = "nym-credential-utils" -version = "0.1.0" +version.workspace = true edition = "2021" license.workspace = true - +description = "Utils crate for dealing with zknym credentials" +repository.workspace = true +homepage.workspace = true +documentation.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] @@ -12,11 +15,11 @@ thiserror = { workspace = true } tokio = { workspace = true } time.workspace = true -nym-bandwidth-controller = { path = "../../common/bandwidth-controller" } -nym-credentials = { path = "../../common/credentials" } -nym-credentials-interface = { path = "../../common/credentials-interface" } -nym-credential-storage = { path = "../../common/credential-storage", features = ["persistent-storage"] } -nym-validator-client = { path = "../../common/client-libs/validator-client" } -nym-config = { path = "../../common/config" } -nym-client-core = { path = "../../common/client-core" } -nym-ecash-time = { path = "../../common/ecash-time" } \ No newline at end of file +nym-bandwidth-controller = { workspace = true } +nym-credentials = { workspace = true } +nym-credentials-interface = { workspace = true } +nym-credential-storage = { workspace = true, features = ["persistent-storage"] } +nym-validator-client = { workspace = true } +nym-config = { workspace = true } +nym-client-core = { workspace = true } +nym-ecash-time = { workspace = true } diff --git a/common/credential-verification/Cargo.toml b/common/credential-verification/Cargo.toml index 07c41a49bf..d5af1fff19 100644 --- a/common/credential-verification/Cargo.toml +++ b/common/credential-verification/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-credential-verification" -version = "0.1.0" +version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true @@ -9,6 +9,7 @@ edition.workspace = true license.workspace = true rust-version.workspace = true readme.workspace = true +description = "Store and verify zknym credentials" [dependencies] async-trait = { workspace = true } @@ -23,14 +24,14 @@ tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } time = { workspace = true } tracing = { workspace = true } -nym-api-requests = { path = "../../nym-api/nym-api-requests" } -nym-credentials = { path = "../credentials" } -nym-credentials-interface = { path = "../credentials-interface" } -nym-crypto = { path = "../crypto", features = ["asymmetric"] } -nym-ecash-contract-common = { path = "../cosmwasm-smart-contracts/ecash-contract" } -nym-gateway-requests = { path = "../gateway-requests" } -nym-gateway-storage = { path = "../gateway-storage" } -nym-metrics = { path = "../nym-metrics" } -nym-task = { path = "../task" } -nym-validator-client = { path = "../client-libs/validator-client" } -nym-upgrade-mode-check = { path = "../upgrade-mode-check" } +nym-api-requests = { workspace = true } +nym-credentials = { workspace = true } +nym-credentials-interface = { workspace = true } +nym-crypto = { workspace = true, features = ["asymmetric"] } +nym-ecash-contract-common = { workspace = true } +nym-gateway-requests = { workspace = true } +nym-gateway-storage = { workspace = true } +nym-metrics = { workspace = true } +nym-task = { workspace = true } +nym-validator-client = { workspace = true, features = ["http-client"] } +nym-upgrade-mode-check = { workspace = true } diff --git a/common/credentials-interface/Cargo.toml b/common/credentials-interface/Cargo.toml index 57a88ea603..fd2f6d895d 100644 --- a/common/credentials-interface/Cargo.toml +++ b/common/credentials-interface/Cargo.toml @@ -1,17 +1,18 @@ [package] name = "nym-credentials-interface" -version = "0.1.0" +version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true documentation.workspace = true edition.workspace = true license.workspace = true +description = "Interface for Nym's compact eacash / zknym credential scheme" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -bls12_381 = { workspace = true, default-features = false } +nym-bls12_381-fork = { workspace = true } serde = { workspace = true, features = ["derive"] } thiserror = { workspace = true } strum = { workspace = true, features = ["derive"] } @@ -20,8 +21,7 @@ time = { workspace = true, features = ["serde"] } utoipa = { workspace = true } rand = { workspace = true } -nym-compact-ecash = { path = "../nym_offline_compact_ecash" } -nym-ecash-time = { path = "../ecash-time" } -nym-network-defaults = { path = "../network-defaults" } -nym-upgrade-mode-check = { path = "../upgrade-mode-check" } - +nym-compact-ecash = { workspace = true } +nym-ecash-time = { workspace = true } +nym-network-defaults = { workspace = true } +nym-upgrade-mode-check = { workspace = true } diff --git a/common/credentials/Cargo.toml b/common/credentials/Cargo.toml index e3cb06ec40..1a429bc24c 100644 --- a/common/credentials/Cargo.toml +++ b/common/credentials/Cargo.toml @@ -1,13 +1,17 @@ [package] name = "nym-credentials" -version = "0.1.0" +version.workspace = true edition = "2021" license.workspace = true +description = "Crate for using Nym's zknym credentials" +repository.workspace = true +homepage.workspace = true +documentation.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -bls12_381 = { workspace = true, default-features = false, features = ["pairings", "alloc", "experimental"] } +nym-bls12_381-fork = { workspace = true, default-features = false, features = ["pairings", "alloc", "experimental"] } bincode = { workspace = true } cosmrs = { workspace = true } thiserror = { workspace = true } @@ -16,18 +20,17 @@ time = { workspace = true, features = ["serde"] } serde = { workspace = true, features = ["derive"] } zeroize = { workspace = true } -nym-ecash-time = { path = "../ecash-time", features = ["expiration"] } +nym-ecash-time = { workspace = true, features = ["expiration"] } # I guess temporarily until we get serde support in coconut up and running -nym-credentials-interface = { path = "../credentials-interface" } -nym-crypto = { path = "../crypto" } -nym-api-requests = { path = "../../nym-api/nym-api-requests" } -nym-http-api-client = { path = "../http-api-client" } -nym-validator-client = { path = "../client-libs/validator-client", default-features = false } -nym-ecash-contract-common = { path = "../cosmwasm-smart-contracts/ecash-contract" } -nym-network-defaults = { path = "../network-defaults" } -nym-serde-helpers = { path = "../serde-helpers", features = ["date"] } +nym-credentials-interface = { workspace = true } +nym-crypto = { workspace = true } +nym-api-requests = { workspace = true } +nym-http-api-client = { workspace = true } +nym-validator-client = { workspace = true, default-features = false } +nym-ecash-contract-common = { workspace = true } +nym-network-defaults = { workspace = true } +nym-serde-helpers = { workspace = true, features = ["date"] } [dev-dependencies] rand = { workspace = true } - diff --git a/common/crypto/Cargo.toml b/common/crypto/Cargo.toml index 03b785494c..8b08749512 100644 --- a/common/crypto/Cargo.toml +++ b/common/crypto/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-crypto" -version = "0.4.0" +version.workspace = true description = "Crypto library for the nym mixnet" edition = { workspace = true } authors = { workspace = true } @@ -33,14 +33,14 @@ thiserror = { workspace = true } zeroize = { workspace = true, optional = true, features = ["zeroize_derive"] } # internal -nym-sphinx-types = { path = "../nymsphinx/types", version = "0.2.0", default-features = false } -nym-pemstore = { path = "../../common/pemstore", version = "0.3.0" } +nym-sphinx-types = { workspace = true } +nym-pemstore = { workspace = true } [dev-dependencies] anyhow = { workspace = true } rand_chacha = { workspace = true } serde_json = { workspace = true } -nym-test-utils = { path = "../test-utils" } +nym-test-utils = { workspace = true } [features] @@ -49,9 +49,9 @@ aead = ["dep:aead", "aead/std", "aes-gcm-siv", "generic-array"] naive_jwt = ["asymmetric", "jwt-simple"] serde = ["dep:serde", "serde_bytes", "ed25519-dalek/serde", "x25519-dalek/serde"] asymmetric = ["x25519-dalek", "ed25519-dalek", "curve25519-dalek", "sha2", "zeroize"] -hashing = ["blake3", "digest", "hkdf", "hmac", "generic-array", "sha2"] +hashing = ["blake3", "digest", "hkdf", "hmac", "generic-array", "sha2", "zeroize"] stream_cipher = ["aes", "ctr", "cipher", "generic-array"] sphinx = ["nym-sphinx-types/sphinx"] [lints] -workspace = true \ No newline at end of file +workspace = true diff --git a/common/dkg/Cargo.toml b/common/dkg/Cargo.toml index b8e4f55731..e45a2a8d7f 100644 --- a/common/dkg/Cargo.toml +++ b/common/dkg/Cargo.toml @@ -1,19 +1,23 @@ [package] name = "nym-dkg" -version = "0.1.0" +version.workspace = true edition = "2021" resolver = "2" license.workspace = true +description = "Nym's Distributed Key Generation functionality" +repository.workspace = true +homepage.workspace = true +documentation.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] bitvec = { workspace = true } -# unfortunately until https://github.com/zkcrypto/bls12_381/issues/10 is resolved, we have to rely on the fork +# unfortunately until https://github.com/zkcrypto/nym-bls12_381-fork/issues/10 is resolved, we have to rely on the fork # as we need to be able to serialize Gt so that we could create the lookup table for baby-step-giant-step algorithm -bls12_381 = { workspace = true, default-features = false, features = ["alloc", "pairings", "experimental", "zeroize"] } -nym-contracts-common = { path = "../cosmwasm-smart-contracts/contracts-common", optional = true } +nym-bls12_381-fork = { workspace = true, features = ["alloc", "pairings", "experimental", "zeroize"] } +nym-contracts-common = { workspace = true, optional = true } bs58 = { workspace = true } @@ -26,7 +30,7 @@ serde_derive = { workspace = true } thiserror = { workspace = true } zeroize = { workspace = true, features = ["zeroize_derive"] } -nym-pemstore = { path = "../pemstore" } +nym-pemstore = { workspace = true } [dependencies.group] workspace = true diff --git a/common/dkg/benches/benchmarks.rs b/common/dkg/benches/benchmarks.rs index d12305bba7..a360268890 100644 --- a/common/dkg/benches/benchmarks.rs +++ b/common/dkg/benches/benchmarks.rs @@ -1,9 +1,9 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use bls12_381::{G1Projective, G2Affine, G2Prepared, Scalar}; use criterion::{black_box, criterion_group, criterion_main, Criterion}; use ff::Field; +use nym_bls12_381_fork::{G1Projective, G2Affine, G2Prepared, Scalar}; use nym_dkg::bte::encryption::BabyStepGiantStepLookup; use nym_dkg::bte::proof_chunking::ProofOfChunking; use nym_dkg::bte::proof_discrete_log::ProofOfDiscreteLog; diff --git a/common/dkg/src/bte/encryption.rs b/common/dkg/src/bte/encryption.rs index 438192757f..9cc11f989d 100644 --- a/common/dkg/src/bte/encryption.rs +++ b/common/dkg/src/bte/encryption.rs @@ -6,9 +6,9 @@ use crate::bte::{evaluate_f, Params, CHUNK_SIZE, G2_GENERATOR_PREPARED, NUM_CHUN use crate::error::DkgError; use crate::utils::{combine_g1_chunks, combine_scalar_chunks, deserialize_g1, deserialize_g2}; use crate::{Chunk, ChunkedShare, Share}; -use bls12_381::{G1Affine, G1Projective, G2Prepared, G2Projective, Gt, Scalar}; use ff::Field; use group::{Curve, Group, GroupEncoding}; +use nym_bls12_381_fork::{G1Affine, G1Projective, G2Prepared, G2Projective, Gt, Scalar}; use rand::CryptoRng; use rand_core::RngCore; use std::collections::HashMap; @@ -43,7 +43,7 @@ impl Ciphertexts { // which is equivalent to checking whether e(R_j, f) • e(S_i, h) • e(g1, Z_i)^-1 == id // and due to bilinear property whether e(R_j, f) • e(S_i, h) • e(g1^-1, Z_i) == id for i in 0..self.rr.len() { - let miller = bls12_381::multi_miller_loop(&[ + let miller = nym_bls12_381_fork::multi_miller_loop(&[ (&self.rr[i].to_affine(), &f_prepared), (&self.ss[i].to_affine(), ¶ms._h_prepared), (&g1_neg, &G2Prepared::from(self.zz[i].to_affine())), @@ -294,7 +294,7 @@ pub fn decrypt_share( let zz_j = ciphertext.zz[j].to_affine(); let cc_ij = &ciphertext.ciphertext_chunks[i][j]; - let miller = bls12_381::multi_miller_loop(&[ + let miller = nym_bls12_381_fork::multi_miller_loop(&[ (&cc_ij.to_affine(), &G2_GENERATOR_PREPARED), (&rr_j.to_affine(), &G2Prepared::from(b_neg)), (&dk.a.to_affine(), &G2Prepared::from(zz_j)), diff --git a/common/dkg/src/bte/keys.rs b/common/dkg/src/bte/keys.rs index b81d6cbf77..9bd1406115 100644 --- a/common/dkg/src/bte/keys.rs +++ b/common/dkg/src/bte/keys.rs @@ -5,9 +5,9 @@ use crate::bte::proof_discrete_log::ProofOfDiscreteLog; use crate::bte::Params; use crate::error::DkgError; use crate::utils::{deserialize_g1, deserialize_g2, deserialize_scalar}; -use bls12_381::{G1Projective, G2Projective, Scalar}; use ff::Field; use group::GroupEncoding; +use nym_bls12_381_fork::{G1Projective, G2Projective, Scalar}; use nym_pemstore::traits::{PemStorableKey, PemStorableKeyPair}; use rand::CryptoRng; use rand_core::RngCore; diff --git a/common/dkg/src/bte/mod.rs b/common/dkg/src/bte/mod.rs index ea5c9a70c6..e1d7285565 100644 --- a/common/dkg/src/bte/mod.rs +++ b/common/dkg/src/bte/mod.rs @@ -6,8 +6,8 @@ use std::sync::LazyLock; use crate::bte::encryption::BabyStepGiantStepLookup; use crate::utils::hash_g2; use crate::{Chunk, Share}; -use bls12_381::{G1Affine, G2Affine, G2Prepared, G2Projective, Gt}; use group::Curve; +use nym_bls12_381_fork::{G1Affine, G2Affine, G2Prepared, G2Projective, Gt}; pub mod encryption; pub mod keys; @@ -19,7 +19,7 @@ pub use encryption::{decrypt_share, encrypt_shares, Ciphertexts}; pub use keys::{keygen, DecryptionKey, PublicKey, PublicKeyWithProof}; pub(crate) static PAIRING_BASE: LazyLock = - LazyLock::new(|| bls12_381::pairing(&G1Affine::generator(), &G2Affine::generator())); + LazyLock::new(|| nym_bls12_381_fork::pairing(&G1Affine::generator(), &G2Affine::generator())); pub(crate) static G2_GENERATOR_PREPARED: LazyLock = LazyLock::new(|| G2Prepared::from(G2Affine::generator())); pub static BSGS_TABLE: LazyLock = diff --git a/common/dkg/src/bte/proof_chunking.rs b/common/dkg/src/bte/proof_chunking.rs index 7e5b5cec2f..cfa4e6262e 100644 --- a/common/dkg/src/bte/proof_chunking.rs +++ b/common/dkg/src/bte/proof_chunking.rs @@ -7,9 +7,9 @@ use crate::ensure_len; use crate::error::DkgError; use crate::utils::{deserialize_g1, hash_to_scalar}; use crate::utils::{deserialize_scalar, RandomOracleBuilder}; -use bls12_381::{G1Projective, Scalar}; use ff::Field; use group::{Group, GroupEncoding}; +use nym_bls12_381_fork::{G1Projective, Scalar}; use rand::{CryptoRng, Rng}; use rand_core::{RngCore, SeedableRng}; use zeroize::{Zeroize, ZeroizeOnDrop}; diff --git a/common/dkg/src/bte/proof_discrete_log.rs b/common/dkg/src/bte/proof_discrete_log.rs index de186f8fb6..59746de721 100644 --- a/common/dkg/src/bte/proof_discrete_log.rs +++ b/common/dkg/src/bte/proof_discrete_log.rs @@ -2,9 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 use crate::utils::hash_to_scalar; -use bls12_381::{G1Projective, Scalar}; use ff::Field; use group::GroupEncoding; +use nym_bls12_381_fork::{G1Projective, Scalar}; use rand::CryptoRng; use rand_core::RngCore; use zeroize::{Zeroize, ZeroizeOnDrop}; diff --git a/common/dkg/src/bte/proof_sharing.rs b/common/dkg/src/bte/proof_sharing.rs index 5659ec9235..2cf8f29352 100644 --- a/common/dkg/src/bte/proof_sharing.rs +++ b/common/dkg/src/bte/proof_sharing.rs @@ -6,9 +6,9 @@ use crate::error::DkgError; use crate::interpolation::polynomial::PublicCoefficients; use crate::utils::{deserialize_g1, deserialize_g2, deserialize_scalar, hash_to_scalar}; use crate::{NodeIndex, Share}; -use bls12_381::{G1Projective, G2Projective, Scalar}; use ff::Field; use group::GroupEncoding; +use nym_bls12_381_fork::{G1Projective, G2Projective, Scalar}; use rand::CryptoRng; use rand_core::RngCore; use std::collections::BTreeMap; diff --git a/common/dkg/src/dealing.rs b/common/dkg/src/dealing.rs index 832e1260ff..def4aa0957 100644 --- a/common/dkg/src/dealing.rs +++ b/common/dkg/src/dealing.rs @@ -11,8 +11,8 @@ use crate::interpolation::{ }; use crate::utils::deserialize_g2; use crate::{NodeIndex, Share, Threshold}; -use bls12_381::{G2Projective, Scalar}; use group::GroupEncoding; +use nym_bls12_381_fork::{G2Projective, Scalar}; use rand::CryptoRng; use rand_core::RngCore; use std::collections::BTreeMap; diff --git a/common/dkg/src/interpolation/mod.rs b/common/dkg/src/interpolation/mod.rs index caedc1114e..ade36327c0 100644 --- a/common/dkg/src/interpolation/mod.rs +++ b/common/dkg/src/interpolation/mod.rs @@ -2,9 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 use crate::error::DkgError; -use bls12_381::Scalar; use core::iter::Sum; use core::ops::Mul; +use nym_bls12_381_fork::Scalar; use std::collections::HashSet; pub mod polynomial; diff --git a/common/dkg/src/interpolation/polynomial.rs b/common/dkg/src/interpolation/polynomial.rs index b6a2331c13..0484400008 100644 --- a/common/dkg/src/interpolation/polynomial.rs +++ b/common/dkg/src/interpolation/polynomial.rs @@ -3,9 +3,9 @@ use crate::error::DkgError; use crate::utils::deserialize_g2; -use bls12_381::{G2Projective, Scalar}; use ff::Field; use group::GroupEncoding; +use nym_bls12_381_fork::{G2Projective, Scalar}; use rand::CryptoRng; use rand_core::RngCore; use std::ops::{Add, Index, IndexMut}; diff --git a/common/dkg/src/lib.rs b/common/dkg/src/lib.rs index d11fa6071b..14d929604a 100644 --- a/common/dkg/src/lib.rs +++ b/common/dkg/src/lib.rs @@ -13,8 +13,8 @@ pub mod dealing; pub(crate) mod share; pub(crate) mod utils; -pub use bls12_381::{G2Projective, Scalar}; pub use dealing::*; +pub use nym_bls12_381_fork::{G2Projective, Scalar}; pub use share::*; // TODO: presumably this should live in a some different, common, crate? @@ -25,7 +25,7 @@ pub type NodeIndex = u64; mod tests { use crate::interpolation::perform_lagrangian_interpolation_at_origin; use crate::interpolation::polynomial::Polynomial; - use bls12_381::Scalar; + use nym_bls12_381_fork::Scalar; use rand_chacha::rand_core::SeedableRng; #[test] diff --git a/common/dkg/src/share.rs b/common/dkg/src/share.rs index 96039048be..d448587b89 100644 --- a/common/dkg/src/share.rs +++ b/common/dkg/src/share.rs @@ -5,7 +5,7 @@ use crate::bte::{CHUNK_BYTES, NUM_CHUNKS, SCALAR_SIZE}; use crate::error::DkgError; use crate::interpolation::perform_lagrangian_interpolation_at_origin; use crate::NodeIndex; -use bls12_381::Scalar; +use nym_bls12_381_fork::Scalar; use zeroize::{Zeroize, ZeroizeOnDrop}; // if this type is changed, one must ensure all values can fit in it diff --git a/common/dkg/src/utils.rs b/common/dkg/src/utils.rs index e654a83f8c..c852379d49 100644 --- a/common/dkg/src/utils.rs +++ b/common/dkg/src/utils.rs @@ -2,10 +2,10 @@ // SPDX-License-Identifier: Apache-2.0 use crate::bte::CHUNK_SIZE; -use bls12_381::hash_to_curve::{ExpandMsgXmd, HashToCurve, HashToField}; -use bls12_381::G1Projective; -use bls12_381::{G2Projective, Scalar}; use group::GroupEncoding; +use nym_bls12_381_fork::hash_to_curve::{ExpandMsgXmd, HashToCurve, HashToField}; +use nym_bls12_381_fork::G1Projective; +use nym_bls12_381_fork::{G2Projective, Scalar}; use sha2::{Digest, Sha256}; #[macro_export] @@ -116,7 +116,7 @@ pub(crate) fn deserialize_g2(b: &[u8]) -> Option { #[cfg(test)] mod tests { use super::*; - use bls12_381::G2Affine; + use nym_bls12_381_fork::G2Affine; #[test] fn test_hash_to_scalar() { diff --git a/common/dkg/tests/integration.rs b/common/dkg/tests/integration.rs index e34195488e..c5560e70bf 100644 --- a/common/dkg/tests/integration.rs +++ b/common/dkg/tests/integration.rs @@ -1,7 +1,7 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use bls12_381::{G2Projective, Scalar}; +use nym_bls12_381_fork::{G2Projective, Scalar}; use nym_dkg::bte::{decrypt_share, keygen, setup}; use nym_dkg::dealing::RecoveredVerificationKeys; use nym_dkg::interpolation::perform_lagrangian_interpolation_at_origin; diff --git a/common/ecash-signer-check-types/Cargo.toml b/common/ecash-signer-check-types/Cargo.toml index 0f07437c71..ac3a6f084d 100644 --- a/common/ecash-signer-check-types/Cargo.toml +++ b/common/ecash-signer-check-types/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-ecash-signer-check-types" -version = "0.1.0" +version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true @@ -9,6 +9,7 @@ edition.workspace = true license.workspace = true rust-version.workspace = true readme.workspace = true +description = "Crate containing types for the `ecash-signer-check` crate used to check if zknym signers are up and running properly" [dependencies] semver = { workspace = true } @@ -19,8 +20,8 @@ time = { workspace = true } tracing = { workspace = true } utoipa = { workspace = true } -nym-coconut-dkg-common = { path = "../cosmwasm-smart-contracts/coconut-dkg" } -nym-crypto = { path = "../crypto", features = ["asymmetric"] } +nym-coconut-dkg-common = { workspace = true } +nym-crypto = { workspace = true, features = ["asymmetric"] } [lints] diff --git a/common/ecash-signer-check/Cargo.toml b/common/ecash-signer-check/Cargo.toml index cc8fb6ec29..8549930a03 100644 --- a/common/ecash-signer-check/Cargo.toml +++ b/common/ecash-signer-check/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-ecash-signer-check" -version = "0.1.0" +version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true @@ -9,6 +9,7 @@ edition.workspace = true license.workspace = true rust-version.workspace = true readme.workspace = true +description = "Functions to interact with zknym signers, checking their status and health" [dependencies] futures = { workspace = true } @@ -19,10 +20,10 @@ tracing = { workspace = true } url = { workspace = true } -nym-validator-client = { path = "../client-libs/validator-client" } -nym-network-defaults = { path = "../network-defaults" } -nym-ecash-signer-check-types = { path = "../ecash-signer-check-types" } -nym-http-api-client = { path = "../http-api-client" } +nym-validator-client = { workspace = true, features = ["http-client"] } +nym-network-defaults = { workspace = true } +nym-ecash-signer-check-types = { workspace = true } +nym-http-api-client = { workspace = true } [lints] workspace = true diff --git a/common/ecash-time/Cargo.toml b/common/ecash-time/Cargo.toml index 4eaee90438..1780a96afe 100644 --- a/common/ecash-time/Cargo.toml +++ b/common/ecash-time/Cargo.toml @@ -1,19 +1,20 @@ [package] name = "nym-ecash-time" -version = "0.1.0" +version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true documentation.workspace = true edition.workspace = true license.workspace = true +description = "Time-related helper functions for Nym's zknym scheme" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] time.workspace = true -nym-compact-ecash = { path = "../nym_offline_compact_ecash", optional = true } +nym-compact-ecash = { workspace = true, optional = true } [features] -expiration = ["nym-compact-ecash"] \ No newline at end of file +expiration = ["nym-compact-ecash"] diff --git a/common/exit-policy/Cargo.toml b/common/exit-policy/Cargo.toml index a5e8f26974..3db220a920 100644 --- a/common/exit-policy/Cargo.toml +++ b/common/exit-policy/Cargo.toml @@ -1,12 +1,13 @@ [package] name = "nym-exit-policy" -version = "0.1.0" +version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true documentation.workspace = true edition.workspace = true license.workspace = true +description = "Get and set the Nym Exit Policy, used by Exit Gateways" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/common/gateway-requests/Cargo.toml b/common/gateway-requests/Cargo.toml index 65a5704fdc..b9cdf43d01 100644 --- a/common/gateway-requests/Cargo.toml +++ b/common/gateway-requests/Cargo.toml @@ -3,10 +3,14 @@ [package] name = "nym-gateway-requests" -version = "0.1.0" +version.workspace = true authors = ["Jedrzej Stuczynski "] edition = "2021" license.workspace = true +description = "Request and response definitions for Nym Gateway <> client communication" +repository.workspace = true +homepage.workspace = true +documentation.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -24,15 +28,15 @@ time = { workspace = true } subtle = { workspace = true } zeroize = { workspace = true } -nym-crypto = { path = "../crypto", features = ["aead", "hashing"] } -nym-pemstore = { path = "../pemstore" } -nym-sphinx = { path = "../nymsphinx" } -nym-serde-helpers = { path = "../serde-helpers", features = ["base64"] } -nym-statistics-common = { path = "../statistics" } -nym-task = { path = "../task" } +nym-crypto = { workspace = true, features = ["aead", "hashing"] } +nym-pemstore = { workspace = true } +nym-sphinx = { workspace = true } +nym-serde-helpers = { workspace = true, features = ["base64"] } +nym-statistics-common = { workspace = true } +nym-task = { workspace = true } -nym-credentials = { path = "../credentials" } -nym-credentials-interface = { path = "../credentials-interface" } +nym-credentials = { workspace = true } +nym-credentials-interface = { workspace = true } [target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio] workspace = true @@ -48,9 +52,9 @@ default-features = false [dev-dependencies] anyhow = { workspace = true } -nym-compact-ecash = { path = "../nym_offline_compact_ecash" } # we need specific imports in tests -nym-test-utils = { path = "../test-utils" } +nym-compact-ecash = { workspace = true } # we need specific imports in tests +nym-test-utils = { workspace = true } tokio = { workspace = true, features = ["full"] } [lints] -workspace = true \ No newline at end of file +workspace = true diff --git a/common/gateway-stats-storage/Cargo.toml b/common/gateway-stats-storage/Cargo.toml index e958e25526..7c1f996059 100644 --- a/common/gateway-stats-storage/Cargo.toml +++ b/common/gateway-stats-storage/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-gateway-stats-storage" -version = "0.1.0" +version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true @@ -8,6 +8,7 @@ documentation.workspace = true edition.workspace = true license.workspace = true rust-version.workspace = true +description = "Functionality Nym Gateway statistics storage" [dependencies] sqlx = { workspace = true, features = [ @@ -22,9 +23,9 @@ time = { workspace = true } thiserror = { workspace = true } tracing = { workspace = true } -nym-sphinx = { path = "../nymsphinx" } -nym-node-metrics = { path = "../../nym-node/nym-node-metrics" } -nym-statistics-common = { path = "../statistics" } +nym-sphinx = { workspace = true } +nym-node-metrics = { workspace = true } +nym-statistics-common = { workspace = true } [build-dependencies] diff --git a/common/gateway-storage/Cargo.toml b/common/gateway-storage/Cargo.toml index c7878511be..32424e7d4a 100644 --- a/common/gateway-storage/Cargo.toml +++ b/common/gateway-storage/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-gateway-storage" -version = "0.1.0" +version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true @@ -8,6 +8,7 @@ documentation.workspace = true edition.workspace = true license.workspace = true rust-version.workspace = true +description = "Crate handling db setup and use for Nym Gateways, used for credentials, packets, connections" [dependencies] async-trait = { workspace = true } @@ -27,9 +28,9 @@ thiserror = { workspace = true } tokio = { workspace = true, features = ["sync"], optional = true } tracing = { workspace = true } -nym-credentials-interface = { path = "../credentials-interface" } -nym-gateway-requests = { path = "../gateway-requests" } -nym-sphinx = { path = "../nymsphinx" } +nym-credentials-interface = { workspace = true } +nym-gateway-requests = { workspace = true } +nym-sphinx = { workspace = true } [build-dependencies] anyhow = { workspace = true } @@ -43,4 +44,4 @@ sqlx = { workspace = true, features = [ [features] default = [] -mock = ["tokio"] \ No newline at end of file +mock = ["tokio"] diff --git a/common/http-api-client-macro/Cargo.toml b/common/http-api-client-macro/Cargo.toml index ea23bbd23a..f5e12bef4c 100644 --- a/common/http-api-client-macro/Cargo.toml +++ b/common/http-api-client-macro/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-http-api-client-macro" -version = "0.1.0" +version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true @@ -9,6 +9,7 @@ edition.workspace = true license.workspace = true rust-version.workspace = true readme.workspace = true +description = "Proc-macros for configuring HTTP clients globally via the `inventory` crate" [lib] proc-macro = true @@ -21,7 +22,7 @@ proc-macro-crate = "3" uuid = { version = "1.0", features = ["v4"] } [dev-dependencies] -nym-http-api-client = { path = "../http-api-client" } +nym-http-api-client = { workspace = true } reqwest = { workspace = true } [features] diff --git a/common/http-api-client/Cargo.toml b/common/http-api-client/Cargo.toml index e748e37888..43daed794f 100644 --- a/common/http-api-client/Cargo.toml +++ b/common/http-api-client/Cargo.toml @@ -1,12 +1,13 @@ [package] name = "nym-http-api-client" -version = "0.1.0" +version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true documentation.workspace = true edition.workspace = true license.workspace = true +description = "Nym's HTTP API client, examples, and tests" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -38,10 +39,10 @@ bytes = { workspace = true } encoding_rs = { workspace = true } mime = { workspace = true } -nym-http-api-common = { path = "../http-api-common", default-features = false } -nym-bin-common = { path = "../bin-common" } -nym-network-defaults = { path = "../network-defaults", optional = true } -nym-http-api-client-macro = { path = "../http-api-client-macro" } +nym-http-api-common = { workspace = true } +nym-bin-common = { workspace = true } +nym-network-defaults = { workspace = true, optional = true } +nym-http-api-client-macro = { workspace = true } [target."cfg(not(target_arch = \"wasm32\"))".dependencies] hickory-resolver = { workspace = true, features = ["https-ring", "tls-ring", "webpki-roots"] } diff --git a/common/http-api-common/Cargo.toml b/common/http-api-common/Cargo.toml index 4a405c155e..17d7521788 100644 --- a/common/http-api-common/Cargo.toml +++ b/common/http-api-common/Cargo.toml @@ -1,12 +1,13 @@ [package] name = "nym-http-api-common" -version = "0.1.0" +version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true documentation.workspace = true edition.workspace = true license.workspace = true +description = "Common crate for Nym-related HTTP API interaction" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -52,4 +53,4 @@ middleware = [ utoipa = ["dep:utoipa"] [lints] -workspace = true \ No newline at end of file +workspace = true diff --git a/common/inclusion-probability/Cargo.toml b/common/inclusion-probability/Cargo.toml index b98a1ec5e8..d27d0a23fa 100644 --- a/common/inclusion-probability/Cargo.toml +++ b/common/inclusion-probability/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-inclusion-probability" -version = "0.1.0" +version.workspace = true description = "Nym active set probability simulator" edition.workspace = true authors.workspace = true diff --git a/common/ip-packet-requests/Cargo.toml b/common/ip-packet-requests/Cargo.toml index cd9663dc90..487ff4d782 100644 --- a/common/ip-packet-requests/Cargo.toml +++ b/common/ip-packet-requests/Cargo.toml @@ -1,20 +1,22 @@ [package] name = "nym-ip-packet-requests" -version = "0.1.0" +version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true documentation.workspace = true edition.workspace = true license.workspace = true +description = "Codec, signing functionality, and different version definitions for IP packet request and responses" + [dependencies] bincode = { workspace = true } bytes = { workspace = true } -nym-bin-common = { path = "../bin-common" } -nym-crypto = { path = "../crypto" } -nym-service-provider-requests-common = { path = "../service-provider-requests-common" } -nym-sphinx = { path = "../nymsphinx" } +nym-bin-common = { workspace = true } +nym-crypto = { workspace = true } +nym-service-provider-requests-common = { workspace = true } +nym-sphinx = { workspace = true } rand = { workspace = true } serde = { workspace = true, features = ["derive"] } thiserror = { workspace = true } diff --git a/common/ledger/Cargo.toml b/common/ledger/Cargo.toml index 72de9bb003..ff3cf35adf 100644 --- a/common/ledger/Cargo.toml +++ b/common/ledger/Cargo.toml @@ -1,8 +1,9 @@ [package] name = "nym-ledger" -version = "0.1.0" +version.workspace = true edition = "2021" license.workspace = true +publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/common/mixnode-common/Cargo.toml b/common/mixnode-common/Cargo.toml index 8fbab39525..50d1c94cde 100644 --- a/common/mixnode-common/Cargo.toml +++ b/common/mixnode-common/Cargo.toml @@ -1,9 +1,13 @@ [package] name = "nym-mixnode-common" -version = "0.1.0" +version.workspace = true authors = ["Jędrzej Stuczyński "] edition = "2021" license.workspace = true +description = "Common crate for Nym Mix Nodes" +repository.workspace = true +homepage.workspace = true +documentation.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -26,14 +30,13 @@ url = { workspace = true } time.workspace = true thiserror = { workspace = true } -nym-crypto = { path = "../crypto", features = ["asymmetric"] } -nym-network-defaults = { path = "../network-defaults" } -nym-sphinx-acknowledgements = { path = "../nymsphinx/acknowledgements" } -nym-sphinx-addressing = { path = "../nymsphinx/addressing" } -nym-sphinx-forwarding = { path = "../nymsphinx/forwarding" } -nym-sphinx-framing = { path = "../nymsphinx/framing" } -nym-sphinx-params = { path = "../nymsphinx/params" } -nym-sphinx-types = { path = "../nymsphinx/types" } -nym-task = { path = "../task" } -nym-metrics = { path = "../nym-metrics" } - +nym-crypto = { workspace = true, features = ["asymmetric"] } +nym-network-defaults = { workspace = true } +nym-sphinx-acknowledgements = { workspace = true } +nym-sphinx-addressing = { workspace = true } +nym-sphinx-forwarding = { workspace = true } +nym-sphinx-framing = { workspace = true } +nym-sphinx-params = { workspace = true } +nym-sphinx-types = { workspace = true } +nym-task = { workspace = true } +nym-metrics = { workspace = true } diff --git a/common/network-defaults/Cargo.toml b/common/network-defaults/Cargo.toml index d8cbc36711..5c3a73e279 100644 --- a/common/network-defaults/Cargo.toml +++ b/common/network-defaults/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-network-defaults" -version = "0.1.0" +version.workspace = true description = "Nym network defaults" edition.workspace = true authors.workspace = true diff --git a/common/node-tester-utils/Cargo.toml b/common/node-tester-utils/Cargo.toml index 89e4043ad3..d3845fb094 100644 --- a/common/node-tester-utils/Cargo.toml +++ b/common/node-tester-utils/Cargo.toml @@ -1,8 +1,12 @@ [package] name = "nym-node-tester-utils" -version = "0.1.0" +version.workspace = true edition = "2021" license.workspace = true +description = "Utils for the Nym Node Tester" +repository.workspace = true +homepage.workspace = true +documentation.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -16,17 +20,17 @@ serde_json = { workspace = true } thiserror = { workspace = true } tokio = { workspace = true, features = ["macros"] } -nym-crypto = { path = "../crypto", features = ["asymmetric"] } -nym-task = { path = "../task" } -nym-topology = { path = "../topology" } -nym-sphinx-params = { path = "../nymsphinx/params" } +nym-crypto = { workspace = true, features = ["asymmetric"] } +nym-task = { workspace = true } +nym-topology = { workspace = true } +nym-sphinx-params = { workspace = true } # TODO: do we need the whole nymsphinx? -nym-sphinx = { path = "../nymsphinx" } +nym-sphinx = { workspace = true } ## non-wasm-only dependencies [target."cfg(not(target_arch = \"wasm32\"))".dependencies.log] workspace = true ## wasm-only dependencies -[target."cfg(target_arch = \"wasm32\")".dependencies.wasm-utils] -path = "../wasm/utils" +[target."cfg(target_arch = \"wasm32\")".dependencies.nym-wasm-utils] +workspace = true diff --git a/common/node-tester-utils/src/lib.rs b/common/node-tester-utils/src/lib.rs index 444d1780f8..6a164c460b 100644 --- a/common/node-tester-utils/src/lib.rs +++ b/common/node-tester-utils/src/lib.rs @@ -21,7 +21,7 @@ pub(crate) type NodeId = u32; macro_rules! log_err { ($($t:tt)*) => {{ #[cfg(target_arch = "wasm32")] - {::wasm_utils::console_error!($($t)*)} + {::nym_wasm_utils::console_error!($($t)*)} #[cfg(not(target_arch = "wasm32"))] {::log::error!($($t)*)} @@ -32,7 +32,7 @@ macro_rules! log_err { macro_rules! log_warn { ($($t:tt)*) => {{ #[cfg(target_arch = "wasm32")] - {::wasm_utils::console_warn!($($t)*)} + {::nym_wasm_utils::console_warn!($($t)*)} #[cfg(not(target_arch = "wasm32"))] {::log::warn!($($t)*)} @@ -43,7 +43,7 @@ macro_rules! log_warn { macro_rules! log_info { ($($t:tt)*) => {{ #[cfg(target_arch = "wasm32")] - {::wasm_utils::console_log!($($t)*)} + {::nym_wasm_utils::console_log!($($t)*)} #[cfg(not(target_arch = "wasm32"))] {::log::info!($($t)*)} diff --git a/common/nonexhaustive-delayqueue/Cargo.toml b/common/nonexhaustive-delayqueue/Cargo.toml index 76903a4b5f..f8d4671688 100644 --- a/common/nonexhaustive-delayqueue/Cargo.toml +++ b/common/nonexhaustive-delayqueue/Cargo.toml @@ -1,9 +1,13 @@ [package] name = "nym-nonexhaustive-delayqueue" -version = "0.1.0" +version.workspace = true authors = ["Jędrzej Stuczyński "] edition = "2021" license.workspace = true +description = "A copy of tokio-util delay_queue with `Sleep` and `Instant` being replaced with`wasm_timer` equivalents" +repository.workspace = true +homepage.workspace = true +documentation.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -20,4 +24,3 @@ features = ["time"] [target."cfg(target_arch = \"wasm32\")".dependencies.wasmtimer] workspace = true features = ["tokio", "tokio-util"] - diff --git a/common/nym-cache/Cargo.toml b/common/nym-cache/Cargo.toml index 8bcfc2e997..3d935c3e89 100644 --- a/common/nym-cache/Cargo.toml +++ b/common/nym-cache/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-cache" -version = "0.1.0" +version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true @@ -9,6 +9,7 @@ edition.workspace = true license.workspace = true rust-version.workspace = true readme.workspace = true +description = "Helper functions around a RwLock for writing to local cache of items" [dependencies] tokio = { workspace = true, features = ["sync"] } diff --git a/common/nym-common/Cargo.toml b/common/nym-common/Cargo.toml index 5bf119c8ee..6bdbef1bce 100644 --- a/common/nym-common/Cargo.toml +++ b/common/nym-common/Cargo.toml @@ -1,10 +1,11 @@ [package] name = "nym-common" -version = "1.18.0" +version.workspace = true authors.workspace = true repository.workspace = true license.workspace = true edition.workspace = true +description = "Runtime diagnostics for high frequency logging, debugging and error handling utilities" [lints] workspace = true diff --git a/common/nym-connection-monitor/Cargo.toml b/common/nym-connection-monitor/Cargo.toml index e19991d3e4..03de5da740 100644 --- a/common/nym-connection-monitor/Cargo.toml +++ b/common/nym-connection-monitor/Cargo.toml @@ -7,6 +7,7 @@ homepage.workspace = true documentation.workspace = true edition.workspace = true license.workspace = true +publish = false [lints] workspace = true @@ -15,10 +16,10 @@ workspace = true bincode.workspace = true bytes.workspace = true futures.workspace = true -nym-config = { path = "../config" } -nym-common = { path = "../nym-common" } -nym-ip-packet-requests = { path = "../ip-packet-requests" } -nym-sdk = { path = "../../sdk/rust/nym-sdk" } +nym-config = { workspace = true } +nym-common = { workspace = true } +nym-ip-packet-requests = { workspace = true } +nym-sdk = { workspace = true } pnet_packet.workspace = true thiserror.workspace = true tokio.workspace = true diff --git a/common/nym-id/Cargo.toml b/common/nym-id/Cargo.toml index eb80073fb7..17b4ac31ef 100644 --- a/common/nym-id/Cargo.toml +++ b/common/nym-id/Cargo.toml @@ -1,12 +1,13 @@ [package] name = "nym-id" -version = "0.1.0" +version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true documentation.workspace = true edition.workspace = true license.workspace = true +description = "Functionality for importing and storing credentials and cryptographic keys" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -16,5 +17,5 @@ time.workspace = true tracing.workspace = true zeroize.workspace = true -nym-credential-storage = { path = "../credential-storage" } -nym-credentials = { path = "../credentials" } +nym-credential-storage = { workspace = true } +nym-credentials = { workspace = true } diff --git a/common/nym-kcp/Cargo.toml b/common/nym-kcp/Cargo.toml index 73cf7edfcb..488c9fff8a 100644 --- a/common/nym-kcp/Cargo.toml +++ b/common/nym-kcp/Cargo.toml @@ -1,8 +1,11 @@ [package] name = "nym-kcp" version = "0.1.0" +description = "KCP protocol implementation for Nym" edition = { workspace = true } +authors = { workspace = true } license = { workspace = true } +repository = { workspace = true } [lib] name = "nym_kcp" diff --git a/common/nym-kkt/Cargo.toml b/common/nym-kkt/Cargo.toml index 1cabf14fb2..64af80cd91 100644 --- a/common/nym-kkt/Cargo.toml +++ b/common/nym-kkt/Cargo.toml @@ -4,6 +4,7 @@ version = "0.1.0" authors = ["Georgio Nicolas "] edition = { workspace = true } license.workspace = true +publish = false [dependencies] blake3 = { workspace = true } diff --git a/common/nym-lp-common/Cargo.toml b/common/nym-lp-common/Cargo.toml index b70550c82b..b84b617154 100644 --- a/common/nym-lp-common/Cargo.toml +++ b/common/nym-lp-common/Cargo.toml @@ -3,5 +3,6 @@ name = "nym-lp-common" version = "0.1.0" edition = { workspace = true } license = { workspace = true } +publish = false [dependencies] diff --git a/common/nym-lp-transport/Cargo.toml b/common/nym-lp-transport/Cargo.toml index c2aba1690d..929d68da37 100644 --- a/common/nym-lp-transport/Cargo.toml +++ b/common/nym-lp-transport/Cargo.toml @@ -9,6 +9,7 @@ edition.workspace = true license.workspace = true rust-version.workspace = true readme.workspace = true +publish = false [dependencies] tokio = { workspace = true, features = ["net"] } diff --git a/common/nym-lp/Cargo.toml b/common/nym-lp/Cargo.toml index 08bdef92f2..58c19b38f8 100644 --- a/common/nym-lp/Cargo.toml +++ b/common/nym-lp/Cargo.toml @@ -3,6 +3,7 @@ name = "nym-lp" version = "0.1.0" edition = { workspace = true } license = { workspace = true } +publish = false [dependencies] thiserror = { workspace = true } diff --git a/common/nym-metrics/Cargo.toml b/common/nym-metrics/Cargo.toml index 1ac3bce16a..491b72f07d 100644 --- a/common/nym-metrics/Cargo.toml +++ b/common/nym-metrics/Cargo.toml @@ -1,12 +1,13 @@ [package] name = "nym-metrics" -version = "0.1.0" +version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true documentation.workspace = true edition.workspace = true license.workspace = true +description = "Metrics macros" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/common/nym_offline_compact_ecash/Cargo.toml b/common/nym_offline_compact_ecash/Cargo.toml index 718787b457..e70063bd17 100644 --- a/common/nym_offline_compact_ecash/Cargo.toml +++ b/common/nym_offline_compact_ecash/Cargo.toml @@ -3,15 +3,19 @@ [package] name = "nym-compact-ecash" -version = "0.1.0" +version.workspace = true authors = ["Ania Piotrowska "] edition = "2021" license = { workspace = true } +description = "Nym's ecash implementation" +repository.workspace = true +homepage.workspace = true +documentation.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -bls12_381 = { workspace = true, features = ["alloc", "pairings", "experimental", "zeroize", "experimental_serde"] } +nym-bls12_381-fork = { workspace = true, features = ["alloc", "pairings", "experimental", "zeroize", "experimental_serde"] } bincode.workspace = true cfg-if.workspace = true itertools = { workspace = true } @@ -27,8 +31,8 @@ ff = { workspace = true } group = { workspace = true } subtle = { workspace = true } -nym-pemstore = { path = "../pemstore" } -nym-network-defaults = { path = "../network-defaults", default-features = false } +nym-pemstore = { workspace = true } +nym-network-defaults = { workspace = true } [dev-dependencies] criterion = { workspace = true, features = ["html_reports"] } diff --git a/common/nym_offline_compact_ecash/benches/benchmarks_group_operations.rs b/common/nym_offline_compact_ecash/benches/benchmarks_group_operations.rs index 73c74472e7..a6769200b0 100644 --- a/common/nym_offline_compact_ecash/benches/benchmarks_group_operations.rs +++ b/common/nym_offline_compact_ecash/benches/benchmarks_group_operations.rs @@ -4,24 +4,24 @@ use std::ops::Neg; use std::time::Duration; -use bls12_381::{ - multi_miller_loop, G1Affine, G1Projective, G2Affine, G2Prepared, G2Projective, Gt, Scalar, -}; use criterion::{criterion_group, criterion_main, Criterion}; use ff::Field; use group::{Curve, Group}; +use nym_bls12_381_fork::{ + multi_miller_loop, G1Affine, G1Projective, G2Affine, G2Prepared, G2Projective, Gt, Scalar, +}; use nym_compact_ecash::utils::check_bilinear_pairing; #[allow(unused)] fn double_pairing(g11: &G1Affine, g21: &G2Affine, g12: &G1Affine, g22: &G2Affine) { - let gt1 = bls12_381::pairing(g11, g21); - let gt2 = bls12_381::pairing(g12, g22); + let gt1 = nym_bls12_381_fork::pairing(g11, g21); + let gt2 = nym_bls12_381_fork::pairing(g12, g22); assert_eq!(gt1, gt2) } #[allow(unused)] fn single_pairing(g11: &G1Affine, g21: &G2Affine) { - let gt1 = bls12_381::pairing(g11, g21); + let gt1 = nym_bls12_381_fork::pairing(g11, g21); } #[allow(unused)] @@ -98,7 +98,7 @@ fn bench_group_operations(c: &mut Criterion) { let g22 = (g2 * r).to_affine(); let g22_prep = G2Prepared::from(g22); - let gt = bls12_381::pairing(&g11, &g21); + let gt = nym_bls12_381_fork::pairing(&g11, &g21); let gen1 = G1Projective::generator(); let gen2 = G2Projective::generator(); diff --git a/common/nym_offline_compact_ecash/src/common_types.rs b/common/nym_offline_compact_ecash/src/common_types.rs index bc450f97a4..90ce4ac0f7 100644 --- a/common/nym_offline_compact_ecash/src/common_types.rs +++ b/common/nym_offline_compact_ecash/src/common_types.rs @@ -7,7 +7,7 @@ use crate::helpers::{g1_tuple_to_bytes, recover_g1_tuple}; use serde::{Deserialize, Serialize}; use subtle::Choice; -pub use bls12_381::{G1Projective, G2Projective, Scalar}; +pub use nym_bls12_381_fork::{G1Projective, G2Projective, Scalar}; pub type SignerIndex = u64; #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] diff --git a/common/nym_offline_compact_ecash/src/constants.rs b/common/nym_offline_compact_ecash/src/constants.rs index 0902c2fa0f..b58762d9a8 100644 --- a/common/nym_offline_compact_ecash/src/constants.rs +++ b/common/nym_offline_compact_ecash/src/constants.rs @@ -1,7 +1,7 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use bls12_381::Scalar; +use nym_bls12_381_fork::Scalar; use nym_network_defaults::ecash::TICKETBOOK_VALIDITY_DAYS; use nym_network_defaults::TICKETBOOK_SIZE; diff --git a/common/nym_offline_compact_ecash/src/helpers.rs b/common/nym_offline_compact_ecash/src/helpers.rs index af741d5f34..856bb397e4 100644 --- a/common/nym_offline_compact_ecash/src/helpers.rs +++ b/common/nym_offline_compact_ecash/src/helpers.rs @@ -3,8 +3,8 @@ use crate::utils::try_deserialize_g1_projective; use crate::{CompactEcashError, EncodedDate, EncodedTicketType}; -use bls12_381::{G1Projective, Scalar}; use group::Curve; +use nym_bls12_381_fork::{G1Projective, Scalar}; use std::any::{type_name, Any}; pub(crate) fn g1_tuple_to_bytes(el: (G1Projective, G1Projective)) -> [u8; 96] { diff --git a/common/nym_offline_compact_ecash/src/lib.rs b/common/nym_offline_compact_ecash/src/lib.rs index 77e1ce5199..46bb93ec9b 100644 --- a/common/nym_offline_compact_ecash/src/lib.rs +++ b/common/nym_offline_compact_ecash/src/lib.rs @@ -6,14 +6,14 @@ #![warn(clippy::todo)] #![warn(clippy::dbg_macro)] -use bls12_381::Scalar; +use nym_bls12_381_fork::Scalar; use std::sync::OnceLock; pub use crate::error::CompactEcashError; pub use crate::traits::Bytable; -pub use bls12_381::G1Projective; pub use common_types::{BlindedSignature, Signature}; pub use helpers::{date_scalar, type_scalar}; +pub use nym_bls12_381_fork::G1Projective; pub use scheme::aggregation::aggregate_verification_keys; pub use scheme::aggregation::aggregate_wallets; pub use scheme::identify; diff --git a/common/nym_offline_compact_ecash/src/proofs/mod.rs b/common/nym_offline_compact_ecash/src/proofs/mod.rs index 5133c2bedd..c1a19acc8c 100644 --- a/common/nym_offline_compact_ecash/src/proofs/mod.rs +++ b/common/nym_offline_compact_ecash/src/proofs/mod.rs @@ -3,9 +3,9 @@ use std::borrow::Borrow; -use bls12_381::Scalar; use digest::generic_array::typenum::Unsigned; use digest::Digest; +use nym_bls12_381_fork::Scalar; use sha2::Sha256; pub mod proof_spend; diff --git a/common/nym_offline_compact_ecash/src/proofs/proof_spend.rs b/common/nym_offline_compact_ecash/src/proofs/proof_spend.rs index 966dd2211c..6b81e50cb1 100644 --- a/common/nym_offline_compact_ecash/src/proofs/proof_spend.rs +++ b/common/nym_offline_compact_ecash/src/proofs/proof_spend.rs @@ -5,8 +5,8 @@ use crate::ecash_group_parameters; use crate::proofs::{compute_challenge, produce_response, produce_responses, ChallengeDigest}; use crate::scheme::keygen::VerificationKeyAuth; use crate::scheme::PayInfo; -use bls12_381::{G1Projective, G2Projective, Scalar}; use group::GroupEncoding; +use nym_bls12_381_fork::{G1Projective, G2Projective, Scalar}; use serde::{Deserialize, Serialize}; use zeroize::{Zeroize, ZeroizeOnDrop}; diff --git a/common/nym_offline_compact_ecash/src/proofs/proof_withdrawal.rs b/common/nym_offline_compact_ecash/src/proofs/proof_withdrawal.rs index b16b66d7ff..0d8576e5d1 100644 --- a/common/nym_offline_compact_ecash/src/proofs/proof_withdrawal.rs +++ b/common/nym_offline_compact_ecash/src/proofs/proof_withdrawal.rs @@ -4,9 +4,9 @@ use crate::ecash_group_parameters; use crate::proofs::{compute_challenge, produce_response, produce_responses, ChallengeDigest}; use crate::scheme::keygen::PublicKeyUser; -use bls12_381::{G1Projective, Scalar}; use group::GroupEncoding; use itertools::izip; +use nym_bls12_381_fork::{G1Projective, Scalar}; use serde::{Deserialize, Serialize}; #[derive(Debug, Serialize, Deserialize)] diff --git a/common/nym_offline_compact_ecash/src/scheme/aggregation.rs b/common/nym_offline_compact_ecash/src/scheme/aggregation.rs index 148a619a41..87131dcd33 100644 --- a/common/nym_offline_compact_ecash/src/scheme/aggregation.rs +++ b/common/nym_offline_compact_ecash/src/scheme/aggregation.rs @@ -9,11 +9,11 @@ use crate::scheme::withdrawal::RequestInfo; use crate::scheme::{PartialWallet, Wallet, WalletSignatures}; use crate::utils::{check_bilinear_pairing, perform_lagrangian_interpolation_at_origin}; use crate::{ecash_group_parameters, Attribute}; -use bls12_381::{G2Prepared, G2Projective, Scalar}; use core::iter::Sum; use core::ops::Mul; use group::Curve; use itertools::Itertools; +use nym_bls12_381_fork::{G2Prepared, G2Projective, Scalar}; use zeroize::Zeroizing; pub(crate) trait Aggregatable: Sized { diff --git a/common/nym_offline_compact_ecash/src/scheme/coin_indices_signatures.rs b/common/nym_offline_compact_ecash/src/scheme/coin_indices_signatures.rs index c98047570e..f140cc5f6e 100644 --- a/common/nym_offline_compact_ecash/src/scheme/coin_indices_signatures.rs +++ b/common/nym_offline_compact_ecash/src/scheme/coin_indices_signatures.rs @@ -8,8 +8,8 @@ use crate::scheme::keygen::{SecretKeyAuth, VerificationKeyAuth}; use crate::scheme::setup::Parameters; use crate::utils::generate_lagrangian_coefficients_at_origin; use crate::utils::{batch_verify_signatures, hash_g1}; -use bls12_381::{G1Projective, Scalar}; use itertools::Itertools; +use nym_bls12_381_fork::{G1Projective, Scalar}; use serde::{Deserialize, Serialize}; use std::borrow::Borrow; diff --git a/common/nym_offline_compact_ecash/src/scheme/expiration_date_signatures.rs b/common/nym_offline_compact_ecash/src/scheme/expiration_date_signatures.rs index c475943b7b..06a2395271 100644 --- a/common/nym_offline_compact_ecash/src/scheme/expiration_date_signatures.rs +++ b/common/nym_offline_compact_ecash/src/scheme/expiration_date_signatures.rs @@ -8,8 +8,8 @@ use crate::scheme::keygen::{SecretKeyAuth, VerificationKeyAuth}; use crate::utils::generate_lagrangian_coefficients_at_origin; use crate::utils::{batch_verify_signatures, hash_g1}; use crate::{constants, EncodedDate}; -use bls12_381::{G1Projective, Scalar}; use itertools::Itertools; +use nym_bls12_381_fork::{G1Projective, Scalar}; use serde::{Deserialize, Serialize}; use std::borrow::Borrow; diff --git a/common/nym_offline_compact_ecash/src/scheme/keygen.rs b/common/nym_offline_compact_ecash/src/scheme/keygen.rs index 151de7c17a..c771b6658f 100644 --- a/common/nym_offline_compact_ecash/src/scheme/keygen.rs +++ b/common/nym_offline_compact_ecash/src/scheme/keygen.rs @@ -11,11 +11,11 @@ use crate::utils::{ try_deserialize_scalar_vec, }; use crate::{ecash_group_parameters, Base58}; -use bls12_381::{G1Projective, G2Projective, Scalar}; use core::borrow::Borrow; use core::iter::Sum; use core::ops::{Add, Mul}; use group::{Curve, GroupEncoding}; +use nym_bls12_381_fork::{G1Projective, G2Projective, Scalar}; use nym_pemstore::traits::{PemStorableKey, PemStorableKeyPair}; use serde::{Deserialize, Serialize}; use zeroize::{Zeroize, ZeroizeOnDrop}; diff --git a/common/nym_offline_compact_ecash/src/scheme/mod.rs b/common/nym_offline_compact_ecash/src/scheme/mod.rs index 7f9687a380..8d830c1aaf 100644 --- a/common/nym_offline_compact_ecash/src/scheme/mod.rs +++ b/common/nym_offline_compact_ecash/src/scheme/mod.rs @@ -15,8 +15,8 @@ use crate::utils::{ }; use crate::{constants, ecash_group_parameters}; use crate::{Base58, EncodedDate, EncodedTicketType}; -use bls12_381::{G1Projective, G2Prepared, G2Projective, Scalar}; use group::Curve; +use nym_bls12_381_fork::{G1Projective, G2Prepared, G2Projective, Scalar}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::borrow::Borrow; use zeroize::{Zeroize, ZeroizeOnDrop}; diff --git a/common/nym_offline_compact_ecash/src/scheme/setup.rs b/common/nym_offline_compact_ecash/src/scheme/setup.rs index 349706fe47..abbd7e1fbb 100644 --- a/common/nym_offline_compact_ecash/src/scheme/setup.rs +++ b/common/nym_offline_compact_ecash/src/scheme/setup.rs @@ -3,9 +3,9 @@ use crate::ecash_group_parameters; use crate::utils::hash_g1; -use bls12_381::{G1Affine, G1Projective, G2Affine, G2Prepared, Scalar}; use ff::Field; use group::GroupEncoding; +use nym_bls12_381_fork::{G1Affine, G1Projective, G2Affine, G2Prepared, Scalar}; use rand::thread_rng; #[derive(Debug)] diff --git a/common/nym_offline_compact_ecash/src/scheme/withdrawal.rs b/common/nym_offline_compact_ecash/src/scheme/withdrawal.rs index af620f3159..454ad6b613 100644 --- a/common/nym_offline_compact_ecash/src/scheme/withdrawal.rs +++ b/common/nym_offline_compact_ecash/src/scheme/withdrawal.rs @@ -12,8 +12,8 @@ use crate::scheme::setup::GroupParameters; use crate::scheme::PartialWallet; use crate::utils::{check_bilinear_pairing, hash_g1}; use crate::{constants, ecash_group_parameters, Attribute, EncodedDate, EncodedTicketType}; -use bls12_381::{multi_miller_loop, G1Projective, G2Prepared, G2Projective, Scalar}; use group::{Curve, Group, GroupEncoding}; +use nym_bls12_381_fork::{multi_miller_loop, G1Projective, G2Prepared, G2Projective, Scalar}; use serde::{Deserialize, Serialize}; use std::ops::Neg; use zeroize::{Zeroize, ZeroizeOnDrop}; @@ -569,7 +569,7 @@ mod tests { use crate::common_types::BlindedSignature; use crate::ecash_group_parameters; use crate::scheme::keygen::{SecretKeyUser, VerificationKeyAuth}; - use bls12_381::G1Projective; + use nym_bls12_381_fork::G1Projective; #[test] fn test_generate_non_identity_h() { diff --git a/common/nym_offline_compact_ecash/src/traits.rs b/common/nym_offline_compact_ecash/src/traits.rs index a9a22144b9..5e3ff82bf2 100644 --- a/common/nym_offline_compact_ecash/src/traits.rs +++ b/common/nym_offline_compact_ecash/src/traits.rs @@ -7,8 +7,8 @@ use crate::proofs::proof_withdrawal::{WithdrawalReqInstance, WithdrawalReqProof} use crate::scheme::withdrawal::RequestInfo; use crate::scheme::{Payment, WalletSignatures}; use crate::{Attribute, CompactEcashError, PartialWallet, WithdrawalRequest}; -use bls12_381::{G1Affine, G1Projective}; use group::GroupEncoding; +use nym_bls12_381_fork::{G1Affine, G1Projective}; #[macro_export] macro_rules! impl_byteable_bs58 { diff --git a/common/nym_offline_compact_ecash/src/utils.rs b/common/nym_offline_compact_ecash/src/utils.rs index 9042975dec..1c262541a3 100644 --- a/common/nym_offline_compact_ecash/src/utils.rs +++ b/common/nym_offline_compact_ecash/src/utils.rs @@ -5,15 +5,15 @@ use crate::common_types::SignerIndex; use crate::error::{CompactEcashError, Result}; use crate::scheme::setup::GroupParameters; use crate::{ecash_group_parameters, Signature, VerificationKeyAuth}; -use bls12_381::hash_to_curve::{ExpandMsgXmd, HashToCurve, HashToField}; -use bls12_381::{ - multi_miller_loop, G1Affine, G1Projective, G2Affine, G2Prepared, G2Projective, Scalar, -}; use core::iter::Sum; use core::ops::Mul; use ff::Field; use group::{Curve, Group}; use itertools::Itertools; +use nym_bls12_381_fork::hash_to_curve::{ExpandMsgXmd, HashToCurve, HashToField}; +use nym_bls12_381_fork::{ + multi_miller_loop, G1Affine, G1Projective, G2Affine, G2Prepared, G2Projective, Scalar, +}; use std::borrow::Borrow; use std::ops::Neg; diff --git a/common/nymnoise/Cargo.toml b/common/nymnoise/Cargo.toml index 63050814ef..64dd0470f5 100644 --- a/common/nymnoise/Cargo.toml +++ b/common/nymnoise/Cargo.toml @@ -1,9 +1,13 @@ [package] name = "nym-noise" -version = "0.1.0" +version.workspace = true authors = ["Simon Wicky "] edition = "2021" license.workspace = true +description = "Nym's Noise protocol implementation" +repository.workspace = true +homepage.workspace = true +documentation.workspace = true [dependencies] arc-swap = { workspace = true } @@ -20,15 +24,15 @@ tokio = { workspace = true, features = ["net", "io-util", "time"] } tokio-util = { workspace = true, features = ["codec"] } # internal -nym-crypto = { path = "../crypto" } -nym-noise-keys = { path = "keys" } +nym-crypto = { workspace = true } +nym-noise-keys = { workspace = true } [dev-dependencies] anyhow = { workspace = true } tokio = { workspace = true, features = ["full"] } rand_chacha = { workspace = true } -nym-crypto = { path = "../crypto", features = ["rand"] } -nym-test-utils = { path = "../test-utils" } +nym-crypto = { workspace = true, features = ["rand"] } +nym-test-utils = { workspace = true } [lints] diff --git a/common/nymnoise/keys/Cargo.toml b/common/nymnoise/keys/Cargo.toml index 94080a004b..3dfc1c64d2 100644 --- a/common/nymnoise/keys/Cargo.toml +++ b/common/nymnoise/keys/Cargo.toml @@ -1,9 +1,13 @@ [package] name = "nym-noise-keys" -version = "0.1.0" +version.workspace = true authors = ["Simon Wicky "] edition = "2021" license.workspace = true +description = "Helpers and type definition of Nym's Noise protocol keys" +repository.workspace = true +homepage.workspace = true +documentation.workspace = true [dependencies] schemars = { workspace = true, features = ["preserve_order"] } @@ -11,7 +15,7 @@ serde = { workspace = true, features = ["derive"] } utoipa = { workspace = true } # internal -nym-crypto = { path = "../../crypto", features = ["asymmetric", "serde"] } +nym-crypto = { workspace = true, features = ["asymmetric", "serde"] } [lints] -workspace = true \ No newline at end of file +workspace = true diff --git a/common/nymsphinx/Cargo.toml b/common/nymsphinx/Cargo.toml index d6b77f6969..1fba34a0c2 100644 --- a/common/nymsphinx/Cargo.toml +++ b/common/nymsphinx/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-sphinx" -version = "0.1.0" +version.workspace = true description = "Top-level crate for sphinx packets as used by the Nym mixnet" edition = { workspace = true } authors = { workspace = true } @@ -14,32 +14,32 @@ rand_distr = { workspace = true } rand_chacha = { workspace = true } thiserror = { workspace = true } -nym-sphinx-acknowledgements = { path = "acknowledgements" } -nym-sphinx-addressing = { path = "addressing" } -nym-sphinx-anonymous-replies = { path = "anonymous-replies" } -nym-sphinx-chunking = { path = "chunking" } -nym-sphinx-cover = { path = "cover" } -nym-sphinx-forwarding = { path = "forwarding" } -nym-sphinx-params = { path = "params" } -nym-sphinx-routing = { path = "routing" } -nym-sphinx-types = { path = "types" } +nym-sphinx-acknowledgements = { workspace = true } +nym-sphinx-addressing = { workspace = true } +nym-sphinx-anonymous-replies = { workspace = true } +nym-sphinx-chunking = { workspace = true } +nym-sphinx-cover = { workspace = true } +nym-sphinx-forwarding = { workspace = true } +nym-sphinx-params = { workspace = true } +nym-sphinx-routing = { workspace = true } +nym-sphinx-types = { workspace = true } # those dependencies are due to intriducing preparer and receiver. Perpaphs that indicates they should be moved # to separate crate? -nym-crypto = { path = "../crypto", version = "0.4.0" } -nym-topology = { path = "../topology" } -nym-metrics = { path = "../nym-metrics" } +nym-crypto = { workspace = true } +nym-topology = { workspace = true } +nym-metrics = { workspace = true } [dev-dependencies] -nym-mixnet-contract-common = { path = "../cosmwasm-smart-contracts/mixnet-contract" } -nym-crypto = { path = "../crypto", version = "0.4.0", features = [ +nym-mixnet-contract-common = { workspace = true } +nym-crypto = { workspace = true, features = [ "asymmetric", ] } # do not include this when compiling into wasm as it somehow when combined together with reqwest, it will require # net2 via tokio-util -> tokio -> mio -> net2 [target."cfg(not(target_arch = \"wasm32\"))".dependencies.nym-sphinx-framing] -path = "framing" +workspace = true [target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio] workspace = true diff --git a/common/nymsphinx/acknowledgements/Cargo.toml b/common/nymsphinx/acknowledgements/Cargo.toml index 117bc6b7d5..6d0fe4fcff 100644 --- a/common/nymsphinx/acknowledgements/Cargo.toml +++ b/common/nymsphinx/acknowledgements/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-sphinx-acknowledgements" -version = "0.1.0" +version.workspace = true description = "Sphinx packet ack messages" edition = { workspace = true } authors = { workspace = true } @@ -14,13 +14,13 @@ generic-array = { workspace = true, optional = true, features = ["serde"] } thiserror = { workspace = true } zeroize = { workspace = true } -nym-crypto = { path = "../../crypto", features = ["stream_cipher", "rand"] } -nym-pemstore = { path = "../../pemstore" } -nym-sphinx-addressing = { path = "../addressing" } -nym-sphinx-params = { path = "../params" } -nym-sphinx-routing = { path = "../routing" } -nym-sphinx-types = { path = "../types", features = ["sphinx"] } -nym-topology = { path = "../../topology" } +nym-crypto = { workspace = true, features = ["stream_cipher", "rand"] } +nym-pemstore = { workspace = true } +nym-sphinx-addressing = { workspace = true } +nym-sphinx-params = { workspace = true } +nym-sphinx-routing = { workspace = true } +nym-sphinx-types = { workspace = true, features = ["sphinx"] } +nym-topology = { workspace = true } [features] serde = ["dep:serde", "generic-array"] diff --git a/common/nymsphinx/addressing/Cargo.toml b/common/nymsphinx/addressing/Cargo.toml index c7f756cb5c..5aec9af3ed 100644 --- a/common/nymsphinx/addressing/Cargo.toml +++ b/common/nymsphinx/addressing/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-sphinx-addressing" -version = "0.1.0" +version.workspace = true description = "Nym mixnet addressing" edition = { workspace = true } authors = { workspace = true } @@ -8,14 +8,14 @@ license = { workspace = true } repository = { workspace = true } [dependencies] -nym-crypto = { path = "../../crypto", features = ["asymmetric", "sphinx"] } # all addresses are expressed in terms on their crypto keys -nym-sphinx-types = { path = "../types", features = ["sphinx"] } # we need to be able to refer to some types defined inside sphinx crate +nym-crypto = { workspace = true, features = ["asymmetric", "sphinx"] } # all addresses are expressed in terms on their crypto keys +nym-sphinx-types = { workspace = true, features = ["sphinx"] } # we need to be able to refer to some types defined inside sphinx crate serde = { workspace = true } # implementing serialization/deserialization for some types, like `Recipient` thiserror = { workspace = true } [dev-dependencies] rand = { workspace = true } -nym-crypto = { path = "../../crypto", features = ["rand"] } +nym-crypto = { workspace = true, features = ["rand"] } bincode = { workspace = true } serde_json = { workspace = true } -serde = { workspace = true, features = ["derive"] } \ No newline at end of file +serde = { workspace = true, features = ["derive"] } diff --git a/common/nymsphinx/anonymous-replies/Cargo.toml b/common/nymsphinx/anonymous-replies/Cargo.toml index ef9c74b73d..fe9f272146 100644 --- a/common/nymsphinx/anonymous-replies/Cargo.toml +++ b/common/nymsphinx/anonymous-replies/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-sphinx-anonymous-replies" -version = "0.1.0" +version.workspace = true description = "Anonymous sphinx packet replies using singly-use-reply-blocks (SURB)" edition = { workspace = true } authors = { workspace = true } @@ -13,12 +13,12 @@ bs58 = { workspace = true } thiserror = { workspace = true } tracing = { workspace = true } -nym-crypto = { path = "../../crypto", features = ["stream_cipher", "rand"] } -nym-sphinx-addressing = { path = "../addressing" } -nym-sphinx-params = { path = "../params" } -nym-sphinx-routing = { path = "../routing" } -nym-sphinx-types = { path = "../types" } -nym-topology = { path = "../../topology" } +nym-crypto = { workspace = true, features = ["stream_cipher", "rand"] } +nym-sphinx-addressing = { workspace = true } +nym-sphinx-params = { workspace = true } +nym-sphinx-routing = { workspace = true } +nym-sphinx-types = { workspace = true } +nym-topology = { workspace = true } [target."cfg(target_arch = \"wasm32\")".dependencies.wasm-bindgen] workspace = true diff --git a/common/nymsphinx/chunking/Cargo.toml b/common/nymsphinx/chunking/Cargo.toml index f5ea240859..708ecb5c10 100644 --- a/common/nymsphinx/chunking/Cargo.toml +++ b/common/nymsphinx/chunking/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-sphinx-chunking" -version = "0.1.0" +version.workspace = true description = "Sphinx packet chunking of underlying data packets" edition = { workspace = true } authors = { workspace = true } @@ -17,11 +17,11 @@ dashmap = { workspace = true, features = ["serde"] } serde = { workspace = true, features = ["derive"] } utoipa = { workspace = true } -nym-sphinx-addressing = { path = "../addressing" } -nym-sphinx-params = { path = "../params" } -nym-sphinx-types = { path = "../types" } -nym-metrics = { path = "../../nym-metrics" } -nym-crypto = { path = "../../crypto", version = "0.4.0", features = [ +nym-sphinx-addressing = { workspace = true } +nym-sphinx-params = { workspace = true } +nym-sphinx-types = { workspace = true } +nym-metrics = { workspace = true } +nym-crypto = { workspace = true, features = [ "asymmetric", ] } diff --git a/common/nymsphinx/cover/Cargo.toml b/common/nymsphinx/cover/Cargo.toml index 59710923d1..69dcf6fe65 100644 --- a/common/nymsphinx/cover/Cargo.toml +++ b/common/nymsphinx/cover/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-sphinx-cover" -version = "0.1.0" +version.workspace = true description = "Sphinx packets used as cover traffic" edition = { workspace = true } authors = { workspace = true } @@ -11,12 +11,12 @@ repository = { workspace = true } rand = { workspace = true } thiserror = { workspace = true } -nym-crypto = { path = "../../crypto" } -nym-sphinx-acknowledgements = { path = "../acknowledgements" } -nym-sphinx-addressing = { path = "../addressing" } -nym-sphinx-chunking = { path = "../chunking" } -nym-sphinx-forwarding = { path = "../forwarding" } -nym-sphinx-params = { path = "../params" } -nym-sphinx-routing = { path = "../routing" } -nym-sphinx-types = { path = "../types" } -nym-topology = { path = "../../topology" } +nym-crypto = { workspace = true } +nym-sphinx-acknowledgements = { workspace = true } +nym-sphinx-addressing = { workspace = true } +nym-sphinx-chunking = { workspace = true } +nym-sphinx-forwarding = { workspace = true } +nym-sphinx-params = { workspace = true } +nym-sphinx-routing = { workspace = true } +nym-sphinx-types = { workspace = true } +nym-topology = { workspace = true } diff --git a/common/nymsphinx/forwarding/Cargo.toml b/common/nymsphinx/forwarding/Cargo.toml index c8beb33b12..e9a299c626 100644 --- a/common/nymsphinx/forwarding/Cargo.toml +++ b/common/nymsphinx/forwarding/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-sphinx-forwarding" -version = "0.1.0" +version.workspace = true description = "Sphinx packet forwarding as Nym mix packets" edition = { workspace = true } authors = { workspace = true } @@ -8,8 +8,8 @@ license = { workspace = true } repository = { workspace = true } [dependencies] -nym-sphinx-addressing = { path = "../addressing" } -nym-sphinx-params = { path = "../params" } -nym-sphinx-types = { path = "../types", features = ["sphinx", "outfox"] } -nym-sphinx-anonymous-replies = { path = "../anonymous-replies" } +nym-sphinx-addressing = { workspace = true } +nym-sphinx-params = { workspace = true } +nym-sphinx-types = { workspace = true, features = ["sphinx", "outfox"] } +nym-sphinx-anonymous-replies = { workspace = true } thiserror = { workspace = true } diff --git a/common/nymsphinx/framing/Cargo.toml b/common/nymsphinx/framing/Cargo.toml index 01c5e6e4ae..a2f42c7e16 100644 --- a/common/nymsphinx/framing/Cargo.toml +++ b/common/nymsphinx/framing/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-sphinx-framing" -version = "0.1.0" +version.workspace = true description = "Sphinx packet framing for the Nym mixnet" edition = { workspace = true } authors = { workspace = true } @@ -14,11 +14,11 @@ tokio-util = { workspace = true, features = ["codec"] } thiserror = { workspace = true } tracing = { workspace = true } -nym-sphinx-types = { path = "../types", features = ["sphinx", "outfox"] } -nym-sphinx-params = { path = "../params", features = ["sphinx", "outfox"] } -nym-sphinx-forwarding = { path = "../forwarding" } -nym-sphinx-addressing = { path = "../addressing" } -nym-sphinx-acknowledgements = { path = "../acknowledgements" } +nym-sphinx-types = { workspace = true, features = ["sphinx", "outfox"] } +nym-sphinx-params = { workspace = true, features = ["sphinx", "outfox"] } +nym-sphinx-forwarding = { workspace = true } +nym-sphinx-addressing = { workspace = true } +nym-sphinx-acknowledgements = { workspace = true } [dev-dependencies] tokio = { workspace = true, features = ["full"] } diff --git a/common/nymsphinx/params/Cargo.toml b/common/nymsphinx/params/Cargo.toml index 8db26e7b1d..c5fd617d63 100644 --- a/common/nymsphinx/params/Cargo.toml +++ b/common/nymsphinx/params/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-sphinx-params" -version = "0.1.0" +version.workspace = true description = "Sphinx packet parameters for the Nym mixnet" edition = { workspace = true } authors = { workspace = true } @@ -11,8 +11,8 @@ repository = { workspace = true } thiserror = { workspace = true } serde = { workspace = true, features = ["derive"] } -nym-crypto = { path = "../../crypto", features = ["hashing", "stream_cipher", "aead"] } -nym-sphinx-types = { path = "../types" } +nym-crypto = { workspace = true, features = ["hashing", "stream_cipher", "aead"] } +nym-sphinx-types = { workspace = true } [features] default = ["sphinx"] diff --git a/common/nymsphinx/routing/Cargo.toml b/common/nymsphinx/routing/Cargo.toml index 37b8d5a4dc..209c9baefb 100644 --- a/common/nymsphinx/routing/Cargo.toml +++ b/common/nymsphinx/routing/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-sphinx-routing" -version = "0.1.0" +version.workspace = true description = "Sphinx packet routing as Nym mix packets" edition = { workspace = true } authors = { workspace = true } @@ -10,7 +10,7 @@ repository = { workspace = true } [dependencies] thiserror = { workspace = true } -nym-sphinx-addressing = { path = "../addressing" } -nym-sphinx-types = { path = "../types", features = ["sphinx"] } +nym-sphinx-addressing = { workspace = true } +nym-sphinx-types = { workspace = true, features = ["sphinx"] } -[features] \ No newline at end of file +[features] diff --git a/common/nymsphinx/types/Cargo.toml b/common/nymsphinx/types/Cargo.toml index b12d22d7f6..776f88a41a 100644 --- a/common/nymsphinx/types/Cargo.toml +++ b/common/nymsphinx/types/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-sphinx-types" -version = "0.2.0" +version.workspace = true description = "Re-export sphinx packet types" edition = { workspace = true } authors = { workspace = true } @@ -9,7 +9,7 @@ repository = { workspace = true } [dependencies] sphinx-packet = { workspace = true, optional = true } -nym-outfox = { path = "../../../nym-outfox", optional = true } +nym-outfox = { workspace = true, optional = true } thiserror = { workspace = true } [features] diff --git a/common/nyxd-scraper-psql/Cargo.toml b/common/nyxd-scraper-psql/Cargo.toml index 0c2253cf9b..c99249f0cd 100644 --- a/common/nyxd-scraper-psql/Cargo.toml +++ b/common/nyxd-scraper-psql/Cargo.toml @@ -9,6 +9,7 @@ edition.workspace = true license.workspace = true rust-version.workspace = true readme.workspace = true +publish = false [dependencies] async-trait = { workspace = true } @@ -21,7 +22,7 @@ thiserror = { workspace = true } tokio = { workspace = true, features = ["full"] } tracing.workspace = true -nyxd-scraper-shared = { path = "../nyxd-scraper-shared" } +nyxd-scraper-shared = { workspace = true } # temp due to cosmrs redefinitions for serde cosmrs = { workspace = true } diff --git a/common/nyxd-scraper-shared/Cargo.toml b/common/nyxd-scraper-shared/Cargo.toml index 3f7a343ec2..299d2a6186 100644 --- a/common/nyxd-scraper-shared/Cargo.toml +++ b/common/nyxd-scraper-shared/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nyxd-scraper-shared" -version = "0.1.0" +version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true @@ -9,6 +9,7 @@ edition.workspace = true license.workspace = true rust-version.workspace = true readme.workspace = true +description = "Common crate for the sqlite and psql Nyxd blockchain scrapers" [dependencies] async-trait.workspace = true diff --git a/common/nyxd-scraper-sqlite/Cargo.toml b/common/nyxd-scraper-sqlite/Cargo.toml index eb10336f0b..bcf59ce2cf 100644 --- a/common/nyxd-scraper-sqlite/Cargo.toml +++ b/common/nyxd-scraper-sqlite/Cargo.toml @@ -7,6 +7,7 @@ homepage.workspace = true documentation.workspace = true edition.workspace = true license.workspace = true +publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -17,7 +18,7 @@ thiserror = { workspace = true } tokio = { workspace = true, features = ["full"] } tracing.workspace = true -nyxd-scraper-shared = { path = "../nyxd-scraper-shared" } +nyxd-scraper-shared = { workspace = true } [build-dependencies] @@ -26,4 +27,4 @@ tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } anyhow.workspace = true [lints] -workspace = true \ No newline at end of file +workspace = true diff --git a/common/pemstore/Cargo.toml b/common/pemstore/Cargo.toml index 9c93dc122d..5534b81dd0 100644 --- a/common/pemstore/Cargo.toml +++ b/common/pemstore/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "nym-pemstore" description = "Store private-public keypairs in PEM format" -version = "0.3.0" +version.workspace = true edition = { workspace = true } authors = { workspace = true } license = { workspace = true } @@ -10,4 +10,4 @@ repository = { workspace = true } [dependencies] pem = { workspace = true } tracing = { workspace = true } -zeroize = { workspace = true } \ No newline at end of file +zeroize = { workspace = true } diff --git a/common/published-crates.txt b/common/published-crates.txt deleted file mode 100644 index 5aee0f41d8..0000000000 --- a/common/published-crates.txt +++ /dev/null @@ -1,8 +0,0 @@ -# List of published common crates -nym-bin-common -nym-contracts-common -nym-crypto -nym-mixnet-contract-common -nym-pemstore -nym-sphinx-types -nym-vesting-contract-common diff --git a/common/registration/Cargo.toml b/common/registration/Cargo.toml index 93104bfbbf..530584e0e5 100644 --- a/common/registration/Cargo.toml +++ b/common/registration/Cargo.toml @@ -1,12 +1,13 @@ [package] name = "nym-registration-common" -version = "0.1.0" +version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true documentation.workspace = true edition.workspace = true license.workspace = true +description = "Struct definitions for NymNode, GatewayData, and AssignedAddresses" [lints] workspace = true @@ -16,12 +17,12 @@ bincode = { workspace = true } serde = { workspace = true, features = ["derive"] } tokio-util.workspace = true -nym-authenticator-requests = { path = "../authenticator-requests" } -nym-credentials-interface = { path = "../credentials-interface" } -nym-crypto = { path = "../crypto" } -nym-ip-packet-requests = { path = "../ip-packet-requests" } -nym-sphinx = { path = "../nymsphinx" } -nym-wireguard-types = { path = "../wireguard-types" } +nym-authenticator-requests = { workspace = true } +nym-credentials-interface = { workspace = true } +nym-crypto = { workspace = true } +nym-ip-packet-requests = { workspace = true } +nym-sphinx = { workspace = true } +nym-wireguard-types = { workspace = true } [dev-dependencies] bincode.workspace = true diff --git a/common/serde-helpers/Cargo.toml b/common/serde-helpers/Cargo.toml index bc9de1862e..9b219e23cb 100644 --- a/common/serde-helpers/Cargo.toml +++ b/common/serde-helpers/Cargo.toml @@ -1,12 +1,13 @@ [package] name = "nym-serde-helpers" -version = "0.1.0" +version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true documentation.workspace = true edition.workspace = true license.workspace = true +description = "Serde helpers for hex/base64/base58/datetime" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -22,4 +23,4 @@ time = { workspace = true, features = ["formatting", "parsing"], optional = true hex = ["dep:hex"] bs58 = ["dep:bs58"] base64 = ["dep:base64"] -date = ["time"] \ No newline at end of file +date = ["time"] diff --git a/common/service-provider-requests-common/Cargo.toml b/common/service-provider-requests-common/Cargo.toml index 63aef2eb76..b289a776b7 100644 --- a/common/service-provider-requests-common/Cargo.toml +++ b/common/service-provider-requests-common/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-service-provider-requests-common" -version = "0.1.0" +version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true @@ -9,6 +9,7 @@ edition.workspace = true license.workspace = true rust-version.workspace = true readme.workspace = true +description = "Common crate for requests and responses for Nym Service Providers" [dependencies] serde = { workspace = true, features = ["derive"] } diff --git a/common/socks5-client-core/Cargo.toml b/common/socks5-client-core/Cargo.toml index ce8421f3d7..f86c6cf686 100644 --- a/common/socks5-client-core/Cargo.toml +++ b/common/socks5-client-core/Cargo.toml @@ -1,8 +1,12 @@ [package] name = "nym-socks5-client-core" -version = "0.1.0" +version.workspace = true edition = "2021" license.workspace = true +description = "Core functionality of the Nym SOCKS client" +repository.workspace = true +homepage.workspace = true +documentation.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -21,19 +25,19 @@ thiserror = { workspace = true } tokio = { workspace = true, features = ["rt-multi-thread", "net", "signal"] } url = { workspace = true } -nym-bandwidth-controller = { path = "../../common/bandwidth-controller" } -nym-client-core = { path = "../client-core", features = ["fs-credentials-storage", "fs-surb-storage", "fs-gateways-storage"] } -nym-config = { path = "../config" } -nym-contracts-common = { path = "../cosmwasm-smart-contracts/contracts-common" } -nym-credential-storage = { path = "../credential-storage" } -nym-mixnet-contract-common = { path = "../cosmwasm-smart-contracts/mixnet-contract" } -nym-network-defaults = { path = "../network-defaults" } -nym-service-providers-common = { path = "../../service-providers/common" } -nym-socks5-proxy-helpers = { path = "../socks5/proxy-helpers" } -nym-socks5-requests = { path = "../socks5/requests" } -nym-sphinx = { path = "../nymsphinx" } -nym-task = { path = "../task" } -nym-validator-client = { path = "../client-libs/validator-client" } +nym-bandwidth-controller = { workspace = true } +nym-client-core = { workspace = true, features = ["fs-credentials-storage", "fs-surb-storage", "fs-gateways-storage"] } +nym-config = { workspace = true } +nym-contracts-common = { workspace = true } +nym-credential-storage = { workspace = true } +nym-mixnet-contract-common = { workspace = true } +nym-network-defaults = { workspace = true } +nym-service-providers-common = { workspace = true } +nym-socks5-proxy-helpers = { workspace = true } +nym-socks5-requests = { workspace = true } +nym-sphinx = { workspace = true } +nym-task = { workspace = true } +nym-validator-client = { workspace = true } [features] default = [] diff --git a/common/socks5/ordered-buffer/Cargo.toml b/common/socks5/ordered-buffer/Cargo.toml index f8d684cd1f..572f2c0d59 100644 --- a/common/socks5/ordered-buffer/Cargo.toml +++ b/common/socks5/ordered-buffer/Cargo.toml @@ -1,9 +1,13 @@ [package] name = "nym-ordered-buffer" -version = "0.1.0" +version.workspace = true authors = ["Dave Hrycyszyn "] edition = "2021" license.workspace = true +description = "This crate takes care of reliably and speedily turning bytes into a series of ordered message fragments on one side, and of reliably reassembling the fragments into the original message on the other" +repository.workspace = true +homepage.workspace = true +documentation.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/common/socks5/proxy-helpers/Cargo.toml b/common/socks5/proxy-helpers/Cargo.toml index da1d98e254..9b74fd005d 100644 --- a/common/socks5/proxy-helpers/Cargo.toml +++ b/common/socks5/proxy-helpers/Cargo.toml @@ -1,9 +1,13 @@ [package] name = "nym-socks5-proxy-helpers" -version = "0.1.0" +version.workspace = true authors = ["Jedrzej Stuczynski "] edition = "2021" license.workspace = true +description = "Helpers for the Nym SOCKS client" +repository.workspace = true +homepage.workspace = true +documentation.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -17,9 +21,9 @@ futures = { workspace = true } log = { workspace = true } # internal -nym-ordered-buffer = { path = "../ordered-buffer" } -nym-socks5-requests = { path = "../requests" } -nym-task = { path = "../../task" } +nym-ordered-buffer = { workspace = true } +nym-socks5-requests = { workspace = true } +nym-task = { workspace = true } [dev-dependencies] tokio-test = { workspace = true } diff --git a/common/socks5/requests/Cargo.toml b/common/socks5/requests/Cargo.toml index be8ec4f8ca..1197c4beb3 100644 --- a/common/socks5/requests/Cargo.toml +++ b/common/socks5/requests/Cargo.toml @@ -1,18 +1,22 @@ [package] name = "nym-socks5-requests" -version = "0.1.0" +version.workspace = true authors = ["Dave Hrycyszyn "] edition = "2021" license.workspace = true +description = "Request and response definitions for the Nym SOCKS client" +repository.workspace = true +homepage.workspace = true +documentation.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] bincode = { workspace = true } log = { workspace = true } -nym-exit-policy = { path = "../../../common/exit-policy"} -nym-service-providers-common = { path = "../../../service-providers/common" } -nym-sphinx-addressing = { path = "../../../common/nymsphinx/addressing" } +nym-exit-policy = { workspace = true} +nym-service-providers-common = { workspace = true } +nym-sphinx-addressing = { workspace = true } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } tap = { workspace = true } diff --git a/common/statistics/Cargo.toml b/common/statistics/Cargo.toml index b0f122646c..c086bd8374 100644 --- a/common/statistics/Cargo.toml +++ b/common/statistics/Cargo.toml @@ -3,9 +3,13 @@ [package] name = "nym-statistics-common" -version = "0.1.0" +version.workspace = true edition.workspace = true license.workspace = true +description = "This crate contains basic statistics utilities and abstractions to be re-used and applied throughout both the client and gateway implementations" +repository.workspace = true +homepage.workspace = true +documentation.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -23,11 +27,11 @@ si-scale = { workspace = true } strum = { workspace = true } strum_macros = { workspace = true } -nym-crypto = { path = "../crypto" } -nym-sphinx = { path = "../nymsphinx" } -nym-credentials-interface = { path = "../credentials-interface" } -nym-metrics = { path = "../nym-metrics" } -nym-task = { path = "../task" } +nym-crypto = { workspace = true } +nym-sphinx = { workspace = true } +nym-credentials-interface = { workspace = true } +nym-metrics = { workspace = true } +nym-task = { workspace = true } utoipa = { workspace = true, optional = true } diff --git a/common/store-cipher/Cargo.toml b/common/store-cipher/Cargo.toml index ca0a628635..124d5b1869 100644 --- a/common/store-cipher/Cargo.toml +++ b/common/store-cipher/Cargo.toml @@ -1,8 +1,12 @@ [package] name = "nym-store-cipher" -version = "0.1.0" +version.workspace = true edition = "2021" license.workspace = true +description = "Helpers for various ciphers used throughout the Nym network" +repository.workspace = true +homepage.workspace = true +documentation.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/common/task/Cargo.toml b/common/task/Cargo.toml index d96fcbe068..0daef2c763 100644 --- a/common/task/Cargo.toml +++ b/common/task/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-task" -version = "0.1.0" +version.workspace = true description = "Task handling" edition.workspace = true authors.workspace = true @@ -36,7 +36,7 @@ tokio-tracing = ["tokio/tracing"] [dev-dependencies] anyhow = { workspace = true } tokio = { workspace = true, features = ["rt-multi-thread", "net", "signal", "test-util", "macros"] } -nym-test-utils = { path = "../test-utils" } +nym-test-utils = { workspace = true } [lints] -workspace = true \ No newline at end of file +workspace = true diff --git a/common/test-utils/Cargo.toml b/common/test-utils/Cargo.toml index 5993d47a30..f89073afc7 100644 --- a/common/test-utils/Cargo.toml +++ b/common/test-utils/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-test-utils" -version = "0.1.0" +version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true @@ -9,6 +9,7 @@ edition.workspace = true license.workspace = true rust-version.workspace = true readme.workspace = true +description = "Helpers, traits, and mock definitions for tests" [dependencies] anyhow = { workspace = true } @@ -17,7 +18,7 @@ rand_chacha = { workspace = true } tokio = { workspace = true, features = ["sync", "time", "rt"] } tracing = { workspace = true } -nym-bin-common = { path = "../bin-common", features = ["tracing"] } +nym-bin-common = { workspace = true, features = ["tracing"] } [dev-dependencies] tokio = { workspace = true, features = ["full"] } diff --git a/common/ticketbooks-merkle/Cargo.toml b/common/ticketbooks-merkle/Cargo.toml index 3c3f278c6b..037852a91d 100644 --- a/common/ticketbooks-merkle/Cargo.toml +++ b/common/ticketbooks-merkle/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-ticketbooks-merkle" -version = "0.1.0" +version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true @@ -9,6 +9,7 @@ edition.workspace = true license.workspace = true rust-version.workspace = true readme.workspace = true +description = "Generate and verify merkleproofs of zknym ticketbooks" [dependencies] sha2 = { workspace = true } @@ -18,8 +19,8 @@ utoipa = { workspace = true } serde = { workspace = true, features = ["derive"] } time = { workspace = true } -nym-credentials-interface = { path = "../credentials-interface" } -nym-serde-helpers = { path = "../serde-helpers", features = ["date", "base64", "hex"] } +nym-credentials-interface = { workspace = true } +nym-serde-helpers = { workspace = true, features = ["date", "base64", "hex"] } [dev-dependencies] rand_chacha = { workspace = true } diff --git a/common/topology/Cargo.toml b/common/topology/Cargo.toml index 64d60171f4..da51f63bbd 100644 --- a/common/topology/Cargo.toml +++ b/common/topology/Cargo.toml @@ -1,12 +1,13 @@ [package] name = "nym-topology" -version = "0.1.0" +version.workspace = true edition = { workspace = true } authors = { workspace = true } license = { workspace = true } repository = { workspace = true } homepage = { workspace = true } documentation = { workspace = true } +description = "Nym's topology crate" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -27,25 +28,25 @@ tsify = { workspace = true, features = ["js"], optional = true } wasm-bindgen = { workspace = true, optional = true } ## internal -nym-crypto = { path = "../crypto" } -nym-mixnet-contract-common = { path = "../cosmwasm-smart-contracts/mixnet-contract" } -nym-sphinx-addressing = { path = "../nymsphinx/addressing" } -nym-sphinx-types = { path = "../nymsphinx/types", features = [ +nym-crypto = { workspace = true } +nym-mixnet-contract-common = { workspace = true } +nym-sphinx-addressing = { workspace = true } +nym-sphinx-types = { workspace = true, features = [ "sphinx", "outfox", ] } # I'm not sure how to feel about pulling in this dependency here... -nym-api-requests = { path = "../../nym-api/nym-api-requests" } +nym-api-requests = { workspace = true } # 'wasm-serde-types' feature -wasm-utils = { path = "../wasm/utils", default-features = false, optional = true } +nym-wasm-utils = { workspace = true, optional = true } [features] default = ["provider-trait"] provider-trait = ["async-trait"] -wasm-serde-types = ["tsify", "wasm-bindgen", "wasm-utils"] +wasm-serde-types = ["tsify", "wasm-bindgen", "nym-wasm-utils"] persistence = ["serde_json"] outfox = [] diff --git a/common/topology/src/wasm_helpers.rs b/common/topology/src/wasm_helpers.rs index 1abf88272b..8d07cbcce0 100644 --- a/common/topology/src/wasm_helpers.rs +++ b/common/topology/src/wasm_helpers.rs @@ -6,13 +6,13 @@ use crate::node::{EntryDetails, RoutingNode, RoutingNodeError, SupportedRoles}; use crate::{CachedEpochRewardedSet, NymTopology, NymTopologyMetadata}; +use nym_wasm_utils::error::simple_js_error; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::net::SocketAddr; use thiserror::Error; use tsify::Tsify; use wasm_bindgen::{JsValue, prelude::wasm_bindgen}; -use wasm_utils::error::simple_js_error; #[derive(Debug, Error)] pub enum SerializableTopologyError { diff --git a/common/tun/Cargo.toml b/common/tun/Cargo.toml index 3915e481e3..e5451f0f81 100644 --- a/common/tun/Cargo.toml +++ b/common/tun/Cargo.toml @@ -1,12 +1,13 @@ [package] name = "nym-tun" -version = "0.1.0" +version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true documentation.workspace = true edition.workspace = true license.workspace = true +description = "Nym's tun implementation" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -15,7 +16,7 @@ thiserror.workspace = true tokio = { workspace = true, features = ["rt-multi-thread", "net", "io-util", "time", "sync", "macros"] } etherparse = { workspace = true } log.workspace = true -nym-wireguard-types = { path = "../wireguard-types", optional = true } +nym-wireguard-types = { workspace = true, optional = true } [target.'cfg(target_os = "linux")'.dependencies] tokio-tun.workspace = true diff --git a/common/types/Cargo.toml b/common/types/Cargo.toml index 620ec264d1..2f6ae032b4 100644 --- a/common/types/Cargo.toml +++ b/common/types/Cargo.toml @@ -1,11 +1,14 @@ [package] name = "nym-types" -version = "1.0.0" +version.workspace = true description = "Nym common types" authors.workspace = true edition = "2021" rust-version.workspace = true license.workspace = true +repository.workspace = true +homepage.workspace = true +documentation.workspace = true [dependencies] base64 = { workspace = true } @@ -29,11 +32,11 @@ x25519-dalek = { workspace = true, features = ["static_secrets"] } cosmwasm-std = { workspace = true } cosmrs = { workspace = true } -nym-validator-client = { path = "../../common/client-libs/validator-client" } -nym-mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-contract" } -nym-vesting-contract-common = { path = "../../common/cosmwasm-smart-contracts/vesting-contract" } -nym-config = { path = "../../common/config" } -nym-crypto = { path = "../../common/crypto", features = ["asymmetric"] } +nym-validator-client = { workspace = true } +nym-mixnet-contract-common = { workspace = true } +nym-vesting-contract-common = { workspace = true } +nym-config = { workspace = true } +nym-crypto = { workspace = true, features = ["asymmetric"] } [dev-dependencies] tempfile = { workspace = true } diff --git a/common/upgrade-mode-check/Cargo.toml b/common/upgrade-mode-check/Cargo.toml index 17f292a566..8078ede9a4 100644 --- a/common/upgrade-mode-check/Cargo.toml +++ b/common/upgrade-mode-check/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-upgrade-mode-check" -version = "0.1.0" +version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true @@ -9,6 +9,7 @@ edition.workspace = true license.workspace = true rust-version.workspace = true readme.workspace = true +description = "Functions and tests for checking Nym's Credential Proxy is being properly upgraded" [dependencies] jwt-simple = { workspace = true } @@ -20,14 +21,14 @@ thiserror = { workspace = true } tracing = { workspace = true } utoipa = { workspace = true, optional = true } -nym-http-api-client = { path = "../http-api-client", default-features = false } -nym-crypto = { path = "../crypto", features = ["asymmetric", "serde", "naive_jwt"] } +nym-http-api-client = { workspace = true } +nym-crypto = { workspace = true, features = ["asymmetric", "serde", "naive_jwt"] } [dev-dependencies] anyhow = { workspace = true } time = { workspace = true, features = ["macros"] } -nym-test-utils = { path = "../test-utils" } -nym-crypto = { path = "../crypto", features = ["rand"] } +nym-test-utils = { workspace = true } +nym-crypto = { workspace = true, features = ["rand"] } [features] diff --git a/common/verloc/Cargo.toml b/common/verloc/Cargo.toml index 32b81ed1d6..fa218f9aee 100644 --- a/common/verloc/Cargo.toml +++ b/common/verloc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-verloc" -version = "0.1.0" +version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true @@ -9,6 +9,7 @@ edition.workspace = true license.workspace = true rust-version.workspace = true readme.workspace = true +description = "Nym's verloc (Verifiable Location) implementation" [dependencies] bytes = { workspace = true } @@ -22,8 +23,8 @@ thiserror = { workspace = true } rand = { workspace = true } url = { workspace = true } -nym-crypto = { path = "../crypto", features = ["asymmetric"] } -nym-task = { path = "../task" } -nym-validator-client = { path = "../client-libs/validator-client" } -nym-http-api-client = { path = "../http-api-client" } -nym-api-requests = { path = "../../nym-api/nym-api-requests" } +nym-crypto = { workspace = true, features = ["asymmetric"] } +nym-task = { workspace = true } +nym-validator-client = { workspace = true } +nym-http-api-client = { workspace = true } +nym-api-requests = { workspace = true } diff --git a/common/wasm/client-core/Cargo.toml b/common/wasm/client-core/Cargo.toml index 85668972f9..f7811e6f84 100644 --- a/common/wasm/client-core/Cargo.toml +++ b/common/wasm/client-core/Cargo.toml @@ -1,10 +1,11 @@ [package] -name = "wasm-client-core" +name = "nym-wasm-client-core" authors = ["Jedrzej Stuczynski "] -version = "0.1.0" +version.workspace = true edition = "2021" license = "Apache-2.0" repository = "https://github.com/nymtech/nym" +description = "Crate containing core WASM client functionality and configs" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -22,21 +23,21 @@ wasm-bindgen = { workspace = true } wasm-bindgen-futures = { workspace = true } zeroize = { workspace = true } -nym-bandwidth-controller = { path = "../../bandwidth-controller" } -nym-client-core = { path = "../../client-core", default-features = false, features = ["wasm"] } -nym-config = { path = "../../config" } -nym-credential-storage = { path = "../../credential-storage" } -nym-crypto = { path = "../../crypto", features = ["asymmetric", "serde"] } -nym-gateway-client = { path = "../../client-libs/gateway-client", default-features = false, features = ["wasm"] } -nym-sphinx = { path = "../../nymsphinx" } -nym-sphinx-acknowledgements = { path = "../../nymsphinx/acknowledgements", features = ["serde"] } -nym-statistics-common = { path = "../../statistics" } -nym-task = { path = "../../task" } -nym-topology = { path = "../../topology", features = ["wasm-serde-types"] } -nym-validator-client = { path = "../../client-libs/validator-client", default-features = false } -nym-http-api-client = { path = "../../http-api-client" } -wasm-utils = { path = "../utils" } -wasm-storage = { path = "../storage" } +nym-bandwidth-controller = { workspace = true } +nym-client-core = { workspace = true, default-features = false, features = ["wasm"] } +nym-config = { workspace = true } +nym-credential-storage = { workspace = true } +nym-crypto = { workspace = true, features = ["asymmetric", "serde"] } +nym-gateway-client = { workspace = true, default-features = false, features = ["wasm"] } +nym-sphinx = { workspace = true } +nym-sphinx-acknowledgements = { workspace = true, features = ["serde"] } +nym-statistics-common = { workspace = true } +nym-task = { workspace = true } +nym-topology = { workspace = true, features = ["wasm-serde-types"] } +nym-validator-client = { workspace = true, default-features = false } +nym-http-api-client = { workspace = true } +nym-wasm-utils = { workspace = true } +nym-wasm-storage = { workspace = true } # The `console_error_panic_hook` crate provides better debugging of panics by diff --git a/common/wasm/client-core/src/error.rs b/common/wasm/client-core/src/error.rs index ab41b190dd..6c93b1846a 100644 --- a/common/wasm/client-core/src/error.rs +++ b/common/wasm/client-core/src/error.rs @@ -11,8 +11,8 @@ use nym_sphinx::addressing::clients::RecipientFormattingError; use nym_sphinx::anonymous_replies::requests::InvalidAnonymousSenderTagRepresentation; use nym_topology::NymTopologyError; use nym_validator_client::ValidatorClientError; +use nym_wasm_utils::wasm_error; use thiserror::Error; -use wasm_utils::wasm_error; #[derive(Debug, Error)] pub enum WasmCoreError { @@ -85,7 +85,7 @@ pub enum WasmCoreError { #[error(transparent)] BaseStorageError { #[from] - source: wasm_storage::error::StorageError, + source: nym_wasm_storage::error::StorageError, }, #[error(transparent)] diff --git a/common/wasm/client-core/src/helpers.rs b/common/wasm/client-core/src/helpers.rs index 8d7c4dc6e4..2b69630cf7 100644 --- a/common/wasm/client-core/src/helpers.rs +++ b/common/wasm/client-core/src/helpers.rs @@ -22,15 +22,15 @@ use nym_topology::wasm_helpers::WasmFriendlyNymTopology; use nym_topology::{EpochRewardedSet, NymTopology, RoutingNode}; use nym_validator_client::client::IdentityKey; use nym_validator_client::{nym_api::NymApiClientExt, UserAgent}; +use nym_wasm_utils::error::PromisableResult; use rand::thread_rng; use url::Url; use wasm_bindgen::prelude::wasm_bindgen; use wasm_bindgen_futures::future_to_promise; -use wasm_utils::error::PromisableResult; pub use nym_credential_storage::ephemeral_storage::EphemeralStorage as EphemeralCredentialStorage; use nym_topology::provider_trait::ToTopologyMetadata; -use wasm_utils::{console_log, console_warn}; +use nym_wasm_utils::{console_log, console_warn}; // don't get too excited about the name, under the hood it's just a big fat placeholder // with no disk_persistence diff --git a/common/wasm/client-core/src/lib.rs b/common/wasm/client-core/src/lib.rs index 761df16ae4..90c70febfd 100644 --- a/common/wasm/client-core/src/lib.rs +++ b/common/wasm/client-core/src/lib.rs @@ -35,4 +35,4 @@ pub use nym_validator_client::{DirectSigningReqwestRpcNyxdClient, QueryReqwestRp pub use nym_validator_client::client::IdentityKey; #[cfg(target_arch = "wasm32")] -pub use wasm_utils::set_panic_hook; +pub use nym_wasm_utils::set_panic_hook; diff --git a/common/wasm/client-core/src/storage/core_client_traits.rs b/common/wasm/client-core/src/storage/core_client_traits.rs index 9e1e409891..7526b28eab 100644 --- a/common/wasm/client-core/src/storage/core_client_traits.rs +++ b/common/wasm/client-core/src/storage/core_client_traits.rs @@ -18,7 +18,7 @@ use nym_client_core::client::key_manager::ClientKeys; use nym_client_core::client::replies::reply_storage::browser_backend; use nym_credential_storage::ephemeral_storage::EphemeralStorage as EphemeralCredentialStorage; use nym_crypto::asymmetric::ed25519; -use wasm_utils::console_log; +use nym_wasm_utils::console_log; // temporary until other variants are properly implemented (probably it should get changed into `ClientStorage` // implementing all traits and everything getting combined diff --git a/common/wasm/client-core/src/storage/mod.rs b/common/wasm/client-core/src/storage/mod.rs index b129ab331b..08c988adef 100644 --- a/common/wasm/client-core/src/storage/mod.rs +++ b/common/wasm/client-core/src/storage/mod.rs @@ -5,15 +5,15 @@ use crate::error::WasmCoreError; use crate::storage::wasm_client_traits::{v1, v2, WasmClientStorage}; use async_trait::async_trait; use js_sys::Promise; +use nym_wasm_storage::traits::BaseWasmStorage; +use nym_wasm_storage::{ + Build, Database, RawDbResult, TryFromJs, TryToJs, VersionChangeEvent, WasmStorage, +}; +use nym_wasm_utils::error::{simple_js_error, PromisableResult}; use serde::de::DeserializeOwned; use serde::Serialize; use wasm_bindgen::prelude::*; use wasm_bindgen_futures::future_to_promise; -use wasm_storage::traits::BaseWasmStorage; -use wasm_storage::{ - Build, Database, RawDbResult, TryFromJs, TryToJs, VersionChangeEvent, WasmStorage, -}; -use wasm_utils::error::{simple_js_error, PromisableResult}; use zeroize::Zeroizing; pub mod core_client_traits; diff --git a/common/wasm/client-core/src/storage/wasm_client_traits.rs b/common/wasm/client-core/src/storage/wasm_client_traits.rs index 916d22cd1d..eb6d791b39 100644 --- a/common/wasm/client-core/src/storage/wasm_client_traits.rs +++ b/common/wasm/client-core/src/storage/wasm_client_traits.rs @@ -6,10 +6,10 @@ use async_trait::async_trait; use nym_client_core::client::base_client::storage::gateways_storage::RawActiveGateway; use nym_crypto::asymmetric::{ed25519, x25519}; use nym_sphinx_acknowledgements::AckKey; +use nym_wasm_storage::traits::BaseWasmStorage; use std::error::Error; use thiserror::Error; use wasm_bindgen::JsValue; -use wasm_storage::traits::BaseWasmStorage; // v1 tables pub(crate) mod v1 { diff --git a/common/wasm/storage/Cargo.toml b/common/wasm/storage/Cargo.toml index 71364c0c57..884a7b89b6 100644 --- a/common/wasm/storage/Cargo.toml +++ b/common/wasm/storage/Cargo.toml @@ -1,9 +1,13 @@ [package] -name = "wasm-storage" -version = "0.1.0" +name = "nym-wasm-storage" +version.workspace = true authors = ["Jedrzej Stuczynski "] edition = "2021" license.workspace = true +description = "indexeddb-backed in-browser storage with optional encryption implentation and helpers" +repository.workspace = true +homepage.workspace = true +documentation.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -17,5 +21,5 @@ serde-wasm-bindgen = { workspace = true } indexed_db_futures = { workspace = true } thiserror = { workspace = true } -nym-store-cipher = { path = "../../store-cipher", features = ["json"] } -wasm-utils = { path = "../utils", default-features = false } +nym-store-cipher = { workspace = true, features = ["json"] } +nym-wasm-utils = { workspace = true, default-features = false } diff --git a/common/wasm/storage/src/error.rs b/common/wasm/storage/src/error.rs index 3ca74adee4..ee28306c8b 100644 --- a/common/wasm/storage/src/error.rs +++ b/common/wasm/storage/src/error.rs @@ -1,10 +1,10 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use nym_wasm_utils::error::simple_js_error; use serde_wasm_bindgen::Error; use thiserror::Error; use wasm_bindgen::JsValue; -use wasm_utils::error::simple_js_error; #[derive(Debug, Error)] pub enum StorageError { diff --git a/common/wasm/storage/src/lib.rs b/common/wasm/storage/src/lib.rs index 8d9e795270..c20015ab53 100644 --- a/common/wasm/storage/src/lib.rs +++ b/common/wasm/storage/src/lib.rs @@ -8,11 +8,11 @@ use nym_store_cipher::{ Aes256Gcm, Algorithm, EncryptedData, KdfInfo, KeySizeUser, Params, StoreCipher, Unsigned, Version, }; +use nym_wasm_utils::console_log; use serde::de::DeserializeOwned; use serde::Serialize; use std::future::IntoFuture; use wasm_bindgen::JsValue; -use wasm_utils::console_log; pub use indexed_db_futures::database::{Database, VersionChangeEvent}; pub use indexed_db_futures::prelude::*; diff --git a/common/wasm/utils/Cargo.toml b/common/wasm/utils/Cargo.toml index 7305908344..49af79aa41 100644 --- a/common/wasm/utils/Cargo.toml +++ b/common/wasm/utils/Cargo.toml @@ -1,9 +1,13 @@ [package] -name = "wasm-utils" -version = "0.1.0" +name = "nym-wasm-utils" +version.workspace = true authors = ["Jedrzej Stuczynski "] edition = "2021" license.workspace = true +description = "Helpers and macros for the Nym WASM client" +repository.workspace = true +homepage.workspace = true +documentation.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -15,7 +19,7 @@ wasm-bindgen-futures = { workspace = true } getrandom = { workspace = true, features = ["js"], optional = true } gloo-utils = { workspace = true } gloo-net = { workspace = true, features = ["websocket"], optional = true } -#gloo-net = { path = "../../../../gloo/crates/net", features = ["websocket"], optional = true } +#gloo-net = { workspace = truepath = "../../../../gloo/crates/net", features = ["websocket"], optional = true } console_error_panic_hook = { workspace = true, optional = true } diff --git a/common/wireguard-private-metadata/client/Cargo.toml b/common/wireguard-private-metadata/client/Cargo.toml index d7d23ce445..d039810387 100644 --- a/common/wireguard-private-metadata/client/Cargo.toml +++ b/common/wireguard-private-metadata/client/Cargo.toml @@ -1,18 +1,19 @@ [package] name = "nym-wireguard-private-metadata-client" -version = "1.0.0" +version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true documentation.workspace = true edition.workspace = true license.workspace = true +description = "nym-wireguard client implementation" [dependencies] async-trait = { workspace = true } tracing = { workspace = true } -nym-http-api-client = { path = "../../http-api-client" } -nym-wireguard-private-metadata-shared = { path = "../shared" } +nym-http-api-client = { workspace = true } +nym-wireguard-private-metadata-shared = { workspace = true } [lints] workspace = true diff --git a/common/wireguard-private-metadata/server/Cargo.toml b/common/wireguard-private-metadata/server/Cargo.toml index fa850181f1..b16725b149 100644 --- a/common/wireguard-private-metadata/server/Cargo.toml +++ b/common/wireguard-private-metadata/server/Cargo.toml @@ -1,12 +1,13 @@ [package] name = "nym-wireguard-private-metadata-server" -version = "1.0.0" +version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true documentation.workspace = true edition.workspace = true license.workspace = true +description = "nym-wireguard server implementation" [dependencies] anyhow = { workspace = true } @@ -25,15 +26,15 @@ tower-http = { workspace = true, features = [ utoipa = { workspace = true, features = ["axum_extras", "time"] } utoipa-swagger-ui = { workspace = true, features = ["axum"] } -nym-credentials-interface = { path = "../../credentials-interface" } -nym-credential-verification = { path = "../../credential-verification" } -nym-http-api-common = { path = "../../http-api-common", features = [ +nym-credentials-interface = { workspace = true } +nym-credential-verification = { workspace = true } +nym-http-api-common = { workspace = true, features = [ "middleware", "utoipa", "output", ] } -nym-wireguard = { path = "../../wireguard" } -nym-wireguard-private-metadata-shared = { path = "../shared" } +nym-wireguard = { workspace = true } +nym-wireguard-private-metadata-shared = { workspace = true } [dev-dependencies] async-trait = { workspace = true } diff --git a/common/wireguard-private-metadata/shared/Cargo.toml b/common/wireguard-private-metadata/shared/Cargo.toml index 26b3cc6cd6..4c4ffacca8 100644 --- a/common/wireguard-private-metadata/shared/Cargo.toml +++ b/common/wireguard-private-metadata/shared/Cargo.toml @@ -1,12 +1,13 @@ [package] name = "nym-wireguard-private-metadata-shared" -version = "1.0.0" +version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true documentation.workspace = true edition.workspace = true license.workspace = true +description = "Common crate for nym-wireguard server, client, and tests" [dependencies] axum = { workspace = true } @@ -16,7 +17,7 @@ serde = { workspace = true } thiserror = { workspace = true } utoipa = { workspace = true } -nym-credentials-interface = { path = "../../credentials-interface" } +nym-credentials-interface = { workspace = true } [features] testing = [] diff --git a/common/wireguard-private-metadata/tests/Cargo.toml b/common/wireguard-private-metadata/tests/Cargo.toml index 074d088f15..de8a2ab501 100644 --- a/common/wireguard-private-metadata/tests/Cargo.toml +++ b/common/wireguard-private-metadata/tests/Cargo.toml @@ -1,28 +1,29 @@ [package] name = "nym-wireguard-private-metadata-tests" -version = "1.0.0" +version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true documentation.workspace = true edition.workspace = true license.workspace = true +description = "Tests for nym-wireguard implementation" [dependencies] async-trait = { workspace = true } axum = { workspace = true, features = ["tokio", "macros"] } futures = { workspace = true } -nym-credential-verification = { path = "../../credential-verification" } -nym-credentials-interface = { path = "../../credentials-interface" } -nym-crypto = { path = "../../crypto", features = ["asymmetric"] } -nym-http-api-client = { path = "../../http-api-client" } -nym-http-api-common = { path = "../../http-api-common", features = [ +nym-credential-verification = { workspace = true } +nym-credentials-interface = { workspace = true } +nym-crypto = { workspace = true, features = ["asymmetric"] } +nym-http-api-client = { workspace = true } +nym-http-api-common = { workspace = true, features = [ "middleware", "utoipa", "output", ] } -nym-upgrade-mode-check = { path = "../../upgrade-mode-check" } -nym-wireguard = { path = "../../wireguard" } +nym-upgrade-mode-check = { workspace = true } +nym-wireguard = { workspace = true } time = { workspace = true, features = ["macros"] } tokio = { workspace = true, features = ["rt-multi-thread", "net", "io-util"] } tower = { workspace = true } @@ -36,9 +37,8 @@ tower-http = { workspace = true, features = [ ] } utoipa = { workspace = true, features = ["axum_extras", "time"] } -nym-wireguard-private-metadata-client = { path = "../client" } -nym-wireguard-private-metadata-shared = { path = "../shared", features = [ +nym-wireguard-private-metadata-client = { workspace = true } +nym-wireguard-private-metadata-shared = { workspace = true, features = [ "testing", ] } -nym-wireguard-private-metadata-server = { path = "../server" } - +nym-wireguard-private-metadata-server = { workspace = true } diff --git a/common/wireguard-types/Cargo.toml b/common/wireguard-types/Cargo.toml index 9a8a783db0..35af9c2341 100644 --- a/common/wireguard-types/Cargo.toml +++ b/common/wireguard-types/Cargo.toml @@ -1,12 +1,13 @@ [package] name = "nym-wireguard-types" -version = "0.1.0" +version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true documentation.workspace = true edition.workspace = true license.workspace = true +description = "Wireguard public key and config definitions" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -16,8 +17,8 @@ serde = { workspace = true, features = ["derive"] } thiserror = { workspace = true } x25519-dalek = { workspace = true, features = ["static_secrets"] } -nym-crypto = { path = "../crypto", features = ["asymmetric"] } +nym-crypto = { workspace = true, features = ["asymmetric"] } [dev-dependencies] rand = { workspace = true } -nym-crypto = { path = "../crypto", features = ["rand"] } +nym-crypto = { workspace = true, features = ["rand"] } diff --git a/common/wireguard/Cargo.toml b/common/wireguard/Cargo.toml index 8e1d63fd89..16b2536f84 100644 --- a/common/wireguard/Cargo.toml +++ b/common/wireguard/Cargo.toml @@ -1,12 +1,13 @@ [package] name = "nym-wireguard" -version = "0.1.0" +version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true documentation.workspace = true edition.workspace = true license.workspace = true +description = "Interface and peer handling functionality for Wireguard protocol" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -22,20 +23,20 @@ tokio = { workspace = true, features = ["rt-multi-thread", "net", "io-util"] } tokio-stream = { workspace = true } tracing = { workspace = true } -nym-credentials-interface = { path = "../credentials-interface" } -nym-credential-verification = { path = "../credential-verification" } -nym-crypto = { path = "../crypto", features = ["asymmetric"] } -nym-gateway-storage = { path = "../gateway-storage" } -nym-gateway-requests = { path = "../gateway-requests" } -nym-ip-packet-requests = { path = "../ip-packet-requests" } -nym-metrics = { path = "../nym-metrics" } -nym-network-defaults = { path = "../network-defaults" } -nym-task = { path = "../task" } -nym-wireguard-types = { path = "../wireguard-types" } -nym-node-metrics = { path = "../../nym-node/nym-node-metrics" } +nym-credentials-interface = { workspace = true } +nym-credential-verification = { workspace = true } +nym-crypto = { workspace = true, features = ["asymmetric"] } +nym-gateway-storage = { workspace = true } +nym-gateway-requests = { workspace = true } +nym-ip-packet-requests = { workspace = true } +nym-metrics = { workspace = true } +nym-network-defaults = { workspace = true } +nym-task = { workspace = true } +nym-wireguard-types = { workspace = true } +nym-node-metrics = { workspace = true } [dev-dependencies] -nym-gateway-storage = { path = "../gateway-storage", features = ["mock"] } +nym-gateway-storage = { workspace = true, features = ["mock"] } [features] default = [] diff --git a/common/zulip-client/Cargo.toml b/common/zulip-client/Cargo.toml index acfc84a540..cc065e917c 100644 --- a/common/zulip-client/Cargo.toml +++ b/common/zulip-client/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "zulip-client" -version = "0.1.0" +version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true @@ -9,6 +9,7 @@ edition.workspace = true license.workspace = true rust-version.workspace = true readme.workspace = true +publish = false [dependencies] thiserror = { workspace = true } @@ -18,8 +19,8 @@ url = { workspace = true, features = ["serde"] } serde = { workspace = true, features = ["derive"] } zeroize = { workspace = true } -nym-bin-common = { path = "../bin-common" } -nym-http-api-client = { path = "../http-api-client" } +nym-bin-common = { workspace = true } +nym-http-api-client = { workspace = true } reqwest = { workspace = true } tracing = { workspace = true } diff --git a/contracts/Cargo.lock b/contracts/Cargo.lock index 36907864f0..92d73ca89e 100644 --- a/contracts/Cargo.lock +++ b/contracts/Cargo.lock @@ -2,6 +2,17 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + [[package]] name = "ahash" version = "0.8.11" @@ -76,7 +87,7 @@ dependencies = [ "ark-serialize", "ark-std", "derivative", - "digest", + "digest 0.10.7", "itertools 0.10.5", "num-bigint", "num-traits", @@ -130,7 +141,7 @@ checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" dependencies = [ "ark-serialize-derive", "ark-std", - "digest", + "digest 0.10.7", "num-bigint", ] @@ -156,6 +167,12 @@ dependencies = [ "rayon", ] +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + [[package]] name = "autocfg" version = "1.2.0" @@ -198,13 +215,25 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d" +[[package]] +name = "blake2" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94cb07b0da6a73955f8fb85d24c466778e70cda767a568229b104f0264089330" +dependencies = [ + "byte-tools", + "crypto-mac", + "digest 0.8.1", + "opaque-debug", +] + [[package]] name = "block-buffer" version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ - "generic-array", + "generic-array 0.14.7", ] [[package]] @@ -222,6 +251,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "byte-tools" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" + [[package]] name = "byteorder" version = "1.5.0" @@ -286,6 +321,26 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chacha" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddf3c081b5fba1e5615640aae998e0fbd10c24cbd897ee39ed754a77601a4862" +dependencies = [ + "byteorder", + "keystream", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + [[package]] name = "const-oid" version = "0.9.6" @@ -325,7 +380,7 @@ dependencies = [ "ark-serialize", "cosmwasm-core", "curve25519-dalek", - "digest", + "digest 0.10.7", "ecdsa", "ed25519-zebra", "k256", @@ -436,9 +491,9 @@ version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" dependencies = [ - "generic-array", + "generic-array 0.14.7", "rand_core", - "subtle", + "subtle 2.4.1", "zeroize", ] @@ -448,10 +503,29 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ - "generic-array", + "generic-array 0.14.7", "typenum", ] +[[package]] +name = "crypto-mac" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5" +dependencies = [ + "generic-array 0.12.4", + "subtle 1.0.0", +] + +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + [[package]] name = "curve25519-dalek" version = "4.1.3" @@ -461,10 +535,10 @@ dependencies = [ "cfg-if", "cpufeatures", "curve25519-dalek-derive", - "digest", + "digest 0.10.7", "fiat-crypto", "rustc_version", - "subtle", + "subtle 2.4.1", "zeroize", ] @@ -722,6 +796,15 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "digest" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" +dependencies = [ + "generic-array 0.12.4", +] + [[package]] name = "digest" version = "0.10.7" @@ -731,7 +814,7 @@ dependencies = [ "block-buffer", "const-oid", "crypto-common", - "subtle", + "subtle 2.4.1", ] [[package]] @@ -742,7 +825,7 @@ checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" [[package]] name = "easy-addr" -version = "0.1.0" +version = "1.20.1" dependencies = [ "cosmwasm-std", "quote", @@ -756,7 +839,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" dependencies = [ "der", - "digest", + "digest 0.10.7", "elliptic-curve", "rfc6979", "signature", @@ -783,7 +866,7 @@ dependencies = [ "rand_core", "serde", "sha2", - "subtle", + "subtle 2.4.1", "zeroize", ] @@ -816,13 +899,13 @@ checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" dependencies = [ "base16ct", "crypto-bigint", - "digest", + "digest 0.10.7", "ff", - "generic-array", + "generic-array 0.14.7", "group", "rand_core", "sec1", - "subtle", + "subtle 2.4.1", "zeroize", ] @@ -839,7 +922,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" dependencies = [ "rand_core", - "subtle", + "subtle 2.4.1", ] [[package]] @@ -848,6 +931,15 @@ version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c007b1ae3abe1cb6f85a16305acd418b7ca6343b953633fee2b76d8f108b830f" +[[package]] +name = "generic-array" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" +dependencies = [ + "typenum", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -878,7 +970,7 @@ checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" dependencies = [ "ff", "rand_core", - "subtle", + "subtle 2.4.1", ] [[package]] @@ -912,13 +1004,22 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + [[package]] name = "hmac" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest", + "digest 0.10.7", ] [[package]] @@ -947,6 +1048,15 @@ dependencies = [ "hashbrown 0.15.2", ] +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array 0.14.7", +] + [[package]] name = "itertools" version = "0.10.5" @@ -992,6 +1102,12 @@ dependencies = [ "sha2", ] +[[package]] +name = "keystream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33070833c9ee02266356de0c43f723152bd38bd96ddf52c82b3af10c9138b28" + [[package]] name = "konst" version = "0.3.16" @@ -1025,6 +1141,24 @@ version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +[[package]] +name = "libm" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" + +[[package]] +name = "lioness" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae926706ba42c425c9457121178330d75e273df2e82e28b758faf3de3a9acb9" +dependencies = [ + "arrayref", + "blake2", + "chacha", + "keystream", +] + [[package]] name = "memchr" version = "2.7.2" @@ -1078,6 +1212,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -1113,7 +1248,7 @@ dependencies = [ [[package]] name = "nym-coconut-dkg-common" -version = "0.1.0" +version = "1.20.1" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -1126,7 +1261,7 @@ dependencies = [ [[package]] name = "nym-contracts-common" -version = "0.5.0" +version = "1.20.1" dependencies = [ "bs58", "cosmwasm-schema", @@ -1140,7 +1275,7 @@ dependencies = [ [[package]] name = "nym-contracts-common-testing" -version = "0.1.0" +version = "1.20.1" dependencies = [ "anyhow", "cosmwasm-std", @@ -1154,7 +1289,7 @@ dependencies = [ [[package]] name = "nym-crypto" -version = "0.4.0" +version = "1.20.1" dependencies = [ "base64 0.22.1", "bs58", @@ -1200,7 +1335,7 @@ dependencies = [ [[package]] name = "nym-ecash-contract-common" -version = "0.1.0" +version = "1.20.1" dependencies = [ "bs58", "cosmwasm-schema", @@ -1214,7 +1349,7 @@ dependencies = [ [[package]] name = "nym-group-contract-common" -version = "0.1.0" +version = "1.20.1" dependencies = [ "cosmwasm-schema", "cw-controllers", @@ -1249,7 +1384,7 @@ dependencies = [ [[package]] name = "nym-mixnet-contract-common" -version = "0.6.0" +version = "1.20.1" dependencies = [ "bs58", "cosmwasm-schema", @@ -1269,7 +1404,7 @@ dependencies = [ [[package]] name = "nym-multisig-contract-common" -version = "0.1.0" +version = "1.20.1" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -1284,7 +1419,7 @@ dependencies = [ [[package]] name = "nym-network-defaults" -version = "0.1.0" +version = "1.20.1" dependencies = [ "cargo_metadata 0.19.2", "regex", @@ -1292,7 +1427,7 @@ dependencies = [ [[package]] name = "nym-pemstore" -version = "0.3.0" +version = "1.20.1" dependencies = [ "pem", "tracing", @@ -1320,7 +1455,7 @@ dependencies = [ [[package]] name = "nym-performance-contract-common" -version = "0.1.0" +version = "1.20.1" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -1348,7 +1483,7 @@ dependencies = [ [[package]] name = "nym-pool-contract-common" -version = "0.1.0" +version = "1.20.1" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -1360,8 +1495,9 @@ dependencies = [ [[package]] name = "nym-sphinx-types" -version = "0.2.0" +version = "1.20.1" dependencies = [ + "sphinx-packet", "thiserror 2.0.12", ] @@ -1388,7 +1524,7 @@ dependencies = [ [[package]] name = "nym-vesting-contract-common" -version = "0.7.0" +version = "1.20.1" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -1405,6 +1541,12 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "opaque-debug" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" + [[package]] name = "p256" version = "0.13.2" @@ -1575,6 +1717,16 @@ dependencies = [ "getrandom", ] +[[package]] +name = "rand_distr" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31" +dependencies = [ + "num-traits", + "rand", +] + [[package]] name = "rayon" version = "1.10.0" @@ -1631,7 +1783,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" dependencies = [ "hmac", - "subtle", + "subtle 2.4.1", ] [[package]] @@ -1709,8 +1861,8 @@ checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" dependencies = [ "base16ct", "der", - "generic-array", - "subtle", + "generic-array 0.14.7", + "subtle 2.4.1", "zeroize", ] @@ -1803,7 +1955,7 @@ checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" dependencies = [ "cfg-if", "cpufeatures", - "digest", + "digest 0.10.7", ] [[package]] @@ -1812,10 +1964,36 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ - "digest", + "digest 0.10.7", "rand_core", ] +[[package]] +name = "sphinx-packet" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c26f0c20d909fdda1c5d0ece3973127ca421984d55b000215df365e93722fc6e" +dependencies = [ + "aes", + "arrayref", + "blake2", + "bs58", + "byteorder", + "chacha", + "ctr", + "curve25519-dalek", + "digest 0.10.7", + "hkdf", + "hmac", + "lioness", + "rand", + "rand_distr", + "sha2", + "subtle 2.4.1", + "x25519-dalek", + "zeroize", +] + [[package]] name = "spki" version = "0.7.3" @@ -1832,6 +2010,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "subtle" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee" + [[package]] name = "subtle" version = "2.4.1" diff --git a/contracts/Cargo.toml b/contracts/Cargo.toml index ec3239772c..edcaf4b335 100644 --- a/contracts/Cargo.toml +++ b/contracts/Cargo.toml @@ -57,6 +57,41 @@ schemars = "0.8.16" thiserror = "2.0.11" +# Common crates from parent workspace (paths relative to contracts/) +# +# TODO: Once these crates are published to crates.io, switch from path dependencies +# to crates.io versions. +# +# TODO add a [patch.crates-io] section at the bottom for local development if you need to use a modded version of common import instead e.g.: +# +# [patch.crates-io] +# nym-contracts-common = { path = "../common/cosmwasm-smart-contracts/contracts-common" } +# +easy-addr = { version = "1.20.1", path = "../common/cosmwasm-smart-contracts/easy_addr" } +nym-coconut-dkg-common = { version = "1.20.1", path = "../common/cosmwasm-smart-contracts/coconut-dkg" } +nym-contracts-common = { version = "1.20.1", path = "../common/cosmwasm-smart-contracts/contracts-common" } +nym-contracts-common-testing = { version = "1.20.1", path = "../common/cosmwasm-smart-contracts/contracts-common-testing" } +nym-crypto = { version = "1.20.1", path = "../common/crypto", default-features = false } +nym-ecash-contract-common = { version = "1.20.1", path = "../common/cosmwasm-smart-contracts/ecash-contract" } +nym-group-contract-common = { version = "1.20.1", path = "../common/cosmwasm-smart-contracts/group-contract" } +nym-mixnet-contract-common = { version = "1.20.1", path = "../common/cosmwasm-smart-contracts/mixnet-contract" } +nym-multisig-contract-common = { version = "1.20.1", path = "../common/cosmwasm-smart-contracts/multisig-contract" } +nym-network-defaults = { version = "1.20.1", path = "../common/network-defaults", default-features = false } +nym-performance-contract-common = { version = "1.20.1", path = "../common/cosmwasm-smart-contracts/nym-performance-contract" } +nym-pool-contract-common = { version = "1.20.1", path = "../common/cosmwasm-smart-contracts/nym-pool-contract" } +nym-vesting-contract-common = { version = "1.20.1", path = "../common/cosmwasm-smart-contracts/vesting-contract" } + +# Aliases for crates that some contracts import under different names +contracts-common = { version = "1.20.1", path = "../common/cosmwasm-smart-contracts/contracts-common", package = "nym-contracts-common" } +mixnet-contract-common = { version = "1.20.1", path = "../common/cosmwasm-smart-contracts/mixnet-contract", package = "nym-mixnet-contract-common" } +vesting-contract-common = { version = "1.20.1", path = "../common/cosmwasm-smart-contracts/vesting-contract", package = "nym-vesting-contract-common" } + +# Internal contract workspace members (for cross-contract testing) +cw3-flex-multisig = { version = "2.0.0", path = "multisig/cw3-flex-multisig" } +cw4-group = { version = "2.0.0", path = "multisig/cw4-group" } +nym-mixnet-contract = { version = "1.5.1", path = "mixnet" } +nym-vesting-contract = { version = "1.4.1", path = "vesting" } + [workspace.lints.clippy] unwrap_used = "deny" expect_used = "deny" diff --git a/contracts/coconut-dkg/Cargo.toml b/contracts/coconut-dkg/Cargo.toml index 977448b9e7..66cb2acbe0 100644 --- a/contracts/coconut-dkg/Cargo.toml +++ b/contracts/coconut-dkg/Cargo.toml @@ -16,10 +16,10 @@ required-features = ["schema-gen"] crate-type = ["cdylib", "rlib"] [dependencies] -nym-coconut-dkg-common = { path = "../../common/cosmwasm-smart-contracts/coconut-dkg" } -nym-contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts-common" } -nym-contracts-common-testing = { path = "../../common/cosmwasm-smart-contracts/contracts-common-testing", optional = true } -nym-group-contract-common = { path = "../../common/cosmwasm-smart-contracts/group-contract", optional = true } +nym-coconut-dkg-common = { workspace = true } +nym-contracts-common = { workspace = true } +nym-contracts-common-testing = { workspace = true, optional = true } +nym-group-contract-common = { workspace = true, optional = true } cosmwasm-schema = { workspace = true, optional = true } cosmwasm-std = { workspace = true } @@ -30,15 +30,15 @@ cw2 = { workspace = true } cw4 = { workspace = true } thiserror = { workspace = true } -cw3-flex-multisig = { path = "../multisig/cw3-flex-multisig", features = ["testable-cw3-contract"], optional = true } -cw4-group = { path = "../multisig/cw4-group", features = ["testable-cw4-contract"], optional = true } +cw3-flex-multisig = { workspace = true, features = ["testable-cw3-contract"], optional = true } +cw4-group = { workspace = true, features = ["testable-cw4-contract"], optional = true } [dev-dependencies] anyhow = { workspace = true } -easy-addr = { path = "../../common/cosmwasm-smart-contracts/easy_addr" } -nym-group-contract-common = { path = "../../common/cosmwasm-smart-contracts/group-contract" } +easy-addr = { workspace = true } +nym-group-contract-common = { workspace = true } cw-multi-test = { workspace = true } -cw4-group = { path = "../multisig/cw4-group" } +cw4-group = { workspace = true } [features] schema-gen = ["nym-coconut-dkg-common/schema", "cosmwasm-schema"] diff --git a/contracts/ecash/Cargo.toml b/contracts/ecash/Cargo.toml index a80b0fb241..4badeb4460 100644 --- a/contracts/ecash/Cargo.toml +++ b/contracts/ecash/Cargo.toml @@ -30,15 +30,15 @@ cw4 = { workspace = true } cw-utils = { workspace = true } semver = { workspace = true, default-features = false } -nym-ecash-contract-common = { path = "../../common/cosmwasm-smart-contracts/ecash-contract" } -nym-contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts-common" } -nym-multisig-contract-common = { path = "../../common/cosmwasm-smart-contracts/multisig-contract" } -nym-network-defaults = { path = "../../common/network-defaults", default-features = false } +nym-ecash-contract-common = { workspace = true } +nym-contracts-common = { workspace = true } +nym-multisig-contract-common = { workspace = true } +nym-network-defaults = { workspace = true, default-features = false } [dev-dependencies] anyhow = { workspace = true } sylvia = { workspace = true, features = ["mt"] } -nym-crypto = { path = "../../common/crypto", features = ["rand", "asymmetric"] } +nym-crypto = { workspace = true, features = ["rand", "asymmetric"] } rand_chacha = "0.3" cw-multi-test = { workspace = true } diff --git a/contracts/mixnet-vesting-integration-tests/Cargo.toml b/contracts/mixnet-vesting-integration-tests/Cargo.toml index c3fc07974f..a1f6aa3104 100644 --- a/contracts/mixnet-vesting-integration-tests/Cargo.toml +++ b/contracts/mixnet-vesting-integration-tests/Cargo.toml @@ -16,15 +16,15 @@ cosmwasm-std = { workspace = true } cw-multi-test = { workspace = true } # contracts dependencies -nym-mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-contract" } -nym-vesting-contract-common = { path = "../../common/cosmwasm-smart-contracts/vesting-contract" } -nym-contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts-common" } +nym-mixnet-contract-common = { workspace = true } +nym-vesting-contract-common = { workspace = true } +nym-contracts-common = { workspace = true } -nym-mixnet-contract = { path = "../mixnet" } -nym-vesting-contract = { path = "../vesting" } +nym-mixnet-contract = { workspace = true } +nym-vesting-contract = { workspace = true } # other local dependencies -nym-crypto = { path = "../../common/crypto", features = ["asymmetric", "rand"] } +nym-crypto = { workspace = true, features = ["asymmetric", "rand"] } # external dependencies rand_chacha = "0.3" diff --git a/contracts/mixnet/Cargo.toml b/contracts/mixnet/Cargo.toml index ae2ad99d36..e5c7b47719 100644 --- a/contracts/mixnet/Cargo.toml +++ b/contracts/mixnet/Cargo.toml @@ -26,10 +26,10 @@ name = "mixnet_contract" crate-type = ["cdylib", "rlib"] [dependencies] -mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-contract", package = "nym-mixnet-contract-common" } -vesting-contract-common = { path = "../../common/cosmwasm-smart-contracts/vesting-contract", package = "nym-vesting-contract-common" } -nym-contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts-common" } -nym-contracts-common-testing = { path = "../../common/cosmwasm-smart-contracts/contracts-common-testing", optional = true } +mixnet-contract-common = { workspace = true } +vesting-contract-common = { workspace = true } +nym-contracts-common = { workspace = true } +nym-contracts-common-testing = { workspace = true, optional = true } cosmwasm-schema = { workspace = true, optional = true } cosmwasm-std = { workspace = true } @@ -46,12 +46,12 @@ semver = { workspace = true } anyhow.workspace = true rand_chacha = { workspace = true } rand = { workspace = true } -nym-crypto = { path = "../../common/crypto", features = ["asymmetric", "rand"] } -easy-addr = { path = "../../common/cosmwasm-smart-contracts/easy_addr" } +nym-crypto = { workspace = true, features = ["asymmetric", "rand"] } +easy-addr = { workspace = true } # activate the `testable-mixnet-contract` in tests (weird workaround, but it does the trick) -nym-mixnet-contract = { path = ".", features = ["testable-mixnet-contract"] } -nym-contracts-common-testing = { path = "../../common/cosmwasm-smart-contracts/contracts-common-testing" } +nym-mixnet-contract = { workspace = true, features = ["testable-mixnet-contract"] } +nym-contracts-common-testing = { workspace = true } [features] default = [] @@ -60,4 +60,4 @@ testable-mixnet-contract = ["nym-contracts-common-testing"] schema-gen = ["mixnet-contract-common/schema", "cosmwasm-schema"] [lints] -workspace = true \ No newline at end of file +workspace = true diff --git a/contracts/multisig/cw3-flex-multisig/Cargo.toml b/contracts/multisig/cw3-flex-multisig/Cargo.toml index 695ec7bcac..e016a5f4c8 100644 --- a/contracts/multisig/cw3-flex-multisig/Cargo.toml +++ b/contracts/multisig/cw3-flex-multisig/Cargo.toml @@ -27,18 +27,18 @@ cw-storage-plus = { workspace = true } cosmwasm-schema = { workspace = true, optional = true } cosmwasm-std = { workspace = true } -nym-group-contract-common = { path = "../../../common/cosmwasm-smart-contracts/group-contract" } -nym-multisig-contract-common = { path = "../../../common/cosmwasm-smart-contracts/multisig-contract" } -nym-contracts-common = { path = "../../../common/cosmwasm-smart-contracts/contracts-common" } -nym-contracts-common-testing = { path = "../../../common/cosmwasm-smart-contracts/contracts-common-testing", optional = true } +nym-group-contract-common = { workspace = true } +nym-multisig-contract-common = { workspace = true } +nym-contracts-common = { workspace = true } +nym-contracts-common-testing = { workspace = true, optional = true } [dev-dependencies] -easy-addr = { path = "../../../common/cosmwasm-smart-contracts/easy_addr" } -cw4-group = { path = "../cw4-group" } +easy-addr = { workspace = true } +cw4-group = { workspace = true } cw-multi-test = { workspace = true } cw20-base = { workspace = true } [features] # use library feature to disable all instantiate/execute/query exports library = [] -testable-cw3-contract = ["nym-contracts-common-testing"] \ No newline at end of file +testable-cw3-contract = ["nym-contracts-common-testing"] diff --git a/contracts/multisig/cw4-group/Cargo.toml b/contracts/multisig/cw4-group/Cargo.toml index f0f3711631..b8334b756e 100644 --- a/contracts/multisig/cw4-group/Cargo.toml +++ b/contracts/multisig/cw4-group/Cargo.toml @@ -23,9 +23,9 @@ name = "schema" crate-type = ["cdylib", "rlib"] [dependencies] -nym-group-contract-common = { path = "../../../common/cosmwasm-smart-contracts/group-contract" } -nym-contracts-common = { path = "../../../common/cosmwasm-smart-contracts/contracts-common" } -nym-contracts-common-testing = { path = "../../../common/cosmwasm-smart-contracts/contracts-common-testing", optional = true } +nym-group-contract-common = { workspace = true } +nym-contracts-common = { workspace = true } +nym-contracts-common-testing = { workspace = true, optional = true } cw-utils = { workspace = true } cw2 = { workspace = true } @@ -39,9 +39,9 @@ serde = { workspace = true, default-features = false, features = ["derive"] } thiserror = { workspace = true } [dev-dependencies] -easy-addr = { path = "../../../common/cosmwasm-smart-contracts/easy_addr" } +easy-addr = { workspace = true } [features] # use library feature to disable all instantiate/execute/query exports library = [] -testable-cw4-contract = ["nym-contracts-common-testing"] \ No newline at end of file +testable-cw4-contract = ["nym-contracts-common-testing"] diff --git a/contracts/nym-pool/Cargo.toml b/contracts/nym-pool/Cargo.toml index 5bd54bb3bc..f3fc80740e 100644 --- a/contracts/nym-pool/Cargo.toml +++ b/contracts/nym-pool/Cargo.toml @@ -22,12 +22,12 @@ cw-controllers = { workspace = true } cosmwasm-schema = { workspace = true, optional = true } -nym-contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts-common" } -nym-pool-contract-common = { path = "../../common/cosmwasm-smart-contracts/nym-pool-contract" } +nym-contracts-common = { workspace = true } +nym-pool-contract-common = { workspace = true } [dev-dependencies] anyhow = { workspace = true } -nym-contracts-common-testing = { path = "../../common/cosmwasm-smart-contracts/contracts-common-testing" } +nym-contracts-common-testing = { workspace = true } [features] schema-gen = ["nym-pool-contract-common/schema", "cosmwasm-schema"] diff --git a/contracts/performance/Cargo.toml b/contracts/performance/Cargo.toml index abeef17f40..72086ad419 100644 --- a/contracts/performance/Cargo.toml +++ b/contracts/performance/Cargo.toml @@ -25,18 +25,18 @@ serde = { workspace = true } cosmwasm-schema = { workspace = true, optional = true } -nym-contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts-common" } -nym-performance-contract-common = { path = "../../common/cosmwasm-smart-contracts/nym-performance-contract" } -nym-mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-contract" } +nym-contracts-common = { workspace = true } +nym-performance-contract-common = { workspace = true } +nym-mixnet-contract-common = { workspace = true } [dev-dependencies] anyhow = { workspace = true } -nym-contracts-common-testing = { path = "../../common/cosmwasm-smart-contracts/contracts-common-testing" } -nym-mixnet-contract = { path = "../mixnet", features = ["testable-mixnet-contract"] } -nym-crypto = { path = "../../common/crypto", features = ["asymmetric", "rand"] } +nym-contracts-common-testing = { workspace = true } +nym-mixnet-contract = { workspace = true, features = ["testable-mixnet-contract"] } +nym-crypto = { workspace = true, features = ["asymmetric", "rand"] } [features] schema-gen = ["nym-performance-contract-common/schema", "cosmwasm-schema"] [lints] -workspace = true \ No newline at end of file +workspace = true diff --git a/contracts/vesting/Cargo.toml b/contracts/vesting/Cargo.toml index 2845ee15a1..bc227c5806 100644 --- a/contracts/vesting/Cargo.toml +++ b/contracts/vesting/Cargo.toml @@ -24,9 +24,9 @@ name = "vesting_contract" crate-type = ["cdylib", "rlib"] [dependencies] -mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-contract", package = "nym-mixnet-contract-common", version = "0.6.0" } -contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts-common", package = "nym-contracts-common", version = "0.5.0" } -vesting-contract-common = { path = "../../common/cosmwasm-smart-contracts/vesting-contract", package = "nym-vesting-contract-common", version = "0.7.0" } +mixnet-contract-common = { workspace = true } +contracts-common = { workspace = true } +vesting-contract-common = { workspace = true } cosmwasm-schema = { workspace = true, optional = true } cosmwasm-std = { workspace = true } diff --git a/crate-publishing.md b/crate-publishing.md new file mode 100644 index 0000000000..780194e8b7 --- /dev/null +++ b/crate-publishing.md @@ -0,0 +1,22 @@ +# Publishing workspace dependencies +## Rationale re: versioning +We publish the majority of our workspace dependencies (essentially everything in the repo aside from binaries, smart contracts, and some internal tooling) to [crates.io](https://crates.io). + +In order to make this easy to maintain, the versions of these workspace dependencies and the `nym-sdk` crate are kept in sync. + +This version is defined in the `[workspace.package]` section of the root monorepo `Cargo.toml` file. Each of the workspace dependencies have their paths and versions (this has to be individually defined at the moment per-dependency, **this version needs to stay the same as the `workspace.package` version**) defined in the `[workspace.dependencies]` section of the root monorepo `Cargo.toml` file. + +## When Developing +If you add a workspace dependency to the SDK when developing, make sure to add this to the workspace dependencies in the root monorepo `Cargo.toml`. + +## Publishing +``` +# List crates to publish +cargo workspaces list + +# Dry run - check for compilation or other problems +cargo workspaces publish --no-git-commit --dry-run + +# Publish +TODO +``` diff --git a/documentation/autodoc/Cargo.toml b/documentation/autodoc/Cargo.toml index 9fae8c5434..1905285f99 100644 --- a/documentation/autodoc/Cargo.toml +++ b/documentation/autodoc/Cargo.toml @@ -7,6 +7,7 @@ homepage.workspace = true documentation.workspace = true edition.workspace = true license.workspace = true +publish = false [dependencies] env_logger = { workspace = true } diff --git a/gateway/Cargo.toml b/gateway/Cargo.toml index c802e5c1b8..8f729975e5 100644 --- a/gateway/Cargo.toml +++ b/gateway/Cargo.toml @@ -12,6 +12,7 @@ authors = [ description = "Implementation of the Nym Mixnet Gateway" edition = "2021" rust-version = "1.85" +publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -47,36 +48,36 @@ zeroize = { workspace = true } # internal -nym-credentials = { path = "../common/credentials" } -nym-credentials-interface = { path = "../common/credentials-interface" } -nym-credential-verification = { path = "../common/credential-verification" } -nym-crypto = { path = "../common/crypto", features = ["sphinx"] } -nym-config = { path = "../common/config" } -nym-gateway-storage = { path = "../common/gateway-storage" } -nym-gateway-stats-storage = { path = "../common/gateway-stats-storage" } -nym-gateway-requests = { path = "../common/gateway-requests" } -nym-mixnet-client = { path = "../common/client-libs/mixnet-client" } -nym-network-defaults = { path = "../common/network-defaults" } +nym-credentials = { workspace = true } +nym-credentials-interface = { workspace = true } +nym-credential-verification = { workspace = true } +nym-crypto = { workspace = true, features = ["sphinx"] } +nym-config = { workspace = true } +nym-gateway-storage = { workspace = true } +nym-gateway-stats-storage = { workspace = true } +nym-gateway-requests = { workspace = true } +nym-mixnet-client = { workspace = true } +nym-network-defaults = { workspace = true } nym-network-requester = { path = "../service-providers/network-requester" } -nym-sdk = { path = "../sdk/rust/nym-sdk" } -nym-sphinx = { path = "../common/nymsphinx" } -nym-statistics-common = { path = "../common/statistics" } -nym-task = { path = "../common/task" } -nym-topology = { path = "../common/topology" } -nym-validator-client = { path = "../common/client-libs/validator-client" } +nym-sdk = { workspace = true } +nym-sphinx = { workspace = true } +nym-statistics-common = { workspace = true } +nym-task = { workspace = true } +nym-topology = { workspace = true } +nym-validator-client = { workspace = true } nym-ip-packet-router = { path = "../service-providers/ip-packet-router" } -nym-node-metrics = { path = "../nym-node/nym-node-metrics" } -nym-upgrade-mode-check = { path = "../common/upgrade-mode-check" } -nym-metrics = { path = "../common/nym-metrics" } +nym-node-metrics = { workspace = true } +nym-upgrade-mode-check = { workspace = true } +nym-metrics = { workspace = true } -nym-wireguard = { path = "../common/wireguard" } -nym-wireguard-private-metadata-server = { path = "../common/wireguard-private-metadata/server" } -nym-wireguard-types = { path = "../common/wireguard-types", default-features = false } +nym-wireguard = { workspace = true } +nym-wireguard-private-metadata-server = { workspace = true } +nym-wireguard-types = { workspace = true } -nym-authenticator-requests = { path = "../common/authenticator-requests" } -nym-client-core = { path = "../common/client-core", features = ["cli"] } -nym-id = { path = "../common/nym-id" } -nym-service-provider-requests-common = { path = "../common/service-provider-requests-common" } +nym-authenticator-requests = { workspace = true } +nym-client-core = { workspace = true, features = ["cli"] } +nym-id = { workspace = true } +nym-service-provider-requests-common = { workspace = true } # LP dependencies nym-lp = { path = "../common/nym-lp" } @@ -88,10 +89,10 @@ bytes = { workspace = true } defguard_wireguard_rs = { workspace = true } [dev-dependencies] -nym-gateway-storage = { path = "../common/gateway-storage", features = ["mock"] } -nym-wireguard = { path = "../common/wireguard", features = ["mock"] } +nym-gateway-storage = { workspace = true, features = ["mock"] } +nym-wireguard = { workspace = true, features = ["mock"] } mock_instant = "0.6.0" time = { workspace = true } [lints] -workspace = true \ No newline at end of file +workspace = true diff --git a/integration-tests/Cargo.toml b/integration-tests/Cargo.toml index 2023980518..f7d2f0897c 100644 --- a/integration-tests/Cargo.toml +++ b/integration-tests/Cargo.toml @@ -9,6 +9,7 @@ edition.workspace = true license.workspace = true rust-version.workspace = true readme.workspace = true +publish = false [dependencies] anyhow = { workspace = true } diff --git a/nym-api/Cargo.toml b/nym-api/Cargo.toml index c7037d32f9..96715767c4 100644 --- a/nym-api/Cargo.toml +++ b/nym-api/Cargo.toml @@ -4,10 +4,11 @@ [package] name = "nym-api" license = "GPL-3.0" -version = "1.1.71" +version.workspace = true authors.workspace = true edition = "2021" rust-version.workspace = true +publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -67,43 +68,43 @@ utoipa-swagger-ui = { workspace = true, features = ["axum"] } tracing = { workspace = true } ## internal -nym-cache = { path = "../common/nym-cache" } -nym-bandwidth-controller = { path = "../common/bandwidth-controller" } -nym-ecash-contract-common = { path = "../common/cosmwasm-smart-contracts/ecash-contract" } -nym-ecash-time = { path = "../common/ecash-time", features = ["expiration"] } -nym-coconut-dkg-common = { path = "../common/cosmwasm-smart-contracts/coconut-dkg" } -nym-compact-ecash = { path = "../common/nym_offline_compact_ecash" } -nym-credentials-interface = { path = "../common/credentials-interface" } -nym-config = { path = "../common/config" } +nym-cache = { workspace = true } +nym-bandwidth-controller = { workspace = true } +nym-ecash-contract-common = { workspace = true } +nym-ecash-time = { workspace = true, features = ["expiration"] } +nym-coconut-dkg-common = { workspace = true } +nym-compact-ecash = { workspace = true } +nym-credentials-interface = { workspace = true } +nym-config = { workspace = true } cosmwasm-std = { workspace = true } -nym-credential-storage = { path = "../common/credential-storage", features = [ +nym-credential-storage = { workspace = true, features = [ "persistent-storage", ] } -nym-credentials = { path = "../common/credentials" } -nym-crypto = { path = "../common/crypto" } +nym-credentials = { workspace = true } +nym-crypto = { workspace = true } cw2 = { workspace = true } cw3 = { workspace = true } cw4 = { workspace = true } -nym-dkg = { path = "../common/dkg", features = ["cw-types"] } -nym-gateway-client = { path = "../common/client-libs/gateway-client" } -nym-mixnet-contract-common = { path = "../common/cosmwasm-smart-contracts/mixnet-contract", features = ["utoipa"] } -nym-contracts-common = { path = "../common/cosmwasm-smart-contracts/contracts-common", features = ["naive_float", "utoipa"] } -nym-sphinx = { path = "../common/nymsphinx" } -nym-pemstore = { path = "../common/pemstore" } -nym-task = { path = "../common/task" } -nym-topology = { path = "../common/topology" } -nym-api-requests = { path = "nym-api-requests" } -nym-validator-client = { path = "../common/client-libs/validator-client" } -nym-http-api-client = { path = "../common/http-api-client" } -nym-bin-common = { path = "../common/bin-common", features = ["output_format", "openapi", "basic_tracing"] } -nym-node-tester-utils = { path = "../common/node-tester-utils" } -nym-node-requests = { path = "../nym-node/nym-node-requests" } -nym-types = { path = "../common/types" } -nym-http-api-common = { path = "../common/http-api-common", features = ["utoipa", "output", "middleware"] } -nym-serde-helpers = { path = "../common/serde-helpers", features = ["date"] } -nym-ticketbooks-merkle = { path = "../common/ticketbooks-merkle" } -nym-statistics-common = { path = "../common/statistics" } -nym-ecash-signer-check = { path = "../common/ecash-signer-check" } +nym-dkg = { workspace = true, features = ["cw-types"] } +nym-gateway-client = { workspace = true } +nym-mixnet-contract-common = { workspace = true, features = ["utoipa"] } +nym-contracts-common = { workspace = true, features = ["naive_float", "utoipa"] } +nym-sphinx = { workspace = true } +nym-pemstore = { workspace = true } +nym-task = { workspace = true } +nym-topology = { workspace = true } +nym-api-requests = { workspace = true } +nym-validator-client = { workspace = true } +nym-http-api-client = { workspace = true } +nym-bin-common = { workspace = true, features = ["output_format", "openapi", "basic_tracing"] } +nym-node-tester-utils = { workspace = true } +nym-node-requests = { workspace = true, features = ["client"] } +nym-types = { workspace = true } +nym-http-api-common = { workspace = true, features = ["utoipa", "output", "middleware"] } +nym-serde-helpers = { workspace = true, features = ["date"] } +nym-ticketbooks-merkle = { workspace = true } +nym-statistics-common = { workspace = true } +nym-ecash-signer-check = { workspace = true } [features] diff --git a/nym-api/nym-api-requests/Cargo.toml b/nym-api/nym-api-requests/Cargo.toml index 267cb731f7..0a683ef8b8 100644 --- a/nym-api/nym-api-requests/Cargo.toml +++ b/nym-api/nym-api-requests/Cargo.toml @@ -1,8 +1,12 @@ [package] name = "nym-api-requests" -version = "0.1.0" +version.workspace = true edition = "2021" license.workspace = true +description = "Nym API request types and functions" +repository.workspace = true +homepage.workspace = true +documentation.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -27,26 +31,26 @@ tracing = { workspace = true } # for serde on secp256k1 signatures ecdsa = { workspace = true, features = ["serde"] } -nym-serde-helpers = { path = "../../common/serde-helpers", features = ["bs58", "base64", "date"] } -nym-credentials-interface = { path = "../../common/credentials-interface" } -nym-crypto = { path = "../../common/crypto", features = ["serde", "asymmetric"] } +nym-serde-helpers = { workspace = true, features = ["bs58", "base64", "date"] } +nym-credentials-interface = { workspace = true } +nym-crypto = { workspace = true, features = ["serde", "asymmetric"] } -nym-config = { path = "../../common/config" } -nym-ecash-time = { path = "../../common/ecash-time" } -nym-compact-ecash = { path = "../../common/nym_offline_compact_ecash" } -nym-contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts-common", features = ["naive_float"] } -nym-mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-contract", features = ["utoipa"] } -nym-coconut-dkg-common = { path = "../../common/cosmwasm-smart-contracts/coconut-dkg" } -nym-node-requests = { path = "../../nym-node/nym-node-requests", default-features = false, features = ["openapi"] } -nym-noise-keys = { path = "../../common/nymnoise/keys" } -nym-network-defaults = { path = "../../common/network-defaults" } -nym-ticketbooks-merkle = { path = "../../common/ticketbooks-merkle" } -nym-ecash-signer-check-types = { path = "../../common/ecash-signer-check-types" } +nym-config = { workspace = true } +nym-ecash-time = { workspace = true } +nym-compact-ecash = { workspace = true } +nym-contracts-common = { workspace = true , features = ["naive_float"] } +nym-mixnet-contract-common = { workspace = true , features = ["utoipa"] } +nym-coconut-dkg-common = { workspace = true } +nym-node-requests = { workspace = true, default-features = false, features = ["openapi"] } +nym-noise-keys = { workspace = true } +nym-network-defaults = { workspace = true } +nym-ticketbooks-merkle = { workspace = true } +nym-ecash-signer-check-types = { workspace = true } [dev-dependencies] rand_chacha = { workspace = true } -nym-crypto = { path = "../../common/crypto", features = ["rand"] } +nym-crypto = { workspace = true, features = ["rand"] } [features] diff --git a/nym-authenticator-client/Cargo.toml b/nym-authenticator-client/Cargo.toml index 770aff710d..b1793b6cbf 100644 --- a/nym-authenticator-client/Cargo.toml +++ b/nym-authenticator-client/Cargo.toml @@ -1,12 +1,13 @@ [package] name = "nym-authenticator-client" -version = "0.1.0" +version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true documentation.workspace = true edition.workspace = true license.workspace = true +publish = false [lints] workspace = true @@ -20,12 +21,12 @@ tokio-util.workspace = true tokio.workspace = true tracing.workspace = true -nym-authenticator-requests = { path = "../common/authenticator-requests" } -nym-bandwidth-controller = { path = "../common/bandwidth-controller" } -nym-credentials-interface = { path = "../common/credentials-interface" } -nym-crypto = { path = "../common/crypto" } -nym-registration-common = { path = "../common/registration" } -nym-sdk = { path = "../sdk/rust/nym-sdk" } -nym-service-provider-requests-common = { path = "../common/service-provider-requests-common" } -nym-validator-client = { path = "../common/client-libs/validator-client" } -nym-wireguard-types = { path = "../common/wireguard-types" } +nym-authenticator-requests = { workspace = true } +nym-bandwidth-controller = { workspace = true } +nym-credentials-interface = { workspace = true } +nym-crypto = { workspace = true } +nym-registration-common = { workspace = true } +nym-sdk = { workspace = true } +nym-service-provider-requests-common = { workspace = true } +nym-validator-client = { workspace = true } +nym-wireguard-types = { workspace = true } diff --git a/nym-browser-extension/storage/Cargo.toml b/nym-browser-extension/storage/Cargo.toml index 61a34e51e7..e25c1b107f 100644 --- a/nym-browser-extension/storage/Cargo.toml +++ b/nym-browser-extension/storage/Cargo.toml @@ -6,6 +6,7 @@ license = "Apache-2.0" repository = "https://github.com/nymtech/nym" description = "WebAssembly-based secure storage for browser extension mnemonics" authors = ["Nym Technologies SA "] +publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -13,20 +14,20 @@ authors = ["Nym Technologies SA "] crate-type = ["cdylib", "rlib"] [dependencies] -bip39 = { workspace = true } -zeroize = { workspace = true } +bip39 = { workspace = true } +zeroize = { workspace = true } -js-sys = { workspace = true } -wasm-bindgen = { workspace = true } -wasm-bindgen-futures = { workspace = true } +js-sys = { workspace = true } +wasm-bindgen = { workspace = true } +wasm-bindgen-futures = { workspace = true } serde-wasm-bindgen = { workspace = true } -thiserror = { workspace = true } +thiserror = { workspace = true } -console_error_panic_hook = { workspace = true, optional = true } +console_error_panic_hook = { workspace = true, optional = true } -wasm-utils = { path = "../../common/wasm/utils" } -wasm-storage = { path = "../../common/wasm/storage" } +nym-wasm-utils = { workspace = true } +nym-wasm-storage = { workspace = true } #[package.metadata.wasm-pack.profile.release] diff --git a/nym-browser-extension/storage/src/error.rs b/nym-browser-extension/storage/src/error.rs index e05d0d5b9a..88f3a60582 100644 --- a/nym-browser-extension/storage/src/error.rs +++ b/nym-browser-extension/storage/src/error.rs @@ -1,9 +1,9 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use nym_wasm_storage::error::StorageError; +use nym_wasm_utils::wasm_error; use thiserror::Error; -use wasm_storage::error::StorageError; -use wasm_utils::wasm_error; #[derive(Debug, Error)] pub enum ExtensionStorageError { diff --git a/nym-browser-extension/storage/src/lib.rs b/nym-browser-extension/storage/src/lib.rs index d8bd0d694e..94e5809cc8 100644 --- a/nym-browser-extension/storage/src/lib.rs +++ b/nym-browser-extension/storage/src/lib.rs @@ -14,4 +14,4 @@ pub use error::ExtensionStorageError; pub use storage::ExtensionStorage; #[cfg(target_arch = "wasm32")] -pub use wasm_utils::set_panic_hook; +pub use nym_wasm_utils::set_panic_hook; diff --git a/nym-browser-extension/storage/src/storage.rs b/nym-browser-extension/storage/src/storage.rs index ba484f46aa..88c93eaa59 100644 --- a/nym-browser-extension/storage/src/storage.rs +++ b/nym-browser-extension/storage/src/storage.rs @@ -9,13 +9,13 @@ use crate::ExtensionStorageError; use js_sys::Promise; +use nym_wasm_storage::RawDbResult; +use nym_wasm_storage::{Build, Database, VersionChangeEvent, WasmStorage}; +use nym_wasm_utils::check_promise_result; +use nym_wasm_utils::error::{PromisableResult, PromisableResultError}; use std::sync::Arc; use wasm_bindgen::prelude::*; use wasm_bindgen_futures::future_to_promise; -use wasm_storage::RawDbResult; -use wasm_storage::{Build, Database, VersionChangeEvent, WasmStorage}; -use wasm_utils::check_promise_result; -use wasm_utils::error::{PromisableResult, PromisableResultError}; use zeroize::Zeroizing; const STORAGE_NAME: &str = "nym-extension-storage"; diff --git a/nym-credential-proxy/nym-credential-proxy-requests/Cargo.toml b/nym-credential-proxy/nym-credential-proxy-requests/Cargo.toml index 76f91373b8..dbe84bf6f1 100644 --- a/nym-credential-proxy/nym-credential-proxy-requests/Cargo.toml +++ b/nym-credential-proxy/nym-credential-proxy-requests/Cargo.toml @@ -1,12 +1,13 @@ [package] name = "nym-credential-proxy-requests" -version = "0.1.0" +version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true documentation.workspace = true edition.workspace = true license.workspace = true +description = "Request and response definitions for interacting with the Nym Credential Proxy" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -25,12 +26,12 @@ wasm-bindgen = { workspace = true, optional = true } ## openapi: utoipa = { workspace = true, optional = true, features = ["uuid"] } -nym-credentials = { path = "../../common/credentials" } -nym-credentials-interface = { path = "../../common/credentials-interface" } -nym-http-api-common = { path = "../../common/http-api-common", optional = true } -nym-http-api-client = { path = "../../common/http-api-client" } -nym-serde-helpers = { path = "../../common/serde-helpers", features = ["bs58"] } -nym-upgrade-mode-check = { path = "../../common/upgrade-mode-check" } +nym-credentials = { workspace = true } +nym-credentials-interface = { workspace = true } +nym-http-api-common = { workspace = true, optional = true } +nym-http-api-client = { workspace = true } +nym-serde-helpers = { workspace = true, features = ["bs58"] } +nym-upgrade-mode-check = { workspace = true } [target."cfg(target_arch = \"wasm32\")".dependencies.wasmtimer] workspace = true @@ -43,4 +44,4 @@ openapi = ["utoipa", "nym-http-api-common/utoipa", "nym-upgrade-mode-check/opena tsify = ["dep:tsify", "wasm-bindgen"] [lints] -workspace = true \ No newline at end of file +workspace = true diff --git a/nym-credential-proxy/nym-credential-proxy/Cargo.toml b/nym-credential-proxy/nym-credential-proxy/Cargo.toml index c727488d37..3d81b37f0f 100644 --- a/nym-credential-proxy/nym-credential-proxy/Cargo.toml +++ b/nym-credential-proxy/nym-credential-proxy/Cargo.toml @@ -8,6 +8,7 @@ documentation.workspace = true edition.workspace = true license.workspace = true rust-version.workspace = true +publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -45,35 +46,35 @@ utoipa = { workspace = true, features = ["axum_extras", "time"] } utoipa-swagger-ui = { workspace = true, features = ["axum"] } zeroize.workspace = true -nym-bin-common = { path = "../../common/bin-common", features = [ +nym-bin-common = { workspace = true, features = [ "basic_tracing", ] } -nym-compact-ecash = { path = "../../common/nym_offline_compact_ecash" } -nym-config = { path = "../../common/config" } -nym-crypto = { path = "../../common/crypto", features = [ +nym-compact-ecash = { workspace = true } +nym-config = { workspace = true } +nym-crypto = { workspace = true, features = [ "asymmetric", "rand", "serde", ] } -nym-credentials = { path = "../../common/credentials" } -nym-credentials-interface = { path = "../../common/credentials-interface" } -nym-ecash-contract-common = { path = "../../common/cosmwasm-smart-contracts/ecash-contract" } -nym-http-api-common = { path = "../../common/http-api-common", features = [ +nym-credentials = { workspace = true } +nym-credentials-interface = { workspace = true } +nym-ecash-contract-common = { workspace = true } +nym-http-api-common = { workspace = true, features = [ "utoipa", "middleware", ] } -nym-http-api-client = { path = "../../common/http-api-client", default-features = false } -nym-validator-client = { path = "../../common/client-libs/validator-client" } -nym-network-defaults = { path = "../../common/network-defaults" } +nym-http-api-client = { workspace = true } +nym-validator-client = { workspace = true } +nym-network-defaults = { workspace = true } -nym-credential-proxy-requests = { path = "../nym-credential-proxy-requests", features = [ +nym-credential-proxy-requests = { workspace = true, features = [ "openapi", ] } -nym-upgrade-mode-check = { path = "../../common/upgrade-mode-check" } -nym-ecash-signer-check = { path = "../../common/ecash-signer-check" } -nym-pemstore = { path = "../../common/pemstore" } +nym-upgrade-mode-check = { workspace = true } +nym-ecash-signer-check = { workspace = true } +nym-pemstore = { workspace = true } -nym-credential-proxy-lib = { path = "../../common/credential-proxy" } +nym-credential-proxy-lib = { workspace = true } [dev-dependencies] tempfile = { workspace = true } diff --git a/nym-credential-proxy/vpn-api-lib-wasm/Cargo.toml b/nym-credential-proxy/vpn-api-lib-wasm/Cargo.toml index 8dc406eaf5..52baabd2cb 100644 --- a/nym-credential-proxy/vpn-api-lib-wasm/Cargo.toml +++ b/nym-credential-proxy/vpn-api-lib-wasm/Cargo.toml @@ -1,12 +1,13 @@ [package] name = "nym-vpn-api-lib-wasm" -version = "0.1.0" +version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true documentation.workspace = true edition.workspace = true license.workspace = true +publish = false [lib] crate-type = ["cdylib", "rlib"] @@ -27,12 +28,11 @@ tsify = { workspace = true, features = ["js"] } serde-wasm-bindgen = { workspace = true } # NYM: -nym-bin-common = { path = "../../common/bin-common" } -nym-crypto = { path = "../../common/crypto", features = ["asymmetric"] } -nym-compact-ecash = { path = "../../common/nym_offline_compact_ecash" } -nym-credentials = { path = "../../common/credentials" } -nym-credentials-interface = { path = "../../common/credentials-interface" } -nym-ecash-time = { path = "../../common/ecash-time", features = ["expiration"] } -nym-credential-proxy-requests = { path = "../nym-credential-proxy-requests", default-features = false, features = ["tsify"] } -wasm-utils = { path = "../../common/wasm/utils" } - +nym-bin-common = { workspace = true } +nym-crypto = { workspace = true, features = ["asymmetric"] } +nym-compact-ecash = { workspace = true } +nym-credentials = { workspace = true } +nym-credentials-interface = { workspace = true } +nym-ecash-time = { workspace = true, features = ["expiration"] } +nym-credential-proxy-requests = { workspace = true, features = ["tsify"] } +nym-wasm-utils = { workspace = true } diff --git a/nym-credential-proxy/vpn-api-lib-wasm/src/error.rs b/nym-credential-proxy/vpn-api-lib-wasm/src/error.rs index 7322254064..5e1255bdbc 100644 --- a/nym-credential-proxy/vpn-api-lib-wasm/src/error.rs +++ b/nym-credential-proxy/vpn-api-lib-wasm/src/error.rs @@ -1,9 +1,9 @@ // Copyright 2024 Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only +use nym_wasm_utils::wasm_error; use serde_wasm_bindgen::Error; use thiserror::Error; -use wasm_utils::wasm_error; #[derive(Debug, Error)] pub enum VpnApiLibError { diff --git a/nym-credential-proxy/vpn-api-lib-wasm/src/lib.rs b/nym-credential-proxy/vpn-api-lib-wasm/src/lib.rs index abdff1bfe8..00ca53805a 100644 --- a/nym-credential-proxy/vpn-api-lib-wasm/src/lib.rs +++ b/nym-credential-proxy/vpn-api-lib-wasm/src/lib.rs @@ -19,12 +19,12 @@ use nym_credentials::{ use nym_credentials_interface::TicketType; use nym_crypto::asymmetric::ed25519; use nym_ecash_time::{EcashTime, ecash_default_expiration_date}; +use nym_wasm_utils::console_error; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use time::Date; use tsify::Tsify; use wasm_bindgen::prelude::*; -use wasm_utils::console_error; use zeroize::Zeroizing; pub mod error; @@ -267,11 +267,11 @@ pub struct FullSerialisedNymIssuedTicketbook { #[wasm_bindgen(start)] pub fn main() { - wasm_utils::console_log!("[rust main]: rust module loaded"); - wasm_utils::console_log!( + nym_wasm_utils::console_log!("[rust main]: rust module loaded"); + nym_wasm_utils::console_log!( "vpn-api-lib version used:\n{}", nym_bin_common::bin_info!().pretty_print() ); - wasm_utils::console_log!("[rust main]: setting panic hook"); - wasm_utils::set_panic_hook(); + nym_wasm_utils::console_log!("[rust main]: setting panic hook"); + nym_wasm_utils::set_panic_hook(); } diff --git a/nym-data-observatory/Cargo.toml b/nym-data-observatory/Cargo.toml index d23b40f20d..2eefa881ef 100644 --- a/nym-data-observatory/Cargo.toml +++ b/nym-data-observatory/Cargo.toml @@ -11,6 +11,7 @@ documentation.workspace = true edition.workspace = true license.workspace = true rust-version.workspace = true +publish = false [dependencies] anyhow = { workspace = true } @@ -19,13 +20,13 @@ axum = { workspace = true, features = ["tokio"] } chrono = { workspace = true } clap = { workspace = true, features = ["cargo", "derive", "env"] } cosmrs = { workspace = true } -nym-config = { path = "../common/config" } -nym-bin-common = { path = "../common/bin-common", features = ["output_format"] } -nym-network-defaults = { path = "../common/network-defaults" } -nym-task = { path = "../common/task" } -nym-validator-client = { path = "../common/client-libs/validator-client" } +nym-config = { workspace = true } +nym-bin-common = { workspace = true, features = ["output_format"] } +nym-network-defaults = { workspace = true } +nym-task = { workspace = true } +nym-validator-client = { workspace = true } nyxd-scraper-psql = { path = "../common/nyxd-scraper-psql" } -nyxd-scraper-shared = { path = "../common/nyxd-scraper-shared" } +nyxd-scraper-shared = { workspace = true } reqwest = { workspace = true, features = ["rustls-tls"] } schemars = { workspace = true } serde = { workspace = true, features = ["derive"] } @@ -49,4 +50,4 @@ anyhow = { workspace = true } blake3 = { workspace = true } glob = { workspace = true } tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } -sqlx = { workspace = true, features = ["runtime-tokio-rustls", "postgres"] } \ No newline at end of file +sqlx = { workspace = true, features = ["runtime-tokio-rustls", "postgres"] } diff --git a/nym-gateway-probe/Cargo.toml b/nym-gateway-probe/Cargo.toml index 8a41137fa5..3f8ab3753b 100644 --- a/nym-gateway-probe/Cargo.toml +++ b/nym-gateway-probe/Cargo.toml @@ -7,6 +7,7 @@ homepage.workspace = true documentation.workspace = true edition.workspace = true license.workspace = true +publish = false [lints] workspace = true @@ -41,22 +42,23 @@ x25519-dalek = { workspace = true, features = [ "static_secrets", ] } + nym-api-requests = { path = "../nym-api/nym-api-requests" } -nym-authenticator-requests = { path = "../common/authenticator-requests" } -nym-bandwidth-controller = { path = "../common/bandwidth-controller" } -nym-bin-common = { path = "../common/bin-common" } -nym-client-core = { path = "../common/client-core" } -nym-crypto = { path = "../common/crypto" } -nym-config = { path = "../common/config" } +nym-authenticator-requests = { workspace = true } +nym-bandwidth-controller = { workspace = true } +nym-bin-common = { workspace = true } +nym-client-core = { workspace = true } +nym-crypto = { workspace = true } +nym-config = { workspace = true } nym-connection-monitor = { path = "../common/nym-connection-monitor" } -nym-credentials-interface = { path = "../common/credentials-interface" } -nym-credential-utils = { path = "../common/credential-utils" } -nym-ip-packet-client = { path = "../nym-ip-packet-client" } -nym-authenticator-client = { path = "../nym-authenticator-client" } -nym-ip-packet-requests = { path = "../common/ip-packet-requests" } -nym-sdk = { path = "../sdk/rust/nym-sdk" } -nym-validator-client = { path = "../common/client-libs/validator-client" } -nym-credentials = { path = "../common/credentials" } +nym-credentials-interface = { workspace = true } +nym-credential-utils = { workspace = true } +nym-ip-packet-client = { workspace = true } +nym-authenticator-client = { workspace = true } +nym-ip-packet-requests = { workspace = true } +nym-sdk = { workspace = true } +nym-validator-client = { workspace = true } +nym-credentials = { workspace = true } nym-http-api-client-macro = { path = "../common/http-api-client-macro" } nym-http-api-client = { path = "../common/http-api-client" } nym-node-status-client = { path = "../nym-node-status-api/nym-node-status-client" } @@ -69,7 +71,7 @@ nym-registration-common = { path = "../common/registration" } time = { workspace = true } # TEMP: REMOVE BEFORE PR -nym-topology = { path = "../common/topology" } +nym-topology = { workspace = true } [build-dependencies] anyhow = { workspace = true } diff --git a/nym-ip-packet-client/Cargo.toml b/nym-ip-packet-client/Cargo.toml index 86b0e21830..e631a759ca 100644 --- a/nym-ip-packet-client/Cargo.toml +++ b/nym-ip-packet-client/Cargo.toml @@ -1,12 +1,13 @@ [package] name = "nym-ip-packet-client" -version = "0.1.0" +version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true documentation.workspace = true edition.workspace = true license.workspace = true +description = "Nym's implementation of a client that sends and receives IP packets" [lints] workspace = true @@ -20,5 +21,5 @@ tokio-util.workspace = true tokio.workspace = true tracing.workspace = true -nym-sdk = { path = "../sdk/rust/nym-sdk" } -nym-ip-packet-requests = { path = "../common/ip-packet-requests" } +nym-sdk = { workspace = true } +nym-ip-packet-requests = { workspace = true } diff --git a/nym-network-monitor/Cargo.toml b/nym-network-monitor/Cargo.toml index a84a5f3c02..5f68dec06e 100644 --- a/nym-network-monitor/Cargo.toml +++ b/nym-network-monitor/Cargo.toml @@ -7,6 +7,7 @@ homepage.workspace = true documentation.workspace = true edition.workspace = true license.workspace = true +publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -30,15 +31,15 @@ utoipa-swagger-ui = { workspace = true, features = ["axum"] } tokio-postgres = { workspace = true } # internal -nym-bin-common = { path = "../common/bin-common", features = ["basic_tracing"] } -nym-client-core = { path = "../common/client-core" } -nym-crypto = { path = "../common/crypto" } -nym-network-defaults = { path = "../common/network-defaults" } -nym-gateway-requests = { path = "../common/gateway-requests" } -nym-sdk = { path = "../sdk/rust/nym-sdk" } -nym-sphinx = { path = "../common/nymsphinx" } -nym-topology = { path = "../common/topology" } -nym-types = { path = "../common/types" } -nym-validator-client = { path = "../common/client-libs/validator-client" } -nym-http-api-client = { path = "../common/http-api-client" } -nym-mixnet-contract-common = { path = "../common/cosmwasm-smart-contracts/mixnet-contract" } +nym-bin-common = { workspace = true, features = ["basic_tracing"] } +nym-client-core = { workspace = true } +nym-crypto = { workspace = true } +nym-network-defaults = { workspace = true } +nym-gateway-requests = { workspace = true } +nym-sdk = { workspace = true } +nym-sphinx = { workspace = true } +nym-topology = { workspace = true } +nym-types = { workspace = true } +nym-validator-client = { workspace = true } +nym-http-api-client = { workspace = true } +nym-mixnet-contract-common = { workspace = true } diff --git a/nym-node-status-api/nym-node-status-agent/Cargo.toml b/nym-node-status-api/nym-node-status-agent/Cargo.toml index b7840cd9a6..f866a4aacb 100644 --- a/nym-node-status-api/nym-node-status-agent/Cargo.toml +++ b/nym-node-status-api/nym-node-status-agent/Cargo.toml @@ -12,13 +12,14 @@ edition.workspace = true license.workspace = true rust-version.workspace = true readme.workspace = true +publish = false [dependencies] anyhow = { workspace = true } clap = { workspace = true, features = ["derive", "env"] } futures = { workspace = true } -nym-bin-common = { path = "../../common/bin-common", features = ["models"] } -nym-crypto = { path = "../../common/crypto", features = ["asymmetric", "rand"] } +nym-bin-common = { workspace = true, features = ["models"] } +nym-crypto = { workspace = true, features = ["asymmetric", "rand"] } nym-node-status-client = { path = "../nym-node-status-client" } rand = { workspace = true } diff --git a/nym-node-status-api/nym-node-status-api/Cargo.toml b/nym-node-status-api/nym-node-status-api/Cargo.toml index df6d5c12cf..28eb90c2c7 100644 --- a/nym-node-status-api/nym-node-status-api/Cargo.toml +++ b/nym-node-status-api/nym-node-status-api/Cargo.toml @@ -11,6 +11,7 @@ documentation.workspace = true edition.workspace = true license.workspace = true rust-version.workspace = true +publish = false [dependencies] ammonia = { workspace = true } @@ -26,22 +27,22 @@ humantime = { workspace = true } itertools = { workspace = true } moka = { workspace = true, features = ["future"] } -nym-credentials = { path = "../../common/credentials" } -nym-credential-proxy-lib = { path = "../../common/credential-proxy" } -nym-contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts-common" } -nym-mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-contract", features = ["utoipa"] } -nym-bin-common = { path = "../../common/bin-common", features = ["models"] } +nym-credentials = { workspace = true } +nym-credential-proxy-lib = { workspace = true } +nym-contracts-common = { workspace = true } +nym-mixnet-contract-common = { workspace = true, features = ["utoipa"] } +nym-bin-common = { workspace = true, features = ["models"] } nym-node-status-client = { path = "../nym-node-status-client" } -nym-crypto = { path = "../../common/crypto", features = ["asymmetric", "serde"] } -nym-http-api-client = { path = "../../common/http-api-client" } -nym-http-api-common = { path = "../../common/http-api-common", features = ["middleware"] } -nym-network-defaults = { path = "../../common/network-defaults" } -nym-serde-helpers = { path = "../../common/serde-helpers" } -nym-statistics-common = { path = "../../common/statistics" } -nym-validator-client = { path = "../../common/client-libs/validator-client" } -nym-task = { path = "../../common/task" } -nym-node-requests = { path = "../../nym-node/nym-node-requests", features = ["openapi"] } -nym-ecash-time = { path = "../../common/ecash-time", features = ["expiration"] } +nym-crypto = { workspace = true, features = ["asymmetric", "serde"] } +nym-http-api-client = { workspace = true } +nym-http-api-common = { workspace = true, features = ["middleware"] } +nym-network-defaults = { workspace = true } +nym-serde-helpers = { workspace = true } +nym-statistics-common = { workspace = true } +nym-validator-client = { workspace = true } +nym-task = { workspace = true } +nym-node-requests = { workspace = true, features = ["openapi", "client"] } +nym-ecash-time = { workspace = true, features = ["expiration"] } rand = { workspace = true } rand_chacha = { workspace = true } @@ -68,7 +69,7 @@ utoipa-swagger-ui = { workspace = true, features = ["axum"] } utoipauto = { workspace = true } zeroize = { workspace = true } -nym-node-metrics = { path = "../../nym-node/nym-node-metrics" } +nym-node-metrics = { workspace = true } [build-dependencies] anyhow = { workspace = true } diff --git a/nym-node-status-api/nym-node-status-client/Cargo.toml b/nym-node-status-api/nym-node-status-client/Cargo.toml index 881cfa159e..194ac61357 100644 --- a/nym-node-status-api/nym-node-status-client/Cargo.toml +++ b/nym-node-status-api/nym-node-status-client/Cargo.toml @@ -12,13 +12,14 @@ edition.workspace = true license.workspace = true rust-version.workspace = true readme.workspace = true +publish = false [dependencies] anyhow = { workspace = true } bs58 = { workspace = true } bincode = { workspace = true } -nym-crypto = { path = "../../common/crypto", features = ["asymmetric", "serde", ] } -nym-credentials = { path = "../../common/credentials" } +nym-crypto = { workspace = true, features = ["asymmetric", "serde", ] } +nym-credentials = { workspace = true } reqwest = { workspace = true, features = ["json"] } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } diff --git a/nym-node/Cargo.toml b/nym-node/Cargo.toml index f8467cdd3c..c9de83d0bb 100644 --- a/nym-node/Cargo.toml +++ b/nym-node/Cargo.toml @@ -10,6 +10,7 @@ homepage.workspace = true documentation.workspace = true edition.workspace = true license = "GPL-3.0" +publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -49,39 +50,39 @@ zeroize = { workspace = true, features = ["zeroize_derive"] } cupid = { workspace = true } sysinfo = { workspace = true } -nym-bin-common = { path = "../common/bin-common", features = [ +nym-bin-common = { workspace = true, features = [ "basic_tracing", "output_format", ] } -nym-client-core-config-types = { path = "../common/client-core/config-types", features = [ +nym-client-core-config-types = { workspace = true, features = [ "disk-persistence", ] } -nym-config = { path = "../common/config" } -nym-credential-verification = { path = "../common/credential-verification" } -nym-crypto = { path = "../common/crypto", features = ["asymmetric", "rand"] } -nym-nonexhaustive-delayqueue = { path = "../common/nonexhaustive-delayqueue" } -nym-mixnet-client = { path = "../common/client-libs/mixnet-client" } -nym-noise = { path = "../common/nymnoise" } -nym-noise-keys = { path = "../common/nymnoise/keys" } -nym-pemstore = { path = "../common/pemstore" } -nym-sphinx-acknowledgements = { path = "../common/nymsphinx/acknowledgements" } -nym-sphinx-addressing = { path = "../common/nymsphinx/addressing" } -nym-sphinx-framing = { path = "../common/nymsphinx/framing" } -nym-sphinx-types = { path = "../common/nymsphinx/types" } -nym-sphinx-forwarding = { path = "../common/nymsphinx/forwarding" } -nym-sphinx-routing = { path = "../common/nymsphinx/routing" } -nym-sphinx-params = { path = "../common/nymsphinx/params" } -nym-statistics-common = { path = "../common/statistics" } -nym-task = { path = "../common/task" } -nym-types = { path = "../common/types" } -nym-validator-client = { path = "../common/client-libs/validator-client" } -nym-wireguard = { path = "../common/wireguard" } -nym-wireguard-types = { path = "../common/wireguard-types", default-features = false } -nym-verloc = { path = "../common/verloc" } -nym-metrics = { path = "../common/nym-metrics" } -nym-gateway-stats-storage = { path = "../common/gateway-stats-storage" } -nym-topology = { path = "../common/topology" } -nym-http-api-client = { path = "../common/http-api-client" } +nym-config = { workspace = true } +nym-credential-verification = { workspace = true } +nym-crypto = { workspace = true, features = ["asymmetric", "rand"] } +nym-nonexhaustive-delayqueue = { workspace = true } +nym-mixnet-client = { workspace = true } +nym-noise = { workspace = true } +nym-noise-keys = { workspace = true } +nym-pemstore = { workspace = true } +nym-sphinx-acknowledgements = { workspace = true } +nym-sphinx-addressing = { workspace = true } +nym-sphinx-framing = { workspace = true } +nym-sphinx-types = { workspace = true } +nym-sphinx-forwarding = { workspace = true } +nym-sphinx-routing = { workspace = true } +nym-sphinx-params = { workspace = true } +nym-statistics-common = { workspace = true } +nym-task = { workspace = true } +nym-types = { workspace = true } +nym-validator-client = { workspace = true } +nym-wireguard = { workspace = true } +nym-wireguard-types = { workspace = true } +nym-verloc = { workspace = true } +nym-metrics = { workspace = true} +nym-gateway-stats-storage = { workspace = true } +nym-topology = { workspace = true } +nym-http-api-client = { workspace = true } # http server # useful for `#[axum_macros::debug_handler]` @@ -92,15 +93,15 @@ tower-http = { workspace = true, features = ["fs"] } utoipa = { workspace = true, features = ["axum_extras", "time"] } utoipa-swagger-ui = { workspace = true, features = ["axum"] } -nym-http-api-common = { path = "../common/http-api-common", features = [ +nym-http-api-common = { workspace = true, features = [ "utoipa", "output", "middleware", ] } -nym-node-requests = { path = "nym-node-requests", default-features = false, features = [ +nym-node-requests = { workspace = true, default-features = false, features = [ "openapi", ] } -nym-node-metrics = { path = "nym-node-metrics" } +nym-node-metrics = { workspace = true } # nodes: nym-gateway = { path = "../gateway" } diff --git a/nym-node/nym-node-metrics/Cargo.toml b/nym-node/nym-node-metrics/Cargo.toml index 7797040114..28b32ac5e8 100644 --- a/nym-node/nym-node-metrics/Cargo.toml +++ b/nym-node/nym-node-metrics/Cargo.toml @@ -1,12 +1,13 @@ [package] name = "nym-node-metrics" -version = "0.1.0" +version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true documentation.workspace = true edition.workspace = true license.workspace = true +description = "Crate defining various metrics for Nym Nodes (Mix Nodes and Gateways) such as ongoing connections, packets mixed, and packets dropped" [dependencies] dashmap = { workspace = true } @@ -16,5 +17,5 @@ time = { workspace = true } strum = { workspace = true } tracing = { workspace = true } -nym-metrics = { path = "../../common/nym-metrics" } -nym-statistics-common = { path = "../../common/statistics" } \ No newline at end of file +nym-metrics = { workspace = true } +nym-statistics-common = { workspace = true } diff --git a/nym-node/nym-node-requests/Cargo.toml b/nym-node/nym-node-requests/Cargo.toml index ad1ed3013a..14a8403379 100644 --- a/nym-node/nym-node-requests/Cargo.toml +++ b/nym-node/nym-node-requests/Cargo.toml @@ -1,12 +1,13 @@ [package] name = "nym-node-requests" -version = "0.1.0" +version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true documentation.workspace = true edition.workspace = true license.workspace = true +description = "Nym Node API endpoint definitions and functions" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -22,31 +23,31 @@ time = { workspace = true, features = ["serde", "formatting", "parsing"] } thiserror = { workspace = true } url = { workspace = true, features = ["serde"] } -nym-crypto = { path = "../../common/crypto", features = [ +nym-crypto = { workspace = true, features = [ "asymmetric", "serde", ] } -nym-exit-policy = { path = "../../common/exit-policy" } -nym-noise-keys = { path = "../../common/nymnoise/keys" } -nym-wireguard-types = { path = "../../common/wireguard-types", default-features = false } -nym-upgrade-mode-check = { path = "../../common/upgrade-mode-check", features = ["openapi"] } +nym-exit-policy = { workspace = true } +nym-noise-keys = { workspace = true } +nym-wireguard-types = { workspace = true } +nym-upgrade-mode-check = { workspace = true, features = ["openapi"] } # feature-specific dependencies: ## client: async-trait = { workspace = true, optional = true } -nym-http-api-client = { path = "../../common/http-api-client", optional = true } +nym-http-api-client = { workspace = true, optional = true } ## openapi: utoipa = { workspace = true, features = ["time"], optional = true } -nym-bin-common = { path = "../../common/bin-common", features = [ +nym-bin-common = { workspace = true, features = [ "bin_info_schema", ] } [dev-dependencies] tokio = { workspace = true, features = ["full"] } rand_chacha = { workspace = true } -nym-crypto = { path = "../../common/crypto", features = ["rand"] } +nym-crypto = { workspace = true, features = ["rand"] } [features] diff --git a/nym-outfox/Cargo.toml b/nym-outfox/Cargo.toml index 0c3c3e8763..8d7855bc48 100644 --- a/nym-outfox/Cargo.toml +++ b/nym-outfox/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-outfox" -version = "0.1.0" +version.workspace = true description = "Outfox package format" edition = { workspace = true } authors = { workspace = true } diff --git a/nym-registration-client/Cargo.toml b/nym-registration-client/Cargo.toml index efa0b5c5a6..3209fc7e47 100644 --- a/nym-registration-client/Cargo.toml +++ b/nym-registration-client/Cargo.toml @@ -1,12 +1,14 @@ [package] name = "nym-registration-client" -version = "0.1.0" +version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true documentation.workspace = true edition.workspace = true license.workspace = true +description = "Crate for dealing with Mixnet and Wireguard registration between Nym clients and Gateways" +publish = false [lints] workspace = true @@ -22,15 +24,16 @@ tracing.workspace = true typed-builder.workspace = true url.workspace = true -nym-authenticator-client = { path = "../nym-authenticator-client" } -nym-bandwidth-controller = { path = "../common/bandwidth-controller" } -nym-credential-storage = { path = "../common/credential-storage" } -nym-credentials-interface = { path = "../common/credentials-interface" } -nym-crypto = { path = "../common/crypto" } -nym-ip-packet-client = { path = "../nym-ip-packet-client" } + +nym-authenticator-client = { workspace = true } +nym-bandwidth-controller = { workspace = true } +nym-credential-storage = { workspace = true } +nym-credentials-interface = { workspace = true } +nym-crypto = { workspace = true } +nym-ip-packet-client = { workspace = true } nym-lp = { path = "../common/nym-lp" } nym-lp-transport = { path = "../common/nym-lp-transport" } -nym-registration-common = { path = "../common/registration" } -nym-sdk = { path = "../sdk/rust/nym-sdk" } -nym-validator-client = { path = "../common/client-libs/validator-client" } +nym-registration-common = { workspace = true } +nym-sdk = { workspace = true } +nym-validator-client = { workspace = true } nym-wireguard-types = { path = "../common/wireguard-types" } diff --git a/nym-signers-monitor/Cargo.toml b/nym-signers-monitor/Cargo.toml index d043aa2871..56adfc4ac5 100644 --- a/nym-signers-monitor/Cargo.toml +++ b/nym-signers-monitor/Cargo.toml @@ -9,6 +9,7 @@ edition.workspace = true license.workspace = true rust-version.workspace = true readme.workspace = true +publish = false [dependencies] anyhow = { workspace = true } @@ -20,11 +21,11 @@ tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } tracing = { workspace = true } url = { workspace = true } -nym-bin-common = { path = "../common/bin-common", features = ["output_format", "basic_tracing"] } -nym-ecash-signer-check = { path = "../common/ecash-signer-check" } -nym-network-defaults = { path = "../common/network-defaults" } -nym-task = { path = "../common/task" } -nym-validator-client = { path = "../common/client-libs/validator-client" } +nym-bin-common = { workspace = true, features = ["output_format", "basic_tracing"] } +nym-ecash-signer-check = { workspace = true } +nym-network-defaults = { workspace = true } +nym-task = { workspace = true } +nym-validator-client = { workspace = true } zulip-client = { path = "../common/zulip-client" } [lints] diff --git a/sqlx-pool-guard/Cargo.toml b/nym-sqlx-pool-guard/Cargo.toml similarity index 80% rename from sqlx-pool-guard/Cargo.toml rename to nym-sqlx-pool-guard/Cargo.toml index cef202e4df..3eecfffb99 100644 --- a/sqlx-pool-guard/Cargo.toml +++ b/nym-sqlx-pool-guard/Cargo.toml @@ -1,8 +1,12 @@ [package] -name = "sqlx-pool-guard" -version = "0.1.0" +name = "nym-sqlx-pool-guard" +version.workspace = true edition = "2024" license.workspace = true +description = "Platform-specific functions for SQLX dbs" +repository.workspace = true +homepage.workspace = true +documentation.workspace = true [lints] workspace = true diff --git a/sqlx-pool-guard/src/apple.rs b/nym-sqlx-pool-guard/src/apple.rs similarity index 100% rename from sqlx-pool-guard/src/apple.rs rename to nym-sqlx-pool-guard/src/apple.rs diff --git a/sqlx-pool-guard/src/lib.rs b/nym-sqlx-pool-guard/src/lib.rs similarity index 100% rename from sqlx-pool-guard/src/lib.rs rename to nym-sqlx-pool-guard/src/lib.rs diff --git a/sqlx-pool-guard/src/linux.rs b/nym-sqlx-pool-guard/src/linux.rs similarity index 100% rename from sqlx-pool-guard/src/linux.rs rename to nym-sqlx-pool-guard/src/linux.rs diff --git a/sqlx-pool-guard/src/windows.rs b/nym-sqlx-pool-guard/src/windows.rs similarity index 100% rename from sqlx-pool-guard/src/windows.rs rename to nym-sqlx-pool-guard/src/windows.rs diff --git a/nym-statistics-api/Cargo.toml b/nym-statistics-api/Cargo.toml index be52fc8b38..d603503585 100644 --- a/nym-statistics-api/Cargo.toml +++ b/nym-statistics-api/Cargo.toml @@ -11,7 +11,7 @@ documentation.workspace = true edition.workspace = true license.workspace = true rust-version.workspace = true - +publish = false [dependencies] anyhow.workspace = true @@ -38,17 +38,17 @@ utoipa-swagger-ui = { workspace = true, features = ["axum"] } utoipauto.workspace = true #internal -nym-bin-common = { path = "../common/bin-common" } -nym-http-api-common = { path = "../common/http-api-common", features = [ +nym-bin-common = { workspace = true } +nym-http-api-common = { workspace = true, features = [ "middleware", ] } -nym-statistics-common = { path = "../common/statistics", features = [ +nym-statistics-common = { workspace = true, features = [ "openapi", ] } -nym-task = { path = "../common/task" } +nym-task = { workspace = true } -nym-http-api-client = { path = "../common/http-api-client" } -nym-validator-client = { path = "../common/client-libs/validator-client" } +nym-http-api-client = { workspace = true } +nym-validator-client = { workspace = true } [build-dependencies] anyhow = { workspace = true } diff --git a/nym-validator-rewarder/Cargo.toml b/nym-validator-rewarder/Cargo.toml index 0655bd21ef..bf2fc671f8 100644 --- a/nym-validator-rewarder/Cargo.toml +++ b/nym-validator-rewarder/Cargo.toml @@ -7,6 +7,7 @@ homepage.workspace = true documentation.workspace = true edition.workspace = true license = "GPL-3.0" +publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -32,22 +33,22 @@ humantime = { workspace = true } humantime-serde.workspace = true # internal -nym-bin-common = { path = "../common/bin-common", features = ["output_format", "basic_tracing"] } -nym-config = { path = "../common/config" } -nym-ecash-time = { path = "../common/ecash-time" } -nym-contracts-common = { path = "../common/cosmwasm-smart-contracts/contracts-common" } -nym-compact-ecash = { path = "../common/nym_offline_compact_ecash" } -nym-crypto = { path = "../common/crypto", features = ["asymmetric", "rand"] } -nym-credentials = { path = "../common/credentials" } -nym-network-defaults = { path = "../common/network-defaults" } -nym-task = { path = "../common/task" } -nym-validator-client = { path = "../common/client-libs/validator-client" } -nym-http-api-client = { path = "../common/http-api-client" } -nym-coconut-dkg-common = { path = "../common/cosmwasm-smart-contracts/coconut-dkg" } +nym-bin-common = { workspace = true, features = ["output_format", "basic_tracing"] } +nym-config = { workspace = true } +nym-ecash-time = { workspace = true } +nym-contracts-common = { workspace = true } +nym-compact-ecash = { workspace = true } +nym-crypto = { workspace = true, features = ["asymmetric", "rand"] } +nym-credentials = { workspace = true } +nym-network-defaults = { workspace = true } +nym-task = { workspace = true } +nym-validator-client = { workspace = true } +nym-http-api-client = { workspace = true } +nym-coconut-dkg-common = { workspace = true } nyxd-scraper-sqlite = { path = "../common/nyxd-scraper-sqlite" } -nym-ticketbooks-merkle = { path = "../common/ticketbooks-merkle" } -nym-serde-helpers = { path = "../common/serde-helpers", features = ["base64"] } -nym-pemstore = { path = "../common/pemstore" } +nym-ticketbooks-merkle = { workspace = true } +nym-serde-helpers = { workspace = true, features = ["base64"] } +nym-pemstore = { workspace = true } [build-dependencies] anyhow = { workspace = true } @@ -56,7 +57,7 @@ tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } [dev-dependencies] rand_chacha = { workspace = true } -nym-credentials-interface = { path = "../common/credentials-interface" } +nym-credentials-interface = { workspace = true } [lints] workspace = true diff --git a/nyx-chain-watcher/Cargo.toml b/nyx-chain-watcher/Cargo.toml index 3a2f09149b..479d24460e 100644 --- a/nyx-chain-watcher/Cargo.toml +++ b/nyx-chain-watcher/Cargo.toml @@ -12,6 +12,7 @@ edition.workspace = true license.workspace = true rust-version.workspace = true readme.workspace = true +publish = false [dependencies] anyhow = { workspace = true } @@ -19,11 +20,11 @@ async-trait.workspace = true axum = { workspace = true, features = ["tokio"] } chrono = { workspace = true } clap = { workspace = true, features = ["cargo", "derive", "env"] } -nym-config = { path = "../common/config" } -nym-bin-common = { path = "../common/bin-common", features = ["output_format"] } -nym-network-defaults = { path = "../common/network-defaults" } -nym-task = { path = "../common/task" } -nym-validator-client = { path = "../common/client-libs/validator-client" } +nym-config = { workspace = true } +nym-bin-common = { workspace = true, features = ["output_format"] } +nym-network-defaults = { workspace = true } +nym-task = { workspace = true } +nym-validator-client = { workspace = true } nyxd-scraper-sqlite = { path = "../common/nyxd-scraper-sqlite" } reqwest = { workspace = true, features = ["rustls-tls"] } schemars = { workspace = true } diff --git a/sdk/ffi/cpp/Cargo.toml b/sdk/ffi/cpp/Cargo.toml index 8ac5f608db..307f669a15 100644 --- a/sdk/ffi/cpp/Cargo.toml +++ b/sdk/ffi/cpp/Cargo.toml @@ -1,8 +1,12 @@ [package] name = "nym-cpp-ffi" -version = "0.1.2" +version.workspace = true edition = "2021" +repository.workspace = true +homepage.workspace = true +documentation.workspace = true license.workspace = true +description = "C++ FFI bindings for Nym Rust SDK" [lib] name = "nym_cpp_ffi" @@ -12,10 +16,10 @@ crate-type = ["cdylib"] # Async runtime tokio = { workspace = true, features = ["full"] } # Nym clients, addressing, packet format, common tools (logging), ffi shared -nym-sdk = { path = "../../rust/nym-sdk/" } -nym-bin-common = { path = "../../../common/bin-common", features = ["basic_tracing"] } -nym-sphinx-anonymous-replies = { path = "../../../common/nymsphinx/anonymous-replies" } -nym-ffi-shared = { path = "../shared" } +nym-sdk = { workspace = true } +nym-bin-common = { workspace = true, features = ["basic_tracing"] } +nym-sphinx-anonymous-replies = { workspace = true } +nym-ffi-shared = { workspace = true } lazy_static = { workspace = true } # error handling anyhow = { workspace = true } diff --git a/sdk/ffi/go/Cargo.toml b/sdk/ffi/go/Cargo.toml index 3366f424a5..76b1e91a57 100644 --- a/sdk/ffi/go/Cargo.toml +++ b/sdk/ffi/go/Cargo.toml @@ -1,8 +1,12 @@ [package] name = "nym-go-ffi" -version = "0.2.2" +version.workspace = true edition = "2021" +repository.workspace = true +homepage.workspace = true +documentation.workspace = true license.workspace = true +description = "Go FFI bindings for Nym Rust SDK" [lib] crate-type = ["cdylib"] @@ -12,11 +16,11 @@ name = "nym_go_ffi" # Bindgen uniffi = { workspace = true, features = ["cli"] } # Nym clients, addressing, packet format, common tools (logging), ffi shared -nym-sdk = { path = "../../rust/nym-sdk/" } -nym-crypto = { path = "../../../common/crypto" } -nym-bin-common = { path = "../../../common/bin-common", features = ["basic_tracing"] } -nym-sphinx-anonymous-replies = { path = "../../../common/nymsphinx/anonymous-replies" } -nym-ffi-shared = { path = "../shared" } +nym-sdk = { workspace = true } +nym-crypto = { workspace = true } +nym-bin-common = { workspace = true, features = ["basic_tracing"] } +nym-sphinx-anonymous-replies = { workspace = true } +nym-ffi-shared = { workspace = true } # Async runtime tokio = { workspace = true, features = ["full"] } lazy_static = { workspace = true } diff --git a/sdk/ffi/shared/Cargo.toml b/sdk/ffi/shared/Cargo.toml index e409035681..d5e3434892 100644 --- a/sdk/ffi/shared/Cargo.toml +++ b/sdk/ffi/shared/Cargo.toml @@ -1,17 +1,21 @@ [package] name = "nym-ffi-shared" -version = "0.2.1" +version.workspace = true edition = "2021" +repository.workspace = true +homepage.workspace = true +documentation.workspace = true license.workspace = true +description = "Common crate for use by Rust SDK FFI crates" [dependencies] # Async runtime tokio = { workspace = true, features = ["full"] } # Nym clients, addressing, packet format, common tools (logging) -nym-sdk = { path = "../../rust/nym-sdk/" } -nym-bin-common = { path = "../../../common/bin-common" } -nym-crypto = { path = "../../../common/crypto" } -nym-sphinx-anonymous-replies = { path = "../../../common/nymsphinx/anonymous-replies" } +nym-sdk = { workspace = true } +nym-bin-common = { workspace = true } +nym-crypto = { workspace = true } +nym-sphinx-anonymous-replies = { workspace = true } # static var macro lazy_static = { workspace = true } # error handling diff --git a/sdk/rust/nym-sdk/Cargo.toml b/sdk/rust/nym-sdk/Cargo.toml index 523e5801c5..70e1441e8a 100644 --- a/sdk/rust/nym-sdk/Cargo.toml +++ b/sdk/rust/nym-sdk/Cargo.toml @@ -1,8 +1,11 @@ [package] name = "nym-sdk" -version = "0.1.0" +version.workspace = true edition = "2021" license.workspace = true +description = "Nym's Rust SDK" +repository = "https://github.com/nymtech/nym/sdk/rust/nym-sdk/" +# documentation = "https://docs.rs/nym-sdk" Max: once we upload to crates.io and this is generated, this can be uncommented. # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -17,33 +20,33 @@ path = "src/tcp_proxy/bin/proxy_client.rs" [dependencies] async-trait = { workspace = true } bip39 = { workspace = true } -nym-client-core = { path = "../../../common/client-core", features = [ +nym-client-core = { workspace = true, features = [ "fs-credentials-storage", "fs-surb-storage", "fs-gateways-storage", ] } -nym-crypto = { path = "../../../common/crypto" } -nym-gateway-requests = { path = "../../../common/gateway-requests" } -nym-bandwidth-controller = { path = "../../../common/bandwidth-controller" } -nym-credentials = { path = "../../../common/credentials" } -nym-credentials-interface = { path = "../../../common/credentials-interface" } -nym-credential-storage = { path = "../../../common/credential-storage" } -nym-credential-utils = { path = "../../../common/credential-utils" } -nym-network-defaults = { path = "../../../common/network-defaults" } -nym-sphinx = { path = "../../../common/nymsphinx" } -nym-statistics-common = { path = "../../../common/statistics" } -nym-task = { path = "../../../common/task" } -nym-topology = { path = "../../../common/topology" } -nym-socks5-client-core = { path = "../../../common/socks5-client-core" } -nym-validator-client = { path = "../../../common/client-libs/validator-client", features = [ +nym-crypto = { workspace = true } +nym-gateway-requests = { workspace = true } +nym-bandwidth-controller = { workspace = true } +nym-credentials = { workspace = true } +nym-credentials-interface = { workspace = true } +nym-credential-storage = { workspace = true } +nym-credential-utils = { workspace = true } +nym-network-defaults = { workspace = true } +nym-sphinx = { workspace = true } +nym-statistics-common = { workspace = true } +nym-task = { workspace = true } +nym-topology = { workspace = true } +nym-socks5-client-core = { workspace = true } +nym-validator-client = { workspace = true , features = [ "http-client", ] } -nym-http-api-client = { path = "../../../common/http-api-client" } -nym-socks5-requests = { path = "../../../common/socks5/requests" } -nym-ordered-buffer = { path = "../../../common/socks5/ordered-buffer" } -nym-service-providers-common = { path = "../../../service-providers/common" } -nym-sphinx-addressing = { path = "../../../common/nymsphinx/addressing" } -nym-bin-common = { path = "../../../common/bin-common", features = [ +nym-http-api-client = { workspace = true } +nym-socks5-requests = { workspace = true } +nym-ordered-buffer = { workspace = true } +nym-service-providers-common = { workspace = true } +nym-sphinx-addressing = { workspace = true } +nym-bin-common = { workspace = true, features = [ "basic_tracing", ] } bytecodec = { workspace = true } @@ -82,7 +85,7 @@ reqwest = { workspace = true, features = ["json", "socks"] } thiserror = { workspace = true } tokio = { workspace = true, features = ["full"] } time = { workspace = true } -nym-bin-common = { path = "../../../common/bin-common", features = ["basic_tracing"] } +nym-bin-common = {workspace = true, features = ["basic_tracing"] } # extra dependencies for libp2p examples #libp2p = { git = "https://github.com/ChainSafe/rust-libp2p.git", rev = "e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6", features = [ "identify", "macros", "ping", "tokio", "tcp", "dns", "websocket", "noise", "mplex", "yamux", "gossipsub" ]} diff --git a/sdk/rust/nym-sdk/src/client_pool.rs b/sdk/rust/nym-sdk/src/client_pool.rs index 0a2471275b..ae3d1a200d 100644 --- a/sdk/rust/nym-sdk/src/client_pool.rs +++ b/sdk/rust/nym-sdk/src/client_pool.rs @@ -1,3 +1,6 @@ +//! A variable-size pool of ephemeral Nym Clients to quickly grab and use. +//! +//! //! use crate::mixnet::{MixnetClient, MixnetClientBuilder, NymNetworkDetails}; //! use anyhow::Result; //! use std::fmt; diff --git a/service-providers/common/Cargo.toml b/service-providers/common/Cargo.toml index f8fb5b201b..ad8b2b7574 100644 --- a/service-providers/common/Cargo.toml +++ b/service-providers/common/Cargo.toml @@ -1,14 +1,18 @@ [package] name = "nym-service-providers-common" -version = "0.1.0" +version.workspace = true edition = "2021" license.workspace = true +description = "Common crate for Nym Service Providers" +repository.workspace = true +homepage.workspace = true +documentation.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -nym-bin-common = { path = "../../common/bin-common" } -nym-sphinx-anonymous-replies = { path = "../../common/nymsphinx/anonymous-replies" } +nym-bin-common = { workspace = true } +nym-sphinx-anonymous-replies = { workspace = true } async-trait = { workspace = true } log = { workspace = true } @@ -21,6 +25,5 @@ serde_json = { workspace = true } anyhow = { workspace = true } tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } -nym-sdk = { path = "../../sdk/rust/nym-sdk" } -nym-socks5-requests = { path = "../../common/socks5/requests" } - +nym-sdk = { workspace = true } +nym-socks5-requests = { workspace = true } diff --git a/service-providers/ip-packet-router/Cargo.toml b/service-providers/ip-packet-router/Cargo.toml index aee3a35ab8..5e3340871f 100644 --- a/service-providers/ip-packet-router/Cargo.toml +++ b/service-providers/ip-packet-router/Cargo.toml @@ -1,12 +1,13 @@ [package] name = "nym-ip-packet-router" -version = "0.1.0" +version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true documentation.workspace = true edition.workspace = true license = "GPL-3.0" +publish = false [dependencies] anyhow.workspace = true @@ -17,25 +18,26 @@ clap.workspace = true etherparse = { workspace = true } futures = { workspace = true } log = { workspace = true } -nym-bin-common = { path = "../../common/bin-common", features = ["clap", "basic_tracing"] } -nym-client-core = { path = "../../common/client-core" } -nym-config = { path = "../../common/config" } -nym-crypto = { path = "../../common/crypto" } -nym-exit-policy = { path = "../../common/exit-policy" } -nym-id = { path = "../../common/nym-id" } -nym-ip-packet-requests = { path = "../../common/ip-packet-requests" } -nym-kcp = { path = "../../common/nym-kcp" } -nym-network-defaults = { path = "../../common/network-defaults" } + +nym-bin-common = { workspace = true, features = ["clap", "basic_tracing"] } +nym-client-core = { workspace = true } +nym-config = { workspace = true } +nym-crypto = { workspace = true } +nym-exit-policy = { workspace = true } +nym-id = { workspace = true } +nym-ip-packet-requests = { workspace = true } +nym-kcp = { path = "../../common/nym-kcp" } # TODO MAX add to workspace dependencies +nym-network-defaults = { workspace = true } nym-network-requester = { path = "../network-requester" } -nym-sdk = { path = "../../sdk/rust/nym-sdk" } -nym-service-provider-requests-common = { path = "../../common/service-provider-requests-common" } -nym-service-providers-common = { path = "../common" } -nym-sphinx = { path = "../../common/nymsphinx" } -nym-task = { path = "../../common/task" } -nym-tun = { path = "../../common/tun" } -nym-types = { path = "../../common/types" } -nym-wireguard = { path = "../../common/wireguard" } -nym-wireguard-types = { path = "../../common/wireguard-types" } +nym-sdk = { workspace = true } +nym-service-provider-requests-common = { workspace = true } +nym-service-providers-common = { workspace = true } +nym-sphinx = { workspace = true } +nym-task = { workspace = true } +nym-tun = { workspace = true } +nym-types = { workspace = true } +nym-wireguard = { workspace = true } +nym-wireguard-types = { workspace = true } rand = { workspace = true } reqwest.workspace = true serde = { workspace = true, features = ["derive"] } diff --git a/service-providers/network-requester/Cargo.toml b/service-providers/network-requester/Cargo.toml index 2c11e6ea3f..e27ea36863 100644 --- a/service-providers/network-requester/Cargo.toml +++ b/service-providers/network-requester/Cargo.toml @@ -8,6 +8,7 @@ version = "1.1.69" authors.workspace = true edition.workspace = true rust-version = "1.85" +publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -42,25 +43,25 @@ time = { workspace = true } zeroize = { workspace = true } # internal -nym-async-file-watcher = { path = "../../common/async-file-watcher" } -nym-bin-common = { path = "../../common/bin-common", features = ["output_format", "clap", "basic_tracing"] } -nym-client-core = { path = "../../common/client-core", features = ["cli", "fs-gateways-storage", "fs-surb-storage"] } -nym-client-websocket-requests = { path = "../../clients/native/websocket-requests" } -nym-config = { path = "../../common/config" } -nym-credentials = { path = "../../common/credentials" } -nym-credential-storage = { path = "../../common/credential-storage" } -nym-crypto = { path = "../../common/crypto" } -nym-network-defaults = { path = "../../common/network-defaults" } -nym-ordered-buffer = { path = "../../common/socks5/ordered-buffer" } -nym-sdk = { path = "../../sdk/rust/nym-sdk" } -nym-service-providers-common = { path = "../common" } -nym-socks5-proxy-helpers = { path = "../../common/socks5/proxy-helpers" } -nym-socks5-requests = { path = "../../common/socks5/requests" } -nym-sphinx = { path = "../../common/nymsphinx" } -nym-task = { path = "../../common/task" } -nym-types = { path = "../../common/types" } -nym-exit-policy = { path = "../../common/exit-policy", features = ["client"] } -nym-id = { path = "../../common/nym-id" } +nym-async-file-watcher = { workspace = true } +nym-bin-common = { workspace = true, features = ["output_format", "clap", "basic_tracing"] } +nym-client-core = { workspace = true, features = ["cli", "fs-gateways-storage", "fs-surb-storage"] } +nym-client-websocket-requests = { workspace = true } +nym-config = { workspace = true } +nym-credentials = { workspace = true } +nym-credential-storage = { workspace = true } +nym-crypto = { workspace = true } +nym-network-defaults = { workspace = true } +nym-ordered-buffer = { workspace = true } +nym-sdk = { workspace = true } +nym-service-providers-common = { workspace = true } +nym-socks5-proxy-helpers = { workspace = true } +nym-socks5-requests = { workspace = true } +nym-sphinx = { workspace = true } +nym-task = { workspace = true } +nym-types = { workspace = true } +nym-exit-policy = { workspace = true, features = ["client"] } +nym-id = { workspace = true } [dev-dependencies] tempfile = { workspace = true } diff --git a/tools/echo-server/Cargo.toml b/tools/echo-server/Cargo.toml index f58a0e46b3..4f9ec4de02 100644 --- a/tools/echo-server/Cargo.toml +++ b/tools/echo-server/Cargo.toml @@ -8,7 +8,7 @@ documentation.workspace = true edition.workspace = true license.workspace = true rust-version.workspace = true - +publish = false [[bin]] name = "echo-server" @@ -26,14 +26,14 @@ serde = { workspace = true, features = ["derive"] } tracing.workspace = true tracing-subscriber = { workspace = true } bytecodec = { workspace = true } -nym-sdk = { path = "../../sdk/rust/nym-sdk" } +nym-sdk = { workspace = true } bytes.workspace = true dirs.workspace = true clap.workspace = true -nym-bin-common = { path = "../../common/bin-common", features = [ +nym-bin-common = { workspace = true, features = [ "basic_tracing", "output_format", ] } -nym-crypto = { path = "../../common/crypto", features = ["asymmetric"] } +nym-crypto = { workspace = true, features = ["asymmetric"] } futures = { workspace = true } tempfile.workspace = true diff --git a/tools/internal/contract-state-importer/importer-cli/Cargo.toml b/tools/internal/contract-state-importer/importer-cli/Cargo.toml index db708db2e8..143b18ffbb 100644 --- a/tools/internal/contract-state-importer/importer-cli/Cargo.toml +++ b/tools/internal/contract-state-importer/importer-cli/Cargo.toml @@ -9,6 +9,7 @@ edition.workspace = true license.workspace = true rust-version.workspace = true readme.workspace = true +publish = false [dependencies] anyhow = { workspace = true } @@ -21,9 +22,9 @@ tokio = { workspace = true, features = ["rt-multi-thread", "net", "signal"] } tracing = { workspace = true } importer-contract = { path = "../importer-contract" } -nym-validator-client = { path = "../../../../common/client-libs/validator-client" } -nym-bin-common = { path = "../../../../common/bin-common", features = ["basic_tracing"] } -nym-network-defaults = { path = "../../../../common/network-defaults" } +nym-validator-client = { workspace = true } +nym-bin-common = { workspace = true, features = ["basic_tracing"] } +nym-network-defaults = { workspace = true } -nym-mixnet-contract-common = { path = "../../../../common/cosmwasm-smart-contracts/mixnet-contract" } -nym-vesting-contract-common = { path = "../../../../common/cosmwasm-smart-contracts/vesting-contract" } \ No newline at end of file +nym-mixnet-contract-common = { workspace = true } +nym-vesting-contract-common = { workspace = true } diff --git a/tools/internal/contract-state-importer/importer-contract/Cargo.toml b/tools/internal/contract-state-importer/importer-contract/Cargo.toml index 2c09e7071e..72f0476206 100644 --- a/tools/internal/contract-state-importer/importer-contract/Cargo.toml +++ b/tools/internal/contract-state-importer/importer-contract/Cargo.toml @@ -8,7 +8,7 @@ documentation.workspace = true edition.workspace = true license.workspace = true readme.workspace = true - +publish = false [lib] crate-type = ["cdylib", "rlib"] @@ -21,4 +21,4 @@ cosmwasm-schema = { workspace = true } [features] default = ["library"] -library = [] \ No newline at end of file +library = [] diff --git a/tools/internal/mixnet-connectivity-check/Cargo.toml b/tools/internal/mixnet-connectivity-check/Cargo.toml index af55790aa4..9d2d36c9d8 100644 --- a/tools/internal/mixnet-connectivity-check/Cargo.toml +++ b/tools/internal/mixnet-connectivity-check/Cargo.toml @@ -9,6 +9,7 @@ edition.workspace = true license.workspace = true rust-version.workspace = true readme.workspace = true +publish = false [dependencies] anyhow = { workspace = true } @@ -17,7 +18,7 @@ futures = { workspace = true } tracing = { workspace = true } tokio = { workspace = true, features = ["rt-multi-thread", "signal", "macros"] } -nym-network-defaults = { path = "../../../common/network-defaults" } -nym-bin-common = { path = "../../../common/bin-common", features = ["basic_tracing", "output_format"] } -nym-crypto = { path = "../../../common/crypto", features = ["asymmetric"] } -nym-sdk = { path = "../../../sdk/rust/nym-sdk" } +nym-network-defaults = { workspace = true } +nym-bin-common = { workspace = true, features = ["basic_tracing", "output_format"] } +nym-crypto = { workspace = true, features = ["asymmetric"] } +nym-sdk = { workspace = true } diff --git a/tools/internal/ssl-inject/Cargo.toml b/tools/internal/ssl-inject/Cargo.toml index 86220b4919..d87167628e 100644 --- a/tools/internal/ssl-inject/Cargo.toml +++ b/tools/internal/ssl-inject/Cargo.toml @@ -3,6 +3,7 @@ name = "ssl-inject" version = "0.1.0" edition = "2021" license.workspace = true +publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/tools/internal/testnet-manager/Cargo.toml b/tools/internal/testnet-manager/Cargo.toml index 6f799d84a4..2ca0b01ece 100644 --- a/tools/internal/testnet-manager/Cargo.toml +++ b/tools/internal/testnet-manager/Cargo.toml @@ -8,6 +8,8 @@ documentation.workspace = true edition.workspace = true license.workspace = true rust-version.workspace = true +publish = false + [dependencies] anyhow.workspace = true @@ -32,24 +34,24 @@ url.workspace = true zeroize = { workspace = true, features = ["zeroize_derive"] } -nym-bin-common = { path = "../../../common/bin-common", features = ["output_format", "basic_tracing"] } -nym-crypto = { path = "../../../common/crypto", features = ["asymmetric", "rand", "serde"] } -nym-config = { path = "../../../common/config" } -nym-validator-client = { path = "../../../common/client-libs/validator-client" } -nym-compact-ecash = { path = "../../../common/nym_offline_compact_ecash" } -nym-http-api-client = { path = "../../../common/http-api-client" } +nym-bin-common = { workspace = true, features = ["output_format", "basic_tracing"] } +nym-crypto = { workspace = true, features = ["asymmetric", "rand", "serde"] } +nym-config = { workspace = true } +nym-validator-client = { workspace = true } +nym-compact-ecash = { workspace = true } +nym-http-api-client = { workspace = true } dkg-bypass-contract = { path = "dkg-bypass-contract", default-features = false } # contracts: -nym-mixnet-contract-common = { path = "../../../common/cosmwasm-smart-contracts/mixnet-contract" } -nym-contracts-common = { path = "../../../common/cosmwasm-smart-contracts/contracts-common" } -nym-vesting-contract-common = { path = "../../../common/cosmwasm-smart-contracts/vesting-contract" } -nym-group-contract-common = { path = "../../../common/cosmwasm-smart-contracts/group-contract" } -nym-ecash-contract-common = { path = "../../../common/cosmwasm-smart-contracts/ecash-contract" } -nym-coconut-dkg-common = { path = "../../../common/cosmwasm-smart-contracts/coconut-dkg" } -nym-multisig-contract-common = { path = "../../../common/cosmwasm-smart-contracts/multisig-contract" } -nym-performance-contract-common = { path = "../../../common/cosmwasm-smart-contracts/nym-performance-contract" } -nym-pemstore = { path = "../../../common/pemstore" } +nym-mixnet-contract-common = { workspace = true } +nym-contracts-common = { workspace = true } +nym-vesting-contract-common = { workspace = true } +nym-group-contract-common = { workspace = true } +nym-ecash-contract-common = { workspace = true } +nym-coconut-dkg-common = { workspace = true } +nym-multisig-contract-common = { workspace = true } +nym-performance-contract-common = { workspace = true } +nym-pemstore = { workspace = true } [build-dependencies] diff --git a/tools/internal/testnet-manager/dkg-bypass-contract/Cargo.toml b/tools/internal/testnet-manager/dkg-bypass-contract/Cargo.toml index fa45302f00..5074d8d91f 100644 --- a/tools/internal/testnet-manager/dkg-bypass-contract/Cargo.toml +++ b/tools/internal/testnet-manager/dkg-bypass-contract/Cargo.toml @@ -5,6 +5,7 @@ edition = { workspace = true } authors = { workspace = true } license = { workspace = true } repository = { workspace = true } +publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -17,9 +18,9 @@ cosmwasm-std = { workspace = true } cosmwasm-schema = { workspace = true } cw-storage-plus = { workspace = true } -nym-coconut-dkg-common = { path = "../../../../common/cosmwasm-smart-contracts/coconut-dkg" } -nym-contracts-common = { path = "../../../../common/cosmwasm-smart-contracts/contracts-common" } +nym-coconut-dkg-common = { workspace = true } +nym-contracts-common = { workspace = true } [features] default = ["library"] -library = [] \ No newline at end of file +library = [] diff --git a/tools/internal/validator-status-check/Cargo.toml b/tools/internal/validator-status-check/Cargo.toml index a0d09e336f..7b335095b7 100644 --- a/tools/internal/validator-status-check/Cargo.toml +++ b/tools/internal/validator-status-check/Cargo.toml @@ -9,6 +9,7 @@ edition.workspace = true license.workspace = true rust-version.workspace = true readme.workspace = true +publish = false [dependencies] anyhow = { workspace = true } @@ -22,10 +23,10 @@ tracing = { workspace = true } time = { workspace = true } -nym-validator-client = { path = "../../../common/client-libs/validator-client" } -nym-bin-common = { path = "../../../common/bin-common", features = ["output_format", "basic_tracing"] } -nym-network-defaults = { path = "../../../common/network-defaults" } -nym-http-api-client = { path = "../../../common/http-api-client" } +nym-validator-client = { workspace = true } +nym-bin-common = { workspace = true, features = ["output_format", "basic_tracing"] } +nym-network-defaults = { workspace = true } +nym-http-api-client = { workspace = true } [lints] workspace = true diff --git a/tools/nym-cli/Cargo.toml b/tools/nym-cli/Cargo.toml index 1a8aa016eb..35772ab17b 100644 --- a/tools/nym-cli/Cargo.toml +++ b/tools/nym-cli/Cargo.toml @@ -4,6 +4,7 @@ version = "1.1.68" authors.workspace = true edition = "2021" license.workspace = true +publish = false [dependencies] base64 = { workspace = true } @@ -22,9 +23,9 @@ anyhow = { workspace = true } tap = { workspace = true } nym-cli-commands = { path = "../../common/commands" } -nym-bin-common = { path = "../../common/bin-common", features = ["basic_tracing"] } -nym-validator-client = { path = "../../common/client-libs/validator-client", features = ["http-client"] } -nym-network-defaults = { path = "../../common/network-defaults" } +nym-bin-common = { workspace = true, features = ["basic_tracing"] } +nym-validator-client = { workspace = true, features = ["http-client"] } +nym-network-defaults = { workspace = true } [package.metadata.deb] name = "nym-cli" diff --git a/tools/nym-id-cli/Cargo.toml b/tools/nym-id-cli/Cargo.toml index bada736bfd..7d264bb2e8 100644 --- a/tools/nym-id-cli/Cargo.toml +++ b/tools/nym-id-cli/Cargo.toml @@ -7,6 +7,7 @@ homepage.workspace = true documentation.workspace = true edition.workspace = true license.workspace = true +publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -17,6 +18,6 @@ clap = { workspace = true, features = ["derive"] } tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } tracing.workspace = true -nym-bin-common = { path = "../../common/bin-common", features = ["output_format", "basic_tracing"] } -nym-credential-storage = { path = "../../common/credential-storage" } -nym-id = { path = "../../common/nym-id" } +nym-bin-common = { workspace = true, features = ["output_format", "basic_tracing"] } +nym-credential-storage = { workspace = true } +nym-id = { workspace = true } diff --git a/tools/nym-lp-client/Cargo.toml b/tools/nym-lp-client/Cargo.toml index f26c2f34f9..188b123a9a 100644 --- a/tools/nym-lp-client/Cargo.toml +++ b/tools/nym-lp-client/Cargo.toml @@ -4,6 +4,7 @@ version = "0.1.0" edition = "2021" description = "LP+KCP mixnet client" license.workspace = true +publish = false [[bin]] name = "nym-lp-client" diff --git a/tools/nym-nr-query/Cargo.toml b/tools/nym-nr-query/Cargo.toml index f5ec6f6475..da2d03e544 100644 --- a/tools/nym-nr-query/Cargo.toml +++ b/tools/nym-nr-query/Cargo.toml @@ -3,6 +3,7 @@ name = "nym-nr-query" version = "0.1.0" edition = "2021" license.workspace = true +publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -10,10 +11,10 @@ license.workspace = true anyhow = { workspace = true } clap = { workspace = true, features = ["cargo", "derive"]} log = { workspace = true } -nym-bin-common = { path = "../../common/bin-common", features = ["output_format", "basic_tracing"] } -nym-network-defaults = { path = "../../common/network-defaults" } -nym-sdk = { path = "../../sdk/rust/nym-sdk" } -nym-service-providers-common = { path = "../../service-providers/common" } -nym-socks5-requests = { path = "../../common/socks5/requests" } +nym-bin-common = { workspace = true, features = ["output_format", "basic_tracing"] } +nym-network-defaults = { workspace = true } +nym-sdk = { workspace = true } +nym-service-providers-common = { workspace = true } +nym-socks5-requests = { workspace = true } serde = { workspace = true } tokio = { workspace = true, features = [ "net", "rt-multi-thread", "macros" ] } diff --git a/tools/nymvisor/Cargo.toml b/tools/nymvisor/Cargo.toml index 7329119eb9..b336604d4f 100644 --- a/tools/nymvisor/Cargo.toml +++ b/tools/nymvisor/Cargo.toml @@ -1,12 +1,13 @@ [package] name = "nymvisor" -version = "0.1.33" +version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true documentation.workspace = true edition.workspace = true license.workspace = true +publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -32,10 +33,10 @@ thiserror = { workspace = true } tracing = { workspace = true } url = { workspace = true, features = ["serde"] } -nym-async-file-watcher = { path = "../../common/async-file-watcher" } -nym-bin-common = { path = "../../common/bin-common", features = ["output_format", "basic_tracing"] } -nym-config = { path = "../../common/config" } -nym-task = { path = "../../common/task"} +nym-async-file-watcher = { workspace = true } +nym-bin-common = { workspace = true, features = ["output_format", "basic_tracing"] } +nym-config = { workspace = true } +nym-task = { workspace = true} [dev-dependencies] tokio = { workspace = true, features = ["full"] } diff --git a/tools/ts-rs-cli/Cargo.toml b/tools/ts-rs-cli/Cargo.toml index 6c75876550..e2230dae3f 100644 --- a/tools/ts-rs-cli/Cargo.toml +++ b/tools/ts-rs-cli/Cargo.toml @@ -3,6 +3,7 @@ name = "ts-rs-cli" version = "0.1.0" edition = "2021" license.workspace = true +publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -11,11 +12,11 @@ anyhow = { workspace = true } ts-rs = { workspace = true } walkdir = { workspace = true } -nym-validator-client = { path = "../../common/client-libs/validator-client", features = [ +nym-validator-client = { workspace = true, features = [ "generate-ts" ] } -nym-api-requests = { path = "../../nym-api/nym-api-requests", features = ["generate-ts"] } -nym-mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-contract", features = ["generate-ts"] } -nym-vesting-contract-common = { path = "../../common/cosmwasm-smart-contracts/vesting-contract", features = ["generate-ts"] } -nym-types = { path = "../../common/types", features = ["generate-ts"] } +nym-api-requests = { workspace = true, features = ["generate-ts"] } +nym-mixnet-contract-common = { workspace = true, features = ["generate-ts"] } +nym-vesting-contract-common = { workspace = true, features = ["generate-ts"] } +nym-types = { workspace = true, features = ["generate-ts"] } nym-wallet-types = { path = "../../nym-wallet/nym-wallet-types", features = ["generate-ts"] } diff --git a/wasm/client/Cargo.toml b/wasm/client/Cargo.toml index 4b5d7d19e9..3bc15e7ade 100644 --- a/wasm/client/Cargo.toml +++ b/wasm/client/Cargo.toml @@ -11,6 +11,7 @@ license = "Apache-2.0" repository = "https://github.com/nymtech/nym" description = "A webassembly client which can be used to interact with the the Nym privacy platform. Wasm is used for Sphinx packet generation." rust-version = "1.85" +publish = false [lib] crate-type = ["cdylib", "rlib"] @@ -32,12 +33,13 @@ thiserror = { workspace = true } tsify = { workspace = true, features = ["js"] } web-sys = { workspace = true } -nym-bin-common = { path = "../../common/bin-common" } -wasm-client-core = { path = "../../common/wasm/client-core" } -wasm-utils = { path = "../../common/wasm/utils" } -nym-gateway-requests = { path = "../../common/gateway-requests" } +nym-bin-common = { workspace = true } +nym-wasm-client-core = { workspace = true } +nym-wasm-utils = { workspace = true } +nym-gateway-requests = { workspace = true } + +nym-node-tester-utils = { workspace = true, optional = true } -nym-node-tester-utils = { path = "../../common/node-tester-utils", optional = true } nym-node-tester-wasm = { path = "../node-tester", optional = true } tokio_with_wasm = { workspace = true, features = ["full"] } diff --git a/wasm/client/src/client.rs b/wasm/client/src/client.rs index 8c006b7f09..7d6df14b35 100644 --- a/wasm/client/src/client.rs +++ b/wasm/client/src/client.rs @@ -14,30 +14,30 @@ use crate::response_pusher::ResponsePusher; use js_sys::Promise; use nym_bin_common::bin_info; use nym_gateway_requests::ClientRequest; +use nym_wasm_client_core::client::base_client::storage::GatewaysDetailsStore; +use nym_wasm_client_core::client::{ + base_client::{BaseClientBuilder, ClientInput, ClientOutput, ClientState}, + inbound_messages::InputMessage, +}; +use nym_wasm_client_core::config::r#override::DebugWasmOverride; +use nym_wasm_client_core::helpers::{ + add_gateway, generate_new_client_keys, parse_recipient, parse_sender_tag, +}; +use nym_wasm_client_core::nym_task::connections::TransmissionLane; +use nym_wasm_client_core::nym_task::ShutdownTracker; +use nym_wasm_client_core::storage::core_client_traits::FullWasmClientStorage; +use nym_wasm_client_core::storage::wasm_client_traits::WasmClientStorage; +use nym_wasm_client_core::storage::ClientStorage; +use nym_wasm_client_core::topology::{SerializableTopologyExt, WasmFriendlyNymTopology}; +use nym_wasm_client_core::{IdentityKey, NymTopology, PacketType, QueryReqwestRpcNyxdClient}; +use nym_wasm_utils::error::PromisableResult; +use nym_wasm_utils::{check_promise_result, console_error, console_log}; use serde::{Deserialize, Serialize}; use std::sync::Arc; use tokio_with_wasm::sync::mpsc; use tsify::Tsify; use wasm_bindgen::prelude::*; use wasm_bindgen_futures::future_to_promise; -use wasm_client_core::client::base_client::storage::GatewaysDetailsStore; -use wasm_client_core::client::{ - base_client::{BaseClientBuilder, ClientInput, ClientOutput, ClientState}, - inbound_messages::InputMessage, -}; -use wasm_client_core::config::r#override::DebugWasmOverride; -use wasm_client_core::helpers::{ - add_gateway, generate_new_client_keys, parse_recipient, parse_sender_tag, -}; -use wasm_client_core::nym_task::connections::TransmissionLane; -use wasm_client_core::nym_task::ShutdownTracker; -use wasm_client_core::storage::core_client_traits::FullWasmClientStorage; -use wasm_client_core::storage::wasm_client_traits::WasmClientStorage; -use wasm_client_core::storage::ClientStorage; -use wasm_client_core::topology::{SerializableTopologyExt, WasmFriendlyNymTopology}; -use wasm_client_core::{IdentityKey, NymTopology, PacketType, QueryReqwestRpcNyxdClient}; -use wasm_utils::error::PromisableResult; -use wasm_utils::{check_promise_result, console_error, console_log}; #[cfg(feature = "node-tester")] use crate::helpers::{NymClientTestRequest, WasmTopologyTestExt}; diff --git a/wasm/client/src/config.rs b/wasm/client/src/config.rs index 9bb19ad8c1..06249c6bd4 100644 --- a/wasm/client/src/config.rs +++ b/wasm/client/src/config.rs @@ -8,10 +8,12 @@ #![allow(clippy::empty_docs)] use crate::error::WasmClientError; +use nym_wasm_client_core::config::{ + new_base_client_config, BaseClientConfig, ConfigDebug, DebugWasm, +}; use serde::{Deserialize, Serialize}; use tsify::Tsify; use wasm_bindgen::prelude::*; -use wasm_client_core::config::{new_base_client_config, BaseClientConfig, ConfigDebug, DebugWasm}; pub const DEFAULT_CLIENT_ID: &str = "nym-mixnet-client"; diff --git a/wasm/client/src/encoded_payload_helper.rs b/wasm/client/src/encoded_payload_helper.rs index 77294f624c..7b2080a2cd 100644 --- a/wasm/client/src/encoded_payload_helper.rs +++ b/wasm/client/src/encoded_payload_helper.rs @@ -325,10 +325,10 @@ mod tests { message.extend_from_slice(serialized_metadata.as_bytes()); message.extend_from_slice(&payload_data); - wasm_utils::console_log!("message length: {}", message.len()); - wasm_utils::console_log!("metadata length: {}", metadata_length); - wasm_utils::console_log!("payload length: {}", payload_data.len()); - wasm_utils::console_log!( + nym_wasm_utils::console_log!("message length: {}", message.len()); + nym_wasm_utils::console_log!("metadata length: {}", metadata_length); + nym_wasm_utils::console_log!("payload length: {}", payload_data.len()); + nym_wasm_utils::console_log!( "8 + metadata_length + payload_length = {}", payload_data.len() + metadata_length as usize + 8 ); diff --git a/wasm/client/src/error.rs b/wasm/client/src/error.rs index a8d7c2190b..48598833b2 100644 --- a/wasm/client/src/error.rs +++ b/wasm/client/src/error.rs @@ -1,11 +1,11 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use nym_wasm_client_core::error::WasmCoreError; +use nym_wasm_client_core::topology::WasmTopologyError; +use nym_wasm_client_core::ClientCoreError; +use nym_wasm_utils::wasm_error; use thiserror::Error; -use wasm_client_core::error::WasmCoreError; -use wasm_client_core::topology::WasmTopologyError; -use wasm_client_core::ClientCoreError; -use wasm_utils::wasm_error; #[cfg(feature = "node-tester")] use nym_node_tester_utils::error::NetworkTestingError; diff --git a/wasm/client/src/helpers.rs b/wasm/client/src/helpers.rs index b6cdba3d33..d568d76250 100644 --- a/wasm/client/src/helpers.rs +++ b/wasm/client/src/helpers.rs @@ -2,16 +2,16 @@ // SPDX-License-Identifier: Apache-2.0 use js_sys::Promise; +use nym_wasm_client_core::client::base_client::{ClientInput, ClientState}; +use nym_wasm_client_core::client::inbound_messages::InputMessage; +use nym_wasm_client_core::error::WasmCoreError; +use nym_wasm_client_core::topology::{Role, WasmFriendlyNymTopology}; +use nym_wasm_client_core::NymTopology; +use nym_wasm_utils::error::simple_js_error; +use nym_wasm_utils::{check_promise_result, console_log}; use std::sync::Arc; use wasm_bindgen::JsValue; use wasm_bindgen_futures::future_to_promise; -use wasm_client_core::client::base_client::{ClientInput, ClientState}; -use wasm_client_core::client::inbound_messages::InputMessage; -use wasm_client_core::error::WasmCoreError; -use wasm_client_core::topology::{Role, WasmFriendlyNymTopology}; -use wasm_client_core::NymTopology; -use wasm_utils::error::simple_js_error; -use wasm_utils::{check_promise_result, console_log}; #[cfg(feature = "node-tester")] use nym_node_tester_wasm::types::{NodeTestMessage, WasmTestMessageExt}; diff --git a/wasm/client/src/lib.rs b/wasm/client/src/lib.rs index ee5ff5a1ba..3fe894a865 100644 --- a/wasm/client/src/lib.rs +++ b/wasm/client/src/lib.rs @@ -14,17 +14,17 @@ mod helpers; #[cfg(target_arch = "wasm32")] mod response_pusher; #[cfg(target_arch = "wasm32")] -use wasm_bindgen::prelude::*; +use nym_wasm_utils::set_panic_hook; #[cfg(target_arch = "wasm32")] -use wasm_utils::set_panic_hook; +use wasm_bindgen::prelude::*; #[wasm_bindgen(start)] #[cfg(target_arch = "wasm32")] pub fn main_js() { - wasm_utils::console_log!("[rust main]: setting panic hook"); + nym_wasm_utils::console_log!("[rust main]: setting panic hook"); set_panic_hook(); - wasm_utils::console_log!("[rust main]: rust module loaded"); - wasm_utils::console_log!( + nym_wasm_utils::console_log!("[rust main]: rust module loaded"); + nym_wasm_utils::console_log!( "wasm client version used: {}", nym_bin_common::bin_info_owned!() ); diff --git a/wasm/client/src/response_pusher.rs b/wasm/client/src/response_pusher.rs index e0a795a79b..be2ba38312 100644 --- a/wasm/client/src/response_pusher.rs +++ b/wasm/client/src/response_pusher.rs @@ -4,13 +4,13 @@ use futures::channel::mpsc; use futures::StreamExt; use js_sys::Uint8Array; -use wasm_bindgen::JsValue; -use wasm_bindgen_futures::spawn_local; -use wasm_client_core::client::base_client::ClientOutput; -use wasm_client_core::client::received_buffer::{ +use nym_wasm_client_core::client::base_client::ClientOutput; +use nym_wasm_client_core::client::received_buffer::{ ReceivedBufferMessage, ReceivedBufferRequestSender, ReconstructedMessagesReceiver, }; -use wasm_utils::console_error; +use nym_wasm_utils::console_error; +use wasm_bindgen::JsValue; +use wasm_bindgen_futures::spawn_local; pub(crate) struct ResponsePusher { reconstructed_receiver: ReconstructedMessagesReceiver, diff --git a/wasm/full-nym-wasm/Cargo.toml b/wasm/full-nym-wasm/Cargo.toml index 3e98e42d24..f3a7357e6c 100644 --- a/wasm/full-nym-wasm/Cargo.toml +++ b/wasm/full-nym-wasm/Cargo.toml @@ -7,6 +7,7 @@ keywords = ["nym", "sphinx", "wasm", "webassembly", "privacy"] license = "Apache-2.0" repository = "https://github.com/nymtech/nym" rust-version = "1.85" +publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -14,9 +15,10 @@ rust-version = "1.85" crate-type = ["cdylib", "rlib"] [dependencies] -nym-client-wasm = { path = "../client", optional = true } +nym-client-wasm = { workspace = true, optional = true } + nym-node-tester-wasm = { path = "../node-tester", optional = true } -mix-fetch-wasm = { path = "../mix-fetch", optional = true } +mix-fetch-wasm = { workspace = true, optional = true } [features] default = ["client", "node-tester"] @@ -26,4 +28,3 @@ mix-fetch = ["mix-fetch-wasm"] [package.metadata.wasm-pack.profile.release] wasm-opt = false - diff --git a/wasm/mix-fetch/Cargo.toml b/wasm/mix-fetch/Cargo.toml index 00d91b2e25..f1d81694ad 100644 --- a/wasm/mix-fetch/Cargo.toml +++ b/wasm/mix-fetch/Cargo.toml @@ -7,6 +7,7 @@ keywords = ["nym", "fetch", "wasm", "webassembly", "privacy"] license = "Apache-2.0" repository = "https://github.com/nymtech/nym" rust-version = "1.85" +publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -27,14 +28,14 @@ wasm-bindgen-futures = { workspace = true } thiserror = { workspace = true } tsify = { workspace = true, features = ["js"] } -nym-bin-common = { path = "../../common/bin-common" } -nym-http-api-client = { path = "../../common/http-api-client" } -nym-socks5-requests = { path = "../../common/socks5/requests" } -nym-ordered-buffer = { path = "../../common/socks5/ordered-buffer" } -nym-service-providers-common = { path = "../../service-providers/common" } -nym-validator-client = { path = "../../common/client-libs/validator-client", default-features = false } -wasm-client-core = { path = "../../common/wasm/client-core" } -wasm-utils = { path = "../../common/wasm/utils" } +nym-bin-common = { workspace = true } +nym-http-api-client = { workspace = true } +nym-socks5-requests = { workspace = true } +nym-ordered-buffer = { workspace = true } +nym-service-providers-common = { workspace = true } +nym-validator-client = { workspace = true, default-features = false } +nym-wasm-client-core = { workspace = true } +nym-wasm-utils = { workspace = true } [package.metadata.wasm-pack.profile.release] wasm-opt = false diff --git a/wasm/mix-fetch/src/active_requests.rs b/wasm/mix-fetch/src/active_requests.rs index 3d4e8a5f31..72fe6ac9d9 100644 --- a/wasm/mix-fetch/src/active_requests.rs +++ b/wasm/mix-fetch/src/active_requests.rs @@ -6,11 +6,11 @@ use crate::go_bridge::{goWasmCloseRemoteSocket, goWasmInjectConnError, goWasmInj use crate::RequestId; use nym_ordered_buffer::OrderedMessageBuffer; use nym_socks5_requests::SocketData; +use nym_wasm_utils::{console_error, console_log}; use rand::{thread_rng, RngCore}; use std::collections::HashMap; use std::sync::Arc; use tokio::sync::Mutex; -use wasm_utils::{console_error, console_log}; #[derive(Clone, Default)] pub struct ActiveRequests { diff --git a/wasm/mix-fetch/src/client.rs b/wasm/mix-fetch/src/client.rs index 736c82c413..6fd5d642ac 100644 --- a/wasm/mix-fetch/src/client.rs +++ b/wasm/mix-fetch/src/client.rs @@ -11,22 +11,22 @@ use crate::{config, RequestId}; use js_sys::Promise; use nym_bin_common::bin_info; use nym_socks5_requests::RemoteAddress; +use nym_wasm_client_core::client::base_client::storage::GatewaysDetailsStore; +use nym_wasm_client_core::client::base_client::{BaseClientBuilder, ClientInput, ClientOutput}; +use nym_wasm_client_core::client::inbound_messages::InputMessage; +use nym_wasm_client_core::helpers::{add_gateway, generate_new_client_keys}; +use nym_wasm_client_core::nym_task::connections::TransmissionLane; +use nym_wasm_client_core::nym_task::ShutdownTracker; +use nym_wasm_client_core::storage::core_client_traits::FullWasmClientStorage; +use nym_wasm_client_core::storage::wasm_client_traits::WasmClientStorage; +use nym_wasm_client_core::storage::ClientStorage; +use nym_wasm_client_core::{IdentityKey, QueryReqwestRpcNyxdClient, Recipient}; +use nym_wasm_utils::console_log; +use nym_wasm_utils::error::PromisableResult; use std::sync::atomic::{AtomicBool, Ordering}; use tokio::sync::Mutex; use wasm_bindgen::prelude::*; use wasm_bindgen_futures::future_to_promise; -use wasm_client_core::client::base_client::storage::GatewaysDetailsStore; -use wasm_client_core::client::base_client::{BaseClientBuilder, ClientInput, ClientOutput}; -use wasm_client_core::client::inbound_messages::InputMessage; -use wasm_client_core::helpers::{add_gateway, generate_new_client_keys}; -use wasm_client_core::nym_task::connections::TransmissionLane; -use wasm_client_core::nym_task::ShutdownTracker; -use wasm_client_core::storage::core_client_traits::FullWasmClientStorage; -use wasm_client_core::storage::wasm_client_traits::WasmClientStorage; -use wasm_client_core::storage::ClientStorage; -use wasm_client_core::{IdentityKey, QueryReqwestRpcNyxdClient, Recipient}; -use wasm_utils::console_log; -use wasm_utils::error::PromisableResult; #[wasm_bindgen] pub struct MixFetchClient { diff --git a/wasm/mix-fetch/src/config.rs b/wasm/mix-fetch/src/config.rs index 621e041913..9d312ac280 100644 --- a/wasm/mix-fetch/src/config.rs +++ b/wasm/mix-fetch/src/config.rs @@ -10,13 +10,15 @@ #![allow(clippy::empty_docs)] use crate::error::MixFetchError; +use nym_wasm_client_core::config::{ + new_base_client_config, BaseClientConfig, ConfigDebug, DebugWasm, +}; +use nym_wasm_client_core::helpers::parse_recipient; +use nym_wasm_client_core::Recipient; use serde::{Deserialize, Serialize}; use std::time::Duration; use tsify::Tsify; use wasm_bindgen::prelude::*; -use wasm_client_core::config::{new_base_client_config, BaseClientConfig, ConfigDebug, DebugWasm}; -use wasm_client_core::helpers::parse_recipient; -use wasm_client_core::Recipient; const DEFAULT_MIX_FETCH_TIMEOUT: Duration = Duration::from_secs(5); const DEFAULT_MIX_FETCH_ID: &str = "_default-nym-mix-fetch"; diff --git a/wasm/mix-fetch/src/error.rs b/wasm/mix-fetch/src/error.rs index c3347acddf..ceb449df04 100644 --- a/wasm/mix-fetch/src/error.rs +++ b/wasm/mix-fetch/src/error.rs @@ -5,10 +5,10 @@ use crate::RequestId; use nym_ordered_buffer::OrderedMessageError; use nym_socks5_requests::ConnectionError; use nym_validator_client::nym_api::error::NymAPIError; +use nym_wasm_client_core::error::WasmCoreError; +use nym_wasm_client_core::ClientCoreError; +use nym_wasm_utils::wasm_error; use thiserror::Error; -use wasm_client_core::error::WasmCoreError; -use wasm_client_core::ClientCoreError; -use wasm_utils::wasm_error; #[derive(Debug, Error)] pub enum MixFetchError { diff --git a/wasm/mix-fetch/src/fetch.rs b/wasm/mix-fetch/src/fetch.rs index 83d7caaff1..a03e8f70fb 100644 --- a/wasm/mix-fetch/src/fetch.rs +++ b/wasm/mix-fetch/src/fetch.rs @@ -12,15 +12,15 @@ use crate::config::{MixFetchConfig, MixFetchConfigOpts, MixFetchDebugOverride}; use crate::error::MixFetchError; use crate::helpers::get_network_requester; use js_sys::Promise; +use nym_wasm_client_core::config::r#override::DebugWasmOverride; +use nym_wasm_utils::console_log; +use nym_wasm_utils::error::PromisableResultError; use serde::{Deserialize, Serialize}; use std::sync::OnceLock; use tsify::Tsify; use wasm_bindgen::prelude::wasm_bindgen; use wasm_bindgen::JsValue; use wasm_bindgen_futures::future_to_promise; -use wasm_client_core::config::r#override::DebugWasmOverride; -use wasm_utils::console_log; -use wasm_utils::error::PromisableResultError; pub type RequestId = u64; diff --git a/wasm/mix-fetch/src/go_bridge.rs b/wasm/mix-fetch/src/go_bridge.rs index 6ea8f0704d..086001c0a5 100644 --- a/wasm/mix-fetch/src/go_bridge.rs +++ b/wasm/mix-fetch/src/go_bridge.rs @@ -4,9 +4,9 @@ use crate::error::MixFetchError; use crate::mix_fetch_client; use js_sys::Promise; +use nym_wasm_utils::error::{simple_rejected_promise, PromisableResult}; use wasm_bindgen::prelude::*; use wasm_bindgen_futures::future_to_promise; -use wasm_utils::error::{simple_rejected_promise, PromisableResult}; /// Called by go runtime whenever local connection produces any data that has to be sent to the remote. // diff --git a/wasm/mix-fetch/src/helpers.rs b/wasm/mix-fetch/src/helpers.rs index 4f6daf85b1..9476d61715 100644 --- a/wasm/mix-fetch/src/helpers.rs +++ b/wasm/mix-fetch/src/helpers.rs @@ -5,9 +5,9 @@ use crate::error::MixFetchError; use crate::error::MixFetchError::NoNetworkRequesters; use nym_http_api_client::Client; use nym_validator_client::nym_api::NymApiClientExt; +use nym_wasm_utils::console_log; use rand::seq::SliceRandom; use rand::thread_rng; -use wasm_utils::console_log; // since this client is temporary (and will be properly integrated into nym-api eventually), // we're using hardcoded URL for mainnet diff --git a/wasm/mix-fetch/src/lib.rs b/wasm/mix-fetch/src/lib.rs index cbb9d151d4..0018748027 100644 --- a/wasm/mix-fetch/src/lib.rs +++ b/wasm/mix-fetch/src/lib.rs @@ -28,11 +28,11 @@ use wasm_bindgen::prelude::*; #[wasm_bindgen(start)] #[cfg(target_arch = "wasm32")] pub fn main() { - wasm_utils::console_log!("[rust main]: rust module loaded"); - wasm_utils::console_log!( + nym_wasm_utils::console_log!("[rust main]: rust module loaded"); + nym_wasm_utils::console_log!( "mix fetch version used: {}", nym_bin_common::bin_info_owned!() ); - wasm_utils::console_log!("[rust main]: setting panic hook"); - wasm_utils::set_panic_hook(); + nym_wasm_utils::console_log!("[rust main]: setting panic hook"); + nym_wasm_utils::set_panic_hook(); } diff --git a/wasm/mix-fetch/src/request_writer.rs b/wasm/mix-fetch/src/request_writer.rs index fa55b746d2..12138bb005 100644 --- a/wasm/mix-fetch/src/request_writer.rs +++ b/wasm/mix-fetch/src/request_writer.rs @@ -6,13 +6,13 @@ use futures::channel::mpsc; use futures::StreamExt; use nym_service_providers_common::interface::ResponseContent; use nym_socks5_requests::{Socks5ProviderResponse, Socks5ResponseContent}; -use wasm_bindgen_futures::spawn_local; -use wasm_client_core::client::base_client::ClientOutput; -use wasm_client_core::client::received_buffer::{ +use nym_wasm_client_core::client::base_client::ClientOutput; +use nym_wasm_client_core::client::received_buffer::{ ReceivedBufferMessage, ReceivedBufferRequestSender, ReconstructedMessagesReceiver, }; -use wasm_client_core::ReconstructedMessage; -use wasm_utils::console_error; +use nym_wasm_client_core::ReconstructedMessage; +use nym_wasm_utils::console_error; +use wasm_bindgen_futures::spawn_local; pub(crate) struct RequestWriter { reconstructed_receiver: ReconstructedMessagesReceiver, diff --git a/wasm/mix-fetch/src/socks_helpers.rs b/wasm/mix-fetch/src/socks_helpers.rs index 827e8ac120..8dffb0ad2e 100644 --- a/wasm/mix-fetch/src/socks_helpers.rs +++ b/wasm/mix-fetch/src/socks_helpers.rs @@ -5,7 +5,7 @@ use nym_service_providers_common::interface::ProviderInterfaceVersion; use nym_socks5_requests::{ ConnectionId, RemoteAddress, SocketData, Socks5ProtocolVersion, Socks5ProviderRequest, }; -use wasm_client_core::Recipient; +use nym_wasm_client_core::Recipient; pub(crate) const PROVIDER_INTERFACE_VERSION: ProviderInterfaceVersion = ProviderInterfaceVersion::new_current(); diff --git a/wasm/node-tester/Cargo.toml b/wasm/node-tester/Cargo.toml index dd1fb9fded..8a61eb06ab 100644 --- a/wasm/node-tester/Cargo.toml +++ b/wasm/node-tester/Cargo.toml @@ -7,6 +7,7 @@ keywords = ["nym", "sphinx", "webassembly", "privacy", "tester"] license = "Apache-2.0" repository = "https://github.com/nymtech/nym" rust-version = "1.85" +publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -27,9 +28,9 @@ tsify = { workspace = true, features = ["js"] } wasmtimer = { workspace = true } -nym-node-tester-utils = { path = "../../common/node-tester-utils" } -wasm-client-core = { path = "../../common/wasm/client-core" } -wasm-utils = { path = "../../common/wasm/utils" } +nym-node-tester-utils = { workspace = true } +nym-wasm-client-core = { workspace = true } +nym-wasm-utils = { workspace = true } [package.metadata.wasm-pack.profile.release] wasm-opt = false diff --git a/wasm/node-tester/src/ephemeral_receiver.rs b/wasm/node-tester/src/ephemeral_receiver.rs index a7a9545de6..4ad0f9d041 100644 --- a/wasm/node-tester/src/ephemeral_receiver.rs +++ b/wasm/node-tester/src/ephemeral_receiver.rs @@ -6,10 +6,10 @@ use futures::StreamExt; use nym_node_tester_utils::processor::Received; use nym_node_tester_utils::receiver::ReceivedReceiver; use nym_node_tester_utils::FragmentIdentifier; +use nym_wasm_utils::{console_error, console_log, console_warn}; use std::collections::HashSet; use std::time::Duration; use tokio::sync::MutexGuard as AsyncMutexGuard; -use wasm_utils::{console_error, console_log, console_warn}; use wasmtimer::tokio::sleep; pub(crate) struct EphemeralTestReceiver<'a> { diff --git a/wasm/node-tester/src/error.rs b/wasm/node-tester/src/error.rs index f5679833d8..65bf60ecbb 100644 --- a/wasm/node-tester/src/error.rs +++ b/wasm/node-tester/src/error.rs @@ -2,11 +2,11 @@ // SPDX-License-Identifier: Apache-2.0 use nym_node_tester_utils::error::NetworkTestingError; +use nym_wasm_client_core::error::WasmCoreError; +use nym_wasm_client_core::topology::WasmTopologyError; +use nym_wasm_client_core::{ClientCoreError, GatewayClientError}; +use nym_wasm_utils::wasm_error; use thiserror::Error; -use wasm_client_core::error::WasmCoreError; -use wasm_client_core::topology::WasmTopologyError; -use wasm_client_core::{ClientCoreError, GatewayClientError}; -use wasm_utils::wasm_error; #[derive(Debug, Error)] pub enum NodeTesterError { diff --git a/wasm/node-tester/src/helpers.rs b/wasm/node-tester/src/helpers.rs index fa7790e2f6..6562ae65ec 100644 --- a/wasm/node-tester/src/helpers.rs +++ b/wasm/node-tester/src/helpers.rs @@ -10,12 +10,12 @@ use crate::types::WasmTestMessageExt; use js_sys::Promise; use nym_node_tester_utils::processor::Received; use nym_node_tester_utils::receiver::ReceivedReceiver; +use nym_wasm_utils::console_warn; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use tokio::sync::{Mutex as AsyncMutex, MutexGuard as AsyncMutexGuard}; use wasm_bindgen::prelude::*; use wasm_bindgen_futures::future_to_promise; -use wasm_utils::console_warn; #[derive(Clone)] pub(super) struct ReceivedReceiverWrapper(Arc>>); diff --git a/wasm/node-tester/src/lib.rs b/wasm/node-tester/src/lib.rs index f2cdee034d..0828ecbc43 100644 --- a/wasm/node-tester/src/lib.rs +++ b/wasm/node-tester/src/lib.rs @@ -13,4 +13,4 @@ pub mod tester; pub mod types; #[cfg(target_arch = "wasm32")] -pub use wasm_client_core::set_panic_hook; +pub use nym_wasm_client_core::set_panic_hook; diff --git a/wasm/node-tester/src/tester.rs b/wasm/node-tester/src/tester.rs index 599e9ce3f9..f525ad1c77 100644 --- a/wasm/node-tester/src/tester.rs +++ b/wasm/node-tester/src/tester.rs @@ -15,6 +15,21 @@ use js_sys::Promise; use nym_node_tester_utils::receiver::SimpleMessageReceiver; use nym_node_tester_utils::tester::LegacyMixLayer; use nym_node_tester_utils::{NodeTester, PacketSize, PreparedFragment}; +use nym_wasm_client_core::client::base_client::storage::gateways_storage::GatewayDetails; +use nym_wasm_client_core::client::mix_traffic::transceiver::PacketRouter; +use nym_wasm_client_core::helpers::{ + current_network_topology_async, setup_from_topology, EphemeralCredentialStorage, +}; +use nym_wasm_client_core::nym_task::ShutdownManager; +use nym_wasm_client_core::storage::ClientStorage; +use nym_wasm_client_core::topology::WasmFriendlyNymTopology; +use nym_wasm_client_core::{ + nym_task, BandwidthController, ClientKeys, ClientStatsSender, GatewayClient, + GatewayClientConfig, GatewayConfig, IdentityKey, InitialisationResult, NodeIdentity, + NymTopology, QueryReqwestRpcNyxdClient, Recipient, +}; +use nym_wasm_utils::check_promise_result; +use nym_wasm_utils::error::PromisableResult; use rand::rngs::OsRng; use serde::{Deserialize, Serialize}; use std::collections::HashSet; @@ -25,21 +40,6 @@ use tokio::sync::Mutex as AsyncMutex; use tsify::Tsify; use wasm_bindgen::prelude::*; use wasm_bindgen_futures::future_to_promise; -use wasm_client_core::client::base_client::storage::gateways_storage::GatewayDetails; -use wasm_client_core::client::mix_traffic::transceiver::PacketRouter; -use wasm_client_core::helpers::{ - current_network_topology_async, setup_from_topology, EphemeralCredentialStorage, -}; -use wasm_client_core::nym_task::ShutdownManager; -use wasm_client_core::storage::ClientStorage; -use wasm_client_core::topology::WasmFriendlyNymTopology; -use wasm_client_core::{ - nym_task, BandwidthController, ClientKeys, ClientStatsSender, GatewayClient, - GatewayClientConfig, GatewayConfig, IdentityKey, InitialisationResult, NodeIdentity, - NymTopology, QueryReqwestRpcNyxdClient, Recipient, -}; -use wasm_utils::check_promise_result; -use wasm_utils::error::PromisableResult; pub const NODE_TESTER_ID: &str = "_nym-node-tester"; diff --git a/wasm/node-tester/src/types.rs b/wasm/node-tester/src/types.rs index e1a5dcf766..2bcbe84395 100644 --- a/wasm/node-tester/src/types.rs +++ b/wasm/node-tester/src/types.rs @@ -2,10 +2,10 @@ // SPDX-License-Identifier: Apache-2.0 use nym_node_tester_utils::TestMessage; +use nym_wasm_utils::console_log; use serde::{Deserialize, Serialize}; use std::fmt::{Display, Formatter}; use wasm_bindgen::prelude::wasm_bindgen; -use wasm_utils::console_log; pub type NodeTestMessage = TestMessage; diff --git a/wasm/zknym-lib/Cargo.toml b/wasm/zknym-lib/Cargo.toml index 572e26c6bd..b9d086535e 100644 --- a/wasm/zknym-lib/Cargo.toml +++ b/wasm/zknym-lib/Cargo.toml @@ -1,12 +1,13 @@ [package] name = "zknym-lib" -version = "0.1.0" +version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true documentation.workspace = true edition.workspace = true license.workspace = true +publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -30,12 +31,12 @@ zeroize.workspace = true rand = { workspace = true } -nym-bin-common = { path = "../../common/bin-common" } -nym-compact-ecash = { path = "../../common/nym_offline_compact_ecash" } -nym-credentials = { path = "../../common/credentials" } -nym-crypto = { path = "../../common/crypto", features = ["asymmetric", "rand"] } -nym-http-api-client = { path = "../../common/http-api-client" } -wasm-utils = { path = "../../common/wasm/utils" } +nym-bin-common = { workspace = true } +nym-compact-ecash = { workspace = true } +nym-credentials = { workspace = true } +nym-crypto = { workspace = true, features = ["asymmetric", "rand"] } +nym-http-api-client = { workspace = true } +nym-wasm-utils = { workspace = true } [dev-dependencies] anyhow = { workspace = true } @@ -43,4 +44,4 @@ tokio = { workspace = true, features = ["full"] } [package.metadata.wasm-pack.profile.release] -wasm-opt = false \ No newline at end of file +wasm-opt = false diff --git a/wasm/zknym-lib/src/bandwidth_voucher.rs b/wasm/zknym-lib/src/bandwidth_voucher.rs index d64fdd58f3..7c5307ad7f 100644 --- a/wasm/zknym-lib/src/bandwidth_voucher.rs +++ b/wasm/zknym-lib/src/bandwidth_voucher.rs @@ -19,7 +19,7 @@ // use std::collections::HashMap; // use tsify::Tsify; // use wasm_bindgen::prelude::wasm_bindgen; -// use wasm_utils::console_error; +// use nym_wasm_utils::console_error; // use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing}; // // // tiny 'hacks' to just allow passing responses from vpn-api queries diff --git a/wasm/zknym-lib/src/error.rs b/wasm/zknym-lib/src/error.rs index b06ca52ef2..31b8091927 100644 --- a/wasm/zknym-lib/src/error.rs +++ b/wasm/zknym-lib/src/error.rs @@ -2,8 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 use nym_http_api_client::HttpClientError; +use nym_wasm_utils::wasm_error; use thiserror::Error; -use wasm_utils::wasm_error; #[derive(Debug, Error)] pub enum ZkNymError { diff --git a/wasm/zknym-lib/src/lib.rs b/wasm/zknym-lib/src/lib.rs index 4d0b342232..7bff719be4 100644 --- a/wasm/zknym-lib/src/lib.rs +++ b/wasm/zknym-lib/src/lib.rs @@ -23,8 +23,8 @@ pub(crate) mod vpn_api_client; #[wasm_bindgen(start)] // #[cfg(target_arch = "wasm32")] pub fn main() { - wasm_utils::console_log!("[rust main]: rust module loaded"); - wasm_utils::console_log!( + nym_wasm_utils::console_log!("[rust main]: rust module loaded"); + nym_wasm_utils::console_log!( "wasm zk-nym version used: {}", nym_bin_common::bin_info_owned!() ); From 6b40013cebc58c1f6a2be762bc31e69e485d1c4d Mon Sep 17 00:00:00 2001 From: Simon Wicky Date: Mon, 19 Jan 2026 14:54:54 +0100 Subject: [PATCH 09/51] small qol changes (#6340) * small qol changes * same * forgot this * more imports move --- common/nym-lp/src/message.rs | 2 +- common/nym-lp/src/noise_protocol.rs | 8 ++++---- common/nym-lp/src/session.rs | 9 ++++----- gateway/src/node/lp_listener/handler.rs | 9 ++++----- integration-tests/src/lp_registration.rs | 6 +++--- nym-gateway-probe/src/lib.rs | 4 ++-- nym-registration-client/src/lib.rs | 8 +++----- nym-registration-client/src/lp_client/client.rs | 4 ++-- nym-registration-client/src/lp_client/mod.rs | 2 +- tools/nym-lp-client/src/client.rs | 4 ++-- 10 files changed, 26 insertions(+), 30 deletions(-) diff --git a/common/nym-lp/src/message.rs b/common/nym-lp/src/message.rs index 578395a957..05d1a39c6c 100644 --- a/common/nym-lp/src/message.rs +++ b/common/nym-lp/src/message.rs @@ -5,6 +5,7 @@ use crate::{BOOTSTRAP_RECEIVER_IDX, LpError}; use bytes::{BufMut, BytesMut}; use num_enum::{IntoPrimitive, TryFromPrimitive}; use nym_crypto::asymmetric::{ed25519, x25519}; +use rand::RngCore; use serde::{Deserialize, Serialize}; use std::fmt::{self, Display}; @@ -58,7 +59,6 @@ impl ClientHelloData { salt[..8].copy_from_slice(×tamp.to_le_bytes()); // Last 24 bytes: random nonce - use rand::RngCore; rand::thread_rng().fill_bytes(&mut salt[8..]); Self { diff --git a/common/nym-lp/src/noise_protocol.rs b/common/nym-lp/src/noise_protocol.rs index 41e601494b..293bd9ccd4 100644 --- a/common/nym-lp/src/noise_protocol.rs +++ b/common/nym-lp/src/noise_protocol.rs @@ -294,8 +294,8 @@ pub fn create_noise_state( remote_public_key: &[u8], psk: &[u8], ) -> Result { - let pattern_name = "Noise_XKpsk3_25519_ChaChaPoly_SHA256"; - let psk_index = 3; + let pattern_name = crate::NOISE_PATTERN; + let psk_index = crate::NOISE_PSK_INDEX; let noise_params: NoiseParams = pattern_name.parse().unwrap(); let builder = snow::Builder::new(noise_params.clone()); @@ -314,8 +314,8 @@ pub fn create_noise_state_responder( remote_public_key: &[u8], psk: &[u8], ) -> Result { - let pattern_name = "Noise_XKpsk3_25519_ChaChaPoly_SHA256"; - let psk_index = 3; + let pattern_name = crate::NOISE_PATTERN; + let psk_index = crate::NOISE_PSK_INDEX; let noise_params: NoiseParams = pattern_name.parse().unwrap(); let builder = snow::Builder::new(noise_params.clone()); diff --git a/common/nym-lp/src/session.rs b/common/nym-lp/src/session.rs index 3a26b6b3e9..774f148146 100644 --- a/common/nym-lp/src/session.rs +++ b/common/nym-lp/src/session.rs @@ -20,9 +20,11 @@ use nym_kkt::ciphersuite::{DecapsulationKey, EncapsulationKey}; use nym_kkt::encryption::KKTSessionSecret; use nym_kkt::kkt::decrypt_kkt_response_frame; use parking_lot::Mutex; +use rand::RngCore; use snow::Builder; use std::sync::Arc; use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; +use std::time::{SystemTime, UNIX_EPOCH}; use zeroize::{Zeroize, ZeroizeOnDrop}; /// PQ shared secret wrapper with automatic memory zeroization. @@ -236,9 +238,6 @@ pub struct LpSession { /// # Returns /// A 32-byte array containing fresh salt material pub fn generate_fresh_salt() -> [u8; 32] { - use rand::RngCore; - use std::time::{SystemTime, UNIX_EPOCH}; - let mut salt = [0u8; 32]; // First 8 bytes: current timestamp as u64 little-endian @@ -366,8 +365,8 @@ impl LpSession { ) -> Result { // XKpsk3 pattern requires remote static key known upfront (XK) // and PSK mixed at position 3. This provides forward secrecy with PSK authentication. - let pattern_name = "Noise_XKpsk3_25519_ChaChaPoly_SHA256"; - let psk_index = 3; + let pattern_name = crate::NOISE_PATTERN; + let psk_index = crate::NOISE_PSK_INDEX; let params = pattern_name.parse()?; let builder = Builder::new(params); diff --git a/gateway/src/node/lp_listener/handler.rs b/gateway/src/node/lp_listener/handler.rs index 5f70199637..7c98bd0f13 100644 --- a/gateway/src/node/lp_listener/handler.rs +++ b/gateway/src/node/lp_listener/handler.rs @@ -6,6 +6,7 @@ use super::registration::process_registration; use super::LpHandlerState; use crate::error::GatewayError; use nym_crypto::asymmetric::{ed25519, x25519}; +use nym_lp::state_machine::{LpAction, LpInput}; use nym_lp::{ codec::OuterAeadKey, message::ForwardPacketData, packet::LpHeader, LpMessage, LpPacket, OuterHeader, @@ -387,8 +388,6 @@ where receiver_idx: u32, packet: LpPacket, ) -> Result<(), GatewayError> { - use nym_lp::state_machine::{LpAction, LpInput}; - debug!( "Processing handshake packet from {} (receiver_idx={})", self.remote_addr, receiver_idx @@ -425,7 +424,6 @@ where .session() .ok() .and_then(|s| s.outer_aead_key()); - drop(state_entry); // Release borrow before send Some((response_packet, outer_key)) } LpAction::HandshakeComplete => { @@ -445,7 +443,6 @@ where // Move state machine to session_states (already in Transport state) // We keep the state machine (not just session) to enable // subsession/rekeying support during transport phase - drop(state_entry); // Release mutable borrow let (_receiver_idx, timestamped_state) = self .state @@ -473,11 +470,13 @@ where } other => { debug!("Received action during handshake: {:?}", other); - drop(state_entry); None } }; + // Release mutable borrow + drop(state_entry); + // Send response packet if needed if let Some((packet, outer_key)) = should_send { self.send_lp_packet(packet, outer_key.as_ref()).await?; diff --git a/integration-tests/src/lp_registration.rs b/integration-tests/src/lp_registration.rs index 28d209adc2..8c9d7c3fdc 100644 --- a/integration-tests/src/lp_registration.rs +++ b/integration-tests/src/lp_registration.rs @@ -385,7 +385,7 @@ mod tests { let client_key = *client_data.base.x25519_wg_keys.public_key(); let mut entry = Gateway::mock(&mut gateway_rng).await?; - let mut client = LpRegistrationClient::::new_with_default_psk( + let mut client = LpRegistrationClient::::new_with_default_config( client_data.base.ed25519_keys, *entry.base.ed25519_keys.public_key(), entry.base.socket_addr, @@ -476,7 +476,7 @@ mod tests { let client_data = Client::mock(&mut client_rng); let mut entry = Gateway::mock(&mut gateway_rng).await?; - let mut client = LpRegistrationClient::::new_with_default_psk( + let mut client = LpRegistrationClient::::new_with_default_config( client_data.base.ed25519_keys, *entry.base.ed25519_keys.public_key(), entry.base.socket_addr, @@ -536,7 +536,7 @@ mod tests { let mut entry = Gateway::mock(&mut entry_rng).await?; let mut exit = Gateway::mock(&mut exit_rng).await?; - let mut entry_client = LpRegistrationClient::::new_with_default_psk( + let mut entry_client = LpRegistrationClient::::new_with_default_config( client_data.base.ed25519_keys.clone(), *entry.base.ed25519_keys.public_key(), entry.base.socket_addr, diff --git a/nym-gateway-probe/src/lib.rs b/nym-gateway-probe/src/lib.rs index 53c3e097e2..604bfb381c 100644 --- a/nym-gateway-probe/src/lib.rs +++ b/nym-gateway-probe/src/lib.rs @@ -1109,7 +1109,7 @@ where let client_ed25519_keypair = std::sync::Arc::new(ed25519::KeyPair::new(&mut rng)); // Create LP registration client (uses Ed25519 keys directly, derives X25519 internally) - let mut client = LpRegistrationClient::::new_with_default_psk( + let mut client = LpRegistrationClient::::new_with_default_config( client_ed25519_keypair, gateway_identity, gateway_lp_address, @@ -1272,7 +1272,7 @@ where // LpRegistrationClient uses packet-per-connection model - connect() is gone, // connection is established automatically during handshake. info!("Establishing outer LP session with entry gateway..."); - let mut entry_client = LpRegistrationClient::::new_with_default_psk( + let mut entry_client = LpRegistrationClient::::new_with_default_config( entry_lp_keypair, entry_gateway.identity, entry_lp_address, diff --git a/nym-registration-client/src/lib.rs b/nym-registration-client/src/lib.rs index 516f7c6245..5843e4ecd4 100644 --- a/nym-registration-client/src/lib.rs +++ b/nym-registration-client/src/lib.rs @@ -7,9 +7,11 @@ use crate::config::RegistrationClientConfig; use nym_authenticator_client::{AuthClientMixnetListener, AuthenticatorClient}; use nym_bandwidth_controller::BandwidthTicketProvider; use nym_credentials_interface::TicketType; +use nym_crypto::asymmetric::ed25519; use nym_ip_packet_client::IprClientConnect; use nym_registration_common::AssignedAddresses; use nym_sdk::mixnet::{EventReceiver, MixnetClient, Recipient}; +use rand::rngs::OsRng; use std::sync::Arc; use tokio::net::TcpStream; @@ -152,8 +154,6 @@ impl RegistrationClient { } async fn register_lp(self) -> Result { - use crate::lp_client::{LpRegistrationClient, NestedLpSession}; - // Extract and validate LP addresses let entry_lp_address = self.config.entry.node.lp_address.ok_or( RegistrationClientError::LpRegistrationNotPossible { @@ -172,8 +172,6 @@ impl RegistrationClient { // Generate fresh Ed25519 keypairs for LP registration // These are ephemeral and used only for the LP handshake protocol - use nym_crypto::asymmetric::ed25519; - use rand::rngs::OsRng; let entry_lp_keypair = Arc::new(ed25519::KeyPair::new(&mut OsRng)); let exit_lp_keypair = Arc::new(ed25519::KeyPair::new(&mut OsRng)); @@ -181,7 +179,7 @@ impl RegistrationClient { // 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_psk( + let mut entry_client = LpRegistrationClient::new_with_default_config( entry_lp_keypair.clone(), self.config.entry.node.identity, entry_lp_address, diff --git a/nym-registration-client/src/lp_client/client.rs b/nym-registration-client/src/lp_client/client.rs index 6e350eeb50..99afac0c88 100644 --- a/nym-registration-client/src/lp_client/client.rs +++ b/nym-registration-client/src/lp_client/client.rs @@ -104,7 +104,7 @@ where /// Uses default config (LpConfig::default()) with sane timeout and TCP parameters. /// PSK is derived automatically during handshake inside the state machine. /// For custom config, use `new()` directly. - pub fn new_with_default_psk( + pub fn new_with_default_config( local_ed25519_keypair: Arc, gateway_ed25519_public_key: ed25519::PublicKey, gateway_lp_address: SocketAddr, @@ -1222,7 +1222,7 @@ mod tests { let address = "127.0.0.1:41264".parse().unwrap(); let client_ip = "192.168.1.100".parse().unwrap(); - let client = LpRegistrationClient::::new_with_default_psk( + let client = LpRegistrationClient::::new_with_default_config( keypair, gateway_key, address, diff --git a/nym-registration-client/src/lp_client/mod.rs b/nym-registration-client/src/lp_client/mod.rs index d4085bbbaa..a83cd80853 100644 --- a/nym-registration-client/src/lp_client/mod.rs +++ b/nym-registration-client/src/lp_client/mod.rs @@ -17,7 +17,7 @@ //! ```ignore //! use nym_registration_client::lp_client::LpRegistrationClient; //! -//! let mut client = LpRegistrationClient::new_with_default_psk( +//! let mut client = LpRegistrationClient::new_with_default_config( //! keypair, //! gateway_public_key, //! gateway_lp_address, diff --git a/tools/nym-lp-client/src/client.rs b/tools/nym-lp-client/src/client.rs index 6d35cb47a5..c9d0d58bbb 100644 --- a/tools/nym-lp-client/src/client.rs +++ b/tools/nym-lp-client/src/client.rs @@ -118,7 +118,7 @@ impl SpeedtestClient { let client_ip = "0.0.0.0".parse()?; - let mut lp_client = LpRegistrationClient::::new_with_default_psk( + let mut lp_client = LpRegistrationClient::::new_with_default_config( self.identity_keypair.clone(), self.gateway.identity, self.gateway.lp_address, @@ -161,7 +161,7 @@ impl SpeedtestClient { let client_ip = "0.0.0.0".parse()?; - let mut lp_client = LpRegistrationClient::::new_with_default_psk( + let mut lp_client = LpRegistrationClient::new_with_default_config( self.identity_keypair.clone(), self.gateway.identity, self.gateway.lp_address, From 4795fa89a923fc6c8bc7f153a2933709efc637d0 Mon Sep 17 00:00:00 2001 From: jmwample Date: Fri, 16 Jan 2026 15:01:08 -0700 Subject: [PATCH 10/51] apply configured api urls via env --- Cargo.lock | 2 + common/network-defaults/Cargo.toml | 4 +- common/network-defaults/src/mainnet.rs | 11 +++++ common/network-defaults/src/network.rs | 63 +++++++++++++++++++----- common/network-defaults/src/var_names.rs | 2 + 5 files changed, 68 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 30db56699b..9908c54241 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6989,6 +6989,8 @@ dependencies = [ "regex", "schemars 0.8.22", "serde", + "serde_json", + "tracing", "url", "utoipa", ] diff --git a/common/network-defaults/Cargo.toml b/common/network-defaults/Cargo.toml index 5c3a73e279..924d09151f 100644 --- a/common/network-defaults/Cargo.toml +++ b/common/network-defaults/Cargo.toml @@ -12,6 +12,8 @@ dotenvy = { workspace = true, optional = true } log = { workspace = true, optional = true } schemars = { workspace = true, features = ["preserve_order"], optional = true } serde = { workspace = true, features = ["derive"], optional = true } +serde_json = {workspace = true, optional = true } +tracing = {workspace = true, optional = true } url = { workspace = true, optional = true } utoipa = { workspace = true, optional = true } @@ -20,7 +22,7 @@ utoipa = { workspace = true, optional = true } [features] default = ["env", "network"] -env = ["dotenvy", "log"] +env = ["dotenvy", "log", "serde_json", "tracing"] network = ["schemars", "serde", "url"] utoipa = [ "dep:utoipa" ] diff --git a/common/network-defaults/src/mainnet.rs b/common/network-defaults/src/mainnet.rs index 8e253a77b7..c4b81aa2db 100644 --- a/common/network-defaults/src/mainnet.rs +++ b/common/network-defaults/src/mainnet.rs @@ -72,6 +72,12 @@ pub const NYM_VPN_APIS: &[ApiUrlConst] = &[ }, ]; +fn serialize_api_urls(urls: &[ApiUrlConst]) -> String { + serde_json::to_string(urls) + .inspect_err(|e| tracing::warn!("failed to serialize nym_api_urls for env: {e}")) + .unwrap_or(String::new()) +} + // I'm making clippy mad on purpose, because that url HAS TO be updated and deployed before merging pub const EXIT_POLICY_URL: &str = "https://nymtech.net/.wellknown/network-requester/exit-policy.txt"; @@ -162,9 +168,11 @@ pub fn export_to_env() { ); set_var_to_default(var_names::NYXD, NYXD_URL); set_var_to_default(var_names::NYM_API, NYM_API); + set_var_to_default(var_names::NYM_APIS, &serialize_api_urls(NYM_APIS)); set_var_to_default(var_names::NYXD_WEBSOCKET, NYXD_WS); set_var_to_default(var_names::EXIT_POLICY_URL, EXIT_POLICY_URL); set_var_to_default(var_names::NYM_VPN_API, NYM_VPN_API); + set_var_to_default(var_names::NYM_VPN_APIS, &serialize_api_urls(NYM_VPN_APIS)); set_var_to_default( var_names::UPGRADE_MODE_ATTESTATION_URL, UPGRADE_MODE_ATTESTATION_URL, @@ -211,6 +219,9 @@ pub fn export_to_env_if_not_set() { ); set_var_conditionally_to_default(var_names::NYXD, NYXD_URL); set_var_conditionally_to_default(var_names::NYM_API, NYM_API); + set_var_conditionally_to_default(var_names::NYM_APIS, &serialize_api_urls(NYM_APIS)); + set_var_conditionally_to_default(var_names::NYM_VPN_API, NYM_VPN_API); + set_var_conditionally_to_default(var_names::NYM_VPN_APIS, &serialize_api_urls(NYM_VPN_APIS)); set_var_conditionally_to_default(var_names::NYXD_WEBSOCKET, NYXD_WS); set_var_conditionally_to_default(var_names::EXIT_POLICY_URL, EXIT_POLICY_URL); set_var_conditionally_to_default( diff --git a/common/network-defaults/src/network.rs b/common/network-defaults/src/network.rs index 23ca16dddf..d6f3a66125 100644 --- a/common/network-defaults/src/network.rs +++ b/common/network-defaults/src/network.rs @@ -7,6 +7,13 @@ use serde::{Deserialize, Serialize}; use std::ops::Not; use url::Url; +#[cfg(feature = "env")] +use crate::var_names; +#[cfg(feature = "env")] +use std::env::{VarError, var}; +#[cfg(feature = "env")] +use std::ffi::OsStr; + #[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize, JsonSchema)] #[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] pub struct ChainDetails { @@ -55,7 +62,7 @@ pub struct ApiUrl { pub front_hosts: Option>, } -#[derive(Copy, Clone)] +#[derive(Copy, Clone, Debug, Serialize)] pub struct ApiUrlConst<'a> { pub url: &'a str, pub front_hosts: Option<&'a [&'a str]>, @@ -106,10 +113,6 @@ impl NymNetworkDetails { #[cfg(feature = "env")] pub fn new_from_env() -> Self { - use crate::var_names; - use std::env::{VarError, var}; - use std::ffi::OsStr; - fn get_optional_env>(env: K) -> Option { match var(env) { Ok(var) => { @@ -125,6 +128,11 @@ impl NymNetworkDetails { } let nym_api = var(var_names::NYM_API).expect("nym api not set"); + let nym_api_urls = try_parse_api_urls(var_names::NYM_APIS).unwrap_or(vec![ApiUrl { + url: nym_api.clone(), + front_hosts: None, + }]); + let nym_vpn_api_urls = try_parse_api_urls(var_names::NYM_VPN_APIS); NymNetworkDetails::new_empty() .with_network_name(var(var_names::NETWORK_NAME).expect("network name not set")) @@ -161,10 +169,8 @@ impl NymNetworkDetails { .with_multisig_contract(get_optional_env(var_names::MULTISIG_CONTRACT_ADDRESS)) .with_coconut_dkg_contract(get_optional_env(var_names::COCONUT_DKG_CONTRACT_ADDRESS)) .with_nym_vpn_api_url(get_optional_env(var_names::NYM_VPN_API)) - .with_nym_api_urls(Some(vec![ApiUrl { - url: nym_api, - front_hosts: None, - }])) + .with_nym_vpn_api_urls(nym_vpn_api_urls) + .with_nym_api_urls(nym_api_urls) } pub fn new_mainnet() -> Self { @@ -218,6 +224,9 @@ impl NymNetworkDetails { } } unsafe { + let nym_api_urls = self.nym_api_urls(); + let nym_vpn_api_urls = self.nym_vpn_api_urls(); + set_var(var_names::NETWORK_NAME, self.network_name); set_var(var_names::BECH32_PREFIX, self.chain_details.bech32_account_prefix); @@ -243,9 +252,9 @@ impl NymNetworkDetails { set_optional_var(var_names::COCONUT_DKG_CONTRACT_ADDRESS, self.contracts.coconut_dkg_contract_address); set_optional_var(var_names::NYM_VPN_API, self.nym_vpn_api_url); + set_optional_var(var_names::NYM_APIS, nym_vpn_api_urls); + } - - } pub fn default_gas_price_amount(&self) -> f64 { @@ -355,8 +364,14 @@ impl NymNetworkDetails { } #[must_use] - pub fn with_nym_api_urls(mut self, urls: Option>) -> Self { - self.nym_api_urls = urls; + pub fn with_nym_api_urls(mut self, urls: Vec) -> Self { + self.nym_api_urls = Some(urls); + self + } + + #[must_use] + pub fn with_nym_vpn_api_urls(mut self, urls: Option>) -> Self { + self.nym_vpn_api_urls = urls; self } @@ -366,6 +381,28 @@ impl NymNetworkDetails { .expect("the provided nym-vpn api url is invalid!") }) } + + #[cfg(feature = "env")] + fn nym_api_urls(&self) -> Option { + serde_json::to_string(self.nym_api_urls.as_deref()?) + .inspect_err(|e| tracing::warn!("failed to serialize nym_api_urls for env: {e}")) + .ok() + } + + #[cfg(feature = "env")] + fn nym_vpn_api_urls(&self) -> Option { + serde_json::to_string(self.nym_vpn_api_urls.as_deref()?) + .inspect_err(|e| tracing::warn!("failed to serialize nym_vpn_api_urls for env: {e}")) + .ok() + } +} + +#[cfg(feature = "env")] +fn try_parse_api_urls(k: impl AsRef) -> Option> { + let raw = var(k).ok()?; + serde_json::from_str(&raw) + .inspect_err(|e| tracing::warn!("failed to parse api urls from env \"{raw:?}\": {e}")) + .ok() } #[derive(Debug, Copy, Serialize, Deserialize, Clone, PartialEq, Eq)] diff --git a/common/network-defaults/src/var_names.rs b/common/network-defaults/src/var_names.rs index 897937b0fe..4bfb1d5877 100644 --- a/common/network-defaults/src/var_names.rs +++ b/common/network-defaults/src/var_names.rs @@ -21,9 +21,11 @@ pub const COCONUT_DKG_CONTRACT_ADDRESS: &str = "COCONUT_DKG_CONTRACT_ADDRESS"; pub const REWARDING_VALIDATOR_ADDRESS: &str = "REWARDING_VALIDATOR_ADDRESS"; pub const NYXD: &str = "NYXD"; pub const NYM_API: &str = "NYM_API"; +pub const NYM_APIS: &str = "NYM_APIS"; pub const NYXD_WEBSOCKET: &str = "NYXD_WS"; pub const EXIT_POLICY_URL: &str = "EXIT_POLICY"; pub const NYM_VPN_API: &str = "NYM_VPN_API"; +pub const NYM_VPN_APIS: &str = "NYM_VPN_APIS"; pub const CLIENT_STATS_COLLECTION_PROVIDER: &str = "CLIENT_STATS_COLLECTION_PROVIDER"; pub const UPGRADE_MODE_ATTESTATION_URL: &str = "UPGRADE_MODE_ATTESTATION_URL"; pub const UPGRADE_MODE_ATTESTER_ED25519_BS58_PUBKEY: &str = "UPGRADE_MODE_ATTESTER_ED25519_PUBKEY"; From 00e8528fed53dfff3cd5f8c7baa66a631cef3502 Mon Sep 17 00:00:00 2001 From: jmwample Date: Fri, 16 Jan 2026 15:11:04 -0700 Subject: [PATCH 11/51] minor fixes --- common/network-defaults/src/mainnet.rs | 2 +- common/network-defaults/src/network.rs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/common/network-defaults/src/mainnet.rs b/common/network-defaults/src/mainnet.rs index c4b81aa2db..c5dd02aaab 100644 --- a/common/network-defaults/src/mainnet.rs +++ b/common/network-defaults/src/mainnet.rs @@ -75,7 +75,7 @@ pub const NYM_VPN_APIS: &[ApiUrlConst] = &[ fn serialize_api_urls(urls: &[ApiUrlConst]) -> String { serde_json::to_string(urls) .inspect_err(|e| tracing::warn!("failed to serialize nym_api_urls for env: {e}")) - .unwrap_or(String::new()) + .unwrap_or_default() } // I'm making clippy mad on purpose, because that url HAS TO be updated and deployed before merging diff --git a/common/network-defaults/src/network.rs b/common/network-defaults/src/network.rs index d6f3a66125..c85101b690 100644 --- a/common/network-defaults/src/network.rs +++ b/common/network-defaults/src/network.rs @@ -252,7 +252,8 @@ impl NymNetworkDetails { set_optional_var(var_names::COCONUT_DKG_CONTRACT_ADDRESS, self.contracts.coconut_dkg_contract_address); set_optional_var(var_names::NYM_VPN_API, self.nym_vpn_api_url); - set_optional_var(var_names::NYM_APIS, nym_vpn_api_urls); + set_optional_var(var_names::NYM_VPN_APIS, nym_vpn_api_urls); + set_optional_var(var_names::NYM_APIS, nym_api_urls); } } From 9cca73bc3a38516fdea2ac16fb1480d7ac6b075f Mon Sep 17 00:00:00 2001 From: jmwample Date: Tue, 20 Jan 2026 09:21:55 -0700 Subject: [PATCH 12/51] env feature locking to protect contracts --- common/network-defaults/src/mainnet.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/common/network-defaults/src/mainnet.rs b/common/network-defaults/src/mainnet.rs index c5dd02aaab..4465875b36 100644 --- a/common/network-defaults/src/mainnet.rs +++ b/common/network-defaults/src/mainnet.rs @@ -72,6 +72,7 @@ pub const NYM_VPN_APIS: &[ApiUrlConst] = &[ }, ]; +#[cfg(feature = "env")] fn serialize_api_urls(urls: &[ApiUrlConst]) -> String { serde_json::to_string(urls) .inspect_err(|e| tracing::warn!("failed to serialize nym_api_urls for env: {e}")) From 7b787403272f7a789a4d37fe09e2ade8aac53be4 Mon Sep 17 00:00:00 2001 From: Simon Wicky Date: Wed, 21 Jan 2026 10:40:11 +0100 Subject: [PATCH 13/51] revert faulty drop changes (#6346) --- gateway/src/node/lp_listener/handler.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gateway/src/node/lp_listener/handler.rs b/gateway/src/node/lp_listener/handler.rs index 7c98bd0f13..9b7f231e27 100644 --- a/gateway/src/node/lp_listener/handler.rs +++ b/gateway/src/node/lp_listener/handler.rs @@ -424,6 +424,7 @@ where .session() .ok() .and_then(|s| s.outer_aead_key()); + drop(state_entry); // Release borrow before send Some((response_packet, outer_key)) } LpAction::HandshakeComplete => { @@ -443,6 +444,7 @@ where // Move state machine to session_states (already in Transport state) // We keep the state machine (not just session) to enable // subsession/rekeying support during transport phase + drop(state_entry); // Release mutable borrow let (_receiver_idx, timestamped_state) = self .state @@ -470,13 +472,11 @@ where } other => { debug!("Received action during handshake: {:?}", other); + drop(state_entry); None } }; - // Release mutable borrow - drop(state_entry); - // Send response packet if needed if let Some((packet, outer_key)) = should_send { self.send_lp_packet(packet, outer_key.as_ref()).await?; From 3b20e22aa124e694eb4045d7696126f981aa7367 Mon Sep 17 00:00:00 2001 From: Georgio Nicolas Date: Thu, 22 Jan 2026 01:45:30 +0100 Subject: [PATCH 14/51] WIP: restore kkt sanity --- common/nym-kkt/Cargo.toml | 1 - common/nym-kkt/src/encryption.rs | 4 +- common/nym-kkt/src/lib.rs | 15 ++- common/nym-lp/src/lib.rs | 2 +- common/nym-lp/src/session.rs | 206 ++++++++++++++++++------------- 5 files changed, 133 insertions(+), 95 deletions(-) diff --git a/common/nym-kkt/Cargo.toml b/common/nym-kkt/Cargo.toml index 64af80cd91..14b3f2f9fc 100644 --- a/common/nym-kkt/Cargo.toml +++ b/common/nym-kkt/Cargo.toml @@ -20,7 +20,6 @@ libcrux-sha3 = { git = "https://github.com/cryspen/libcrux" } libcrux-ecdh = { git = "https://github.com/cryspen/libcrux", features = ["codec"] } libcrux-chacha20poly1305 = { git = "https://github.com/cryspen/libcrux" } -#rand = "0.9.2" rand = "0.9.2" zeroize = { workspace = true, features = ["zeroize_derive"] } classic-mceliece-rust = { git = "https://github.com/georgio/classic-mceliece-rust", features = ["mceliece460896f", "zeroize"] } diff --git a/common/nym-kkt/src/encryption.rs b/common/nym-kkt/src/encryption.rs index 7bb7610622..ef8bc56342 100644 --- a/common/nym-kkt/src/encryption.rs +++ b/common/nym-kkt/src/encryption.rs @@ -1,9 +1,9 @@ // Copyright 2025-2026 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::kkt::KKT_INITIAL_FRAME_AAD; use crate::{ - ciphersuite::CURVE25519_KEY_LEN, context::KKTContext, error::KKTError, frame::KKTFrame, + KKT_INITIAL_FRAME_AAD, ciphersuite::CURVE25519_KEY_LEN, context::KKTContext, error::KKTError, + frame::KKTFrame, }; use blake3::Hasher; use libcrux_chacha20poly1305::{NONCE_LEN, TAG_LEN}; diff --git a/common/nym-kkt/src/lib.rs b/common/nym-kkt/src/lib.rs index bdfd643cb7..354241254d 100644 --- a/common/nym-kkt/src/lib.rs +++ b/common/nym-kkt/src/lib.rs @@ -7,17 +7,20 @@ pub mod encryption; pub mod error; pub mod frame; pub mod key_utils; -pub mod kkt; +// pub mod kkt; pub mod session; // This must be less than 4 bits pub const KKT_VERSION: u8 = 1; const _: () = assert!(KKT_VERSION < 1 << 4); +pub const KKT_RESPONSE_AAD: &[u8] = b"KKT_Response"; +pub(crate) const KKT_INITIAL_FRAME_AAD: &[u8] = b"KKT_INITIAL_FRAME"; #[cfg(test)] mod test { - use crate::kkt::KKT_RESPONSE_AAD; + // use crate::kkt::KKT_RESPONSE_AAD; use crate::{ + KKT_RESPONSE_AAD, ciphersuite::{Ciphersuite, EncapsulationKey, HashFunction, KEM}, encryption::{ decrypt_initial_kkt_frame, decrypt_kkt_frame, encrypt_initial_kkt_frame, @@ -495,3 +498,11 @@ mod test { } } } + +// #[test] +// fn test_keys() { +// let a = nym_crypto::asymmetric::x25519::KeyPair::new(&mut rand::rng()); +// println!("{:?}", a.private_key().as_bytes()); +// println!("{:?}", a.public_key().as_bytes()); +// } +// } diff --git a/common/nym-lp/src/lib.rs b/common/nym-lp/src/lib.rs index 7e8eae8bd1..ddaff3968d 100644 --- a/common/nym-lp/src/lib.rs +++ b/common/nym-lp/src/lib.rs @@ -4,7 +4,7 @@ pub mod codec; pub mod config; pub mod error; -pub mod kkt_orchestrator; +// pub mod kkt_orchestrator; pub mod message; pub mod noise_protocol; pub mod packet; diff --git a/common/nym-lp/src/session.rs b/common/nym-lp/src/session.rs index 774f148146..ad819444a0 100644 --- a/common/nym-lp/src/session.rs +++ b/common/nym-lp/src/session.rs @@ -8,6 +8,7 @@ use crate::codec::OuterAeadKey; use crate::message::{EncryptedDataPayload, HandshakeData}; +// noiserm use crate::noise_protocol::{NoiseError, NoiseProtocol, ReadResult}; use crate::packet::LpHeader; use crate::psk::{ @@ -16,9 +17,16 @@ use crate::psk::{ use crate::replay::ReceivingKeyCounterValidator; use crate::{LpError, LpMessage, LpPacket}; use nym_crypto::asymmetric::{ed25519, x25519}; +use nym_kkt::KKT_RESPONSE_AAD; use nym_kkt::ciphersuite::{DecapsulationKey, EncapsulationKey}; -use nym_kkt::encryption::KKTSessionSecret; -use nym_kkt::kkt::decrypt_kkt_response_frame; +use nym_kkt::encryption::{ + KKTSessionSecret, decrypt_initial_kkt_frame, decrypt_kkt_frame, encrypt_initial_kkt_frame, + encrypt_kkt_frame, +}; +use nym_kkt::session::{ + anonymous_initiator_process, initiator_ingest_response, responder_ingest_message, + responder_process, +}; use parking_lot::Mutex; use rand::RngCore; use snow::Builder; @@ -161,6 +169,7 @@ pub struct LpSession { /// Flag indicating if this session acts as the Noise protocol initiator. is_initiator: bool, + // noiserm /// Noise protocol state machine noise_state: Mutex, @@ -179,6 +188,7 @@ pub struct LpSession { /// Validator for incoming packet counters to prevent replay attacks receiving_counter: Mutex, + // noiserm /// Safety flag: `true` if real PSK was injected via PSQ, `false` if still using dummy PSK. /// This prevents transport mode operations from running with the insecure dummy `[0u8; 32]` PSK. psk_injected: AtomicBool, @@ -228,6 +238,7 @@ pub struct LpSession { negotiated_version: std::sync::atomic::AtomicU8, } +// noiserm /// Generates a fresh salt for PSK derivation. /// /// Salt format: 8 bytes timestamp (u64 LE) + 24 bytes random nonce @@ -363,37 +374,47 @@ impl LpSession { remote_x25519_key: &x25519::PublicKey, salt: &[u8; 32], ) -> Result { + // noiserm // XKpsk3 pattern requires remote static key known upfront (XK) // and PSK mixed at position 3. This provides forward secrecy with PSK authentication. let pattern_name = crate::NOISE_PATTERN; let psk_index = crate::NOISE_PSK_INDEX; + // noiserm let params = pattern_name.parse()?; let builder = Builder::new(params); let local_key_bytes = local_x25519_keypair.private_key().as_bytes(); + // noiserm let builder = builder.local_private_key(local_key_bytes); let remote_key_bytes = remote_x25519_key.to_bytes(); + // noiserm let builder = builder.remote_public_key(&remote_key_bytes); + // noiserm // Initialize with dummy PSK - real PSK will be injected via set_psk() during handshake // when PSQ runs using X25519 as DHKEM let dummy_psk = [0u8; 32]; let builder = builder.psk(psk_index, &dummy_psk); + // noiserm let initial_state = if is_initiator { builder.build_initiator().map_err(LpError::SnowKeyError)? } else { builder.build_responder().map_err(LpError::SnowKeyError)? }; + // noiserm let noise_protocol = NoiseProtocol::new(initial_state); // Initialize KKT state - both roles start at NotStarted let kkt_state = KKTState::NotStarted; // Initialize PSQ state based on role + // georgio: why PSQState::ResponderWaiting if responder? + // georgio: maybe because we can start straight with PSQ? + // georgio: either way, doesn't matter so much because a bad PSQ request will be rejected let psq_state = if is_initiator { PSQState::NotStarted } else { @@ -403,12 +424,14 @@ impl LpSession { Ok(Self { id, is_initiator, + // noiserm noise_state: Mutex::new(noise_protocol), kkt_state: Mutex::new(kkt_state), psq_state: Mutex::new(psq_state), psk_handle: Mutex::new(None), sending_counter: AtomicU64::new(0), receiving_counter: Mutex::new(ReceivingKeyCounterValidator::default()), + // noiserm psk_injected: AtomicBool::new(false), local_ed25519: local_ed25519_keypair.clone(), remote_ed25519_public: *remote_ed25519_key, @@ -503,16 +526,16 @@ impl LpSession { self.psk_handle.lock().clone() } - /// Prepares a KKT (KEM Key Transfer) request message. + /// Prepares an KKT (KEM Key Transfer) request message. + /// Config: Encryption Enabled, One-Way, Anonymous /// - /// This should be called by the initiator before starting the Noise handshake - /// to obtain the responder's KEM public key. The KKT protocol authenticates - /// the exchange using Ed25519 signatures. + /// This should be called by the initiator before starting PSQ + /// to obtain the responder's KEM public key. /// /// **Protocol Flow:** - /// 1. Initiator creates KKT request with Ed25519 signature - /// 2. Responder validates signature and responds with KEM public key + signature - /// 3. Initiator validates response and stores KEM key for PSQ + /// 1. Initiator creates an Encrypted, Anonymous, One-Way, KKT request + /// 2. Responder responds with encrypted KEM public key + signature + /// 3. Initiator validates response and stores KEM key for PSQ use /// /// # Returns /// @@ -520,10 +543,7 @@ impl LpSession { /// * `Some(Err(LpError))` - Error creating KKT request /// * `None` - KKT not applicable (responder, or already completed) pub fn prepare_kkt_request(&self) -> Option> { - use nym_kkt::{ - ciphersuite::{Ciphersuite, HashFunction, KEM, SignatureScheme}, - kkt::request_kem_key, - }; + use nym_kkt::ciphersuite::{Ciphersuite, HashFunction, KEM, SignatureScheme}; let mut kkt_state = self.kkt_state.lock(); @@ -532,9 +552,9 @@ impl LpSession { return None; } - // Use X25519 as KEM for now (can extend to ML-KEM-768 later) + // georgio this needs to be moved one level up (maybe chosen in LPSession) let ciphersuite = match Ciphersuite::resolve_ciphersuite( - KEM::X25519, + KEM::MlKem768, HashFunction::Blake3, SignatureScheme::Ed25519, None, @@ -549,23 +569,26 @@ impl LpSession { }; let mut rng = rand09::rng(); - match request_kem_key( - &mut rng, - ciphersuite, - self.local_ed25519.private_key(), - &self.remote_x25519_public, - ) { - Ok((session_secret, context, request_bytes)) => { - // Store context for response validation - *kkt_state = KKTState::InitiatorWaiting { - context, - session_secret, - }; + match anonymous_initiator_process(&mut rng, ciphersuite) { + Ok((context, kkt_frame)) => { + match encrypt_initial_kkt_frame(&mut rng, &self.remote_x25519_public, &kkt_frame) { + Ok((session_secret, encrypted_request_bytes)) => { + // Store context for response validation + *kkt_state = KKTState::InitiatorWaiting { + context, + session_secret, + }; - // Serialize KKT frame to bytes - Some(Ok(LpMessage::KKTRequest(crate::message::KKTRequestData( - request_bytes, - )))) + // Serialize KKT frame to bytes + Some(Ok(LpMessage::KKTRequest(crate::message::KKTRequestData( + encrypted_request_bytes, + )))) + } + Err(e) => Some(Err(LpError::Internal(format!( + "KKT request encryption failed: {:?}", + e + )))), + } } Err(e) => Some(Err(LpError::Internal(format!( "KKT request creation failed: {:?}", @@ -576,35 +599,24 @@ impl LpSession { /// Processes a KKT response from the responder. /// - /// Validates the responder's signature and stores the authenticated KEM public key + /// Decrypts and validates the response and stores the authenticated KEM public key /// for use in PSQ encapsulation. /// /// # Arguments /// /// * `response_bytes` - Raw KKT response message from responder - /// * `expected_key_hash` - Optional expected hash of responder's KEM key. - /// - `Some(hash)`: Full KKT validation (signature + hash) - use when directory service available - /// - `None`: Signature-only validation (hash computed from received key) - temporary mode + /// * `expected_key_hash` - Expected hash of responder's KEM key (obtained from directory). /// /// # Returns /// /// * `Ok(())` - KKT exchange completed, KEM key stored /// * `Err(LpError)` - Signature verification failed, hash mismatch, or invalid state /// - /// # Note - /// - /// When None is passed, the function computes the hash from the received key and validates against - /// that (effectively signature-only mode). This allows easy upgrade: just pass Some(directory_hash) - /// when directory service becomes available. The full KKT protocol with hash pinning provides - /// protection against key substitution attacks. pub fn process_kkt_response( &self, - response_bytes: &[u8], - expected_key_hash: Option<&[u8]>, + encrypted_response_bytes: &[u8], + expected_key_hash: &[u8], ) -> Result<(), LpError> { - use nym_kkt::key_utils::hash_encapsulation_key; - use nym_kkt::kkt::validate_kem_response; - let mut kkt_state = self.kkt_state.lock(); // Extract context from waiting state @@ -620,42 +632,37 @@ impl LpSession { } }; - // Determine hash to validate against - let hash_for_validation: Vec; - let hash_ref = match expected_key_hash { - Some(hash) => hash, - None => { - // Signature-only mode: extract key from response and compute its hash - // This effectively bypasses hash validation while keeping signature validation - let (frame, _) = decrypt_kkt_response_frame(&session_secret, response_bytes) - .map_err(|e| { - LpError::Internal(format!("Failed to decrypt KKT response: {:?}", e)) - })?; - - hash_for_validation = hash_encapsulation_key( - &context.ciphersuite().hash_function(), - context.ciphersuite().hash_len(), - frame.body_ref(), - ); - &hash_for_validation - } - }; - - // Validate response and extract KEM key - let kem_pk = validate_kem_response( - &mut context, - &session_secret, - &self.remote_ed25519_public, - hash_ref, - response_bytes, - ) - .map_err(|e| LpError::Internal(format!("KKT response validation failed: {:?}", e)))?; + let remote_encapsulation_key = + match decrypt_kkt_frame(&session_secret, encrypted_response_bytes, KKT_RESPONSE_AAD) { + Ok((response_frame, remote_context)) => { + match initiator_ingest_response( + &mut context, + &response_frame, + &remote_context, + &self.remote_ed25519_public, + expected_key_hash, + ) { + Ok(remote_encapsulation_key) => remote_encapsulation_key, + Err(e) => { + return Err(LpError::Internal(format!( + "KKT response handling failure: {:?}", + e + ))); + } + } + } + Err(e) => { + return Err(LpError::Internal(format!( + "KKT response decryption failure: {:?}", + e + ))); + } + }; // Store the authenticated KEM key *kkt_state = KKTState::Completed { - kem_pk: Box::new(kem_pk), + kem_pk: Box::new(remote_encapsulation_key), }; - Ok(()) } @@ -678,22 +685,38 @@ impl LpSession { request_bytes: &[u8], responder_kem_pk: &EncapsulationKey, ) -> Result { - use nym_kkt::kkt::handle_kem_request; - let mut rng = rand09::rng(); let mut kkt_state = self.kkt_state.lock(); - // Handle request and create signed response - let response_bytes = handle_kem_request( - &mut rng, - request_bytes, - Some(&self.remote_ed25519_public), // Verify initiator signature - self.local_ed25519.private_key(), // Sign response - self.local_x25519.private_key(), - responder_kem_pk, + let (session_secret, request_frame, remote_context) = + decrypt_initial_kkt_frame(self.local_x25519.private_key(), &request_bytes).unwrap(); + + let (mut context, _) = + responder_ingest_message(&remote_context, None, None, &request_frame).unwrap(); + + let response_frame = responder_process( + &mut context, + request_frame.session_id(), + self.local_ed25519.private_key(), + &responder_kem_pk, ) - .map_err(|e| LpError::Internal(format!("KKT request handling failed: {:?}", e)))?; + .unwrap(); + + let response_bytes = + encrypt_kkt_frame(&mut rng, &session_secret, &response_frame, KKT_RESPONSE_AAD) + .unwrap(); + + // Handle request and create signed response + // let response_bytes = handle_kem_request( + // &mut rng, + // request_bytes, + // Some(&self.remote_ed25519_public), // Verify initiator signature + // self.local_ed25519.private_key(), // Sign response + // self.local_x25519.private_key(), + // responder_kem_pk, + // ) + // .map_err(|e| LpError::Internal(format!("KKT request handling failed: {:?}", e)))?; // Mark KKT as processed // Responder doesn't store the kem_pk since they already have their own KEM keypair @@ -762,10 +785,13 @@ impl LpSession { // Store PQ shared secret for subsession PSK derivation *self.pq_shared_secret.lock() = Some(PqSharedSecret::new(psq_result.pq_shared_secret)); + // georgio start psq v2 here + // Inject PSK into Noise HandshakeState if let Err(e) = noise_state.set_psk(3, &psk) { return Some(Err(LpError::NoiseError(e))); } + // noiserm // Mark PSK as injected for safety checks in transport mode self.psk_injected.store(true, Ordering::Release); @@ -1316,6 +1342,7 @@ impl SubsessionHandshake { Ok(LpSession { id: receiver_index, is_initiator: self.is_initiator, + // noiserm noise_state: Mutex::new(noise_state), // KKT: subsession inherits from parent, mark as processed kkt_state: Mutex::new(KKTState::ResponderProcessed), @@ -1326,6 +1353,7 @@ impl SubsessionHandshake { psk_handle: Mutex::new(None), // Subsession doesn't have its own handle sending_counter: AtomicU64::new(0), receiving_counter: Mutex::new(ReceivingKeyCounterValidator::new(0)), + // noiserm psk_injected: AtomicBool::new(true), // PSK was in KKpsk0 local_ed25519: self.local_ed25519, remote_ed25519_public: self.remote_ed25519_public, From c1ddcc75cf4c6f7760f53fc1131a02e54c7c5ecd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Thu, 22 Jan 2026 14:29:33 +0000 Subject: [PATCH 15/51] LP: announced KEM key hashes (#6349) * announce KEM key hashes and use generated value within LpStateMachine * added digest of remote KEM key into LpSession * changed constructor to LpSession to take explicit key materials for local and remote this makes it easier to change keys required by each party without having to change all the interfaces everywhere again * extended the changes to LpStateMachine constructor * modify the interface to LpRegistrationHandler and LpListener * gateway probe fixes * temp nym-lp-client fixes * review nits * remove network test * introduced v2/nym-nodes/described endpoint for returning nodes description alongside LP data * missed V1 -> V2 description replacements * removed deprecated call within mix-fetch * use old v1 call in network stats --- Cargo.lock | 6 + .../bin-common/src/build_information/mod.rs | 4 + .../validator-client/src/client.rs | 74 +-- .../validator-client/src/nym_api/mod.rs | 56 +- .../mixnet/query/query_all_gateways.rs | 2 +- .../mixnet/query/query_all_mixnodes.rs | 2 +- common/http-api-common/src/lib.rs | 4 +- common/nym-kkt/src/ciphersuite.rs | 12 +- common/nym-kkt/src/encryption.rs | 4 +- common/nym-lp/Cargo.toml | 1 + common/nym-lp/src/error.rs | 3 + common/nym-lp/src/kkt_orchestrator.rs | 25 +- common/nym-lp/src/lib.rs | 68 +- common/nym-lp/src/message.rs | 6 + common/nym-lp/src/peer.rs | 138 +++++ common/nym-lp/src/session.rs | 579 +++++------------- common/nym-lp/src/session_integration/mod.rs | 162 +---- common/nym-lp/src/session_manager.rs | 112 +--- common/nym-lp/src/state_machine.rs | 231 ++----- common/nymnoise/keys/src/lib.rs | 10 +- common/nymnoise/src/config.rs | 8 +- common/registration/src/lib.rs | 11 +- common/topology/src/node.rs | 6 +- common/verloc/src/measurements/measurer.rs | 8 +- gateway/src/node/lp_listener/handler.rs | 14 +- gateway/src/node/lp_listener/mod.rs | 14 +- gateway/src/node/lp_listener/registration.rs | 7 +- gateway/src/node/mod.rs | 21 +- integration-tests/src/lp_registration.rs | 44 +- nym-api/nym-api-requests/Cargo.toml | 15 +- .../nym-api-requests/src/models/described.rs | 418 ------------- .../src/models/described/mod.rs | 26 + .../src/models/described/type_translation.rs | 426 +++++++++++++ .../src/models/described/v1.rs | 198 ++++++ .../src/models/described/v2.rs | 343 +++++++++++ nym-api/nym-api-requests/src/models/mod.rs | 2 +- nym-api/nym-api-requests/src/nym_nodes.rs | 10 +- .../src/network_monitor/monitor/preparer.rs | 8 +- nym-api/src/node_describe_cache/cache.rs | 28 +- nym-api/src/node_describe_cache/mod.rs | 16 +- .../src/node_describe_cache/query_helpers.rs | 67 +- nym-api/src/node_describe_cache/refresh.rs | 14 +- .../src/node_status_api/cache/config_score.rs | 4 +- nym-api/src/nym_nodes/handlers/mod.rs | 494 +-------------- nym-api/src/nym_nodes/handlers/v1.rs | 502 +++++++++++++++ nym-api/src/nym_nodes/handlers/v2.rs | 54 ++ nym-api/src/support/http/router.rs | 12 +- .../v2/nym_nodes/semi_skimmed/mod.rs | 4 +- .../v2/nym_nodes/skimmed/helpers.rs | 6 +- nym-gateway-probe/src/lib.rs | 36 +- nym-gateway-probe/src/nodes.rs | 57 +- .../nym-node-status-api/src/db/models.rs | 52 +- .../src/db/queries/nym_nodes.rs | 19 +- .../nym-node-status-api/src/db/tests.rs | 12 +- .../nym-node-status-api/src/http/models.rs | 10 +- .../src/metrics_scraper/mod.rs | 12 +- .../nym-node-status-api/src/monitor/mod.rs | 8 +- nym-node/Cargo.toml | 3 +- nym-node/nym-node-requests/src/api/mod.rs | 8 +- .../src/api/v1/lewes_protocol/models.rs | 101 ++- .../src/api/v1/node/models.rs | 6 +- .../http/router/api/v1/lewes_protocol/mod.rs | 2 +- nym-node/src/node/http/state/mod.rs | 4 +- nym-node/src/node/mod.rs | 67 +- nym-registration-client/src/error.rs | 3 + nym-registration-client/src/lib.rs | 58 +- .../src/lp_client/client.rs | 67 +- .../src/lp_client/error.rs | 4 - .../src/lp_client/nested_session.rs | 66 +- nym-statistics-api/src/network_view.rs | 5 +- nym-wallet/Cargo.lock | 291 ++++++--- tools/nym-lp-client/src/client.rs | 11 +- tools/nym-lp-client/src/topology.rs | 75 +-- tools/ts-rs-cli/src/main.rs | 6 +- wasm/mix-fetch/src/helpers.rs | 2 +- 75 files changed, 2930 insertions(+), 2334 deletions(-) create mode 100644 common/nym-lp/src/peer.rs delete mode 100644 nym-api/nym-api-requests/src/models/described.rs create mode 100644 nym-api/nym-api-requests/src/models/described/mod.rs create mode 100644 nym-api/nym-api-requests/src/models/described/type_translation.rs create mode 100644 nym-api/nym-api-requests/src/models/described/v1.rs create mode 100644 nym-api/nym-api-requests/src/models/described/v2.rs create mode 100644 nym-api/src/nym_nodes/handlers/v1.rs create mode 100644 nym-api/src/nym_nodes/handlers/v2.rs diff --git a/Cargo.lock b/Cargo.lock index 9908c54241..2705d107e0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5403,6 +5403,7 @@ name = "nym-api-requests" version = "1.20.1" dependencies = [ "bs58", + "celes", "cosmrs", "cosmwasm-std", "ecdsa", @@ -5421,12 +5422,15 @@ dependencies = [ "nym-node-requests", "nym-noise-keys", "nym-serde-helpers", + "nym-test-utils", "nym-ticketbooks-merkle", "rand_chacha 0.3.1", "schemars 0.8.22", "serde", "serde_json", "sha2 0.10.9", + "strum", + "strum_macros", "tendermint", "tendermint-rpc", "thiserror 2.0.17", @@ -6828,6 +6832,7 @@ dependencies = [ "nym-crypto", "nym-kkt", "nym-lp-common", + "nym-test-utils", "parking_lot", "rand 0.8.5", "rand 0.9.2", @@ -7120,6 +7125,7 @@ dependencies = [ "nym-http-api-client", "nym-http-api-common", "nym-ip-packet-router", + "nym-kkt", "nym-metrics", "nym-mixnet-client", "nym-network-requester", diff --git a/common/bin-common/src/build_information/mod.rs b/common/bin-common/src/build_information/mod.rs index 6c5d13d4f1..5ed969e9e9 100644 --- a/common/bin-common/src/build_information/mod.rs +++ b/common/bin-common/src/build_information/mod.rs @@ -124,6 +124,10 @@ impl BinaryBuildInformation { } } +// to whoever is thinking of modifying this struct. +// you MUST NOT change its structure in any way - adding, removing or changing fields +// otherwise, it will break old clients as bincode serialisation is not backwards compatible +// even if you put `#[serde(default)]` all over the place #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] #[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] #[cfg_attr(feature = "bin_info_schema", derive(schemars::JsonSchema))] diff --git a/common/client-libs/validator-client/src/client.rs b/common/client-libs/validator-client/src/client.rs index eb23ba60ca..43ab5808a7 100644 --- a/common/client-libs/validator-client/src/client.rs +++ b/common/client-libs/validator-client/src/client.rs @@ -20,7 +20,7 @@ use nym_api_requests::ecash::{ }; use nym_api_requests::models::{ ApiHealthResponse, GatewayCoreStatusResponse, HistoricalPerformanceResponse, - MixnodeCoreStatusResponse, NymNodeDescription, + MixnodeCoreStatusResponse, NymNodeDescriptionV1, NymNodeDescriptionV2, }; use nym_api_requests::nym_nodes::{ NodesByAddressesResponse, SemiSkimmedNodesWithMetadata, SkimmedNode, SkimmedNodesWithMetadata, @@ -273,48 +273,23 @@ impl Client { Ok(history) } - // TODO: combine with NymApiClient... + #[deprecated(note = "use get_all_cached_described_nodes_v2 instead")] pub async fn get_all_cached_described_nodes( &self, - ) -> Result, ValidatorClientError> { - // TODO: deal with paging in macro or some helper function or something, because it's the same pattern everywhere - let mut page = 0; - let mut descriptions = Vec::new(); - - loop { - let mut res = self.nym_api.get_nodes_described(Some(page), None).await?; - - descriptions.append(&mut res.data); - if descriptions.len() < res.pagination.total { - page += 1 - } else { - break; - } - } - - Ok(descriptions) + ) -> Result, ValidatorClientError> { + Ok(self.nym_api.get_all_described_nodes().await?) + } + + pub async fn get_all_cached_described_nodes_v2( + &self, + ) -> Result, ValidatorClientError> { + Ok(self.nym_api.get_all_described_nodes_v2().await?) } - // TODO: combine with NymApiClient... pub async fn get_all_cached_bonded_nym_nodes( &self, ) -> Result, ValidatorClientError> { - // TODO: deal with paging in macro or some helper function or something, because it's the same pattern everywhere - let mut page = 0; - let mut bonds = Vec::new(); - - loop { - let mut res = self.nym_api.get_nym_nodes(Some(page), None).await?; - - bonds.append(&mut res.data); - if bonds.len() < res.pagination.total { - page += 1 - } else { - break; - } - } - - Ok(bonds) + self.nym_api.get_all_bonded_nym_nodes().await } pub async fn blind_sign( @@ -498,9 +473,10 @@ impl NymApiClient { Ok(self.nym_api.health().await?) } + #[deprecated(note = "use .get_all_described_nodes_v2 instead")] pub async fn get_all_described_nodes( &self, - ) -> Result, ValidatorClientError> { + ) -> Result, ValidatorClientError> { // TODO: deal with paging in macro or some helper function or something, because it's the same pattern everywhere let mut page = 0; let mut descriptions = Vec::new(); @@ -519,6 +495,30 @@ impl NymApiClient { Ok(descriptions) } + pub async fn get_all_described_nodes_v2( + &self, + ) -> Result, ValidatorClientError> { + // TODO: deal with paging in macro or some helper function or something, because it's the same pattern everywhere + let mut page = 0; + let mut descriptions = Vec::new(); + + loop { + let mut res = self + .nym_api + .get_nodes_described_v2(Some(page), None) + .await?; + + descriptions.append(&mut res.data); + if descriptions.len() < res.pagination.total { + page += 1 + } else { + break; + } + } + + Ok(descriptions) + } + pub async fn get_all_bonded_nym_nodes( &self, ) -> Result, ValidatorClientError> { diff --git a/common/client-libs/validator-client/src/nym_api/mod.rs b/common/client-libs/validator-client/src/nym_api/mod.rs index 8c549491e9..cf71f706a4 100644 --- a/common/client-libs/validator-client/src/nym_api/mod.rs +++ b/common/client-libs/validator-client/src/nym_api/mod.rs @@ -17,7 +17,8 @@ use nym_api_requests::ecash::VerificationKeyResponse; use nym_api_requests::models::{ AnnotationResponse, ApiHealthResponse, BinaryBuildInformationOwned, ChainBlocksStatusResponse, ChainStatusResponse, KeyRotationInfoResponse, NodePerformanceResponse, NodeRefreshBody, - NymNodeDescription, PerformanceHistoryResponse, RewardedSetResponse, SignerInformationResponse, + NymNodeDescriptionV1, NymNodeDescriptionV2, PerformanceHistoryResponse, RewardedSetResponse, + SignerInformationResponse, }; use nym_api_requests::nym_nodes::{ NodesByAddressesRequestBody, NodesByAddressesResponse, PaginatedCachedNodesResponseV1, @@ -116,11 +117,12 @@ pub trait NymApiClientExt: ApiClient { } #[tracing::instrument(level = "debug", skip_all)] + #[deprecated(note = "use .get_nodes_described_v2 instead")] async fn get_nodes_described( &self, page: Option, per_page: Option, - ) -> Result, NymAPIError> { + ) -> Result, NymAPIError> { let mut params = Vec::new(); if let Some(page) = page { @@ -142,6 +144,33 @@ pub trait NymApiClientExt: ApiClient { .await } + #[tracing::instrument(level = "debug", skip_all)] + async fn get_nodes_described_v2( + &self, + page: Option, + per_page: Option, + ) -> Result, NymAPIError> { + let mut params = Vec::new(); + + if let Some(page) = page { + params.push(("page", page.to_string())) + } + + if let Some(per_page) = per_page { + params.push(("per_page", per_page.to_string())) + } + + self.get_json( + &[ + routes::V2_API_VERSION, + routes::NYM_NODES_ROUTES, + routes::NYM_NODES_DESCRIBED, + ], + ¶ms, + ) + .await + } + async fn get_current_rewarded_set(&self) -> Result { self.get_rewarded_set().await } @@ -273,7 +302,9 @@ pub trait NymApiClientExt: ApiClient { Ok(SkimmedNodesWithMetadata::new(nodes, metadata)) } - async fn get_all_described_nodes(&self) -> Result, NymAPIError> { + #[deprecated(note = "use .get_all_described_nodes_v2 instead")] + #[allow(deprecated)] + async fn get_all_described_nodes(&self) -> Result, NymAPIError> { // TODO: deal with paging in macro or some helper function or something, because it's the same pattern everywhere let mut page = 0; let mut descriptions = Vec::new(); @@ -292,6 +323,25 @@ pub trait NymApiClientExt: ApiClient { Ok(descriptions) } + async fn get_all_described_nodes_v2(&self) -> Result, NymAPIError> { + // TODO: deal with paging in macro or some helper function or something, because it's the same pattern everywhere + let mut page = 0; + let mut descriptions = Vec::new(); + + loop { + let mut res = self.get_nodes_described_v2(Some(page), None).await?; + + descriptions.append(&mut res.data); + if descriptions.len() < res.pagination.total { + page += 1 + } else { + break; + } + } + + Ok(descriptions) + } + #[tracing::instrument(level = "debug", skip_all)] async fn get_nym_nodes( &self, diff --git a/common/commands/src/validator/mixnet/query/query_all_gateways.rs b/common/commands/src/validator/mixnet/query/query_all_gateways.rs index f03fb61d28..516a533315 100644 --- a/common/commands/src/validator/mixnet/query/query_all_gateways.rs +++ b/common/commands/src/validator/mixnet/query/query_all_gateways.rs @@ -14,7 +14,7 @@ pub struct Args { } pub async fn query(args: Args, client: &QueryClientWithNyxd) { - match client.get_all_cached_described_nodes().await { + match client.get_all_cached_described_nodes_v2().await { Ok(res) => match args.identity_key { Some(identity_key) => { let node = res.iter().find(|node| { diff --git a/common/commands/src/validator/mixnet/query/query_all_mixnodes.rs b/common/commands/src/validator/mixnet/query/query_all_mixnodes.rs index ef5b4a9548..b1a81780b9 100644 --- a/common/commands/src/validator/mixnet/query/query_all_mixnodes.rs +++ b/common/commands/src/validator/mixnet/query/query_all_mixnodes.rs @@ -14,7 +14,7 @@ pub struct Args { } pub async fn query(args: Args, client: &QueryClientWithNyxd) { - match client.get_all_cached_described_nodes().await { + match client.get_all_cached_described_nodes_v2().await { Ok(res) => match args.identity_key { Some(identity_key) => { let node = res.iter().find(|node| { diff --git a/common/http-api-common/src/lib.rs b/common/http-api-common/src/lib.rs index 9ed715cde2..3ce026396e 100644 --- a/common/http-api-common/src/lib.rs +++ b/common/http-api-common/src/lib.rs @@ -11,8 +11,10 @@ pub mod response; #[cfg(feature = "output")] pub use response::*; +pub use ::bincode::Options as BincodeOptions; + // be explicit about those values because bincode uses different defaults in different places -pub fn make_bincode_serializer() -> impl ::bincode::Options { +pub fn make_bincode_serializer() -> impl BincodeOptions { use ::bincode::Options; ::bincode::DefaultOptions::new() .with_little_endian() diff --git a/common/nym-kkt/src/ciphersuite.rs b/common/nym-kkt/src/ciphersuite.rs index 7dd5a85ae3..a822afdf7d 100644 --- a/common/nym-kkt/src/ciphersuite.rs +++ b/common/nym-kkt/src/ciphersuite.rs @@ -1,14 +1,15 @@ // Copyright 2025 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use std::fmt::Display; - use libcrux_kem::{Algorithm, MlKem768PublicKey}; use nym_crypto::asymmetric::ed25519; +use std::fmt::Display; use crate::error::KKTError; -pub const HASH_LEN_256: usize = 32; +pub const DEFAULT_HASH_LEN: usize = 32; +const _: () = assert!(DEFAULT_HASH_LEN <= u8::MAX as usize); + pub const CIPHERSUITE_ENCODING_LEN: usize = 4; pub const CURVE25519_KEY_LEN: usize = 32; @@ -20,6 +21,7 @@ pub enum HashFunction { SHAKE256, SHA256, } + impl Display for HashFunction { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str(match self { @@ -172,7 +174,7 @@ impl Ciphersuite { l } } - None => HASH_LEN_256 as u8, + None => DEFAULT_HASH_LEN as u8, }; Ok(Self { hash_function, @@ -219,7 +221,7 @@ impl Ciphersuite { HashFunction::SHA256 => 3, }, match self.hash_length as usize { - HASH_LEN_256 => 0u8, + DEFAULT_HASH_LEN => 0u8, _ => self.hash_length, }, match self.signature_scheme { diff --git a/common/nym-kkt/src/encryption.rs b/common/nym-kkt/src/encryption.rs index 7bb7610622..b80abffbed 100644 --- a/common/nym-kkt/src/encryption.rs +++ b/common/nym-kkt/src/encryption.rs @@ -181,7 +181,7 @@ mod test { use crate::encryption::{decrypt_kkt_frame, encrypt_kkt_frame}; use crate::frame::{KKT_SESSION_ID_LEN, KKTFrame}; use crate::{ - ciphersuite::HASH_LEN_256, + ciphersuite::DEFAULT_HASH_LEN, encryption::{KKTSessionSecret, decrypt, encrypt}, key_utils::generate_keypair_x25519, }; @@ -209,7 +209,7 @@ mod test { fn test_encryption() { let mut rng = rng(); - let mut secret_key = [0u8; HASH_LEN_256]; + let mut secret_key = [0u8; DEFAULT_HASH_LEN]; rng.fill_bytes(&mut secret_key); let mut plaintext = vec![0; 100]; diff --git a/common/nym-lp/Cargo.toml b/common/nym-lp/Cargo.toml index 58c19b38f8..58f376b1e5 100644 --- a/common/nym-lp/Cargo.toml +++ b/common/nym-lp/Cargo.toml @@ -38,6 +38,7 @@ zeroize = { workspace = true, features = ["zeroize_derive"] } criterion = { version = "0.5", features = ["html_reports"] } rand_chacha = "0.3" nym-crypto = { path = "../crypto", features = ["rand"] } +nym-test-utils = { workspace = true } [[bench]] diff --git a/common/nym-lp/src/error.rs b/common/nym-lp/src/error.rs index 7beae8365b..290289fd0a 100644 --- a/common/nym-lp/src/error.rs +++ b/common/nym-lp/src/error.rs @@ -90,4 +90,7 @@ pub enum LpError { /// Received an LP packet with an incompatible, legacy, version #[error("incompatible LP packet version. got: {got}, lowest supported: {lowest_supported}")] IncompatibleLegacyPacketVersion { got: u8, lowest_supported: u8 }, + + #[error("attempted to create an LP responder without providing a valid KEM key")] + ResponderWithMissingKEMKey, } diff --git a/common/nym-lp/src/kkt_orchestrator.rs b/common/nym-lp/src/kkt_orchestrator.rs index f07168a933..98e3cdff71 100644 --- a/common/nym-lp/src/kkt_orchestrator.rs +++ b/common/nym-lp/src/kkt_orchestrator.rs @@ -184,6 +184,7 @@ pub fn handle_request<'a>( #[cfg(test)] mod tests { use super::*; + use crate::peer::mock_peers; use nym_kkt::ciphersuite::{HashFunction, KEM, SignatureScheme}; use nym_kkt::key_utils::{ generate_keypair_ed25519, generate_keypair_libcrux, generate_keypair_x25519, @@ -362,16 +363,8 @@ mod tests { #[test] fn test_hash_mismatch_rejected() { - let mut rng = rand09::rng(); - - // Generate Ed25519 keypairs for both parties - let initiator_ed25519_keypair = generate_keypair_ed25519(&mut rng, Some(0)); - let responder_ed25519_keypair = generate_keypair_ed25519(&mut rng, Some(1)); - - let responder_x25519 = generate_keypair_x25519(&mut rng); - - let (_, responder_kem_pk) = generate_keypair_libcrux(&mut rng, KEM::X25519).unwrap(); - let responder_kem_key = EncapsulationKey::X25519(responder_kem_pk); + let (init, resp) = mock_peers(); + let responder_kem_key = resp.encapsulate_kem_key().unwrap(); let ciphersuite = Ciphersuite::resolve_ciphersuite( KEM::X25519, @@ -386,16 +379,16 @@ mod tests { let (session_secret, context, request_data) = create_request( ciphersuite, - initiator_ed25519_keypair.private_key(), - responder_x25519.public_key(), + init.ed25519.private_key(), + resp.x25519.public_key(), ) .unwrap(); let response_data = handle_request( &request_data, - Some(initiator_ed25519_keypair.public_key()), - responder_ed25519_keypair.private_key(), - responder_x25519.private_key(), + Some(init.ed25519.public_key()), + resp.ed25519.private_key(), + resp.x25519.private_key(), &responder_kem_key, ) .unwrap(); @@ -404,7 +397,7 @@ mod tests { let result = process_response( context, &session_secret, - responder_ed25519_keypair.public_key(), + resp.ed25519.public_key(), &wrong_hash, // Wrong! &response_data, ); diff --git a/common/nym-lp/src/lib.rs b/common/nym-lp/src/lib.rs index 7e8eae8bd1..7a154cc37e 100644 --- a/common/nym-lp/src/lib.rs +++ b/common/nym-lp/src/lib.rs @@ -8,6 +8,7 @@ pub mod kkt_orchestrator; pub mod message; pub mod noise_protocol; pub mod packet; +pub mod peer; pub mod psk; pub mod replay; pub mod session; @@ -29,50 +30,20 @@ pub const NOISE_PSK_INDEX: u8 = 3; #[cfg(test)] pub fn sessions_for_tests() -> (LpSession, LpSession) { - use nym_crypto::asymmetric::{ed25519, x25519}; - use std::sync::Arc; - - let mut rng = rand::thread_rng(); - - // X25519 keypairs for Noise protocol - let keypair_1 = Arc::new(x25519::KeyPair::new(&mut rng)); - let keypair_2 = Arc::new(x25519::KeyPair::new(&mut rng)); + let (init, resp) = crate::peer::mock_peers(); // Use a fixed receiver_index for deterministic tests let receiver_index: u32 = 12345; - // Ed25519 keypairs for PSQ authentication (placeholders for testing) - let ed25519_keypair_1 = ed25519::KeyPair::from_secret([1u8; 32], 0); - let ed25519_keypair_2 = ed25519::KeyPair::from_secret([2u8; 32], 1); - - let ed25519_keypair1_pubkey = *ed25519_keypair_1.public_key(); - // Use consistent salt for deterministic tests let salt = [1u8; 32]; - // PSQ will always derive the PSK during handshake using X25519 as DHKEM + let initiator_session = + LpSession::new(receiver_index, true, init.clone(), resp.as_remote(), &salt) + .expect("Test session creation failed"); - let initiator_session = LpSession::new( - receiver_index, - true, - Arc::new(ed25519_keypair_1), - keypair_1.clone(), - ed25519_keypair_2.public_key(), - keypair_2.public_key(), - &salt, - ) - .expect("Test session creation failed"); - - let responder_session = LpSession::new( - receiver_index, - false, - Arc::new(ed25519_keypair_2), - keypair_2.clone(), - &ed25519_keypair1_pubkey, - keypair_1.public_key(), - &salt, - ) - .expect("Test session creation failed"); + let responder_session = LpSession::new(receiver_index, false, resp, init.as_remote(), &salt) + .expect("Test session creation failed"); (initiator_session, responder_session) } @@ -84,10 +55,10 @@ mod tests { use crate::session_manager::SessionManager; use crate::{LpError, sessions_for_tests}; use bytes::BytesMut; - use std::sync::Arc; // Import the new standalone functions use crate::codec::{parse_lp_packet, serialize_lp_packet}; + use crate::peer::mock_peers; #[test] fn test_replay_protection_integration() { @@ -190,19 +161,12 @@ mod tests { #[test] fn test_session_manager_integration() { - use nym_crypto::asymmetric::ed25519; - // Create session manager let local_manager = SessionManager::new(); let remote_manager = SessionManager::new(); // Generate Ed25519 keypairs for PSQ authentication - let ed25519_keypair_local = ed25519::KeyPair::from_secret([8u8; 32], 0); - let ed25519_keypair_remote = ed25519::KeyPair::from_secret([9u8; 32], 1); - - let ed25519_keypair_local_pubkey = *ed25519_keypair_local.public_key(); - let x25519_keypair_local_pubkey = ed25519_keypair_local_pubkey.to_x25519().unwrap(); - let x25519_keypair_remote_pubkey = ed25519_keypair_remote.public_key().to_x25519().unwrap(); + let (init, resp) = mock_peers(); // Use fixed receiver_index for deterministic test let receiver_index: u32 = 54321; @@ -214,23 +178,15 @@ mod tests { let _ = local_manager .create_session_state_machine( receiver_index, - Arc::new(ed25519_keypair_local), - ed25519_keypair_remote.public_key(), - &x25519_keypair_remote_pubkey, true, + init.clone(), + resp.as_remote(), &salt, ) .unwrap(); let _ = remote_manager - .create_session_state_machine( - receiver_index, - Arc::new(ed25519_keypair_remote), - &ed25519_keypair_local_pubkey, - &x25519_keypair_local_pubkey, - false, - &salt, - ) + .create_session_state_machine(receiver_index, false, resp, init.as_remote(), &salt) .unwrap(); // === Packet 1 (Counter 0 - Should succeed) === let packet1 = LpPacket { diff --git a/common/nym-lp/src/message.rs b/common/nym-lp/src/message.rs index 05d1a39c6c..8447b44684 100644 --- a/common/nym-lp/src/message.rs +++ b/common/nym-lp/src/message.rs @@ -1,6 +1,7 @@ // Copyright 2025 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use crate::peer::LpRemotePeer; use crate::{BOOTSTRAP_RECEIVER_IDX, LpError}; use bytes::{BufMut, BytesMut}; use num_enum::{IntoPrimitive, TryFromPrimitive}; @@ -109,6 +110,11 @@ impl ClientHelloData { salt: b[68..].try_into().unwrap(), }) } + + /// Attempt to construct remote peer information based on the data provided in this packet. + pub fn to_remote_peer(&self) -> LpRemotePeer { + LpRemotePeer::new(self.client_ed25519_public_key, self.client_lp_public_key) + } } #[derive(Debug, Copy, Clone, PartialEq, Eq, IntoPrimitive, TryFromPrimitive)] diff --git a/common/nym-lp/src/peer.rs b/common/nym-lp/src/peer.rs new file mode 100644 index 0000000000..02a6372ea6 --- /dev/null +++ b/common/nym-lp/src/peer.rs @@ -0,0 +1,138 @@ +// Copyright 2026 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use nym_crypto::asymmetric::{ed25519, x25519}; +use std::sync::Arc; + +/// Representation of a local Lewes Protocol peer +/// encapsulating all the known information and keys. +#[derive(Debug, Clone)] +pub struct LpLocalPeer { + /// Local Ed25519 keys for PSQ authentication + pub(crate) ed25519: Arc, + + /// Local x25519 keys (Noise static key) + pub(crate) x25519: Arc, + + /// Local KEM key used for PSQ + pub(crate) kem_psq: Option>, +} + +impl LpLocalPeer { + pub fn new(ed25519: Arc, x25519: Arc) -> Self { + LpLocalPeer { + ed25519, + x25519, + kem_psq: None, + } + } + + #[must_use] + pub fn with_kem_psq_key(mut self, key: Arc) -> Self { + self.kem_psq = Some(key); + self + } + + pub fn ed25519(&self) -> &Arc { + &self.ed25519 + } + + pub fn x25519(&self) -> &Arc { + &self.x25519 + } + + /// Convert this `LpLocalPeer` into a valid `LpRemotePeer` that can be used within tests + #[doc(hidden)] + pub fn as_remote(&self) -> LpRemotePeer { + let expected_kem_key_digest = match &self.kem_psq { + None => Vec::new(), + Some(kem_keys) => nym_kkt::key_utils::hash_key_bytes( + &nym_kkt::ciphersuite::HashFunction::Blake3, + nym_kkt::ciphersuite::DEFAULT_HASH_LEN, + kem_keys.public_key().as_bytes(), + ), + }; + LpRemotePeer { + ed25519_public: *self.ed25519.public_key(), + x25519_public: *self.x25519.public_key(), + expected_kem_key_digest, + } + } + + // this is only exposed in tests as ideally we should be storing the proper types to begin with + #[cfg(test)] + pub fn encapsulate_kem_key(&self) -> Option> { + let pk_bytes = self.kem_psq.as_ref()?.public_key().to_bytes(); + let libcrux_pk = + libcrux_kem::PublicKey::decode(libcrux_kem::Algorithm::X25519, &pk_bytes).ok()?; + + Some(nym_kkt::ciphersuite::EncapsulationKey::X25519(libcrux_pk)) + } +} + +/// Representation of a remote Lewes Protocol peer +/// encapsulating all the known information and keys. +#[derive(Debug, Clone)] +pub struct LpRemotePeer { + /// Remote Ed25519 public key for PSQ authentication + pub(crate) ed25519_public: ed25519::PublicKey, + + /// Remote X25519 public key (Noise static key) + pub(crate) x25519_public: x25519::PublicKey, + + /// Expected digest of the remote's KEM key + // TODO: this might have to be replaced by a HashMap> instead + pub(crate) expected_kem_key_digest: Vec, +} + +impl LpRemotePeer { + pub fn new(ed25519_public: ed25519::PublicKey, x25519_public: x25519::PublicKey) -> Self { + LpRemotePeer { + ed25519_public, + x25519_public, + expected_kem_key_digest: vec![], + } + } + + pub fn ed25519(&self) -> ed25519::PublicKey { + self.ed25519_public + } + + pub fn x25519(&self) -> x25519::PublicKey { + self.x25519_public + } + + #[must_use] + pub fn with_kem_key_digest(mut self, expected_kem_key_digest: Vec) -> Self { + self.expected_kem_key_digest = expected_kem_key_digest; + self + } +} + +#[cfg(test)] +pub fn mock_peer() -> LpLocalPeer { + // use deterministic rng + let mut rng = nym_test_utils::helpers::deterministic_rng(); + random_peer(&mut rng) +} + +#[cfg(test)] +pub fn random_peer(rng: &mut R) -> LpLocalPeer { + let ed25519 = Arc::new(ed25519::KeyPair::new(rng)); + let x25519 = Arc::new(ed25519.to_x25519()); + let kem_psq = Some(x25519.clone()); + + LpLocalPeer { + ed25519, + x25519, + kem_psq, + } +} + +#[cfg(test)] +pub fn mock_peers() -> (LpLocalPeer, LpLocalPeer) { + // use deterministic rng + let mut rng = nym_test_utils::helpers::deterministic_rng(); + + (random_peer(&mut rng), random_peer(&mut rng)) +} diff --git a/common/nym-lp/src/session.rs b/common/nym-lp/src/session.rs index 774f148146..024df03162 100644 --- a/common/nym-lp/src/session.rs +++ b/common/nym-lp/src/session.rs @@ -10,19 +10,19 @@ use crate::codec::OuterAeadKey; use crate::message::{EncryptedDataPayload, HandshakeData}; use crate::noise_protocol::{NoiseError, NoiseProtocol, ReadResult}; use crate::packet::LpHeader; +use crate::peer::{LpLocalPeer, LpRemotePeer}; use crate::psk::{ derive_subsession_psk, psq_initiator_create_message, psq_responder_process_message, }; use crate::replay::ReceivingKeyCounterValidator; use crate::{LpError, LpMessage, LpPacket}; -use nym_crypto::asymmetric::{ed25519, x25519}; +use nym_crypto::asymmetric::x25519; use nym_kkt::ciphersuite::{DecapsulationKey, EncapsulationKey}; use nym_kkt::encryption::KKTSessionSecret; -use nym_kkt::kkt::decrypt_kkt_response_frame; +use nym_kkt::kkt::validate_kem_response; use parking_lot::Mutex; use rand::RngCore; use snow::Builder; -use std::sync::Arc; use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; use std::time::{SystemTime, UNIX_EPOCH}; use zeroize::{Zeroize, ZeroizeOnDrop}; @@ -124,7 +124,7 @@ pub enum PSQState { NotStarted, /// Initiator has sent PSQ ciphertext and is waiting for confirmation. - /// PSK is already derived but we don't encrypt outgoing packets yet + /// PSK is already derived, but we don't encrypt outgoing packets yet /// because the responder may not have processed our message yet. InitiatorWaiting { /// The derived PSK, stored until we transition to Completed @@ -183,18 +183,13 @@ pub struct LpSession { /// This prevents transport mode operations from running with the insecure dummy `[0u8; 32]` PSK. psk_injected: AtomicBool, - // PSQ-related keys stored for handshake - /// Local Ed25519 keys for PSQ authentication - local_ed25519: Arc, + /// Representation of a local Lewes Protocol peer + /// encapsulating all the known information and keys. + local_peer: LpLocalPeer, - /// Remote Ed25519 public key for PSQ authentication - remote_ed25519_public: ed25519::PublicKey, - - /// Local x25519 keys (Noise static key) - local_x25519: Arc, - - /// Remote X25519 public key (Noise static key) - remote_x25519_public: x25519::PublicKey, + /// Representation of a remote Lewes Protocol peer + /// encapsulating all the known information and keys. + remote_peer: LpRemotePeer, /// Salt for PSK derivation salt: [u8; 32], @@ -288,7 +283,7 @@ impl LpSession { /// This is used for KKT protocol when the responder needs to send their /// KEM public key in the KKT response. pub fn local_x25519_public(&self) -> x25519::PublicKey { - *self.local_x25519.public_key() + *self.local_peer.x25519.public_key() } /// Returns the remote X25519 public key. @@ -296,7 +291,7 @@ impl LpSession { /// Used for tie-breaking in simultaneous subsession initiation. /// Lower key loses and becomes responder. pub fn remote_x25519_public(&self) -> &x25519::PublicKey { - &self.remote_x25519_public + &self.remote_peer.x25519_public } /// Returns the outer AEAD key for packet encryption/decryption. @@ -349,20 +344,21 @@ impl LpSession { /// /// * `id` - Session identifier /// * `is_initiator` - True if this side initiates the Noise handshake. - /// * `local_ed25519_keypair` - This side's Ed25519 keypair for PSQ authentication - /// * `local_x25519_keypair` - This side's X25519 keypair for Noise protocol and DHKEM - /// * `remote_ed25519_key` - Peer's Ed25519 public key for PSQ authentication - /// * `remote_x25519_key` - Peer's X25519 public key for Noise protocol and DHKEM + /// * `local_peer` - This side's LP peer's keys + /// * `remote_peer` - The remote's LP peer's keys /// * `salt` - Salt for PSK derivation pub fn new( id: u32, is_initiator: bool, - local_ed25519_keypair: Arc, - local_x25519_keypair: Arc, - remote_ed25519_key: &ed25519::PublicKey, - remote_x25519_key: &x25519::PublicKey, + local_peer: LpLocalPeer, + remote_peer: LpRemotePeer, salt: &[u8; 32], ) -> Result { + // if we're LP responder, we **must** set our kem key + if !is_initiator && local_peer.kem_psq.is_none() { + return Err(LpError::ResponderWithMissingKEMKey); + } + // XKpsk3 pattern requires remote static key known upfront (XK) // and PSK mixed at position 3. This provides forward secrecy with PSK authentication. let pattern_name = crate::NOISE_PATTERN; @@ -371,10 +367,10 @@ impl LpSession { let params = pattern_name.parse()?; let builder = Builder::new(params); - let local_key_bytes = local_x25519_keypair.private_key().as_bytes(); + let local_key_bytes = local_peer.x25519.private_key().as_bytes(); let builder = builder.local_private_key(local_key_bytes); - let remote_key_bytes = remote_x25519_key.to_bytes(); + let remote_key_bytes = remote_peer.x25519_public.to_bytes(); let builder = builder.remote_public_key(&remote_key_bytes); // Initialize with dummy PSK - real PSK will be injected via set_psk() during handshake @@ -410,10 +406,8 @@ impl LpSession { sending_counter: AtomicU64::new(0), receiving_counter: Mutex::new(ReceivingKeyCounterValidator::default()), psk_injected: AtomicBool::new(false), - local_ed25519: local_ed25519_keypair.clone(), - remote_ed25519_public: *remote_ed25519_key, - local_x25519: local_x25519_keypair, - remote_x25519_public: *remote_x25519_key, + local_peer, + remote_peer, salt: *salt, outer_aead_key: Mutex::new(None), pq_shared_secret: Mutex::new(None), @@ -503,6 +497,50 @@ impl LpSession { self.psk_handle.lock().clone() } + /// Returns the reference to the KEM Public key of the peer (if available). + pub fn get_kem_key_handle(&self) -> Result<&x25519::PublicKey, LpError> { + self.local_peer + .kem_psq + .as_ref() + .map(|kp| kp.public_key()) + .ok_or(LpError::ResponderWithMissingKEMKey) + } + + // TODO: missing Zeroize + /// Convert local KEM PSQ keys to typed EncapsulationKey / EncapsulationKey + pub fn encapsulated_kem_keys( + &self, + ) -> Result<(DecapsulationKey<'_>, EncapsulationKey<'_>), LpError> { + let kem_keys = self + .local_peer + .kem_psq + .as_ref() + .ok_or(LpError::ResponderWithMissingKEMKey)?; + + let libcrux_private_key = libcrux_kem::PrivateKey::decode( + libcrux_kem::Algorithm::X25519, + kem_keys.private_key().as_bytes(), + ) + .map_err(|e| { + LpError::KKTError(format!( + "Failed to convert X25519 private key to libcrux PrivateKey: {e:?}", + )) + })?; + let dec_key = DecapsulationKey::X25519(libcrux_private_key); + + let libcrux_public_key = libcrux_kem::PublicKey::decode( + libcrux_kem::Algorithm::X25519, + kem_keys.public_key().as_bytes(), + ) + .map_err(|e| { + LpError::KKTError(format!( + "Failed to convert X25519 public key to libcrux PublicKey: {e:?}", + )) + })?; + let enc_key = EncapsulationKey::X25519(libcrux_public_key); + Ok((dec_key, enc_key)) + } + /// Prepares a KKT (KEM Key Transfer) request message. /// /// This should be called by the initiator before starting the Noise handshake @@ -552,8 +590,8 @@ impl LpSession { match request_kem_key( &mut rng, ciphersuite, - self.local_ed25519.private_key(), - &self.remote_x25519_public, + self.local_peer.ed25519.private_key(), + &self.remote_peer.x25519_public, ) { Ok((session_secret, context, request_bytes)) => { // Store context for response validation @@ -582,29 +620,14 @@ impl LpSession { /// # Arguments /// /// * `response_bytes` - Raw KKT response message from responder - /// * `expected_key_hash` - Optional expected hash of responder's KEM key. - /// - `Some(hash)`: Full KKT validation (signature + hash) - use when directory service available - /// - `None`: Signature-only validation (hash computed from received key) - temporary mode + /// * `expected_key_hash` - Expected hash of responder's KEM key. /// /// # Returns /// /// * `Ok(())` - KKT exchange completed, KEM key stored /// * `Err(LpError)` - Signature verification failed, hash mismatch, or invalid state /// - /// # Note - /// - /// When None is passed, the function computes the hash from the received key and validates against - /// that (effectively signature-only mode). This allows easy upgrade: just pass Some(directory_hash) - /// when directory service becomes available. The full KKT protocol with hash pinning provides - /// protection against key substitution attacks. - pub fn process_kkt_response( - &self, - response_bytes: &[u8], - expected_key_hash: Option<&[u8]>, - ) -> Result<(), LpError> { - use nym_kkt::key_utils::hash_encapsulation_key; - use nym_kkt::kkt::validate_kem_response; - + pub fn process_kkt_response(&self, response_bytes: &[u8]) -> Result<(), LpError> { let mut kkt_state = self.kkt_state.lock(); // Extract context from waiting state @@ -620,33 +643,12 @@ impl LpSession { } }; - // Determine hash to validate against - let hash_for_validation: Vec; - let hash_ref = match expected_key_hash { - Some(hash) => hash, - None => { - // Signature-only mode: extract key from response and compute its hash - // This effectively bypasses hash validation while keeping signature validation - let (frame, _) = decrypt_kkt_response_frame(&session_secret, response_bytes) - .map_err(|e| { - LpError::Internal(format!("Failed to decrypt KKT response: {:?}", e)) - })?; - - hash_for_validation = hash_encapsulation_key( - &context.ciphersuite().hash_function(), - context.ciphersuite().hash_len(), - frame.body_ref(), - ); - &hash_for_validation - } - }; - // Validate response and extract KEM key let kem_pk = validate_kem_response( &mut context, &session_secret, - &self.remote_ed25519_public, - hash_ref, + &self.remote_peer.ed25519_public, + &self.remote_peer.expected_kem_key_digest, response_bytes, ) .map_err(|e| LpError::Internal(format!("KKT response validation failed: {:?}", e)))?; @@ -688,9 +690,9 @@ impl LpSession { let response_bytes = handle_kem_request( &mut rng, request_bytes, - Some(&self.remote_ed25519_public), // Verify initiator signature - self.local_ed25519.private_key(), // Sign response - self.local_x25519.private_key(), + Some(&self.remote_peer.ed25519_public), // Verify initiator signature + self.local_peer.ed25519.private_key(), // Sign response + self.local_peer.x25519.private_key(), responder_kem_pk, ) .map_err(|e| LpError::Internal(format!("KKT request handling failed: {:?}", e)))?; @@ -742,11 +744,11 @@ impl LpSession { let session_context = self.id.to_le_bytes(); let psq_result = match psq_initiator_create_message( - self.local_x25519.private_key(), - &self.remote_x25519_public, + self.local_peer.x25519.private_key(), + &self.remote_peer.x25519_public, remote_kem, - self.local_ed25519.private_key(), - self.local_ed25519.public_key(), + self.local_peer.ed25519.private_key(), + self.local_peer.ed25519.public_key(), &self.salt, &session_context, ) { @@ -879,42 +881,16 @@ impl LpSession { let psq_payload = &payload[2..2 + psq_len]; let noise_payload = &payload[2 + psq_len..]; - // Convert X25519 local keys to DecapsulationKey/EncapsulationKey (DHKEM) - let local_private_bytes = &self.local_x25519.private_key().to_bytes(); - let libcrux_private_key = libcrux_kem::PrivateKey::decode( - libcrux_kem::Algorithm::X25519, - local_private_bytes, - ) - .map_err(|e| { - LpError::KKTError(format!( - "Failed to convert X25519 private key to libcrux PrivateKey: {:?}", - e - )) - })?; - let dec_key = DecapsulationKey::X25519(libcrux_private_key); - - let local_public_key = self.local_x25519_public(); - let local_public_bytes = local_public_key.as_bytes(); - let libcrux_public_key = libcrux_kem::PublicKey::decode( - libcrux_kem::Algorithm::X25519, - local_public_bytes, - ) - .map_err(|e| { - LpError::KKTError(format!( - "Failed to convert X25519 public key to libcrux PublicKey: {:?}", - e - )) - })?; - let enc_key = EncapsulationKey::X25519(libcrux_public_key); + let (dec_key, enc_key) = self.encapsulated_kem_keys()?; // Decapsulate PSK from PSQ payload using X25519 as DHKEM let session_context = self.id.to_le_bytes(); let psq_result = match psq_responder_process_message( - self.local_x25519.private_key(), - &self.remote_x25519_public, + self.local_peer.x25519.private_key(), + &self.remote_peer.x25519_public, (&dec_key, &enc_key), - &self.remote_ed25519_public, + &self.remote_peer.ed25519_public, psq_payload, &self.salt, &session_context, @@ -1170,8 +1146,8 @@ impl LpSession { let pattern_name = "Noise_KKpsk0_25519_ChaChaPoly_SHA256"; let params = pattern_name.parse()?; - let local_key_bytes = self.local_x25519.private_key().to_bytes(); - let remote_key_bytes = self.remote_x25519_public.to_bytes(); + let local_key_bytes = self.local_peer.x25519.private_key().to_bytes(); + let remote_key_bytes = self.remote_x25519_public().to_bytes(); let builder = Builder::new(params) .local_private_key(&local_key_bytes) @@ -1188,13 +1164,10 @@ impl LpSession { index: subsession_index, noise_state: Mutex::new(NoiseProtocol::new(handshake_state)), is_initiator, - // Copy key material from parent for into_session() conversion - local_ed25519: self.local_ed25519.clone(), - remote_ed25519_public: self.remote_ed25519_public, - remote_x25519_public: self.remote_x25519_public, + local_peer: self.local_peer.clone(), + remote_peer: self.remote_peer.clone(), pq_shared_secret: PqSharedSecret::new(pq_secret), subsession_psk, - local_x25519: self.local_x25519.clone(), }) } } @@ -1224,17 +1197,14 @@ pub struct SubsessionHandshake { is_initiator: bool, // Key material inherited from parent session for into_session() conversion - /// Local Ed25519 keys (for PSQ auth if needed) - local_ed25519: Arc, + /// Representation of a local Lewes Protocol peer + /// encapsulating all the known information and keys. + local_peer: LpLocalPeer, - /// Local x25519 keys (Noise static key) - local_x25519: Arc, + /// Representation of a remote Lewes Protocol peer + /// encapsulating all the known information and keys. + remote_peer: LpRemotePeer, - /// Remote Ed25519 public key - remote_ed25519_public: ed25519::PublicKey, - - /// Remote X25519 public key (Noise static key) - remote_x25519_public: x25519::PublicKey, /// PQ shared secret inherited from parent (for creating further subsessions) pq_shared_secret: PqSharedSecret, /// Subsession PSK (for deriving outer AEAD key) @@ -1327,10 +1297,8 @@ impl SubsessionHandshake { sending_counter: AtomicU64::new(0), receiving_counter: Mutex::new(ReceivingKeyCounterValidator::new(0)), psk_injected: AtomicBool::new(true), // PSK was in KKpsk0 - local_ed25519: self.local_ed25519, - remote_ed25519_public: self.remote_ed25519_public, - local_x25519: self.local_x25519, - remote_x25519_public: self.remote_x25519_public, + local_peer: self.local_peer, + remote_peer: self.remote_peer, salt, outer_aead_key: Mutex::new(Some(outer_key)), pq_shared_secret: Mutex::new(Some(self.pq_shared_secret)), @@ -1346,54 +1314,37 @@ impl SubsessionHandshake { #[cfg(test)] mod tests { use super::*; + use crate::peer::mock_peers; use crate::{replay::ReplayError, sessions_for_tests}; + use nym_crypto::asymmetric::ed25519; use rand::thread_rng; // Helper function to generate keypairs for tests - fn generate_keypair() -> x25519::KeyPair { + fn generate_x25519_keypair() -> x25519::KeyPair { x25519::KeyPair::new(&mut thread_rng()) } // Helper function to create a session with real keys for handshake tests - fn create_handshake_test_session( - receiver_index: u32, - is_initiator: bool, - local_keys: &x25519::KeyPair, - remote_pub_key: &x25519::PublicKey, - ) -> LpSession { - use nym_crypto::asymmetric::ed25519; + fn create_handshake_test_session(receiver_index: u32, is_initiator: bool) -> LpSession { + let (keys_1, keys_2) = mock_peers(); // Create Ed25519 keypairs that correspond to initiator/responder roles // Initiator uses [1u8], Responder uses [2u8] - let (local_ed25519_seed, remote_ed25519_seed) = if is_initiator { - ([1u8; 32], [2u8; 32]) + let (local, remote) = if is_initiator { + (keys_1, keys_2.as_remote()) } else { - ([2u8; 32], [1u8; 32]) + (keys_2, keys_1.as_remote()) }; - let local_ed25519 = ed25519::KeyPair::from_secret(local_ed25519_seed, 0); - let local_x25519 = x25519::PrivateKey::from_bytes(local_keys.private_key().as_bytes()) - .unwrap() - .into(); - let remote_ed25519 = ed25519::KeyPair::from_secret(remote_ed25519_seed, 1); - let salt = [0u8; 32]; // Test salt // PSQ will derive the PSK during handshake using X25519 as DHKEM - let session = LpSession::new( - receiver_index, - is_initiator, - Arc::new(local_ed25519), - Arc::new(local_x25519), - remote_ed25519.public_key(), - remote_pub_key, - &salt, - ) - .expect("Test session creation failed"); + let session = LpSession::new(receiver_index, is_initiator, local, remote.clone(), &salt) + .expect("Test session creation failed"); // Initialize KKT state to Completed for tests (bypasses KKT exchange) // This simulates having already received the remote party's KEM key via KKT - session.set_kkt_completed_for_test(remote_pub_key); + session.set_kkt_completed_for_test(&remote.x25519_public); session } @@ -1489,21 +1440,13 @@ mod tests { #[test] fn test_prepare_handshake_message_initial_state() { - let initiator_keys = Arc::new(generate_keypair()); - let responder_keys = Arc::new(generate_keypair()); let receiver_index = 12345u32; - let initiator_session = create_handshake_test_session( - receiver_index, - true, - &initiator_keys, - responder_keys.public_key(), - ); + let initiator_session = create_handshake_test_session(receiver_index, true); let responder_session = create_handshake_test_session( receiver_index, false, - &responder_keys, - initiator_keys.public_key(), // Responder also needs initiator's key for XK + // Responder also needs initiator's key for XK ); // Initiator should have a message to send immediately (-> e) @@ -1521,22 +1464,10 @@ mod tests { #[test] fn test_process_handshake_message_first_step() { - let initiator_keys = Arc::new(generate_keypair()); - let responder_keys = Arc::new(generate_keypair()); let receiver_index = 12345u32; - let initiator_session = create_handshake_test_session( - receiver_index, - true, - &initiator_keys, - responder_keys.public_key(), - ); - let responder_session = create_handshake_test_session( - receiver_index, - false, - &responder_keys, - initiator_keys.public_key(), - ); + let initiator_session = create_handshake_test_session(receiver_index, true); + let responder_session = create_handshake_test_session(receiver_index, false); // 1. Initiator prepares the first message (-> e) let initiator_msg_result = initiator_session.prepare_handshake_message(); @@ -1567,21 +1498,8 @@ mod tests { #[test] fn test_handshake_driver_simulation() { - let initiator_keys = Arc::new(generate_keypair()); - let responder_keys = Arc::new(generate_keypair()); - - let initiator_session = create_handshake_test_session( - 12345u32, - true, - &initiator_keys, - responder_keys.public_key(), - ); - let responder_session = create_handshake_test_session( - 12345u32, - false, - &responder_keys, - initiator_keys.public_key(), - ); + let initiator_session = create_handshake_test_session(12345u32, true); + let responder_session = create_handshake_test_session(12345u32, false); let mut responder_to_initiator_msg = None; let mut rounds = 0; @@ -1662,21 +1580,9 @@ mod tests { #[test] fn test_encrypt_decrypt_after_handshake() { // --- Setup Handshake --- - let initiator_keys = Arc::new(generate_keypair()); - let responder_keys = Arc::new(generate_keypair()); - let initiator_session = create_handshake_test_session( - 12345u32, - true, - &initiator_keys, - responder_keys.public_key(), - ); - let responder_session = create_handshake_test_session( - 12345u32, - false, - &responder_keys, - initiator_keys.public_key(), - ); + let initiator_session = create_handshake_test_session(12345u32, true); + let responder_session = create_handshake_test_session(12345u32, false); // Drive handshake to completion (simplified loop from previous test) let mut i_msg = initiator_session @@ -1731,15 +1637,7 @@ mod tests { #[test] fn test_encrypt_decrypt_before_handshake() { - let initiator_keys = Arc::new(generate_keypair()); - let responder_keys = Arc::new(generate_keypair()); - - let initiator_session = create_handshake_test_session( - 12345u32, - true, - &initiator_keys, - responder_keys.public_key(), - ); + let initiator_session = create_handshake_test_session(12345u32, true); assert!(!initiator_session.is_handshake_complete()); @@ -1807,21 +1705,8 @@ mod tests { /// Test that PSQ runs during handshake and derives a PSK #[test] fn test_psq_handshake_runs_with_psk_injection() { - let initiator_keys = Arc::new(generate_keypair()); - let responder_keys = Arc::new(generate_keypair()); - - let initiator_session = create_handshake_test_session( - 12345u32, - true, - &initiator_keys, - responder_keys.public_key(), - ); - let responder_session = create_handshake_test_session( - 12345u32, - false, - &responder_keys, - initiator_keys.public_key(), - ); + let initiator_session = create_handshake_test_session(12345u32, true); + let responder_session = create_handshake_test_session(12345u32, false); // Drive the handshake let mut i_msg = initiator_session @@ -1884,8 +1769,8 @@ mod tests { fn test_x25519_to_kem_conversion() { use nym_kkt::ciphersuite::EncapsulationKey; - let initiator_keys = Arc::new(generate_keypair()); - let responder_keys = Arc::new(generate_keypair()); + let initiator_keys = generate_x25519_keypair(); + let responder_keys = generate_x25519_keypair(); // Verify we can convert X25519 public key to KEM format (as done in session.rs) let x25519_public_bytes = responder_keys.public_key().as_bytes(); @@ -1908,22 +1793,9 @@ mod tests { /// Test that PSQ actually derives a different PSK (not using dummy) #[test] fn test_psq_derived_psk_differs_from_dummy() { - let initiator_keys = Arc::new(generate_keypair()); - let responder_keys = Arc::new(generate_keypair()); - // Create sessions - they start with dummy PSK [0u8; 32] - let initiator_session = create_handshake_test_session( - 12345u32, - true, - &initiator_keys, - responder_keys.public_key(), - ); - let responder_session = create_handshake_test_session( - 12345u32, - false, - &responder_keys, - initiator_keys.public_key(), - ); + let initiator_session = create_handshake_test_session(12345u32, true); + let responder_session = create_handshake_test_session(12345u32, false); // Prepare first message (initiator runs PSQ and injects PSK) let i_msg = initiator_session @@ -1982,21 +1854,8 @@ mod tests { /// Test full end-to-end handshake with PSQ integration #[test] fn test_handshake_with_psq_end_to_end() { - let initiator_keys = Arc::new(generate_keypair()); - let responder_keys = Arc::new(generate_keypair()); - - let initiator_session = create_handshake_test_session( - 12345u32, - true, - &initiator_keys, - responder_keys.public_key(), - ); - let responder_session = create_handshake_test_session( - 12345u32, - false, - &responder_keys, - initiator_keys.public_key(), - ); + let initiator_session = create_handshake_test_session(12345u32, true); + let responder_session = create_handshake_test_session(12345u32, false); // Verify initial state assert!(!initiator_session.is_handshake_complete()); @@ -2068,22 +1927,9 @@ mod tests { /// Test that Ed25519 keys are used in PSQ authentication #[test] fn test_psq_handshake_uses_ed25519_authentication() { - let initiator_keys = Arc::new(generate_keypair()); - let responder_keys = Arc::new(generate_keypair()); - // Create sessions with explicit Ed25519 keys - let initiator_session = create_handshake_test_session( - 12345u32, - true, - &initiator_keys, - responder_keys.public_key(), - ); - let responder_session = create_handshake_test_session( - 12345u32, - false, - &responder_keys, - initiator_keys.public_key(), - ); + let initiator_session = create_handshake_test_session(12345u32, true); + let responder_session = create_handshake_test_session(12345u32, false); // Verify sessions store Ed25519 keys // (Internal verification - keys are used in PSQ calls) @@ -2122,15 +1968,8 @@ mod tests { #[test] fn test_psq_deserialization_failure() { // Test that corrupted PSQ payload causes clean abort - let responder_keys = generate_keypair(); - let initiator_keys = generate_keypair(); - let responder_session = create_handshake_test_session( - 12345u32, - false, - &responder_keys, - initiator_keys.public_key(), - ); + let responder_session = create_handshake_test_session(12345u32, false); // Create a handshake message with corrupted PSQ payload let corrupted_psq_data = vec![0xFF; 128]; // Random garbage @@ -2150,45 +1989,30 @@ mod tests { #[test] fn test_handshake_abort_on_psq_failure() { - // Test that Ed25519 auth failure causes handshake abort - let initiator_keys = Arc::new(generate_keypair()); - let responder_keys = Arc::new(generate_keypair()); + let (init, resp) = mock_peers(); + + let mut bad_resp = resp.as_remote(); + let wrong_ed25519 = ed25519::KeyPair::from_secret([99u8; 32], 99); // Different key! + bad_resp.ed25519_public = *wrong_ed25519.public_key(); + + let mut bad_init = init.as_remote(); + bad_init.ed25519_public = *wrong_ed25519.public_key(); // Create sessions with MISMATCHED Ed25519 keys // This simulates authentication failure - let initiator_ed25519 = ed25519::KeyPair::from_secret([1u8; 32], 0); - let wrong_ed25519 = ed25519::KeyPair::from_secret([99u8; 32], 99); // Different key! - let receiver_index: u32 = 55555; let salt = [0u8; 32]; - let initiator_session = LpSession::new( - receiver_index, - true, - Arc::new(initiator_ed25519), - initiator_keys.clone(), - wrong_ed25519.public_key(), // Responder expects THIS key - responder_keys.public_key(), - &salt, - ) - .unwrap(); - // Initialize KKT state for test - initiator_session.set_kkt_completed_for_test(responder_keys.public_key()); + let initiator_session = + LpSession::new(receiver_index, true, init.clone(), bad_resp, &salt).unwrap(); - let responder_ed25519 = ed25519::KeyPair::from_secret([2u8; 32], 1); - - let responder_session = LpSession::new( - receiver_index, - false, - Arc::new(responder_ed25519), - responder_keys.clone(), - wrong_ed25519.public_key(), // Expects WRONG key (not initiator's) - initiator_keys.public_key(), - &salt, - ) - .unwrap(); // Initialize KKT state for test - responder_session.set_kkt_completed_for_test(initiator_keys.public_key()); + initiator_session.set_kkt_completed_for_test(resp.x25519.public_key()); + + let responder_session = + LpSession::new(receiver_index, false, resp, bad_init, &salt).unwrap(); + // Initialize KKT state for test + responder_session.set_kkt_completed_for_test(init.x25519.public_key()); // Initiator prepares message (should succeed - signing works) let msg1 = initiator_session @@ -2212,48 +2036,24 @@ mod tests { #[test] fn test_psq_invalid_signature() { - // Test Ed25519 signature validation specifically - // Setup with matching X25519 keys but mismatched Ed25519 keys - let initiator_keys = Arc::new(generate_keypair()); - let responder_keys = Arc::new(generate_keypair()); + let (init, resp) = mock_peers(); - // Initiator uses Ed25519 key [1u8] - let initiator_ed25519 = ed25519::KeyPair::from_secret([1u8; 32], 0); - - // Responder expects Ed25519 key [99u8] (wrong!) - let wrong_ed25519_keypair = ed25519::KeyPair::from_secret([99u8; 32], 99); - let wrong_ed25519_public = wrong_ed25519_keypair.public_key(); + let mut bad_init = init.as_remote(); + let wrong_ed25519 = ed25519::KeyPair::from_secret([99u8; 32], 99); // Different key! + bad_init.ed25519_public = *wrong_ed25519.public_key(); let receiver_index: u32 = 66666; let salt = [0u8; 32]; - let initiator_session = LpSession::new( - receiver_index, - true, - Arc::new(initiator_ed25519), - initiator_keys.clone(), - wrong_ed25519_public, // This doesn't matter for initiator - responder_keys.public_key(), - &salt, - ) - .unwrap(); + let initiator_session = + LpSession::new(receiver_index, true, init.clone(), resp.as_remote(), &salt).unwrap(); // Initialize KKT state for test - initiator_session.set_kkt_completed_for_test(responder_keys.public_key()); + initiator_session.set_kkt_completed_for_test(resp.x25519.public_key()); - let responder_ed25519 = ed25519::KeyPair::from_secret([2u8; 32], 1); - - let responder_session = LpSession::new( - receiver_index, - false, - Arc::new(responder_ed25519), - responder_keys.clone(), - wrong_ed25519_public, // Responder expects WRONG key - initiator_keys.public_key(), - &salt, - ) - .unwrap(); + let responder_session = + LpSession::new(receiver_index, false, resp, bad_init, &salt).unwrap(); // Initialize KKT state for test - responder_session.set_kkt_completed_for_test(initiator_keys.public_key()); + responder_session.set_kkt_completed_for_test(init.x25519.public_key()); // Initiator creates message with valid signature (signed with [1u8]) let msg = initiator_session @@ -2279,15 +2079,7 @@ mod tests { #[test] fn test_psq_state_unchanged_on_error() { // Verify that PSQ errors leave session in clean state - let responder_keys = generate_keypair(); - let initiator_keys = generate_keypair(); - - let responder_session = create_handshake_test_session( - 12345u32, - false, - &responder_keys, - initiator_keys.public_key(), - ); + let responder_session = create_handshake_test_session(12345u32, false); // Capture initial PSQ state (should be ResponderWaiting) // (We can't directly access psq_state, but we can verify behavior) @@ -2304,12 +2096,7 @@ mod tests { // Session should still be functional - can process valid messages // Create a proper initiator to send valid message - let initiator_session = create_handshake_test_session( - 12345u32, - true, - &initiator_keys, - responder_keys.public_key(), - ); + let initiator_session = create_handshake_test_session(12345u32, true); let valid_msg = initiator_session .prepare_handshake_message() @@ -2331,16 +2118,8 @@ mod tests { // This test verifies the safety mechanism that prevents transport mode operations // from running with the dummy PSK if PSQ injection fails or is skipped. - let initiator_keys = Arc::new(generate_keypair()); - let responder_keys = Arc::new(generate_keypair()); - // Create session but don't complete handshake (no PSK injection will occur) - let session = create_handshake_test_session( - 12345u32, - true, - &initiator_keys, - responder_keys.public_key(), - ); + let session = create_handshake_test_session(12345u32, true); // Verify session was created successfully assert!(!session.is_handshake_complete()); @@ -2380,15 +2159,7 @@ mod tests { #[test] fn test_demote_sets_read_only() { - let initiator_keys = Arc::new(generate_keypair()); - let responder_keys = Arc::new(generate_keypair()); - - let session = create_handshake_test_session( - 12345u32, - true, - &initiator_keys, - responder_keys.public_key(), - ); + let session = create_handshake_test_session(12345u32, true); // Initially not read-only assert!(!session.is_read_only()); @@ -2405,21 +2176,9 @@ mod tests { #[test] fn test_encrypt_fails_after_demotion() { // --- Setup Handshake --- - let initiator_keys = Arc::new(generate_keypair()); - let responder_keys = Arc::new(generate_keypair()); - let initiator_session = create_handshake_test_session( - 12345u32, - true, - &initiator_keys, - responder_keys.public_key(), - ); - let responder_session = create_handshake_test_session( - 12345u32, - false, - &responder_keys, - initiator_keys.public_key(), - ); + let initiator_session = create_handshake_test_session(12345u32, true); + let responder_session = create_handshake_test_session(12345u32, false); // Drive handshake to completion let i_msg = initiator_session @@ -2461,21 +2220,9 @@ mod tests { #[test] fn test_decrypt_works_after_demotion() { // --- Setup Handshake --- - let initiator_keys = Arc::new(generate_keypair()); - let responder_keys = Arc::new(generate_keypair()); - let initiator_session = create_handshake_test_session( - 12345u32, - true, - &initiator_keys, - responder_keys.public_key(), - ); - let responder_session = create_handshake_test_session( - 12345u32, - false, - &responder_keys, - initiator_keys.public_key(), - ); + let initiator_session = create_handshake_test_session(12345u32, true); + let responder_session = create_handshake_test_session(12345u32, false); // Drive handshake to completion let i_msg = initiator_session diff --git a/common/nym-lp/src/session_integration/mod.rs b/common/nym-lp/src/session_integration/mod.rs index 74f4dfdfb4..833634e670 100644 --- a/common/nym-lp/src/session_integration/mod.rs +++ b/common/nym-lp/src/session_integration/mod.rs @@ -8,8 +8,6 @@ mod tests { session_manager::SessionManager, }; use bytes::BytesMut; - use nym_crypto::asymmetric::{ed25519, x25519}; - use std::sync::Arc; // Function to create a test packet - similar to how it's done in codec.rs tests fn create_test_packet( @@ -50,29 +48,7 @@ mod tests { let session_manager_2 = SessionManager::new(); // 2. Generate Ed25519 keypairs for PSQ authentication - let ed25519_keypair_a = ed25519::KeyPair::from_secret([1u8; 32], 0); - let ed25519_keypair_b = ed25519::KeyPair::from_secret([2u8; 32], 1); - - let x25519_keypair_a = ed25519_keypair_a.to_x25519(); - let x25519_keypair_b = ed25519_keypair_b.to_x25519(); - - let ed25519_pubkey_a = *ed25519_keypair_a.public_key(); - - // Derive X25519 keys from Ed25519 (needed for KKT init test) - let x25519_pub_a = ed25519_keypair_a - .public_key() - .to_x25519() - .expect("Failed to derive X25519 from Ed25519"); - let x25519_pub_b = ed25519_keypair_b - .public_key() - .to_x25519() - .expect("Failed to derive X25519 from Ed25519"); - - // Convert to LP keypair types - let lp_pub_a = x25519::PublicKey::from_bytes(x25519_pub_a.as_bytes()) - .expect("Failed to create PublicKey from bytes"); - let lp_pub_b = x25519::PublicKey::from_bytes(x25519_pub_b.as_bytes()) - .expect("Failed to create PublicKey from bytes"); + let (a, b) = mock_peers(); // Use fixed receiver_index for deterministic test let receiver_index: u32 = 100001; @@ -82,25 +58,11 @@ mod tests { // 4. Create sessions using the pre-built Noise states let peer_a_sm = session_manager_1 - .create_session_state_machine( - receiver_index, - Arc::new(ed25519_keypair_a), - ed25519_keypair_b.public_key(), - x25519_keypair_b.public_key(), - true, - &salt, - ) + .create_session_state_machine(receiver_index, true, a.clone(), b.as_remote(), &salt) .expect("Failed to create session A"); let peer_b_sm = session_manager_2 - .create_session_state_machine( - receiver_index, - Arc::new(ed25519_keypair_b), - &ed25519_pubkey_a, - x25519_keypair_a.public_key(), - false, - &salt, - ) + .create_session_state_machine(receiver_index, false, b.clone(), a.as_remote(), &salt) .expect("Failed to create session B"); // Verify session count @@ -109,10 +71,10 @@ mod tests { // Initialize KKT state for both sessions (test bypass) session_manager_1 - .init_kkt_for_test(peer_a_sm, &lp_pub_b) + .init_kkt_for_test(peer_a_sm, b.x25519.public_key()) .expect("Failed to init KKT for peer A"); session_manager_2 - .init_kkt_for_test(peer_b_sm, &lp_pub_a) + .init_kkt_for_test(peer_b_sm, a.x25519.public_key()) .expect("Failed to init KKT for peer B"); // 5. Simulate Noise Handshake (Sans-IO) @@ -510,29 +472,7 @@ mod tests { let session_manager_2 = SessionManager::new(); // 2. Generate Ed25519 keypairs for PSQ authentication - let ed25519_keypair_a = ed25519::KeyPair::from_secret([3u8; 32], 0); - let ed25519_keypair_b = ed25519::KeyPair::from_secret([4u8; 32], 1); - - let x25519_keypair_a = ed25519_keypair_a.to_x25519(); - let x25519_keypair_b = ed25519_keypair_b.to_x25519(); - - let ed25519_pubkey_a = *ed25519_keypair_a.public_key(); - - // Derive X25519 keys from Ed25519 (same as state machine does internally) - let x25519_pub_a = ed25519_keypair_a - .public_key() - .to_x25519() - .expect("Failed to derive X25519 from Ed25519"); - let x25519_pub_b = ed25519_keypair_b - .public_key() - .to_x25519() - .expect("Failed to derive X25519 from Ed25519"); - - // Convert to LP keypair types - let lp_pub_a = x25519::PublicKey::from_bytes(x25519_pub_a.as_bytes()) - .expect("Failed to create PublicKey from bytes"); - let lp_pub_b = x25519::PublicKey::from_bytes(x25519_pub_b.as_bytes()) - .expect("Failed to create PublicKey from bytes"); + let (a, b) = mock_peers(); // Use fixed receiver_index for test let receiver_index: u32 = 100002; @@ -541,32 +481,19 @@ mod tests { let salt = [43u8; 32]; let peer_a_sm = session_manager_1 - .create_session_state_machine( - receiver_index, - Arc::new(ed25519_keypair_a), - ed25519_keypair_b.public_key(), - x25519_keypair_b.public_key(), - true, - &salt, - ) - .unwrap(); + .create_session_state_machine(receiver_index, true, a.clone(), b.as_remote(), &salt) + .expect("Failed to create session A"); + let peer_b_sm = session_manager_2 - .create_session_state_machine( - receiver_index, - Arc::new(ed25519_keypair_b), - &ed25519_pubkey_a, - x25519_keypair_a.public_key(), - false, - &salt, - ) - .unwrap(); + .create_session_state_machine(receiver_index, false, b.clone(), a.as_remote(), &salt) + .expect("Failed to create session B"); // Initialize KKT state for both sessions (test bypass) session_manager_1 - .init_kkt_for_test(peer_a_sm, &lp_pub_b) + .init_kkt_for_test(peer_a_sm, b.x25519.public_key()) .expect("Failed to init KKT for peer A"); session_manager_2 - .init_kkt_for_test(peer_b_sm, &lp_pub_a) + .init_kkt_for_test(peer_b_sm, a.x25519.public_key()) .expect("Failed to init KKT for peer B"); // Drive handshake to completion (simplified) @@ -722,23 +649,7 @@ mod tests { let session_manager = SessionManager::new(); // Generate Ed25519 keypair for PSQ authentication - let ed25519_keypair_a = ed25519::KeyPair::from_secret([5u8; 32], 0); - let ed25519_keypair_b = ed25519::KeyPair::from_secret([6u8; 32], 0); - - let x25519_keypair_a = ed25519_keypair_a.to_x25519(); - let x25519_keypair_b = ed25519_keypair_b.to_x25519(); - - // Derive X25519 key from Ed25519 (same as state machine does internally) - let x25519_pub = ed25519_keypair_a - .public_key() - .to_x25519() - .expect("Failed to derive X25519 from Ed25519"); - - let keypair_a = Arc::new(ed25519_keypair_a); - - // Convert to LP keypair type (still needed for init_kkt_for_test below if used) - let _lp_pub = x25519::PublicKey::from_bytes(x25519_pub.as_bytes()) - .expect("Failed to create PublicKey from bytes"); + let (a, b) = mock_peers(); // Use fixed receiver_index for test let receiver_index: u32 = 100003; @@ -748,14 +659,7 @@ mod tests { // 2. Create a session (using real noise state) let _session = session_manager - .create_session_state_machine( - receiver_index, - keypair_a.clone(), - ed25519_keypair_b.public_key(), - x25519_keypair_b.public_key(), - true, - &salt, - ) + .create_session_state_machine(receiver_index, true, a.clone(), b.as_remote(), &salt) .expect("Failed to create session"); // 3. Try to get a non-existent session @@ -774,10 +678,9 @@ mod tests { let _temp_session = session_manager .create_session_state_machine( receiver_index_temp, - keypair_a.clone(), - ed25519_keypair_b.public_key(), - x25519_keypair_a.public_key(), true, + a.clone(), + b.as_remote(), &salt, ) .expect("Failed to create temp session"); @@ -830,13 +733,15 @@ mod tests { } // Remove unused imports if SessionManager methods are no longer direct dependencies // use crate::noise_protocol::{create_noise_state, create_noise_state_responder}; + use crate::peer::mock_peers; use crate::{ // Bring in state machine types state_machine::{LpAction, LpInput, LpStateBare}, // message::LpMessage, // LpMessage likely still needed for LpInput/LpAction // packet::{LpHeader, LpPacket, TRAILER_LEN}, // LpPacket needed for LpAction/LpInput }; - use bytes::Bytes; // Use Bytes for SendData input + use bytes::Bytes; + // Use Bytes for SendData input // Keep helper function for creating test packets if needed, // but LpAction::SendPacket should provide the packets now. @@ -856,14 +761,7 @@ mod tests { let session_manager_2 = SessionManager::new(); // 2. Generate Ed25519 keypairs for PSQ authentication - let ed25519_keypair_a = ed25519::KeyPair::from_secret([6u8; 32], 0); - let ed25519_keypair_b = ed25519::KeyPair::from_secret([7u8; 32], 1); - - let x25519_keypair_a = ed25519_keypair_a.to_x25519(); - let x25519_keypair_b = ed25519_keypair_b.to_x25519(); - - let pubkey_a = *ed25519_keypair_a.public_key(); - let pubkey_b = *ed25519_keypair_b.public_key(); + let (a, b) = mock_peers(); // Use fixed receiver_index for test let receiver_index: u32 = 100005; @@ -874,26 +772,12 @@ mod tests { // 3. Create sessions state machines assert!( session_manager_1 - .create_session_state_machine( - receiver_index, - Arc::new(ed25519_keypair_a), - &pubkey_b, - x25519_keypair_b.public_key(), - true, - &salt, - ) // Initiator + .create_session_state_machine(receiver_index, true, a.clone(), b.as_remote(), &salt,) // Initiator .is_ok() ); assert!( session_manager_2 - .create_session_state_machine( - receiver_index, - Arc::new(ed25519_keypair_b), - &pubkey_a, - x25519_keypair_a.public_key(), - false, - &salt, - ) // Responder + .create_session_state_machine(receiver_index, false, b, a.as_remote(), &salt,) // Responder .is_ok() ); diff --git a/common/nym-lp/src/session_manager.rs b/common/nym-lp/src/session_manager.rs index c669a9df41..41b28da1c7 100644 --- a/common/nym-lp/src/session_manager.rs +++ b/common/nym-lp/src/session_manager.rs @@ -6,13 +6,11 @@ //! This module implements session lifecycle management functionality, handling //! creation, retrieval, and storage of sessions. -use dashmap::DashMap; -use nym_crypto::asymmetric::{ed25519, x25519}; -use std::sync::Arc; - use crate::noise_protocol::ReadResult; +use crate::peer::{LpLocalPeer, LpRemotePeer}; use crate::state_machine::{LpAction, LpInput, LpState, LpStateBare}; use crate::{LpError, LpMessage, LpSession, LpStateMachine}; +use dashmap::DashMap; /// Manages the lifecycle of Lewes Protocol sessions. /// @@ -170,20 +168,12 @@ impl SessionManager { pub fn create_session_state_machine( &self, receiver_index: u32, - local_ed25519_keypair: Arc, - remote_ed25519_key: &ed25519::PublicKey, - remote_x25519_key: &x25519::PublicKey, is_initiator: bool, + local_peer: LpLocalPeer, + remote_peer: LpRemotePeer, salt: &[u8; 32], ) -> Result { - let sm = LpStateMachine::new( - receiver_index, - is_initiator, - local_ed25519_keypair, - remote_ed25519_key, - remote_x25519_key, - salt, - )?; + let sm = LpStateMachine::new(receiver_index, is_initiator, local_peer, remote_peer, salt)?; self.state_machines.insert(receiver_index, sm); Ok(receiver_index) @@ -202,7 +192,7 @@ impl SessionManager { pub fn init_kkt_for_test( &self, lp_id: u32, - remote_x25519_pub: &x25519::PublicKey, + remote_x25519_pub: &nym_crypto::asymmetric::x25519::PublicKey, ) -> Result<(), LpError> { self.with_state_machine(lp_id, |sm| { sm.session()?.set_kkt_completed_for_test(remote_x25519_pub); @@ -214,28 +204,21 @@ impl SessionManager { #[cfg(test)] mod tests { use super::*; - use nym_crypto::asymmetric::ed25519; + use crate::peer::{mock_peers, random_peer}; + use nym_test_utils::helpers::deterministic_rng; #[test] fn test_session_manager_get() { let manager = SessionManager::new(); - let ed25519_keypair = ed25519::KeyPair::from_secret([10u8; 32], 0); - let ed25519_keypair2 = ed25519::KeyPair::from_secret([16u8; 32], 0); - - let x25519_keypair2 = ed25519_keypair2.to_x25519(); + let mut rng = deterministic_rng(); + let local = random_peer(&mut rng); + let peer1 = random_peer(&mut rng); let salt = [47u8; 32]; let receiver_index: u32 = 1001; let sm_1_id = manager - .create_session_state_machine( - receiver_index, - Arc::new(ed25519_keypair), - ed25519_keypair2.public_key(), - x25519_keypair2.public_key(), - true, - &salt, - ) + .create_session_state_machine(receiver_index, true, local, peer1.as_remote(), &salt) .unwrap(); let retrieved = manager.state_machine_exists(sm_1_id); @@ -248,23 +231,15 @@ mod tests { #[test] fn test_session_manager_remove() { let manager = SessionManager::new(); - let ed25519_keypair = ed25519::KeyPair::from_secret([11u8; 32], 0); - let ed25519_keypair2 = ed25519::KeyPair::from_secret([16u8; 32], 0); - - let x25519_keypair2 = ed25519_keypair2.to_x25519(); + let mut rng = deterministic_rng(); + let local = random_peer(&mut rng); + let peer1 = random_peer(&mut rng); let salt = [48u8; 32]; let receiver_index: u32 = 2002; let sm_1_id = manager - .create_session_state_machine( - receiver_index, - Arc::new(ed25519_keypair), - ed25519_keypair2.public_key(), - x25519_keypair2.public_key(), - true, - &salt, - ) + .create_session_state_machine(receiver_index, true, local, peer1.as_remote(), &salt) .unwrap(); let removed = manager.remove_state_machine(sm_1_id); @@ -278,50 +253,24 @@ mod tests { #[test] fn test_multiple_sessions() { let manager = SessionManager::new(); - let ed25519_keypair_1 = ed25519::KeyPair::from_secret([12u8; 32], 0); - let ed25519_keypair_2 = ed25519::KeyPair::from_secret([13u8; 32], 1); - let ed25519_keypair_3 = ed25519::KeyPair::from_secret([14u8; 32], 2); + let mut rng = deterministic_rng(); + let local = random_peer(&mut rng); + let peer1 = random_peer(&mut rng); + let peer2 = random_peer(&mut rng); + let peer3 = random_peer(&mut rng); + let salt = [49u8; 32]; - let pubkey1 = *ed25519_keypair_1.public_key(); - let pubkey2 = *ed25519_keypair_2.public_key(); - let pubkey3 = *ed25519_keypair_3.public_key(); - - let xpubkey1 = *ed25519_keypair_1.to_x25519().public_key(); - let xpubkey2 = *ed25519_keypair_2.to_x25519().public_key(); - let xpubkey3 = *ed25519_keypair_3.to_x25519().public_key(); - let sm_1 = manager - .create_session_state_machine( - 3001, - Arc::new(ed25519_keypair_1), - &pubkey2, - &xpubkey2, - true, - &salt, - ) + .create_session_state_machine(3001, true, local.clone(), peer1.as_remote(), &salt) .unwrap(); let sm_2 = manager - .create_session_state_machine( - 3002, - Arc::new(ed25519_keypair_2), - &pubkey3, - &xpubkey3, - true, - &salt, - ) + .create_session_state_machine(3002, true, local.clone(), peer2.as_remote(), &salt) .unwrap(); let sm_3 = manager - .create_session_state_machine( - 3003, - Arc::new(ed25519_keypair_3), - &pubkey1, - &xpubkey1, - true, - &salt, - ) + .create_session_state_machine(3003, true, local.clone(), peer3.as_remote(), &salt) .unwrap(); assert_eq!(manager.session_count(), 3); @@ -338,19 +287,16 @@ mod tests { #[test] fn test_session_manager_create_session() { let manager = SessionManager::new(); - let ed25519_keypair = ed25519::KeyPair::from_secret([15u8; 32], 0); - let ed25519_keypair2 = ed25519::KeyPair::from_secret([16u8; 32], 0); + let (init, resp) = mock_peers(); + let salt = [50u8; 32]; let receiver_index: u32 = 4004; - let x25519_keypair2 = ed25519_keypair2.to_x25519(); - let sm = manager.create_session_state_machine( receiver_index, - Arc::new(ed25519_keypair), - ed25519_keypair2.public_key(), - x25519_keypair2.public_key(), true, + init, + resp.as_remote(), &salt, ); diff --git a/common/nym-lp/src/state_machine.rs b/common/nym-lp/src/state_machine.rs index fb467d213e..5d91df2b71 100644 --- a/common/nym-lp/src/state_machine.rs +++ b/common/nym-lp/src/state_machine.rs @@ -13,6 +13,7 @@ //! State machine ensures protocol steps execute in correct order. Invalid transitions //! return LpError, preventing protocol violations. +use crate::peer::{LpLocalPeer, LpRemotePeer}; use crate::{ LpError, message::{LpMessage, SubsessionKK1Data, SubsessionKK2Data, SubsessionReadyData}, @@ -21,9 +22,7 @@ use crate::{ session::{LpSession, SubsessionHandshake}, }; use bytes::BytesMut; -use nym_crypto::asymmetric::{ed25519, x25519}; use std::mem; -use std::sync::Arc; use tracing::debug; /// Represents the possible states of the Lewes Protocol connection. @@ -178,57 +177,25 @@ impl LpStateMachine { Ok(self.session()?.id()) } - /// Creates a new state machine from Ed25519 keys, internally deriving X25519 keys. - /// - /// This is the primary constructor that accepts only Ed25519 keys (identity/signing keys) - /// and internally derives the X25519 keys needed for Noise protocol and DHKEM. - /// This simplifies the API by hiding the X25519 derivation as an implementation detail. + /// Creates a new state machine from Ed25519 keys. /// /// # Arguments /// /// * `receiver_index` - Client-proposed session identifier (random 4 bytes) /// * `is_initiator` - Whether this side initiates the handshake - /// * `local_ed25519_keypair` - Ed25519 keypair for PSQ authentication and X25519 derivation - /// (from client identity key or gateway signing key) - /// * `remote_ed25519_key` - Peer's Ed25519 public key for PSQ authentication - /// * `remote_x25519_key` - Peer's x25519 public key for Noise protocol and DHKEM + /// * `local_peer` - This side's LP peer's keys + /// * `remote_peer` - The remote's LP peer's keys /// * `salt` - Fresh salt for PSK derivation (must be unique per session) - /// - /// # Errors - /// - /// Returns `LpError::Ed25519RecoveryError` if Ed25519→X25519 conversion fails for the remote key. - /// Local private key conversion cannot fail. pub fn new( receiver_index: u32, is_initiator: bool, - local_ed25519_keypair: Arc, - remote_ed25519_key: &ed25519::PublicKey, - remote_x25519_key: &x25519::PublicKey, + local_peer: LpLocalPeer, + remote_peer: LpRemotePeer, salt: &[u8; 32], ) -> Result { - // 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) - - // Convert Ed25519 keys to X25519 for Noise protocol - let local_x25519 = Arc::new(local_ed25519_keypair.to_x25519()); - // Create the session with both Ed25519 (for PSQ auth) and derived X25519 keys (for Noise) // receiver_index is client-proposed, passed through directly - let session = LpSession::new( - receiver_index, - is_initiator, - local_ed25519_keypair, - local_x25519, - remote_ed25519_key, - remote_x25519_key, - salt, - )?; + let session = LpSession::new(receiver_index, is_initiator, local_peer, remote_peer, salt)?; Ok(LpStateMachine { state: LpState::ReadyToHandshake { @@ -369,8 +336,7 @@ impl LpStateMachine { } } LpMessage::KKTResponse(kkt_response) if session.is_initiator() => { - // Initiator processes KKT response (signature-only mode with None) - match session.process_kkt_response(&kkt_response.0, None) { + match session.process_kkt_response(&kkt_response.0) { Ok(()) => { result_action = Some(Ok(LpAction::KKTComplete)); // After successful KKT, move to Handshaking @@ -1083,32 +1049,20 @@ impl LpStateMachine { #[cfg(test)] mod tests { use super::*; + use crate::peer::mock_peers; use bytes::Bytes; - use nym_crypto::asymmetric::ed25519; #[test] fn test_state_machine_init() { - // Ed25519 keypairs for PSQ authentication and X25519 derivation - let ed25519_keypair_init = ed25519::KeyPair::from_secret([16u8; 32], 0); - let ed25519_keypair_resp = ed25519::KeyPair::from_secret([17u8; 32], 1); - - let ed25519_pubkey_init = *ed25519_keypair_init.public_key(); - let x25519_pubkey_init = ed25519_pubkey_init.to_x25519().unwrap(); - let x25519_pubkey_resp = *ed25519_keypair_resp.to_x25519().public_key(); + let (init, resp) = mock_peers(); // Test salt let salt = [51u8; 32]; let receiver_index: u32 = 77777; - let initiator_sm = LpStateMachine::new( - receiver_index, - true, - Arc::new(ed25519_keypair_init), - ed25519_keypair_resp.public_key(), - &x25519_pubkey_resp, - &salt, - ); + let initiator_sm = + LpStateMachine::new(receiver_index, true, init.clone(), resp.as_remote(), &salt); assert!(initiator_sm.is_ok()); let initiator_sm = initiator_sm.unwrap(); assert!(matches!( @@ -1118,14 +1072,8 @@ mod tests { let init_session = initiator_sm.session().unwrap(); assert!(init_session.is_initiator()); - let responder_sm = LpStateMachine::new( - receiver_index, - false, - Arc::new(ed25519_keypair_resp), - &ed25519_pubkey_init, - &x25519_pubkey_init, - &salt, - ); + let responder_sm = + LpStateMachine::new(receiver_index, false, resp, init.as_remote(), &salt); assert!(responder_sm.is_ok()); let responder_sm = responder_sm.unwrap(); assert!(matches!( @@ -1141,13 +1089,7 @@ mod tests { #[test] fn test_state_machine_simplified_flow() { - // Ed25519 keypairs for PSQ authentication and X25519 derivation - let ed25519_keypair_init = ed25519::KeyPair::from_secret([18u8; 32], 0); - let ed25519_keypair_resp = ed25519::KeyPair::from_secret([19u8; 32], 1); - - let ed25519_pubkey_init = *ed25519_keypair_init.public_key(); - let x25519_pubkey_init = ed25519_pubkey_init.to_x25519().unwrap(); - let x25519_pubkey_resp = *ed25519_keypair_resp.to_x25519().public_key(); + let (init, resp) = mock_peers(); // Test salt let salt = [52u8; 32]; @@ -1157,9 +1099,8 @@ mod tests { let mut initiator = LpStateMachine::new( receiver_index, true, // is_initiator - Arc::new(ed25519_keypair_init), - ed25519_keypair_resp.public_key(), - &x25519_pubkey_resp, + init.clone(), + resp.as_remote(), &salt, ) .unwrap(); @@ -1167,9 +1108,8 @@ mod tests { let mut responder = LpStateMachine::new( receiver_index, false, // is_initiator - Arc::new(ed25519_keypair_resp), - &ed25519_pubkey_init, - &x25519_pubkey_init, + resp, + init.as_remote(), &salt, ) .unwrap(); @@ -1349,25 +1289,14 @@ mod tests { #[test] fn test_kkt_exchange_initiator_flow() { - // Ed25519 keypairs for PSQ authentication and X25519 derivation - let ed25519_keypair_init = ed25519::KeyPair::from_secret([20u8; 32], 0); - let ed25519_keypair_resp = ed25519::KeyPair::from_secret([21u8; 32], 1); - - let x25519_pubkey_init = *ed25519_keypair_init.to_x25519().public_key(); + let (init, resp) = mock_peers(); let salt = [53u8; 32]; let receiver_index: u32 = 99901; // Create initiator state machine - let mut initiator = LpStateMachine::new( - receiver_index, - true, - Arc::new(ed25519_keypair_init), - ed25519_keypair_resp.public_key(), - &x25519_pubkey_init, - &salt, - ) - .unwrap(); + let mut initiator = + LpStateMachine::new(receiver_index, true, init, resp.as_remote(), &salt).unwrap(); // Verify initial state assert!(matches!(initiator.state, LpState::ReadyToHandshake { .. })); @@ -1380,26 +1309,14 @@ mod tests { #[test] fn test_kkt_exchange_responder_flow() { - // Ed25519 keypairs for PSQ authentication and X25519 derivation - let ed25519_keypair_init = ed25519::KeyPair::from_secret([22u8; 32], 0); - let ed25519_keypair_resp = ed25519::KeyPair::from_secret([23u8; 32], 1); - - let ed25519_pubkey_init = *ed25519_keypair_init.public_key(); - let x25519_pubkey_init = ed25519_pubkey_init.to_x25519().unwrap(); + let (init, resp) = mock_peers(); let salt = [54u8; 32]; let receiver_index: u32 = 99902; // Create responder state machine - let mut responder = LpStateMachine::new( - receiver_index, - false, - Arc::new(ed25519_keypair_resp), - &ed25519_pubkey_init, - &x25519_pubkey_init, - &salt, - ) - .unwrap(); + let mut responder = + LpStateMachine::new(receiver_index, false, resp, init.as_remote(), &salt).unwrap(); // Verify initial state assert!(matches!(responder.state, LpState::ReadyToHandshake { .. })); @@ -1413,36 +1330,18 @@ mod tests { #[test] fn test_kkt_exchange_full_roundtrip() { // Ed25519 keypairs for PSQ authentication and X25519 derivation - let ed25519_keypair_init = ed25519::KeyPair::from_secret([24u8; 32], 0); - let ed25519_keypair_resp = ed25519::KeyPair::from_secret([25u8; 32], 1); - - let ed25519_pubkey_init = *ed25519_keypair_init.public_key(); - let x25519_pubkey_init = ed25519_pubkey_init.to_x25519().unwrap(); - let x25519_pubkey_resp = *ed25519_keypair_resp.to_x25519().public_key(); + let (init, resp) = mock_peers(); let salt = [55u8; 32]; let receiver_index: u32 = 99903; // Create both state machines - let mut initiator = LpStateMachine::new( - receiver_index, - true, - Arc::new(ed25519_keypair_init), - ed25519_keypair_resp.public_key(), - &x25519_pubkey_resp, - &salt, - ) - .unwrap(); + let mut initiator = + LpStateMachine::new(receiver_index, true, init.clone(), resp.as_remote(), &salt) + .unwrap(); - let mut responder = LpStateMachine::new( - receiver_index, - false, - Arc::new(ed25519_keypair_resp), - &ed25519_pubkey_init, - &x25519_pubkey_init, - &salt, - ) - .unwrap(); + let mut responder = + LpStateMachine::new(receiver_index, false, resp, init.as_remote(), &salt).unwrap(); // Step 1: Initiator starts handshake, sends KKT request let init_action = initiator.process_input(LpInput::StartHandshake); @@ -1470,6 +1369,7 @@ mod tests { // Step 4: Initiator receives KKT response, completes KKT let init_action = initiator.process_input(LpInput::ReceivePacket(kkt_response_packet)); + assert!(matches!(init_action, Some(Ok(LpAction::KKTComplete)))); // After KKT complete, initiator moves to Handshaking assert!(matches!(initiator.state, LpState::Handshaking { .. })); @@ -1477,25 +1377,15 @@ mod tests { #[test] fn test_kkt_exchange_close() { - // Ed25519 keypairs for KKT authentication - let ed25519_keypair_init = ed25519::KeyPair::from_secret([26u8; 32], 0); - let ed25519_keypair_resp = ed25519::KeyPair::from_secret([27u8; 32], 1); - - let x25519_pubkey_resp = *ed25519_keypair_resp.to_x25519().public_key(); + let (init, resp) = mock_peers(); let salt = [56u8; 32]; let receiver_index: u32 = 99904; // Create initiator state machine - let mut initiator = LpStateMachine::new( - receiver_index, - true, - Arc::new(ed25519_keypair_init), - ed25519_keypair_resp.public_key(), - &x25519_pubkey_resp, - &salt, - ) - .unwrap(); + let mut initiator = + LpStateMachine::new(receiver_index, true, init.clone(), resp.as_remote(), &salt) + .unwrap(); // Start handshake to enter KKTExchange state initiator.process_input(LpInput::StartHandshake); @@ -1509,25 +1399,15 @@ mod tests { #[test] fn test_kkt_exchange_rejects_invalid_inputs() { - // Ed25519 keypairs for KKT authentication - let ed25519_keypair_init = ed25519::KeyPair::from_secret([28u8; 32], 0); - let ed25519_keypair_resp = ed25519::KeyPair::from_secret([29u8; 32], 1); - - let x25519_pubkey_resp = *ed25519_keypair_resp.to_x25519().public_key(); + let (init, resp) = mock_peers(); let salt = [57u8; 32]; let receiver_index: u32 = 99905; // Create initiator state machine - let mut initiator = LpStateMachine::new( - receiver_index, - true, - Arc::new(ed25519_keypair_init), - ed25519_keypair_resp.public_key(), - &x25519_pubkey_resp, - &salt, - ) - .unwrap(); + let mut initiator = + LpStateMachine::new(receiver_index, true, init.clone(), resp.as_remote(), &salt) + .unwrap(); // Start handshake to enter KKTExchange state initiator.process_input(LpInput::StartHandshake); @@ -1553,39 +1433,16 @@ mod tests { /// Helper function to complete a full handshake between initiator and responder, /// returning both in Transport state ready for subsession testing. fn setup_transport_sessions() -> (LpStateMachine, LpStateMachine) { - // Use different seeds to get different X25519 keys. - // The tie-breaker compares X25519 public keys. - let ed25519_keypair_a = ed25519::KeyPair::from_secret([30u8; 32], 0); - let ed25519_keypair_b = ed25519::KeyPair::from_secret([31u8; 32], 1); - - let ed25519_pubkey_a = *ed25519_keypair_a.public_key(); - - let x25519_pubkey_a = *ed25519_keypair_a.to_x25519().public_key(); - let x25519_pubkey_b = *ed25519_keypair_b.to_x25519().public_key(); + let (a, b) = mock_peers(); let salt = [60u8; 32]; let receiver_index: u32 = 111111; // Create state machines - Alice is initiator, Bob is responder - let mut alice = LpStateMachine::new( - receiver_index, - true, - Arc::new(ed25519_keypair_a), - ed25519_keypair_b.public_key(), - &x25519_pubkey_b, - &salt, - ) - .unwrap(); + let mut alice = + LpStateMachine::new(receiver_index, true, a.clone(), b.as_remote(), &salt).unwrap(); - let mut bob = LpStateMachine::new( - receiver_index, - false, - Arc::new(ed25519_keypair_b), - &ed25519_pubkey_a, - &x25519_pubkey_a, - &salt, - ) - .unwrap(); + let mut bob = LpStateMachine::new(receiver_index, false, b, a.as_remote(), &salt).unwrap(); // --- Complete KKT Exchange --- // Alice starts handshake diff --git a/common/nymnoise/keys/src/lib.rs b/common/nymnoise/keys/src/lib.rs index b4cd70148b..59fd6cfb12 100644 --- a/common/nymnoise/keys/src/lib.rs +++ b/common/nymnoise/keys/src/lib.rs @@ -30,8 +30,14 @@ impl From for u8 { } } -#[derive(Copy, Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, utoipa::ToSchema)] -pub struct VersionedNoiseKey { +// to whoever is thinking of modifying this struct. +// you MUST NOT change its structure in any way - adding, removing or changing fields +// otherwise, it will break old clients as bincode serialisation is not backwards compatible +// even if you put `#[serde(default)]` all over the place +#[derive( + Copy, Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, utoipa::ToSchema, PartialEq, +)] +pub struct VersionedNoiseKeyV1 { #[schemars(with = "u8")] #[schema(value_type = u8)] pub supported_version: NoiseVersion, diff --git a/common/nymnoise/src/config.rs b/common/nymnoise/src/config.rs index 6f5e087d6c..1ef2235928 100644 --- a/common/nymnoise/src/config.rs +++ b/common/nymnoise/src/config.rs @@ -10,7 +10,7 @@ use std::{ use arc_swap::ArcSwap; use nym_crypto::asymmetric::x25519; -use nym_noise_keys::{NoiseVersion, VersionedNoiseKey}; +use nym_noise_keys::{NoiseVersion, VersionedNoiseKeyV1}; use snow::params::NoiseParams; use strum_macros::{EnumIter, FromRepr}; @@ -55,7 +55,7 @@ impl NoisePattern { #[derive(Debug, Default)] struct SocketAddrToKey { - inner: ArcSwap>, + inner: ArcSwap>, } // SW NOTE : Only for phased upgrade. To remove once we decide all nodes have to support Noise @@ -78,7 +78,7 @@ impl NoiseNetworkView { } } - pub fn swap_view(&self, new: HashMap) { + pub fn swap_view(&self, new: HashMap) { let noise_support = new .iter() .map(|(s_addr, key)| (s_addr.ip(), key.supported_version)) @@ -126,7 +126,7 @@ impl NoiseConfig { self } - pub(crate) fn get_noise_key(&self, s_address: &SocketAddr) -> Option { + pub(crate) fn get_noise_key(&self, s_address: &SocketAddr) -> Option { self.network.keys.inner.load().get(s_address).copied() } diff --git a/common/registration/src/lib.rs b/common/registration/src/lib.rs index 801daa9525..37ad465765 100644 --- a/common/registration/src/lib.rs +++ b/common/registration/src/lib.rs @@ -17,13 +17,13 @@ pub use lp_messages::{ }; pub use serialisation::BincodeError; -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone)] pub struct NymNode { pub identity: NodeIdentity, pub ip_address: IpAddr, pub ipr_address: Option, pub authenticator_address: Option, - pub lp_address: Option, + pub lp_data: Option, pub version: AuthenticatorVersion, } #[derive(Clone, Debug, Serialize, Deserialize)] @@ -35,6 +35,13 @@ pub struct GatewayData { pub private_ipv6: Ipv6Addr, } +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct LpData { + pub address: SocketAddr, + // TODO: modify it into a map once we know more about the PSQv2 structure + pub expected_kem_key_hash: Vec, +} + #[derive(Clone, Copy, Debug)] pub struct AssignedAddresses { pub entry_mixnet_gateway_ip: IpAddr, diff --git a/common/topology/src/node.rs b/common/topology/src/node.rs index 85cb81b51f..8100977bee 100644 --- a/common/topology/src/node.rs +++ b/common/topology/src/node.rs @@ -1,7 +1,7 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use nym_api_requests::models::DeclaredRoles; +use nym_api_requests::models::DeclaredRolesV1; use nym_api_requests::nym_nodes::SkimmedNode; use nym_crypto::asymmetric::{ed25519, x25519}; use nym_mixnet_contract_common::NodeId; @@ -35,8 +35,8 @@ pub struct SupportedRoles { pub mixnet_exit: bool, } -impl From for SupportedRoles { - fn from(value: DeclaredRoles) -> Self { +impl From for SupportedRoles { + fn from(value: DeclaredRolesV1) -> Self { SupportedRoles { mixnode: value.mixnode, mixnet_entry: value.entry, diff --git a/common/verloc/src/measurements/measurer.rs b/common/verloc/src/measurements/measurer.rs index cc83426a64..edbbfab765 100644 --- a/common/verloc/src/measurements/measurer.rs +++ b/common/verloc/src/measurements/measurer.rs @@ -9,7 +9,7 @@ use futures::StreamExt; use futures::stream::FuturesUnordered; use nym_crypto::asymmetric::ed25519; use nym_task::ShutdownToken; -use nym_validator_client::models::NymNodeDescription; +use nym_validator_client::models::NymNodeDescriptionV1; use nym_validator_client::nym_api::NymApiClientExt; use rand::prelude::SliceRandom; use rand::thread_rng; @@ -132,7 +132,7 @@ impl VerlocMeasurer { MeasurementOutcome::Done } - async fn get_list_of_nodes(&self) -> Option> { + async fn get_list_of_nodes(&self) -> Option> { let mut api_endpoints = self.config.nym_api_urls.clone(); api_endpoints.shuffle(&mut thread_rng()); for api_endpoint in api_endpoints { @@ -145,6 +145,10 @@ impl VerlocMeasurer { continue; } }; + + // for now allow usage of old endpoint for we don't need LP related data + // and the new endpoint might not be immediately deployed + #[allow(deprecated)] match client.get_all_described_nodes().await { Ok(res) => return Some(res), Err(err) => { diff --git a/gateway/src/node/lp_listener/handler.rs b/gateway/src/node/lp_listener/handler.rs index 9b7f231e27..948d62a0b6 100644 --- a/gateway/src/node/lp_listener/handler.rs +++ b/gateway/src/node/lp_listener/handler.rs @@ -342,9 +342,8 @@ where let mut state_machine = LpStateMachine::new( receiver_index, false, // responder - self.state.local_identity.clone(), - &hello_data.client_ed25519_public_key, - &hello_data.client_lp_public_key, + self.state.local_lp_peer.clone(), + hello_data.to_remote_peer(), &hello_data.salt, ) .map_err(|e| { @@ -1248,10 +1247,10 @@ mod tests { use nym_lp::codec::{parse_lp_packet, serialize_lp_packet}; use nym_lp::message::{ClientHelloData, EncryptedDataPayload, HandshakeData, LpMessage}; use nym_lp::packet::{LpHeader, LpPacket}; + use nym_lp::peer::LpLocalPeer; use std::sync::Arc; use std::time::{SystemTime, UNIX_EPOCH}; use tokio::io::{AsyncRead, AsyncReadExt, AsyncWriteExt}; - // ==================== Test Helpers ==================== /// Create a minimal test state for handler tests @@ -1282,12 +1281,17 @@ mod tests { // Create mix forwarding channel (unused in tests but required by struct) let (mix_sender, _mix_receiver) = nym_mixnet_client::forwarder::mix_forwarding_channels(); + let id_keys = Arc::new(ed25519::KeyPair::new(&mut OsRng)); + let x_keys = Arc::new(id_keys.to_x25519()); + + let lp_peer = LpLocalPeer::new(id_keys, x_keys.clone()).with_kem_psq_key(x_keys); + LpHandlerState { lp_config, ecash_verifier: Arc::new(ecash_verifier) as Arc, storage, - local_identity: Arc::new(ed25519::KeyPair::new(&mut OsRng)), + local_lp_peer: lp_peer, metrics: nym_node_metrics::NymNodeMetrics::default(), active_clients_store: ActiveClientsStore::new(), wg_peer_controller: None, diff --git a/gateway/src/node/lp_listener/mod.rs b/gateway/src/node/lp_listener/mod.rs index 0e1a948e7a..8e63293759 100644 --- a/gateway/src/node/lp_listener/mod.rs +++ b/gateway/src/node/lp_listener/mod.rs @@ -71,11 +71,14 @@ use crate::error::GatewayError; use crate::node::ActiveClientsStore; use dashmap::DashMap; use nym_config::serde_helpers::de_maybe_port; -use nym_crypto::asymmetric::ed25519; use nym_gateway_storage::GatewayStorage; use nym_lp::state_machine::LpStateMachine; +pub use nym_mixnet_client::forwarder::{ + mix_forwarding_channels, MixForwardingReceiver, MixForwardingSender, +}; use nym_node_metrics::NymNodeMetrics; use nym_task::ShutdownTracker; +pub use nym_wireguard::{PeerControlRequest, WireguardGatewayData}; use std::net::{IpAddr, Ipv6Addr, SocketAddr}; use std::sync::Arc; use std::time::Duration; @@ -83,10 +86,7 @@ use tokio::net::TcpListener; use tokio::sync::{mpsc, Semaphore}; use tracing::*; -pub use nym_mixnet_client::forwarder::{ - mix_forwarding_channels, MixForwardingReceiver, MixForwardingSender, -}; -pub use nym_wireguard::{PeerControlRequest, WireguardGatewayData}; +pub use nym_lp::peer::LpLocalPeer; mod data_handler; pub mod handler; @@ -339,8 +339,8 @@ pub struct LpHandlerState { /// Storage backend for persistence pub storage: GatewayStorage, - /// Gateway's identity keypair - pub local_identity: Arc, + /// Encapsulates all required key information of a local Lewes Protocol Peer. + pub local_lp_peer: LpLocalPeer, /// Metrics collection pub metrics: NymNodeMetrics, diff --git a/gateway/src/node/lp_listener/registration.rs b/gateway/src/node/lp_listener/registration.rs index 7ff41acc4a..c14197c405 100644 --- a/gateway/src/node/lp_listener/registration.rs +++ b/gateway/src/node/lp_listener/registration.rs @@ -346,13 +346,12 @@ pub async fn process_registration( } // Get gateway identity and derive sphinx key - let gateway_identity = state.local_identity.public_key().to_bytes(); + let ed25519_key = state.local_lp_peer.ed25519().public_key(); + let gateway_identity = ed25519_key.to_bytes(); warn!("TEMPORARY ed25519 -> x25519 conversion"); #[allow(clippy::expect_used)] - let gateway_sphinx_key = state - .local_identity - .public_key() + let gateway_sphinx_key = ed25519_key .to_x25519() .expect("valid ed25519 key should convert to x25519") .to_bytes(); diff --git a/gateway/src/node/mod.rs b/gateway/src/node/mod.rs index 418a12673d..153b8ba59f 100644 --- a/gateway/src/node/mod.rs +++ b/gateway/src/node/mod.rs @@ -16,7 +16,7 @@ use nym_credential_verification::ecash::{ use nym_credential_verification::upgrade_mode::{ UpgradeModeCheckConfig, UpgradeModeDetails, UpgradeModeState, }; -use nym_crypto::asymmetric::ed25519; +use nym_crypto::asymmetric::{ed25519, x25519}; use nym_ip_packet_router::IpPacketRouter; use nym_mixnet_client::forwarder::MixForwardingSender; use nym_network_defaults::NymNetworkDetails; @@ -46,6 +46,7 @@ pub use nym_gateway_storage::{ traits::{BandwidthGatewayStorage, InboxGatewayStorage}, GatewayStorage, }; +use nym_lp::peer::LpLocalPeer; pub use nym_sdk::{NymApiTopologyProvider, NymApiTopologyProviderConfig, UserAgent}; pub(crate) mod client_handling; @@ -92,6 +93,9 @@ pub struct GatewayTasksBuilder { /// ed25519 keypair used to assert one's identity. identity_keypair: Arc, + /// x25519 (for now, to be changed into MlKem) keypair used for the PSQ derivation + kem_psq_keys: Arc, + storage: GatewayStorage, mix_packet_sender: MixForwardingSender, @@ -120,6 +124,7 @@ impl GatewayTasksBuilder { pub fn new( config: Config, identity: Arc, + kem_psq_keys: Arc, storage: GatewayStorage, mix_packet_sender: MixForwardingSender, metrics_sender: MetricEventsSender, @@ -137,6 +142,7 @@ impl GatewayTasksBuilder { wireguard_data: None, user_agent, identity_keypair: identity, + kem_psq_keys, storage, mix_packet_sender, metrics_sender, @@ -325,10 +331,21 @@ 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_identity: Arc::clone(&self.identity_keypair), + local_lp_peer: LpLocalPeer::new(self.identity_keypair.clone(), x25519_keys) + .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 8c9d7c3fdc..40533cbb1f 100644 --- a/integration-tests/src/lp_registration.rs +++ b/integration-tests/src/lp_registration.rs @@ -11,8 +11,8 @@ mod tests { use nym_gateway::GatewayError; use nym_gateway::node::lp_listener::handler::LpConnectionHandler; use nym_gateway::node::lp_listener::{ - LpDebug, LpHandlerState, MixForwardingReceiver, PeerControlRequest, WireguardGatewayData, - mix_forwarding_channels, + LpDebug, LpHandlerState, LpLocalPeer, MixForwardingReceiver, PeerControlRequest, + WireguardGatewayData, mix_forwarding_channels, }; use nym_gateway::node::{ActiveClientsStore, GatewayStorage, LpConfig}; use nym_registration_client::{LpClientError, LpRegistrationClient}; @@ -46,7 +46,7 @@ mod tests { } struct Party { - ed25519_keys: Arc, + peer: LpLocalPeer, x25519_wg_keys: Arc, socket_addr: SocketAddr, } @@ -58,10 +58,15 @@ mod tests { rng.fill_bytes(&mut ip); rng.fill_bytes(&mut port); + let ed25519_keys = Arc::new(ed25519::KeyPair::new(rng)); + let x25519_wg_keys = Arc::new(x25519::KeyPair::new(rng)); + + let lp_x25519_keys = Arc::new(ed25519_keys.to_x25519()); Party { - ed25519_keys: Arc::new(ed25519::KeyPair::new(rng)), - x25519_wg_keys: Arc::new(x25519::KeyPair::new(rng)), + peer: LpLocalPeer::new(ed25519_keys, lp_x25519_keys.clone()) + .with_kem_psq_key(lp_x25519_keys), + x25519_wg_keys, socket_addr: SocketAddr::from((ip, u16::from_le_bytes(port))), } } @@ -207,10 +212,8 @@ mod tests { // use in-memory database (no need for persistency) storage, - // reuse the same identity we just generated - local_identity: base.ed25519_keys.clone(), + local_lp_peer: base.peer.clone(), - // we don't care about metrics - all zeroes are perfectly fine metrics: Default::default(), // no clients at the beginning @@ -386,8 +389,8 @@ mod tests { let mut entry = Gateway::mock(&mut gateway_rng).await?; let mut client = LpRegistrationClient::::new_with_default_config( - client_data.base.ed25519_keys, - *entry.base.ed25519_keys.public_key(), + client_data.base.peer.ed25519().clone(), + entry.base.peer.as_remote(), entry.base.socket_addr, client_data.base.socket_addr.ip(), ); @@ -436,7 +439,7 @@ mod tests { // 6. perform registration with entry only let wg_keypair = client_data.base.x25519_wg_keys; - let gateway_identity = entry.base.ed25519_keys.public_key(); + let gateway_identity = entry.base.peer.ed25519().public_key(); let registration_result = client .register( &wg_keypair, @@ -477,8 +480,8 @@ mod tests { let mut entry = Gateway::mock(&mut gateway_rng).await?; let mut client = LpRegistrationClient::::new_with_default_config( - client_data.base.ed25519_keys, - *entry.base.ed25519_keys.public_key(), + client_data.base.peer.ed25519().clone(), + entry.base.peer.as_remote(), entry.base.socket_addr, client_data.base.socket_addr.ip(), ); @@ -502,7 +505,7 @@ mod tests { // 4. perform registration with entry only // but WITHOUT performing the handshake let wg_keypair = client_data.base.x25519_wg_keys; - let gateway_identity = entry.base.ed25519_keys.public_key(); + let gateway_identity = entry.base.peer.ed25519().public_key(); let registration_result = client .register( &wg_keypair, @@ -537,8 +540,8 @@ mod tests { let mut exit = Gateway::mock(&mut exit_rng).await?; let mut entry_client = LpRegistrationClient::::new_with_default_config( - client_data.base.ed25519_keys.clone(), - *entry.base.ed25519_keys.public_key(), + client_data.base.peer.ed25519().clone(), + entry.base.peer.as_remote(), entry.base.socket_addr, client_data.base.socket_addr.ip(), ); @@ -636,10 +639,9 @@ mod tests { // technically we should use different ephemeral keys than we had for the entry // but crypto is going to work the same let mut nested_session = NestedLpSession::new( - exit.base.ed25519_keys.public_key().to_bytes(), exit.base.socket_addr.to_string(), - client_data.base.ed25519_keys, - *exit.base.ed25519_keys.public_key(), + client_data.base.peer.ed25519().clone(), + exit.base.peer.as_remote(), ); // 13. Perform handshake and registration with exit gateway (all via entry forwarding) @@ -647,7 +649,7 @@ mod tests { .handshake_and_register( &mut entry_client, &client_data.base.x25519_wg_keys, - exit.base.ed25519_keys.public_key(), + exit.base.peer.ed25519().public_key(), &client_data.ticket_provider, TicketType::V1WireguardExit, client_data.base.socket_addr.ip(), @@ -659,7 +661,7 @@ mod tests { let entry_registration_result = entry_client .register( &client_data.base.x25519_wg_keys, - entry.base.ed25519_keys.public_key(), + entry.base.peer.ed25519().public_key(), &client_data.ticket_provider, TicketType::V1WireguardEntry, ) diff --git a/nym-api/nym-api-requests/Cargo.toml b/nym-api/nym-api-requests/Cargo.toml index 0a683ef8b8..25279ba1cb 100644 --- a/nym-api/nym-api-requests/Cargo.toml +++ b/nym-api/nym-api-requests/Cargo.toml @@ -12,10 +12,13 @@ documentation.workspace = true [dependencies] bs58 = { workspace = true } +celes = { workspace = true } # country codes cosmrs = { workspace = true } cosmwasm-std = { workspace = true } schemars = { workspace = true, features = ["preserve_order"] } serde = { workspace = true, features = ["derive"] } +strum = { workspace = true, features = ["derive"] } +strum_macros = { workspace = true } humantime-serde = { workspace = true } hex = { workspace = true } serde_json = { workspace = true } @@ -35,11 +38,11 @@ nym-serde-helpers = { workspace = true, features = ["bs58", "base64", "date"] } nym-credentials-interface = { workspace = true } nym-crypto = { workspace = true, features = ["serde", "asymmetric"] } -nym-config = { workspace = true } -nym-ecash-time = { workspace = true } -nym-compact-ecash = { workspace = true } -nym-contracts-common = { workspace = true , features = ["naive_float"] } -nym-mixnet-contract-common = { workspace = true , features = ["utoipa"] } +nym-config = { workspace = true } +nym-ecash-time = { workspace = true } +nym-compact-ecash = { workspace = true } +nym-contracts-common = { workspace = true, features = ["naive_float"] } +nym-mixnet-contract-common = { workspace = true, features = ["utoipa"] } nym-coconut-dkg-common = { workspace = true } nym-node-requests = { workspace = true, default-features = false, features = ["openapi"] } nym-noise-keys = { workspace = true } @@ -51,7 +54,7 @@ nym-ecash-signer-check-types = { workspace = true } [dev-dependencies] rand_chacha = { workspace = true } nym-crypto = { workspace = true, features = ["rand"] } - +nym-test-utils = { workspace = true } [features] default = [] diff --git a/nym-api/nym-api-requests/src/models/described.rs b/nym-api/nym-api-requests/src/models/described.rs deleted file mode 100644 index 73612d7f38..0000000000 --- a/nym-api/nym-api-requests/src/models/described.rs +++ /dev/null @@ -1,418 +0,0 @@ -// Copyright 2025 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use crate::models::{BinaryBuildInformationOwned, OffsetDateTimeJsonSchemaWrapper}; -use crate::nym_nodes::{BasicEntryInformation, NodeRole, SemiSkimmedNode, SkimmedNode}; -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_mixnet_contract_common::reward_params::Performance; -use nym_mixnet_contract_common::NodeId; -use nym_network_defaults::{ - DEFAULT_MIX_LISTENING_PORT, DEFAULT_VERLOC_LISTENING_PORT, WG_METADATA_PORT, WG_TUNNEL_PORT, -}; -use nym_node_requests::api::v1::authenticator::models::Authenticator; -use nym_node_requests::api::v1::gateway::models::Wireguard; -use nym_node_requests::api::v1::ip_packet_router::models::IpPacketRouter; -use nym_node_requests::api::v1::lewes_protocol::models::LewesProtocol; -use nym_node_requests::api::v1::node::models::{AuxiliaryDetails, NodeRoles}; -use nym_noise_keys::VersionedNoiseKey; -use serde::{Deserialize, Serialize}; -use std::net::IpAddr; -use tracing::warn; -use utoipa::ToSchema; - -#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] -pub struct HostInformation { - #[schema(value_type = Vec)] - pub ip_address: Vec, - pub hostname: Option, - pub keys: HostKeys, -} - -impl From for HostInformation { - fn from(value: nym_node_requests::api::v1::node::models::HostInformation) -> Self { - HostInformation { - ip_address: value.ip_address, - hostname: value.hostname, - keys: value.keys.into(), - } - } -} - -#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] -pub struct HostKeys { - #[serde(with = "bs58_ed25519_pubkey")] - #[schemars(with = "String")] - #[schema(value_type = String)] - pub ed25519: ed25519::PublicKey, - - #[deprecated(note = "use the current_x25519_sphinx_key with explicit rotation information")] - #[serde(with = "bs58_x25519_pubkey")] - #[schemars(with = "String")] - #[schema(value_type = String)] - pub x25519: x25519::PublicKey, - - pub current_x25519_sphinx_key: SphinxKey, - - #[serde(default)] - pub pre_announced_x25519_sphinx_key: Option, - - #[serde(default)] - pub x25519_versioned_noise: Option, -} - -#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] -pub struct SphinxKey { - pub rotation_id: u32, - - #[serde(with = "bs58_x25519_pubkey")] - #[schemars(with = "String")] - #[schema(value_type = String)] - pub public_key: x25519::PublicKey, -} - -impl From for SphinxKey { - fn from(value: nym_node_requests::api::v1::node::models::SphinxKey) -> Self { - SphinxKey { - rotation_id: value.rotation_id, - public_key: value.public_key, - } - } -} - -impl From for HostKeys { - fn from(value: nym_node_requests::api::v1::node::models::HostKeys) -> Self { - HostKeys { - ed25519: value.ed25519_identity, - x25519: value.x25519_sphinx, - current_x25519_sphinx_key: value.primary_x25519_sphinx_key.into(), - pre_announced_x25519_sphinx_key: value.pre_announced_x25519_sphinx_key.map(Into::into), - x25519_versioned_noise: value.x25519_versioned_noise, - } - } -} - -#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] -pub struct WebSockets { - pub ws_port: u16, - - pub wss_port: Option, -} - -impl From for WebSockets { - fn from(value: nym_node_requests::api::v1::gateway::models::WebSockets) -> Self { - WebSockets { - ws_port: value.ws_port, - wss_port: value.wss_port, - } - } -} - -#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] -pub struct NoiseDetails { - pub key: VersionedNoiseKey, - - pub mixnet_port: u16, - - #[schema(value_type = Vec)] - pub ip_addresses: Vec, -} - -#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] -pub struct NymNodeDescription { - #[schema(value_type = u32)] - pub node_id: NodeId, - pub contract_node_type: DescribedNodeType, - pub description: NymNodeData, -} - -impl NymNodeDescription { - pub fn version(&self) -> &str { - &self.description.build_information.build_version - } - - pub fn entry_information(&self) -> BasicEntryInformation { - BasicEntryInformation { - hostname: self.description.host_information.hostname.clone(), - ws_port: self.description.mixnet_websockets.ws_port, - wss_port: self.description.mixnet_websockets.wss_port, - } - } - - pub fn ed25519_identity_key(&self) -> ed25519::PublicKey { - self.description.host_information.keys.ed25519 - } - - pub fn current_sphinx_key(&self, current_rotation_id: u32) -> x25519::PublicKey { - let keys = &self.description.host_information.keys; - - if keys.current_x25519_sphinx_key.rotation_id == u32::MAX { - // legacy case (i.e. node doesn't support rotation) - return keys.current_x25519_sphinx_key.public_key; - } - - if current_rotation_id == keys.current_x25519_sphinx_key.rotation_id { - // it's the 'current' key - return keys.current_x25519_sphinx_key.public_key; - } - - if let Some(pre_announced) = &keys.pre_announced_x25519_sphinx_key { - if pre_announced.rotation_id == current_rotation_id { - return pre_announced.public_key; - } - } - - warn!( - "unexpected key rotation {current_rotation_id} for node {}", - self.node_id - ); - // this should never be reached, but just in case, return the fallback option - keys.current_x25519_sphinx_key.public_key - } - - pub fn to_skimmed_node( - &self, - current_rotation_id: u32, - role: NodeRole, - performance: Performance, - ) -> SkimmedNode { - let keys = &self.description.host_information.keys; - let entry = if self.description.declared_role.entry { - Some(self.entry_information()) - } else { - None - }; - - SkimmedNode { - node_id: self.node_id, - ed25519_identity_pubkey: keys.ed25519, - ip_addresses: self.description.host_information.ip_address.clone(), - mix_port: self.description.mix_port(), - x25519_sphinx_pubkey: self.current_sphinx_key(current_rotation_id), - // we can't use the declared roles, we have to take whatever was provided in the contract. - // why? say this node COULD operate as an exit, but it might be the case the contract decided - // to assign it an ENTRY role only. we have to use that one instead. - role, - supported_roles: self.description.declared_role, - entry, - performance, - } - } - - pub fn to_semi_skimmed_node( - &self, - current_rotation_id: u32, - role: NodeRole, - performance: Performance, - ) -> SemiSkimmedNode { - let skimmed_node = self.to_skimmed_node(current_rotation_id, role, performance); - - SemiSkimmedNode { - basic: skimmed_node, - x25519_noise_versioned_key: self - .description - .host_information - .keys - .x25519_versioned_noise, - } - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] -#[serde(rename_all = "snake_case")] -#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))] -#[cfg_attr( - feature = "generate-ts", - ts( - export, - export_to = "ts-packages/types/src/types/rust/DescribedNodeType.ts" - ) -)] -pub enum DescribedNodeType { - LegacyMixnode, - LegacyGateway, - NymNode, -} - -impl DescribedNodeType { - pub fn is_nym_node(&self) -> bool { - matches!(self, DescribedNodeType::NymNode) - } -} - -#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] -#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))] -#[cfg_attr( - feature = "generate-ts", - ts( - export, - export_to = "ts-packages/types/src/types/rust/DeclaredRoles.ts" - ) -)] -pub struct DeclaredRoles { - pub mixnode: bool, - pub entry: bool, - pub exit_nr: bool, - pub exit_ipr: bool, -} - -impl DeclaredRoles { - pub fn can_operate_exit_gateway(&self) -> bool { - self.exit_ipr && self.exit_nr - } -} - -impl From for DeclaredRoles { - fn from(value: NodeRoles) -> Self { - DeclaredRoles { - mixnode: value.mixnode_enabled, - entry: value.gateway_enabled, - exit_nr: value.gateway_enabled && value.network_requester_enabled, - exit_ipr: value.gateway_enabled && value.ip_packet_router_enabled, - } - } -} - -#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] -pub struct NetworkRequesterDetails { - /// address of the embedded network requester - pub address: String, - - /// flag indicating whether this network requester uses the exit policy rather than the deprecated allow list - pub uses_exit_policy: bool, -} - -#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] -pub struct IpPacketRouterDetails { - /// address of the embedded ip packet router - pub address: String, -} - -// works for current simple case. -impl From for IpPacketRouterDetails { - fn from(value: IpPacketRouter) -> Self { - IpPacketRouterDetails { - address: value.address, - } - } -} - -#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] -pub struct AuthenticatorDetails { - /// address of the embedded authenticator - pub address: String, -} - -// works for current simple case. -impl From for AuthenticatorDetails { - fn from(value: Authenticator) -> Self { - AuthenticatorDetails { - address: value.address, - } - } -} -#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] -pub struct WireguardDetails { - // NOTE: the port field is deprecated in favour of tunnel_port - pub port: u16, - #[serde(default = "default_tunnel_port")] - pub tunnel_port: u16, - #[serde(default = "default_metadata_port")] - pub metadata_port: u16, - pub public_key: String, -} - -fn default_tunnel_port() -> u16 { - WG_TUNNEL_PORT -} -fn default_metadata_port() -> u16 { - WG_METADATA_PORT -} - -// works for current simple case. -impl From for WireguardDetails { - fn from(value: Wireguard) -> Self { - WireguardDetails { - port: value.port, - tunnel_port: value.tunnel_port, - metadata_port: value.metadata_port, - public_key: value.public_key, - } - } -} - -#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] -pub struct LewesProtocolDetails { - /// Helper field that specifies whether the LP listener(s) is enabled on this node. - /// It is directly controlled by the node's role (i.e. it is enabled if it supports 'entry' mode) - pub enabled: bool, - - /// LP TCP control address (default: 41264) for establishing LP sessions - pub control_port: u16, - - /// LP UDP data address (default: 51264) for Sphinx packets wrapped in LP - pub data_port: u16, -} - -impl From for LewesProtocolDetails { - fn from(value: LewesProtocol) -> Self { - LewesProtocolDetails { - enabled: value.enabled, - control_port: value.control_port, - data_port: value.data_port, - } - } -} - -// this struct is getting quite bloated... -#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] -pub struct NymNodeData { - #[serde(default)] - pub last_polled: OffsetDateTimeJsonSchemaWrapper, - - pub host_information: HostInformation, - - #[serde(default)] - pub declared_role: DeclaredRoles, - - #[serde(default)] - pub auxiliary_details: AuxiliaryDetails, - - // TODO: do we really care about ALL build info or just the version? - pub build_information: BinaryBuildInformationOwned, - - #[serde(default)] - pub network_requester: Option, - - #[serde(default)] - pub ip_packet_router: Option, - - #[serde(default)] - pub authenticator: Option, - - #[serde(default)] - pub wireguard: Option, - - #[serde(default)] - pub lewes_protocol: Option, - - // for now we only care about their ws/wss situation, nothing more - pub mixnet_websockets: WebSockets, -} - -impl NymNodeData { - pub fn mix_port(&self) -> u16 { - self.auxiliary_details - .announce_ports - .mix_port - .unwrap_or(DEFAULT_MIX_LISTENING_PORT) - } - - pub fn verloc_port(&self) -> u16 { - self.auxiliary_details - .announce_ports - .verloc_port - .unwrap_or(DEFAULT_VERLOC_LISTENING_PORT) - } -} diff --git a/nym-api/nym-api-requests/src/models/described/mod.rs b/nym-api/nym-api-requests/src/models/described/mod.rs new file mode 100644 index 0000000000..f3b38925be --- /dev/null +++ b/nym-api/nym-api-requests/src/models/described/mod.rs @@ -0,0 +1,26 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use nym_noise_keys::VersionedNoiseKeyV1; +use serde::{Deserialize, Serialize}; +use std::net::IpAddr; +use utoipa::ToSchema; + +pub mod type_translation; +pub mod v1; +pub mod v2; + +// don't break existing imports +pub use type_translation::*; +pub use v1::*; +pub use v2::*; + +#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] +pub struct NoiseDetails { + pub key: VersionedNoiseKeyV1, + + pub mixnet_port: u16, + + #[schema(value_type = Vec)] + pub ip_addresses: Vec, +} 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 new file mode 100644 index 0000000000..54c751ea46 --- /dev/null +++ b/nym-api/nym-api-requests/src/models/described/type_translation.rs @@ -0,0 +1,426 @@ +// Copyright 2026 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +//! This redefines relevant types present within nym-node-requests for the purposes of this crate +//! and defines required conversion methods + +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_network_defaults::{WG_METADATA_PORT, WG_TUNNEL_PORT}; +use nym_noise_keys::VersionedNoiseKeyV1; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::net::IpAddr; +use strum_macros::{Display, EnumString}; +use utoipa::ToSchema; + +// to whoever is thinking of modifying this struct. +// you MUST NOT change its structure in any way - adding, removing or changing fields +// otherwise, it will break old clients as bincode serialisation is not backwards compatible +// even if you put `#[serde(default)]` all over the place +#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema, PartialEq)] +pub struct HostInformationV1 { + #[schema(value_type = Vec)] + pub ip_address: Vec, + pub hostname: Option, + pub keys: HostKeysV1, +} + +// to whoever is thinking of modifying this struct. +// you MUST NOT change its structure in any way - adding, removing or changing fields +// otherwise, it will break old clients as bincode serialisation is not backwards compatible +// even if you put `#[serde(default)]` all over the place +#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema, PartialEq)] +pub struct HostKeysV1 { + #[serde(with = "bs58_ed25519_pubkey")] + #[schemars(with = "String")] + #[schema(value_type = String)] + pub ed25519: ed25519::PublicKey, + + #[deprecated(note = "use the current_x25519_sphinx_key with explicit rotation information")] + #[serde(with = "bs58_x25519_pubkey")] + #[schemars(with = "String")] + #[schema(value_type = String)] + pub x25519: x25519::PublicKey, + + pub current_x25519_sphinx_key: SphinxKeyV1, + + #[serde(default)] + pub pre_announced_x25519_sphinx_key: Option, + + #[serde(default)] + pub x25519_versioned_noise: Option, +} + +// to whoever is thinking of modifying this struct. +// you MUST NOT change its structure in any way - adding, removing or changing fields +// otherwise, it will break old clients as bincode serialisation is not backwards compatible +// even if you put `#[serde(default)]` all over the place +#[derive( + Clone, Copy, Debug, Default, Serialize, Deserialize, schemars::JsonSchema, ToSchema, PartialEq, +)] +pub struct AnnouncePortsV1 { + pub verloc_port: Option, + pub mix_port: Option, +} + +// to whoever is thinking of modifying this struct. +// you MUST NOT change its structure in any way - adding, removing or changing fields +// otherwise, it will break old clients as bincode serialisation is not backwards compatible +// even if you put `#[serde(default)]` all over the place +#[derive( + Clone, Copy, Debug, Default, Serialize, Deserialize, schemars::JsonSchema, ToSchema, PartialEq, +)] +pub struct AuxiliaryDetailsV1 { + /// Optional ISO 3166 alpha-2 two-letter country code of the node's **physical** location + #[schema(example = "PL", value_type = Option)] + #[schemars(with = "Option")] + #[schemars(length(equal = 2))] + pub location: Option, + + #[serde(default)] + pub announce_ports: AnnouncePortsV1, + + /// Specifies whether this node operator has agreed to the terms and conditions + /// as defined at + // make sure to include the default deserialisation as this field hasn't existed when the struct was first created + #[serde(default)] + pub accepted_operator_terms_and_conditions: bool, +} + +// to whoever is thinking of modifying this struct. +// you MUST NOT change its structure in any way - adding, removing or changing fields +// otherwise, it will break old clients as bincode serialisation is not backwards compatible +// even if you put `#[serde(default)]` all over the place +#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema, PartialEq)] +pub struct SphinxKeyV1 { + pub rotation_id: u32, + + #[serde(with = "bs58_x25519_pubkey")] + #[schemars(with = "String")] + #[schema(value_type = String)] + pub public_key: x25519::PublicKey, +} + +// to whoever is thinking of modifying this struct. +// you MUST NOT change its structure in any way - adding, removing or changing fields +// otherwise, it will break old clients as bincode serialisation is not backwards compatible +// even if you put `#[serde(default)]` all over the place +#[derive( + Clone, Copy, Debug, Default, Serialize, Deserialize, schemars::JsonSchema, ToSchema, PartialEq, +)] +#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))] +#[cfg_attr( + feature = "generate-ts", + ts( + export, + export_to = "ts-packages/types/src/types/rust/DeclaredRoles.ts" + ) +)] +pub struct DeclaredRolesV1 { + pub mixnode: bool, + pub entry: bool, + pub exit_nr: bool, + pub exit_ipr: bool, +} + +impl DeclaredRolesV1 { + pub fn can_operate_exit_gateway(&self) -> bool { + self.exit_ipr && self.exit_nr + } +} + +// to whoever is thinking of modifying this struct. +// you MUST NOT change its structure in any way - adding, removing or changing fields +// otherwise, it will break old clients as bincode serialisation is not backwards compatible +// even if you put `#[serde(default)]` all over the place +#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema, PartialEq)] +pub struct WebSocketsV1 { + pub ws_port: u16, + + pub wss_port: Option, +} + +// to whoever is thinking of modifying this struct. +// you MUST NOT change its structure in any way - adding, removing or changing fields +// otherwise, it will break old clients as bincode serialisation is not backwards compatible +// even if you put `#[serde(default)]` all over the place +#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema, PartialEq)] +pub struct NetworkRequesterDetailsV1 { + /// address of the embedded network requester + pub address: String, + + /// flag indicating whether this network requester uses the exit policy rather than the deprecated allow list + pub uses_exit_policy: bool, +} + +// to whoever is thinking of modifying this struct. +// you MUST NOT change its structure in any way - adding, removing or changing fields +// otherwise, it will break old clients as bincode serialisation is not backwards compatible +// even if you put `#[serde(default)]` all over the place +#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema, PartialEq)] +pub struct IpPacketRouterDetailsV1 { + /// address of the embedded ip packet router + pub address: String, +} + +// to whoever is thinking of modifying this struct. +// you MUST NOT change its structure in any way - adding, removing or changing fields +// otherwise, it will break old clients as bincode serialisation is not backwards compatible +// even if you put `#[serde(default)]` all over the place +#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema, PartialEq)] +pub struct AuthenticatorDetailsV1 { + /// address of the embedded authenticator + pub address: String, +} + +// to whoever is thinking of modifying this struct. +// you MUST NOT change its structure in any way - adding, removing or changing fields +// otherwise, it will break old clients as bincode serialisation is not backwards compatible +// even if you put `#[serde(default)]` all over the place +#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema, PartialEq)] +pub struct WireguardDetailsV1 { + // NOTE: the port field is deprecated in favour of tunnel_port + pub port: u16, + #[serde(default = "default_tunnel_port")] + pub tunnel_port: u16, + #[serde(default = "default_metadata_port")] + pub metadata_port: u16, + pub public_key: String, +} + +// to whoever is thinking of modifying this struct. +// you MUST NOT change its structure in any way - adding, removing or changing fields +// otherwise, it will break old clients as bincode serialisation is not backwards compatible +// even if you put `#[serde(default)]` all over the place +#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema, PartialEq)] +pub struct LewesProtocolDetailsV1 { + /// Helper field that specifies whether the LP listener(s) is enabled on this node. + /// It is directly controlled by the node's role (i.e. it is enabled if it supports 'entry' mode) + pub enabled: bool, + + /// LP TCP control address (default: 41264) for establishing LP sessions + pub control_port: u16, + + /// LP UDP data address (default: 51264) for Sphinx packets wrapped in LP + pub data_port: u16, + + /// Digests of the KEM keys available to this node alongside hashing algorithms used + /// for their computation. + pub kem_keys: HashMap>>, +} + +#[derive( + Serialize, + Deserialize, + Debug, + Clone, + Copy, + JsonSchema, + Hash, + PartialEq, + Eq, + PartialOrd, + Display, + EnumString, + ToSchema, +)] +#[strum(serialize_all = "lowercase")] +pub enum LPKEM { + MlKem768, + XWing, + X25519, + McEliece, +} + +#[derive( + Serialize, + Deserialize, + Debug, + Clone, + Copy, + JsonSchema, + Hash, + PartialEq, + Eq, + PartialOrd, + Display, + EnumString, + ToSchema, +)] +#[strum(serialize_all = "lowercase")] +pub enum LPHashFunction { + Blake3, + Shake128, + Shake256, + Sha256, +} + +impl From for HostInformationV1 { + fn from(value: nym_node_requests::api::v1::node::models::HostInformation) -> Self { + HostInformationV1 { + ip_address: value.ip_address, + hostname: value.hostname, + keys: value.keys.into(), + } + } +} + +impl From for SphinxKeyV1 { + fn from(value: nym_node_requests::api::v1::node::models::SphinxKey) -> Self { + SphinxKeyV1 { + rotation_id: value.rotation_id, + public_key: value.public_key, + } + } +} + +impl From for HostKeysV1 { + fn from(value: nym_node_requests::api::v1::node::models::HostKeys) -> Self { + HostKeysV1 { + ed25519: value.ed25519_identity, + x25519: value.x25519_sphinx, + current_x25519_sphinx_key: value.primary_x25519_sphinx_key.into(), + pre_announced_x25519_sphinx_key: value.pre_announced_x25519_sphinx_key.map(Into::into), + x25519_versioned_noise: value.x25519_versioned_noise, + } + } +} + +impl From for AnnouncePortsV1 { + fn from(value: nym_node_requests::api::v1::node::models::AnnouncePorts) -> Self { + AnnouncePortsV1 { + verloc_port: value.verloc_port, + mix_port: value.mix_port, + } + } +} + +impl From for AuxiliaryDetailsV1 { + fn from(value: nym_node_requests::api::v1::node::models::AuxiliaryDetails) -> Self { + AuxiliaryDetailsV1 { + location: value.location, + announce_ports: value.announce_ports.into(), + accepted_operator_terms_and_conditions: value.accepted_operator_terms_and_conditions, + } + } +} + +impl From for DeclaredRolesV1 { + fn from(value: nym_node_requests::api::v1::node::models::NodeRoles) -> Self { + DeclaredRolesV1 { + mixnode: value.mixnode_enabled, + entry: value.gateway_enabled, + exit_nr: value.gateway_enabled && value.network_requester_enabled, + exit_ipr: value.gateway_enabled && value.ip_packet_router_enabled, + } + } +} + +impl From for WebSocketsV1 { + fn from(value: nym_node_requests::api::v1::gateway::models::WebSockets) -> Self { + WebSocketsV1 { + ws_port: value.ws_port, + wss_port: value.wss_port, + } + } +} + +// works for current simple case. +impl From + for IpPacketRouterDetailsV1 +{ + fn from(value: nym_node_requests::api::v1::ip_packet_router::models::IpPacketRouter) -> Self { + IpPacketRouterDetailsV1 { + address: value.address, + } + } +} + +// works for current simple case. +impl From + for AuthenticatorDetailsV1 +{ + fn from(value: nym_node_requests::api::v1::authenticator::models::Authenticator) -> Self { + AuthenticatorDetailsV1 { + address: value.address, + } + } +} + +fn default_tunnel_port() -> u16 { + WG_TUNNEL_PORT +} +fn default_metadata_port() -> u16 { + WG_METADATA_PORT +} + +// works for current simple case. +impl From for WireguardDetailsV1 { + fn from(value: nym_node_requests::api::v1::gateway::models::Wireguard) -> Self { + WireguardDetailsV1 { + port: value.port, + tunnel_port: value.tunnel_port, + metadata_port: value.metadata_port, + public_key: value.public_key, + } + } +} + +impl From + for LewesProtocolDetailsV1 +{ + fn from(value: nym_node_requests::api::v1::lewes_protocol::models::LewesProtocol) -> Self { + LewesProtocolDetailsV1 { + enabled: value.enabled, + control_port: value.control_port, + data_port: value.data_port, + kem_keys: value + .kem_keys + .into_iter() + .map(|(kem, digests)| { + ( + kem.into(), + digests + .into_iter() + .map(|(hash_fn, digest)| (hash_fn.into(), digest)) + .collect(), + ) + }) + .collect(), + } + } +} + +impl From for LPKEM { + fn from(value: nym_node_requests::api::v1::lewes_protocol::models::LPKEM) -> Self { + match value { + nym_node_requests::api::v1::lewes_protocol::models::LPKEM::MlKem768 => LPKEM::MlKem768, + nym_node_requests::api::v1::lewes_protocol::models::LPKEM::XWing => LPKEM::XWing, + nym_node_requests::api::v1::lewes_protocol::models::LPKEM::X25519 => LPKEM::X25519, + nym_node_requests::api::v1::lewes_protocol::models::LPKEM::McEliece => LPKEM::McEliece, + } + } +} + +impl From for LPHashFunction { + fn from(value: nym_node_requests::api::v1::lewes_protocol::models::LPHashFunction) -> Self { + match value { + nym_node_requests::api::v1::lewes_protocol::models::LPHashFunction::Blake3 => { + LPHashFunction::Blake3 + } + nym_node_requests::api::v1::lewes_protocol::models::LPHashFunction::Shake128 => { + LPHashFunction::Shake128 + } + nym_node_requests::api::v1::lewes_protocol::models::LPHashFunction::Shake256 => { + LPHashFunction::Shake256 + } + nym_node_requests::api::v1::lewes_protocol::models::LPHashFunction::Sha256 => { + LPHashFunction::Sha256 + } + } + } +} diff --git a/nym-api/nym-api-requests/src/models/described/v1.rs b/nym-api/nym-api-requests/src/models/described/v1.rs new file mode 100644 index 0000000000..2108422c2a --- /dev/null +++ b/nym-api/nym-api-requests/src/models/described/v1.rs @@ -0,0 +1,198 @@ +// Copyright 2026 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::models::{ + AuthenticatorDetailsV1, AuxiliaryDetailsV1, BinaryBuildInformationOwned, DeclaredRolesV1, + HostInformationV1, IpPacketRouterDetailsV1, NetworkRequesterDetailsV1, + OffsetDateTimeJsonSchemaWrapper, WebSocketsV1, WireguardDetailsV1, +}; +use crate::nym_nodes::{BasicEntryInformation, NodeRole, SemiSkimmedNode, SkimmedNode}; +use nym_crypto::asymmetric::{ed25519, x25519}; +use nym_mixnet_contract_common::reward_params::Performance; +use nym_mixnet_contract_common::NodeId; +use nym_network_defaults::{DEFAULT_MIX_LISTENING_PORT, DEFAULT_VERLOC_LISTENING_PORT}; +use serde::{Deserialize, Serialize}; +use tracing::warn; +use utoipa::ToSchema; + +// to whoever is thinking of modifying this struct. +// you MUST NOT change its structure in any way - adding, removing or changing fields +// otherwise, it will break old clients as bincode serialisation is not backwards compatible +// even if you put `#[serde(default)]` all over the place +#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] +pub struct NymNodeDescriptionV1 { + #[schema(value_type = u32)] + pub node_id: NodeId, + pub contract_node_type: DescribedNodeTypeV1, + pub description: NymNodeDataV1, +} + +impl NymNodeDescriptionV1 { + pub fn version(&self) -> &str { + &self.description.build_information.build_version + } + + pub fn entry_information(&self) -> BasicEntryInformation { + BasicEntryInformation { + hostname: self.description.host_information.hostname.clone(), + ws_port: self.description.mixnet_websockets.ws_port, + wss_port: self.description.mixnet_websockets.wss_port, + } + } + + pub fn ed25519_identity_key(&self) -> ed25519::PublicKey { + self.description.host_information.keys.ed25519 + } + + pub fn current_sphinx_key(&self, current_rotation_id: u32) -> x25519::PublicKey { + let keys = &self.description.host_information.keys; + + if keys.current_x25519_sphinx_key.rotation_id == u32::MAX { + // legacy case (i.e. node doesn't support rotation) + return keys.current_x25519_sphinx_key.public_key; + } + + if current_rotation_id == keys.current_x25519_sphinx_key.rotation_id { + // it's the 'current' key + return keys.current_x25519_sphinx_key.public_key; + } + + if let Some(pre_announced) = &keys.pre_announced_x25519_sphinx_key { + if pre_announced.rotation_id == current_rotation_id { + return pre_announced.public_key; + } + } + + warn!( + "unexpected key rotation {current_rotation_id} for node {}", + self.node_id + ); + // this should never be reached, but just in case, return the fallback option + keys.current_x25519_sphinx_key.public_key + } + + pub fn to_skimmed_node( + &self, + current_rotation_id: u32, + role: NodeRole, + performance: Performance, + ) -> SkimmedNode { + let keys = &self.description.host_information.keys; + let entry = if self.description.declared_role.entry { + Some(self.entry_information()) + } else { + None + }; + + SkimmedNode { + node_id: self.node_id, + ed25519_identity_pubkey: keys.ed25519, + ip_addresses: self.description.host_information.ip_address.clone(), + mix_port: self.description.mix_port(), + x25519_sphinx_pubkey: self.current_sphinx_key(current_rotation_id), + // we can't use the declared roles, we have to take whatever was provided in the contract. + // why? say this node COULD operate as an exit, but it might be the case the contract decided + // to assign it an ENTRY role only. we have to use that one instead. + role, + supported_roles: self.description.declared_role, + entry, + performance, + } + } + + pub fn to_semi_skimmed_node( + &self, + current_rotation_id: u32, + role: NodeRole, + performance: Performance, + ) -> SemiSkimmedNode { + let skimmed_node = self.to_skimmed_node(current_rotation_id, role, performance); + + SemiSkimmedNode { + basic: skimmed_node, + x25519_noise_versioned_key: self + .description + .host_information + .keys + .x25519_versioned_noise, + } + } +} + +// to whoever is thinking of modifying this struct. +// you MUST NOT change its structure in any way - adding, removing or changing fields +// otherwise, it will break old clients as bincode serialisation is not backwards compatible +// even if you put `#[serde(default)]` all over the place +#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] +#[serde(rename_all = "snake_case")] +#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))] +#[cfg_attr( + feature = "generate-ts", + ts( + export, + export_to = "ts-packages/types/src/types/rust/DescribedNodeType.ts" + ) +)] +pub enum DescribedNodeTypeV1 { + LegacyMixnode, + LegacyGateway, + NymNode, +} + +impl DescribedNodeTypeV1 { + pub fn is_nym_node(&self) -> bool { + matches!(self, DescribedNodeTypeV1::NymNode) + } +} + +// to whoever is thinking of modifying this struct. +// you MUST NOT change its structure in any way - adding, removing or changing fields +// otherwise, it will break old clients as bincode serialisation is not backwards compatible +// even if you put `#[serde(default)]` all over the place +#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] +pub struct NymNodeDataV1 { + #[serde(default)] + pub last_polled: OffsetDateTimeJsonSchemaWrapper, + + pub host_information: HostInformationV1, + + #[serde(default)] + pub declared_role: DeclaredRolesV1, + + #[serde(default)] + pub auxiliary_details: AuxiliaryDetailsV1, + + // TODO: do we really care about ALL build info or just the version? + pub build_information: BinaryBuildInformationOwned, + + #[serde(default)] + pub network_requester: Option, + + #[serde(default)] + pub ip_packet_router: Option, + + #[serde(default)] + pub authenticator: Option, + + #[serde(default)] + pub wireguard: Option, + + // for now we only care about their ws/wss situation, nothing more + pub mixnet_websockets: WebSocketsV1, +} + +impl NymNodeDataV1 { + pub fn mix_port(&self) -> u16 { + self.auxiliary_details + .announce_ports + .mix_port + .unwrap_or(DEFAULT_MIX_LISTENING_PORT) + } + + pub fn verloc_port(&self) -> u16 { + self.auxiliary_details + .announce_ports + .verloc_port + .unwrap_or(DEFAULT_VERLOC_LISTENING_PORT) + } +} diff --git a/nym-api/nym-api-requests/src/models/described/v2.rs b/nym-api/nym-api-requests/src/models/described/v2.rs new file mode 100644 index 0000000000..ac712e3d5e --- /dev/null +++ b/nym-api/nym-api-requests/src/models/described/v2.rs @@ -0,0 +1,343 @@ +// Copyright 2026 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::models::{ + AuthenticatorDetailsV1, AuxiliaryDetailsV1, BinaryBuildInformationOwned, DeclaredRolesV1, + DescribedNodeTypeV1, HostInformationV1, HostKeysV1, IpPacketRouterDetailsV1, + LewesProtocolDetailsV1, NetworkRequesterDetailsV1, NymNodeDataV1, NymNodeDescriptionV1, + OffsetDateTimeJsonSchemaWrapper, SphinxKeyV1, WebSocketsV1, WireguardDetailsV1, +}; +use crate::nym_nodes::{BasicEntryInformation, NodeRole, SemiSkimmedNode, SkimmedNode}; +use nym_crypto::asymmetric::{ed25519, x25519}; +use nym_mixnet_contract_common::reward_params::Performance; +use nym_mixnet_contract_common::NodeId; +use nym_network_defaults::{DEFAULT_MIX_LISTENING_PORT, DEFAULT_VERLOC_LISTENING_PORT}; +use nym_noise_keys::VersionedNoiseKeyV1; +use serde::{Deserialize, Serialize}; +use tracing::warn; +use utoipa::ToSchema; + +// no changes for the following types +pub type HostInformationV2 = HostInformationV1; +pub type DeclaredRolesV2 = DeclaredRolesV1; +pub type AuxiliaryDetailsV2 = AuxiliaryDetailsV1; +pub type NetworkRequesterDetailsV2 = NetworkRequesterDetailsV1; +pub type IpPacketRouterDetailsV2 = IpPacketRouterDetailsV1; +pub type AuthenticatorDetailsV2 = AuthenticatorDetailsV1; +pub type WireguardDetailsV2 = WireguardDetailsV1; +pub type WebSocketsV2 = WebSocketsV1; +pub type DescribedNodeTypeV2 = DescribedNodeTypeV1; +pub type HostKeysV2 = HostKeysV1; +pub type SphinxKeyV2 = SphinxKeyV1; +pub type VersionedNoiseKeyV2 = VersionedNoiseKeyV1; + +// to whoever is thinking of modifying this struct. +// you MUST NOT change its structure in any way - adding, removing or changing fields +// otherwise, it will break old clients as bincode serialisation is not backwards compatible +// even if you put `#[serde(default)]` all over the place +#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] +pub struct NymNodeDescriptionV2 { + #[schema(value_type = u32)] + pub node_id: NodeId, + pub contract_node_type: DescribedNodeTypeV2, + pub description: NymNodeDataV2, +} + +impl NymNodeDescriptionV2 { + pub fn version(&self) -> &str { + &self.description.build_information.build_version + } + + pub fn entry_information(&self) -> BasicEntryInformation { + BasicEntryInformation { + hostname: self.description.host_information.hostname.clone(), + ws_port: self.description.mixnet_websockets.ws_port, + wss_port: self.description.mixnet_websockets.wss_port, + } + } + + pub fn ed25519_identity_key(&self) -> ed25519::PublicKey { + self.description.host_information.keys.ed25519 + } + + pub fn current_sphinx_key(&self, current_rotation_id: u32) -> x25519::PublicKey { + let keys = &self.description.host_information.keys; + + if keys.current_x25519_sphinx_key.rotation_id == u32::MAX { + // legacy case (i.e. node doesn't support rotation) + return keys.current_x25519_sphinx_key.public_key; + } + + if current_rotation_id == keys.current_x25519_sphinx_key.rotation_id { + // it's the 'current' key + return keys.current_x25519_sphinx_key.public_key; + } + + if let Some(pre_announced) = &keys.pre_announced_x25519_sphinx_key { + if pre_announced.rotation_id == current_rotation_id { + return pre_announced.public_key; + } + } + + warn!( + "unexpected key rotation {current_rotation_id} for node {}", + self.node_id + ); + // this should never be reached, but just in case, return the fallback option + keys.current_x25519_sphinx_key.public_key + } + + pub fn to_skimmed_node( + &self, + current_rotation_id: u32, + role: NodeRole, + performance: Performance, + ) -> SkimmedNode { + let keys = &self.description.host_information.keys; + let entry = if self.description.declared_role.entry { + Some(self.entry_information()) + } else { + None + }; + + SkimmedNode { + node_id: self.node_id, + ed25519_identity_pubkey: keys.ed25519, + ip_addresses: self.description.host_information.ip_address.clone(), + mix_port: self.description.mix_port(), + x25519_sphinx_pubkey: self.current_sphinx_key(current_rotation_id), + // we can't use the declared roles, we have to take whatever was provided in the contract. + // why? say this node COULD operate as an exit, but it might be the case the contract decided + // to assign it an ENTRY role only. we have to use that one instead. + role, + supported_roles: self.description.declared_role, + entry, + performance, + } + } + + pub fn to_semi_skimmed_node( + &self, + current_rotation_id: u32, + role: NodeRole, + performance: Performance, + ) -> SemiSkimmedNode { + let skimmed_node = self.to_skimmed_node(current_rotation_id, role, performance); + + SemiSkimmedNode { + basic: skimmed_node, + x25519_noise_versioned_key: self + .description + .host_information + .keys + .x25519_versioned_noise, + } + } +} + +// to whoever is thinking of modifying this struct. +// you MUST NOT change its structure in any way - adding, removing or changing fields +// otherwise, it will break old clients as bincode serialisation is not backwards compatible +// even if you put `#[serde(default)]` all over the place +#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] +pub struct NymNodeDataV2 { + #[serde(default)] + pub last_polled: OffsetDateTimeJsonSchemaWrapper, + + pub host_information: HostInformationV2, + + #[serde(default)] + pub declared_role: DeclaredRolesV2, + + #[serde(default)] + pub auxiliary_details: AuxiliaryDetailsV2, + + // TODO: do we really care about ALL build info or just the version? + pub build_information: BinaryBuildInformationOwned, + + #[serde(default)] + pub network_requester: Option, + + #[serde(default)] + pub ip_packet_router: Option, + + #[serde(default)] + pub authenticator: Option, + + #[serde(default)] + pub wireguard: Option, + + // for now we only care about their ws/wss situation, nothing more + pub mixnet_websockets: WebSocketsV2, + + #[serde(default)] + pub lewes_protocol: Option, +} + +impl NymNodeDataV2 { + pub fn mix_port(&self) -> u16 { + self.auxiliary_details + .announce_ports + .mix_port + .unwrap_or(DEFAULT_MIX_LISTENING_PORT) + } + + pub fn verloc_port(&self) -> u16 { + self.auxiliary_details + .announce_ports + .verloc_port + .unwrap_or(DEFAULT_VERLOC_LISTENING_PORT) + } +} + +impl From for NymNodeDataV1 { + fn from(data: NymNodeDataV2) -> Self { + NymNodeDataV1 { + last_polled: data.last_polled, + host_information: data.host_information, + declared_role: data.declared_role, + auxiliary_details: data.auxiliary_details, + build_information: data.build_information, + network_requester: data.network_requester, + ip_packet_router: data.ip_packet_router, + authenticator: data.authenticator, + wireguard: data.wireguard, + mixnet_websockets: data.mixnet_websockets, + } + } +} + +impl From for NymNodeDataV2 { + fn from(data: NymNodeDataV1) -> Self { + NymNodeDataV2 { + last_polled: data.last_polled, + host_information: data.host_information, + declared_role: data.declared_role, + auxiliary_details: data.auxiliary_details, + build_information: data.build_information, + network_requester: data.network_requester, + ip_packet_router: data.ip_packet_router, + authenticator: data.authenticator, + wireguard: data.wireguard, + mixnet_websockets: data.mixnet_websockets, + lewes_protocol: Default::default(), + } + } +} + +impl From for NymNodeDescriptionV1 { + fn from(value: NymNodeDescriptionV2) -> Self { + NymNodeDescriptionV1 { + node_id: value.node_id, + contract_node_type: value.contract_node_type, + description: value.description.into(), + } + } +} + +impl From for NymNodeDescriptionV2 { + fn from(value: NymNodeDescriptionV1) -> Self { + NymNodeDescriptionV2 { + node_id: value.node_id, + contract_node_type: value.contract_node_type, + description: value.description.into(), + } + } +} + +#[cfg(test)] +pub fn mock_nym_node_description(seed: u64) -> NymNodeDescriptionV2 { + use crate::models::{LPHashFunction, LPKEM}; + use nym_test_utils::helpers::{u64_seeded_rng, RngCore}; + + let mut rng = u64_seeded_rng(seed); + + let ed25519 = ed25519::KeyPair::new(&mut rng); + + // just reuse the same x25519 key for everything - this is just a data mock + let x25519 = x25519::KeyPair::new(&mut rng); + + let mut hashes_wrapper = std::collections::HashMap::new(); + let mut hashes = std::collections::HashMap::new(); + + hashes.insert(LPHashFunction::Sha256, vec![(seed % 256) as u8; 32]); + hashes_wrapper.insert(LPKEM::X25519, hashes); + + NymNodeDescriptionV2 { + node_id: rng.next_u32(), + contract_node_type: DescribedNodeTypeV1::NymNode, + description: NymNodeDataV2 { + last_polled: time::OffsetDateTime::from_unix_timestamp(1767225600) + .unwrap() + .into(), + host_information: HostInformationV2 { + ip_address: vec![ + std::net::IpAddr::V4(std::net::Ipv4Addr::new(1, 2, 3, (seed % 255) as u8)), + ], + hostname: Some(format!("my-awesome-node-{seed}.com")), + keys: HostKeysV2 { + ed25519: *ed25519.public_key(), + x25519: *x25519.public_key(), + current_x25519_sphinx_key: SphinxKeyV2 { + rotation_id: 123, + public_key: *x25519.public_key(), + }, + pre_announced_x25519_sphinx_key: None, + x25519_versioned_noise: Some(VersionedNoiseKeyV2 { + supported_version: nym_noise_keys::NoiseVersion::V1, + x25519_pubkey: *x25519.public_key(), + }), + }, + }, + declared_role: DeclaredRolesV2 { + mixnode: false, + entry: true, + exit_nr: true, + exit_ipr: true, + }, + auxiliary_details: AuxiliaryDetailsV2 { + location: Some(celes::Country::switzerland()), + announce_ports: Default::default(), + accepted_operator_terms_and_conditions: true, + }, + build_information: BinaryBuildInformationOwned { + binary_name: "dummy-node".to_string(), + build_timestamp: "2021-02-23T20:14:46.558472672+00:00".to_string(), + build_version: "0.1.0-9-g46f83e1".to_string(), + commit_sha: "46f83e112520533338245862d366f6a02cef07d4".to_string(), + commit_timestamp: "2021-02-23T08:08:02-05:00".to_string(), + commit_branch: "master".to_string(), + rustc_version: "1.52.0-nightly".to_string(), + rustc_channel: "nightly".to_string(), + cargo_profile: "release".to_string(), + cargo_triple: "wasm32-unknown-unknown".to_string(), + }, + network_requester: Some(NetworkRequesterDetailsV2 { + address: "FhtkzizQg2JbZ19kGkRKXdjV2QnFbT5ww88ZAKaD4nkF.7Remi4UVYzn1yL3qYtEcQBGh6tzTYxMdYB4uqyHVc5Z4@62F81C9GrHDRja9WCqozemRFSzFPMecY85MbGwn6efve".to_string(), + uses_exit_policy: true, + }), + ip_packet_router: Some(IpPacketRouterDetailsV2 { + address: "FhtkzizQg2JbZ19kGkRKXdjV2QnFbT5ww88ZAKaD4nkF.7Remi4UVYzn1yL3qYtEcQBGh6tzTYxMdYB4uqyHVc5Z4@62F81C9GrHDRja9WCqozemRFSzFPMecY85MbGwn6efve".to_string(), + }), + authenticator: Some(AuthenticatorDetailsV2 { + address: "FhtkzizQg2JbZ19kGkRKXdjV2QnFbT5ww88ZAKaD4nkF.7Remi4UVYzn1yL3qYtEcQBGh6tzTYxMdYB4uqyHVc5Z4@62F81C9GrHDRja9WCqozemRFSzFPMecY85MbGwn6efve".to_string(), + }), + wireguard: Some(WireguardDetailsV2 { + port: 123, + tunnel_port: 234, + metadata_port: 456, + public_key: x25519.public_key().to_base58_string(), + }), + lewes_protocol: Some(LewesProtocolDetailsV1 { + enabled: true, + control_port: 1234, + data_port: 2345, + kem_keys: hashes_wrapper, + }), + mixnet_websockets: WebSocketsV2 { + ws_port: 9000, + wss_port: None, + }, + }, + } +} diff --git a/nym-api/nym-api-requests/src/models/mod.rs b/nym-api/nym-api-requests/src/models/mod.rs index 6d7ab23f96..c5f0026d44 100644 --- a/nym-api/nym-api-requests/src/models/mod.rs +++ b/nym-api/nym-api-requests/src/models/mod.rs @@ -31,7 +31,7 @@ pub use schema_helpers::*; pub use nym_mixnet_contract_common::{EpochId, KeyRotationId, KeyRotationState}; pub use nym_node_requests::api::v1::node::models::BinaryBuildInformationOwned; -pub use nym_noise_keys::VersionedNoiseKey; +pub use nym_noise_keys::VersionedNoiseKeyV1; #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] pub struct RequestError { diff --git a/nym-api/nym-api-requests/src/nym_nodes.rs b/nym-api/nym-api-requests/src/nym_nodes.rs index ca7ffab7e8..40b088a681 100644 --- a/nym-api/nym-api-requests/src/nym_nodes.rs +++ b/nym-api/nym-api-requests/src/nym_nodes.rs @@ -1,7 +1,7 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::models::{DeclaredRoles, NymNodeData, OffsetDateTimeJsonSchemaWrapper}; +use crate::models::{DeclaredRolesV1, NymNodeDataV1, OffsetDateTimeJsonSchemaWrapper}; use crate::pagination::{PaginatedResponse, Pagination}; use nym_crypto::asymmetric::ed25519::serde_helpers::bs58_ed25519_pubkey; use nym_crypto::asymmetric::x25519::serde_helpers::bs58_x25519_pubkey; @@ -9,7 +9,7 @@ use nym_crypto::asymmetric::{ed25519, x25519}; use nym_mixnet_contract_common::nym_node::Role; use nym_mixnet_contract_common::reward_params::Performance; use nym_mixnet_contract_common::{EpochId, Interval, NodeId}; -use nym_noise_keys::VersionedNoiseKey; +use nym_noise_keys::VersionedNoiseKeyV1; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::net::IpAddr; @@ -254,7 +254,7 @@ pub struct SkimmedNode { // needed for the purposes of sending appropriate test packets #[serde(default)] - pub supported_roles: DeclaredRoles, + pub supported_roles: DeclaredRolesV1, pub entry: Option, @@ -278,7 +278,7 @@ impl SkimmedNode { pub struct SemiSkimmedNode { pub basic: SkimmedNode, - pub x25519_noise_versioned_key: Option, + pub x25519_noise_versioned_key: Option, // pub location: } @@ -287,7 +287,7 @@ pub struct FullFatNode { pub expanded: SemiSkimmedNode, // kinda temporary for now to make as few changes as possible for now - pub self_described: Option, + pub self_described: Option, } #[derive(Clone, Serialize, Deserialize, Debug, PartialEq, ToSchema)] diff --git a/nym-api/src/network_monitor/monitor/preparer.rs b/nym-api/src/network_monitor/monitor/preparer.rs index 074a4f612f..d2d23a9715 100644 --- a/nym-api/src/network_monitor/monitor/preparer.rs +++ b/nym-api/src/network_monitor/monitor/preparer.rs @@ -8,7 +8,7 @@ use crate::node_describe_cache::cache::DescribedNodes; use crate::node_describe_cache::NodeDescriptionTopologyExt; use crate::node_status_api::NodeStatusCache; use crate::support::caching::cache::SharedCache; -use nym_api_requests::models::{NodeAnnotation, NymNodeDescription}; +use nym_api_requests::models::{NodeAnnotation, NymNodeDescriptionV2}; use nym_contracts_common::NaiveFloat; use nym_crypto::asymmetric::{ed25519, x25519}; use nym_mixnet_contract_common::{LegacyMixLayer, NodeId}; @@ -179,7 +179,7 @@ impl PacketPreparer { rng: &mut R, current_rotation_id: u32, node_statuses: &HashMap, - mixing_nym_nodes: impl Iterator + 'a, + mixing_nym_nodes: impl Iterator + 'a, ) -> HashMap> { let mut layered_mixes = HashMap::new(); @@ -206,7 +206,7 @@ impl PacketPreparer { &self, current_rotation_id: u32, node_statuses: &HashMap, - gateway_capable_nym_nodes: impl Iterator + 'a, + gateway_capable_nym_nodes: impl Iterator + 'a, ) -> Vec<(RoutingNode, f64)> { let mut gateways = Vec::new(); @@ -361,7 +361,7 @@ impl PacketPreparer { fn nym_node_to_routing_node( &self, current_rotation_id: u32, - description: &NymNodeDescription, + description: &NymNodeDescriptionV2, ) -> Option { description.try_to_topology_node(current_rotation_id).ok() } diff --git a/nym-api/src/node_describe_cache/cache.rs b/nym-api/src/node_describe_cache/cache.rs index 3f3de776d6..0e00b986d6 100644 --- a/nym-api/src/node_describe_cache/cache.rs +++ b/nym-api/src/node_describe_cache/cache.rs @@ -1,61 +1,61 @@ // Copyright 2025 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use nym_api_requests::models::{DescribedNodeType, NymNodeData, NymNodeDescription}; +use nym_api_requests::models::{DescribedNodeTypeV2, NymNodeDataV2, NymNodeDescriptionV2}; use nym_mixnet_contract_common::NodeId; use std::collections::HashMap; use std::net::IpAddr; #[derive(Debug, Clone)] pub struct DescribedNodes { - pub(crate) nodes: HashMap, + pub(crate) nodes: HashMap, pub(crate) addresses_cache: HashMap, } impl DescribedNodes { - pub fn force_update(&mut self, node: NymNodeDescription) { + pub fn force_update(&mut self, node: NymNodeDescriptionV2) { for ip in &node.description.host_information.ip_address { self.addresses_cache.insert(*ip, node.node_id); } self.nodes.insert(node.node_id, node); } - pub fn get_description(&self, node_id: &NodeId) -> Option<&NymNodeData> { + pub fn get_description(&self, node_id: &NodeId) -> Option<&NymNodeDataV2> { self.nodes.get(node_id).map(|n| &n.description) } - pub fn get_node(&self, node_id: &NodeId) -> Option<&NymNodeDescription> { + pub fn get_node(&self, node_id: &NodeId) -> Option<&NymNodeDescriptionV2> { self.nodes.get(node_id) } - pub fn all_nodes(&self) -> impl Iterator { + pub fn all_nodes(&self) -> impl Iterator { self.nodes.values() } - pub fn all_nym_nodes(&self) -> impl Iterator { + pub fn all_nym_nodes(&self) -> impl Iterator { self.nodes .values() - .filter(|n| n.contract_node_type == DescribedNodeType::NymNode) + .filter(|n| n.contract_node_type == DescribedNodeTypeV2::NymNode) } - pub fn mixing_nym_nodes(&self) -> impl Iterator { + pub fn mixing_nym_nodes(&self) -> impl Iterator { self.nodes .values() - .filter(|n| n.contract_node_type == DescribedNodeType::NymNode) + .filter(|n| n.contract_node_type == DescribedNodeTypeV2::NymNode) .filter(|n| n.description.declared_role.mixnode) } - pub fn entry_capable_nym_nodes(&self) -> impl Iterator { + pub fn entry_capable_nym_nodes(&self) -> impl Iterator { self.nodes .values() - .filter(|n| n.contract_node_type == DescribedNodeType::NymNode) + .filter(|n| n.contract_node_type == DescribedNodeTypeV2::NymNode) .filter(|n| n.description.declared_role.entry) } - pub fn exit_capable_nym_nodes(&self) -> impl Iterator { + pub fn exit_capable_nym_nodes(&self) -> impl Iterator { self.nodes .values() - .filter(|n| n.contract_node_type == DescribedNodeType::NymNode) + .filter(|n| n.contract_node_type == DescribedNodeTypeV2::NymNode) .filter(|n| n.description.declared_role.can_operate_exit_gateway()) } diff --git a/nym-api/src/node_describe_cache/mod.rs b/nym-api/src/node_describe_cache/mod.rs index b72c1a9103..976904ce2c 100644 --- a/nym-api/src/node_describe_cache/mod.rs +++ b/nym-api/src/node_describe_cache/mod.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: GPL-3.0-only use crate::support::caching::cache::UninitialisedCache; -use nym_api_requests::models::NymNodeDescription; +use nym_api_requests::models::{NymNodeDescriptionV1, NymNodeDescriptionV2}; use nym_config::defaults::DEFAULT_NYM_NODE_HTTP_PORT; use nym_mixnet_contract_common::NodeId; use nym_node_requests::api::client::NymNodeApiClientError; @@ -68,7 +68,19 @@ pub(crate) trait NodeDescriptionTopologyExt { ) -> Result; } -impl NodeDescriptionTopologyExt for NymNodeDescription { +impl NodeDescriptionTopologyExt for NymNodeDescriptionV1 { + fn try_to_topology_node( + &self, + current_rotation_id: u32, + ) -> Result { + // for the purposes of routing, performance is completely ignored, + // so add dummy value and piggyback on existing conversion + (&self.to_skimmed_node(current_rotation_id, Default::default(), Default::default())) + .try_into() + } +} + +impl NodeDescriptionTopologyExt for NymNodeDescriptionV2 { fn try_to_topology_node( &self, current_rotation_id: u32, diff --git a/nym-api/src/node_describe_cache/query_helpers.rs b/nym-api/src/node_describe_cache/query_helpers.rs index bcfbdf4049..c800638efc 100644 --- a/nym-api/src/node_describe_cache/query_helpers.rs +++ b/nym-api/src/node_describe_cache/query_helpers.rs @@ -5,14 +5,14 @@ use crate::node_describe_cache::NodeDescribeCacheError; use futures::future::{maybe_done, MaybeDone}; use futures::{FutureExt, TryFutureExt}; use nym_api_requests::models::{ - AuthenticatorDetails, DeclaredRoles, HostInformation, IpPacketRouterDetails, - LewesProtocolDetails, NetworkRequesterDetails, NymNodeData, WebSockets, WireguardDetails, + AuthenticatorDetailsV2, AuxiliaryDetailsV2, DeclaredRolesV2, HostInformationV2, + IpPacketRouterDetailsV2, LewesProtocolDetailsV1, NetworkRequesterDetailsV2, NymNodeDataV2, + WebSocketsV2, WireguardDetailsV2, }; use nym_bin_common::build_information::BinaryBuildInformationOwned; use nym_config::defaults::mainnet; use nym_mixnet_contract_common::NodeId; use nym_node_requests::api::client::{NymNodeApiClientError, NymNodeApiClientExt}; -use nym_node_requests::api::v1::node::models::AuxiliaryDetails; use nym_node_requests::api::Client; use pin_project::pin_project; use std::future::Future; @@ -23,14 +23,14 @@ use tracing::debug; async fn network_requester_future( client: &Client, -) -> Result, NymNodeApiClientError> { +) -> Result, NymNodeApiClientError> { let Ok(nr) = client.get_network_requester().await else { return Ok(None); }; client.get_exit_policy().await.map(|exit_policy| { let uses_nym_exit_policy = exit_policy.upstream_source == mainnet::EXIT_POLICY_URL; - Some(NetworkRequesterDetails { + Some(NetworkRequesterDetailsV2 { address: nr.address, uses_exit_policy: exit_policy.enabled && uses_nym_exit_policy, }) @@ -55,7 +55,8 @@ pub(crate) async fn query_for_described_data( // old nym-nodes will not have this field, so use the default instead debug!("could not obtain auxiliary details of node {node_id}: {err} is it running an old version?") }) - .unwrap_or_else(|_| AuxiliaryDetails::default()), + .ok_into() + .unwrap_or_else(|_| AuxiliaryDetailsV2::default()), client.get_mixnet_websockets().ok_into().map_err(map_query_err), network_requester_future(client).map_err(map_query_err), // `ok_into` ultimately calls `IpPacketRouter::into` to transform it into `IpPacketRouterDetails` @@ -112,14 +113,14 @@ impl Future for NodeDescribedInfoMegaFuture where F1: Future>, - F2: Future>, - F3: Future, - F4: Future>, - F5: Future, NodeDescribeCacheError>>, - F6: Future>, - F7: Future>, - F8: Future>, - F9: Future>, + F2: Future>, + F3: Future, + F4: Future>, + F5: Future, NodeDescribeCacheError>>, + F6: Future>, + F7: Future>, + F8: Future>, + F9: Future>, { type Output = Result; @@ -202,15 +203,15 @@ where struct ResolvedNodeDescribedInfo { build_info: Result, - roles: Result, + roles: Result, // TODO: in the future make it return a Result as well. - auxiliary_details: AuxiliaryDetails, - websockets: Result, - network_requester: Result, NodeDescribeCacheError>, - ipr: Option, - authenticator: Option, - wireguard: Option, - lewes_protocol: Option, + auxiliary_details: AuxiliaryDetailsV2, + websockets: Result, + network_requester: Result, NodeDescribeCacheError>, + ipr: Option, + authenticator: Option, + wireguard: Option, + lewes_protocol: Option, } impl ResolvedNodeDescribedInfo { @@ -232,22 +233,22 @@ impl ResolvedNodeDescribedInfo { #[derive(Debug)] pub(crate) struct UnwrappedResolvedNodeDescribedInfo { pub(crate) build_info: BinaryBuildInformationOwned, - pub(crate) roles: DeclaredRoles, - pub(crate) auxiliary_details: AuxiliaryDetails, - pub(crate) websockets: WebSockets, - pub(crate) network_requester: Option, - pub(crate) ipr: Option, - pub(crate) authenticator: Option, - pub(crate) wireguard: Option, - pub(crate) lewes_protocol: Option, + pub(crate) roles: DeclaredRolesV2, + pub(crate) auxiliary_details: AuxiliaryDetailsV2, + pub(crate) websockets: WebSocketsV2, + pub(crate) network_requester: Option, + pub(crate) ipr: Option, + pub(crate) authenticator: Option, + pub(crate) wireguard: Option, + pub(crate) lewes_protocol: Option, } impl UnwrappedResolvedNodeDescribedInfo { pub(crate) fn into_node_description( self, - host_info: impl Into, - ) -> NymNodeData { - NymNodeData { + host_info: impl Into, + ) -> NymNodeDataV2 { + NymNodeDataV2 { host_information: host_info.into(), last_polled: OffsetDateTime::now_utc().into(), build_information: self.build_info, diff --git a/nym-api/src/node_describe_cache/refresh.rs b/nym-api/src/node_describe_cache/refresh.rs index 24fea96c44..801aa5dc29 100644 --- a/nym-api/src/node_describe_cache/refresh.rs +++ b/nym-api/src/node_describe_cache/refresh.rs @@ -3,7 +3,7 @@ use crate::node_describe_cache::query_helpers::query_for_described_data; use crate::node_describe_cache::NodeDescribeCacheError; -use nym_api_requests::models::{DescribedNodeType, NymNodeDescription}; +use nym_api_requests::models::{DescribedNodeTypeV2, NymNodeDescriptionV2}; use nym_bin_common::bin_info; use nym_config::defaults::DEFAULT_NYM_NODE_HTTP_PORT; use nym_crypto::asymmetric::ed25519; @@ -18,7 +18,7 @@ pub(crate) struct RefreshData { host: String, node_id: NodeId, expected_identity: ed25519::PublicKey, - node_type: DescribedNodeType, + node_type: DescribedNodeTypeV2, port: Option, } @@ -30,7 +30,7 @@ impl<'a> TryFrom<&'a NymNodeDetails> for RefreshData { Ok(RefreshData::new( &node.bond_information.node.host, node.bond_information.identity().parse()?, - DescribedNodeType::NymNode, + DescribedNodeTypeV2::NymNode, node.node_id(), node.bond_information.node.custom_http_port, )) @@ -41,7 +41,7 @@ impl RefreshData { pub fn new( host: impl Into, expected_identity: ed25519::PublicKey, - node_type: DescribedNodeType, + node_type: DescribedNodeTypeV2, node_id: NodeId, port: Option, ) -> Self { @@ -58,7 +58,7 @@ impl RefreshData { self.node_id } - pub(crate) async fn try_refresh(self, allow_all_ips: bool) -> Option { + pub(crate) async fn try_refresh(self, allow_all_ips: bool) -> Option { match try_get_description(self, allow_all_ips).await { Ok(description) => Some(description), Err(err) => { @@ -124,7 +124,7 @@ async fn try_get_client( async fn try_get_description( data: RefreshData, allow_all_ips: bool, -) -> Result { +) -> Result { let client = try_get_client(&data.host, data.node_id, data.port).await?; let map_query_err = |err| NodeDescribeCacheError::ApiFailure { @@ -158,7 +158,7 @@ async fn try_get_description( let node_info = query_for_described_data(&client, data.node_id).await?; let description = node_info.into_node_description(host_info.data); - Ok(NymNodeDescription { + Ok(NymNodeDescriptionV2 { node_id: data.node_id, contract_node_type: data.node_type, description, diff --git a/nym-api/src/node_status_api/cache/config_score.rs b/nym-api/src/node_status_api/cache/config_score.rs index 30f24d0b40..02ae220436 100644 --- a/nym-api/src/node_status_api/cache/config_score.rs +++ b/nym-api/src/node_status_api/cache/config_score.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: GPL-3.0-only use crate::mixnet_contract_cache::cache::data::ConfigScoreData; -use nym_api_requests::models::{ConfigScore, NymNodeDescription}; +use nym_api_requests::models::{ConfigScore, NymNodeDescriptionV2}; use nym_contracts_common::NaiveFloat; use nym_mixnet_contract_common::VersionScoreFormulaParams; @@ -19,7 +19,7 @@ fn versions_behind_factor_to_config_score( pub(crate) fn calculate_config_score( config_score_data: &ConfigScoreData, - described_data: Option<&NymNodeDescription>, + described_data: Option<&NymNodeDescriptionV2>, ) -> ConfigScore { let Some(described) = described_data else { return ConfigScore::unavailable(); diff --git a/nym-api/src/nym_nodes/handlers/mod.rs b/nym-api/src/nym_nodes/handlers/mod.rs index 954349c03a..d242f328cf 100644 --- a/nym-api/src/nym_nodes/handlers/mod.rs +++ b/nym-api/src/nym_nodes/handlers/mod.rs @@ -1,495 +1,5 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::node_status_api::models::{AxumErrorResponse, AxumResult}; -use crate::support::http::helpers::{NodeIdParam, PaginationRequest}; -use crate::support::http::state::AppState; -use axum::extract::{Path, Query, State}; -use axum::routing::{get, post}; -use axum::{Json, Router}; -use nym_api_requests::models::{ - AnnotationResponse, NodeDatePerformanceResponse, NodePerformanceResponse, NodeRefreshBody, - NoiseDetails, NymNodeDescription, PerformanceHistoryResponse, RewardedSetResponse, - StakeSaturationResponse, UptimeHistoryResponse, -}; -use nym_api_requests::pagination::{PaginatedResponse, Pagination}; -use nym_contracts_common::NaiveFloat; -use nym_http_api_common::{FormattedResponse, Output, OutputParams}; -use nym_mixnet_contract_common::reward_params::Performance; -use nym_mixnet_contract_common::NymNodeDetails; -use serde::{Deserialize, Serialize}; -use std::time::Duration; -use time::{Date, OffsetDateTime}; -use tower_http::compression::CompressionLayer; -use utoipa::{IntoParams, ToSchema}; - -pub(crate) fn nym_node_routes() -> Router { - Router::new() - .route("/refresh-described", post(refresh_described)) - .route("/noise", get(nodes_noise)) - .route("/bonded", get(get_bonded_nodes)) - .route("/described", get(get_described_nodes)) - .route("/annotation/:node_id", get(get_node_annotation)) - .route("/performance/:node_id", get(get_current_node_performance)) - .route("/stake-saturation/:node_id", get(get_node_stake_saturation)) - .route( - "/historical-performance/:node_id", - get(get_historical_performance), - ) - .route( - "/performance-history/:node_id", - get(get_node_performance_history), - ) - // to make it compatible with all the explorers that were used to using 0-100 values - .route("/uptime-history/:node_id", get(get_node_uptime_history)) - .route("/rewarded-set", get(rewarded_set)) - .layer(CompressionLayer::new()) -} - -#[utoipa::path( - tag = "Nym Nodes", - get, - path = "/rewarded-set", - context_path = "/v1/nym-nodes", - responses( - (status = 200, content( - (RewardedSetResponse = "application/json"), - (RewardedSetResponse = "application/yaml"), - (RewardedSetResponse = "application/bincode") - )) - ), - params(OutputParams) -)] -async fn rewarded_set( - Query(output): Query, - State(state): State, -) -> AxumResult> { - let output = output.output.unwrap_or_default(); - - let rewarded_set = state.nym_contract_cache().rewarded_set_owned().await?; - - Ok(output.to_response(nym_mixnet_contract_common::EpochRewardedSet::from(rewarded_set).into())) -} - -#[utoipa::path( - tag = "Nym Nodes", - post, - request_body = NodeRefreshBody, - path = "/refresh-described", - context_path = "/v1/nym-nodes", -)] -async fn refresh_described( - State(state): State, - Json(request_body): Json, -) -> AxumResult> { - let Some(refresh_data) = state - .nym_contract_cache() - .get_node_refresh_data(request_body.node_identity) - .await - else { - return Err(AxumErrorResponse::not_found(format!( - "node with identity {} does not seem to exist", - request_body.node_identity - ))); - }; - - if !request_body.verify_signature() { - return Err(AxumErrorResponse::unauthorised("invalid request signature")); - } - - if request_body.is_stale() { - return Err(AxumErrorResponse::bad_request("the request is stale")); - } - - let node_id = refresh_data.node_id(); - if let Some(last) = state.forced_refresh.last_refreshed(node_id).await { - // max 1 refresh a minute - let minute_ago = OffsetDateTime::now_utc() - Duration::from_secs(60); - if last > minute_ago { - return Err(AxumErrorResponse::too_many( - "already refreshed node in the last minute", - )); - } - } - // to make sure you can't ddos the endpoint while a request is in progress - state.forced_refresh.set_last_refreshed(node_id).await; - let allow_all_ips = state.forced_refresh.allow_all_ip_addresses; - - if let Some(updated_data) = refresh_data.try_refresh(allow_all_ips).await { - let Ok(mut describe_cache) = state.described_nodes_cache.write().await else { - return Err(AxumErrorResponse::service_unavailable()); - }; - describe_cache.get_mut().force_update(updated_data) - } else { - return Err(AxumErrorResponse::unprocessable_entity( - "failed to refresh node description", - )); - } - - Ok(Json(())) -} - -#[utoipa::path( - tag = "Nym Nodes", - get, - path = "/noise", - context_path = "/v1/nym-nodes", - responses( - (status = 200, content( - (PaginatedResponse = "application/json"), - (PaginatedResponse = "application/yaml"), - (PaginatedResponse = "application/bincode") - )) - ), - params(PaginationRequest) -)] -async fn nodes_noise( - State(state): State, - Query(pagination): Query, -) -> AxumResult>> { - // TODO: implement it - let _ = pagination; - let output = pagination.output.unwrap_or_default(); - - let describe_cache = state.describe_nodes_cache_data().await?; - - let nodes = describe_cache - .all_nodes() - .filter_map(|n| { - n.description - .host_information - .keys - .x25519_versioned_noise - .map(|noise_key| (noise_key, n)) - }) - .map(|(noise_key, node)| NoiseDetails { - key: noise_key, - mixnet_port: node.description.mix_port(), - ip_addresses: node.description.host_information.ip_address.clone(), - }) - .collect::>(); - - let total = nodes.len(); - - Ok(output.to_response(PaginatedResponse { - pagination: Pagination { - total, - page: 0, - size: total, - }, - data: nodes, - })) -} - -#[utoipa::path( - tag = "Nym Nodes", - get, - path = "/bonded", - context_path = "/v1/nym-nodes", - responses( - (status = 200, content( - (PaginatedResponse = "application/json"), - (PaginatedResponse = "application/yaml"), - (PaginatedResponse = "application/bincode") - )) - ), - params(PaginationRequest) -)] -async fn get_bonded_nodes( - State(state): State, - Query(pagination): Query, -) -> FormattedResponse> { - // TODO: implement it - let _ = pagination; - let output = pagination.output.unwrap_or_default(); - - let details = state.nym_contract_cache().nym_nodes().await; - let total = details.len(); - - output.to_response(PaginatedResponse { - pagination: Pagination { - total, - page: 0, - size: total, - }, - data: details, - }) -} - -#[utoipa::path( - tag = "Nym Nodes", - get, - path = "/described", - context_path = "/v1/nym-nodes", - responses( - (status = 200, content( - (PaginatedResponse = "application/json"), - (PaginatedResponse = "application/yaml"), - (PaginatedResponse = "application/bincode") - )) - ), - params(PaginationRequest) -)] -async fn get_described_nodes( - State(state): State, - Query(pagination): Query, -) -> AxumResult>> { - // TODO: implement it - let _ = pagination; - let output = pagination.output.unwrap_or_default(); - - let cache = state.described_nodes_cache.get().await?; - let descriptions = cache.all_nodes().cloned().collect::>(); - - Ok(output.to_response(PaginatedResponse { - pagination: Pagination { - total: descriptions.len(), - page: 0, - size: descriptions.len(), - }, - data: descriptions, - })) -} - -#[utoipa::path( - tag = "Nym Nodes", - get, - path = "/annotation/{node_id}", - context_path = "/v1/nym-nodes", - responses( - (status = 200, content( - (AnnotationResponse = "application/json"), - (AnnotationResponse = "application/yaml"), - (AnnotationResponse = "application/bincode") - )) - ), - params(NodeIdParam, OutputParams), -)] -async fn get_node_annotation( - Path(NodeIdParam { node_id }): Path, - Query(output): Query, - State(state): State, -) -> AxumResult> { - let output = output.output.unwrap_or_default(); - - let annotations = state - .node_status_cache - .node_annotations() - .await - .ok_or_else(AxumErrorResponse::internal)?; - - Ok(output.to_response(AnnotationResponse { - node_id, - annotation: annotations.get(&node_id).cloned(), - })) -} - -#[utoipa::path( - tag = "Nym Nodes", - get, - path = "/performance/{node_id}", - context_path = "/v1/nym-nodes", - responses( - (status = 200, content( - (NodePerformanceResponse = "application/json"), - (NodePerformanceResponse = "application/yaml"), - (NodePerformanceResponse = "application/bincode") - )) - ), - params(NodeIdParam, OutputParams), -)] -async fn get_current_node_performance( - Path(NodeIdParam { node_id }): Path, - Query(output): Query, - State(state): State, -) -> AxumResult> { - let output = output.output.unwrap_or_default(); - - let annotations = state - .node_status_cache - .node_annotations() - .await - .ok_or_else(AxumErrorResponse::internal)?; - - Ok(output.to_response(NodePerformanceResponse { - node_id, - performance: annotations - .get(&node_id) - .map(|n| n.last_24h_performance.naive_to_f64()), - })) -} - -#[utoipa::path( - tag = "Nym Nodes", - get, - path = "/stake-saturation/{node_id}", - context_path = "/v1/nym-nodes", - responses( - (status = 200, content( - (StakeSaturationResponse = "application/json"), - (StakeSaturationResponse = "application/yaml"), - (StakeSaturationResponse = "application/bincode") - )) - ), - params(NodeIdParam, OutputParams), -)] -async fn get_node_stake_saturation( - Path(NodeIdParam { node_id }): Path, - Query(output): Query, - State(state): State, -) -> AxumResult> { - let output = output.get_output(); - - let contract_cache = state.nym_contract_cache(); - let Some(node) = contract_cache.nym_node(node_id).await? else { - return Err(AxumErrorResponse::not_found("nym node bond not found")); - }; - - let rewarding_params = contract_cache.interval_reward_params().await?; - let as_at = contract_cache.cache_timestamp().await; - - Ok(output.to_response(StakeSaturationResponse { - saturation: node.rewarding_details.bond_saturation(&rewarding_params), - uncapped_saturation: node - .rewarding_details - .uncapped_bond_saturation(&rewarding_params), - as_at: as_at.unix_timestamp(), - })) -} - -// todo; probably extract it to requests crate -#[derive(Debug, Serialize, Deserialize, Copy, Clone, IntoParams, ToSchema)] -#[into_params(parameter_in = Query)] -pub(crate) struct DateQuery { - #[schema(value_type = String, example = "1970-01-01")] - pub(crate) date: Date, - - pub(crate) output: Option, -} - -#[utoipa::path( - tag = "Nym Nodes", - get, - path = "/historical-performance/{node_id}", - context_path = "/v1/nym-nodes", - responses( - (status = 200, content( - (NodeDatePerformanceResponse = "application/json"), - (NodeDatePerformanceResponse = "application/yaml"), - (NodeDatePerformanceResponse = "application/bincode") - )) - ), - params(DateQuery, NodeIdParam) -)] -async fn get_historical_performance( - Path(NodeIdParam { node_id }): Path, - Query(DateQuery { date, output }): Query, - State(state): State, -) -> AxumResult> { - let output = output.unwrap_or_default(); - - let uptime = state - .storage() - .get_historical_node_uptime_on(node_id, date) - .await?; - - Ok(output.to_response(NodeDatePerformanceResponse { - node_id, - date, - performance: uptime.and_then(|u| { - Performance::from_percentage_value(u.uptime as u64) - .map(|p| p.naive_to_f64()) - .ok() - }), - })) -} - -#[utoipa::path( - tag = "Nym Nodes", - get, - path = "/performance-history/{node_id}", - context_path = "/v1/nym-nodes", - responses( - (status = 200, content( - (PerformanceHistoryResponse = "application/json"), - (PerformanceHistoryResponse = "application/yaml"), - (PerformanceHistoryResponse = "application/bincode") - )) - ), - params(PaginationRequest, NodeIdParam) -)] -async fn get_node_performance_history( - Path(NodeIdParam { node_id }): Path, - State(state): State, - Query(pagination): Query, -) -> AxumResult> { - // TODO: implement it - let _ = pagination; - let output = pagination.output.unwrap_or_default(); - - let history = state - .storage() - .get_node_uptime_history(node_id) - .await? - .into_iter() - .filter_map(|u| u.try_into().ok()) - .collect::>(); - let total = history.len(); - - Ok(output.to_response(PerformanceHistoryResponse { - node_id, - history: PaginatedResponse { - pagination: Pagination { - total, - page: 0, - size: total, - }, - data: history, - }, - })) -} - -#[utoipa::path( - tag = "Nym Nodes", - get, - path = "/uptime-history/{node_id}", - context_path = "/v1/nym-nodes", - responses( - (status = 200, content( - (PerformanceHistoryResponse = "application/json"), - (PerformanceHistoryResponse = "application/yaml"), - (PerformanceHistoryResponse = "application/bincode") - )) - ), - params(PaginationRequest, NodeIdParam) -)] -async fn get_node_uptime_history( - Path(NodeIdParam { node_id }): Path, - State(state): State, - Query(pagination): Query, -) -> AxumResult> { - // TODO: implement it - let _ = pagination; - let output = pagination.output.unwrap_or_default(); - - let history = state - .storage() - .get_node_uptime_history(node_id) - .await? - .into_iter() - .filter_map(|u| u.try_into().ok()) - .collect::>(); - let total = history.len(); - - Ok(output.to_response(UptimeHistoryResponse { - node_id, - history: PaginatedResponse { - pagination: Pagination { - total, - page: 0, - size: total, - }, - data: history, - }, - })) -} +pub mod v1; +pub mod v2; diff --git a/nym-api/src/nym_nodes/handlers/v1.rs b/nym-api/src/nym_nodes/handlers/v1.rs new file mode 100644 index 0000000000..36834a5fc4 --- /dev/null +++ b/nym-api/src/nym_nodes/handlers/v1.rs @@ -0,0 +1,502 @@ +// Copyright 2026 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use crate::node_status_api::models::{AxumErrorResponse, AxumResult}; +use crate::support::http::helpers::{NodeIdParam, PaginationRequest}; +use crate::support::http::state::AppState; +use axum::extract::{Path, Query, State}; +use axum::routing::{get, post}; +use axum::{Json, Router}; +use nym_api_requests::models::{ + AnnotationResponse, NodeDatePerformanceResponse, NodePerformanceResponse, NodeRefreshBody, + NoiseDetails, NymNodeDescriptionV1, PerformanceHistoryResponse, RewardedSetResponse, + StakeSaturationResponse, UptimeHistoryResponse, +}; +use nym_api_requests::pagination::{PaginatedResponse, Pagination}; +use nym_contracts_common::NaiveFloat; +use nym_http_api_common::{FormattedResponse, Output, OutputParams}; +use nym_mixnet_contract_common::reward_params::Performance; +use nym_mixnet_contract_common::NymNodeDetails; +use serde::{Deserialize, Serialize}; +use std::time::Duration; +use time::{Date, OffsetDateTime}; +use tower_http::compression::CompressionLayer; +use utoipa::{IntoParams, ToSchema}; + +#[allow(deprecated)] +pub(crate) fn routes() -> Router { + Router::new() + .route("/refresh-described", post(refresh_described)) + .route("/noise", get(nodes_noise)) + .route("/bonded", get(get_bonded_nodes)) + .route("/described", get(get_described_nodes)) + .route("/annotation/:node_id", get(get_node_annotation)) + .route("/performance/:node_id", get(get_current_node_performance)) + .route("/stake-saturation/:node_id", get(get_node_stake_saturation)) + .route( + "/historical-performance/:node_id", + get(get_historical_performance), + ) + .route( + "/performance-history/:node_id", + get(get_node_performance_history), + ) + // to make it compatible with all the explorers that were used to using 0-100 values + .route("/uptime-history/:node_id", get(get_node_uptime_history)) + .route("/rewarded-set", get(rewarded_set)) + .layer(CompressionLayer::new()) +} + +#[utoipa::path( + tag = "Nym Nodes", + get, + path = "/rewarded-set", + context_path = "/v1/nym-nodes", + responses( + (status = 200, content( + (RewardedSetResponse = "application/json"), + (RewardedSetResponse = "application/yaml"), + (RewardedSetResponse = "application/bincode") + )) + ), + params(OutputParams) +)] +async fn rewarded_set( + Query(output): Query, + State(state): State, +) -> AxumResult> { + let output = output.output.unwrap_or_default(); + + let rewarded_set = state.nym_contract_cache().rewarded_set_owned().await?; + + Ok(output.to_response(nym_mixnet_contract_common::EpochRewardedSet::from(rewarded_set).into())) +} + +#[utoipa::path( + tag = "Nym Nodes", + post, + request_body = NodeRefreshBody, + path = "/refresh-described", + context_path = "/v1/nym-nodes", +)] +async fn refresh_described( + State(state): State, + Json(request_body): Json, +) -> AxumResult> { + let Some(refresh_data) = state + .nym_contract_cache() + .get_node_refresh_data(request_body.node_identity) + .await + else { + return Err(AxumErrorResponse::not_found(format!( + "node with identity {} does not seem to exist", + request_body.node_identity + ))); + }; + + if !request_body.verify_signature() { + return Err(AxumErrorResponse::unauthorised("invalid request signature")); + } + + if request_body.is_stale() { + return Err(AxumErrorResponse::bad_request("the request is stale")); + } + + let node_id = refresh_data.node_id(); + if let Some(last) = state.forced_refresh.last_refreshed(node_id).await { + // max 1 refresh a minute + let minute_ago = OffsetDateTime::now_utc() - Duration::from_secs(60); + if last > minute_ago { + return Err(AxumErrorResponse::too_many( + "already refreshed node in the last minute", + )); + } + } + // to make sure you can't ddos the endpoint while a request is in progress + state.forced_refresh.set_last_refreshed(node_id).await; + let allow_all_ips = state.forced_refresh.allow_all_ip_addresses; + + if let Some(updated_data) = refresh_data.try_refresh(allow_all_ips).await { + let Ok(mut describe_cache) = state.described_nodes_cache.write().await else { + return Err(AxumErrorResponse::service_unavailable()); + }; + describe_cache.get_mut().force_update(updated_data) + } else { + return Err(AxumErrorResponse::unprocessable_entity( + "failed to refresh node description", + )); + } + + Ok(Json(())) +} + +#[utoipa::path( + tag = "Nym Nodes", + get, + path = "/noise", + context_path = "/v1/nym-nodes", + responses( + (status = 200, content( + (PaginatedResponse = "application/json"), + (PaginatedResponse = "application/yaml"), + (PaginatedResponse = "application/bincode") + )) + ), + params(PaginationRequest) +)] +async fn nodes_noise( + State(state): State, + Query(pagination): Query, +) -> AxumResult>> { + // TODO: implement it + let _ = pagination; + let output = pagination.output.unwrap_or_default(); + + let describe_cache = state.describe_nodes_cache_data().await?; + + let nodes = describe_cache + .all_nodes() + .filter_map(|n| { + n.description + .host_information + .keys + .x25519_versioned_noise + .map(|noise_key| (noise_key, n)) + }) + .map(|(noise_key, node)| NoiseDetails { + key: noise_key, + mixnet_port: node.description.mix_port(), + ip_addresses: node.description.host_information.ip_address.clone(), + }) + .collect::>(); + + let total = nodes.len(); + + Ok(output.to_response(PaginatedResponse { + pagination: Pagination { + total, + page: 0, + size: total, + }, + data: nodes, + })) +} + +#[utoipa::path( + tag = "Nym Nodes", + get, + path = "/bonded", + context_path = "/v1/nym-nodes", + responses( + (status = 200, content( + (PaginatedResponse = "application/json"), + (PaginatedResponse = "application/yaml"), + (PaginatedResponse = "application/bincode") + )) + ), + params(PaginationRequest) +)] +async fn get_bonded_nodes( + State(state): State, + Query(pagination): Query, +) -> FormattedResponse> { + // TODO: implement it + let _ = pagination; + let output = pagination.output.unwrap_or_default(); + + let details = state.nym_contract_cache().nym_nodes().await; + let total = details.len(); + + output.to_response(PaginatedResponse { + pagination: Pagination { + total, + page: 0, + size: total, + }, + data: details, + }) +} + +#[utoipa::path( + tag = "Nym Nodes", + get, + path = "/described", + context_path = "/v1/nym-nodes", + responses( + (status = 200, content( + (PaginatedResponse = "application/json"), + (PaginatedResponse = "application/yaml"), + (PaginatedResponse = "application/bincode") + )) + ), + params(PaginationRequest) +)] +#[deprecated(note = "use '/v2/nym-nodes/described' instead")] +async fn get_described_nodes( + State(state): State, + Query(pagination): Query, +) -> AxumResult>> { + // TODO: implement it + let _ = pagination; + let output = pagination.output.unwrap_or_default(); + + let cache = state.described_nodes_cache.get().await?; + + // convert description to V1 + let descriptions = cache + .all_nodes() + .map(|d| d.clone().into()) + .collect::>(); + + Ok(output.to_response(PaginatedResponse { + pagination: Pagination { + total: descriptions.len(), + page: 0, + size: descriptions.len(), + }, + data: descriptions, + })) +} + +#[utoipa::path( + tag = "Nym Nodes", + get, + path = "/annotation/{node_id}", + context_path = "/v1/nym-nodes", + responses( + (status = 200, content( + (AnnotationResponse = "application/json"), + (AnnotationResponse = "application/yaml"), + (AnnotationResponse = "application/bincode") + )) + ), + params(NodeIdParam, OutputParams), +)] +async fn get_node_annotation( + Path(NodeIdParam { node_id }): Path, + Query(output): Query, + State(state): State, +) -> AxumResult> { + let output = output.output.unwrap_or_default(); + + let annotations = state + .node_status_cache + .node_annotations() + .await + .ok_or_else(AxumErrorResponse::internal)?; + + Ok(output.to_response(AnnotationResponse { + node_id, + annotation: annotations.get(&node_id).cloned(), + })) +} + +#[utoipa::path( + tag = "Nym Nodes", + get, + path = "/performance/{node_id}", + context_path = "/v1/nym-nodes", + responses( + (status = 200, content( + (NodePerformanceResponse = "application/json"), + (NodePerformanceResponse = "application/yaml"), + (NodePerformanceResponse = "application/bincode") + )) + ), + params(NodeIdParam, OutputParams), +)] +async fn get_current_node_performance( + Path(NodeIdParam { node_id }): Path, + Query(output): Query, + State(state): State, +) -> AxumResult> { + let output = output.output.unwrap_or_default(); + + let annotations = state + .node_status_cache + .node_annotations() + .await + .ok_or_else(AxumErrorResponse::internal)?; + + Ok(output.to_response(NodePerformanceResponse { + node_id, + performance: annotations + .get(&node_id) + .map(|n| n.last_24h_performance.naive_to_f64()), + })) +} + +#[utoipa::path( + tag = "Nym Nodes", + get, + path = "/stake-saturation/{node_id}", + context_path = "/v1/nym-nodes", + responses( + (status = 200, content( + (StakeSaturationResponse = "application/json"), + (StakeSaturationResponse = "application/yaml"), + (StakeSaturationResponse = "application/bincode") + )) + ), + params(NodeIdParam, OutputParams), +)] +async fn get_node_stake_saturation( + Path(NodeIdParam { node_id }): Path, + Query(output): Query, + State(state): State, +) -> AxumResult> { + let output = output.get_output(); + + let contract_cache = state.nym_contract_cache(); + let Some(node) = contract_cache.nym_node(node_id).await? else { + return Err(AxumErrorResponse::not_found("nym node bond not found")); + }; + + let rewarding_params = contract_cache.interval_reward_params().await?; + let as_at = contract_cache.cache_timestamp().await; + + Ok(output.to_response(StakeSaturationResponse { + saturation: node.rewarding_details.bond_saturation(&rewarding_params), + uncapped_saturation: node + .rewarding_details + .uncapped_bond_saturation(&rewarding_params), + as_at: as_at.unix_timestamp(), + })) +} + +// todo; probably extract it to requests crate +#[derive(Debug, Serialize, Deserialize, Copy, Clone, IntoParams, ToSchema)] +#[into_params(parameter_in = Query)] +pub(crate) struct DateQuery { + #[schema(value_type = String, example = "1970-01-01")] + pub(crate) date: Date, + + pub(crate) output: Option, +} + +#[utoipa::path( + tag = "Nym Nodes", + get, + path = "/historical-performance/{node_id}", + context_path = "/v1/nym-nodes", + responses( + (status = 200, content( + (NodeDatePerformanceResponse = "application/json"), + (NodeDatePerformanceResponse = "application/yaml"), + (NodeDatePerformanceResponse = "application/bincode") + )) + ), + params(DateQuery, NodeIdParam) +)] +async fn get_historical_performance( + Path(NodeIdParam { node_id }): Path, + Query(DateQuery { date, output }): Query, + State(state): State, +) -> AxumResult> { + let output = output.unwrap_or_default(); + + let uptime = state + .storage() + .get_historical_node_uptime_on(node_id, date) + .await?; + + Ok(output.to_response(NodeDatePerformanceResponse { + node_id, + date, + performance: uptime.and_then(|u| { + Performance::from_percentage_value(u.uptime as u64) + .map(|p| p.naive_to_f64()) + .ok() + }), + })) +} + +#[utoipa::path( + tag = "Nym Nodes", + get, + path = "/performance-history/{node_id}", + context_path = "/v1/nym-nodes", + responses( + (status = 200, content( + (PerformanceHistoryResponse = "application/json"), + (PerformanceHistoryResponse = "application/yaml"), + (PerformanceHistoryResponse = "application/bincode") + )) + ), + params(PaginationRequest, NodeIdParam) +)] +async fn get_node_performance_history( + Path(NodeIdParam { node_id }): Path, + State(state): State, + Query(pagination): Query, +) -> AxumResult> { + // TODO: implement it + let _ = pagination; + let output = pagination.output.unwrap_or_default(); + + let history = state + .storage() + .get_node_uptime_history(node_id) + .await? + .into_iter() + .filter_map(|u| u.try_into().ok()) + .collect::>(); + let total = history.len(); + + Ok(output.to_response(PerformanceHistoryResponse { + node_id, + history: PaginatedResponse { + pagination: Pagination { + total, + page: 0, + size: total, + }, + data: history, + }, + })) +} + +#[utoipa::path( + tag = "Nym Nodes", + get, + path = "/uptime-history/{node_id}", + context_path = "/v1/nym-nodes", + responses( + (status = 200, content( + (PerformanceHistoryResponse = "application/json"), + (PerformanceHistoryResponse = "application/yaml"), + (PerformanceHistoryResponse = "application/bincode") + )) + ), + params(PaginationRequest, NodeIdParam) +)] +async fn get_node_uptime_history( + Path(NodeIdParam { node_id }): Path, + State(state): State, + Query(pagination): Query, +) -> AxumResult> { + // TODO: implement it + let _ = pagination; + let output = pagination.output.unwrap_or_default(); + + let history = state + .storage() + .get_node_uptime_history(node_id) + .await? + .into_iter() + .filter_map(|u| u.try_into().ok()) + .collect::>(); + let total = history.len(); + + Ok(output.to_response(UptimeHistoryResponse { + node_id, + history: PaginatedResponse { + pagination: Pagination { + total, + page: 0, + size: total, + }, + data: history, + }, + })) +} diff --git a/nym-api/src/nym_nodes/handlers/v2.rs b/nym-api/src/nym_nodes/handlers/v2.rs new file mode 100644 index 0000000000..ee49e575fa --- /dev/null +++ b/nym-api/src/nym_nodes/handlers/v2.rs @@ -0,0 +1,54 @@ +// Copyright 2026 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use crate::node_status_api::models::AxumResult; +use crate::support::http::helpers::PaginationRequest; +use crate::support::http::state::AppState; +use axum::extract::{Query, State}; +use axum::routing::get; +use axum::Router; +use nym_api_requests::models::NymNodeDescriptionV2; +use nym_api_requests::pagination::{PaginatedResponse, Pagination}; +use nym_http_api_common::FormattedResponse; +use tower_http::compression::CompressionLayer; + +pub(crate) fn routes() -> Router { + Router::new() + .route("/described", get(get_described_nodes)) + .layer(CompressionLayer::new()) +} + +#[utoipa::path( + tag = "Nym Nodes", + get, + path = "/described", + context_path = "/v2/nym-nodes", + responses( + (status = 200, content( + (PaginatedResponse = "application/json"), + (PaginatedResponse = "application/yaml"), + (PaginatedResponse = "application/bincode") + )) + ), + params(PaginationRequest) +)] +async fn get_described_nodes( + State(state): State, + Query(pagination): Query, +) -> AxumResult>> { + // TODO: implement it + let _ = pagination; + let output = pagination.output.unwrap_or_default(); + + let cache = state.described_nodes_cache.get().await?; + let descriptions = cache.all_nodes().cloned().collect::>(); + + Ok(output.to_response(PaginatedResponse { + pagination: Pagination { + total: descriptions.len(), + page: 0, + size: descriptions.len(), + }, + data: descriptions, + })) +} diff --git a/nym-api/src/support/http/router.rs b/nym-api/src/support/http/router.rs index 56a8950968..88ca68bc8f 100644 --- a/nym-api/src/support/http/router.rs +++ b/nym-api/src/support/http/router.rs @@ -6,12 +6,11 @@ use crate::ecash::api_routes::handlers::ecash_routes; use crate::mixnet_contract_cache::handlers::{epoch_routes, legacy_nodes_routes}; use crate::network::handlers::nym_network_routes; use crate::node_status_api::handlers::status_routes; -use crate::nym_nodes::handlers::nym_node_routes; -use crate::status; use crate::support::http::openapi::ApiDoc; use crate::support::http::state::AppState; use crate::unstable_routes::v1::unstable_routes_v1; use crate::unstable_routes::v2::unstable_routes_v2; +use crate::{nym_nodes, status}; use anyhow::anyhow; use axum::response::Redirect; use axum::routing::get; @@ -61,12 +60,17 @@ impl RouterBuilder { .nest("/status", status_routes(network_monitor)) .nest("/network", nym_network_routes()) .nest("/api-status", status::handlers::api_status_routes()) - .nest("/nym-nodes", nym_node_routes()) + .nest("/nym-nodes", nym_nodes::handlers::v1::routes()) .nest("/ecash", ecash_routes()) .nest("/unstable", unstable_routes_v1()) .nest("/legacy", legacy_nodes_routes()), // CORS layer needs to be "outside" of routes ) - .nest("/v2", Router::new().nest("/unstable", unstable_routes_v2())); + .nest( + "/v2", + Router::new() + .nest("/unstable", unstable_routes_v2()) + .nest("/nym-nodes", nym_nodes::handlers::v2::routes()), + ); Self { unfinished_router: default_routes, diff --git a/nym-api/src/unstable_routes/v2/nym_nodes/semi_skimmed/mod.rs b/nym-api/src/unstable_routes/v2/nym_nodes/semi_skimmed/mod.rs index d86003c091..555718f50d 100644 --- a/nym-api/src/unstable_routes/v2/nym_nodes/semi_skimmed/mod.rs +++ b/nym-api/src/unstable_routes/v2/nym_nodes/semi_skimmed/mod.rs @@ -7,7 +7,7 @@ use crate::unstable_routes::helpers::refreshed_at; use crate::unstable_routes::v2::nym_nodes::helpers::NodesParamsWithRole; use axum::extract::{Query, State}; use nym_api_requests::models::{ - NodeAnnotation, NymNodeDescription, OffsetDateTimeJsonSchemaWrapper, + NodeAnnotation, NymNodeDescriptionV2, OffsetDateTimeJsonSchemaWrapper, }; use nym_api_requests::nym_nodes::{NodeRole, PaginatedCachedNodesResponseV2, SemiSkimmedNode}; use nym_api_requests::pagination::PaginatedResponse; @@ -29,7 +29,7 @@ fn build_nym_nodes_response<'a, NI>( active_only: bool, ) -> Vec where - NI: Iterator + 'a, + NI: Iterator + 'a, { let mut nodes = Vec::new(); for nym_node in nym_nodes_subset { diff --git a/nym-api/src/unstable_routes/v2/nym_nodes/skimmed/helpers.rs b/nym-api/src/unstable_routes/v2/nym_nodes/skimmed/helpers.rs index 56f9619803..3456c82444 100644 --- a/nym-api/src/unstable_routes/v2/nym_nodes/skimmed/helpers.rs +++ b/nym-api/src/unstable_routes/v2/nym_nodes/skimmed/helpers.rs @@ -7,7 +7,7 @@ use crate::unstable_routes::v2::nym_nodes::helpers::NodesParams; use crate::unstable_routes::v2::nym_nodes::skimmed::PaginatedSkimmedNodes; use axum::extract::{Query, State}; use nym_api_requests::models::{ - NodeAnnotation, NymNodeDescription, OffsetDateTimeJsonSchemaWrapper, + NodeAnnotation, NymNodeDescriptionV2, OffsetDateTimeJsonSchemaWrapper, }; use nym_api_requests::nym_nodes::{NodeRole, PaginatedCachedNodesResponseV2, SkimmedNode}; use nym_http_api_common::Output; @@ -25,7 +25,7 @@ fn build_nym_nodes_response<'a, NI>( active_only: bool, ) -> Vec where - NI: Iterator + 'a, + NI: Iterator + 'a, { let mut nodes = Vec::new(); for nym_node in nym_nodes_subset { @@ -91,7 +91,7 @@ pub(crate) async fn build_skimmed_nodes_response<'a, NI>( ) -> PaginatedSkimmedNodes where // iterator returning relevant subset of nym-nodes (like mixing nym-nodes, entries, etc.) - NI: Iterator + 'a, + NI: Iterator + 'a, { // TODO: implement it let _ = query_params.per_page; diff --git a/nym-gateway-probe/src/lib.rs b/nym-gateway-probe/src/lib.rs index 604bfb381c..3e5ab6377b 100644 --- a/nym-gateway-probe/src/lib.rs +++ b/nym-gateway-probe/src/lib.rs @@ -8,7 +8,7 @@ use std::{ }; use crate::types::Entry; -use anyhow::bail; +use anyhow::{Context, bail}; use base64::{Engine as _, engine::general_purpose}; use bytes::BytesMut; use clap::Args; @@ -62,6 +62,7 @@ mod types; use crate::bandwidth_helpers::{acquire_bandwidth, import_bandwidth}; use crate::nodes::{DirectoryNode, NymApiDirectory}; pub use mode::TestMode; +use nym_lp::peer::LpRemotePeer; use nym_node_status_client::models::AttachedTicketMaterials; pub use types::{IpPingReplies, ProbeOutcome, ProbeResult}; @@ -1108,10 +1109,16 @@ where let mut rng = rand::thread_rng(); 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, - gateway_identity, + peer, gateway_lp_address, gateway_ip, ); @@ -1264,17 +1271,31 @@ where 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); + // STEP 1: Establish outer LP session with entry gateway // LpRegistrationClient uses packet-per-connection model - connect() is gone, // connection is established automatically during handshake. info!("Establishing outer LP session with entry gateway..."); let mut entry_client = LpRegistrationClient::::new_with_default_config( entry_lp_keypair, - entry_gateway.identity, + entry_peer, entry_lp_address, entry_ip, ); @@ -1288,13 +1309,8 @@ 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_gateway.identity.to_bytes(), - exit_lp_address.to_string(), - exit_lp_keypair, - ed25519::PublicKey::from_bytes(&exit_gateway.identity.to_bytes()) - .map_err(|e| anyhow::anyhow!("Invalid exit gateway identity: {}", e))?, - ); + let mut nested_session = + NestedLpSession::new(exit_lp_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 9bb68046cb..bc44e8c035 100644 --- a/nym-gateway-probe/src/nodes.rs +++ b/nym-gateway-probe/src/nodes.rs @@ -4,9 +4,9 @@ use crate::TestedNodeDetails; use anyhow::{Context, anyhow, bail}; use nym_api_requests::models::{ - AuthenticatorDetails, DeclaredRoles, DescribedNodeType, HostInformation, IpPacketRouterDetails, - LewesProtocolDetails, NetworkRequesterDetails, NymNodeData, OffsetDateTimeJsonSchemaWrapper, - WebSockets, WireguardDetails, + AuthenticatorDetailsV2, DeclaredRolesV2, DescribedNodeTypeV2, HostInformationV2, + IpPacketRouterDetailsV2, LewesProtocolDetailsV1, NetworkRequesterDetailsV2, NymNodeDataV2, + OffsetDateTimeJsonSchemaWrapper, WebSocketsV2, WireguardDetailsV2, }; use nym_authenticator_requests::AuthenticatorVersion; use nym_bin_common::build_information::BinaryBuildInformationOwned; @@ -16,7 +16,7 @@ use nym_node_requests::api::client::NymNodeApiClientExt; use nym_node_requests::api::v1::node::models::AuxiliaryDetails as NodeAuxiliaryDetails; use nym_sdk::mixnet::NodeIdentity; use nym_validator_client::client::NymApiClientExt; -use nym_validator_client::models::NymNodeDescription; +use nym_validator_client::models::NymNodeDescriptionV2; use rand::prelude::IteratorRandom; use std::collections::HashMap; use std::time::Duration; @@ -87,7 +87,7 @@ use url::Url; #[derive(Clone)] pub struct DirectoryNode { - described: NymNodeDescription, + described: NymNodeDescriptionV2, } impl DirectoryNode { @@ -153,21 +153,18 @@ impl DirectoryNode { /// # Returns /// A `DirectoryNode` containing all gateway metadata, or an error if the query fails pub async fn query_gateway_by_ip(address: String) -> anyhow::Result { - info!("Querying gateway directly at address: {}", address); + info!("Querying gateway directly at address: {address}"); // Parse the address to check if it contains a port let addresses_to_try = if address.contains(':') { // Address already has port specified, use it directly - vec![ - format!("http://{}", address), - format!("https://{}", address), - ] + vec![format!("http://{address}"), format!("https://{address}")] } else { // No port specified, try multiple ports in order of likelihood vec![ - format!("http://{}:{}", address, DEFAULT_NYM_NODE_HTTP_PORT), // Standard port 8080 - format!("https://{}", address), // HTTPS proxy (443) - format!("http://{}", address), // HTTP proxy (80) + format!("http://{address}:{DEFAULT_NYM_NODE_HTTP_PORT}"), // Standard port 8080 + format!("https://{address}"), // HTTPS proxy (443) + format!("http://{address}"), // HTTP proxy (80) ] }; @@ -175,7 +172,7 @@ pub async fn query_gateway_by_ip(address: String) -> anyhow::Result anyhow::Result = None; // Not needed for LP testing - let ip_packet_router: Option = - ipr_result.map(|ipr| IpPacketRouterDetails { + let network_requester: Option = None; // Not needed for LP testing + let ip_packet_router: Option = + ipr_result.map(|ipr| IpPacketRouterDetailsV2 { address: ipr.address, }); - let authenticator: Option = - authenticator_result.map(|auth| AuthenticatorDetails { + let authenticator: Option = + authenticator_result.map(|auth| AuthenticatorDetailsV2 { address: auth.address, }); #[allow(deprecated)] - let wireguard: Option = - wireguard_result.map(|wg| WireguardDetails { + let wireguard: Option = + wireguard_result.map(|wg| WireguardDetailsV2 { port: wg.tunnel_port, // Use tunnel_port for deprecated port field tunnel_port: wg.tunnel_port, metadata_port: wg.metadata_port, public_key: wg.public_key, }); - let lp: Option = lp_result.ok().map(Into::into); + let lp: Option = lp_result.ok().map(Into::into); // Construct NymNodeData - let node_data = NymNodeData { + let node_data = NymNodeDataV2 { last_polled: OffsetDateTimeJsonSchemaWrapper(OffsetDateTime::now_utc()), - host_information: HostInformation { + host_information: HostInformationV2 { ip_address: host_info.data.ip_address, hostname: host_info.data.hostname, keys: host_info.data.keys.into(), }, - declared_role: DeclaredRoles { + declared_role: DeclaredRolesV2 { mixnode: roles.mixnode_enabled, entry: roles.gateway_enabled, exit_nr: roles.network_requester_enabled, exit_ipr: roles.ip_packet_router_enabled, }, - auxiliary_details: aux_details, + auxiliary_details: aux_details.into(), build_information: BinaryBuildInformationOwned { binary_name: build_info.binary_name, build_timestamp: build_info.build_timestamp, @@ -287,16 +284,16 @@ pub async fn query_gateway_by_ip(address: String) -> anyhow::Result, - self_described: Option<&NymNodeDescription>, + self_described: Option<&NymNodeDescriptionV1>, ) -> anyhow::Result { let now = OffsetDateTime::now_utc().unix_timestamp(); @@ -553,33 +555,33 @@ pub struct InsertNodeScraperRecords { #[derive(Clone, Serialize, Deserialize, Debug)] pub struct NymNodeDescriptionDeHelper { pub node_id: NodeId, - pub contract_node_type: DescribedNodeType, + pub contract_node_type: DescribedNodeTypeV2, pub description: NymNodeDataDeHelper, } #[allow(deprecated)] -impl From for NymNodeDescription { +impl From for NymNodeDescriptionV2 { fn from(helper: NymNodeDescriptionDeHelper) -> Self { let current_x25519_sphinx_key = helper .description .host_information .keys .current_x25519_sphinx_key - .unwrap_or(SphinxKey { + .unwrap_or(SphinxKeyV1 { // indicate the legacy case rotation_id: u32::MAX, public_key: helper.description.host_information.keys.x25519, }); - NymNodeDescription { + NymNodeDescriptionV2 { node_id: helper.node_id, contract_node_type: helper.contract_node_type, - description: NymNodeData { + description: NymNodeDataV2 { last_polled: helper.description.last_polled, - host_information: HostInformation { + host_information: HostInformationV2 { ip_address: helper.description.host_information.ip_address, hostname: helper.description.host_information.hostname, - keys: HostKeys { + keys: HostKeysV2 { ed25519: helper.description.host_information.keys.ed25519, x25519: helper.description.host_information.keys.x25519, current_x25519_sphinx_key, @@ -609,6 +611,8 @@ impl From for NymNodeDescription { } } +// in this instance, since data is stored as json +// adding a new field with #[serde(default)] is fine #[derive(Clone, Serialize, Deserialize, Debug)] pub struct NymNodeDataDeHelper { #[serde(default)] @@ -617,31 +621,31 @@ pub struct NymNodeDataDeHelper { pub host_information: HostInformationDeHelper, #[serde(default)] - pub declared_role: DeclaredRoles, + pub declared_role: DeclaredRolesV2, #[serde(default)] - pub auxiliary_details: AuxiliaryDetails, + pub auxiliary_details: AuxiliaryDetailsV2, // TODO: do we really care about ALL build info or just the version? pub build_information: BinaryBuildInformationOwned, #[serde(default)] - pub network_requester: Option, + pub network_requester: Option, #[serde(default)] - pub ip_packet_router: Option, + pub ip_packet_router: Option, #[serde(default)] - pub authenticator: Option, + pub authenticator: Option, #[serde(default)] - pub wireguard: Option, + pub wireguard: Option, #[serde(default)] - pub lewes_protocol: Option, + pub lewes_protocol: Option, // for now we only care about their ws/wss situation, nothing more - pub mixnet_websockets: WebSockets, + pub mixnet_websockets: WebSocketsV1, } #[derive(Clone, Serialize, Deserialize, Debug)] @@ -661,11 +665,11 @@ pub struct HostKeysDeHelper { pub x25519: x25519::PublicKey, // legacy data will NOT have this information - pub current_x25519_sphinx_key: Option, + pub current_x25519_sphinx_key: Option, #[serde(default)] - pub pre_announced_x25519_sphinx_key: Option, + pub pre_announced_x25519_sphinx_key: Option, #[serde(default)] - pub x25519_versioned_noise: Option, + pub x25519_versioned_noise: Option, } diff --git a/nym-node-status-api/nym-node-status-api/src/db/queries/nym_nodes.rs b/nym-node-status-api/nym-node-status-api/src/db/queries/nym_nodes.rs index ae53e041c2..969fd087f6 100644 --- a/nym-node-status-api/nym-node-status-api/src/db/queries/nym_nodes.rs +++ b/nym-node-status-api/nym-node-status-api/src/db/queries/nym_nodes.rs @@ -1,14 +1,5 @@ -use crate::db::models::NymNodeDescriptionDeHelper; -use futures_util::TryStreamExt; -use nym_node_requests::api::v1::node::models::NodeDescription; -use nym_validator_client::{ - client::{NodeId, NymNodeDetails}, - models::NymNodeDescription, -}; -use std::collections::HashMap; -use tracing::{instrument, warn}; - use crate::db::DbConnection; +use crate::db::models::NymNodeDescriptionDeHelper; use crate::http::models::DailyStats; use crate::{ db::{ @@ -17,6 +8,12 @@ use crate::{ }, node_scraper::helpers::NodeDescriptionResponse, }; +use futures_util::TryStreamExt; +use nym_node_requests::api::v1::node::models::NodeDescription; +use nym_validator_client::client::{NodeId, NymNodeDetails}; +use nym_validator_client::models::NymNodeDescriptionV2; +use std::collections::HashMap; +use tracing::{instrument, warn}; pub(crate) async fn get_all_nym_nodes(pool: &DbPool) -> anyhow::Result> { let mut conn = pool.acquire().await?; @@ -195,7 +192,7 @@ pub(crate) async fn get_described_node_bond_info( pub(crate) async fn get_node_self_description( pool: &DbPool, -) -> anyhow::Result> { +) -> anyhow::Result> { let mut conn = pool.acquire().await?; sqlx::query!( diff --git a/nym-node-status-api/nym-node-status-api/src/db/tests.rs b/nym-node-status-api/nym-node-status-api/src/db/tests.rs index a80bdc1bdd..e1f444301c 100644 --- a/nym-node-status-api/nym-node-status-api/src/db/tests.rs +++ b/nym-node-status-api/nym-node-status-api/src/db/tests.rs @@ -128,7 +128,7 @@ mod db_tests { node_role: serde_json::json!(nym_validator_client::nym_nodes::NodeRole::Mixnode { layer: 1 }), - supported_roles: serde_json::json!(nym_validator_client::models::DeclaredRoles { + supported_roles: serde_json::json!(nym_validator_client::models::DeclaredRolesV1 { entry: false, mixnode: true, exit_nr: false, @@ -181,7 +181,7 @@ fn test_nym_node_insert_record_new() { mix_port: 1789, x25519_sphinx_pubkey: x25519_pk, role: nym_validator_client::nym_nodes::NodeRole::Mixnode { layer: 1 }, - supported_roles: nym_validator_client::models::DeclaredRoles { + supported_roles: nym_validator_client::models::DeclaredRolesV1 { entry: false, mixnode: true, exit_nr: false, @@ -208,7 +208,7 @@ fn test_nym_node_insert_record_new() { ); assert_eq!( record.supported_roles, - serde_json::json!(nym_validator_client::models::DeclaredRoles { + serde_json::json!(nym_validator_client::models::DeclaredRolesV1 { entry: false, mixnode: true, exit_nr: false, @@ -233,7 +233,7 @@ fn test_nym_node_insert_record_with_entry() { mix_port: 1789, x25519_sphinx_pubkey: x25519_pk, role: nym_validator_client::nym_nodes::NodeRole::EntryGateway, - supported_roles: nym_validator_client::models::DeclaredRoles { + supported_roles: nym_validator_client::models::DeclaredRolesV1 { entry: true, mixnode: false, exit_nr: true, @@ -425,7 +425,7 @@ fn test_nym_node_dto_with_invalid_keys() { node_role: serde_json::json!(nym_validator_client::nym_nodes::NodeRole::Mixnode { layer: 1 }), - supported_roles: serde_json::json!(nym_validator_client::models::DeclaredRoles { + supported_roles: serde_json::json!(nym_validator_client::models::DeclaredRolesV1 { entry: false, mixnode: true, exit_nr: false, @@ -463,7 +463,7 @@ fn test_nym_node_dto_with_invalid_performance() { node_role: serde_json::json!(nym_validator_client::nym_nodes::NodeRole::Mixnode { layer: 1 }), - supported_roles: serde_json::json!(nym_validator_client::models::DeclaredRoles { + supported_roles: serde_json::json!(nym_validator_client::models::DeclaredRolesV1 { entry: false, mixnode: true, exit_nr: false, diff --git a/nym-node-status-api/nym-node-status-api/src/http/models.rs b/nym-node-status-api/nym-node-status-api/src/http/models.rs index 2f0fe09183..cce5f0cf8b 100644 --- a/nym-node-status-api/nym-node-status-api/src/http/models.rs +++ b/nym-node-status-api/nym-node-status-api/src/http/models.rs @@ -6,7 +6,7 @@ use nym_mixnet_contract_common::CoinSchema; use nym_node_requests::api::v1::node::models::NodeDescription; use nym_validator_client::{ client::NodeId, - models::{AuthenticatorDetails, BinaryBuildInformationOwned, IpPacketRouterDetails}, + models::{AuthenticatorDetailsV1, BinaryBuildInformationOwned, IpPacketRouterDetailsV1}, nym_api::SkimmedNode, nym_nodes::{BasicEntryInformation, NodeRole}, }; @@ -100,8 +100,8 @@ pub struct DVpnGateway { pub identity_key: String, pub name: String, pub description: Option, - pub ip_packet_router: Option, - pub authenticator: Option, + pub ip_packet_router: Option, + pub authenticator: Option, pub location: Location, pub last_probe: Option, #[schema(value_type = Vec)] @@ -917,10 +917,10 @@ pub(crate) struct ExtendedNymNode { pub(crate) original_pledge: u128, pub(crate) bonding_address: Option, pub(crate) bonded: bool, - pub(crate) node_type: nym_validator_client::models::DescribedNodeType, + pub(crate) node_type: nym_validator_client::models::DescribedNodeTypeV1, pub(crate) ip_address: String, pub(crate) accepted_tnc: bool, - pub(crate) self_description: nym_validator_client::models::NymNodeData, + pub(crate) self_description: nym_validator_client::models::NymNodeDataV2, pub(crate) rewarding_details: Option, pub(crate) description: NodeDescription, pub(crate) geoip: Option, diff --git a/nym-node-status-api/nym-node-status-api/src/metrics_scraper/mod.rs b/nym-node-status-api/nym-node-status-api/src/metrics_scraper/mod.rs index c89fcc40c3..ab843af8e5 100644 --- a/nym-node-status-api/nym-node-status-api/src/metrics_scraper/mod.rs +++ b/nym-node-status-api/nym-node-status-api/src/metrics_scraper/mod.rs @@ -4,7 +4,7 @@ use nym_network_defaults::{DEFAULT_NYM_NODE_HTTP_PORT, NymNetworkDetails}; use nym_node_requests::api::{client::NymNodeApiClientExt, v1::metrics::models::SessionStats}; use nym_validator_client::{ client::{NodeId, NymNodeDetails}, - models::{DescribedNodeType, NymNodeDescription}, + models::{DescribedNodeTypeV1, NymNodeDescriptionV1}, }; use time::OffsetDateTime; @@ -64,6 +64,10 @@ async fn run( //SW TBC what nodes exactly need to be scraped, the skimmed node endpoint seems to return more nodes let bonded_nodes = nym_api.get_all_bonded_nym_nodes().await?; + + // for now allow usage of old endpoint for we don't need LP related data + // and the new endpoint might not be immediately deployed + #[allow(deprecated)] let all_nodes = nym_api.get_all_described_nodes().await?; //legacy node that did not upgrade the contract bond yet tracing::debug!("Fetched {} total nodes", all_nodes.len()); @@ -74,7 +78,7 @@ async fn run( all_nodes .into_iter() - .filter(|n| n.contract_node_type != DescribedNodeType::LegacyMixnode) + .filter(|n| n.contract_node_type != DescribedNodeTypeV1::LegacyMixnode) .for_each(|n| { nodes_to_scrape.entry(n.node_id).or_insert_with(|| n.into()); }); @@ -211,8 +215,8 @@ impl From for MetricsScrapingData { } } -impl From for MetricsScrapingData { - fn from(value: NymNodeDescription) -> Self { +impl From for MetricsScrapingData { + fn from(value: NymNodeDescriptionV1) -> Self { MetricsScrapingData::new( value.description.host_information.ip_address[0].to_string(), value.node_id, diff --git a/nym-node-status-api/nym-node-status-api/src/monitor/mod.rs b/nym-node-status-api/nym-node-status-api/src/monitor/mod.rs index a1fcab2b8f..75a1926a66 100644 --- a/nym-node-status-api/nym-node-status-api/src/monitor/mod.rs +++ b/nym-node-status-api/nym-node-status-api/src/monitor/mod.rs @@ -16,7 +16,7 @@ use nym_validator_client::{ }; use nym_validator_client::{ client::{NodeId, NymApiClientExt, NymNodeDetails}, - models::NymNodeDescription, + models::NymNodeDescriptionV1, }; use std::{collections::HashMap, sync::Arc}; use tokio::{sync::RwLock, time::Duration}; @@ -282,7 +282,7 @@ impl Monitor { } #[instrument(level = "info", skip_all)] - async fn location_cached(&mut self, node: &NymNodeDescription) -> Location { + async fn location_cached(&mut self, node: &NymNodeDescriptionV1) -> Location { let node_id = node.node_id; match self.geocache.get(&node_id).await { @@ -310,7 +310,7 @@ impl Monitor { &self, skimmed_nodes: Vec, bonded_node_info: &HashMap, - described_nodes: &HashMap, + described_nodes: &HashMap, ) -> Vec { skimmed_nodes .into_iter() @@ -335,7 +335,7 @@ impl Monitor { async fn prepare_gateway_data( &mut self, - described_gateways: &[&NymNodeDescription], + described_gateways: &[&NymNodeDescriptionV1], skimmed_gateways: &[SkimmedNode], bonded_nodes: &HashMap, ) -> anyhow::Result> { diff --git a/nym-node/Cargo.toml b/nym-node/Cargo.toml index c9de83d0bb..426f6f3137 100644 --- a/nym-node/Cargo.toml +++ b/nym-node/Cargo.toml @@ -60,6 +60,7 @@ nym-client-core-config-types = { workspace = true, features = [ nym-config = { workspace = true } nym-credential-verification = { workspace = true } nym-crypto = { workspace = true, features = ["asymmetric", "rand"] } +nym-kkt = { path = "../common/nym-kkt" } nym-nonexhaustive-delayqueue = { workspace = true } nym-mixnet-client = { workspace = true } nym-noise = { workspace = true } @@ -79,7 +80,7 @@ nym-validator-client = { workspace = true } nym-wireguard = { workspace = true } nym-wireguard-types = { workspace = true } nym-verloc = { workspace = true } -nym-metrics = { workspace = true} +nym-metrics = { workspace = true } nym-gateway-stats-storage = { workspace = true } nym-topology = { workspace = true } nym-http-api-client = { workspace = true } diff --git a/nym-node/nym-node-requests/src/api/mod.rs b/nym-node/nym-node-requests/src/api/mod.rs index 9c3e0a67d2..ba32570c2e 100644 --- a/nym-node/nym-node-requests/src/api/mod.rs +++ b/nym-node/nym-node-requests/src/api/mod.rs @@ -132,7 +132,7 @@ mod tests { use super::*; use crate::api::v1::node::models::{HostKeys, SphinxKey}; use nym_crypto::asymmetric::{ed25519, x25519}; - use nym_noise_keys::{NoiseVersion, VersionedNoiseKey}; + use nym_noise_keys::{NoiseVersion, VersionedNoiseKeyV1}; use rand_chacha::rand_core::SeedableRng; #[test] @@ -141,7 +141,7 @@ mod tests { let ed22519 = ed25519::KeyPair::new(&mut rng); let x25519_sphinx = x25519::KeyPair::new(&mut rng); let x25519_sphinx2 = x25519::KeyPair::new(&mut rng); - let x25519_versioned_noise = VersionedNoiseKey { + let x25519_versioned_noise = VersionedNoiseKeyV1 { supported_version: NoiseVersion::V1, x25519_pubkey: *x25519::KeyPair::new(&mut rng).public_key(), }; @@ -305,7 +305,7 @@ mod tests { public_key: *x25519_sphinx.public_key(), }, pre_announced_x25519_sphinx_key: None, - x25519_versioned_noise: Some(VersionedNoiseKey { + x25519_versioned_noise: Some(VersionedNoiseKeyV1 { supported_version: NoiseVersion::V1, x25519_pubkey: legacy_info_noise.keys.x25519_noise.unwrap(), }), @@ -384,7 +384,7 @@ mod tests { public_key: *x25519_sphinx.public_key(), }, pre_announced_x25519_sphinx_key: None, - x25519_versioned_noise: Some(VersionedNoiseKey { + x25519_versioned_noise: Some(VersionedNoiseKeyV1 { supported_version: NoiseVersion::V1, x25519_pubkey: legacy_info_noise.keys.x25519_noise.parse().unwrap(), }), 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 687b6d621c..4e668e5d75 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 @@ -3,8 +3,10 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use strum_macros::{Display, EnumIter, EnumString}; -#[derive(Serialize, Deserialize, Debug, Clone, Copy, JsonSchema)] +#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)] #[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] pub struct LewesProtocol { /// Helper field that specifies whether the LP listener(s) is enabled on this node. @@ -16,4 +18,101 @@ pub struct LewesProtocol { /// LP UDP data address (default: 51264) for Sphinx packets wrapped in LP pub data_port: u16, + + /// Digests of the KEM keys available to this node alongside hashing algorithms used + /// for their computation. + pub kem_keys: HashMap>>, +} + +impl LewesProtocol { + pub fn new(enabled: bool, control_port: u16, data_port: u16) -> Self { + LewesProtocol { + enabled, + control_port, + data_port, + kem_keys: Default::default(), + } + } + + pub fn with_kem_key_hashes( + mut self, + kem: LPKEM, + hashes: HashMap>, + ) -> Self { + self.kem_keys.insert(kem, hashes); + self + } +} + +// explicitly redefine available HashFunctions and KEMs so that we would not +// accidentally remove some type and thus break backwards compatibility at deserialisation level +// (the only thing that should break at that point would be conversion into proper nym-kkt types +// which would return a concrete error variant) + +#[derive( + Serialize, + Deserialize, + Debug, + Clone, + Copy, + JsonSchema, + Hash, + PartialEq, + Eq, + PartialOrd, + Display, + EnumString, +)] +#[strum(serialize_all = "lowercase")] +#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] +pub enum LPKEM { + MlKem768, + XWing, + X25519, + McEliece, +} + +#[derive( + Serialize, + Deserialize, + Debug, + Clone, + Copy, + JsonSchema, + Hash, + PartialEq, + Eq, + PartialOrd, + Display, + EnumString, + EnumIter, +)] +#[strum(serialize_all = "lowercase")] +#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] +pub enum LPHashFunction { + Blake3, + Shake128, + Shake256, + Sha256, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn kem_display() { + assert_eq!(LPKEM::MlKem768.to_string(), "mlkem768"); + assert_eq!(LPKEM::XWing.to_string(), "xwing"); + assert_eq!(LPKEM::X25519.to_string(), "x25519"); + assert_eq!(LPKEM::McEliece.to_string(), "mceliece"); + } + + #[test] + fn hash_function_display() { + assert_eq!(LPHashFunction::Blake3.to_string(), "blake3"); + assert_eq!(LPHashFunction::Shake128.to_string(), "shake128"); + assert_eq!(LPHashFunction::Shake256.to_string(), "shake256"); + assert_eq!(LPHashFunction::Sha256.to_string(), "sha256"); + } } diff --git a/nym-node/nym-node-requests/src/api/v1/node/models.rs b/nym-node/nym-node-requests/src/api/v1/node/models.rs index 2d0a9fe16f..4a6a750688 100644 --- a/nym-node/nym-node-requests/src/api/v1/node/models.rs +++ b/nym-node/nym-node-requests/src/api/v1/node/models.rs @@ -6,7 +6,7 @@ use nym_crypto::asymmetric::ed25519::{self, serde_helpers::bs58_ed25519_pubkey}; use nym_crypto::asymmetric::x25519::{ self, serde_helpers::bs58_x25519_pubkey, serde_helpers::option_bs58_x25519_pubkey, }; -use nym_noise_keys::VersionedNoiseKey; +use nym_noise_keys::VersionedNoiseKeyV1; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use std::net::IpAddr; @@ -146,7 +146,7 @@ pub struct HostKeys { /// Base58-encoded x25519 public key of this node used for the noise protocol. #[serde(default)] - pub x25519_versioned_noise: Option, + pub x25519_versioned_noise: Option, } // we need the intermediate struct to help us with the new explicit sphinx key fields @@ -193,7 +193,7 @@ struct HostKeysDeHelper { /// Base58-encoded x25519 public key of this node used for the noise protocol. #[serde(default)] - pub x25519_versioned_noise: Option, + pub x25519_versioned_noise: Option, } #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] diff --git a/nym-node/src/node/http/router/api/v1/lewes_protocol/mod.rs b/nym-node/src/node/http/router/api/v1/lewes_protocol/mod.rs index 67496def85..36d8dc785d 100644 --- a/nym-node/src/node/http/router/api/v1/lewes_protocol/mod.rs +++ b/nym-node/src/node/http/router/api/v1/lewes_protocol/mod.rs @@ -7,7 +7,7 @@ use nym_node_requests::api::v1::lewes_protocol::models; pub mod root; -#[derive(Debug, Default, Clone, Copy)] +#[derive(Debug, Default, Clone)] pub struct Config { pub details: Option, } diff --git a/nym-node/src/node/http/state/mod.rs b/nym-node/src/node/http/state/mod.rs index 8ed756aa60..f1c9f2d81b 100644 --- a/nym-node/src/node/http/state/mod.rs +++ b/nym-node/src/node/http/state/mod.rs @@ -7,7 +7,7 @@ use crate::node::key_rotation::active_keys::ActiveSphinxKeys; use nym_credential_verification::UpgradeModeState; use nym_crypto::asymmetric::ed25519; use nym_node_metrics::NymNodeMetrics; -use nym_noise_keys::VersionedNoiseKey; +use nym_noise_keys::VersionedNoiseKeyV1; use nym_verloc::measurements::SharedVerlocStats; use std::net::IpAddr; use std::sync::Arc; @@ -20,7 +20,7 @@ pub mod metrics; pub(crate) struct StaticNodeInformation { pub(crate) ed25519_identity_keys: Arc, - pub(crate) x25519_versioned_noise_key: Option, + pub(crate) x25519_versioned_noise_key: Option, pub(crate) ip_addresses: Vec, pub(crate) hostname: Option, } diff --git a/nym-node/src/node/mod.rs b/nym-node/src/node/mod.rs index 42d803f46d..a46ae3e7da 100644 --- a/nym-node/src/node/mod.rs +++ b/nym-node/src/node/mod.rs @@ -42,6 +42,8 @@ use nym_bin_common::bin_info; use nym_credential_verification::UpgradeModeState; use nym_crypto::asymmetric::{ed25519, x25519}; use nym_gateway::node::{ActiveClientsStore, GatewayTasksBuilder, UpgradeModeCheckRequestSender}; +use nym_kkt::ciphersuite::{DEFAULT_HASH_LEN, HashFunction}; +use nym_kkt::key_utils::hash_encapsulation_key; use nym_mixnet_client::client::ActiveConnections; use nym_mixnet_client::forwarder::MixForwardingSender; use nym_network_requester::{ @@ -50,9 +52,10 @@ use nym_network_requester::{ }; use nym_node_metrics::NymNodeMetrics; use nym_node_metrics::events::MetricEventsSender; +use nym_node_requests::api::v1::lewes_protocol::models::{LPHashFunction, LPKEM}; use nym_node_requests::api::v1::node::models::{AnnouncePorts, NodeDescription}; use nym_noise::config::{NoiseConfig, NoiseNetworkView}; -use nym_noise_keys::VersionedNoiseKey; +use nym_noise_keys::VersionedNoiseKeyV1; use nym_sphinx_acknowledgements::AckKey; use nym_sphinx_addressing::Recipient; use nym_task::{ShutdownManager, ShutdownToken, ShutdownTracker}; @@ -62,6 +65,7 @@ use nym_verloc::{self, measurements::VerlocMeasurer}; use nym_wireguard::{WireguardGatewayData, peer_controller::PeerControlRequest}; use rand::rngs::OsRng; use rand::{CryptoRng, RngCore}; +use std::collections::HashMap; use std::net::SocketAddr; use std::ops::Deref; use std::path::Path; @@ -84,6 +88,7 @@ mod shared_network; pub struct GatewayTasksData { mnemonic: Arc>, + psq_kem_key: Arc, client_storage: nym_gateway::node::GatewayStorage, stats_storage: nym_gateway::node::PersistentStatsStorage, } @@ -105,7 +110,11 @@ impl GatewayTasksData { Ok(()) } - async fn new(config: &GatewayTasksConfig) -> Result { + async fn new( + config: &GatewayTasksConfig, + // this argument is temporary while we still derive KEM x25519 out of identity ed25519 + ed25519_identity: &ed25519::KeyPair, + ) -> Result { let client_storage = nym_gateway::node::GatewayStorage::init( &config.storage_paths.clients_storage, config.debug.message_retrieval_limit, @@ -120,6 +129,7 @@ impl GatewayTasksData { Ok(GatewayTasksData { mnemonic: Arc::new(config.storage_paths.load_mnemonic_from_file()?), + psq_kem_key: Arc::new(ed25519_identity.to_x25519()), client_storage, stats_storage, }) @@ -454,10 +464,14 @@ impl NymNode { let current_rotation_id = get_current_rotation_id(&config.mixnet.nym_api_urls, &config.mixnet.nyxd_urls).await?; + let ed25519_identity_keys = load_ed25519_identity_keypair( + &config.storage_paths.keys.ed25519_identity_storage_paths(), + )?; + let entry_gateway = + GatewayTasksData::new(&config.gateway_tasks, &ed25519_identity_keys).await?; + Ok(NymNode { - ed25519_identity_keys: Arc::new(load_ed25519_identity_keypair( - &config.storage_paths.keys.ed25519_identity_storage_paths(), - )?), + ed25519_identity_keys: Arc::new(ed25519_identity_keys), sphinx_key_manager: Some(SphinxKeyManager::try_load_or_regenerate( current_rotation_id, &config.storage_paths.keys.primary_x25519_sphinx_key_file, @@ -469,7 +483,7 @@ impl NymNode { description: load_node_description(&config.storage_paths.description)?, metrics: NymNodeMetrics::new(), verloc_stats: Default::default(), - entry_gateway: GatewayTasksData::new(&config.gateway_tasks).await?, + entry_gateway, upgrade_mode_state: UpgradeModeState::new( config.gateway_tasks.upgrade_mode.attester_public_key, ), @@ -631,6 +645,7 @@ impl NymNode { let mut gateway_tasks_builder = GatewayTasksBuilder::new( config.gateway, self.ed25519_identity_keys.clone(), + self.entry_gateway.psq_kem_key.clone(), self.entry_gateway.client_storage.clone(), mix_packet_sender, metrics_sender, @@ -760,6 +775,32 @@ impl NymNode { Ok(()) } + fn compute_kem_key_hashes(&self) -> (LPKEM, HashMap>) { + let kem = LPKEM::X25519; + let mut hashes = HashMap::new(); + + let kem_key_bytes = self.entry_gateway.psq_kem_key.public_key().as_bytes(); + + hashes.insert( + LPHashFunction::Blake3, + hash_encapsulation_key(&HashFunction::Blake3, DEFAULT_HASH_LEN, kem_key_bytes), + ); + hashes.insert( + LPHashFunction::Shake128, + hash_encapsulation_key(&HashFunction::SHAKE128, DEFAULT_HASH_LEN, kem_key_bytes), + ); + hashes.insert( + LPHashFunction::Shake256, + hash_encapsulation_key(&HashFunction::SHAKE256, DEFAULT_HASH_LEN, kem_key_bytes), + ); + hashes.insert( + LPHashFunction::Sha256, + hash_encapsulation_key(&HashFunction::SHA256, DEFAULT_HASH_LEN, kem_key_bytes), + ); + + (kem, hashes) + } + pub(crate) async fn build_http_server(&self) -> Result { let auxiliary_details = api_requests::v1::node::models::AuxiliaryDetails { location: self.config.host.location, @@ -833,11 +874,13 @@ impl NymNode { policy: None, }; - let lp_details = api_requests::v1::lewes_protocol::models::LewesProtocol { - enabled: self.modes().entry, - control_port: self.config.gateway_tasks.lp.announced_control_port(), - data_port: self.config.gateway_tasks.lp.announced_data_port(), - }; + let (kem, hashes) = self.compute_kem_key_hashes(); + let lp_details = api_requests::v1::lewes_protocol::models::LewesProtocol::new( + self.modes().entry, + self.config.gateway_tasks.lp.announced_control_port(), + self.config.gateway_tasks.lp.announced_data_port(), + ) + .with_kem_key_hashes(kem, hashes); let mut config = HttpServerConfig::new() .with_landing_page_assets(self.config.http.landing_page_assets_path.as_ref()) @@ -878,7 +921,7 @@ impl NymNode { let x25519_versioned_noise_key = if self.config.mixnet.debug.unsafe_disable_noise { None } else { - Some(VersionedNoiseKey { + Some(VersionedNoiseKeyV1 { supported_version: nym_noise::LATEST_NOISE_VERSION, x25519_pubkey: *self.x25519_noise_keys.public_key(), }) diff --git a/nym-registration-client/src/error.rs b/nym-registration-client/src/error.rs index 449d6d4608..932eb1bf54 100644 --- a/nym-registration-client/src/error.rs +++ b/nym-registration-client/src/error.rs @@ -93,6 +93,9 @@ 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 5843e4ecd4..796d56de74 100644 --- a/nym-registration-client/src/lib.rs +++ b/nym-registration-client/src/lib.rs @@ -29,6 +29,7 @@ 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, }; @@ -154,35 +155,58 @@ impl RegistrationClient { } async fn register_lp(self) -> Result { - // Extract and validate LP addresses - let entry_lp_address = self.config.entry.node.lp_address.ok_or( + // Extract and validate LP data + let entry_lp_data = self.config.entry.node.lp_data.ok_or( RegistrationClientError::LpRegistrationNotPossible { node_id: self.config.entry.node.identity.to_base58_string(), }, )?; - let exit_lp_address = self.config.exit.node.lp_address.ok_or( + let exit_lp_data = self.config.exit.node.lp_data.ok_or( RegistrationClientError::LpRegistrationNotPossible { node_id: self.config.exit.node.identity.to_base58_string(), }, )?; - tracing::debug!("Entry gateway LP address: {entry_lp_address}"); - tracing::debug!("Exit gateway LP address: {exit_lp_address}"); + tracing::debug!("Entry gateway LP address: {}", entry_lp_data.address); + tracing::debug!("Exit gateway LP address: {}", exit_lp_data.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: Establish outer session with entry gateway + // 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 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_digest(entry_lp_data.expected_kem_key_hash); + + let exit_peer = LpRemotePeer::new(self.config.exit.node.identity, exit_x25519_public) + .with_kem_key_digest(exit_lp_data.expected_kem_key_hash); + + // STEP 2: 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(), - self.config.entry.node.identity, - entry_lp_address, + entry_peer, + entry_lp_data.address, self.config.entry.node.ip_address, ); @@ -190,22 +214,18 @@ impl RegistrationClient { entry_client.perform_handshake().await.map_err(|source| { RegistrationClientError::EntryGatewayRegisterLp { gateway_id: self.config.entry.node.identity.to_base58_string(), - lp_address: entry_lp_address, + lp_address: entry_lp_data.address, source: Box::new(source), } })?; tracing::info!("Outer session with entry gateway established"); - // STEP 2: Use nested session to register with exit gateway via forwarding + // STEP 3: 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( - self.config.exit.node.identity.to_bytes(), - exit_lp_address.to_string(), - exit_lp_keypair, - self.config.exit.node.identity, - ); + let mut nested_session = + NestedLpSession::new(exit_lp_data.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 @@ -220,13 +240,13 @@ impl RegistrationClient { .await .map_err(|source| RegistrationClientError::ExitGatewayRegisterLp { gateway_id: self.config.exit.node.identity.to_base58_string(), - lp_address: exit_lp_address, + lp_address: exit_lp_data.address, source: Box::new(source), })?; tracing::info!("Exit gateway registration completed via forwarding"); - // STEP 3: Register with entry gateway (packet-per-connection) + // STEP 4: Register with entry gateway (packet-per-connection) tracing::info!("Registering with entry gateway"); let entry_gateway_data = entry_client .register( @@ -238,7 +258,7 @@ impl RegistrationClient { .await .map_err(|source| RegistrationClientError::EntryGatewayRegisterLp { gateway_id: self.config.entry.node.identity.to_base58_string(), - lp_address: entry_lp_address, + lp_address: entry_lp_data.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 99afac0c88..7899cd223d 100644 --- a/nym-registration-client/src/lp_client/client.rs +++ b/nym-registration-client/src/lp_client/client.rs @@ -12,6 +12,7 @@ use nym_crypto::asymmetric::{ed25519, x25519}; use nym_lp::LpPacket; use nym_lp::codec::{OuterAeadKey, parse_lp_packet, serialize_lp_packet}; use nym_lp::message::ForwardPacketData; +use nym_lp::peer::{LpLocalPeer, LpRemotePeer}; use nym_lp::state_machine::{LpAction, LpInput, LpStateMachine}; use nym_lp_transport::traits::LpTransport; use nym_registration_common::{GatewayData, LpRegistrationRequest, LpRegistrationResponse}; @@ -36,11 +37,11 @@ use tokio::net::TcpStream; /// // Connection automatically closes after registration /// ``` pub struct LpRegistrationClient { - /// Client's Ed25519 identity keypair (used for PSQ authentication and X25519 derivation). - local_ed25519_keypair: Arc, + /// Encapsulates all the client keys needed for the Lewes Protocol. + lp_local_peer: LpLocalPeer, - /// Gateway's Ed25519 public key (from directory/discovery). - gateway_ed25519_public_key: ed25519::PublicKey, + /// Encapsulates all the gateway keys needed for the Lewes Protocol. + gateway_lp_peer: LpRemotePeer, /// Gateway LP listener address (host:port, e.g., "1.1.1.1:41264"). gateway_lp_address: SocketAddr, @@ -67,8 +68,8 @@ where /// Creates a new LP registration client. /// /// # Arguments - /// * `local_ed25519_keypair` - Client's Ed25519 identity keypair (for PSQ auth and X25519 derivation) - /// * `gateway_ed25519_public_key` - Gateway's Ed25519 public key (from directory/discovery) + /// * `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()`) @@ -77,14 +78,16 @@ where /// This creates the client. Call `perform_handshake()` to establish the LP session. pub fn new( local_ed25519_keypair: Arc, - gateway_ed25519_public_key: ed25519::PublicKey, + gateway_lp_peer: LpRemotePeer, gateway_lp_address: SocketAddr, client_ip: IpAddr, config: LpConfig, ) -> Self { + let local_x25519_keypair = local_ed25519_keypair.to_x25519(); + let lp_local_peer = LpLocalPeer::new(local_ed25519_keypair, Arc::new(local_x25519_keypair)); Self { - local_ed25519_keypair, - gateway_ed25519_public_key, + lp_local_peer, + gateway_lp_peer, gateway_lp_address, state_machine: None, client_ip, @@ -97,7 +100,7 @@ where /// /// # Arguments /// * `local_ed25519_keypair` - Client's Ed25519 identity keypair - /// * `gateway_ed25519_public_key` - Gateway's Ed25519 public key + /// * `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 /// @@ -106,13 +109,13 @@ where /// For custom config, use `new()` directly. pub fn new_with_default_config( local_ed25519_keypair: Arc, - gateway_ed25519_public_key: ed25519::PublicKey, + gateway_lp_peer: LpRemotePeer, gateway_lp_address: SocketAddr, client_ip: IpAddr, ) -> Self { Self::new( local_ed25519_keypair, - gateway_ed25519_public_key, + gateway_lp_peer, gateway_lp_address, client_ip, LpConfig::default(), @@ -318,29 +321,15 @@ where // Ensure we have a TCP connection self.ensure_connected().await?; - // Step 1: Derive X25519 keys from Ed25519 for Noise protocol (internal to ClientHello) - // The Ed25519 keys are used for PSQ authentication and also converted to X25519 - let client_x25519_public = self - .local_ed25519_keypair - .public_key() - .to_x25519() - .map_err(|e| { - LpClientError::Crypto(format!("Failed to derive X25519 public key: {e}")) - })?; - - let gateway_x25519_public = self.gateway_ed25519_public_key.to_x25519().map_err(|e| { - LpClientError::Crypto(format!("Failed to derive X25519 public key: {e}")) - })?; - let timestamp = SystemTime::now() .duration_since(UNIX_EPOCH) .map_err(|_| LpClientError::Other("System time before UNIX epoch".into()))? .as_secs(); - // Step 2: Generate ClientHelloData with fresh salt and both public keys + // Step 1: Generate ClientHelloData with fresh salt and both public keys let client_hello_data = nym_lp::ClientHelloData::new_with_fresh_salt( - client_x25519_public, - *self.local_ed25519_keypair.public_key(), + *self.lp_local_peer.x25519().public_key(), + *self.lp_local_peer.ed25519().public_key(), timestamp, ); let salt = client_hello_data.salt; @@ -352,7 +341,7 @@ where receiver_index ); - // Step 3: Send ClientHello and receive Ack (persistent connection) + // Step 2: Send ClientHello and receive Ack (persistent connection) let client_hello_header = nym_lp::packet::LpHeader::new( nym_lp::BOOTSTRAP_RECEIVER_IDX, // session_id not yet established 0, // counter starts at 0 @@ -380,18 +369,17 @@ where } } - // Step 4: Create state machine as initiator with Ed25519 keys + // Step 3: Create state machine as initiator with Ed25519 keys // PSK derivation happens internally in the state machine constructor let mut state_machine = LpStateMachine::new( receiver_index, true, // is_initiator - self.local_ed25519_keypair.clone(), - &self.gateway_ed25519_public_key, - &gateway_x25519_public, + self.lp_local_peer.clone(), + self.gateway_lp_peer.clone(), &salt, )?; - // Step 5: Start handshake - get first packet to send (KKT request) + // Step 4: Start handshake - get first packet to send (KKT request) let mut pending_packet: Option = None; if let Some(action) = state_machine.process_input(LpInput::StartHandshake) { match action? { @@ -407,7 +395,7 @@ where } } - // Step 6: Handshake loop - all packets on persistent connection + // Step 5: Handshake loop - all packets on persistent connection loop { // Send pending packet if we have one if let Some(packet) = pending_packet.take() { @@ -1218,13 +1206,16 @@ mod tests { fn test_client_creation() { let mut rng = rand::thread_rng(); let keypair = Arc::new(ed25519::KeyPair::new(&mut rng)); - let gateway_key = *ed25519::KeyPair::new(&mut rng).public_key(); + let gateway_ed_keys = ed25519::KeyPair::new(&mut rng); + let gateway_x_keys = gateway_ed_keys.to_x25519(); + 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_key, + gateway_peer, address, client_ip, ); diff --git a/nym-registration-client/src/lp_client/error.rs b/nym-registration-client/src/lp_client/error.rs index 2fc74e3fd1..2026e15991 100644 --- a/nym-registration-client/src/lp_client/error.rs +++ b/nym-registration-client/src/lp_client/error.rs @@ -55,10 +55,6 @@ pub enum LpClientError { #[error("Timeout waiting for {operation}")] Timeout { operation: String }, - /// Cryptographic operation failed - #[error("Cryptographic error: {0}")] - Crypto(String), - /// Another uncategorized error #[error("{0}")] Other(String), diff --git a/nym-registration-client/src/lp_client/nested_session.rs b/nym-registration-client/src/lp_client/nested_session.rs index 7fbdbbc11e..8f40f74c26 100644 --- a/nym-registration-client/src/lp_client/nested_session.rs +++ b/nym-registration-client/src/lp_client/nested_session.rs @@ -25,6 +25,7 @@ use nym_bandwidth_controller::BandwidthTicketProvider; use nym_credentials_interface::TicketType; use nym_crypto::asymmetric::{ed25519, x25519}; use nym_lp::codec::{OuterAeadKey, parse_lp_packet, serialize_lp_packet}; +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; @@ -55,17 +56,14 @@ use std::time::{SystemTime, UNIX_EPOCH}; /// let gateway_data = nested.handshake_and_register(&mut outer_client, ...).await?; /// ``` pub struct NestedLpSession { - /// Exit gateway's Ed25519 identity (32 bytes) - exit_identity: [u8; 32], - /// Exit gateway's LP address (e.g., "2.2.2.2:41264") exit_address: String, - /// Client's Ed25519 keypair (for PSQ authentication and X25519 derivation) - client_keypair: Arc, + /// Encapsulates all the client keys needed for the Lewes Protocol. + lp_local_peer: LpLocalPeer, - /// Exit gateway's Ed25519 public key - exit_public_key: ed25519::PublicKey, + /// Encapsulates all the exit gateway keys needed for the Lewes Protocol. + gateway_lp_peer: LpRemotePeer, /// LP state machine for exit gateway session (populated after handshake) state_machine: Option, @@ -75,21 +73,21 @@ impl NestedLpSession { /// Creates a new nested LP session handler. /// /// # Arguments - /// * `exit_identity` - Exit gateway's Ed25519 identity (32 bytes) /// * `exit_address` - Exit gateway's LP address (e.g., "2.2.2.2:41264") /// * `client_keypair` - Client's Ed25519 keypair - /// * `exit_public_key` - Exit gateway's Ed25519 public key + /// * `gateway_lp_peer` - Encapsulates all the gateway keys needed for the Lewes Protocol pub fn new( - exit_identity: [u8; 32], exit_address: String, client_keypair: Arc, - exit_public_key: ed25519::PublicKey, + gateway_lp_peer: LpRemotePeer, ) -> Self { + let local_x25519_keypair = client_keypair.to_x25519(); + let lp_local_peer = LpLocalPeer::new(client_keypair, Arc::new(local_x25519_keypair)); + Self { - exit_identity, exit_address, - client_keypair, - exit_public_key, + lp_local_peer, + gateway_lp_peer, state_machine: None, } } @@ -124,24 +122,15 @@ impl NestedLpSession { self.exit_address ); - // Step 1: Derive X25519 keys from Ed25519 for Noise protocol - let client_x25519_public = self.client_keypair.public_key().to_x25519().map_err(|e| { - LpClientError::Crypto(format!("Failed to derive X25519 public key: {}", e)) - })?; - - let gateway_x25519_public = self.exit_public_key.to_x25519().map_err(|e| { - LpClientError::Crypto(format!("Failed to derive X25519 public key: {e}")) - })?; - let timestamp = SystemTime::now() .duration_since(UNIX_EPOCH) .map_err(|_| LpClientError::Other("System time before UNIX epoch".into()))? .as_secs(); - // Step 2: Generate ClientHello for exit gateway + // Step 1: Generate ClientHello for exit gateway let client_hello_data = nym_lp::ClientHelloData::new_with_fresh_salt( - client_x25519_public, - *self.client_keypair.public_key(), + *self.lp_local_peer.x25519().public_key(), + *self.lp_local_peer.ed25519().public_key(), timestamp, ); let salt = client_hello_data.salt; @@ -152,7 +141,7 @@ impl NestedLpSession { client_hello_data.extract_timestamp() ); - // Step 3: Send ClientHello to exit gateway via forwarding + // Step 2: Send ClientHello to exit gateway via forwarding let client_hello_header = nym_lp::packet::LpHeader::new( nym_lp::BOOTSTRAP_RECEIVER_IDX, // Use constant for bootstrap session 0, // counter starts at 0 @@ -166,7 +155,7 @@ impl NestedLpSession { let client_hello_bytes = Self::serialize_packet(&client_hello_packet, None)?; let response_bytes = outer_client .send_forward_packet( - self.exit_identity, + self.gateway_lp_peer.ed25519().to_bytes(), self.exit_address.clone(), client_hello_bytes, ) @@ -191,17 +180,16 @@ impl NestedLpSession { } } - // Step 4: Create state machine for exit gateway handshake + // Step 3: Create state machine for exit gateway handshake let mut state_machine = LpStateMachine::new( receiver_index, true, // is_initiator - self.client_keypair.clone(), - &self.exit_public_key, - &gateway_x25519_public, + self.lp_local_peer.clone(), + self.gateway_lp_peer.clone(), &salt, )?; - // Step 5: Get initial packet from StartHandshake + // Step 4: Get initial packet from StartHandshake let mut pending_packet: Option = None; if let Some(action) = state_machine.process_input(LpInput::StartHandshake) { match action? { @@ -216,7 +204,7 @@ impl NestedLpSession { } } - // Step 6: Handshake loop - each packet on new connection via forwarding + // Step 5: Handshake loop - each packet on new connection via forwarding loop { if let Some(packet) = pending_packet.take() { tracing::trace!("Sending handshake packet to exit via forwarding"); @@ -356,7 +344,7 @@ impl NestedLpSession { let packet_bytes = Self::serialize_packet(&packet, outer_key.as_ref())?; outer_client .send_forward_packet( - self.exit_identity, + self.gateway_lp_peer.ed25519().to_bytes(), self.exit_address.clone(), packet_bytes, ) @@ -530,7 +518,7 @@ impl NestedLpSession { let packet_bytes = Self::serialize_packet(&packet, outer_key.as_ref())?; outer_client .send_forward_packet( - self.exit_identity, + self.gateway_lp_peer.ed25519().to_bytes(), self.exit_address.clone(), packet_bytes, ) @@ -743,7 +731,11 @@ impl NestedLpSession { let send_key = Self::get_send_key(state_machine); let packet_bytes = Self::serialize_packet(packet, send_key.as_ref())?; let response_bytes = outer_client - .send_forward_packet(self.exit_identity, self.exit_address.clone(), packet_bytes) + .send_forward_packet( + self.gateway_lp_peer.ed25519().to_bytes(), + self.exit_address.clone(), + packet_bytes, + ) .await?; let recv_key = Self::get_recv_key(state_machine); Self::parse_packet(&response_bytes, recv_key.as_ref()) diff --git a/nym-statistics-api/src/network_view.rs b/nym-statistics-api/src/network_view.rs index 1491a00903..d4e44d50a5 100644 --- a/nym-statistics-api/src/network_view.rs +++ b/nym-statistics-api/src/network_view.rs @@ -5,7 +5,7 @@ use anyhow::Result; use nym_task::ShutdownToken; use celes::Country; -use nym_validator_client::models::NymNodeDescription; +use nym_validator_client::models::NymNodeDescriptionV1; use std::collections::HashMap; use std::time::Duration; use std::{net::IpAddr, sync::Arc}; @@ -28,7 +28,8 @@ struct NodesQuerier { } impl NodesQuerier { - async fn current_nymnodes(&self) -> Result> { + #[allow(deprecated)] + async fn current_nymnodes(&self) -> Result> { Ok(self .client .get_all_described_nodes() diff --git a/nym-wallet/Cargo.lock b/nym-wallet/Cargo.lock index 0ebc2184e4..047a50110b 100644 --- a/nym-wallet/Cargo.lock +++ b/nym-wallet/Cargo.lock @@ -81,7 +81,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" dependencies = [ "crypto-common", - "generic-array", + "generic-array 0.14.7", ] [[package]] @@ -106,7 +106,7 @@ dependencies = [ "cipher", "ctr", "ghash", - "subtle", + "subtle 2.6.1", ] [[package]] @@ -259,7 +259,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" dependencies = [ "base64ct", - "blake2", + "blake2 0.10.6", "cpufeatures", "password-hash", ] @@ -681,7 +681,7 @@ dependencies = [ "ripemd", "secp256k1", "sha2 0.10.9", - "subtle", + "subtle 2.6.1", "zeroize", ] @@ -730,6 +730,18 @@ dependencies = [ "serde", ] +[[package]] +name = "blake2" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94cb07b0da6a73955f8fb85d24c466778e70cda767a568229b104f0264089330" +dependencies = [ + "byte-tools", + "crypto-mac", + "digest 0.8.1", + "opaque-debug 0.2.3", +] + [[package]] name = "blake2" version = "0.10.6" @@ -756,7 +768,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" dependencies = [ - "generic-array", + "generic-array 0.14.7", ] [[package]] @@ -765,7 +777,7 @@ version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ - "generic-array", + "generic-array 0.14.7", ] [[package]] @@ -799,22 +811,6 @@ dependencies = [ "piper", ] -[[package]] -name = "bls12_381" -version = "0.8.0" -source = "git+https://github.com/jstuczyn/bls12_381?branch=temp%2Fexperimental-serdect-updated#9bf520059cb28323fc51469cae86868ef4fa6fbd" -dependencies = [ - "digest 0.10.7", - "ff", - "group", - "pairing", - "rand_core 0.6.4", - "serde", - "serdect 0.3.0", - "subtle", - "zeroize", -] - [[package]] name = "bnum" version = "0.11.0" @@ -858,6 +854,12 @@ version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" +[[package]] +name = "byte-tools" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" + [[package]] name = "bytemuck" version = "1.22.0" @@ -1026,6 +1028,16 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "chacha" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddf3c081b5fba1e5615640aae998e0fbd10c24cbd897ee39ed754a77601a4862" +dependencies = [ + "byteorder", + "keystream", +] + [[package]] name = "chrono" version = "0.4.40" @@ -1423,9 +1435,9 @@ version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" dependencies = [ - "generic-array", + "generic-array 0.14.7", "rand_core 0.6.4", - "subtle", + "subtle 2.6.1", "zeroize", ] @@ -1435,11 +1447,21 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ - "generic-array", + "generic-array 0.14.7", "rand_core 0.6.4", "typenum", ] +[[package]] +name = "crypto-mac" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5" +dependencies = [ + "generic-array 0.12.4", + "subtle 1.0.0", +] + [[package]] name = "cssparser" version = "0.27.2" @@ -1505,7 +1527,7 @@ dependencies = [ "fiat-crypto", "rustc_version", "serde", - "subtle", + "subtle 2.6.1", "zeroize", ] @@ -1746,13 +1768,22 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "digest" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" +dependencies = [ + "generic-array 0.12.4", +] + [[package]] name = "digest" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" dependencies = [ - "generic-array", + "generic-array 0.14.7", ] [[package]] @@ -1764,7 +1795,7 @@ dependencies = [ "block-buffer 0.10.4", "const-oid", "crypto-common", - "subtle", + "subtle 2.6.1", ] [[package]] @@ -1956,7 +1987,7 @@ dependencies = [ "rand_core 0.6.4", "serde", "sha2 0.10.9", - "subtle", + "subtle 2.6.1", "zeroize", ] @@ -1991,7 +2022,7 @@ dependencies = [ "crypto-bigint", "digest 0.10.7", "ff", - "generic-array", + "generic-array 0.14.7", "group", "hkdf", "pem-rfc7468", @@ -1999,7 +2030,7 @@ dependencies = [ "rand_core 0.6.4", "sec1", "serdect 0.2.0", - "subtle", + "subtle 2.6.1", "zeroize", ] @@ -2179,7 +2210,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" dependencies = [ "rand_core 0.6.4", - "subtle", + "subtle 2.6.1", ] [[package]] @@ -2526,6 +2557,15 @@ dependencies = [ "windows 0.58.0", ] +[[package]] +name = "generic-array" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" +dependencies = [ + "typenum", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -2591,7 +2631,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" dependencies = [ - "opaque-debug", + "opaque-debug 0.3.1", "polyval", ] @@ -2705,7 +2745,7 @@ checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" dependencies = [ "ff", "rand_core 0.6.4", - "subtle", + "subtle 2.6.1", ] [[package]] @@ -3458,7 +3498,7 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" dependencies = [ - "generic-array", + "generic-array 0.14.7", ] [[package]] @@ -3709,6 +3749,12 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "keystream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33070833c9ee02266356de0c43f723152bd38bd96ddf52c82b3af10c9138b28" + [[package]] name = "kuchikiki" version = "0.8.2" @@ -3800,6 +3846,18 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe7db12097d22ec582439daf8618b8fdd1a7bef6270e9af3b1ebcd30893cf413" +[[package]] +name = "lioness" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae926706ba42c425c9457121178330d75e273df2e82e28b758faf3de3a9acb9" +dependencies = [ + "arrayref", + "blake2 0.8.1", + "chacha", + "keystream", +] + [[package]] name = "litemap" version = "0.7.5" @@ -4153,9 +4211,10 @@ dependencies = [ [[package]] name = "nym-api-requests" -version = "0.1.0" +version = "1.20.1" dependencies = [ "bs58", + "celes", "cosmrs", "cosmwasm-std", "ecdsa", @@ -4179,6 +4238,8 @@ dependencies = [ "serde", "serde_json", "sha2 0.10.9", + "strum", + "strum_macros", "tendermint", "tendermint-rpc", "thiserror 2.0.12", @@ -4189,7 +4250,7 @@ dependencies = [ [[package]] name = "nym-bin-common" -version = "0.6.0" +version = "1.20.1" dependencies = [ "const-str", "log", @@ -4201,9 +4262,26 @@ dependencies = [ "vergen", ] +[[package]] +name = "nym-bls12_381-fork" +version = "0.8.0-forked" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce84633751030f960a2fd167b5270ec21da4c40d9b6400e1b56676a682fe6f3d" +dependencies = [ + "digest 0.10.7", + "ff", + "group", + "pairing", + "rand_core 0.6.4", + "serde", + "serdect 0.3.0", + "subtle 2.6.1", + "zeroize", +] + [[package]] name = "nym-coconut-dkg-common" -version = "0.1.0" +version = "1.20.1" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -4216,29 +4294,29 @@ dependencies = [ [[package]] name = "nym-compact-ecash" -version = "0.1.0" +version = "1.20.1" dependencies = [ "bincode", - "bls12_381", "bs58", "cfg-if", "digest 0.10.7", "ff", "group", "itertools 0.14.0", + "nym-bls12_381-fork", "nym-network-defaults", "nym-pemstore", "rand 0.8.5", "serde", "sha2 0.10.9", - "subtle", + "subtle 2.6.1", "thiserror 2.0.12", "zeroize", ] [[package]] name = "nym-config" -version = "0.1.0" +version = "1.20.1" dependencies = [ "dirs 6.0.0", "handlebars", @@ -4252,7 +4330,7 @@ dependencies = [ [[package]] name = "nym-contracts-common" -version = "0.5.0" +version = "1.20.1" dependencies = [ "bs58", "cosmwasm-schema", @@ -4266,9 +4344,9 @@ dependencies = [ [[package]] name = "nym-credentials-interface" -version = "0.1.0" +version = "1.20.1" dependencies = [ - "bls12_381", + "nym-bls12_381-fork", "nym-compact-ecash", "nym-ecash-time", "nym-network-defaults", @@ -4284,7 +4362,7 @@ dependencies = [ [[package]] name = "nym-crypto" -version = "0.4.0" +version = "1.20.1" dependencies = [ "base64 0.22.1", "bs58", @@ -4305,7 +4383,7 @@ dependencies = [ [[package]] name = "nym-ecash-contract-common" -version = "0.1.0" +version = "1.20.1" dependencies = [ "bs58", "cosmwasm-schema", @@ -4318,7 +4396,7 @@ dependencies = [ [[package]] name = "nym-ecash-signer-check-types" -version = "0.1.0" +version = "1.20.1" dependencies = [ "nym-coconut-dkg-common", "nym-crypto", @@ -4333,14 +4411,14 @@ dependencies = [ [[package]] name = "nym-ecash-time" -version = "0.1.0" +version = "1.20.1" dependencies = [ "time", ] [[package]] name = "nym-exit-policy" -version = "0.1.0" +version = "1.20.1" dependencies = [ "serde", "serde_json", @@ -4351,7 +4429,7 @@ dependencies = [ [[package]] name = "nym-group-contract-common" -version = "0.1.0" +version = "1.20.1" dependencies = [ "cosmwasm-schema", "cw-controllers", @@ -4362,7 +4440,7 @@ dependencies = [ [[package]] name = "nym-http-api-client" -version = "0.1.0" +version = "1.20.1" dependencies = [ "async-trait", "bincode", @@ -4392,7 +4470,7 @@ dependencies = [ [[package]] name = "nym-http-api-client-macro" -version = "0.1.0" +version = "1.20.1" dependencies = [ "proc-macro-crate 3.3.0", "proc-macro2", @@ -4403,7 +4481,7 @@ dependencies = [ [[package]] name = "nym-http-api-common" -version = "0.1.0" +version = "1.20.1" dependencies = [ "bincode", "serde", @@ -4413,7 +4491,7 @@ dependencies = [ [[package]] name = "nym-mixnet-contract-common" -version = "0.6.0" +version = "1.20.1" dependencies = [ "bs58", "cosmwasm-schema", @@ -4434,7 +4512,7 @@ dependencies = [ [[package]] name = "nym-multisig-contract-common" -version = "0.1.0" +version = "1.20.1" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -4449,7 +4527,7 @@ dependencies = [ [[package]] name = "nym-network-defaults" -version = "0.1.0" +version = "1.20.1" dependencies = [ "cargo_metadata 0.19.2", "dotenvy", @@ -4463,7 +4541,7 @@ dependencies = [ [[package]] name = "nym-node-requests" -version = "0.1.0" +version = "1.20.1" dependencies = [ "async-trait", "celes", @@ -4488,7 +4566,7 @@ dependencies = [ [[package]] name = "nym-noise-keys" -version = "0.1.0" +version = "1.20.1" dependencies = [ "nym-crypto", "schemars", @@ -4498,7 +4576,7 @@ dependencies = [ [[package]] name = "nym-pemstore" -version = "0.3.0" +version = "1.20.1" dependencies = [ "pem", "tracing", @@ -4507,7 +4585,7 @@ dependencies = [ [[package]] name = "nym-performance-contract-common" -version = "0.1.0" +version = "1.20.1" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -4520,7 +4598,7 @@ dependencies = [ [[package]] name = "nym-serde-helpers" -version = "0.1.0" +version = "1.20.1" dependencies = [ "base64 0.22.1", "bs58", @@ -4531,18 +4609,19 @@ dependencies = [ [[package]] name = "nym-sphinx-types" -version = "0.2.0" +version = "1.20.1" dependencies = [ + "sphinx-packet", "thiserror 2.0.12", ] [[package]] name = "nym-store-cipher" -version = "0.1.0" +version = "1.20.1" dependencies = [ "aes-gcm", "argon2", - "generic-array", + "generic-array 0.14.7", "getrandom 0.2.15", "rand 0.8.5", "serde", @@ -4553,7 +4632,7 @@ dependencies = [ [[package]] name = "nym-ticketbooks-merkle" -version = "0.1.0" +version = "1.20.1" dependencies = [ "nym-credentials-interface", "nym-serde-helpers", @@ -4567,7 +4646,7 @@ dependencies = [ [[package]] name = "nym-types" -version = "1.0.0" +version = "1.20.1" dependencies = [ "base64 0.22.1", "cosmrs", @@ -4597,7 +4676,7 @@ dependencies = [ [[package]] name = "nym-upgrade-mode-check" -version = "0.1.0" +version = "1.20.1" dependencies = [ "jwt-simple", "nym-crypto", @@ -4613,7 +4692,7 @@ dependencies = [ [[package]] name = "nym-validator-client" -version = "0.1.0" +version = "1.20.1" dependencies = [ "async-trait", "base64 0.22.1", @@ -4662,7 +4741,7 @@ dependencies = [ [[package]] name = "nym-vesting-contract-common" -version = "0.7.0" +version = "1.20.1" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -4708,7 +4787,7 @@ dependencies = [ [[package]] name = "nym-wireguard-types" -version = "0.1.0" +version = "1.20.1" dependencies = [ "base64 0.22.1", "nym-crypto", @@ -4960,6 +5039,12 @@ dependencies = [ "portable-atomic", ] +[[package]] +name = "opaque-debug" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" + [[package]] name = "opaque-debug" version = "0.3.1" @@ -5157,7 +5242,7 @@ checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" dependencies = [ "base64ct", "rand_core 0.6.4", - "subtle", + "subtle 2.6.1", ] [[package]] @@ -5533,7 +5618,7 @@ checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" dependencies = [ "cfg-if", "cpufeatures", - "opaque-debug", + "opaque-debug 0.3.1", "universal-hash", ] @@ -5865,6 +5950,16 @@ dependencies = [ "getrandom 0.3.2", ] +[[package]] +name = "rand_distr" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31" +dependencies = [ + "num-traits", + "rand 0.8.5", +] + [[package]] name = "rand_hc" version = "0.2.0" @@ -6078,7 +6173,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" dependencies = [ "hmac", - "subtle", + "subtle 2.6.1", ] [[package]] @@ -6152,7 +6247,7 @@ dependencies = [ "sha2 0.10.9", "signature", "spki", - "subtle", + "subtle 2.6.1", "zeroize", ] @@ -6226,7 +6321,7 @@ dependencies = [ "ring", "rustls-pki-types", "rustls-webpki 0.103.1", - "subtle", + "subtle 2.6.1", "zeroize", ] @@ -6377,10 +6472,10 @@ checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" dependencies = [ "base16ct", "der", - "generic-array", + "generic-array 0.14.7", "pkcs8", "serdect 0.2.0", - "subtle", + "subtle 2.6.1", "zeroize", ] @@ -6672,7 +6767,7 @@ dependencies = [ "cfg-if", "cpufeatures", "digest 0.9.0", - "opaque-debug", + "opaque-debug 0.3.1", ] [[package]] @@ -6831,6 +6926,32 @@ dependencies = [ "system-deps", ] +[[package]] +name = "sphinx-packet" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c26f0c20d909fdda1c5d0ece3973127ca421984d55b000215df365e93722fc6e" +dependencies = [ + "aes", + "arrayref", + "blake2 0.8.1", + "bs58", + "byteorder", + "chacha", + "ctr", + "curve25519-dalek", + "digest 0.10.7", + "hkdf", + "hmac", + "lioness", + "rand 0.8.5", + "rand_distr", + "sha2 0.10.9", + "subtle 2.6.1", + "x25519-dalek", + "zeroize", +] + [[package]] name = "spin" version = "0.9.8" @@ -6911,6 +7032,12 @@ dependencies = [ "syn 2.0.100", ] +[[package]] +name = "subtle" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee" + [[package]] name = "subtle" version = "2.6.1" @@ -7499,7 +7626,7 @@ dependencies = [ "serde_repr", "sha2 0.10.9", "signature", - "subtle", + "subtle 2.6.1", "subtle-encoding", "tendermint-proto", "time", @@ -7554,7 +7681,7 @@ dependencies = [ "serde", "serde_bytes", "serde_json", - "subtle", + "subtle 2.6.1", "subtle-encoding", "tendermint", "tendermint-config", @@ -8115,7 +8242,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" dependencies = [ "crypto-common", - "subtle", + "subtle 2.6.1", ] [[package]] diff --git a/tools/nym-lp-client/src/client.rs b/tools/nym-lp-client/src/client.rs index c9d0d58bbb..fe78da54b8 100644 --- a/tools/nym-lp-client/src/client.rs +++ b/tools/nym-lp-client/src/client.rs @@ -32,6 +32,7 @@ use tracing::{debug, info, trace}; use crate::topology::{GatewayInfo, SpeedtestTopology}; use nym_ip_packet_requests::v8::request::IpPacketRequest; +use nym_lp::peer::LpRemotePeer; use nym_sphinx::forwarding::packet::MixPacket; /// Conv ID for KCP - hash of source and destination addresses @@ -118,9 +119,12 @@ impl SpeedtestClient { let client_ip = "0.0.0.0".parse()?; + let gw_peer = LpRemotePeer::new(self.gateway.identity, self.gateway.identity.to_x25519()?) + .with_kem_key_digest(self.gateway.kem_key_hash.clone()); + let mut lp_client = LpRegistrationClient::::new_with_default_config( self.identity_keypair.clone(), - self.gateway.identity, + gw_peer, self.gateway.lp_address, client_ip, ); @@ -161,9 +165,12 @@ impl SpeedtestClient { let client_ip = "0.0.0.0".parse()?; + let gw_peer = LpRemotePeer::new(self.gateway.identity, self.gateway.identity.to_x25519()?) + .with_kem_key_digest(self.gateway.kem_key_hash.clone()); + let mut lp_client = LpRegistrationClient::new_with_default_config( self.identity_keypair.clone(), - self.gateway.identity, + gw_peer, self.gateway.lp_address, client_ip, ); diff --git a/tools/nym-lp-client/src/topology.rs b/tools/nym-lp-client/src/topology.rs index 9cb44206a1..cbc5a35699 100644 --- a/tools/nym-lp-client/src/topology.rs +++ b/tools/nym-lp-client/src/topology.rs @@ -27,6 +27,8 @@ const LP_DATA_PORT: u16 = 51264; #[derive(Debug, Clone)] pub struct GatewayInfo { pub identity: ed25519::PublicKey, + pub kem_key_hash: Vec, + pub sphinx_key: nym_crypto::asymmetric::x25519::PublicKey, /// Mix host (IP:port for Sphinx mixing) pub mix_host: SocketAddr, @@ -181,41 +183,42 @@ impl SpeedtestTopology { /// Extract gateway info for LP connections from a SkimmedNode fn gateway_info_from_skimmed(node: &SkimmedNode) -> Result { - let first_ip = node - .ip_addresses - .first() - .ok_or_else(|| anyhow!("node has no IP addresses"))?; - - // LP default control port - const LP_CONTROL_PORT: u16 = 41264; - - Ok(GatewayInfo { - identity: node.ed25519_identity_pubkey, - sphinx_key: node.x25519_sphinx_pubkey, - mix_host: SocketAddr::new(*first_ip, node.mix_port), - lp_address: SocketAddr::new(*first_ip, LP_CONTROL_PORT), - lp_data_address: SocketAddr::new(*first_ip, LP_DATA_PORT), - }) + todo!("insufficient information to convert into GatewayInfo") + // let first_ip = node + // .ip_addresses + // .first() + // .ok_or_else(|| anyhow!("node has no IP addresses"))?; + // + // // LP default control port + // const LP_CONTROL_PORT: u16 = 41264; + // + // Ok(GatewayInfo { + // identity: node.ed25519_identity_pubkey, + // sphinx_key: node.x25519_sphinx_pubkey, + // mix_host: SocketAddr::new(*first_ip, node.mix_port), + // lp_address: SocketAddr::new(*first_ip, LP_CONTROL_PORT), + // lp_data_address: SocketAddr::new(*first_ip, LP_DATA_PORT), + // }) } -#[cfg(test)] -mod tests { - use super::*; - - #[tokio::test] - #[ignore = "requires network access"] - async fn test_fetch_topology() { - let nym_api = Url::parse("https://validator.nymtech.net/api").unwrap(); - let topology = SpeedtestTopology::fetch(&nym_api).await.unwrap(); - - assert!(topology.gateway_count() > 0); - println!("Found {} gateways", topology.gateway_count()); - - let mut rng = rand::thread_rng(); - let gateway = topology.random_gateway(&mut rng).unwrap(); - println!("Selected gateway: {:?}", gateway.identity); - - let route = topology.random_route_to_gateway(&mut rng, gateway).unwrap(); - println!("Route has {} hops", route.len()); - } -} +// #[cfg(test)] +// mod tests { +// use super::*; +// +// #[tokio::test] +// #[ignore = "requires network access"] +// async fn test_fetch_topology() { +// let nym_api = Url::parse("https://validator.nymtech.net/api").unwrap(); +// let topology = SpeedtestTopology::fetch(&nym_api).await.unwrap(); +// +// assert!(topology.gateway_count() > 0); +// println!("Found {} gateways", topology.gateway_count()); +// +// let mut rng = rand::thread_rng(); +// let gateway = topology.random_gateway(&mut rng).unwrap(); +// println!("Selected gateway: {:?}", gateway.identity); +// +// let route = topology.random_route_to_gateway(&mut rng, gateway).unwrap(); +// println!("Route has {} hops", route.len()); +// } +// } diff --git a/tools/ts-rs-cli/src/main.rs b/tools/ts-rs-cli/src/main.rs index 9c80766327..546d6bd316 100644 --- a/tools/ts-rs-cli/src/main.rs +++ b/tools/ts-rs-cli/src/main.rs @@ -1,7 +1,7 @@ #![allow(deprecated)] use nym_api_requests::models::{ - AnnotationResponse, DeclaredRoles, DescribedNodeType, GatewayCoreStatusResponse, + AnnotationResponse, DeclaredRolesV1, DescribedNodeTypeV1, GatewayCoreStatusResponse, HistoricalPerformanceResponse, HistoricalUptimeResponse, MixnodeCoreStatusResponse, MixnodeStatus, MixnodeStatusResponse, NodeAnnotation, NodeDatePerformanceResponse, NodePerformanceResponse, PerformanceHistoryResponse, StakeSaturationResponse, @@ -152,8 +152,8 @@ fn main() -> anyhow::Result<()> { do_export!(UptimeHistoryResponse); do_export!(HistoricalUptimeResponse); do_export!(HistoricalPerformanceResponse); - do_export!(DescribedNodeType); - do_export!(DeclaredRoles); + do_export!(DescribedNodeTypeV1); + do_export!(DeclaredRolesV1); do_export!(PaginatedResponse); do_export!(Pagination); diff --git a/wasm/mix-fetch/src/helpers.rs b/wasm/mix-fetch/src/helpers.rs index 9476d61715..27b8513e70 100644 --- a/wasm/mix-fetch/src/helpers.rs +++ b/wasm/mix-fetch/src/helpers.rs @@ -25,7 +25,7 @@ pub(crate) async fn get_network_requester( url::Url::parse(&nym_api_url.unwrap_or(NYM_API_URL.to_string()))?, None, ); - let nodes = client.get_all_described_nodes().await?; + let nodes = client.get_all_described_nodes_v2().await?; let providers: Vec<_> = nodes .iter() .filter_map(|node| { From 74b05d906609d4026bcb26ecf1aca2c4274972d4 Mon Sep 17 00:00:00 2001 From: Georgio Nicolas Date: Thu, 22 Jan 2026 18:53:24 +0100 Subject: [PATCH 16/51] WIP: Attempt merge develop p2 --- common/nym-lp/src/session.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/common/nym-lp/src/session.rs b/common/nym-lp/src/session.rs index 045009e4b7..afd1780d75 100644 --- a/common/nym-lp/src/session.rs +++ b/common/nym-lp/src/session.rs @@ -655,11 +655,7 @@ impl LpSession { /// * `Ok(())` - KKT exchange completed, KEM key stored /// * `Err(LpError)` - Signature verification failed, hash mismatch, or invalid state /// - pub fn process_kkt_response( - &self, - encrypted_response_bytes: &[u8], - expected_key_hash: &[u8], - ) -> Result<(), LpError> { + pub fn process_kkt_response(&self, encrypted_response_bytes: &[u8]) -> Result<(), LpError> { let mut kkt_state = self.kkt_state.lock(); // Extract context from waiting state @@ -683,7 +679,7 @@ impl LpSession { &response_frame, &remote_context, &self.remote_peer.ed25519_public, - expected_key_hash, + &self.remote_peer.expected_kem_key_digest, ) { Ok(remote_encapsulation_key) => remote_encapsulation_key, Err(e) => { From 441b46d2cc8ec71cb4c2b59d7a8f7cebc901e86b Mon Sep 17 00:00:00 2001 From: Georgio Nicolas Date: Thu, 22 Jan 2026 19:50:39 +0100 Subject: [PATCH 17/51] Complete merge (revert back to dhkem for now) --- common/nym-lp/src/session.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/common/nym-lp/src/session.rs b/common/nym-lp/src/session.rs index afd1780d75..3ae10745ea 100644 --- a/common/nym-lp/src/session.rs +++ b/common/nym-lp/src/session.rs @@ -17,7 +17,7 @@ use crate::psk::{ }; use crate::replay::ReceivingKeyCounterValidator; use crate::{LpError, LpMessage, LpPacket}; -use nym_crypto::asymmetric::{ed25519, x25519}; +use nym_crypto::asymmetric::x25519; use nym_kkt::KKT_RESPONSE_AAD; use nym_kkt::ciphersuite::{DecapsulationKey, EncapsulationKey}; use nym_kkt::encryption::{ @@ -593,7 +593,7 @@ impl LpSession { // georgio this needs to be moved one level up (maybe chosen in LPSession) let ciphersuite = match Ciphersuite::resolve_ciphersuite( - KEM::MlKem768, + KEM::X25519, HashFunction::Blake3, SignatureScheme::Ed25519, None, @@ -729,7 +729,7 @@ impl LpSession { let mut kkt_state = self.kkt_state.lock(); let (session_secret, request_frame, remote_context) = - decrypt_initial_kkt_frame(self.local_peer.x25519().private_key(), &request_bytes) + decrypt_initial_kkt_frame(self.local_peer.x25519().private_key(), request_bytes) .unwrap(); let (mut context, _) = @@ -739,7 +739,7 @@ impl LpSession { &mut context, request_frame.session_id(), self.local_peer.ed25519().private_key(), - &responder_kem_pk, + responder_kem_pk, ) .unwrap(); From 751929fa04ab71f813ce902e831ac2170472f8cb Mon Sep 17 00:00:00 2001 From: Georgio Nicolas Date: Thu, 22 Jan 2026 20:11:40 +0100 Subject: [PATCH 18/51] KKT Request Error Handling --- common/nym-lp/src/session.rs | 77 ++++++++++++++++++++++-------------- 1 file changed, 48 insertions(+), 29 deletions(-) diff --git a/common/nym-lp/src/session.rs b/common/nym-lp/src/session.rs index 3ae10745ea..58683f2176 100644 --- a/common/nym-lp/src/session.rs +++ b/common/nym-lp/src/session.rs @@ -728,35 +728,54 @@ impl LpSession { let mut kkt_state = self.kkt_state.lock(); - let (session_secret, request_frame, remote_context) = - decrypt_initial_kkt_frame(self.local_peer.x25519().private_key(), request_bytes) - .unwrap(); - - let (mut context, _) = - responder_ingest_message(&remote_context, None, None, &request_frame).unwrap(); - - let response_frame = responder_process( - &mut context, - request_frame.session_id(), - self.local_peer.ed25519().private_key(), - responder_kem_pk, - ) - .unwrap(); - - let response_bytes = - encrypt_kkt_frame(&mut rng, &session_secret, &response_frame, KKT_RESPONSE_AAD) - .unwrap(); - - // Handle request and create signed response - // let response_bytes = handle_kem_request( - // &mut rng, - // request_bytes, - // Some(&self.remote_ed25519_public), // Verify initiator signature - // self.local_ed25519.private_key(), // Sign response - // self.local_x25519.private_key(), - // responder_kem_pk, - // ) - // .map_err(|e| LpError::Internal(format!("KKT request handling failed: {:?}", e)))?; + let response_bytes = match decrypt_initial_kkt_frame( + self.local_peer.x25519().private_key(), + request_bytes, + ) { + Ok((session_secret, request_frame, remote_context)) => { + match responder_ingest_message(&remote_context, None, None, &request_frame) { + Ok((mut context, _)) => match responder_process( + &mut context, + request_frame.session_id(), + self.local_peer.ed25519().private_key(), + responder_kem_pk, + ) { + Ok(response_frame) => match encrypt_kkt_frame( + &mut rng, + &session_secret, + &response_frame, + KKT_RESPONSE_AAD, + ) { + Ok(response_bytes) => response_bytes, + Err(e) => { + return Err(LpError::Internal(format!( + "KKT response encryption failure : {:?}", + e + ))); + } + }, + Err(e) => { + return Err(LpError::Internal(format!( + "KKT response generation failure : {:?}", + e + ))); + } + }, + Err(e) => { + return Err(LpError::Internal(format!( + "KKT request handling failure: {:?}", + e + ))); + } + } + } + Err(e) => { + return Err(LpError::Internal(format!( + "KKT request decryption failure: {:?}", + e + ))); + } + }; // Mark KKT as processed // Responder doesn't store the kem_pk since they already have their own KEM keypair From 9c81a87173ac4a60c76d102eea2218688fe790ed Mon Sep 17 00:00:00 2001 From: Georgio Nicolas Date: Thu, 22 Jan 2026 20:11:54 +0100 Subject: [PATCH 19/51] Cleanup --- common/nym-kkt/src/kkt.rs | 3 --- common/nym-kkt/src/lib.rs | 9 --------- 2 files changed, 12 deletions(-) diff --git a/common/nym-kkt/src/kkt.rs b/common/nym-kkt/src/kkt.rs index c83454a52a..05b55d575d 100644 --- a/common/nym-kkt/src/kkt.rs +++ b/common/nym-kkt/src/kkt.rs @@ -27,9 +27,6 @@ pub use crate::session::{ use crate::encryption::{KKTSessionSecret, encrypt_initial_kkt_frame}; use crate::frame::KKTFrame; -pub(crate) const KKT_RESPONSE_AAD: &[u8] = b"KKT_Response"; -pub(crate) const KKT_INITIAL_FRAME_AAD: &[u8] = b"KKT_INITIAL_FRAME"; - /// Perform an *Encrypted* request for a KEM public key from a responder (OneWay mode). /// /// This is the client-side operation that initiates a KKT exchange. diff --git a/common/nym-kkt/src/lib.rs b/common/nym-kkt/src/lib.rs index 354241254d..4fbb51b197 100644 --- a/common/nym-kkt/src/lib.rs +++ b/common/nym-kkt/src/lib.rs @@ -18,7 +18,6 @@ pub(crate) const KKT_INITIAL_FRAME_AAD: &[u8] = b"KKT_INITIAL_FRAME"; #[cfg(test)] mod test { - // use crate::kkt::KKT_RESPONSE_AAD; use crate::{ KKT_RESPONSE_AAD, ciphersuite::{Ciphersuite, EncapsulationKey, HashFunction, KEM}, @@ -498,11 +497,3 @@ mod test { } } } - -// #[test] -// fn test_keys() { -// let a = nym_crypto::asymmetric::x25519::KeyPair::new(&mut rand::rng()); -// println!("{:?}", a.private_key().as_bytes()); -// println!("{:?}", a.public_key().as_bytes()); -// } -// } From a63a1e745e9cf00f877a3b2bd8c028889eb9fa41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Fri, 23 Jan 2026 11:41:55 +0000 Subject: [PATCH 20/51] LP: modified LPRemotePeer to dynamically choose required KEM key hash (#6358) * LP: modified LPRemotePeer to dynamically choose required KEM key hash * nym-lp-client fixes --- Cargo.lock | 18 +- Cargo.toml | 2 +- common/nym-kkt-ciphersuite/Cargo.toml | 26 ++ common/nym-kkt-ciphersuite/src/error.rs | 21 ++ common/nym-kkt-ciphersuite/src/lib.rs | 337 ++++++++++++++++++ common/nym-kkt/Cargo.toml | 3 +- common/nym-kkt/src/ciphersuite.rs | 225 +----------- common/nym-kkt/src/context.rs | 14 +- common/nym-kkt/src/encryption.rs | 15 +- common/nym-kkt/src/error.rs | 8 +- common/nym-kkt/src/key_utils.rs | 33 +- common/nym-lp/src/error.rs | 9 + common/nym-lp/src/peer.rs | 33 +- common/nym-lp/src/session.rs | 21 +- common/registration/Cargo.toml | 1 + common/registration/src/lib.rs | 7 +- nym-node/nym-node-requests/Cargo.toml | 1 + .../src/api/v1/lewes_protocol/models.rs | 44 +++ nym-node/src/node/mod.rs | 27 +- nym-registration-client/src/lib.rs | 4 +- nym-wallet/Cargo.lock | 22 +- tools/nym-lp-client/Cargo.toml | 1 + tools/nym-lp-client/src/client.rs | 4 +- tools/nym-lp-client/src/topology.rs | 5 +- 24 files changed, 568 insertions(+), 313 deletions(-) create mode 100644 common/nym-kkt-ciphersuite/Cargo.toml create mode 100644 common/nym-kkt-ciphersuite/src/error.rs create mode 100644 common/nym-kkt-ciphersuite/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 2705d107e0..f4042c9595 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6796,15 +6796,28 @@ dependencies = [ "libcrux-chacha20poly1305", "libcrux-ecdh", "libcrux-kem", - "libcrux-sha3", "num_enum", "nym-crypto", + "nym-kkt-ciphersuite", "rand 0.9.2", "rand_chacha 0.9.0", + "strum", "thiserror 2.0.17", "zeroize", ] +[[package]] +name = "nym-kkt-ciphersuite" +version = "1.20.1" +dependencies = [ + "blake3", + "libcrux-sha3", + "num_enum", + "strum", + "strum_macros", + "thiserror 2.0.17", +] + [[package]] name = "nym-ledger" version = "1.20.1" @@ -6858,6 +6871,7 @@ dependencies = [ "nym-http-api-client", "nym-ip-packet-requests", "nym-kcp", + "nym-kkt-ciphersuite", "nym-lp", "nym-registration-client", "nym-sphinx", @@ -7197,6 +7211,7 @@ dependencies = [ "nym-crypto", "nym-exit-policy", "nym-http-api-client", + "nym-kkt-ciphersuite", "nym-noise-keys", "nym-upgrade-mode-check", "nym-wireguard-types", @@ -7503,6 +7518,7 @@ dependencies = [ "nym-credentials-interface", "nym-crypto", "nym-ip-packet-requests", + "nym-kkt-ciphersuite", "nym-sphinx", "nym-wireguard-types", "serde", diff --git a/Cargo.toml b/Cargo.toml index 7cf7fbe6d2..e195477930 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -174,7 +174,7 @@ members = [ "wasm/node-tester", "wasm/zknym-lib", "nym-gateway-probe", - "integration-tests", "common/nym-lp-transport", + "integration-tests", "common/nym-lp-transport", "common/nym-kkt-ciphersuite", ] default-members = [ diff --git a/common/nym-kkt-ciphersuite/Cargo.toml b/common/nym-kkt-ciphersuite/Cargo.toml new file mode 100644 index 0000000000..079325f625 --- /dev/null +++ b/common/nym-kkt-ciphersuite/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "nym-kkt-ciphersuite" +authors.workspace = true +repository.workspace = true +homepage.workspace = true +documentation.workspace = true +edition.workspace = true +license.workspace = true +rust-version.workspace = true +readme.workspace = true +version.workspace = true + +[dependencies] +thiserror = { workspace = true } +num_enum = { workspace = true } +strum = { workspace = true } +strum_macros = { workspace = true } + +blake3 = { workspace = true, optional = true } +libcrux-sha3 = { git = "https://github.com/cryspen/libcrux", optional = true } + +[features] +digests = ["blake3", "libcrux-sha3"] + +[lints] +workspace = true diff --git a/common/nym-kkt-ciphersuite/src/error.rs b/common/nym-kkt-ciphersuite/src/error.rs new file mode 100644 index 0000000000..f644888d1e --- /dev/null +++ b/common/nym-kkt-ciphersuite/src/error.rs @@ -0,0 +1,21 @@ +// Copyright 2026 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum KKTCiphersuiteError { + #[error( + "attempted to use an insecure encapsulation key hash length. requested: {requested}. minimum: {minimum}" + )] + InsecureHashLen { requested: u8, minimum: u8 }, + + #[error("{raw} does not correspond to any known KEM type encoding")] + UnknownKEMType { raw: u8 }, + + #[error("{raw} does not correspond to any known Hash Function type encoding")] + UnknownHashFunctionType { raw: u8 }, + + #[error("{raw} does not correspond to any known Signature Scheme type encoding")] + UnknownSignatureSchemeType { raw: u8 }, +} diff --git a/common/nym-kkt-ciphersuite/src/lib.rs b/common/nym-kkt-ciphersuite/src/lib.rs new file mode 100644 index 0000000000..c34d40f0bc --- /dev/null +++ b/common/nym-kkt-ciphersuite/src/lib.rs @@ -0,0 +1,337 @@ +// Copyright 2026 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::error::KKTCiphersuiteError; +use num_enum::{IntoPrimitive, TryFromPrimitive}; +use std::collections::HashMap; +use std::fmt::Display; +use strum_macros::EnumIter; + +pub mod error; + +pub const DEFAULT_HASH_LEN: usize = 32; +const _: () = assert!(DEFAULT_HASH_LEN <= u8::MAX as usize); + +pub const MINIMUM_SECURE_HASH_LEN: u8 = 16; +const _: () = assert!(MINIMUM_SECURE_HASH_LEN <= DEFAULT_HASH_LEN as u8); + +pub const CIPHERSUITE_ENCODING_LEN: usize = 4; + +// no point in importing curve libraries for well-defined constants +pub mod ed25519 { + pub const SECRET_KEY_LENGTH: usize = 32; + pub const PUBLIC_KEY_LENGTH: usize = 32; + pub const SIGNATURE_LENGTH: usize = 64; +} + +pub mod x25519 { + pub const PUBLIC_KEY_LENGTH: usize = 32; + pub const SECRET_KEY_LENGTH: usize = 32; +} + +pub mod ml_kem768 { + pub const PUBLIC_KEY_LENGTH: usize = 1184; +} + +pub mod mceliece { + pub const PUBLIC_KEY_LENGTH: usize = 524160; + pub const SECRET_KEY_LENGTH: usize = 13608; + pub const CIPHERTEXT_LENGTH: usize = 156; +} + +pub mod xwing { + use crate::{ml_kem768, x25519}; + + pub const PUBLIC_KEY_LENGTH: usize = x25519::PUBLIC_KEY_LENGTH + ml_kem768::PUBLIC_KEY_LENGTH; +} + +pub type KEMKeyDigests = HashMap>; + +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, IntoPrimitive, TryFromPrimitive, EnumIter)] +#[repr(u8)] +pub enum HashFunction { + Blake3 = 0, + SHAKE256 = 1, + SHAKE128 = 2, + SHA256 = 3, +} + +impl HashFunction { + #[cfg(feature = "digests")] + pub fn digest>(&self, data: M, output_length: usize) -> Vec { + let mut out = vec![0u8; output_length]; + match self { + HashFunction::Blake3 => { + let mut hasher = blake3::Hasher::new(); + hasher.update(data.as_ref()); + 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::SHA256 => libcrux_sha3::sha256_ema(&mut out, data.as_ref()), + } + + out + } +} + +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::SHA256 => "sha256", + }) + } +} + +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, IntoPrimitive)] +#[repr(u8)] +pub enum HashLength { + Default = 0, + #[num_enum(catch_all)] + Custom(u8), +} + +impl Display for HashLength { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + HashLength::Default => DEFAULT_HASH_LEN.fmt(f), + HashLength::Custom(custom_len) => custom_len.fmt(f), + } + } +} + +impl HashLength { + pub fn decode(raw: u8) -> Result { + // check if we're using encoding for 'default' value + if raw == u8::from(Self::Default) { + return Ok(Self::Default); + } + // otherwise, we treat it as a custom length, and we have to validate its security + let custom_len = raw; + + if custom_len < MINIMUM_SECURE_HASH_LEN { + return Err(KKTCiphersuiteError::InsecureHashLen { + requested: custom_len, + minimum: MINIMUM_SECURE_HASH_LEN, + }); + } + Ok(Self::Custom(custom_len)) + } +} + +impl TryFrom> for HashLength { + type Error = KKTCiphersuiteError; + + fn try_from(value: Option) -> Result { + match value { + None => Ok(Self::Default), + Some(custom_len) => Self::decode(custom_len), + } + } +} + +impl HashLength { + pub const fn value(&self) -> usize { + match self { + HashLength::Default => DEFAULT_HASH_LEN, + HashLength::Custom(custom_len) => *custom_len as usize, + } + } +} + +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, IntoPrimitive, TryFromPrimitive)] +#[repr(u8)] +pub enum SignatureScheme { + Ed25519 = 0, +} + +impl SignatureScheme { + pub const fn signing_key_length(&self) -> usize { + match self { + // 32 bytes + SignatureScheme::Ed25519 => ed25519::SECRET_KEY_LENGTH, + } + } + + pub const fn verification_key_length(&self) -> usize { + match self { + // 32 bytes + SignatureScheme::Ed25519 => ed25519::PUBLIC_KEY_LENGTH, + } + } + + pub const fn signature_length(&self) -> usize { + match self { + // 64 bytes + SignatureScheme::Ed25519 => ed25519::SIGNATURE_LENGTH, + } + } +} + +impl Display for SignatureScheme { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(match self { + SignatureScheme::Ed25519 => "ed25519", + }) + } +} + +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, IntoPrimitive, TryFromPrimitive)] +#[repr(u8)] +pub enum KEM { + XWing = 0, + MlKem768 = 1, + McEliece = 2, + X25519 = 255, +} + +impl KEM { + pub fn encapsulation_key_length(&self) -> usize { + match self { + KEM::MlKem768 => ml_kem768::PUBLIC_KEY_LENGTH, + KEM::XWing => xwing::PUBLIC_KEY_LENGTH, + KEM::X25519 => x25519::PUBLIC_KEY_LENGTH, + KEM::McEliece => mceliece::PUBLIC_KEY_LENGTH, + } + } +} + +impl Display for KEM { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(match self { + KEM::MlKem768 => "mlkem768", + KEM::XWing => "xwing", + KEM::X25519 => "x25519", + KEM::McEliece => "mceliece", + }) + } +} + +#[derive(Clone, Copy, PartialEq, Debug)] +pub struct Ciphersuite { + hash_function: HashFunction, + signature_scheme: SignatureScheme, + kem: KEM, + hash_length: HashLength, + encapsulation_key_length: usize, + signing_key_length: usize, + verification_key_length: usize, + signature_length: usize, +} + +impl Ciphersuite { + pub fn new( + kem: KEM, + hash_function: HashFunction, + signature_scheme: SignatureScheme, + hash_length: HashLength, + ) -> Self { + Self { + hash_function, + signature_scheme, + kem, + hash_length, + encapsulation_key_length: kem.encapsulation_key_length(), + signing_key_length: signature_scheme.signing_key_length(), + verification_key_length: signature_scheme.verification_key_length(), + signature_length: signature_scheme.signature_length(), + } + } + + pub fn kem_key_len(&self) -> usize { + self.encapsulation_key_length + } + + pub fn signature_len(&self) -> usize { + self.signature_length + } + + pub fn signing_key_len(&self) -> usize { + self.signing_key_length + } + + pub fn verification_key_len(&self) -> usize { + self.verification_key_length + } + + pub fn hash_function(&self) -> HashFunction { + self.hash_function + } + + pub fn kem(&self) -> KEM { + self.kem + } + + pub fn signature_scheme(&self) -> SignatureScheme { + self.signature_scheme + } + + pub fn hash_len(&self) -> usize { + self.hash_length.value() + } + + pub fn resolve_ciphersuite( + kem: KEM, + hash_function: HashFunction, + signature_scheme: SignatureScheme, + // This should be None 99.9999% of the time + custom_hash_length: Option, + ) -> Result { + let hash_length = HashLength::try_from(custom_hash_length)?; + + Ok(Ciphersuite::new( + kem, + hash_function, + signature_scheme, + hash_length, + )) + } + pub fn encode(&self) -> [u8; CIPHERSUITE_ENCODING_LEN] { + // [kem, hash, hashlen, sig] + [ + self.kem.into(), + self.hash_function.into(), + self.hash_length.into(), + self.signature_scheme.into(), + ] + } + + pub fn decode(encoding: [u8; CIPHERSUITE_ENCODING_LEN]) -> Result { + let raw_kem = encoding[0]; + let raw_hash_function = encoding[1]; + let hash_len = encoding[2]; + let raw_signature_scheme = encoding[3]; + + let kem = KEM::try_from(raw_kem) + .map_err(|_| KKTCiphersuiteError::UnknownKEMType { raw: raw_kem })?; + let hash_function = HashFunction::try_from(raw_hash_function).map_err(|_| { + KKTCiphersuiteError::UnknownHashFunctionType { + raw: raw_hash_function, + } + })?; + let hash_length = HashLength::decode(hash_len)?; + let signature_scheme = SignatureScheme::try_from(raw_signature_scheme).map_err(|_| { + KKTCiphersuiteError::UnknownSignatureSchemeType { + raw: raw_signature_scheme, + } + })?; + + Ok(Self::new(kem, hash_function, signature_scheme, hash_length)) + } +} + +impl Display for Ciphersuite { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + &format!( + "{}_{}({})_{}", + self.kem, self.hash_function, self.hash_length, self.signature_scheme + ) + .to_ascii_lowercase(), + ) + } +} diff --git a/common/nym-kkt/Cargo.toml b/common/nym-kkt/Cargo.toml index 14b3f2f9fc..26c6159c49 100644 --- a/common/nym-kkt/Cargo.toml +++ b/common/nym-kkt/Cargo.toml @@ -10,13 +10,14 @@ publish = false blake3 = { workspace = true } thiserror = { workspace = true } num_enum = { workspace = true } +strum = { workspace = true } # internal nym-crypto = { path = "../crypto", features = ["asymmetric", "serde"] } +nym-kkt-ciphersuite = { path = "../nym-kkt-ciphersuite", features = ["digests"] } libcrux-kem = { git = "https://github.com/cryspen/libcrux" } -libcrux-sha3 = { git = "https://github.com/cryspen/libcrux" } libcrux-ecdh = { git = "https://github.com/cryspen/libcrux", features = ["codec"] } libcrux-chacha20poly1305 = { git = "https://github.com/cryspen/libcrux" } diff --git a/common/nym-kkt/src/ciphersuite.rs b/common/nym-kkt/src/ciphersuite.rs index a822afdf7d..c87915881c 100644 --- a/common/nym-kkt/src/ciphersuite.rs +++ b/common/nym-kkt/src/ciphersuite.rs @@ -1,37 +1,10 @@ // Copyright 2025 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use libcrux_kem::{Algorithm, MlKem768PublicKey}; -use nym_crypto::asymmetric::ed25519; -use std::fmt::Display; - use crate::error::KKTError; +use libcrux_kem::Algorithm; -pub const DEFAULT_HASH_LEN: usize = 32; -const _: () = assert!(DEFAULT_HASH_LEN <= u8::MAX as usize); - -pub const CIPHERSUITE_ENCODING_LEN: usize = 4; - -pub const CURVE25519_KEY_LEN: usize = 32; - -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum HashFunction { - Blake3, - SHAKE128, - SHAKE256, - SHA256, -} - -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::SHA256 => "SHA256", - }) - } -} +pub use nym_kkt_ciphersuite::*; pub enum EncapsulationKey<'a> { MlKem768(libcrux_kem::PublicKey), @@ -89,200 +62,6 @@ impl<'a> EncapsulationKey<'a> { } } -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum SignatureScheme { - Ed25519, -} -impl Display for SignatureScheme { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(match self { - SignatureScheme::Ed25519 => "Ed25519", - }) - } -} - -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum KEM { - MlKem768, - XWing, - X25519, - McEliece, -} - -impl Display for KEM { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(match self { - KEM::MlKem768 => "MlKem768", - KEM::XWing => "XWing", - KEM::X25519 => "x25519", - KEM::McEliece => "McEliece", - }) - } -} - -#[derive(Clone, Copy, Debug, PartialEq)] -pub struct Ciphersuite { - hash_function: HashFunction, - signature_scheme: SignatureScheme, - kem: KEM, - hash_length: u8, - encapsulation_key_length: usize, - signing_key_length: usize, - verification_key_length: usize, - signature_length: usize, -} - -impl Ciphersuite { - pub fn kem_key_len(&self) -> usize { - self.encapsulation_key_length - } - - pub fn signature_len(&self) -> usize { - self.signature_length - } - pub fn signing_key_len(&self) -> usize { - self.signing_key_length - } - pub fn verification_key_len(&self) -> usize { - self.verification_key_length - } - pub fn hash_function(&self) -> HashFunction { - self.hash_function - } - pub fn kem(&self) -> KEM { - self.kem - } - pub fn signature_scheme(&self) -> SignatureScheme { - self.signature_scheme - } - pub fn hash_len(&self) -> usize { - self.hash_length as usize - } - - pub fn resolve_ciphersuite( - kem: KEM, - hash_function: HashFunction, - signature_scheme: SignatureScheme, - // This should be None 99.9999% of the time - custom_hash_length: Option, - ) -> Result { - let hash_len = match custom_hash_length { - Some(l) => { - if l < 16 { - return Err(KKTError::InsecureHashLen); - } else { - l - } - } - None => DEFAULT_HASH_LEN as u8, - }; - Ok(Self { - hash_function, - signature_scheme, - kem, - hash_length: hash_len, - encapsulation_key_length: match kem { - // 1184 bytes - KEM::MlKem768 => MlKem768PublicKey::len(), - // 1216 bytes = 1184 + 32 - KEM::XWing => MlKem768PublicKey::len() + CURVE25519_KEY_LEN, - // 32 bytes - KEM::X25519 => CURVE25519_KEY_LEN, - // 524160 bytes - KEM::McEliece => classic_mceliece_rust::CRYPTO_PUBLICKEYBYTES, - }, - signing_key_length: match signature_scheme { - // 32 bytes - SignatureScheme::Ed25519 => ed25519::SECRET_KEY_LENGTH, - }, - verification_key_length: match signature_scheme { - // 32 bytes - SignatureScheme::Ed25519 => ed25519::PUBLIC_KEY_LENGTH, - }, - signature_length: match signature_scheme { - // 64 bytes - SignatureScheme::Ed25519 => ed25519::SIGNATURE_LENGTH, - }, - }) - } - pub fn encode(&self) -> [u8; CIPHERSUITE_ENCODING_LEN] { - // [kem, hash, hashlen, sig] - [ - match self.kem { - KEM::XWing => 0, - KEM::MlKem768 => 1, - KEM::McEliece => 2, - KEM::X25519 => 255, - }, - match self.hash_function { - HashFunction::Blake3 => 0, - HashFunction::SHAKE256 => 1, - HashFunction::SHAKE128 => 2, - HashFunction::SHA256 => 3, - }, - match self.hash_length as usize { - DEFAULT_HASH_LEN => 0u8, - _ => self.hash_length, - }, - match self.signature_scheme { - SignatureScheme::Ed25519 => 0, - }, - ] - } - pub fn decode(encoding: [u8; CIPHERSUITE_ENCODING_LEN]) -> Result { - let kem = match encoding[0] { - 0 => KEM::XWing, - 1 => KEM::MlKem768, - 2 => KEM::McEliece, - 255 => KEM::X25519, - _ => { - return Err(KKTError::CiphersuiteDecodingError { - info: format!("Undefined KEM: {}", encoding[0]), - }); - } - }; - let hash_function = match encoding[1] { - 0 => HashFunction::Blake3, - 1 => HashFunction::SHAKE256, - 2 => HashFunction::SHAKE128, - 3 => HashFunction::SHA256, - _ => { - return Err(KKTError::CiphersuiteDecodingError { - info: format!("Undefined Hash Function: {}", encoding[1]), - }); - } - }; - - let custom_hash_length = match encoding[2] { - 0 => None, - _ => Some(encoding[2]), - }; - - let signature_scheme = match encoding[3] { - 0 => SignatureScheme::Ed25519, - _ => { - return Err(KKTError::CiphersuiteDecodingError { - info: format!("Undefined Signature Scheme: {}", encoding[3]), - }); - } - }; - - Self::resolve_ciphersuite(kem, hash_function, signature_scheme, custom_hash_length) - } -} - -impl Display for Ciphersuite { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str( - &format!( - "{}_{}({})_{}", - self.kem, self.hash_function, self.hash_length, self.signature_scheme - ) - .to_ascii_lowercase(), - ) - } -} - pub const fn map_kem_to_libcrux_kem(kem: KEM) -> Result { match kem { KEM::MlKem768 => Ok(Algorithm::MlKem768), diff --git a/common/nym-kkt/src/context.rs b/common/nym-kkt/src/context.rs index 8ca1b351c7..8034c52af7 100644 --- a/common/nym-kkt/src/context.rs +++ b/common/nym-kkt/src/context.rs @@ -6,7 +6,7 @@ use crate::{KKT_VERSION, ciphersuite::Ciphersuite, error::KKTError, frame::KKT_S use num_enum::{IntoPrimitive, TryFromPrimitive}; use std::fmt::Display; -pub const KKT_CONTEXT_LEN: usize = 7; +pub const KKT_CONTEXT_LEN: usize = 3 + CIPHERSUITE_ENCODING_LEN; // bitmask used: 0b1110_0000 #[derive(Clone, Copy, PartialEq, Debug, IntoPrimitive, TryFromPrimitive)] @@ -202,13 +202,11 @@ impl KKTContext { info: format!("Header - Invalid KKT Mode: {raw_kkt_mode}"), })?; - let ciphersuite_bytes = header_bytes[2..6].try_into().map_err(|_| { - KKTError::CiphersuiteDecodingError { - info: format!( - "Incorrect Encoding Length: actual: 4 != expected: {CIPHERSUITE_ENCODING_LEN}", - ), - } - })?; + // SAFETY: we're taking exactly `CIPHERSUITE_ENCODING_LEN` bytes + #[allow(clippy::unwrap_used)] + let ciphersuite_bytes = header_bytes[2..2 + CIPHERSUITE_ENCODING_LEN] + .try_into() + .unwrap(); Ok(KKTContext { version: kkt_version, diff --git a/common/nym-kkt/src/encryption.rs b/common/nym-kkt/src/encryption.rs index 4176d45515..75257fc79c 100644 --- a/common/nym-kkt/src/encryption.rs +++ b/common/nym-kkt/src/encryption.rs @@ -1,10 +1,7 @@ // Copyright 2025-2026 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::{ - KKT_INITIAL_FRAME_AAD, ciphersuite::CURVE25519_KEY_LEN, context::KKTContext, error::KKTError, - frame::KKTFrame, -}; +use crate::{KKT_INITIAL_FRAME_AAD, context::KKTContext, error::KKTError, frame::KKTFrame}; use blake3::Hasher; use libcrux_chacha20poly1305::{NONCE_LEN, TAG_LEN}; use nym_crypto::asymmetric::x25519; @@ -36,7 +33,7 @@ impl KKTSessionSecret { fn try_derive(private_key: &x25519::PrivateKey, public_key: &[u8]) -> Result { let mut pub_key: [u8; 32] = [0u8; 32]; - pub_key.copy_from_slice(&public_key[0..CURVE25519_KEY_LEN]); + pub_key.copy_from_slice(&public_key[0..x25519::PUBLIC_KEY_SIZE]); // Todo: check validity of pk... let pk = x25519::PublicKey::from(pub_key); @@ -71,7 +68,7 @@ where let mut encrypted_frame = encrypt_kkt_frame(rng, &session_secret_key, kkt_frame, KKT_INITIAL_FRAME_AAD)?; - let mut output_buffer = Vec::with_capacity(encrypted_frame.len() + CURVE25519_KEY_LEN); + let mut output_buffer = Vec::with_capacity(encrypted_frame.len() + x25519::PUBLIC_KEY_SIZE); output_buffer.extend_from_slice(ephemeral_public_key.as_bytes()); output_buffer.append(&mut encrypted_frame); @@ -84,19 +81,19 @@ pub fn decrypt_initial_kkt_frame( responder_private_key: &x25519::PrivateKey, encrypted_frame_bytes: &[u8], ) -> Result<(KKTSessionSecret, KKTFrame, KKTContext), KKTError> { - if encrypted_frame_bytes.len() < CURVE25519_KEY_LEN + TAG_LEN + NONCE_LEN { + if encrypted_frame_bytes.len() < x25519::PUBLIC_KEY_SIZE + TAG_LEN + NONCE_LEN { Err(KKTError::AEADError { info: "Encrypted KKT Frame is too short.", }) } else { let shared_secret = KKTSessionSecret::try_derive( responder_private_key, - &encrypted_frame_bytes[0..CURVE25519_KEY_LEN], + &encrypted_frame_bytes[0..x25519::PUBLIC_KEY_SIZE], )?; let (kkt_frame, kkt_context) = decrypt_kkt_frame( &shared_secret, - &encrypted_frame_bytes[CURVE25519_KEY_LEN..], + &encrypted_frame_bytes[x25519::PUBLIC_KEY_SIZE..], KKT_INITIAL_FRAME_AAD, )?; Ok((shared_secret, kkt_frame, kkt_context)) diff --git a/common/nym-kkt/src/error.rs b/common/nym-kkt/src/error.rs index 61744154a1..1defc1b1e0 100644 --- a/common/nym-kkt/src/error.rs +++ b/common/nym-kkt/src/error.rs @@ -1,19 +1,19 @@ // Copyright 2025 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use crate::context::KKTStatus; +use nym_kkt_ciphersuite::error::KKTCiphersuiteError; use std::fmt::Debug; use thiserror::Error; -use crate::context::KKTStatus; - #[derive(Error, Debug)] pub enum KKTError { #[error("Signature constructor error")] SigConstructorError, #[error("Signature verification error")] SigVerifError, - #[error("Ciphersuite Decoding Error: {}", info)] - CiphersuiteDecodingError { info: String }, + #[error(transparent)] + CiphersuiteDecodingError(#[from] KKTCiphersuiteError), #[error("KEM mapping failure: {}", info)] KEMMapping { info: &'static str }, diff --git a/common/nym-kkt/src/key_utils.rs b/common/nym-kkt/src/key_utils.rs index 2845e7a907..e612cbe3bf 100644 --- a/common/nym-kkt/src/key_utils.rs +++ b/common/nym-kkt/src/key_utils.rs @@ -1,8 +1,9 @@ use crate::ciphersuite::HashFunction; +use std::collections::HashMap; use classic_mceliece_rust::keypair_boxed; -use libcrux_sha3; +use nym_kkt_ciphersuite::{DEFAULT_HASH_LEN, KEMKeyDigests}; use rand::{CryptoRng, RngCore}; pub fn generate_keypair_ed25519( @@ -72,26 +73,18 @@ pub fn hash_key_bytes( hash_length: usize, key_bytes: &[u8], ) -> Vec { - let mut hashed_key: Vec = vec![0u8; hash_length]; - match hash_function { - HashFunction::Blake3 => { - let mut hasher = blake3::Hasher::new(); - hasher.update(key_bytes); - hasher.finalize_xof().fill(&mut hashed_key); - hasher.reset(); - } - HashFunction::SHAKE256 => { - libcrux_sha3::shake256_ema(&mut hashed_key, key_bytes); - } - HashFunction::SHAKE128 => { - libcrux_sha3::shake128_ema(&mut hashed_key, key_bytes); - } - HashFunction::SHA256 => { - libcrux_sha3::sha256_ema(&mut hashed_key, key_bytes); - } - } + hash_function.digest(key_bytes, hash_length) +} - hashed_key +/// attempt to produce digests of the provided key using all known [HashFunction] with a default +/// hash length where variable output is available +pub fn produce_key_digests(key_bytes: &[u8]) -> KEMKeyDigests { + use strum::IntoEnumIterator; + let mut digests = HashMap::new(); + for hash in HashFunction::iter() { + digests.insert(hash, hash.digest(key_bytes, DEFAULT_HASH_LEN)); + } + digests } /// This does NOT run in constant time. diff --git a/common/nym-lp/src/error.rs b/common/nym-lp/src/error.rs index 290289fd0a..caedcc84e4 100644 --- a/common/nym-lp/src/error.rs +++ b/common/nym-lp/src/error.rs @@ -3,6 +3,7 @@ use crate::{noise_protocol::NoiseError, replay::ReplayError}; use nym_crypto::asymmetric::ed25519::Ed25519RecoveryError; +use nym_kkt::ciphersuite::{HashFunction, KEM}; use thiserror::Error; #[derive(Error, Debug)] @@ -93,4 +94,12 @@ pub enum LpError { #[error("attempted to create an LP responder without providing a valid KEM key")] ResponderWithMissingKEMKey, + + #[error( + "there are no known digests for remote's KEM key with {kem} KEM and {hash_function} hash function" + )] + NoKnownKEMKeyDigests { + kem: KEM, + hash_function: HashFunction, + }, } diff --git a/common/nym-lp/src/peer.rs b/common/nym-lp/src/peer.rs index 02a6372ea6..ee81fd5351 100644 --- a/common/nym-lp/src/peer.rs +++ b/common/nym-lp/src/peer.rs @@ -2,6 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 use nym_crypto::asymmetric::{ed25519, x25519}; +use nym_kkt::ciphersuite::{HashFunction, KEM}; +use std::collections::HashMap; use std::sync::Arc; /// Representation of a local Lewes Protocol peer @@ -44,18 +46,21 @@ impl LpLocalPeer { /// Convert this `LpLocalPeer` into a valid `LpRemotePeer` that can be used within tests #[doc(hidden)] pub fn as_remote(&self) -> LpRemotePeer { - let expected_kem_key_digest = match &self.kem_psq { - None => Vec::new(), - Some(kem_keys) => nym_kkt::key_utils::hash_key_bytes( - &nym_kkt::ciphersuite::HashFunction::Blake3, - nym_kkt::ciphersuite::DEFAULT_HASH_LEN, - kem_keys.public_key().as_bytes(), - ), + let expected_kem_key_digests = match &self.kem_psq { + None => HashMap::new(), + Some(kem_keys) => { + let hashes = + nym_kkt::key_utils::produce_key_digests(kem_keys.public_key().as_bytes()); + + let mut digests = HashMap::new(); + digests.insert(KEM::X25519, hashes); + digests + } }; LpRemotePeer { ed25519_public: *self.ed25519.public_key(), x25519_public: *self.x25519.public_key(), - expected_kem_key_digest, + expected_kem_key_digests, } } @@ -81,8 +86,7 @@ pub struct LpRemotePeer { pub(crate) x25519_public: x25519::PublicKey, /// Expected digest of the remote's KEM key - // TODO: this might have to be replaced by a HashMap> instead - pub(crate) expected_kem_key_digest: Vec, + pub(crate) expected_kem_key_digests: HashMap>>, } impl LpRemotePeer { @@ -90,7 +94,7 @@ impl LpRemotePeer { LpRemotePeer { ed25519_public, x25519_public, - expected_kem_key_digest: vec![], + expected_kem_key_digests: Default::default(), } } @@ -103,8 +107,11 @@ impl LpRemotePeer { } #[must_use] - pub fn with_kem_key_digest(mut self, expected_kem_key_digest: Vec) -> Self { - self.expected_kem_key_digest = expected_kem_key_digest; + pub fn with_kem_key_digests( + mut self, + expected_kem_key_digests: HashMap>>, + ) -> Self { + self.expected_kem_key_digests = expected_kem_key_digests; self } } diff --git a/common/nym-lp/src/session.rs b/common/nym-lp/src/session.rs index 58683f2176..7e7a9e1b41 100644 --- a/common/nym-lp/src/session.rs +++ b/common/nym-lp/src/session.rs @@ -20,6 +20,7 @@ use crate::{LpError, LpMessage, LpPacket}; use nym_crypto::asymmetric::x25519; use nym_kkt::KKT_RESPONSE_AAD; use nym_kkt::ciphersuite::{DecapsulationKey, EncapsulationKey}; +use nym_kkt::context::KKTContext; use nym_kkt::encryption::{ KKTSessionSecret, decrypt_initial_kkt_frame, decrypt_kkt_frame, encrypt_initial_kkt_frame, encrypt_kkt_frame, @@ -640,6 +641,23 @@ impl LpSession { } } + /// Attempt to retrieve expected KEM key hash of the remote + /// for [KEM] key type and [HashFunction] specified by own [KKTContext] + fn expected_kem_key_hash(&self, context: KKTContext) -> Result<&Vec, LpError> { + let kem = context.ciphersuite().kem(); + let hash_function = context.ciphersuite().hash_function(); + + let digests = self + .remote_peer + .expected_kem_key_digests + .get(&kem) + .ok_or(LpError::NoKnownKEMKeyDigests { kem, hash_function })?; + + digests + .get(&hash_function) + .ok_or(LpError::NoKnownKEMKeyDigests { kem, hash_function }) + } + /// Processes a KKT response from the responder. /// /// Decrypts and validates the response and stores the authenticated KEM public key @@ -674,12 +692,13 @@ impl LpSession { let remote_encapsulation_key = match decrypt_kkt_frame(&session_secret, encrypted_response_bytes, KKT_RESPONSE_AAD) { Ok((response_frame, remote_context)) => { + let expected_kem_key_digest = self.expected_kem_key_hash(context)?; match initiator_ingest_response( &mut context, &response_frame, &remote_context, &self.remote_peer.ed25519_public, - &self.remote_peer.expected_kem_key_digest, + expected_kem_key_digest, ) { Ok(remote_encapsulation_key) => remote_encapsulation_key, Err(e) => { diff --git a/common/registration/Cargo.toml b/common/registration/Cargo.toml index 530584e0e5..2320182701 100644 --- a/common/registration/Cargo.toml +++ b/common/registration/Cargo.toml @@ -23,6 +23,7 @@ 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" } [dev-dependencies] bincode.workspace = true diff --git a/common/registration/src/lib.rs b/common/registration/src/lib.rs index 37ad465765..04fff700c0 100644 --- a/common/registration/src/lib.rs +++ b/common/registration/src/lib.rs @@ -1,6 +1,7 @@ // Copyright 2025 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use std::collections::HashMap; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; use nym_authenticator_requests::AuthenticatorVersion; @@ -15,6 +16,7 @@ mod serialisation; pub use lp_messages::{ LpGatewayData, LpRegistrationRequest, LpRegistrationResponse, RegistrationMode, }; +use nym_kkt_ciphersuite::{KEM, KEMKeyDigests}; pub use serialisation::BincodeError; #[derive(Debug, Clone)] @@ -35,11 +37,10 @@ pub struct GatewayData { pub private_ipv6: Ipv6Addr, } -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug)] pub struct LpData { pub address: SocketAddr, - // TODO: modify it into a map once we know more about the PSQv2 structure - pub expected_kem_key_hash: Vec, + pub expected_kem_key_hashes: HashMap, } #[derive(Clone, Copy, Debug)] diff --git a/nym-node/nym-node-requests/Cargo.toml b/nym-node/nym-node-requests/Cargo.toml index 14a8403379..ec7b6822c8 100644 --- a/nym-node/nym-node-requests/Cargo.toml +++ b/nym-node/nym-node-requests/Cargo.toml @@ -31,6 +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" } # 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 4e668e5d75..7f41cb9a42 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 @@ -72,6 +72,28 @@ pub enum LPKEM { McEliece, } +impl From for nym_kkt_ciphersuite::KEM { + fn from(lpkem: LPKEM) -> Self { + match lpkem { + LPKEM::MlKem768 => nym_kkt_ciphersuite::KEM::MlKem768, + LPKEM::XWing => nym_kkt_ciphersuite::KEM::XWing, + LPKEM::X25519 => nym_kkt_ciphersuite::KEM::X25519, + LPKEM::McEliece => nym_kkt_ciphersuite::KEM::McEliece, + } + } +} + +impl From for LPKEM { + fn from(kem: nym_kkt_ciphersuite::KEM) -> Self { + match kem { + nym_kkt_ciphersuite::KEM::MlKem768 => LPKEM::MlKem768, + nym_kkt_ciphersuite::KEM::XWing => LPKEM::XWing, + nym_kkt_ciphersuite::KEM::X25519 => LPKEM::X25519, + nym_kkt_ciphersuite::KEM::McEliece => LPKEM::McEliece, + } + } +} + #[derive( Serialize, Deserialize, @@ -96,6 +118,28 @@ pub enum LPHashFunction { Sha256, } +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::Sha256 => nym_kkt_ciphersuite::HashFunction::SHA256, + } + } +} + +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::SHA256 => LPHashFunction::Sha256, + } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/nym-node/src/node/mod.rs b/nym-node/src/node/mod.rs index a46ae3e7da..0167b0a253 100644 --- a/nym-node/src/node/mod.rs +++ b/nym-node/src/node/mod.rs @@ -42,8 +42,7 @@ use nym_bin_common::bin_info; use nym_credential_verification::UpgradeModeState; use nym_crypto::asymmetric::{ed25519, x25519}; use nym_gateway::node::{ActiveClientsStore, GatewayTasksBuilder, UpgradeModeCheckRequestSender}; -use nym_kkt::ciphersuite::{DEFAULT_HASH_LEN, HashFunction}; -use nym_kkt::key_utils::hash_encapsulation_key; +use nym_kkt::key_utils::produce_key_digests; use nym_mixnet_client::client::ActiveConnections; use nym_mixnet_client::forwarder::MixForwardingSender; use nym_network_requester::{ @@ -777,28 +776,16 @@ impl NymNode { fn compute_kem_key_hashes(&self) -> (LPKEM, HashMap>) { let kem = LPKEM::X25519; - let mut hashes = HashMap::new(); let kem_key_bytes = self.entry_gateway.psq_kem_key.public_key().as_bytes(); - hashes.insert( - LPHashFunction::Blake3, - hash_encapsulation_key(&HashFunction::Blake3, DEFAULT_HASH_LEN, kem_key_bytes), - ); - hashes.insert( - LPHashFunction::Shake128, - hash_encapsulation_key(&HashFunction::SHAKE128, DEFAULT_HASH_LEN, kem_key_bytes), - ); - hashes.insert( - LPHashFunction::Shake256, - hash_encapsulation_key(&HashFunction::SHAKE256, DEFAULT_HASH_LEN, kem_key_bytes), - ); - hashes.insert( - LPHashFunction::Sha256, - hash_encapsulation_key(&HashFunction::SHA256, DEFAULT_HASH_LEN, kem_key_bytes), - ); + // convert from `nym_kkt_ciphersuite` types into `nym_nodes_requests` + let digests = produce_key_digests(kem_key_bytes.as_ref()) + .into_iter() + .map(|(f, d)| (f.into(), d)) + .collect(); - (kem, hashes) + (kem, digests) } pub(crate) async fn build_http_server(&self) -> Result { diff --git a/nym-registration-client/src/lib.rs b/nym-registration-client/src/lib.rs index 796d56de74..fc2489909c 100644 --- a/nym-registration-client/src/lib.rs +++ b/nym-registration-client/src/lib.rs @@ -194,10 +194,10 @@ impl RegistrationClient { .map_err(|_| RegistrationClientError::X25519PubkeyConversionFailure)?; let entry_peer = LpRemotePeer::new(self.config.entry.node.identity, entry_x25519_public) - .with_kem_key_digest(entry_lp_data.expected_kem_key_hash); + .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_digest(exit_lp_data.expected_kem_key_hash); + .with_kem_key_digests(exit_lp_data.expected_kem_key_hashes); // STEP 2: Establish outer session with entry gateway // This creates the LP session that will be used to forward packets to exit. diff --git a/nym-wallet/Cargo.lock b/nym-wallet/Cargo.lock index 047a50110b..bc4614b5ef 100644 --- a/nym-wallet/Cargo.lock +++ b/nym-wallet/Cargo.lock @@ -4181,18 +4181,19 @@ dependencies = [ [[package]] name = "num_enum" -version = "0.7.3" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" +checksum = "b1207a7e20ad57b847bbddc6776b968420d38292bbfe2089accff5e19e82454c" dependencies = [ "num_enum_derive", + "rustversion", ] [[package]] name = "num_enum_derive" -version = "0.7.3" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" +checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7" dependencies = [ "proc-macro-crate 3.3.0", "proc-macro2", @@ -4489,6 +4490,16 @@ dependencies = [ "tracing", ] +[[package]] +name = "nym-kkt-ciphersuite" +version = "1.20.1" +dependencies = [ + "num_enum", + "strum", + "strum_macros", + "thiserror 2.0.12", +] + [[package]] name = "nym-mixnet-contract-common" version = "1.20.1" @@ -4535,6 +4546,8 @@ dependencies = [ "regex", "schemars", "serde", + "serde_json", + "tracing", "url", "utoipa", ] @@ -4550,6 +4563,7 @@ dependencies = [ "nym-crypto", "nym-exit-policy", "nym-http-api-client", + "nym-kkt-ciphersuite", "nym-noise-keys", "nym-upgrade-mode-check", "nym-wireguard-types", diff --git a/tools/nym-lp-client/Cargo.toml b/tools/nym-lp-client/Cargo.toml index 188b123a9a..1c6b18fc43 100644 --- a/tools/nym-lp-client/Cargo.toml +++ b/tools/nym-lp-client/Cargo.toml @@ -28,6 +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-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 fe78da54b8..744bc14dcb 100644 --- a/tools/nym-lp-client/src/client.rs +++ b/tools/nym-lp-client/src/client.rs @@ -120,7 +120,7 @@ impl SpeedtestClient { let client_ip = "0.0.0.0".parse()?; let gw_peer = LpRemotePeer::new(self.gateway.identity, self.gateway.identity.to_x25519()?) - .with_kem_key_digest(self.gateway.kem_key_hash.clone()); + .with_kem_key_digests(self.gateway.kem_key_hashes.clone()); let mut lp_client = LpRegistrationClient::::new_with_default_config( self.identity_keypair.clone(), @@ -166,7 +166,7 @@ impl SpeedtestClient { let client_ip = "0.0.0.0".parse()?; let gw_peer = LpRemotePeer::new(self.gateway.identity, self.gateway.identity.to_x25519()?) - .with_kem_key_digest(self.gateway.kem_key_hash.clone()); + .with_kem_key_digests(self.gateway.kem_key_hashes.clone()); let mut lp_client = LpRegistrationClient::new_with_default_config( self.identity_keypair.clone(), diff --git a/tools/nym-lp-client/src/topology.rs b/tools/nym-lp-client/src/topology.rs index cbc5a35699..ebf3d8997f 100644 --- a/tools/nym-lp-client/src/topology.rs +++ b/tools/nym-lp-client/src/topology.rs @@ -6,14 +6,17 @@ #![allow(unused)] use anyhow::{anyhow, bail, Context, Result}; +use nym_api_requests::models::{LPHashFunction, LPKEM}; use nym_api_requests::nym_nodes::SkimmedNode; use nym_crypto::asymmetric::ed25519; use nym_http_api_client::UserAgent; +use nym_kkt_ciphersuite::{KEMKeyDigests, KEM}; use nym_sphinx_types::Node as SphinxNode; use nym_topology::{NymRouteProvider, NymTopology, NymTopologyMetadata}; use nym_validator_client::nym_api::NymApiClientExt; use rand::prelude::IteratorRandom; use rand::{CryptoRng, Rng}; +use std::collections::HashMap; use std::net::SocketAddr; use tracing::{debug, info}; use url::Url; @@ -27,7 +30,7 @@ const LP_DATA_PORT: u16 = 51264; #[derive(Debug, Clone)] pub struct GatewayInfo { pub identity: ed25519::PublicKey, - pub kem_key_hash: Vec, + pub kem_key_hashes: HashMap, pub sphinx_key: nym_crypto::asymmetric::x25519::PublicKey, /// Mix host (IP:port for Sphinx mixing) From e2be2b0b34ee483947318911f1733b69d7925218 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Fri, 23 Jan 2026 13:21:52 +0000 Subject: [PATCH 21/51] LP: mixnet reg fixes (#6356) * removed x25519 key used within LP mixnet registration * use Vec rather than BytesMut for LpAction::DeliverData * introduced an explicit kind prefix for raw data sent and received within LP * review nits --- Cargo.lock | 1 + common/nym-lp/src/session.rs | 7 +- common/nym-lp/src/session_integration/mod.rs | 46 ++- common/nym-lp/src/state_machine.rs | 137 +++++++-- common/registration/Cargo.toml | 1 + common/registration/src/lib.rs | 3 +- common/registration/src/lp_messages.rs | 145 ++++----- gateway/src/node/lp_listener/data_handler.rs | 2 +- gateway/src/node/lp_listener/handler.rs | 194 ++++++------ gateway/src/node/lp_listener/messages.rs | 10 - gateway/src/node/lp_listener/mod.rs | 3 +- gateway/src/node/lp_listener/registration.rs | 276 ++++++++---------- integration-tests/src/lp_registration.rs | 2 +- nym-gateway-probe/src/lib.rs | 2 - nym-registration-client/src/lib.rs | 1 - .../src/lp_client/client.rs | 66 ++--- .../src/lp_client/helpers.rs | 83 ++++++ nym-registration-client/src/lp_client/mod.rs | 1 + .../src/lp_client/nested_session.rs | 84 ++---- sdk/rust/nym-sdk/src/mixnet/socks5_client.rs | 29 +- 20 files changed, 558 insertions(+), 535 deletions(-) delete mode 100644 gateway/src/node/lp_listener/messages.rs create mode 100644 nym-registration-client/src/lp_client/helpers.rs diff --git a/Cargo.lock b/Cargo.lock index f4042c9595..1868f7b6c1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7520,6 +7520,7 @@ dependencies = [ "nym-ip-packet-requests", "nym-kkt-ciphersuite", "nym-sphinx", + "nym-test-utils", "nym-wireguard-types", "serde", "time", diff --git a/common/nym-lp/src/session.rs b/common/nym-lp/src/session.rs index 7e7a9e1b41..a6561eb199 100644 --- a/common/nym-lp/src/session.rs +++ b/common/nym-lp/src/session.rs @@ -17,7 +17,7 @@ use crate::psk::{ }; use crate::replay::ReceivingKeyCounterValidator; use crate::{LpError, LpMessage, LpPacket}; -use nym_crypto::asymmetric::x25519; +use nym_crypto::asymmetric::{ed25519, x25519}; use nym_kkt::KKT_RESPONSE_AAD; use nym_kkt::ciphersuite::{DecapsulationKey, EncapsulationKey}; use nym_kkt::context::KKTContext; @@ -298,6 +298,11 @@ impl LpSession { *self.local_peer.x25519.public_key() } + /// Returns the remote ed25519 public key. + pub fn remote_ed25519_public(&self) -> ed25519::PublicKey { + self.remote_peer.ed25519_public + } + /// Returns the remote X25519 public key. /// /// Used for tie-breaking in simultaneous subsession initiation. diff --git a/common/nym-lp/src/session_integration/mod.rs b/common/nym-lp/src/session_integration/mod.rs index 833634e670..7733b5459f 100644 --- a/common/nym-lp/src/session_integration/mod.rs +++ b/common/nym-lp/src/session_integration/mod.rs @@ -734,13 +734,13 @@ mod tests { // Remove unused imports if SessionManager methods are no longer direct dependencies // use crate::noise_protocol::{create_noise_state, create_noise_state_responder}; use crate::peer::mock_peers; + use crate::state_machine::LpData; use crate::{ // Bring in state machine types state_machine::{LpAction, LpInput, LpStateBare}, // message::LpMessage, // LpMessage likely still needed for LpInput/LpAction // packet::{LpHeader, LpPacket, TRAILER_LEN}, // LpPacket needed for LpAction/LpInput }; - use bytes::Bytes; // Use Bytes for SendData input // Keep helper function for creating test packets if needed, @@ -1058,13 +1058,13 @@ mod tests { // --- 5. Simulate Data Transfer via process_input --- println!("Starting data transfer simulation via process_input..."); - let plaintext_a_to_b = b"Hello from A via process_input!"; - let plaintext_b_to_a = b"Hello from B via process_input!"; + let plaintext_a_to_b = LpData::new_opaque(b"Hello from A via process_input!".to_vec()); + let plaintext_b_to_a = LpData::new_opaque(b"Hello from B via process_input!".to_vec()); // --- A sends to B --- println!(" A sends to B"); let action_a_send = session_manager_1 - .process_input(receiver_index, LpInput::SendData(plaintext_a_to_b.to_vec())) + .process_input(receiver_index, LpInput::SendData(plaintext_a_to_b.clone())) .expect("A SendData should produce action") .expect("A SendData failed"); @@ -1087,14 +1087,10 @@ mod tests { .expect("B ReceivePacket (data) failed"); if let LpAction::DeliverData(data) = action_b_recv { - assert_eq!( - data, - Bytes::copy_from_slice(plaintext_a_to_b), - "Decrypted data mismatch A->B" - ); + assert_eq!(data, plaintext_a_to_b, "Decrypted data mismatch A->B"); println!( " B successfully decrypted: {:?}", - String::from_utf8_lossy(&data) + String::from_utf8_lossy(&data.content) ); } else { panic!("B ReceivePacket did not produce DeliverData"); @@ -1103,7 +1099,7 @@ mod tests { // --- B sends to A --- println!(" B sends to A"); let action_b_send = session_manager_2 - .process_input(receiver_index, LpInput::SendData(plaintext_b_to_a.to_vec())) + .process_input(receiver_index, LpInput::SendData(plaintext_b_to_a.clone())) .expect("B SendData should produce action") .expect("B SendData failed"); @@ -1128,14 +1124,10 @@ mod tests { .expect("A ReceivePacket (data) failed"); if let LpAction::DeliverData(data) = action_a_recv { - assert_eq!( - data, - Bytes::copy_from_slice(plaintext_b_to_a), - "Decrypted data mismatch B->A" - ); + assert_eq!(data, plaintext_b_to_a, "Decrypted data mismatch B->A"); println!( " A successfully decrypted: {:?}", - String::from_utf8_lossy(&data) + String::from_utf8_lossy(&data.content) ); } else { panic!("A ReceivePacket did not produce DeliverData"); @@ -1160,11 +1152,11 @@ mod tests { println!("Testing out-of-order reception via process_input..."); // A prepares N+1 then N - let data_n_plus_1 = Bytes::from_static(b"Message N+1"); - let data_n = Bytes::from_static(b"Message N"); + let data_n_plus_1 = LpData::new_opaque(b"Message N+1".to_vec()); + let data_n = LpData::new_opaque(b"Message N".to_vec()); let action_send_n1 = session_manager_1 - .process_input(receiver_index, LpInput::SendData(data_n_plus_1.to_vec())) + .process_input(receiver_index, LpInput::SendData(data_n_plus_1.clone())) .unwrap() .unwrap(); let packet_n1 = match action_send_n1 { @@ -1173,7 +1165,7 @@ mod tests { }; let action_send_n = session_manager_1 - .process_input(receiver_index, LpInput::SendData(data_n.to_vec())) + .process_input(receiver_index, LpInput::SendData(data_n.clone())) .unwrap() .unwrap(); let packet_n = match action_send_n { @@ -1230,8 +1222,10 @@ mod tests { ); // Further actions on A fail - let send_after_close_a = - session_manager_1.process_input(receiver_index, LpInput::SendData(b"fail".to_vec())); + let send_after_close_a = session_manager_1.process_input( + receiver_index, + LpInput::SendData(LpData::new_opaque(b"fail".to_vec())), + ); assert!(send_after_close_a.is_err()); assert!(matches!( send_after_close_a.err().unwrap(), @@ -1250,8 +1244,10 @@ mod tests { ); // Further actions on B fail - let send_after_close_b = - session_manager_2.process_input(receiver_index, LpInput::SendData(b"fail".to_vec())); + let send_after_close_b = session_manager_2.process_input( + receiver_index, + LpInput::SendData(LpData::new_opaque(b"fail".to_vec())), + ); assert!(send_after_close_b.is_err()); assert!(matches!( send_after_close_b.err().unwrap(), diff --git a/common/nym-lp/src/state_machine.rs b/common/nym-lp/src/state_machine.rs index 5d91df2b71..9bf3541c6a 100644 --- a/common/nym-lp/src/state_machine.rs +++ b/common/nym-lp/src/state_machine.rs @@ -21,7 +21,8 @@ use crate::{ packet::LpPacket, session::{LpSession, SubsessionHandshake}, }; -use bytes::BytesMut; +use bytes::{Buf, Bytes}; +use num_enum::{IntoPrimitive, TryFromPrimitive}; use std::mem; use tracing::debug; @@ -97,7 +98,7 @@ pub enum LpInput { /// Received an LP Packet from the network. ReceivePacket(LpPacket), /// Application wants to send data (only valid in Transport state). - SendData(Vec), // Using Bytes for efficiency + SendData(LpData), /// Close the connection. Close, /// Initiate a subsession handshake (only valid in Transport state). @@ -111,7 +112,7 @@ pub enum LpAction { /// Send an LP Packet over the network. SendPacket(LpPacket), /// Deliver decrypted application data received from the peer. - DeliverData(BytesMut), + DeliverData(LpData), /// Inform the environment that KKT exchange completed successfully. KKTComplete, /// Inform the environment that the handshake is complete. @@ -134,6 +135,75 @@ pub enum LpAction { }, } +/// Represent application data being sent in Transport mode +#[derive(Debug, Clone, PartialEq)] +pub struct LpData { + pub kind: LpDataKind, + pub content: Bytes, +} + +impl AsRef<[u8]> for LpData { + fn as_ref(&self) -> &[u8] { + &self.content + } +} + +impl LpData { + pub fn new(kind: LpDataKind, content: impl Into) -> Self { + Self { + kind, + content: content.into(), + } + } + pub fn new_opaque(content: impl Into) -> Self { + Self::new(LpDataKind::Opaque, content) + } + + pub fn new_registration(data: impl Into) -> Self { + Self::new(LpDataKind::Registration, data) + } + + pub fn new_forward(data: impl Into) -> Self { + Self::new(LpDataKind::Forward, data) + } + + pub fn to_vec(self) -> Vec { + self.into() + } +} + +impl From for Vec { + fn from(data: LpData) -> Self { + let mut out = Vec::with_capacity(data.content.len() + 1); + out.push(data.kind as u8); + out.extend_from_slice(data.content.as_ref()); + out + } +} + +impl TryFrom> for LpData { + type Error = LpError; + + fn try_from(value: Vec) -> Result { + let kind = LpDataKind::try_from(value[0]).map_err(|_| { + LpError::DeserializationError(format!("unknown data type: {}", value[0])) + })?; + let mut content = Bytes::from(value); + content.advance(1); + + Ok(LpData::new(kind, content)) + } +} + +/// Represent kind of application data being sent in Transport mode +#[derive(Clone, Copy, PartialEq, Eq, Debug, IntoPrimitive, TryFromPrimitive)] +#[repr(u8)] +pub enum LpDataKind { + Opaque = 0, + Registration = 1, + Forward = 2, +} + /// The Lewes Protocol State Machine. pub struct LpStateMachine { pub state: LpState, @@ -551,8 +621,17 @@ impl LpStateMachine { LpState::Transport { session } } else { // 4. Deliver data - result_action = Some(Ok(LpAction::DeliverData(BytesMut::from(plaintext.as_slice())))); - LpState::Transport { session } + match plaintext.try_into() { + Ok(data) => { + result_action = Some(Ok(LpAction::DeliverData(data))); + LpState::Transport { session } + }, + Err(e) => { + let reason = e.to_string(); + result_action = Some(Err(e)); + LpState::Closed { reason } + } + } } } Err(e) => { @@ -586,7 +665,7 @@ impl LpStateMachine { } (LpState::Transport { session }, LpInput::SendData(data)) => { // Encrypt and send application data - match self.prepare_data_packet(&session, &data) { + match self.prepare_data_packet(&session, data) { Ok(packet) => result_action = Some(Ok(LpAction::SendPacket(packet))), Err(e) => { // If prepare fails, should we close? Let's report error and stay Transport for now. @@ -823,8 +902,16 @@ impl LpStateMachine { result_action = Some(Err(e)); LpState::SubsessionHandshaking { session, subsession } } else { - result_action = Some(Ok(LpAction::DeliverData(BytesMut::from(plaintext.as_slice())))); - LpState::SubsessionHandshaking { session, subsession } + match plaintext.try_into() { + Ok(data) => { + result_action = Some(Ok(LpAction::DeliverData(data))); + LpState::SubsessionHandshaking { session, subsession } + } + Err(err) => { + result_action = Some(Err(err)); + LpState::SubsessionHandshaking { session, subsession } + } + } } } Err(e) => { @@ -890,7 +977,7 @@ impl LpStateMachine { // Parent can still send data during subsession handshake (LpState::SubsessionHandshaking { session, subsession }, LpInput::SendData(data)) => { - match self.prepare_data_packet(&session, &data) { + match self.prepare_data_packet(&session, data) { Ok(packet) => result_action = Some(Ok(LpAction::SendPacket(packet))), Err(e) => { result_action = Some(Err(e.into())); @@ -932,8 +1019,16 @@ impl LpStateMachine { result_action = Some(Err(e)); LpState::ReadOnlyTransport { session } } else { - result_action = Some(Ok(LpAction::DeliverData(BytesMut::from(plaintext.as_slice())))); - LpState::ReadOnlyTransport { session } + match plaintext.try_into() { + Ok(data) => { + result_action = Some(Ok(LpAction::DeliverData(data))); + LpState::ReadOnlyTransport { session } + } + Err(err) => { + result_action = Some(Err(err)); + LpState::ReadOnlyTransport { session } + } + } } } Err(e) => { @@ -1037,9 +1132,9 @@ impl LpStateMachine { fn prepare_data_packet( &self, session: &LpSession, - data: &[u8], + data: LpData, ) -> Result { - let encrypted_message = session.encrypt_data(data)?; + let encrypted_message = session.encrypt_data(Vec::::from(data).as_ref())?; session .next_packet(encrypted_message) .map_err(|e| NoiseError::Other(e.to_string())) // Improve error conversion? @@ -1050,7 +1145,6 @@ impl LpStateMachine { mod tests { use super::*; use crate::peer::mock_peers; - use bytes::Bytes; #[test] fn test_state_machine_init() { @@ -1233,8 +1327,8 @@ mod tests { // --- Transport Phase --- println!("--- Step 8: Initiator sends data ---"); - let data_to_send_1 = b"hello responder"; - let init_actions_4 = initiator.process_input(LpInput::SendData(data_to_send_1.to_vec())); + let data_to_send_1 = LpData::new_opaque(b"hello responder".to_vec()); + let init_actions_4 = initiator.process_input(LpInput::SendData(data_to_send_1.clone())); let data_packet_1 = if let Some(Ok(LpAction::SendPacket(packet))) = init_actions_4 { packet.clone() } else { @@ -1249,11 +1343,11 @@ mod tests { } else { panic!("Responder should deliver data"); }; - assert_eq!(resp_data_1, Bytes::copy_from_slice(data_to_send_1)); + assert_eq!(resp_data_1, data_to_send_1); println!("--- Step 10: Responder sends data ---"); - let data_to_send_2 = b"hello initiator"; - let resp_actions_6 = responder.process_input(LpInput::SendData(data_to_send_2.to_vec())); + let data_to_send_2 = LpData::new_opaque(b"hello initiator".to_vec()); + let resp_actions_6 = responder.process_input(LpInput::SendData(data_to_send_2.clone())); let data_packet_2 = if let Some(Ok(LpAction::SendPacket(packet))) = resp_actions_6 { packet.clone() } else { @@ -1264,7 +1358,7 @@ mod tests { println!("--- Step 11: Initiator receives data ---"); let init_actions_5 = initiator.process_input(LpInput::ReceivePacket(data_packet_2)); if let Some(Ok(LpAction::DeliverData(data))) = init_actions_5 { - assert_eq!(data, Bytes::copy_from_slice(data_to_send_2)); + assert_eq!(data, data_to_send_2); } else { panic!("Initiator should deliver data"); } @@ -1414,7 +1508,8 @@ mod tests { assert!(matches!(initiator.state, LpState::KKTExchange { .. })); // Try SendData during KKT exchange (should be rejected) - let send_action = initiator.process_input(LpInput::SendData(vec![1, 2, 3])); + let send_data = LpData::new_opaque(vec![1, 2, 3]); + let send_action = initiator.process_input(LpInput::SendData(send_data)); assert!(matches!( send_action, Some(Err(LpError::InvalidStateTransition { .. })) diff --git a/common/registration/Cargo.toml b/common/registration/Cargo.toml index 2320182701..68dd0ad00c 100644 --- a/common/registration/Cargo.toml +++ b/common/registration/Cargo.toml @@ -28,3 +28,4 @@ nym-kkt-ciphersuite = { path = "../nym-kkt-ciphersuite" } [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 04fff700c0..665b5a7d68 100644 --- a/common/registration/src/lib.rs +++ b/common/registration/src/lib.rs @@ -14,7 +14,8 @@ mod lp_messages; mod serialisation; pub use lp_messages::{ - LpGatewayData, LpRegistrationRequest, LpRegistrationResponse, RegistrationMode, + LpDvpnRegistrationRequest, LpMixnetGatewayData, LpMixnetRegistrationRequest, + LpRegistrationData, LpRegistrationRequest, LpRegistrationResponse, RegistrationMode, }; use nym_kkt_ciphersuite::{KEM, KEMKeyDigests}; pub use serialisation::BincodeError; diff --git a/common/registration/src/lp_messages.rs b/common/registration/src/lp_messages.rs index 6b54532c6f..3d62ea2180 100644 --- a/common/registration/src/lp_messages.rs +++ b/common/registration/src/lp_messages.rs @@ -3,17 +3,45 @@ //! LP (Lewes Protocol) registration message types shared between client and gateway. -use nym_credentials_interface::{CredentialSpendingData, TicketType}; -use serde::{Deserialize, Serialize}; -use std::net::IpAddr; - use crate::GatewayData; use crate::serialisation::{BincodeError, BincodeOptions, lp_bincode_serializer}; +use nym_credentials_interface::{CredentialSpendingData, TicketType}; +use nym_crypto::asymmetric::ed25519; +use serde::{Deserialize, Serialize}; /// Registration request sent by client after LP handshake /// Aligned with existing authenticator registration flow #[derive(Debug, Clone, Serialize, Deserialize)] pub struct LpRegistrationRequest { + /// Mode specific registration data + pub registration_data: LpRegistrationData, + + /// Unix timestamp for replay protection + pub timestamp: u64, +} + +impl LpRegistrationRequest { + pub fn mode(&self) -> RegistrationMode { + match self.registration_data { + LpRegistrationData::Dvpn { .. } => RegistrationMode::Dvpn, + LpRegistrationData::Mixnet { .. } => RegistrationMode::Mixnet, + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum LpRegistrationData { + /// dVPN mode - register as WireGuard peer (most common) + Dvpn { + data: Box, + }, + + /// Mixnet mode - register for mixnet routing via IPR + Mixnet { data: LpMixnetRegistrationRequest }, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct LpDvpnRegistrationRequest { /// Client's WireGuard public key (for dVPN mode) pub wg_public_key: nym_wireguard_types::PeerPublicKey, @@ -22,15 +50,14 @@ pub struct LpRegistrationRequest { /// Ticket type for bandwidth allocation pub ticket_type: TicketType, +} - /// Registration mode - pub mode: RegistrationMode, - - /// Client's IP address (for tracking/metrics) - pub client_ip: IpAddr, - - /// Unix timestamp for replay protection - pub timestamp: u64, +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct LpMixnetRegistrationRequest { + /// Client's ed25519 public key (identity) + /// + /// Used to derive DestinationAddressBytes for ActiveClientsStore lookup. + pub client_ed25519_pubkey: ed25519::PublicKey, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -39,22 +66,7 @@ pub enum RegistrationMode { Dvpn, /// Mixnet mode - register for mixnet routing via IPR - /// - /// Client provides identity and encryption keys for nym address derivation. - /// Gateway stores client in ActiveClientsStore for SURB reply delivery. - Mixnet { - /// Client's ed25519 public key (identity) - /// - /// Used to derive DestinationAddressBytes for ActiveClientsStore lookup. - /// Must match the key used in LP handshake for authentication. - client_ed25519_pubkey: [u8; 32], - - /// Client's x25519 public key (encryption) - /// - /// Used for SURB reply encryption. Combined with ed25519 identity - /// and gateway identity to form the full nym Recipient address. - client_x25519_pubkey: [u8; 32], - }, + Mixnet, } /// Gateway data for mixnet mode registration @@ -62,16 +74,12 @@ pub enum RegistrationMode { /// Contains the gateway's identity and sphinx key needed for the client /// to construct its full nym Recipient address. #[derive(Debug, Clone, Serialize, Deserialize)] -pub struct LpGatewayData { +pub struct LpMixnetGatewayData { /// Gateway's ed25519 identity public key /// /// Forms part of the client's nym Recipient address. - pub gateway_identity: [u8; 32], - - /// Gateway's x25519 sphinx public key - /// - /// Used by the client for Sphinx packet construction. - pub gateway_sphinx_key: [u8; 32], + pub gateway_identity: ed25519::PublicKey, + // TODO: what we really need in here is the address of internal IPR } /// Registration response from gateway @@ -92,7 +100,7 @@ pub struct LpRegistrationResponse { /// /// Contains gateway identity and sphinx key needed for nym address construction. /// Only populated for Mixnet mode registrations. - pub lp_gateway_data: Option, + pub lp_gateway_data: Option, /// Allocated bandwidth in bytes pub allocated_bandwidth: i64, @@ -104,14 +112,15 @@ impl LpRegistrationRequest { wg_public_key: nym_wireguard_types::PeerPublicKey, credential: CredentialSpendingData, ticket_type: TicketType, - client_ip: IpAddr, ) -> Self { Self { - wg_public_key, - credential, - ticket_type, - mode: RegistrationMode::Dvpn, - client_ip, + registration_data: LpRegistrationData::Dvpn { + data: Box::new(LpDvpnRegistrationRequest { + wg_public_key, + credential, + ticket_type, + }), + }, #[allow(clippy::expect_used)] timestamp: std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) @@ -154,7 +163,7 @@ impl LpRegistrationResponse { } /// Create a success response for mixnet mode with LpGatewayData - pub fn success_mixnet(allocated_bandwidth: i64, lp_gateway_data: LpGatewayData) -> Self { + pub fn success_mixnet(allocated_bandwidth: i64, lp_gateway_data: LpMixnetGatewayData) -> Self { Self { success: true, error: None, @@ -189,8 +198,8 @@ impl LpRegistrationResponse { #[cfg(test)] mod tests { use super::*; + use nym_test_utils::helpers::deterministic_rng; use std::net::Ipv4Addr; - // ==================== Helper Functions ==================== fn create_test_gateway_data() -> GatewayData { @@ -242,49 +251,14 @@ mod tests { assert!(response.gateway_data.is_none()); assert_eq!(response.allocated_bandwidth, 0); } - // ==================== RegistrationMode Tests ==================== - - #[test] - fn test_registration_mode_serialize_dvpn() { - let mode = RegistrationMode::Dvpn; - - let serialized = bincode::serialize(&mode).expect("Failed to serialize mode"); - let deserialized: RegistrationMode = - bincode::deserialize(&serialized).expect("Failed to deserialize mode"); - - assert!(matches!(deserialized, RegistrationMode::Dvpn)); - } - - #[test] - fn test_registration_mode_serialize_mixnet() { - let client_ed25519_pubkey = [99u8; 32]; - let client_x25519_pubkey = [88u8; 32]; - let mode = RegistrationMode::Mixnet { - client_ed25519_pubkey, - client_x25519_pubkey, - }; - - let serialized = bincode::serialize(&mode).expect("Failed to serialize mode"); - let deserialized: RegistrationMode = - bincode::deserialize(&serialized).expect("Failed to deserialize mode"); - - match deserialized { - RegistrationMode::Mixnet { - client_ed25519_pubkey: ed25519, - client_x25519_pubkey: x25519, - } => { - assert_eq!(ed25519, client_ed25519_pubkey); - assert_eq!(x25519, client_x25519_pubkey); - } - _ => panic!("Expected Mixnet mode"), - } - } #[test] fn test_lp_registration_response_success_mixnet() { - let lp_gateway_data = LpGatewayData { - gateway_identity: [1u8; 32], - gateway_sphinx_key: [2u8; 32], + let mut rng = deterministic_rng(); + let valid_key = ed25519::KeyPair::new(&mut rng); + + let lp_gateway_data = LpMixnetGatewayData { + gateway_identity: *valid_key.public_key(), }; let allocated_bandwidth = 500_000_000; @@ -299,7 +273,6 @@ mod tests { let gw_data = response .lp_gateway_data .expect("LpGatewayData should be present"); - assert_eq!(gw_data.gateway_identity, [1u8; 32]); - assert_eq!(gw_data.gateway_sphinx_key, [2u8; 32]); + assert_eq!(gw_data.gateway_identity, *valid_key.public_key()); } } diff --git a/gateway/src/node/lp_listener/data_handler.rs b/gateway/src/node/lp_listener/data_handler.rs index eda6367aa2..2b2ce11c64 100644 --- a/gateway/src/node/lp_listener/data_handler.rs +++ b/gateway/src/node/lp_listener/data_handler.rs @@ -191,7 +191,7 @@ impl LpDataHandler { match action { LpAction::DeliverData(data) => { // Decrypted application data - forward as Sphinx packet - self.forward_sphinx_packet(&data).await?; + self.forward_sphinx_packet(&data.content).await?; inc!("lp_data_packets_forwarded"); Ok(()) } diff --git a/gateway/src/node/lp_listener/handler.rs b/gateway/src/node/lp_listener/handler.rs index 948d62a0b6..b2bfac5a91 100644 --- a/gateway/src/node/lp_listener/handler.rs +++ b/gateway/src/node/lp_listener/handler.rs @@ -1,18 +1,18 @@ // Copyright 2025 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use super::messages::LpRegistrationRequest; use super::registration::process_registration; use super::LpHandlerState; use crate::error::GatewayError; use nym_crypto::asymmetric::{ed25519, x25519}; -use nym_lp::state_machine::{LpAction, LpInput}; +use nym_lp::state_machine::{LpAction, LpData, LpDataKind, LpInput}; use nym_lp::{ codec::OuterAeadKey, message::ForwardPacketData, packet::LpHeader, LpMessage, LpPacket, OuterHeader, }; use nym_lp_transport::traits::LpTransport; use nym_metrics::{add_histogram_obs, inc}; +use nym_registration_common::LpRegistrationRequest; use std::net::SocketAddr; use std::time::Duration; use tokio::io::{AsyncReadExt, AsyncWriteExt}; @@ -535,16 +535,12 @@ where })? .map_err(|e| GatewayError::LpProtocolError(format!("State machine error: {}", e)))?; + let lp_session = state_machine.session().map_err(|e| { + GatewayError::LpProtocolError(format!("Session unavailable after processing: {}", e)) + })?; + // Get outer key before releasing borrow - let outer_key = state_machine - .session() - .map_err(|e| { - GatewayError::LpProtocolError(format!( - "Session unavailable after processing: {}", - e - )) - })? - .outer_aead_key(); + let outer_key = lp_session.outer_aead_key(); drop(state_entry); match action { @@ -562,8 +558,7 @@ where } LpAction::DeliverData(data) => { // Decrypted application data - process as registration/forwarding - self.handle_decrypted_payload(receiver_idx, data.to_vec()) - .await + self.handle_decrypted_payload(receiver_idx, data).await } LpAction::SubsessionComplete { packet: ready_packet, @@ -597,38 +592,41 @@ where async fn handle_decrypted_payload( &mut self, receiver_idx: u32, - decrypted_bytes: Vec, + decrypted_data: LpData, ) -> Result<(), GatewayError> { let remote = self.remote_addr; - // Try to deserialize as LpRegistrationRequest first (most common case after handshake) - if let Ok(request) = LpRegistrationRequest::try_deserialise(&decrypted_bytes) { - debug!( - "LP registration request from {remote} (receiver_idx={receiver_idx}): mode={:?}", - request.mode - ); - return self - .handle_registration_request(receiver_idx, request) - .await; - } + let bytes = decrypted_data.content; + match decrypted_data.kind { + LpDataKind::Registration => { + let request = LpRegistrationRequest::try_deserialise(&bytes).map_err(|err| { + GatewayError::LpProtocolError(format!("malformed LpRegistrationRequest: {err}")) + })?; - // Try to deserialize as ForwardPacketData (entry gateway forwarding to exit) - if let Ok(forward_data) = ForwardPacketData::decode(&decrypted_bytes) { - debug!( - "LP forward request from {remote} (receiver_idx={receiver_idx}) to {}", - forward_data.target_lp_address - ); - return self - .handle_forwarding_request(receiver_idx, forward_data) - .await; - } + debug!( + "LP registration request from {remote} (receiver_idx={receiver_idx}): mode={:?}", + request.mode()); - // Neither registration nor forwarding - unknown payload type - warn!("Unknown transport payload type from {remote} (receiver_idx={receiver_idx})"); - inc!("lp_errors_unknown_payload_type"); - Err(GatewayError::LpProtocolError( - "Unknown transport payload type (not registration or forwarding)".to_string(), - )) + self.handle_registration_request(receiver_idx, request) + .await + } + LpDataKind::Forward => { + let forward_data = ForwardPacketData::decode(&bytes).map_err(|err| { + GatewayError::LpProtocolError(format!("malformed ForwardPacketData: {err}")) + })?; + + self.handle_forwarding_request(receiver_idx, forward_data) + .await + } + LpDataKind::Opaque => { + // Neither registration nor forwarding - unknown payload type + warn!("Unknown transport payload type from {remote} (receiver_idx={receiver_idx}). dropping {} bytes", bytes.len()); + inc!("lp_errors_unknown_payload_type"); + Err(GatewayError::LpProtocolError( + "Unknown transport payload type (not registration or forwarding)".to_string(), + )) + } + } } /// Handle subsession completion - promote subsession to new session @@ -699,6 +697,48 @@ where Ok(()) } + /// Attempt to wrap and send specified response back to the client + async fn send_response_packet( + &mut self, + receiver_idx: u32, + serialised_response: Vec, + response_kind: LpDataKind, + ) -> Result<(), GatewayError> { + let session_entry = self + .state + .session_states + .get(&receiver_idx) + .ok_or_else(|| { + GatewayError::LpProtocolError(format!("Session not found: {receiver_idx}")) + })?; + + // Access session via state machine for subsession support + let session = session_entry + .value() + .state + .session() + .map_err(|e| GatewayError::LpProtocolError(format!("Session error: {e}")))?; + + let wrapped_lp_data = LpData::new(response_kind, serialised_response); + let data_bytes = wrapped_lp_data.to_vec(); + + let encrypted_message = session.encrypt_data(&data_bytes).map_err(|e| { + GatewayError::LpProtocolError(format!("Failed to encrypt response: {e}")) + })?; + + let response_packet = session.next_packet(encrypted_message).map_err(|e| { + GatewayError::LpProtocolError(format!("Failed to create response packet: {e}")) + })?; + + let outer_key = session.outer_aead_key(); + drop(session_entry); + + // Send response (encrypted with outer AEAD) + self.send_lp_packet(response_packet, outer_key.as_ref()) + .await?; + Ok(()) + } + /// Handle registration request on an established session async fn handle_registration_request( &mut self, @@ -707,43 +747,11 @@ where ) -> Result<(), GatewayError> { // Process registration (might modify state) let response = process_registration(request, &self.state).await; + let response_bytes = response.serialise().map_err(|e| { + GatewayError::LpProtocolError(format!("Failed to serialize response: {e}")) + })?; - // Acquire session lock for encryption and get outer AEAD key - let (response_packet, outer_key) = { - let session_entry = self - .state - .session_states - .get(&receiver_idx) - .ok_or_else(|| { - GatewayError::LpProtocolError(format!("Session not found: {}", receiver_idx)) - })?; - // Access session via state machine for subsession support - let session = session_entry - .value() - .state - .session() - .map_err(|e| GatewayError::LpProtocolError(format!("Session error: {}", e)))?; - - // Serialize and encrypt response - let response_bytes = response.serialise().map_err(|e| { - GatewayError::LpProtocolError(format!("Failed to serialize response: {}", e)) - })?; - - let encrypted_message = session.encrypt_data(&response_bytes).map_err(|e| { - GatewayError::LpProtocolError(format!("Failed to encrypt response: {}", e)) - })?; - - let packet = session.next_packet(encrypted_message).map_err(|e| { - GatewayError::LpProtocolError(format!("Failed to create response packet: {}", e)) - })?; - - // Get outer AEAD key for packet encryption - let outer_key = session.outer_aead_key(); - (packet, outer_key) - }; - - // Send response (encrypted with outer AEAD) - self.send_lp_packet(response_packet, outer_key.as_ref()) + self.send_response_packet(receiver_idx, response_bytes, LpDataKind::Registration) .await?; if response.success { @@ -767,40 +775,10 @@ where receiver_idx: u32, forward_data: ForwardPacketData, ) -> Result<(), GatewayError> { - // Forward the packet to the target gateway + // Forward the packet to the target gateway and retrieve its response let response_bytes = self.handle_forward_packet(forward_data).await?; - // Encrypt response for client and get outer AEAD key - let (response_packet, outer_key) = { - let session_entry = self - .state - .session_states - .get(&receiver_idx) - .ok_or_else(|| { - GatewayError::LpProtocolError(format!("Session not found: {}", receiver_idx)) - })?; - // Access session via state machine for subsession support - let session = session_entry - .value() - .state - .session() - .map_err(|e| GatewayError::LpProtocolError(format!("Session error: {}", e)))?; - - let encrypted_message = session.encrypt_data(&response_bytes).map_err(|e| { - GatewayError::LpProtocolError(format!("Failed to encrypt forward response: {}", e)) - })?; - - let packet = session.next_packet(encrypted_message).map_err(|e| { - GatewayError::LpProtocolError(format!("Failed to create response packet: {}", e)) - })?; - - // Get outer AEAD key for packet encryption - let outer_key = session.outer_aead_key(); - (packet, outer_key) - }; - - // Send encrypted response to client (encrypted with outer AEAD) - self.send_lp_packet(response_packet, outer_key.as_ref()) + self.send_response_packet(receiver_idx, response_bytes, LpDataKind::Forward) .await?; debug!( diff --git a/gateway/src/node/lp_listener/messages.rs b/gateway/src/node/lp_listener/messages.rs deleted file mode 100644 index 4dc60af0d2..0000000000 --- a/gateway/src/node/lp_listener/messages.rs +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright 2025 - Nym Technologies SA -// SPDX-License-Identifier: GPL-3.0-only - -//! LP registration message types. -//! -//! Re-exports shared message types from nym-registration-common. - -pub use nym_registration_common::{ - LpGatewayData, LpRegistrationRequest, LpRegistrationResponse, RegistrationMode, -}; diff --git a/gateway/src/node/lp_listener/mod.rs b/gateway/src/node/lp_listener/mod.rs index 8e63293759..4fc45c4f2f 100644 --- a/gateway/src/node/lp_listener/mod.rs +++ b/gateway/src/node/lp_listener/mod.rs @@ -78,7 +78,6 @@ pub use nym_mixnet_client::forwarder::{ }; use nym_node_metrics::NymNodeMetrics; use nym_task::ShutdownTracker; -pub use nym_wireguard::{PeerControlRequest, WireguardGatewayData}; use std::net::{IpAddr, Ipv6Addr, SocketAddr}; use std::sync::Arc; use std::time::Duration; @@ -87,10 +86,10 @@ use tokio::sync::{mpsc, Semaphore}; use tracing::*; pub use nym_lp::peer::LpLocalPeer; +pub use nym_wireguard::{PeerControlRequest, WireguardGatewayData}; mod data_handler; pub mod handler; -mod messages; mod registration; /// Configuration for LP listener diff --git a/gateway/src/node/lp_listener/registration.rs b/gateway/src/node/lp_listener/registration.rs index c14197c405..0be9925d21 100644 --- a/gateway/src/node/lp_listener/registration.rs +++ b/gateway/src/node/lp_listener/registration.rs @@ -1,9 +1,6 @@ // Copyright 2025 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use super::messages::{ - LpGatewayData, LpRegistrationRequest, LpRegistrationResponse, RegistrationMode, -}; use super::LpHandlerState; use crate::error::GatewayError; use crate::node::client_handling::websocket::message_receiver::IsActive; @@ -16,12 +13,14 @@ use nym_credential_verification::{ ClientBandwidth, CredentialVerifier, }; use nym_credentials_interface::CredentialSpendingData; -use nym_crypto::asymmetric::ed25519; use nym_gateway_requests::models::CredentialSpendingRequest; 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; +use nym_registration_common::{ + GatewayData, LpDvpnRegistrationRequest, LpMixnetGatewayData, LpMixnetRegistrationRequest, + LpRegistrationData, LpRegistrationRequest, LpRegistrationResponse, +}; use nym_wireguard::PeerControlRequest; use std::sync::Arc; use time::OffsetDateTime; @@ -195,12 +194,121 @@ async fn check_existing_registration( )) } +async fn process_dvpn_registration( + request: Box, + state: &LpHandlerState, +) -> LpRegistrationResponse { + // Track dVPN registration attempts + inc!("lp_registration_dvpn_attempts"); + + // Check for idempotent re-registration (same WG key already registered) + // This allows clients to retry registration after network failures + // without wasting credentials + let wg_key_str = request.wg_public_key.to_string(); + if let Some(existing_response) = check_existing_registration(&wg_key_str, state).await { + info!("LP dVPN re-registration for existing peer {wg_key_str} (idempotent)",); + inc!("lp_registration_dvpn_idempotent"); + return existing_response; + } + + // Register as WireGuard peer first to get client_id + let (gateway_data, client_id) = match register_wg_peer( + request.wg_public_key.inner().as_ref(), + request.ticket_type, + state, + ) + .await + { + Ok(result) => result, + Err(e) => { + error!("LP WireGuard peer registration failed: {e}"); + inc!("lp_registration_dvpn_failed"); + inc!("lp_errors_wg_peer_registration"); + return LpRegistrationResponse::error(format!( + "WireGuard peer registration failed: {e}", + )); + } + }; + + // Verify credential with CredentialVerifier (handles double-spend, storage, etc.) + let allocated_bandwidth = + match credential_verification(state.ecash_verifier.clone(), request.credential, client_id) + .await + { + Ok(bandwidth) => bandwidth, + Err(e) => { + // Credential verification failed, remove the peer + warn!("LP credential verification failed for client {client_id}: {e}",); + inc!("lp_registration_dvpn_failed"); + if let Err(remove_err) = state + .storage + .remove_wireguard_peer(&request.wg_public_key.to_string()) + .await + { + error!( + "Failed to remove peer after credential verification failure: {remove_err}" + ); + } + return LpRegistrationResponse::error(format!( + "Credential verification failed: {e}", + )); + } + }; + + info!("LP dVPN registration successful (client_id: {client_id})"); + inc!("lp_registration_dvpn_success"); + LpRegistrationResponse::success(allocated_bandwidth, gateway_data) +} + +async fn process_mixnet_registration( + request: LpMixnetRegistrationRequest, + state: &LpHandlerState, +) -> LpRegistrationResponse { + let session_id = rand::random::(); + + // Track mixnet registration attempts + inc!("lp_registration_mixnet_attempts"); + + // Derive destination address for ActiveClientsStore lookup + let client_identity = request.client_ed25519_pubkey; + let client_address = client_identity.derive_destination_address(); + + info!("LP Mixnet registration for client {client_identity}, session {session_id}"); + + warn!("unimplemented: LP mixnet registration initial bandwidth allocation"); + // (the old implementation was wrong - it wasn't creating correct db entries) + + // Create channels for client message delivery + let (mix_sender, _mix_receiver) = mpsc::unbounded(); + let (is_active_request_sender, _is_active_request_receiver) = + mpsc::unbounded::>(); + + // Insert client into ActiveClientsStore for SURB reply delivery + if !state.active_clients_store.insert_remote( + client_address, + mix_sender, + is_active_request_sender, + OffsetDateTime::now_utc(), + ) { + warn!("LP Mixnet registration failed: client {client_identity} already registered",); + inc!("lp_registration_mixnet_failed"); + return LpRegistrationResponse::error("Client already registered".to_string()); + } + + // Get gateway identity and derive sphinx key + let gateway_identity = *state.local_lp_peer.ed25519().public_key(); + + info!("LP Mixnet registration successful (client: {client_identity})",); + inc!("lp_registration_mixnet_success"); + + LpRegistrationResponse::success_mixnet(0, LpMixnetGatewayData { gateway_identity }) +} + /// Process an LP registration request pub async fn process_registration( request: LpRegistrationRequest, state: &LpHandlerState, ) -> LpRegistrationResponse { - let session_id = rand::random::(); let registration_start = std::time::Instant::now(); // Track total registration attempts @@ -214,159 +322,9 @@ pub async fn process_registration( } // 2. Process based on mode - let result = match request.mode { - RegistrationMode::Dvpn => { - // Track dVPN registration attempts - inc!("lp_registration_dvpn_attempts"); - - // Check for idempotent re-registration (same WG key already registered) - // This allows clients to retry registration after network failures - // without wasting credentials - let wg_key_str = request.wg_public_key.to_string(); - if let Some(existing_response) = check_existing_registration(&wg_key_str, state).await { - info!("LP dVPN re-registration for existing peer {wg_key_str} (idempotent)",); - inc!("lp_registration_dvpn_idempotent"); - return existing_response; - } - - // Register as WireGuard peer first to get client_id - let (gateway_data, client_id) = match register_wg_peer( - request.wg_public_key.inner().as_ref(), - request.ticket_type, - state, - ) - .await - { - Ok(result) => result, - Err(e) => { - error!("LP WireGuard peer registration failed: {e}"); - inc!("lp_registration_dvpn_failed"); - inc!("lp_errors_wg_peer_registration"); - return LpRegistrationResponse::error(format!( - "WireGuard peer registration failed: {e}", - )); - } - }; - - // Verify credential with CredentialVerifier (handles double-spend, storage, etc.) - let allocated_bandwidth = match credential_verification( - state.ecash_verifier.clone(), - request.credential, - client_id, - ) - .await - { - Ok(bandwidth) => bandwidth, - Err(e) => { - // Credential verification failed, remove the peer - warn!("LP credential verification failed for client {client_id}: {e}",); - inc!("lp_registration_dvpn_failed"); - if let Err(remove_err) = state - .storage - .remove_wireguard_peer(&request.wg_public_key.to_string()) - .await - { - error!("Failed to remove peer after credential verification failure: {remove_err}"); - } - return LpRegistrationResponse::error(format!( - "Credential verification failed: {e}", - )); - } - }; - - info!("LP dVPN registration successful (client_id: {})", client_id); - inc!("lp_registration_dvpn_success"); - LpRegistrationResponse::success(allocated_bandwidth, gateway_data) - } - RegistrationMode::Mixnet { - client_ed25519_pubkey, - client_x25519_pubkey: _, - } => { - // Track mixnet registration attempts - inc!("lp_registration_mixnet_attempts"); - - // Parse client's ed25519 public key - let client_identity = match ed25519::PublicKey::from_bytes(&client_ed25519_pubkey) { - Ok(key) => key, - Err(e) => { - warn!("LP Mixnet registration failed: invalid ed25519 key: {e}"); - inc!("lp_registration_mixnet_failed"); - return LpRegistrationResponse::error(format!( - "Invalid client ed25519 key: {e}", - )); - } - }; - - // Derive destination address for ActiveClientsStore lookup - let client_address = client_identity.derive_destination_address(); - - // Generate client_id for credential verification (first 8 bytes of ed25519 key) - #[allow(clippy::expect_used)] - let client_id = i64::from_be_bytes( - client_ed25519_pubkey[0..8] - .try_into() - .expect("This cannot fail, since the key is 32 bytes long"), - ); - - info!("LP Mixnet registration for client {client_identity}, session {session_id}",); - - // Verify credential with CredentialVerifier - let allocated_bandwidth = match credential_verification( - state.ecash_verifier.clone(), - request.credential, - client_id, - ) - .await - { - Ok(bandwidth) => bandwidth, - Err(e) => { - warn!("LP Mixnet credential verification failed for client {client_identity}: {e}"); - inc!("lp_registration_mixnet_failed"); - return LpRegistrationResponse::error(format!( - "Credential verification failed: {e}" - )); - } - }; - - // Create channels for client message delivery - let (mix_sender, _mix_receiver) = mpsc::unbounded(); - let (is_active_request_sender, _is_active_request_receiver) = - mpsc::unbounded::>(); - - // Insert client into ActiveClientsStore for SURB reply delivery - if !state.active_clients_store.insert_remote( - client_address, - mix_sender, - is_active_request_sender, - OffsetDateTime::now_utc(), - ) { - warn!("LP Mixnet registration failed: client {client_identity} already registered",); - inc!("lp_registration_mixnet_failed"); - return LpRegistrationResponse::error("Client already registered".to_string()); - } - - // Get gateway identity and derive sphinx key - let ed25519_key = state.local_lp_peer.ed25519().public_key(); - let gateway_identity = ed25519_key.to_bytes(); - - warn!("TEMPORARY ed25519 -> x25519 conversion"); - #[allow(clippy::expect_used)] - let gateway_sphinx_key = ed25519_key - .to_x25519() - .expect("valid ed25519 key should convert to x25519") - .to_bytes(); - - info!("LP Mixnet registration successful (client: {client_identity})",); - inc!("lp_registration_mixnet_success"); - - LpRegistrationResponse::success_mixnet( - allocated_bandwidth, - LpGatewayData { - gateway_identity, - gateway_sphinx_key, - }, - ) - } + let result = match request.registration_data { + LpRegistrationData::Dvpn { data } => process_dvpn_registration(data, state).await, + LpRegistrationData::Mixnet { data } => process_mixnet_registration(data, state).await, }; // Track registration duration diff --git a/integration-tests/src/lp_registration.rs b/integration-tests/src/lp_registration.rs index 40533cbb1f..1eee3a3b42 100644 --- a/integration-tests/src/lp_registration.rs +++ b/integration-tests/src/lp_registration.rs @@ -529,6 +529,7 @@ mod tests { #[tokio::test] async fn test_basic_lp_exit_registration() -> anyhow::Result<()> { + // nym_test_utils::helpers::setup_test_logger(); // initialise random, but deterministic, keys, addresses, etc. for the parties let mut client_rng = u64_seeded_rng(0); let mut entry_rng = u64_seeded_rng(1); @@ -652,7 +653,6 @@ mod tests { exit.base.peer.ed25519().public_key(), &client_data.ticket_provider, TicketType::V1WireguardExit, - client_data.base.socket_addr.ip(), ) .timeboxed() .await??; diff --git a/nym-gateway-probe/src/lib.rs b/nym-gateway-probe/src/lib.rs index 3e5ab6377b..f3be8e64b9 100644 --- a/nym-gateway-probe/src/lib.rs +++ b/nym-gateway-probe/src/lib.rs @@ -1329,7 +1329,6 @@ where &exit_wg_keypair, credential, TicketType::V1WireguardExit, - exit_ip, ) .await { @@ -1347,7 +1346,6 @@ where &exit_gateway_pubkey, bandwidth_controller, TicketType::V1WireguardExit, - exit_ip, ) .await { diff --git a/nym-registration-client/src/lib.rs b/nym-registration-client/src/lib.rs index fc2489909c..abf40816f5 100644 --- a/nym-registration-client/src/lib.rs +++ b/nym-registration-client/src/lib.rs @@ -235,7 +235,6 @@ impl RegistrationClient { &self.config.exit.node.identity, &*self.bandwidth_controller, TicketType::V1WireguardExit, - self.config.exit.node.ip_address, ) .await .map_err(|source| RegistrationClientError::ExitGatewayRegisterLp { diff --git a/nym-registration-client/src/lp_client/client.rs b/nym-registration-client/src/lp_client/client.rs index 7899cd223d..b5264000d4 100644 --- a/nym-registration-client/src/lp_client/client.rs +++ b/nym-registration-client/src/lp_client/client.rs @@ -5,6 +5,10 @@ use super::config::LpConfig; use super::error::{LpClientError, Result}; +use crate::lp_client::helpers::{ + convert_forward_data, convert_registration_request, try_convert_forward_response, + try_convert_registration_response, +}; use bytes::BytesMut; use nym_bandwidth_controller::{BandwidthTicketProvider, DEFAULT_TICKETS_TO_SPEND}; use nym_credentials_interface::{CredentialSpendingData, TicketType}; @@ -13,9 +17,9 @@ use nym_lp::LpPacket; use nym_lp::codec::{OuterAeadKey, parse_lp_packet, serialize_lp_packet}; use nym_lp::message::ForwardPacketData; use nym_lp::peer::{LpLocalPeer, LpRemotePeer}; -use nym_lp::state_machine::{LpAction, LpInput, LpStateMachine}; +use nym_lp::state_machine::{LpAction, LpData, LpInput, LpStateMachine}; use nym_lp_transport::traits::LpTransport; -use nym_registration_common::{GatewayData, LpRegistrationRequest, LpRegistrationResponse}; +use nym_registration_common::{GatewayData, LpRegistrationRequest}; use nym_wireguard_types::PeerPublicKey; use std::net::{IpAddr, SocketAddr}; use std::sync::Arc; @@ -740,20 +744,12 @@ where // 1. Build registration request let wg_public_key = PeerPublicKey::new(wg_keypair.public_key().to_bytes().into()); - let request = - LpRegistrationRequest::new_dvpn(wg_public_key, credential, ticket_type, self.client_ip); + let request = LpRegistrationRequest::new_dvpn(wg_public_key, credential, ticket_type); tracing::trace!("Built registration request: {:?}", request); // 2. Serialize the request - let request_bytes = request.serialise().map_err(|e| { - LpClientError::SendRegistrationRequest(format!("Failed to serialize request: {e}")) - })?; - - tracing::debug!( - "Sending registration request ({} bytes)", - request_bytes.len() - ); + let input = convert_registration_request(request)?; // 3. Encrypt and prepare packet via state machine (scoped borrow) let (request_packet, send_key, recv_key) = { @@ -762,7 +758,7 @@ where })?; let action = state_machine - .process_input(LpInput::SendData(request_bytes)) + .process_input(input) .ok_or_else(|| LpClientError::transport("State machine returned no action"))? .map_err(|e| { LpClientError::SendRegistrationRequest(format!( @@ -821,29 +817,15 @@ where )) })?; - // 7. Extract decrypted data - let response_data = match action { - LpAction::DeliverData(data) => data, - other => { - return Err(LpClientError::Transport(format!( - "Unexpected action when receiving registration response: {other:?}" - ))); - } - }; - - // 8. Deserialize the response - let response = LpRegistrationResponse::try_deserialise(&response_data).map_err(|e| { - LpClientError::ReceiveRegistrationResponse(format!( - "Failed to deserialize registration response: {e}", - )) - })?; + // 6. Extract decrypted data and deserialise the response + let response = try_convert_registration_response(action)?; tracing::debug!( "Received registration response: success={}", response.success, ); - // 9. Validate and extract GatewayData + // 7. Validate and extract GatewayData if !response.success { let error_msg = response .error @@ -1022,14 +1004,8 @@ where target_lp_address: target_address.clone(), inner_packet_bytes, }; - // 2. Serialize the ForwardPacketData - let forward_data_bytes = forward_data.to_bytes(); - - tracing::trace!( - "Serialized ForwardPacketData ({} bytes)", - forward_data_bytes.len() - ); + let input = convert_forward_data(forward_data); // 3. Encrypt and prepare packet via state machine (scoped borrow) let (forward_packet, send_key, recv_key) = { @@ -1038,7 +1014,7 @@ where })?; let action = state_machine - .process_input(LpInput::SendData(forward_data_bytes)) + .process_input(input) .ok_or_else(|| LpClientError::transport("State machine returned no action"))? .map_err(|e| { LpClientError::Transport(format!("Failed to encrypt ForwardPacket: {e}")) @@ -1094,15 +1070,7 @@ where })?; // 7. Extract decrypted response data - let response_data = match action { - LpAction::DeliverData(data) => data, - other => { - return Err(LpClientError::Transport(format!( - "Unexpected action when receiving forward response: {:?}", - other - ))); - } - }; + let response_data = try_convert_forward_response(action)?; tracing::debug!( "Successfully received forward response from {} ({} bytes)", @@ -1110,7 +1078,7 @@ where response_data.len() ); - Ok(response_data.to_vec()) + Ok(response_data) } /// Wrap data in an LP packet for UDP transmission to the data plane (port 51264). @@ -1148,7 +1116,7 @@ where // Process data through state machine to create LP packet let action = state_machine - .process_input(LpInput::SendData(data.to_vec())) + .process_input(LpInput::SendData(LpData::new_opaque(data.to_vec()))) .ok_or_else(|| LpClientError::transport("State machine returned no action"))? .map_err(|e| LpClientError::Transport(format!("Failed to encrypt data: {e}")))?; diff --git a/nym-registration-client/src/lp_client/helpers.rs b/nym-registration-client/src/lp_client/helpers.rs new file mode 100644 index 0000000000..6eb1a0f487 --- /dev/null +++ b/nym-registration-client/src/lp_client/helpers.rs @@ -0,0 +1,83 @@ +// Copyright 2026 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::LpClientError; +use nym_lp::message::ForwardPacketData; +use nym_lp::state_machine::{LpAction, LpData, LpDataKind, LpInput}; +use nym_registration_common::{LpRegistrationRequest, LpRegistrationResponse}; + +pub(crate) fn convert_registration_request( + request: LpRegistrationRequest, +) -> Result { + let request_bytes = request.serialise().map_err(|e| { + LpClientError::SendRegistrationRequest(format!("Failed to serialize request: {e}")) + })?; + + tracing::debug!( + "Sending registration request ({} bytes)", + request_bytes.len() + ); + + let data = LpData::new_registration(request_bytes); + Ok(LpInput::SendData(data)) +} + +pub(crate) fn try_convert_registration_response( + action: LpAction, +) -> Result { + let response_data = match action { + LpAction::DeliverData(data) => data, + other => { + return Err(LpClientError::Transport(format!( + "Unexpected action when receiving registration response: {other:?}" + ))); + } + }; + + if response_data.kind != LpDataKind::Registration { + return Err(LpClientError::Transport(format!( + "did not receive a valid registration response. got {:?} instead", + response_data.kind + ))); + } + + let response = + LpRegistrationResponse::try_deserialise(&response_data.content).map_err(|e| { + LpClientError::Transport(format!("Failed to deserialize registration response: {e}",)) + })?; + + Ok(response) +} + +pub(crate) fn convert_forward_data(request: ForwardPacketData) -> LpInput { + let request_bytes = request.to_bytes(); + + tracing::trace!( + "Sending forward packet data request ({} bytes)", + request_bytes.len() + ); + + let data = LpData::new_forward(request_bytes); + LpInput::SendData(data) +} + +pub(crate) fn try_convert_forward_response(action: LpAction) -> Result, LpClientError> { + let response_data = match action { + LpAction::DeliverData(data) => data, + other => { + return Err(LpClientError::Transport(format!( + "Unexpected action when receiving forward response: {:?}", + other + ))); + } + }; + + if response_data.kind != LpDataKind::Forward { + return Err(LpClientError::Transport(format!( + "did not receive a valid foreward response. got {:?} instead", + response_data.kind + ))); + } + + Ok(response_data.content.into()) +} diff --git a/nym-registration-client/src/lp_client/mod.rs b/nym-registration-client/src/lp_client/mod.rs index a83cd80853..b0dba1db25 100644 --- a/nym-registration-client/src/lp_client/mod.rs +++ b/nym-registration-client/src/lp_client/mod.rs @@ -34,6 +34,7 @@ mod client; mod config; pub(crate) mod error; +pub(crate) mod helpers; mod nested_session; pub use client::LpRegistrationClient; diff --git a/nym-registration-client/src/lp_client/nested_session.rs b/nym-registration-client/src/lp_client/nested_session.rs index 8f40f74c26..df1394a5cc 100644 --- a/nym-registration-client/src/lp_client/nested_session.rs +++ b/nym-registration-client/src/lp_client/nested_session.rs @@ -20,6 +20,7 @@ use super::client::LpRegistrationClient; use super::error::{LpClientError, Result}; +use crate::lp_client::helpers::{convert_registration_request, try_convert_registration_response}; use bytes::BytesMut; use nym_bandwidth_controller::BandwidthTicketProvider; use nym_credentials_interface::TicketType; @@ -29,9 +30,8 @@ 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, LpRegistrationResponse}; +use nym_registration_common::{GatewayData, LpRegistrationRequest}; use nym_wireguard_types::PeerPublicKey; -use std::net::IpAddr; use std::sync::Arc; use std::time::{SystemTime, UNIX_EPOCH}; @@ -293,7 +293,6 @@ impl NestedLpSession { wg_keypair: &x25519::KeyPair, credential: nym_credentials_interface::CredentialSpendingData, ticket_type: TicketType, - client_ip: IpAddr, ) -> Result where S: LpTransport + Unpin, @@ -312,24 +311,17 @@ impl NestedLpSession { // Step 3: Build registration request (credential already provided) let wg_public_key = PeerPublicKey::new(wg_keypair.public_key().to_bytes().into()); - let request = - LpRegistrationRequest::new_dvpn(wg_public_key, credential, ticket_type, client_ip); + let request = LpRegistrationRequest::new_dvpn(wg_public_key, credential, ticket_type); tracing::trace!("Built registration request: {:?}", request); // Step 4: Serialize the request - let request_bytes = request.serialise().map_err(|e| { - LpClientError::Transport(format!("Failed to serialize registration request: {}", e)) - })?; - - tracing::debug!( - "Sending registration request to exit gateway via forwarding ({} bytes)", - request_bytes.len() - ); + let input = convert_registration_request(request)?; + tracing::debug!("Sending registration request to exit gateway via forwarding",); // Step 5: Encrypt and prepare packet via state machine let action = state_machine - .process_input(LpInput::SendData(request_bytes)) + .process_input(input) .ok_or_else(|| { LpClientError::Transport("State machine returned no action".to_string()) })? @@ -374,36 +366,20 @@ impl NestedLpSession { LpClientError::Transport(format!("Failed to decrypt registration response: {}", e)) })?; - // Step 9: Extract decrypted data - let response_data = match action { - LpAction::DeliverData(data) => data, - other => { - return Err(LpClientError::Transport(format!( - "Unexpected action when receiving registration response: {:?}", - other - ))); - } - }; - - // Step 10: Deserialize the response - let response = LpRegistrationResponse::try_deserialise(&response_data).map_err(|e| { - LpClientError::Transport(format!( - "Failed to deserialize registration response: {}", - e - )) - })?; + // Step 9: Extract decrypted data and deserialise the response + let response = try_convert_registration_response(action)?; tracing::debug!( "Received registration response from exit: success={}", response.success, ); - // Step 11: Validate and extract GatewayData + // Step 10: Validate and extract GatewayData if !response.success { let error_msg = response .error .unwrap_or_else(|| "Unknown error".to_string()); - tracing::warn!("Exit gateway rejected registration: {}", error_msg); + tracing::warn!("Exit gateway rejected registration: {error_msg}"); return Err(LpClientError::RegistrationRejected { reason: error_msg }); } @@ -456,7 +432,6 @@ impl NestedLpSession { gateway_identity: &ed25519::PublicKey, bandwidth_controller: &dyn BandwidthTicketProvider, ticket_type: TicketType, - client_ip: IpAddr, ) -> Result where S: LpTransport + Unpin, @@ -486,24 +461,17 @@ impl NestedLpSession { // Step 4: Build registration request let wg_public_key = PeerPublicKey::new(wg_keypair.public_key().to_bytes().into()); - let request = - LpRegistrationRequest::new_dvpn(wg_public_key, credential, ticket_type, client_ip); + let request = LpRegistrationRequest::new_dvpn(wg_public_key, credential, ticket_type); tracing::trace!("Built registration request: {:?}", request); // Step 5: Serialize the request - let request_bytes = request.serialise().map_err(|e| { - LpClientError::Transport(format!("Failed to serialize registration request: {}", e)) - })?; - - tracing::debug!( - "Sending registration request to exit gateway via forwarding ({} bytes)", - request_bytes.len() - ); + let input = convert_registration_request(request)?; + tracing::debug!("Sending registration request to exit gateway via forwarding"); // Step 6: Encrypt and prepare packet via state machine let action = state_machine - .process_input(LpInput::SendData(request_bytes)) + .process_input(input) .ok_or_else(|| { LpClientError::Transport("State machine returned no action".to_string()) })? @@ -548,31 +516,15 @@ impl NestedLpSession { LpClientError::Transport(format!("Failed to decrypt registration response: {}", e)) })?; - // Step 10: Extract decrypted data - let response_data = match action { - LpAction::DeliverData(data) => data, - other => { - return Err(LpClientError::Transport(format!( - "Unexpected action when receiving registration response: {:?}", - other - ))); - } - }; - - // Step 11: Deserialize the response - let response = LpRegistrationResponse::try_deserialise(&response_data).map_err(|e| { - LpClientError::Transport(format!( - "Failed to deserialize registration response: {}", - e - )) - })?; + // Step 10: Extract decrypted data and deserialise the response + let response = try_convert_registration_response(action)?; tracing::debug!( "Received registration response from exit: success={}", response.success, ); - // Step 12: Validate and extract GatewayData + // Step 11: Validate and extract GatewayData if !response.success { let error_msg = response .error @@ -632,7 +584,6 @@ impl NestedLpSession { gateway_identity: &ed25519::PublicKey, bandwidth_controller: &dyn BandwidthTicketProvider, ticket_type: TicketType, - client_ip: IpAddr, max_retries: u32, ) -> Result where @@ -687,7 +638,6 @@ impl NestedLpSession { wg_keypair, credential.clone(), ticket_type, - client_ip, ) .await { diff --git a/sdk/rust/nym-sdk/src/mixnet/socks5_client.rs b/sdk/rust/nym-sdk/src/mixnet/socks5_client.rs index 0a9ee37c90..1875924616 100644 --- a/sdk/rust/nym-sdk/src/mixnet/socks5_client.rs +++ b/sdk/rust/nym-sdk/src/mixnet/socks5_client.rs @@ -1,10 +1,13 @@ +use std::time::Duration; + use nym_client_core::client::base_client::ClientState; use nym_socks5_client_core::config::Socks5; use nym_sphinx::addressing::clients::Recipient; use nym_task::connections::LaneQueueLengths; use nym_task::ShutdownTracker; +use tokio::sync::RwLockReadGuard; -use nym_topology::NymTopology; +use nym_topology::{NymRouteProvider, NymTopology, NymTopologyError}; use crate::mixnet::client::MixnetClientBuilder; use crate::Result; @@ -85,4 +88,28 @@ impl Socks5MixnetClient { pub async fn disconnect(self) { self.task_handle.shutdown().await; } + + /// Gets the current route provider if topology is available. + /// Returns `None` if topology is empty/not yet fetched. + async fn read_current_route_provider(&self) -> Option> { + self.client_state + .topology_accessor + .current_route_provider() + .await + } + + /// Wait for topology to become available, with a timeout. + /// Returns `Ok(())` when topology is ready, or `Err` if timeout is reached. + pub async fn wait_for_topology(&self, timeout: Duration) -> Result<(), NymTopologyError> { + let deadline = tokio::time::Instant::now() + timeout; + loop { + if self.read_current_route_provider().await.is_some() { + return Ok(()); + } + if tokio::time::Instant::now() >= deadline { + return Err(NymTopologyError::EmptyNetworkTopology); + } + tokio::time::sleep(Duration::from_millis(100)).await; + } + } } From dbf571cb0a1679a1a4b37f18f56084d6f9610675 Mon Sep 17 00:00:00 2001 From: Merve <111695676+merve64@users.noreply.github.com> Date: Mon, 26 Jan 2026 12:58:30 +0300 Subject: [PATCH 22/51] docs typos fixed (#6368) --- .../docs/components/snippets-general/varible-info-snippet.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/docs/components/snippets-general/varible-info-snippet.mdx b/documentation/docs/components/snippets-general/varible-info-snippet.mdx index 4ab6ed8f16..9414218ddb 100644 --- a/documentation/docs/components/snippets-general/varible-info-snippet.mdx +++ b/documentation/docs/components/snippets-general/varible-info-snippet.mdx @@ -1,6 +1,6 @@ import { Callout } from 'nextra/components' -Our documentation often refer to syntax annotated in `<>` brackets. We use this expression for variables that are unique to each user (like path, local moniker, versions etcetra). +Our documentation often refers to syntax annotated in `<>` brackets. We use this expression for variables that are unique to each user (like path, local moniker, versions et cetera). Any syntax in `<>` brackets needs to be substituted with your correct name or version, without the `<>` brackets. If you are unsure, please check our table of essential [parameters and variables](https://nym.com/docs/operators/variables). From a4638b8d2f368b0bc2419fb210a023dff3be4f21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Mon, 26 Jan 2026 13:15:37 +0000 Subject: [PATCH 23/51] Lp/use noise x25519 (#6372) * use x25519 noise key for ktt instead of deriving one from ed25519 * removed client's IpAddr from RegistrationClient constructor * Adjusted the gateway probe to inject correct lp data * remove redundant argument from nym-lp-client * consistent naming for HashFunction variants * use workspace dep import for nym-kkt-ciphersuite * struct renaming --- Cargo.lock | 4 +- Cargo.toml | 7 +- common/nym-kkt-ciphersuite/src/lib.rs | 47 ++++-- common/nym-kkt/Cargo.toml | 2 +- common/nym-kkt/benches/benches.rs | 4 +- common/nym-kkt/src/lib.rs | 8 +- common/registration/Cargo.toml | 4 +- common/registration/src/lib.rs | 34 ++-- common/registration/src/lp_messages.rs | 10 +- gateway/src/node/lp_listener/registration.rs | 10 +- gateway/src/node/mod.rs | 22 ++- integration-tests/src/lp_registration.rs | 3 - nym-api/nym-api-requests/Cargo.toml | 2 +- .../src/models/described/type_translation.rs | 23 +++ nym-authenticator-client/src/lib.rs | 6 +- nym-gateway-probe/Cargo.toml | 2 + nym-gateway-probe/src/lib.rs | 151 ++++++++---------- nym-gateway-probe/src/nodes.rs | 59 ++++--- nym-gateway-probe/src/run.rs | 136 ++++++++++++---- nym-node/nym-node-requests/Cargo.toml | 2 +- .../src/api/v1/lewes_protocol/models.rs | 8 +- nym-node/src/node/mod.rs | 2 +- nym-registration-client/src/builder/config.rs | 4 +- nym-registration-client/src/error.rs | 3 - nym-registration-client/src/lib.rs | 65 +++----- .../src/lp_client/client.rs | 26 +-- .../src/lp_client/helpers.rs | 13 +- .../src/lp_client/nested_session.rs | 8 +- nym-registration-client/src/types.rs | 10 +- nym-wallet/Cargo.lock | 1 + tools/nym-lp-client/Cargo.toml | 2 +- tools/nym-lp-client/src/client.rs | 6 - 32 files changed, 389 insertions(+), 295 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 64e92004c1..351e140330 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5417,6 +5417,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", @@ -6474,6 +6475,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", @@ -7523,8 +7525,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 26c6159c49..fcc535016d 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 4e76fc452f..2acb7ab7b5 100644 --- a/common/nym-kkt/benches/benches.rs +++ b/common/nym-kkt/benches/benches.rs @@ -54,8 +54,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 4fbb51b197..e6a91a83ec 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, @@ -254,8 +254,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(); From 3597682b33a5326cea605cd6b34d1b7e5c59b87a Mon Sep 17 00:00:00 2001 From: benedettadavico Date: Tue, 27 Jan 2026 11:55:37 +0100 Subject: [PATCH 24/51] update lower envs details --- envs/canary.env | 6 +++++- envs/sandbox.env | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/envs/canary.env b/envs/canary.env index fbf97b2643..96c2dd38e7 100644 --- a/envs/canary.env +++ b/envs/canary.env @@ -23,4 +23,8 @@ NYM_API=https://canary-api.performance.nymte.ch/api/ NYXD_WS=wss://rpc.canary-validator.performance.nymte.ch/websocket NYM_VPN_API=https://nym-vpn-api-git-deploy-canary-nyx-network-staging.vercel.app/api/ -UPGRADE_MODE_ATTESTER_ED25519_PUBKEY=U1NXToPYUTsh7pYPLcwXCXwcL6pGoLUou7fyAJrNz8b \ No newline at end of file +UPGRADE_MODE_ATTESTER_ED25519_PUBKEY=U1NXToPYUTsh7pYPLcwXCXwcL6pGoLUou7fyAJrNz8b +UPGRADE_MODE_ATTESTATION_URL=http://upgrade-mode.performance.nymte.ch/.wellknown/canary/attestation.json + +NYM_APIS=[{"url":"https://canary-api.performance.nymte.ch/api/","front_hosts":null},{"url":"https://nym-frontdoor.vercel.app/canary/nym-api/","front_hosts":["vercel.app","vercel.com"]}] +NYM_VPN_APIS=[{"front_hosts":null},{"url":"https://nym-vpn-api-git-deploy-canary-nyx-network-staging.vercel.app/api/","front_hosts":["vercel.app","vercel.com"]},{"url":"https://nym-frontdoor.vercel.app/canary/nym-vpn-api/","front_hosts":["vercel.app","vercel.com"]}] \ No newline at end of file diff --git a/envs/sandbox.env b/envs/sandbox.env index 96a46881dd..549947e440 100644 --- a/envs/sandbox.env +++ b/envs/sandbox.env @@ -24,4 +24,8 @@ NYXD_WS=wss://rpc.sandbox.nymtech.net/websocket NYM_API=https://sandbox-nym-api1.nymtech.net/api/ NYM_VPN_API=https://nym-vpn-api-git-deploy-sandbox-nyx-network-staging.vercel.app/api/ -UPGRADE_MODE_ATTESTER_ED25519_PUBKEY=EGwzKXPrqStv8cHF68VT2LbQuEBGDPzhCAixScvybfem \ No newline at end of file +UPGRADE_MODE_ATTESTER_ED25519_PUBKEY=EGwzKXPrqStv8cHF68VT2LbQuEBGDPzhCAixScvybfem +UPGRADE_MODE_ATTESTATION_URL=http://upgrade-mode.performance.nymte.ch/.wellknown/sandbox/attestation.json + +NYM_APIS=[{"url":"https://sandbox-nym-api1.nymtech.net/api/","front_hosts":null},{"url":"https://nym-frontdoor.vercel.app/sandbox/nym-api/","front_hosts":["vercel.app","vercel.com"]}] +NYM_VPN_APIS=[{"front_hosts":null},{"url":"https://nym-vpn-api-git-deploy-sandbox-nyx-network-staging.vercel.app/api/","front_hosts":["vercel.app","vercel.com"]},{"url":"https://nym-frontdoor.vercel.app/sandbox/nym-vpn-api/","front_hosts":["vercel.app","vercel.com"]}] \ No newline at end of file From 8af759fb1d209179dba1d8b777018ff9326fc994 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Tue, 27 Jan 2026 12:23:52 +0000 Subject: [PATCH 25/51] LP: include signing key digests to LP responses (#6373) * include signing key digests to LP responses * mock --- common/nym-kkt-ciphersuite/src/lib.rs | 87 +++++++++++-------- common/nym-kkt/src/key_utils.rs | 4 +- common/nym-lp/src/peer.rs | 32 +++++-- common/registration/src/lib.rs | 3 +- .../src/models/described/type_translation.rs | 71 ++++++++++++--- .../src/models/described/v2.rs | 18 ++-- nym-gateway-probe/src/lib.rs | 8 +- nym-gateway-probe/src/nodes.rs | 13 +++ nym-gateway-probe/src/run.rs | 5 ++ .../src/api/v1/lewes_protocol/models.rs | 67 +++++++++++--- nym-node/src/node/mod.rs | 34 ++++++-- .../src/lp_client/helpers.rs | 5 +- tools/nym-lp-client/src/client.rs | 10 ++- tools/nym-lp-client/src/topology.rs | 3 +- 14 files changed, 274 insertions(+), 86 deletions(-) diff --git a/common/nym-kkt-ciphersuite/src/lib.rs b/common/nym-kkt-ciphersuite/src/lib.rs index 6be109febd..e72d873a87 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, EnumString}; +use strum_macros::{Display, EnumIter, EnumString}; pub mod error; @@ -45,12 +45,26 @@ pub mod xwing { pub const PUBLIC_KEY_LENGTH: usize = x25519::PUBLIC_KEY_LENGTH + ml_kem768::PUBLIC_KEY_LENGTH; } -pub type KEMKeyDigests = HashMap>; +pub type KEMKeyDigests = KeyDigests; +pub type SigningKeyDigests = KeyDigests; + +pub type KeyDigests = HashMap>; #[derive( - Clone, Copy, PartialEq, Eq, Hash, Debug, IntoPrimitive, TryFromPrimitive, EnumIter, EnumString, + Clone, + Copy, + PartialEq, + Eq, + Hash, + Debug, + IntoPrimitive, + TryFromPrimitive, + EnumIter, + EnumString, + Display, )] #[strum(ascii_case_insensitive)] +#[strum(serialize_all = "lowercase")] #[repr(u8)] pub enum HashFunction { Blake3 = 0, @@ -79,17 +93,6 @@ impl HashFunction { } } -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::SHA256 => "sha256", - }) - } -} - #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, IntoPrimitive)] #[repr(u8)] pub enum HashLength { @@ -146,7 +149,21 @@ impl HashLength { } } -#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, IntoPrimitive, TryFromPrimitive)] +#[derive( + Clone, + Copy, + PartialEq, + Eq, + Hash, + Debug, + IntoPrimitive, + TryFromPrimitive, + EnumIter, + EnumString, + Display, +)] +#[strum(ascii_case_insensitive)] +#[strum(serialize_all = "lowercase")] #[repr(u8)] pub enum SignatureScheme { Ed25519 = 0, @@ -175,18 +192,21 @@ impl SignatureScheme { } } -impl Display for SignatureScheme { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(match self { - SignatureScheme::Ed25519 => "ed25519", - }) - } -} - #[derive( - Clone, Copy, PartialEq, Eq, Hash, Debug, IntoPrimitive, TryFromPrimitive, EnumIter, EnumString, + Clone, + Copy, + PartialEq, + Eq, + Hash, + Debug, + IntoPrimitive, + TryFromPrimitive, + EnumIter, + EnumString, + Display, )] #[strum(ascii_case_insensitive)] +#[strum(serialize_all = "lowercase")] #[repr(u8)] pub enum KEM { XWing = 0, @@ -206,17 +226,6 @@ impl KEM { } } -impl Display for KEM { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(match self { - KEM::MlKem768 => "mlkem768", - KEM::XWing => "xwing", - KEM::X25519 => "x25519", - KEM::McEliece => "mceliece", - }) - } -} - #[derive(Clone, Copy, PartialEq, Debug)] pub struct Ciphersuite { hash_function: HashFunction, @@ -363,4 +372,12 @@ mod tests { assert_eq!(hash_fn, HashFunction::from_str(&display).unwrap()); } } + + #[test] + fn signature_scheme_display_consistency() { + for scheme in SignatureScheme::iter() { + let display = format!("{scheme}"); + assert_eq!(scheme, SignatureScheme::from_str(&display).unwrap()); + } + } } diff --git a/common/nym-kkt/src/key_utils.rs b/common/nym-kkt/src/key_utils.rs index e612cbe3bf..535ba8b000 100644 --- a/common/nym-kkt/src/key_utils.rs +++ b/common/nym-kkt/src/key_utils.rs @@ -3,7 +3,7 @@ use std::collections::HashMap; use classic_mceliece_rust::keypair_boxed; -use nym_kkt_ciphersuite::{DEFAULT_HASH_LEN, KEMKeyDigests}; +use nym_kkt_ciphersuite::{DEFAULT_HASH_LEN, KeyDigests}; use rand::{CryptoRng, RngCore}; pub fn generate_keypair_ed25519( @@ -78,7 +78,7 @@ pub fn hash_key_bytes( /// attempt to produce digests of the provided key using all known [HashFunction] with a default /// hash length where variable output is available -pub fn produce_key_digests(key_bytes: &[u8]) -> KEMKeyDigests { +pub fn produce_key_digests(key_bytes: &[u8]) -> KeyDigests { use strum::IntoEnumIterator; let mut digests = HashMap::new(); for hash in HashFunction::iter() { diff --git a/common/nym-lp/src/peer.rs b/common/nym-lp/src/peer.rs index ee81fd5351..93f1fb2746 100644 --- a/common/nym-lp/src/peer.rs +++ b/common/nym-lp/src/peer.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use nym_crypto::asymmetric::{ed25519, x25519}; -use nym_kkt::ciphersuite::{HashFunction, KEM}; +use nym_kkt::ciphersuite::{KEM, KEMKeyDigests, SignatureScheme, SigningKeyDigests}; use std::collections::HashMap; use std::sync::Arc; @@ -49,18 +49,26 @@ impl LpLocalPeer { let expected_kem_key_digests = match &self.kem_psq { None => HashMap::new(), Some(kem_keys) => { - let hashes = - nym_kkt::key_utils::produce_key_digests(kem_keys.public_key().as_bytes()); - let mut digests = HashMap::new(); - digests.insert(KEM::X25519, hashes); + digests.insert( + KEM::X25519, + nym_kkt::key_utils::produce_key_digests(kem_keys.public_key().as_bytes()), + ); digests } }; + + let mut expected_signing_key_digests = HashMap::new(); + expected_signing_key_digests.insert( + SignatureScheme::Ed25519, + nym_kkt::key_utils::produce_key_digests(self.ed25519.public_key().as_bytes()), + ); + LpRemotePeer { ed25519_public: *self.ed25519.public_key(), x25519_public: *self.x25519.public_key(), expected_kem_key_digests, + expected_signing_key_digests, } } @@ -85,8 +93,11 @@ pub struct LpRemotePeer { /// Remote X25519 public key (Noise static key) pub(crate) x25519_public: x25519::PublicKey, - /// Expected digest of the remote's KEM key - pub(crate) expected_kem_key_digests: HashMap>>, + /// Expected digests of the remote's KEM key + pub(crate) expected_kem_key_digests: HashMap, + + /// Expected digests of the remote's signing key + pub(crate) expected_signing_key_digests: HashMap, } impl LpRemotePeer { @@ -95,6 +106,7 @@ impl LpRemotePeer { ed25519_public, x25519_public, expected_kem_key_digests: Default::default(), + expected_signing_key_digests: Default::default(), } } @@ -107,11 +119,13 @@ impl LpRemotePeer { } #[must_use] - pub fn with_kem_key_digests( + pub fn with_key_digests( mut self, - expected_kem_key_digests: HashMap>>, + expected_kem_key_digests: HashMap, + expected_signing_key_digests: HashMap, ) -> Self { self.expected_kem_key_digests = expected_kem_key_digests; + self.expected_signing_key_digests = expected_signing_key_digests; self } } diff --git a/common/registration/src/lib.rs b/common/registration/src/lib.rs index 56a3cee0bb..96d23bcf0f 100644 --- a/common/registration/src/lib.rs +++ b/common/registration/src/lib.rs @@ -5,7 +5,7 @@ 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_kkt_ciphersuite::{KEM, KEMKeyDigests, SignatureScheme}; use nym_sphinx::addressing::Recipient; use serde::{Deserialize, Serialize}; use std::collections::HashMap; @@ -43,6 +43,7 @@ pub struct WireguardConfiguration { pub struct NymNodeLPInformation { pub address: SocketAddr, pub expected_kem_key_hashes: HashMap, + pub expected_signing_key_hashes: HashMap, pub x25519: x25519::PublicKey, } 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 5f1b078cb8..94746950d3 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,7 +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_kkt_ciphersuite::{HashFunction, SignatureScheme, KEM}; use nym_network_defaults::{WG_METADATA_PORT, WG_TUNNEL_PORT}; use nym_noise_keys::VersionedNoiseKeyV1; use schemars::JsonSchema; @@ -212,6 +212,20 @@ pub struct LewesProtocolDetailsV1 { /// Digests of the KEM keys available to this node alongside hashing algorithms used /// for their computation. pub kem_keys: HashMap>>, + + /// Digests of the signing keys available to this node alongside hashing algorithms used + /// for their computation. + pub signing_keys: HashMap>>, +} + +/// Convert map of digests from `nym_node_requests` types into `nym-api-requests` types +fn translate_digests( + digests: HashMap>, +) -> HashMap> { + digests + .into_iter() + .map(|(hash_fn, digest)| (hash_fn.into(), digest)) + .collect() } #[derive( @@ -260,6 +274,26 @@ pub enum LPHashFunction { Sha256, } +#[derive( + Serialize, + Deserialize, + Debug, + Clone, + Copy, + JsonSchema, + Hash, + PartialEq, + Eq, + PartialOrd, + Display, + EnumString, + ToSchema, +)] +#[strum(serialize_all = "lowercase")] +pub enum LPSignatureScheme { + Ed25519, +} + impl From for HostInformationV1 { fn from(value: nym_node_requests::api::v1::node::models::HostInformation) -> Self { HostInformationV1 { @@ -382,15 +416,12 @@ impl From kem_keys: value .kem_keys .into_iter() - .map(|(kem, digests)| { - ( - kem.into(), - digests - .into_iter() - .map(|(hash_fn, digest)| (hash_fn.into(), digest)) - .collect(), - ) - }) + .map(|(kem, digests)| (kem.into(), translate_digests(digests))) + .collect(), + signing_keys: value + .signing_keys + .into_iter() + .map(|(scheme, digests)| (scheme.into(), translate_digests(digests))) .collect(), } } @@ -426,6 +457,18 @@ impl From fo } } +impl From + for LPSignatureScheme +{ + fn from(value: nym_node_requests::api::v1::lewes_protocol::models::LPSignatureScheme) -> Self { + match value { + nym_node_requests::api::v1::lewes_protocol::models::LPSignatureScheme::Ed25519 => { + LPSignatureScheme::Ed25519 + } + } + } +} + impl From for KEM { fn from(value: LPKEM) -> Self { match value { @@ -447,3 +490,11 @@ impl From for HashFunction { } } } + +impl From for SignatureScheme { + fn from(value: LPSignatureScheme) -> Self { + match value { + LPSignatureScheme::Ed25519 => SignatureScheme::Ed25519, + } + } +} diff --git a/nym-api/nym-api-requests/src/models/described/v2.rs b/nym-api/nym-api-requests/src/models/described/v2.rs index ac712e3d5e..bd68aa5271 100644 --- a/nym-api/nym-api-requests/src/models/described/v2.rs +++ b/nym-api/nym-api-requests/src/models/described/v2.rs @@ -247,7 +247,7 @@ impl From for NymNodeDescriptionV2 { #[cfg(test)] pub fn mock_nym_node_description(seed: u64) -> NymNodeDescriptionV2 { - use crate::models::{LPHashFunction, LPKEM}; + use crate::models::{LPHashFunction, LPSignatureScheme, LPKEM}; use nym_test_utils::helpers::{u64_seeded_rng, RngCore}; let mut rng = u64_seeded_rng(seed); @@ -257,11 +257,16 @@ pub fn mock_nym_node_description(seed: u64) -> NymNodeDescriptionV2 { // just reuse the same x25519 key for everything - this is just a data mock let x25519 = x25519::KeyPair::new(&mut rng); - let mut hashes_wrapper = std::collections::HashMap::new(); - let mut hashes = std::collections::HashMap::new(); + let mut kem_hashes_wrapper = std::collections::HashMap::new(); + let mut signing_keys_hashes_wrapper = std::collections::HashMap::new(); + let mut kem_hashes = std::collections::HashMap::new(); + let mut signing_keys_hashes = std::collections::HashMap::new(); - hashes.insert(LPHashFunction::Sha256, vec![(seed % 256) as u8; 32]); - hashes_wrapper.insert(LPKEM::X25519, hashes); + kem_hashes.insert(LPHashFunction::Sha256, vec![(seed % 256) as u8; 32]); + kem_hashes_wrapper.insert(LPKEM::X25519, kem_hashes); + + signing_keys_hashes.insert(LPHashFunction::Sha256, vec![(seed % 256) as u8; 32]); + signing_keys_hashes_wrapper.insert(LPSignatureScheme::Ed25519, signing_keys_hashes); NymNodeDescriptionV2 { node_id: rng.next_u32(), @@ -332,7 +337,8 @@ pub fn mock_nym_node_description(seed: u64) -> NymNodeDescriptionV2 { enabled: true, control_port: 1234, data_port: 2345, - kem_keys: hashes_wrapper, + kem_keys: kem_hashes_wrapper, + signing_keys: signing_keys_hashes_wrapper, }), mixnet_websockets: WebSocketsV2 { ws_port: 9000, diff --git a/nym-gateway-probe/src/lib.rs b/nym-gateway-probe/src/lib.rs index f6cdb0a7fc..50695533c1 100644 --- a/nym-gateway-probe/src/lib.rs +++ b/nym-gateway-probe/src/lib.rs @@ -64,7 +64,7 @@ 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_kkt_ciphersuite::{KEM, KEMKeyDigests, SignatureScheme}; use nym_lp::peer::LpRemotePeer; use nym_node_status_client::models::AttachedTicketMaterials; use nym_registration_client::{LpRegistrationClient, NestedLpSession}; @@ -167,6 +167,7 @@ pub struct TestedNodeDetails { pub struct TestedNodeLpDetails { pub address: SocketAddr, pub expected_kem_key_hashes: HashMap, + pub expected_signing_key_hashes: HashMap, pub x25519: x25519::PublicKey, } @@ -1077,7 +1078,10 @@ async fn wg_probe( } 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) + LpRemotePeer::new(identity, data.x25519).with_key_digests( + data.expected_kem_key_hashes, + data.expected_signing_key_hashes, + ) } async fn lp_registration_probe( diff --git a/nym-gateway-probe/src/nodes.rs b/nym-gateway-probe/src/nodes.rs index 1eeee1657c..202e00cd15 100644 --- a/nym-gateway-probe/src/nodes.rs +++ b/nym-gateway-probe/src/nodes.rs @@ -141,6 +141,19 @@ impl DirectoryNode { ) }) .collect(), + expected_signing_key_hashes: lp_data + .signing_keys + .into_iter() + .map(|(scheme, digests)| { + ( + scheme.into(), + digests + .into_iter() + .map(|(hash_fn, digest)| (hash_fn.into(), digest)) + .collect(), + ) + }) + .collect(), x25519: noise_key.x25519_pubkey, }), _ => None, diff --git a/nym-gateway-probe/src/run.rs b/nym-gateway-probe/src/run.rs index 085aaae2b1..0ce0b98b8f 100644 --- a/nym-gateway-probe/src/run.rs +++ b/nym-gateway-probe/src/run.rs @@ -244,6 +244,9 @@ fn mode_to_flags(mode: TestMode) -> (bool, bool, bool) { } } +#[allow(clippy::todo)] +#[allow(unreachable_code, unused)] +// ^^^^ // NOTE: to be changed by @SW #[allow(clippy::unwrap_used)] pub(crate) async fn run() -> anyhow::Result { let args = CliArgs::parse(); @@ -306,6 +309,7 @@ pub(crate) async fn run() -> anyhow::Result { let entry_lp_node = TestedNodeLpDetails { address: entry_lp_addr, expected_kem_key_hashes, + expected_signing_key_hashes: todo!(), x25519: x25519_key, }; let entry_details = TestedNodeDetails::from_cli(identity, entry_lp_node); @@ -338,6 +342,7 @@ pub(crate) async fn run() -> anyhow::Result { let exit_lp_node = TestedNodeLpDetails { address: exit_lp_addr, expected_kem_key_hashes, + expected_signing_key_hashes: Default::default(), x25519: x25519_key, }; 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 24848610f7..ac38f316b1 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 @@ -22,26 +22,28 @@ pub struct LewesProtocol { /// Digests of the KEM keys available to this node alongside hashing algorithms used /// for their computation. pub kem_keys: HashMap>>, + + /// Digests of the signing keys available to this node alongside hashing algorithms used + /// for their computation. + pub signing_keys: HashMap>>, } impl LewesProtocol { - pub fn new(enabled: bool, control_port: u16, data_port: u16) -> Self { + pub fn new( + enabled: bool, + control_port: u16, + data_port: u16, + kem_keys: HashMap>>, + signing_keys: HashMap>>, + ) -> Self { LewesProtocol { enabled, control_port, data_port, - kem_keys: Default::default(), + kem_keys, + signing_keys, } } - - pub fn with_kem_key_hashes( - mut self, - kem: LPKEM, - hashes: HashMap>, - ) -> Self { - self.kem_keys.insert(kem, hashes); - self - } } // explicitly redefine available HashFunctions and KEMs so that we would not @@ -140,9 +142,47 @@ impl From for LPHashFunction { } } +#[derive( + Serialize, + Deserialize, + Debug, + Clone, + Copy, + JsonSchema, + Hash, + PartialEq, + Eq, + PartialOrd, + Display, + EnumString, + EnumIter, +)] +#[strum(serialize_all = "lowercase")] +#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] +pub enum LPSignatureScheme { + Ed25519, +} + +impl From for nym_kkt_ciphersuite::SignatureScheme { + fn from(lp_hash_fnction: LPSignatureScheme) -> Self { + match lp_hash_fnction { + LPSignatureScheme::Ed25519 => nym_kkt_ciphersuite::SignatureScheme::Ed25519, + } + } +} + +impl From for LPSignatureScheme { + fn from(kem: nym_kkt_ciphersuite::SignatureScheme) -> Self { + match kem { + nym_kkt_ciphersuite::SignatureScheme::Ed25519 => LPSignatureScheme::Ed25519, + } + } +} + #[cfg(test)] mod tests { use super::*; + use nym_kkt_ciphersuite::SignatureScheme; #[test] fn kem_display() { @@ -159,4 +199,9 @@ mod tests { assert_eq!(LPHashFunction::Shake256.to_string(), "shake256"); assert_eq!(LPHashFunction::Sha256.to_string(), "sha256"); } + + #[test] + fn signature_scheme_display() { + assert_eq!(SignatureScheme::Ed25519.to_string(), "ed25519"); + } } diff --git a/nym-node/src/node/mod.rs b/nym-node/src/node/mod.rs index 80eff44f06..1c9f3bb3fb 100644 --- a/nym-node/src/node/mod.rs +++ b/nym-node/src/node/mod.rs @@ -51,7 +51,9 @@ use nym_network_requester::{ }; use nym_node_metrics::NymNodeMetrics; use nym_node_metrics::events::MetricEventsSender; -use nym_node_requests::api::v1::lewes_protocol::models::{LPHashFunction, LPKEM}; +use nym_node_requests::api::v1::lewes_protocol::models::{ + LPHashFunction, LPKEM, LPSignatureScheme, +}; use nym_node_requests::api::v1::node::models::{AnnouncePorts, NodeDescription}; use nym_noise::config::{NoiseConfig, NoiseNetworkView}; use nym_noise_keys::VersionedNoiseKeyV1; @@ -774,7 +776,7 @@ impl NymNode { Ok(()) } - fn compute_kem_key_hashes(&self) -> (LPKEM, HashMap>) { + fn compute_kem_key_hashes(&self) -> HashMap>> { let kem = LPKEM::X25519; let kem_key_bytes = self.entry_gateway.psq_kem_key.public_key().as_bytes(); @@ -785,7 +787,27 @@ impl NymNode { .map(|(f, d)| (f.into(), d)) .collect(); - (kem, digests) + let mut hashes = HashMap::new(); + hashes.insert(kem, digests); + hashes + } + + fn compute_signing_key_hashes( + &self, + ) -> HashMap>> { + let scheme = LPSignatureScheme::Ed25519; + + let kem_key_bytes = self.ed25519_identity_keys.public_key().as_bytes(); + + // convert from `nym_kkt_ciphersuite` types into `nym_nodes_requests` + let digests = produce_key_digests(kem_key_bytes.as_ref()) + .into_iter() + .map(|(f, d)| (f.into(), d)) + .collect(); + + let mut hashes = HashMap::new(); + hashes.insert(scheme, digests); + hashes } pub(crate) async fn build_http_server(&self) -> Result { @@ -861,13 +883,13 @@ impl NymNode { policy: None, }; - let (kem, hashes) = self.compute_kem_key_hashes(); let lp_details = api_requests::v1::lewes_protocol::models::LewesProtocol::new( self.modes().entry, self.config.gateway_tasks.lp.announced_control_port(), self.config.gateway_tasks.lp.announced_data_port(), - ) - .with_kem_key_hashes(kem, hashes); + self.compute_kem_key_hashes(), + self.compute_signing_key_hashes(), + ); let mut config = HttpServerConfig::new() .with_landing_page_assets(self.config.http.landing_page_assets_path.as_ref()) diff --git a/nym-registration-client/src/lp_client/helpers.rs b/nym-registration-client/src/lp_client/helpers.rs index 2637ea0110..79b1b027b1 100644 --- a/nym-registration-client/src/lp_client/helpers.rs +++ b/nym-registration-client/src/lp_client/helpers.rs @@ -90,5 +90,8 @@ 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) + LpRemotePeer::new(identity, data.x25519).with_key_digests( + data.expected_kem_key_hashes, + data.expected_signing_key_hashes, + ) } diff --git a/tools/nym-lp-client/src/client.rs b/tools/nym-lp-client/src/client.rs index 7dfd2122ee..cc1e94768b 100644 --- a/tools/nym-lp-client/src/client.rs +++ b/tools/nym-lp-client/src/client.rs @@ -118,7 +118,10 @@ impl SpeedtestClient { ); let gw_peer = LpRemotePeer::new(self.gateway.identity, self.gateway.identity.to_x25519()?) - .with_kem_key_digests(self.gateway.kem_key_hashes.clone()); + .with_key_digests( + self.gateway.kem_key_hashes.clone(), + self.gateway.signing_key_hashes.clone(), + ); let mut lp_client = LpRegistrationClient::::new_with_default_config( self.identity_keypair.clone(), @@ -161,7 +164,10 @@ impl SpeedtestClient { ); let gw_peer = LpRemotePeer::new(self.gateway.identity, self.gateway.identity.to_x25519()?) - .with_kem_key_digests(self.gateway.kem_key_hashes.clone()); + .with_key_digests( + self.gateway.kem_key_hashes.clone(), + self.gateway.signing_key_hashes.clone(), + ); let mut lp_client = LpRegistrationClient::new_with_default_config( self.identity_keypair.clone(), diff --git a/tools/nym-lp-client/src/topology.rs b/tools/nym-lp-client/src/topology.rs index ebf3d8997f..5735134317 100644 --- a/tools/nym-lp-client/src/topology.rs +++ b/tools/nym-lp-client/src/topology.rs @@ -10,7 +10,7 @@ use nym_api_requests::models::{LPHashFunction, LPKEM}; use nym_api_requests::nym_nodes::SkimmedNode; use nym_crypto::asymmetric::ed25519; use nym_http_api_client::UserAgent; -use nym_kkt_ciphersuite::{KEMKeyDigests, KEM}; +use nym_kkt_ciphersuite::{KEMKeyDigests, SignatureScheme, SigningKeyDigests, KEM}; use nym_sphinx_types::Node as SphinxNode; use nym_topology::{NymRouteProvider, NymTopology, NymTopologyMetadata}; use nym_validator_client::nym_api::NymApiClientExt; @@ -31,6 +31,7 @@ const LP_DATA_PORT: u16 = 51264; pub struct GatewayInfo { pub identity: ed25519::PublicKey, pub kem_key_hashes: HashMap, + pub signing_key_hashes: HashMap, pub sphinx_key: nym_crypto::asymmetric::x25519::PublicKey, /// Mix host (IP:port for Sphinx mixing) From d99eff917879f53910a8dbe842f0b08809493ec7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Tue, 27 Jan 2026 15:39:07 +0000 Subject: [PATCH 26/51] Lp/dvpn psk injection (#6378) * feat: clients to generate pseudorandom PSK for wg reg * updating PSK of existing peers * gateway probe fixes --- .../20260127120000_add_wireguard_peer_psk.sql | 7 ++++ common/gateway-storage/src/lib.rs | 17 ++++++++++ common/gateway-storage/src/models.rs | 8 +++++ common/gateway-storage/src/traits.rs | 23 +++++++++++++ common/gateway-storage/src/wireguard_peers.rs | 25 ++++++++++++++ common/registration/src/lp_messages.rs | 16 +++++++-- gateway/src/node/lp_listener/registration.rs | 29 +++++++++++++++- integration-tests/src/lp_registration.rs | 4 +++ nym-gateway-probe/src/lib.rs | 13 +++++-- nym-registration-client/src/lib.rs | 20 +++++++++-- .../src/lp_client/client.rs | 34 ++++++++++++++----- .../src/lp_client/nested_session.rs | 18 +++++++--- 12 files changed, 193 insertions(+), 21 deletions(-) create mode 100644 common/gateway-storage/migrations/20260127120000_add_wireguard_peer_psk.sql diff --git a/common/gateway-storage/migrations/20260127120000_add_wireguard_peer_psk.sql b/common/gateway-storage/migrations/20260127120000_add_wireguard_peer_psk.sql new file mode 100644 index 0000000000..783c675022 --- /dev/null +++ b/common/gateway-storage/migrations/20260127120000_add_wireguard_peer_psk.sql @@ -0,0 +1,7 @@ +/* + * Copyright 2026 - Nym Technologies SA + * SPDX-License-Identifier: Apache-2.0 + */ + +ALTER TABLE wireguard_peer + ADD COLUMN psk VARCHAR; \ No newline at end of file diff --git a/common/gateway-storage/src/lib.rs b/common/gateway-storage/src/lib.rs index 064a34daf9..55eada49e1 100644 --- a/common/gateway-storage/src/lib.rs +++ b/common/gateway-storage/src/lib.rs @@ -577,4 +577,21 @@ impl BandwidthGatewayStorage for GatewayStorage { .await?; Ok(()) } + + /// Update the stored PSK of the wireguard peer. + /// + /// # Arguments + /// + /// * `public_key`: the unique public key of the wireguard peer. + /// * `psk`: the PSK of the wireguard peer. + async fn update_peer_psk( + &self, + public_key: &str, + psk: Option<&str>, + ) -> Result<(), GatewayStorageError> { + self.wireguard_peer_manager + .update_peer_psk(public_key, psk) + .await?; + Ok(()) + } } diff --git a/common/gateway-storage/src/models.rs b/common/gateway-storage/src/models.rs index d982f2ec28..86c4457ddd 100644 --- a/common/gateway-storage/src/models.rs +++ b/common/gateway-storage/src/models.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-3.0-only use crate::{error::GatewayStorageError, make_bincode_serializer}; +use defguard_wireguard_rs::key::Key; use nym_credentials_interface::{AvailableBandwidth, ClientTicket, CredentialSpendingData}; use nym_gateway_requests::shared_key::SharedSymmetricKey; use sqlx::FromRow; @@ -95,6 +96,7 @@ pub struct WireguardPeer { pub public_key: String, pub allowed_ips: Vec, pub client_id: i64, + pub psk: Option, } impl WireguardPeer { @@ -110,6 +112,7 @@ impl WireguardPeer { source, })?, client_id, + psk: value.preshared_key.map(|psk| psk.to_lower_hex()), }) } } @@ -132,6 +135,11 @@ impl TryFrom for defguard_wireguard_rs::host::Peer { field_key: "allowed_ips", source, })?, + preshared_key: value + .psk + .map(|psk| Key::decode(&psk)) + .transpose() + .map_err(|_| Self::Error::TypeConversion { field_key: "psk" })?, ..Default::default() }) } diff --git a/common/gateway-storage/src/traits.rs b/common/gateway-storage/src/traits.rs index f3a8efcdf8..f07a2b275b 100644 --- a/common/gateway-storage/src/traits.rs +++ b/common/gateway-storage/src/traits.rs @@ -170,6 +170,18 @@ pub trait BandwidthGatewayStorage: dyn_clone::DynClone { /// * `peer_public_key`: wireguard public key of the peer to be removed. async fn remove_wireguard_peer(&self, peer_public_key: &str) -> Result<(), GatewayStorageError>; + + /// Update the stored PSK of the wireguard peer. + /// + /// # Arguments + /// + /// * `public_key`: the unique public key of the wireguard peer. + /// * `psk`: the PSK of the wireguard peer. + async fn update_peer_psk( + &self, + public_key: &str, + psk: Option<&str>, + ) -> Result<(), GatewayStorageError>; } #[cfg(feature = "mock")] @@ -507,5 +519,16 @@ pub mod mock { self.write().await.wireguard_peers.remove(peer_public_key); Ok(()) } + + async fn update_peer_psk( + &self, + public_key: &str, + psk: Option<&str>, + ) -> Result<(), GatewayStorageError> { + if let Some(peer) = self.write().await.wireguard_peers.get_mut(public_key) { + peer.psk = psk.map(|psk| psk.to_owned()) + } + Ok(()) + } } } diff --git a/common/gateway-storage/src/wireguard_peers.rs b/common/gateway-storage/src/wireguard_peers.rs index 22bf178d99..c999d09358 100644 --- a/common/gateway-storage/src/wireguard_peers.rs +++ b/common/gateway-storage/src/wireguard_peers.rs @@ -98,4 +98,29 @@ impl WgPeerManager { .await?; Ok(()) } + + /// Update the stored PSK of the wireguard peer. + /// + /// # Arguments + /// + /// * `public_key`: the unique public key of the wireguard peer. + /// * `psk`: the PSK of the wireguard peer. + pub(crate) async fn update_peer_psk( + &self, + public_key: &str, + psk: Option<&str>, + ) -> Result<(), sqlx::Error> { + sqlx::query!( + r#" + UPDATE wireguard_peer + SET psk = ? + WHERE public_key = ? + "#, + psk, + public_key, + ) + .execute(&self.connection_pool) + .await?; + Ok(()) + } } diff --git a/common/registration/src/lp_messages.rs b/common/registration/src/lp_messages.rs index 1f20710f8a..5e36fb4885 100644 --- a/common/registration/src/lp_messages.rs +++ b/common/registration/src/lp_messages.rs @@ -6,6 +6,7 @@ use crate::WireguardConfiguration; use crate::serialisation::{BincodeError, BincodeOptions, lp_bincode_serializer}; use nym_credentials_interface::{CredentialSpendingData, TicketType}; +use nym_crypto::aes::cipher::crypto_common::rand_core::{CryptoRng, RngCore}; use nym_crypto::asymmetric::ed25519; use serde::{Deserialize, Serialize}; @@ -50,6 +51,9 @@ pub struct LpDvpnRegistrationRequest { /// Ticket type for bandwidth allocation pub ticket_type: TicketType, + + /// Preshared key to be used for the connection + pub psk: [u8; 32], } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -108,17 +112,25 @@ pub struct LpRegistrationResponse { impl LpRegistrationRequest { /// Create a new dVPN registration request - pub fn new_dvpn( + pub fn new_dvpn( + rng: &mut R, wg_public_key: nym_wireguard_types::PeerPublicKey, credential: CredentialSpendingData, ticket_type: TicketType, - ) -> Self { + ) -> Self + where + R: RngCore + CryptoRng, + { + let mut psk = [0u8; 32]; + rng.fill_bytes(&mut psk); + Self { registration_data: LpRegistrationData::Dvpn { data: Box::new(LpDvpnRegistrationRequest { wg_public_key, credential, ticket_type, + psk, }), }, #[allow(clippy::expect_used)] diff --git a/gateway/src/node/lp_listener/registration.rs b/gateway/src/node/lp_listener/registration.rs index 850f4651a1..8d004d1d28 100644 --- a/gateway/src/node/lp_listener/registration.rs +++ b/gateway/src/node/lp_listener/registration.rs @@ -22,6 +22,7 @@ use nym_registration_common::{ LpRegistrationData, LpRegistrationRequest, LpRegistrationResponse, WireguardConfiguration, }; use nym_wireguard::PeerControlRequest; +use nym_wireguard_types::PeerPublicKey; use std::sync::Arc; use time::OffsetDateTime; use tracing::*; @@ -194,6 +195,20 @@ async fn check_existing_registration( )) } +/// In the case of an already registered WG peer, update its PSK. +async fn update_peer_psk( + peer: PeerPublicKey, + psk: Key, + state: &LpHandlerState, +) -> Result<(), GatewayError> { + let encoded_psk = psk.to_lower_hex(); + state + .storage + .update_peer_psk(&peer.to_string(), Some(&encoded_psk)) + .await?; + Ok(()) +} + async fn process_dvpn_registration( request: Box, state: &LpHandlerState, @@ -206,6 +221,13 @@ async fn process_dvpn_registration( // without wasting credentials let wg_key_str = request.wg_public_key.to_string(); if let Some(existing_response) = check_existing_registration(&wg_key_str, state).await { + // TODO: this flow will be changed in subsequent PRs as it's wasting credentials regardless + if let Err(err) = update_peer_psk(request.wg_public_key, Key::new(request.psk), state).await + { + return LpRegistrationResponse::error(format!( + "WireGuard peer PSK update failed: {err}" + )); + } info!("LP dVPN re-registration for existing peer {wg_key_str} (idempotent)",); inc!("lp_registration_dvpn_idempotent"); return existing_response; @@ -215,6 +237,7 @@ async fn process_dvpn_registration( let (gateway_data, client_id) = match register_wg_peer( request.wg_public_key.inner().as_ref(), request.ticket_type, + Key::new(request.psk), state, ) .await @@ -349,6 +372,7 @@ pub async fn process_registration( async fn register_wg_peer( public_key_bytes: &[u8], ticket_type: nym_credentials_interface::TicketType, + psk: Key, state: &LpHandlerState, ) -> Result<(WireguardConfiguration, i64), GatewayError> { let Some(wg_controller) = &state.wg_peer_controller else { @@ -374,8 +398,10 @@ async fn register_wg_peer( let peer_key = Key::new(key_bytes); // Allocate IPs from centralized pool managed by PeerController - let registration_data = nym_wireguard::PeerRegistrationData::new(peer_key.clone()); + let registration_data = + nym_wireguard::PeerRegistrationData::new(peer_key.clone()).with_preshared_key(psk); + let psk = registration_data.preshared_key.clone(); // Request IP allocation from PeerController let (tx, rx) = oneshot::channel(); wg_controller @@ -415,6 +441,7 @@ async fn register_wg_peer( format!("{client_ipv6}/128").parse()?, ]; peer.persistent_keepalive_interval = Some(25); + peer.preshared_key = psk; // Store peer in database FIRST (before adding to controller) // This ensures bandwidth storage exists when controller's generate_bandwidth_manager() is called diff --git a/integration-tests/src/lp_registration.rs b/integration-tests/src/lp_registration.rs index 6fed38ee7e..595c48128f 100644 --- a/integration-tests/src/lp_registration.rs +++ b/integration-tests/src/lp_registration.rs @@ -441,6 +441,7 @@ mod tests { let gateway_identity = entry.base.peer.ed25519().public_key(); let registration_result = client .register( + &mut client_rng, &wg_keypair, gateway_identity, &client_data.ticket_provider, @@ -506,6 +507,7 @@ mod tests { let gateway_identity = entry.base.peer.ed25519().public_key(); let registration_result = client .register( + &mut client_rng, &wg_keypair, gateway_identity, &client_data.ticket_provider, @@ -646,6 +648,7 @@ mod tests { let exit_registration_result = nested_session .handshake_and_register( &mut entry_client, + &mut client_rng, &client_data.base.x25519_wg_keys, exit.base.peer.ed25519().public_key(), &client_data.ticket_provider, @@ -657,6 +660,7 @@ mod tests { // 14. complete registration with the entry let entry_registration_result = entry_client .register( + &mut client_rng, &client_data.base.x25519_wg_keys, entry.base.peer.ed25519().public_key(), &client_data.ticket_provider, diff --git a/nym-gateway-probe/src/lib.rs b/nym-gateway-probe/src/lib.rs index 50695533c1..925c9c030c 100644 --- a/nym-gateway-probe/src/lib.rs +++ b/nym-gateway-probe/src/lib.rs @@ -1168,7 +1168,7 @@ where ); match client - .register_with_credential(&wg_keypair, credential, ticket_type) + .register_with_credential(&mut rng, &wg_keypair, credential, ticket_type) .await { Ok(data) => data, @@ -1183,6 +1183,7 @@ where info!("Using real bandwidth controller for LP registration"); match client .register( + &mut rng, &wg_keypair, &gateway_ed25519_pubkey, bandwidth_controller, @@ -1311,6 +1312,7 @@ where match nested_session .handshake_and_register_with_credential( &mut entry_client, + &mut rng, &exit_wg_keypair, credential, TicketType::V1WireguardExit, @@ -1327,6 +1329,7 @@ where match nested_session .handshake_and_register( &mut entry_client, + &mut rng, &exit_wg_keypair, &exit_gateway_pubkey, bandwidth_controller, @@ -1357,7 +1360,12 @@ where TicketType::V1WireguardEntry, ); match entry_client - .register_with_credential(&entry_wg_keypair, credential, TicketType::V1WireguardEntry) + .register_with_credential( + &mut rng, + &entry_wg_keypair, + credential, + TicketType::V1WireguardEntry, + ) .await { Ok(data) => data, @@ -1369,6 +1377,7 @@ where } else { match entry_client .register( + &mut rng, &entry_wg_keypair, &entry_gateway_pubkey, bandwidth_controller, diff --git a/nym-registration-client/src/lib.rs b/nym-registration-client/src/lib.rs index 8b31d92658..42bd7b4be6 100644 --- a/nym-registration-client/src/lib.rs +++ b/nym-registration-client/src/lib.rs @@ -23,6 +23,7 @@ pub use builder::config::{ pub use config::RegistrationMode; pub use error::RegistrationClientError; pub use lp_client::{LpConfig, LpRegistrationClient, NestedLpSession, error::LpClientError}; +use nym_crypto::aes::cipher::crypto_common::rand_core::{CryptoRng, RngCore}; pub use types::{ LpRegistrationResult, MixnetRegistrationResult, RegistrationResult, WireguardRegistrationResult, }; @@ -153,7 +154,14 @@ impl RegistrationClient { ))) } - async fn register_lp(self) -> Result { + // create dedicated method taking RNG instance for tests + async fn register_lp_with_rng( + self, + rng: &mut R, + ) -> Result + where + R: RngCore + CryptoRng, + { // Extract and validate LP data let entry_lp_data = self.config.entry.node.lp_data.ok_or( RegistrationClientError::LpRegistrationNotPossible { @@ -210,8 +218,9 @@ impl RegistrationClient { // Perform handshake and registration with exit gateway (all via entry forwarding) let exit_gateway_data = nested_session - .handshake_and_register::( + .handshake_and_register::( &mut entry_client, + rng, &self.config.exit.keys, &self.config.exit.node.identity, &*self.bandwidth_controller, @@ -230,6 +239,7 @@ impl RegistrationClient { tracing::info!("Registering with entry gateway"); let entry_gateway_data = entry_client .register( + rng, &self.config.entry.keys, &self.config.entry.node.identity, &*self.bandwidth_controller, @@ -257,6 +267,12 @@ impl RegistrationClient { }))) } + async fn register_lp(self) -> Result { + let mut rng = rand::thread_rng(); + + self.register_lp_with_rng(&mut rng).await + } + pub async fn register(self) -> Result { self.cancel_token .clone() diff --git a/nym-registration-client/src/lp_client/client.rs b/nym-registration-client/src/lp_client/client.rs index 42c2489974..4d15e2a5bc 100644 --- a/nym-registration-client/src/lp_client/client.rs +++ b/nym-registration-client/src/lp_client/client.rs @@ -21,6 +21,7 @@ use nym_lp::state_machine::{LpAction, LpData, LpInput, LpStateMachine}; use nym_lp_transport::traits::LpTransport; use nym_registration_common::{LpRegistrationRequest, WireguardConfiguration}; use nym_wireguard_types::PeerPublicKey; +use rand::{CryptoRng, RngCore}; use std::net::SocketAddr; use std::sync::Arc; use std::time::{SystemTime, UNIX_EPOCH}; @@ -662,6 +663,7 @@ where /// for that please use [`Self::register_with_retry`] instead /// /// # Arguments + /// * `rng` - RNG instance for generating PSK /// * `wg_keypair` - Client's WireGuard x25519 keypair /// * `gateway_identity` - Gateway's ed25519 identity for credential verification /// * `bandwidth_controller` - Provider for bandwidth credentials @@ -678,13 +680,17 @@ where /// - Network communication fails /// - Gateway rejected the registration /// - Response times out (see LpConfig::registration_timeout) - pub async fn register( + pub async fn register( &mut self, + rng: &mut R, wg_keypair: &x25519::KeyPair, gateway_identity: &ed25519::PublicKey, bandwidth_controller: &dyn BandwidthTicketProvider, ticket_type: TicketType, - ) -> Result { + ) -> Result + where + R: RngCore + CryptoRng, + { tracing::debug!("Acquiring bandwidth credential for registration"); // Get bandwidth credential from controller @@ -698,7 +704,7 @@ where })? .data; - self.register_with_credential(wg_keypair, credential, ticket_type) + self.register_with_credential(rng, wg_keypair, credential, ticket_type) .await } @@ -708,6 +714,7 @@ where /// Uses the persistent TCP connection established during handshake. /// /// # Arguments + /// * `rng` - RNG instance for generating PSK /// * `wg_keypair` - Client's WireGuard x25519 keypair /// * `credential` - Pre-generated bandwidth credential /// * `ticket_type` - Type of bandwidth ticket @@ -721,17 +728,21 @@ where /// /// # Panics / Errors /// Returns error if handshake not completed or if connection was closed. - pub async fn register_with_credential( + pub async fn register_with_credential( &mut self, + rng: &mut R, wg_keypair: &x25519::KeyPair, credential: CredentialSpendingData, ticket_type: TicketType, - ) -> Result { + ) -> Result + where + R: RngCore + CryptoRng, + { tracing::debug!("Sending registration request (persistent connection)"); // 1. Build registration request let wg_public_key = PeerPublicKey::new(wg_keypair.public_key().to_bytes().into()); - let request = LpRegistrationRequest::new_dvpn(wg_public_key, credential, ticket_type); + let request = LpRegistrationRequest::new_dvpn(rng, wg_public_key, credential, ticket_type); tracing::trace!("Built registration request: {:?}", request); @@ -850,6 +861,7 @@ where /// will return the cached result instead of spending a new credential. /// /// # Arguments + /// * `rng` - RNG instance for generating PSK /// * `wg_keypair` - Client's WireGuard x25519 keypair (same key used for all retries) /// * `gateway_identity` - Gateway's ed25519 identity for credential verification /// * `bandwidth_controller` - Provider for bandwidth credentials @@ -865,14 +877,18 @@ where /// # Note /// Unlike `register()`, this method handles the full flow including handshake. /// Do NOT call `perform_handshake()` before this method. - pub async fn register_with_retry( + pub async fn register_with_retry( &mut self, + rng: &mut R, wg_keypair: &x25519::KeyPair, gateway_identity: &ed25519::PublicKey, bandwidth_controller: &dyn BandwidthTicketProvider, ticket_type: TicketType, max_retries: u32, - ) -> Result { + ) -> Result + where + R: RngCore + CryptoRng, + { tracing::debug!("Starting resilient registration (max_retries={max_retries})",); // Acquire credential ONCE before any attempts @@ -916,7 +932,7 @@ where } match self - .register_with_credential(wg_keypair, credential.clone(), ticket_type) + .register_with_credential(rng, wg_keypair, credential.clone(), ticket_type) .await { Ok(data) => { diff --git a/nym-registration-client/src/lp_client/nested_session.rs b/nym-registration-client/src/lp_client/nested_session.rs index 4d74105999..fd4e486378 100644 --- a/nym-registration-client/src/lp_client/nested_session.rs +++ b/nym-registration-client/src/lp_client/nested_session.rs @@ -32,6 +32,7 @@ use nym_lp::{LpMessage, LpPacket}; use nym_lp_transport::traits::LpTransport; use nym_registration_common::{LpRegistrationRequest, WireguardConfiguration}; use nym_wireguard_types::PeerPublicKey; +use rand::{CryptoRng, RngCore}; use std::sync::Arc; use std::time::{SystemTime, UNIX_EPOCH}; @@ -287,15 +288,17 @@ impl NestedLpSession { /// /// # Returns /// * `Ok(GatewayData)` - Exit gateway configuration data on successful registration - pub async fn handshake_and_register_with_credential( + pub async fn handshake_and_register_with_credential( &mut self, outer_client: &mut LpRegistrationClient, + rng: &mut R, wg_keypair: &x25519::KeyPair, credential: nym_credentials_interface::CredentialSpendingData, ticket_type: TicketType, ) -> Result where S: LpTransport + Unpin, + R: RngCore + CryptoRng, { // Step 1: Perform handshake with exit gateway via forwarding self.perform_handshake(outer_client).await?; @@ -311,7 +314,7 @@ impl NestedLpSession { // Step 3: Build registration request (credential already provided) let wg_public_key = PeerPublicKey::new(wg_keypair.public_key().to_bytes().into()); - let request = LpRegistrationRequest::new_dvpn(wg_public_key, credential, ticket_type); + let request = LpRegistrationRequest::new_dvpn(rng, wg_public_key, credential, ticket_type); tracing::trace!("Built registration request: {:?}", request); @@ -425,9 +428,10 @@ impl NestedLpSession { /// - Forwarding through entry gateway fails /// - Response decryption/deserialization fails /// - Gateway rejects the registration - pub async fn handshake_and_register( + pub async fn handshake_and_register( &mut self, outer_client: &mut LpRegistrationClient, + rng: &mut R, wg_keypair: &x25519::KeyPair, gateway_identity: &ed25519::PublicKey, bandwidth_controller: &dyn BandwidthTicketProvider, @@ -435,6 +439,7 @@ impl NestedLpSession { ) -> Result where S: LpTransport + Unpin, + R: RngCore + CryptoRng, { // Step 1: Perform handshake with exit gateway via forwarding self.perform_handshake(outer_client).await?; @@ -461,7 +466,7 @@ impl NestedLpSession { // Step 4: Build registration request let wg_public_key = PeerPublicKey::new(wg_keypair.public_key().to_bytes().into()); - let request = LpRegistrationRequest::new_dvpn(wg_public_key, credential, ticket_type); + let request = LpRegistrationRequest::new_dvpn(rng, wg_public_key, credential, ticket_type); tracing::trace!("Built registration request: {:?}", request); @@ -577,8 +582,9 @@ impl NestedLpSession { /// # Errors /// Returns an error if all retry attempts fail. #[allow(clippy::too_many_arguments)] - pub async fn handshake_and_register_with_retry( + pub async fn handshake_and_register_with_retry( &mut self, + rng: &mut R, outer_client: &mut LpRegistrationClient, wg_keypair: &x25519::KeyPair, gateway_identity: &ed25519::PublicKey, @@ -588,6 +594,7 @@ impl NestedLpSession { ) -> Result where S: LpTransport + Unpin, + R: RngCore + CryptoRng, { tracing::debug!( "Starting resilient exit registration (max_retries={})", @@ -635,6 +642,7 @@ impl NestedLpSession { match self .handshake_and_register_with_credential( outer_client, + rng, wg_keypair, credential.clone(), ticket_type, From 00e4caec08627dad55f3bd911fff99813191bf95 Mon Sep 17 00:00:00 2001 From: Tommy Verrall <60836166+tommyv1987@users.noreply.github.com> Date: Tue, 27 Jan 2026 17:04:34 +0100 Subject: [PATCH 27/51] Update NYM_VPN_APIS configuration in sandbox.env Remove front hosts null --- envs/sandbox.env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/envs/sandbox.env b/envs/sandbox.env index 549947e440..8495e73e18 100644 --- a/envs/sandbox.env +++ b/envs/sandbox.env @@ -28,4 +28,4 @@ UPGRADE_MODE_ATTESTER_ED25519_PUBKEY=EGwzKXPrqStv8cHF68VT2LbQuEBGDPzhCAixScvybfe UPGRADE_MODE_ATTESTATION_URL=http://upgrade-mode.performance.nymte.ch/.wellknown/sandbox/attestation.json NYM_APIS=[{"url":"https://sandbox-nym-api1.nymtech.net/api/","front_hosts":null},{"url":"https://nym-frontdoor.vercel.app/sandbox/nym-api/","front_hosts":["vercel.app","vercel.com"]}] -NYM_VPN_APIS=[{"front_hosts":null},{"url":"https://nym-vpn-api-git-deploy-sandbox-nyx-network-staging.vercel.app/api/","front_hosts":["vercel.app","vercel.com"]},{"url":"https://nym-frontdoor.vercel.app/sandbox/nym-vpn-api/","front_hosts":["vercel.app","vercel.com"]}] \ No newline at end of file +NYM_VPN_APIS=[{"url":"https://nym-vpn-api-git-deploy-sandbox-nyx-network-staging.vercel.app/api/","front_hosts":["vercel.app","vercel.com"]},{"url":"https://nym-frontdoor.vercel.app/sandbox/nym-vpn-api/","front_hosts":["vercel.app","vercel.com"]}] From 8e4cae2f570417517a6a51d18f0ed64c667cded9 Mon Sep 17 00:00:00 2001 From: Tommy Verrall <60836166+tommyv1987@users.noreply.github.com> Date: Tue, 27 Jan 2026 17:05:02 +0100 Subject: [PATCH 28/51] Update NYM_VPN_APIS configuration in canary.env Tidy up --- envs/canary.env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/envs/canary.env b/envs/canary.env index 96c2dd38e7..c42ce00a20 100644 --- a/envs/canary.env +++ b/envs/canary.env @@ -27,4 +27,4 @@ UPGRADE_MODE_ATTESTER_ED25519_PUBKEY=U1NXToPYUTsh7pYPLcwXCXwcL6pGoLUou7fyAJrNz8b UPGRADE_MODE_ATTESTATION_URL=http://upgrade-mode.performance.nymte.ch/.wellknown/canary/attestation.json NYM_APIS=[{"url":"https://canary-api.performance.nymte.ch/api/","front_hosts":null},{"url":"https://nym-frontdoor.vercel.app/canary/nym-api/","front_hosts":["vercel.app","vercel.com"]}] -NYM_VPN_APIS=[{"front_hosts":null},{"url":"https://nym-vpn-api-git-deploy-canary-nyx-network-staging.vercel.app/api/","front_hosts":["vercel.app","vercel.com"]},{"url":"https://nym-frontdoor.vercel.app/canary/nym-vpn-api/","front_hosts":["vercel.app","vercel.com"]}] \ No newline at end of file +NYM_VPN_APIS=[{"url":"https://nym-vpn-api-git-deploy-canary-nyx-network-staging.vercel.app/api/","front_hosts":["vercel.app","vercel.com"]},{"url":"https://nym-frontdoor.vercel.app/canary/nym-vpn-api/","front_hosts":["vercel.app","vercel.com"]}] From f4b59158dfeafb1eacf2f07eaea20e594d60972a Mon Sep 17 00:00:00 2001 From: Andrej Mihajlov Date: Thu, 29 Jan 2026 07:26:14 +0100 Subject: [PATCH 29/51] Box reqwest::Url to keep HttpClientError below 128 byte size which triggers clippy --- common/http-api-client/src/lib.rs | 24 +++++++++---------- .../src/metrics_scraper/error.rs | 8 +++++-- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/common/http-api-client/src/lib.rs b/common/http-api-client/src/lib.rs index bc3a8d7430..06a0511564 100644 --- a/common/http-api-client/src/lib.rs +++ b/common/http-api-client/src/lib.rs @@ -329,14 +329,14 @@ pub enum HttpClientError { #[error("failed to send request for {url}: {source}")] RequestSendFailure { - url: reqwest::Url, + url: Box, #[source] source: ReqwestErrorWrapper, }, #[error("failed to read response body from {url}: {source}")] ResponseReadFailure { - url: reqwest::Url, + url: Box, headers: Box, status: StatusCode, #[source] @@ -353,7 +353,7 @@ pub enum HttpClientError { }, #[error("the requested resource could not be found at {url}")] - NotFound { url: reqwest::Url }, + NotFound { url: Box }, #[error("attempted to use domain fronting and clone a request containing stream data")] AttemptedToCloneStreamRequest, @@ -365,7 +365,7 @@ pub enum HttpClientError { "the request for {url} failed with status '{status}'. no additional error message provided. response headers: {headers:?}" )] RequestFailure { - url: reqwest::Url, + url: Box, status: StatusCode, headers: Box, }, @@ -374,7 +374,7 @@ pub enum HttpClientError { "the returned response from {url} was empty. status: '{status}'. response headers: {headers:?}" )] EmptyResponse { - url: reqwest::Url, + url: Box, status: StatusCode, headers: Box, }, @@ -383,7 +383,7 @@ pub enum HttpClientError { "failed to resolve request for {url}. status: '{status}'. response headers: {headers:?}. additional error message: {error}" )] EndpointFailure { - url: reqwest::Url, + url: Box, status: StatusCode, headers: Box, error: String, @@ -453,7 +453,7 @@ impl HttpClientError { pub fn request_send_error(url: reqwest::Url, source: reqwest::Error) -> Self { HttpClientError::RequestSendFailure { - url, + url: Box::new(url), source: ReqwestErrorWrapper(source), } } @@ -1517,7 +1517,7 @@ where if !allow_empty && let Some(0) = res.content_length() { return Err(HttpClientError::EmptyResponse { - url, + url: Box::new(url), status, headers: Box::new(headers), }); @@ -1530,25 +1530,25 @@ where .bytes() .await .map_err(|source| HttpClientError::ResponseReadFailure { - url, + url: Box::new(url), headers: Box::new(headers.clone()), status, source: ReqwestErrorWrapper(source), })?; decode_raw_response(&headers, full) } else if res.status() == StatusCode::NOT_FOUND { - Err(HttpClientError::NotFound { url }) + Err(HttpClientError::NotFound { url: Box::new(url) }) } else { let Ok(plaintext) = res.text().await else { return Err(HttpClientError::RequestFailure { - url, + url: Box::new(url), status, headers: Box::new(headers), }); }; Err(HttpClientError::EndpointFailure { - url, + url: Box::new(url), status, headers: Box::new(headers), error: plaintext, diff --git a/nym-node-status-api/nym-node-status-api/src/metrics_scraper/error.rs b/nym-node-status-api/nym-node-status-api/src/metrics_scraper/error.rs index 26ff4a5d97..65004fbeaf 100644 --- a/nym-node-status-api/nym-node-status-api/src/metrics_scraper/error.rs +++ b/nym-node-status-api/nym-node-status-api/src/metrics_scraper/error.rs @@ -57,7 +57,9 @@ mod tests { let error = NodeScraperError::MalformedHost { host: "".to_string(), node_id: 0, - source: Box::new(NymNodeApiClientError::NotFound { url: dummy_url() }), + source: Box::new(NymNodeApiClientError::NotFound { + url: Box::new(dummy_url()), + }), }; let error_msg = error.to_string(); @@ -112,7 +114,9 @@ mod tests { #[allow(deprecated)] fn test_error_different_sources() { // Test with different NymNodeApiClientError variants - let not_found_error = NymNodeApiClientError::NotFound { url: dummy_url() }; + let not_found_error = NymNodeApiClientError::NotFound { + url: Box::new(dummy_url()), + }; let error1 = NodeScraperError::MalformedHost { host: "host1".to_string(), node_id: 1, From 561182ce6b4d434a50e6fd7a7ef432456cf208fe Mon Sep 17 00:00:00 2001 From: Simon Wicky Date: Thu, 29 Jan 2026 10:34:57 +0100 Subject: [PATCH 30/51] shuffling files around in the probe, before improving it (#6391) --- .../src/{ => common}/bandwidth_helpers.rs | 0 nym-gateway-probe/src/common/helpers.rs | 64 + nym-gateway-probe/src/{ => common}/icmp.rs | 0 nym-gateway-probe/src/common/mod.rs | 13 +- .../src/{ => common}/netstack.rs | 2 +- nym-gateway-probe/src/{ => common}/nodes.rs | 73 +- nym-gateway-probe/src/common/probe_tests.rs | 697 +++++++++++ nym-gateway-probe/src/{ => common}/types.rs | 0 nym-gateway-probe/src/common/wireguard.rs | 12 +- nym-gateway-probe/src/config/credentials.rs | 29 + nym-gateway-probe/src/config/mod.rs | 10 + nym-gateway-probe/src/config/netstack.rs | 209 ++++ .../src/{mode/mod.rs => config/test_mode.rs} | 0 nym-gateway-probe/src/lib.rs | 1088 +---------------- nym-gateway-probe/src/run.rs | 5 +- 15 files changed, 1118 insertions(+), 1084 deletions(-) rename nym-gateway-probe/src/{ => common}/bandwidth_helpers.rs (100%) create mode 100644 nym-gateway-probe/src/common/helpers.rs rename nym-gateway-probe/src/{ => common}/icmp.rs (100%) rename nym-gateway-probe/src/{ => common}/netstack.rs (99%) rename nym-gateway-probe/src/{ => common}/nodes.rs (89%) create mode 100644 nym-gateway-probe/src/common/probe_tests.rs rename nym-gateway-probe/src/{ => common}/types.rs (100%) create mode 100644 nym-gateway-probe/src/config/credentials.rs create mode 100644 nym-gateway-probe/src/config/mod.rs create mode 100644 nym-gateway-probe/src/config/netstack.rs rename nym-gateway-probe/src/{mode/mod.rs => config/test_mode.rs} (100%) diff --git a/nym-gateway-probe/src/bandwidth_helpers.rs b/nym-gateway-probe/src/common/bandwidth_helpers.rs similarity index 100% rename from nym-gateway-probe/src/bandwidth_helpers.rs rename to nym-gateway-probe/src/common/bandwidth_helpers.rs diff --git a/nym-gateway-probe/src/common/helpers.rs b/nym-gateway-probe/src/common/helpers.rs new file mode 100644 index 0000000000..e80912abc5 --- /dev/null +++ b/nym-gateway-probe/src/common/helpers.rs @@ -0,0 +1,64 @@ +// Copyright 2026 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::common::nodes::TestedNodeLpDetails; +use nym_crypto::asymmetric::ed25519; +use nym_ip_packet_requests::v8::response::{ + ControlResponse, DataResponse, InfoLevel, IpPacketResponse, IpPacketResponseData, +}; +use nym_lp::peer::LpRemotePeer; +use nym_sdk::mixnet::ReconstructedMessage; +use tracing::*; + +pub fn to_lp_remote_peer(identity: ed25519::PublicKey, data: TestedNodeLpDetails) -> LpRemotePeer { + LpRemotePeer::new(identity, data.x25519).with_key_digests( + data.expected_kem_key_hashes, + data.expected_signing_key_hashes, + ) +} + +pub fn mixnet_debug_config( + min_gateway_performance: Option, + ignore_egress_epoch_role: bool, +) -> nym_client_core::config::DebugConfig { + let mut debug_config = nym_client_core::config::DebugConfig::default(); + debug_config + .traffic + .disable_main_poisson_packet_distribution = true; + debug_config.cover_traffic.disable_loop_cover_traffic_stream = true; + if let Some(minimum_gateway_performance) = min_gateway_performance { + debug_config.topology.minimum_gateway_performance = minimum_gateway_performance; + } + if ignore_egress_epoch_role { + debug_config.topology.ignore_egress_epoch_role = ignore_egress_epoch_role; + } + + debug_config +} + +pub fn unpack_data_response(reconstructed_message: &ReconstructedMessage) -> Option { + match IpPacketResponse::from_reconstructed_message(reconstructed_message) { + Ok(response) => match response.data { + IpPacketResponseData::Data(data_response) => Some(data_response), + IpPacketResponseData::Control(control) => match *control { + ControlResponse::Info(info) => { + let msg = format!("Received info response from the mixnet: {}", info.reply); + match info.level { + InfoLevel::Info => info!("{msg}"), + InfoLevel::Warn => warn!("{msg}"), + InfoLevel::Error => error!("{msg}"), + } + None + } + _ => { + info!("Ignoring: {:?}", control); + None + } + }, + }, + Err(err) => { + warn!("Failed to parse mixnet message: {err}"); + None + } + } +} diff --git a/nym-gateway-probe/src/icmp.rs b/nym-gateway-probe/src/common/icmp.rs similarity index 100% rename from nym-gateway-probe/src/icmp.rs rename to nym-gateway-probe/src/common/icmp.rs diff --git a/nym-gateway-probe/src/common/mod.rs b/nym-gateway-probe/src/common/mod.rs index 930623615d..3f2cda9f35 100644 --- a/nym-gateway-probe/src/common/mod.rs +++ b/nym-gateway-probe/src/common/mod.rs @@ -6,8 +6,11 @@ //! This module contains shared functionality used by multiple test modes: //! - WireGuard tunnel testing via netstack -pub mod wireguard; - -pub use wireguard::{ - TwoHopWgTunnelConfig, WgTunnelConfig, run_tunnel_tests, run_two_hop_tunnel_tests, -}; +pub(crate) mod bandwidth_helpers; +pub(crate) mod helpers; +pub(crate) mod icmp; +pub(crate) mod netstack; +pub(crate) mod nodes; +pub(crate) mod probe_tests; +pub(crate) mod types; +pub(crate) mod wireguard; diff --git a/nym-gateway-probe/src/netstack.rs b/nym-gateway-probe/src/common/netstack.rs similarity index 99% rename from nym-gateway-probe/src/netstack.rs rename to nym-gateway-probe/src/common/netstack.rs index 3c7bd0772f..9b033f5898 100644 --- a/nym-gateway-probe/src/netstack.rs +++ b/nym-gateway-probe/src/common/netstack.rs @@ -15,7 +15,7 @@ mod sys { } } -use crate::NetstackArgs; +use crate::config::NetstackArgs; #[derive(serde::Serialize)] pub struct NetstackRequest { diff --git a/nym-gateway-probe/src/nodes.rs b/nym-gateway-probe/src/common/nodes.rs similarity index 89% rename from nym-gateway-probe/src/nodes.rs rename to nym-gateway-probe/src/common/nodes.rs index 202e00cd15..6485920574 100644 --- a/nym-gateway-probe/src/nodes.rs +++ b/nym-gateway-probe/src/common/nodes.rs @@ -1,7 +1,6 @@ // Copyright 2025 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::{TestedNodeDetails, TestedNodeLpDetails}; use anyhow::{Context, anyhow, bail}; use nym_api_requests::models::{ AuthenticatorDetailsV2, DeclaredRolesV2, DescribedNodeTypeV2, HostInformationV2, @@ -10,16 +9,20 @@ use nym_api_requests::models::{ }; use nym_authenticator_requests::AuthenticatorVersion; use nym_bin_common::build_information::BinaryBuildInformationOwned; +use nym_crypto::asymmetric::x25519; use nym_http_api_client::UserAgent; +use nym_kkt_ciphersuite::SignatureScheme; +use nym_kkt_ciphersuite::{KEM, KEMKeyDigests}; use nym_network_defaults::DEFAULT_NYM_NODE_HTTP_PORT; use nym_node_requests::api::client::NymNodeApiClientExt; use nym_node_requests::api::v1::node::models::AuxiliaryDetails as NodeAuxiliaryDetails; use nym_sdk::mixnet::NodeIdentity; +use nym_sdk::mixnet::Recipient; use nym_validator_client::client::NymApiClientExt; use nym_validator_client::models::NymNodeDescriptionV2; use rand::prelude::IteratorRandom; use std::collections::HashMap; -use std::net::SocketAddr; +use std::net::{IpAddr, SocketAddr}; use std::time::Duration; use time::OffsetDateTime; use tracing::{debug, info, warn}; @@ -437,3 +440,69 @@ impl NymApiDirectory { Ok(maybe_entry) } } + +#[derive(Default, Debug)] +pub enum TestedNode { + #[default] + SameAsEntry, + Custom { + identity: NodeIdentity, + shares_entry: bool, + }, +} + +impl TestedNode { + pub fn is_same_as_entry(&self) -> bool { + matches!( + self, + TestedNode::SameAsEntry + | TestedNode::Custom { + shares_entry: true, + .. + } + ) + } +} + +#[derive(Debug, Clone)] +pub struct TestedNodeDetails { + pub identity: NodeIdentity, + pub exit_router_address: Option, + pub authenticator_address: Option, + pub authenticator_version: AuthenticatorVersion, + pub ip_address: Option, + pub lp_data: Option, +} + +#[derive(Debug, Clone)] +pub struct TestedNodeLpDetails { + pub address: SocketAddr, + pub expected_kem_key_hashes: HashMap, + pub expected_signing_key_hashes: HashMap, + pub x25519: x25519::PublicKey, +} + +impl TestedNodeDetails { + /// Create from CLI args (localnet mode - no HTTP query needed) + pub fn from_cli(identity: NodeIdentity, lp_data: TestedNodeLpDetails) -> Self { + Self { + identity, + 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, + authenticator_version: AuthenticatorVersion::UNKNOWN, + } + } + + /// Check if this node has sufficient info for LP testing + pub fn can_test_lp(&self) -> bool { + self.lp_data.is_some() + } + + /// Check if this node has sufficient info for mixnet testing + pub fn can_test_mixnet(&self) -> bool { + self.exit_router_address.is_some() || self.authenticator_address.is_some() + } +} diff --git a/nym-gateway-probe/src/common/probe_tests.rs b/nym-gateway-probe/src/common/probe_tests.rs new file mode 100644 index 0000000000..ef039e6ab4 --- /dev/null +++ b/nym-gateway-probe/src/common/probe_tests.rs @@ -0,0 +1,697 @@ +// Copyright 2026 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::common::nodes::{TestedNodeDetails, TestedNodeLpDetails}; +use crate::common::types::{ + Entry, Exit, IpPingReplies, LpProbeResults, ProbeOutcome, WgProbeResults, +}; +use crate::common::wireguard::{ + TwoHopWgTunnelConfig, WgTunnelConfig, run_tunnel_tests, run_two_hop_tunnel_tests, +}; +use crate::common::{bandwidth_helpers, helpers, icmp}; +use crate::config::NetstackArgs; +use anyhow::bail; +use base64::{Engine, engine::general_purpose}; +use bytes::BytesMut; +use futures::StreamExt; +use nym_authenticator_client::AuthenticatorClient; +use nym_authenticator_requests::{ + AuthenticatorVersion, client_message::ClientMessage, response::AuthenticatorResponse, v2, v3, + v4, v5, v6, +}; +use nym_config::defaults::mixnet_vpn::{NYM_TUN_DEVICE_ADDRESS_V4, NYM_TUN_DEVICE_ADDRESS_V6}; +use nym_connection_monitor::self_ping_and_wait; +use nym_credentials_interface::{CredentialSpendingData, TicketType}; +use nym_crypto::asymmetric::{ed25519, x25519}; +use nym_ip_packet_client::IprClientConnect; +use nym_ip_packet_requests::{IpPair, codec::MultiIpPacketCodec}; +use nym_registration_client::{LpRegistrationClient, NestedLpSession}; +use nym_sdk::mixnet::{MixnetClient, NodeIdentity, Recipient}; +use std::{ + net::{IpAddr, Ipv4Addr, Ipv6Addr}, + sync::Arc, + time::Duration, +}; +use tokio::net::TcpStream; +use tokio_util::{codec::Decoder, sync::CancellationToken}; +use tracing::*; + +pub async fn wg_probe( + mut auth_client: AuthenticatorClient, + gateway_ip: IpAddr, + auth_version: AuthenticatorVersion, + awg_args: String, + netstack_args: NetstackArgs, + // TODO: update type + credential: CredentialSpendingData, +) -> anyhow::Result { + info!("attempting to use authenticator version {auth_version:?}"); + + let mut rng = rand::thread_rng(); + + // that's a long conversion chain + // (it should be simplified later...) + // nym x25519 -> dalek x25519 -> wireguard wrapper x25519 + let private_key = nym_crypto::asymmetric::encryption::PrivateKey::new(&mut rng); + let public_key = private_key.public_key(); + + let authenticator_pub_key = public_key.inner().into(); + let init_message = match auth_version { + AuthenticatorVersion::V2 => ClientMessage::Initial(Box::new( + v2::registration::InitMessage::new(authenticator_pub_key), + )), + AuthenticatorVersion::V3 => ClientMessage::Initial(Box::new( + v3::registration::InitMessage::new(authenticator_pub_key), + )), + AuthenticatorVersion::V4 => ClientMessage::Initial(Box::new( + v4::registration::InitMessage::new(authenticator_pub_key), + )), + AuthenticatorVersion::V5 => ClientMessage::Initial(Box::new( + v5::registration::InitMessage::new(authenticator_pub_key), + )), + AuthenticatorVersion::V6 => ClientMessage::Initial(Box::new( + v6::registration::InitMessage::new(authenticator_pub_key), + )), + AuthenticatorVersion::V1 | AuthenticatorVersion::UNKNOWN => bail!("unknown version number"), + }; + + let mut wg_outcome = WgProbeResults::default(); + + info!( + "connecting to authenticator: {}...", + auth_client.auth_recipient + ); + let response = auth_client + .send_and_wait_for_response(&init_message) + .await?; + + let registered_data = match response { + AuthenticatorResponse::PendingRegistration(pending_registration_response) => { + // Unwrap since we have already checked that we have the keypair. + debug!("Verifying data"); + pending_registration_response.verify(&private_key)?; + + let credential = credential + .try_into() + .inspect_err(|err| error!("invalid zk-nym data: {err}")) + .ok(); + + let finalized_message = + pending_registration_response.finalise_registration(&private_key, credential); + let client_message = ClientMessage::Final(finalized_message); + + let response = auth_client + .send_and_wait_for_response(&client_message) + .await?; + let AuthenticatorResponse::Registered(registered_response) = response else { + bail!("Unexpected response"); + }; + registered_response + } + AuthenticatorResponse::Registered(registered_response) => registered_response, + _ => bail!("Unexpected response"), + }; + + let peer_public = registered_data.pub_key().inner(); + let static_private = x25519_dalek::StaticSecret::from(private_key.to_bytes()); + let public_key_bs64 = general_purpose::STANDARD.encode(peer_public.as_bytes()); + let private_key_hex = hex::encode(static_private.to_bytes()); + let public_key_hex = hex::encode(peer_public.as_bytes()); + + info!("WG connection details"); + info!("Peer public key: {}", public_key_bs64); + info!( + "ips {}(v4) {}(v6), port {}", + registered_data.private_ips().ipv4, + registered_data.private_ips().ipv6, + registered_data.wg_port(), + ); + + let wg_endpoint = format!("{gateway_ip}:{}", registered_data.wg_port()); + + info!("Successfully registered with the gateway"); + + wg_outcome.can_register = true; + + // Run tunnel connectivity tests using shared helper + let tunnel_config = WgTunnelConfig::new( + registered_data.private_ips().ipv4.to_string(), + registered_data.private_ips().ipv6.to_string(), + private_key_hex, + public_key_hex, + wg_endpoint, + ); + + run_tunnel_tests(&tunnel_config, &netstack_args, &awg_args, &mut wg_outcome); + + Ok(wg_outcome) +} + +pub async fn lp_registration_probe( + gateway_identity: NodeIdentity, + gateway_lp_data: TestedNodeLpDetails, + bandwidth_controller: &nym_bandwidth_controller::BandwidthController< + nym_validator_client::nyxd::NyxdClient, + St, + >, + use_mock_ecash: bool, +) -> anyhow::Result +where + St: nym_sdk::mixnet::CredentialStorage + Clone + Send + Sync + 'static, + ::StorageError: Send + Sync, +{ + use nym_crypto::asymmetric::ed25519; + use nym_registration_client::LpRegistrationClient; + + let lp_address = gateway_lp_data.address; + let peer = helpers::to_lp_remote_peer(gateway_identity, gateway_lp_data); + + info!("Starting LP registration probe for gateway at {lp_address}",); + + let mut lp_outcome = LpProbeResults::default(); + + // Generate Ed25519 keypair for this connection (X25519 will be derived internally by LP) + let mut rng = rand::thread_rng(); + let client_ed25519_keypair = std::sync::Arc::new(ed25519::KeyPair::new(&mut rng)); + + // Step 0: Derive X25519 keys from Ed25519 for the gateways + + // Create LP registration client (uses Ed25519 keys directly, derives X25519 internally) + let mut client = LpRegistrationClient::::new_with_default_config( + client_ed25519_keypair, + peer, + 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 {lp_address}..."); + match client.perform_handshake().await { + Ok(_) => { + info!("LP handshake completed successfully"); + lp_outcome.can_connect = true; // Connection succeeded if handshake succeeded + lp_outcome.can_handshake = true; + } + Err(e) => { + let error_msg = format!("LP handshake failed: {}", e); + error!("{}", error_msg); + lp_outcome.error = Some(error_msg); + return Ok(lp_outcome); + } + } + + // Step 2: Register with gateway (send request + receive response in one call) + info!("Sending LP registration request..."); + + // Generate WireGuard keypair for dVPN registration + let mut rng = rand::thread_rng(); + let wg_keypair = nym_crypto::asymmetric::x25519::KeyPair::new(&mut rng); + + // Convert gateway identity to ed25519 public key + let gateway_ed25519_pubkey = match nym_crypto::asymmetric::ed25519::PublicKey::from_bytes( + &gateway_identity.to_bytes(), + ) { + Ok(key) => key, + Err(e) => { + let error_msg = format!("Failed to convert gateway identity: {}", e); + error!("{}", error_msg); + lp_outcome.error = Some(error_msg); + return Ok(lp_outcome); + } + }; + + // Register using the new packet-per-connection API (returns GatewayData directly) + let ticket_type = TicketType::V1WireguardEntry; + let gateway_data = if use_mock_ecash { + info!("Using mock ecash credential for LP registration"); + let credential = bandwidth_helpers::create_dummy_credential( + &gateway_ed25519_pubkey.to_bytes(), + ticket_type, + ); + + match client + .register_with_credential(&mut rng, &wg_keypair, credential, ticket_type) + .await + { + Ok(data) => data, + Err(e) => { + let error_msg = format!("LP registration failed (mock ecash): {}", e); + error!("{}", error_msg); + lp_outcome.error = Some(error_msg); + return Ok(lp_outcome); + } + } + } else { + info!("Using real bandwidth controller for LP registration"); + match client + .register( + &mut rng, + &wg_keypair, + &gateway_ed25519_pubkey, + bandwidth_controller, + ticket_type, + ) + .await + { + Ok(data) => data, + Err(e) => { + let error_msg = format!("LP registration failed: {}", e); + error!("{}", error_msg); + lp_outcome.error = Some(error_msg); + return Ok(lp_outcome); + } + } + }; + + info!("LP registration successful! Received gateway data:"); + info!(" - Gateway public key: {:?}", gateway_data.public_key); + info!(" - Private IPv4: {}", gateway_data.private_ipv4); + info!(" - Private IPv6: {}", gateway_data.private_ipv6); + info!(" - Endpoint: {}", gateway_data.endpoint); + lp_outcome.can_register = true; + + Ok(lp_outcome) +} + +/// LP-based WireGuard probe: Tests LP nested session registration + WireGuard tunnel connectivity +/// +/// This function tests the full VPN flow using LP registration instead of mixnet+authenticator: +/// 1. Connects to entry gateway (outer LP session) +/// 2. Registers with exit gateway via entry forwarding (nested LP session) +/// 3. Receives WireGuard configuration from both gateways +/// 4. Tests WireGuard tunnel connectivity (IPv4/IPv6) +/// +/// This validates that IP hiding works (exit sees entry IP, not client IP) and that the +/// full VPN tunnel operates correctly after LP registration. +/// +// Known issue in localnet mode - After this probe runs, container networking +// to the external internet becomes unstable while internal container-to-container traffic +// continues to work. The two-hop WireGuard tunnel itself succeeds (handshake completes), +// but subsequent DNS/ping tests may timeout. This appears to be related to Apple Container +// Runtime networking quirks combined with our NAT/iptables configuration. Tracked in +// beads issue nym-vbdo. Workaround: restart the localnet containers between probe runs. +pub async fn wg_probe_lp( + entry_gateway: &TestedNodeDetails, + exit_gateway: &TestedNodeDetails, + bandwidth_controller: &nym_bandwidth_controller::BandwidthController< + nym_validator_client::nyxd::NyxdClient, + St, + >, + use_mock_ecash: bool, + awg_args: String, + netstack_args: NetstackArgs, +) -> anyhow::Result +where + St: nym_sdk::mixnet::CredentialStorage + Clone + Send + Sync + 'static, + ::StorageError: Send + Sync, +{ + // 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(); + + // Generate Ed25519 keypairs for LP protocol + let mut rng = rand::thread_rng(); + let entry_lp_keypair = Arc::new(ed25519::KeyPair::new(&mut rng)); + let exit_lp_keypair = Arc::new(ed25519::KeyPair::new(&mut rng)); + + // Generate 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 = helpers::to_lp_remote_peer(entry_gateway.identity, entry_lp_data); + let exit_peer = helpers::to_lp_remote_peer(exit_gateway.identity, exit_lp_data); + + // STEP 1: Establish outer LP session with entry gateway + // LpRegistrationClient uses packet-per-connection model - connect() is gone, + // connection is established automatically during handshake. + info!("Establishing outer LP session with entry gateway..."); + let mut entry_client = LpRegistrationClient::::new_with_default_config( + entry_lp_keypair, + entry_peer, + entry_address, + ); + + // Perform handshake with entry gateway (connection is implicit) + if let Err(e) = entry_client.perform_handshake().await { + error!("Failed to handshake with entry gateway: {}", e); + return Ok(wg_outcome); + } + info!("Outer LP session with entry gateway established"); + + // STEP 2: Use nested session to register with exit gateway via forwarding + info!("Registering with exit gateway via entry forwarding..."); + let mut nested_session = + NestedLpSession::new(exit_address.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()) + .map_err(|e| anyhow::anyhow!("Invalid exit gateway identity: {}", e))?; + + // Perform handshake and registration with exit gateway via forwarding + let exit_gateway_data = if use_mock_ecash { + info!("Using mock ecash credential for exit gateway registration"); + let credential = bandwidth_helpers::create_dummy_credential( + &exit_gateway_pubkey.to_bytes(), + TicketType::V1WireguardExit, + ); + match nested_session + .handshake_and_register_with_credential( + &mut entry_client, + &mut rng, + &exit_wg_keypair, + credential, + TicketType::V1WireguardExit, + ) + .await + { + Ok(data) => data, + Err(e) => { + error!("Failed to register with exit gateway (mock ecash): {}", e); + return Ok(wg_outcome); + } + } + } else { + match nested_session + .handshake_and_register( + &mut entry_client, + &mut rng, + &exit_wg_keypair, + &exit_gateway_pubkey, + bandwidth_controller, + TicketType::V1WireguardExit, + ) + .await + { + Ok(data) => data, + Err(e) => { + error!("Failed to register with exit gateway: {}", e); + return Ok(wg_outcome); + } + } + }; + info!("Exit gateway registration successful via forwarding"); + + // STEP 3: Register with entry gateway + info!("Registering with entry gateway..."); + let entry_gateway_pubkey = + ed25519::PublicKey::from_bytes(&entry_gateway.identity.to_bytes()) + .map_err(|e| anyhow::anyhow!("Invalid entry gateway identity: {}", e))?; + + // Use packet-per-connection register() which returns GatewayData directly + let entry_gateway_data = if use_mock_ecash { + info!("Using mock ecash credential for entry gateway registration"); + let credential = bandwidth_helpers::create_dummy_credential( + &entry_gateway_pubkey.to_bytes(), + TicketType::V1WireguardEntry, + ); + match entry_client + .register_with_credential( + &mut rng, + &entry_wg_keypair, + credential, + TicketType::V1WireguardEntry, + ) + .await + { + Ok(data) => data, + Err(e) => { + error!("Failed to register with entry gateway (mock ecash): {}", e); + return Ok(wg_outcome); + } + } + } else { + match entry_client + .register( + &mut rng, + &entry_wg_keypair, + &entry_gateway_pubkey, + bandwidth_controller, + TicketType::V1WireguardEntry, + ) + .await + { + Ok(data) => data, + Err(e) => { + error!("Failed to register with entry gateway: {}", e); + return Ok(wg_outcome); + } + } + }; + info!("Entry gateway registration successful"); + + info!("LP registration successful for both gateways!"); + wg_outcome.can_register = true; + + // STEP 4: Test WireGuard tunnels using two-hop configuration + // Traffic flows: Exit tunnel -> UDP Forwarder -> Entry tunnel -> Exit Gateway -> Internet + // The exit gateway endpoint is not directly reachable from the host in localnet. + // We must tunnel through the entry gateway using the UDP forwarder pattern. + + // Convert keys to hex for netstack + let entry_private_key_hex = hex::encode(entry_wg_keypair.private_key().to_bytes()); + let entry_public_key_hex = hex::encode(entry_gateway_data.public_key.to_bytes()); + let exit_private_key_hex = hex::encode(exit_wg_keypair.private_key().to_bytes()); + let exit_public_key_hex = hex::encode(exit_gateway_data.public_key.to_bytes()); + + // Build WireGuard endpoint addresses + // Entry endpoint uses entry_ip (host-reachable) + port from registration + let entry_wg_endpoint = format!("{}:{}", entry_ip, entry_gateway_data.endpoint.port()); + // Exit endpoint uses exit_ip + port from registration (forwarded via entry) + let exit_wg_endpoint = format!("{}:{}", exit_ip, exit_gateway_data.endpoint.port()); + + info!("Two-hop WireGuard configuration:"); + info!(" Entry gateway:"); + info!(" Private IPv4: {}", entry_gateway_data.private_ipv4); + info!(" Endpoint: {}", entry_wg_endpoint); + info!(" Exit gateway:"); + info!(" Private IPv4: {}", exit_gateway_data.private_ipv4); + info!(" Endpoint (via forwarder): {}", exit_wg_endpoint); + + // Build two-hop tunnel configuration + let two_hop_config = TwoHopWgTunnelConfig::new( + entry_gateway_data.private_ipv4.to_string(), + entry_private_key_hex, + entry_public_key_hex, + entry_wg_endpoint, + awg_args.clone(), // Entry AWG args + exit_gateway_data.private_ipv4.to_string(), + exit_private_key_hex, + exit_public_key_hex, + exit_wg_endpoint, + awg_args, // Exit AWG args + ); + + // Run two-hop tunnel connectivity tests + run_two_hop_tunnel_tests(&two_hop_config, &netstack_args, &mut wg_outcome); + + info!("LP-based two-hop WireGuard probe completed"); + Ok(wg_outcome) +} + +pub async fn do_ping( + mut mixnet_client: MixnetClient, + our_address: Recipient, + exit_router_address: Option, + tested_entry: bool, +) -> (anyhow::Result, MixnetClient) { + let entry = do_ping_entry(&mut mixnet_client, our_address, tested_entry).await; + + let (exit_result, mixnet_client) = if let Some(exit_router_address) = exit_router_address { + let (maybe_ip_pair, mut mixnet_client) = + connect_exit(mixnet_client, exit_router_address).await; + match maybe_ip_pair { + Some(ip_pair) => ( + do_ping_exit(&mut mixnet_client, ip_pair, exit_router_address).await, + mixnet_client, + ), + None => (Ok(Some(Exit::fail_to_connect())), mixnet_client), + } + } else { + (Ok(None), mixnet_client) + }; + + ( + exit_result.map(|exit| ProbeOutcome { + as_entry: entry, + as_exit: exit, + wg: None, + lp: None, + }), + mixnet_client, + ) +} + +async fn do_ping_entry( + mixnet_client: &mut MixnetClient, + our_address: Recipient, + tested_entry: bool, +) -> Entry { + // Step 1: confirm that the entry gateway is routing our mixnet traffic + info!("Sending mixnet ping to ourselves to verify mixnet connection"); + + if self_ping_and_wait(our_address, mixnet_client) + .await + .is_err() + { + return if tested_entry { + Entry::fail_to_connect() + } else { + Entry::EntryFailure + }; + } + info!("Successfully mixnet pinged ourselves"); + + Entry::success() +} + +async fn connect_exit( + mixnet_client: MixnetClient, + exit_router_address: Recipient, +) -> (Option, MixnetClient) { + // Step 2: connect to the exit gateway + info!( + "Connecting to exit gateway: {}", + exit_router_address.gateway().to_base58_string() + ); + // The IPR supports cancellation, but it's unused in the gateway probe + let cancel_token = CancellationToken::new(); + let mut ipr_client = IprClientConnect::new(mixnet_client, cancel_token); + + let maybe_ip_pair = ipr_client.connect(exit_router_address).await; + let mixnet_client = ipr_client.into_mixnet_client(); + + if let Ok(our_ips) = maybe_ip_pair { + info!("Successfully connected to exit gateway"); + info!("Using mixnet VPN IP addresses: {our_ips}"); + (Some(our_ips), mixnet_client) + } else { + (None, mixnet_client) + } +} + +pub async fn do_ping_exit( + mixnet_client: &mut MixnetClient, + our_ips: IpPair, + exit_router_address: Recipient, +) -> anyhow::Result> { + // Step 3: perform ICMP connectivity checks for the exit gateway + send_icmp_pings(mixnet_client, our_ips, exit_router_address).await?; + listen_for_icmp_ping_replies(mixnet_client, our_ips).await +} + +async fn send_icmp_pings( + mixnet_client: &MixnetClient, + our_ips: IpPair, + exit_router_address: Recipient, +) -> anyhow::Result<()> { + // ipv4 addresses for testing + let ipr_tun_ip_v4 = NYM_TUN_DEVICE_ADDRESS_V4; + let external_ip_v4 = Ipv4Addr::new(8, 8, 8, 8); + + // ipv6 addresses for testing + let ipr_tun_ip_v6 = NYM_TUN_DEVICE_ADDRESS_V6; + let external_ip_v6 = Ipv6Addr::new(0x2001, 0x4860, 0x4860, 0, 0, 0, 0, 0x8888); + + info!( + "Sending ICMP echo requests to: {ipr_tun_ip_v4}, {ipr_tun_ip_v6}, {external_ip_v4}, {external_ip_v6}" + ); + + // send ipv4 pings + for ii in 0..10 { + icmp::send_ping_v4( + mixnet_client, + our_ips, + ii, + ipr_tun_ip_v4, + exit_router_address, + ) + .await?; + icmp::send_ping_v4( + mixnet_client, + our_ips, + ii, + external_ip_v4, + exit_router_address, + ) + .await?; + } + + // send ipv6 pings + for ii in 0..10 { + icmp::send_ping_v6( + mixnet_client, + our_ips, + ii, + ipr_tun_ip_v6, + exit_router_address, + ) + .await?; + icmp::send_ping_v6( + mixnet_client, + our_ips, + ii, + external_ip_v6, + exit_router_address, + ) + .await?; + } + Ok(()) +} + +pub async fn listen_for_icmp_ping_replies( + mixnet_client: &mut MixnetClient, + our_ips: IpPair, +) -> anyhow::Result> { + let mut multi_ip_packet_decoder = MultiIpPacketCodec::new(); + let mut registered_replies = IpPingReplies::new(); + + loop { + tokio::select! { + _ = tokio::time::sleep(Duration::from_secs(2)) => { + info!("Finished waiting for ICMP echo reply from exit gateway"); + break; + } + Some(reconstructed_message) = mixnet_client.next() => { + let Some(data_response) = helpers::unpack_data_response(&reconstructed_message) else { + continue; + }; + + // IP packets are bundled together in a mixnet message + let mut bytes = BytesMut::from(&*data_response.ip_packet); + while let Ok(Some(packet)) = multi_ip_packet_decoder.decode(&mut bytes) { + if let Some(event) = icmp::check_for_icmp_beacon_reply(&packet.into_bytes(), icmp::icmp_identifier(), our_ips) { + info!("Received ICMP echo reply from exit gateway"); + info!("Connection event: {event:?}"); + registered_replies.register_event(&event); + } + } + } + } + } + + Ok(Some(Exit { + can_connect: true, + can_route_ip_v4: registered_replies.ipr_tun_ip_v4, + can_route_ip_external_v4: registered_replies.external_ip_v4, + can_route_ip_v6: registered_replies.ipr_tun_ip_v6, + can_route_ip_external_v6: registered_replies.external_ip_v6, + })) +} diff --git a/nym-gateway-probe/src/types.rs b/nym-gateway-probe/src/common/types.rs similarity index 100% rename from nym-gateway-probe/src/types.rs rename to nym-gateway-probe/src/common/types.rs diff --git a/nym-gateway-probe/src/common/wireguard.rs b/nym-gateway-probe/src/common/wireguard.rs index 3d58d68a53..2a9e2ba5b4 100644 --- a/nym-gateway-probe/src/common/wireguard.rs +++ b/nym-gateway-probe/src/common/wireguard.rs @@ -9,11 +9,11 @@ use nym_config::defaults::{WG_METADATA_PORT, WG_TUN_DEVICE_IP_ADDRESS_V4}; use tracing::{error, info}; -use crate::NetstackArgs; -use crate::netstack::{ +use crate::common::netstack::{ NetstackRequest, NetstackRequestGo, NetstackResult, TwoHopNetstackRequestGo, }; -use crate::types::WgProbeResults; +use crate::common::types::WgProbeResults; +use crate::config::NetstackArgs; /// Safe division that returns 0.0 when divisor is 0 (instead of NaN/Inf) fn safe_ratio(received: u16, sent: u16) -> f32 { @@ -99,7 +99,7 @@ pub fn run_tunnel_tests( info!("Testing IPv4 tunnel connectivity..."); let ipv4_request = NetstackRequestGo::from_rust_v4(&netstack_request); - match crate::netstack::ping(&ipv4_request) { + match crate::common::netstack::ping(&ipv4_request) { Ok(NetstackResult::Response(netstack_response_v4)) => { info!( "WireGuard probe response for IPv4: {:#?}", @@ -137,7 +137,7 @@ pub fn run_tunnel_tests( info!("Testing IPv6 tunnel connectivity..."); let ipv6_request = NetstackRequestGo::from_rust_v6(&netstack_request); - match crate::netstack::ping(&ipv6_request) { + match crate::common::netstack::ping(&ipv6_request) { Ok(NetstackResult::Response(netstack_response_v6)) => { info!( "WireGuard probe response for IPv6: {:#?}", @@ -281,7 +281,7 @@ pub fn run_two_hop_tunnel_tests( info!(" Entry endpoint: {}", config.entry_endpoint); info!(" Exit endpoint (via forwarder): {}", config.exit_endpoint); - match crate::netstack::ping_two_hop(&request) { + match crate::common::netstack::ping_two_hop(&request) { Ok(NetstackResult::Response(response)) => { info!("Two-hop WireGuard probe response (IPv4): {:#?}", response); wg_outcome.can_handshake_v4 = response.can_handshake; diff --git a/nym-gateway-probe/src/config/credentials.rs b/nym-gateway-probe/src/config/credentials.rs new file mode 100644 index 0000000000..50cf308b4b --- /dev/null +++ b/nym-gateway-probe/src/config/credentials.rs @@ -0,0 +1,29 @@ +// Copyright 2026 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use clap::Args; +use nym_node_status_client::models::AttachedTicketMaterials; + +#[derive(Args)] +pub struct CredentialArgs { + #[arg(long)] + ticket_materials: Option, + + #[arg(long, default_value_t = 1)] + ticket_materials_revision: u8, +} + +impl CredentialArgs { + pub fn decode_attached_ticket_materials(&self) -> anyhow::Result { + let ticket_materials = self + .ticket_materials + .as_ref() + .ok_or_else(|| anyhow::anyhow!("ticket_materials is required"))? + .clone(); + + Ok(AttachedTicketMaterials::from_serialised_string( + ticket_materials, + self.ticket_materials_revision, + )?) + } +} diff --git a/nym-gateway-probe/src/config/mod.rs b/nym-gateway-probe/src/config/mod.rs new file mode 100644 index 0000000000..3601045253 --- /dev/null +++ b/nym-gateway-probe/src/config/mod.rs @@ -0,0 +1,10 @@ +// Copyright 2026 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +mod credentials; +mod netstack; +mod test_mode; + +pub use credentials::CredentialArgs; +pub use netstack::NetstackArgs; +pub use test_mode::TestMode; diff --git a/nym-gateway-probe/src/config/netstack.rs b/nym-gateway-probe/src/config/netstack.rs new file mode 100644 index 0000000000..de6dda47d7 --- /dev/null +++ b/nym-gateway-probe/src/config/netstack.rs @@ -0,0 +1,209 @@ +// Copyright 2026 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use clap::Args; + +#[derive(Args, Clone)] +pub struct NetstackArgs { + #[arg(long, default_value_t = 180)] + pub netstack_download_timeout_sec: u64, + + #[arg(long, default_value_t = 30)] + pub metadata_timeout_sec: u64, + + #[arg(long, default_value = "1.1.1.1")] + pub netstack_v4_dns: String, + + #[arg(long, default_value = "2606:4700:4700::1111")] + pub netstack_v6_dns: String, + + #[arg(long, default_value_t = 5)] + pub netstack_num_ping: u8, + + #[arg(long, default_value_t = 3)] + pub netstack_send_timeout_sec: u64, + + #[arg(long, default_value_t = 3)] + pub netstack_recv_timeout_sec: u64, + + #[arg(long, default_values_t = vec!["nym.com".to_string()])] + pub netstack_ping_hosts_v4: Vec, + + #[arg(long, default_values_t = vec!["1.1.1.1".to_string()])] + pub netstack_ping_ips_v4: Vec, + + #[arg(long, default_values_t = vec!["cloudflare.com".to_string()])] + pub netstack_ping_hosts_v6: Vec, + + #[arg(long, default_values_t = vec!["2001:4860:4860::8888".to_string(), "2606:4700:4700::1111".to_string(), "2620:fe::fe".to_string()])] + pub netstack_ping_ips_v6: Vec, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_netstack_args_default_values() { + // Test that the default values are correctly set in the struct definition + // This validates that our changes to the default values are correct + + // Create a default instance to test the values + let args = NetstackArgs { + netstack_download_timeout_sec: 180, + metadata_timeout_sec: 30, + netstack_v4_dns: "1.1.1.1".to_string(), + netstack_v6_dns: "2606:4700:4700::1111".to_string(), + netstack_num_ping: 5, + netstack_send_timeout_sec: 3, + netstack_recv_timeout_sec: 3, + netstack_ping_hosts_v4: vec!["nym.com".to_string()], + netstack_ping_ips_v4: vec!["1.1.1.1".to_string()], + netstack_ping_hosts_v6: vec!["cloudflare.com".to_string()], + netstack_ping_ips_v6: vec![ + "2001:4860:4860::8888".to_string(), + "2606:4700:4700::1111".to_string(), + "2620:fe::fe".to_string(), + ], + }; + + // Test IPv4 defaults + assert_eq!(args.netstack_ping_hosts_v4, vec!["nym.com"]); + assert_eq!(args.netstack_ping_ips_v4, vec!["1.1.1.1"]); + assert_eq!(args.netstack_v4_dns, "1.1.1.1"); + + // Test IPv6 defaults + assert_eq!(args.netstack_ping_hosts_v6, vec!["cloudflare.com"]); + assert_eq!( + args.netstack_ping_ips_v6, + vec![ + "2001:4860:4860::8888", + "2606:4700:4700::1111", + "2620:fe::fe" + ] + ); + assert_eq!(args.netstack_v6_dns, "2606:4700:4700::1111"); + + // Test other defaults + assert_eq!(args.netstack_download_timeout_sec, 180); + assert_eq!(args.netstack_num_ping, 5); + assert_eq!(args.netstack_send_timeout_sec, 3); + assert_eq!(args.netstack_recv_timeout_sec, 3); + } + + #[test] + fn test_netstack_args_custom_construction() { + // Test that we can create instances with custom values + let args = NetstackArgs { + netstack_download_timeout_sec: 300, + metadata_timeout_sec: 30, + netstack_v4_dns: "8.8.8.8".to_string(), + netstack_v6_dns: "2001:4860:4860::8888".to_string(), + netstack_num_ping: 10, + netstack_send_timeout_sec: 5, + netstack_recv_timeout_sec: 5, + netstack_ping_hosts_v4: vec!["example.com".to_string()], + netstack_ping_ips_v4: vec!["8.8.8.8".to_string()], + netstack_ping_hosts_v6: vec!["ipv6.example.com".to_string()], + netstack_ping_ips_v6: vec!["2001:4860:4860::8888".to_string()], + }; + + assert_eq!(args.netstack_ping_hosts_v4, vec!["example.com"]); + assert_eq!(args.netstack_ping_hosts_v6, vec!["ipv6.example.com"]); + assert_eq!(args.netstack_ping_ips_v4, vec!["8.8.8.8"]); + assert_eq!(args.netstack_ping_ips_v6, vec!["2001:4860:4860::8888"]); + assert_eq!(args.netstack_v4_dns, "8.8.8.8"); + assert_eq!(args.netstack_v6_dns, "2001:4860:4860::8888"); + assert_eq!(args.netstack_download_timeout_sec, 300); + assert_eq!(args.netstack_num_ping, 10); + assert_eq!(args.netstack_send_timeout_sec, 5); + assert_eq!(args.netstack_recv_timeout_sec, 5); + } + + #[test] + fn test_netstack_args_multiple_values() { + // Test that multiple hosts and IPs can be stored + let args = NetstackArgs { + netstack_download_timeout_sec: 180, + metadata_timeout_sec: 30, + netstack_v4_dns: "1.1.1.1".to_string(), + netstack_v6_dns: "2606:4700:4700::1111".to_string(), + netstack_num_ping: 5, + netstack_send_timeout_sec: 3, + netstack_recv_timeout_sec: 3, + netstack_ping_hosts_v4: vec!["nym.com".to_string(), "example.com".to_string()], + netstack_ping_ips_v4: vec!["1.1.1.1".to_string(), "8.8.8.8".to_string()], + netstack_ping_hosts_v6: vec![ + "cloudflare.com".to_string(), + "ipv6.example.com".to_string(), + ], + netstack_ping_ips_v6: vec![ + "2001:4860:4860::8888".to_string(), + "2606:4700:4700::1111".to_string(), + ], + }; + + assert_eq!(args.netstack_ping_hosts_v4, vec!["nym.com", "example.com"]); + assert_eq!( + args.netstack_ping_hosts_v6, + vec!["cloudflare.com", "ipv6.example.com"] + ); + assert_eq!(args.netstack_ping_ips_v4, vec!["1.1.1.1", "8.8.8.8"]); + assert_eq!( + args.netstack_ping_ips_v6, + vec!["2001:4860:4860::8888", "2606:4700:4700::1111"] + ); + } + + #[test] + fn test_netstack_args_edge_cases() { + // Test edge cases like zero values and empty vectors + let args = NetstackArgs { + netstack_download_timeout_sec: 0, + metadata_timeout_sec: 30, + netstack_v4_dns: "1.1.1.1".to_string(), + netstack_v6_dns: "2606:4700:4700::1111".to_string(), + netstack_num_ping: 0, + netstack_send_timeout_sec: 0, + netstack_recv_timeout_sec: 0, + netstack_ping_hosts_v4: vec![], + netstack_ping_ips_v4: vec![], + netstack_ping_hosts_v6: vec![], + netstack_ping_ips_v6: vec![], + }; + + assert_eq!(args.netstack_num_ping, 0); + assert_eq!(args.netstack_send_timeout_sec, 0); + assert_eq!(args.netstack_recv_timeout_sec, 0); + assert_eq!(args.netstack_download_timeout_sec, 0); + assert!(args.netstack_ping_hosts_v4.is_empty()); + assert!(args.netstack_ping_ips_v4.is_empty()); + assert!(args.netstack_ping_hosts_v6.is_empty()); + assert!(args.netstack_ping_ips_v6.is_empty()); + } + + #[test] + fn test_netstack_args_domain_validation() { + // Test that our domain choices are reasonable + let args = NetstackArgs { + netstack_download_timeout_sec: 180, + metadata_timeout_sec: 30, + netstack_v4_dns: "1.1.1.1".to_string(), + netstack_v6_dns: "2606:4700:4700::1111".to_string(), + netstack_num_ping: 5, + netstack_send_timeout_sec: 3, + netstack_recv_timeout_sec: 3, + netstack_ping_hosts_v4: vec!["nym.com".to_string()], + netstack_ping_ips_v4: vec!["1.1.1.1".to_string()], + netstack_ping_hosts_v6: vec!["cloudflare.com".to_string()], + netstack_ping_ips_v6: vec!["2001:4860:4860::8888".to_string()], + }; + + assert!(args.netstack_ping_hosts_v4[0].contains("nym")); + + assert!(args.netstack_ping_hosts_v6[0].contains("cloudflare")); + + assert_eq!(args.netstack_v4_dns, "1.1.1.1"); + assert_eq!(args.netstack_v6_dns, "2606:4700:4700::1111"); + } +} diff --git a/nym-gateway-probe/src/mode/mod.rs b/nym-gateway-probe/src/config/test_mode.rs similarity index 100% rename from nym-gateway-probe/src/mode/mod.rs rename to nym-gateway-probe/src/config/test_mode.rs diff --git a/nym-gateway-probe/src/lib.rs b/nym-gateway-probe/src/lib.rs index 925c9c030c..b74cf2df7d 100644 --- a/nym-gateway-probe/src/lib.rs +++ b/nym-gateway-probe/src/lib.rs @@ -1,200 +1,36 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::types::Entry; +use crate::common::helpers; +use crate::common::probe_tests::{do_ping, lp_registration_probe, wg_probe, wg_probe_lp}; +use crate::common::types::{Entry, Exit, WgProbeResults}; use anyhow::bail; -use base64::{Engine as _, engine::general_purpose}; -use bytes::BytesMut; -use clap::Args; -use futures::StreamExt; use nym_authenticator_client::{AuthClientMixnetListener, AuthenticatorClient}; -use nym_authenticator_requests::{ - AuthenticatorVersion, client_message::ClientMessage, response::AuthenticatorResponse, v2, v3, - v4, v5, v6, -}; use nym_client_core::config::ForgetMe; -use nym_config::defaults::{ - NymNetworkDetails, - mixnet_vpn::{NYM_TUN_DEVICE_ADDRESS_V4, NYM_TUN_DEVICE_ADDRESS_V6}, -}; -use nym_connection_monitor::self_ping_and_wait; -use nym_credentials_interface::{CredentialSpendingData, TicketType}; +use nym_config::defaults::NymNetworkDetails; +use nym_credentials_interface::TicketType; use nym_crypto::asymmetric::x25519::KeyPair; -use nym_ip_packet_client::IprClientConnect; -use nym_ip_packet_requests::{ - IpPair, - codec::MultiIpPacketCodec, - v8::response::{ - ControlResponse, DataResponse, InfoLevel, IpPacketResponse, IpPacketResponseData, - }, -}; use nym_sdk::mixnet::{ CredentialStorage, Ephemeral, KeyStore, MixnetClient, MixnetClientBuilder, MixnetClientStorage, - NodeIdentity, Recipient, ReconstructedMessage, StoragePaths, + NodeIdentity, 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 std::sync::Arc; +use tokio_util::sync::CancellationToken; use tracing::*; -use types::WgProbeResults; use url::Url; -use crate::{ - icmp::{check_for_icmp_beacon_reply, icmp_identifier, send_ping_v4, send_ping_v6}, - types::Exit, -}; - -mod bandwidth_helpers; mod common; -mod icmp; -pub mod mode; -mod netstack; -pub mod nodes; -mod types; +pub mod config; -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, SignatureScheme}; -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)] -pub struct NetstackArgs { - #[arg(long, default_value_t = 180)] - netstack_download_timeout_sec: u64, - - #[arg(long, default_value_t = 30)] - metadata_timeout_sec: u64, - - #[arg(long, default_value = "1.1.1.1")] - netstack_v4_dns: String, - - #[arg(long, default_value = "2606:4700:4700::1111")] - netstack_v6_dns: String, - - #[arg(long, default_value_t = 5)] - netstack_num_ping: u8, - - #[arg(long, default_value_t = 3)] - netstack_send_timeout_sec: u64, - - #[arg(long, default_value_t = 3)] - netstack_recv_timeout_sec: u64, - - #[arg(long, default_values_t = vec!["nym.com".to_string()])] - netstack_ping_hosts_v4: Vec, - - #[arg(long, default_values_t = vec!["1.1.1.1".to_string()])] - netstack_ping_ips_v4: Vec, - - #[arg(long, default_values_t = vec!["cloudflare.com".to_string()])] - netstack_ping_hosts_v6: Vec, - - #[arg(long, default_values_t = vec!["2001:4860:4860::8888".to_string(), "2606:4700:4700::1111".to_string(), "2620:fe::fe".to_string()])] - netstack_ping_ips_v6: Vec, -} - -#[derive(Args)] -pub struct CredentialArgs { - #[arg(long)] - ticket_materials: Option, - - #[arg(long, default_value_t = 1)] - ticket_materials_revision: u8, -} - -impl CredentialArgs { - fn decode_attached_ticket_materials(&self) -> anyhow::Result { - let ticket_materials = self - .ticket_materials - .as_ref() - .ok_or_else(|| anyhow::anyhow!("ticket_materials is required"))? - .clone(); - - Ok(AttachedTicketMaterials::from_serialised_string( - ticket_materials, - self.ticket_materials_revision, - )?) - } -} - -#[derive(Default, Debug)] -pub enum TestedNode { - #[default] - SameAsEntry, - Custom { - identity: NodeIdentity, - shares_entry: bool, - }, -} - -impl TestedNode { - pub fn is_same_as_entry(&self) -> bool { - matches!( - self, - TestedNode::SameAsEntry - | TestedNode::Custom { - shares_entry: true, - .. - } - ) - } -} - -#[derive(Debug, Clone)] -pub struct TestedNodeDetails { - identity: NodeIdentity, - exit_router_address: Option, - authenticator_address: Option, - authenticator_version: AuthenticatorVersion, - ip_address: Option, - lp_data: Option, -} - -#[derive(Debug, Clone)] -pub struct TestedNodeLpDetails { - pub address: SocketAddr, - pub expected_kem_key_hashes: HashMap, - pub expected_signing_key_hashes: HashMap, - pub x25519: x25519::PublicKey, -} - -impl TestedNodeDetails { - /// Create from CLI args (localnet mode - no HTTP query needed) - pub fn from_cli(identity: NodeIdentity, lp_data: TestedNodeLpDetails) -> Self { - Self { - identity, - 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, - authenticator_version: AuthenticatorVersion::UNKNOWN, - } - } - - /// Check if this node has sufficient info for LP testing - pub fn can_test_lp(&self) -> bool { - self.lp_data.is_some() - } - - /// Check if this node has sufficient info for mixnet testing - pub fn can_test_mixnet(&self) -> bool { - self.exit_router_address.is_some() || self.authenticator_address.is_some() - } -} +use crate::common::bandwidth_helpers::{acquire_bandwidth, import_bandwidth}; +pub use crate::common::nodes::{ + DirectoryNode, NymApiDirectory, TestedNode, TestedNodeDetails, TestedNodeLpDetails, + query_gateway_by_ip, +}; +pub use crate::common::types::{IpPingReplies, ProbeOutcome, ProbeResult}; +pub use crate::config::{CredentialArgs, NetstackArgs, TestMode}; pub struct Probe { entrypoint: NodeIdentity, @@ -324,7 +160,7 @@ impl Probe { let disconnected_mixnet_client = MixnetClientBuilder::new_with_storage(storage.clone()) .request_gateway(mixnet_entry_gateway_id.to_string()) .network_details(NymNetworkDetails::new_from_env()) - .debug_config(mixnet_debug_config( + .debug_config(helpers::mixnet_debug_config( min_mixnet_performance, ignore_egress_epoch_role, )) @@ -491,7 +327,7 @@ impl Probe { let disconnected_mixnet_client = MixnetClientBuilder::new_with_storage(storage.clone()) .request_gateway(mixnet_entry_gateway_id.to_string()) .network_details(NymNetworkDetails::new_from_env()) - .debug_config(mixnet_debug_config( + .debug_config(helpers::mixnet_debug_config( min_mixnet_performance, ignore_egress_epoch_role, )) @@ -614,12 +450,12 @@ impl Probe { Ok(ProbeResult { node: node_info.identity.to_string(), used_entry: mixnet_entry_gateway_id.to_string(), - outcome: types::ProbeOutcome { - as_entry: types::Entry::NotTested, + outcome: ProbeOutcome { + as_entry: Entry::NotTested, as_exit: if tested_entry { None } else { - Some(types::Exit::fail_to_connect()) + Some(Exit::fail_to_connect()) }, wg: None, lp: Some(lp_outcome), @@ -965,885 +801,3 @@ impl Probe { }) } } - -async fn wg_probe( - mut auth_client: AuthenticatorClient, - gateway_ip: IpAddr, - auth_version: AuthenticatorVersion, - awg_args: String, - netstack_args: NetstackArgs, - // TODO: update type - credential: CredentialSpendingData, -) -> anyhow::Result { - info!("attempting to use authenticator version {auth_version:?}"); - - let mut rng = rand::thread_rng(); - - // that's a long conversion chain - // (it should be simplified later...) - // nym x25519 -> dalek x25519 -> wireguard wrapper x25519 - let private_key = nym_crypto::asymmetric::encryption::PrivateKey::new(&mut rng); - let public_key = private_key.public_key(); - - let authenticator_pub_key = public_key.inner().into(); - let init_message = match auth_version { - AuthenticatorVersion::V2 => ClientMessage::Initial(Box::new( - v2::registration::InitMessage::new(authenticator_pub_key), - )), - AuthenticatorVersion::V3 => ClientMessage::Initial(Box::new( - v3::registration::InitMessage::new(authenticator_pub_key), - )), - AuthenticatorVersion::V4 => ClientMessage::Initial(Box::new( - v4::registration::InitMessage::new(authenticator_pub_key), - )), - AuthenticatorVersion::V5 => ClientMessage::Initial(Box::new( - v5::registration::InitMessage::new(authenticator_pub_key), - )), - AuthenticatorVersion::V6 => ClientMessage::Initial(Box::new( - v6::registration::InitMessage::new(authenticator_pub_key), - )), - AuthenticatorVersion::V1 | AuthenticatorVersion::UNKNOWN => bail!("unknown version number"), - }; - - let mut wg_outcome = WgProbeResults::default(); - - info!( - "connecting to authenticator: {}...", - auth_client.auth_recipient - ); - let response = auth_client - .send_and_wait_for_response(&init_message) - .await?; - - let registered_data = match response { - AuthenticatorResponse::PendingRegistration(pending_registration_response) => { - // Unwrap since we have already checked that we have the keypair. - debug!("Verifying data"); - pending_registration_response.verify(&private_key)?; - - let credential = credential - .try_into() - .inspect_err(|err| error!("invalid zk-nym data: {err}")) - .ok(); - - let finalized_message = - pending_registration_response.finalise_registration(&private_key, credential); - let client_message = ClientMessage::Final(finalized_message); - - let response = auth_client - .send_and_wait_for_response(&client_message) - .await?; - let AuthenticatorResponse::Registered(registered_response) = response else { - bail!("Unexpected response"); - }; - registered_response - } - AuthenticatorResponse::Registered(registered_response) => registered_response, - _ => bail!("Unexpected response"), - }; - - let peer_public = registered_data.pub_key().inner(); - let static_private = x25519_dalek::StaticSecret::from(private_key.to_bytes()); - let public_key_bs64 = general_purpose::STANDARD.encode(peer_public.as_bytes()); - let private_key_hex = hex::encode(static_private.to_bytes()); - let public_key_hex = hex::encode(peer_public.as_bytes()); - - info!("WG connection details"); - info!("Peer public key: {}", public_key_bs64); - info!( - "ips {}(v4) {}(v6), port {}", - registered_data.private_ips().ipv4, - registered_data.private_ips().ipv6, - registered_data.wg_port(), - ); - - let wg_endpoint = format!("{gateway_ip}:{}", registered_data.wg_port()); - - info!("Successfully registered with the gateway"); - - wg_outcome.can_register = true; - - // Run tunnel connectivity tests using shared helper - let tunnel_config = common::WgTunnelConfig::new( - registered_data.private_ips().ipv4.to_string(), - registered_data.private_ips().ipv6.to_string(), - private_key_hex, - public_key_hex, - wg_endpoint, - ); - - common::run_tunnel_tests(&tunnel_config, &netstack_args, &awg_args, &mut wg_outcome); - - Ok(wg_outcome) -} - -fn to_lp_remote_peer(identity: ed25519::PublicKey, data: TestedNodeLpDetails) -> LpRemotePeer { - LpRemotePeer::new(identity, data.x25519).with_key_digests( - data.expected_kem_key_hashes, - data.expected_signing_key_hashes, - ) -} - -async fn lp_registration_probe( - gateway_identity: NodeIdentity, - gateway_lp_data: TestedNodeLpDetails, - bandwidth_controller: &nym_bandwidth_controller::BandwidthController< - nym_validator_client::nyxd::NyxdClient, - St, - >, - use_mock_ecash: bool, -) -> anyhow::Result -where - St: nym_sdk::mixnet::CredentialStorage + Clone + Send + Sync + 'static, - ::StorageError: Send + Sync, -{ - use nym_crypto::asymmetric::ed25519; - use nym_registration_client::LpRegistrationClient; - - 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(); - - // Generate Ed25519 keypair for this connection (X25519 will be derived internally by LP) - let mut rng = rand::thread_rng(); - let client_ed25519_keypair = std::sync::Arc::new(ed25519::KeyPair::new(&mut rng)); - - // Step 0: Derive X25519 keys from Ed25519 for the gateways - - // Create LP registration client (uses Ed25519 keys directly, derives X25519 internally) - let mut client = LpRegistrationClient::::new_with_default_config( - client_ed25519_keypair, - peer, - 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 {lp_address}..."); - match client.perform_handshake().await { - Ok(_) => { - info!("LP handshake completed successfully"); - lp_outcome.can_connect = true; // Connection succeeded if handshake succeeded - lp_outcome.can_handshake = true; - } - Err(e) => { - let error_msg = format!("LP handshake failed: {}", e); - error!("{}", error_msg); - lp_outcome.error = Some(error_msg); - return Ok(lp_outcome); - } - } - - // Step 2: Register with gateway (send request + receive response in one call) - info!("Sending LP registration request..."); - - // Generate WireGuard keypair for dVPN registration - let mut rng = rand::thread_rng(); - let wg_keypair = nym_crypto::asymmetric::x25519::KeyPair::new(&mut rng); - - // Convert gateway identity to ed25519 public key - let gateway_ed25519_pubkey = match nym_crypto::asymmetric::ed25519::PublicKey::from_bytes( - &gateway_identity.to_bytes(), - ) { - Ok(key) => key, - Err(e) => { - let error_msg = format!("Failed to convert gateway identity: {}", e); - error!("{}", error_msg); - lp_outcome.error = Some(error_msg); - return Ok(lp_outcome); - } - }; - - // Register using the new packet-per-connection API (returns GatewayData directly) - let ticket_type = TicketType::V1WireguardEntry; - let gateway_data = if use_mock_ecash { - info!("Using mock ecash credential for LP registration"); - let credential = crate::bandwidth_helpers::create_dummy_credential( - &gateway_ed25519_pubkey.to_bytes(), - ticket_type, - ); - - match client - .register_with_credential(&mut rng, &wg_keypair, credential, ticket_type) - .await - { - Ok(data) => data, - Err(e) => { - let error_msg = format!("LP registration failed (mock ecash): {}", e); - error!("{}", error_msg); - lp_outcome.error = Some(error_msg); - return Ok(lp_outcome); - } - } - } else { - info!("Using real bandwidth controller for LP registration"); - match client - .register( - &mut rng, - &wg_keypair, - &gateway_ed25519_pubkey, - bandwidth_controller, - ticket_type, - ) - .await - { - Ok(data) => data, - Err(e) => { - let error_msg = format!("LP registration failed: {}", e); - error!("{}", error_msg); - lp_outcome.error = Some(error_msg); - return Ok(lp_outcome); - } - } - }; - - info!("LP registration successful! Received gateway data:"); - info!(" - Gateway public key: {:?}", gateway_data.public_key); - info!(" - Private IPv4: {}", gateway_data.private_ipv4); - info!(" - Private IPv6: {}", gateway_data.private_ipv6); - info!(" - Endpoint: {}", gateway_data.endpoint); - lp_outcome.can_register = true; - - Ok(lp_outcome) -} - -/// LP-based WireGuard probe: Tests LP nested session registration + WireGuard tunnel connectivity -/// -/// This function tests the full VPN flow using LP registration instead of mixnet+authenticator: -/// 1. Connects to entry gateway (outer LP session) -/// 2. Registers with exit gateway via entry forwarding (nested LP session) -/// 3. Receives WireGuard configuration from both gateways -/// 4. Tests WireGuard tunnel connectivity (IPv4/IPv6) -/// -/// This validates that IP hiding works (exit sees entry IP, not client IP) and that the -/// full VPN tunnel operates correctly after LP registration. -/// -// Known issue in localnet mode - After this probe runs, container networking -// to the external internet becomes unstable while internal container-to-container traffic -// continues to work. The two-hop WireGuard tunnel itself succeeds (handshake completes), -// but subsequent DNS/ping tests may timeout. This appears to be related to Apple Container -// Runtime networking quirks combined with our NAT/iptables configuration. Tracked in -// beads issue nym-vbdo. Workaround: restart the localnet containers between probe runs. -async fn wg_probe_lp( - entry_gateway: &TestedNodeDetails, - exit_gateway: &TestedNodeDetails, - bandwidth_controller: &nym_bandwidth_controller::BandwidthController< - nym_validator_client::nyxd::NyxdClient, - St, - >, - use_mock_ecash: bool, - awg_args: String, - netstack_args: NetstackArgs, -) -> anyhow::Result -where - St: nym_sdk::mixnet::CredentialStorage + Clone + Send + Sync + 'static, - ::StorageError: Send + Sync, -{ - // 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(); - - // Generate Ed25519 keypairs for LP protocol - let mut rng = rand::thread_rng(); - let entry_lp_keypair = Arc::new(ed25519::KeyPair::new(&mut rng)); - let exit_lp_keypair = Arc::new(ed25519::KeyPair::new(&mut rng)); - - // Generate 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 = 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, - // connection is established automatically during handshake. - info!("Establishing outer LP session with entry gateway..."); - let mut entry_client = LpRegistrationClient::::new_with_default_config( - entry_lp_keypair, - entry_peer, - entry_address, - ); - - // Perform handshake with entry gateway (connection is implicit) - if let Err(e) = entry_client.perform_handshake().await { - error!("Failed to handshake with entry gateway: {}", e); - return Ok(wg_outcome); - } - info!("Outer LP session with entry gateway established"); - - // STEP 2: Use nested session to register with exit gateway via forwarding - info!("Registering with exit gateway via entry forwarding..."); - let mut nested_session = - NestedLpSession::new(exit_address.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()) - .map_err(|e| anyhow::anyhow!("Invalid exit gateway identity: {}", e))?; - - // Perform handshake and registration with exit gateway via forwarding - let exit_gateway_data = if use_mock_ecash { - info!("Using mock ecash credential for exit gateway registration"); - let credential = crate::bandwidth_helpers::create_dummy_credential( - &exit_gateway_pubkey.to_bytes(), - TicketType::V1WireguardExit, - ); - match nested_session - .handshake_and_register_with_credential( - &mut entry_client, - &mut rng, - &exit_wg_keypair, - credential, - TicketType::V1WireguardExit, - ) - .await - { - Ok(data) => data, - Err(e) => { - error!("Failed to register with exit gateway (mock ecash): {}", e); - return Ok(wg_outcome); - } - } - } else { - match nested_session - .handshake_and_register( - &mut entry_client, - &mut rng, - &exit_wg_keypair, - &exit_gateway_pubkey, - bandwidth_controller, - TicketType::V1WireguardExit, - ) - .await - { - Ok(data) => data, - Err(e) => { - error!("Failed to register with exit gateway: {}", e); - return Ok(wg_outcome); - } - } - }; - info!("Exit gateway registration successful via forwarding"); - - // STEP 3: Register with entry gateway - info!("Registering with entry gateway..."); - let entry_gateway_pubkey = - ed25519::PublicKey::from_bytes(&entry_gateway.identity.to_bytes()) - .map_err(|e| anyhow::anyhow!("Invalid entry gateway identity: {}", e))?; - - // Use packet-per-connection register() which returns GatewayData directly - let entry_gateway_data = if use_mock_ecash { - info!("Using mock ecash credential for entry gateway registration"); - let credential = crate::bandwidth_helpers::create_dummy_credential( - &entry_gateway_pubkey.to_bytes(), - TicketType::V1WireguardEntry, - ); - match entry_client - .register_with_credential( - &mut rng, - &entry_wg_keypair, - credential, - TicketType::V1WireguardEntry, - ) - .await - { - Ok(data) => data, - Err(e) => { - error!("Failed to register with entry gateway (mock ecash): {}", e); - return Ok(wg_outcome); - } - } - } else { - match entry_client - .register( - &mut rng, - &entry_wg_keypair, - &entry_gateway_pubkey, - bandwidth_controller, - TicketType::V1WireguardEntry, - ) - .await - { - Ok(data) => data, - Err(e) => { - error!("Failed to register with entry gateway: {}", e); - return Ok(wg_outcome); - } - } - }; - info!("Entry gateway registration successful"); - - info!("LP registration successful for both gateways!"); - wg_outcome.can_register = true; - - // STEP 4: Test WireGuard tunnels using two-hop configuration - // Traffic flows: Exit tunnel -> UDP Forwarder -> Entry tunnel -> Exit Gateway -> Internet - // The exit gateway endpoint is not directly reachable from the host in localnet. - // We must tunnel through the entry gateway using the UDP forwarder pattern. - - // Convert keys to hex for netstack - let entry_private_key_hex = hex::encode(entry_wg_keypair.private_key().to_bytes()); - let entry_public_key_hex = hex::encode(entry_gateway_data.public_key.to_bytes()); - let exit_private_key_hex = hex::encode(exit_wg_keypair.private_key().to_bytes()); - let exit_public_key_hex = hex::encode(exit_gateway_data.public_key.to_bytes()); - - // Build WireGuard endpoint addresses - // Entry endpoint uses entry_ip (host-reachable) + port from registration - let entry_wg_endpoint = format!("{}:{}", entry_ip, entry_gateway_data.endpoint.port()); - // Exit endpoint uses exit_ip + port from registration (forwarded via entry) - let exit_wg_endpoint = format!("{}:{}", exit_ip, exit_gateway_data.endpoint.port()); - - info!("Two-hop WireGuard configuration:"); - info!(" Entry gateway:"); - info!(" Private IPv4: {}", entry_gateway_data.private_ipv4); - info!(" Endpoint: {}", entry_wg_endpoint); - info!(" Exit gateway:"); - info!(" Private IPv4: {}", exit_gateway_data.private_ipv4); - info!(" Endpoint (via forwarder): {}", exit_wg_endpoint); - - // Build two-hop tunnel configuration - let two_hop_config = common::TwoHopWgTunnelConfig::new( - entry_gateway_data.private_ipv4.to_string(), - entry_private_key_hex, - entry_public_key_hex, - entry_wg_endpoint, - awg_args.clone(), // Entry AWG args - exit_gateway_data.private_ipv4.to_string(), - exit_private_key_hex, - exit_public_key_hex, - exit_wg_endpoint, - awg_args, // Exit AWG args - ); - - // Run two-hop tunnel connectivity tests - common::run_two_hop_tunnel_tests(&two_hop_config, &netstack_args, &mut wg_outcome); - - info!("LP-based two-hop WireGuard probe completed"); - Ok(wg_outcome) -} - -fn mixnet_debug_config( - min_gateway_performance: Option, - ignore_egress_epoch_role: bool, -) -> nym_client_core::config::DebugConfig { - let mut debug_config = nym_client_core::config::DebugConfig::default(); - debug_config - .traffic - .disable_main_poisson_packet_distribution = true; - debug_config.cover_traffic.disable_loop_cover_traffic_stream = true; - if let Some(minimum_gateway_performance) = min_gateway_performance { - debug_config.topology.minimum_gateway_performance = minimum_gateway_performance; - } - if ignore_egress_epoch_role { - debug_config.topology.ignore_egress_epoch_role = ignore_egress_epoch_role; - } - - debug_config -} - -async fn do_ping( - mut mixnet_client: MixnetClient, - our_address: Recipient, - exit_router_address: Option, - tested_entry: bool, -) -> (anyhow::Result, MixnetClient) { - let entry = do_ping_entry(&mut mixnet_client, our_address, tested_entry).await; - - let (exit_result, mixnet_client) = if let Some(exit_router_address) = exit_router_address { - let (maybe_ip_pair, mut mixnet_client) = - connect_exit(mixnet_client, exit_router_address).await; - match maybe_ip_pair { - Some(ip_pair) => ( - do_ping_exit(&mut mixnet_client, ip_pair, exit_router_address).await, - mixnet_client, - ), - None => (Ok(Some(Exit::fail_to_connect())), mixnet_client), - } - } else { - (Ok(None), mixnet_client) - }; - - ( - exit_result.map(|exit| ProbeOutcome { - as_entry: entry, - as_exit: exit, - wg: None, - lp: None, - }), - mixnet_client, - ) -} - -async fn do_ping_entry( - mixnet_client: &mut MixnetClient, - our_address: Recipient, - tested_entry: bool, -) -> Entry { - // Step 1: confirm that the entry gateway is routing our mixnet traffic - info!("Sending mixnet ping to ourselves to verify mixnet connection"); - - if self_ping_and_wait(our_address, mixnet_client) - .await - .is_err() - { - return if tested_entry { - Entry::fail_to_connect() - } else { - Entry::EntryFailure - }; - } - info!("Successfully mixnet pinged ourselves"); - - Entry::success() -} - -async fn connect_exit( - mixnet_client: MixnetClient, - exit_router_address: Recipient, -) -> (Option, MixnetClient) { - // Step 2: connect to the exit gateway - info!( - "Connecting to exit gateway: {}", - exit_router_address.gateway().to_base58_string() - ); - // The IPR supports cancellation, but it's unused in the gateway probe - let cancel_token = CancellationToken::new(); - let mut ipr_client = IprClientConnect::new(mixnet_client, cancel_token); - - let maybe_ip_pair = ipr_client.connect(exit_router_address).await; - let mixnet_client = ipr_client.into_mixnet_client(); - - if let Ok(our_ips) = maybe_ip_pair { - info!("Successfully connected to exit gateway"); - info!("Using mixnet VPN IP addresses: {our_ips}"); - (Some(our_ips), mixnet_client) - } else { - (None, mixnet_client) - } -} - -async fn do_ping_exit( - mixnet_client: &mut MixnetClient, - our_ips: IpPair, - exit_router_address: Recipient, -) -> anyhow::Result> { - // Step 3: perform ICMP connectivity checks for the exit gateway - send_icmp_pings(mixnet_client, our_ips, exit_router_address).await?; - listen_for_icmp_ping_replies(mixnet_client, our_ips).await -} - -async fn send_icmp_pings( - mixnet_client: &MixnetClient, - our_ips: IpPair, - exit_router_address: Recipient, -) -> anyhow::Result<()> { - // ipv4 addresses for testing - let ipr_tun_ip_v4 = NYM_TUN_DEVICE_ADDRESS_V4; - let external_ip_v4 = Ipv4Addr::new(8, 8, 8, 8); - - // ipv6 addresses for testing - let ipr_tun_ip_v6 = NYM_TUN_DEVICE_ADDRESS_V6; - let external_ip_v6 = Ipv6Addr::new(0x2001, 0x4860, 0x4860, 0, 0, 0, 0, 0x8888); - - info!( - "Sending ICMP echo requests to: {ipr_tun_ip_v4}, {ipr_tun_ip_v6}, {external_ip_v4}, {external_ip_v6}" - ); - - // send ipv4 pings - for ii in 0..10 { - send_ping_v4( - mixnet_client, - our_ips, - ii, - ipr_tun_ip_v4, - exit_router_address, - ) - .await?; - send_ping_v4( - mixnet_client, - our_ips, - ii, - external_ip_v4, - exit_router_address, - ) - .await?; - } - - // send ipv6 pings - for ii in 0..10 { - send_ping_v6( - mixnet_client, - our_ips, - ii, - ipr_tun_ip_v6, - exit_router_address, - ) - .await?; - send_ping_v6( - mixnet_client, - our_ips, - ii, - external_ip_v6, - exit_router_address, - ) - .await?; - } - Ok(()) -} - -async fn listen_for_icmp_ping_replies( - mixnet_client: &mut MixnetClient, - our_ips: IpPair, -) -> anyhow::Result> { - let mut multi_ip_packet_decoder = MultiIpPacketCodec::new(); - let mut registered_replies = IpPingReplies::new(); - - loop { - tokio::select! { - _ = tokio::time::sleep(Duration::from_secs(2)) => { - info!("Finished waiting for ICMP echo reply from exit gateway"); - break; - } - Some(reconstructed_message) = mixnet_client.next() => { - let Some(data_response) = unpack_data_response(&reconstructed_message) else { - continue; - }; - - // IP packets are bundled together in a mixnet message - let mut bytes = BytesMut::from(&*data_response.ip_packet); - while let Ok(Some(packet)) = multi_ip_packet_decoder.decode(&mut bytes) { - if let Some(event) = check_for_icmp_beacon_reply(&packet.into_bytes(), icmp_identifier(), our_ips) { - info!("Received ICMP echo reply from exit gateway"); - info!("Connection event: {event:?}"); - registered_replies.register_event(&event); - } - } - } - } - } - - Ok(Some(Exit { - can_connect: true, - can_route_ip_v4: registered_replies.ipr_tun_ip_v4, - can_route_ip_external_v4: registered_replies.external_ip_v4, - can_route_ip_v6: registered_replies.ipr_tun_ip_v6, - can_route_ip_external_v6: registered_replies.external_ip_v6, - })) -} - -fn unpack_data_response(reconstructed_message: &ReconstructedMessage) -> Option { - match IpPacketResponse::from_reconstructed_message(reconstructed_message) { - Ok(response) => match response.data { - IpPacketResponseData::Data(data_response) => Some(data_response), - IpPacketResponseData::Control(control) => match *control { - ControlResponse::Info(info) => { - let msg = format!("Received info response from the mixnet: {}", info.reply); - match info.level { - InfoLevel::Info => info!("{msg}"), - InfoLevel::Warn => warn!("{msg}"), - InfoLevel::Error => error!("{msg}"), - } - None - } - _ => { - info!("Ignoring: {:?}", control); - None - } - }, - }, - Err(err) => { - warn!("Failed to parse mixnet message: {err}"); - None - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_netstack_args_default_values() { - // Test that the default values are correctly set in the struct definition - // This validates that our changes to the default values are correct - - // Create a default instance to test the values - let args = NetstackArgs { - netstack_download_timeout_sec: 180, - metadata_timeout_sec: 30, - netstack_v4_dns: "1.1.1.1".to_string(), - netstack_v6_dns: "2606:4700:4700::1111".to_string(), - netstack_num_ping: 5, - netstack_send_timeout_sec: 3, - netstack_recv_timeout_sec: 3, - netstack_ping_hosts_v4: vec!["nym.com".to_string()], - netstack_ping_ips_v4: vec!["1.1.1.1".to_string()], - netstack_ping_hosts_v6: vec!["cloudflare.com".to_string()], - netstack_ping_ips_v6: vec![ - "2001:4860:4860::8888".to_string(), - "2606:4700:4700::1111".to_string(), - "2620:fe::fe".to_string(), - ], - }; - - // Test IPv4 defaults - assert_eq!(args.netstack_ping_hosts_v4, vec!["nym.com"]); - assert_eq!(args.netstack_ping_ips_v4, vec!["1.1.1.1"]); - assert_eq!(args.netstack_v4_dns, "1.1.1.1"); - - // Test IPv6 defaults - assert_eq!(args.netstack_ping_hosts_v6, vec!["cloudflare.com"]); - assert_eq!( - args.netstack_ping_ips_v6, - vec![ - "2001:4860:4860::8888", - "2606:4700:4700::1111", - "2620:fe::fe" - ] - ); - assert_eq!(args.netstack_v6_dns, "2606:4700:4700::1111"); - - // Test other defaults - assert_eq!(args.netstack_download_timeout_sec, 180); - assert_eq!(args.netstack_num_ping, 5); - assert_eq!(args.netstack_send_timeout_sec, 3); - assert_eq!(args.netstack_recv_timeout_sec, 3); - } - - #[test] - fn test_netstack_args_custom_construction() { - // Test that we can create instances with custom values - let args = NetstackArgs { - netstack_download_timeout_sec: 300, - metadata_timeout_sec: 30, - netstack_v4_dns: "8.8.8.8".to_string(), - netstack_v6_dns: "2001:4860:4860::8888".to_string(), - netstack_num_ping: 10, - netstack_send_timeout_sec: 5, - netstack_recv_timeout_sec: 5, - netstack_ping_hosts_v4: vec!["example.com".to_string()], - netstack_ping_ips_v4: vec!["8.8.8.8".to_string()], - netstack_ping_hosts_v6: vec!["ipv6.example.com".to_string()], - netstack_ping_ips_v6: vec!["2001:4860:4860::8888".to_string()], - }; - - assert_eq!(args.netstack_ping_hosts_v4, vec!["example.com"]); - assert_eq!(args.netstack_ping_hosts_v6, vec!["ipv6.example.com"]); - assert_eq!(args.netstack_ping_ips_v4, vec!["8.8.8.8"]); - assert_eq!(args.netstack_ping_ips_v6, vec!["2001:4860:4860::8888"]); - assert_eq!(args.netstack_v4_dns, "8.8.8.8"); - assert_eq!(args.netstack_v6_dns, "2001:4860:4860::8888"); - assert_eq!(args.netstack_download_timeout_sec, 300); - assert_eq!(args.netstack_num_ping, 10); - assert_eq!(args.netstack_send_timeout_sec, 5); - assert_eq!(args.netstack_recv_timeout_sec, 5); - } - - #[test] - fn test_netstack_args_multiple_values() { - // Test that multiple hosts and IPs can be stored - let args = NetstackArgs { - netstack_download_timeout_sec: 180, - metadata_timeout_sec: 30, - netstack_v4_dns: "1.1.1.1".to_string(), - netstack_v6_dns: "2606:4700:4700::1111".to_string(), - netstack_num_ping: 5, - netstack_send_timeout_sec: 3, - netstack_recv_timeout_sec: 3, - netstack_ping_hosts_v4: vec!["nym.com".to_string(), "example.com".to_string()], - netstack_ping_ips_v4: vec!["1.1.1.1".to_string(), "8.8.8.8".to_string()], - netstack_ping_hosts_v6: vec![ - "cloudflare.com".to_string(), - "ipv6.example.com".to_string(), - ], - netstack_ping_ips_v6: vec![ - "2001:4860:4860::8888".to_string(), - "2606:4700:4700::1111".to_string(), - ], - }; - - assert_eq!(args.netstack_ping_hosts_v4, vec!["nym.com", "example.com"]); - assert_eq!( - args.netstack_ping_hosts_v6, - vec!["cloudflare.com", "ipv6.example.com"] - ); - assert_eq!(args.netstack_ping_ips_v4, vec!["1.1.1.1", "8.8.8.8"]); - assert_eq!( - args.netstack_ping_ips_v6, - vec!["2001:4860:4860::8888", "2606:4700:4700::1111"] - ); - } - - #[test] - fn test_netstack_args_edge_cases() { - // Test edge cases like zero values and empty vectors - let args = NetstackArgs { - netstack_download_timeout_sec: 0, - metadata_timeout_sec: 30, - netstack_v4_dns: "1.1.1.1".to_string(), - netstack_v6_dns: "2606:4700:4700::1111".to_string(), - netstack_num_ping: 0, - netstack_send_timeout_sec: 0, - netstack_recv_timeout_sec: 0, - netstack_ping_hosts_v4: vec![], - netstack_ping_ips_v4: vec![], - netstack_ping_hosts_v6: vec![], - netstack_ping_ips_v6: vec![], - }; - - assert_eq!(args.netstack_num_ping, 0); - assert_eq!(args.netstack_send_timeout_sec, 0); - assert_eq!(args.netstack_recv_timeout_sec, 0); - assert_eq!(args.netstack_download_timeout_sec, 0); - assert!(args.netstack_ping_hosts_v4.is_empty()); - assert!(args.netstack_ping_ips_v4.is_empty()); - assert!(args.netstack_ping_hosts_v6.is_empty()); - assert!(args.netstack_ping_ips_v6.is_empty()); - } - - #[test] - fn test_netstack_args_domain_validation() { - // Test that our domain choices are reasonable - let args = NetstackArgs { - netstack_download_timeout_sec: 180, - metadata_timeout_sec: 30, - netstack_v4_dns: "1.1.1.1".to_string(), - netstack_v6_dns: "2606:4700:4700::1111".to_string(), - netstack_num_ping: 5, - netstack_send_timeout_sec: 3, - netstack_recv_timeout_sec: 3, - netstack_ping_hosts_v4: vec!["nym.com".to_string()], - netstack_ping_ips_v4: vec!["1.1.1.1".to_string()], - netstack_ping_hosts_v6: vec!["cloudflare.com".to_string()], - netstack_ping_ips_v6: vec!["2001:4860:4860::8888".to_string()], - }; - - assert!(args.netstack_ping_hosts_v4[0].contains("nym")); - - assert!(args.netstack_ping_hosts_v6[0].contains("cloudflare")); - - assert_eq!(args.netstack_v4_dns, "1.1.1.1"); - assert_eq!(args.netstack_v6_dns, "2606:4700:4700::1111"); - } -} diff --git a/nym-gateway-probe/src/run.rs b/nym-gateway-probe/src/run.rs index 0ce0b98b8f..d03a9aa267 100644 --- a/nym-gateway-probe/src/run.rs +++ b/nym-gateway-probe/src/run.rs @@ -6,10 +6,9 @@ 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, + CredentialArgs, NetstackArgs, NymApiDirectory, ProbeResult, TestMode, TestedNode, + TestedNodeDetails, TestedNodeLpDetails, query_gateway_by_ip, }; use nym_kkt_ciphersuite::{HashFunction, KEM}; use nym_sdk::mixnet::NodeIdentity; From 982786b678486fb8d2e418ed66a20be5270d770c Mon Sep 17 00:00:00 2001 From: import this <97586125+serinko@users.noreply.github.com> Date: Thu, 29 Jan 2026 13:21:28 +0000 Subject: [PATCH 31/51] [NTM]: NIP-7 port update & [DOCs/operators]: Release notes for v2026.2 oscypek (#6384) * add operators notes * add dev notes * bump up version * open NIP-7 ports * bump up stats * fix incorrect dash --- .../nyx-outputs/nyx-percent-stake.md | 2 +- .../nyx-outputs/nyx-total-stake.md | 2 +- .../outputs/api-scraping-outputs/time-now.md | 2 +- .../command-outputs/nym-node-run-help.md | 4 + .../outputs/csv2md-outputs/isp-sheet.md | 4 +- .../docs/pages/operators/changelog.mdx | 132 +++++++++++++++++- .../pages/operators/nodes/nym-node/setup.mdx | 10 +- .../nym-node-setup/network-tunnel-manager.sh | 7 + 8 files changed, 152 insertions(+), 11 deletions(-) diff --git a/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/nyx-percent-stake.md b/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/nyx-percent-stake.md index 3381dfa5c0..00cd13c087 100644 --- a/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/nyx-percent-stake.md +++ b/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/nyx-percent-stake.md @@ -1 +1 @@ -0.83% +0.87% diff --git a/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/nyx-total-stake.md b/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/nyx-total-stake.md index 3926dee04e..6b54162743 100644 --- a/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/nyx-total-stake.md +++ b/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/nyx-total-stake.md @@ -1 +1 @@ -34.648 +33.087 diff --git a/documentation/docs/components/outputs/api-scraping-outputs/time-now.md b/documentation/docs/components/outputs/api-scraping-outputs/time-now.md index edf8f1ed5d..8e121587e5 100644 --- a/documentation/docs/components/outputs/api-scraping-outputs/time-now.md +++ b/documentation/docs/components/outputs/api-scraping-outputs/time-now.md @@ -1 +1 @@ -Thursday, January 15th 2026, 09:44:55 UTC +Wednesday, January 28th 2026, 09:28:07 UTC diff --git a/documentation/docs/components/outputs/command-outputs/nym-node-run-help.md b/documentation/docs/components/outputs/command-outputs/nym-node-run-help.md index 29cf2e2826..ab11bb896e 100644 --- a/documentation/docs/components/outputs/command-outputs/nym-node-run-help.md +++ b/documentation/docs/components/outputs/command-outputs/nym-node-run-help.md @@ -76,6 +76,10 @@ Options: Indicates whether this gateway is accepting only coconut credentials for accessing the mixnet or if it also accepts non-paying clients [env: NYMNODE_ENFORCE_ZK_NYMS=] [possible values: true, false] --mnemonic Custom cosmos wallet mnemonic used for zk-nym redemption. If no value is provided, a fresh mnemonic is going to be generated [env: NYMNODE_MNEMONIC=] + --upgrade-mode-attestation-url + Endpoint to query to retrieve current upgrade mode attestation. This argument should never be set outside testnets and local networks [env: NYMNODE_UPGRADE_MODE_ATTESTATION_URL=] + --upgrade-mode-attester-public-key + Expected public key of the entity signing the published attestation. This argument should never be set outside testnets and local networks [env: NYMNODE_UPGRADE_MODE_ATTESTER_PUBKEY=] --upstream-exit-policy-url Specifies the url for an upstream source of the exit policy used by this node [env: NYMNODE_UPSTREAM_EXIT_POLICY=] --open-proxy diff --git a/documentation/docs/components/outputs/csv2md-outputs/isp-sheet.md b/documentation/docs/components/outputs/csv2md-outputs/isp-sheet.md index 6c0d218cff..168049880d 100644 --- a/documentation/docs/components/outputs/csv2md-outputs/isp-sheet.md +++ b/documentation/docs/components/outputs/csv2md-outputs/isp-sheet.md @@ -25,12 +25,12 @@ | [LiteServer](https://liteserver.nl) | Netherlands | Yes, on by default | Yes | Very reliable Dutch provider. They do allow Relay nodes but for Exit nodes you need to contact them. Always check T&C https://liteserver.nl/legal | 07/2024 | | [Lowendbox](https://lowendbox.com/category/dedicated-servers) | | | | Just an aggregator with good offers | 07/2025 | | [M247](https://m247.com/eu/services/host/dedicated-servers/) | UK, Austria, Br, Sw, Jp, Poland, Fr, USA, Netherlands | Yes | No | nan | 07/2025 | -| [Mebilcom](https://www.melbicom.net/dedicatedserver/) | NL, USA, DE, UAE, NG, ESP, IN, IT, FR, LT, SG, BG, LV, PL | nan | No | nan | 07/2025 | +| [Mebilcom](https://www.melbicom.net/dedicatedserver/) | NL, US, DE, UAE, NG, ESP, IN, IT, FR, LT, SG, BG, LV, PL | nan | No | nan | 07/2025 | | [Mevspace](https://mevspace.com) | Poland | Yes, on by default | Yes | Flexible Polish providers with 3 DCs in Poland. They do allow Tor Exit nodes but you may need a dedicated server for this. Make sure you open a ticket to check. As of today's date, they have 48h for 1 EUR tariff | 07/2024 | | [Misaka](https://www.misaka.io/) | South Africa | Yes, native support | No | Very Expensive | 05/2024 | | [NiceVPS](https://nicevps.net/) | Netherlands | Yes | nan | nan | 07/2025 | | [Njalla](https://nja.la) | Sweden | Yes | Yes | Privacy vandguards! The biggest VPS 45 is 3 cores only, but it works better than many “larger” servers on the market. | 05/2024 | -| [OVH](https://us.ovhcloud.com/bare-metal/rise/rise-3/) | USA, DE, FR, UK, PL, CA | | No | Not all locations always available | 07/2025 | +| [OVH](https://us.ovhcloud.com/bare-metal/rise/rise-3/) | USA, DE, FR, UK, PL, CA | | No | Exit nodes not allowed on VPS offering, see their [Service Specific Terms](https://us.ovhcloud.com/legal/service-specific-terms/). Not all locations always available | 09/2025 | | [Oneprovider](https://oneprovider.com/en/dedicated-servers/ipv6) | PL, FR, NL, UA, USA, BG, RO, DK, ESP, NO, CZ, RS, IE, IT, UK, HU, CH, SK, AT, BE, BA, HK, JP, SG, LU, AU, SWE, UAE, BR, CR, MX, GR, CL, MA, AR | Yes | No | nan | 07/2025 | | [PrivateLayer](https://privatelayer.com) | Swiss | Yes | Yes | Slow customer response | 07/2025 | | [Privex](https://www.privex.io/tor-exit-policy/) | USA, Germany, Sweden | Yes | Yes | nan | 07/2025 | diff --git a/documentation/docs/pages/operators/changelog.mdx b/documentation/docs/pages/operators/changelog.mdx index 1f7b4b690b..9ea5885dd6 100644 --- a/documentation/docs/pages/operators/changelog.mdx +++ b/documentation/docs/pages/operators/changelog.mdx @@ -49,6 +49,136 @@ This page displays a full list of all the changes during our release cycle from +## `v2026.2-oscypek` + +- [Release Binaries](https://github.com/nymtech/nym/releases/tag/nym-binaries-v2026.2-oscypek) +- [`nym-node`](nodes/nym-node.mdx) version `1.24.0` + +```sh +nym-node +Binary Name: nym-node +Build Timestamp: 2026-01-27T14:54:15.579821601Z +Build Version: 1.24.0 +Commit SHA: 83bf9dc7cc2b01f65cab671733f2bf6c3abd471d +Commit Date: 2026-01-27T15:46:52.000000000+01:00 +Commit Branch: HEAD +rustc Version: 1.91.1 +rustc Channel: stable +cargo Profile: release +``` + +### Operators Updates & Tools + + +**This release comes with breaking changes - please follow the [steps below](#oscypek-special-update) before upgrading!** + +Secondly, the outcome of [NIP-7: Nym Exit Policy Update - Opening Ports for Steam, Discord & SSH](https://governator.nym.com/proposal/prop-281e9ec1-8e10-4e97-848c-311823e83f61), is added to the [Network tunnel manager (NTM)](https://github.com/nymtech/nym/blob/develop/scripts/nym-node-setup/network-tunnel-manager.sh) and operators are required to rerun the tool on their servers as [documented here](update-nym-exit-policy). + + +#### `oscypek` special update + +This release brings changes which would lead into a *foreign constraint bug* if operators just switched binaries and restarted the node. To prevent it we need to do a little `sqlite` tweak on the node database. + +To simplify this, we made **a build in command, which operators must run after getting the new binary, but beofre restarting the node.** + +These are the steps to follow: + + +###### 1. Get `oscypek` binary +- SSH to your machine as root +- Navigate to the destination where you have `nym-node` binary +- Get the latest binary and provide it with permissions to run +```sh +curl -L "https://github.com/nymtech/nym/releases/download/nym-binaries-v2026.2-oscypek/nym-node" -o nym-node && \ +chmod +x nym-node +``` + +###### 2. Run `debug` command +```sh +./nym-node debug reset-providers-gateway-dbs --id +``` + +###### 3. Restart your node +- Restart the `nym-node.service` +```sh +systemctl restart nym-node +``` +- Additionaly look for starus or serivice journal +```sh +service nym-node status +# or +journalctl -u nym-node -f --all +``` + + +#### Update Nym exit policy + +As a result of [NIP-7: Nym Exit Policy Update - Opening Ports for Steam, Discord & SSH](https://governator.nym.com/proposal/prop-281e9ec1-8e10-4e97-848c-311823e83f61), we updated [`network-tunnel-manager.sh` (NTM)](https://github.com/nymtech/nym/blob/develop/scripts/nym-node-setup/network-tunnel-manager.sh). Every operator is required to download and re-run the current version of NTM on the servers hosting Nym nodes. + +These are the steps for the exit policy update, using NTM. + + +###### 1. Get the new NTM +- Download the updated NTM and make executable +```sh +curl -L https://raw.githubusercontent.com/nymtech/nym/refs/heads/develop/scripts/nym-node-setup/network-tunnel-manager.sh -o ./network-tunnel-manager.sh && \ +chmod +x network-tunnel-manager.sh +``` + +###### 2. Update exit policy +- To be sure that your routing is clean, run this command: +```sh +./network-tunnel-manager.sh complete_networking_configuration +``` + + +### Features + +- [Deriving `Serialize` for `GatewayData`](https://github.com/nymtech/nym/pull/6314): Deriving `Serialize` for gateway data, that will be used by the diagnostic tool in the `vpn-client` repo + +- [DNS static table pre-resolve](https://github.com/nymtech/nym/pull/6297): This PR adds pre-resolve stage that returns addres if we have used static table previously. This ensures that we don't continually suffer the penalty of a lookup timeout, while also allowing for the possibility of going back to the default internal secure resolver if one or more nameservers becomes usable again at a future time. + +- [Add `Copy+Clone` to `nym_api_provider::Config`](https://github.com/nymtech/nym/pull/6296): Add `Copy+Clone` to `nym_client_core::client::topology_control::nym_api_provider::Config` + +- [LP Registration + Telescoping + Gateway Probe Localnet Mode](https://github.com/nymtech/nym/pull/6286): Combines LP registration protocol implementation, adds telescoping/nested sessions support, adds localnet mode for `gateway-probe` testing, integrates KKT & PSQ cryptographic primitives + +- [Minor DNS improvements](https://github.com/nymtech/nym/pull/6283): Increase timeouts back to 10 seconds for overall lookup and 5 seconds per query, gnore unreliable test, remove JIT resolution in http client as it is at best not useful, and at worst increasing timeout + +- [HTTP client without default features](https://github.com/nymtech/nym/pull/6281): Fix compile issue caused when using the http client using `default-features=false` + +- [DNS: reduce number of attempts](https://github.com/nymtech/nym/pull/6278): Reduce number of retry attempts performed by hickory to `0`, define `new_resolver` as infallible, use `ResolverOpts` to build builder + +- [Fallback gateway listener and remove legacy key support](https://github.com/nymtech/nym/pull/6249) + +- [Fix assertion](https://github.com/nymtech/nym/pull/6238) + +- [Initial changes to support extra configurable parameters and to print…](https://github.com/nymtech/nym/pull/6237): This branch adds support for the additional configurable parameters introduced in `nicolas/sdk-param-support-debug` in the nym vpn client branch and also debugging messages to verify that it works + +- [Data Observatory](https://github.com/nymtech/nym/pull/6172): This PR adds the Data Observatory that is: + - chain scraper + - parses Cosmos SDK messages + - parses Cosmwasm messages + - stores data in pgsql + +### Bugfix + +- [Downgrade gateway protocol to clients proposed version](https://github.com/nymtech/nym/pull/6377) + +- [Ack fix](https://github.com/nymtech/nym/pull/6364) + +- [Sqlite transaction escalation was causing errors ](https://github.com/nymtech/nym/pull/6299): Getting tickets from credential storage requires a transaction doing a read and then a write. Running registration in parallel was causing sqlite to return errors, because it can't escalate two transactions, only one. + +- [Use proper mixing delay instead of poisson delay in cover traffic](https://github.com/nymtech/nym/pull/6269): Currently the secondary cover traffic loop uses its Poisson process delays instead of a proper mixing delay, this PR fixes that + +### Refactors & Maintenance + +- [Update nix to `v0.30.1`](https://github.com/nymtech/nym/pull/6316) + +- [Rremove repetitive words in comment](https://github.com/nymtech/nym/pull/6313) + +- [Clippy fixes and use fixed rust version from `REQUIRED_RUSTC_VERSION`](https://github.com/nymtech/nym/pull/6295) + + ## `v2026.1-niolo` - [Release Binaries](https://github.com/nymtech/nym/releases/tag/nym-binaries-v2026.1-niolo) @@ -120,7 +250,7 @@ Please, let us know how that worked for you. - [`gateway-probe` fixes for run-local](https://github.com/nymtech/nym/pull/6212) -- [Upgrade mode: VPN adjustments](https://github.com/nymtech/nym/pull/6189): This PR further builds up on [\#6174](https://github.com/nymtech/nym/pull/6174) to include changes required by the VPN-client to fully support the upgrade mode, what is relevant here is that this PR modifies the credential storage to allow it to storage an opaque `emergency credential` that lets it be shared between sessions (if it is still valid) ] +- [Upgrade mode: VPN adjustments](https://github.com/nymtech/nym/pull/6189): This PR further builds up on [\#6174](https://github.com/nymtech/nym/pull/6174) to include changes required by the VPN-client to fully support the upgrade mode, what is relevant here is that this PR modifies the credential storage to allow it to storage an opaque `emergency credential` that lets it be shared between sessions (if it is still valid) - [Add weighted scoring to NS API](https://github.com/nymtech/nym/pull/6144) diff --git a/documentation/docs/pages/operators/nodes/nym-node/setup.mdx b/documentation/docs/pages/operators/nodes/nym-node/setup.mdx index 59d401496f..971a629c0f 100644 --- a/documentation/docs/pages/operators/nodes/nym-node/setup.mdx +++ b/documentation/docs/pages/operators/nodes/nym-node/setup.mdx @@ -21,11 +21,11 @@ This documentation page provides a guide on how to set up and run a [NYM NODE](. ```sh nym-node Binary Name: nym-node -Build Timestamp: 2025-12-02T16:21:03.251191389Z -Build Version: 1.23.0 -Commit SHA: 46fe1bc8191f42aa27f34743c96e9e9f26453d87 -Commit Date: 2025-12-02T15:29:30.000000000Z -Commit Branch: release/2025.22-niolo +Build Timestamp: 2026-01-27T14:54:15.579821601Z +Build Version: 1.24.0 +Commit SHA: 83bf9dc7cc2b01f65cab671733f2bf6c3abd471d +Commit Date: 2026-01-27T15:46:52.000000000+01:00 +Commit Branch: HEAD rustc Version: 1.91.1 rustc Channel: stable cargo Profile: release diff --git a/scripts/nym-node-setup/network-tunnel-manager.sh b/scripts/nym-node-setup/network-tunnel-manager.sh index 8306cb512c..5360157ed3 100755 --- a/scripts/nym-node-setup/network-tunnel-manager.sh +++ b/scripts/nym-node-setup/network-tunnel-manager.sh @@ -651,6 +651,7 @@ apply_port_allowlist() { ["NTP"]="123" ["IMAP"]="143" ["IMAP3"]="220" + ["SSHAlternative1"]="223" ["LDAP"]="389" ["HTTPS"]="443" ["SMBWindowsFileShare"]="445" @@ -688,10 +689,13 @@ apply_port_allowlist() { ["GNUnet"]="2086-2087" ["NBX"]="2095-2096" ["Zephyr"]="2102-2104" + ["SSHAlternative2"]="2222" ["XboxLive"]="3074" ["MySQL"]="3306" + ["SteamGaming1"]="3478" ["SVN"]="3690" ["RWHOIS"]="4321" + ["SteamGaming2"]="4379-4380" ["Virtuozzo"]="4643" ["RTPVOIP"]="5000-5005" ["MMCC"]="5050" @@ -727,6 +731,7 @@ apply_port_allowlist() { ["DarkFi"]="26661" ["Steam"]="27000-27050" ["WhatsAppRange"]="3478-3484" + ["DiscordVoiceChat"]="50000-65535" ["ElectrumSSL"]="50002" ["MOSH"]="60000-61000" ["Mumble"]="64738" @@ -1067,6 +1072,8 @@ test_port_range_rules() { "8332-8333:tcp:bitcoin" "18080-18081:tcp:monero" "3478-3484:tcp:whatsapp" + "50000-65535:tcp:discord" + "4379-4380:tcp:steam" ) local failures=0 From 7dd1dd1a6cfcac126f11f68359c214c3e13edc98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Thu, 29 Jan 2026 13:38:21 +0000 Subject: [PATCH 32/51] Lp/two step dvpn reg (#6386) * squashing Lp/two step dvpn reg #6386 fixed integration tests by extending the mocks remove dead code compiling client-side code gateway side handling of updated lp-wg reg wip: countless changes on the gateway handler side splitting up NestedLpSession * fixed lp-messages tests * gateway probe fixes * unused variable * resolved nits --- Cargo.lock | 3 + common/bandwidth-controller/src/mock.rs | 6 +- common/bandwidth-controller/src/traits.rs | 19 + .../src/client/base_client/storage/mod.rs | 2 +- common/nym-lp-transport/Cargo.toml | 3 +- common/nym-lp-transport/src/traits.rs | 75 +- common/nym-lp/src/message.rs | 12 + common/nym-lp/src/peer.rs | 9 + common/registration/Cargo.toml | 1 + common/registration/src/lib.rs | 7 +- common/registration/src/lp_messages.rs | 585 +++++++++---- common/wireguard/src/lib.rs | 1 + common/wireguard/src/peer_controller/mod.rs | 50 +- gateway/src/error.rs | 7 +- .../authenticator/mixnet_listener.rs | 18 +- .../authenticator/mod.rs | 2 +- gateway/src/node/lp_listener/handler.rs | 77 +- gateway/src/node/lp_listener/mod.rs | 294 ++++--- gateway/src/node/lp_listener/peer_manager.rs | 127 +++ gateway/src/node/lp_listener/registration.rs | 826 ++++++++++-------- gateway/src/node/mod.rs | 19 +- integration-tests/Cargo.toml | 2 +- integration-tests/src/lp_registration.rs | 81 +- .../src/common/bandwidth_helpers.rs | 116 +-- nym-gateway-probe/src/common/probe_tests.rs | 188 ++-- nym-gateway-probe/src/lib.rs | 37 +- nym-node/src/node/mod.rs | 5 +- nym-registration-client/src/lib.rs | 4 +- .../src/lp_client/client.rs | 553 ++++++------ .../src/lp_client/error.rs | 14 + .../src/lp_client/helpers.rs | 84 +- nym-registration-client/src/lp_client/mod.rs | 1 + .../src/lp_client/nested_session.rs | 498 +++++------ .../src/lp_client/state_machine_helpers.rs | 106 +++ 34 files changed, 2259 insertions(+), 1573 deletions(-) create mode 100644 gateway/src/node/lp_listener/peer_manager.rs create mode 100644 nym-registration-client/src/lp_client/state_machine_helpers.rs diff --git a/Cargo.lock b/Cargo.lock index 351e140330..25ac7b4776 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4108,6 +4108,7 @@ name = "integration-tests" version = "0.1.0" dependencies = [ "anyhow", + "futures", "nym-bandwidth-controller", "nym-credential-verification", "nym-credentials-interface", @@ -6906,6 +6907,7 @@ version = "0.1.0" dependencies = [ "nym-test-utils", "tokio", + "tracing", ] [[package]] @@ -7525,6 +7527,7 @@ dependencies = [ "nym-test-utils", "nym-wireguard-types", "serde", + "tracing", ] [[package]] diff --git a/common/bandwidth-controller/src/mock.rs b/common/bandwidth-controller/src/mock.rs index d889c83987..5bd7a04ebe 100644 --- a/common/bandwidth-controller/src/mock.rs +++ b/common/bandwidth-controller/src/mock.rs @@ -21,7 +21,7 @@ pub struct MockBandwidthController { impl BandwidthTicketProvider for MockBandwidthController { async fn get_ecash_ticket( &self, - _ticket_type: TicketType, + ticket_type: TicketType, _gateway_id: PublicKey, tickets_to_spend: u32, ) -> Result { @@ -100,6 +100,10 @@ impl BandwidthTicketProvider for MockBandwidthController { let mut credential = CredentialSpendingData::try_from_bytes(&CREDENTIAL_BYTES) .expect("Failed to deserialize test credential - this is a bug in the test harness"); + // change the ticket type to the requested ticket + // note that verification outside mocks is going to fail + credential.payment.t_type = ticket_type.to_repr() as u8; + // Update spend_date to today to pass validation credential.spend_date = OffsetDateTime::now_utc().date(); diff --git a/common/bandwidth-controller/src/traits.rs b/common/bandwidth-controller/src/traits.rs index d611649004..bb958f482b 100644 --- a/common/bandwidth-controller/src/traits.rs +++ b/common/bandwidth-controller/src/traits.rs @@ -57,3 +57,22 @@ where Ok(Some(token)) } } + +#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] +#[cfg_attr(not(target_arch = "wasm32"), async_trait)] +impl BandwidthTicketProvider for Box { + async fn get_ecash_ticket( + &self, + ticket_type: TicketType, + gateway_id: ed25519::PublicKey, + tickets_to_spend: u32, + ) -> Result { + (**self) + .get_ecash_ticket(ticket_type, gateway_id, tickets_to_spend) + .await + } + + async fn get_upgrade_mode_token(&self) -> Result, BandwidthControllerError> { + (**self).get_upgrade_mode_token().await + } +} diff --git a/common/client-core/src/client/base_client/storage/mod.rs b/common/client-core/src/client/base_client/storage/mod.rs index 5f24c8ef7f..bc92fb86bc 100644 --- a/common/client-core/src/client/base_client/storage/mod.rs +++ b/common/client-core/src/client/base_client/storage/mod.rs @@ -26,7 +26,7 @@ use crate::{ error::ClientCoreError, }; #[cfg(all(not(target_arch = "wasm32"), feature = "fs-credentials-storage"))] -use nym_credential_storage::persistent_storage::PersistentStorage as PersistentCredentialStorage; +pub use nym_credential_storage::persistent_storage::PersistentStorage as PersistentCredentialStorage; pub use nym_client_core_gateways_storage as gateways_storage; pub use nym_client_core_gateways_storage::{GatewaysDetailsStore, InMemGatewaysDetails}; diff --git a/common/nym-lp-transport/Cargo.toml b/common/nym-lp-transport/Cargo.toml index 929d68da37..d4448e49c8 100644 --- a/common/nym-lp-transport/Cargo.toml +++ b/common/nym-lp-transport/Cargo.toml @@ -12,8 +12,9 @@ readme.workspace = true publish = false [dependencies] -tokio = { workspace = true, features = ["net"] } +tokio = { workspace = true, features = ["net", "io-util"] } nym-test-utils = { path = "../test-utils", optional = true } +tracing = { workspace = true } [features] io-mocks = ["nym-test-utils"] diff --git a/common/nym-lp-transport/src/traits.rs b/common/nym-lp-transport/src/traits.rs index 5b3d300884..888c800f64 100644 --- a/common/nym-lp-transport/src/traits.rs +++ b/common/nym-lp-transport/src/traits.rs @@ -4,8 +4,9 @@ #[cfg(feature = "io-mocks")] use nym_test_utils::mocks::async_read_write::MockIOStream; use std::net::SocketAddr; -use tokio::io::{AsyncRead, AsyncWrite}; +use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; use tokio::net::TcpStream; +use tracing::debug; // only used in internal code (and tests) #[allow(async_fn_in_trait)] @@ -13,6 +14,78 @@ pub trait LpTransport: AsyncRead + AsyncWrite + Sized { async fn connect(endpoint: SocketAddr) -> std::io::Result; fn set_no_delay(&mut self, nodelay: bool) -> std::io::Result<()>; + + /// Sends a serialised (and optionally encrypted) LP packet over the data stream with length-prefixed framing. + /// + /// Format: 4-byte big-endian u32 length + packet bytes + /// + /// # Arguments + /// * `packet_data` - The serialised LP packet to send + /// + /// # Errors + /// Returns an error on network transmission fails. + async fn send_serialised_packet(&mut self, packet_data: &[u8]) -> std::io::Result<()> + where + Self: Unpin, + { + // Send 4-byte length prefix (u32 big-endian) + let len = packet_data.len() as u32; + self.write_all(&len.to_be_bytes()) + .await + .inspect_err(|e| debug!("Failed to send packet length: {e}"))?; + + // Send the actual packet data + self.write_all(packet_data) + .await + .inspect_err(|e| debug!("Failed to send packet data: {e}"))?; + + // Flush to ensure data is sent immediately + self.flush() + .await + .inspect_err(|e| debug!("Failed to flush stream: {e}"))?; + + tracing::trace!( + "Sent LP packet ({} bytes + 4 byte header)", + packet_data.len() + ); + Ok(()) + } + + /// Receives an LP packet from a TCP stream with length-prefixed framing. + /// + /// Format: 4-byte big-endian u32 length + packet bytes + /// + /// # Errors + /// Returns an error on network transmission fails. + async fn receive_raw_packet(&mut self) -> std::io::Result> + where + Self: Unpin, + { + // Read 4-byte length prefix (u32 big-endian) + let mut len_buf = [0u8; 4]; + self.read_exact(&mut len_buf) + .await + .inspect_err(|e| debug!("Failed to read packet length: {e}"))?; + + let packet_len = u32::from_be_bytes(len_buf) as usize; + + // Sanity check to prevent huge allocations + const MAX_PACKET_SIZE: usize = 65536; // 64KB max + if packet_len > MAX_PACKET_SIZE { + return Err(std::io::Error::other(format!( + "Packet size {packet_len} exceeds maximum {MAX_PACKET_SIZE}", + ))); + } + + // Read the actual packet data + let mut packet_buf = vec![0u8; packet_len]; + self.read_exact(&mut packet_buf) + .await + .inspect_err(|e| debug!("Failed to read packet data: {e}"))?; + + tracing::trace!("Received LP packet ({packet_len} bytes + 4 byte header)"); + Ok(packet_buf) + } } impl LpTransport for TcpStream { diff --git a/common/nym-lp/src/message.rs b/common/nym-lp/src/message.rs index 8447b44684..fb836e63f5 100644 --- a/common/nym-lp/src/message.rs +++ b/common/nym-lp/src/message.rs @@ -239,6 +239,18 @@ pub struct ForwardPacketData { } impl ForwardPacketData { + pub fn new( + target_gateway_identity: ed25519::PublicKey, + target_lp_address: String, + inner_packet_bytes: Vec, + ) -> Self { + ForwardPacketData { + target_gateway_identity: target_gateway_identity.to_bytes(), + target_lp_address, + inner_packet_bytes, + } + } + fn len(&self) -> usize { // 32 bytes target gateway identity // + diff --git a/common/nym-lp/src/peer.rs b/common/nym-lp/src/peer.rs index 93f1fb2746..fd6e5ab7f3 100644 --- a/common/nym-lp/src/peer.rs +++ b/common/nym-lp/src/peer.rs @@ -1,6 +1,7 @@ // Copyright 2026 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use crate::ClientHelloData; use nym_crypto::asymmetric::{ed25519, x25519}; use nym_kkt::ciphersuite::{KEM, KEMKeyDigests, SignatureScheme, SigningKeyDigests}; use std::collections::HashMap; @@ -29,6 +30,14 @@ impl LpLocalPeer { } } + pub fn build_client_hello_data(&self, timestamp: u64) -> ClientHelloData { + ClientHelloData::new_with_fresh_salt( + *self.x25519().public_key(), + *self.ed25519().public_key(), + timestamp, + ) + } + #[must_use] pub fn with_kem_psq_key(mut self, key: Arc) -> Self { self.kem_psq = Some(key); diff --git a/common/registration/Cargo.toml b/common/registration/Cargo.toml index 230833559c..c361b21461 100644 --- a/common/registration/Cargo.toml +++ b/common/registration/Cargo.toml @@ -15,6 +15,7 @@ workspace = true [dependencies] bincode = { workspace = true } serde = { workspace = true, features = ["derive"] } +tracing = { workspace = true } nym-authenticator-requests = { workspace = true } nym-credentials-interface = { workspace = true } diff --git a/common/registration/src/lib.rs b/common/registration/src/lib.rs index 96d23bcf0f..ddc46a593f 100644 --- a/common/registration/src/lib.rs +++ b/common/registration/src/lib.rs @@ -11,10 +11,7 @@ use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; -pub use lp_messages::{ - LpDvpnRegistrationRequest, LpMixnetGatewayData, LpMixnetRegistrationRequest, - LpRegistrationData, LpRegistrationRequest, LpRegistrationResponse, RegistrationMode, -}; +pub use lp_messages::*; pub use serialisation::BincodeError; mod lp_messages; @@ -30,7 +27,7 @@ pub struct NymNodeInformation { pub version: AuthenticatorVersion, } -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] pub struct WireguardConfiguration { #[serde(with = "bs58_x25519_pubkey")] pub public_key: x25519::PublicKey, diff --git a/common/registration/src/lp_messages.rs b/common/registration/src/lp_messages.rs index 5e36fb4885..9a7bcc59fb 100644 --- a/common/registration/src/lp_messages.rs +++ b/common/registration/src/lp_messages.rs @@ -4,65 +4,22 @@ //! LP (Lewes Protocol) registration message types shared between client and gateway. use crate::WireguardConfiguration; +use crate::dvpn::{ + LpDvpnRegistrationFinalisation, LpDvpnRegistrationInitialRequest, + LpDvpnRegistrationRequestMessage, LpDvpnRegistrationRequestMessageContent, + LpDvpnRegistrationResponseMessage, LpDvpnRegistrationResponseMessageContent, + RequiresCredentialResponse, +}; +use crate::mixnet::{ + LpMixnetGatewayData, LpMixnetRegistrationRequestMessage, LpMixnetRegistrationResponseMessage, + LpMixnetRegistrationResponseMessageContent, +}; use crate::serialisation::{BincodeError, BincodeOptions, lp_bincode_serializer}; -use nym_credentials_interface::{CredentialSpendingData, TicketType}; +use nym_authenticator_requests::models::BandwidthClaim; +use nym_credentials_interface::TicketType; use nym_crypto::aes::cipher::crypto_common::rand_core::{CryptoRng, RngCore}; -use nym_crypto::asymmetric::ed25519; use serde::{Deserialize, Serialize}; - -/// Registration request sent by client after LP handshake -/// Aligned with existing authenticator registration flow -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct LpRegistrationRequest { - /// Mode specific registration data - pub registration_data: LpRegistrationData, - - /// Unix timestamp for replay protection - pub timestamp: u64, -} - -impl LpRegistrationRequest { - pub fn mode(&self) -> RegistrationMode { - match self.registration_data { - LpRegistrationData::Dvpn { .. } => RegistrationMode::Dvpn, - LpRegistrationData::Mixnet { .. } => RegistrationMode::Mixnet, - } - } -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub enum LpRegistrationData { - /// dVPN mode - register as WireGuard peer (most common) - Dvpn { - data: Box, - }, - - /// Mixnet mode - register for mixnet routing via IPR - Mixnet { data: LpMixnetRegistrationRequest }, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct LpDvpnRegistrationRequest { - /// Client's WireGuard public key (for dVPN mode) - pub wg_public_key: nym_wireguard_types::PeerPublicKey, - - /// Bandwidth credential for payment - pub credential: CredentialSpendingData, - - /// Ticket type for bandwidth allocation - pub ticket_type: TicketType, - - /// Preshared key to be used for the connection - pub psk: [u8; 32], -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct LpMixnetRegistrationRequest { - /// Client's ed25519 public key (identity) - /// - /// Used to derive DestinationAddressBytes for ActiveClientsStore lookup. - pub client_ed25519_pubkey: ed25519::PublicKey, -} +use tracing::error; #[derive(Debug, Clone, Serialize, Deserialize)] pub enum RegistrationMode { @@ -73,49 +30,112 @@ pub enum RegistrationMode { Mixnet, } -/// Gateway data for mixnet mode registration -/// -/// Contains the gateway's identity and sphinx key needed for the client -/// to construct its full nym Recipient address. +/// Registration request sent by client after LP handshake +/// Aligned with existing authenticator registration flow #[derive(Debug, Clone, Serialize, Deserialize)] -pub struct LpMixnetGatewayData { - /// Gateway's ed25519 identity public key - /// - /// Forms part of the client's nym Recipient address. - pub gateway_identity: ed25519::PublicKey, - // TODO: what we really need in here is the address of internal IPR +pub struct LpRegistrationRequest { + /// Mode specific registration data + pub registration_data: LpRegistrationRequestData, + + /// Unix timestamp for replay protection + pub timestamp: u64, +} + +impl LpRegistrationRequest { + pub fn mode(&self) -> RegistrationMode { + match self.registration_data { + LpRegistrationRequestData::Dvpn { .. } => RegistrationMode::Dvpn, + LpRegistrationRequestData::Mixnet { .. } => RegistrationMode::Mixnet, + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum LpRegistrationRequestData { + /// dVPN mode - register as WireGuard peer (most common) + Dvpn { + data: Box, + }, + + /// Mixnet mode - register for mixnet routing via IPR + Mixnet { + data: LpMixnetRegistrationRequestMessage, + }, } /// Registration response from gateway /// Contains GatewayData for compatibility with existing client code #[derive(Debug, Clone, Serialize, Deserialize)] pub struct LpRegistrationResponse { - /// Whether registration succeeded - pub success: bool, + /// The status of this registration after the last received client message + pub status: RegistrationStatus, - /// Error message if registration failed - pub error: Option, + /// Mode specific registration response + pub response_data: LpRegistrationResponseData, +} - /// Gateway configuration data for dVPN mode (WireGuard) - /// This matches what WireguardRegistrationResult expects - pub gateway_data: Option, +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum LpRegistrationResponseData { + /// dVPN mode - register as WireGuard peer (most common) + Dvpn { + data: LpDvpnRegistrationResponseMessage, + }, - /// Gateway data for mixnet mode - /// - /// Contains gateway identity and sphinx key needed for nym address construction. - /// Only populated for Mixnet mode registrations. - pub lp_gateway_data: Option, + /// Mixnet mode - register for mixnet routing via IPR + Mixnet { + data: LpMixnetRegistrationResponseMessage, + }, +} - /// Allocated bandwidth in bytes - pub allocated_bandwidth: i64, +/// Represents the registration status after the last received client message. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum RegistrationStatus { + /// The registration has been completed successfully + Completed, + + /// The registration has failed + Failed, + + /// To complete registration the client needs to send additional data, + /// e.g. a credential. it is context dependent. + PendingMoreData, +} + +impl RegistrationStatus { + pub fn is_successful(&self) -> bool { + matches!(self, RegistrationStatus::Completed) + } + + pub fn is_failed(&self) -> bool { + matches!(self, RegistrationStatus::Failed) + } + + pub fn is_pending(&self) -> bool { + matches!(self, RegistrationStatus::PendingMoreData) + } +} + +fn current_timestamp() -> u64 { + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .inspect_err(|_| error!("the current timestamp predates unix epoch!")) + .unwrap_or_default() + .as_secs() } impl LpRegistrationRequest { - /// Create a new dVPN registration request - pub fn new_dvpn( + /// Helper wrapping timestamp extraction + fn new(registration_data: LpRegistrationRequestData) -> LpRegistrationRequest { + Self { + registration_data, + timestamp: current_timestamp(), + } + } + + /// Create new dVPN registration initialisation request + pub fn new_initial_dvpn( rng: &mut R, wg_public_key: nym_wireguard_types::PeerPublicKey, - credential: CredentialSpendingData, ticket_type: TicketType, ) -> Self where @@ -124,29 +144,32 @@ impl LpRegistrationRequest { let mut psk = [0u8; 32]; rng.fill_bytes(&mut psk); - Self { - registration_data: LpRegistrationData::Dvpn { - data: Box::new(LpDvpnRegistrationRequest { - wg_public_key, - credential, - ticket_type, - psk, - }), - }, - #[allow(clippy::expect_used)] - timestamp: std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .expect("System time before UNIX epoch") - .as_secs(), - } + Self::new(LpRegistrationRequestData::Dvpn { + data: Box::new(LpDvpnRegistrationRequestMessage { + content: LpDvpnRegistrationRequestMessageContent::InitialRequest( + LpDvpnRegistrationInitialRequest { + wg_public_key, + psk, + ticket_type, + }, + ), + }), + }) + } + + pub fn new_finalise_dvpn(credential: BandwidthClaim) -> Self { + Self::new(LpRegistrationRequestData::Dvpn { + data: Box::new(LpDvpnRegistrationRequestMessage { + content: LpDvpnRegistrationRequestMessageContent::Finalisation( + LpDvpnRegistrationFinalisation { credential }, + ), + }), + }) } /// Validate the request timestamp is within acceptable bounds pub fn validate_timestamp(&self, max_skew_secs: u64) -> bool { - let now = std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap_or_default() - .as_secs(); + let now = current_timestamp(); (now as i64 - self.timestamp as i64).abs() <= max_skew_secs as i64 } @@ -164,35 +187,95 @@ impl LpRegistrationRequest { impl LpRegistrationResponse { /// Create a success response with GatewayData (for dVPN mode) - pub fn success(allocated_bandwidth: i64, gateway_data: WireguardConfiguration) -> Self { + pub fn success_dvpn(config: WireguardConfiguration, available_bandwidth: i64) -> Self { Self { - success: true, - error: None, - gateway_data: Some(gateway_data), - lp_gateway_data: None, - allocated_bandwidth, + status: RegistrationStatus::Completed, + response_data: LpRegistrationResponseData::Dvpn { + data: LpDvpnRegistrationResponseMessage { + content: LpDvpnRegistrationResponseMessageContent::CompletedRegistration( + dvpn::CompletedRegistrationResponse { + config, + available_bandwidth, + }, + ), + }, + }, } } - /// Create a success response for mixnet mode with LpGatewayData - pub fn success_mixnet(allocated_bandwidth: i64, lp_gateway_data: LpMixnetGatewayData) -> Self { + pub fn success_mixnet(config: LpMixnetGatewayData, available_bandwidth: i64) -> Self { Self { - success: true, - error: None, - gateway_data: None, - lp_gateway_data: Some(lp_gateway_data), - allocated_bandwidth, + status: RegistrationStatus::Completed, + response_data: LpRegistrationResponseData::Mixnet { + data: LpMixnetRegistrationResponseMessage { + content: LpMixnetRegistrationResponseMessageContent::CompletedRegistration( + mixnet::CompletedRegistrationResponse { + config, + available_bandwidth, + }, + ), + }, + }, } } /// Create an error response - pub fn error(error: String) -> Self { - Self { - success: false, - error: Some(error), - gateway_data: None, - lp_gateway_data: None, - allocated_bandwidth: 0, + pub fn error(error: impl Into, mode: RegistrationMode) -> Self { + let response_data = match mode { + RegistrationMode::Dvpn => LpRegistrationResponseData::Dvpn { + data: LpDvpnRegistrationResponseMessage::error(error), + }, + RegistrationMode::Mixnet => LpRegistrationResponseData::Mixnet { + data: LpMixnetRegistrationResponseMessage::error(error), + }, + }; + LpRegistrationResponse { + status: RegistrationStatus::Failed, + response_data, + } + } + + pub fn request_dvpn_credential() -> Self { + LpRegistrationResponse { + status: RegistrationStatus::PendingMoreData, + response_data: LpRegistrationResponseData::Dvpn { + data: LpDvpnRegistrationResponseMessage { + content: LpDvpnRegistrationResponseMessageContent::RequiresCredential( + RequiresCredentialResponse, + ), + }, + }, + } + } + + pub fn into_dvpn_response(self) -> Option { + match self.response_data { + LpRegistrationResponseData::Dvpn { data } => Some(data), + LpRegistrationResponseData::Mixnet { .. } => None, + } + } + + pub fn into_mixnet_response(self) -> Option { + match self.response_data { + LpRegistrationResponseData::Mixnet { data } => Some(data), + LpRegistrationResponseData::Dvpn { .. } => None, + } + } + + pub fn error_message(&self) -> Option<&str> { + match &self.response_data { + LpRegistrationResponseData::Dvpn { data } => match &data.content { + LpDvpnRegistrationResponseMessageContent::RegistrationFailure(response) => { + Some(&response.error) + } + _ => None, + }, + LpRegistrationResponseData::Mixnet { data } => match &data.content { + LpMixnetRegistrationResponseMessageContent::RegistrationFailure(response) => { + Some(&response.error) + } + _ => None, + }, } } @@ -207,16 +290,170 @@ impl LpRegistrationResponse { } } +pub mod dvpn { + use crate::WireguardConfiguration; + use nym_authenticator_requests::models::BandwidthClaim; + use nym_credentials_interface::TicketType; + use serde::{Deserialize, Serialize}; + + // client + #[derive(Debug, Clone, Serialize, Deserialize)] + pub struct LpDvpnRegistrationRequestMessage { + pub content: LpDvpnRegistrationRequestMessageContent, + } + + #[derive(Debug, Clone, Serialize, Deserialize)] + pub enum LpDvpnRegistrationRequestMessageContent { + InitialRequest(LpDvpnRegistrationInitialRequest), + Finalisation(LpDvpnRegistrationFinalisation), + // in theory, we could also extend it with Bandwidth-related messages, + // but that shouldn't really be the responsibility of a Registration client. + } + + #[derive(Debug, Clone, Serialize, Deserialize)] + pub struct LpDvpnRegistrationInitialRequest { + /// Client's WireGuard public key (for dVPN mode) + pub wg_public_key: nym_wireguard_types::PeerPublicKey, + + /// Preshared key to be used for the connection + pub psk: [u8; 32], + + /// Type of the ticket/gateway we're going to register with + pub ticket_type: TicketType, + } + + #[derive(Debug, Clone, Serialize, Deserialize)] + pub struct LpDvpnRegistrationFinalisation { + /// Ecash credential + pub credential: BandwidthClaim, + } + + // gateway + #[derive(Debug, Clone, Serialize, Deserialize)] + pub struct LpDvpnRegistrationResponseMessage { + pub content: LpDvpnRegistrationResponseMessageContent, + } + + impl LpDvpnRegistrationResponseMessage { + pub fn error(error: impl Into) -> Self { + LpDvpnRegistrationResponseMessage { + content: LpDvpnRegistrationResponseMessageContent::RegistrationFailure( + RegistrationFailureResponse { + error: error.into(), + }, + ), + } + } + } + + #[derive(Debug, Clone, Serialize, Deserialize)] + pub enum LpDvpnRegistrationResponseMessageContent { + RequiresCredential(RequiresCredentialResponse), + CompletedRegistration(CompletedRegistrationResponse), + RegistrationFailure(RegistrationFailureResponse), + } + + #[derive(Debug, Clone, Copy, Serialize, Deserialize)] + pub struct CompletedRegistrationResponse { + /// Gateway configuration data for dVPN mode (WireGuard) + /// This matches what WireguardRegistrationResult expects + pub config: WireguardConfiguration, + + /// The bandwidth available to this client, + pub available_bandwidth: i64, + } + + #[derive(Debug, Clone, Copy, Serialize, Deserialize)] + pub struct RequiresCredentialResponse; + + #[derive(Debug, Clone, Serialize, Deserialize)] + pub struct RegistrationFailureResponse { + pub error: String, + } +} + +pub mod mixnet { + use nym_crypto::asymmetric::ed25519; + use serde::{Deserialize, Serialize}; + + // client + #[derive(Debug, Clone, Serialize, Deserialize)] + pub struct LpMixnetRegistrationRequestMessage { + pub content: LpMixnetRegistrationRequestContent, + } + + #[derive(Debug, Clone, Serialize, Deserialize)] + pub struct LpMixnetRegistrationRequestContent { + /// Client's ed25519 public key (identity) + /// + /// Used to derive DestinationAddressBytes for ActiveClientsStore lookup. + pub client_ed25519_pubkey: ed25519::PublicKey, + } + + // gateway + + #[derive(Debug, Clone, Serialize, Deserialize)] + pub struct LpMixnetRegistrationResponseMessage { + pub content: LpMixnetRegistrationResponseMessageContent, + } + + impl LpMixnetRegistrationResponseMessage { + pub fn error(error: impl Into) -> Self { + LpMixnetRegistrationResponseMessage { + content: LpMixnetRegistrationResponseMessageContent::RegistrationFailure( + RegistrationFailureResponse { + error: error.into(), + }, + ), + } + } + } + + #[derive(Debug, Clone, Serialize, Deserialize)] + pub enum LpMixnetRegistrationResponseMessageContent { + CompletedRegistration(CompletedRegistrationResponse), + RegistrationFailure(RegistrationFailureResponse), + } + + /// Gateway data for mixnet mode registration + /// + /// Contains the gateway's identity and sphinx key needed for the client + /// to construct its full nym Recipient address. + #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] + pub struct LpMixnetGatewayData { + /// Gateway's ed25519 identity public key + /// + /// Forms part of the client's nym Recipient address. + pub gateway_identity: ed25519::PublicKey, + // TODO: what we really need in here is the address of internal IPR + } + + #[derive(Debug, Clone, Serialize, Deserialize)] + pub struct CompletedRegistrationResponse { + /// Gateway data for mixnet mode + /// + /// Contains gateway identity and sphinx key needed for nym address construction. + pub config: LpMixnetGatewayData, + + /// The bandwidth available to this client, + pub available_bandwidth: i64, + } + + #[derive(Debug, Clone, Serialize, Deserialize)] + pub struct RegistrationFailureResponse { + pub error: String, + } +} + #[cfg(test)] mod tests { use super::*; + use nym_crypto::asymmetric::ed25519; use nym_test_utils::helpers::deterministic_rng; - use std::net::Ipv4Addr; + use std::net::{Ipv4Addr, Ipv6Addr}; // ==================== Helper Functions ==================== fn create_test_gateway_data() -> WireguardConfiguration { - use std::net::Ipv6Addr; - WireguardConfiguration { public_key: nym_crypto::asymmetric::x25519::PublicKey::from( nym_sphinx::PublicKey::from([1u8; 32]), @@ -231,37 +468,60 @@ mod tests { // ==================== LpRegistrationResponse Tests ==================== - #[test] - fn test_lp_registration_response_success() { - let gateway_data = create_test_gateway_data(); - let allocated_bandwidth = 1_000_000_000; - - let response = LpRegistrationResponse::success(allocated_bandwidth, gateway_data.clone()); - - assert!(response.success); - assert!(response.error.is_none()); - assert!(response.gateway_data.is_some()); - assert_eq!(response.allocated_bandwidth, allocated_bandwidth); - - let returned_gw_data = response - .gateway_data - .expect("Gateway data should be present in success response"); - assert_eq!(returned_gw_data.public_key, gateway_data.public_key); - assert_eq!(returned_gw_data.private_ipv4, gateway_data.private_ipv4); - assert_eq!(returned_gw_data.private_ipv6, gateway_data.private_ipv6); - assert_eq!(returned_gw_data.endpoint, gateway_data.endpoint); - } - #[test] fn test_lp_registration_response_error() { let error_msg = String::from("Insufficient bandwidth"); - let response = LpRegistrationResponse::error(error_msg.clone()); + let response_mixnet = + LpRegistrationResponse::error(error_msg.clone(), RegistrationMode::Mixnet); + let response_dvpn = + LpRegistrationResponse::error(error_msg.clone(), RegistrationMode::Dvpn); - assert!(!response.success); - assert_eq!(response.error, Some(error_msg)); - assert!(response.gateway_data.is_none()); - assert_eq!(response.allocated_bandwidth, 0); + assert!(response_mixnet.status.is_failed()); + assert!(response_dvpn.status.is_failed()); + + // check mixnet + let LpRegistrationResponseData::Mixnet { data } = response_mixnet.response_data else { + panic!("unexpected response") + }; + + let LpMixnetRegistrationResponseMessageContent::RegistrationFailure(failure) = data.content + else { + panic!("unexpected response") + }; + assert_eq!(failure.error, error_msg); + + // check dvpn + let LpRegistrationResponseData::Dvpn { data } = response_dvpn.response_data else { + panic!("unexpected response") + }; + + let LpDvpnRegistrationResponseMessageContent::RegistrationFailure(failure) = data.content + else { + panic!("unexpected response") + }; + assert_eq!(failure.error, error_msg); + } + + #[test] + fn test_lp_registration_response_success_dvpn() { + let cfg = create_test_gateway_data(); + let allocated_bandwidth = 500_000_000; + + let response = LpRegistrationResponse::success_dvpn(cfg, allocated_bandwidth); + assert!(response.status.is_successful()); + + let LpRegistrationResponseData::Dvpn { data } = response.response_data else { + panic!("unexpected response") + }; + + let LpDvpnRegistrationResponseMessageContent::CompletedRegistration(complete) = + data.content + else { + panic!("unexpected response") + }; + assert_eq!(complete.config, cfg); + assert_eq!(complete.available_bandwidth, allocated_bandwidth); } #[test] @@ -274,17 +534,20 @@ mod tests { }; let allocated_bandwidth = 500_000_000; - let response = LpRegistrationResponse::success_mixnet(allocated_bandwidth, lp_gateway_data); + let response = + LpRegistrationResponse::success_mixnet(lp_gateway_data.clone(), allocated_bandwidth); + assert!(response.status.is_successful()); - assert!(response.success); - assert!(response.error.is_none()); - assert!(response.gateway_data.is_none()); - assert!(response.lp_gateway_data.is_some()); - assert_eq!(response.allocated_bandwidth, allocated_bandwidth); + let LpRegistrationResponseData::Mixnet { data } = response.response_data else { + panic!("unexpected response") + }; - let gw_data = response - .lp_gateway_data - .expect("LpGatewayData should be present"); - assert_eq!(gw_data.gateway_identity, *valid_key.public_key()); + let LpMixnetRegistrationResponseMessageContent::CompletedRegistration(complete) = + data.content + else { + panic!("unexpected response") + }; + assert_eq!(complete.config, lp_gateway_data); + assert_eq!(complete.available_bandwidth, allocated_bandwidth); } } diff --git a/common/wireguard/src/lib.rs b/common/wireguard/src/lib.rs index 1bf229db03..459677b492 100644 --- a/common/wireguard/src/lib.rs +++ b/common/wireguard/src/lib.rs @@ -28,6 +28,7 @@ pub mod peer_controller; pub mod peer_handle; pub mod peer_storage_manager; +pub use defguard_wireguard_rs::host::Peer as DefguardPeer; pub use error::Error; pub use ip_pool::{IpPool, IpPoolError}; pub use nym_wireguard_types::Config as WireguardConfig; diff --git a/common/wireguard/src/peer_controller/mod.rs b/common/wireguard/src/peer_controller/mod.rs index 7848409b75..60d7c98ee2 100644 --- a/common/wireguard/src/peer_controller/mod.rs +++ b/common/wireguard/src/peer_controller/mod.rs @@ -27,12 +27,12 @@ use nym_wireguard_types::{ }; use std::{collections::HashMap, sync::Arc}; use std::{ - net::{IpAddr, SocketAddr}, + net::IpAddr, time::{Duration, SystemTime}, }; use tokio::sync::{RwLock, mpsc}; use tokio_stream::{StreamExt, wrappers::IntervalStream}; -use tracing::{debug, error, info, trace}; +use tracing::{debug, error, info, trace, warn}; pub use nym_ip_packet_requests::IpPair; @@ -43,35 +43,30 @@ pub mod mock; #[derive(Debug, Clone)] pub struct PeerRegistrationData { pub public_key: Key, - pub preshared_key: Option, - pub endpoint: Option, - pub persistent_keepalive_interval: Option, + pub preshared_key: Key, + // pub endpoint: Option, + // pub persistent_keepalive_interval: Option, } impl PeerRegistrationData { - pub fn new(public_key: Key) -> Self { + pub fn new(public_key: Key, psk: Key) -> Self { Self { public_key, - preshared_key: None, - endpoint: None, - persistent_keepalive_interval: None, + preshared_key: psk, + // endpoint: None, + // persistent_keepalive_interval: None, } } - - pub fn with_preshared_key(mut self, key: Key) -> Self { - self.preshared_key = Some(key); - self - } - - pub fn with_endpoint(mut self, endpoint: SocketAddr) -> Self { - self.endpoint = Some(endpoint); - self - } - - pub fn with_keepalive(mut self, interval: u16) -> Self { - self.persistent_keepalive_interval = Some(interval); - self - } + // + // pub fn with_endpoint(mut self, endpoint: SocketAddr) -> Self { + // self.endpoint = Some(endpoint); + // self + // } + // + // pub fn with_keepalive(mut self, interval: u16) -> Self { + // self.persistent_keepalive_interval = Some(interval); + // self + // } } pub enum PeerControlRequest { @@ -211,6 +206,11 @@ impl PeerController { .remove_wireguard_peer(&key.to_string()) .await?; self.bw_storage_managers.remove(key); + + warn!("MISSING CALL TO IP POOL RELEASE"); + // need to figure out what addresses to release + // self.ip_pool.release() + let ret = self.wg_api.remove_peer(key); if ret.is_err() { nym_metrics::inc!("wg_peer_removal_failed"); @@ -308,7 +308,7 @@ impl PeerController { .map_err(|e| Error::IpPool(e.to_string()))?; nym_metrics::inc!("wg_ip_allocation_success"); - tracing::debug!("Allocated IP pair: {}", ip_pair); + tracing::debug!("Allocated IP pair: {ip_pair}"); Ok(ip_pair) } diff --git a/gateway/src/error.rs b/gateway/src/error.rs index 7801ef01d2..c620e019d8 100644 --- a/gateway/src/error.rs +++ b/gateway/src/error.rs @@ -1,7 +1,9 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only +pub use crate::node::client_handling::websocket::connection_handler::authenticated::RequestHandlingError; use crate::node::internal_service_providers::authenticator::error::AuthenticatorError; +use nym_credential_verification::upgrade_mode::UpgradeModeEnableError; use nym_gateway_stats_storage::error::StatsStorageError; use nym_gateway_storage::error::GatewayStorageError; use nym_ip_packet_router::error::IpPacketRouterError; @@ -12,8 +14,6 @@ use nym_validator_client::ValidatorClientError; use std::net::IpAddr; use thiserror::Error; -pub use crate::node::client_handling::websocket::connection_handler::authenticated::RequestHandlingError; - #[derive(Debug, Error)] pub enum GatewayError { #[error("the configured version of the gateway ({config_version}) is incompatible with the binary version ({binary_version})")] @@ -155,6 +155,9 @@ pub enum GatewayError { #[error("Invalid SystemTime: {0}")] InvalidSystemTime(#[from] std::time::SystemTimeError), + + #[error(transparent)] + UpgradeModeEnable(#[from] UpgradeModeEnableError), } impl From for GatewayError { diff --git a/gateway/src/node/internal_service_providers/authenticator/mixnet_listener.rs b/gateway/src/node/internal_service_providers/authenticator/mixnet_listener.rs index d5f14c70e9..7b13c5cc42 100644 --- a/gateway/src/node/internal_service_providers/authenticator/mixnet_listener.rs +++ b/gateway/src/node/internal_service_providers/authenticator/mixnet_listener.rs @@ -53,14 +53,14 @@ const DEFAULT_REGISTRATION_TIMEOUT_CHECK: Duration = Duration::from_secs(60); // const DEFAULT_WG_CLIENT_BANDWIDTH_THRESHOLD: i64 = 1024 * 1024 * 1024; pub(crate) struct RegisteredAndFree { - registration_in_progres: PendingRegistrations, + registration_in_progress: PendingRegistrations, free_private_network_ips: PrivateIPs, } impl RegisteredAndFree { pub(crate) fn new(free_private_network_ips: PrivateIPs) -> Self { RegisteredAndFree { - registration_in_progres: Default::default(), + registration_in_progress: Default::default(), free_private_network_ips, } } @@ -134,7 +134,7 @@ impl MixnetListener { async fn remove_stale_registrations(&self) -> Result<(), AuthenticatorError> { let mut registered_and_free = self.registered_and_free.write().await; let registered_values: Vec<_> = registered_and_free - .registration_in_progres + .registration_in_progress .values() .cloned() .collect(); @@ -149,7 +149,7 @@ impl MixnetListener { let Some(timestamp) = ip else { registered_and_free - .registration_in_progres + .registration_in_progress .remove(®.gateway_data.pub_key()); tracing::debug!( "Removed stale registration of {}", @@ -165,7 +165,7 @@ impl MixnetListener { if duration > DEFAULT_REGISTRATION_TIMEOUT_CHECK { *ip = None; registered_and_free - .registration_in_progres + .registration_in_progress .remove(®.gateway_data.pub_key()); tracing::debug!( "Removed stale registration of {}", @@ -187,7 +187,7 @@ impl MixnetListener { let nonce: u64 = fastrand::u64(..); let mut registered_and_free = self.registered_and_free.write().await; if let Some(registration_data) = registered_and_free - .registration_in_progres + .registration_in_progress .get(&remote_public) { let gateway_data = registration_data.gateway_data.clone(); @@ -404,7 +404,7 @@ impl MixnetListener { wg_port: self.config.authenticator.tunnel_announced_port, }; registered_and_free - .registration_in_progres + .registration_in_progress .insert(remote_public, registration_data.clone()); let bytes = match AuthenticatorVersion::from(protocol) { AuthenticatorVersion::V1 => { @@ -541,7 +541,7 @@ impl MixnetListener { ) -> AuthenticatorHandleResult { let mut registered_and_free = self.registered_and_free.write().await; let registration_data = registered_and_free - .registration_in_progres + .registration_in_progress .get(&final_message.gateway_client_pub_key()) .ok_or(AuthenticatorError::RegistrationNotInProgress)? .clone(); @@ -596,7 +596,7 @@ impl MixnetListener { } registered_and_free - .registration_in_progres + .registration_in_progress .remove(&final_message.gateway_client_pub_key()); let bytes = match AuthenticatorVersion::from(protocol) { diff --git a/gateway/src/node/internal_service_providers/authenticator/mod.rs b/gateway/src/node/internal_service_providers/authenticator/mod.rs index a98c31868e..cee2c9041b 100644 --- a/gateway/src/node/internal_service_providers/authenticator/mod.rs +++ b/gateway/src/node/internal_service_providers/authenticator/mod.rs @@ -152,7 +152,7 @@ impl Authenticator { } }) .collect(); - let mixnet_listener = crate::node::internal_service_providers::authenticator::mixnet_listener::MixnetListener::new( + let mixnet_listener = mixnet_listener::MixnetListener::new( self.config, free_private_network_ips, self.wireguard_gateway_data, diff --git a/gateway/src/node/lp_listener/handler.rs b/gateway/src/node/lp_listener/handler.rs index b2bfac5a91..35e94d4b66 100644 --- a/gateway/src/node/lp_listener/handler.rs +++ b/gateway/src/node/lp_listener/handler.rs @@ -1,8 +1,7 @@ // Copyright 2025 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use super::registration::process_registration; -use super::LpHandlerState; +use super::{LpHandlerState, ReceiverIndex}; use crate::error::GatewayError; use nym_crypto::asymmetric::{ed25519, x25519}; use nym_lp::state_machine::{LpAction, LpData, LpDataKind, LpInput}; @@ -12,7 +11,7 @@ use nym_lp::{ }; use nym_lp_transport::traits::LpTransport; use nym_metrics::{add_histogram_obs, inc}; -use nym_registration_common::LpRegistrationRequest; +use nym_registration_common::{LpRegistrationRequest, RegistrationStatus}; use std::net::SocketAddr; use std::time::Duration; use tokio::io::{AsyncReadExt, AsyncWriteExt}; @@ -742,11 +741,11 @@ where /// Handle registration request on an established session async fn handle_registration_request( &mut self, - receiver_idx: u32, + receiver_idx: ReceiverIndex, request: LpRegistrationRequest, ) -> Result<(), GatewayError> { // Process registration (might modify state) - let response = process_registration(request, &self.state).await; + let response = self.state.process_registration(receiver_idx, request).await; let response_bytes = response.serialise().map_err(|e| { GatewayError::LpProtocolError(format!("Failed to serialize response: {e}")) })?; @@ -754,13 +753,23 @@ where self.send_response_packet(receiver_idx, response_bytes, LpDataKind::Registration) .await?; - if response.success { - info!("LP registration successful for {})", self.remote_addr); - } else { - warn!( - "LP registration failed for {}: {:?}", - self.remote_addr, response.error - ); + match response.status { + RegistrationStatus::Completed => { + info!("LP registration successful for {}", self.remote_addr); + } + RegistrationStatus::Failed => { + warn!( + "LP registration failed for {}: {:?}", + self.remote_addr, + response.error_message() + ); + } + RegistrationStatus::PendingMoreData => { + info!( + "we required more deta from {} to complete registration", + self.remote_addr + ); + } } Ok(()) @@ -1219,16 +1228,24 @@ where #[cfg(test)] mod tests { use super::*; + use crate::node::lp_listener::peer_manager::PeerManager; use crate::node::lp_listener::{LpConfig, LpDebug}; use crate::node::ActiveClientsStore; use bytes::BytesMut; + use nym_credential_verification::upgrade_mode::{ + UpgradeModeCheckConfig, UpgradeModeCheckRequestSender, UpgradeModeDetails, + }; + use nym_credential_verification::UpgradeModeState; use nym_lp::codec::{parse_lp_packet, serialize_lp_packet}; use nym_lp::message::{ClientHelloData, EncryptedDataPayload, HandshakeData, LpMessage}; use nym_lp::packet::{LpHeader, LpPacket}; use nym_lp::peer::LpLocalPeer; + use nym_wireguard::{PeerControlRequest, WireguardConfig, WireguardGatewayData}; + use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; use std::sync::Arc; use std::time::{SystemTime, UNIX_EPOCH}; use tokio::io::{AsyncRead, AsyncReadExt, AsyncWriteExt}; + use tokio::sync::mpsc::Receiver; // ==================== Test Helpers ==================== /// Create a minimal test state for handler tests @@ -1236,6 +1253,23 @@ mod tests { use nym_crypto::asymmetric::ed25519; use rand::rngs::OsRng; + fn wireguard_data( + keys: Arc, + ) -> (WireguardGatewayData, Receiver) { + // some sensible default values (ports don't matter anyway) + let cfg = WireguardConfig { + bind_address: SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 51822), + private_ipv4: Ipv4Addr::new(10, 1, 0, 1), + private_ipv6: Ipv6Addr::new(0xfc01, 0, 0, 0, 0, 0, 0, 0x1), // fc01::1, + announced_tunnel_port: 51822, + announced_metadata_port: 51830, + private_network_prefix_v4: 16, + private_network_prefix_v6: 112, + }; + + WireguardGatewayData::new(cfg, keys) + } + // Create in-memory storage for testing let storage = nym_gateway_storage::GatewayStorage::init(":memory:", 100) .await @@ -1262,6 +1296,20 @@ mod tests { let id_keys = Arc::new(ed25519::KeyPair::new(&mut OsRng)); let x_keys = Arc::new(id_keys.to_x25519()); + let (wireguard_data, _) = wireguard_data(x_keys.clone()); + + let (um_recheck_tx, _) = futures::channel::mpsc::unbounded(); + + let upgrade_mode_state = UpgradeModeState::new(*id_keys.public_key()); + let upgrade_mode_details = UpgradeModeDetails::new( + UpgradeModeCheckConfig { + // essentially we never want to trigger this in our tests + min_staleness_recheck: Duration::from_nanos(1), + }, + UpgradeModeCheckRequestSender::new(um_recheck_tx), + upgrade_mode_state.clone(), + ); + let lp_peer = LpLocalPeer::new(id_keys, x_keys.clone()).with_kem_psq_key(x_keys); LpHandlerState { @@ -1272,12 +1320,13 @@ mod tests { local_lp_peer: lp_peer, metrics: nym_node_metrics::NymNodeMetrics::default(), active_clients_store: ActiveClientsStore::new(), - wg_peer_controller: None, - wireguard_data: None, + upgrade_mode: upgrade_mode_details, outbound_mix_sender: mix_sender, handshake_states: Arc::new(dashmap::DashMap::new()), session_states: Arc::new(dashmap::DashMap::new()), + registrations_in_progress: Default::default(), forward_semaphore, + peer_manager: Arc::new(PeerManager::new(wireguard_data)), } } diff --git a/gateway/src/node/lp_listener/mod.rs b/gateway/src/node/lp_listener/mod.rs index 4fc45c4f2f..d20b83205d 100644 --- a/gateway/src/node/lp_listener/mod.rs +++ b/gateway/src/node/lp_listener/mod.rs @@ -68,30 +68,37 @@ // They can be exported via Prometheus format using the metrics endpoint. use crate::error::GatewayError; +use crate::node::lp_listener::peer_manager::PeerManager; +use crate::node::lp_listener::registration::RegistrationsInProgress; use crate::node::ActiveClientsStore; use dashmap::DashMap; use nym_config::serde_helpers::de_maybe_port; +use nym_credential_verification::ecash::traits::EcashManager; +use nym_credential_verification::upgrade_mode::UpgradeModeDetails; use nym_gateway_storage::GatewayStorage; use nym_lp::state_machine::LpStateMachine; -pub use nym_mixnet_client::forwarder::{ - mix_forwarding_channels, MixForwardingReceiver, MixForwardingSender, -}; use nym_node_metrics::NymNodeMetrics; use nym_task::ShutdownTracker; use std::net::{IpAddr, Ipv6Addr, SocketAddr}; use std::sync::Arc; use std::time::Duration; use tokio::net::TcpListener; -use tokio::sync::{mpsc, Semaphore}; +use tokio::sync::Semaphore; use tracing::*; pub use nym_lp::peer::LpLocalPeer; +pub use nym_mixnet_client::forwarder::{ + mix_forwarding_channels, MixForwardingReceiver, MixForwardingSender, +}; pub use nym_wireguard::{PeerControlRequest, WireguardGatewayData}; mod data_handler; pub mod handler; +pub mod peer_manager; mod registration; +pub type ReceiverIndex = u32; + /// Configuration for LP listener #[derive(Debug, Clone, Copy, serde::Deserialize, serde::Serialize)] #[serde(default)] @@ -111,7 +118,7 @@ pub struct LpConfig { pub announce_control_port: Option, /// Custom announced port for listening for the UDP LP data traffic. - /// If unspecified, the value from the `data_bind_address` will be used instead + /// If unspecified, the value from the `data_bind_address` will be used instead /// (default: None) #[serde(deserialize_with = "de_maybe_port")] pub announce_data_port: Option, @@ -178,6 +185,10 @@ pub struct LpDebug { #[serde(with = "humantime_serde")] pub demoted_session_ttl: Duration, + /// Maximum age of in-progress dVPN registration before cleanup (default: 60s) + #[serde(with = "humantime_serde")] + pub pending_registration_ttl: Duration, + /// How often to run the state cleanup task (default: 5 minutes) /// /// The cleanup task scans for and removes stale handshakes and sessions. @@ -247,6 +258,9 @@ impl LpDebug { // 5 minutes - balances memory reclamation with task overhead pub const DEFAULT_STATE_CLEANUP_INTERVAL: Duration = Duration::from_secs(300); + // 1 minute - enough for client to send retrieve credential from its storage and send it across + pub const DEFAULT_PENDING_REGISTRATION_TTL: Duration = Duration::from_secs(60); + // Limits concurrent outbound connections to prevent fd exhaustion pub const DEFAULT_MAX_CONCURRENT_FORWARDS: usize = 1000; } @@ -260,6 +274,7 @@ impl Default for LpDebug { handshake_ttl: Self::DEFAULT_HANDSHAKE_TTL, session_ttl: Self::DEFAULT_SESSION_TTL, demoted_session_ttl: Self::DEFAULT_DEMOTED_SESSION_TTL, + pending_registration_ttl: Self::DEFAULT_PENDING_REGISTRATION_TTL, state_cleanup_interval: Self::DEFAULT_STATE_CLEANUP_INTERVAL, max_concurrent_forwards: Self::DEFAULT_MAX_CONCURRENT_FORWARDS, } @@ -311,12 +326,12 @@ impl TimestampedState { } /// Get age since creation - pub fn age(&self) -> std::time::Duration { + pub fn age(&self) -> Duration { self.created_at.elapsed() } /// Get time since last activity - pub fn since_activity(&self) -> std::time::Duration { + pub fn since_activity(&self) -> Duration { let now = std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .unwrap_or_default() @@ -332,8 +347,7 @@ impl TimestampedState { #[derive(Clone)] pub struct LpHandlerState { /// Ecash verifier for bandwidth credentials - pub ecash_verifier: - Arc, + pub ecash_verifier: Arc, /// Storage backend for persistence pub storage: GatewayStorage, @@ -347,11 +361,12 @@ pub struct LpHandlerState { /// Active clients tracking pub active_clients_store: ActiveClientsStore, - /// WireGuard peer controller channel (for dVPN registrations) - pub wg_peer_controller: Option>, + /// Current state of the Upgrade Mode as perceived by this gateway + pub upgrade_mode: UpgradeModeDetails, /// WireGuard gateway data (contains keypair and config) - pub wireguard_data: Option, + /// alongside helpers for managing peers + pub peer_manager: Arc, /// LP configuration (for timestamp validation, etc.) pub lp_config: LpConfig, @@ -369,7 +384,7 @@ pub struct LpHandlerState { /// state moves to session_states map. /// /// Wrapped in TimestampedState for TTL-based cleanup of stale handshakes. - pub handshake_states: Arc>>, + pub handshake_states: Arc>>, /// Established sessions keyed by session_id /// @@ -383,7 +398,11 @@ pub struct LpHandlerState { /// subsession/rekeying support. The state machine handles subsession initiation /// (SubsessionKK1/KK2/Ready) during transport phase, allowing long-lived connections /// to rekey without re-authentication. - pub session_states: Arc>>, + pub session_states: Arc>>, + + /// In-progress dVPN registrations that require additional data (e.g. credentials) + /// to finalise. + pub registrations_in_progress: RegistrationsInProgress, /// Semaphore limiting concurrent forward connections /// @@ -561,28 +580,28 @@ impl LpListener { fn spawn_state_cleanup_task(&self) -> tokio::task::JoinHandle<()> { let handshake_states = Arc::clone(&self.handler_state.handshake_states); let session_states = Arc::clone(&self.handler_state.session_states); + let pending_registrations = self.handler_state.registrations_in_progress.clone(); let dbg_cfg = self.handler_state.lp_config.debug; let handshake_ttl = dbg_cfg.handshake_ttl; let session_ttl = dbg_cfg.session_ttl; let demoted_session_ttl = dbg_cfg.demoted_session_ttl; + let pending_reg_ttl = dbg_cfg.pending_registration_ttl; let interval = dbg_cfg.state_cleanup_interval; let shutdown = self.shutdown.clone_shutdown_token(); let metrics = self.handler_state.metrics.clone(); info!( - "Starting LP state cleanup task (handshake_ttl={}s, session_ttl={}s, demoted_ttl={}s, interval={}s)", - handshake_ttl.as_secs(), session_ttl.as_secs(), demoted_session_ttl.as_secs(), interval.as_secs() + "Starting LP state cleanup task (handshake_ttl={}s, session_ttl={}s, demoted_ttl={}s, reg_ttl={}s, interval={}s)", + handshake_ttl.as_secs(), session_ttl.as_secs(), demoted_session_ttl.as_secs(),pending_reg_ttl.as_secs(), interval.as_secs() ); self.shutdown.try_spawn_named( - Self::cleanup_loop( + cleanup_task::cleanup_loop( handshake_states, session_states, - handshake_ttl, - session_ttl, - demoted_session_ttl, - interval, + pending_registrations, + dbg_cfg, shutdown, metrics, ), @@ -590,104 +609,6 @@ impl LpListener { ) } - /// Background loop for cleaning up stale state entries - /// - /// Runs periodically to scan handshake_states and session_states maps, - /// removing entries that have exceeded their TTL. - /// - /// Demoted sessions (ReadOnlyTransport) use shorter TTL since they - /// only need to drain in-flight packets after subsession promotion. - #[allow(clippy::too_many_arguments)] - async fn cleanup_loop( - handshake_states: Arc>>, - session_states: Arc>>, - handshake_ttl: Duration, - session_ttl: Duration, - demoted_session_ttl: Duration, - interval: Duration, - shutdown: nym_task::ShutdownToken, - _metrics: NymNodeMetrics, - ) { - use nym_lp::state_machine::LpStateBare; - use nym_metrics::inc_by; - - let mut cleanup_interval = tokio::time::interval(interval); - - loop { - tokio::select! { - biased; - - _ = shutdown.cancelled() => { - debug!("LP state cleanup task: received shutdown signal"); - break; - } - - _ = cleanup_interval.tick() => { - let start = std::time::Instant::now(); - let mut hs_removed = 0u64; - let mut ss_removed = 0u64; - let mut demoted_removed = 0u64; - - // Remove stale handshakes (based on age since creation) - handshake_states.retain(|_, timestamped| { - if timestamped.age() > handshake_ttl { - hs_removed += 1; - false - } else { - true - } - }); - - // Remove stale sessions (based on time since last activity) - // Use shorter TTL for demoted (ReadOnlyTransport) sessions - session_states.retain(|_, timestamped| { - let is_demoted = timestamped.state.bare_state() == LpStateBare::ReadOnlyTransport; - let ttl = if is_demoted { - demoted_session_ttl - } else { - session_ttl - }; - - if timestamped.since_activity() > ttl { - if is_demoted { - demoted_removed += 1; - } else { - ss_removed += 1; - } - false - } else { - true - } - }); - - if hs_removed > 0 || ss_removed > 0 || demoted_removed > 0 { - let duration = start.elapsed(); - info!( - "LP state cleanup: removed {} handshakes, {} sessions, {} demoted (took {:.3}s)", - hs_removed, - ss_removed, - demoted_removed, - duration.as_secs_f64() - ); - - // Track metrics - if hs_removed > 0 { - inc_by!("lp_states_cleanup_handshake_removed", hs_removed as i64); - } - if ss_removed > 0 { - inc_by!("lp_states_cleanup_session_removed", ss_removed as i64); - } - if demoted_removed > 0 { - inc_by!("lp_states_cleanup_demoted_removed", demoted_removed as i64); - } - } - } - } - } - - info!("LP state cleanup task shutdown complete"); - } - fn active_lp_connections(&self) -> usize { self.handler_state .metrics @@ -695,3 +616,138 @@ impl LpListener { .active_lp_connections_count() } } + +pub(crate) mod cleanup_task { + use crate::node::lp_listener::registration::RegistrationsInProgress; + use crate::node::lp_listener::{LpDebug, TimestampedState}; + use dashmap::DashMap; + use nym_lp::state_machine::LpStateBare; + use nym_lp::LpStateMachine; + use nym_metrics::inc_by; + use nym_node_metrics::NymNodeMetrics; + use std::sync::Arc; + use tracing::{debug, info}; + + async fn perform_cleanup( + handshake_states: &Arc>>, + session_states: &Arc>>, + registrations_in_progress: &RegistrationsInProgress, + cfg: LpDebug, + ) { + let handshake_ttl = cfg.handshake_ttl; + let session_ttl = cfg.session_ttl; + let demoted_session_ttl = cfg.demoted_session_ttl; + let pending_registration_ttl = cfg.pending_registration_ttl; + + let start = std::time::Instant::now(); + let mut hs_removed = 0u64; + let mut ss_removed = 0u64; + let mut pending_reg_removed = 0u64; + let mut demoted_removed = 0u64; + + // Remove stale handshakes (based on age since creation) + handshake_states.retain(|_, timestamped| { + if timestamped.age() > handshake_ttl { + hs_removed += 1; + false + } else { + true + } + }); + + // Remove stale sessions (based on time since last activity) + // Use shorter TTL for demoted (ReadOnlyTransport) sessions + session_states.retain(|_, timestamped| { + let is_demoted = timestamped.state.bare_state() == LpStateBare::ReadOnlyTransport; + let ttl = if is_demoted { + demoted_session_ttl + } else { + session_ttl + }; + + if timestamped.since_activity() > ttl { + if is_demoted { + demoted_removed += 1; + } else { + ss_removed += 1; + } + false + } else { + true + } + }); + + // Remove stale registrations (based on time since last activity) + registrations_in_progress + .lock() + .await + .retain(|_, timestamped| { + if timestamped.age() > pending_registration_ttl { + pending_reg_removed += 1; + false + } else { + true + } + }); + + if hs_removed > 0 || ss_removed > 0 || demoted_removed > 0 || pending_reg_removed > 0 { + let duration = start.elapsed(); + info!( + "LP state cleanup: removed {hs_removed} handshakes, {pending_reg_removed} pending registrations, {ss_removed} sessions, {demoted_removed} demoted (took {:.3}s)", + duration.as_secs_f64() + ); + + // Track metrics + if hs_removed > 0 { + inc_by!("lp_states_cleanup_handshake_removed", hs_removed as i64); + } + if ss_removed > 0 { + inc_by!("lp_states_cleanup_session_removed", ss_removed as i64); + } + if demoted_removed > 0 { + inc_by!("lp_states_cleanup_demoted_removed", demoted_removed as i64); + } + if pending_reg_removed > 0 { + inc_by!( + "lp_states_cleanup_pending_registrations_removed", + pending_reg_removed as i64 + ); + } + } + } + + /// Background loop for cleaning up stale state entries + /// + /// Runs periodically to scan handshake_states and session_states maps, + /// removing entries that have exceeded their TTL. + /// + /// Demoted sessions (ReadOnlyTransport) use shorter TTL since they + /// only need to drain in-flight packets after subsession promotion. + pub(crate) async fn cleanup_loop( + handshake_states: Arc>>, + session_states: Arc>>, + registrations_in_progress: RegistrationsInProgress, + cfg: LpDebug, + shutdown: nym_task::ShutdownToken, + _metrics: NymNodeMetrics, + ) { + let interval = cfg.state_cleanup_interval; + + let mut cleanup_interval = tokio::time::interval(interval); + + loop { + tokio::select! { + biased; + _ = shutdown.cancelled() => { + debug!("LP state cleanup task: received shutdown signal"); + break; + } + _ = cleanup_interval.tick() => { + perform_cleanup(&handshake_states, &session_states, ®istrations_in_progress, cfg).await; + } + } + } + + info!("LP state cleanup task shutdown complete"); + } +} diff --git a/gateway/src/node/lp_listener/peer_manager.rs b/gateway/src/node/lp_listener/peer_manager.rs new file mode 100644 index 0000000000..027fe9221f --- /dev/null +++ b/gateway/src/node/lp_listener/peer_manager.rs @@ -0,0 +1,127 @@ +// Copyright 2026 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::GatewayError; +use defguard_wireguard_rs::host::Peer; +use defguard_wireguard_rs::key::Key; +use futures::channel::oneshot; +use nym_credential_verification::ClientBandwidth; +use nym_wireguard::peer_controller::IpPair; +use nym_wireguard::{PeerControlRequest, PeerRegistrationData, WireguardGatewayData}; +use nym_wireguard_types::PeerPublicKey; +use tracing::error; + +/// attempts to replicate [`crate::node::internal_service_providers::authenticator::peer_manager::PeerManager`] +// TODO: put those in the shared crate +pub struct PeerManager { + pub(crate) wireguard_gateway_data: WireguardGatewayData, +} + +impl PeerManager { + pub fn new(wireguard_gateway_data: WireguardGatewayData) -> Self { + PeerManager { + wireguard_gateway_data, + } + } + + pub async fn register_peer( + &self, + registration_data: PeerRegistrationData, + ) -> Result { + let (response_tx, response_rx) = oneshot::channel(); + let msg = PeerControlRequest::RegisterPeer { + registration_data, + response_tx, + }; + self.wireguard_gateway_data + .peer_tx() + .send(msg) + .await + .map_err(|e| { + GatewayError::InternalError(format!("Failed to send IP allocation request: {e}")) + })?; + + response_rx + .await + .map_err(|e| { + GatewayError::InternalError(format!("Failed to receive IP allocation: {e}")) + })? + .map_err(|e| { + error!("Failed to allocate IPs from pool: {e}"); + GatewayError::InternalError(format!("Failed to allocate IPs: {e}")) + }) + } + + pub async fn add_peer(&self, peer: Peer) -> Result<(), GatewayError> { + let (response_tx, response_rx) = oneshot::channel(); + let msg = PeerControlRequest::AddPeer { peer, response_tx }; + self.wireguard_gateway_data + .peer_tx() + .send(msg) + .await + .map_err(|e| { + GatewayError::InternalError(format!("Failed to send peer request: {e}")) + })?; + + response_rx + .await + .map_err(|_| GatewayError::InternalError("no response for add peer".to_string()))? + .map_err(|err| { + GatewayError::InternalError(format!("adding peer could not be performed: {err:?}")) + }) + } + + pub async fn query_peer( + &self, + public_key: PeerPublicKey, + ) -> Result, GatewayError> { + let key = Key::new(public_key.to_bytes()); + let (response_tx, response_rx) = oneshot::channel(); + let msg = PeerControlRequest::QueryPeer { key, response_tx }; + self.wireguard_gateway_data + .peer_tx() + .send(msg) + .await + .map_err(|_| { + GatewayError::InternalError("Failed to send peer query request".to_string()) + })?; + + response_rx + .await + .map_err(|_| GatewayError::InternalError("no response for query peer".to_string()))? + .map_err(|err| { + GatewayError::InternalError(format!( + "querying peer could not be performed: {err:?}" + )) + }) + } + + pub async fn query_client_bandwidth( + &self, + key: PeerPublicKey, + ) -> Result { + let key = Key::new(key.to_bytes()); + let (response_tx, response_rx) = oneshot::channel(); + let msg = PeerControlRequest::GetClientBandwidthByKey { key, response_tx }; + self.wireguard_gateway_data + .peer_tx() + .send(msg) + .await + .map_err(|_| { + GatewayError::InternalError( + "Failed to send peer bandwidth query request".to_string(), + ) + })?; + + response_rx + .await + .map_err(|_| { + GatewayError::InternalError("no response for query peer bandwidth".to_string()) + })? + .map_err(|err| { + GatewayError::InternalError(format!( + "querying client bandwidth could not be performed: {err:?}" + )) + }) + } +} diff --git a/gateway/src/node/lp_listener/registration.rs b/gateway/src/node/lp_listener/registration.rs index 8d004d1d28..5df0c609d3 100644 --- a/gateway/src/node/lp_listener/registration.rs +++ b/gateway/src/node/lp_listener/registration.rs @@ -1,30 +1,37 @@ // Copyright 2025 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use super::LpHandlerState; +use super::{LpHandlerState, ReceiverIndex, TimestampedState}; use crate::error::GatewayError; -use crate::node::client_handling::websocket::message_receiver::IsActive; use defguard_wireguard_rs::host::Peer; use defguard_wireguard_rs::key::Key; -use futures::channel::{mpsc, oneshot}; +use nym_authenticator_requests::models::BandwidthClaim; use nym_credential_verification::ecash::traits::EcashManager; use nym_credential_verification::{ bandwidth_storage_manager::BandwidthStorageManager, BandwidthFlushingBehaviourConfig, ClientBandwidth, CredentialVerifier, }; -use nym_credentials_interface::CredentialSpendingData; +use nym_credentials_interface::{BandwidthCredential, CredentialSpendingData, TicketType}; +use nym_crypto::asymmetric::encryption::KeyPair; use nym_gateway_requests::models::CredentialSpendingRequest; 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::{ - LpDvpnRegistrationRequest, LpMixnetGatewayData, LpMixnetRegistrationRequest, - LpRegistrationData, LpRegistrationRequest, LpRegistrationResponse, WireguardConfiguration, +use nym_registration_common::dvpn::{ + LpDvpnRegistrationFinalisation, LpDvpnRegistrationInitialRequest, + LpDvpnRegistrationRequestMessage, LpDvpnRegistrationRequestMessageContent, }; -use nym_wireguard::PeerControlRequest; +use nym_registration_common::mixnet::LpMixnetRegistrationRequestMessage; +use nym_registration_common::{ + LpRegistrationRequest, LpRegistrationRequestData, LpRegistrationResponse, RegistrationMode, + RegistrationStatus, WireguardConfiguration, +}; +use nym_wireguard::WireguardConfig; use nym_wireguard_types::PeerPublicKey; +use std::collections::HashMap; +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; use std::sync::Arc; -use time::OffsetDateTime; +use tokio::sync::{Mutex, MutexGuard}; use tracing::*; // Histogram buckets for LP registration duration tracking @@ -56,6 +63,434 @@ const WG_CONTROLLER_LATENCY_BUCKETS: &[f64] = &[ 2.0, // 2s ]; +#[derive(Clone, Copy)] +pub struct PendingRegistrationState { + client_id: i64, + peer_key: PeerPublicKey, + ticket_type: TicketType, + wireguard_config: WireguardConfiguration, +} + +#[derive(Clone, Default)] +pub struct RegistrationsInProgress { + /// Wrapped in TimestampedState for TTL-based cleanup of stale data. + inner: Arc>>>, +} + +impl RegistrationsInProgress { + pub async fn lock( + &self, + ) -> MutexGuard<'_, HashMap>> { + self.inner.lock().await + } +} + +impl LpHandlerState { + fn upgrade_mode_enabled(&self) -> bool { + self.upgrade_mode.enabled() + } + + fn keypair(&self) -> &Arc { + self.peer_manager.wireguard_gateway_data.keypair() + } + + fn wireguard_config(&self) -> WireguardConfig { + self.peer_manager.wireguard_gateway_data.config() + } + + fn successful_dvpn_registration( + &self, + peer_private_ipv4: Ipv4Addr, + peer_private_ipv6: Ipv6Addr, + bandwidth: i64, + ) -> LpRegistrationResponse { + LpRegistrationResponse::success_dvpn( + WireguardConfiguration { + public_key: *self.keypair().public_key(), + // TODO: according to @SW this is most likely very wrong + endpoint: self.wireguard_config().bind_address, + private_ipv4: peer_private_ipv4, + private_ipv6: peer_private_ipv6, + }, + bandwidth, + ) + } + + /// Check if WG peer already registered, return cached response if so. + /// + /// This enables idempotent registration: if a client retries registration + /// with the same WG public key (e.g., after network failure), we return + /// the existing registration data instead of re-processing. This prevents + /// wasting credentials on network issues. + async fn check_existing_dvpn_registration( + &self, + public_key: PeerPublicKey, + ) -> Option { + // Look up existing peer + let Ok(maybe_peer) = self.peer_manager.query_peer(public_key).await else { + return Some(LpRegistrationResponse::error( + "iternal failure: failed to resolve peer information", + RegistrationMode::Dvpn, + )); + }; + + let peer = maybe_peer?; + + // Extract IPv4 and IPv6 from allowed_ips + let mut private_ipv4 = None; + let mut private_ipv6 = None; + for ip_mask in &peer.allowed_ips { + match ip_mask.address { + IpAddr::V4(v4) => private_ipv4 = Some(v4), + IpAddr::V6(v6) => private_ipv6 = Some(v6), + } + if private_ipv4.is_some() && private_ipv6.is_some() { + break; + } + } + + // Incomplete data, treat as new registration + let (Some(private_ipv4), Some(private_ipv6)) = (private_ipv4, private_ipv6) else { + return None; + }; + + // Get current bandwidth + let Ok(bandwidth) = self.peer_manager.query_client_bandwidth(public_key).await else { + return Some(LpRegistrationResponse::error( + "iternal failure: failed to resolve peer bandwidth", + RegistrationMode::Dvpn, + )); + }; + + Some(self.successful_dvpn_registration( + private_ipv4, + private_ipv6, + bandwidth.available().await, + )) + } + + /// In the case of an already registered WG peer, update its PSK. + async fn update_peer_psk(&self, peer: PeerPublicKey, psk: Key) -> Result<(), GatewayError> { + let encoded_psk = psk.to_lower_hex(); + self.storage + .update_peer_psk(&peer.to_string(), Some(&encoded_psk)) + .await?; + + // TODO: do we have to go through a peer manager to also update PSK if a peer is currently active? + // seems like an edge case. maybe we should force disconnect here? + Ok(()) + } + + async fn process_dvpn_initial_registration( + &self, + sender: ReceiverIndex, + request: LpDvpnRegistrationInitialRequest, + ) -> LpRegistrationResponse { + let wg_key_str = request.wg_public_key.to_string(); + + // check for an existing registration (same WG key already registered) + // This allows clients to retry registration after network failures + // or to re-use gateway without spending additional bandwidth + if let Some(existing_response) = self + .check_existing_dvpn_registration(request.wg_public_key) + .await + { + // if there already exists registration for this client, update the psk and return the peer data + if let Err(err) = self + .update_peer_psk(request.wg_public_key, Key::new(request.psk)) + .await + { + return LpRegistrationResponse::error( + format!("WireGuard peer PSK update failed: {err}"), + RegistrationMode::Dvpn, + ); + } + info!("LP dVPN re-registration for existing peer {wg_key_str} (idempotent)",); + inc!("lp_registration_dvpn_idempotent"); + return existing_response; + } + + // TODO: this could be a source of some issue as we pre-allocate ip before validating credentials + // (but we do the same in the authenticator anyway...) + if let Err(err) = self + .register_wg_peer( + sender, + request.wg_public_key, + request.ticket_type, + Key::new(request.psk), + ) + .await + { + return LpRegistrationResponse::error( + format!("WireGuard peer IP allocation failed: {err}"), + RegistrationMode::Dvpn, + ); + } + + LpRegistrationResponse::request_dvpn_credential() + } + + // TODO: dedup + async fn handle_final_credential_claim( + &self, + claim: BandwidthClaim, + client_id: i64, + ) -> Result { + match claim.credential { + BandwidthCredential::ZkNym(zk_nym) => { + // if we got zk-nym, we just try to verify it + let bandwidth = + credential_verification(self.ecash_verifier.clone(), *zk_nym, client_id) + .await?; + Ok(bandwidth) + } + BandwidthCredential::UpgradeModeJWT { token } => { + // TODO: move + const UM_BANDWIDTH: i64 = 1024 * 1024 * 1024; + + // if we're already in the upgrade mode, don't bother validating the token + if self.upgrade_mode_enabled() { + return Ok(UM_BANDWIDTH); + } + + self.upgrade_mode.try_enable_via_received_jwt(token).await?; + Ok(UM_BANDWIDTH) + } + } + } + + async fn process_dvpn_registration_finalisation( + &self, + sender: ReceiverIndex, + request: LpDvpnRegistrationFinalisation, + ) -> LpRegistrationResponse { + // see if we still have the pending registration + // (e.g. it's illegal for client to request registration and only finalise it, + // for example the next day; we can't keep the data forever) + let Some(pending) = self + .registrations_in_progress + .lock() + .await + .get(&sender) + .map(|pending| pending.state) + else { + return LpRegistrationResponse::error( + "no pending registration", + RegistrationMode::Dvpn, + ); + }; + + if pending.ticket_type != request.credential.kind { + return LpRegistrationResponse::error( + format!( + "inconsistent ticket type. used {} for initial request and {} for finalisation", + pending.ticket_type, request.credential.kind + ), + RegistrationMode::Dvpn, + ); + } + + let client_id = pending.client_id; + + let allocated_bandwidth = match self + .handle_final_credential_claim(request.credential, client_id) + .await + { + Ok(bandwidth) => bandwidth, + Err(err) => { + // Credential verification failed, remove the peer + warn!("LP credential verification failed for client {client_id}: {err}"); + inc!("lp_registration_dvpn_failed"); + if let Err(remove_err) = self + .storage + .remove_wireguard_peer(&pending.peer_key.to_string()) + .await + { + error!( + "Failed to remove peer after credential verification failure: {remove_err}" + ); + } + self.registrations_in_progress.lock().await.remove(&sender); + return LpRegistrationResponse::error( + format!("Credential verification failed: {err}"), + RegistrationMode::Dvpn, + ); + } + }; + + info!("LP dVPN registration successful (client_id: {client_id})"); + inc!("lp_registration_dvpn_success"); + LpRegistrationResponse::success_dvpn(pending.wireguard_config, allocated_bandwidth) + } + + async fn process_dvpn_registration( + &self, + sender: ReceiverIndex, + request: Box, + ) -> LpRegistrationResponse { + // Track dVPN registration attempts + inc!("lp_registration_dvpn_attempts"); + + match request.content { + LpDvpnRegistrationRequestMessageContent::InitialRequest(req) => { + self.process_dvpn_initial_registration(sender, req).await + } + LpDvpnRegistrationRequestMessageContent::Finalisation(req) => { + self.process_dvpn_registration_finalisation(sender, req) + .await + } + } + } + + async fn process_mixnet_registration( + &self, + request: LpMixnetRegistrationRequestMessage, + ) -> LpRegistrationResponse { + let _ = request; + LpRegistrationResponse::error( + "mixnet registration is not yet supported", + RegistrationMode::Mixnet, + ) + } + + /// Process an LP registration request + pub async fn process_registration( + &self, + sender: ReceiverIndex, + request: LpRegistrationRequest, + ) -> LpRegistrationResponse { + let registration_start = std::time::Instant::now(); + + // Track total registration attempts + inc!("lp_registration_attempts_total"); + + // 1. Validate timestamp for replay protection + if !request.validate_timestamp(30) { + warn!("LP registration failed: timestamp too old or too far in future"); + inc!("lp_registration_failed_timestamp"); + return LpRegistrationResponse::error("invalid timestamp", request.mode()); + } + + // 2. Process based on mode + let result = match request.registration_data { + LpRegistrationRequestData::Dvpn { data } => { + self.process_dvpn_registration(sender, data).await + } + LpRegistrationRequestData::Mixnet { data } => { + self.process_mixnet_registration(data).await + } + }; + + // Track registration duration + let duration = registration_start.elapsed().as_secs_f64(); + add_histogram_obs!( + "lp_registration_duration_seconds", + duration, + LP_REGISTRATION_DURATION_BUCKETS + ); + + // Track overall success/failure + match result.status { + RegistrationStatus::Completed => { + inc!("lp_registration_success_total"); + } + RegistrationStatus::Failed => { + inc!("lp_registration_failed_total"); + } + RegistrationStatus::PendingMoreData => { + inc!("lp_registration_pending_more_data"); + } + } + + result + } + + /// Register a WireGuard peer and return gateway data along with the client_id + async fn register_wg_peer( + &self, + sender: ReceiverIndex, + peer_key: PeerPublicKey, + ticket_type: nym_credentials_interface::TicketType, + psk: Key, + ) -> Result<(), GatewayError> { + // Allocate IPs from centralized pool managed by PeerController + let defguard_key = Key::new(peer_key.to_bytes()); + + let registration_data = nym_wireguard::PeerRegistrationData::new(defguard_key.clone(), psk); + + let psk = registration_data.preshared_key.clone(); + + // Request IP allocation from PeerController + let ip_pair = self.peer_manager.register_peer(registration_data).await?; + + let client_ipv4 = ip_pair.ipv4; + let client_ipv6 = ip_pair.ipv6; + + info!("Allocated IPs for peer {peer_key}: {client_ipv4} / {client_ipv6}"); + + // Create WireGuard peer with allocated IPs + let mut peer = Peer::new(defguard_key); + peer.endpoint = None; + peer.allowed_ips = vec![ + format!("{client_ipv4}/32").parse()?, + format!("{client_ipv6}/128").parse()?, + ]; + peer.persistent_keepalive_interval = Some(25); + peer.preshared_key = Some(psk); + + // Store peer in database FIRST (before adding to controller) + // This ensures bandwidth storage exists when controller's generate_bandwidth_manager() is called + let client_id = self + .storage + .insert_wireguard_peer(&peer, ticket_type.into()) + .await + .map_err(|e| { + error!("Failed to store WireGuard peer in database: {}", e); + GatewayError::InternalError(format!("Failed to store peer: {}", e)) + })?; + + // Create bandwidth entry for the client + // This must happen BEFORE AddPeer because generate_bandwidth_manager() expects it to exist + credential_storage_preparation(self.ecash_verifier.clone(), client_id).await?; + + // Now send peer to WireGuard controller and track latency + let controller_start = std::time::Instant::now(); + let result = self.peer_manager.add_peer(peer).await; + + // Record peer controller channel latency + let latency = controller_start.elapsed().as_secs_f64(); + add_histogram_obs!( + "wg_peer_controller_channel_latency_seconds", + latency, + WG_CONTROLLER_LATENCY_BUCKETS + ); + + result?; + + // Get gateway's actual WireGuard public key + let gateway_pubkey = *self.keypair().public_key(); + + // Get gateway's WireGuard endpoint from config + let gateway_endpoint = self.wireguard_config().bind_address; + self.registrations_in_progress.lock().await.insert( + sender, + TimestampedState::new(PendingRegistrationState { + client_id, + peer_key, + ticket_type, + wireguard_config: WireguardConfiguration { + public_key: gateway_pubkey, + endpoint: gateway_endpoint, + private_ipv4: client_ipv4, + private_ipv6: client_ipv6, + }, + }), + ); + Ok(()) + } +} + +// TODO: dedup /// Prepare bandwidth storage for a client async fn credential_storage_preparation( ecash_verifier: Arc, @@ -83,6 +518,7 @@ async fn credential_storage_preparation( Ok(bandwidth) } +// TODO: dedup /// Verify credential and allocate bandwidth using CredentialVerifier async fn credential_verification( ecash_verifier: Arc, @@ -131,375 +567,3 @@ async fn credential_verification( Ok(allocated) } - -/// Check if WG peer already registered, return cached response if so. -/// -/// This enables idempotent registration: if a client retries registration -/// with the same WG public key (e.g., after network failure), we return -/// the existing registration data instead of re-processing. This prevents -/// wasting credentials on network issues. -async fn check_existing_registration( - wg_key_str: &str, - state: &LpHandlerState, -) -> Option { - // Need WG data to build GatewayData - let wg_data = state.wireguard_data.as_ref()?; - - // Look up existing peer - let peer = state.storage.get_wireguard_peer(wg_key_str).await.ok()??; - - // Convert to defguard Peer to access allowed_ips - let defguard_peer: Peer = peer.clone().try_into().ok()?; - - // Extract IPv4 and IPv6 from allowed_ips - let mut ipv4 = None; - let mut ipv6 = None; - for ip_mask in &defguard_peer.allowed_ips { - match ip_mask.address { - std::net::IpAddr::V4(v4) => ipv4 = Some(v4), - std::net::IpAddr::V6(v6) => ipv6 = Some(v6), - } - } - - let (private_ipv4, private_ipv6) = match (ipv4, ipv6) { - (Some(v4), Some(v6)) => (v4, v6), - _ => return None, // Incomplete data, treat as new registration - }; - - // Get current bandwidth - let bandwidth = state - .ecash_verifier - .storage() - .get_available_bandwidth(peer.client_id) - .await - .ok()? - .map(|b| b.available) - .unwrap_or(0); - - // Only return cached response if bandwidth was actually allocated. - // If bandwidth is 0, registration was incomplete (peer exists but - // credential verification failed or never completed). Let the caller - // proceed with normal registration flow which will handle cleanup. - if bandwidth == 0 { - return None; - } - - Some(LpRegistrationResponse::success( - bandwidth, - WireguardConfiguration { - public_key: *wg_data.keypair().public_key(), - endpoint: wg_data.config().bind_address, - private_ipv4, - private_ipv6, - }, - )) -} - -/// In the case of an already registered WG peer, update its PSK. -async fn update_peer_psk( - peer: PeerPublicKey, - psk: Key, - state: &LpHandlerState, -) -> Result<(), GatewayError> { - let encoded_psk = psk.to_lower_hex(); - state - .storage - .update_peer_psk(&peer.to_string(), Some(&encoded_psk)) - .await?; - Ok(()) -} - -async fn process_dvpn_registration( - request: Box, - state: &LpHandlerState, -) -> LpRegistrationResponse { - // Track dVPN registration attempts - inc!("lp_registration_dvpn_attempts"); - - // Check for idempotent re-registration (same WG key already registered) - // This allows clients to retry registration after network failures - // without wasting credentials - let wg_key_str = request.wg_public_key.to_string(); - if let Some(existing_response) = check_existing_registration(&wg_key_str, state).await { - // TODO: this flow will be changed in subsequent PRs as it's wasting credentials regardless - if let Err(err) = update_peer_psk(request.wg_public_key, Key::new(request.psk), state).await - { - return LpRegistrationResponse::error(format!( - "WireGuard peer PSK update failed: {err}" - )); - } - info!("LP dVPN re-registration for existing peer {wg_key_str} (idempotent)",); - inc!("lp_registration_dvpn_idempotent"); - return existing_response; - } - - // Register as WireGuard peer first to get client_id - let (gateway_data, client_id) = match register_wg_peer( - request.wg_public_key.inner().as_ref(), - request.ticket_type, - Key::new(request.psk), - state, - ) - .await - { - Ok(result) => result, - Err(e) => { - error!("LP WireGuard peer registration failed: {e}"); - inc!("lp_registration_dvpn_failed"); - inc!("lp_errors_wg_peer_registration"); - return LpRegistrationResponse::error(format!( - "WireGuard peer registration failed: {e}", - )); - } - }; - - // Verify credential with CredentialVerifier (handles double-spend, storage, etc.) - let allocated_bandwidth = - match credential_verification(state.ecash_verifier.clone(), request.credential, client_id) - .await - { - Ok(bandwidth) => bandwidth, - Err(e) => { - // Credential verification failed, remove the peer - warn!("LP credential verification failed for client {client_id}: {e}",); - inc!("lp_registration_dvpn_failed"); - if let Err(remove_err) = state - .storage - .remove_wireguard_peer(&request.wg_public_key.to_string()) - .await - { - error!( - "Failed to remove peer after credential verification failure: {remove_err}" - ); - } - return LpRegistrationResponse::error(format!( - "Credential verification failed: {e}", - )); - } - }; - - info!("LP dVPN registration successful (client_id: {client_id})"); - inc!("lp_registration_dvpn_success"); - LpRegistrationResponse::success(allocated_bandwidth, gateway_data) -} - -async fn process_mixnet_registration( - request: LpMixnetRegistrationRequest, - state: &LpHandlerState, -) -> LpRegistrationResponse { - let session_id = rand::random::(); - - // Track mixnet registration attempts - inc!("lp_registration_mixnet_attempts"); - - // Derive destination address for ActiveClientsStore lookup - let client_identity = request.client_ed25519_pubkey; - let client_address = client_identity.derive_destination_address(); - - info!("LP Mixnet registration for client {client_identity}, session {session_id}"); - - warn!("unimplemented: LP mixnet registration initial bandwidth allocation"); - // (the old implementation was wrong - it wasn't creating correct db entries) - - // Create channels for client message delivery - let (mix_sender, _mix_receiver) = mpsc::unbounded(); - let (is_active_request_sender, _is_active_request_receiver) = - mpsc::unbounded::>(); - - // Insert client into ActiveClientsStore for SURB reply delivery - if !state.active_clients_store.insert_remote( - client_address, - mix_sender, - is_active_request_sender, - OffsetDateTime::now_utc(), - ) { - warn!("LP Mixnet registration failed: client {client_identity} already registered",); - inc!("lp_registration_mixnet_failed"); - return LpRegistrationResponse::error("Client already registered".to_string()); - } - - // Get gateway identity and derive sphinx key - let gateway_identity = *state.local_lp_peer.ed25519().public_key(); - - info!("LP Mixnet registration successful (client: {client_identity})",); - inc!("lp_registration_mixnet_success"); - - LpRegistrationResponse::success_mixnet(0, LpMixnetGatewayData { gateway_identity }) -} - -/// Process an LP registration request -pub async fn process_registration( - request: LpRegistrationRequest, - state: &LpHandlerState, -) -> LpRegistrationResponse { - let registration_start = std::time::Instant::now(); - - // Track total registration attempts - inc!("lp_registration_attempts_total"); - - // 1. Validate timestamp for replay protection - if !request.validate_timestamp(30) { - warn!("LP registration failed: timestamp too old or too far in future"); - inc!("lp_registration_failed_timestamp"); - return LpRegistrationResponse::error("Invalid timestamp".to_string()); - } - - // 2. Process based on mode - let result = match request.registration_data { - LpRegistrationData::Dvpn { data } => process_dvpn_registration(data, state).await, - LpRegistrationData::Mixnet { data } => process_mixnet_registration(data, state).await, - }; - - // Track registration duration - let duration = registration_start.elapsed().as_secs_f64(); - add_histogram_obs!( - "lp_registration_duration_seconds", - duration, - LP_REGISTRATION_DURATION_BUCKETS - ); - - // Track overall success/failure - if result.success { - inc!("lp_registration_success_total"); - } else { - inc!("lp_registration_failed_total"); - } - - result -} - -/// Register a WireGuard peer and return gateway data along with the client_id -async fn register_wg_peer( - public_key_bytes: &[u8], - ticket_type: nym_credentials_interface::TicketType, - psk: Key, - state: &LpHandlerState, -) -> Result<(WireguardConfiguration, i64), GatewayError> { - let Some(wg_controller) = &state.wg_peer_controller else { - return Err(GatewayError::ServiceProviderNotRunning { - service: "WireGuard".to_string(), - }); - }; - - let Some(wg_data) = &state.wireguard_data else { - return Err(GatewayError::ServiceProviderNotRunning { - service: "WireGuard".to_string(), - }); - }; - - // Convert public key bytes to WireGuard Key - let mut key_bytes = [0u8; 32]; - if public_key_bytes.len() != 32 { - return Err(GatewayError::LpProtocolError( - "Invalid WireGuard public key length".to_string(), - )); - } - key_bytes.copy_from_slice(public_key_bytes); - let peer_key = Key::new(key_bytes); - - // Allocate IPs from centralized pool managed by PeerController - let registration_data = - nym_wireguard::PeerRegistrationData::new(peer_key.clone()).with_preshared_key(psk); - - let psk = registration_data.preshared_key.clone(); - // Request IP allocation from PeerController - let (tx, rx) = oneshot::channel(); - wg_controller - .send(PeerControlRequest::RegisterPeer { - registration_data, - response_tx: tx, - }) - .await - .map_err(|e| { - GatewayError::InternalError(format!("Failed to send IP allocation request: {}", e)) - })?; - - // Wait for IP allocation from pool - let ip_pair = rx - .await - .map_err(|e| { - GatewayError::InternalError(format!("Failed to receive IP allocation: {}", e)) - })? - .map_err(|e| { - error!("Failed to allocate IPs from pool: {}", e); - GatewayError::InternalError(format!("Failed to allocate IPs: {:?}", e)) - })?; - - let client_ipv4 = ip_pair.ipv4; - let client_ipv6 = ip_pair.ipv6; - - info!( - "Allocated IPs for peer {}: {} / {}", - peer_key, client_ipv4, client_ipv6 - ); - - // Create WireGuard peer with allocated IPs - let mut peer = Peer::new(peer_key.clone()); - peer.endpoint = None; - peer.allowed_ips = vec![ - format!("{client_ipv4}/32").parse()?, - format!("{client_ipv6}/128").parse()?, - ]; - peer.persistent_keepalive_interval = Some(25); - peer.preshared_key = psk; - - // Store peer in database FIRST (before adding to controller) - // This ensures bandwidth storage exists when controller's generate_bandwidth_manager() is called - let client_id = state - .storage - .insert_wireguard_peer(&peer, ticket_type.into()) - .await - .map_err(|e| { - error!("Failed to store WireGuard peer in database: {}", e); - GatewayError::InternalError(format!("Failed to store peer: {}", e)) - })?; - - // Create bandwidth entry for the client - // This must happen BEFORE AddPeer because generate_bandwidth_manager() expects it to exist - credential_storage_preparation(state.ecash_verifier.clone(), client_id).await?; - - // Now send peer to WireGuard controller and track latency - let controller_start = std::time::Instant::now(); - let (tx, rx) = oneshot::channel(); - wg_controller - .send(PeerControlRequest::AddPeer { - peer: peer.clone(), - response_tx: tx, - }) - .await - .map_err(|e| GatewayError::InternalError(format!("Failed to send peer request: {}", e)))?; - - let result = rx - .await - .map_err(|e| { - GatewayError::InternalError(format!("Failed to receive peer response: {}", e)) - })? - .map_err(|e| GatewayError::InternalError(format!("Failed to add peer: {:?}", e))); - - // Record peer controller channel latency - let latency = controller_start.elapsed().as_secs_f64(); - add_histogram_obs!( - "wg_peer_controller_channel_latency_seconds", - latency, - WG_CONTROLLER_LATENCY_BUCKETS - ); - - result?; - - // Get gateway's actual WireGuard public key - let gateway_pubkey = *wg_data.keypair().public_key(); - - // Get gateway's WireGuard endpoint from config - let gateway_endpoint = wg_data.config().bind_address; - - // Create GatewayData response (matching authenticator response format) - Ok(( - WireguardConfiguration { - public_key: gateway_pubkey, - endpoint: gateway_endpoint, - private_ipv4: client_ipv4, - private_ipv6: client_ipv6, - }, - client_id, - )) -} diff --git a/gateway/src/node/mod.rs b/gateway/src/node/mod.rs index b904143ac6..62263d521c 100644 --- a/gateway/src/node/mod.rs +++ b/gateway/src/node/mod.rs @@ -36,6 +36,7 @@ use tokio::sync::Semaphore; use tracing::*; use zeroize::Zeroizing; +use crate::node::lp_listener::peer_manager::PeerManager; pub use crate::node::upgrade_mode::watcher::UpgradeModeWatcher; pub use client_handling::active_clients::ActiveClientsStore; pub use lp_listener::LpConfig; @@ -328,13 +329,18 @@ impl GatewayTasksBuilder { pub async fn build_lp_listener( &mut self, + upgrade_mode_common_state: UpgradeModeDetails, active_clients_store: ActiveClientsStore, ) -> Result { // Get WireGuard peer controller if available - let wg_peer_controller = self - .wireguard_data - .as_ref() - .map(|wg_data| wg_data.inner.peer_tx().clone()); + let Some(wireguard_data) = &self.wireguard_data else { + return Err(GatewayError::InternalWireguardError( + "wireguard not set".to_string(), + )); + }; + + // TODO: combine this `PeerManager` with the one used within the authenticator + let peer_manager = Arc::new(PeerManager::new(wireguard_data.inner.clone())); let handler_state = lp_listener::LpHandlerState { ecash_verifier: self.ecash_manager().await?, @@ -346,12 +352,13 @@ impl GatewayTasksBuilder { .with_kem_psq_key(self.kem_psq_keys.clone()), metrics: self.metrics.clone(), active_clients_store, - wg_peer_controller, - wireguard_data: self.wireguard_data.as_ref().map(|wd| wd.inner.clone()), + upgrade_mode: upgrade_mode_common_state, + peer_manager, lp_config: self.config.lp, outbound_mix_sender: self.mix_packet_sender.clone(), handshake_states: Arc::new(dashmap::DashMap::new()), session_states: Arc::new(dashmap::DashMap::new()), + registrations_in_progress: Default::default(), forward_semaphore: Arc::new(Semaphore::new( self.config.lp.debug.max_concurrent_forwards, )), diff --git a/integration-tests/Cargo.toml b/integration-tests/Cargo.toml index f7d2f0897c..3d825c7e73 100644 --- a/integration-tests/Cargo.toml +++ b/integration-tests/Cargo.toml @@ -26,7 +26,7 @@ nym-lp-transport = { path = "../common/nym-lp-transport", features = ["io-mocks" nym-gateway = { path = "../gateway" } sqlx = { workspace = true, features = ["runtime-tokio-rustls", "sqlite"] } tracing = { workspace = true } - +futures = { workspace = true } [lints] workspace = true diff --git a/integration-tests/src/lp_registration.rs b/integration-tests/src/lp_registration.rs index 595c48128f..d292a2a441 100644 --- a/integration-tests/src/lp_registration.rs +++ b/integration-tests/src/lp_registration.rs @@ -5,11 +5,16 @@ mod tests { use anyhow::Context; use nym_bandwidth_controller::mock::MockBandwidthController; + use nym_credential_verification::UpgradeModeState; use nym_credential_verification::ecash::MockEcashManager; + use nym_credential_verification::upgrade_mode::{ + UpgradeModeCheckConfig, UpgradeModeCheckRequestSender, UpgradeModeDetails, + }; use nym_credentials_interface::TicketType; use nym_crypto::asymmetric::{ed25519, x25519}; use nym_gateway::GatewayError; use nym_gateway::node::lp_listener::handler::LpConnectionHandler; + use nym_gateway::node::lp_listener::peer_manager::PeerManager; use nym_gateway::node::lp_listener::{ LpDebug, LpHandlerState, LpLocalPeer, MixForwardingReceiver, PeerControlRequest, WireguardGatewayData, mix_forwarding_channels, @@ -174,6 +179,17 @@ mod tests { Ok(GatewayStorage::from_connection_pool(conn_pool, 100).await?) } + const DUMMY_ATTESTER_ED25519_PRIVATE_KEY: [u8; 32] = [ + 108, 49, 193, 21, 126, 161, 249, 85, 242, 207, 74, 195, 238, 6, 64, 149, 201, 140, 248, + 163, 122, 170, 79, 198, 87, 85, 36, 29, 243, 92, 64, 161, + ]; + + pub(crate) fn dummy_attester_public_key() -> ed25519::PublicKey { + let private_key = + ed25519::PrivateKey::from_bytes(&Self::DUMMY_ATTESTER_ED25519_PRIVATE_KEY).unwrap(); + private_key.public_key() + } + async fn mock(rng: &mut (impl RngCore + CryptoRng)) -> anyhow::Result { let base = Party::generate(rng); @@ -199,11 +215,27 @@ mod tests { // create wireguard data let (wireguard_data, peer_request_rx) = Self::wireguard_data(&base); + let (um_recheck_tx, um_recheck_rx) = futures::channel::mpsc::unbounded(); + + // TODO: use it if we ever want to test UM + let _ = um_recheck_rx; + // mock the wg peer controller let (mock_peer_controller, peer_controller_state) = mock_peer_controller(peer_request_rx); + let upgrade_mode_state = UpgradeModeState::new(Self::dummy_attester_public_key()); + let upgrade_mode_details = UpgradeModeDetails::new( + UpgradeModeCheckConfig { + // essentially we never want to trigger this in our tests + min_staleness_recheck: Duration::from_nanos(1), + }, + UpgradeModeCheckRequestSender::new(um_recheck_tx), + upgrade_mode_state.clone(), + ); + // registering particular responses for peer controller is up to given test + let peer_manager = Arc::new(PeerManager::new(wireguard_data)); let lp_state = LpHandlerState { // use mock instance of ecash verifier @@ -220,9 +252,8 @@ mod tests { active_clients_store: ActiveClientsStore::new(), // handles required for wg registration - wg_peer_controller: Some(wireguard_data.peer_tx().clone()), - - wireguard_data: Some(wireguard_data), + upgrade_mode: upgrade_mode_details, + peer_manager, // use default lp config (with enabled flag) lp_config, @@ -237,6 +268,7 @@ mod tests { session_states: Arc::new(Default::default()), // sensible default value for tests + registrations_in_progress: Default::default(), forward_semaphore, }; @@ -376,6 +408,7 @@ mod tests { mod using_lp_registration_client { use super::*; use nym_registration_client::NestedLpSession; + use nym_wireguard::DefguardPeer; #[tokio::test] async fn test_basic_lp_entry_registration() -> anyhow::Result<()> { @@ -430,6 +463,16 @@ mod tests { ) .await; + // 3) peer query - check for prior registrations + let query_res = Ok::<_, nym_wireguard::Error>(Option::::None); + let key = client_key.to_wg_key(); + entry + .register_peer_controller_response( + PeerControlRequestType::QueryPeer { key }, + query_res, + ) + .await; + // 4. spawn peer controller to be able to handle dvpn registration requests entry.spawn_peer_controller(); @@ -440,7 +483,7 @@ mod tests { let wg_keypair = client_data.base.x25519_wg_keys; let gateway_identity = entry.base.peer.ed25519().public_key(); let registration_result = client - .register( + .register_dvpn( &mut client_rng, &wg_keypair, gateway_identity, @@ -506,7 +549,7 @@ mod tests { let wg_keypair = client_data.base.x25519_wg_keys; let gateway_identity = entry.base.peer.ed25519().public_key(); let registration_result = client - .register( + .register_dvpn( &mut client_rng, &wg_keypair, gateway_identity, @@ -520,7 +563,10 @@ mod tests { let LpClientError::Transport(err) = registration_result else { panic!("unexpected error"); }; - assert_eq!(err, "Cannot register: handshake not completed"); + assert_eq!( + err, + "State machine not available - has the handshake been completed?" + ); // 5. stop the gateway task and finish the test entry.stop_tasks().await?; @@ -590,6 +636,16 @@ mod tests { ) .await; + // 3) peer query - check for prior registrations + let query_res = Ok::<_, nym_wireguard::Error>(Option::::None); + let key = client_key.to_wg_key(); + entry + .register_peer_controller_response( + PeerControlRequestType::QueryPeer { key }, + query_res, + ) + .await; + // 5. spawn peer controller to be able to handle dvpn registration requests entry.spawn_peer_controller(); @@ -630,6 +686,15 @@ mod tests { ) .await; + // 3) peer query - check for prior registrations + let query_res = Ok::<_, nym_wireguard::Error>(Option::::None); + let key = client_key.to_wg_key(); + exit.register_peer_controller_response( + PeerControlRequestType::QueryPeer { key }, + query_res, + ) + .await; + // 11. spawn peer controller to be able to handle dvpn registration requests exit.spawn_peer_controller(); @@ -646,7 +711,7 @@ mod tests { // 13. Perform handshake and registration with exit gateway (all via entry forwarding) let exit_registration_result = nested_session - .handshake_and_register( + .handshake_and_register_dvpn( &mut entry_client, &mut client_rng, &client_data.base.x25519_wg_keys, @@ -659,7 +724,7 @@ mod tests { // 14. complete registration with the entry let entry_registration_result = entry_client - .register( + .register_dvpn( &mut client_rng, &client_data.base.x25519_wg_keys, entry.base.peer.ed25519().public_key(), diff --git a/nym-gateway-probe/src/common/bandwidth_helpers.rs b/nym-gateway-probe/src/common/bandwidth_helpers.rs index 1f0048d722..dea3d6bfd5 100644 --- a/nym-gateway-probe/src/common/bandwidth_helpers.rs +++ b/nym-gateway-probe/src/common/bandwidth_helpers.rs @@ -2,18 +2,38 @@ // SPDX-License-Identifier: Apache-2.0 use anyhow::{Context, bail}; +use nym_bandwidth_controller::BandwidthTicketProvider; use nym_bandwidth_controller::error::BandwidthControllerError; +use nym_bandwidth_controller::mock::MockBandwidthController; use nym_client_core::client::base_client::storage::OnDiskPersistent; -use nym_credentials::CredentialSpendingData; use nym_credentials_interface::TicketType; use nym_node_status_client::models::AttachedTicketMaterials; use nym_sdk::bandwidth::BandwidthImporter; -use nym_sdk::mixnet::{DisconnectedMixnetClient, EphemeralCredentialStorage}; +use nym_sdk::mixnet::{CredentialStorage, DisconnectedMixnetClient, EphemeralCredentialStorage}; +use nym_validator_client::QueryHttpRpcNyxdClient; use nym_validator_client::nyxd::error::NyxdError; use std::time::Duration; -use time::OffsetDateTime; use tracing::{error, info}; +pub(crate) fn build_bandwidth_controller( + rpc_client: QueryHttpRpcNyxdClient, + on_disk_storage: S, + use_mock_ecash: bool, +) -> Box +where + S: CredentialStorage + 'static, + S::StorageError: Send + Sync + 'static, +{ + if !use_mock_ecash { + Box::new(nym_bandwidth_controller::BandwidthController::new( + on_disk_storage, + rpc_client, + )) + } else { + Box::new(MockBandwidthController::default()) + } +} + pub(crate) async fn import_bandwidth( bandwidth_importer: BandwidthImporter<'_, EphemeralCredentialStorage>, attached_ticket_materials: AttachedTicketMaterials, @@ -157,93 +177,3 @@ pub(crate) async fn acquire_bandwidth( bail!("failed to acquire bandwidth after {MAX_RETRIES} attempts") } - -/// Create a dummy credential for mock ecash testing -/// -/// Gateway with --lp-use-mock-ecash accepts any credential without verification, -/// so we only need to provide properly structured data with correct types. -/// -/// This is useful for local testing without requiring blockchain access or funded accounts. -/// -/// This uses a pre-serialized test credential from the wireguard tests - since MockEcashManager -/// doesn't verify anything, any valid CredentialSpendingData structure will work. -#[allow(clippy::expect_used)] // Test helper with hardcoded valid data -pub(crate) fn create_dummy_credential( - _gateway_identity: &[u8; 32], - _ticket_type: TicketType, -) -> CredentialSpendingData { - // This is a valid serialized CredentialSpendingData taken from integration tests - // See: common/wireguard-private-metadata/tests/src/lib.rs:CREDENTIAL_BYTES - const CREDENTIAL_BYTES: [u8; 1245] = [ - 0, 0, 4, 133, 96, 179, 223, 185, 136, 23, 213, 166, 59, 203, 66, 69, 209, 181, 227, 254, - 16, 102, 98, 237, 59, 119, 170, 111, 31, 194, 51, 59, 120, 17, 115, 229, 79, 91, 11, 139, - 154, 2, 212, 23, 68, 70, 167, 3, 240, 54, 224, 171, 221, 1, 69, 48, 60, 118, 119, 249, 123, - 35, 172, 227, 131, 96, 232, 209, 187, 123, 4, 197, 102, 90, 96, 45, 125, 135, 140, 99, 1, - 151, 17, 131, 143, 157, 97, 107, 139, 232, 212, 87, 14, 115, 253, 255, 166, 167, 186, 43, - 90, 96, 173, 105, 120, 40, 10, 163, 250, 224, 214, 200, 178, 4, 160, 16, 130, 59, 76, 193, - 39, 240, 3, 101, 141, 209, 183, 226, 186, 207, 56, 210, 187, 7, 164, 240, 164, 205, 37, 81, - 184, 214, 193, 195, 90, 205, 238, 225, 195, 104, 12, 123, 203, 57, 233, 243, 215, 145, 195, - 196, 57, 38, 125, 172, 18, 47, 63, 165, 110, 219, 180, 40, 58, 116, 92, 254, 160, 98, 48, - 92, 254, 232, 107, 184, 80, 234, 60, 160, 235, 249, 76, 41, 38, 165, 28, 40, 136, 74, 48, - 166, 50, 245, 23, 201, 140, 101, 79, 93, 235, 128, 186, 146, 126, 180, 134, 43, 13, 186, - 19, 195, 48, 168, 201, 29, 216, 95, 176, 198, 132, 188, 64, 39, 212, 150, 32, 52, 53, 38, - 228, 199, 122, 226, 217, 75, 40, 191, 151, 48, 164, 242, 177, 79, 14, 122, 105, 151, 85, - 88, 199, 162, 17, 96, 103, 83, 178, 128, 9, 24, 30, 74, 108, 241, 85, 240, 166, 97, 241, - 85, 199, 11, 198, 226, 234, 70, 107, 145, 28, 208, 114, 51, 12, 234, 108, 101, 202, 112, - 48, 185, 22, 159, 67, 109, 49, 27, 149, 90, 109, 32, 226, 112, 7, 201, 208, 209, 104, 31, - 97, 134, 204, 145, 27, 181, 206, 181, 106, 32, 110, 136, 115, 249, 201, 111, 5, 245, 203, - 71, 121, 169, 126, 151, 178, 236, 59, 221, 195, 48, 135, 115, 6, 50, 227, 74, 97, 107, 107, - 213, 90, 2, 203, 154, 138, 47, 128, 52, 134, 128, 224, 51, 65, 240, 90, 8, 55, 175, 180, - 178, 204, 206, 168, 110, 51, 57, 189, 169, 48, 169, 136, 121, 99, 51, 170, 178, 214, 74, 1, - 96, 151, 167, 25, 173, 180, 171, 155, 10, 55, 142, 234, 190, 113, 90, 79, 80, 244, 71, 166, - 30, 235, 113, 150, 133, 1, 218, 17, 109, 111, 223, 24, 216, 177, 41, 2, 204, 65, 221, 212, - 207, 236, 144, 6, 65, 224, 55, 42, 1, 1, 161, 134, 118, 127, 111, 220, 110, 127, 240, 71, - 223, 129, 12, 93, 20, 220, 60, 56, 71, 146, 184, 95, 132, 69, 28, 56, 53, 192, 213, 22, - 119, 230, 152, 225, 182, 188, 163, 219, 37, 175, 247, 73, 14, 247, 38, 72, 243, 1, 48, 131, - 59, 8, 13, 96, 143, 185, 127, 241, 161, 217, 24, 149, 193, 40, 16, 30, 202, 151, 28, 119, - 240, 153, 101, 156, 61, 193, 72, 245, 199, 181, 12, 231, 65, 166, 67, 142, 121, 207, 202, - 58, 197, 113, 188, 248, 42, 124, 105, 48, 161, 241, 55, 209, 36, 194, 27, 63, 233, 144, - 189, 85, 117, 234, 9, 139, 46, 31, 206, 114, 95, 131, 29, 240, 13, 81, 142, 140, 133, 33, - 30, 41, 141, 37, 80, 217, 95, 221, 76, 115, 86, 201, 165, 51, 252, 9, 28, 209, 1, 48, 150, - 74, 248, 212, 187, 222, 66, 210, 3, 200, 19, 217, 171, 184, 42, 148, 53, 150, 57, 50, 6, - 227, 227, 62, 49, 42, 148, 148, 157, 82, 191, 58, 24, 34, 56, 98, 120, 89, 105, 176, 85, - 15, 253, 241, 41, 153, 195, 136, 1, 48, 142, 126, 213, 101, 223, 79, 133, 230, 105, 38, - 161, 149, 2, 21, 136, 150, 42, 72, 218, 85, 146, 63, 223, 58, 108, 186, 183, 248, 62, 20, - 47, 34, 113, 160, 177, 204, 181, 16, 24, 212, 224, 35, 84, 51, 168, 56, 136, 11, 1, 48, - 135, 242, 62, 149, 230, 178, 32, 224, 119, 26, 234, 163, 237, 224, 114, 95, 112, 140, 170, - 150, 96, 125, 136, 221, 180, 78, 18, 11, 12, 184, 2, 198, 217, 119, 43, 69, 4, 172, 109, - 55, 183, 40, 131, 172, 161, 88, 183, 101, 1, 48, 173, 216, 22, 73, 42, 255, 211, 93, 249, - 87, 159, 115, 61, 91, 55, 130, 17, 216, 60, 34, 122, 55, 8, 244, 244, 153, 151, 57, 5, 144, - 178, 55, 249, 64, 211, 168, 34, 148, 56, 89, 92, 203, 70, 124, 219, 152, 253, 165, 0, 32, - 203, 116, 63, 7, 240, 222, 82, 86, 11, 149, 167, 72, 224, 55, 190, 66, 201, 65, 168, 184, - 96, 47, 194, 241, 168, 124, 7, 74, 214, 250, 37, 76, 32, 218, 69, 122, 103, 215, 145, 169, - 24, 212, 229, 168, 106, 10, 144, 31, 13, 25, 178, 242, 250, 106, 159, 40, 48, 163, 165, 61, - 130, 57, 146, 4, 73, 32, 254, 233, 125, 135, 212, 29, 111, 4, 177, 114, 15, 210, 170, 82, - 108, 110, 62, 166, 81, 209, 106, 176, 156, 14, 133, 242, 60, 127, 120, 242, 28, 97, 0, 1, - 32, 103, 93, 109, 89, 240, 91, 1, 84, 150, 50, 206, 157, 203, 49, 220, 120, 234, 175, 234, - 150, 126, 225, 94, 163, 164, 199, 138, 114, 62, 99, 106, 112, 1, 32, 171, 40, 220, 82, 241, - 203, 76, 146, 111, 139, 182, 179, 237, 182, 115, 75, 128, 201, 107, 43, 214, 0, 135, 217, - 160, 68, 150, 232, 144, 114, 237, 98, 32, 30, 134, 232, 59, 93, 163, 253, 244, 13, 202, 52, - 147, 168, 83, 121, 123, 95, 21, 210, 209, 225, 223, 143, 49, 10, 205, 238, 1, 22, 83, 81, - 70, 1, 32, 26, 76, 6, 234, 160, 50, 139, 102, 161, 232, 155, 106, 130, 171, 226, 210, 233, - 178, 85, 247, 71, 123, 55, 53, 46, 67, 148, 137, 156, 207, 208, 107, 1, 32, 102, 31, 4, 98, - 110, 156, 144, 61, 229, 140, 198, 84, 196, 238, 128, 35, 131, 182, 137, 125, 241, 95, 69, - 131, 170, 27, 2, 144, 75, 72, 242, 102, 3, 32, 121, 80, 45, 173, 56, 65, 218, 27, 40, 251, - 197, 32, 169, 104, 123, 110, 90, 78, 153, 166, 38, 9, 129, 228, 99, 8, 1, 116, 142, 233, - 162, 69, 32, 216, 169, 159, 116, 95, 12, 63, 176, 195, 6, 183, 123, 135, 75, 61, 112, 106, - 83, 235, 176, 41, 27, 248, 48, 71, 165, 170, 12, 92, 103, 103, 81, 32, 58, 74, 75, 145, - 192, 94, 153, 69, 80, 128, 241, 3, 16, 117, 192, 86, 161, 103, 44, 174, 211, 196, 182, 124, - 55, 11, 107, 142, 49, 88, 6, 41, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, - 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, - 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 0, 37, 139, 240, 0, 0, - 0, 0, 0, 0, 0, 1, - ]; - - let mut credential = CredentialSpendingData::try_from_bytes(&CREDENTIAL_BYTES) - .expect("Failed to deserialize test credential - this is a bug in the test harness"); - - // Update spend_date to today to pass validation - credential.spend_date = OffsetDateTime::now_utc().date(); - - credential -} diff --git a/nym-gateway-probe/src/common/probe_tests.rs b/nym-gateway-probe/src/common/probe_tests.rs index ef039e6ab4..a892901aa7 100644 --- a/nym-gateway-probe/src/common/probe_tests.rs +++ b/nym-gateway-probe/src/common/probe_tests.rs @@ -8,7 +8,7 @@ use crate::common::types::{ use crate::common::wireguard::{ TwoHopWgTunnelConfig, WgTunnelConfig, run_tunnel_tests, run_two_hop_tunnel_tests, }; -use crate::common::{bandwidth_helpers, helpers, icmp}; +use crate::common::{helpers, icmp}; use crate::config::NetstackArgs; use anyhow::bail; use base64::{Engine, engine::general_purpose}; @@ -19,6 +19,7 @@ use nym_authenticator_requests::{ AuthenticatorVersion, client_message::ClientMessage, response::AuthenticatorResponse, v2, v3, v4, v5, v6, }; +use nym_bandwidth_controller::BandwidthTicketProvider; use nym_config::defaults::mixnet_vpn::{NYM_TUN_DEVICE_ADDRESS_V4, NYM_TUN_DEVICE_ADDRESS_V6}; use nym_connection_monitor::self_ping_and_wait; use nym_credentials_interface::{CredentialSpendingData, TicketType}; @@ -147,22 +148,11 @@ pub async fn wg_probe( Ok(wg_outcome) } -pub async fn lp_registration_probe( +pub async fn lp_registration_probe( gateway_identity: NodeIdentity, gateway_lp_data: TestedNodeLpDetails, - bandwidth_controller: &nym_bandwidth_controller::BandwidthController< - nym_validator_client::nyxd::NyxdClient, - St, - >, - use_mock_ecash: bool, -) -> anyhow::Result -where - St: nym_sdk::mixnet::CredentialStorage + Clone + Send + Sync + 'static, - ::StorageError: Send + Sync, -{ - use nym_crypto::asymmetric::ed25519; - use nym_registration_client::LpRegistrationClient; - + bandwidth_controller: &dyn BandwidthTicketProvider, +) -> anyhow::Result { let lp_address = gateway_lp_data.address; let peer = helpers::to_lp_remote_peer(gateway_identity, gateway_lp_data); @@ -223,44 +213,22 @@ where // Register using the new packet-per-connection API (returns GatewayData directly) let ticket_type = TicketType::V1WireguardEntry; - let gateway_data = if use_mock_ecash { - info!("Using mock ecash credential for LP registration"); - let credential = bandwidth_helpers::create_dummy_credential( - &gateway_ed25519_pubkey.to_bytes(), + let gateway_data = match client + .register_dvpn( + &mut rng, + &wg_keypair, + &gateway_ed25519_pubkey, + bandwidth_controller, ticket_type, - ); - - match client - .register_with_credential(&mut rng, &wg_keypair, credential, ticket_type) - .await - { - Ok(data) => data, - Err(e) => { - let error_msg = format!("LP registration failed (mock ecash): {}", e); - error!("{}", error_msg); - lp_outcome.error = Some(error_msg); - return Ok(lp_outcome); - } - } - } else { - info!("Using real bandwidth controller for LP registration"); - match client - .register( - &mut rng, - &wg_keypair, - &gateway_ed25519_pubkey, - bandwidth_controller, - ticket_type, - ) - .await - { - Ok(data) => data, - Err(e) => { - let error_msg = format!("LP registration failed: {}", e); - error!("{}", error_msg); - lp_outcome.error = Some(error_msg); - return Ok(lp_outcome); - } + ) + .await + { + Ok(data) => data, + Err(e) => { + let error_msg = format!("LP registration failed: {}", e); + error!("{}", error_msg); + lp_outcome.error = Some(error_msg); + return Ok(lp_outcome); } }; @@ -291,21 +259,13 @@ where // but subsequent DNS/ping tests may timeout. This appears to be related to Apple Container // Runtime networking quirks combined with our NAT/iptables configuration. Tracked in // beads issue nym-vbdo. Workaround: restart the localnet containers between probe runs. -pub async fn wg_probe_lp( +pub async fn wg_probe_lp( entry_gateway: &TestedNodeDetails, exit_gateway: &TestedNodeDetails, - bandwidth_controller: &nym_bandwidth_controller::BandwidthController< - nym_validator_client::nyxd::NyxdClient, - St, - >, - use_mock_ecash: bool, + bandwidth_controller: &dyn BandwidthTicketProvider, awg_args: String, netstack_args: NetstackArgs, -) -> anyhow::Result -where - St: nym_sdk::mixnet::CredentialStorage + Clone + Send + Sync + 'static, - ::StorageError: Send + Sync, -{ +) -> anyhow::Result { // Validate that both gateways have required information let entry_lp_data = entry_gateway .lp_data @@ -366,47 +326,24 @@ where .map_err(|e| anyhow::anyhow!("Invalid exit gateway identity: {}", e))?; // Perform handshake and registration with exit gateway via forwarding - let exit_gateway_data = if use_mock_ecash { - info!("Using mock ecash credential for exit gateway registration"); - let credential = bandwidth_helpers::create_dummy_credential( - &exit_gateway_pubkey.to_bytes(), + let exit_gateway_data = match nested_session + .handshake_and_register_dvpn( + &mut entry_client, + &mut rng, + &exit_wg_keypair, + &exit_gateway_pubkey, + bandwidth_controller, TicketType::V1WireguardExit, - ); - match nested_session - .handshake_and_register_with_credential( - &mut entry_client, - &mut rng, - &exit_wg_keypair, - credential, - TicketType::V1WireguardExit, - ) - .await - { - Ok(data) => data, - Err(e) => { - error!("Failed to register with exit gateway (mock ecash): {}", e); - return Ok(wg_outcome); - } - } - } else { - match nested_session - .handshake_and_register( - &mut entry_client, - &mut rng, - &exit_wg_keypair, - &exit_gateway_pubkey, - bandwidth_controller, - TicketType::V1WireguardExit, - ) - .await - { - Ok(data) => data, - Err(e) => { - error!("Failed to register with exit gateway: {}", e); - return Ok(wg_outcome); - } + ) + .await + { + Ok(data) => data, + Err(e) => { + error!("Failed to register with exit gateway: {}", e); + return Ok(wg_outcome); } }; + info!("Exit gateway registration successful via forwarding"); // STEP 3: Register with entry gateway @@ -416,43 +353,20 @@ where .map_err(|e| anyhow::anyhow!("Invalid entry gateway identity: {}", e))?; // Use packet-per-connection register() which returns GatewayData directly - let entry_gateway_data = if use_mock_ecash { - info!("Using mock ecash credential for entry gateway registration"); - let credential = bandwidth_helpers::create_dummy_credential( - &entry_gateway_pubkey.to_bytes(), + let entry_gateway_data = match entry_client + .register_dvpn( + &mut rng, + &entry_wg_keypair, + &entry_gateway_pubkey, + bandwidth_controller, TicketType::V1WireguardEntry, - ); - match entry_client - .register_with_credential( - &mut rng, - &entry_wg_keypair, - credential, - TicketType::V1WireguardEntry, - ) - .await - { - Ok(data) => data, - Err(e) => { - error!("Failed to register with entry gateway (mock ecash): {}", e); - return Ok(wg_outcome); - } - } - } else { - match entry_client - .register( - &mut rng, - &entry_wg_keypair, - &entry_gateway_pubkey, - bandwidth_controller, - TicketType::V1WireguardEntry, - ) - .await - { - Ok(data) => data, - Err(e) => { - error!("Failed to register with entry gateway: {}", e); - return Ok(wg_outcome); - } + ) + .await + { + Ok(data) => data, + Err(e) => { + error!("Failed to register with entry gateway: {}", e); + return Ok(wg_outcome); } }; info!("Entry gateway registration successful"); diff --git a/nym-gateway-probe/src/lib.rs b/nym-gateway-probe/src/lib.rs index b74cf2df7d..ccd42a4121 100644 --- a/nym-gateway-probe/src/lib.rs +++ b/nym-gateway-probe/src/lib.rs @@ -24,7 +24,9 @@ use url::Url; mod common; pub mod config; -use crate::common::bandwidth_helpers::{acquire_bandwidth, import_bandwidth}; +use crate::common::bandwidth_helpers::{ + acquire_bandwidth, build_bandwidth_controller, import_bandwidth, +}; pub use crate::common::nodes::{ DirectoryNode, NymApiDirectory, TestedNode, TestedNodeDetails, TestedNodeLpDetails, query_gateway_by_ip, @@ -435,16 +437,14 @@ impl Probe { &NymNetworkDetails::new_from_env(), )?; let client = nym_validator_client::nyxd::NyxdClient::connect(config, nyxd_url.as_str())?; - let bw_controller = nym_bandwidth_controller::BandwidthController::new( - storage.credential_store().clone(), - client, - ); + + let bw_controller = + build_bandwidth_controller(client, storage.credential_store().clone(), use_mock_ecash); // Run LP registration probe - let lp_outcome = - lp_registration_probe(node_info.identity, lp_data, &bw_controller, use_mock_ecash) - .await - .unwrap_or_default(); + let lp_outcome = lp_registration_probe(node_info.identity, lp_data, &bw_controller) + .await + .unwrap_or_default(); // Return result with only LP outcome Ok(ProbeResult { @@ -644,9 +644,11 @@ impl Probe { )?; let client = nym_validator_client::nyxd::NyxdClient::connect(config, nyxd_url.as_str())?; - let bw_controller = nym_bandwidth_controller::BandwidthController::new( - storage.credential_store().clone(), + + let bw_controller = build_bandwidth_controller( client, + storage.credential_store().clone(), + use_mock_ecash, ); // Determine entry and exit gateways @@ -691,7 +693,6 @@ impl Probe { &entry_gateway, &exit_gateway, &bw_controller, - use_mock_ecash, self.amnezia_args.clone(), self.netstack_args.clone(), ) @@ -773,15 +774,15 @@ impl Probe { )?; let client = nym_validator_client::nyxd::NyxdClient::connect(config, nyxd_url.as_str())?; - let bw_controller = nym_bandwidth_controller::BandwidthController::new( - storage.credential_store().clone(), + let bw_controller = build_bandwidth_controller( client, + storage.credential_store().clone(), + use_mock_ecash, ); - let outcome = - lp_registration_probe(node_info.identity, lp_data, &bw_controller, use_mock_ecash) - .await - .unwrap_or_default(); + let outcome = lp_registration_probe(node_info.identity, lp_data, &bw_controller) + .await + .unwrap_or_default(); Some(outcome) } else { diff --git a/nym-node/src/node/mod.rs b/nym-node/src/node/mod.rs index 1c9f3bb3fb..01fb355825 100644 --- a/nym-node/src/node/mod.rs +++ b/nym-node/src/node/mod.rs @@ -704,7 +704,10 @@ impl NymNode { self.config.gateway_tasks.lp.data_bind_address, ); let mut lp_listener = gateway_tasks_builder - .build_lp_listener(active_clients_store.clone()) + .build_lp_listener( + upgrade_mode_common_state.clone(), + active_clients_store.clone(), + ) .await?; self.shutdown_tracker() .try_spawn_named(async move { lp_listener.run().await }, "LpListener"); diff --git a/nym-registration-client/src/lib.rs b/nym-registration-client/src/lib.rs index 42bd7b4be6..e89143fad6 100644 --- a/nym-registration-client/src/lib.rs +++ b/nym-registration-client/src/lib.rs @@ -218,7 +218,7 @@ impl RegistrationClient { // Perform handshake and registration with exit gateway (all via entry forwarding) let exit_gateway_data = nested_session - .handshake_and_register::( + .handshake_and_register_dvpn::( &mut entry_client, rng, &self.config.exit.keys, @@ -238,7 +238,7 @@ impl RegistrationClient { // STEP 3: Register with entry gateway (packet-per-connection) tracing::info!("Registering with entry gateway"); let entry_gateway_data = entry_client - .register( + .register_dvpn( rng, &self.config.entry.keys, &self.config.entry.node.identity, diff --git a/nym-registration-client/src/lp_client/client.rs b/nym-registration-client/src/lp_client/client.rs index 4d15e2a5bc..827c98e1b1 100644 --- a/nym-registration-client/src/lp_client/client.rs +++ b/nym-registration-client/src/lp_client/client.rs @@ -6,12 +6,14 @@ use super::config::LpConfig; use super::error::{LpClientError, Result}; use crate::lp_client::helpers::{ - convert_forward_data, convert_registration_request, try_convert_forward_response, - try_convert_registration_response, + LpDataDeliverExt, LpDataSendExt, convert_forward_data, try_convert_forward_response, +}; +use crate::lp_client::state_machine_helpers::{ + extract_forwarded_response, get_recv_key, get_send_key, prepare_send_packet, }; use bytes::BytesMut; use nym_bandwidth_controller::{BandwidthTicketProvider, DEFAULT_TICKETS_TO_SPEND}; -use nym_credentials_interface::{CredentialSpendingData, TicketType}; +use nym_credentials_interface::TicketType; use nym_crypto::asymmetric::{ed25519, x25519}; use nym_lp::LpPacket; use nym_lp::codec::{OuterAeadKey, parse_lp_packet, serialize_lp_packet}; @@ -19,13 +21,15 @@ 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::{LpRegistrationRequest, WireguardConfiguration}; +use nym_registration_common::dvpn::LpDvpnRegistrationResponseMessageContent; +use nym_registration_common::{ + LpRegistrationRequest, LpRegistrationResponse, WireguardConfiguration, +}; use nym_wireguard_types::PeerPublicKey; use rand::{CryptoRng, RngCore}; use std::net::SocketAddr; use std::sync::Arc; -use std::time::{SystemTime, UNIX_EPOCH}; -use tokio::io::{AsyncReadExt, AsyncWriteExt}; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; use tokio::net::TcpStream; /// LP (Lewes Protocol) registration client for direct gateway connections. @@ -95,6 +99,28 @@ where } } + fn state_machine(&self) -> Result<&LpStateMachine> { + self.state_machine.as_ref().ok_or_else(|| { + LpClientError::transport( + "State machine not available - has the handshake been completed?", + ) + }) + } + + fn state_machine_mut(&mut self) -> Result<&mut LpStateMachine> { + self.state_machine.as_mut().ok_or_else(|| { + LpClientError::transport( + "State machine not available - has the handshake been completed?", + ) + }) + } + + fn stream_mut(&mut self) -> Result<&mut S> { + self.stream + .as_mut() + .ok_or_else(|| LpClientError::transport("Cannot send: not connected")) + } + /// Creates a new LP registration client with default configuration. /// /// # Arguments @@ -194,27 +220,86 @@ where Ok(()) } + /// Attempt to send an Lp packet on the persistent stream + /// and attempt to immediately read a response. + /// + /// Both packets are going to be optionally encrypted/decrypted based on the availability of keys + /// within the internal `LpStateMachine` + /// + /// # Arguments + /// * `packet` - The LP packet to send + /// + /// # Errors + /// Returns an error if not connected or if send or receive fails. + async fn send_and_receive_packet(&mut self, packet: &LpPacket) -> Result { + self.try_send_packet(packet).await?; + self.try_receive_packet().await + } + + /// Attempt to send an Lp packet on the persistent stream + /// and attempt to immediately read a response + /// within the provided timeout. + /// + /// Both packets are going to be optionally encrypted/decrypted based on the availability of keys + /// within the internal `LpStateMachine` + /// + /// # Arguments + /// * `packet` - The LP packet to send + /// + /// # Errors + /// Returns an error if not connected, the timeout has been reached, or if send or receive fails. + async fn send_and_receive_packet_with_timeout( + &mut self, + packet: &LpPacket, + timeout: Duration, + ) -> Result { + tokio::time::timeout(timeout, self.send_and_receive_packet(packet)) + .await + .map_err(|_| LpClientError::ResponseReceiveTimeout { timeout })? + } + /// Sends an LP packet on the persistent stream. /// /// # Arguments /// * `packet` - The LP packet to send + /// + /// # Errors + /// Returns an error if not connected or if send fails. + async fn try_send_packet(&mut self, packet: &LpPacket) -> Result<()> { + let state_machine = self.state_machine()?; + let send_key = get_send_key(state_machine); + self.try_send_packet_with_key(packet, send_key.as_ref()) + .await + } + + /// Sends an LP packet (and optionally encrypted) on the persistent stream. + /// + /// # Arguments + /// * `packet` - The LP packet to send /// * `outer_key` - Optional outer AEAD key for encryption /// /// # Errors /// Returns an error if not connected or if send fails. - async fn send_packet( + async fn try_send_packet_with_key( &mut self, packet: &LpPacket, outer_key: Option<&OuterAeadKey>, ) -> Result<()> { - let stream = self - .stream - .as_mut() - .ok_or_else(|| LpClientError::Transport("Cannot send: not connected".to_string()))?; - + let stream = self.stream_mut()?; Self::send_packet_with_key(stream, packet, outer_key).await } + /// Receives an LP packet from the persistent stream. + /// + /// # Errors + /// Returns an error if not connected or if receive fails. + async fn try_receive_packet(&mut self) -> Result { + let state_machine = self.state_machine()?; + let recv_key = get_recv_key(state_machine); + + self.try_receive_packet_with_key(recv_key.as_ref()).await + } + /// Receives an LP packet from the persistent stream. /// /// # Arguments @@ -222,11 +307,11 @@ where /// /// # Errors /// Returns an error if not connected or if receive fails. - async fn receive_packet(&mut self, outer_key: Option<&OuterAeadKey>) -> Result { - let stream = self - .stream - .as_mut() - .ok_or_else(|| LpClientError::Transport("Cannot receive: not connected".to_string()))?; + async fn try_receive_packet_with_key( + &mut self, + outer_key: Option<&OuterAeadKey>, + ) -> Result { + let stream = self.stream_mut()?; Self::receive_packet_with_key(stream, outer_key).await } @@ -319,11 +404,7 @@ where .as_secs(); // Step 1: Generate ClientHelloData with fresh salt and both public keys - let client_hello_data = nym_lp::ClientHelloData::new_with_fresh_salt( - *self.lp_local_peer.x25519().public_key(), - *self.lp_local_peer.ed25519().public_key(), - timestamp, - ); + let client_hello_data = self.lp_local_peer.build_client_hello_data(timestamp); let salt = client_hello_data.salt; let receiver_index = client_hello_data.receiver_index; @@ -344,9 +425,10 @@ where ); // Send ClientHello (no outer key - before PSK) - self.send_packet(&client_hello_packet, None).await?; + self.try_send_packet_with_key(&client_hello_packet, None) + .await?; // Receive Ack (no outer key - before PSK) - let ack_response = self.receive_packet(None).await?; + let ack_response = self.try_receive_packet_with_key(None).await?; // Verify we received Ack match ack_response.message() { @@ -408,8 +490,9 @@ where send_key.is_some(), recv_key.is_some() ); - self.send_packet(&packet, send_key.as_ref()).await?; - let response = self.receive_packet(recv_key.as_ref()).await?; + self.try_send_packet_with_key(&packet, send_key.as_ref()) + .await?; + let response = self.try_receive_packet_with_key(recv_key.as_ref()).await?; tracing::trace!("Received handshake response"); // Process the received packet @@ -433,9 +516,10 @@ where .ok() .and_then(|s| s.outer_aead_key()); tracing::trace!("Sending final handshake packet"); - self.send_packet(&final_packet, send_key.as_ref()).await?; + self.try_send_packet_with_key(&final_packet, send_key.as_ref()) + .await?; let ack_response = - self.receive_packet(recv_key.as_ref()).await?; + self.try_receive_packet_with_key(recv_key.as_ref()).await?; // Validate Ack response match ack_response.message() { @@ -580,30 +664,10 @@ where serialize_lp_packet(packet, &mut packet_buf, outer_key) .map_err(|e| LpClientError::Transport(format!("Failed to serialize packet: {e}")))?; - // Send 4-byte length prefix (u32 big-endian) - let len = packet_buf.len() as u32; stream - .write_all(&len.to_be_bytes()) + .send_serialised_packet(&packet_buf) .await - .map_err(|e| LpClientError::Transport(format!("Failed to send packet length: {e}")))?; - - // Send the actual packet data - stream - .write_all(&packet_buf) - .await - .map_err(|e| LpClientError::Transport(format!("Failed to send packet data: {e}")))?; - - // Flush to ensure data is sent immediately - stream - .flush() - .await - .map_err(|e| LpClientError::Transport(format!("Failed to flush stream: {e}")))?; - - tracing::trace!( - "Sent LP packet ({} bytes + 4 byte header)", - packet_buf.len() - ); - Ok(()) + .map_err(|err| LpClientError::Transport(err.to_string())) } /// Receives an LP packet from a TCP stream with length-prefixed framing. @@ -623,39 +687,115 @@ where stream: &mut S, outer_key: Option<&OuterAeadKey>, ) -> Result { - // Read 4-byte length prefix (u32 big-endian) - let mut len_buf = [0u8; 4]; - stream - .read_exact(&mut len_buf) + let packet_buf = stream + .receive_raw_packet() .await - .map_err(|e| LpClientError::Transport(format!("Failed to read packet length: {e}")))?; - - let packet_len = u32::from_be_bytes(len_buf) as usize; - - // Sanity check to prevent huge allocations - const MAX_PACKET_SIZE: usize = 65536; // 64KB max - if packet_len > MAX_PACKET_SIZE { - return Err(LpClientError::Transport(format!( - "Packet size {packet_len} exceeds maximum {MAX_PACKET_SIZE}", - ))); - } - - // Read the actual packet data - let mut packet_buf = vec![0u8; packet_len]; - stream - .read_exact(&mut packet_buf) - .await - .map_err(|e| LpClientError::Transport(format!("Failed to read packet data: {e}")))?; + .map_err(|err| LpClientError::transport(err.to_string()))?; let packet = parse_lp_packet(&packet_buf, outer_key) .map_err(|e| LpClientError::Transport(format!("Failed to parse packet: {e}")))?; - tracing::trace!("Received LP packet ({} bytes + 4 byte header)", packet_len); Ok(packet) } - /// Sends registration request and receives response in a single operation. + /// This is an internal method only meant to be called by `Self::register_dvpn` if the gateway + /// responds with a credential request. This is expected in every initial interaction with a particular gateway. /// + /// This method will actually attempt to retrieve a valid credential from the `bandwidth_controller` + /// + /// # Arguments + /// * `gateway_identity` - Gateway's ed25519 identity for credential verification + /// * `bandwidth_controller` - Provider for bandwidth credentials + /// * `ticket_type` - Type of bandwidth ticket to use + /// + /// # Returns + /// * `Ok(WireguardConfiguration)` - Gateway configuration data on successful registration + /// + /// # Errors + /// Returns an error if: + /// - Credential acquisition fails + /// - Request serialization/encryption fails + /// - Network communication fails + /// - Gateway rejected the registration + /// - Response times out (see LpConfig::registration_timeout) + async fn finalise_dvpn_registration( + &mut self, + gateway_identity: ed25519::PublicKey, + bandwidth_controller: &dyn BandwidthTicketProvider, + ticket_type: TicketType, + ) -> Result { + tracing::debug!("Acquiring bandwidth credential for registration"); + + // 1. Get bandwidth credential from controller + let credential_spending = bandwidth_controller + .get_ecash_ticket(ticket_type, gateway_identity, DEFAULT_TICKETS_TO_SPEND) + .await + .map_err(|e| { + LpClientError::SendRegistrationRequest(format!( + "Failed to acquire bandwidth credential: {e}", + )) + })? + .data; + + // 2. Build registration request + + // for now we do NOT support upgrade mode (yeah... no.) + let credential = credential_spending + .try_into() + .map_err(|err| LpClientError::Other(format!("malformed stored credential: {err}")))?; + + let request = LpRegistrationRequest::new_finalise_dvpn(credential); + + tracing::trace!("Built dVPN registration finalisation request"); + + // 3. Serialize the request + let lp_data = request.to_lp_data()?; + + // 4. Encrypt and prepare packet via state machine + let state_machine = self.state_machine_mut()?; + let request_packet = prepare_send_packet(lp_data, state_machine)?; + + // 5. Send initial request and receive response on persistent connection with timeout + let response_packet = self + .send_and_receive_packet_with_timeout(&request_packet, self.config.registration_timeout) + .await?; + + // 6. Decrypt via state machine (re-borrow) + let state_machine = self.state_machine_mut()?; + let received_data = extract_forwarded_response(response_packet, state_machine)?; + + // 7. Extract decrypted data and deserialise the response + let response = LpRegistrationResponse::from_lp_data(received_data)?; + let Some(dvpn_response) = response.into_dvpn_response() else { + return Err(LpClientError::unexpected_response( + "did not get a dvpn registration response after sending initial request", + )); + }; + + // 8. check response to the initial request + match dvpn_response.content { + LpDvpnRegistrationResponseMessageContent::RegistrationFailure(res) => { + let reason = res.error; + // the registration has failed + tracing::warn!("Gateway rejected registration: {reason}"); + Err(LpClientError::RegistrationRejected { reason }) + } + LpDvpnRegistrationResponseMessageContent::CompletedRegistration(res) => { + // we have managed to complete the registration + tracing::info!( + "LP registration successful! Allocated bandwidth: {} bytes", + res.available_bandwidth + ); + Ok(res.config) + } + LpDvpnRegistrationResponseMessageContent::RequiresCredential(_) => { + Err(LpClientError::unexpected_response( + "received request for additional dvpn data after sending credential!", + )) + } + } + } + /// This is the primary registration method. It acquires a bandwidth credential, /// sends the registration request, and receives the response /// on the same underlying connection. @@ -670,7 +810,7 @@ where /// * `ticket_type` - Type of bandwidth ticket to use /// /// # Returns - /// * `Ok(GatewayData)` - Gateway configuration data on successful registration + /// * `Ok(WireguardConfiguration)` - Gateway configuration data on successful registration /// /// # Errors /// Returns an error if: @@ -680,7 +820,7 @@ where /// - Network communication fails /// - Gateway rejected the registration /// - Response times out (see LpConfig::registration_timeout) - pub async fn register( + pub async fn register_dvpn( &mut self, rng: &mut R, wg_keypair: &x25519::KeyPair, @@ -691,159 +831,64 @@ where where R: RngCore + CryptoRng, { - tracing::debug!("Acquiring bandwidth credential for registration"); - - // Get bandwidth credential from controller - let credential = bandwidth_controller - .get_ecash_ticket(ticket_type, *gateway_identity, DEFAULT_TICKETS_TO_SPEND) - .await - .map_err(|e| { - LpClientError::SendRegistrationRequest(format!( - "Failed to acquire bandwidth credential: {e}", - )) - })? - .data; - - self.register_with_credential(rng, wg_keypair, credential, ticket_type) - .await - } - - /// Sends registration request with a pre-generated credential. - /// - /// This is useful for testing with mock ecash credentials. - /// Uses the persistent TCP connection established during handshake. - /// - /// # Arguments - /// * `rng` - RNG instance for generating PSK - /// * `wg_keypair` - Client's WireGuard x25519 keypair - /// * `credential` - Pre-generated bandwidth credential - /// * `ticket_type` - Type of bandwidth ticket - /// - /// # Returns - /// * `Ok(GatewayData)` - Gateway configuration data on successful registration - /// - /// # Connection Lifecycle - /// The connection stays open after registration to support `send_forward_packet()`. - /// Callers should call `close()` when done with all operations. - /// - /// # Panics / Errors - /// Returns error if handshake not completed or if connection was closed. - pub async fn register_with_credential( - &mut self, - rng: &mut R, - wg_keypair: &x25519::KeyPair, - credential: CredentialSpendingData, - ticket_type: TicketType, - ) -> Result - where - R: RngCore + CryptoRng, - { - tracing::debug!("Sending registration request (persistent connection)"); - // 1. Build registration request - let wg_public_key = PeerPublicKey::new(wg_keypair.public_key().to_bytes().into()); - let request = LpRegistrationRequest::new_dvpn(rng, wg_public_key, credential, ticket_type); + let wg_public_key = PeerPublicKey::from(*wg_keypair.public_key()); + let request = LpRegistrationRequest::new_initial_dvpn(rng, wg_public_key, ticket_type); - tracing::trace!("Built registration request: {:?}", request); + tracing::trace!("Built dVPN registration request: {request:?}"); // 2. Serialize the request - let input = convert_registration_request(request)?; + let lp_data = request.to_lp_data()?; - // 3. Encrypt and prepare packet via state machine (scoped borrow) - let (request_packet, send_key, recv_key) = { - let state_machine = self.state_machine.as_mut().ok_or_else(|| { - LpClientError::transport("Cannot register: handshake not completed") - })?; + // 3. Encrypt and prepare packet via state machine + let state_machine = self.state_machine_mut()?; + let request_packet = prepare_send_packet(lp_data, state_machine)?; - let action = state_machine - .process_input(input) - .ok_or_else(|| LpClientError::transport("State machine returned no action"))? - .map_err(|e| { - LpClientError::SendRegistrationRequest(format!( - "Failed to encrypt registration request: {e}", - )) - })?; - - let request_packet = match action { - LpAction::SendPacket(packet) => packet, - other => { - return Err(LpClientError::Transport(format!( - "Unexpected action when sending registration data: {other:?}", - ))); - } - }; - - // Get outer keys from session - let send_key = state_machine - .session() - .ok() - .and_then(|s| s.outer_aead_key_for_sending()); - let recv_key = state_machine - .session() - .ok() - .and_then(|s| s.outer_aead_key()); - - (request_packet, send_key, recv_key) - }; // state_machine borrow ends here - - // 4. Send request and receive response on persistent connection with timeout - let response_packet = tokio::time::timeout(self.config.registration_timeout, async { - self.send_packet(&request_packet, send_key.as_ref()).await?; - self.receive_packet(recv_key.as_ref()).await - }) - .await - .map_err(|_| { - LpClientError::ReceiveRegistrationResponse(format!( - "Registration timeout after {:?}", - self.config.registration_timeout - )) - })??; - - tracing::trace!("Received registration response packet"); + // 4. Send initial request and receive response on persistent connection with timeout + let response_packet = self + .send_and_receive_packet_with_timeout(&request_packet, self.config.registration_timeout) + .await?; // 5. Decrypt via state machine (re-borrow) - let state_machine = self - .state_machine - .as_mut() - .ok_or_else(|| LpClientError::transport("State machine disappeared unexpectedly"))?; - let action = state_machine - .process_input(LpInput::ReceivePacket(response_packet)) - .ok_or_else(|| LpClientError::transport("State machine returned no action"))? - .map_err(|e| { - LpClientError::ReceiveRegistrationResponse(format!( - "Failed to decrypt registration response: {e}", - )) - })?; + let state_machine = self.state_machine_mut()?; + let received_data = extract_forwarded_response(response_packet, state_machine)?; // 6. Extract decrypted data and deserialise the response - let response = try_convert_registration_response(action)?; + let response = LpRegistrationResponse::from_lp_data(received_data)?; + let Some(dvpn_response) = response.into_dvpn_response() else { + return Err(LpClientError::unexpected_response( + "did not get a dvpn registration response after sending initial request", + )); + }; - tracing::debug!( - "Received registration response: success={}", - response.success, - ); + // 7. check response to the initial request + match dvpn_response.content { + LpDvpnRegistrationResponseMessageContent::RegistrationFailure(res) => { + let reason = res.error; + // the registration has failed + tracing::warn!("Gateway rejected registration: {reason}"); + Err(LpClientError::RegistrationRejected { reason }) + } + LpDvpnRegistrationResponseMessageContent::CompletedRegistration(res) => { + // we have already registered with this gateway before, the gateway has updated the psk and sent us the config + tracing::info!( + "LP registration successful! Allocated bandwidth: {} bytes", + res.available_bandwidth + ); + Ok(res.config) + } + LpDvpnRegistrationResponseMessageContent::RequiresCredential(_) => { + // we're registering for the first time with this gateway - we need to attach a credential - // 7. Validate and extract GatewayData - if !response.success { - let error_msg = response - .error - .unwrap_or_else(|| "Unknown error".to_string()); - tracing::warn!("Gateway rejected registration: {error_msg}"); - return Err(LpClientError::RegistrationRejected { reason: error_msg }); + // 8. retrieve credential from the controller + self.finalise_dvpn_registration( + *gateway_identity, + bandwidth_controller, + ticket_type, + ) + .await + } } - - let gateway_data = response.gateway_data.ok_or_else(|| { - LpClientError::ReceiveRegistrationResponse( - "Gateway response missing gateway_data despite success=true".to_string(), - ) - })?; - - tracing::info!( - "LP registration successful! Allocated bandwidth: {} bytes", - response.allocated_bandwidth - ); - - Ok(gateway_data) } /// Register with automatic retry on network failure. @@ -891,29 +936,16 @@ where { tracing::debug!("Starting resilient registration (max_retries={max_retries})",); - // Acquire credential ONCE before any attempts - let credential = bandwidth_controller - .get_ecash_ticket(ticket_type, *gateway_identity, DEFAULT_TICKETS_TO_SPEND) - .await - .map_err(|e| { - LpClientError::SendRegistrationRequest(format!( - "Failed to acquire bandwidth credential: {e}", - )) - })? - .data; - let mut last_error = None; for attempt in 0..=max_retries { + let attempt_display = attempt + 1; + if attempt > 0 { // Exponential backoff with jitter: 100ms, 200ms, 400ms, 800ms, 1600ms (capped) let base_delay_ms = 100u64 * (1 << attempt.min(4)); let jitter_ms = rand::random::() % (base_delay_ms / 4 + 1); let delay = std::time::Duration::from_millis(base_delay_ms + jitter_ms); - tracing::info!( - "Retrying registration (attempt {}) after {:?}", - attempt + 1, - delay - ); + tracing::info!("Retrying registration (attempt {attempt_display}) after {delay:?}"); tokio::time::sleep(delay).await; } @@ -925,24 +957,30 @@ where self.state_machine = None; if let Err(e) = self.perform_handshake().await { - tracing::warn!("Handshake failed on attempt {}: {e}", attempt + 1); + tracing::warn!("Handshake failed on attempt {attempt_display}: {e}"); last_error = Some(e); continue; } } match self - .register_with_credential(rng, wg_keypair, credential.clone(), ticket_type) + .register_dvpn( + rng, + wg_keypair, + gateway_identity, + bandwidth_controller, + ticket_type, + ) .await { Ok(data) => { if attempt > 0 { - tracing::info!("Registration succeeded on retry attempt {}", attempt + 1); + tracing::info!("Registration succeeded on retry attempt {attempt_display}"); } return Ok(data); } Err(e) => { - tracing::warn!("Registration attempt {} failed: {e}", attempt + 1); + tracing::warn!("Registration attempt {attempt_display} failed: {e}"); last_error = Some(e); } } @@ -962,9 +1000,7 @@ where /// Multiple forward packets can be sent on the same connection. /// /// # Arguments - /// * `target_identity` - Target gateway's Ed25519 identity (32 bytes) - /// * `target_address` - Target gateway's LP address (e.g., "1.1.1.1:41264") - /// * `inner_packet_bytes` - Complete inner LP packet bytes to forward to exit gateway + /// * `forward_data` - encapsulated target gateway's ed25519 identity, socket address and serialised inner LP packet /// /// # Returns /// * `Ok(Vec)` - Decrypted response bytes from the exit gateway @@ -989,28 +1025,21 @@ where /// inner_bytes.to_vec(), /// ).await?; /// ``` - pub async fn send_forward_packet( + pub async fn send_forward_packet_with_response( &mut self, - target_identity: [u8; 32], - target_address: String, - inner_packet_bytes: Vec, + forward_data: ForwardPacketData, ) -> Result> { + let target_address = forward_data.target_lp_address.clone(); + tracing::debug!( - "Sending ForwardPacket to {} ({} inner bytes, persistent connection)", - target_address, - inner_packet_bytes.len() + "Sending ForwardPacket to {target_address} ({} inner bytes, persistent connection)", + forward_data.inner_packet_bytes.len() ); - // 1. Construct ForwardPacketData - let forward_data = ForwardPacketData { - target_gateway_identity: target_identity, - target_lp_address: target_address.clone(), - inner_packet_bytes, - }; - // 2. Serialize the ForwardPacketData - let input = convert_forward_data(forward_data); + // 1. Serialize the ForwardPacketData + let input = convert_forward_data(forward_data)?; - // 3. Encrypt and prepare packet via state machine (scoped borrow) + // 2. Encrypt and prepare packet via state machine (scoped borrow) let (forward_packet, send_key, recv_key) = { let state_machine = self.state_machine.as_mut().ok_or_else(|| { LpClientError::transport("Cannot send forward packet: handshake not completed") @@ -1046,10 +1075,11 @@ where (forward_packet, send_key, recv_key) }; // state_machine borrow ends here - // 4. Send and receive on persistent connection with timeout + // 3. Send and receive on persistent connection with timeout let response_packet = tokio::time::timeout(self.config.forward_timeout, async { - self.send_packet(&forward_packet, send_key.as_ref()).await?; - self.receive_packet(recv_key.as_ref()).await + self.try_send_packet_with_key(&forward_packet, send_key.as_ref()) + .await?; + self.try_receive_packet_with_key(recv_key.as_ref()).await }) .await .map_err(|_| { @@ -1060,7 +1090,7 @@ where })??; tracing::trace!("Received response packet from entry gateway"); - // 5. Decrypt via state machine (re-borrow) + // 4. Decrypt via state machine (re-borrow) let state_machine = self .state_machine .as_mut() @@ -1072,12 +1102,11 @@ where LpClientError::Transport(format!("Failed to decrypt forward response: {e}")) })?; - // 7. Extract decrypted response data + // 5. Extract decrypted response data let response_data = try_convert_forward_response(action)?; tracing::debug!( - "Successfully received forward response from {} ({} bytes)", - target_address, + "Successfully received forward response from {target_address} ({} bytes)", response_data.len() ); diff --git a/nym-registration-client/src/lp_client/error.rs b/nym-registration-client/src/lp_client/error.rs index 2026e15991..32443f4e01 100644 --- a/nym-registration-client/src/lp_client/error.rs +++ b/nym-registration-client/src/lp_client/error.rs @@ -6,6 +6,7 @@ use nym_lp::LpError; use nym_registration_common::BincodeError; use std::io; +use std::time::Duration; use thiserror::Error; /// Errors that can occur during LP client operations. @@ -35,6 +36,10 @@ pub enum LpClientError { #[error("Gateway rejected registration: {reason}")] RegistrationRejected { reason: String }, + /// Failed to receive response within specified deadline + #[error("Failed to receive response within the set timeout: {timeout:?}")] + ResponseReceiveTimeout { timeout: Duration }, + /// LP transport error #[error("LP transport error: {0}")] Transport(String), @@ -55,6 +60,9 @@ pub enum LpClientError { #[error("Timeout waiting for {operation}")] Timeout { operation: String }, + #[error("received an unexpected response: {message}")] + UnexpectedResponse { message: String }, + /// Another uncategorized error #[error("{0}")] Other(String), @@ -64,6 +72,12 @@ impl LpClientError { pub fn transport(message: impl Into) -> LpClientError { LpClientError::Transport(message.into()) } + + pub fn unexpected_response(message: impl Into) -> LpClientError { + LpClientError::UnexpectedResponse { + message: message.into(), + } + } } pub type Result = std::result::Result; diff --git a/nym-registration-client/src/lp_client/helpers.rs b/nym-registration-client/src/lp_client/helpers.rs index 79b1b027b1..e972b21b45 100644 --- a/nym-registration-client/src/lp_client/helpers.rs +++ b/nym-registration-client/src/lp_client/helpers.rs @@ -10,59 +10,61 @@ use nym_registration_common::{ LpRegistrationRequest, LpRegistrationResponse, NymNodeLPInformation, }; -pub(crate) fn convert_registration_request( - request: LpRegistrationRequest, -) -> Result { - let request_bytes = request.serialise().map_err(|e| { - LpClientError::SendRegistrationRequest(format!("Failed to serialize request: {e}")) - })?; - - tracing::debug!( - "Sending registration request ({} bytes)", - request_bytes.len() - ); - - let data = LpData::new_registration(request_bytes); - Ok(LpInput::SendData(data)) +pub(crate) trait LpDataSendExt { + fn to_lp_data(&self) -> Result; } -pub(crate) fn try_convert_registration_response( - action: LpAction, -) -> Result { - let response_data = match action { - LpAction::DeliverData(data) => data, - other => { +pub(crate) trait LpDataDeliverExt: Sized { + fn from_lp_data(data: LpData) -> Result; +} + +impl LpDataSendExt for LpRegistrationRequest { + fn to_lp_data(&self) -> Result { + let request_bytes = self.serialise().map_err(|e| { + LpClientError::SendRegistrationRequest(format!("Failed to serialize request: {e}")) + })?; + + tracing::debug!( + "Sending registration request ({} bytes)", + request_bytes.len() + ); + + Ok(LpData::new_registration(request_bytes)) + } +} + +impl LpDataDeliverExt for LpRegistrationResponse { + fn from_lp_data(data: LpData) -> Result { + if data.kind != LpDataKind::Registration { return Err(LpClientError::Transport(format!( - "Unexpected action when receiving registration response: {other:?}" + "did not receive a valid registration response. got {:?} instead", + data.kind ))); } - }; - if response_data.kind != LpDataKind::Registration { - return Err(LpClientError::Transport(format!( - "did not receive a valid registration response. got {:?} instead", - response_data.kind - ))); - } - - let response = - LpRegistrationResponse::try_deserialise(&response_data.content).map_err(|e| { + let response = LpRegistrationResponse::try_deserialise(&data.content).map_err(|e| { LpClientError::Transport(format!("Failed to deserialize registration response: {e}",)) })?; - Ok(response) + Ok(response) + } } -pub(crate) fn convert_forward_data(request: ForwardPacketData) -> LpInput { - let request_bytes = request.to_bytes(); +impl LpDataSendExt for ForwardPacketData { + fn to_lp_data(&self) -> Result { + let request_bytes = self.to_bytes(); - tracing::trace!( - "Sending forward packet data request ({} bytes)", - request_bytes.len() - ); + tracing::trace!( + "Sending forward packet data request ({} bytes)", + request_bytes.len() + ); - let data = LpData::new_forward(request_bytes); - LpInput::SendData(data) + Ok(LpData::new_forward(request_bytes)) + } +} + +pub(crate) fn convert_forward_data(request: ForwardPacketData) -> Result { + Ok(LpInput::SendData(request.to_lp_data()?)) } pub(crate) fn try_convert_forward_response(action: LpAction) -> Result, LpClientError> { @@ -78,7 +80,7 @@ pub(crate) fn try_convert_forward_response(action: LpAction) -> Result, if response_data.kind != LpDataKind::Forward { return Err(LpClientError::Transport(format!( - "did not receive a valid foreward response. got {:?} instead", + "did not receive a valid forward response. got {:?} instead", response_data.kind ))); } diff --git a/nym-registration-client/src/lp_client/mod.rs b/nym-registration-client/src/lp_client/mod.rs index b0dba1db25..d533303d29 100644 --- a/nym-registration-client/src/lp_client/mod.rs +++ b/nym-registration-client/src/lp_client/mod.rs @@ -36,6 +36,7 @@ mod config; pub(crate) mod error; pub(crate) mod helpers; mod nested_session; +mod state_machine_helpers; pub use client::LpRegistrationClient; pub use config::LpConfig; diff --git a/nym-registration-client/src/lp_client/nested_session.rs b/nym-registration-client/src/lp_client/nested_session.rs index fd4e486378..ed10f09880 100644 --- a/nym-registration-client/src/lp_client/nested_session.rs +++ b/nym-registration-client/src/lp_client/nested_session.rs @@ -20,17 +20,24 @@ use super::client::LpRegistrationClient; use super::error::{LpClientError, Result}; -use crate::lp_client::helpers::{convert_registration_request, try_convert_registration_response}; -use bytes::BytesMut; -use nym_bandwidth_controller::BandwidthTicketProvider; +use crate::lp_client::helpers::{LpDataDeliverExt, LpDataSendExt}; +use crate::lp_client::state_machine_helpers::{ + extract_forwarded_response, get_recv_key, get_send_key, prepare_serialised_send_packet, + serialize_packet, +}; +use nym_bandwidth_controller::{BandwidthTicketProvider, DEFAULT_TICKETS_TO_SPEND}; use nym_credentials_interface::TicketType; use nym_crypto::asymmetric::{ed25519, x25519}; -use nym_lp::codec::{OuterAeadKey, parse_lp_packet, serialize_lp_packet}; +use nym_lp::codec::{OuterAeadKey, parse_lp_packet}; +use nym_lp::message::ForwardPacketData; use nym_lp::peer::{LpLocalPeer, LpRemotePeer}; -use nym_lp::state_machine::{LpAction, LpInput, LpStateMachine}; +use nym_lp::state_machine::{LpAction, LpData, LpInput, LpStateMachine}; use nym_lp::{LpMessage, LpPacket}; use nym_lp_transport::traits::LpTransport; -use nym_registration_common::{LpRegistrationRequest, WireguardConfiguration}; +use nym_registration_common::dvpn::LpDvpnRegistrationResponseMessageContent; +use nym_registration_common::{ + LpRegistrationRequest, LpRegistrationResponse, WireguardConfiguration, +}; use nym_wireguard_types::PeerPublicKey; use rand::{CryptoRng, RngCore}; use std::sync::Arc; @@ -93,6 +100,51 @@ impl NestedLpSession { } } + fn state_machine(&self) -> Result<&LpStateMachine> { + self.state_machine.as_ref().ok_or_else(|| { + LpClientError::transport( + "State machine not available - has the handshake been completed?", + ) + }) + } + + fn state_machine_mut(&mut self) -> Result<&mut LpStateMachine> { + self.state_machine.as_mut().ok_or_else(|| { + LpClientError::transport( + "State machine not available - has the handshake been completed?", + ) + }) + } + + /// Attempt to parse received bytes into an LpPacket + fn parse_received_lp_packet(&self, response_bytes: Vec) -> Result { + let state_machine = self.state_machine()?; + let outer_key = get_recv_key(state_machine); + Self::parse_packet(&response_bytes, outer_key.as_ref()) + } + + /// Attempt to wrap the provided `LpData` into a `ForwardPacketData` + /// using the inner state machine. + fn prepare_forward_packet(&mut self, data: LpData) -> Result { + let target_gateway_identity = self.gateway_lp_peer.ed25519(); + let target_lp_address = self.exit_address.clone(); + + let state_machine = self.state_machine_mut()?; + let inner_packet_bytes = prepare_serialised_send_packet(data, state_machine)?; + Ok(ForwardPacketData { + target_gateway_identity: target_gateway_identity.to_bytes(), + target_lp_address, + inner_packet_bytes, + }) + } + + /// Attempt to recover received `LpData` from the received `LpPacket` + /// using the inner state machine. + fn extract_forwarded_response(&mut self, response_packet: LpPacket) -> Result { + let state_machine = self.state_machine_mut()?; + extract_forwarded_response(response_packet, state_machine) + } + /// Performs the LP handshake with the exit gateway by forwarding packets /// through the entry gateway. /// @@ -129,11 +181,7 @@ impl NestedLpSession { .as_secs(); // Step 1: Generate ClientHello for exit gateway - let client_hello_data = nym_lp::ClientHelloData::new_with_fresh_salt( - *self.lp_local_peer.x25519().public_key(), - *self.lp_local_peer.ed25519().public_key(), - timestamp, - ); + let client_hello_data = self.lp_local_peer.build_client_hello_data(timestamp); let salt = client_hello_data.salt; let receiver_index = client_hello_data.receiver_index; @@ -153,13 +201,15 @@ impl NestedLpSession { ); // Serialize and forward ClientHello (no state machine yet, no outer key) - let client_hello_bytes = Self::serialize_packet(&client_hello_packet, None)?; + let client_hello_bytes = serialize_packet(&client_hello_packet, None)?; + let forward_packet_data = ForwardPacketData::new( + self.gateway_lp_peer.ed25519(), + self.exit_address.clone(), + client_hello_bytes, + ); + let response_bytes = outer_client - .send_forward_packet( - self.gateway_lp_peer.ed25519().to_bytes(), - self.exit_address.clone(), - client_hello_bytes, - ) + .send_forward_packet_with_response(forward_packet_data) .await?; // Parse and validate Ack response (cleartext, no outer key before PSK derivation) @@ -273,132 +323,109 @@ impl NestedLpSession { Ok(()) } - /// Performs handshake and registration with the exit gateway via forwarding, - /// using a pre-made credential. + /// This is an internal method only meant to be called by `Self::handshake_and_register_dvpn` if the gateway + /// responds with a credential request. This is expected in every initial interaction with a particular gateway. /// - /// This variant is useful for mock ecash testing where the credential is provided - /// directly instead of being acquired from a bandwidth controller. + /// This method will actually attempt to retrieve a valid credential from the `bandwidth_controller` /// /// # Arguments /// * `outer_client` - Connected LP client with established outer session to entry gateway - /// * `wg_keypair` - Client's WireGuard x25519 keypair - /// * `credential` - Pre-made bandwidth credential (e.g., mock ecash) + /// * `gateway_identity` - Gateway's ed25519 identity for credential verification + /// * `bandwidth_controller` - Provider for bandwidth credentials /// * `ticket_type` - Type of bandwidth ticket to use - /// * `client_ip` - Client IP address for registration metadata /// /// # Returns - /// * `Ok(GatewayData)` - Exit gateway configuration data on successful registration - pub async fn handshake_and_register_with_credential( + /// * `Ok(WireguardConfiguration)` - Gateway configuration data on successful registration + /// + /// # Errors + /// Returns an error if: + /// - Credential acquisition fails + /// - Request serialization/encryption fails + /// - Forwarding through entry gateway fails + /// - Network communication fails + /// - Gateway rejected the registration + /// - Response times out (see LpConfig::registration_timeout) + async fn finalise_dvpn_registration( &mut self, outer_client: &mut LpRegistrationClient, - rng: &mut R, - wg_keypair: &x25519::KeyPair, - credential: nym_credentials_interface::CredentialSpendingData, + gateway_identity: ed25519::PublicKey, + bandwidth_controller: &dyn BandwidthTicketProvider, ticket_type: TicketType, ) -> Result where S: LpTransport + Unpin, - R: RngCore + CryptoRng, { - // Step 1: Perform handshake with exit gateway via forwarding - self.perform_handshake(outer_client).await?; + tracing::debug!("Acquiring bandwidth credential for registration"); - // Step 2: Get the state machine (must exist after successful handshake) - let state_machine = self.state_machine.as_mut().ok_or_else(|| { - LpClientError::Transport("State machine missing after handshake".to_string()) - })?; - - tracing::debug!( - "Building registration request for exit gateway (with pre-made credential)" - ); - - // Step 3: Build registration request (credential already provided) - let wg_public_key = PeerPublicKey::new(wg_keypair.public_key().to_bytes().into()); - let request = LpRegistrationRequest::new_dvpn(rng, wg_public_key, credential, ticket_type); - - tracing::trace!("Built registration request: {:?}", request); - - // Step 4: Serialize the request - let input = convert_registration_request(request)?; - tracing::debug!("Sending registration request to exit gateway via forwarding",); - - // Step 5: Encrypt and prepare packet via state machine - let action = state_machine - .process_input(input) - .ok_or_else(|| { - LpClientError::Transport("State machine returned no action".to_string()) - })? + // Step 1: Get bandwidth credential from controller + let credential_spending = bandwidth_controller + .get_ecash_ticket(ticket_type, gateway_identity, DEFAULT_TICKETS_TO_SPEND) + .await .map_err(|e| { - LpClientError::Transport(format!("Failed to encrypt registration request: {}", e)) - })?; + LpClientError::SendRegistrationRequest(format!( + "Failed to acquire bandwidth credential: {e}", + )) + })? + .data; - // Step 6: Send the encrypted packet via forwarding - let outer_key = Self::get_send_key(state_machine); - let response_bytes = match action { - LpAction::SendPacket(packet) => { - let packet_bytes = Self::serialize_packet(&packet, outer_key.as_ref())?; - outer_client - .send_forward_packet( - self.gateway_lp_peer.ed25519().to_bytes(), - self.exit_address.clone(), - packet_bytes, - ) - .await? - } - other => { - return Err(LpClientError::Transport(format!( - "Unexpected action when sending registration data: {:?}", - other - ))); - } + // Step 2: Build registration request + + // for now we do NOT support upgrade mode (yeah... no.) + let credential = credential_spending + .try_into() + .map_err(|err| LpClientError::Other(format!("malformed stored credential: {err}")))?; + + let request = LpRegistrationRequest::new_finalise_dvpn(credential); + + tracing::trace!("Built dVPN registration finalisation request"); + + // Step 3: Serialize the request + let send_data = request.to_lp_data()?; + + // Step 4: Encrypt and prepare packet via state machine + let forward_packet = self.prepare_forward_packet(send_data)?; + + // Step 5: Send the encrypted packet via forwarding + let response_bytes = outer_client + .send_forward_packet_with_response(forward_packet) + .await?; + + // Step 6: Parse response bytes to LP packet + let response_packet = self.parse_received_lp_packet(response_bytes)?; + + // Step 7: Decrypt via state machine + let response_data = self.extract_forwarded_response(response_packet)?; + + // Step 8: Extract decrypted data and deserialise the response + let response = LpRegistrationResponse::from_lp_data(response_data)?; + let Some(dvpn_response) = response.into_dvpn_response() else { + return Err(LpClientError::unexpected_response( + "did not get a dvpn registration response after sending initial request", + )); }; - tracing::trace!("Received registration response from exit gateway"); - - // Step 7: Parse response bytes to LP packet - let outer_key = Self::get_recv_key(state_machine); - let response_packet = Self::parse_packet(&response_bytes, outer_key.as_ref())?; - - // Step 8: Decrypt via state machine - let action = state_machine - .process_input(LpInput::ReceivePacket(response_packet)) - .ok_or_else(|| { - LpClientError::Transport("State machine returned no action".to_string()) - })? - .map_err(|e| { - LpClientError::Transport(format!("Failed to decrypt registration response: {}", e)) - })?; - - // Step 9: Extract decrypted data and deserialise the response - let response = try_convert_registration_response(action)?; - - tracing::debug!( - "Received registration response from exit: success={}", - response.success, - ); - - // Step 10: Validate and extract GatewayData - if !response.success { - let error_msg = response - .error - .unwrap_or_else(|| "Unknown error".to_string()); - tracing::warn!("Exit gateway rejected registration: {error_msg}"); - return Err(LpClientError::RegistrationRejected { reason: error_msg }); + // Step 9: check response to the initial request + match dvpn_response.content { + LpDvpnRegistrationResponseMessageContent::RegistrationFailure(res) => { + let reason = res.error; + // the registration has failed + tracing::warn!("Gateway rejected registration: {reason}"); + Err(LpClientError::RegistrationRejected { reason }) + } + LpDvpnRegistrationResponseMessageContent::CompletedRegistration(res) => { + // we have managed to complete the registration + tracing::info!( + "LP registration successful! Allocated bandwidth: {} bytes", + res.available_bandwidth + ); + Ok(res.config) + } + LpDvpnRegistrationResponseMessageContent::RequiresCredential(_) => { + Err(LpClientError::unexpected_response( + "received request for additional dvpn data after sending credential!", + )) + } } - - // Extract gateway_data - let gateway_data = response.gateway_data.ok_or_else(|| { - LpClientError::Transport( - "Gateway response missing gateway_data despite success=true".to_string(), - ) - })?; - - tracing::info!( - "Exit gateway registration successful! Allocated bandwidth: {} bytes", - response.allocated_bandwidth - ); - - Ok(gateway_data) } /// Performs handshake and registration with the exit gateway via forwarding. @@ -428,7 +455,7 @@ impl NestedLpSession { /// - Forwarding through entry gateway fails /// - Response decryption/deserialization fails /// - Gateway rejects the registration - pub async fn handshake_and_register( + pub async fn handshake_and_register_dvpn( &mut self, outer_client: &mut LpRegistrationClient, rng: &mut R, @@ -444,113 +471,68 @@ impl NestedLpSession { // Step 1: Perform handshake with exit gateway via forwarding self.perform_handshake(outer_client).await?; - // Step 2: Get the state machine (must exist after successful handshake) - let state_machine = self.state_machine.as_mut().ok_or_else(|| { - LpClientError::Transport("State machine missing after handshake".to_string()) - })?; - tracing::debug!("Building registration request for exit gateway"); - // Step 3: Acquire bandwidth credential - let credential = bandwidth_controller - .get_ecash_ticket( - ticket_type, - *gateway_identity, - nym_bandwidth_controller::DEFAULT_TICKETS_TO_SPEND, - ) - .await - .map_err(|e| { - LpClientError::Transport(format!("Failed to acquire bandwidth credential: {}", e)) - })? - .data; + // Step 2: Build registration request + let wg_public_key = PeerPublicKey::from(*wg_keypair.public_key()); + let request = LpRegistrationRequest::new_initial_dvpn(rng, wg_public_key, ticket_type); - // Step 4: Build registration request - let wg_public_key = PeerPublicKey::new(wg_keypair.public_key().to_bytes().into()); - let request = LpRegistrationRequest::new_dvpn(rng, wg_public_key, credential, ticket_type); + // Step 3: Serialize the request + let send_data = request.to_lp_data()?; - tracing::trace!("Built registration request: {:?}", request); + // Step 4: Encrypt and prepare packet via state machine + let forward_packet = self.prepare_forward_packet(send_data)?; - // Step 5: Serialize the request - let input = convert_registration_request(request)?; - tracing::debug!("Sending registration request to exit gateway via forwarding"); - - // Step 6: Encrypt and prepare packet via state machine - let action = state_machine - .process_input(input) - .ok_or_else(|| { - LpClientError::Transport("State machine returned no action".to_string()) - })? - .map_err(|e| { - LpClientError::Transport(format!("Failed to encrypt registration request: {}", e)) - })?; - - // Step 7: Send the encrypted packet via forwarding - let outer_key = Self::get_send_key(state_machine); - let response_bytes = match action { - LpAction::SendPacket(packet) => { - let packet_bytes = Self::serialize_packet(&packet, outer_key.as_ref())?; - outer_client - .send_forward_packet( - self.gateway_lp_peer.ed25519().to_bytes(), - self.exit_address.clone(), - packet_bytes, - ) - .await? - } - other => { - return Err(LpClientError::Transport(format!( - "Unexpected action when sending registration data: {:?}", - other - ))); - } - }; + // Step 5: Send the encrypted packet via forwarding + let response_bytes = outer_client + .send_forward_packet_with_response(forward_packet) + .await?; tracing::trace!("Received registration response from exit gateway"); - // Step 8: Parse response bytes to LP packet - let outer_key = Self::get_recv_key(state_machine); - let response_packet = Self::parse_packet(&response_bytes, outer_key.as_ref())?; + // Step 6: Parse response bytes to LP packet + let response_packet = self.parse_received_lp_packet(response_bytes)?; - // Step 9: Decrypt via state machine - let action = state_machine - .process_input(LpInput::ReceivePacket(response_packet)) - .ok_or_else(|| { - LpClientError::Transport("State machine returned no action".to_string()) - })? - .map_err(|e| { - LpClientError::Transport(format!("Failed to decrypt registration response: {}", e)) - })?; + // Step 7: Decrypt via state machine + let response_data = self.extract_forwarded_response(response_packet)?; - // Step 10: Extract decrypted data and deserialise the response - let response = try_convert_registration_response(action)?; + // Step 8: Extract decrypted data and deserialise the response + let response = LpRegistrationResponse::from_lp_data(response_data)?; + let Some(dvpn_response) = response.into_dvpn_response() else { + return Err(LpClientError::unexpected_response( + "did not get a dvpn registration response after sending initial request", + )); + }; - tracing::debug!( - "Received registration response from exit: success={}", - response.success, - ); + // Step 9: check response to the initial request + match dvpn_response.content { + LpDvpnRegistrationResponseMessageContent::RegistrationFailure(res) => { + let reason = res.error; + // the registration has failed + tracing::warn!("Gateway rejected registration: {reason}"); + Err(LpClientError::RegistrationRejected { reason }) + } + LpDvpnRegistrationResponseMessageContent::CompletedRegistration(res) => { + // we have already registered with this gateway before, the gateway has updated the psk and sent us the config + tracing::info!( + "LP registration successful! Allocated bandwidth: {} bytes", + res.available_bandwidth + ); + Ok(res.config) + } + LpDvpnRegistrationResponseMessageContent::RequiresCredential(_) => { + // we're registering for the first time with this gateway - we need to attach a credential - // Step 11: Validate and extract GatewayData - if !response.success { - let error_msg = response - .error - .unwrap_or_else(|| "Unknown error".to_string()); - tracing::warn!("Exit gateway rejected registration: {}", error_msg); - return Err(LpClientError::RegistrationRejected { reason: error_msg }); + // Step 10: retrieve credential from the controller + self.finalise_dvpn_registration( + outer_client, + *gateway_identity, + bandwidth_controller, + ticket_type, + ) + .await + } } - - // Extract gateway_data - let gateway_data = response.gateway_data.ok_or_else(|| { - LpClientError::Transport( - "Gateway response missing gateway_data despite success=true".to_string(), - ) - })?; - - tracing::info!( - "Exit gateway registration successful! Allocated bandwidth: {} bytes", - response.allocated_bandwidth - ); - - Ok(gateway_data) } /// Performs handshake and registration with the exit gateway via forwarding, @@ -582,10 +564,10 @@ impl NestedLpSession { /// # Errors /// Returns an error if all retry attempts fail. #[allow(clippy::too_many_arguments)] - pub async fn handshake_and_register_with_retry( + pub async fn handshake_and_register_dvpn_with_retry( &mut self, - rng: &mut R, outer_client: &mut LpRegistrationClient, + rng: &mut R, wg_keypair: &x25519::KeyPair, gateway_identity: &ed25519::PublicKey, bandwidth_controller: &dyn BandwidthTicketProvider, @@ -601,19 +583,6 @@ impl NestedLpSession { max_retries ); - // Acquire credential ONCE before any attempts - let credential = bandwidth_controller - .get_ecash_ticket( - ticket_type, - *gateway_identity, - nym_bandwidth_controller::DEFAULT_TICKETS_TO_SPEND, - ) - .await - .map_err(|e| { - LpClientError::Transport(format!("Failed to acquire bandwidth credential: {}", e)) - })? - .data; - let mut last_error = None; for attempt in 0..=max_retries { if attempt > 0 { @@ -640,11 +609,12 @@ impl NestedLpSession { } match self - .handshake_and_register_with_credential( + .handshake_and_register_dvpn( outer_client, rng, wg_keypair, - credential.clone(), + gateway_identity, + bandwidth_controller, ticket_type, ) .await @@ -686,58 +656,20 @@ impl NestedLpSession { where S: LpTransport + Unpin, { - let send_key = Self::get_send_key(state_machine); - let packet_bytes = Self::serialize_packet(packet, send_key.as_ref())?; + let send_key = get_send_key(state_machine); + let packet_bytes = serialize_packet(packet, send_key.as_ref())?; + let forward_data = ForwardPacketData::new( + self.gateway_lp_peer.ed25519(), + self.exit_address.clone(), + packet_bytes, + ); let response_bytes = outer_client - .send_forward_packet( - self.gateway_lp_peer.ed25519().to_bytes(), - self.exit_address.clone(), - packet_bytes, - ) + .send_forward_packet_with_response(forward_data) .await?; - let recv_key = Self::get_recv_key(state_machine); + let recv_key = get_recv_key(state_machine); Self::parse_packet(&response_bytes, recv_key.as_ref()) } - /// Gets the outer AEAD key for sending (encryption) from the state machine. - /// - /// Returns `None` during early handshake before PSK derivation. - fn get_send_key(state_machine: &LpStateMachine) -> Option { - state_machine - .session() - .ok() - .and_then(|s| s.outer_aead_key_for_sending()) - } - - /// Gets the outer AEAD key for receiving (decryption) from the state machine. - /// - /// Returns `None` during early handshake before PSK derivation. - fn get_recv_key(state_machine: &LpStateMachine) -> Option { - state_machine - .session() - .ok() - .and_then(|s| s.outer_aead_key()) - } - - /// Serializes an LP packet to bytes. - /// - /// # Arguments - /// * `packet` - The LP packet to serialize - /// - /// # Returns - /// * `Ok(Vec)` - Serialized packet bytes - /// - /// # Errors - /// Returns an error if serialization fails - fn serialize_packet(packet: &LpPacket, outer_key: Option<&OuterAeadKey>) -> Result> { - let mut buf = BytesMut::new(); - // Use outer AEAD key when available (after PSK derivation) - serialize_lp_packet(packet, &mut buf, outer_key).map_err(|e| { - LpClientError::Transport(format!("Failed to serialize LP packet: {}", e)) - })?; - Ok(buf.to_vec()) - } - /// Parses an LP packet from bytes. /// /// # Arguments @@ -751,6 +683,6 @@ impl NestedLpSession { fn parse_packet(bytes: &[u8], outer_key: Option<&OuterAeadKey>) -> Result { // Use outer AEAD key when available (after PSK derivation) parse_lp_packet(bytes, outer_key) - .map_err(|e| LpClientError::Transport(format!("Failed to parse LP packet: {}", e))) + .map_err(|e| LpClientError::Transport(format!("Failed to parse LP packet: {e}"))) } } diff --git a/nym-registration-client/src/lp_client/state_machine_helpers.rs b/nym-registration-client/src/lp_client/state_machine_helpers.rs new file mode 100644 index 0000000000..47da7c1255 --- /dev/null +++ b/nym-registration-client/src/lp_client/state_machine_helpers.rs @@ -0,0 +1,106 @@ +// Copyright 2026 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::LpClientError; +use bytes::BytesMut; +use nym_lp::codec::{OuterAeadKey, serialize_lp_packet}; +use nym_lp::state_machine::{LpAction, LpData, LpInput}; +use nym_lp::{LpPacket, LpStateMachine}; + +/// Gets the outer AEAD key for sending (encryption) from the state machine. +/// +/// Returns `None` during early handshake before PSK derivation. +pub(crate) fn get_send_key(state_machine: &LpStateMachine) -> Option { + state_machine + .session() + .ok() + .and_then(|s| s.outer_aead_key_for_sending()) +} + +/// Gets the outer AEAD key for receiving (decryption) from the state machine. +/// +/// Returns `None` during early handshake before PSK derivation. +pub(crate) fn get_recv_key(state_machine: &LpStateMachine) -> Option { + state_machine + .session() + .ok() + .and_then(|s| s.outer_aead_key()) +} + +/// Serializes an LP packet to bytes. +/// +/// # Arguments +/// * `packet` - The LP packet to serialize +/// +/// # Returns +/// * `Ok(Vec)` - Serialized packet bytes +/// +/// # Errors +/// Returns an error if serialization fails +pub(crate) fn serialize_packet( + packet: &LpPacket, + outer_key: Option<&OuterAeadKey>, +) -> Result, LpClientError> { + let mut buf = BytesMut::new(); + // Use outer AEAD key when available (after PSK derivation) + serialize_lp_packet(packet, &mut buf, outer_key) + .map_err(|e| LpClientError::Transport(format!("Failed to serialize LP packet: {}", e)))?; + Ok(buf.to_vec()) +} + +/// Attempt to prepare the provided data for sending by wrapping it in appropriate `LpAction`, +/// and attempting to extract `LpPacket` from the provided srtate machine. +pub(crate) fn prepare_send_packet( + data: LpData, + state_machine: &mut LpStateMachine, +) -> Result { + let action = state_machine + .process_input(LpInput::SendData(data)) + .ok_or_else(|| LpClientError::transport("State machine returned no action"))? + .map_err(|e| { + LpClientError::SendRegistrationRequest(format!( + "Failed to encrypt registration request: {e}", + )) + })?; + + match action { + LpAction::SendPacket(packet) => Ok(packet), + other => Err(LpClientError::Transport(format!( + "Unexpected action when trying to send packet data: {other:?}", + ))), + } +} + +/// Attempt to prepare the provided data for sending by wrapping it in appropriate `LpAction`, +/// serialising and finally encrypting (if appropriate key is available) the resultant `LpPacket` +/// It uses the provided state machine. +pub(crate) fn prepare_serialised_send_packet( + data: LpData, + state_machine: &mut LpStateMachine, +) -> Result, LpClientError> { + let packet = prepare_send_packet(data, state_machine)?; + + let send_key = get_send_key(state_machine); + serialize_packet(&packet, send_key.as_ref()) +} + +/// Attempt to recover received `LpData` from the received `LpPacket` +/// using the provided state machine. +pub(crate) fn extract_forwarded_response( + response_packet: LpPacket, + state_machine: &mut LpStateMachine, +) -> Result { + let action = state_machine + .process_input(LpInput::ReceivePacket(response_packet)) + .ok_or_else(|| LpClientError::Transport("State machine returned no action".to_string()))? + .map_err(|e| { + LpClientError::Transport(format!("Failed to decrypt received response: {e}")) + })?; + + match action { + LpAction::DeliverData(data) => Ok(data), + other => Err(LpClientError::Transport(format!( + "Unexpected action when receiving response: {other:?}" + ))), + } +} From f4ba8ac2b3a0591467936c90600089d18e58ac1e Mon Sep 17 00:00:00 2001 From: Jack Wampler Date: Thu, 29 Jan 2026 07:09:02 -0700 Subject: [PATCH 33/51] add extra configured nym api url to env (#6382) --- common/http-api-client/src/dns/constants.rs | 4 ++++ common/network-defaults/src/mainnet.rs | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/common/http-api-client/src/dns/constants.rs b/common/http-api-client/src/dns/constants.rs index b25dc43a1c..b13d6f3fde 100644 --- a/common/http-api-client/src/dns/constants.rs +++ b/common/http-api-client/src/dns/constants.rs @@ -51,6 +51,9 @@ pub const VERCEL_COM_IPS: &[IpAddr] = &[ IpAddr::V4(Ipv4Addr::new(198, 169, 1, 193)), ]; +pub const NYM_API_CDN: &str = "cdn1.media-platform.net"; +pub const NYM_API_CDN_IPS: &[IpAddr] = &[IpAddr::V4(Ipv4Addr::new(172, 104, 178, 252))]; + pub const NYM_COM_DOMAIN: &str = "nym.com"; pub const NYM_COM_IPS: &[IpAddr] = &[IpAddr::V4(Ipv4Addr::new(76, 76, 21, 22))]; @@ -88,6 +91,7 @@ pub fn default_static_addrs() -> HashMap> { m.insert(YELP_FASTLY_DOMAIN.to_string(), YELP_FASTLY_IPS.to_vec()); m.insert(VERCEL_APP_DOMAIN.to_string(), VERCEL_APP_IPS.to_vec()); m.insert(VERCEL_COM_DOMAIN.to_string(), VERCEL_COM_IPS.to_vec()); + m.insert(NYM_API_CDN.to_string(), NYM_API_CDN_IPS.to_vec()); m.insert(NYM_COM_DOMAIN.to_string(), NYM_COM_IPS.to_vec()); m.insert(NYM_STATS_API_DOMAIN.to_string(), NYM_STATS_API_IPS.to_vec()); m.insert(NYM_RPC_DOMAIN.to_string(), NYM_RPC_IPS.to_vec()); diff --git a/common/network-defaults/src/mainnet.rs b/common/network-defaults/src/mainnet.rs index 4465875b36..e2c33def93 100644 --- a/common/network-defaults/src/mainnet.rs +++ b/common/network-defaults/src/mainnet.rs @@ -51,6 +51,10 @@ pub const NYM_APIS: &[ApiUrlConst] = &[ url: "https://nym-frontdoor.global.ssl.fastly.net/api/", front_hosts: Some(&["yelp.global.ssl.fastly.net"]), }, + ApiUrlConst { + url: "https://cdn1.media-platform.net/api/", + front_hosts: None, + }, ]; pub const NYM_VPN_API: &str = "https://nymvpn.com/api/"; From 9a931b92510cdb04d861c7048f46577fb35f8a79 Mon Sep 17 00:00:00 2001 From: dynco-nym <173912580+dynco-nym@users.noreply.github.com> Date: Thu, 29 Jan 2026 18:20:51 +0100 Subject: [PATCH 34/51] Add socks5 test to gateway-probe (#6393) * Socks5 in GW probe Bump NS agent version Fix bugs - force route construction - use same entry = exit Fix NS API version check workflow PR feedback More robust test attempts CLI arg validation Fix clippy PR feedback * Test provided endpoints in config at startup Require one valid endpoint * Bump agent to 1.1.0 --- .github/workflows/ci-check-ns-api-version.yml | 2 +- Cargo.lock | 3 +- ...bad1e16988a3f9989d135840500e1143ce5e5.json | 32 --- ...e14f63ccb56fa62976f7ce3d3b4f6eca8b711.json | 26 -- ...15212a6f526f78258e173c96ec177988696ef.json | 12 + ...7cec1442967e53764b83ba0a66cd9a72494f9.json | 12 + ...751a5ca340ac9c6dd86da1a5e9a3e65ea589f.json | 26 ++ ...fb84ab5e7d7435297573523e2532560c2e302.json | 26 -- ...2d1d937dd4cc4778dfb42709adf08c1497f2.json} | 6 +- ...11aca3d875f67c77e2e7c75653c3f337d594e.json | 26 -- ...e38ffdcaa672afbae44993365ca6910f68f6.json} | 6 +- ...6156311713d2e9f69d39c868cd16296b56326.json | 12 + ...e1025442a594dcb5c6401b3dfac7df9c25073.json | 20 -- ...83ead09f4d7319b7997fa2a9bb628f9404166.json | 26 -- ...749e457f8cd4ba3d7d906a7ab3bf5b4e7dc9c.json | 27 -- ...03b5ee5b27879b0026bb0480b3f2722318a75.json | 15 -- ...16422b1f723d0a316314b50c43c8b29f8891d.json | 14 - ...a93e944b0d44ed1f7c1036f306e34372da11c.json | 20 -- ...c57402119ec7c3aae731b0da831327301466f.json | 14 - ...a70635028f392fe794d6131827b083e1755e1.json | 14 - ...3f419a849d4ec45af40b052a4cbf09b44f3ec.json | 20 -- ...5b67fd9281ce1de0653efa53b9d9b93cf335d.json | 14 - ...7a22f0444fbc679db1c06b651fb8b5538b278.json | 18 -- ...ed56b6e270ce186f0e49528865d1924343b78.json | 19 -- ...19cc462d04222fb20ad76de2a40f3f4f8fe15.json | 22 -- ...6b2e2d6a9ad4b225c4c883aafc4e9f0428008.json | 22 -- ...e77b7bc0a1af315ffd42c3e68156d6e4ace70.json | 24 -- ...ab7bd82ecd68041aa932a56c8ce09623251e4.json | 28 -- ...9ce71582635df47f52dcf3fd1df4e7be6b96d.json | 20 -- ...890bbf6150ab394c72783114340d4def5f9ef.json | 19 -- ...3cf00236490b86779559d84740ec18bcfa3a9.json | 14 - ...13d0598fd856aa019a0cbbae12d7cafb4672f.json | 14 - nym-gateway-probe/Cargo.toml | 1 + nym-gateway-probe/src/common/mod.rs | 1 + nym-gateway-probe/src/common/nodes.rs | 33 ++- nym-gateway-probe/src/common/probe_tests.rs | 189 ++++++++++++- .../src/common/socks5_test/json_rpc_client.rs | 255 ++++++++++++++++++ .../src/common/socks5_test/mod.rs | 161 +++++++++++ nym-gateway-probe/src/common/types.rs | 35 +++ nym-gateway-probe/src/config/mod.rs | 2 + nym-gateway-probe/src/config/socks5.rs | 32 +++ nym-gateway-probe/src/lib.rs | 110 +++++++- nym-gateway-probe/src/run.rs | 26 +- .../nym-node-status-agent/Cargo.toml | 2 +- .../nym-node-status-agent/run.sh | 16 +- sdk/rust/nym-sdk/src/mixnet/client.rs | 7 + 46 files changed, 914 insertions(+), 529 deletions(-) delete mode 100644 nym-api/.sqlx/query-00d857b624e7edab1198114b17cbad1e16988a3f9989d135840500e1143ce5e5.json delete mode 100644 nym-api/.sqlx/query-1d4535b58abdefaaca96bc7312fe14f63ccb56fa62976f7ce3d3b4f6eca8b711.json create mode 100644 nym-api/.sqlx/query-2f3c12cb0c48084b569e12ecb0315212a6f526f78258e173c96ec177988696ef.json create mode 100644 nym-api/.sqlx/query-4a4f3b32b313f7fbc6eb579659e7cec1442967e53764b83ba0a66cd9a72494f9.json create mode 100644 nym-api/.sqlx/query-5d3b8ad051ab6f46c702308c2fc751a5ca340ac9c6dd86da1a5e9a3e65ea589f.json delete mode 100644 nym-api/.sqlx/query-676299beb2004ab89f7b38cf21ffb84ab5e7d7435297573523e2532560c2e302.json rename nym-api/.sqlx/{query-5eb13bfbee53b50641f69d4d6b62383c7f43864bffe98642bb8d1cf7c259d7be.json => query-697e6d738aecf115a3608139579b2d1d937dd4cc4778dfb42709adf08c1497f2.json} (72%) delete mode 100644 nym-api/.sqlx/query-73ca856950a0157acfd3e2ed07b11aca3d875f67c77e2e7c75653c3f337d594e.json rename nym-api/.sqlx/{query-1f72d6f538a3655a031a3a8706794559c4c0df6defdfd179c84d02d3b8a6c055.json => query-7e1829a17b81d7ee3dd8134b7a2be38ffdcaa672afbae44993365ca6910f68f6.json} (72%) create mode 100644 nym-api/.sqlx/query-805ad4f26e0234d7f482a263e186156311713d2e9f69d39c868cd16296b56326.json delete mode 100644 nym-api/.sqlx/query-924f8eb10c6cbb7f35da6c1bb77e1025442a594dcb5c6401b3dfac7df9c25073.json delete mode 100644 nym-api/.sqlx/query-c19e1b3768bf2929407599e6e8783ead09f4d7319b7997fa2a9bb628f9404166.json delete mode 100644 nym-data-observatory/.sqlx/query-08f4e54ac24fccd54f4208797b3749e457f8cd4ba3d7d906a7ab3bf5b4e7dc9c.json delete mode 100644 nym-data-observatory/.sqlx/query-0d3709efacf763b06bf14803bb803b5ee5b27879b0026bb0480b3f2722318a75.json delete mode 100644 nym-data-observatory/.sqlx/query-1c2fb0e9ffceca21ef8dbea19b116422b1f723d0a316314b50c43c8b29f8891d.json delete mode 100644 nym-data-observatory/.sqlx/query-2561fb016951ea4cd29e43fb9a4a93e944b0d44ed1f7c1036f306e34372da11c.json delete mode 100644 nym-data-observatory/.sqlx/query-2679cdf11fa66c7920678cde860c57402119ec7c3aae731b0da831327301466f.json delete mode 100644 nym-data-observatory/.sqlx/query-36ba5941aca6e7b604a10b8b0aba70635028f392fe794d6131827b083e1755e1.json delete mode 100644 nym-data-observatory/.sqlx/query-3bdf81a9db6075f6f77224c30553f419a849d4ec45af40b052a4cbf09b44f3ec.json delete mode 100644 nym-data-observatory/.sqlx/query-52c27143720ddfdfd0f5644b60f5b67fd9281ce1de0653efa53b9d9b93cf335d.json delete mode 100644 nym-data-observatory/.sqlx/query-62e14613f5ffe692346a79086857a22f0444fbc679db1c06b651fb8b5538b278.json delete mode 100644 nym-data-observatory/.sqlx/query-64a484fd46d8ec46797f944a4cced56b6e270ce186f0e49528865d1924343b78.json delete mode 100644 nym-data-observatory/.sqlx/query-7e82426f5dbcadf1631ba1a806e19cc462d04222fb20ad76de2a40f3f4f8fe15.json delete mode 100644 nym-data-observatory/.sqlx/query-9455331f9be5a3be28e2bd399a36b2e2d6a9ad4b225c4c883aafc4e9f0428008.json delete mode 100644 nym-data-observatory/.sqlx/query-bc7795e58ce71893c3f32a19db8e77b7bc0a1af315ffd42c3e68156d6e4ace70.json delete mode 100644 nym-data-observatory/.sqlx/query-be43d4873911deca784b7be0531ab7bd82ecd68041aa932a56c8ce09623251e4.json delete mode 100644 nym-data-observatory/.sqlx/query-c88d07fecc3f33deaa6e93db1469ce71582635df47f52dcf3fd1df4e7be6b96d.json delete mode 100644 nym-data-observatory/.sqlx/query-cc0ae74082d7d8a89f2d3364676890bbf6150ab394c72783114340d4def5f9ef.json delete mode 100644 nym-data-observatory/.sqlx/query-cdba9b267f143c8a8c6c3d6ed713cf00236490b86779559d84740ec18bcfa3a9.json delete mode 100644 nym-data-observatory/.sqlx/query-d89558c37c51e8e6b1e6a9d5a2b13d0598fd856aa019a0cbbae12d7cafb4672f.json create mode 100644 nym-gateway-probe/src/common/socks5_test/json_rpc_client.rs create mode 100644 nym-gateway-probe/src/common/socks5_test/mod.rs create mode 100644 nym-gateway-probe/src/config/socks5.rs diff --git a/.github/workflows/ci-check-ns-api-version.yml b/.github/workflows/ci-check-ns-api-version.yml index f449750764..e767c26112 100644 --- a/.github/workflows/ci-check-ns-api-version.yml +++ b/.github/workflows/ci-check-ns-api-version.yml @@ -3,7 +3,7 @@ name: ci-check-ns-api-version on: pull_request: paths: - - "nym-node-status-api/**" + - "nym-node-status-api/nym-node-status-api/**" env: WORKING_DIRECTORY: "nym-node-status-api/nym-node-status-api" diff --git a/Cargo.lock b/Cargo.lock index 25ac7b4776..91a632d24f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6489,6 +6489,7 @@ dependencies = [ "nym-validator-client", "pnet_packet", "rand 0.8.5", + "reqwest 0.12.28", "serde", "serde_json", "thiserror 2.0.17", @@ -7234,7 +7235,7 @@ dependencies = [ [[package]] name = "nym-node-status-agent" -version = "1.0.7" +version = "1.1.0" dependencies = [ "anyhow", "clap", diff --git a/nym-api/.sqlx/query-00d857b624e7edab1198114b17cbad1e16988a3f9989d135840500e1143ce5e5.json b/nym-api/.sqlx/query-00d857b624e7edab1198114b17cbad1e16988a3f9989d135840500e1143ce5e5.json deleted file mode 100644 index 03474ab35e..0000000000 --- a/nym-api/.sqlx/query-00d857b624e7edab1198114b17cbad1e16988a3f9989d135840500e1143ce5e5.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "db_name": "SQLite", - "query": "\n SELECT epoch_id as \"epoch_id: u32\", serialised_signatures, serialization_revision as \"serialization_revision: u8\"\n FROM expiration_date_signatures\n WHERE expiration_date = ?\n ", - "describe": { - "columns": [ - { - "name": "epoch_id: u32", - "ordinal": 0, - "type_info": "Integer" - }, - { - "name": "serialised_signatures", - "ordinal": 1, - "type_info": "Blob" - }, - { - "name": "serialization_revision: u8", - "ordinal": 2, - "type_info": "Integer" - } - ], - "parameters": { - "Right": 1 - }, - "nullable": [ - false, - false, - false - ] - }, - "hash": "00d857b624e7edab1198114b17cbad1e16988a3f9989d135840500e1143ce5e5" -} diff --git a/nym-api/.sqlx/query-1d4535b58abdefaaca96bc7312fe14f63ccb56fa62976f7ce3d3b4f6eca8b711.json b/nym-api/.sqlx/query-1d4535b58abdefaaca96bc7312fe14f63ccb56fa62976f7ce3d3b4f6eca8b711.json deleted file mode 100644 index 131935337d..0000000000 --- a/nym-api/.sqlx/query-1d4535b58abdefaaca96bc7312fe14f63ccb56fa62976f7ce3d3b4f6eca8b711.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "db_name": "SQLite", - "query": "\n SELECT timestamp, reliability as \"reliability: u8\"\n FROM gateway_status\n JOIN gateway_details\n ON gateway_status.gateway_details_id = gateway_details.id\n WHERE gateway_details.node_id=? AND gateway_status.timestamp > ?;\n ", - "describe": { - "columns": [ - { - "name": "timestamp", - "ordinal": 0, - "type_info": "Integer" - }, - { - "name": "reliability: u8", - "ordinal": 1, - "type_info": "Integer" - } - ], - "parameters": { - "Right": 2 - }, - "nullable": [ - true, - true - ] - }, - "hash": "1d4535b58abdefaaca96bc7312fe14f63ccb56fa62976f7ce3d3b4f6eca8b711" -} diff --git a/nym-api/.sqlx/query-2f3c12cb0c48084b569e12ecb0315212a6f526f78258e173c96ec177988696ef.json b/nym-api/.sqlx/query-2f3c12cb0c48084b569e12ecb0315212a6f526f78258e173c96ec177988696ef.json new file mode 100644 index 0000000000..405049043d --- /dev/null +++ b/nym-api/.sqlx/query-2f3c12cb0c48084b569e12ecb0315212a6f526f78258e173c96ec177988696ef.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n DELETE FROM emergency_credential\n WHERE id = ?\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 1 + }, + "nullable": [] + }, + "hash": "2f3c12cb0c48084b569e12ecb0315212a6f526f78258e173c96ec177988696ef" +} diff --git a/nym-api/.sqlx/query-4a4f3b32b313f7fbc6eb579659e7cec1442967e53764b83ba0a66cd9a72494f9.json b/nym-api/.sqlx/query-4a4f3b32b313f7fbc6eb579659e7cec1442967e53764b83ba0a66cd9a72494f9.json new file mode 100644 index 0000000000..e71f300174 --- /dev/null +++ b/nym-api/.sqlx/query-4a4f3b32b313f7fbc6eb579659e7cec1442967e53764b83ba0a66cd9a72494f9.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n DELETE FROM emergency_credential\n WHERE type = ?\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 1 + }, + "nullable": [] + }, + "hash": "4a4f3b32b313f7fbc6eb579659e7cec1442967e53764b83ba0a66cd9a72494f9" +} diff --git a/nym-api/.sqlx/query-5d3b8ad051ab6f46c702308c2fc751a5ca340ac9c6dd86da1a5e9a3e65ea589f.json b/nym-api/.sqlx/query-5d3b8ad051ab6f46c702308c2fc751a5ca340ac9c6dd86da1a5e9a3e65ea589f.json new file mode 100644 index 0000000000..eb6a62ba22 --- /dev/null +++ b/nym-api/.sqlx/query-5d3b8ad051ab6f46c702308c2fc751a5ca340ac9c6dd86da1a5e9a3e65ea589f.json @@ -0,0 +1,26 @@ +{ + "db_name": "SQLite", + "query": "\n SELECT serialised_signatures, serialization_revision as \"serialization_revision: u8\"\n FROM expiration_date_signatures\n WHERE expiration_date = ? AND epoch_id = ?\n ", + "describe": { + "columns": [ + { + "name": "serialised_signatures", + "ordinal": 0, + "type_info": "Blob" + }, + { + "name": "serialization_revision: u8", + "ordinal": 1, + "type_info": "Integer" + } + ], + "parameters": { + "Right": 2 + }, + "nullable": [ + false, + false + ] + }, + "hash": "5d3b8ad051ab6f46c702308c2fc751a5ca340ac9c6dd86da1a5e9a3e65ea589f" +} diff --git a/nym-api/.sqlx/query-676299beb2004ab89f7b38cf21ffb84ab5e7d7435297573523e2532560c2e302.json b/nym-api/.sqlx/query-676299beb2004ab89f7b38cf21ffb84ab5e7d7435297573523e2532560c2e302.json deleted file mode 100644 index 903ee12ebf..0000000000 --- a/nym-api/.sqlx/query-676299beb2004ab89f7b38cf21ffb84ab5e7d7435297573523e2532560c2e302.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "db_name": "SQLite", - "query": "\n SELECT\n d.node_id as \"node_id: NodeId\",\n CASE WHEN count(*) > 3 THEN AVG(reliability) ELSE 100 END as \"value: f32\"\n FROM\n gateway_details d\n JOIN\n gateway_status s on d.id = s.gateway_details_id\n WHERE\n timestamp >= ? AND\n timestamp <= ?\n GROUP BY 1\n ", - "describe": { - "columns": [ - { - "name": "node_id: NodeId", - "ordinal": 0, - "type_info": "Integer" - }, - { - "name": "value: f32", - "ordinal": 1, - "type_info": "Null" - } - ], - "parameters": { - "Right": 2 - }, - "nullable": [ - false, - null - ] - }, - "hash": "676299beb2004ab89f7b38cf21ffb84ab5e7d7435297573523e2532560c2e302" -} diff --git a/nym-api/.sqlx/query-5eb13bfbee53b50641f69d4d6b62383c7f43864bffe98642bb8d1cf7c259d7be.json b/nym-api/.sqlx/query-697e6d738aecf115a3608139579b2d1d937dd4cc4778dfb42709adf08c1497f2.json similarity index 72% rename from nym-api/.sqlx/query-5eb13bfbee53b50641f69d4d6b62383c7f43864bffe98642bb8d1cf7c259d7be.json rename to nym-api/.sqlx/query-697e6d738aecf115a3608139579b2d1d937dd4cc4778dfb42709adf08c1497f2.json index 8d03e6b94b..1fe08369b7 100644 --- a/nym-api/.sqlx/query-5eb13bfbee53b50641f69d4d6b62383c7f43864bffe98642bb8d1cf7c259d7be.json +++ b/nym-api/.sqlx/query-697e6d738aecf115a3608139579b2d1d937dd4cc4778dfb42709adf08c1497f2.json @@ -1,6 +1,6 @@ { "db_name": "SQLite", - "query": "\n SELECT epoch_id as \"epoch_id: u32\", serialised_signatures\n FROM global_expiration_date_signatures\n WHERE expiration_date = ?\n ", + "query": "\n SELECT epoch_id as \"epoch_id: u32\", serialised_signatures\n FROM global_expiration_date_signatures\n WHERE expiration_date = ? AND epoch_id = ?\n ", "describe": { "columns": [ { @@ -15,12 +15,12 @@ } ], "parameters": { - "Right": 1 + "Right": 2 }, "nullable": [ false, false ] }, - "hash": "5eb13bfbee53b50641f69d4d6b62383c7f43864bffe98642bb8d1cf7c259d7be" + "hash": "697e6d738aecf115a3608139579b2d1d937dd4cc4778dfb42709adf08c1497f2" } diff --git a/nym-api/.sqlx/query-73ca856950a0157acfd3e2ed07b11aca3d875f67c77e2e7c75653c3f337d594e.json b/nym-api/.sqlx/query-73ca856950a0157acfd3e2ed07b11aca3d875f67c77e2e7c75653c3f337d594e.json deleted file mode 100644 index 6c62e5e972..0000000000 --- a/nym-api/.sqlx/query-73ca856950a0157acfd3e2ed07b11aca3d875f67c77e2e7c75653c3f337d594e.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "db_name": "SQLite", - "query": "\n SELECT timestamp, reliability as \"reliability: u8\"\n FROM mixnode_status\n JOIN mixnode_details\n ON mixnode_status.mixnode_details_id = mixnode_details.id\n WHERE mixnode_details.mix_id=? AND mixnode_status.timestamp > ?;\n ", - "describe": { - "columns": [ - { - "name": "timestamp", - "ordinal": 0, - "type_info": "Integer" - }, - { - "name": "reliability: u8", - "ordinal": 1, - "type_info": "Integer" - } - ], - "parameters": { - "Right": 2 - }, - "nullable": [ - true, - true - ] - }, - "hash": "73ca856950a0157acfd3e2ed07b11aca3d875f67c77e2e7c75653c3f337d594e" -} diff --git a/nym-api/.sqlx/query-1f72d6f538a3655a031a3a8706794559c4c0df6defdfd179c84d02d3b8a6c055.json b/nym-api/.sqlx/query-7e1829a17b81d7ee3dd8134b7a2be38ffdcaa672afbae44993365ca6910f68f6.json similarity index 72% rename from nym-api/.sqlx/query-1f72d6f538a3655a031a3a8706794559c4c0df6defdfd179c84d02d3b8a6c055.json rename to nym-api/.sqlx/query-7e1829a17b81d7ee3dd8134b7a2be38ffdcaa672afbae44993365ca6910f68f6.json index 21a1f70f90..57a7eee0f8 100644 --- a/nym-api/.sqlx/query-1f72d6f538a3655a031a3a8706794559c4c0df6defdfd179c84d02d3b8a6c055.json +++ b/nym-api/.sqlx/query-7e1829a17b81d7ee3dd8134b7a2be38ffdcaa672afbae44993365ca6910f68f6.json @@ -1,6 +1,6 @@ { "db_name": "SQLite", - "query": "\n SELECT epoch_id as \"epoch_id: u32\", serialised_signatures\n FROM partial_expiration_date_signatures\n WHERE expiration_date = ?\n ", + "query": "\n SELECT epoch_id as \"epoch_id: u32\", serialised_signatures\n FROM partial_expiration_date_signatures\n WHERE expiration_date = ? AND epoch_id = ?\n ", "describe": { "columns": [ { @@ -15,12 +15,12 @@ } ], "parameters": { - "Right": 1 + "Right": 2 }, "nullable": [ false, false ] }, - "hash": "1f72d6f538a3655a031a3a8706794559c4c0df6defdfd179c84d02d3b8a6c055" + "hash": "7e1829a17b81d7ee3dd8134b7a2be38ffdcaa672afbae44993365ca6910f68f6" } diff --git a/nym-api/.sqlx/query-805ad4f26e0234d7f482a263e186156311713d2e9f69d39c868cd16296b56326.json b/nym-api/.sqlx/query-805ad4f26e0234d7f482a263e186156311713d2e9f69d39c868cd16296b56326.json new file mode 100644 index 0000000000..e1eb54bb9c --- /dev/null +++ b/nym-api/.sqlx/query-805ad4f26e0234d7f482a263e186156311713d2e9f69d39c868cd16296b56326.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n INSERT INTO emergency_credential\n (type, content, expiration)\n VALUES (?, ?, ?)\n ON CONFLICT(type, content) DO NOTHING;\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 3 + }, + "nullable": [] + }, + "hash": "805ad4f26e0234d7f482a263e186156311713d2e9f69d39c868cd16296b56326" +} diff --git a/nym-api/.sqlx/query-924f8eb10c6cbb7f35da6c1bb77e1025442a594dcb5c6401b3dfac7df9c25073.json b/nym-api/.sqlx/query-924f8eb10c6cbb7f35da6c1bb77e1025442a594dcb5c6401b3dfac7df9c25073.json deleted file mode 100644 index c9cf3e5e09..0000000000 --- a/nym-api/.sqlx/query-924f8eb10c6cbb7f35da6c1bb77e1025442a594dcb5c6401b3dfac7df9c25073.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "db_name": "SQLite", - "query": "SELECT identity FROM gateway_details WHERE node_id = ?", - "describe": { - "columns": [ - { - "name": "identity", - "ordinal": 0, - "type_info": "Text" - } - ], - "parameters": { - "Right": 1 - }, - "nullable": [ - false - ] - }, - "hash": "924f8eb10c6cbb7f35da6c1bb77e1025442a594dcb5c6401b3dfac7df9c25073" -} diff --git a/nym-api/.sqlx/query-c19e1b3768bf2929407599e6e8783ead09f4d7319b7997fa2a9bb628f9404166.json b/nym-api/.sqlx/query-c19e1b3768bf2929407599e6e8783ead09f4d7319b7997fa2a9bb628f9404166.json deleted file mode 100644 index c4b25591f3..0000000000 --- a/nym-api/.sqlx/query-c19e1b3768bf2929407599e6e8783ead09f4d7319b7997fa2a9bb628f9404166.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "db_name": "SQLite", - "query": "\n SELECT\n d.mix_id as \"mix_id: NodeId\",\n AVG(s.reliability) as \"value: f32\"\n FROM\n mixnode_details d\n JOIN\n mixnode_status s on d.id = s.mixnode_details_id\n WHERE\n timestamp >= ? AND\n timestamp <= ?\n GROUP BY 1\n ", - "describe": { - "columns": [ - { - "name": "mix_id: NodeId", - "ordinal": 0, - "type_info": "Integer" - }, - { - "name": "value: f32", - "ordinal": 1, - "type_info": "Null" - } - ], - "parameters": { - "Right": 2 - }, - "nullable": [ - false, - null - ] - }, - "hash": "c19e1b3768bf2929407599e6e8783ead09f4d7319b7997fa2a9bb628f9404166" -} diff --git a/nym-data-observatory/.sqlx/query-08f4e54ac24fccd54f4208797b3749e457f8cd4ba3d7d906a7ab3bf5b4e7dc9c.json b/nym-data-observatory/.sqlx/query-08f4e54ac24fccd54f4208797b3749e457f8cd4ba3d7d906a7ab3bf5b4e7dc9c.json deleted file mode 100644 index cc5863fd0e..0000000000 --- a/nym-data-observatory/.sqlx/query-08f4e54ac24fccd54f4208797b3749e457f8cd4ba3d7d906a7ab3bf5b4e7dc9c.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n INSERT INTO transaction\n (hash, height, index, success, messages, memo, signatures, signer_infos, fee, gas_wanted, gas_used, raw_log, logs, events)\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)\n ON CONFLICT (hash) DO UPDATE\n SET height = excluded.height,\n index = excluded.index,\n success = excluded.success,\n messages = excluded.messages,\n memo = excluded.memo,\n signatures = excluded.signatures,\n signer_infos = excluded.signer_infos,\n fee = excluded.fee,\n gas_wanted = excluded.gas_wanted,\n gas_used = excluded.gas_used,\n raw_log = excluded.raw_log,\n logs = excluded.logs,\n events = excluded.events\n ", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Text", - "Int8", - "Int4", - "Bool", - "Jsonb", - "Text", - "TextArray", - "Jsonb", - "Jsonb", - "Int8", - "Int8", - "Text", - "Jsonb", - "Jsonb" - ] - }, - "nullable": [] - }, - "hash": "08f4e54ac24fccd54f4208797b3749e457f8cd4ba3d7d906a7ab3bf5b4e7dc9c" -} diff --git a/nym-data-observatory/.sqlx/query-0d3709efacf763b06bf14803bb803b5ee5b27879b0026bb0480b3f2722318a75.json b/nym-data-observatory/.sqlx/query-0d3709efacf763b06bf14803bb803b5ee5b27879b0026bb0480b3f2722318a75.json deleted file mode 100644 index 36ba8bb96b..0000000000 --- a/nym-data-observatory/.sqlx/query-0d3709efacf763b06bf14803bb803b5ee5b27879b0026bb0480b3f2722318a75.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n INSERT INTO validator (consensus_address, consensus_pubkey)\n VALUES ($1, $2)\n ON CONFLICT DO NOTHING\n ", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Text", - "Text" - ] - }, - "nullable": [] - }, - "hash": "0d3709efacf763b06bf14803bb803b5ee5b27879b0026bb0480b3f2722318a75" -} diff --git a/nym-data-observatory/.sqlx/query-1c2fb0e9ffceca21ef8dbea19b116422b1f723d0a316314b50c43c8b29f8891d.json b/nym-data-observatory/.sqlx/query-1c2fb0e9ffceca21ef8dbea19b116422b1f723d0a316314b50c43c8b29f8891d.json deleted file mode 100644 index 2e10a89220..0000000000 --- a/nym-data-observatory/.sqlx/query-1c2fb0e9ffceca21ef8dbea19b116422b1f723d0a316314b50c43c8b29f8891d.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "DELETE FROM pre_commit WHERE height < $1", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Int8" - ] - }, - "nullable": [] - }, - "hash": "1c2fb0e9ffceca21ef8dbea19b116422b1f723d0a316314b50c43c8b29f8891d" -} diff --git a/nym-data-observatory/.sqlx/query-2561fb016951ea4cd29e43fb9a4a93e944b0d44ed1f7c1036f306e34372da11c.json b/nym-data-observatory/.sqlx/query-2561fb016951ea4cd29e43fb9a4a93e944b0d44ed1f7c1036f306e34372da11c.json deleted file mode 100644 index 0d1b70f8cc..0000000000 --- a/nym-data-observatory/.sqlx/query-2561fb016951ea4cd29e43fb9a4a93e944b0d44ed1f7c1036f306e34372da11c.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n SELECT height\n FROM block\n ORDER BY height ASC\n LIMIT 1\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "height", - "type_info": "Int8" - } - ], - "parameters": { - "Left": [] - }, - "nullable": [ - false - ] - }, - "hash": "2561fb016951ea4cd29e43fb9a4a93e944b0d44ed1f7c1036f306e34372da11c" -} diff --git a/nym-data-observatory/.sqlx/query-2679cdf11fa66c7920678cde860c57402119ec7c3aae731b0da831327301466f.json b/nym-data-observatory/.sqlx/query-2679cdf11fa66c7920678cde860c57402119ec7c3aae731b0da831327301466f.json deleted file mode 100644 index b97ea34d16..0000000000 --- a/nym-data-observatory/.sqlx/query-2679cdf11fa66c7920678cde860c57402119ec7c3aae731b0da831327301466f.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE metadata SET last_processed_height = GREATEST(last_processed_height, $1)", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Int8" - ] - }, - "nullable": [] - }, - "hash": "2679cdf11fa66c7920678cde860c57402119ec7c3aae731b0da831327301466f" -} diff --git a/nym-data-observatory/.sqlx/query-36ba5941aca6e7b604a10b8b0aba70635028f392fe794d6131827b083e1755e1.json b/nym-data-observatory/.sqlx/query-36ba5941aca6e7b604a10b8b0aba70635028f392fe794d6131827b083e1755e1.json deleted file mode 100644 index dede45475e..0000000000 --- a/nym-data-observatory/.sqlx/query-36ba5941aca6e7b604a10b8b0aba70635028f392fe794d6131827b083e1755e1.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE pruning SET last_pruned_height = $1", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Int8" - ] - }, - "nullable": [] - }, - "hash": "36ba5941aca6e7b604a10b8b0aba70635028f392fe794d6131827b083e1755e1" -} diff --git a/nym-data-observatory/.sqlx/query-3bdf81a9db6075f6f77224c30553f419a849d4ec45af40b052a4cbf09b44f3ec.json b/nym-data-observatory/.sqlx/query-3bdf81a9db6075f6f77224c30553f419a849d4ec45af40b052a4cbf09b44f3ec.json deleted file mode 100644 index e638bce922..0000000000 --- a/nym-data-observatory/.sqlx/query-3bdf81a9db6075f6f77224c30553f419a849d4ec45af40b052a4cbf09b44f3ec.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n SELECT last_pruned_height FROM pruning\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "last_pruned_height", - "type_info": "Int8" - } - ], - "parameters": { - "Left": [] - }, - "nullable": [ - false - ] - }, - "hash": "3bdf81a9db6075f6f77224c30553f419a849d4ec45af40b052a4cbf09b44f3ec" -} diff --git a/nym-data-observatory/.sqlx/query-52c27143720ddfdfd0f5644b60f5b67fd9281ce1de0653efa53b9d9b93cf335d.json b/nym-data-observatory/.sqlx/query-52c27143720ddfdfd0f5644b60f5b67fd9281ce1de0653efa53b9d9b93cf335d.json deleted file mode 100644 index 58af4f89c4..0000000000 --- a/nym-data-observatory/.sqlx/query-52c27143720ddfdfd0f5644b60f5b67fd9281ce1de0653efa53b9d9b93cf335d.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "DELETE FROM message WHERE height < $1", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Int8" - ] - }, - "nullable": [] - }, - "hash": "52c27143720ddfdfd0f5644b60f5b67fd9281ce1de0653efa53b9d9b93cf335d" -} diff --git a/nym-data-observatory/.sqlx/query-62e14613f5ffe692346a79086857a22f0444fbc679db1c06b651fb8b5538b278.json b/nym-data-observatory/.sqlx/query-62e14613f5ffe692346a79086857a22f0444fbc679db1c06b651fb8b5538b278.json deleted file mode 100644 index a7c102469d..0000000000 --- a/nym-data-observatory/.sqlx/query-62e14613f5ffe692346a79086857a22f0444fbc679db1c06b651fb8b5538b278.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n INSERT INTO pre_commit (validator_address, height, timestamp, voting_power, proposer_priority)\n VALUES ($1, $2, $3, $4, $5)\n ON CONFLICT (validator_address, timestamp) DO NOTHING\n ", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Text", - "Int8", - "Timestamp", - "Int8", - "Int8" - ] - }, - "nullable": [] - }, - "hash": "62e14613f5ffe692346a79086857a22f0444fbc679db1c06b651fb8b5538b278" -} diff --git a/nym-data-observatory/.sqlx/query-64a484fd46d8ec46797f944a4cced56b6e270ce186f0e49528865d1924343b78.json b/nym-data-observatory/.sqlx/query-64a484fd46d8ec46797f944a4cced56b6e270ce186f0e49528865d1924343b78.json deleted file mode 100644 index 08983f2af9..0000000000 --- a/nym-data-observatory/.sqlx/query-64a484fd46d8ec46797f944a4cced56b6e270ce186f0e49528865d1924343b78.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n INSERT INTO block (height, hash, num_txs, total_gas, proposer_address, timestamp)\n VALUES ($1, $2, $3, $4, $5, $6)\n ON CONFLICT DO NOTHING\n ", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Int8", - "Text", - "Int4", - "Int8", - "Text", - "Timestamp" - ] - }, - "nullable": [] - }, - "hash": "64a484fd46d8ec46797f944a4cced56b6e270ce186f0e49528865d1924343b78" -} diff --git a/nym-data-observatory/.sqlx/query-7e82426f5dbcadf1631ba1a806e19cc462d04222fb20ad76de2a40f3f4f8fe15.json b/nym-data-observatory/.sqlx/query-7e82426f5dbcadf1631ba1a806e19cc462d04222fb20ad76de2a40f3f4f8fe15.json deleted file mode 100644 index 3a60c573ed..0000000000 --- a/nym-data-observatory/.sqlx/query-7e82426f5dbcadf1631ba1a806e19cc462d04222fb20ad76de2a40f3f4f8fe15.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n SELECT height\n FROM block\n WHERE timestamp < $1\n ORDER BY timestamp DESC\n LIMIT 1\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "height", - "type_info": "Int8" - } - ], - "parameters": { - "Left": [ - "Timestamp" - ] - }, - "nullable": [ - false - ] - }, - "hash": "7e82426f5dbcadf1631ba1a806e19cc462d04222fb20ad76de2a40f3f4f8fe15" -} diff --git a/nym-data-observatory/.sqlx/query-9455331f9be5a3be28e2bd399a36b2e2d6a9ad4b225c4c883aafc4e9f0428008.json b/nym-data-observatory/.sqlx/query-9455331f9be5a3be28e2bd399a36b2e2d6a9ad4b225c4c883aafc4e9f0428008.json deleted file mode 100644 index 309aa81d9c..0000000000 --- a/nym-data-observatory/.sqlx/query-9455331f9be5a3be28e2bd399a36b2e2d6a9ad4b225c4c883aafc4e9f0428008.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n SELECT height\n FROM block\n WHERE timestamp > $1\n ORDER BY timestamp\n LIMIT 1\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "height", - "type_info": "Int8" - } - ], - "parameters": { - "Left": [ - "Timestamp" - ] - }, - "nullable": [ - false - ] - }, - "hash": "9455331f9be5a3be28e2bd399a36b2e2d6a9ad4b225c4c883aafc4e9f0428008" -} diff --git a/nym-data-observatory/.sqlx/query-bc7795e58ce71893c3f32a19db8e77b7bc0a1af315ffd42c3e68156d6e4ace70.json b/nym-data-observatory/.sqlx/query-bc7795e58ce71893c3f32a19db8e77b7bc0a1af315ffd42c3e68156d6e4ace70.json deleted file mode 100644 index caca484b94..0000000000 --- a/nym-data-observatory/.sqlx/query-bc7795e58ce71893c3f32a19db8e77b7bc0a1af315ffd42c3e68156d6e4ace70.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n SELECT COUNT(*) as count FROM pre_commit\n WHERE\n validator_address = $1\n AND height >= $2\n AND height <= $3\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "count", - "type_info": "Int8" - } - ], - "parameters": { - "Left": [ - "Text", - "Int8", - "Int8" - ] - }, - "nullable": [ - null - ] - }, - "hash": "bc7795e58ce71893c3f32a19db8e77b7bc0a1af315ffd42c3e68156d6e4ace70" -} diff --git a/nym-data-observatory/.sqlx/query-be43d4873911deca784b7be0531ab7bd82ecd68041aa932a56c8ce09623251e4.json b/nym-data-observatory/.sqlx/query-be43d4873911deca784b7be0531ab7bd82ecd68041aa932a56c8ce09623251e4.json deleted file mode 100644 index f1df706371..0000000000 --- a/nym-data-observatory/.sqlx/query-be43d4873911deca784b7be0531ab7bd82ecd68041aa932a56c8ce09623251e4.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n SELECT * FROM validator\n WHERE EXISTS (\n SELECT 1 FROM pre_commit\n WHERE height = $1\n AND pre_commit.validator_address = validator.consensus_address\n )\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "consensus_address", - "type_info": "Text" - }, - { - "ordinal": 1, - "name": "consensus_pubkey", - "type_info": "Text" - } - ], - "parameters": { - "Left": [ - "Int8" - ] - }, - "nullable": [ - false, - false - ] - }, - "hash": "be43d4873911deca784b7be0531ab7bd82ecd68041aa932a56c8ce09623251e4" -} diff --git a/nym-data-observatory/.sqlx/query-c88d07fecc3f33deaa6e93db1469ce71582635df47f52dcf3fd1df4e7be6b96d.json b/nym-data-observatory/.sqlx/query-c88d07fecc3f33deaa6e93db1469ce71582635df47f52dcf3fd1df4e7be6b96d.json deleted file mode 100644 index 9bf3eaf97b..0000000000 --- a/nym-data-observatory/.sqlx/query-c88d07fecc3f33deaa6e93db1469ce71582635df47f52dcf3fd1df4e7be6b96d.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n SELECT last_processed_height FROM metadata\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "last_processed_height", - "type_info": "Int8" - } - ], - "parameters": { - "Left": [] - }, - "nullable": [ - false - ] - }, - "hash": "c88d07fecc3f33deaa6e93db1469ce71582635df47f52dcf3fd1df4e7be6b96d" -} diff --git a/nym-data-observatory/.sqlx/query-cc0ae74082d7d8a89f2d3364676890bbf6150ab394c72783114340d4def5f9ef.json b/nym-data-observatory/.sqlx/query-cc0ae74082d7d8a89f2d3364676890bbf6150ab394c72783114340d4def5f9ef.json deleted file mode 100644 index 5c0da1448a..0000000000 --- a/nym-data-observatory/.sqlx/query-cc0ae74082d7d8a89f2d3364676890bbf6150ab394c72783114340d4def5f9ef.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n INSERT INTO message(transaction_hash, index, type, value, involved_accounts_addresses, height)\n VALUES ($1, $2, $3, $4, $5, $6)\n ON CONFLICT (transaction_hash, index) DO UPDATE\n SET height = excluded.height,\n type = excluded.type,\n value = excluded.value,\n involved_accounts_addresses = excluded.involved_accounts_addresses\n ", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Text", - "Int8", - "Text", - "Jsonb", - "TextArray", - "Int8" - ] - }, - "nullable": [] - }, - "hash": "cc0ae74082d7d8a89f2d3364676890bbf6150ab394c72783114340d4def5f9ef" -} diff --git a/nym-data-observatory/.sqlx/query-cdba9b267f143c8a8c6c3d6ed713cf00236490b86779559d84740ec18bcfa3a9.json b/nym-data-observatory/.sqlx/query-cdba9b267f143c8a8c6c3d6ed713cf00236490b86779559d84740ec18bcfa3a9.json deleted file mode 100644 index 2ae11a8fbb..0000000000 --- a/nym-data-observatory/.sqlx/query-cdba9b267f143c8a8c6c3d6ed713cf00236490b86779559d84740ec18bcfa3a9.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "DELETE FROM block WHERE height < $1", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Int8" - ] - }, - "nullable": [] - }, - "hash": "cdba9b267f143c8a8c6c3d6ed713cf00236490b86779559d84740ec18bcfa3a9" -} diff --git a/nym-data-observatory/.sqlx/query-d89558c37c51e8e6b1e6a9d5a2b13d0598fd856aa019a0cbbae12d7cafb4672f.json b/nym-data-observatory/.sqlx/query-d89558c37c51e8e6b1e6a9d5a2b13d0598fd856aa019a0cbbae12d7cafb4672f.json deleted file mode 100644 index 1970629169..0000000000 --- a/nym-data-observatory/.sqlx/query-d89558c37c51e8e6b1e6a9d5a2b13d0598fd856aa019a0cbbae12d7cafb4672f.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "DELETE FROM transaction WHERE height < $1", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Int8" - ] - }, - "nullable": [] - }, - "hash": "d89558c37c51e8e6b1e6a9d5a2b13d0598fd856aa019a0cbbae12d7cafb4672f" -} diff --git a/nym-gateway-probe/Cargo.toml b/nym-gateway-probe/Cargo.toml index 16fe2c08e9..536412f580 100644 --- a/nym-gateway-probe/Cargo.toml +++ b/nym-gateway-probe/Cargo.toml @@ -24,6 +24,7 @@ hex.workspace = true tracing.workspace = true pnet_packet.workspace = true rand.workspace = true +reqwest = { workspace = true, features = ["socks"] } serde.workspace = true serde_json.workspace = true thiserror.workspace = true diff --git a/nym-gateway-probe/src/common/mod.rs b/nym-gateway-probe/src/common/mod.rs index 3f2cda9f35..6276ec032c 100644 --- a/nym-gateway-probe/src/common/mod.rs +++ b/nym-gateway-probe/src/common/mod.rs @@ -12,5 +12,6 @@ pub(crate) mod icmp; pub(crate) mod netstack; pub(crate) mod nodes; pub(crate) mod probe_tests; +pub(crate) mod socks5_test; pub(crate) mod types; pub(crate) mod wireguard; diff --git a/nym-gateway-probe/src/common/nodes.rs b/nym-gateway-probe/src/common/nodes.rs index 6485920574..9716a923fd 100644 --- a/nym-gateway-probe/src/common/nodes.rs +++ b/nym-gateway-probe/src/common/nodes.rs @@ -4,8 +4,9 @@ use anyhow::{Context, anyhow, bail}; use nym_api_requests::models::{ AuthenticatorDetailsV2, DeclaredRolesV2, DescribedNodeTypeV2, HostInformationV2, - IpPacketRouterDetailsV2, LewesProtocolDetailsV1, NetworkRequesterDetailsV2, NymNodeDataV2, - OffsetDateTimeJsonSchemaWrapper, WebSocketsV2, WireguardDetailsV2, + IpPacketRouterDetailsV2, LewesProtocolDetailsV1, NetworkRequesterDetailsV1, + NetworkRequesterDetailsV2, NymNodeDataV2, OffsetDateTimeJsonSchemaWrapper, WebSocketsV2, + WireguardDetailsV2, }; use nym_authenticator_requests::AuthenticatorVersion; use nym_bin_common::build_information::BinaryBuildInformationOwned; @@ -161,10 +162,12 @@ impl DirectoryNode { }), _ => None, }; + let network_requester_details = self.described.description.network_requester.clone(); Ok(TestedNodeDetails { identity: self.identity(), exit_router_address, + network_requester_details, authenticator_address, authenticator_version, ip_address: Some(ip_address), @@ -409,7 +412,17 @@ impl NymApiDirectory { .iter() .filter(|(_, n)| n.described.description.ip_packet_router.is_some()) .choose(&mut rand::thread_rng()) - .ok_or(anyhow!("no gateways running IPR available")) + .context("no gateways running IPR available") + .map(|(id, _)| *id) + } + + pub fn random_exit_with_nr(&self) -> anyhow::Result { + info!("Selecting random gateway with NR enabled"); + self.nodes + .iter() + .filter(|(_, n)| n.described.description.ip_packet_router.is_some()) + .choose(&mut rand::thread_rng()) + .context("no gateways running NR available") .map(|(id, _)| *id) } @@ -419,7 +432,7 @@ impl NymApiDirectory { .iter() .filter(|(_, n)| n.described.description.declared_role.entry) .choose(&mut rand::thread_rng()) - .ok_or(anyhow!("no entry gateways available")) + .context("no entry gateways available") .map(|(id, _)| *id) } @@ -439,6 +452,16 @@ impl NymApiDirectory { }; Ok(maybe_entry) } + + pub fn exit_gateway_nr(&self, identity: &NodeIdentity) -> anyhow::Result { + let Some(maybe_entry) = self.nodes.get(identity).cloned() else { + bail!("{identity} not found in directory") + }; + if !maybe_entry.described.description.declared_role.exit_nr { + bail!("{identity} doesn't support exit NR mode") + }; + Ok(maybe_entry) + } } #[derive(Default, Debug)] @@ -468,6 +491,7 @@ impl TestedNode { pub struct TestedNodeDetails { pub identity: NodeIdentity, pub exit_router_address: Option, + pub network_requester_details: Option, pub authenticator_address: Option, pub authenticator_version: AuthenticatorVersion, pub ip_address: Option, @@ -489,6 +513,7 @@ impl TestedNodeDetails { identity, ip_address: Some(lp_data.address.ip()), lp_data: Some(lp_data), + network_requester_details: None, // These are None in localnet mode - only needed for mixnet/authenticator exit_router_address: None, authenticator_address: None, diff --git a/nym-gateway-probe/src/common/probe_tests.rs b/nym-gateway-probe/src/common/probe_tests.rs index a892901aa7..018c414a11 100644 --- a/nym-gateway-probe/src/common/probe_tests.rs +++ b/nym-gateway-probe/src/common/probe_tests.rs @@ -1,9 +1,12 @@ // Copyright 2026 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use crate::NymApiDirectory; +use crate::common::helpers::mixnet_debug_config; use crate::common::nodes::{TestedNodeDetails, TestedNodeLpDetails}; +use crate::common::socks5_test::HttpsConnectivityTest; use crate::common::types::{ - Entry, Exit, IpPingReplies, LpProbeResults, ProbeOutcome, WgProbeResults, + Entry, Exit, IpPingReplies, LpProbeResults, ProbeOutcome, Socks5ProbeResults, WgProbeResults, }; use crate::common::wireguard::{ TwoHopWgTunnelConfig, WgTunnelConfig, run_tunnel_tests, run_two_hop_tunnel_tests, @@ -27,7 +30,12 @@ use nym_crypto::asymmetric::{ed25519, x25519}; use nym_ip_packet_client::IprClientConnect; use nym_ip_packet_requests::{IpPair, codec::MultiIpPacketCodec}; use nym_registration_client::{LpRegistrationClient, NestedLpSession}; -use nym_sdk::mixnet::{MixnetClient, NodeIdentity, Recipient}; +use nym_sdk::mixnet::{MixnetClient, MixnetClientBuilder, NodeIdentity, Recipient, Socks5}; +use nym_sdk::{ + DebugConfig, NymApiTopologyProvider, NymApiTopologyProviderConfig, NymNetworkDetails, + TopologyProvider, +}; +use nym_topology::{HardcodedTopologyProvider, NymTopology}; use std::{ net::{IpAddr, Ipv4Addr, Ipv6Addr}, sync::Arc, @@ -36,6 +44,7 @@ use std::{ use tokio::net::TcpStream; use tokio_util::{codec::Decoder, sync::CancellationToken}; use tracing::*; +use url::Url; pub async fn wg_probe( mut auth_client: AuthenticatorClient, @@ -446,6 +455,7 @@ pub async fn do_ping( exit_result.map(|exit| ProbeOutcome { as_entry: entry, as_exit: exit, + socks5: None, wg: None, lp: None, }), @@ -609,3 +619,178 @@ pub async fn listen_for_icmp_ping_replies( can_route_ip_external_v6: registered_replies.external_ip_v6, })) } + +/// Creates a SOCKS5 proxy connection through the mixnet to the exit GW +/// and performs necessary tests. +#[allow(clippy::too_many_arguments)] +#[instrument(level = "info", name = "socks5_test", skip_all)] +pub(crate) async fn do_socks5_connectivity_test( + network_requester_address: &str, + network_details: NymNetworkDetails, + directory: &NymApiDirectory, + json_rpc_endpoints: Vec, + mixnet_client_timeout: u64, + test_run_count: u64, + failure_count_cutoff: usize, + topology: Option, +) -> anyhow::Result { + info!( + "Starting SOCKS5 test through Network Requester: {}", + network_requester_address + ); + if json_rpc_endpoints.is_empty() { + bail!("You need to define JSON RPC URLs in order to test SOCKS5") + } + + // parse the network requester address + let nr_recipient = match network_requester_address.parse::() { + Ok(addr) => addr, + Err(e) => { + error!("Invalid Network Requester address: {}", e); + + return Ok(Socks5ProbeResults::error_before_connecting(format!( + "Invalid NR address: {}", + e + ))); + } + }; + + info!( + "Network Requester gateway: {}", + nr_recipient.gateway().to_base58_string() + ); + info!( + "Network Requester identity: {}", + nr_recipient.identity().to_base58_string() + ); + + // create ephemeral SOCKS5 client + let socks5_config = Socks5::new(network_requester_address.to_string()); + + // since we define both entry & exit gateways to be the same tested GW, + // this shouldn't negatively affect mixnet layers but it will force route + // construction in case GW would get filtered out of topology + let min_gw_performance = Some(0); + + // debug config similar to main probe + let debug_config = mixnet_debug_config(min_gw_performance, true); + + // Verify the NR gateway exists in the directory with exit_nr role + let nr_gateway_id = nr_recipient.gateway(); + if let Err(e) = directory.exit_gateway_nr(&nr_gateway_id) { + return Ok(Socks5ProbeResults::error_before_connecting(e.to_string())); + } else { + info!("✔️ Network Requester gateway found in directory with exit_nr role"); + } + + // use intended exit as entry as well + let entry_gateway = nr_gateway_id; + + // use existing topology if available, otherwise fetch it + let topology_provider: Box = match topology { + Some(t) => { + info!("✔️ Reusing topology from main mixnet client"); + Box::new(HardcodedTopologyProvider::new(t)) + } + None => { + info!("Fetching topology for SOCKS5 client..."); + match hardcoded_topology(&network_details, &debug_config).await { + Ok(provider) => provider, + Err(e) => return Ok(Socks5ProbeResults::error_before_connecting(e)), + } + } + }; + + let socks5_client_builder = MixnetClientBuilder::new_ephemeral() + // Specify entry gateway explicitly + .request_gateway(entry_gateway.to_base58_string()) + .socks5_config(socks5_config) + .network_details(network_details) + .debug_config(debug_config) + .custom_topology_provider(topology_provider) + .build()?; + + // connect to mixnet via SOCKS5 + let socks5_client = match socks5_client_builder.connect_to_mixnet_via_socks5().await { + Ok(client) => { + info!("🌐 Successfully connected to mixnet via SOCKS5 proxy"); + info!( + "Connected via entry gateway: {}", + client.nym_address().gateway().to_base58_string() + ); + client + } + Err(e) => { + error!("Failed to establish SOCKS5 connection: {}", e); + return Ok(Socks5ProbeResults::error_before_connecting(format!( + "SOCKS5 connection failed: {}", + e + ))); + } + }; + + let test = match HttpsConnectivityTest::new( + test_run_count, + mixnet_client_timeout, + failure_count_cutoff, + json_rpc_endpoints, + socks5_client.socks5_url(), + ) { + Ok(test) => test, + Err(err) => { + socks5_client.disconnect().await; + + error!("{err}"); + return Ok(Socks5ProbeResults::error_after_connecting( + "Failed to create client", + )); + } + }; + + let result = test.run_tests().await; + socks5_client.disconnect().await; + + Ok(Socks5ProbeResults::with_http_result(result)) +} + +async fn hardcoded_topology( + network_details: &NymNetworkDetails, + debug_config: &DebugConfig, +) -> Result, String> { + // get Nym API URLs from network_details + let nym_api_urls: Vec = network_details + .nym_api_urls + .as_ref() + .map(|urls| urls.iter().filter_map(|u| u.url.parse().ok()).collect()) + .or_else(|| { + network_details + .endpoints + .first() + .and_then(|e| e.api_url()) + .map(|url| vec![url]) + }) + .unwrap_or_default(); + + if nym_api_urls.is_empty() { + return Err(String::from("No nym-api URLs available to fetch topology")); + } + + let topology_config = NymApiTopologyProviderConfig { + min_mixnode_performance: debug_config.topology.minimum_mixnode_performance, + min_gateway_performance: debug_config.topology.minimum_gateway_performance, + use_extended_topology: debug_config.topology.use_extended_topology, + ignore_egress_epoch_role: debug_config.topology.ignore_egress_epoch_role, + }; + + let api_client = nym_http_api_client::Client::new_url(nym_api_urls[0].clone(), None) + .map_err(|e| e.to_string())?; + let mut provider = NymApiTopologyProvider::new(topology_config, nym_api_urls, api_client); + + match provider.get_new_topology().await { + Some(topology) => { + info!("Fetched network topology"); + Ok(Box::new(HardcodedTopologyProvider::new(topology))) + } + None => Err(String::from("Failed to fetch network topology")), + } +} diff --git a/nym-gateway-probe/src/common/socks5_test/json_rpc_client.rs b/nym-gateway-probe/src/common/socks5_test/json_rpc_client.rs new file mode 100644 index 0000000000..e38269ed04 --- /dev/null +++ b/nym-gateway-probe/src/common/socks5_test/json_rpc_client.rs @@ -0,0 +1,255 @@ +use anyhow::bail; +use rand::Rng; +use reqwest::Proxy; +use serde::{Deserialize, Serialize}; +use std::time::Duration; +use tokio::time::Instant; +use tracing::{debug, error, info, warn}; + +use crate::common::socks5_test::SingleHttpsTestResult; + +pub struct JsonRpcClient { + client: reqwest::Client, + client_timeout: Duration, + test_endpoints: Vec, +} + +impl JsonRpcClient { + pub fn new( + client_timeout: u64, + proxy: Option, + test_endpoints: Vec, + ) -> anyhow::Result { + let mut builder = reqwest::Client::builder().timeout(Duration::from_secs(client_timeout)); + + if let Some(proxy) = proxy { + builder = builder.proxy(proxy); + } + let client = builder.build()?; + + Ok(Self { + client_timeout: Duration::from_secs(client_timeout), + test_endpoints, + client, + }) + } + + pub(super) async fn https_request_with_fallbacks(&self) -> SingleHttpsTestResult { + let mut error_msg = String::new(); + + // endpoints are used as fallbacks: in case of success, return early + for endpoint in self.test_endpoints.iter() { + info!( + "Testing against {} with timeout {}s", + endpoint, + self.client_timeout.as_secs() + ); + let start = Instant::now(); + + let res = self.eth_chainid(endpoint).await; + let elapsed = start.elapsed(); + match res { + Ok((status, JsonRpcResponse::Ok { .. })) => { + debug!( + "HTTPS test completed: status={}, latency={}ms", + status.as_u16(), + elapsed.as_millis() + ); + return SingleHttpsTestResult { + success: true, + status_code: Some(status.as_u16()), + latency_ms: Some(elapsed.as_millis() as u64), + endpoint_used: Some(endpoint.to_string()), + error: None, + }; + } + Ok((_, JsonRpcResponse::Err { error, .. })) => { + warn!("JSON-RPC error: {} (code: {})", error.message, error.code); + error_msg = format!("JSON-RPC error: {}", error.message); + } + Err(e) => { + error_msg = e.to_string(); + error!("{}", &error_msg); + } + } + } + + SingleHttpsTestResult { + success: false, + status_code: None, + latency_ms: None, + endpoint_used: self.test_endpoints.last().cloned(), + error: Some(error_msg), + } + } + + async fn eth_chainid( + &self, + endpoint: &str, + ) -> anyhow::Result<(reqwest::StatusCode, JsonRpcResponse)> { + match self + .client + .post(endpoint) + .header(reqwest::header::CONTENT_TYPE, "application/json") + .json(&JsonRpcRequestBody::eth_chainid()) + .send() + .await + .and_then(reqwest::Response::error_for_status) + { + Ok(response) => { + let status = response.status(); + if status.is_success() { + // Deserialize body into JsonRpcResponse + response + .json::() + .await + .map(|res| (status, res)) + .map_err(From::from) + } else { + bail!("HTTP error status: {}", status.as_u16()); + } + } + Err(e) => { + error!("HTTPS request failed: {}", e); + bail!("HTTPS request failed: {}", e); + } + } + } + + pub async fn ensure_endpoint_works(&self) -> anyhow::Result<()> { + let mut any_works = false; + for endpoint in self.test_endpoints.iter() { + if let Err(err) = self.eth_chainid(endpoint).await { + warn!("Endpoint {endpoint} error: {err}"); + } else { + any_works = true; + } + } + + if any_works { + Ok(()) + } else { + bail!("None of the endpoints are valid, see logs"); + } + } +} + +/// https://www.jsonrpc.org/specification +#[derive(Serialize)] +struct JsonRpcRequestBody { + // A String specifying the version of the JSON-RPC protocol. MUST be exactly "2.0". + jsonrpc: String, + method: String, + // A Structured value that holds the parameter values to be used during the invocation of the method. This member MAY be omitted. + params: serde_json::Value, + // The Server MUST reply with the same value in the Response object if included. + // This member is used to correlate the context between the two objects. + id: i64, +} + +impl JsonRpcRequestBody { + /// Very simple endpoint that requires no dynamic input + /// + /// https://ethereum.org/developers/docs/apis/json-rpc/#eth_chainId + pub fn eth_chainid() -> Self { + Self { + jsonrpc: String::from("2.0"), + method: String::from("eth_chainId"), + params: serde_json::json!([]), + id: rand::thread_rng().r#gen(), + } + } + + /// Create an eth_getBlockByNumber request with invalid params for testing error responses + #[cfg(test)] + pub fn eth_get_block_by_number_invalid() -> Self { + Self { + jsonrpc: String::from("2.0"), + method: String::from("eth_getBlockByNumber"), + // Invalid params: should be [blockNumber, boolean] but we pass garbage + params: serde_json::json!(["invalid_block_number"]), + id: rand::thread_rng().r#gen(), + } + } +} + +// dead code: we need these fields for deserialization, even if we don't read them explicitly +#[allow(dead_code)] +#[derive(Deserialize)] +#[serde(untagged)] +enum JsonRpcResponse { + Ok { + jsonrpc: String, + // have to use opaque Value because spec say this might be string, number or null (we don't care either way) + id: serde_json::Value, + // we don't really care for the exact result, just whether the response is OK or error + result: serde_json::Value, + }, + Err { + jsonrpc: String, + // have to use opaque Value because spec say this might be string, number or null (we don't care either way) + id: serde_json::Value, + error: JsonRpcError, + }, +} + +// dead code: we need these fields for deserialization, even if we don't read them explicitly +#[allow(dead_code)] +#[derive(Debug, Deserialize)] +struct JsonRpcError { + pub code: i64, + pub message: String, + #[serde(default)] + pub data: Option, +} + +#[cfg(test)] +mod test { + use super::*; + + const JSON_RPC_ENDPOINT: &str = "https://cloudflare-eth.com"; + + #[tokio::test] + async fn test_eth_chainid_returns_ok_response() { + let client = reqwest::Client::new(); + let response = client + .post(JSON_RPC_ENDPOINT) + .header(reqwest::header::CONTENT_TYPE, "application/json") + .json(&JsonRpcRequestBody::eth_chainid()) + .send() + .await + .expect("Failed to send request"); + + assert!(response.status().is_success()); + + let json_response: JsonRpcResponse = + response.json().await.expect("Failed to parse response"); + + assert!( + matches!(json_response, JsonRpcResponse::Ok { .. }), + "Expected Ok variant for eth_chainId" + ); + } + + #[tokio::test] + async fn test_eth_get_block_by_number_invalid_returns_error_response() { + let client = reqwest::Client::new(); + let response = client + .post(JSON_RPC_ENDPOINT) + .header(reqwest::header::CONTENT_TYPE, "application/json") + .json(&JsonRpcRequestBody::eth_get_block_by_number_invalid()) + .send() + .await + .expect("Failed to send request"); + + assert!(response.status().is_success()); // HTTP 200 but JSON-RPC error + + let json_response: JsonRpcResponse = + response.json().await.expect("Failed to parse response"); + + assert!( + matches!(json_response, JsonRpcResponse::Err { .. }), + "Expected Err variant for invalid params" + ); + } +} diff --git a/nym-gateway-probe/src/common/socks5_test/mod.rs b/nym-gateway-probe/src/common/socks5_test/mod.rs new file mode 100644 index 0000000000..5ea4e361d1 --- /dev/null +++ b/nym-gateway-probe/src/common/socks5_test/mod.rs @@ -0,0 +1,161 @@ +use serde::{Deserialize, Serialize}; +use tracing::{info, warn}; + +pub(crate) use json_rpc_client::JsonRpcClient; + +mod json_rpc_client; + +pub struct HttpsConnectivityTest { + test_count: u64, + failure_count_cutoff: usize, + client: JsonRpcClient, +} + +impl HttpsConnectivityTest { + pub fn new( + test_count: u64, + mixnet_client_timeout: u64, + failure_count_cutoff: usize, + json_rpc_test_endpoints: Vec, + socks5_proxy_url: String, + ) -> anyhow::Result { + let proxy = reqwest::Proxy::all(socks5_proxy_url) + .map_err(|e| anyhow::anyhow!("Failed to create proxy: {}", e))?; + let client = + JsonRpcClient::new(mixnet_client_timeout, Some(proxy), json_rpc_test_endpoints)?; + let res = Self { + test_count: std::cmp::max(test_count, 1), + failure_count_cutoff, + client, + }; + + Ok(res) + } + + pub async fn run_tests(self) -> HttpsConnectivityResult { + let mut results = Vec::new(); + + for i in 1..=self.test_count { + info!("Running test {}/{}", i, self.test_count); + let interim_res = self.client.https_request_with_fallbacks().await; + + if interim_res.success { + info!( + "{}/{} latency: {}ms", + i, + self.test_count, + interim_res.latency_ms.unwrap_or(0) + ); + } + + results.push(interim_res); + + // early exit + let unsuccessful = results.iter().filter(|r| !r.success).count(); + if unsuccessful > self.failure_count_cutoff { + warn!("Too many failed runs: returning early..."); + break; + } + } + + let final_result = HttpsConnectivityResult::from_results(results); + info!("AVG latency (in ms): {:?}", final_result.https_latency_ms); + final_result + } +} + +/// single HTTPS test attempt +struct SingleHttpsTestResult { + success: bool, + status_code: Option, + latency_ms: Option, + endpoint_used: Option, + error: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize, Default)] +pub struct HttpsConnectivityResult { + /// successfully completed HTTPS request + https_success: bool, + + /// HTTPS status code received + https_status_code: Option, + + /// average HTTPS request latency in milliseconds + https_latency_ms: Option, + + /// among multiple endpoints available, list the one actually used + endpoint_used: Option, + + /// error message(s) (if any) + error: Option>, +} + +impl HttpsConnectivityResult { + pub fn with_errors(error: Vec) -> Self { + Self { + https_success: false, + https_status_code: None, + https_latency_ms: None, + endpoint_used: None, + error: Some(error), + } + } + + fn from_results(results: Vec) -> Self { + let (successes, errors): (Vec, Vec) = + results.into_iter().partition(|r| r.success); + let errors = errors + .into_iter() + .map(|r| r.error) + .collect::>>() + // partition above guarantees this vec is non-empty + .unwrap_or_default(); + + // use the last successful result for status_code and endpoint + // this works as an empty check as well: if there is no last success, array must be empty hence only errors are present + let Some(last_success) = successes.last() else { + return Self::with_errors(errors); + }; + + // average latency from successful runs + let mut successes_count = 0; + let total_latency: u64 = successes + .iter() + .filter_map(|r| { + successes_count += 1; + r.latency_ms + }) + .sum(); + let avg_latency = total_latency / successes_count as u64; + + Self { + https_success: true, + https_status_code: last_success.status_code, + https_latency_ms: Some(avg_latency), + endpoint_used: last_success.endpoint_used.clone(), + // even in case of success, some errors were possible + error: if errors.is_empty() { + None + } else { + Some(errors) + }, + } + } + + pub fn https_success(&self) -> bool { + self.https_success + } + + pub fn https_status_code(&self) -> Option<&u16> { + self.https_status_code.as_ref() + } + + pub fn https_latency_ms(&self) -> Option<&u64> { + self.https_latency_ms.as_ref() + } + + pub fn endpoint_used(&self) -> Option<&String> { + self.endpoint_used.as_ref() + } +} diff --git a/nym-gateway-probe/src/common/types.rs b/nym-gateway-probe/src/common/types.rs index ec887d61fb..f2027ec78f 100644 --- a/nym-gateway-probe/src/common/types.rs +++ b/nym-gateway-probe/src/common/types.rs @@ -1,6 +1,8 @@ use nym_connection_monitor::ConnectionStatusEvent; use serde::{Deserialize, Serialize}; +pub use super::socks5_test::HttpsConnectivityResult; + #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ProbeResult { pub node: String, @@ -12,6 +14,7 @@ pub struct ProbeResult { pub struct ProbeOutcome { pub as_entry: Entry, pub as_exit: Option, + pub socks5: Option, pub wg: Option, pub lp: Option, } @@ -132,6 +135,38 @@ impl Exit { } } +#[derive(Debug, Clone, Serialize, Deserialize, Default)] +pub struct Socks5ProbeResults { + /// whether we could establish a SOCKS5 proxy connection + can_connect_socks5: bool, + + /// HTTPS connectivity test + https_connectivity: HttpsConnectivityResult, +} + +impl Socks5ProbeResults { + pub fn with_http_result(https_connectivity: HttpsConnectivityResult) -> Self { + Self { + can_connect_socks5: true, + https_connectivity, + } + } + + pub fn error_before_connecting(error: impl Into) -> Self { + Self { + can_connect_socks5: false, + https_connectivity: HttpsConnectivityResult::with_errors(vec![error.into()]), + } + } + + pub fn error_after_connecting(error: impl Into) -> Self { + Self { + can_connect_socks5: true, + https_connectivity: HttpsConnectivityResult::with_errors(vec![error.into()]), + } + } +} + #[derive(Debug, Clone, Default)] pub struct IpPingReplies { pub ipr_tun_ip_v4: bool, diff --git a/nym-gateway-probe/src/config/mod.rs b/nym-gateway-probe/src/config/mod.rs index 3601045253..a87be29839 100644 --- a/nym-gateway-probe/src/config/mod.rs +++ b/nym-gateway-probe/src/config/mod.rs @@ -3,8 +3,10 @@ mod credentials; mod netstack; +mod socks5; mod test_mode; pub use credentials::CredentialArgs; pub use netstack::NetstackArgs; +pub use socks5::Socks5Args; pub use test_mode::TestMode; diff --git a/nym-gateway-probe/src/config/socks5.rs b/nym-gateway-probe/src/config/socks5.rs new file mode 100644 index 0000000000..5c2a29c80a --- /dev/null +++ b/nym-gateway-probe/src/config/socks5.rs @@ -0,0 +1,32 @@ +use clap::Args; + +use crate::common::socks5_test::JsonRpcClient; + +#[derive(Args)] +pub struct Socks5Args { + #[arg(long, value_delimiter = ';')] + pub socks5_json_rpc_url_list: Vec, + + #[arg(long, default_value_t = 30)] + pub mixnet_client_timeout_sec: u64, + + #[arg(long, default_value_t = 10)] + pub test_count: u64, + + /// stops socks5 test early after this many failed attempts + #[arg(long, default_value_t = 3)] + pub failure_count_cutoff: usize, +} + +impl Socks5Args { + pub async fn validate_socks5_endpoints(&self) -> anyhow::Result<()> { + let client = JsonRpcClient::new( + self.mixnet_client_timeout_sec, + None, + self.socks5_json_rpc_url_list.clone(), + )?; + client.ensure_endpoint_works().await?; + + Ok(()) + } +} diff --git a/nym-gateway-probe/src/lib.rs b/nym-gateway-probe/src/lib.rs index ccd42a4121..cbd388efeb 100644 --- a/nym-gateway-probe/src/lib.rs +++ b/nym-gateway-probe/src/lib.rs @@ -2,9 +2,13 @@ // SPDX-License-Identifier: GPL-3.0-only use crate::common::helpers; -use crate::common::probe_tests::{do_ping, lp_registration_probe, wg_probe, wg_probe_lp}; -use crate::common::types::{Entry, Exit, WgProbeResults}; +use crate::common::probe_tests::{ + do_ping, do_socks5_connectivity_test, lp_registration_probe, wg_probe, wg_probe_lp, +}; +use crate::common::types::{Entry, Exit, Socks5ProbeResults, WgProbeResults}; +use crate::config::Socks5Args; use anyhow::bail; +use nym_api_requests::models::NetworkRequesterDetailsV1; use nym_authenticator_client::{AuthClientMixnetListener, AuthenticatorClient}; use nym_client_core::config::ForgetMe; use nym_config::defaults::NymNetworkDetails; @@ -14,6 +18,7 @@ use nym_sdk::mixnet::{ CredentialStorage, Ephemeral, KeyStore, MixnetClient, MixnetClientBuilder, MixnetClientStorage, NodeIdentity, StoragePaths, }; +use nym_topology::NymTopology; use rand::rngs::OsRng; use std::path::PathBuf; use std::sync::Arc; @@ -48,6 +53,7 @@ pub struct Probe { localnet_entry: Option, /// Localnet exit gateway info (used when --exit-gateway-identity is specified) localnet_exit: Option, + socks5_args: Socks5Args, } impl Probe { @@ -56,6 +62,7 @@ impl Probe { tested_node: TestedNode, netstack_args: NetstackArgs, credentials_args: CredentialArgs, + socks5_args: Socks5Args, ) -> Self { Self { entrypoint, @@ -67,6 +74,7 @@ impl Probe { exit_gateway_node: None, localnet_entry: None, localnet_exit: None, + socks5_args, } } @@ -77,6 +85,7 @@ impl Probe { netstack_args: NetstackArgs, credentials_args: CredentialArgs, gateway_node: DirectoryNode, + socks5_args: Socks5Args, ) -> Self { Self { entrypoint, @@ -88,6 +97,7 @@ impl Probe { exit_gateway_node: None, localnet_entry: None, localnet_exit: None, + socks5_args, } } @@ -99,6 +109,7 @@ impl Probe { credentials_args: CredentialArgs, entry_gateway_node: DirectoryNode, exit_gateway_node: DirectoryNode, + socks5_args: Socks5Args, ) -> Self { Self { entrypoint, @@ -110,6 +121,7 @@ impl Probe { exit_gateway_node: Some(exit_gateway_node), localnet_entry: None, localnet_exit: None, + socks5_args, } } @@ -120,6 +132,7 @@ impl Probe { exit: Option, netstack_args: NetstackArgs, credentials_args: CredentialArgs, + socks5_args: Socks5Args, ) -> Self { let entrypoint = entry.identity; Self { @@ -132,6 +145,7 @@ impl Probe { exit_gateway_node: None, localnet_entry: Some(entry), localnet_exit: exit, + socks5_args, } } @@ -150,6 +164,7 @@ impl Probe { only_lp_registration: bool, test_lp_wg: bool, min_mixnet_performance: Option, + network_details: NymNetworkDetails, ) -> anyhow::Result { let tickets_materials = self.credentials_args.decode_attached_ticket_materials()?; @@ -161,7 +176,7 @@ impl Probe { // Connect to the mixnet via the entry gateway let disconnected_mixnet_client = MixnetClientBuilder::new_with_storage(storage.clone()) .request_gateway(mixnet_entry_gateway_id.to_string()) - .network_details(NymNetworkDetails::new_from_env()) + .network_details(network_details.clone()) .debug_config(helpers::mixnet_debug_config( min_mixnet_performance, ignore_egress_epoch_role, @@ -176,6 +191,15 @@ impl Probe { let mixnet_client = Box::pin(disconnected_mixnet_client.connect_to_mixnet()).await; + // Extract topology from the connected client (if successful) to reuse for SOCKS5 test + let topology = match &mixnet_client { + Ok(client) => client + .read_current_route_provider() + .await + .map(|rp| rp.topology.clone()), + Err(_) => None, + }; + // Convert legacy flags to TestMode let has_exit = self.exit_gateway_node.is_some() || self.localnet_exit.is_some(); let test_mode = @@ -192,6 +216,8 @@ impl Probe { test_mode, only_wireguard, false, // Not using mock ecash in regular probe mode + network_details, + topology, ) .await } @@ -209,6 +235,7 @@ impl Probe { test_lp_wg: bool, min_mixnet_performance: Option, use_mock_ecash: bool, + network_details: NymNetworkDetails, ) -> anyhow::Result { // Localnet mode - identity + LP address from CLI, no HTTP query // This path is used when --entry-gateway-identity is specified @@ -249,6 +276,8 @@ impl Probe { test_mode, only_wireguard, use_mock_ecash, + network_details, + None, // No topology (no mixnet client in localnet mode) ) .await; } @@ -296,6 +325,8 @@ impl Probe { test_mode, only_wireguard, use_mock_ecash, + network_details, + None, // No topology (no mixnet client in direct gateway mode) ) .await; } @@ -328,7 +359,7 @@ impl Probe { // and keeps its bandwidth between probe runs let disconnected_mixnet_client = MixnetClientBuilder::new_with_storage(storage.clone()) .request_gateway(mixnet_entry_gateway_id.to_string()) - .network_details(NymNetworkDetails::new_from_env()) + .network_details(network_details.clone()) .debug_config(helpers::mixnet_debug_config( min_mixnet_performance, ignore_egress_epoch_role, @@ -371,6 +402,15 @@ impl Probe { let mixnet_client = Box::pin(disconnected_mixnet_client.connect_to_mixnet()).await; + // extract topology from the connected client (if any) to reuse for SOCKS5 test + let topology = match &mixnet_client { + Ok(client) => client + .read_current_route_provider() + .await + .map(|rp| rp.topology.clone()), + Err(_) => None, + }; + // Convert legacy flags to TestMode let has_exit = self.exit_gateway_node.is_some() || self.localnet_exit.is_some(); let test_mode = @@ -387,6 +427,8 @@ impl Probe { test_mode, only_wireguard, use_mock_ecash, + network_details, + topology, ) .await } @@ -458,11 +500,44 @@ impl Probe { Some(Exit::fail_to_connect()) }, wg: None, + socks5: None, lp: Some(lp_outcome), }, }) } + async fn test_socks5_if_possible( + &self, + network_details: NymNetworkDetails, + network_requester_details: &Option, + directory: &NymApiDirectory, + topology: Option, + ) -> Option { + if let Some(nr_details) = network_requester_details { + match do_socks5_connectivity_test( + &nr_details.address, + network_details, + directory, + self.socks5_args.socks5_json_rpc_url_list.clone(), + self.socks5_args.mixnet_client_timeout_sec, + self.socks5_args.test_count, + self.socks5_args.failure_count_cutoff, + topology, + ) + .await + { + Ok(results) => Some(results), + Err(e) => { + error!("SOCKS5 test failed: {}", e); + None + } + } + } else { + info!("No NR available, skipping SOCKS5 tests"); + None + } + } + pub async fn lookup_gateway( &self, directory: &Option, @@ -535,11 +610,16 @@ impl Probe { test_mode: TestMode, only_wireguard: bool, use_mock_ecash: bool, + network_details: NymNetworkDetails, + topology: Option, ) -> anyhow::Result where T: MixnetClientStorage + Clone + 'static, ::StorageError: Send + Sync, { + let Some(directory) = directory else { + bail!("You need to provide NYM API through environment") + }; // test_mode replaces the old only_lp_registration and test_lp_wg flags. // only_wireguard is kept separate as it controls ping behavior within Mixnet mode. let mut rng = rand::thread_rng(); @@ -557,6 +637,7 @@ impl Probe { Entry::EntryFailure }, as_exit: None, + socks5: None, wg: None, lp: None, }, @@ -595,6 +676,7 @@ impl Probe { Entry::NotTested }, as_exit: None, + socks5: None, wg: None, lp: None, }), @@ -609,6 +691,7 @@ impl Probe { Ok(ProbeOutcome { as_entry: Entry::NotTested, as_exit: None, + socks5: None, wg: None, lp: None, }), @@ -624,6 +707,7 @@ impl Probe { Entry::EntryFailure }, as_exit: None, + socks5: None, wg: None, lp: None, }), @@ -681,8 +765,6 @@ impl Probe { // The tested node is the exit let exit_gateway = node_info.clone(); - let directory = directory - .ok_or_else(|| anyhow::anyhow!("Directory is required for LP-WG test mode"))?; let entry_gateway_node = directory.entry_gateway(&mixnet_entry_gateway_id)?; let entry_gateway = entry_gateway_node.to_testable_node()?; @@ -723,9 +805,8 @@ impl Probe { Arc::new(KeyPair::new(&mut rng)), ip_address, ); - let config = nym_validator_client::nyxd::Config::try_from_nym_network_details( - &NymNetworkDetails::new_from_env(), - )?; + let config = + nym_validator_client::nyxd::Config::try_from_nym_network_details(&network_details)?; let client = nym_validator_client::nyxd::NyxdClient::connect(config, nyxd_url.as_str())?; let bw_controller = nym_bandwidth_controller::BandwidthController::new( @@ -790,10 +871,21 @@ impl Probe { None }; + // test failure doesn't stop further tests + let socks5_outcome = self + .test_socks5_if_possible( + network_details, + &node_info.network_requester_details, + directory, + topology, + ) + .await; + // Disconnect the mixnet client gracefully outcome.map(|mut outcome| { outcome.wg = Some(wg_outcome); outcome.lp = lp_outcome; + outcome.socks5 = socks5_outcome; ProbeResult { node: node_info.identity.to_string(), used_entry: mixnet_entry_gateway_id.to_string(), diff --git a/nym-gateway-probe/src/run.rs b/nym-gateway-probe/src/run.rs index d03a9aa267..889617406c 100644 --- a/nym-gateway-probe/src/run.rs +++ b/nym-gateway-probe/src/run.rs @@ -6,6 +6,7 @@ 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::config::Socks5Args; use nym_gateway_probe::{ CredentialArgs, NetstackArgs, NymApiDirectory, ProbeResult, TestMode, TestedNode, TestedNodeDetails, TestedNodeLpDetails, query_gateway_by_ip, @@ -172,6 +173,10 @@ struct CliArgs { /// Arguments to manage credentials #[command(flatten)] credential_args: CredentialArgs, + + /// Arguments to configure socks5 probe + #[command(flatten)] + socks5_args: Socks5Args, } const DEFAULT_CONFIG_DIR: &str = "/tmp/nym-gateway-probe/config/"; @@ -263,6 +268,8 @@ pub(crate) async fn run() -> anyhow::Result { .map(|ep| ep.nyxd_url()) .ok_or(anyhow::anyhow!("missing nyxd url"))?; + args.socks5_args.validate_socks5_endpoints().await?; + // Three resolution modes in priority order: // 1. Localnet mode: --entry-gateway-identity provided (no HTTP query) // 2. Direct IP mode: --gateway-ip provided (queries HTTP API) @@ -381,6 +388,7 @@ pub(crate) async fn run() -> anyhow::Result { exit_details, args.netstack_args, args.credential_args, + args.socks5_args, ); if let Some(awg_args) = args.amnezia_args { @@ -414,6 +422,7 @@ pub(crate) async fn run() -> anyhow::Result { test_lp_wg, args.min_gateway_mixnet_performance, *use_mock_ecash, + network, )) .await } @@ -426,6 +435,7 @@ pub(crate) async fn run() -> anyhow::Result { only_lp_registration, test_lp_wg, args.min_gateway_mixnet_performance, + network, )) .await } @@ -515,6 +525,7 @@ pub(crate) async fn run() -> anyhow::Result { args.credential_args, entry_node.clone(), exit_node.clone(), + args.socks5_args, ) } else if let Some(gw_node) = gateway_node { // Only entry gateway provided @@ -524,17 +535,24 @@ pub(crate) async fn run() -> anyhow::Result { args.netstack_args, args.credential_args, gw_node, + args.socks5_args, ) } else { // No direct gateways, use directory lookup - nym_gateway_probe::Probe::new(entry, test_point, args.netstack_args, args.credential_args) + nym_gateway_probe::Probe::new( + entry, + test_point, + args.netstack_args, + args.credential_args, + args.socks5_args, + ) }; if let Some(awg_args) = args.amnezia_args { trial.with_amnezia(&awg_args); } - match &args.command { + match args.command { Some(Commands::RunLocal { mnemonic, config_dir, @@ -559,7 +577,8 @@ pub(crate) async fn run() -> anyhow::Result { only_lp_registration, test_lp_wg, args.min_gateway_mixnet_performance, - *use_mock_ecash, + use_mock_ecash, + network, )) .await } @@ -572,6 +591,7 @@ pub(crate) async fn run() -> anyhow::Result { only_lp_registration, test_lp_wg, args.min_gateway_mixnet_performance, + network, )) .await } diff --git a/nym-node-status-api/nym-node-status-agent/Cargo.toml b/nym-node-status-api/nym-node-status-agent/Cargo.toml index f866a4aacb..23817c5eb2 100644 --- a/nym-node-status-api/nym-node-status-agent/Cargo.toml +++ b/nym-node-status-api/nym-node-status-agent/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "nym-node-status-agent" -version = "1.0.7" +version = "1.1.0" authors.workspace = true repository.workspace = true homepage.workspace = true diff --git a/nym-node-status-api/nym-node-status-agent/run.sh b/nym-node-status-api/nym-node-status-agent/run.sh index 1061ad5f95..2a5a0b5e41 100755 --- a/nym-node-status-api/nym-node-status-agent/run.sh +++ b/nym-node-status-api/nym-node-status-agent/run.sh @@ -1,15 +1,16 @@ #!/bin/bash +# used primarily for local testing + set -eu export ENVIRONMENT=${ENVIRONMENT:-"mainnet"} -probe_git_ref="nym-vpn-core-v1.4.0" - crate_root=$(dirname $(realpath "$0")) +echo crate_root=${crate_root} monorepo_root=$(realpath "${crate_root}/../..") +echo monorepo_root=${monorepo_root} -echo "Expecting nym-vpn-client repo at a sibling level of nym monorepo dir" -gateway_probe_src=$(dirname "${monorepo_root}")/nym-vpn-client/nym-vpn-core +gateway_probe_src="${monorepo_root}/nym-gateway-probe" echo "gateway_probe_src=$gateway_probe_src" set -a @@ -25,7 +26,8 @@ export RUST_LOG="info" NODE_STATUS_AGENT_SERVER_ADDRESS="http://127.0.0.1" NODE_STATUS_AGENT_SERVER_PORT="8000" SERVER="${NODE_STATUS_AGENT_SERVER_ADDRESS}|${NODE_STATUS_AGENT_SERVER_PORT}" -export NODE_STATUS_AGENT_AUTH_KEY="BjyC9SsHAZUzPRkQR4sPTvVrp4GgaquTh5YfSJksvvWT" +# hardcoded key used only for LOCAL TESTING +export NODE_STATUS_AGENT_AUTH_KEY=${NODE_STATUS_AGENT_AUTH_KEY_STAGING:-"BjyC9SsHAZUzPRkQR4sPTvVrp4GgaquTh5YfSJksvvWT"} export NODE_STATUS_AGENT_PROBE_PATH="$crate_root/nym-gateway-probe" export NODE_STATUS_AGENT_PROBE_EXTRA_ARGS="netstack-download-timeout-sec=30,netstack-num-ping=2,netstack-send-timeout-sec=1,netstack-recv-timeout-sec=1" @@ -35,11 +37,9 @@ echo "Running $workers workers in parallel" # build & copy over GW probe function copy_gw_probe() { pushd $gateway_probe_src - git fetch -a - git checkout $probe_git_ref cargo build --release --package nym-gateway-probe - cp target/release/nym-gateway-probe "$crate_root" + cp "${monorepo_root}/target/release/nym-gateway-probe" "$crate_root" $crate_root/nym-gateway-probe --version popd diff --git a/sdk/rust/nym-sdk/src/mixnet/client.rs b/sdk/rust/nym-sdk/src/mixnet/client.rs index 5020c1bd5c..20e562e662 100644 --- a/sdk/rust/nym-sdk/src/mixnet/client.rs +++ b/sdk/rust/nym-sdk/src/mixnet/client.rs @@ -601,6 +601,13 @@ where ); let available_gateways = self.available_gateways().await?; + for node in available_gateways.iter() { + debug!( + "node_id={}, identity_key={}", + node.node_id, + node.identity_key.to_base58_string() + ); + } Ok(GatewaySetup::New { specification: selection_spec, From d3648f13c5e5e6c1dd3895e03c03443dddee7edd Mon Sep 17 00:00:00 2001 From: dynco-nym <173912580+dynco-nym@users.noreply.github.com> Date: Thu, 29 Jan 2026 20:54:21 +0100 Subject: [PATCH 35/51] NS API socks5 support (#6361) * Add conversion from gw_probe crate type * Move code around - split 1000+ LoC files into smaller ones * Add socks5 field - code improvements in gw_probe crate * Fix docker build - install go - required as build dependency of gw probe * Add logs to agent * NS API: configure DB via env * rebase fix * socks5 score calc * Cargo fmt * use existing div_ceil * Code improvements * Bump NS API version * Rename variables * Bump API & agent version * Try to fix CI * Build only on linux --- .github/workflows/ci-build.yml | 10 +- Cargo.lock | 8 +- Cargo.toml | 1 - nym-gateway-probe/Cargo.toml | 4 + nym-gateway-probe/src/common/mod.rs | 2 +- .../src/common/socks5_test/mod.rs | 13 +- nym-gateway-probe/src/common/types.rs | 17 +- nym-gateway-probe/src/lib.rs | 1 + .../nym-node-status-agent/Cargo.toml | 4 +- .../nym-node-status-agent/README.md | 1 - .../nym-node-status-agent/run.sh | 6 +- .../src/cli/run_probe.rs | 44 ++ ...afa2525da6c0b871633aad80ad555db9cf47c.json | 32 -- ...105614dea7fd301f0ec38bfe85bfe546dad40.json | 12 - ...a317aabce063a650be362d3a8ed83cc7c3549.json | 12 - ...9f166ad7f57681f76a1d0c7723d007c1f2c1e.json | 12 - ...bd97845350f4fb9f806c60b93c7cebd5e410d.json | 20 - ...91d2a2af57656b405212af414d765b2263347.json | 26 - ...11e5732ece0bf84ea98f2328b20add8f2b5ef.json | 12 - ...7fca89d80869484f2f3c66cabb898f0298c62.json | 12 - ...57f91a11f5057273cb70bd0e629712d17dd41.json | 12 - ...13ab15fed747fb0cee1c9f949fb58461b3f79.json | 32 -- ...6a811c593aea8febf1f891117e5e84213f147.json | 12 - ...c9f560017b4da6b0303ea0397d9568229e167.json | 38 -- ...abd806877ce914b514d4f7cd6be318c4debe6.json | 12 - ...8f633a94d18a19b7f0f96935a62560def7d0f.json | 12 - ...52f2bfed2d918b894ec0f588e38dd5e8ad726.json | 12 - ...6a2e314787586a46cffd074abb67f2f4d109e.json | 38 -- ...18a1f62fa67dfe8f130deb876ebee11bf1602.json | 20 - .../nym-node-status-api/Cargo.toml | 3 +- .../nym-node-status-api/Dockerfile | 7 + .../nym-node-status-api/Makefile | 16 + .../nym-node-status-api/src/cli/mod.rs | 9 + .../nym-node-status-api/src/db/mod.rs | 27 +- .../src/http/models/gw_probe/mod.rs | 447 +++++++++++++++ .../src/http/models/gw_probe/socks5_calc.rs | 289 ++++++++++ .../src/http/models/gw_probe/test.rs | 533 ++++++++++++++++++ .../src/http/{models.rs => models/mod.rs} | 385 +------------ .../nym-node-status-api/src/http/state.rs | 9 +- .../nym-node-status-api/src/main.rs | 15 +- 40 files changed, 1466 insertions(+), 711 deletions(-) delete mode 100644 nym-node-status-api/nym-node-status-api/.sqlx/query-0112296b190328a3856d1adf51aafa2525da6c0b871633aad80ad555db9cf47c.json delete mode 100644 nym-node-status-api/nym-node-status-api/.sqlx/query-1fc72f8ba24039548047e1766c9105614dea7fd301f0ec38bfe85bfe546dad40.json delete mode 100644 nym-node-status-api/nym-node-status-api/.sqlx/query-28681fcd8e2d4326f628681b8f2a317aabce063a650be362d3a8ed83cc7c3549.json delete mode 100644 nym-node-status-api/nym-node-status-api/.sqlx/query-2930ca6e3875c74acb7abb9ad889f166ad7f57681f76a1d0c7723d007c1f2c1e.json delete mode 100644 nym-node-status-api/nym-node-status-api/.sqlx/query-396f40c33f0f62796eb7449d640bd97845350f4fb9f806c60b93c7cebd5e410d.json delete mode 100644 nym-node-status-api/nym-node-status-api/.sqlx/query-3cc446220668fb3e02f0578104291d2a2af57656b405212af414d765b2263347.json delete mode 100644 nym-node-status-api/nym-node-status-api/.sqlx/query-52b378e282d93db941eff53b5b311e5732ece0bf84ea98f2328b20add8f2b5ef.json delete mode 100644 nym-node-status-api/nym-node-status-api/.sqlx/query-70d8f240ad6edda6b8c7f2e800e7fca89d80869484f2f3c66cabb898f0298c62.json delete mode 100644 nym-node-status-api/nym-node-status-api/.sqlx/query-97d97ebb6bc8f4114fdea9ebc9f57f91a11f5057273cb70bd0e629712d17dd41.json delete mode 100644 nym-node-status-api/nym-node-status-api/.sqlx/query-a8b7ce0fe4755c28b96d1e503e313ab15fed747fb0cee1c9f949fb58461b3f79.json delete mode 100644 nym-node-status-api/nym-node-status-api/.sqlx/query-b8257a0832d0124f0a8aaaf81dc6a811c593aea8febf1f891117e5e84213f147.json delete mode 100644 nym-node-status-api/nym-node-status-api/.sqlx/query-c2b841762bdb963fff337ef5c8ec9f560017b4da6b0303ea0397d9568229e167.json delete mode 100644 nym-node-status-api/nym-node-status-api/.sqlx/query-d3510846941fa2525926b9bfbcdabd806877ce914b514d4f7cd6be318c4debe6.json delete mode 100644 nym-node-status-api/nym-node-status-api/.sqlx/query-db176e98198fe594d88eb860d918f633a94d18a19b7f0f96935a62560def7d0f.json delete mode 100644 nym-node-status-api/nym-node-status-api/.sqlx/query-e584253e3856355899537eb8fc152f2bfed2d918b894ec0f588e38dd5e8ad726.json delete mode 100644 nym-node-status-api/nym-node-status-api/.sqlx/query-e77ffab19b099b84470fe5611716a2e314787586a46cffd074abb67f2f4d109e.json delete mode 100644 nym-node-status-api/nym-node-status-api/.sqlx/query-ef60c2683211cc4ec2d3e46392518a1f62fa67dfe8f130deb876ebee11bf1602.json create mode 100644 nym-node-status-api/nym-node-status-api/src/http/models/gw_probe/mod.rs create mode 100644 nym-node-status-api/nym-node-status-api/src/http/models/gw_probe/socks5_calc.rs create mode 100644 nym-node-status-api/nym-node-status-api/src/http/models/gw_probe/test.rs rename nym-node-status-api/nym-node-status-api/src/http/{models.rs => models/mod.rs} (61%) diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index a5b069dccb..5ba0612c73 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -89,7 +89,7 @@ jobs: uses: actions-rs/cargo@v1 with: command: clippy - args: --workspace --all-targets --exclude nym-gateway-probe -- -D warnings + args: --workspace --all-targets --exclude nym-gateway-probe --exclude nym-node-status-api -- -D warnings - name: Clippy (non-macos) if: contains(matrix.os, 'linux') || contains(matrix.os, 'windows') @@ -104,6 +104,14 @@ jobs: with: command: build + # only build on linux because of wg FFI bindings of its dependency (network probe) + - name: Build nym-node-status-api (linux only) + if: runner.os == 'Linux' + uses: actions-rs/cargo@v1 + with: + command: build + args: -p nym-node-status-api + - name: Build all examples if: contains(matrix.os, 'linux') uses: actions-rs/cargo@v1 diff --git a/Cargo.lock b/Cargo.lock index 91a632d24f..845cb75272 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6499,6 +6499,7 @@ dependencies = [ "tracing", "tracing-subscriber", "url", + "utoipa", "vergen-gitcl", "x25519-dalek", ] @@ -7235,7 +7236,7 @@ dependencies = [ [[package]] name = "nym-node-status-agent" -version = "1.1.0" +version = "1.1.1" dependencies = [ "anyhow", "clap", @@ -7244,6 +7245,8 @@ dependencies = [ "nym-crypto", "nym-node-status-client", "rand 0.8.5", + "regex", + "serde_json", "tempfile", "tokio", "tracing", @@ -7252,7 +7255,7 @@ dependencies = [ [[package]] name = "nym-node-status-api" -version = "4.0.12" +version = "4.0.14" dependencies = [ "ammonia", "anyhow", @@ -7273,6 +7276,7 @@ dependencies = [ "nym-credentials", "nym-crypto", "nym-ecash-time", + "nym-gateway-probe", "nym-http-api-client", "nym-http-api-common", "nym-mixnet-contract-common", diff --git a/Cargo.toml b/Cargo.toml index b52bcac5df..a9a240b504 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -185,7 +185,6 @@ default-members = [ "nym-credential-proxy/nym-credential-proxy", "nym-node", "nym-node-status-api/nym-node-status-agent", - "nym-node-status-api/nym-node-status-api", "nym-statistics-api", "nym-validator-rewarder", "nyx-chain-watcher", diff --git a/nym-gateway-probe/Cargo.toml b/nym-gateway-probe/Cargo.toml index 536412f580..72336bb532 100644 --- a/nym-gateway-probe/Cargo.toml +++ b/nym-gateway-probe/Cargo.toml @@ -38,6 +38,7 @@ tokio = { workspace = true, features = [ tokio-util.workspace = true tracing-subscriber.workspace = true url = { workspace = true } +utoipa = { workspace = true, optional = true } x25519-dalek = { workspace = true, features = [ "reusable_secrets", "static_secrets", @@ -76,6 +77,9 @@ time = { workspace = true } # TEMP: REMOVE BEFORE PR nym-topology = { workspace = true } +[features] +utoipa = ["dep:utoipa"] + [build-dependencies] anyhow = { workspace = true } vergen-gitcl = { workspace = true, default-features = false, features = [ diff --git a/nym-gateway-probe/src/common/mod.rs b/nym-gateway-probe/src/common/mod.rs index 6276ec032c..718ab6d8f3 100644 --- a/nym-gateway-probe/src/common/mod.rs +++ b/nym-gateway-probe/src/common/mod.rs @@ -13,5 +13,5 @@ pub(crate) mod netstack; pub(crate) mod nodes; pub(crate) mod probe_tests; pub(crate) mod socks5_test; -pub(crate) mod types; +pub mod types; pub(crate) mod wireguard; diff --git a/nym-gateway-probe/src/common/socks5_test/mod.rs b/nym-gateway-probe/src/common/socks5_test/mod.rs index 5ea4e361d1..61372f7c71 100644 --- a/nym-gateway-probe/src/common/socks5_test/mod.rs +++ b/nym-gateway-probe/src/common/socks5_test/mod.rs @@ -73,6 +73,7 @@ struct SingleHttpsTestResult { error: Option, } +#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct HttpsConnectivityResult { /// successfully completed HTTPS request @@ -88,17 +89,17 @@ pub struct HttpsConnectivityResult { endpoint_used: Option, /// error message(s) (if any) - error: Option>, + errors: Option>, } impl HttpsConnectivityResult { - pub fn with_errors(error: Vec) -> Self { + pub fn with_errors(errors: Vec) -> Self { Self { https_success: false, https_status_code: None, https_latency_ms: None, endpoint_used: None, - error: Some(error), + errors: Some(errors), } } @@ -135,7 +136,7 @@ impl HttpsConnectivityResult { https_latency_ms: Some(avg_latency), endpoint_used: last_success.endpoint_used.clone(), // even in case of success, some errors were possible - error: if errors.is_empty() { + errors: if errors.is_empty() { None } else { Some(errors) @@ -158,4 +159,8 @@ impl HttpsConnectivityResult { pub fn endpoint_used(&self) -> Option<&String> { self.endpoint_used.as_ref() } + + pub fn errors(&self) -> Option<&Vec> { + self.errors.as_ref() + } } diff --git a/nym-gateway-probe/src/common/types.rs b/nym-gateway-probe/src/common/types.rs index f2027ec78f..34edadabfb 100644 --- a/nym-gateway-probe/src/common/types.rs +++ b/nym-gateway-probe/src/common/types.rs @@ -10,6 +10,7 @@ pub struct ProbeResult { pub outcome: ProbeOutcome, } +#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ProbeOutcome { pub as_entry: Entry, @@ -19,6 +20,7 @@ pub struct ProbeOutcome { pub lp: Option, } +#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] #[derive(Debug, Clone, Serialize, Deserialize, Default)] #[serde(rename = "wg")] pub struct WgProbeResults { @@ -48,6 +50,7 @@ pub struct WgProbeResults { pub download_error_v6: String, } +#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] #[derive(Debug, Clone, Serialize, Deserialize, Default)] #[serde(rename = "lp")] pub struct LpProbeResults { @@ -57,6 +60,7 @@ pub struct LpProbeResults { pub error: Option, } +#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] #[allow(clippy::enum_variant_names)] @@ -98,12 +102,14 @@ impl Entry { } } +#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] #[derive(Debug, Clone, Serialize, Deserialize)] pub struct EntryTestResult { pub can_connect: bool, pub can_route: bool, } +#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Exit { pub can_connect: bool, @@ -135,7 +141,8 @@ impl Exit { } } -#[derive(Debug, Clone, Serialize, Deserialize, Default)] +#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct Socks5ProbeResults { /// whether we could establish a SOCKS5 proxy connection can_connect_socks5: bool, @@ -165,6 +172,14 @@ impl Socks5ProbeResults { https_connectivity: HttpsConnectivityResult::with_errors(vec![error.into()]), } } + + pub fn can_connect_socks5(&self) -> bool { + self.can_connect_socks5 + } + + pub fn https_connectivity(&self) -> &HttpsConnectivityResult { + &self.https_connectivity + } } #[derive(Debug, Clone, Default)] diff --git a/nym-gateway-probe/src/lib.rs b/nym-gateway-probe/src/lib.rs index cbd388efeb..a694d6de86 100644 --- a/nym-gateway-probe/src/lib.rs +++ b/nym-gateway-probe/src/lib.rs @@ -27,6 +27,7 @@ use tracing::*; use url::Url; mod common; +pub use common::types; pub mod config; use crate::common::bandwidth_helpers::{ diff --git a/nym-node-status-api/nym-node-status-agent/Cargo.toml b/nym-node-status-api/nym-node-status-agent/Cargo.toml index 23817c5eb2..9c8daf7007 100644 --- a/nym-node-status-api/nym-node-status-agent/Cargo.toml +++ b/nym-node-status-api/nym-node-status-agent/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "nym-node-status-agent" -version = "1.1.0" +version = "1.1.1" authors.workspace = true repository.workspace = true homepage.workspace = true @@ -23,6 +23,8 @@ nym-crypto = { workspace = true, features = ["asymmetric", "rand"] } nym-node-status-client = { path = "../nym-node-status-client" } rand = { workspace = true } +regex = { workspace = true } +serde_json = { workspace = true } tokio = { workspace = true, features = [ "macros", "rt-multi-thread", diff --git a/nym-node-status-api/nym-node-status-agent/README.md b/nym-node-status-api/nym-node-status-agent/README.md index bf75649c25..c70732570e 100644 --- a/nym-node-status-api/nym-node-status-agent/README.md +++ b/nym-node-status-api/nym-node-status-agent/README.md @@ -4,7 +4,6 @@ An agent to run tests and report results back to the Node Status API. Environment variables that can be set individually are: -- `NYM_NODE_MNEMONICS` - mnemonic to get tickets for tests - `NODE_STATUS_AGENT_SERVER_PORT` - Node Status API port - `NODE_STATUS_AGENT_SERVER_ADDRESS` - Node Status API address diff --git a/nym-node-status-api/nym-node-status-agent/run.sh b/nym-node-status-api/nym-node-status-agent/run.sh index 2a5a0b5e41..99ebb1867a 100755 --- a/nym-node-status-api/nym-node-status-agent/run.sh +++ b/nym-node-status-api/nym-node-status-agent/run.sh @@ -17,10 +17,6 @@ set -a source "${monorepo_root}/envs/${ENVIRONMENT}.env" set +a -if [ -z "$NYM_NODE_MNEMONICS" ]; then - echo "NYM_NODE_MNEMONICS is required to run an agent" - exit 1 -fi export RUST_LOG="info" NODE_STATUS_AGENT_SERVER_ADDRESS="http://127.0.0.1" @@ -29,7 +25,7 @@ SERVER="${NODE_STATUS_AGENT_SERVER_ADDRESS}|${NODE_STATUS_AGENT_SERVER_PORT}" # hardcoded key used only for LOCAL TESTING export NODE_STATUS_AGENT_AUTH_KEY=${NODE_STATUS_AGENT_AUTH_KEY_STAGING:-"BjyC9SsHAZUzPRkQR4sPTvVrp4GgaquTh5YfSJksvvWT"} export NODE_STATUS_AGENT_PROBE_PATH="$crate_root/nym-gateway-probe" -export NODE_STATUS_AGENT_PROBE_EXTRA_ARGS="netstack-download-timeout-sec=30,netstack-num-ping=2,netstack-send-timeout-sec=1,netstack-recv-timeout-sec=1" +export NODE_STATUS_AGENT_PROBE_EXTRA_ARGS="netstack-download-timeout-sec=30,netstack-num-ping=2,netstack-send-timeout-sec=1,netstack-recv-timeout-sec=1,socks5-json-rpc-url-list=https://cloudflare-eth.com;https://ethereum.publicnode.com" workers=${1:-1} echo "Running $workers workers in parallel" diff --git a/nym-node-status-api/nym-node-status-agent/src/cli/run_probe.rs b/nym-node-status-api/nym-node-status-agent/src/cli/run_probe.rs index 7d0e106b76..8f82428e75 100644 --- a/nym-node-status-api/nym-node-status-agent/src/cli/run_probe.rs +++ b/nym-node-status-api/nym-node-status-agent/src/cli/run_probe.rs @@ -57,6 +57,36 @@ pub(crate) async fn run_probe( testrun.ticket_materials, ); + // Inspect the probe output for socks5 field + // Extract JSON from log output (probe outputs logs followed by JSON) + let json_str = extract_json_from_log(&log); + if json_str.is_empty() { + tracing::error!("Failed to extract JSON from probe output"); + } else { + match serde_json::from_str::(&json_str) { + Ok(json) => { + if let Some(outcome) = json.get("outcome") { + match outcome.get("socks5") { + Some(socks5) if socks5.is_null() => { + tracing::warn!("🌐⚠️ socks5 field is NULL in probe output"); + } + Some(socks5) => { + tracing::info!("🌐 socks5 field present: {}", socks5); + } + None => { + tracing::warn!("🌐⚠️ socks5 field is MISSING from probe output"); + } + } + } else { + tracing::warn!("🌐⚠️ outcome field is MISSING from probe output"); + } + } + Err(e) => { + tracing::error!("Failed to parse probe output as JSON: {e}"); + } + } + } + // Submit to ALL servers in parallel let handles = servers .iter() @@ -122,3 +152,17 @@ pub(crate) async fn run_probe( Ok(()) } + +/// Extract JSON from probe log output. +/// The probe outputs log lines followed by JSON starting with `\n{ `. +fn extract_json_from_log(log: &str) -> String { + static RE: std::sync::LazyLock = + std::sync::LazyLock::new(|| regex::Regex::new(r"\n\{\s").expect("Invalid regex pattern")); + + let result: Vec<_> = RE.splitn(log, 2).collect(); + if result.len() == 2 { + format!("{{ {}", result[1]) + } else { + String::new() + } +} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-0112296b190328a3856d1adf51aafa2525da6c0b871633aad80ad555db9cf47c.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-0112296b190328a3856d1adf51aafa2525da6c0b871633aad80ad555db9cf47c.json deleted file mode 100644 index 068bc54b4b..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-0112296b190328a3856d1adf51aafa2525da6c0b871633aad80ad555db9cf47c.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "db_name": "SQLite", - "query": "\n SELECT epoch_id as \"epoch_id: u32\", serialised_key, serialization_revision as \"serialization_revision: u8\"\n FROM master_verification_key WHERE epoch_id = ?\n ", - "describe": { - "columns": [ - { - "name": "epoch_id: u32", - "ordinal": 0, - "type_info": "Integer" - }, - { - "name": "serialised_key", - "ordinal": 1, - "type_info": "Blob" - }, - { - "name": "serialization_revision: u8", - "ordinal": 2, - "type_info": "Integer" - } - ], - "parameters": { - "Right": 1 - }, - "nullable": [ - false, - false, - false - ] - }, - "hash": "0112296b190328a3856d1adf51aafa2525da6c0b871633aad80ad555db9cf47c" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-1fc72f8ba24039548047e1766c9105614dea7fd301f0ec38bfe85bfe546dad40.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-1fc72f8ba24039548047e1766c9105614dea7fd301f0ec38bfe85bfe546dad40.json deleted file mode 100644 index cdc057df57..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-1fc72f8ba24039548047e1766c9105614dea7fd301f0ec38bfe85bfe546dad40.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "SQLite", - "query": "\n INSERT INTO ecash_deposit_usage (deposit_id, ticketbooks_requested_on, client_pubkey, request_uuid)\n VALUES (?, ?, ?, ?)\n ", - "describe": { - "columns": [], - "parameters": { - "Right": 4 - }, - "nullable": [] - }, - "hash": "1fc72f8ba24039548047e1766c9105614dea7fd301f0ec38bfe85bfe546dad40" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-28681fcd8e2d4326f628681b8f2a317aabce063a650be362d3a8ed83cc7c3549.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-28681fcd8e2d4326f628681b8f2a317aabce063a650be362d3a8ed83cc7c3549.json deleted file mode 100644 index 32f3f9958c..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-28681fcd8e2d4326f628681b8f2a317aabce063a650be362d3a8ed83cc7c3549.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "SQLite", - "query": "\n DELETE FROM blinded_shares WHERE created < ?\n ", - "describe": { - "columns": [], - "parameters": { - "Right": 1 - }, - "nullable": [] - }, - "hash": "28681fcd8e2d4326f628681b8f2a317aabce063a650be362d3a8ed83cc7c3549" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-2930ca6e3875c74acb7abb9ad889f166ad7f57681f76a1d0c7723d007c1f2c1e.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-2930ca6e3875c74acb7abb9ad889f166ad7f57681f76a1d0c7723d007c1f2c1e.json deleted file mode 100644 index 9ef7d96cca..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-2930ca6e3875c74acb7abb9ad889f166ad7f57681f76a1d0c7723d007c1f2c1e.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "SQLite", - "query": "\n INSERT INTO global_expiration_date_signatures(expiration_date, epoch_id, serialised_signatures, serialization_revision)\n VALUES (?, ?, ?, ?)\n ", - "describe": { - "columns": [], - "parameters": { - "Right": 4 - }, - "nullable": [] - }, - "hash": "2930ca6e3875c74acb7abb9ad889f166ad7f57681f76a1d0c7723d007c1f2c1e" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-396f40c33f0f62796eb7449d640bd97845350f4fb9f806c60b93c7cebd5e410d.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-396f40c33f0f62796eb7449d640bd97845350f4fb9f806c60b93c7cebd5e410d.json deleted file mode 100644 index 944c86e33e..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-396f40c33f0f62796eb7449d640bd97845350f4fb9f806c60b93c7cebd5e410d.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "db_name": "SQLite", - "query": "\n SELECT error_message\n FROM blinded_shares\n WHERE id = ?;\n ", - "describe": { - "columns": [ - { - "name": "error_message", - "ordinal": 0, - "type_info": "Text" - } - ], - "parameters": { - "Right": 1 - }, - "nullable": [ - true - ] - }, - "hash": "396f40c33f0f62796eb7449d640bd97845350f4fb9f806c60b93c7cebd5e410d" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-3cc446220668fb3e02f0578104291d2a2af57656b405212af414d765b2263347.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-3cc446220668fb3e02f0578104291d2a2af57656b405212af414d765b2263347.json deleted file mode 100644 index 9d8cff1326..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-3cc446220668fb3e02f0578104291d2a2af57656b405212af414d765b2263347.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "db_name": "SQLite", - "query": "\n SELECT serialised_signatures, serialization_revision as \"serialization_revision: u8\"\n FROM global_expiration_date_signatures\n WHERE expiration_date = ? AND epoch_id = ?\n ", - "describe": { - "columns": [ - { - "name": "serialised_signatures", - "ordinal": 0, - "type_info": "Blob" - }, - { - "name": "serialization_revision: u8", - "ordinal": 1, - "type_info": "Integer" - } - ], - "parameters": { - "Right": 2 - }, - "nullable": [ - false, - false - ] - }, - "hash": "3cc446220668fb3e02f0578104291d2a2af57656b405212af414d765b2263347" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-52b378e282d93db941eff53b5b311e5732ece0bf84ea98f2328b20add8f2b5ef.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-52b378e282d93db941eff53b5b311e5732ece0bf84ea98f2328b20add8f2b5ef.json deleted file mode 100644 index 77a99816d4..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-52b378e282d93db941eff53b5b311e5732ece0bf84ea98f2328b20add8f2b5ef.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "SQLite", - "query": "\n DELETE FROM partial_blinded_wallet_failure WHERE created < ?\n ", - "describe": { - "columns": [], - "parameters": { - "Right": 1 - }, - "nullable": [] - }, - "hash": "52b378e282d93db941eff53b5b311e5732ece0bf84ea98f2328b20add8f2b5ef" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-70d8f240ad6edda6b8c7f2e800e7fca89d80869484f2f3c66cabb898f0298c62.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-70d8f240ad6edda6b8c7f2e800e7fca89d80869484f2f3c66cabb898f0298c62.json deleted file mode 100644 index 2f22ed93fa..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-70d8f240ad6edda6b8c7f2e800e7fca89d80869484f2f3c66cabb898f0298c62.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "SQLite", - "query": "INSERT INTO master_verification_key(epoch_id, serialised_key, serialization_revision) VALUES (?, ?, ?)", - "describe": { - "columns": [], - "parameters": { - "Right": 3 - }, - "nullable": [] - }, - "hash": "70d8f240ad6edda6b8c7f2e800e7fca89d80869484f2f3c66cabb898f0298c62" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-97d97ebb6bc8f4114fdea9ebc9f57f91a11f5057273cb70bd0e629712d17dd41.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-97d97ebb6bc8f4114fdea9ebc9f57f91a11f5057273cb70bd0e629712d17dd41.json deleted file mode 100644 index 84e8ba9f16..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-97d97ebb6bc8f4114fdea9ebc9f57f91a11f5057273cb70bd0e629712d17dd41.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "SQLite", - "query": "\n INSERT INTO partial_blinded_wallet_failure(corresponding_deposit, epoch_id, expiration_date, node_id, created, failure_message)\n VALUES (?, ?, ?, ?, ?, ?)\n ", - "describe": { - "columns": [], - "parameters": { - "Right": 6 - }, - "nullable": [] - }, - "hash": "97d97ebb6bc8f4114fdea9ebc9f57f91a11f5057273cb70bd0e629712d17dd41" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-a8b7ce0fe4755c28b96d1e503e313ab15fed747fb0cee1c9f949fb58461b3f79.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-a8b7ce0fe4755c28b96d1e503e313ab15fed747fb0cee1c9f949fb58461b3f79.json deleted file mode 100644 index 6cb8138cc5..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-a8b7ce0fe4755c28b96d1e503e313ab15fed747fb0cee1c9f949fb58461b3f79.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "db_name": "SQLite", - "query": "\n SELECT epoch_id as \"epoch_id: u32\", serialised_signatures, serialization_revision as \"serialization_revision: u8\"\n FROM global_coin_index_signatures WHERE epoch_id = ?\n ", - "describe": { - "columns": [ - { - "name": "epoch_id: u32", - "ordinal": 0, - "type_info": "Integer" - }, - { - "name": "serialised_signatures", - "ordinal": 1, - "type_info": "Blob" - }, - { - "name": "serialization_revision: u8", - "ordinal": 2, - "type_info": "Integer" - } - ], - "parameters": { - "Right": 1 - }, - "nullable": [ - false, - false, - false - ] - }, - "hash": "a8b7ce0fe4755c28b96d1e503e313ab15fed747fb0cee1c9f949fb58461b3f79" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-b8257a0832d0124f0a8aaaf81dc6a811c593aea8febf1f891117e5e84213f147.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-b8257a0832d0124f0a8aaaf81dc6a811c593aea8febf1f891117e5e84213f147.json deleted file mode 100644 index 03efdc4734..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-b8257a0832d0124f0a8aaaf81dc6a811c593aea8febf1f891117e5e84213f147.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "SQLite", - "query": "\n DELETE FROM partial_blinded_wallet WHERE created < ?\n ", - "describe": { - "columns": [], - "parameters": { - "Right": 1 - }, - "nullable": [] - }, - "hash": "b8257a0832d0124f0a8aaaf81dc6a811c593aea8febf1f891117e5e84213f147" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-c2b841762bdb963fff337ef5c8ec9f560017b4da6b0303ea0397d9568229e167.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-c2b841762bdb963fff337ef5c8ec9f560017b4da6b0303ea0397d9568229e167.json deleted file mode 100644 index 6b70481c75..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-c2b841762bdb963fff337ef5c8ec9f560017b4da6b0303ea0397d9568229e167.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "db_name": "SQLite", - "query": "\n SELECT\n t1.node_id as \"node_id!\",\n t1.blinded_signature as \"blinded_signature!\",\n t1.epoch_id as \"epoch_id!\",\n t1.expiration_date as \"expiration_date!: Date\"\n FROM partial_blinded_wallet as t1\n JOIN ecash_deposit_usage as t2\n on t1.corresponding_deposit = t2.deposit_id\n JOIN blinded_shares as t3\n ON t2.request_uuid = t3.request_uuid\n WHERE t3.device_id = ? AND t3.credential_id = ?;\n ", - "describe": { - "columns": [ - { - "name": "node_id!", - "ordinal": 0, - "type_info": "Integer" - }, - { - "name": "blinded_signature!", - "ordinal": 1, - "type_info": "Blob" - }, - { - "name": "epoch_id!", - "ordinal": 2, - "type_info": "Integer" - }, - { - "name": "expiration_date!: Date", - "ordinal": 3, - "type_info": "Date" - } - ], - "parameters": { - "Right": 2 - }, - "nullable": [ - true, - true, - true, - true - ] - }, - "hash": "c2b841762bdb963fff337ef5c8ec9f560017b4da6b0303ea0397d9568229e167" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-d3510846941fa2525926b9bfbcdabd806877ce914b514d4f7cd6be318c4debe6.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-d3510846941fa2525926b9bfbcdabd806877ce914b514d4f7cd6be318c4debe6.json deleted file mode 100644 index 9de4c7f230..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-d3510846941fa2525926b9bfbcdabd806877ce914b514d4f7cd6be318c4debe6.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "SQLite", - "query": "INSERT INTO global_coin_index_signatures(epoch_id, serialised_signatures, serialization_revision) VALUES (?, ?, ?)", - "describe": { - "columns": [], - "parameters": { - "Right": 3 - }, - "nullable": [] - }, - "hash": "d3510846941fa2525926b9bfbcdabd806877ce914b514d4f7cd6be318c4debe6" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-db176e98198fe594d88eb860d918f633a94d18a19b7f0f96935a62560def7d0f.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-db176e98198fe594d88eb860d918f633a94d18a19b7f0f96935a62560def7d0f.json deleted file mode 100644 index e5dd7c8413..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-db176e98198fe594d88eb860d918f633a94d18a19b7f0f96935a62560def7d0f.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "SQLite", - "query": "\n INSERT INTO partial_blinded_wallet(corresponding_deposit, epoch_id, expiration_date, node_id, created, blinded_signature)\n VALUES (?, ?, ?, ?, ?, ?)\n ", - "describe": { - "columns": [], - "parameters": { - "Right": 6 - }, - "nullable": [] - }, - "hash": "db176e98198fe594d88eb860d918f633a94d18a19b7f0f96935a62560def7d0f" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-e584253e3856355899537eb8fc152f2bfed2d918b894ec0f588e38dd5e8ad726.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-e584253e3856355899537eb8fc152f2bfed2d918b894ec0f588e38dd5e8ad726.json deleted file mode 100644 index 340f83a280..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-e584253e3856355899537eb8fc152f2bfed2d918b894ec0f588e38dd5e8ad726.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "SQLite", - "query": "\n UPDATE ecash_deposit_usage\n SET ticketbook_request_error = ?\n WHERE deposit_id = ?\n ", - "describe": { - "columns": [], - "parameters": { - "Right": 2 - }, - "nullable": [] - }, - "hash": "e584253e3856355899537eb8fc152f2bfed2d918b894ec0f588e38dd5e8ad726" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-e77ffab19b099b84470fe5611716a2e314787586a46cffd074abb67f2f4d109e.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-e77ffab19b099b84470fe5611716a2e314787586a46cffd074abb67f2f4d109e.json deleted file mode 100644 index 449bc6869f..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-e77ffab19b099b84470fe5611716a2e314787586a46cffd074abb67f2f4d109e.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "db_name": "SQLite", - "query": "\n SELECT t1.node_id, t1.blinded_signature, t1.epoch_id, t1.expiration_date as \"expiration_date!: Date\"\n FROM partial_blinded_wallet as t1\n JOIN ecash_deposit_usage as t2\n on t1.corresponding_deposit = t2.deposit_id\n JOIN blinded_shares as t3\n ON t2.request_uuid = t3.request_uuid\n WHERE t3.id = ?;\n ", - "describe": { - "columns": [ - { - "name": "node_id", - "ordinal": 0, - "type_info": "Integer" - }, - { - "name": "blinded_signature", - "ordinal": 1, - "type_info": "Blob" - }, - { - "name": "epoch_id", - "ordinal": 2, - "type_info": "Integer" - }, - { - "name": "expiration_date!: Date", - "ordinal": 3, - "type_info": "Date" - } - ], - "parameters": { - "Right": 1 - }, - "nullable": [ - false, - false, - false, - false - ] - }, - "hash": "e77ffab19b099b84470fe5611716a2e314787586a46cffd074abb67f2f4d109e" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-ef60c2683211cc4ec2d3e46392518a1f62fa67dfe8f130deb876ebee11bf1602.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-ef60c2683211cc4ec2d3e46392518a1f62fa67dfe8f130deb876ebee11bf1602.json deleted file mode 100644 index c78ff30f50..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-ef60c2683211cc4ec2d3e46392518a1f62fa67dfe8f130deb876ebee11bf1602.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "db_name": "SQLite", - "query": "\n SELECT error_message\n FROM blinded_shares\n WHERE device_id = ? AND credential_id = ?;\n ", - "describe": { - "columns": [ - { - "name": "error_message", - "ordinal": 0, - "type_info": "Text" - } - ], - "parameters": { - "Right": 2 - }, - "nullable": [ - true - ] - }, - "hash": "ef60c2683211cc4ec2d3e46392518a1f62fa67dfe8f130deb876ebee11bf1602" -} diff --git a/nym-node-status-api/nym-node-status-api/Cargo.toml b/nym-node-status-api/nym-node-status-api/Cargo.toml index 28eb90c2c7..8d414c8817 100644 --- a/nym-node-status-api/nym-node-status-api/Cargo.toml +++ b/nym-node-status-api/nym-node-status-api/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "nym-node-status-api" -version = "4.0.12" +version = "4.0.14" authors.workspace = true repository.workspace = true homepage.workspace = true @@ -34,6 +34,7 @@ nym-mixnet-contract-common = { workspace = true, features = ["utoipa"] } nym-bin-common = { workspace = true, features = ["models"] } nym-node-status-client = { path = "../nym-node-status-client" } nym-crypto = { workspace = true, features = ["asymmetric", "serde"] } +nym-gateway-probe = { path = "../../nym-gateway-probe", features = ["utoipa"] } nym-http-api-client = { workspace = true } nym-http-api-common = { workspace = true, features = ["middleware"] } nym-network-defaults = { workspace = true } diff --git a/nym-node-status-api/nym-node-status-api/Dockerfile b/nym-node-status-api/nym-node-status-api/Dockerfile index 0c31f97e71..0b022bd350 100644 --- a/nym-node-status-api/nym-node-status-api/Dockerfile +++ b/nym-node-status-api/nym-node-status-api/Dockerfile @@ -1,6 +1,13 @@ # this will only work with VPN, otherwise remove the harbor part FROM harbor.nymte.ch/dockerhub/rust:latest AS builder +RUN apt update && apt install -yy libdbus-1-dev pkg-config libclang-dev + +# Install go (required for nym-gateway-probe dependency) +RUN wget https://go.dev/dl/go1.22.5.linux-amd64.tar.gz -O go.tar.gz +RUN tar -xzvf go.tar.gz -C /usr/local +ENV PATH=/go/bin:/usr/local/go/bin:$PATH + COPY ./ /usr/src/nym WORKDIR /usr/src/nym/nym-node-status-api/nym-node-status-api/ diff --git a/nym-node-status-api/nym-node-status-api/Makefile b/nym-node-status-api/nym-node-status-api/Makefile index 410e39661e..65e699d116 100644 --- a/nym-node-status-api/nym-node-status-api/Makefile +++ b/nym-node-status-api/nym-node-status-api/Makefile @@ -2,6 +2,8 @@ # --- Configuration --- TEST_DATABASE_URL := postgres://testuser:testpass@localhost:5433/nym_node_status_api_test +ENVIRONMENT ?= mainnet +MONOREPO_ROOT := $(shell realpath ../..) # Docker compose service names DB_SERVICE_NAME := postgres-test @@ -54,6 +56,20 @@ test-db-prepare: ## Run sqlx prepare for compile-time query verification @echo "Running sqlx prepare for PostgreSQL..." DATABASE_URL="$(TEST_DATABASE_URL)" cargo sqlx prepare +# --- Run Targets --- +.PHONY: run +run: ## Run the service (assumes database is already running) + @echo "Starting nym-node-status-api with $(ENVIRONMENT) environment..." + @set -a && source "$(MONOREPO_ROOT)/envs/$(ENVIRONMENT).env" && set +a && \ + DATABASE_URL="$(TEST_DATABASE_URL)" \ + NYM_API_CLIENT_TIMEOUT="$${NYM_API_CLIENT_TIMEOUT:-60}" \ + NODE_STATUS_API_AGENT_KEY_LIST="$${NODE_STATUS_API_AGENT_KEY_LIST:-H4z8kx5Kkf5JMQHhxaW1MwYndjKCDHC7HsVhHTFfBZ4J}" \ + RUST_LOG="$${RUST_LOG:-debug}" \ + cargo run --package nym-node-status-api + +.PHONY: run-with-db +run-with-db: dev-db run ## Start database and run the service + # --- Build and Test Targets --- .PHONY: test-db-run test-db-run: ## Run tests diff --git a/nym-node-status-api/nym-node-status-api/src/cli/mod.rs b/nym-node-status-api/nym-node-status-api/src/cli/mod.rs index 4883e95139..812f24dbf7 100644 --- a/nym-node-status-api/nym-node-status-api/src/cli/mod.rs +++ b/nym-node-status-api/nym-node-status-api/src/cli/mod.rs @@ -53,10 +53,19 @@ pub(crate) struct Cli { #[clap(long, env = "DATABASE_URL")] pub(crate) database_url: String, + /// SQLx pool acquire timeout in seconds (how long to wait for a connection) #[clap(long, default_value = "5", env = "SQLX_BUSY_TIMEOUT_S")] #[arg(value_parser = parse_duration_std)] pub(crate) sqlx_busy_timeout_s: Duration, + /// Maximum number of connections in the SQLx pool + #[clap(long, default_value_t = 20, env = "SQLX_MAX_CONNECTIONS")] + pub(crate) sqlx_max_connections: u32, + + /// Minimum number of connections to keep in the SQLx pool + #[clap(long, default_value_t = 5, env = "SQLX_MIN_CONNECTIONS")] + pub(crate) sqlx_min_connections: u32, + #[clap( long, default_value = "300", diff --git a/nym-node-status-api/nym-node-status-api/src/db/mod.rs b/nym-node-status-api/nym-node-status-api/src/db/mod.rs index 0205b4b64e..03fe202e3a 100644 --- a/nym-node-status-api/nym-node-status-api/src/db/mod.rs +++ b/nym-node-status-api/nym-node-status-api/src/db/mod.rs @@ -7,7 +7,11 @@ pub(crate) mod queries; #[cfg(test)] mod tests; -use sqlx::{ConnectOptions, PgPool, Postgres, migrate::Migrator, postgres::PgConnectOptions}; +use sqlx::{ + ConnectOptions, PgPool, Postgres, + migrate::Migrator, + postgres::{PgConnectOptions, PgPoolOptions}, +}; static MIGRATOR: Migrator = sqlx::migrate!("./migrations_pg"); @@ -21,7 +25,12 @@ pub(crate) struct Storage { } impl Storage { - pub async fn init(connection_url: String, _busy_timeout: Duration) -> Result { + pub async fn init( + connection_url: String, + acquire_timeout: Duration, + max_connections: u32, + min_connections: u32, + ) -> Result { use std::env; let mut connect_options = PgConnectOptions::from_str(&connection_url)?.disable_statement_logging(); @@ -33,7 +42,19 @@ impl Storage { .ssl_mode(sqlx::postgres::PgSslMode::Require) .ssl_root_cert(ssl_cert); } - let pool = sqlx::PgPool::connect_with(connect_options) + + tracing::info!( + "Initializing DB pool: max_connections={}, min_connections={}, acquire_timeout={}s", + max_connections, + min_connections, + acquire_timeout.as_secs() + ); + + let pool = PgPoolOptions::new() + .max_connections(max_connections) + .min_connections(min_connections) + .acquire_timeout(acquire_timeout) + .connect_with(connect_options) .await .map_err(|err| anyhow!("Failed to connect to {}: {}", &connection_url, err))?; diff --git a/nym-node-status-api/nym-node-status-api/src/http/models/gw_probe/mod.rs b/nym-node-status-api/nym-node-status-api/src/http/models/gw_probe/mod.rs new file mode 100644 index 0000000000..dbce0bdce6 --- /dev/null +++ b/nym-node-status-api/nym-node-status-api/src/http/models/gw_probe/mod.rs @@ -0,0 +1,447 @@ +use serde::Deserialize; +use serde::Serialize; +use strum::EnumString; +use tracing::error; +use utoipa::ToSchema; + +pub(crate) mod socks5_calc; +#[cfg(test)] +mod test; + +#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)] +pub struct LastProbeResult { + node: String, + used_entry: String, + outcome: ProbeOutcome, +} + +use nym_gateway_probe::types::ProbeResult as ProbeResultLatest; + +impl From for LastProbeResult { + fn from(value: ProbeResultLatest) -> Self { + Self { + node: value.node, + used_entry: value.used_entry, + outcome: value.outcome.into(), + } + } +} + +impl LastProbeResult { + pub(crate) fn deserialize_with_fallback(value: serde_json::Value) -> anyhow::Result { + // first try matching latest struct from GW probe crate + let mut probe_result = match serde_json::from_value::(value.clone()) { + Ok(probe_result) => probe_result.into(), + // as a fallback, try parsing struct from this crate + Err(_) => match serde_json::from_value::(value) { + Ok(probe_result) => probe_result, + Err(e) => { + error!("Failed to deserialize probe result: {e}"); + return Err(e.into()); + } + }, + }; + + probe_result.outcome.wg = probe_result.outcome.wg.clone().map(|mut wg| { + if wg.can_handshake.is_none() { + wg.can_handshake = Some(wg.can_handshake_v4); + } + if wg.can_resolve_dns.is_none() { + wg.can_resolve_dns = Some(wg.can_resolve_dns_v4); + } + if wg.ping_hosts_performance.is_none() { + wg.ping_hosts_performance = Some(wg.ping_hosts_performance_v4); + } + if wg.ping_ips_performance.is_none() { + wg.ping_ips_performance = Some(wg.ping_ips_performance_v4); + } + wg + }); + + Ok(probe_result) + } + + pub(crate) fn outcome(self) -> ProbeOutcome { + self.outcome + } +} + +/// gateway probe output returned on the API +#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)] +pub struct DvpnGwProbe { + last_updated_utc: String, + outcome: DvpnProbeOutcome, +} + +impl DvpnGwProbe { + pub fn from_outcome(outcome: DvpnProbeOutcome, last_updated_utc: String) -> Self { + Self { + last_updated_utc, + outcome, + } + } + + pub fn can_route_entry(&self) -> bool { + match &self.outcome.as_entry { + Entry::Tested(entry_test_result) => entry_test_result.can_route, + Entry::NotTested | Entry::EntryFailure => false, + } + } + + pub fn can_route_exit(&self) -> Option { + self.outcome + .as_exit + .as_ref() + .map(|outcome| outcome.can_route_ip_external_v4 && outcome.can_route_ip_external_v6) + } +} + +/// this structure is parsed on VPN API so it has some fields which must not be changed +#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)] +pub struct DvpnProbeOutcome { + pub as_entry: Entry, + pub as_exit: Option, + pub wg: Option, + pub socks5: Option, +} + +impl DvpnProbeOutcome { + pub fn from_raw_probe_outcome(outcome: ProbeOutcome, score: ScoreValue) -> Self { + let errors = outcome + .socks5 + .clone() + .and_then(|s| s.https_connectivity.errors); + let can_proxy_https = outcome + .socks5 + .map(|s| s.https_connectivity.https_success) + .unwrap_or_else(|| match score { + ScoreValue::Offline => false, + ScoreValue::Low | ScoreValue::Medium | ScoreValue::High => true, + }); + Self { + as_entry: outcome.as_entry.clone(), + as_exit: outcome.as_exit.clone(), + wg: outcome.wg.clone(), + socks5: Some(Socks5 { + can_proxy_https, + score, + errors, + }), + } + } +} + +#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)] +pub struct ProbeOutcome { + pub as_entry: Entry, + pub as_exit: Option, + pub wg: Option, + pub socks5: Option, +} + +use nym_gateway_probe::types::ProbeOutcome as ProbeOutcomeLatest; + +impl From for ProbeOutcome { + fn from(value: ProbeOutcomeLatest) -> Self { + Self { + as_entry: value.as_entry.into(), + as_exit: value.as_exit.map(From::from), + wg: value.wg.map(From::from), + socks5: value.socks5.map(From::from), + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] +#[serde(untagged)] +#[allow(clippy::enum_variant_names)] +pub enum Entry { + Tested(EntryTestResult), + NotTested, + EntryFailure, +} + +use nym_gateway_probe::types::Entry as EntryLatest; + +impl From for Entry { + fn from(value: EntryLatest) -> Self { + match value { + EntryLatest::Tested(entry_test_result) => Self::Tested(entry_test_result.into()), + EntryLatest::NotTested => Self::NotTested, + EntryLatest::EntryFailure => Self::EntryFailure, + } + } +} + +use nym_gateway_probe::types::EntryTestResult as EntryTestResultLatest; + +#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] +pub struct EntryTestResult { + pub can_connect: bool, + pub can_route: bool, +} + +impl From for EntryTestResult { + fn from(value: EntryTestResultLatest) -> Self { + Self { + can_connect: value.can_connect, + can_route: value.can_route, + } + } +} + +#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)] +pub struct Exit { + pub can_connect: bool, + pub can_route_ip_v4: bool, + pub can_route_ip_external_v4: bool, + pub can_route_ip_v6: bool, + pub can_route_ip_external_v6: bool, +} + +use nym_gateway_probe::types::Exit as ExitLatest; + +impl From for Exit { + fn from(value: ExitLatest) -> Self { + Self { + can_connect: value.can_connect, + can_route_ip_v4: value.can_route_ip_v4, + can_route_ip_external_v4: value.can_route_ip_external_v4, + can_route_ip_v6: value.can_route_ip_v6, + can_route_ip_external_v6: value.can_route_ip_external_v6, + } + } +} + +#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)] +pub struct WgProbeResults { + // mandatory fields + pub can_register: bool, + pub can_handshake: Option, + pub can_resolve_dns: Option, + pub ping_hosts_performance: Option, + pub ping_ips_performance: Option, + + pub can_query_metadata_v4: Option, + pub can_handshake_v4: bool, + pub can_resolve_dns_v4: bool, + pub ping_hosts_performance_v4: f32, + pub ping_ips_performance_v4: f32, + + pub can_handshake_v6: bool, + pub can_resolve_dns_v6: bool, + pub ping_hosts_performance_v6: f32, + pub ping_ips_performance_v6: f32, + + pub download_duration_sec_v4: u64, + pub download_duration_milliseconds_v4: Option, + pub downloaded_file_size_bytes_v4: Option, + pub downloaded_file_v4: String, + pub download_error_v4: String, + + pub download_duration_sec_v6: u64, + pub download_duration_milliseconds_v6: Option, + pub downloaded_file_size_bytes_v6: Option, + pub downloaded_file_v6: String, + pub download_error_v6: String, +} + +use nym_gateway_probe::types::WgProbeResults as WgProbeResultsLatest; + +use crate::http::models::Gateway; + +impl From for WgProbeResults { + fn from(value: WgProbeResultsLatest) -> Self { + Self { + can_register: value.can_register, + can_handshake: Some(value.can_handshake_v4), + can_resolve_dns: Some(value.can_resolve_dns_v4), + ping_hosts_performance: Some(value.ping_hosts_performance_v4), + ping_ips_performance: Some(value.ping_ips_performance_v4), + + can_query_metadata_v4: Some(value.can_query_metadata_v4), + can_handshake_v4: value.can_handshake_v4, + can_resolve_dns_v4: value.can_resolve_dns_v4, + ping_hosts_performance_v4: value.ping_hosts_performance_v4, + ping_ips_performance_v4: value.ping_ips_performance_v4, + + can_handshake_v6: value.can_handshake_v6, + can_resolve_dns_v6: value.can_resolve_dns_v6, + ping_hosts_performance_v6: value.ping_hosts_performance_v6, + ping_ips_performance_v6: value.ping_ips_performance_v6, + + download_duration_sec_v4: value.download_duration_sec_v4, + download_duration_milliseconds_v4: Some(value.download_duration_milliseconds_v4), + downloaded_file_size_bytes_v4: Some(value.downloaded_file_size_bytes_v4), + downloaded_file_v4: value.downloaded_file_v4, + download_error_v4: value.download_error_v4, + + download_duration_sec_v6: value.download_duration_sec_v6, + download_duration_milliseconds_v6: Some(value.download_duration_milliseconds_v6), + downloaded_file_size_bytes_v6: Some(value.downloaded_file_size_bytes_v6), + downloaded_file_v6: value.downloaded_file_v6, + download_error_v6: value.download_error_v6, + } + } +} + +struct NodeScore { + download_speed_score: f64, + ping_ips_score: f64, + mixnet_performance: f64, +} + +impl NodeScore { + // Weighted scoring: mixnet performance (40%), download speed (30%), ping performance (30%) + fn calculate_weighted_score(&self) -> f64 { + (self.mixnet_performance * 0.4) + + (self.download_speed_score * 0.3) + + (self.ping_ips_score * 0.3) + } +} + +#[derive(Debug, Clone, Deserialize, Serialize, ToSchema, EnumString)] +#[serde(rename_all = "snake_case")] +#[strum(serialize_all = "snake_case")] +#[derive(PartialEq)] +pub enum ScoreValue { + Offline, + Low, + Medium, + High, +} + +/// calculates a visual score for the gateway using weighted metrics +pub(super) fn calculate_score(gateway: &Gateway, probe_outcome: &LastProbeResult) -> ScoreValue { + let mixnet_performance = gateway.performance as f64 / 100.0; + + let node_score = probe_outcome + .outcome + .wg + .as_ref() + .map(|p| { + let ping_ips_performance = p.ping_ips_performance_v4 as f64; + + let duration_sec = + p.download_duration_milliseconds_v4 + .unwrap_or_else(|| p.download_duration_sec_v4 * 1000) as f64 + / 1000f64; + + // get the file size downloaded in bytes and convert to MB, or default to 1MB + let file_size_mb = + p.downloaded_file_size_bytes_v4.unwrap_or(1048576) as f64 / 1024f64 / 1024f64; + let speed_mbps = file_size_mb / duration_sec; + + let file_download_score = if speed_mbps > 5.0 { + 1.0 + } else if speed_mbps > 2.0 { + 0.75 + } else if speed_mbps > 1.0 { + 0.5 + } else if speed_mbps > 0.5 { + 0.25 + } else { + 0.1 + }; + + NodeScore { + download_speed_score: file_download_score, + ping_ips_score: ping_ips_performance, + mixnet_performance, + } + }) + .unwrap_or(NodeScore { + download_speed_score: 0.0, + ping_ips_score: 0.0, + mixnet_performance, + }); + + let weighted_score = node_score.calculate_weighted_score(); + + if weighted_score > 0.75 { + ScoreValue::High + } else if weighted_score > 0.5 { + ScoreValue::Medium + } else if weighted_score > 0.1 { + ScoreValue::Low + } else { + ScoreValue::Offline + } +} + +/// calculates a visual load score for the gateway +pub(super) fn calculate_load(probe_outcome: &LastProbeResult) -> ScoreValue { + let score = probe_outcome + .outcome + .wg + .clone() + .map(|p| p.ping_ips_performance_v4 as f64) + .unwrap_or(0f64); + + if score > 0.8 { + ScoreValue::Low + } else if score > 0.4 { + ScoreValue::Medium + } else { + ScoreValue::High + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] +pub struct Socks5 { + pub can_proxy_https: bool, + pub score: ScoreValue, + pub errors: Option>, +} + +use nym_gateway_probe::types::Socks5ProbeResults as Socks5ProbeResultsLatest; + +#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] +pub struct Socks5ProbeResults { + /// whether we could establish a SOCKS5 proxy connection + pub can_connect_socks5: bool, + + /// HTTPS connectivity test + pub https_connectivity: HttpsConnectivityResult, +} + +impl From for Socks5ProbeResults { + fn from(value: Socks5ProbeResultsLatest) -> Self { + Self { + can_connect_socks5: value.can_connect_socks5(), + https_connectivity: value.https_connectivity().clone().into(), + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] +pub struct HttpsConnectivityResult { + /// successfully completed HTTPS request + https_success: bool, + + /// HTTPS status code received + https_status_code: Option, + + /// average HTTPS request latency in milliseconds + https_latency_ms: Option, + + /// among multiple endpoints available, list the one actually used + endpoint_used: Option, + + /// error message(s) (if any) + errors: Option>, +} + +use nym_gateway_probe::types::HttpsConnectivityResult as HttpsConnectivityResultLatest; + +impl From for HttpsConnectivityResult { + fn from(value: HttpsConnectivityResultLatest) -> Self { + Self { + https_success: value.https_success(), + https_status_code: value.https_status_code().cloned(), + https_latency_ms: value.https_latency_ms().cloned(), + endpoint_used: value.endpoint_used().cloned(), + errors: value.errors().cloned(), + } + } +} diff --git a/nym-node-status-api/nym-node-status-api/src/http/models/gw_probe/socks5_calc.rs b/nym-node-status-api/nym-node-status-api/src/http/models/gw_probe/socks5_calc.rs new file mode 100644 index 0000000000..beb31a4ca7 --- /dev/null +++ b/nym-node-status-api/nym-node-status-api/src/http/models/gw_probe/socks5_calc.rs @@ -0,0 +1,289 @@ +use std::collections::HashMap; + +use tracing::warn; + +use crate::http::models::{ + Gateway, + gw_probe::{LastProbeResult, ScoreValue}, +}; + +pub(crate) fn calculate_socks5_percentiles(gateways: &[Gateway]) -> HashMap { + let parsed_gateways = gateways + .iter() + // discard untested gateways + .filter_map(|gw| { + gw.last_probe_result + .as_ref() + .map(|res| (gw.gateway_identity_key.clone(), res)) + }) + // discard unparsable probe results (error) + .filter_map(|(id, value)| { + LastProbeResult::deserialize_with_fallback(value.to_owned()) + .inspect_err(|err| warn!("Failed to deserialize probe result: {err}")) + .ok() + .map(|parsed| (id, parsed)) + }) + .map(|(id, res)| { + let latency = res + .outcome + .socks5 + // if socks5 is null, test failed or gw doesn't support it + .and_then(|socks5| socks5.https_connectivity.https_latency_ms) + .unwrap_or(0); + + (id, latency) + }) + .collect::>(); + + score_from_sorted_latencies(parsed_gateways) +} + +/// Assigns a score to each gateway based on their relative latency compared to +/// the whole set. Higher score = lower latency. +/// +/// - latency == 0 => Offline +/// - nonzero buckets: +/// - High = lowest 50% +/// - Medium = next 25% +/// - Low = worst 25% +pub fn score_from_sorted_latencies(gateways: Vec<(String, u64)>) -> HashMap { + // sort ascending + let mut gateways = gateways; + gateways.sort_by_cached_key(|(_, latency)| *latency); + + let (offline_gws, online_gws): (Vec<_>, Vec<_>) = + gateways.into_iter().partition(|(_, latency)| *latency == 0); + + let online_count = online_gws.len(); + + // x / 2 = 0.5x + let high_end_idx = online_count.div_ceil(2); + // 3x / 4 = 0.75x + let medium_end_idx = online_count.saturating_mul(3).div_ceil(4); + // `Low` gets assigned to everything else + + let mut result = HashMap::new(); + + // first flag all the offline gateways + for (id, _lat) in offline_gws { + result.entry(id).or_insert(ScoreValue::Offline); + } + + // secondly go over remaining non-zero elements, assign into buckets + for (idx, (id, _)) in online_gws.into_iter().enumerate() { + let score = if idx < high_end_idx { + ScoreValue::High + } else if idx < medium_end_idx { + ScoreValue::Medium + } else { + ScoreValue::Low + }; + + result.entry(id).or_insert(score); + } + + result +} + +#[cfg(test)] +mod socks5_score_calc_tests { + // clippy complains despite imports being used + #[allow(unused_imports)] + use super::*; + + #[test] + fn empty_input() { + let result = score_from_sorted_latencies(vec![]); + assert!(result.is_empty()); + } + + #[test] + fn all_offline() { + let items = vec![ + ("a".to_string(), 0), + ("b".to_string(), 0), + ("c".to_string(), 0), + ]; + let result = score_from_sorted_latencies(items); + + assert_eq!(result.len(), 3); + assert_eq!(result.get("a"), Some(&ScoreValue::Offline)); + assert_eq!(result.get("b"), Some(&ScoreValue::Offline)); + assert_eq!(result.get("c"), Some(&ScoreValue::Offline)); + } + + #[test] + fn single_zero() { + let items = vec![("a".to_string(), 0)]; + let result = score_from_sorted_latencies(items); + + assert_eq!(result.len(), 1); + assert_eq!(result.get("a"), Some(&ScoreValue::Offline)); + } + + #[test] + fn single_nonzero() { + // Single non-zero element: lowest 50% of 1 = ceil(1/2) = 1, so it's High + let items = vec![("a".to_string(), 100)]; + let result = score_from_sorted_latencies(items); + + assert_eq!(result.len(), 1); + assert_eq!(result.get("a"), Some(&ScoreValue::High)); + } + + #[test] + fn two_nonzero_elements() { + // 2 non-zero: high_end = ceil(2/2) = 1, medium_end = ceil(6/4) = 2 + // idx 0 -> High, idx 1 -> Medium + let items = vec![("a".to_string(), 100), ("b".to_string(), 200)]; + let result = score_from_sorted_latencies(items); + + assert_eq!(result.len(), 2); + assert_eq!(result.get("a"), Some(&ScoreValue::High)); + assert_eq!(result.get("b"), Some(&ScoreValue::Medium)); + } + + #[test] + fn mix_zeros_and_nonzeros() { + // Zeros become Offline, non-zeros get percentile scores + let items = vec![ + ("offline1".to_string(), 0), + ("fast".to_string(), 50), + ("offline2".to_string(), 0), + ("slow".to_string(), 200), + ]; + let result = score_from_sorted_latencies(items); + + assert_eq!(result.len(), 4); + assert_eq!(result.get("offline1"), Some(&ScoreValue::Offline)); + assert_eq!(result.get("offline2"), Some(&ScoreValue::Offline)); + // 2 non-zero: high_end = 1, medium_end = 2 + assert_eq!(result.get("fast"), Some(&ScoreValue::High)); + assert_eq!(result.get("slow"), Some(&ScoreValue::Medium)); + } + + #[test] + fn unsorted_input() { + // Input is unsorted, function should work regardless + let items = vec![ + ("slow".to_string(), 300), + ("fast".to_string(), 100), + ("medium".to_string(), 200), + ]; + let result = score_from_sorted_latencies(items); + + // 3 non-zero: high_end = ceil(3/2) = 2, medium_end = ceil(9/4) = 3 + // sorted: fast(100), medium(200), slow(300) + // idx 0,1 -> High, idx 2 -> Medium + assert_eq!(result.get("fast"), Some(&ScoreValue::High)); + assert_eq!(result.get("medium"), Some(&ScoreValue::High)); + assert_eq!(result.get("slow"), Some(&ScoreValue::Medium)); + } + + #[test] + fn duplicate_ids_keeps_first() { + // Duplicate IDs: first occurrence is kept + let items = vec![ + ("dup".to_string(), 100), // first occurrence, fast + ("other".to_string(), 200), + ("dup".to_string(), 300), // duplicate, slow - should be ignored + ]; + let result = score_from_sorted_latencies(items); + + // 3 non-zero: high_end = 2, medium_end = 3 + // sorted: dup(100), other(200), dup(300) + // idx 0 -> High (dup first), idx 1 -> High (other), idx 2 -> Medium (dup second, ignored) + assert_eq!(result.len(), 2); + assert_eq!(result.get("dup"), Some(&ScoreValue::High)); + assert_eq!(result.get("other"), Some(&ScoreValue::High)); + } + + #[test] + fn four_nonzero_clean_percentiles() { + // 4 non-zero: high_end = ceil(4/2) = 2, medium_end = ceil(12/4) = 3 + // idx 0,1 -> High (50%), idx 2 -> Medium (25%), idx 3 -> Low (25%) + let items = vec![ + ("a".to_string(), 100), + ("b".to_string(), 200), + ("c".to_string(), 300), + ("d".to_string(), 400), + ]; + let result = score_from_sorted_latencies(items); + + assert_eq!(result.len(), 4); + assert_eq!(result.get("a"), Some(&ScoreValue::High)); + assert_eq!(result.get("b"), Some(&ScoreValue::High)); + assert_eq!(result.get("c"), Some(&ScoreValue::Medium)); + assert_eq!(result.get("d"), Some(&ScoreValue::Low)); + } + + #[test] + fn eight_nonzero_clean_percentiles() { + // 8 non-zero: high_end = ceil(8/2) = 4, medium_end = ceil(24/4) = 6 + // idx 0-3 -> High, idx 4-5 -> Medium, idx 6-7 -> Low + let items: Vec<(String, u64)> = (1..=8) + .map(|i| (format!("node{}", i), i as u64 * 100)) + .collect(); + let result = score_from_sorted_latencies(items); + + assert_eq!(result.len(), 8); + assert_eq!(result.get("node1"), Some(&ScoreValue::High)); + assert_eq!(result.get("node2"), Some(&ScoreValue::High)); + assert_eq!(result.get("node3"), Some(&ScoreValue::High)); + assert_eq!(result.get("node4"), Some(&ScoreValue::High)); + assert_eq!(result.get("node5"), Some(&ScoreValue::Medium)); + assert_eq!(result.get("node6"), Some(&ScoreValue::Medium)); + assert_eq!(result.get("node7"), Some(&ScoreValue::Low)); + assert_eq!(result.get("node8"), Some(&ScoreValue::Low)); + } + + #[test] + fn five_nonzero_ceiling_division() { + // 5 non-zero: high_end = ceil(5/2) = 3, medium_end = ceil(15/4) = 4 + // idx 0,1,2 -> High (3), idx 3 -> Medium (1), idx 4 -> Low (1) + let items: Vec<(String, u64)> = (1..=5) + .map(|i| (format!("n{}", i), i as u64 * 10)) + .collect(); + let result = score_from_sorted_latencies(items); + + assert_eq!(result.get("n1"), Some(&ScoreValue::High)); + assert_eq!(result.get("n2"), Some(&ScoreValue::High)); + assert_eq!(result.get("n3"), Some(&ScoreValue::High)); + assert_eq!(result.get("n4"), Some(&ScoreValue::Medium)); + assert_eq!(result.get("n5"), Some(&ScoreValue::Low)); + } + + #[test] + fn seven_nonzero_ceiling_division() { + // 7 non-zero: high_end = ceil(7/2) = 4, medium_end = ceil(21/4) = 6 + // idx 0-3 -> High (4), idx 4-5 -> Medium (2), idx 6 -> Low (1) + let items: Vec<(String, u64)> = (1..=7) + .map(|i| (format!("n{}", i), i as u64 * 10)) + .collect(); + let result = score_from_sorted_latencies(items); + + assert_eq!(result.get("n1"), Some(&ScoreValue::High)); + assert_eq!(result.get("n2"), Some(&ScoreValue::High)); + assert_eq!(result.get("n3"), Some(&ScoreValue::High)); + assert_eq!(result.get("n4"), Some(&ScoreValue::High)); + assert_eq!(result.get("n5"), Some(&ScoreValue::Medium)); + assert_eq!(result.get("n6"), Some(&ScoreValue::Medium)); + assert_eq!(result.get("n7"), Some(&ScoreValue::Low)); + } + + #[test] + fn three_nonzero() { + // 3 non-zero: high_end = ceil(3/2) = 2, medium_end = ceil(9/4) = 3 + // idx 0,1 -> High, idx 2 -> Medium (no Low bucket) + let items = vec![ + ("a".to_string(), 100), + ("b".to_string(), 200), + ("c".to_string(), 300), + ]; + let result = score_from_sorted_latencies(items); + + assert_eq!(result.get("a"), Some(&ScoreValue::High)); + assert_eq!(result.get("b"), Some(&ScoreValue::High)); + assert_eq!(result.get("c"), Some(&ScoreValue::Medium)); + } +} diff --git a/nym-node-status-api/nym-node-status-api/src/http/models/gw_probe/test.rs b/nym-node-status-api/nym-node-status-api/src/http/models/gw_probe/test.rs new file mode 100644 index 0000000000..66bae5605b --- /dev/null +++ b/nym-node-status-api/nym-node-status-api/src/http/models/gw_probe/test.rs @@ -0,0 +1,533 @@ +use crate::http::models::Gateway; + +use super::*; + +#[test] +fn test_weighted_score_calculation() { + // Helper function to create a test gateway + fn create_test_gateway(performance: u8) -> Gateway { + Gateway { + gateway_identity_key: "test_key".to_string(), + bonded: true, + performance, + self_described: None, + explorer_pretty_bond: None, + description: nym_node_requests::api::v1::node::models::NodeDescription { + moniker: "test".to_string(), + details: "test".to_string(), + security_contact: "test@example.com".to_string(), + website: "https://example.com".to_string(), + }, + last_probe_result: None, + last_probe_log: None, + last_testrun_utc: None, + last_updated_utc: "2025-10-10T00:00:00Z".to_string(), + routing_score: 0.0, + config_score: 0, + bridges: None, + } + } + + // Helper function to create a test probe outcome + fn create_test_probe_outcome( + download_speed_mbps: f64, + ping_ips_performance: f32, + ) -> LastProbeResult { + let duration_sec = 1.0; + let file_size_mb = download_speed_mbps; + + LastProbeResult { + node: "test_node".to_string(), + used_entry: "test_entry".to_string(), + outcome: ProbeOutcome { + as_entry: Entry::Tested(EntryTestResult { + can_connect: true, + can_route: true, + }), + as_exit: None, + wg: Some(WgProbeResults { + can_register: true, + can_handshake: Some(true), + can_resolve_dns: Some(true), + ping_hosts_performance: Some(ping_ips_performance), + ping_ips_performance: Some(ping_ips_performance), + can_query_metadata_v4: Some(true), + can_handshake_v4: true, + can_resolve_dns_v4: true, + ping_hosts_performance_v4: ping_ips_performance, + ping_ips_performance_v4: ping_ips_performance, + can_handshake_v6: true, + can_resolve_dns_v6: true, + ping_hosts_performance_v6: ping_ips_performance, + ping_ips_performance_v6: ping_ips_performance, + download_duration_sec_v4: (duration_sec * 1000.0) as u64, + download_duration_milliseconds_v4: Some((duration_sec * 1000.0) as u64), + downloaded_file_size_bytes_v4: Some((file_size_mb * 1024.0 * 1024.0) as u64), + downloaded_file_v4: "test".to_string(), + download_error_v4: "".to_string(), + download_duration_sec_v6: 0, + download_duration_milliseconds_v6: Some(0), + downloaded_file_size_bytes_v6: Some(0), + downloaded_file_v6: "".to_string(), + download_error_v6: "".to_string(), + }), + // TODO dz test this as well + socks5: None, + }, + } + } + + // Test case 1: Excellent node (should be High) + let gateway = create_test_gateway(90); // 90% mixnet performance + let probe = create_test_probe_outcome(6.0, 1.0); // 6 Mbps, 100% ping + let score = calculate_score(&gateway, &probe); + assert_eq!(score, ScoreValue::High, "Excellent node should be High"); + + // Test case 2: Good node (should be High with weighted scoring) + let gateway = create_test_gateway(90); // 90% mixnet performance + let probe = create_test_probe_outcome(3.0, 0.9); // 3 Mbps (0.75 score), 90% ping + let score = calculate_score(&gateway, &probe); + assert_eq!( + score, + ScoreValue::High, + "Good node should be High with weighted scoring" + ); + + // Test case 3: Medium node + let gateway = create_test_gateway(80); // 80% mixnet performance + let probe = create_test_probe_outcome(1.5, 0.8); // 1.5 Mbps (0.5 score), 80% ping + let score = calculate_score(&gateway, &probe); + assert_eq!(score, ScoreValue::Medium, "Medium node should be Medium"); + + // Test case 4: Poor node + let gateway = create_test_gateway(60); // 60% mixnet performance + let probe = create_test_probe_outcome(0.3, 0.3); // 0.3 Mbps (0.1 score), 30% ping + let score = calculate_score(&gateway, &probe); + assert_eq!(score, ScoreValue::Low, "Poor node should be Low"); + + // Test case 5: Failed node + let gateway = create_test_gateway(10); // 10% mixnet performance + let probe = create_test_probe_outcome(0.1, 0.0); // 0.1 Mbps (0.1 score), 0% ping + let score = calculate_score(&gateway, &probe); + assert_eq!(score, ScoreValue::Offline, "Failed node should be Offline"); + + // Test case 6: Edge case - just above threshold + let gateway = create_test_gateway(76); // 76% mixnet performance + let probe = create_test_probe_outcome(2.1, 0.75); // 2.1 Mbps (0.75 score), 75% ping + let score = calculate_score(&gateway, &probe); + // Weighted: (0.76 * 0.4) + (0.75 * 0.3) + (0.75 * 0.3) = 0.304 + 0.225 + 0.225 = 0.754 + assert_eq!( + score, + ScoreValue::High, + "Edge case just above 0.75 threshold should be High" + ); +} + +/// Smoke test to ensure conversion from nym_gateway_probe crate's ProbeResult +/// to this crate's ProbeResult doesn't silently drop or default fields when +/// nym_gateway_probe types change. +/// +/// All values are set to non-default (booleans=true, numbers non-zero, strings non-empty) +/// to catch cases where new fields might be left as default after conversion. +#[test] +fn conversion_from_gw_probe_latest() { + use nym_gateway_probe::types::{ + Entry as EntryLatest, EntryTestResult as EntryTestResultLatest, Exit as ExitLatest, + ProbeOutcome as ProbeOutcomeLatest, ProbeResult as ProbeResultLatest, + WgProbeResults as WgProbeResultsLatest, + }; + + // Build a ProbeResultLatest with ALL non-default values + let wg_latest = WgProbeResultsLatest { + can_register: true, + can_query_metadata_v4: true, + can_handshake_v4: true, + can_resolve_dns_v4: true, + ping_hosts_performance_v4: 0.95, + ping_ips_performance_v4: 0.92, + can_handshake_v6: true, + can_resolve_dns_v6: true, + ping_hosts_performance_v6: 0.88, + ping_ips_performance_v6: 0.85, + download_duration_sec_v4: 5, + download_duration_milliseconds_v4: 5123, + downloaded_file_size_bytes_v4: 10485760, + downloaded_file_v4: "test-file-v4.bin".to_string(), + download_error_v4: "none-v4".to_string(), + download_duration_sec_v6: 6, + download_duration_milliseconds_v6: 6234, + downloaded_file_size_bytes_v6: 20971520, + downloaded_file_v6: "test-file-v6.bin".to_string(), + download_error_v6: "none-v6".to_string(), + }; + let probe_latest = ProbeResultLatest { + node: "test-node-identity-key".to_string(), + used_entry: "test-entry-node".to_string(), + outcome: ProbeOutcomeLatest { + as_entry: EntryLatest::Tested(EntryTestResultLatest { + can_connect: true, + can_route: true, + }), + as_exit: Some(ExitLatest { + can_connect: true, + can_route_ip_v4: true, + can_route_ip_external_v4: true, + can_route_ip_v6: true, + can_route_ip_external_v6: true, + }), + socks5: None, + lp: None, + wg: Some(wg_latest.clone()), + }, + }; + + // convert to this crate's LastProbeResult + let result: LastProbeResult = probe_latest.clone().into(); + + assert_eq!(result.node, probe_latest.node); + assert_eq!(result.used_entry, probe_latest.used_entry); + + match &result.outcome.as_entry { + Entry::Tested(entry) => { + assert!(entry.can_connect); + assert!(entry.can_route); + } + other => panic!("Expected Entry::Tested, got {:?}", other), + } + + // Exit conversion + let exit = result + .outcome + .as_exit + .as_ref() + .expect("as_exit should be Some"); + assert!(exit.can_connect); + assert!(exit.can_route_ip_v4); + assert!(exit.can_route_ip_external_v4,); + assert!(exit.can_route_ip_v6); + assert!(exit.can_route_ip_external_v6,); + + // WgProbeResults conversion + let wg = result.outcome.wg.as_ref().expect("wg should be Some"); + assert!(wg.can_register); + assert_eq!(wg.can_query_metadata_v4, Some(true),); + assert!(wg.can_handshake_v4); + assert!(wg.can_resolve_dns_v4,); + assert_eq!( + wg.ping_hosts_performance_v4, + wg_latest.ping_hosts_performance_v4 + ); + assert_eq!( + wg.ping_ips_performance_v4, + wg_latest.ping_ips_performance_v4 + ); + assert!(wg.can_handshake_v6); + assert!(wg.can_resolve_dns_v6); + assert_eq!( + wg.ping_hosts_performance_v6, + wg_latest.ping_hosts_performance_v6 + ); + assert_eq!( + wg.ping_ips_performance_v6, + wg_latest.ping_ips_performance_v6 + ); + assert_eq!( + wg.download_duration_sec_v4, + wg_latest.download_duration_sec_v4 + ); + assert_eq!( + wg.download_duration_milliseconds_v4, + Some(wg_latest.download_duration_milliseconds_v4), + ); + assert_eq!( + wg.downloaded_file_size_bytes_v4, + Some(wg_latest.downloaded_file_size_bytes_v4), + ); + assert_eq!(wg.downloaded_file_v4, wg_latest.downloaded_file_v4); + assert_eq!(wg.download_error_v4, wg_latest.download_error_v4); + assert_eq!( + wg.download_duration_sec_v6, + wg_latest.download_duration_sec_v6 + ); + assert_eq!( + wg.download_duration_milliseconds_v6, + Some(wg_latest.download_duration_milliseconds_v6) + ); + assert_eq!( + wg.downloaded_file_size_bytes_v6, + Some(wg_latest.downloaded_file_size_bytes_v6) + ); + assert_eq!(wg.downloaded_file_v6, wg_latest.downloaded_file_v6); + assert_eq!(wg.download_error_v6, wg_latest.download_error_v6); + + // fields that map from v4 values + assert_eq!(wg.can_handshake, Some(true)); + assert_eq!(wg.can_resolve_dns, Some(true)); + assert_eq!( + wg.ping_hosts_performance, + Some(wg_latest.ping_hosts_performance_v4) + ); + assert_eq!( + wg.ping_ips_performance, + Some(wg_latest.ping_ips_performance_v4) + ); +} + +#[test] +fn conversion_entry_variants() { + use nym_gateway_probe::types::Entry as EntryLatest; + + let not_tested: Entry = EntryLatest::NotTested.into(); + assert!(matches!(not_tested, Entry::NotTested)); + + let failure: Entry = EntryLatest::EntryFailure.into(); + assert!(matches!(failure, Entry::EntryFailure)); +} + +/// Backwards compatibility: this crate's struct may be present in DB of +/// some gateways even after the new nym_gateway_probe format is published. +/// DB entry needs to stay deserializable into a valid struct. +#[test] +fn deserialize_this_crate_format() { + // JSON that matches this crate's ProbeResult format (not nym_gateway_probe) + let old_format_json = serde_json::json!({ + "node": "old-node-key", + "used_entry": "old-entry-key", + "outcome": { + "as_entry": { + "can_connect": true, + "can_route": false + }, + "as_exit": null, + "wg": null + } + }); + + let result = LastProbeResult::deserialize_with_fallback(old_format_json) + .expect("Should deserialize old format"); + + assert_eq!(result.node, "old-node-key"); + assert_eq!(result.used_entry, "old-entry-key"); + match &result.outcome.as_entry { + Entry::Tested(entry) => { + assert!(entry.can_connect); + assert!(!entry.can_route); + } + other => panic!("Expected Entry::Tested, got {:?}", other), + } + assert!(result.outcome.as_exit.is_none()); + assert!(result.outcome.wg.is_none()); +} + +/// Test that the latest nym_gateway_probe format deserializes correctly +#[test] +fn deserialize_latest_gw_probe_format() { + // JSON that matches nym_gateway_probe::types::ProbeResult format + let latest_format_json = serde_json::json!({ + "node": "new-node-key", + "used_entry": "new-entry-key", + "outcome": { + "as_entry": { + "can_connect": true, + "can_route": true + }, + "as_exit": { + "can_connect": true, + "can_route_ip_v4": true, + "can_route_ip_external_v4": true, + "can_route_ip_v6": false, + "can_route_ip_external_v6": false + }, + "socks5": null, + "lp": null, + "wg": { + "can_register": true, + "can_query_metadata_v4": true, + "can_handshake_v4": true, + "can_resolve_dns_v4": true, + "ping_hosts_performance_v4": 0.9, + "ping_ips_performance_v4": 0.85, + "can_handshake_v6": false, + "can_resolve_dns_v6": false, + "ping_hosts_performance_v6": 0.0, + "ping_ips_performance_v6": 0.0, + "download_duration_sec_v4": 3, + "download_duration_milliseconds_v4": 3456, + "downloaded_file_size_bytes_v4": 5242880, + "downloaded_file_v4": "5mb.bin", + "download_error_v4": "", + "download_duration_sec_v6": 0, + "download_duration_milliseconds_v6": 0, + "downloaded_file_size_bytes_v6": 0, + "downloaded_file_v6": "", + "download_error_v6": "ipv6 not supported" + } + } + }); + + let result = LastProbeResult::deserialize_with_fallback(latest_format_json) + .expect("Should deserialize latest format"); + + assert_eq!(result.node, "new-node-key"); + assert_eq!(result.used_entry, "new-entry-key"); + + let exit = result.outcome.as_exit.as_ref().expect("should have exit"); + assert!(exit.can_route_ip_external_v4); + assert!(!exit.can_route_ip_external_v6); + + let wg = result.outcome.wg.as_ref().expect("should have wg"); + assert!(wg.can_register); + assert_eq!(wg.download_duration_milliseconds_v4, Some(3456)); + assert_eq!(wg.download_error_v6, "ipv6 not supported"); +} + +/// Serialize LastProbeResult to JSON and back to ensure serde attributes +/// work correctly and no data is lost. +#[test] +fn round_trip_serialization() { + let original = LastProbeResult { + node: "round-trip-node".to_string(), + used_entry: "round-trip-entry".to_string(), + outcome: ProbeOutcome { + as_entry: Entry::Tested(EntryTestResult { + can_connect: true, + can_route: true, + }), + as_exit: Some(Exit { + can_connect: true, + can_route_ip_v4: true, + can_route_ip_external_v4: true, + can_route_ip_v6: true, + can_route_ip_external_v6: true, + }), + wg: Some(WgProbeResults { + can_register: true, + can_handshake: Some(true), + can_resolve_dns: Some(true), + ping_hosts_performance: Some(0.95), + ping_ips_performance: Some(0.92), + can_query_metadata_v4: Some(true), + can_handshake_v4: true, + can_resolve_dns_v4: true, + ping_hosts_performance_v4: 0.95, + ping_ips_performance_v4: 0.92, + can_handshake_v6: true, + can_resolve_dns_v6: true, + ping_hosts_performance_v6: 0.88, + ping_ips_performance_v6: 0.85, + download_duration_sec_v4: 5, + download_duration_milliseconds_v4: Some(5123), + downloaded_file_size_bytes_v4: Some(10485760), + downloaded_file_v4: "test-file-v4.bin".to_string(), + download_error_v4: "none-v4".to_string(), + download_duration_sec_v6: 6, + download_duration_milliseconds_v6: Some(6234), + downloaded_file_size_bytes_v6: Some(20971520), + downloaded_file_v6: "test-file-v6.bin".to_string(), + download_error_v6: "none-v6".to_string(), + }), + // TODO dz test this as well + socks5: None, + }, + }; + + // Serialize to JSON + let json_string = + serde_json::to_string(&original).expect("Should serialize LastProbeResult to JSON"); + + // Deserialize back + let deserialized: LastProbeResult = + serde_json::from_str(&json_string).expect("Should deserialize JSON to LastProbeResult"); + + // Verify top-level fields + assert_eq!(deserialized.node, original.node); + assert_eq!(deserialized.used_entry, original.used_entry); + + // Verify Entry + match (&original.outcome.as_entry, &deserialized.outcome.as_entry) { + (Entry::Tested(orig), Entry::Tested(deser)) => { + assert_eq!(orig.can_connect, deser.can_connect); + assert_eq!(orig.can_route, deser.can_route); + } + _ => panic!("Entry mismatch after round-trip"), + } + + // Verify Exit + let orig_exit = original.outcome.as_exit.as_ref().unwrap(); + let deser_exit = deserialized.outcome.as_exit.as_ref().unwrap(); + assert_eq!(orig_exit.can_connect, deser_exit.can_connect); + assert_eq!(orig_exit.can_route_ip_v4, deser_exit.can_route_ip_v4); + assert_eq!( + orig_exit.can_route_ip_external_v4, + deser_exit.can_route_ip_external_v4 + ); + assert_eq!(orig_exit.can_route_ip_v6, deser_exit.can_route_ip_v6); + assert_eq!( + orig_exit.can_route_ip_external_v6, + deser_exit.can_route_ip_external_v6 + ); + + // Verify WgProbeResults + let orig_wg = original.outcome.wg.as_ref().unwrap(); + let deser_wg = deserialized.outcome.wg.as_ref().unwrap(); + assert_eq!(orig_wg.can_register, deser_wg.can_register); + assert_eq!(orig_wg.can_handshake, deser_wg.can_handshake); + assert_eq!(orig_wg.can_resolve_dns, deser_wg.can_resolve_dns); + assert_eq!( + orig_wg.ping_hosts_performance, + deser_wg.ping_hosts_performance + ); + assert_eq!(orig_wg.ping_ips_performance, deser_wg.ping_ips_performance); + assert_eq!( + orig_wg.can_query_metadata_v4, + deser_wg.can_query_metadata_v4 + ); + assert_eq!(orig_wg.can_handshake_v4, deser_wg.can_handshake_v4); + assert_eq!(orig_wg.can_resolve_dns_v4, deser_wg.can_resolve_dns_v4); + assert_eq!( + orig_wg.ping_hosts_performance_v4, + deser_wg.ping_hosts_performance_v4 + ); + assert_eq!( + orig_wg.ping_ips_performance_v4, + deser_wg.ping_ips_performance_v4 + ); + assert_eq!(orig_wg.can_handshake_v6, deser_wg.can_handshake_v6); + assert_eq!(orig_wg.can_resolve_dns_v6, deser_wg.can_resolve_dns_v6); + assert_eq!( + orig_wg.ping_hosts_performance_v6, + deser_wg.ping_hosts_performance_v6 + ); + assert_eq!( + orig_wg.ping_ips_performance_v6, + deser_wg.ping_ips_performance_v6 + ); + assert_eq!( + orig_wg.download_duration_sec_v4, + deser_wg.download_duration_sec_v4 + ); + assert_eq!( + orig_wg.download_duration_milliseconds_v4, + deser_wg.download_duration_milliseconds_v4 + ); + assert_eq!( + orig_wg.downloaded_file_size_bytes_v4, + deser_wg.downloaded_file_size_bytes_v4 + ); + assert_eq!(orig_wg.downloaded_file_v4, deser_wg.downloaded_file_v4); + assert_eq!(orig_wg.download_error_v4, deser_wg.download_error_v4); + assert_eq!( + orig_wg.download_duration_sec_v6, + deser_wg.download_duration_sec_v6 + ); + assert_eq!( + orig_wg.download_duration_milliseconds_v6, + deser_wg.download_duration_milliseconds_v6 + ); + assert_eq!( + orig_wg.downloaded_file_size_bytes_v6, + deser_wg.downloaded_file_size_bytes_v6 + ); + assert_eq!(orig_wg.downloaded_file_v6, deser_wg.downloaded_file_v6); + assert_eq!(orig_wg.download_error_v6, deser_wg.download_error_v6); +} diff --git a/nym-node-status-api/nym-node-status-api/src/http/models.rs b/nym-node-status-api/nym-node-status-api/src/http/models/mod.rs similarity index 61% rename from nym-node-status-api/nym-node-status-api/src/http/models.rs rename to nym-node-status-api/nym-node-status-api/src/http/models/mod.rs index cce5f0cf8b..6fd599ba6e 100644 --- a/nym-node-status-api/nym-node-status-api/src/http/models.rs +++ b/nym-node-status-api/nym-node-status-api/src/http/models/mod.rs @@ -1,6 +1,11 @@ use std::net::IpAddr; -use crate::monitor::ExplorerPrettyBond; +use crate::{ + http::models::gw_probe::{ + DvpnGwProbe, DvpnProbeOutcome, LastProbeResult, ScoreValue, calculate_load, calculate_score, + }, + monitor::ExplorerPrettyBond, +}; use cosmwasm_std::{Addr, Coin, Decimal}; use nym_mixnet_contract_common::CoinSchema; use nym_node_requests::api::v1::node::models::NodeDescription; @@ -17,8 +22,11 @@ use utoipa::ToSchema; use crate::db::models::NymNodeDataDeHelper; use crate::node_scraper::models::BridgeInformation; + pub(crate) use nym_node_status_client::models::TestrunAssignment; +pub(crate) mod gw_probe; + #[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] pub struct Gateway { pub gateway_identity_key: String, @@ -75,17 +83,6 @@ pub struct Location { pub asn: Option, } -#[derive(Debug, Clone, Deserialize, Serialize, ToSchema, EnumString)] -#[serde(rename_all = "snake_case")] -#[strum(serialize_all = "snake_case")] -#[derive(PartialEq)] -pub enum ScoreValue { - Offline, - Low, - Medium, - High, -} - #[derive(Debug, Clone, Deserialize, Serialize, ToSchema)] pub struct DVpnGatewayPerformance { last_updated_utc: String, @@ -103,7 +100,7 @@ pub struct DVpnGateway { pub ip_packet_router: Option, pub authenticator: Option, pub location: Location, - pub last_probe: Option, + pub last_probe: Option, #[schema(value_type = Vec)] pub ip_addresses: Vec, pub mix_port: u16, @@ -126,124 +123,25 @@ impl DVpnGateway { pub fn can_route_entry(&self) -> bool { self.last_probe .as_ref() - .map(|probe| match &probe.outcome.as_entry { - directory_gw_probe_outcome::Entry::Tested(entry_test_result) => { - entry_test_result.can_route - } - directory_gw_probe_outcome::Entry::NotTested - | directory_gw_probe_outcome::Entry::EntryFailure => false, - }) + .map(DvpnGwProbe::can_route_entry) .unwrap_or(false) } pub fn can_route_exit(&self) -> bool { self.last_probe .as_ref() - .map(|probe| { - probe - .outcome - .as_exit - .as_ref() - .map(|outcome| { - outcome.can_route_ip_external_v4 && outcome.can_route_ip_external_v6 - }) - .unwrap_or(false) - }) + .map(|probe| probe.can_route_exit().unwrap_or(false)) .unwrap_or(false) } } -/// based on -/// https://github.com/nymtech/nym-vpn-client/blob/nym-vpn-core-v1.10.0/nym-vpn-core/crates/nym-gateway-probe/src/types.rs -/// TODO: long term types should be moved into this repo because nym-vpn-client -/// could pull it as a dependency and we'd have a single source of truth -#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)] -pub struct LastProbeResult { - node: String, - used_entry: String, - outcome: ProbeOutcome, -} - -#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)] -pub struct DirectoryGwProbe { - last_updated_utc: String, - outcome: ProbeOutcome, -} - -#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)] -pub struct ProbeOutcome { - as_entry: directory_gw_probe_outcome::Entry, - as_exit: Option, - wg: Option, -} - -pub mod directory_gw_probe_outcome { - use super::*; - - #[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] - #[serde(untagged)] - #[allow(clippy::enum_variant_names)] - pub enum Entry { - Tested(EntryTestResult), - NotTested, - EntryFailure, - } - - #[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] - pub struct EntryTestResult { - pub can_connect: bool, - pub can_route: bool, - } - - #[derive(Debug, Clone, Deserialize, Serialize, ToSchema)] - pub struct Exit { - pub can_connect: bool, - pub can_route_ip_v4: bool, - pub can_route_ip_external_v4: bool, - pub can_route_ip_v6: bool, - pub can_route_ip_external_v6: bool, - } -} - -pub mod wg_outcome_versions { - use super::*; - - #[derive(Debug, Clone, Deserialize, Serialize, ToSchema)] - pub struct ProbeOutcomeV1 { - pub can_register: bool, - pub can_handshake: Option, - pub can_resolve_dns: Option, - pub ping_hosts_performance: Option, - pub ping_ips_performance: Option, - - pub can_query_metadata_v4: Option, - pub can_handshake_v4: bool, - pub can_resolve_dns_v4: bool, - pub ping_hosts_performance_v4: f32, - pub ping_ips_performance_v4: f32, - - pub can_handshake_v6: bool, - pub can_resolve_dns_v6: bool, - pub ping_hosts_performance_v6: f32, - pub ping_ips_performance_v6: f32, - - pub download_duration_sec_v4: u64, - pub download_duration_milliseconds_v4: Option, - pub downloaded_file_size_bytes_v4: Option, - pub downloaded_file_v4: String, - pub download_error_v4: String, - - pub download_duration_sec_v6: u64, - pub download_duration_milliseconds_v6: Option, - pub downloaded_file_size_bytes_v6: Option, - pub downloaded_file_v6: String, - pub download_error_v6: String, - } -} - impl DVpnGateway { #[instrument(level = tracing::Level::INFO, name = "dvpn_gw_new", skip_all, fields(gateway_key = gateway.gateway_identity_key, node_id = skimmed_node.node_id))] - pub(crate) fn new(gateway: Gateway, skimmed_node: &SkimmedNode) -> anyhow::Result { + pub(crate) fn new( + gateway: Gateway, + skimmed_node: &SkimmedNode, + socks5_score: Option<&ScoreValue>, + ) -> anyhow::Result { let location = gateway .explorer_pretty_bond .clone() @@ -275,35 +173,22 @@ impl DVpnGateway { .ok() }); - tracing::info!("🌈 gateway probe result: {:?}", gateway.last_probe_result); + tracing::debug!("🌈 gateway probe result: {:?}", gateway.last_probe_result); let (last_probe_result, performance_v2) = match gateway.last_probe_result { Some(ref value) => { - let mut parsed = serde_json::from_value::(value.clone()) + let parsed = LastProbeResult::deserialize_with_fallback(value.clone()) .inspect_err(|err| { error!("Failed to deserialize probe result: {err}"); })?; - parsed.outcome.wg = parsed.outcome.wg.clone().map(|mut wg| { - if wg.can_handshake.is_none() { - wg.can_handshake = Some(wg.can_handshake_v4); - } - if wg.can_resolve_dns.is_none() { - wg.can_resolve_dns = Some(wg.can_resolve_dns_v4); - } - if wg.ping_hosts_performance.is_none() { - wg.ping_hosts_performance = Some(wg.ping_hosts_performance_v4); - } - if wg.ping_ips_performance.is_none() { - wg.ping_ips_performance = Some(wg.ping_ips_performance_v4); - } - wg - }); - - tracing::info!("🌈 gateway probe parsed: {:?}", parsed); + tracing::trace!("🌈 gateway probe parsed: {:?}", parsed); let mixnet_score = calculate_mixnet_score(&gateway); let score = calculate_score(&gateway, &parsed); let mut load = calculate_load(&parsed); + let socks5_score = socks5_score.unwrap_or(&ScoreValue::Offline).to_owned(); + let dvpn_probe_result = + DvpnProbeOutcome::from_raw_probe_outcome(parsed.outcome(), socks5_score); // clamp the load value to offline, when the score is offline if score == ScoreValue::Offline { @@ -319,7 +204,7 @@ impl DVpnGateway { // the network monitor's measure is a good proxy for node uptime, it can be improved in the future uptime_percentage_last_24_hours: network_monitor_performance_mixnet_mode, }; - (Some(parsed), Some(performance_v2)) + (Some(dvpn_probe_result), Some(performance_v2)) } None => (None, None), }; @@ -356,10 +241,8 @@ impl DVpnGateway { } }), }, - last_probe: last_probe_result.map(|res| DirectoryGwProbe { - last_updated_utc: last_updated_utc.to_string(), - outcome: res.outcome, - }), + last_probe: last_probe_result + .map(|res| DvpnGwProbe::from_outcome(res, last_updated_utc)), ip_addresses: skimmed_node.ip_addresses.clone(), mix_port: skimmed_node.mix_port, role: skimmed_node.role.clone(), @@ -372,21 +255,6 @@ impl DVpnGateway { } } -struct NodeScore { - download_speed_score: f64, - ping_ips_score: f64, - mixnet_performance: f64, -} - -impl NodeScore { - // Weighted scoring: mixnet performance (40%), download speed (30%), ping performance (30%) - fn calculate_weighted_score(&self) -> f64 { - (self.mixnet_performance * 0.4) - + (self.download_speed_score * 0.3) - + (self.ping_ips_score * 0.3) - } -} - /// calculates the gateway probe score for mixnet mode fn calculate_mixnet_score(gateway: &Gateway) -> ScoreValue { let mixnet_performance = gateway.performance as f64 / 100.0; @@ -402,82 +270,6 @@ fn calculate_mixnet_score(gateway: &Gateway) -> ScoreValue { } } -/// calculates a visual score for the gateway using weighted metrics -fn calculate_score(gateway: &Gateway, probe_outcome: &LastProbeResult) -> ScoreValue { - let mixnet_performance = gateway.performance as f64 / 100.0; - - let node_score = probe_outcome - .outcome - .wg - .as_ref() - .map(|p| { - let ping_ips_performance = p.ping_ips_performance_v4 as f64; - - let duration_sec = - p.download_duration_milliseconds_v4 - .unwrap_or_else(|| p.download_duration_sec_v4 * 1000) as f64 - / 1000f64; - - // get the file size downloaded in bytes and convert to MB, or default to 1MB - let file_size_mb = - p.downloaded_file_size_bytes_v4.unwrap_or(1048576) as f64 / 1024f64 / 1024f64; - let speed_mbps = file_size_mb / duration_sec; - - let file_download_score = if speed_mbps > 5.0 { - 1.0 - } else if speed_mbps > 2.0 { - 0.75 - } else if speed_mbps > 1.0 { - 0.5 - } else if speed_mbps > 0.5 { - 0.25 - } else { - 0.1 - }; - - NodeScore { - download_speed_score: file_download_score, - ping_ips_score: ping_ips_performance, - mixnet_performance, - } - }) - .unwrap_or(NodeScore { - download_speed_score: 0.0, - ping_ips_score: 0.0, - mixnet_performance, - }); - - let weighted_score = node_score.calculate_weighted_score(); - - if weighted_score > 0.75 { - ScoreValue::High - } else if weighted_score > 0.5 { - ScoreValue::Medium - } else if weighted_score > 0.1 { - ScoreValue::Low - } else { - ScoreValue::Offline - } -} - -/// calculates a visual load score for the gateway -fn calculate_load(probe_outcome: &LastProbeResult) -> ScoreValue { - let score = probe_outcome - .outcome - .wg - .clone() - .map(|p| p.ping_ips_performance_v4 as f64) - .unwrap_or(0f64); - - if score > 0.8 { - ScoreValue::Low - } else if score > 0.4 { - ScoreValue::Medium - } else { - ScoreValue::High - } -} - fn to_percent(performance: u8) -> String { let fraction = performance as f32 / 100.0; format!("{fraction:.2}") @@ -485,6 +277,7 @@ fn to_percent(performance: u8) -> String { #[cfg(test)] mod test { + use super::*; use std::str::FromStr; @@ -756,130 +549,6 @@ mod test { assert!(service.mixnet_websockets.is_none()); assert!(service.last_successful_ping_utc.is_none()); } - - #[test] - fn test_weighted_score_calculation() { - use crate::http::models::directory_gw_probe_outcome::EntryTestResult; - use crate::http::models::wg_outcome_versions::ProbeOutcomeV1; - - // Helper function to create a test gateway - fn create_test_gateway(performance: u8) -> Gateway { - Gateway { - gateway_identity_key: "test_key".to_string(), - bonded: true, - performance, - self_described: None, - explorer_pretty_bond: None, - description: nym_node_requests::api::v1::node::models::NodeDescription { - moniker: "test".to_string(), - details: "test".to_string(), - security_contact: "test@example.com".to_string(), - website: "https://example.com".to_string(), - }, - last_probe_result: None, - last_probe_log: None, - last_testrun_utc: None, - last_updated_utc: "2025-10-10T00:00:00Z".to_string(), - routing_score: 0.0, - config_score: 0, - bridges: None, - } - } - - // Helper function to create a test probe outcome - fn create_test_probe_outcome( - download_speed_mbps: f64, - ping_ips_performance: f32, - ) -> LastProbeResult { - let duration_sec = 1.0; - let file_size_mb = download_speed_mbps; - - LastProbeResult { - node: "test_node".to_string(), - used_entry: "test_entry".to_string(), - outcome: ProbeOutcome { - as_entry: directory_gw_probe_outcome::Entry::Tested(EntryTestResult { - can_connect: true, - can_route: true, - }), - as_exit: None, - wg: Some(ProbeOutcomeV1 { - can_register: true, - can_handshake: Some(true), - can_resolve_dns: Some(true), - ping_hosts_performance: Some(ping_ips_performance), - ping_ips_performance: Some(ping_ips_performance), - can_query_metadata_v4: Some(true), - can_handshake_v4: true, - can_resolve_dns_v4: true, - ping_hosts_performance_v4: ping_ips_performance, - ping_ips_performance_v4: ping_ips_performance, - can_handshake_v6: true, - can_resolve_dns_v6: true, - ping_hosts_performance_v6: ping_ips_performance, - ping_ips_performance_v6: ping_ips_performance, - download_duration_sec_v4: (duration_sec * 1000.0) as u64, - download_duration_milliseconds_v4: Some((duration_sec * 1000.0) as u64), - downloaded_file_size_bytes_v4: Some( - (file_size_mb * 1024.0 * 1024.0) as u64, - ), - downloaded_file_v4: "test".to_string(), - download_error_v4: "".to_string(), - download_duration_sec_v6: 0, - download_duration_milliseconds_v6: Some(0), - downloaded_file_size_bytes_v6: Some(0), - downloaded_file_v6: "".to_string(), - download_error_v6: "".to_string(), - }), - }, - } - } - - // Test case 1: Excellent node (should be High) - let gateway = create_test_gateway(90); // 90% mixnet performance - let probe = create_test_probe_outcome(6.0, 1.0); // 6 Mbps, 100% ping - let score = calculate_score(&gateway, &probe); - assert_eq!(score, ScoreValue::High, "Excellent node should be High"); - - // Test case 2: Good node (should be High with weighted scoring) - let gateway = create_test_gateway(90); // 90% mixnet performance - let probe = create_test_probe_outcome(3.0, 0.9); // 3 Mbps (0.75 score), 90% ping - let score = calculate_score(&gateway, &probe); - assert_eq!( - score, - ScoreValue::High, - "Good node should be High with weighted scoring" - ); - - // Test case 3: Medium node - let gateway = create_test_gateway(80); // 80% mixnet performance - let probe = create_test_probe_outcome(1.5, 0.8); // 1.5 Mbps (0.5 score), 80% ping - let score = calculate_score(&gateway, &probe); - assert_eq!(score, ScoreValue::Medium, "Medium node should be Medium"); - - // Test case 4: Poor node - let gateway = create_test_gateway(60); // 60% mixnet performance - let probe = create_test_probe_outcome(0.3, 0.3); // 0.3 Mbps (0.1 score), 30% ping - let score = calculate_score(&gateway, &probe); - assert_eq!(score, ScoreValue::Low, "Poor node should be Low"); - - // Test case 5: Failed node - let gateway = create_test_gateway(10); // 10% mixnet performance - let probe = create_test_probe_outcome(0.1, 0.0); // 0.1 Mbps (0.1 score), 0% ping - let score = calculate_score(&gateway, &probe); - assert_eq!(score, ScoreValue::Offline, "Failed node should be Offline"); - - // Test case 6: Edge case - just above threshold - let gateway = create_test_gateway(76); // 76% mixnet performance - let probe = create_test_probe_outcome(2.1, 0.75); // 2.1 Mbps (0.75 score), 75% ping - let score = calculate_score(&gateway, &probe); - // Weighted: (0.76 * 0.4) + (0.75 * 0.3) + (0.75 * 0.3) = 0.304 + 0.225 + 0.225 = 0.754 - assert_eq!( - score, - ScoreValue::High, - "Edge case just above 0.75 threshold should be High" - ); - } } #[derive(Debug, Clone, Deserialize, Serialize, ToSchema)] diff --git a/nym-node-status-api/nym-node-status-api/src/http/state.rs b/nym-node-status-api/nym-node-status-api/src/http/state.rs index f03bfe0398..14bfc99c8d 100644 --- a/nym-node-status-api/nym-node-status-api/src/http/state.rs +++ b/nym-node-status-api/nym-node-status-api/src/http/state.rs @@ -20,7 +20,10 @@ use crate::{ db::{DbPool, queries}, http::{ error::{HttpError, HttpResult}, - models::{DVpnGateway, DailyStats, ExtendedNymNode, Gateway, NodeGeoData, SummaryHistory}, + models::{ + DVpnGateway, DailyStats, ExtendedNymNode, Gateway, NodeGeoData, SummaryHistory, + gw_probe::socks5_calc::calculate_socks5_percentiles, + }, }, monitor::{DelegationsCache, NodeGeoCache}, }; @@ -321,6 +324,8 @@ impl HttpCache { } }; + let socks5_scores = calculate_socks5_percentiles(&gateways); + let res_gws = gateways .iter() .filter(|gw| gw.bonded) @@ -335,7 +340,7 @@ impl HttpCache { } }) .filter_map( - |(gw, skimmed_node)| match DVpnGateway::new(gw.clone(), skimmed_node) { + |(gw, skimmed_node)| match DVpnGateway::new(gw.clone(), skimmed_node, socks5_scores.get(&gw.gateway_identity_key)) { Ok(gw) => Some(gw), Err(err) => { error!( diff --git a/nym-node-status-api/nym-node-status-api/src/main.rs b/nym-node-status-api/nym-node-status-api/src/main.rs index ebb76373ac..352f310c01 100644 --- a/nym-node-status-api/nym-node-status-api/src/main.rs +++ b/nym-node-status-api/nym-node-status-api/src/main.rs @@ -31,6 +31,7 @@ async fn main() -> anyhow::Result<()> { if let Some(env_file) = &args.config_env_file { setup_env(Some(env_file)); } + let network = nym_network_defaults::NymNetworkDetails::new_from_env(); let mut shutdown_manager = ShutdownManager::build_new_default()?; @@ -46,7 +47,13 @@ async fn main() -> anyhow::Result<()> { tracing::debug!("Using config:\n{:#?}", args); } - let storage = db::Storage::init(connection_url, args.sqlx_busy_timeout_s).await?; + let storage = db::Storage::init( + connection_url, + args.sqlx_busy_timeout_s, + args.sqlx_max_connections, + args.sqlx_min_connections, + ) + .await?; let db_pool = storage.pool_owned(); // node geocache is shared between node monitor and HTTP server @@ -55,9 +62,9 @@ async fn main() -> anyhow::Result<()> { .build(); let delegations_cache = DelegationsCache::new(); - let client_config = nym_validator_client::nyxd::Config::try_from_nym_network_details( - &nym_network_defaults::NymNetworkDetails::new_from_env(), - )?; + let client_config = nym_validator_client::nyxd::Config::try_from_nym_network_details(&network)?; + tracing::info!("Network: {}", network.network_name); + let nyxd_client = NyxdClient::connect(client_config.clone(), args.nyxd_addr.as_str()) .map_err(|err| anyhow::anyhow!("Couldn't connect: {}", err))?; From 76ce1bc0f92c98ee5d102525420501906aeeca48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Fri, 30 Jan 2026 08:44:29 +0000 Subject: [PATCH 36/51] feat: use hex-encoding for lp key digests (#6394) * feat: use hex-encoding for lp key digests * removed needless borrow in test code * gateway probe fixes --- Cargo.lock | 1 + .../src/models/described/type_translation.rs | 109 +++++++++++++++--- .../src/models/described/v2.rs | 10 +- nym-gateway-probe/src/common/nodes.rs | 28 +---- nym-node/Cargo.toml | 1 + .../src/api/v1/lewes_protocol/models.rs | 10 +- nym-node/src/node/mod.rs | 8 +- 7 files changed, 112 insertions(+), 55 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 845cb75272..f68e8f7c03 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7129,6 +7129,7 @@ dependencies = [ "csv", "cupid", "futures", + "hex", "hkdf", "human-repr", "humantime-serde", 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 94746950d3..efdd558ac3 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 @@ -16,6 +16,7 @@ use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::net::IpAddr; use strum_macros::{Display, EnumString}; +use thiserror::Error; use utoipa::ToSchema; // to whoever is thinking of modifying this struct. @@ -211,17 +212,57 @@ pub struct LewesProtocolDetailsV1 { /// Digests of the KEM keys available to this node alongside hashing algorithms used /// for their computation. - pub kem_keys: HashMap>>, + /// note: digests are hex encoded + pub kem_keys: HashMap>, /// Digests of the signing keys available to this node alongside hashing algorithms used /// for their computation. - pub signing_keys: HashMap>>, + /// note: digests are hex encoded + pub signing_keys: HashMap>, +} + +impl LewesProtocolDetailsV1 { + fn decode_digests( + digests: &HashMap, + ) -> Result>, MalformedLPData> { + let mut kem_digests = HashMap::new(); + for (hash_function, digest) in digests { + let digest = hex::decode(digest).map_err(|source| MalformedLPData::MalformedHash { + value: digest.to_string(), + source, + })?; + kem_digests.insert((*hash_function).try_into()?, digest); + } + Ok(kem_digests) + } + + pub fn kem_keys( + &self, + ) -> Result>>, MalformedLPData> { + let mut kem_keys = HashMap::new(); + for (kem, digests) in &self.kem_keys { + let kem_digests = Self::decode_digests(digests)?; + kem_keys.insert((*kem).try_into()?, kem_digests); + } + Ok(kem_keys) + } + + pub fn signing_keys( + &self, + ) -> Result>>, MalformedLPData> { + let mut signing_keys = HashMap::new(); + for (signature_scheme, digests) in &self.signing_keys { + let kem_digests = Self::decode_digests(digests)?; + signing_keys.insert((*signature_scheme).try_into()?, kem_digests); + } + Ok(signing_keys) + } } /// Convert map of digests from `nym_node_requests` types into `nym-api-requests` types fn translate_digests( - digests: HashMap>, -) -> HashMap> { + digests: HashMap, +) -> HashMap { digests .into_iter() .map(|(hash_fn, digest)| (hash_fn.into(), digest)) @@ -244,6 +285,7 @@ fn translate_digests( ToSchema, )] #[strum(serialize_all = "lowercase")] +#[non_exhaustive] pub enum LPKEM { MlKem768, XWing, @@ -267,6 +309,7 @@ pub enum LPKEM { ToSchema, )] #[strum(serialize_all = "lowercase")] +#[non_exhaustive] pub enum LPHashFunction { Blake3, Shake128, @@ -290,6 +333,7 @@ pub enum LPHashFunction { ToSchema, )] #[strum(serialize_all = "lowercase")] +#[non_exhaustive] pub enum LPSignatureScheme { Ed25519, } @@ -469,32 +513,59 @@ impl From } } -impl From for KEM { - fn from(value: LPKEM) -> Self { +#[derive(Debug, Error)] +pub enum MalformedLPData { + #[error("{value} does not correspond to any valid LP KEM")] + UnknownLpKEM { value: LPKEM }, + + #[error("{value} does not correspond to any valid LP Signature Scheme")] + UnknownLpSignatureScheme { value: LPSignatureScheme }, + + #[error("{value} does not correspond to any valid LP Hash Function")] + UnknownLpHashFunction { value: LPHashFunction }, + + #[error("{value} is not a valid hex encoded hash: {source}")] + MalformedHash { + value: String, + source: hex::FromHexError, + }, +} + +impl TryFrom for KEM { + type Error = MalformedLPData; + fn try_from(value: LPKEM) -> Result { match value { - LPKEM::MlKem768 => KEM::MlKem768, - LPKEM::XWing => KEM::XWing, - LPKEM::X25519 => KEM::X25519, - LPKEM::McEliece => KEM::McEliece, + LPKEM::MlKem768 => Ok(KEM::MlKem768), + LPKEM::XWing => Ok(KEM::XWing), + LPKEM::X25519 => Ok(KEM::X25519), + LPKEM::McEliece => Ok(KEM::McEliece), + // TODO: for backwards compatibility once variants within the LP crate change + // other => Err(MalformedLPData::UnknownLpKEM { value: other }), } } } -impl From for HashFunction { - fn from(value: LPHashFunction) -> Self { +impl TryFrom for HashFunction { + type Error = MalformedLPData; + fn try_from(value: LPHashFunction) -> Result { match value { - LPHashFunction::Blake3 => HashFunction::Blake3, - LPHashFunction::Shake128 => HashFunction::Shake128, - LPHashFunction::Shake256 => HashFunction::Shake256, - LPHashFunction::Sha256 => HashFunction::SHA256, + LPHashFunction::Blake3 => Ok(HashFunction::Blake3), + LPHashFunction::Shake128 => Ok(HashFunction::Shake128), + LPHashFunction::Shake256 => Ok(HashFunction::Shake256), + LPHashFunction::Sha256 => Ok(HashFunction::SHA256), + // TODO: for backwards compatibility once variants within the LP crate change + // other => Err(MalformedLPData::UnknownLpHashFunction { value: other }), } } } -impl From for SignatureScheme { - fn from(value: LPSignatureScheme) -> Self { +impl TryFrom for SignatureScheme { + type Error = MalformedLPData; + fn try_from(value: LPSignatureScheme) -> Result { match value { - LPSignatureScheme::Ed25519 => SignatureScheme::Ed25519, + LPSignatureScheme::Ed25519 => Ok(SignatureScheme::Ed25519), + // TODO: for backwards compatibility once variants within the LP crate change + // other => Err(MalformedLPData::UnknownLpSignatureScheme { value: other }), } } } diff --git a/nym-api/nym-api-requests/src/models/described/v2.rs b/nym-api/nym-api-requests/src/models/described/v2.rs index bd68aa5271..8be3710eed 100644 --- a/nym-api/nym-api-requests/src/models/described/v2.rs +++ b/nym-api/nym-api-requests/src/models/described/v2.rs @@ -262,10 +262,16 @@ pub fn mock_nym_node_description(seed: u64) -> NymNodeDescriptionV2 { let mut kem_hashes = std::collections::HashMap::new(); let mut signing_keys_hashes = std::collections::HashMap::new(); - kem_hashes.insert(LPHashFunction::Sha256, vec![(seed % 256) as u8; 32]); + kem_hashes.insert( + LPHashFunction::Sha256, + hex::encode([(seed % 256) as u8; 32]), + ); kem_hashes_wrapper.insert(LPKEM::X25519, kem_hashes); - signing_keys_hashes.insert(LPHashFunction::Sha256, vec![(seed % 256) as u8; 32]); + signing_keys_hashes.insert( + LPHashFunction::Sha256, + hex::encode([(seed % 256) as u8; 32]), + ); signing_keys_hashes_wrapper.insert(LPSignatureScheme::Ed25519, signing_keys_hashes); NymNodeDescriptionV2 { diff --git a/nym-gateway-probe/src/common/nodes.rs b/nym-gateway-probe/src/common/nodes.rs index 9716a923fd..6950978b14 100644 --- a/nym-gateway-probe/src/common/nodes.rs +++ b/nym-gateway-probe/src/common/nodes.rs @@ -132,32 +132,8 @@ impl DirectoryNode { ) { (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(), - expected_signing_key_hashes: lp_data - .signing_keys - .into_iter() - .map(|(scheme, digests)| { - ( - scheme.into(), - digests - .into_iter() - .map(|(hash_fn, digest)| (hash_fn.into(), digest)) - .collect(), - ) - }) - .collect(), + expected_kem_key_hashes: lp_data.kem_keys()?, + expected_signing_key_hashes: lp_data.signing_keys()?, x25519: noise_key.x25519_pubkey, }), _ => None, diff --git a/nym-node/Cargo.toml b/nym-node/Cargo.toml index 0002bc60f4..55105c54bf 100644 --- a/nym-node/Cargo.toml +++ b/nym-node/Cargo.toml @@ -28,6 +28,7 @@ console-subscriber = { workspace = true, optional = true } csv = { workspace = true } clap = { workspace = true, features = ["cargo", "env"] } futures = { workspace = true } +hex = { workspace = true } humantime-serde = { workspace = true } human-repr = { workspace = true } ipnetwork = { workspace = true } 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 ac38f316b1..5dffa99541 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 @@ -21,11 +21,13 @@ pub struct LewesProtocol { /// Digests of the KEM keys available to this node alongside hashing algorithms used /// for their computation. - pub kem_keys: HashMap>>, + /// note: digests are hex encoded + pub kem_keys: HashMap>, /// Digests of the signing keys available to this node alongside hashing algorithms used /// for their computation. - pub signing_keys: HashMap>>, + /// note: digests are hex encoded + pub signing_keys: HashMap>, } impl LewesProtocol { @@ -33,8 +35,8 @@ impl LewesProtocol { enabled: bool, control_port: u16, data_port: u16, - kem_keys: HashMap>>, - signing_keys: HashMap>>, + kem_keys: HashMap>, + signing_keys: HashMap>, ) -> Self { LewesProtocol { enabled, diff --git a/nym-node/src/node/mod.rs b/nym-node/src/node/mod.rs index 01fb355825..abf246c453 100644 --- a/nym-node/src/node/mod.rs +++ b/nym-node/src/node/mod.rs @@ -779,7 +779,7 @@ impl NymNode { Ok(()) } - fn compute_kem_key_hashes(&self) -> HashMap>> { + fn compute_kem_key_hashes(&self) -> HashMap> { let kem = LPKEM::X25519; let kem_key_bytes = self.entry_gateway.psq_kem_key.public_key().as_bytes(); @@ -787,7 +787,7 @@ impl NymNode { // convert from `nym_kkt_ciphersuite` types into `nym_nodes_requests` let digests = produce_key_digests(kem_key_bytes.as_ref()) .into_iter() - .map(|(f, d)| (f.into(), d)) + .map(|(f, digest)| (f.into(), hex::encode(&digest))) .collect(); let mut hashes = HashMap::new(); @@ -797,7 +797,7 @@ impl NymNode { fn compute_signing_key_hashes( &self, - ) -> HashMap>> { + ) -> HashMap> { let scheme = LPSignatureScheme::Ed25519; let kem_key_bytes = self.ed25519_identity_keys.public_key().as_bytes(); @@ -805,7 +805,7 @@ impl NymNode { // convert from `nym_kkt_ciphersuite` types into `nym_nodes_requests` let digests = produce_key_digests(kem_key_bytes.as_ref()) .into_iter() - .map(|(f, d)| (f.into(), d)) + .map(|(f, digest)| (f.into(), hex::encode(&digest))) .collect(); let mut hashes = HashMap::new(); From 9d661e7a7b13047bf653cec5eebc33b50f872dd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Fri, 30 Jan 2026 09:39:47 +0000 Subject: [PATCH 37/51] bugfix: use correct reserved bytes when parsing LpHeader (#6398) --- common/nym-lp/src/packet.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/nym-lp/src/packet.rs b/common/nym-lp/src/packet.rs index 12024ef1e1..2873951680 100644 --- a/common/nym-lp/src/packet.rs +++ b/common/nym-lp/src/packet.rs @@ -265,7 +265,7 @@ impl LpHeader { Ok(LpHeader { protocol_version, - reserved: [0u8; 3], + reserved, receiver_idx, counter, }) From dccdde108c474277449533b44dee0f7d8e78ab8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Fri, 30 Jan 2026 11:38:17 +0000 Subject: [PATCH 38/51] Lp/bugfix/share ip allocation (#6395) * feat: use shared PeerManager between Authenticator and LpHandlerState * feat: share IpPool * clippy and test fixes * PR suggestions --- Cargo.lock | 2 +- .../src/v6/registration.rs | 3 +- .../tests/src/v2/peer_controller.rs | 14 +- common/wireguard/Cargo.toml | 2 +- common/wireguard/src/ip_pool/compat.rs | 60 +++++++++ .../src/{ip_pool.rs => ip_pool/mod.rs} | 68 ++++++++-- common/wireguard/src/lib.rs | 51 +++---- common/wireguard/src/peer_controller/mock.rs | 32 ++--- common/wireguard/src/peer_controller/mod.rs | 119 ++++++++++------ gateway/src/error.rs | 4 + .../authenticator/error.rs | 7 +- .../authenticator/mixnet_listener.rs | 61 ++++----- .../authenticator/mod.rs | 27 +--- gateway/src/node/lp_listener/handler.rs | 2 +- gateway/src/node/lp_listener/mod.rs | 44 +++--- gateway/src/node/lp_listener/peer_manager.rs | 127 ------------------ gateway/src/node/lp_listener/registration.rs | 16 ++- gateway/src/node/mod.rs | 19 +-- gateway/src/node/wireguard/error.rs | 19 +++ gateway/src/node/wireguard/mod.rs | 8 ++ .../peer_manager.rs | 104 +++++++++----- integration-tests/src/lp_registration.rs | 14 +- 22 files changed, 429 insertions(+), 374 deletions(-) create mode 100644 common/wireguard/src/ip_pool/compat.rs rename common/wireguard/src/{ip_pool.rs => ip_pool/mod.rs} (76%) delete mode 100644 gateway/src/node/lp_listener/peer_manager.rs create mode 100644 gateway/src/node/wireguard/error.rs create mode 100644 gateway/src/node/wireguard/mod.rs rename gateway/src/node/{internal_service_providers/authenticator => wireguard}/peer_manager.rs (84%) diff --git a/Cargo.lock b/Cargo.lock index f68e8f7c03..2e041700b7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8375,12 +8375,12 @@ dependencies = [ "futures", "ip_network", "ipnetwork", + "nym-authenticator-requests", "nym-credential-verification", "nym-credentials-interface", "nym-crypto", "nym-gateway-requests", "nym-gateway-storage", - "nym-ip-packet-requests", "nym-metrics", "nym-network-defaults", "nym-node-metrics", diff --git a/common/authenticator-requests/src/v6/registration.rs b/common/authenticator-requests/src/v6/registration.rs index 11fcf34116..14e7462c05 100644 --- a/common/authenticator-requests/src/v6/registration.rs +++ b/common/authenticator-requests/src/v6/registration.rs @@ -20,13 +20,12 @@ use nym_crypto::asymmetric::x25519::{PrivateKey, PublicKey}; use sha2::Sha256; pub type PendingRegistrations = HashMap; -pub type PrivateIPs = HashMap; +pub type PrivateIPs = HashMap; #[cfg(feature = "verify")] pub type HmacSha256 = Hmac; pub type Nonce = u64; -pub type Taken = Option; #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct IpPair { diff --git a/common/wireguard-private-metadata/tests/src/v2/peer_controller.rs b/common/wireguard-private-metadata/tests/src/v2/peer_controller.rs index 68fd5cdeed..15312a1f52 100644 --- a/common/wireguard-private-metadata/tests/src/v2/peer_controller.rs +++ b/common/wireguard-private-metadata/tests/src/v2/peer_controller.rs @@ -26,7 +26,7 @@ impl From<&PeerControlRequest> for PeerControlRequestTypeV2 { fn from(req: &PeerControlRequest) -> Self { match req { PeerControlRequest::AddPeer { .. } => PeerControlRequestTypeV2::AddPeer, - PeerControlRequest::RegisterPeer { .. } => PeerControlRequestTypeV2::AddPeer, + PeerControlRequest::AllocatePeerIpPair { .. } => PeerControlRequestTypeV2::AddPeer, PeerControlRequest::RemovePeer { .. } => PeerControlRequestTypeV2::RemovePeer, PeerControlRequest::QueryPeer { .. } => PeerControlRequestTypeV2::QueryPeer, PeerControlRequest::GetClientBandwidthByKey { .. } => { @@ -41,6 +41,7 @@ impl From<&PeerControlRequest> for PeerControlRequestTypeV2 { PeerControlRequest::GetVerifierByIp { ip, .. } => { PeerControlRequestTypeV2::GetVerifierByIp { ip: *ip } } + PeerControlRequest::ReleaseIpPair { .. } => unreachable!(), } } } @@ -113,7 +114,7 @@ impl MockPeerControllerV2 { ) .unwrap(); } - PeerControlRequest::RegisterPeer { response_tx, .. } => { + PeerControlRequest::AllocatePeerIpPair { response_tx, .. } => { response_tx .send( *response @@ -176,6 +177,15 @@ impl MockPeerControllerV2 { ) .ok(); } + PeerControlRequest::ReleaseIpPair { response_tx, .. } => { + response_tx + .send( + *response + .downcast() + .expect("registered response has mismatched type"), + ) + .ok(); + } } } diff --git a/common/wireguard/Cargo.toml b/common/wireguard/Cargo.toml index 16b2536f84..3857d1165a 100644 --- a/common/wireguard/Cargo.toml +++ b/common/wireguard/Cargo.toml @@ -28,7 +28,7 @@ nym-credential-verification = { workspace = true } nym-crypto = { workspace = true, features = ["asymmetric"] } nym-gateway-storage = { workspace = true } nym-gateway-requests = { workspace = true } -nym-ip-packet-requests = { workspace = true } +nym-authenticator-requests = { workspace = true } nym-metrics = { workspace = true } nym-network-defaults = { workspace = true } nym-task = { workspace = true } diff --git a/common/wireguard/src/ip_pool/compat.rs b/common/wireguard/src/ip_pool/compat.rs new file mode 100644 index 0000000000..95ae7da686 --- /dev/null +++ b/common/wireguard/src/ip_pool/compat.rs @@ -0,0 +1,60 @@ +// Copyright 2026 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::ip_pool::IpPair; + +impl From for nym_authenticator_requests::v6::registration::IpPair { + fn from(ip_pair: IpPair) -> Self { + nym_authenticator_requests::v6::registration::IpPair { + ipv4: ip_pair.ipv4, + ipv6: ip_pair.ipv6, + } + } +} + +impl From for nym_authenticator_requests::v5::registration::IpPair { + fn from(ip_pair: IpPair) -> Self { + nym_authenticator_requests::v5::registration::IpPair { + ipv4: ip_pair.ipv4, + ipv6: ip_pair.ipv6, + } + } +} + +impl From for nym_authenticator_requests::v4::registration::IpPair { + fn from(ip_pair: IpPair) -> Self { + nym_authenticator_requests::v4::registration::IpPair { + ipv4: ip_pair.ipv4, + ipv6: ip_pair.ipv6, + } + } +} + +// + +impl From for IpPair { + fn from(ip_pair: nym_authenticator_requests::v6::registration::IpPair) -> Self { + IpPair { + ipv4: ip_pair.ipv4, + ipv6: ip_pair.ipv6, + } + } +} + +impl From for IpPair { + fn from(ip_pair: nym_authenticator_requests::v5::registration::IpPair) -> Self { + IpPair { + ipv4: ip_pair.ipv4, + ipv6: ip_pair.ipv6, + } + } +} + +impl From for IpPair { + fn from(ip_pair: nym_authenticator_requests::v4::registration::IpPair) -> Self { + IpPair { + ipv4: ip_pair.ipv4, + ipv6: ip_pair.ipv6, + } + } +} diff --git a/common/wireguard/src/ip_pool.rs b/common/wireguard/src/ip_pool/mod.rs similarity index 76% rename from common/wireguard/src/ip_pool.rs rename to common/wireguard/src/ip_pool/mod.rs index e1c2b0453f..b34a7134c2 100644 --- a/common/wireguard/src/ip_pool.rs +++ b/common/wireguard/src/ip_pool/mod.rs @@ -1,14 +1,56 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +mod compat; + +use defguard_wireguard_rs::host::Peer; use ipnetwork::IpNetwork; -use nym_ip_packet_requests::IpPair; use rand::seq::IteratorRandom; use std::collections::HashMap; +use std::fmt::{Display, Formatter}; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; use std::sync::Arc; -use std::time::SystemTime; +use std::time::{Duration, SystemTime}; use tokio::sync::RwLock; +use tokio::time::Instant; +use tracing::{trace, warn}; + +// helper to convert peer's allocation into an `IpPair` +pub fn allocated_ip_pair(peer: &Peer) -> Option { + for allowed_ip in &peer.allowed_ips { + // Extract IPv4 and IPv6 from peer's allowed_ips + if let IpAddr::V4(ipv4) = allowed_ip.address { + // Find corresponding IPv6 + if let Some(ipv6_mask) = peer + .allowed_ips + .iter() + .find(|ip| matches!(ip.address, IpAddr::V6(_))) + && let IpAddr::V6(ipv6) = ipv6_mask.address + { + return Some(IpPair::new(ipv4, ipv6)); + } + } + } + None +} + +#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Eq, Hash)] +pub struct IpPair { + pub ipv4: Ipv4Addr, + pub ipv6: Ipv6Addr, +} + +impl IpPair { + pub fn new(ipv4: Ipv4Addr, ipv6: Ipv6Addr) -> Self { + IpPair { ipv4, ipv6 } + } +} + +impl Display for IpPair { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "IPv4: {}, IPv6: {}", self.ipv4, self.ipv6) + } +} /// Represents the state of an IP allocation #[derive(Debug, Clone, Copy)] @@ -83,12 +125,8 @@ impl IpPool { } tracing::info!( - "Initialized IP pool with {} address pairs from {}/{} and {}/{}", + "Initialized IP pool with {} address pairs from {ipv4_network}/{ipv4_prefix} and {ipv6_network}/{ipv6_prefix}", allocations.len(), - ipv4_network, - ipv4_prefix, - ipv6_network, - ipv6_prefix ); Ok(IpPool { @@ -98,24 +136,28 @@ impl IpPool { /// Allocate a free IP pair from the pool /// - /// Randomly selects an available IP pair and marks it as allocated. - /// /// # Errors /// Returns `IpPoolError::NoFreeIp` if no IPs are available pub async fn allocate(&self) -> Result { let mut pool = self.allocations.write().await; // Find a free IP and allocate it + let assignment_start = Instant::now(); let free_ip = pool .iter_mut() .filter(|(_, state)| matches!(state, AllocationState::Free)) .choose(&mut rand::thread_rng()) .ok_or(IpPoolError::NoFreeIp)?; + let taken = assignment_start.elapsed(); + trace!("assigning free ip pair took {taken:?}"); + if taken > Duration::from_millis(500) { + warn!("assigning free ip pair took {taken:?}"); + } let ip_pair = *free_ip.0; *free_ip.1 = AllocationState::Allocated(SystemTime::now()); - tracing::debug!("Allocated IP pair: {}", ip_pair); + tracing::debug!("Allocated IP pair: {ip_pair}"); Ok(ip_pair) } @@ -126,7 +168,7 @@ impl IpPool { let mut pool = self.allocations.write().await; if let Some(state) = pool.get_mut(&ip_pair) { *state = AllocationState::Free; - tracing::debug!("Released IP pair: {}", ip_pair); + tracing::debug!("Released IP pair: {ip_pair}"); } } @@ -137,9 +179,9 @@ impl IpPool { let mut pool = self.allocations.write().await; if let Some(state) = pool.get_mut(&ip_pair) { *state = AllocationState::Allocated(SystemTime::now()); - tracing::debug!("Marked IP pair as used: {}", ip_pair); + tracing::debug!("Marked IP pair as used: {ip_pair}"); } else { - tracing::warn!("Attempted to mark unknown IP pair as used: {}", ip_pair); + tracing::warn!("Attempted to mark unknown IP pair as used: {ip_pair}"); } } diff --git a/common/wireguard/src/lib.rs b/common/wireguard/src/lib.rs index 459677b492..e73da973a2 100644 --- a/common/wireguard/src/lib.rs +++ b/common/wireguard/src/lib.rs @@ -2,9 +2,6 @@ // SPDX-License-Identifier: Apache-2.0 #![cfg_attr(not(target_os = "linux"), allow(dead_code))] -// #![warn(clippy::pedantic)] -// #![warn(clippy::expect_used)] -// #![warn(clippy::unwrap_used)] use defguard_wireguard_rs::{ WGApi, WireguardInterfaceApi, error::WireguardInterfaceError, host::Peer, key::Key, @@ -16,9 +13,6 @@ use std::sync::Arc; use tokio::sync::mpsc::{self, Receiver, Sender}; use tracing::error; -#[cfg(target_os = "linux")] -use nym_ip_packet_requests::IpPair; - #[cfg(target_os = "linux")] use nym_network_defaults::constants::WG_TUN_BASE_NAME; @@ -288,6 +282,22 @@ pub async fn start_wireguard( peer_bandwidth_managers.insert(peer.public_key.clone(), (bandwidth_manager, peer.clone())); } + // Initialize IP pool from configuration + info!("Initializing IP pool for WireGuard peer allocation"); + let ip_pool = IpPool::new( + wireguard_data.inner.config().private_ipv4, + wireguard_data.inner.config().private_network_prefix_v4, + wireguard_data.inner.config().private_ipv6, + wireguard_data.inner.config().private_network_prefix_v6, + )?; + + // Mark existing peer IPs as used in the pool + for peer in &peers { + if let Some(ip_pair) = crate::ip_pool::allocated_ip_pair(peer) { + ip_pool.mark_used(ip_pair).await; + } + } + wg_api.create_interface()?; let interface_config = InterfaceConfiguration { name: ifname.clone(), @@ -296,7 +306,7 @@ pub async fn start_wireguard( wireguard_data.inner.config().private_ipv4, ))], port: wireguard_data.inner.config().announced_tunnel_port, - peers: peers.clone(), // Clone since we need to use peers later to mark IPs as used + peers, mtu: None, }; info!( @@ -350,33 +360,6 @@ pub async fn start_wireguard( let host = wg_api.read_interface_data()?; - // Initialize IP pool from configuration - info!("Initializing IP pool for WireGuard peer allocation"); - let ip_pool = IpPool::new( - wireguard_data.inner.config().private_ipv4, - wireguard_data.inner.config().private_network_prefix_v4, - wireguard_data.inner.config().private_ipv6, - wireguard_data.inner.config().private_network_prefix_v6, - )?; - - // Mark existing peer IPs as used in the pool - for peer in &peers { - for allowed_ip in &peer.allowed_ips { - // Extract IPv4 and IPv6 from peer's allowed_ips - if let IpAddr::V4(ipv4) = allowed_ip.address { - // Find corresponding IPv6 - if let Some(ipv6_mask) = peer - .allowed_ips - .iter() - .find(|ip| matches!(ip.address, IpAddr::V6(_))) - && let IpAddr::V6(ipv6) = ipv6_mask.address - { - ip_pool.mark_used(IpPair::new(ipv4, ipv6)).await; - } - } - } - } - let wg_api = std::sync::Arc::new(wg_api); let mut controller = PeerController::new( ecash_manager, diff --git a/common/wireguard/src/peer_controller/mock.rs b/common/wireguard/src/peer_controller/mock.rs index 5398183127..89f2821b31 100644 --- a/common/wireguard/src/peer_controller/mock.rs +++ b/common/wireguard/src/peer_controller/mock.rs @@ -19,6 +19,7 @@ use std::sync::Arc; use tokio::sync::RwLock; use tokio::sync::mpsc::Receiver; +use crate::ip_pool::IpPair; pub use defguard_wireguard_rs::key::Key; pub fn mock_peer_controller( @@ -70,7 +71,8 @@ impl From<&Key> for KeyWrapper { #[derive(Hash, PartialOrd, PartialEq, Clone, Debug, Eq)] pub enum PeerControlRequestType { AddPeer { public_key: KeyWrapper }, - RegisterPeer { public_key: KeyWrapper }, + AllocatePeerIpPair {}, + ReleaseIpPair { ip_pair: IpPair }, RemovePeer { key: KeyWrapper }, QueryPeer { key: KeyWrapper }, GetClientBandwidthByKey { key: KeyWrapper }, @@ -83,7 +85,8 @@ impl PeerControlRequestType { pub fn peer_key(&self) -> Option { match self { PeerControlRequestType::AddPeer { public_key } => Some(public_key.clone()), - PeerControlRequestType::RegisterPeer { public_key } => Some(public_key.clone()), + PeerControlRequestType::AllocatePeerIpPair {} => None, + PeerControlRequestType::ReleaseIpPair { .. } => None, PeerControlRequestType::RemovePeer { key } => Some(key.clone()), PeerControlRequestType::QueryPeer { key } => Some(key.clone()), PeerControlRequestType::GetClientBandwidthByKey { key } => Some(key.clone()), @@ -104,11 +107,12 @@ impl From<&PeerControlRequest> for PeerControlRequestType { PeerControlRequest::AddPeer { peer, .. } => PeerControlRequestType::AddPeer { public_key: (&peer.public_key).into(), }, - PeerControlRequest::RegisterPeer { - registration_data, .. - } => PeerControlRequestType::RegisterPeer { - public_key: (®istration_data.public_key).into(), - }, + PeerControlRequest::AllocatePeerIpPair { .. } => { + PeerControlRequestType::AllocatePeerIpPair {} + } + PeerControlRequest::ReleaseIpPair { ip_pair, .. } => { + PeerControlRequestType::ReleaseIpPair { ip_pair: *ip_pair } + } PeerControlRequest::RemovePeer { key, .. } => { PeerControlRequestType::RemovePeer { key: key.into() } } @@ -166,9 +170,6 @@ pub struct MockPeerControllerState { #[derive(Clone, Default)] pub struct PeerState { - /// Has IpPair been allocated to the peer? - pub register_success: bool, - // in the future maybe we could extend it with `ClientBandwidth` information /// Has the client handle been spawned pub add_success: bool, @@ -265,14 +266,13 @@ impl MockPeerController { } response_tx.send_downcasted(response.content) } - PeerControlRequest::RegisterPeer { response_tx, .. } => { - let key = typ.peer_key_unchecked(); - let peer = peers_guard.peers.entry(key).or_default(); - if response.success { - peer.register_success = true; - } + PeerControlRequest::AllocatePeerIpPair { response_tx, .. } => { response_tx.send_downcasted(response.content) } + PeerControlRequest::ReleaseIpPair { + response_tx, + ip_pair: _, + } => response_tx.send_downcasted(response.content), PeerControlRequest::RemovePeer { response_tx, .. } => { let key = typ.peer_key_unchecked(); if response.success { diff --git a/common/wireguard/src/peer_controller/mod.rs b/common/wireguard/src/peer_controller/mod.rs index 60d7c98ee2..e8ba3c4078 100644 --- a/common/wireguard/src/peer_controller/mod.rs +++ b/common/wireguard/src/peer_controller/mod.rs @@ -1,6 +1,7 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use crate::ip_pool::allocated_ip_pair; use crate::{ IpPool, error::{Error, Result}, @@ -32,9 +33,9 @@ use std::{ }; use tokio::sync::{RwLock, mpsc}; use tokio_stream::{StreamExt, wrappers::IntervalStream}; -use tracing::{debug, error, info, trace, warn}; +use tracing::{debug, error, info, trace}; -pub use nym_ip_packet_requests::IpPair; +pub use crate::ip_pool::IpPair; #[cfg(feature = "mock")] pub mod mock; @@ -75,10 +76,14 @@ pub enum PeerControlRequest { peer: Peer, response_tx: oneshot::Sender, }, - /// Register a new peer and allocate IPs from the pool - RegisterPeer { - registration_data: PeerRegistrationData, - response_tx: oneshot::Sender, + /// Attempt to allocate an IP pair from the pool + AllocatePeerIpPair { + response_tx: oneshot::Sender, + }, + /// Attempt to return an IP pair back to the pool + ReleaseIpPair { + response_tx: oneshot::Sender, + ip_pair: IpPair, }, RemovePeer { key: Key, @@ -109,7 +114,8 @@ pub enum PeerControlRequest { } pub type AddPeerControlResponse = Result<()>; -pub type RegisterPeerControlResponse = Result; +pub type AllocatePeerControlResponse = Result; +pub type ReleaseIpPairControlResponse = Result<()>; pub type RemovePeerControlResponse = Result<()>; pub type QueryPeerControlResponse = Result>; pub type GetClientBandwidthControlResponse = Result; @@ -207,9 +213,11 @@ impl PeerController { .await?; self.bw_storage_managers.remove(key); - warn!("MISSING CALL TO IP POOL RELEASE"); - // need to figure out what addresses to release - // self.ip_pool.release() + if let Ok(Some(peer)) = self.handle_query_peer_by_key(key).await + && let Some(ip_pair) = allocated_ip_pair(&peer) + { + self.ip_pool.release(ip_pair).await + } let ret = self.wg_api.remove_peer(key); if ret.is_err() { @@ -294,10 +302,7 @@ impl PeerController { /// /// This only allocates IPs - the caller must handle database storage and /// then call AddPeer with a complete Peer struct. - async fn handle_register_request( - &mut self, - _registration_data: PeerRegistrationData, - ) -> Result { + async fn handle_ip_allocation_request(&mut self) -> Result { nym_metrics::inc!("wg_ip_allocation_attempts"); // Allocate IP pair from pool @@ -313,6 +318,11 @@ impl PeerController { Ok(ip_pair) } + /// Return IP pair back to the pool + async fn handle_ip_release(&mut self, ip_pair: IpPair) { + self.ip_pool.release(ip_pair).await + } + async fn ip_to_key(&self, ip: IpAddr) -> Result> { Ok(self .bw_storage_managers @@ -477,6 +487,62 @@ impl PeerController { ); } + async fn handle_peer_control_request(&mut self, msg: PeerControlRequest) { + match msg { + PeerControlRequest::AddPeer { peer, response_tx } => { + response_tx.send(self.handle_add_request(&peer).await).ok(); + } + PeerControlRequest::AllocatePeerIpPair { response_tx } => { + response_tx + .send(self.handle_ip_allocation_request().await) + .ok(); + } + PeerControlRequest::ReleaseIpPair { + response_tx, + ip_pair, + } => { + self.handle_ip_release(ip_pair).await; + response_tx.send(Ok(())).ok(); + } + PeerControlRequest::RemovePeer { key, response_tx } => { + response_tx.send(self.remove_peer(&key).await).ok(); + } + PeerControlRequest::QueryPeer { key, response_tx } => { + response_tx + .send(self.handle_query_peer_by_key(&key).await) + .ok(); + } + PeerControlRequest::GetClientBandwidthByKey { key, response_tx } => { + response_tx + .send(self.handle_get_client_bandwidth_by_key(&key).await) + .ok(); + } + PeerControlRequest::GetClientBandwidthByIp { ip, response_tx } => { + response_tx + .send(self.handle_get_client_bandwidth_by_ip(ip).await) + .ok(); + } + PeerControlRequest::GetVerifierByKey { + key, + credential, + response_tx, + } => { + response_tx + .send(self.handle_query_verifier_by_key(&key, *credential).await) + .ok(); + } + PeerControlRequest::GetVerifierByIp { + ip, + credential, + response_tx, + } => { + response_tx + .send(self.handle_query_verifier_by_ip(ip, *credential).await) + .ok(); + } + } + } + pub async fn run(&mut self) { info!("started wireguard peer controller"); loop { @@ -504,30 +570,7 @@ impl PeerController { } msg = self.request_rx.recv() => { match msg { - Some(PeerControlRequest::AddPeer { peer, response_tx }) => { - response_tx.send(self.handle_add_request(&peer).await).ok(); - } - Some(PeerControlRequest::RegisterPeer { registration_data, response_tx }) => { - response_tx.send(self.handle_register_request(registration_data).await).ok(); - } - Some(PeerControlRequest::RemovePeer { key, response_tx }) => { - response_tx.send(self.remove_peer(&key).await).ok(); - } - Some(PeerControlRequest::QueryPeer { key, response_tx }) => { - response_tx.send(self.handle_query_peer_by_key(&key).await).ok(); - } - Some(PeerControlRequest::GetClientBandwidthByKey { key, response_tx }) => { - response_tx.send(self.handle_get_client_bandwidth_by_key(&key).await).ok(); - } - Some(PeerControlRequest::GetClientBandwidthByIp { ip, response_tx }) => { - response_tx.send(self.handle_get_client_bandwidth_by_ip(ip).await).ok(); - } - Some(PeerControlRequest::GetVerifierByKey { key, credential, response_tx }) => { - response_tx.send(self.handle_query_verifier_by_key(&key, *credential).await).ok(); - } - Some(PeerControlRequest::GetVerifierByIp { ip, credential, response_tx }) => { - response_tx.send(self.handle_query_verifier_by_ip(ip, *credential).await).ok(); - } + Some(request) => self.handle_peer_control_request(request).await, None => { trace!("PeerController [main loop]: stopping since channel closed"); break; diff --git a/gateway/src/error.rs b/gateway/src/error.rs index c620e019d8..1b9a39b6ae 100644 --- a/gateway/src/error.rs +++ b/gateway/src/error.rs @@ -3,6 +3,7 @@ pub use crate::node::client_handling::websocket::connection_handler::authenticated::RequestHandlingError; use crate::node::internal_service_providers::authenticator::error::AuthenticatorError; +use crate::node::wireguard::GatewayWireguardError; use nym_credential_verification::upgrade_mode::UpgradeModeEnableError; use nym_gateway_stats_storage::error::StatsStorageError; use nym_gateway_storage::error::GatewayStorageError; @@ -158,6 +159,9 @@ pub enum GatewayError { #[error(transparent)] UpgradeModeEnable(#[from] UpgradeModeEnableError), + + #[error(transparent)] + WireguardFailure(#[from] GatewayWireguardError), } impl From for GatewayError { diff --git a/gateway/src/node/internal_service_providers/authenticator/error.rs b/gateway/src/node/internal_service_providers/authenticator/error.rs index 5bdde15919..15a7edf1e0 100644 --- a/gateway/src/node/internal_service_providers/authenticator/error.rs +++ b/gateway/src/node/internal_service_providers/authenticator/error.rs @@ -1,6 +1,7 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use crate::node::wireguard::GatewayWireguardError; use ipnetwork::IpNetworkError; use nym_client_core::error::ClientCoreError; use nym_credential_verification::upgrade_mode::UpgradeModeEnableError; @@ -82,9 +83,6 @@ pub enum AuthenticatorError { #[error("internal data corruption: {0}")] InternalDataCorruption(String), - #[error("peers can't be interacted with anymore")] - PeerInteractionStopped, - #[error("unknown version number")] UnknownVersion, @@ -108,6 +106,9 @@ pub enum AuthenticatorError { #[error(transparent)] UpgradeModeEnable(#[from] UpgradeModeEnableError), + + #[error(transparent)] + WireguardFailure(#[from] GatewayWireguardError), } impl AuthenticatorError { diff --git a/gateway/src/node/internal_service_providers/authenticator/mixnet_listener.rs b/gateway/src/node/internal_service_providers/authenticator/mixnet_listener.rs index 7b13c5cc42..3607ff7926 100644 --- a/gateway/src/node/internal_service_providers/authenticator/mixnet_listener.rs +++ b/gateway/src/node/internal_service_providers/authenticator/mixnet_listener.rs @@ -2,9 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 use crate::node::internal_service_providers::authenticator::{ - config::Config, error::AuthenticatorError, peer_manager::PeerManager, - seen_credential_cache::SeenCredentialCache, + config::Config, error::AuthenticatorError, seen_credential_cache::SeenCredentialCache, }; +use crate::node::wireguard::PeerManager; use defguard_wireguard_rs::net::IpAddrMask; use defguard_wireguard_rs::{host::Peer, key::Key}; use futures::StreamExt; @@ -35,7 +35,6 @@ use nym_sphinx::receiver::ReconstructedMessage; use nym_task::ShutdownToken; use nym_wireguard::WireguardGatewayData; use nym_wireguard_types::PeerPublicKey; -use rand::{prelude::IteratorRandom, thread_rng}; use std::cmp::max; use std::{ net::IpAddr, @@ -54,14 +53,14 @@ const DEFAULT_WG_CLIENT_BANDWIDTH_THRESHOLD: i64 = 1024 * 1024 * 1024; pub(crate) struct RegisteredAndFree { registration_in_progress: PendingRegistrations, - free_private_network_ips: PrivateIPs, + taken_private_network_ips: PrivateIPs, } impl RegisteredAndFree { - pub(crate) fn new(free_private_network_ips: PrivateIPs) -> Self { + pub(crate) fn new() -> Self { RegisteredAndFree { registration_in_progress: Default::default(), - free_private_network_ips, + taken_private_network_ips: Default::default(), } } } @@ -90,7 +89,6 @@ pub(crate) struct MixnetListener { impl MixnetListener { pub fn new( config: Config, - free_private_network_ips: PrivateIPs, wireguard_gateway_data: WireguardGatewayData, mixnet_client: nym_sdk::mixnet::MixnetClient, upgrade_mode: UpgradeModeDetails, @@ -101,7 +99,7 @@ impl MixnetListener { MixnetListener { config, mixnet_client, - registered_and_free: RwLock::new(RegisteredAndFree::new(free_private_network_ips)), + registered_and_free: RwLock::new(RegisteredAndFree::new()), peer_manager: PeerManager::new(wireguard_gateway_data), upgrade_mode, ecash_verifier, @@ -139,34 +137,30 @@ impl MixnetListener { .cloned() .collect(); for reg in registered_values { - let ip = registered_and_free - .free_private_network_ips + let timestamp = registered_and_free + .taken_private_network_ips .get_mut(®.gateway_data.private_ips) .ok_or(AuthenticatorError::InternalDataCorruption(format!( "IPs {} should be present", reg.gateway_data.private_ips )))?; - let Some(timestamp) = ip else { - registered_and_free - .registration_in_progress - .remove(®.gateway_data.pub_key()); - tracing::debug!( - "Removed stale registration of {}", - reg.gateway_data.pub_key() - ); - continue; - }; let duration = SystemTime::now().duration_since(*timestamp).map_err(|_| { AuthenticatorError::InternalDataCorruption( "set timestamp shouldn't have been set in the future".to_string(), ) })?; if duration > DEFAULT_REGISTRATION_TIMEOUT_CHECK { - *ip = None; registered_and_free .registration_in_progress .remove(®.gateway_data.pub_key()); + registered_and_free + .taken_private_network_ips + .remove(®.gateway_data.private_ips); + self.peer_manager + .release_ip_pair(reg.gateway_data.private_ips.into()) + .await?; + tracing::debug!( "Removed stale registration of {}", reg.gateway_data.pub_key() @@ -383,19 +377,18 @@ impl MixnetListener { return Ok((bytes, reply_to)); } - let private_ip_ref = registered_and_free - .free_private_network_ips - .iter_mut() - .filter(|r| r.1.is_none()) - .choose(&mut thread_rng()) - .ok_or(AuthenticatorError::NoFreeIp)?; - let private_ips = *private_ip_ref.0; // mark it as used, even though it's not final - *private_ip_ref.1 = Some(SystemTime::now()); + let ip_allocation = self.peer_manager.allocate_peer_ip_pair().await?; + self.registered_and_free + .write() + .await + .taken_private_network_ips + .insert(ip_allocation.into(), SystemTime::now()); + let gateway_data = GatewayClient::new( self.keypair().private_key(), remote_public.inner(), - *private_ip_ref.0, + ip_allocation.into(), nonce, ); let registration_data = latest::registration::RegistrationData { @@ -414,7 +407,7 @@ impl MixnetListener { gateway_data: v1::registration::GatewayClient::new( self.keypair().private_key(), remote_public.inner(), - private_ips.ipv4.into(), + ip_allocation.ipv4.into(), nonce, ), wg_port: registration_data.wg_port, @@ -432,7 +425,7 @@ impl MixnetListener { gateway_data: v2::registration::GatewayClient::new( self.keypair().private_key(), remote_public.inner(), - private_ips.ipv4.into(), + ip_allocation.ipv4.into(), nonce, ), wg_port: registration_data.wg_port, @@ -450,7 +443,7 @@ impl MixnetListener { gateway_data: v3::registration::GatewayClient::new( self.keypair().private_key(), remote_public.inner(), - private_ips.ipv4.into(), + ip_allocation.ipv4.into(), nonce, ), wg_port: registration_data.wg_port, @@ -592,7 +585,7 @@ impl MixnetListener { .storage() .remove_wireguard_peer(&public_key) .await?; - return Err(e); + return Err(e.into()); } registered_and_free diff --git a/gateway/src/node/internal_service_providers/authenticator/mod.rs b/gateway/src/node/internal_service_providers/authenticator/mod.rs index cee2c9041b..95423a66e7 100644 --- a/gateway/src/node/internal_service_providers/authenticator/mod.rs +++ b/gateway/src/node/internal_service_providers/authenticator/mod.rs @@ -3,21 +3,19 @@ use crate::node::internal_service_providers::authenticator::error::AuthenticatorError; use futures::channel::oneshot; -use ipnetwork::IpNetwork; use nym_client_core::{HardcodedTopologyProvider, TopologyProvider}; +use nym_credential_verification::upgrade_mode::UpgradeModeDetails; use nym_sdk::{mixnet::Recipient, GatewayTransceiver}; use nym_task::ShutdownTracker; use nym_wireguard::WireguardGatewayData; -use std::{net::IpAddr, path::Path, sync::Arc, time::SystemTime}; +use std::{path::Path, sync::Arc}; pub use config::Config; -use nym_credential_verification::upgrade_mode::UpgradeModeDetails; pub mod config; pub mod error; pub mod mixnet_client; pub mod mixnet_listener; -mod peer_manager; mod seen_credential_cache; pub struct OnStartData { @@ -40,7 +38,6 @@ pub struct Authenticator { custom_gateway_transceiver: Option>, wireguard_gateway_data: WireguardGatewayData, ecash_verifier: Arc, - used_private_network_ips: Vec, shutdown: ShutdownTracker, on_start: Option>, } @@ -50,7 +47,6 @@ impl Authenticator { config: Config, upgrade_mode_state: UpgradeModeDetails, wireguard_gateway_data: WireguardGatewayData, - used_private_network_ips: Vec, ecash_verifier: Arc< dyn nym_credential_verification::ecash::traits::EcashManager + Send + Sync, >, @@ -64,7 +60,6 @@ impl Authenticator { custom_gateway_transceiver: None, ecash_verifier, wireguard_gateway_data, - used_private_network_ips, shutdown, on_start: None, } @@ -135,26 +130,8 @@ impl Authenticator { let self_address = *mixnet_client.nym_address(); - let used_private_network_ips = - std::collections::BTreeSet::from_iter(self.used_private_network_ips.iter()); - let private_ip_network = IpNetwork::new( - self.config.authenticator.private_ipv4.into(), - self.config.authenticator.private_network_prefix_v4, - )?; - let now = SystemTime::now(); - let free_private_network_ips = private_ip_network - .iter() - .map(|ip: IpAddr| { - if used_private_network_ips.contains(&ip) { - (ip.into(), Some(now)) - } else { - (ip.into(), None) - } - }) - .collect(); let mixnet_listener = mixnet_listener::MixnetListener::new( self.config, - free_private_network_ips, self.wireguard_gateway_data, mixnet_client, self.upgrade_mode_state, diff --git a/gateway/src/node/lp_listener/handler.rs b/gateway/src/node/lp_listener/handler.rs index 35e94d4b66..9f348bb422 100644 --- a/gateway/src/node/lp_listener/handler.rs +++ b/gateway/src/node/lp_listener/handler.rs @@ -1228,8 +1228,8 @@ where #[cfg(test)] mod tests { use super::*; - use crate::node::lp_listener::peer_manager::PeerManager; use crate::node::lp_listener::{LpConfig, LpDebug}; + use crate::node::wireguard::PeerManager; use crate::node::ActiveClientsStore; use bytes::BytesMut; use nym_credential_verification::upgrade_mode::{ diff --git a/gateway/src/node/lp_listener/mod.rs b/gateway/src/node/lp_listener/mod.rs index d20b83205d..0114e28d95 100644 --- a/gateway/src/node/lp_listener/mod.rs +++ b/gateway/src/node/lp_listener/mod.rs @@ -68,7 +68,6 @@ // They can be exported via Prometheus format using the metrics endpoint. use crate::error::GatewayError; -use crate::node::lp_listener::peer_manager::PeerManager; use crate::node::lp_listener::registration::RegistrationsInProgress; use crate::node::ActiveClientsStore; use dashmap::DashMap; @@ -86,6 +85,7 @@ use tokio::net::TcpListener; use tokio::sync::Semaphore; use tracing::*; +use crate::node::wireguard::PeerManager; pub use nym_lp::peer::LpLocalPeer; pub use nym_mixnet_client::forwarder::{ mix_forwarding_channels, MixForwardingReceiver, MixForwardingSender, @@ -94,7 +94,6 @@ pub use nym_wireguard::{PeerControlRequest, WireguardGatewayData}; mod data_handler; pub mod handler; -pub mod peer_manager; mod registration; pub type ReceiverIndex = u32; @@ -578,6 +577,7 @@ impl LpListener { /// /// The task automatically stops when the shutdown signal is received. fn spawn_state_cleanup_task(&self) -> tokio::task::JoinHandle<()> { + let peer_manager = Arc::clone(&self.handler_state.peer_manager); let handshake_states = Arc::clone(&self.handler_state.handshake_states); let session_states = Arc::clone(&self.handler_state.session_states); let pending_registrations = self.handler_state.registrations_in_progress.clone(); @@ -598,6 +598,7 @@ impl LpListener { self.shutdown.try_spawn_named( cleanup_task::cleanup_loop( + peer_manager, handshake_states, session_states, pending_registrations, @@ -620,15 +621,17 @@ impl LpListener { pub(crate) mod cleanup_task { use crate::node::lp_listener::registration::RegistrationsInProgress; use crate::node::lp_listener::{LpDebug, TimestampedState}; + use crate::node::wireguard::PeerManager; use dashmap::DashMap; use nym_lp::state_machine::LpStateBare; use nym_lp::LpStateMachine; use nym_metrics::inc_by; use nym_node_metrics::NymNodeMetrics; use std::sync::Arc; - use tracing::{debug, info}; + use tracing::{debug, error, info}; async fn perform_cleanup( + peer_manager: &PeerManager, handshake_states: &Arc>>, session_states: &Arc>>, registrations_in_progress: &RegistrationsInProgress, @@ -678,17 +681,27 @@ pub(crate) mod cleanup_task { }); // Remove stale registrations (based on time since last activity) - registrations_in_progress - .lock() - .await - .retain(|_, timestamped| { - if timestamped.age() > pending_registration_ttl { - pending_reg_removed += 1; - false - } else { - true - } - }); + let mut reg_guard = registrations_in_progress.lock().await; + let mut stale_registrations = Vec::new(); + for (k, timestamped) in reg_guard.iter() { + if timestamped.age() > pending_registration_ttl { + stale_registrations.push(*k) + } + } + + for to_remove in stale_registrations { + pending_reg_removed += 1; + + // SAFETY: we never dropped the guard and the entry existed + #[allow(clippy::unwrap_used)] + let entry = reg_guard.remove(&to_remove).unwrap(); + if let Err(err) = peer_manager + .release_ip_pair(entry.state.allocated_ip_pair()) + .await + { + error!("failed to release allocated ip pair: {err}") + } + } if hs_removed > 0 || ss_removed > 0 || demoted_removed > 0 || pending_reg_removed > 0 { let duration = start.elapsed(); @@ -724,6 +737,7 @@ pub(crate) mod cleanup_task { /// Demoted sessions (ReadOnlyTransport) use shorter TTL since they /// only need to drain in-flight packets after subsession promotion. pub(crate) async fn cleanup_loop( + peer_manager: Arc, handshake_states: Arc>>, session_states: Arc>>, registrations_in_progress: RegistrationsInProgress, @@ -743,7 +757,7 @@ pub(crate) mod cleanup_task { break; } _ = cleanup_interval.tick() => { - perform_cleanup(&handshake_states, &session_states, ®istrations_in_progress, cfg).await; + perform_cleanup(&peer_manager, &handshake_states, &session_states, ®istrations_in_progress, cfg).await; } } } diff --git a/gateway/src/node/lp_listener/peer_manager.rs b/gateway/src/node/lp_listener/peer_manager.rs deleted file mode 100644 index 027fe9221f..0000000000 --- a/gateway/src/node/lp_listener/peer_manager.rs +++ /dev/null @@ -1,127 +0,0 @@ -// Copyright 2026 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use crate::GatewayError; -use defguard_wireguard_rs::host::Peer; -use defguard_wireguard_rs::key::Key; -use futures::channel::oneshot; -use nym_credential_verification::ClientBandwidth; -use nym_wireguard::peer_controller::IpPair; -use nym_wireguard::{PeerControlRequest, PeerRegistrationData, WireguardGatewayData}; -use nym_wireguard_types::PeerPublicKey; -use tracing::error; - -/// attempts to replicate [`crate::node::internal_service_providers::authenticator::peer_manager::PeerManager`] -// TODO: put those in the shared crate -pub struct PeerManager { - pub(crate) wireguard_gateway_data: WireguardGatewayData, -} - -impl PeerManager { - pub fn new(wireguard_gateway_data: WireguardGatewayData) -> Self { - PeerManager { - wireguard_gateway_data, - } - } - - pub async fn register_peer( - &self, - registration_data: PeerRegistrationData, - ) -> Result { - let (response_tx, response_rx) = oneshot::channel(); - let msg = PeerControlRequest::RegisterPeer { - registration_data, - response_tx, - }; - self.wireguard_gateway_data - .peer_tx() - .send(msg) - .await - .map_err(|e| { - GatewayError::InternalError(format!("Failed to send IP allocation request: {e}")) - })?; - - response_rx - .await - .map_err(|e| { - GatewayError::InternalError(format!("Failed to receive IP allocation: {e}")) - })? - .map_err(|e| { - error!("Failed to allocate IPs from pool: {e}"); - GatewayError::InternalError(format!("Failed to allocate IPs: {e}")) - }) - } - - pub async fn add_peer(&self, peer: Peer) -> Result<(), GatewayError> { - let (response_tx, response_rx) = oneshot::channel(); - let msg = PeerControlRequest::AddPeer { peer, response_tx }; - self.wireguard_gateway_data - .peer_tx() - .send(msg) - .await - .map_err(|e| { - GatewayError::InternalError(format!("Failed to send peer request: {e}")) - })?; - - response_rx - .await - .map_err(|_| GatewayError::InternalError("no response for add peer".to_string()))? - .map_err(|err| { - GatewayError::InternalError(format!("adding peer could not be performed: {err:?}")) - }) - } - - pub async fn query_peer( - &self, - public_key: PeerPublicKey, - ) -> Result, GatewayError> { - let key = Key::new(public_key.to_bytes()); - let (response_tx, response_rx) = oneshot::channel(); - let msg = PeerControlRequest::QueryPeer { key, response_tx }; - self.wireguard_gateway_data - .peer_tx() - .send(msg) - .await - .map_err(|_| { - GatewayError::InternalError("Failed to send peer query request".to_string()) - })?; - - response_rx - .await - .map_err(|_| GatewayError::InternalError("no response for query peer".to_string()))? - .map_err(|err| { - GatewayError::InternalError(format!( - "querying peer could not be performed: {err:?}" - )) - }) - } - - pub async fn query_client_bandwidth( - &self, - key: PeerPublicKey, - ) -> Result { - let key = Key::new(key.to_bytes()); - let (response_tx, response_rx) = oneshot::channel(); - let msg = PeerControlRequest::GetClientBandwidthByKey { key, response_tx }; - self.wireguard_gateway_data - .peer_tx() - .send(msg) - .await - .map_err(|_| { - GatewayError::InternalError( - "Failed to send peer bandwidth query request".to_string(), - ) - })?; - - response_rx - .await - .map_err(|_| { - GatewayError::InternalError("no response for query peer bandwidth".to_string()) - })? - .map_err(|err| { - GatewayError::InternalError(format!( - "querying client bandwidth could not be performed: {err:?}" - )) - }) - } -} diff --git a/gateway/src/node/lp_listener/registration.rs b/gateway/src/node/lp_listener/registration.rs index 5df0c609d3..577ab9a460 100644 --- a/gateway/src/node/lp_listener/registration.rs +++ b/gateway/src/node/lp_listener/registration.rs @@ -26,6 +26,7 @@ use nym_registration_common::{ LpRegistrationRequest, LpRegistrationRequestData, LpRegistrationResponse, RegistrationMode, RegistrationStatus, WireguardConfiguration, }; +use nym_wireguard::peer_controller::IpPair; use nym_wireguard::WireguardConfig; use nym_wireguard_types::PeerPublicKey; use std::collections::HashMap; @@ -71,6 +72,15 @@ pub struct PendingRegistrationState { wireguard_config: WireguardConfiguration, } +impl PendingRegistrationState { + pub(crate) fn allocated_ip_pair(&self) -> IpPair { + IpPair::new( + self.wireguard_config.private_ipv4, + self.wireguard_config.private_ipv6, + ) + } +} + #[derive(Clone, Default)] pub struct RegistrationsInProgress { /// Wrapped in TimestampedState for TTL-based cleanup of stale data. @@ -416,12 +426,8 @@ impl LpHandlerState { // Allocate IPs from centralized pool managed by PeerController let defguard_key = Key::new(peer_key.to_bytes()); - let registration_data = nym_wireguard::PeerRegistrationData::new(defguard_key.clone(), psk); - - let psk = registration_data.preshared_key.clone(); - // Request IP allocation from PeerController - let ip_pair = self.peer_manager.register_peer(registration_data).await?; + let ip_pair = self.peer_manager.allocate_peer_ip_pair().await?; let client_ipv4 = ip_pair.ipv4; let client_ipv6 = ip_pair.ipv6; diff --git a/gateway/src/node/mod.rs b/gateway/src/node/mod.rs index 62263d521c..a8a613f2ec 100644 --- a/gateway/src/node/mod.rs +++ b/gateway/src/node/mod.rs @@ -18,6 +18,7 @@ use nym_credential_verification::upgrade_mode::{ }; use nym_crypto::asymmetric::{ed25519, x25519}; use nym_ip_packet_router::IpPacketRouter; +use nym_lp::peer::LpLocalPeer; use nym_mixnet_client::forwarder::MixForwardingSender; use nym_network_defaults::NymNetworkDetails; use nym_network_requester::NRServiceProviderBuilder; @@ -36,8 +37,8 @@ use tokio::sync::Semaphore; use tracing::*; use zeroize::Zeroizing; -use crate::node::lp_listener::peer_manager::PeerManager; pub use crate::node::upgrade_mode::watcher::UpgradeModeWatcher; +use crate::node::wireguard::PeerManager; pub use client_handling::active_clients::ActiveClientsStore; pub use lp_listener::LpConfig; pub use nym_credential_verification::upgrade_mode::UpgradeModeCheckRequestSender; @@ -47,7 +48,6 @@ pub use nym_gateway_storage::{ traits::{BandwidthGatewayStorage, InboxGatewayStorage}, GatewayStorage, }; -use nym_lp::peer::LpLocalPeer; pub use nym_sdk::{NymApiTopologyProvider, NymApiTopologyProviderConfig, UserAgent}; pub(crate) mod client_handling; @@ -55,6 +55,7 @@ pub(crate) mod internal_service_providers; pub mod lp_listener; mod stale_data_cleaner; pub mod upgrade_mode; +pub mod wireguard; #[derive(Debug, Clone)] pub struct LocalNetworkRequesterOpts { @@ -492,25 +493,12 @@ impl GatewayTasksBuilder { Ok(peers) } - async fn get_wireguard_networks(&mut self) -> Result, GatewayError> { - if let Some(cached) = self.wireguard_networks.take() { - return Ok(cached); - } - - let (peers, used_private_network_ips) = self.build_wireguard_peers_and_networks().await?; - // cache peers for the other task - - self.wireguard_peers = Some(peers); - Ok(used_private_network_ips) - } - pub async fn build_wireguard_authenticator( &mut self, upgrade_mode_common: UpgradeModeDetails, topology_provider: Box, ) -> Result, GatewayError> { let ecash_manager = self.ecash_manager().await?; - let used_private_network_ips = self.get_wireguard_networks().await?; let Some(opts) = &self.authenticator_opts else { return Err(GatewayError::UnspecifiedAuthenticatorConfig); @@ -533,7 +521,6 @@ impl GatewayTasksBuilder { opts.config.clone(), upgrade_mode_common, wireguard_data.inner.clone(), - used_private_network_ips, ecash_manager, self.shutdown_tracker.clone(), ) diff --git a/gateway/src/node/wireguard/error.rs b/gateway/src/node/wireguard/error.rs new file mode 100644 index 0000000000..de0a6aaa9f --- /dev/null +++ b/gateway/src/node/wireguard/error.rs @@ -0,0 +1,19 @@ +// Copyright 2026 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum GatewayWireguardError { + #[error("internal error: {0}")] + InternalError(String), + + #[error("peers can't be interacted with anymore")] + PeerInteractionStopped, +} + +impl GatewayWireguardError { + pub fn internal(message: impl Into) -> Self { + GatewayWireguardError::InternalError(message.into()) + } +} diff --git a/gateway/src/node/wireguard/mod.rs b/gateway/src/node/wireguard/mod.rs new file mode 100644 index 0000000000..7c9a02b8e1 --- /dev/null +++ b/gateway/src/node/wireguard/mod.rs @@ -0,0 +1,8 @@ +// Copyright 2026 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +pub mod error; +pub mod peer_manager; + +pub use error::GatewayWireguardError; +pub use peer_manager::PeerManager; diff --git a/gateway/src/node/internal_service_providers/authenticator/peer_manager.rs b/gateway/src/node/wireguard/peer_manager.rs similarity index 84% rename from gateway/src/node/internal_service_providers/authenticator/peer_manager.rs rename to gateway/src/node/wireguard/peer_manager.rs index c057dfa57c..0921b758ca 100644 --- a/gateway/src/node/internal_service_providers/authenticator/peer_manager.rs +++ b/gateway/src/node/wireguard/peer_manager.rs @@ -1,14 +1,17 @@ -// Copyright 2024 - Nym Technologies SA +// Copyright 2026 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::node::internal_service_providers::authenticator::error::AuthenticatorError; +use crate::node::wireguard::GatewayWireguardError; use defguard_wireguard_rs::{host::Peer, key::Key}; use futures::channel::oneshot; use nym_credential_verification::{ClientBandwidth, TicketVerifier}; use nym_credentials_interface::CredentialSpendingData; +use nym_wireguard::peer_controller::IpPair; use nym_wireguard::{peer_controller::PeerControlRequest, WireguardGatewayData}; use nym_wireguard_types::PeerPublicKey; +use tracing::error; +#[derive(Clone)] pub struct PeerManager { pub(crate) wireguard_gateway_data: WireguardGatewayData, } @@ -19,26 +22,73 @@ impl PeerManager { wireguard_gateway_data, } } - pub async fn add_peer(&self, peer: Peer) -> Result<(), AuthenticatorError> { + + pub async fn allocate_peer_ip_pair(&self) -> Result { + let (response_tx, response_rx) = oneshot::channel(); + let msg = PeerControlRequest::AllocatePeerIpPair { response_tx }; + self.wireguard_gateway_data + .peer_tx() + .send(msg) + .await + .map_err(|_| GatewayWireguardError::PeerInteractionStopped)?; + + response_rx + .await + .map_err(|e| { + GatewayWireguardError::InternalError(format!( + "Failed to receive IP allocation: {e}" + )) + })? + .map_err(|e| { + error!("Failed to allocate IPs from pool: {e}"); + GatewayWireguardError::InternalError(format!("Failed to allocate IPs: {e}")) + }) + } + + pub async fn release_ip_pair(&self, ip_pair: IpPair) -> Result<(), GatewayWireguardError> { + let (response_tx, response_rx) = oneshot::channel(); + let msg = PeerControlRequest::ReleaseIpPair { + response_tx, + ip_pair, + }; + self.wireguard_gateway_data + .peer_tx() + .send(msg) + .await + .map_err(|_| GatewayWireguardError::PeerInteractionStopped)?; + + response_rx + .await + .map_err(|_| GatewayWireguardError::internal("no response for release ip allocation"))? + .map_err(|err| { + GatewayWireguardError::InternalError(format!( + "releasing ip pair not be performed: {err:?}" + )) + })?; + + Ok(()) + } + + pub async fn add_peer(&self, peer: Peer) -> Result<(), GatewayWireguardError> { let (response_tx, response_rx) = oneshot::channel(); let msg = PeerControlRequest::AddPeer { peer, response_tx }; self.wireguard_gateway_data .peer_tx() .send(msg) .await - .map_err(|_| AuthenticatorError::PeerInteractionStopped)?; + .map_err(|_| GatewayWireguardError::PeerInteractionStopped)?; response_rx .await - .map_err(|_| AuthenticatorError::InternalError("no response for add peer".to_string()))? + .map_err(|_| GatewayWireguardError::internal("no response for add peer".to_string()))? .map_err(|err| { - AuthenticatorError::InternalError(format!( + GatewayWireguardError::internal(format!( "adding peer could not be performed: {err:?}" )) }) } - pub async fn _remove_peer(&self, pub_key: PeerPublicKey) -> Result<(), AuthenticatorError> { + pub async fn _remove_peer(&self, pub_key: PeerPublicKey) -> Result<(), GatewayWireguardError> { let key = Key::new(pub_key.to_bytes()); let (response_tx, response_rx) = oneshot::channel(); let msg = PeerControlRequest::RemovePeer { key, response_tx }; @@ -46,15 +96,13 @@ impl PeerManager { .peer_tx() .send(msg) .await - .map_err(|_| AuthenticatorError::PeerInteractionStopped)?; + .map_err(|_| GatewayWireguardError::PeerInteractionStopped)?; response_rx .await - .map_err(|_| { - AuthenticatorError::InternalError("no response for remove peer".to_string()) - })? + .map_err(|_| GatewayWireguardError::internal("no response for remove peer"))? .map_err(|err| { - AuthenticatorError::InternalError(format!( + GatewayWireguardError::InternalError(format!( "removing peer could not be performed: {err:?}" )) }) @@ -63,7 +111,7 @@ impl PeerManager { pub async fn query_peer( &self, public_key: PeerPublicKey, - ) -> Result, AuthenticatorError> { + ) -> Result, GatewayWireguardError> { let key = Key::new(public_key.to_bytes()); let (response_tx, response_rx) = oneshot::channel(); let msg = PeerControlRequest::QueryPeer { key, response_tx }; @@ -71,15 +119,13 @@ impl PeerManager { .peer_tx() .send(msg) .await - .map_err(|_| AuthenticatorError::PeerInteractionStopped)?; + .map_err(|_| GatewayWireguardError::PeerInteractionStopped)?; response_rx .await - .map_err(|_| { - AuthenticatorError::InternalError("no response for query peer".to_string()) - })? + .map_err(|_| GatewayWireguardError::internal("no response for query peer".to_string()))? .map_err(|err| { - AuthenticatorError::InternalError(format!( + GatewayWireguardError::internal(format!( "querying peer could not be performed: {err:?}" )) }) @@ -88,7 +134,7 @@ impl PeerManager { pub async fn query_bandwidth( &self, public_key: PeerPublicKey, - ) -> Result { + ) -> Result { let client_bandwidth = self.query_client_bandwidth(public_key).await?; Ok(client_bandwidth.available().await) } @@ -96,7 +142,7 @@ impl PeerManager { pub async fn query_client_bandwidth( &self, key: PeerPublicKey, - ) -> Result { + ) -> Result { let key = Key::new(key.to_bytes()); let (response_tx, response_rx) = oneshot::channel(); let msg = PeerControlRequest::GetClientBandwidthByKey { key, response_tx }; @@ -104,17 +150,13 @@ impl PeerManager { .peer_tx() .send(msg) .await - .map_err(|_| AuthenticatorError::PeerInteractionStopped)?; + .map_err(|_| GatewayWireguardError::PeerInteractionStopped)?; response_rx .await - .map_err(|_| { - AuthenticatorError::InternalError( - "no response for query client bandwidth".to_string(), - ) - })? + .map_err(|_| GatewayWireguardError::internal("no response for query client bandwidth"))? .map_err(|err| { - AuthenticatorError::InternalError(format!( + GatewayWireguardError::internal(format!( "querying client bandwidth could not be performed: {err:?}" )) }) @@ -124,7 +166,7 @@ impl PeerManager { &self, key: PeerPublicKey, credential: CredentialSpendingData, - ) -> Result, AuthenticatorError> { + ) -> Result, GatewayWireguardError> { let key = Key::new(key.to_bytes()); let (response_tx, response_rx) = oneshot::channel(); let msg = PeerControlRequest::GetVerifierByKey { @@ -136,15 +178,15 @@ impl PeerManager { .peer_tx() .send(msg) .await - .map_err(|_| AuthenticatorError::PeerInteractionStopped)?; + .map_err(|_| GatewayWireguardError::PeerInteractionStopped)?; response_rx .await .map_err(|_| { - AuthenticatorError::InternalError("no response for query verifier".to_string()) + GatewayWireguardError::internal("no response for query verifier".to_string()) })? .map_err(|err| { - AuthenticatorError::InternalError(format!( + GatewayWireguardError::internal(format!( "querying verifier could not be performed: {err:?}" )) }) diff --git a/integration-tests/src/lp_registration.rs b/integration-tests/src/lp_registration.rs index d292a2a441..2e171962c4 100644 --- a/integration-tests/src/lp_registration.rs +++ b/integration-tests/src/lp_registration.rs @@ -14,11 +14,11 @@ mod tests { use nym_crypto::asymmetric::{ed25519, x25519}; use nym_gateway::GatewayError; use nym_gateway::node::lp_listener::handler::LpConnectionHandler; - use nym_gateway::node::lp_listener::peer_manager::PeerManager; use nym_gateway::node::lp_listener::{ LpDebug, LpHandlerState, LpLocalPeer, MixForwardingReceiver, PeerControlRequest, WireguardGatewayData, mix_forwarding_channels, }; + use nym_gateway::node::wireguard::PeerManager; use nym_gateway::node::{ActiveClientsStore, GatewayStorage, LpConfig}; use nym_registration_client::{LpClientError, LpRegistrationClient}; use nym_test_utils::helpers::{CryptoRng, RngCore, u64_seeded_rng}; @@ -443,11 +443,10 @@ mod tests { // 1) peer registration - ip pair allocation let ip_pair = entry.allocate_ip_pair().await; let reg_res = Ok::<_, nym_wireguard::Error>(ip_pair); - let public_key = client_key.to_wg_key(); entry .register_peer_controller_response( - PeerControlRequestType::RegisterPeer { public_key }, + PeerControlRequestType::AllocatePeerIpPair {}, reg_res, ) .await; @@ -497,7 +496,6 @@ mod tests { let peers_guard = entry.mock_peer_controller_state.peers.read().await; let peer = peers_guard.get_by_x25519_key(&client_key).unwrap().clone(); drop(peers_guard); - assert!(peer.register_success); assert!(peer.add_success); assert_eq!(registration_result.private_ipv4, ip_pair.ipv4); @@ -616,11 +614,10 @@ mod tests { // 1) peer registration - ip pair allocation let entry_ip_pair = entry.allocate_ip_pair().await; let reg_res = Ok::<_, nym_wireguard::Error>(entry_ip_pair); - let public_key = client_key.to_wg_key(); entry .register_peer_controller_response( - PeerControlRequestType::RegisterPeer { public_key }, + PeerControlRequestType::AllocatePeerIpPair {}, reg_res, ) .await; @@ -668,10 +665,9 @@ mod tests { // 1) peer registration - ip pair allocation let exit_ip_pair = exit.allocate_ip_pair().await; let reg_res = Ok::<_, nym_wireguard::Error>(exit_ip_pair); - let public_key = client_key.to_wg_key(); exit.register_peer_controller_response( - PeerControlRequestType::RegisterPeer { public_key }, + PeerControlRequestType::AllocatePeerIpPair {}, reg_res, ) .await; @@ -738,13 +734,11 @@ mod tests { let peers_guard = entry.mock_peer_controller_state.peers.read().await; let entry_peer = peers_guard.get_by_x25519_key(&client_key).unwrap().clone(); drop(peers_guard); - assert!(entry_peer.register_success); assert!(entry_peer.add_success); let peers_guard = exit.mock_peer_controller_state.peers.read().await; let exit_peer = peers_guard.get_by_x25519_key(&client_key).unwrap().clone(); drop(peers_guard); - assert!(exit_peer.register_success); assert!(exit_peer.add_success); assert_eq!(entry_registration_result.private_ipv4, entry_ip_pair.ipv4); From 8916b021a9f9695d2492bb9f1d7a7b9db3a384aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Fri, 30 Jan 2026 12:38:32 +0000 Subject: [PATCH 39/51] lp: attempt to negotiate (and use) protocol version (#6399) --- common/nym-lp/src/lib.rs | 35 ++++- common/nym-lp/src/packet.rs | 4 +- common/nym-lp/src/session.rs | 84 ++++++++---- common/nym-lp/src/session_integration/mod.rs | 65 ++++++++- common/nym-lp/src/session_manager.rs | 57 +++++++- common/nym-lp/src/state_machine.rs | 124 ++++++++++++++---- common/registration/src/lib.rs | 4 + gateway/src/node/lp_listener/handler.rs | 35 +++-- integration-tests/src/lp_registration.rs | 6 + nym-gateway-probe/src/common/nodes.rs | 5 +- nym-gateway-probe/src/common/probe_tests.rs | 14 +- nym-gateway-probe/src/run.rs | 4 +- nym-registration-client/src/lib.rs | 12 +- .../src/lp_client/client.rs | 75 +++++++---- .../src/lp_client/nested_session.rs | 23 ++++ tools/nym-lp-client/src/client.rs | 3 + tools/nym-lp-client/src/topology.rs | 1 + 17 files changed, 441 insertions(+), 110 deletions(-) diff --git a/common/nym-lp/src/lib.rs b/common/nym-lp/src/lib.rs index 0d2afe0b22..e1198e7fdc 100644 --- a/common/nym-lp/src/lib.rs +++ b/common/nym-lp/src/lib.rs @@ -38,12 +38,25 @@ pub fn sessions_for_tests() -> (LpSession, LpSession) { // Use consistent salt for deterministic tests let salt = [1u8; 32]; - let initiator_session = - LpSession::new(receiver_index, true, init.clone(), resp.as_remote(), &salt) - .expect("Test session creation failed"); + let initiator_session = LpSession::new( + receiver_index, + true, + init.clone(), + resp.as_remote(), + &salt, + packet::version::CURRENT, + ) + .expect("Test session creation failed"); - let responder_session = LpSession::new(receiver_index, false, resp, init.as_remote(), &salt) - .expect("Test session creation failed"); + let responder_session = LpSession::new( + receiver_index, + false, + resp, + init.as_remote(), + &salt, + packet::version::CURRENT, + ) + .expect("Test session creation failed"); (initiator_session, responder_session) } @@ -51,7 +64,7 @@ pub fn sessions_for_tests() -> (LpSession, LpSession) { #[cfg(test)] mod tests { use crate::message::LpMessage; - use crate::packet::{LpHeader, LpPacket, TRAILER_LEN}; + use crate::packet::{LpHeader, LpPacket, TRAILER_LEN, version}; use crate::session_manager::SessionManager; use crate::{LpError, sessions_for_tests}; use bytes::BytesMut; @@ -182,11 +195,19 @@ mod tests { init.clone(), resp.as_remote(), &salt, + version::CURRENT, ) .unwrap(); let _ = remote_manager - .create_session_state_machine(receiver_index, false, resp, init.as_remote(), &salt) + .create_session_state_machine( + receiver_index, + false, + resp, + init.as_remote(), + &salt, + version::CURRENT, + ) .unwrap(); // === Packet 1 (Counter 0 - Should succeed) === let packet1 = LpPacket { diff --git a/common/nym-lp/src/packet.rs b/common/nym-lp/src/packet.rs index 2873951680..e93edfaad4 100644 --- a/common/nym-lp/src/packet.rs +++ b/common/nym-lp/src/packet.rs @@ -203,9 +203,9 @@ impl LpHeader { } impl LpHeader { - pub fn new(receiver_idx: u32, counter: u64) -> Self { + pub fn new(receiver_idx: u32, counter: u64, protocol_version: u8) -> Self { Self { - protocol_version: version::CURRENT, + protocol_version, reserved: [0u8; 3], receiver_idx, counter, diff --git a/common/nym-lp/src/session.rs b/common/nym-lp/src/session.rs index a6561eb199..0b8cee57a3 100644 --- a/common/nym-lp/src/session.rs +++ b/common/nym-lp/src/session.rs @@ -231,7 +231,7 @@ pub struct LpSession { /// Negotiated protocol version from handshake. /// Set during handshake completion from the ClientHello/ServerHello packet header. /// Used for future version negotiation and compatibility checks. - negotiated_version: std::sync::atomic::AtomicU8, + negotiated_version: u8, } // noiserm @@ -276,18 +276,9 @@ impl LpSession { /// Returns the negotiated protocol version from the handshake. /// - /// Defaults to 1 (current LP version). Set during handshake via - /// `set_negotiated_version()` when ClientHello/ServerHello is processed. + /// Set during `LpSession` creation after sending / receiving `ClientHelloData` pub fn negotiated_version(&self) -> u8 { - self.negotiated_version.load(Ordering::Acquire) - } - - /// Sets the negotiated protocol version from handshake packet header. - /// - /// Should be called during handshake when processing ClientHello (responder) - /// or ServerHello (initiator) to record the agreed protocol version. - pub fn set_negotiated_version(&self, version: u8) { - self.negotiated_version.store(version, Ordering::Release); + self.negotiated_version } /// Returns the local X25519 public key. @@ -364,12 +355,14 @@ impl LpSession { /// * `local_peer` - This side's LP peer's keys /// * `remote_peer` - The remote's LP peer's keys /// * `salt` - Salt for PSK derivation + /// * `protocol_version` - Protocol version to attach in all `LpPacket`s pub fn new( id: u32, is_initiator: bool, local_peer: LpLocalPeer, remote_peer: LpRemotePeer, salt: &[u8; 32], + protocol_version: u8, ) -> Result { // noiserm // if we're LP responder, we **must** set our kem key @@ -444,13 +437,13 @@ impl LpSession { subsession_counter: AtomicU64::new(0), read_only: AtomicBool::new(false), successor_session_id: Mutex::new(None), - negotiated_version: std::sync::atomic::AtomicU8::new(1), // Default to version 1 + negotiated_version: protocol_version, }) } pub fn next_packet(&self, message: LpMessage) -> Result { let counter = self.next_counter(); - let header = LpHeader::new(self.id(), counter); + let header = LpHeader::new(self.id(), counter, self.negotiated_version); let packet = LpPacket::new(header, message); Ok(packet) } @@ -1275,6 +1268,7 @@ impl LpSession { remote_peer: self.remote_peer.clone(), pq_shared_secret: PqSharedSecret::new(pq_secret), subsession_psk, + negotiated_version: self.negotiated_version, }) } } @@ -1314,8 +1308,12 @@ pub struct SubsessionHandshake { /// PQ shared secret inherited from parent (for creating further subsessions) pq_shared_secret: PqSharedSecret, + /// Subsession PSK (for deriving outer AEAD key) subsession_psk: [u8; 32], + + /// Negotiated protocol version from handshake. + negotiated_version: u8, } impl SubsessionHandshake { @@ -1415,7 +1413,7 @@ impl SubsessionHandshake { read_only: AtomicBool::new(false), successor_session_id: Mutex::new(None), // Inherit parent's protocol version - negotiated_version: std::sync::atomic::AtomicU8::new(1), + negotiated_version: self.negotiated_version, }) } } @@ -1423,6 +1421,7 @@ impl SubsessionHandshake { #[cfg(test)] mod tests { use super::*; + use crate::packet::version; use crate::peer::mock_peers; use crate::{replay::ReplayError, sessions_for_tests}; use nym_crypto::asymmetric::ed25519; @@ -1448,8 +1447,15 @@ mod tests { let salt = [0u8; 32]; // Test salt // PSQ will derive the PSK during handshake using X25519 as DHKEM - let session = LpSession::new(receiver_index, is_initiator, local, remote.clone(), &salt) - .expect("Test session creation failed"); + let session = LpSession::new( + receiver_index, + is_initiator, + local, + remote.clone(), + &salt, + version::CURRENT, + ) + .expect("Test session creation failed"); // Initialize KKT state to Completed for tests (bypasses KKT exchange) // This simulates having already received the remote party's KEM key via KKT @@ -2112,14 +2118,28 @@ mod tests { let receiver_index: u32 = 55555; let salt = [0u8; 32]; - let initiator_session = - LpSession::new(receiver_index, true, init.clone(), bad_resp, &salt).unwrap(); + let initiator_session = LpSession::new( + receiver_index, + true, + init.clone(), + bad_resp, + &salt, + version::CURRENT, + ) + .unwrap(); // Initialize KKT state for test initiator_session.set_kkt_completed_for_test(resp.x25519.public_key()); - let responder_session = - LpSession::new(receiver_index, false, resp, bad_init, &salt).unwrap(); + let responder_session = LpSession::new( + receiver_index, + false, + resp, + bad_init, + &salt, + version::CURRENT, + ) + .unwrap(); // Initialize KKT state for test responder_session.set_kkt_completed_for_test(init.x25519.public_key()); @@ -2154,13 +2174,27 @@ mod tests { let receiver_index: u32 = 66666; let salt = [0u8; 32]; - let initiator_session = - LpSession::new(receiver_index, true, init.clone(), resp.as_remote(), &salt).unwrap(); + let initiator_session = LpSession::new( + receiver_index, + true, + init.clone(), + resp.as_remote(), + &salt, + version::CURRENT, + ) + .unwrap(); // Initialize KKT state for test initiator_session.set_kkt_completed_for_test(resp.x25519.public_key()); - let responder_session = - LpSession::new(receiver_index, false, resp, bad_init, &salt).unwrap(); + let responder_session = LpSession::new( + receiver_index, + false, + resp, + bad_init, + &salt, + version::CURRENT, + ) + .unwrap(); // Initialize KKT state for test responder_session.set_kkt_completed_for_test(init.x25519.public_key()); diff --git a/common/nym-lp/src/session_integration/mod.rs b/common/nym-lp/src/session_integration/mod.rs index 7733b5459f..7e52d88b9c 100644 --- a/common/nym-lp/src/session_integration/mod.rs +++ b/common/nym-lp/src/session_integration/mod.rs @@ -58,11 +58,25 @@ mod tests { // 4. Create sessions using the pre-built Noise states let peer_a_sm = session_manager_1 - .create_session_state_machine(receiver_index, true, a.clone(), b.as_remote(), &salt) + .create_session_state_machine( + receiver_index, + true, + a.clone(), + b.as_remote(), + &salt, + version::CURRENT, + ) .expect("Failed to create session A"); let peer_b_sm = session_manager_2 - .create_session_state_machine(receiver_index, false, b.clone(), a.as_remote(), &salt) + .create_session_state_machine( + receiver_index, + false, + b.clone(), + a.as_remote(), + &salt, + version::CURRENT, + ) .expect("Failed to create session B"); // Verify session count @@ -481,11 +495,25 @@ mod tests { let salt = [43u8; 32]; let peer_a_sm = session_manager_1 - .create_session_state_machine(receiver_index, true, a.clone(), b.as_remote(), &salt) + .create_session_state_machine( + receiver_index, + true, + a.clone(), + b.as_remote(), + &salt, + version::CURRENT, + ) .expect("Failed to create session A"); let peer_b_sm = session_manager_2 - .create_session_state_machine(receiver_index, false, b.clone(), a.as_remote(), &salt) + .create_session_state_machine( + receiver_index, + false, + b.clone(), + a.as_remote(), + &salt, + version::CURRENT, + ) .expect("Failed to create session B"); // Initialize KKT state for both sessions (test bypass) @@ -659,7 +687,14 @@ mod tests { // 2. Create a session (using real noise state) let _session = session_manager - .create_session_state_machine(receiver_index, true, a.clone(), b.as_remote(), &salt) + .create_session_state_machine( + receiver_index, + true, + a.clone(), + b.as_remote(), + &salt, + version::CURRENT, + ) .expect("Failed to create session"); // 3. Try to get a non-existent session @@ -682,6 +717,7 @@ mod tests { a.clone(), b.as_remote(), &salt, + version::CURRENT, ) .expect("Failed to create temp session"); @@ -733,6 +769,7 @@ mod tests { } // Remove unused imports if SessionManager methods are no longer direct dependencies // use crate::noise_protocol::{create_noise_state, create_noise_state_responder}; + use crate::packet::version; use crate::peer::mock_peers; use crate::state_machine::LpData; use crate::{ @@ -772,12 +809,26 @@ mod tests { // 3. Create sessions state machines assert!( session_manager_1 - .create_session_state_machine(receiver_index, true, a.clone(), b.as_remote(), &salt,) // Initiator + .create_session_state_machine( + receiver_index, + true, + a.clone(), + b.as_remote(), + &salt, + version::CURRENT + ) // Initiator .is_ok() ); assert!( session_manager_2 - .create_session_state_machine(receiver_index, false, b, a.as_remote(), &salt,) // Responder + .create_session_state_machine( + receiver_index, + false, + b, + a.as_remote(), + &salt, + version::CURRENT + ) // Responder .is_ok() ); diff --git a/common/nym-lp/src/session_manager.rs b/common/nym-lp/src/session_manager.rs index 41b28da1c7..9988167164 100644 --- a/common/nym-lp/src/session_manager.rs +++ b/common/nym-lp/src/session_manager.rs @@ -172,8 +172,16 @@ impl SessionManager { local_peer: LpLocalPeer, remote_peer: LpRemotePeer, salt: &[u8; 32], + protocol_version: u8, ) -> Result { - let sm = LpStateMachine::new(receiver_index, is_initiator, local_peer, remote_peer, salt)?; + let sm = LpStateMachine::new( + receiver_index, + is_initiator, + local_peer, + remote_peer, + salt, + protocol_version, + )?; self.state_machines.insert(receiver_index, sm); Ok(receiver_index) @@ -204,6 +212,7 @@ impl SessionManager { #[cfg(test)] mod tests { use super::*; + use crate::packet::version; use crate::peer::{mock_peers, random_peer}; use nym_test_utils::helpers::deterministic_rng; @@ -218,7 +227,14 @@ mod tests { let receiver_index: u32 = 1001; let sm_1_id = manager - .create_session_state_machine(receiver_index, true, local, peer1.as_remote(), &salt) + .create_session_state_machine( + receiver_index, + true, + local, + peer1.as_remote(), + &salt, + version::CURRENT, + ) .unwrap(); let retrieved = manager.state_machine_exists(sm_1_id); @@ -239,7 +255,14 @@ mod tests { let receiver_index: u32 = 2002; let sm_1_id = manager - .create_session_state_machine(receiver_index, true, local, peer1.as_remote(), &salt) + .create_session_state_machine( + receiver_index, + true, + local, + peer1.as_remote(), + &salt, + version::CURRENT, + ) .unwrap(); let removed = manager.remove_state_machine(sm_1_id); @@ -262,15 +285,36 @@ mod tests { let salt = [49u8; 32]; let sm_1 = manager - .create_session_state_machine(3001, true, local.clone(), peer1.as_remote(), &salt) + .create_session_state_machine( + 3001, + true, + local.clone(), + peer1.as_remote(), + &salt, + version::CURRENT, + ) .unwrap(); let sm_2 = manager - .create_session_state_machine(3002, true, local.clone(), peer2.as_remote(), &salt) + .create_session_state_machine( + 3002, + true, + local.clone(), + peer2.as_remote(), + &salt, + version::CURRENT, + ) .unwrap(); let sm_3 = manager - .create_session_state_machine(3003, true, local.clone(), peer3.as_remote(), &salt) + .create_session_state_machine( + 3003, + true, + local.clone(), + peer3.as_remote(), + &salt, + version::CURRENT, + ) .unwrap(); assert_eq!(manager.session_count(), 3); @@ -298,6 +342,7 @@ mod tests { init, resp.as_remote(), &salt, + version::CURRENT, ); assert!(sm.is_ok()); diff --git a/common/nym-lp/src/state_machine.rs b/common/nym-lp/src/state_machine.rs index 9bf3541c6a..0283f2f244 100644 --- a/common/nym-lp/src/state_machine.rs +++ b/common/nym-lp/src/state_machine.rs @@ -256,16 +256,25 @@ impl LpStateMachine { /// * `local_peer` - This side's LP peer's keys /// * `remote_peer` - The remote's LP peer's keys /// * `salt` - Fresh salt for PSK derivation (must be unique per session) + /// * `protocol_version` - Protocol version to attach in all `LpPacket`s pub fn new( receiver_index: u32, is_initiator: bool, local_peer: LpLocalPeer, remote_peer: LpRemotePeer, salt: &[u8; 32], + protocol_version: u8, ) -> Result { // Create the session with both Ed25519 (for PSQ auth) and derived X25519 keys (for Noise) // receiver_index is client-proposed, passed through directly - let session = LpSession::new(receiver_index, is_initiator, local_peer, remote_peer, salt)?; + let session = LpSession::new( + receiver_index, + is_initiator, + local_peer, + remote_peer, + salt, + protocol_version, + )?; Ok(LpStateMachine { state: LpState::ReadyToHandshake { @@ -1144,6 +1153,7 @@ impl LpStateMachine { #[cfg(test)] mod tests { use super::*; + use crate::packet::version; use crate::peer::mock_peers; #[test] @@ -1155,8 +1165,14 @@ mod tests { let receiver_index: u32 = 77777; - let initiator_sm = - LpStateMachine::new(receiver_index, true, init.clone(), resp.as_remote(), &salt); + let initiator_sm = LpStateMachine::new( + receiver_index, + true, + init.clone(), + resp.as_remote(), + &salt, + version::CURRENT, + ); assert!(initiator_sm.is_ok()); let initiator_sm = initiator_sm.unwrap(); assert!(matches!( @@ -1166,8 +1182,14 @@ mod tests { let init_session = initiator_sm.session().unwrap(); assert!(init_session.is_initiator()); - let responder_sm = - LpStateMachine::new(receiver_index, false, resp, init.as_remote(), &salt); + let responder_sm = LpStateMachine::new( + receiver_index, + false, + resp, + init.as_remote(), + &salt, + version::CURRENT, + ); assert!(responder_sm.is_ok()); let responder_sm = responder_sm.unwrap(); assert!(matches!( @@ -1196,6 +1218,7 @@ mod tests { init.clone(), resp.as_remote(), &salt, + version::CURRENT, ) .unwrap(); @@ -1205,6 +1228,7 @@ mod tests { resp, init.as_remote(), &salt, + version::CURRENT, ) .unwrap(); @@ -1389,8 +1413,15 @@ mod tests { let receiver_index: u32 = 99901; // Create initiator state machine - let mut initiator = - LpStateMachine::new(receiver_index, true, init, resp.as_remote(), &salt).unwrap(); + let mut initiator = LpStateMachine::new( + receiver_index, + true, + init, + resp.as_remote(), + &salt, + version::CURRENT, + ) + .unwrap(); // Verify initial state assert!(matches!(initiator.state, LpState::ReadyToHandshake { .. })); @@ -1409,8 +1440,15 @@ mod tests { let receiver_index: u32 = 99902; // Create responder state machine - let mut responder = - LpStateMachine::new(receiver_index, false, resp, init.as_remote(), &salt).unwrap(); + let mut responder = LpStateMachine::new( + receiver_index, + false, + resp, + init.as_remote(), + &salt, + version::CURRENT, + ) + .unwrap(); // Verify initial state assert!(matches!(responder.state, LpState::ReadyToHandshake { .. })); @@ -1430,12 +1468,25 @@ mod tests { let receiver_index: u32 = 99903; // Create both state machines - let mut initiator = - LpStateMachine::new(receiver_index, true, init.clone(), resp.as_remote(), &salt) - .unwrap(); + let mut initiator = LpStateMachine::new( + receiver_index, + true, + init.clone(), + resp.as_remote(), + &salt, + version::CURRENT, + ) + .unwrap(); - let mut responder = - LpStateMachine::new(receiver_index, false, resp, init.as_remote(), &salt).unwrap(); + let mut responder = LpStateMachine::new( + receiver_index, + false, + resp, + init.as_remote(), + &salt, + version::CURRENT, + ) + .unwrap(); // Step 1: Initiator starts handshake, sends KKT request let init_action = initiator.process_input(LpInput::StartHandshake); @@ -1477,9 +1528,15 @@ mod tests { let receiver_index: u32 = 99904; // Create initiator state machine - let mut initiator = - LpStateMachine::new(receiver_index, true, init.clone(), resp.as_remote(), &salt) - .unwrap(); + let mut initiator = LpStateMachine::new( + receiver_index, + true, + init.clone(), + resp.as_remote(), + &salt, + version::CURRENT, + ) + .unwrap(); // Start handshake to enter KKTExchange state initiator.process_input(LpInput::StartHandshake); @@ -1499,9 +1556,15 @@ mod tests { let receiver_index: u32 = 99905; // Create initiator state machine - let mut initiator = - LpStateMachine::new(receiver_index, true, init.clone(), resp.as_remote(), &salt) - .unwrap(); + let mut initiator = LpStateMachine::new( + receiver_index, + true, + init.clone(), + resp.as_remote(), + &salt, + version::CURRENT, + ) + .unwrap(); // Start handshake to enter KKTExchange state initiator.process_input(LpInput::StartHandshake); @@ -1534,10 +1597,25 @@ mod tests { let receiver_index: u32 = 111111; // Create state machines - Alice is initiator, Bob is responder - let mut alice = - LpStateMachine::new(receiver_index, true, a.clone(), b.as_remote(), &salt).unwrap(); + let mut alice = LpStateMachine::new( + receiver_index, + true, + a.clone(), + b.as_remote(), + &salt, + version::CURRENT, + ) + .unwrap(); - let mut bob = LpStateMachine::new(receiver_index, false, b, a.as_remote(), &salt).unwrap(); + let mut bob = LpStateMachine::new( + receiver_index, + false, + b, + a.as_remote(), + &salt, + version::CURRENT, + ) + .unwrap(); // --- Complete KKT Exchange --- // Alice starts handshake diff --git a/common/registration/src/lib.rs b/common/registration/src/lib.rs index ddc46a593f..777e97fed2 100644 --- a/common/registration/src/lib.rs +++ b/common/registration/src/lib.rs @@ -42,6 +42,10 @@ pub struct NymNodeLPInformation { pub expected_kem_key_hashes: HashMap, pub expected_signing_key_hashes: HashMap, pub x25519: x25519::PublicKey, + + /// Supported protocol version of the remote gateway. + /// Included in case we have to downgrade our version. + pub lp_protocol_version: u8, } #[derive(Clone, Copy, Debug)] diff --git a/gateway/src/node/lp_listener/handler.rs b/gateway/src/node/lp_listener/handler.rs index 9f348bb422..db9e77bdf3 100644 --- a/gateway/src/node/lp_listener/handler.rs +++ b/gateway/src/node/lp_listener/handler.rs @@ -292,6 +292,10 @@ where use nym_lp::state_machine::{LpInput, LpStateMachine}; let remote = self.remote_addr; + // if we managed to parse out the header, i.e. we have code supporting whatever + // protocol version has been sent - use that one instead + let protocol_version = packet.header().protocol_version; + // Extract ClientHello data let hello_data = match packet.message() { LpMessage::ClientHello(hello_data) => hello_data, @@ -325,8 +329,10 @@ where // Send Collision response to tell client to retry with new receiver_index // No outer key - this is before PSK derivation // Note: Do NOT set binding on collision - client may retry with new receiver_index - let collision_packet = - LpPacket::new(LpHeader::new(receiver_index, 0), LpMessage::Collision); + let collision_packet = LpPacket::new( + LpHeader::new(receiver_index, 0, protocol_version), + LpMessage::Collision, + ); self.send_lp_packet(collision_packet, None).await?; return Ok(()); @@ -344,6 +350,7 @@ where self.state.local_lp_peer.clone(), hello_data.to_remote_peer(), &hello_data.salt, + protocol_version, ) .map_err(|e| { inc!("lp_client_hello_failed"); @@ -374,7 +381,10 @@ where // Send Ack to confirm ClientHello received // No outer key - this is before PSK derivation - let ack_packet = LpPacket::new(LpHeader::new(receiver_index, 0), LpMessage::Ack); + let ack_packet = LpPacket::new( + LpHeader::new(receiver_index, 0, protocol_version), + LpMessage::Ack, + ); self.send_lp_packet(ack_packet, None).await?; Ok(()) @@ -431,13 +441,15 @@ where self.remote_addr, receiver_idx ); + let session = state_entry.value().state.session().map_err(|err| { + GatewayError::LpHandshakeError(format!("no session available: {err}")) + })?; + // Get outer key for Ack encryption before releasing borrow - let outer_key = state_entry - .value() - .state - .session() - .ok() - .and_then(|s| s.outer_aead_key()); + let outer_key = session.outer_aead_key(); + + // Get previously negotiated protocol version for header creation + let negotiated_version = session.negotiated_version(); // Move state machine to session_states (already in Transport state) // We keep the state machine (not just session) to enable @@ -461,7 +473,10 @@ where inc!("lp_handshakes_success"); // Send Ack to confirm handshake completion to the client - let ack_packet = LpPacket::new(LpHeader::new(receiver_idx, 0), LpMessage::Ack); + let ack_packet = LpPacket::new( + LpHeader::new(receiver_idx, 0, negotiated_version), + LpMessage::Ack, + ); trace!( "Moved session {} to transport mode, sending Ack", receiver_idx diff --git a/integration-tests/src/lp_registration.rs b/integration-tests/src/lp_registration.rs index 2e171962c4..80afb54004 100644 --- a/integration-tests/src/lp_registration.rs +++ b/integration-tests/src/lp_registration.rs @@ -54,6 +54,7 @@ mod tests { peer: LpLocalPeer, x25519_wg_keys: Arc, socket_addr: SocketAddr, + lp_version: u8, } impl Party { @@ -73,6 +74,7 @@ mod tests { .with_kem_psq_key(lp_x25519_keys), x25519_wg_keys, socket_addr: SocketAddr::from((ip, u16::from_le_bytes(port))), + lp_version: 1, } } } @@ -425,6 +427,7 @@ mod tests { client_data.base.peer.ed25519().clone(), entry.base.peer.as_remote(), entry.base.socket_addr, + entry.base.lp_version, ); // 1. establish mock connection between client and gateway and retrieve gateway's handle @@ -524,6 +527,7 @@ mod tests { client_data.base.peer.ed25519().clone(), entry.base.peer.as_remote(), entry.base.socket_addr, + entry.base.lp_version, ); // 1. establish mock connection between client and gateway and retrieve gateway's handle @@ -588,6 +592,7 @@ mod tests { client_data.base.peer.ed25519().clone(), entry.base.peer.as_remote(), entry.base.socket_addr, + entry.base.lp_version, ); // START: ENTRY SETUP @@ -703,6 +708,7 @@ mod tests { exit.base.socket_addr.to_string(), client_data.base.peer.ed25519().clone(), exit.base.peer.as_remote(), + exit.base.lp_version, ); // 13. Perform handshake and registration with exit gateway (all via entry forwarding) diff --git a/nym-gateway-probe/src/common/nodes.rs b/nym-gateway-probe/src/common/nodes.rs index 6950978b14..3e99c02608 100644 --- a/nym-gateway-probe/src/common/nodes.rs +++ b/nym-gateway-probe/src/common/nodes.rs @@ -14,6 +14,7 @@ use nym_crypto::asymmetric::x25519; use nym_http_api_client::UserAgent; use nym_kkt_ciphersuite::SignatureScheme; use nym_kkt_ciphersuite::{KEM, KEMKeyDigests}; +use nym_lp::packet::version; use nym_network_defaults::DEFAULT_NYM_NODE_HTTP_PORT; use nym_node_requests::api::client::NymNodeApiClientExt; use nym_node_requests::api::v1::node::models::AuxiliaryDetails as NodeAuxiliaryDetails; @@ -28,7 +29,6 @@ use std::time::Duration; use time::OffsetDateTime; use tracing::{debug, info, warn}; use url::Url; - // in the old behaviour we were getting all skimmed nodes to retrieve performance // that was ultimately unused // should we want to use it again, the code is commented out below @@ -135,6 +135,8 @@ impl DirectoryNode { expected_kem_key_hashes: lp_data.kem_keys()?, expected_signing_key_hashes: lp_data.signing_keys()?, x25519: noise_key.x25519_pubkey, + // \/ TODO: proper derivation from build version + lp_version: version::CURRENT, }), _ => None, }; @@ -480,6 +482,7 @@ pub struct TestedNodeLpDetails { pub expected_kem_key_hashes: HashMap, pub expected_signing_key_hashes: HashMap, pub x25519: x25519::PublicKey, + pub lp_version: u8, } impl TestedNodeDetails { diff --git a/nym-gateway-probe/src/common/probe_tests.rs b/nym-gateway-probe/src/common/probe_tests.rs index 018c414a11..b96564c020 100644 --- a/nym-gateway-probe/src/common/probe_tests.rs +++ b/nym-gateway-probe/src/common/probe_tests.rs @@ -163,6 +163,7 @@ pub async fn lp_registration_probe( bandwidth_controller: &dyn BandwidthTicketProvider, ) -> anyhow::Result { let lp_address = gateway_lp_data.address; + let lp_version = gateway_lp_data.lp_version; let peer = helpers::to_lp_remote_peer(gateway_identity, gateway_lp_data); info!("Starting LP registration probe for gateway at {lp_address}",); @@ -180,6 +181,7 @@ pub async fn lp_registration_probe( client_ed25519_keypair, peer, lp_address, + lp_version, ); // Step 1: Perform handshake (connection is implicit in packet-per-connection model) @@ -289,6 +291,9 @@ pub async fn wg_probe_lp( let entry_address = entry_lp_data.address; let exit_address = exit_lp_data.address; + let entry_lp_version = entry_lp_data.lp_version; + let exit_lp_version = exit_lp_data.lp_version; + let entry_ip = entry_address.ip(); let exit_ip = exit_address.ip(); @@ -316,6 +321,7 @@ pub async fn wg_probe_lp( entry_lp_keypair, entry_peer, entry_address, + entry_lp_version, ); // Perform handshake with entry gateway (connection is implicit) @@ -327,8 +333,12 @@ pub async fn wg_probe_lp( // STEP 2: Use nested session to register with exit gateway via forwarding info!("Registering with exit gateway via entry forwarding..."); - let mut nested_session = - NestedLpSession::new(exit_address.to_string(), exit_lp_keypair, exit_peer); + let mut nested_session = NestedLpSession::new( + exit_address.to_string(), + exit_lp_keypair, + exit_peer, + exit_lp_version, + ); // 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/run.rs b/nym-gateway-probe/src/run.rs index 889617406c..b0cc2e9951 100644 --- a/nym-gateway-probe/src/run.rs +++ b/nym-gateway-probe/src/run.rs @@ -317,6 +317,7 @@ pub(crate) async fn run() -> anyhow::Result { expected_kem_key_hashes, expected_signing_key_hashes: todo!(), x25519: x25519_key, + lp_version: todo!(), }; let entry_details = TestedNodeDetails::from_cli(identity, entry_lp_node); @@ -348,8 +349,9 @@ pub(crate) async fn run() -> anyhow::Result { let exit_lp_node = TestedNodeLpDetails { address: exit_lp_addr, expected_kem_key_hashes, - expected_signing_key_hashes: Default::default(), + expected_signing_key_hashes: todo!(), x25519: x25519_key, + lp_version: todo!(), }; Some(TestedNodeDetails::from_cli(identity, exit_lp_node)) diff --git a/nym-registration-client/src/lib.rs b/nym-registration-client/src/lib.rs index e89143fad6..b175e2e94d 100644 --- a/nym-registration-client/src/lib.rs +++ b/nym-registration-client/src/lib.rs @@ -175,6 +175,9 @@ impl RegistrationClient { }, )?; + let entry_lp_protocol = entry_lp_data.lp_protocol_version; + let exit_lp_protocol = exit_lp_data.lp_protocol_version; + let entry_address = entry_lp_data.address; let exit_address = exit_lp_data.address; @@ -197,6 +200,7 @@ impl RegistrationClient { entry_lp_keypair.clone(), entry_peer, entry_address, + entry_lp_protocol, ); // Perform handshake with entry gateway (outer session now established) @@ -213,8 +217,12 @@ impl RegistrationClient { // 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_address.to_string(), exit_lp_keypair, exit_peer); + let mut nested_session = NestedLpSession::new( + exit_address.to_string(), + exit_lp_keypair, + exit_peer, + exit_lp_protocol, + ); // Perform handshake and registration with exit gateway (all via entry forwarding) let exit_gateway_data = nested_session diff --git a/nym-registration-client/src/lp_client/client.rs b/nym-registration-client/src/lp_client/client.rs index 827c98e1b1..c09f2138b9 100644 --- a/nym-registration-client/src/lp_client/client.rs +++ b/nym-registration-client/src/lp_client/client.rs @@ -18,6 +18,7 @@ use nym_crypto::asymmetric::{ed25519, x25519}; use nym_lp::LpPacket; use nym_lp::codec::{OuterAeadKey, parse_lp_packet, serialize_lp_packet}; use nym_lp::message::ForwardPacketData; +use nym_lp::packet::version; use nym_lp::peer::{LpLocalPeer, LpRemotePeer}; use nym_lp::state_machine::{LpAction, LpData, LpInput, LpStateMachine}; use nym_lp_transport::traits::LpTransport; @@ -31,6 +32,7 @@ use std::net::SocketAddr; use std::sync::Arc; use std::time::{Duration, SystemTime, UNIX_EPOCH}; use tokio::net::TcpStream; +use tracing::warn; /// LP (Lewes Protocol) registration client for direct gateway connections. /// @@ -55,6 +57,10 @@ pub struct LpRegistrationClient { /// Gateway LP listener address (host:port, e.g., "1.1.1.1:41264"). gateway_lp_address: SocketAddr, + /// Supported protocol version of the remote gateway. + /// Included in case we have to downgrade our version. + gateway_supported_lp_protocol_version: u8, + /// LP state machine for managing connection lifecycle. /// Created during handshake initiation. Persists across packet-per-connection calls. state_machine: Option, @@ -77,6 +83,7 @@ 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 + /// * `gateway_supported_lp_protocol_version` - Gateway's LP protocol version /// * `config` - Configuration for timeouts and TCP parameters (use `LpConfig::default()`) /// /// # Note @@ -85,20 +92,58 @@ where local_ed25519_keypair: Arc, gateway_lp_peer: LpRemotePeer, gateway_lp_address: SocketAddr, + gateway_supported_lp_protocol_version: u8, config: LpConfig, ) -> Self { + let lp_protocol = if gateway_supported_lp_protocol_version > version::CURRENT { + warn!( + "suggested LP protocol ({gateway_supported_lp_protocol_version}) is higher than the current known version. attempting to downgrade it to {}", + version::CURRENT + ); + version::CURRENT + } else { + gateway_supported_lp_protocol_version + }; + let local_x25519_keypair = local_ed25519_keypair.to_x25519(); let lp_local_peer = LpLocalPeer::new(local_ed25519_keypair, Arc::new(local_x25519_keypair)); Self { lp_local_peer, gateway_lp_peer, gateway_lp_address, + gateway_supported_lp_protocol_version: lp_protocol, state_machine: None, config, stream: None, } } + /// Creates a new LP registration client with default configuration. + /// + /// # Arguments + /// * `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 + /// * `gateway_supported_lp_protocol_version` - Gateway's LP protocol version + /// + /// Uses default config (LpConfig::default()) with sane timeout and TCP parameters. + /// PSK is derived automatically during handshake inside the state machine. + /// For custom config, use `new()` directly. + pub fn new_with_default_config( + local_ed25519_keypair: Arc, + gateway_lp_peer: LpRemotePeer, + gateway_lp_address: SocketAddr, + gateway_supported_lp_protocol_version: u8, + ) -> Self { + Self::new( + local_ed25519_keypair, + gateway_lp_peer, + gateway_lp_address, + gateway_supported_lp_protocol_version, + LpConfig::default(), + ) + } + fn state_machine(&self) -> Result<&LpStateMachine> { self.state_machine.as_ref().ok_or_else(|| { LpClientError::transport( @@ -121,30 +166,6 @@ where .ok_or_else(|| LpClientError::transport("Cannot send: not connected")) } - /// Creates a new LP registration client with default configuration. - /// - /// # Arguments - /// * `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 - /// - /// Uses default config (LpConfig::default()) with sane timeout and TCP parameters. - /// PSK is derived automatically during handshake inside the state machine. - /// For custom config, use `new()` directly. - pub fn new_with_default_config( - local_ed25519_keypair: Arc, - gateway_lp_peer: LpRemotePeer, - gateway_lp_address: SocketAddr, - ) -> Self { - Self::new( - local_ed25519_keypair, - gateway_lp_peer, - gateway_lp_address, - LpConfig::default(), - ) - } - /// Returns whether the client has completed the handshake and is ready for registration. pub fn is_handshake_complete(&self) -> bool { self.state_machine @@ -418,6 +439,7 @@ where let client_hello_header = nym_lp::packet::LpHeader::new( nym_lp::BOOTSTRAP_RECEIVER_IDX, // session_id not yet established 0, // counter starts at 0 + self.gateway_supported_lp_protocol_version, ); let client_hello_packet = nym_lp::LpPacket::new( client_hello_header, @@ -431,6 +453,8 @@ where let ack_response = self.try_receive_packet_with_key(None).await?; // Verify we received Ack + // this confirms that gateway is fine with our suggested protocol version + // in the future we probably have some fancier negotiation match ack_response.message() { nym_lp::LpMessage::Ack => { tracing::debug!("Received Ack for ClientHello"); @@ -451,6 +475,7 @@ where self.lp_local_peer.clone(), self.gateway_lp_peer.clone(), &salt, + self.gateway_supported_lp_protocol_version, )?; // Step 4: Start handshake - get first packet to send (KKT request) @@ -1201,6 +1226,7 @@ where #[cfg(test)] mod tests { use super::*; + use nym_lp::packet::version; #[test] fn test_client_creation() { @@ -1216,6 +1242,7 @@ mod tests { keypair, gateway_peer, address, + version::CURRENT, ); assert!(!client.is_handshake_complete()); diff --git a/nym-registration-client/src/lp_client/nested_session.rs b/nym-registration-client/src/lp_client/nested_session.rs index ed10f09880..a8e7a1be9b 100644 --- a/nym-registration-client/src/lp_client/nested_session.rs +++ b/nym-registration-client/src/lp_client/nested_session.rs @@ -30,6 +30,7 @@ use nym_credentials_interface::TicketType; use nym_crypto::asymmetric::{ed25519, x25519}; use nym_lp::codec::{OuterAeadKey, parse_lp_packet}; use nym_lp::message::ForwardPacketData; +use nym_lp::packet::version; use nym_lp::peer::{LpLocalPeer, LpRemotePeer}; use nym_lp::state_machine::{LpAction, LpData, LpInput, LpStateMachine}; use nym_lp::{LpMessage, LpPacket}; @@ -42,6 +43,7 @@ use nym_wireguard_types::PeerPublicKey; use rand::{CryptoRng, RngCore}; use std::sync::Arc; use std::time::{SystemTime, UNIX_EPOCH}; +use tracing::warn; /// Manages a nested LP session where the client establishes a handshake with /// an exit gateway by forwarding packets through an entry gateway. @@ -73,6 +75,10 @@ pub struct NestedLpSession { /// Encapsulates all the exit gateway keys needed for the Lewes Protocol. gateway_lp_peer: LpRemotePeer, + /// Supported protocol version of the remote gateway. + /// Included in case we have to downgrade our version. + gateway_supported_lp_protocol_version: u8, + /// LP state machine for exit gateway session (populated after handshake) state_machine: Option, } @@ -84,18 +90,31 @@ impl NestedLpSession { /// * `exit_address` - Exit gateway's LP address (e.g., "2.2.2.2:41264") /// * `client_keypair` - Client's Ed25519 keypair /// * `gateway_lp_peer` - Encapsulates all the gateway keys needed for the Lewes Protocol + /// * `gateway_supported_lp_protocol_version` - Gateway's LP protocol version pub fn new( exit_address: String, client_keypair: Arc, gateway_lp_peer: LpRemotePeer, + gateway_supported_lp_protocol_version: u8, ) -> Self { let local_x25519_keypair = client_keypair.to_x25519(); let lp_local_peer = LpLocalPeer::new(client_keypair, Arc::new(local_x25519_keypair)); + let lp_protocol = if gateway_supported_lp_protocol_version > version::CURRENT { + warn!( + "suggested LP protocol ({gateway_supported_lp_protocol_version}) is higher than the current known version. attempting to downgrade it to {}", + version::CURRENT + ); + version::CURRENT + } else { + gateway_supported_lp_protocol_version + }; + Self { exit_address, lp_local_peer, gateway_lp_peer, + gateway_supported_lp_protocol_version: lp_protocol, state_machine: None, } } @@ -194,6 +213,7 @@ impl NestedLpSession { let client_hello_header = nym_lp::packet::LpHeader::new( nym_lp::BOOTSTRAP_RECEIVER_IDX, // Use constant for bootstrap session 0, // counter starts at 0 + self.gateway_supported_lp_protocol_version, ); let client_hello_packet = nym_lp::LpPacket::new( client_hello_header, @@ -213,6 +233,8 @@ impl NestedLpSession { .await?; // Parse and validate Ack response (cleartext, no outer key before PSK derivation) + // this confirms that gateway is fine with our suggested protocol version + // in the future we probably have some fancier negotiation let ack_response = Self::parse_packet(&response_bytes, None)?; match ack_response.message() { LpMessage::Ack => { @@ -238,6 +260,7 @@ impl NestedLpSession { self.lp_local_peer.clone(), self.gateway_lp_peer.clone(), &salt, + self.gateway_supported_lp_protocol_version, )?; // Step 4: Get initial packet from StartHandshake diff --git a/tools/nym-lp-client/src/client.rs b/tools/nym-lp-client/src/client.rs index cc1e94768b..3be0bc18b2 100644 --- a/tools/nym-lp-client/src/client.rs +++ b/tools/nym-lp-client/src/client.rs @@ -32,6 +32,7 @@ use tracing::{debug, info, trace}; use crate::topology::{GatewayInfo, SpeedtestTopology}; use nym_ip_packet_requests::v8::request::IpPacketRequest; +use nym_lp::packet::version; use nym_lp::peer::LpRemotePeer; use nym_sphinx::forwarding::packet::MixPacket; @@ -127,6 +128,7 @@ impl SpeedtestClient { self.identity_keypair.clone(), gw_peer, self.gateway.lp_address, + self.gateway.lp_version, ); let start = Instant::now(); @@ -173,6 +175,7 @@ impl SpeedtestClient { self.identity_keypair.clone(), gw_peer, self.gateway.lp_address, + self.gateway.lp_version, ); let start = Instant::now(); diff --git a/tools/nym-lp-client/src/topology.rs b/tools/nym-lp-client/src/topology.rs index 5735134317..3e0f76b6ff 100644 --- a/tools/nym-lp-client/src/topology.rs +++ b/tools/nym-lp-client/src/topology.rs @@ -40,6 +40,7 @@ pub struct GatewayInfo { pub lp_address: SocketAddr, /// LP data address (IP:51264) for Sphinx packets wrapped in LP pub lp_data_address: SocketAddr, + pub lp_version: u8, } /// Topology for routing Sphinx packets From cd0881462bc8532e9a55e36ff84768b18df23ad3 Mon Sep 17 00:00:00 2001 From: Simon Wicky Date: Fri, 30 Jan 2026 16:55:29 +0100 Subject: [PATCH 40/51] [LP Gateway Probe] CLI and behavior improvements (#6400) * attempt to de-spaghettificationize the gateway probe * applying suggestions --- .github/workflows/ci-build.yml | 1 + Cargo.lock | 7 +- nym-gateway-probe/Cargo.toml | 37 +- .../src/common/bandwidth_helpers.rs | 25 +- nym-gateway-probe/src/common/helpers.rs | 49 +- nym-gateway-probe/src/common/netstack.rs | 3 +- nym-gateway-probe/src/common/nodes.rs | 118 +- nym-gateway-probe/src/common/probe_tests.rs | 166 +-- nym-gateway-probe/src/common/wireguard.rs | 2 +- nym-gateway-probe/src/config/credentials.rs | 93 +- nym-gateway-probe/src/config/mod.rs | 39 +- nym-gateway-probe/src/config/netstack.rs | 24 +- nym-gateway-probe/src/config/socks5.rs | 12 +- nym-gateway-probe/src/config/test_mode.rs | 273 ++-- nym-gateway-probe/src/lib.rs | 1140 +++++------------ nym-gateway-probe/src/run.rs | 656 +++------- .../nym-node-status-agent/Cargo.toml | 2 +- .../src/cli/run_probe.rs | 2 +- .../nym-node-status-agent/src/probe.rs | 7 +- 19 files changed, 900 insertions(+), 1756 deletions(-) diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index 5ba0612c73..063c6e242c 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -10,6 +10,7 @@ on: - 'nym-api/**' - 'nym-authenticator-client/**' - 'nym-credential-proxy/**' + - 'nym-gateway-probe/**' - 'nym-ip-packet-client/**' - 'nym-network-monitor/**' - 'nym-node/**' diff --git a/Cargo.lock b/Cargo.lock index 2e041700b7..d11569e9d7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6454,8 +6454,6 @@ version = "1.18.0" dependencies = [ "anyhow", "base64 0.22.1", - "bincode", - "bs58", "bytes", "clap", "futures", @@ -6478,7 +6476,6 @@ dependencies = [ "nym-ip-packet-requests", "nym-kkt-ciphersuite", "nym-lp", - "nym-mixnet-contract-common", "nym-network-defaults", "nym-node-requests", "nym-node-status-client", @@ -6492,7 +6489,6 @@ dependencies = [ "reqwest 0.12.28", "serde", "serde_json", - "thiserror 2.0.17", "time", "tokio", "tokio-util", @@ -6501,7 +6497,6 @@ dependencies = [ "url", "utoipa", "vergen-gitcl", - "x25519-dalek", ] [[package]] @@ -7237,7 +7232,7 @@ dependencies = [ [[package]] name = "nym-node-status-agent" -version = "1.1.1" +version = "1.1.2" dependencies = [ "anyhow", "clap", diff --git a/nym-gateway-probe/Cargo.toml b/nym-gateway-probe/Cargo.toml index 72336bb532..bf8c621e59 100644 --- a/nym-gateway-probe/Cargo.toml +++ b/nym-gateway-probe/Cargo.toml @@ -15,8 +15,6 @@ workspace = true [dependencies] anyhow.workspace = true base64.workspace = true -bs58.workspace = true -bincode.workspace = true bytes.workspace = true clap = { workspace = true, features = ["cargo", "derive"] } futures.workspace = true @@ -27,7 +25,7 @@ rand.workspace = true reqwest = { workspace = true, features = ["socks"] } serde.workspace = true serde_json.workspace = true -thiserror.workspace = true +time = { workspace = true } tokio = { workspace = true, features = [ "process", "rt-multi-thread", @@ -39,43 +37,34 @@ tokio-util.workspace = true tracing-subscriber.workspace = true url = { workspace = true } utoipa = { workspace = true, optional = true } -x25519-dalek = { workspace = true, features = [ - "reusable_secrets", - "static_secrets", -] } nym-api-requests = { path = "../nym-api/nym-api-requests" } +nym-authenticator-client = { workspace = true } nym-authenticator-requests = { workspace = true } nym-bandwidth-controller = { workspace = true } nym-bin-common = { workspace = true } nym-client-core = { workspace = true } -nym-crypto = { workspace = true } nym-config = { workspace = true } nym-connection-monitor = { path = "../common/nym-connection-monitor" } +nym-credentials = { workspace = true } nym-credentials-interface = { workspace = true } nym-credential-utils = { workspace = true } -nym-ip-packet-client = { workspace = true } -nym-authenticator-client = { workspace = true } -nym-ip-packet-requests = { workspace = true } -nym-sdk = { workspace = true } -nym-validator-client = { workspace = true } -nym-credentials = { workspace = true } -nym-http-api-client-macro = { path = "../common/http-api-client-macro" } +nym-crypto = { workspace = true } nym-http-api-client = { path = "../common/http-api-client" } -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-http-api-client-macro = { path = "../common/http-api-client-macro" } +nym-ip-packet-client = { workspace = true } +nym-ip-packet-requests = { workspace = true } nym-kkt-ciphersuite = { workspace = true } - -nym-mixnet-contract-common = { path = "../common/cosmwasm-smart-contracts/mixnet-contract" } +nym-lp = { path = "../common/nym-lp" } nym-network-defaults = { path = "../common/network-defaults" } +nym-node-requests = { path = "../nym-node/nym-node-requests" } +nym-node-status-client = { path = "../nym-node-status-api/nym-node-status-client" } +nym-registration-client = { path = "../nym-registration-client" } nym-registration-common = { path = "../common/registration" } -time = { workspace = true } - -# TEMP: REMOVE BEFORE PR +nym-sdk = { workspace = true } nym-topology = { workspace = true } +nym-validator-client = { workspace = true } [features] utoipa = ["dep:utoipa"] diff --git a/nym-gateway-probe/src/common/bandwidth_helpers.rs b/nym-gateway-probe/src/common/bandwidth_helpers.rs index dea3d6bfd5..ee4bb8583e 100644 --- a/nym-gateway-probe/src/common/bandwidth_helpers.rs +++ b/nym-gateway-probe/src/common/bandwidth_helpers.rs @@ -8,29 +8,38 @@ use nym_bandwidth_controller::mock::MockBandwidthController; use nym_client_core::client::base_client::storage::OnDiskPersistent; use nym_credentials_interface::TicketType; use nym_node_status_client::models::AttachedTicketMaterials; +use nym_sdk::NymNetworkDetails; use nym_sdk::bandwidth::BandwidthImporter; use nym_sdk::mixnet::{CredentialStorage, DisconnectedMixnetClient, EphemeralCredentialStorage}; -use nym_validator_client::QueryHttpRpcNyxdClient; use nym_validator_client::nyxd::error::NyxdError; use std::time::Duration; use tracing::{error, info}; pub(crate) fn build_bandwidth_controller( - rpc_client: QueryHttpRpcNyxdClient, - on_disk_storage: S, + network: &NymNetworkDetails, + storage: S, use_mock_ecash: bool, -) -> Box +) -> anyhow::Result> where S: CredentialStorage + 'static, S::StorageError: Send + Sync + 'static, { if !use_mock_ecash { - Box::new(nym_bandwidth_controller::BandwidthController::new( - on_disk_storage, - rpc_client, + let config = nym_validator_client::nyxd::Config::try_from_nym_network_details(network)?; + + let nyxd_url = network + .endpoints + .first() + .map(|ep| ep.nyxd_url()) + .ok_or(anyhow::anyhow!("missing nyxd url"))?; + let rpc_client = + nym_validator_client::nyxd::NyxdClient::connect(config, nyxd_url.as_str())?; + + Ok(Box::new( + nym_bandwidth_controller::BandwidthController::new(storage, rpc_client), )) } else { - Box::new(MockBandwidthController::default()) + Ok(Box::new(MockBandwidthController::default())) } } diff --git a/nym-gateway-probe/src/common/helpers.rs b/nym-gateway-probe/src/common/helpers.rs index e80912abc5..5693b8a7a9 100644 --- a/nym-gateway-probe/src/common/helpers.rs +++ b/nym-gateway-probe/src/common/helpers.rs @@ -7,8 +7,13 @@ use nym_ip_packet_requests::v8::response::{ ControlResponse, DataResponse, InfoLevel, IpPacketResponse, IpPacketResponseData, }; use nym_lp::peer::LpRemotePeer; -use nym_sdk::mixnet::ReconstructedMessage; +use nym_sdk::{ + DebugConfig, NymApiTopologyProvider, NymApiTopologyProviderConfig, NymNetworkDetails, + TopologyProvider, mixnet::ReconstructedMessage, +}; +use nym_topology::NymTopology; use tracing::*; +use url::Url; pub fn to_lp_remote_peer(identity: ed25519::PublicKey, data: TestedNodeLpDetails) -> LpRemotePeer { LpRemotePeer::new(identity, data.x25519).with_key_digests( @@ -62,3 +67,45 @@ pub fn unpack_data_response(reconstructed_message: &ReconstructedMessage) -> Opt } } } + +pub async fn fetch_topology( + network_details: &NymNetworkDetails, + debug_config: &DebugConfig, +) -> Result { + // get Nym API URLs from network_details + let nym_api_urls: Vec = network_details + .nym_api_urls + .as_ref() + .map(|urls| urls.iter().filter_map(|u| u.url.parse().ok()).collect()) + .or_else(|| { + network_details + .endpoints + .first() + .and_then(|e| e.api_url()) + .map(|url| vec![url]) + }) + .unwrap_or_default(); + + if nym_api_urls.is_empty() { + return Err(String::from("No nym-api URLs available to fetch topology")); + } + + let topology_config = NymApiTopologyProviderConfig { + min_mixnode_performance: debug_config.topology.minimum_mixnode_performance, + min_gateway_performance: debug_config.topology.minimum_gateway_performance, + use_extended_topology: debug_config.topology.use_extended_topology, + ignore_egress_epoch_role: debug_config.topology.ignore_egress_epoch_role, + }; + + let api_client = nym_http_api_client::Client::new_url(nym_api_urls[0].clone(), None) + .map_err(|e| e.to_string())?; + let mut provider = NymApiTopologyProvider::new(topology_config, nym_api_urls, api_client); + + match provider.get_new_topology().await { + Some(topology) => { + info!("Fetched network topology"); + Ok(topology) + } + None => Err(String::from("Failed to fetch network topology")), + } +} diff --git a/nym-gateway-probe/src/common/netstack.rs b/nym-gateway-probe/src/common/netstack.rs index 9b033f5898..c7dff8a5cb 100644 --- a/nym-gateway-probe/src/common/netstack.rs +++ b/nym-gateway-probe/src/common/netstack.rs @@ -1,6 +1,7 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only +use crate::config::NetstackArgs; use anyhow::Context; use serde::Deserialize; use std::ffi::{CStr, CString}; @@ -15,8 +16,6 @@ mod sys { } } -use crate::config::NetstackArgs; - #[derive(serde::Serialize)] pub struct NetstackRequest { private_key: String, diff --git a/nym-gateway-probe/src/common/nodes.rs b/nym-gateway-probe/src/common/nodes.rs index 3e99c02608..bb88848e1a 100644 --- a/nym-gateway-probe/src/common/nodes.rs +++ b/nym-gateway-probe/src/common/nodes.rs @@ -4,9 +4,8 @@ use anyhow::{Context, anyhow, bail}; use nym_api_requests::models::{ AuthenticatorDetailsV2, DeclaredRolesV2, DescribedNodeTypeV2, HostInformationV2, - IpPacketRouterDetailsV2, LewesProtocolDetailsV1, NetworkRequesterDetailsV1, - NetworkRequesterDetailsV2, NymNodeDataV2, OffsetDateTimeJsonSchemaWrapper, WebSocketsV2, - WireguardDetailsV2, + IpPacketRouterDetailsV2, NetworkRequesterDetailsV2, NymNodeDataV2, + OffsetDateTimeJsonSchemaWrapper, WebSocketsV2, WireguardDetailsV2, }; use nym_authenticator_requests::AuthenticatorVersion; use nym_bin_common::build_information::BinaryBuildInformationOwned; @@ -27,7 +26,7 @@ use std::collections::HashMap; use std::net::{IpAddr, SocketAddr}; use std::time::Duration; use time::OffsetDateTime; -use tracing::{debug, info, warn}; +use tracing::{debug, error, info, warn}; use url::Url; // in the old behaviour we were getting all skimmed nodes to retrieve performance // that was ultimately unused @@ -108,6 +107,11 @@ impl DirectoryNode { .as_ref() .map(|ipr| ipr.address.parse().context("malformed ipr address")) .transpose()?; + let network_requester_address = description + .network_requester + .as_ref() + .map(|nr| nr.address.parse().context("malformed nr address")) + .transpose()?; let authenticator_address = description .authenticator .as_ref() @@ -140,12 +144,11 @@ impl DirectoryNode { }), _ => None, }; - let network_requester_details = self.described.description.network_requester.clone(); Ok(TestedNodeDetails { identity: self.identity(), exit_router_address, - network_requester_details, + network_requester_address, authenticator_address, authenticator_version, ip_address: Some(ip_address), @@ -217,12 +220,33 @@ pub async fn query_gateway_by_ip(address: String) -> anyhow::Result anyhow::Result = None; // Not needed for LP testing + let network_requester: Option = + nr_result.map(|nr| NetworkRequesterDetailsV2 { + address: nr.address, + uses_exit_policy: false, // Field not availabe, to change if it becomes useful here + }); let ip_packet_router: Option = ipr_result.map(|ipr| IpPacketRouterDetailsV2 { address: ipr.address, @@ -260,8 +288,6 @@ pub async fn query_gateway_by_ip(address: String) -> anyhow::Result = lp_result.ok().map(Into::into); - // Construct NymNodeData let node_data = NymNodeDataV2 { last_polled: OffsetDateTimeJsonSchemaWrapper(OffsetDateTime::now_utc()), @@ -293,7 +319,7 @@ pub async fn query_gateway_by_ip(address: String) -> anyhow::Result anyhow::Result { + pub fn exit_gateway(&self, identity: &NodeIdentity) -> anyhow::Result { let Some(maybe_entry) = self.nodes.get(identity).cloned() else { - bail!("{identity} not found in directory") + bail!("{identity} does not exist") }; - if !maybe_entry.described.description.declared_role.exit_nr { - bail!("{identity} doesn't support exit NR mode") + if !maybe_entry + .described + .description + .declared_role + .can_operate_exit_gateway() + { + bail!("{identity} is not an exit node") }; Ok(maybe_entry) } } -#[derive(Default, Debug)] -pub enum TestedNode { - #[default] - SameAsEntry, - Custom { - identity: NodeIdentity, - shares_entry: bool, - }, -} - -impl TestedNode { - pub fn is_same_as_entry(&self) -> bool { - matches!( - self, - TestedNode::SameAsEntry - | TestedNode::Custom { - shares_entry: true, - .. - } - ) - } -} - #[derive(Debug, Clone)] pub struct TestedNodeDetails { pub identity: NodeIdentity, pub exit_router_address: Option, - pub network_requester_details: Option, + pub network_requester_address: Option, pub authenticator_address: Option, pub authenticator_version: AuthenticatorVersion, pub ip_address: Option, @@ -484,29 +492,3 @@ pub struct TestedNodeLpDetails { pub x25519: x25519::PublicKey, pub lp_version: u8, } - -impl TestedNodeDetails { - /// Create from CLI args (localnet mode - no HTTP query needed) - pub fn from_cli(identity: NodeIdentity, lp_data: TestedNodeLpDetails) -> Self { - Self { - identity, - ip_address: Some(lp_data.address.ip()), - lp_data: Some(lp_data), - network_requester_details: None, - // These are None in localnet mode - only needed for mixnet/authenticator - exit_router_address: None, - authenticator_address: None, - authenticator_version: AuthenticatorVersion::UNKNOWN, - } - } - - /// Check if this node has sufficient info for LP testing - pub fn can_test_lp(&self) -> bool { - self.lp_data.is_some() - } - - /// Check if this node has sufficient info for mixnet testing - pub fn can_test_mixnet(&self) -> bool { - self.exit_router_address.is_some() || self.authenticator_address.is_some() - } -} diff --git a/nym-gateway-probe/src/common/probe_tests.rs b/nym-gateway-probe/src/common/probe_tests.rs index b96564c020..ddeab981fd 100644 --- a/nym-gateway-probe/src/common/probe_tests.rs +++ b/nym-gateway-probe/src/common/probe_tests.rs @@ -1,7 +1,6 @@ // Copyright 2026 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::NymApiDirectory; use crate::common::helpers::mixnet_debug_config; use crate::common::nodes::{TestedNodeDetails, TestedNodeLpDetails}; use crate::common::socks5_test::HttpsConnectivityTest; @@ -12,7 +11,7 @@ use crate::common::wireguard::{ TwoHopWgTunnelConfig, WgTunnelConfig, run_tunnel_tests, run_two_hop_tunnel_tests, }; use crate::common::{helpers, icmp}; -use crate::config::NetstackArgs; +use crate::config::{NetstackArgs, Socks5Args}; use anyhow::bail; use base64::{Engine, engine::general_purpose}; use bytes::BytesMut; @@ -30,11 +29,8 @@ use nym_crypto::asymmetric::{ed25519, x25519}; use nym_ip_packet_client::IprClientConnect; use nym_ip_packet_requests::{IpPair, codec::MultiIpPacketCodec}; use nym_registration_client::{LpRegistrationClient, NestedLpSession}; +use nym_sdk::NymNetworkDetails; use nym_sdk::mixnet::{MixnetClient, MixnetClientBuilder, NodeIdentity, Recipient, Socks5}; -use nym_sdk::{ - DebugConfig, NymApiTopologyProvider, NymApiTopologyProviderConfig, NymNetworkDetails, - TopologyProvider, -}; use nym_topology::{HardcodedTopologyProvider, NymTopology}; use std::{ net::{IpAddr, Ipv4Addr, Ipv6Addr}, @@ -44,13 +40,12 @@ use std::{ use tokio::net::TcpStream; use tokio_util::{codec::Decoder, sync::CancellationToken}; use tracing::*; -use url::Url; pub async fn wg_probe( mut auth_client: AuthenticatorClient, gateway_ip: IpAddr, auth_version: AuthenticatorVersion, - awg_args: String, + awg_args: Option, netstack_args: NetstackArgs, // TODO: update type credential: CredentialSpendingData, @@ -123,9 +118,8 @@ pub async fn wg_probe( }; let peer_public = registered_data.pub_key().inner(); - let static_private = x25519_dalek::StaticSecret::from(private_key.to_bytes()); let public_key_bs64 = general_purpose::STANDARD.encode(peer_public.as_bytes()); - let private_key_hex = hex::encode(static_private.to_bytes()); + let private_key_hex = hex::encode(private_key.to_bytes()); let public_key_hex = hex::encode(peer_public.as_bytes()); info!("WG connection details"); @@ -152,7 +146,12 @@ pub async fn wg_probe( wg_endpoint, ); - run_tunnel_tests(&tunnel_config, &netstack_args, &awg_args, &mut wg_outcome); + run_tunnel_tests( + &tunnel_config, + &netstack_args, + &awg_args.unwrap_or_default(), + &mut wg_outcome, + ); Ok(wg_outcome) } @@ -166,7 +165,7 @@ pub async fn lp_registration_probe( let lp_version = gateway_lp_data.lp_version; let peer = helpers::to_lp_remote_peer(gateway_identity, gateway_lp_data); - info!("Starting LP registration probe for gateway at {lp_address}",); + info!("Starting LP registration probe for gateway at {lp_address}"); let mut lp_outcome = LpProbeResults::default(); @@ -274,7 +273,7 @@ pub async fn wg_probe_lp( entry_gateway: &TestedNodeDetails, exit_gateway: &TestedNodeDetails, bandwidth_controller: &dyn BandwidthTicketProvider, - awg_args: String, + awg_args: Option, netstack_args: NetstackArgs, ) -> anyhow::Result { // Validate that both gateways have required information @@ -340,9 +339,7 @@ pub async fn wg_probe_lp( exit_lp_version, ); - // Convert exit gateway identity to ed25519 public key for registration - let exit_gateway_pubkey = ed25519::PublicKey::from_bytes(&exit_gateway.identity.to_bytes()) - .map_err(|e| anyhow::anyhow!("Invalid exit gateway identity: {}", e))?; + let exit_gateway_pubkey = exit_gateway.identity; // Perform handshake and registration with exit gateway via forwarding let exit_gateway_data = match nested_session @@ -424,12 +421,12 @@ pub async fn wg_probe_lp( entry_private_key_hex, entry_public_key_hex, entry_wg_endpoint, - awg_args.clone(), // Entry AWG args + awg_args.clone().unwrap_or_default(), // Entry AWG args exit_gateway_data.private_ipv4.to_string(), exit_private_key_hex, exit_public_key_hex, exit_wg_endpoint, - awg_args, // Exit AWG args + awg_args.unwrap_or_default(), // Exit AWG args ); // Run two-hop tunnel connectivity tests @@ -632,39 +629,23 @@ pub async fn listen_for_icmp_ping_replies( /// Creates a SOCKS5 proxy connection through the mixnet to the exit GW /// and performs necessary tests. -#[allow(clippy::too_many_arguments)] #[instrument(level = "info", name = "socks5_test", skip_all)] pub(crate) async fn do_socks5_connectivity_test( - network_requester_address: &str, + nr_recipient: &Recipient, + entry_gateway_id: NodeIdentity, network_details: NymNetworkDetails, - directory: &NymApiDirectory, - json_rpc_endpoints: Vec, - mixnet_client_timeout: u64, - test_run_count: u64, - failure_count_cutoff: usize, - topology: Option, + min_gw_performance: Option, + socks5_args: Socks5Args, + maybe_topology: Option, ) -> anyhow::Result { info!( "Starting SOCKS5 test through Network Requester: {}", - network_requester_address + nr_recipient ); - if json_rpc_endpoints.is_empty() { + if socks5_args.socks5_json_rpc_url_list.is_empty() { bail!("You need to define JSON RPC URLs in order to test SOCKS5") } - // parse the network requester address - let nr_recipient = match network_requester_address.parse::() { - Ok(addr) => addr, - Err(e) => { - error!("Invalid Network Requester address: {}", e); - - return Ok(Socks5ProbeResults::error_before_connecting(format!( - "Invalid NR address: {}", - e - ))); - } - }; - info!( "Network Requester gateway: {}", nr_recipient.gateway().to_base58_string() @@ -675,53 +656,30 @@ pub(crate) async fn do_socks5_connectivity_test( ); // create ephemeral SOCKS5 client - let socks5_config = Socks5::new(network_requester_address.to_string()); - - // since we define both entry & exit gateways to be the same tested GW, - // this shouldn't negatively affect mixnet layers but it will force route - // construction in case GW would get filtered out of topology - let min_gw_performance = Some(0); + let socks5_config = Socks5::new(nr_recipient.to_string()); // debug config similar to main probe let debug_config = mixnet_debug_config(min_gw_performance, true); - // Verify the NR gateway exists in the directory with exit_nr role - let nr_gateway_id = nr_recipient.gateway(); - if let Err(e) = directory.exit_gateway_nr(&nr_gateway_id) { - return Ok(Socks5ProbeResults::error_before_connecting(e.to_string())); - } else { - info!("✔️ Network Requester gateway found in directory with exit_nr role"); - } - - // use intended exit as entry as well - let entry_gateway = nr_gateway_id; - - // use existing topology if available, otherwise fetch it - let topology_provider: Box = match topology { - Some(t) => { - info!("✔️ Reusing topology from main mixnet client"); - Box::new(HardcodedTopologyProvider::new(t)) - } - None => { - info!("Fetching topology for SOCKS5 client..."); - match hardcoded_topology(&network_details, &debug_config).await { - Ok(provider) => provider, - Err(e) => return Ok(Socks5ProbeResults::error_before_connecting(e)), - } - } - }; - - let socks5_client_builder = MixnetClientBuilder::new_ephemeral() + let mut socks5_client_builder = MixnetClientBuilder::new_ephemeral() // Specify entry gateway explicitly - .request_gateway(entry_gateway.to_base58_string()) + .request_gateway(entry_gateway_id.to_base58_string()) .socks5_config(socks5_config) .network_details(network_details) - .debug_config(debug_config) - .custom_topology_provider(topology_provider) - .build()?; + .debug_config(debug_config); + + if let Some(topology) = maybe_topology { + socks5_client_builder = socks5_client_builder + .custom_topology_provider(Box::new(HardcodedTopologyProvider::new(topology))); + } + + let disconnected_socks5_client = socks5_client_builder.build()?; // connect to mixnet via SOCKS5 - let socks5_client = match socks5_client_builder.connect_to_mixnet_via_socks5().await { + let socks5_client = match disconnected_socks5_client + .connect_to_mixnet_via_socks5() + .await + { Ok(client) => { info!("🌐 Successfully connected to mixnet via SOCKS5 proxy"); info!( @@ -740,10 +698,10 @@ pub(crate) async fn do_socks5_connectivity_test( }; let test = match HttpsConnectivityTest::new( - test_run_count, - mixnet_client_timeout, - failure_count_cutoff, - json_rpc_endpoints, + socks5_args.test_count, + socks5_args.mixnet_client_timeout_sec, + socks5_args.failure_count_cutoff, + socks5_args.socks5_json_rpc_url_list, socks5_client.socks5_url(), ) { Ok(test) => test, @@ -762,45 +720,3 @@ pub(crate) async fn do_socks5_connectivity_test( Ok(Socks5ProbeResults::with_http_result(result)) } - -async fn hardcoded_topology( - network_details: &NymNetworkDetails, - debug_config: &DebugConfig, -) -> Result, String> { - // get Nym API URLs from network_details - let nym_api_urls: Vec = network_details - .nym_api_urls - .as_ref() - .map(|urls| urls.iter().filter_map(|u| u.url.parse().ok()).collect()) - .or_else(|| { - network_details - .endpoints - .first() - .and_then(|e| e.api_url()) - .map(|url| vec![url]) - }) - .unwrap_or_default(); - - if nym_api_urls.is_empty() { - return Err(String::from("No nym-api URLs available to fetch topology")); - } - - let topology_config = NymApiTopologyProviderConfig { - min_mixnode_performance: debug_config.topology.minimum_mixnode_performance, - min_gateway_performance: debug_config.topology.minimum_gateway_performance, - use_extended_topology: debug_config.topology.use_extended_topology, - ignore_egress_epoch_role: debug_config.topology.ignore_egress_epoch_role, - }; - - let api_client = nym_http_api_client::Client::new_url(nym_api_urls[0].clone(), None) - .map_err(|e| e.to_string())?; - let mut provider = NymApiTopologyProvider::new(topology_config, nym_api_urls, api_client); - - match provider.get_new_topology().await { - Some(topology) => { - info!("Fetched network topology"); - Ok(Box::new(HardcodedTopologyProvider::new(topology))) - } - None => Err(String::from("Failed to fetch network topology")), - } -} diff --git a/nym-gateway-probe/src/common/wireguard.rs b/nym-gateway-probe/src/common/wireguard.rs index 2a9e2ba5b4..20a0e7ec8d 100644 --- a/nym-gateway-probe/src/common/wireguard.rs +++ b/nym-gateway-probe/src/common/wireguard.rs @@ -9,11 +9,11 @@ use nym_config::defaults::{WG_METADATA_PORT, WG_TUN_DEVICE_IP_ADDRESS_V4}; use tracing::{error, info}; +use crate::NetstackArgs; use crate::common::netstack::{ NetstackRequest, NetstackRequestGo, NetstackResult, TwoHopNetstackRequestGo, }; use crate::common::types::WgProbeResults; -use crate::config::NetstackArgs; /// Safe division that returns 0.0 when divisor is 0 (instead of NaN/Inf) fn safe_ratio(received: u16, sent: u16) -> f32 { diff --git a/nym-gateway-probe/src/config/credentials.rs b/nym-gateway-probe/src/config/credentials.rs index 50cf308b4b..71bc92b56f 100644 --- a/nym-gateway-probe/src/config/credentials.rs +++ b/nym-gateway-probe/src/config/credentials.rs @@ -1,29 +1,94 @@ // Copyright 2026 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use clap::Args; +use clap::{ArgGroup, Args}; +use nym_credentials_interface::TicketType; use nym_node_status_client::models::AttachedTicketMaterials; +use nym_sdk::mixnet::{ + CredentialStorage, DisconnectedMixnetClient, Ephemeral, MixnetClientStorage, OnDiskPersistent, +}; -#[derive(Args)] +use crate::common::bandwidth_helpers::{acquire_bandwidth, import_bandwidth}; + +#[derive(Debug, Args)] pub struct CredentialArgs { + /// Serialized credential data #[arg(long)] - ticket_materials: Option, + pub ticket_materials: String, + /// Version of the serialized credential #[arg(long, default_value_t = 1)] - ticket_materials_revision: u8, + pub ticket_materials_revision: u8, } impl CredentialArgs { - pub fn decode_attached_ticket_materials(&self) -> anyhow::Result { - let ticket_materials = self - .ticket_materials - .as_ref() - .ok_or_else(|| anyhow::anyhow!("ticket_materials is required"))? - .clone(); - - Ok(AttachedTicketMaterials::from_serialised_string( - ticket_materials, + pub async fn import_credential( + self, + mixnet_client: &DisconnectedMixnetClient, + ) -> anyhow::Result<()> { + let tickets_materials = AttachedTicketMaterials::from_serialised_string( + self.ticket_materials, self.ticket_materials_revision, - )?) + )?; + let bandwidth_import = mixnet_client.begin_bandwidth_import(); + import_bandwidth(bandwidth_import, tickets_materials).await?; + Ok(()) + } +} + +/// Two ways to inject credentials when not running as agent +/// 1. Mnemonic : expected to be used on lower envs +/// - mnemonic +/// 2. Mock ecash : expected to be used for local setups +/// - use_mock_ecash +#[derive(Debug, Args)] +#[command(group( + ArgGroup::new("credential_mode") + .args(["use_mock_ecash","mnemonic"]) + .required(true) + .multiple(false) +))] +pub struct CredentialMode { + /// Use mock ecash credentials for testing (requires gateway with --lp-use-mock-ecash) + #[arg(long, action = clap::ArgAction::SetTrue)] + pub use_mock_ecash: bool, + + /// Mnemonic to get credentials from the blockchain. It needs NYMs. + #[arg(long)] + pub mnemonic: Option, +} + +impl CredentialMode { + pub async fn acquire( + &self, + disconnected_mixnet_client: &DisconnectedMixnetClient, + storage: &OnDiskPersistent, + ) -> anyhow::Result<()> { + // Return immediately as there is nothing to do + if self.use_mock_ecash { + return Ok(()); + } + let ticketbook_count = storage + .credential_store() + .get_ticketbooks_info() + .await? + .len(); + tracing::info!("Credential store contains {} ticketbooks", ticketbook_count); + + if ticketbook_count < 1 { + let mnemonic = self.mnemonic.as_ref().ok_or_else(|| { + anyhow::anyhow!( + "We are not using mock ecash and mnemonic is not set, this should not happen" + ) + })?; + for ticketbook_type in [ + TicketType::V1MixnetEntry, + TicketType::V1WireguardEntry, + TicketType::V1WireguardExit, + ] { + acquire_bandwidth(mnemonic, disconnected_mixnet_client, ticketbook_type).await?; + } + } + Ok(()) } } diff --git a/nym-gateway-probe/src/config/mod.rs b/nym-gateway-probe/src/config/mod.rs index a87be29839..6ad78c942f 100644 --- a/nym-gateway-probe/src/config/mod.rs +++ b/nym-gateway-probe/src/config/mod.rs @@ -1,12 +1,49 @@ // Copyright 2026 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use clap::Args; + mod credentials; mod netstack; mod socks5; mod test_mode; -pub use credentials::CredentialArgs; +pub use credentials::{CredentialArgs, CredentialMode}; pub use netstack::NetstackArgs; pub use socks5::Socks5Args; pub use test_mode::TestMode; + +#[derive(Args, Debug)] +pub struct ProbeConfig { + /// Only choose gateway with that minimum performance + #[arg(long)] + pub min_gateway_mixnet_performance: Option, + + /// Test mode - explicitly specify which tests to run + /// + /// Modes: + /// core. - Traditional mixnet testing (entry/exit pings + WireGuard via authenticator) + /// wg-mix - Wireguard via authenticator + /// wg-lp - Entry LP + Exit LP (nested forwarding) + WireGuard + /// lp-only - LP registration only (no WireGuard) + /// socks5-only - Socks5 network requester test + /// all - Mixnet, wireguard over authenticator and LP registration + /// + #[arg(long, default_value_t = TestMode::default(), verbatim_doc_comment)] + pub test_mode: TestMode, + + #[arg(long, global = true)] + pub ignore_egress_epoch_role: bool, + + /// Arguments to be appended to the wireguard config enabling amnezia-wg configuration + #[arg(long)] + pub amnezia_args: Option, + + /// Arguments to manage netstack downloads + #[command(flatten)] + pub netstack_args: NetstackArgs, + + /// Arguments to configure socks5 probe + #[command(flatten)] + pub socks5_args: Socks5Args, +} diff --git a/nym-gateway-probe/src/config/netstack.rs b/nym-gateway-probe/src/config/netstack.rs index de6dda47d7..1e704bcf55 100644 --- a/nym-gateway-probe/src/config/netstack.rs +++ b/nym-gateway-probe/src/config/netstack.rs @@ -3,39 +3,39 @@ use clap::Args; -#[derive(Args, Clone)] +#[derive(Args, Clone, Debug)] pub struct NetstackArgs { - #[arg(long, default_value_t = 180)] + #[arg(long, hide = true, default_value_t = 180)] pub netstack_download_timeout_sec: u64, - #[arg(long, default_value_t = 30)] + #[arg(long, hide = true, default_value_t = 30)] pub metadata_timeout_sec: u64, - #[arg(long, default_value = "1.1.1.1")] + #[arg(long, hide = true, default_value = "1.1.1.1")] pub netstack_v4_dns: String, - #[arg(long, default_value = "2606:4700:4700::1111")] + #[arg(long, hide = true, default_value = "2606:4700:4700::1111")] pub netstack_v6_dns: String, - #[arg(long, default_value_t = 5)] + #[arg(long, hide = true, default_value_t = 5)] pub netstack_num_ping: u8, - #[arg(long, default_value_t = 3)] + #[arg(long, hide = true, default_value_t = 3)] pub netstack_send_timeout_sec: u64, - #[arg(long, default_value_t = 3)] + #[arg(long, hide = true, default_value_t = 3)] pub netstack_recv_timeout_sec: u64, - #[arg(long, default_values_t = vec!["nym.com".to_string()])] + #[arg(long, hide= true, default_values_t = vec!["nym.com".to_string()])] pub netstack_ping_hosts_v4: Vec, - #[arg(long, default_values_t = vec!["1.1.1.1".to_string()])] + #[arg(long, hide= true, default_values_t = vec!["1.1.1.1".to_string()])] pub netstack_ping_ips_v4: Vec, - #[arg(long, default_values_t = vec!["cloudflare.com".to_string()])] + #[arg(long, hide= true, default_values_t = vec!["cloudflare.com".to_string()])] pub netstack_ping_hosts_v6: Vec, - #[arg(long, default_values_t = vec!["2001:4860:4860::8888".to_string(), "2606:4700:4700::1111".to_string(), "2620:fe::fe".to_string()])] + #[arg(long, hide= true, default_values_t = vec!["2001:4860:4860::8888".to_string(), "2606:4700:4700::1111".to_string(), "2620:fe::fe".to_string()])] pub netstack_ping_ips_v6: Vec, } diff --git a/nym-gateway-probe/src/config/socks5.rs b/nym-gateway-probe/src/config/socks5.rs index 5c2a29c80a..3832dd4320 100644 --- a/nym-gateway-probe/src/config/socks5.rs +++ b/nym-gateway-probe/src/config/socks5.rs @@ -2,19 +2,21 @@ use clap::Args; use crate::common::socks5_test::JsonRpcClient; -#[derive(Args)] +const DEFAULT_RPC_ENDPOINT: &str = "https://cloudflare-eth.com"; + +#[derive(Args, Debug)] pub struct Socks5Args { - #[arg(long, value_delimiter = ';')] + #[arg(long, hide = true, value_delimiter = ';', default_value = DEFAULT_RPC_ENDPOINT)] pub socks5_json_rpc_url_list: Vec, - #[arg(long, default_value_t = 30)] + #[arg(long, hide = true, default_value_t = 30)] pub mixnet_client_timeout_sec: u64, - #[arg(long, default_value_t = 10)] + #[arg(long, hide = true, default_value_t = 10)] pub test_count: u64, /// stops socks5 test early after this many failed attempts - #[arg(long, default_value_t = 3)] + #[arg(long, hide = true, default_value_t = 3)] pub failure_count_cutoff: usize, } diff --git a/nym-gateway-probe/src/config/test_mode.rs b/nym-gateway-probe/src/config/test_mode.rs index 5c6e45450d..eec509f0e9 100644 --- a/nym-gateway-probe/src/config/test_mode.rs +++ b/nym-gateway-probe/src/config/test_mode.rs @@ -4,87 +4,72 @@ //! Test mode definitions for gateway probe. //! //! This module defines the different test modes supported by the gateway probe: -//! - Mixnet: Traditional mixnet path testing -//! - SingleHop: LP registration + WireGuard on single gateway -//! - TwoHop: Entry LP + Exit LP (nested forwarding) + WireGuard +//! - Core: Traditional mixnet path testing and Wireguard via authenticator +//! - WgMix: Wireguard via authenticator +//! - WgLp: Entry LP + Exit LP (nested forwarding) + WireGuard //! - LpOnly: LP registration only, no WireGuard +//! - Socks5Only: Socks5 test +//! - All: Mixnet, wireguard over authenticator and LP registration /// Test mode for the gateway probe. /// /// Determines which tests are performed and how connections are established. -// This enum replaces the scattered boolean flags (only_wireguard, -// only_lp_registration, test_lp_wg) with explicit, named modes for clarity. #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub enum TestMode { - /// Traditional mixnet testing - connects via mixnet, tests entry/exit pings + WireGuard via authenticator + /// Mixnet tests + WireGuard via authenticator #[default] - Mixnet, - /// LP registration + WireGuard on single gateway (no mixnet, no forwarding) - SingleHop, - /// Entry LP + Exit LP (nested session forwarding) + WireGuard tunnel - TwoHop, - /// LP registration only - test handshake and registration, skip WireGuard + Core, + /// Wireguard via authenticator + WgMix, + /// Wireguard over LP + WgLp, + /// LP registration only - test handshake and registration LpOnly, + /// Socks5 test only + Socks5Only, + /// Mixnet tests, Wireguard tests, LP tests, Socks5 test + All, } impl TestMode { - /// Infer test mode from legacy boolean flags (backward compatibility) - pub fn from_flags( - only_wireguard: bool, - only_lp_registration: bool, - test_lp_wg: bool, - has_exit_gateway: bool, - ) -> Self { - if only_lp_registration { - TestMode::LpOnly - } else if test_lp_wg { - if has_exit_gateway { - TestMode::TwoHop - } else { - TestMode::SingleHop - } - } else if only_wireguard { - // WireGuard via authenticator (still uses mixnet path) - TestMode::Mixnet - } else { - TestMode::Mixnet - } + // Wether we need to run mixnet tests + pub fn mixnet_tests(&self) -> bool { + matches!(self, TestMode::Core | TestMode::All) + } + + // Wether we need to run Wiregurd tests + pub fn wireguard_tests(&self) -> bool { + matches!( + self, + TestMode::Core | TestMode::WgMix | TestMode::WgLp | TestMode::All + ) + } + + // Wether we need to run Lp tests + pub fn lp_tests(&self) -> bool { + matches!(self, TestMode::WgLp | TestMode::LpOnly | TestMode::All) + } + + // Wether we need to run socks5 tests + pub fn socks5_tests(&self) -> bool { + matches!(self, TestMode::Socks5Only | TestMode::All) } /// Whether this mode requires a mixnet client pub fn needs_mixnet(&self) -> bool { - matches!(self, TestMode::Mixnet) - } - - /// Whether this mode uses LP registration - pub fn uses_lp(&self) -> bool { - matches!( - self, - TestMode::SingleHop | TestMode::TwoHop | TestMode::LpOnly - ) - } - - /// Whether this mode tests WireGuard tunnels - pub fn tests_wireguard(&self) -> bool { - matches!( - self, - TestMode::Mixnet | TestMode::SingleHop | TestMode::TwoHop - ) - } - - /// Whether this mode requires an exit gateway - pub fn needs_exit_gateway(&self) -> bool { - matches!(self, TestMode::TwoHop) + matches!(self, TestMode::Core | TestMode::All | TestMode::WgMix) } } impl std::fmt::Display for TestMode { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - TestMode::Mixnet => write!(f, "mixnet"), - TestMode::SingleHop => write!(f, "single-hop"), - TestMode::TwoHop => write!(f, "two-hop"), + TestMode::Core => write!(f, "core"), + TestMode::WgMix => write!(f, "wg-mix"), + TestMode::WgLp => write!(f, "wg-lp"), TestMode::LpOnly => write!(f, "lp-only"), + TestMode::Socks5Only => write!(f, "socks5-only"), + TestMode::All => write!(f, "all"), } } } @@ -94,12 +79,14 @@ impl std::str::FromStr for TestMode { fn from_str(s: &str) -> Result { match s.to_lowercase().as_str() { - "mixnet" => Ok(TestMode::Mixnet), - "single-hop" | "singlehop" | "single_hop" => Ok(TestMode::SingleHop), - "two-hop" | "twohop" | "two_hop" => Ok(TestMode::TwoHop), + "mixnet" | "core" => Ok(TestMode::Core), + "wg-mix" | "wgmix" | "wg_mix" => Ok(TestMode::WgMix), + "wg-lp" | "wglp" | "wg_lp" => Ok(TestMode::WgLp), "lp-only" | "lponly" | "lp_only" => Ok(TestMode::LpOnly), + "socks5-only" | "socks5only" | "socks5_only" => Ok(TestMode::Socks5Only), + "all" => Ok(TestMode::All), _ => Err(format!( - "Unknown test mode: '{}'. Valid modes: mixnet, single-hop, two-hop, lp-only", + "Unknown test mode: '{}'. Valid modes: core, wg-mix, wg-lp, lp-only, socks5-only, all", s )), } @@ -110,152 +97,80 @@ impl std::str::FromStr for TestMode { mod tests { use super::*; - // ============ from_flags() tests ============ - - #[test] - fn test_from_flags_default_is_mixnet() { - // All flags false -> Mixnet (default) - assert_eq!( - TestMode::from_flags(false, false, false, false), - TestMode::Mixnet - ); - } - - #[test] - fn test_from_flags_only_wireguard_is_mixnet() { - // only_wireguard still uses mixnet path (WG via authenticator) - assert_eq!( - TestMode::from_flags(true, false, false, false), - TestMode::Mixnet - ); - } - - #[test] - fn test_from_flags_only_lp_registration() { - // only_lp_registration -> LpOnly (takes priority) - assert_eq!( - TestMode::from_flags(false, true, false, false), - TestMode::LpOnly - ); - // Even with other flags set, only_lp_registration wins - assert_eq!( - TestMode::from_flags(true, true, true, true), - TestMode::LpOnly - ); - } - - #[test] - fn test_from_flags_test_lp_wg_single_hop() { - // test_lp_wg without exit gateway -> SingleHop - assert_eq!( - TestMode::from_flags(false, false, true, false), - TestMode::SingleHop - ); - } - - #[test] - fn test_from_flags_test_lp_wg_two_hop() { - // test_lp_wg with exit gateway -> TwoHop - assert_eq!( - TestMode::from_flags(false, false, true, true), - TestMode::TwoHop - ); - } - - #[test] - fn test_from_flags_has_exit_gateway_alone_is_mixnet() { - // has_exit_gateway alone doesn't change mode - assert_eq!( - TestMode::from_flags(false, false, false, true), - TestMode::Mixnet - ); - } - // ============ Helper method tests ============ #[test] fn test_needs_mixnet() { - assert!(TestMode::Mixnet.needs_mixnet()); - assert!(!TestMode::SingleHop.needs_mixnet()); - assert!(!TestMode::TwoHop.needs_mixnet()); + assert!(TestMode::Core.needs_mixnet()); + assert!(TestMode::WgMix.needs_mixnet()); + assert!(!TestMode::WgLp.needs_mixnet()); assert!(!TestMode::LpOnly.needs_mixnet()); - } - - #[test] - fn test_uses_lp() { - assert!(!TestMode::Mixnet.uses_lp()); - assert!(TestMode::SingleHop.uses_lp()); - assert!(TestMode::TwoHop.uses_lp()); - assert!(TestMode::LpOnly.uses_lp()); - } - - #[test] - fn test_tests_wireguard() { - assert!(TestMode::Mixnet.tests_wireguard()); - assert!(TestMode::SingleHop.tests_wireguard()); - assert!(TestMode::TwoHop.tests_wireguard()); - assert!(!TestMode::LpOnly.tests_wireguard()); - } - - #[test] - fn test_needs_exit_gateway() { - assert!(!TestMode::Mixnet.needs_exit_gateway()); - assert!(!TestMode::SingleHop.needs_exit_gateway()); - assert!(TestMode::TwoHop.needs_exit_gateway()); - assert!(!TestMode::LpOnly.needs_exit_gateway()); + assert!(!TestMode::Socks5Only.needs_mixnet()); + assert!(TestMode::All.needs_mixnet()); } // ============ Display tests ============ #[test] fn test_display() { - assert_eq!(TestMode::Mixnet.to_string(), "mixnet"); - assert_eq!(TestMode::SingleHop.to_string(), "single-hop"); - assert_eq!(TestMode::TwoHop.to_string(), "two-hop"); + assert_eq!(TestMode::Core.to_string(), "core"); + assert_eq!(TestMode::WgMix.to_string(), "wg-mix"); + assert_eq!(TestMode::WgLp.to_string(), "wg-lp"); assert_eq!(TestMode::LpOnly.to_string(), "lp-only"); + assert_eq!(TestMode::Socks5Only.to_string(), "socks5-only"); + assert_eq!(TestMode::All.to_string(), "all"); } // ============ FromStr tests ============ #[test] fn test_from_str_canonical() { - assert_eq!("mixnet".parse::().unwrap(), TestMode::Mixnet); - assert_eq!( - "single-hop".parse::().unwrap(), - TestMode::SingleHop - ); - assert_eq!("two-hop".parse::().unwrap(), TestMode::TwoHop); + assert_eq!("core".parse::().unwrap(), TestMode::Core); + assert_eq!("wg-mix".parse::().unwrap(), TestMode::WgMix); + assert_eq!("wg-lp".parse::().unwrap(), TestMode::WgLp); assert_eq!("lp-only".parse::().unwrap(), TestMode::LpOnly); + assert_eq!( + "socks5-only".parse::().unwrap(), + TestMode::Socks5Only + ); + assert_eq!("all".parse::().unwrap(), TestMode::All); } #[test] fn test_from_str_alternate_formats() { + // Default aliases + assert_eq!("mixnet".parse::().unwrap(), TestMode::Core); + // snake_case - assert_eq!( - "single_hop".parse::().unwrap(), - TestMode::SingleHop - ); - assert_eq!("two_hop".parse::().unwrap(), TestMode::TwoHop); + assert_eq!("wg_mix".parse::().unwrap(), TestMode::WgMix); + assert_eq!("wg_lp".parse::().unwrap(), TestMode::WgLp); assert_eq!("lp_only".parse::().unwrap(), TestMode::LpOnly); + assert_eq!( + "socks5_only".parse::().unwrap(), + TestMode::Socks5Only + ); // no separator - assert_eq!( - "singlehop".parse::().unwrap(), - TestMode::SingleHop - ); - assert_eq!("twohop".parse::().unwrap(), TestMode::TwoHop); + assert_eq!("wgmix".parse::().unwrap(), TestMode::WgMix); + assert_eq!("wglp".parse::().unwrap(), TestMode::WgLp); assert_eq!("lponly".parse::().unwrap(), TestMode::LpOnly); + assert_eq!( + "socks5only".parse::().unwrap(), + TestMode::Socks5Only + ); } #[test] fn test_from_str_case_insensitive() { - assert_eq!("MIXNET".parse::().unwrap(), TestMode::Mixnet); - assert_eq!( - "Single-Hop".parse::().unwrap(), - TestMode::SingleHop - ); - assert_eq!("TWO_HOP".parse::().unwrap(), TestMode::TwoHop); + assert_eq!("cOrE".parse::().unwrap(), TestMode::Core); + assert_eq!("WG-MIX".parse::().unwrap(), TestMode::WgMix); + assert_eq!("Wg_Lp".parse::().unwrap(), TestMode::WgLp); assert_eq!("LpOnly".parse::().unwrap(), TestMode::LpOnly); + assert_eq!( + "soCkS5-oNlY".parse::().unwrap(), + TestMode::Socks5Only + ); + assert_eq!("ALL".parse::().unwrap(), TestMode::All); } #[test] @@ -270,10 +185,12 @@ mod tests { #[test] fn test_display_fromstr_roundtrip() { for mode in [ - TestMode::Mixnet, - TestMode::SingleHop, - TestMode::TwoHop, + TestMode::Core, + TestMode::WgMix, + TestMode::WgLp, TestMode::LpOnly, + TestMode::Socks5Only, + TestMode::All, ] { let s = mode.to_string(); let parsed: TestMode = s.parse().unwrap(); diff --git a/nym-gateway-probe/src/lib.rs b/nym-gateway-probe/src/lib.rs index a694d6de86..b7a6a23b19 100644 --- a/nym-gateway-probe/src/lib.rs +++ b/nym-gateway-probe/src/lib.rs @@ -1,897 +1,449 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only +use crate::common::bandwidth_helpers::build_bandwidth_controller; use crate::common::helpers; +use crate::common::nodes::TestedNodeDetails; use crate::common::probe_tests::{ do_ping, do_socks5_connectivity_test, lp_registration_probe, wg_probe, wg_probe_lp, }; -use crate::common::types::{Entry, Exit, Socks5ProbeResults, WgProbeResults}; -use crate::config::Socks5Args; -use anyhow::bail; -use nym_api_requests::models::NetworkRequesterDetailsV1; +use crate::common::types::{Entry, LpProbeResults}; +use crate::config::{CredentialArgs, CredentialMode, NetstackArgs, ProbeConfig}; use nym_authenticator_client::{AuthClientMixnetListener, AuthenticatorClient}; +use nym_bandwidth_controller::BandwidthTicketProvider; use nym_client_core::config::ForgetMe; use nym_config::defaults::NymNetworkDetails; use nym_credentials_interface::TicketType; -use nym_crypto::asymmetric::x25519::KeyPair; +use nym_crypto::asymmetric::x25519; use nym_sdk::mixnet::{ - CredentialStorage, Ephemeral, KeyStore, MixnetClient, MixnetClientBuilder, MixnetClientStorage, - NodeIdentity, StoragePaths, + Ephemeral, KeyStore, MixnetClient, MixnetClientBuilder, MixnetClientStorage, StoragePaths, }; -use nym_topology::NymTopology; +use nym_topology::{HardcodedTopologyProvider, NymTopology}; use rand::rngs::OsRng; use std::path::PathBuf; use std::sync::Arc; use tokio_util::sync::CancellationToken; use tracing::*; -use url::Url; + +pub use crate::common::nodes::{NymApiDirectory, query_gateway_by_ip}; +pub use crate::common::types::{ProbeOutcome, ProbeResult}; mod common; pub use common::types; pub mod config; -use crate::common::bandwidth_helpers::{ - acquire_bandwidth, build_bandwidth_controller, import_bandwidth, -}; -pub use crate::common::nodes::{ - DirectoryNode, NymApiDirectory, TestedNode, TestedNodeDetails, TestedNodeLpDetails, - query_gateway_by_ip, -}; -pub use crate::common::types::{IpPingReplies, ProbeOutcome, ProbeResult}; -pub use crate::config::{CredentialArgs, NetstackArgs, TestMode}; - pub struct Probe { - entrypoint: NodeIdentity, - tested_node: TestedNode, - amnezia_args: String, - netstack_args: NetstackArgs, - credentials_args: CredentialArgs, - /// Pre-queried gateway node (used when --gateway-ip is specified) - direct_gateway_node: Option, - /// Pre-queried exit gateway node (used when --exit-gateway-ip is specified for LP forwarding) - exit_gateway_node: Option, - /// Localnet entry gateway info (used when --entry-gateway-identity is specified) - localnet_entry: Option, - /// Localnet exit gateway info (used when --exit-gateway-identity is specified) - localnet_exit: Option, - socks5_args: Socks5Args, + /// Entry node + entry_node: TestedNodeDetails, + /// Optional exit gateway node. If not provided, entry will be used + exit_node: Option, + + config: ProbeConfig, + + network: NymNetworkDetails, + + topology: Option, } impl Probe { + /// Create a probe with pre-queried gateway nodes pub fn new( - entrypoint: NodeIdentity, - tested_node: TestedNode, - netstack_args: NetstackArgs, - credentials_args: CredentialArgs, - socks5_args: Socks5Args, + entry_node: TestedNodeDetails, + exit_node: Option, + network: NymNetworkDetails, + config: ProbeConfig, ) -> Self { Self { - entrypoint, - tested_node, - amnezia_args: "".into(), - netstack_args, - credentials_args, - direct_gateway_node: None, - exit_gateway_node: None, - localnet_entry: None, - localnet_exit: None, - socks5_args, + entry_node, + exit_node, + network, + config, + topology: None, } } - /// Create a probe with a pre-queried gateway node (for direct IP mode) - pub fn new_with_gateway( - entrypoint: NodeIdentity, - tested_node: TestedNode, - netstack_args: NetstackArgs, - credentials_args: CredentialArgs, - gateway_node: DirectoryNode, - socks5_args: Socks5Args, - ) -> Self { - Self { - entrypoint, - tested_node, - amnezia_args: "".into(), - netstack_args, - credentials_args, - direct_gateway_node: Some(gateway_node), - exit_gateway_node: None, - localnet_entry: None, - localnet_exit: None, - socks5_args, - } - } - - /// Create a probe with both entry and exit gateways pre-queried (for LP forwarding tests) - pub fn new_with_gateways( - entrypoint: NodeIdentity, - tested_node: TestedNode, - netstack_args: NetstackArgs, - credentials_args: CredentialArgs, - entry_gateway_node: DirectoryNode, - exit_gateway_node: DirectoryNode, - socks5_args: Socks5Args, - ) -> Self { - Self { - entrypoint, - tested_node, - amnezia_args: "".into(), - netstack_args, - credentials_args, - direct_gateway_node: Some(entry_gateway_node), - exit_gateway_node: Some(exit_gateway_node), - localnet_entry: None, - localnet_exit: None, - socks5_args, - } - } - - /// Create a probe for localnet mode (no HTTP query needed) - /// Uses identity + LP address directly from CLI args - pub fn new_localnet( - entry: TestedNodeDetails, - exit: Option, - netstack_args: NetstackArgs, - credentials_args: CredentialArgs, - socks5_args: Socks5Args, - ) -> Self { - let entrypoint = entry.identity; - Self { - entrypoint, - tested_node: TestedNode::SameAsEntry, - amnezia_args: "".into(), - netstack_args, - credentials_args, - direct_gateway_node: None, - exit_gateway_node: None, - localnet_entry: Some(entry), - localnet_exit: exit, - socks5_args, - } - } - - pub fn with_amnezia(&mut self, args: &str) -> &Self { - self.amnezia_args = args.to_string(); - self - } - - #[allow(clippy::too_many_arguments)] - pub async fn probe( - self, - directory: Option, - nyxd_url: Url, - ignore_egress_epoch_role: bool, - only_wireguard: bool, - only_lp_registration: bool, - test_lp_wg: bool, - min_mixnet_performance: Option, - network_details: NymNetworkDetails, + /// Run a probe as an NS agent, i.e. a bonded node on a known network + pub async fn probe_run_agent( + mut self, + credential_args: CredentialArgs, ) -> anyhow::Result { - let tickets_materials = self.credentials_args.decode_attached_ticket_materials()?; - - let tested_entry = self.tested_node.is_same_as_entry(); - let (mixnet_entry_gateway_id, node_info) = self.lookup_gateway(&directory).await?; - let storage = Ephemeral::default(); + let mixnet_debug_config = helpers::mixnet_debug_config( + self.config.min_gateway_mixnet_performance, + self.config.ignore_egress_epoch_role, + ); + + // If we need to run at least one mixnet client, prefetch topology + if self.config.test_mode.needs_mixnet() || self.config.test_mode.socks5_tests() { + self.topology = helpers::fetch_topology(&self.network, &mixnet_debug_config) + .await + .inspect_err(|e| warn!("Failed to fetch topology for that run, mixnet clients will have to handle themselves : {e}")).ok(); + } + // Connect to the mixnet via the entry gateway - let disconnected_mixnet_client = MixnetClientBuilder::new_with_storage(storage.clone()) - .request_gateway(mixnet_entry_gateway_id.to_string()) - .network_details(network_details.clone()) - .debug_config(helpers::mixnet_debug_config( - min_mixnet_performance, - ignore_egress_epoch_role, - )) + let mut mixnet_client_builder = MixnetClientBuilder::new_with_storage(storage.clone()) + .request_gateway(self.entry_node.identity.to_string()) + .network_details(self.network.clone()) + .debug_config(mixnet_debug_config) .with_forget_me(ForgetMe::new_all()) - .credentials_mode(true) - .build()?; + .credentials_mode(true); - // in normal operation expects the ticket material to be provided as an argument - let bandwidth_import = disconnected_mixnet_client.begin_bandwidth_import(); - import_bandwidth(bandwidth_import, tickets_materials).await?; + if let Some(topology) = &self.topology { + mixnet_client_builder = mixnet_client_builder.custom_topology_provider(Box::new( + HardcodedTopologyProvider::new(topology.clone()), + )); + } - let mixnet_client = Box::pin(disconnected_mixnet_client.connect_to_mixnet()).await; + let disconnected_mixnet_client = mixnet_client_builder.build()?; - // Extract topology from the connected client (if successful) to reuse for SOCKS5 test - let topology = match &mixnet_client { - Ok(client) => client - .read_current_route_provider() - .await - .map(|rp| rp.topology.clone()), - Err(_) => None, + // Import credential + credential_args + .import_credential(&disconnected_mixnet_client) + .await?; + + let bandwidth_provider = + build_bandwidth_controller(&self.network, storage.credential_store().clone(), false)?; + + // Mixnet client start + let mixnet_client = if self.config.test_mode.needs_mixnet() { + Some(disconnected_mixnet_client.connect_to_mixnet().await) + } else { + // Make sure keys are generated, in case we don't start the mixnet client + let key_store = storage.key_store(); + let mut rng = OsRng; + if key_store.load_keys().await.is_err() { + tracing::log::debug!("Generating new client keys"); + nym_client_core::init::generate_new_client_keys(&mut rng, key_store).await?; + } + None }; - // Convert legacy flags to TestMode - let has_exit = self.exit_gateway_node.is_some() || self.localnet_exit.is_some(); - let test_mode = - TestMode::from_flags(only_wireguard, only_lp_registration, test_lp_wg, has_exit); - - self.do_probe_test( - Some(mixnet_client), - storage, - mixnet_entry_gateway_id, - node_info, - directory.as_ref(), - nyxd_url, - tested_entry, - test_mode, - only_wireguard, - false, // Not using mock ecash in regular probe mode - network_details, - topology, - ) - .await + self.do_probe_test(mixnet_client, bandwidth_provider).await } - #[allow(clippy::too_many_arguments)] + /// Run a probe on unannounced gateway(s) some tests will not be available pub async fn probe_run_locally( self, config_dir: &PathBuf, - mnemonic: Option<&str>, - directory: Option, - nyxd_url: Url, - ignore_egress_epoch_role: bool, - only_wireguard: bool, - only_lp_registration: bool, - test_lp_wg: bool, - min_mixnet_performance: Option, - use_mock_ecash: bool, - network_details: NymNetworkDetails, + credential: CredentialMode, ) -> anyhow::Result { - // Localnet mode - identity + LP address from CLI, no HTTP query - // This path is used when --entry-gateway-identity is specified - if let Some(entry_info) = &self.localnet_entry { - info!("Using localnet mode with CLI-provided gateway identities"); - - // Initialize storage (needed for credentials) - if !config_dir.exists() { - std::fs::create_dir_all(config_dir)?; - } - let storage_paths = StoragePaths::new_from_dir(config_dir)?; - let storage = storage_paths - .initialise_default_persistent_storage() - .await?; - - // For localnet, use entry as the test node (or exit if provided) - let mixnet_entry_gateway_id = entry_info.identity; - let node_info = if let Some(exit_info) = &self.localnet_exit { - exit_info.clone() - } else { - entry_info.clone() - }; - - // Convert legacy flags to TestMode - let has_exit = self.localnet_exit.is_some(); - let test_mode = - TestMode::from_flags(only_wireguard, only_lp_registration, test_lp_wg, has_exit); - - return self - .do_probe_test( - None, - storage, - mixnet_entry_gateway_id, - node_info, - directory.as_ref(), - nyxd_url, - false, // tested_entry - test_mode, - only_wireguard, - use_mock_ecash, - network_details, - None, // No topology (no mixnet client in localnet mode) - ) - .await; - } - - // If both gateways are pre-queried via --gateway-ip and --exit-gateway-ip, - // skip mixnet setup entirely - we have all the data we need - if self.direct_gateway_node.is_some() && self.exit_gateway_node.is_some() { - let entry_node = if let Some(entry_node) = self.direct_gateway_node.as_ref() { - entry_node - } else { - return Err(anyhow::anyhow!("Entry gateway node is missing")); - }; - let exit_node = if let Some(exit_node) = self.exit_gateway_node.as_ref() { - exit_node - } else { - return Err(anyhow::anyhow!("Exit gateway node is missing")); - }; - - // Initialize storage (needed for credentials) - if !config_dir.exists() { - std::fs::create_dir_all(config_dir)?; - } - let storage_paths = StoragePaths::new_from_dir(config_dir)?; - let storage = storage_paths - .initialise_default_persistent_storage() - .await?; - - // Get node details from pre-queried nodes - let mixnet_entry_gateway_id = entry_node.identity(); - let node_info = exit_node.to_testable_node()?; - - // Convert legacy flags to TestMode (has_exit = true since we have exit_gateway_node) - let test_mode = - TestMode::from_flags(only_wireguard, only_lp_registration, test_lp_wg, true); - - return self - .do_probe_test( - None, - storage, - mixnet_entry_gateway_id, - node_info, - directory.as_ref(), - nyxd_url, - false, // tested_entry - test_mode, - only_wireguard, - use_mock_ecash, - network_details, - None, // No topology (no mixnet client in direct gateway mode) - ) - .await; - } - - // If only testing LP registration, use the dedicated LP-only path - // This skips mixnet setup entirely and allows testing local gateways - if only_lp_registration { - return self - .probe_lp_only(config_dir, directory, nyxd_url, use_mock_ecash) - .await; - } - - let tested_entry = self.tested_node.is_same_as_entry(); - let (mixnet_entry_gateway_id, node_info) = self.lookup_gateway(&directory).await?; - - if config_dir.is_file() { - bail!("provided configuration directory is a file"); - } - - if !config_dir.exists() { - std::fs::create_dir_all(config_dir)?; - } - let storage_paths = StoragePaths::new_from_dir(config_dir)?; let storage = storage_paths .initialise_default_persistent_storage() .await?; - // Connect to the mixnet via the entry gateway, without forget-me flag so that gateway remembers client - // and keeps its bandwidth between probe runs + // We cannot run mixnet tests on unannounced gateway, but we still need one to import credential if not using mock ecash let disconnected_mixnet_client = MixnetClientBuilder::new_with_storage(storage.clone()) - .request_gateway(mixnet_entry_gateway_id.to_string()) - .network_details(network_details.clone()) - .debug_config(helpers::mixnet_debug_config( - min_mixnet_performance, - ignore_egress_epoch_role, - )) - .credentials_mode(true) + .credentials_mode(!credential.use_mock_ecash) .build()?; + // Acquire credential if needed + credential + .acquire(&disconnected_mixnet_client, &storage) + .await?; + + let bandwidth_provider = build_bandwidth_controller( + &self.network, + storage.credential_store().clone(), + credential.use_mock_ecash, + )?; + + // Make sure keys are generated let key_store = storage.key_store(); let mut rng = OsRng; - - // WORKAROUND SINCE IT HASN'T MADE IT TO THE MONOREPO: if key_store.load_keys().await.is_err() { tracing::log::debug!("Generating new client keys"); nym_client_core::init::generate_new_client_keys(&mut rng, key_store).await?; } - let ticketbook_count = storage - .credential_store() - .get_ticketbooks_info() - .await? - .len(); - - info!("Credential store contains {} ticketbooks", ticketbook_count); - - // Only acquire real bandwidth if not using mock ecash - if ticketbook_count < 1 && !use_mock_ecash { - let mnemonic = mnemonic.ok_or_else(|| { - anyhow::anyhow!("mnemonic is required when not using mock ecash (--use-mock-ecash)") - })?; - for ticketbook_type in [ - TicketType::V1MixnetEntry, - TicketType::V1WireguardEntry, - TicketType::V1WireguardExit, - ] { - acquire_bandwidth(mnemonic, &disconnected_mixnet_client, ticketbook_type).await?; - } - } else if use_mock_ecash { - info!("Using mock ecash mode - skipping bandwidth acquisition"); - } - - let mixnet_client = Box::pin(disconnected_mixnet_client.connect_to_mixnet()).await; - - // extract topology from the connected client (if any) to reuse for SOCKS5 test - let topology = match &mixnet_client { - Ok(client) => client - .read_current_route_provider() - .await - .map(|rp| rp.topology.clone()), - Err(_) => None, - }; - - // Convert legacy flags to TestMode - let has_exit = self.exit_gateway_node.is_some() || self.localnet_exit.is_some(); - let test_mode = - TestMode::from_flags(only_wireguard, only_lp_registration, test_lp_wg, has_exit); - - self.do_probe_test( - Some(mixnet_client), - storage, - mixnet_entry_gateway_id, - node_info, - directory.as_ref(), - nyxd_url, - tested_entry, - test_mode, - only_wireguard, - use_mock_ecash, - network_details, - topology, - ) - .await + self.do_probe_test(None, bandwidth_provider).await } - /// Probe LP registration only, skipping all mixnet tests - /// This is useful for testing local dev gateways that aren't registered in nym-api - pub async fn probe_lp_only( - self, + pub async fn probe_run( + mut self, config_dir: &PathBuf, - directory: Option, - nyxd_url: Url, - use_mock_ecash: bool, + credential: CredentialMode, ) -> anyhow::Result { - let tested_entry = self.tested_node.is_same_as_entry(); - let (mixnet_entry_gateway_id, node_info) = self.lookup_gateway(&directory).await?; - - if config_dir.is_file() { - bail!("provided configuration directory is a file"); - } - - if !config_dir.exists() { - std::fs::create_dir_all(config_dir)?; - } - let storage_paths = StoragePaths::new_from_dir(config_dir)?; let storage = storage_paths .initialise_default_persistent_storage() .await?; - let key_store = storage.key_store(); - let mut rng = OsRng; + let mixnet_debug_config = helpers::mixnet_debug_config( + self.config.min_gateway_mixnet_performance, + self.config.ignore_egress_epoch_role, + ); - // Generate client keys if they don't exist - if key_store.load_keys().await.is_err() { - tracing::log::debug!("Generating new client keys"); - nym_client_core::init::generate_new_client_keys(&mut rng, key_store).await?; + // If we need to run at least one mixnet client, prefetch topology + if self.config.test_mode.needs_mixnet() || self.config.test_mode.socks5_tests() { + self.topology = helpers::fetch_topology(&self.network, &mixnet_debug_config) + .await + .inspect_err(|e| warn!("Failed to fetch topology for that run, mixnet clients will have to handle themselves : {e}")).ok(); } - // Check if node has LP address - let Some(lp_data) = node_info.lp_data else { - bail!("Gateway does not have LP data configured"); + // Connect to the mixnet via the entry gateway, with forget-me flag only for stats so that gateway remembers client + // and keeps its bandwidth between probe runs + let mut mixnet_client_builder = MixnetClientBuilder::new_with_storage(storage.clone()) + .request_gateway(self.entry_node.identity.to_string()) + .network_details(self.network.clone()) + .debug_config(mixnet_debug_config) + .with_forget_me(ForgetMe::new_stats()) + .credentials_mode(!credential.use_mock_ecash); + + if let Some(topology) = &self.topology { + mixnet_client_builder = mixnet_client_builder.custom_topology_provider(Box::new( + HardcodedTopologyProvider::new(topology.clone()), + )); + } + + let disconnected_mixnet_client = mixnet_client_builder.build()?; + + // Acquire credential if needed + credential + .acquire(&disconnected_mixnet_client, &storage) + .await?; + + let bandwidth_provider = build_bandwidth_controller( + &self.network, + storage.credential_store().clone(), + credential.use_mock_ecash, + )?; + + // Mixnet client start + let mixnet_client = if self.config.test_mode.needs_mixnet() { + Some(disconnected_mixnet_client.connect_to_mixnet().await) + } else { + // Make sure keys are generated, in case we don't start the mixnet client + let key_store = storage.key_store(); + let mut rng = OsRng; + if key_store.load_keys().await.is_err() { + tracing::log::debug!("Generating new client keys"); + nym_client_core::init::generate_new_client_keys(&mut rng, key_store).await?; + } + None }; - info!("Testing LP registration for gateway {}", node_info.identity); - - // Create bandwidth controller for credential preparation - let config = nym_validator_client::nyxd::Config::try_from_nym_network_details( - &NymNetworkDetails::new_from_env(), - )?; - let client = nym_validator_client::nyxd::NyxdClient::connect(config, nyxd_url.as_str())?; - - let bw_controller = - build_bandwidth_controller(client, storage.credential_store().clone(), use_mock_ecash); - - // Run LP registration probe - let lp_outcome = lp_registration_probe(node_info.identity, lp_data, &bw_controller) - .await - .unwrap_or_default(); - - // Return result with only LP outcome - Ok(ProbeResult { - node: node_info.identity.to_string(), - used_entry: mixnet_entry_gateway_id.to_string(), - outcome: ProbeOutcome { - as_entry: Entry::NotTested, - as_exit: if tested_entry { - None - } else { - Some(Exit::fail_to_connect()) - }, - wg: None, - socks5: None, - lp: Some(lp_outcome), - }, - }) + self.do_probe_test(mixnet_client, bandwidth_provider).await } - async fn test_socks5_if_possible( - &self, - network_details: NymNetworkDetails, - network_requester_details: &Option, - directory: &NymApiDirectory, - topology: Option, - ) -> Option { - if let Some(nr_details) = network_requester_details { - match do_socks5_connectivity_test( - &nr_details.address, - network_details, - directory, - self.socks5_args.socks5_json_rpc_url_list.clone(), - self.socks5_args.mixnet_client_timeout_sec, - self.socks5_args.test_count, - self.socks5_args.failure_count_cutoff, - topology, - ) - .await - { - Ok(results) => Some(results), - Err(e) => { - error!("SOCKS5 test failed: {}", e); + #[allow(clippy::too_many_arguments)] + pub async fn do_probe_test( + self, + mixnet_client: Option>, + bandwith_provider: Box, + ) -> anyhow::Result { + // Setup exit node + let entry_under_test = self.exit_node.is_none(); + let exit_node = self.exit_node.unwrap_or(self.entry_node.clone()); + + let mut probe_result = ProbeResult { + node: self.entry_node.identity.to_string(), + used_entry: exit_node.identity.to_string(), + outcome: ProbeOutcome { + as_entry: Entry::NotTested, + as_exit: None, + wg: None, + lp: None, + socks5: None, + }, + }; + + let mixnet_client = match mixnet_client { + Some(Ok(mixnet_client)) => { + // We can connect, we don't know about routing yet, but having `false` if we don't test it is weird + probe_result.outcome.as_entry = Entry::success(); + info!( + "Successfully connected to entry gateway: {}", + self.entry_node.identity + ); + info!("Our nym address: {}", *mixnet_client.nym_address()); + Some(mixnet_client) + } + Some(Err(err)) => { + error!("Failed to connect to mixnet: {err}"); + probe_result.outcome.as_entry = if entry_under_test { + Entry::fail_to_connect() + } else { + Entry::EntryFailure + }; + None + } + None => { + // At the moment, this is no-op. But if the initialization changes, we will have the correct value + probe_result.outcome.as_entry = Entry::NotTested; + None + } + }; + + // Mixnet ping tests + // There is some weird gymnastics with the mixnet client, but we need to give and then retrieve ownership + let mixnet_client = if self.config.test_mode.mixnet_tests() { + match mixnet_client { + Some(client) => { + let nym_address = *client.nym_address(); + let (outcome, client) = do_ping( + client, + nym_address, + exit_node.exit_router_address, + entry_under_test, + ) + .await; + match outcome { + Ok(outcome) => { + probe_result.outcome = outcome; + } + Err(e) => { + error!("Mixnet ping tests ended with an error : {e}"); + } + } + Some(client) + } + None => { + error!("Mixnet tests cannot be run without a mixnet client"); + probe_result.outcome.as_entry = if entry_under_test { + Entry::fail_to_connect() + } else { + Entry::EntryFailure + }; None } } } else { - info!("No NR available, skipping SOCKS5 tests"); - None - } - } - - pub async fn lookup_gateway( - &self, - directory: &Option, - ) -> anyhow::Result<(NodeIdentity, TestedNodeDetails)> { - // If we have a pre-queried gateway node (direct IP mode), use that - if let Some(direct_node) = &self.direct_gateway_node { - info!("Using pre-queried gateway node from direct IP query"); - let node_info = direct_node.to_testable_node()?; - info!("connecting to entry gateway: {}", direct_node.identity()); - debug!( - "authenticator version: {:?}", - node_info.authenticator_version - ); - return Ok((self.entrypoint, node_info)); - } - - // Otherwise, use the directory (original behavior) - let directory = directory - .as_ref() - .ok_or_else(|| anyhow::anyhow!("Directory is required when not using --gateway-ip"))?; - - // Setup the entry gateways - let entry_gateway = directory.entry_gateway(&self.entrypoint)?; - - let node_info: TestedNodeDetails = match self.tested_node { - TestedNode::Custom { - identity: _, - shares_entry: true, - } => { - debug!( - "testing node {} as both entry and exit", - entry_gateway.identity() - ); - entry_gateway.to_testable_node()? - } - TestedNode::Custom { - identity, - shares_entry: false, - } => { - let node = directory.get_nym_node(identity)?; - info!( - "testing node {} (via entry {})", - node.identity(), - entry_gateway.identity() - ); - node.to_testable_node()? - } - TestedNode::SameAsEntry => entry_gateway.to_testable_node()?, + mixnet_client }; - info!("connecting to entry gateway: {}", entry_gateway.identity()); - debug!( - "authenticator version: {:?}", - node_info.authenticator_version - ); + // Wireguard with Authenticator test + if let Some(mixnet_client) = mixnet_client { + // We have a mixnet_client to disconnect at the end here + if self.config.test_mode.wireguard_tests() { + if let (Some(authenticator), Some(ip_address)) = + (exit_node.authenticator_address, exit_node.ip_address) + { + info!("Testing WireGuard via Mixnet registration"); + // Run wireguard with authenticator + let nym_address = *mixnet_client.nym_address(); + // Start the mixnet listener that the auth clients use to receive messages. + let mixnet_listener_task = + AuthClientMixnetListener::new(mixnet_client, CancellationToken::new()) + .start(); + let mut rng = rand::thread_rng(); + let auth_client = AuthenticatorClient::new( + mixnet_listener_task.subscribe(), + mixnet_listener_task.mixnet_sender(), + nym_address, + authenticator, + exit_node.authenticator_version, + Arc::new(x25519::KeyPair::new(&mut rng)), + ip_address, + ); - Ok((self.entrypoint, node_info)) - } - - #[allow(clippy::too_many_arguments)] - pub async fn do_probe_test( - &self, - mixnet_client: Option>, - storage: T, - mixnet_entry_gateway_id: NodeIdentity, - node_info: TestedNodeDetails, - directory: Option<&NymApiDirectory>, - nyxd_url: Url, - tested_entry: bool, - test_mode: TestMode, - only_wireguard: bool, - use_mock_ecash: bool, - network_details: NymNetworkDetails, - topology: Option, - ) -> anyhow::Result - where - T: MixnetClientStorage + Clone + 'static, - ::StorageError: Send + Sync, - { - let Some(directory) = directory else { - bail!("You need to provide NYM API through environment") - }; - // test_mode replaces the old only_lp_registration and test_lp_wg flags. - // only_wireguard is kept separate as it controls ping behavior within Mixnet mode. - let mut rng = rand::thread_rng(); - let mixnet_client = match mixnet_client { - Some(Ok(mixnet_client)) => Some(mixnet_client), - Some(Err(err)) => { - error!("Failed to connect to mixnet: {err}"); - return Ok(ProbeResult { - node: node_info.identity.to_string(), - used_entry: mixnet_entry_gateway_id.to_string(), - outcome: ProbeOutcome { - as_entry: if tested_entry { - Entry::fail_to_connect() - } else { - Entry::EntryFailure - }, - as_exit: None, - socks5: None, - wg: None, - lp: None, - }, - }); - } - None => None, - }; - - // Determine if we should run ping tests: - // - Only in Mixnet mode (LP modes don't use mixnet) - // - And only if not --only-wireguard (which skips pings) - let run_ping_tests = test_mode.needs_mixnet() && !only_wireguard; - - let (outcome, mixnet_client) = if let Some(mixnet_client) = mixnet_client { - let nym_address = *mixnet_client.nym_address(); - let entry_gateway = nym_address.gateway().to_base58_string(); - - info!("Successfully connected to entry gateway: {entry_gateway}"); - info!("Our nym address: {nym_address}"); - - // Run ping tests if applicable - let (outcome, mixnet_client) = if run_ping_tests { - do_ping( - mixnet_client, - nym_address, - node_info.exit_router_address, - tested_entry, - ) - .await - } else { - ( - Ok(ProbeOutcome { - as_entry: if tested_entry { - Entry::success() - } else { - Entry::NotTested - }, - as_exit: None, - socks5: None, - wg: None, - lp: None, - }), - mixnet_client, - ) - }; - (outcome, Some(mixnet_client)) - } else if test_mode.uses_lp() && test_mode.tests_wireguard() { - // LP modes (SingleHop/TwoHop) don't need mixnet client - // Create default outcome and continue to LP-WG test below - ( - Ok(ProbeOutcome { - as_entry: Entry::NotTested, - as_exit: None, - socks5: None, - wg: None, - lp: None, - }), - None, - ) - } else { - // For Mixnet mode, missing mixnet client is a failure - ( - Ok(ProbeOutcome { - as_entry: if tested_entry { - Entry::fail_to_connect() + let (wg_ticket_type, credential_provider) = if entry_under_test { + (TicketType::V1WireguardEntry, self.entry_node.identity) } else { - Entry::EntryFailure - }, - as_exit: None, - socks5: None, - wg: None, - lp: None, - }), - None, - ) - }; + (TicketType::V1WireguardExit, exit_node.identity) + }; - let wg_outcome = if !test_mode.tests_wireguard() { - // LpOnly mode: skip WireGuard test - WgProbeResults::default() - } else if test_mode.uses_lp() { + let credential = bandwith_provider + .get_ecash_ticket(wg_ticket_type, credential_provider, 1) + .await? + .data; + + let outcome = wg_probe( + auth_client, + ip_address, + exit_node.authenticator_version, + self.config.amnezia_args.clone(), + self.config.netstack_args.clone(), + credential, + ) + .await + .unwrap_or_default(); + + // Add wg results to probe result + probe_result.outcome.wg = Some(outcome); + mixnet_listener_task.stop().await; + } else { + warn!("Not enough information to run WireGuard via mixnet registration tests"); + mixnet_client.disconnect().await; + } + } else { + // We are not running WG tests, we don't need the mixnet client anmore + mixnet_client.disconnect().await; + } + } + + // At this point, any mixnet client MUST be disconnected + + // The current probe includes registration as part of the Wireguard result, which makes that a bit awkward + // If we're supposed to run WireGuard tests and LP tests, and Mixnet registration failed, let's try to do it with lp + // This behavior should change in the future + if probe_result.outcome.wg.is_none() + && self.config.test_mode.wireguard_tests() + && self.config.test_mode.lp_tests() + { // Test WireGuard via LP registration (nested session forwarding) info!("Testing WireGuard via LP registration (no mixnet)"); - // Create bandwidth controller for LP registration - let config = nym_validator_client::nyxd::Config::try_from_nym_network_details( - &NymNetworkDetails::new_from_env(), - )?; - let client = - nym_validator_client::nyxd::NyxdClient::connect(config, nyxd_url.as_str())?; - - let bw_controller = build_bandwidth_controller( - client, - storage.credential_store().clone(), - use_mock_ecash, - ); - - // Determine entry and exit gateways - // Three modes for gateway resolution: - // 1. direct_gateway_node/exit_gateway_node - from --gateway-ip (HTTP API query) - // 2. localnet_entry/localnet_exit - from --entry-gateway-identity (CLI-only) - // 3. directory lookup - original behavior for production - let (entry_gateway, exit_gateway) = if let Some(exit_node) = &self.exit_gateway_node { - // Both entry and exit gateways were pre-queried (direct IP mode) - info!("Using pre-queried entry and exit gateways for LP forwarding test"); - let entry_node = self - .direct_gateway_node - .as_ref() - .ok_or_else(|| anyhow::anyhow!("Entry gateway not available"))?; - - let entry_gateway = entry_node.to_testable_node()?; - let exit_gateway = exit_node.to_testable_node()?; - - (entry_gateway, exit_gateway) - } else if let Some(exit_localnet) = &self.localnet_exit { - // Localnet mode: use CLI-provided identities and LP addresses - info!("Using localnet entry and exit gateways for LP forwarding test"); - let entry_localnet = self.localnet_entry.as_ref().ok_or_else(|| { - anyhow::anyhow!("Entry gateway not available in localnet mode") - })?; - - (entry_localnet.clone(), exit_localnet.clone()) - } else { - // Original behavior: query from directory - // The tested node is the exit - let exit_gateway = node_info.clone(); - - let entry_gateway_node = directory.entry_gateway(&mixnet_entry_gateway_id)?; - let entry_gateway = entry_gateway_node.to_testable_node()?; - - (entry_gateway, exit_gateway) - }; - - wg_probe_lp( - &entry_gateway, - &exit_gateway, - &bw_controller, - self.amnezia_args.clone(), - self.netstack_args.clone(), - ) - .await - .unwrap_or_default() - } else if let (Some(authenticator), Some(ip_address)) = - (node_info.authenticator_address, node_info.ip_address) - { - let mixnet_client = if let Some(mixnet_client) = mixnet_client { - mixnet_client - } else { - bail!( - "Mixnet client is required for authenticator WireGuard probe, run in LP mode instead" - ); - }; - - let nym_address = *mixnet_client.nym_address(); - // Start the mixnet listener that the auth clients use to receive messages. - let mixnet_listener_task = - AuthClientMixnetListener::new(mixnet_client, CancellationToken::new()).start(); - - let auth_client = AuthenticatorClient::new( - mixnet_listener_task.subscribe(), - mixnet_listener_task.mixnet_sender(), - nym_address, - authenticator, - node_info.authenticator_version, - Arc::new(KeyPair::new(&mut rng)), - ip_address, - ); - let config = - nym_validator_client::nyxd::Config::try_from_nym_network_details(&network_details)?; - let client = - nym_validator_client::nyxd::NyxdClient::connect(config, nyxd_url.as_str())?; - let bw_controller = nym_bandwidth_controller::BandwidthController::new( - storage.credential_store().clone(), - client, - ); - let (wg_ticket_type, credential_provider) = if tested_entry { - ( - TicketType::V1WireguardEntry, - nym_address.gateway().to_bytes(), - ) - } else { - (TicketType::V1WireguardExit, node_info.identity.to_bytes()) - }; - - let credential = bw_controller - .prepare_ecash_ticket(wg_ticket_type, credential_provider, 1) - .await? - .data; - - let outcome = wg_probe( - auth_client, - ip_address, - node_info.authenticator_version, - self.amnezia_args.clone(), - self.netstack_args.clone(), - credential, + let outcome = wg_probe_lp( + &self.entry_node, + &exit_node, + &bandwith_provider, + self.config.amnezia_args.clone(), + self.config.netstack_args.clone(), ) .await .unwrap_or_default(); - - mixnet_listener_task.stop().await; - - outcome - } else { - WgProbeResults::default() - }; + probe_result.outcome.wg = Some(outcome); + } // Test LP registration if node has LP address - let lp_outcome = if let Some(lp_data) = node_info.lp_data { - info!("Node has LP data, testing LP registration..."); + if self.config.test_mode.lp_tests() { + if let Some(lp_data) = self.entry_node.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( - &NymNetworkDetails::new_from_env(), - )?; - let client = - nym_validator_client::nyxd::NyxdClient::connect(config, nyxd_url.as_str())?; - let bw_controller = build_bandwidth_controller( - client, - storage.credential_store().clone(), - use_mock_ecash, - ); + let outcome = + lp_registration_probe(self.entry_node.identity, lp_data, &bandwith_provider) + .await + .unwrap_or_default(); - let outcome = lp_registration_probe(node_info.identity, lp_data, &bw_controller) + probe_result.outcome.lp = Some(outcome); + } else { + warn!("LP test was requested, but node did not have LP data"); + + probe_result.outcome.lp = Some(LpProbeResults { + can_connect: false, + can_handshake: false, + can_register: false, + error: Some("no LP data".into()), + }) + }; + } + + // Test socks5 connectivity + if self.config.test_mode.socks5_tests() { + // test failure doesn't stop further tests + if let Some(network_requester) = exit_node.network_requester_address { + match do_socks5_connectivity_test( + &network_requester, + self.entry_node.identity, + self.network.clone(), + self.config.min_gateway_mixnet_performance, + self.config.socks5_args, + self.topology, + ) .await - .unwrap_or_default(); - - Some(outcome) - } else { - info!("Node does not have LP address, skipping LP registration test"); - None - }; - - // test failure doesn't stop further tests - let socks5_outcome = self - .test_socks5_if_possible( - network_details, - &node_info.network_requester_details, - directory, - topology, - ) - .await; - - // Disconnect the mixnet client gracefully - outcome.map(|mut outcome| { - outcome.wg = Some(wg_outcome); - outcome.lp = lp_outcome; - outcome.socks5 = socks5_outcome; - ProbeResult { - node: node_info.identity.to_string(), - used_entry: mixnet_entry_gateway_id.to_string(), - outcome, + { + Ok(results) => probe_result.outcome.socks5 = Some(results), + Err(e) => { + error!("SOCKS5 test failed: {}", e); + } + } + } else { + warn!("No NR available, skipping SOCKS5 tests"); } - }) + } + + Ok(probe_result) } } diff --git a/nym-gateway-probe/src/run.rs b/nym-gateway-probe/src/run.rs index b0cc2e9951..7bb668206f 100644 --- a/nym-gateway-probe/src/run.rs +++ b/nym-gateway-probe/src/run.rs @@ -1,20 +1,12 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -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::config::Socks5Args; -use nym_gateway_probe::{ - CredentialArgs, NetstackArgs, NymApiDirectory, ProbeResult, TestMode, TestedNode, - TestedNodeDetails, TestedNodeLpDetails, query_gateway_by_ip, -}; -use nym_kkt_ciphersuite::{HashFunction, KEM}; +use nym_gateway_probe::config::{CredentialArgs, CredentialMode, ProbeConfig}; +use nym_gateway_probe::{NymApiDirectory, ProbeResult, query_gateway_by_ip}; use nym_sdk::mixnet::NodeIdentity; -use std::collections::HashMap; -use std::net::SocketAddr; use std::path::Path; use std::{path::PathBuf, sync::OnceLock}; use tracing::*; @@ -23,178 +15,84 @@ fn pretty_build_info_static() -> &'static str { static PRETTY_BUILD_INFORMATION: OnceLock = OnceLock::new(); PRETTY_BUILD_INFORMATION.get_or_init(|| bin_info!().pretty_print()) } - -fn validate_node_identity(s: &str) -> Result { - match s.parse() { - Ok(cg) => Ok(cg), - Err(_) => Err(format!("failed to parse country group: {s}")), - } -} +const DEFAULT_CONFIG_DIR: &str = "/tmp/nym-gateway-probe/config/"; #[derive(Parser)] #[clap(author = "Nymtech", version, long_version = pretty_build_info_static(), about)] struct CliArgs { #[command(subcommand)] - command: Option, + command: Commands, /// Path pointing to an env file describing the network. - #[arg(short, long, global = true)] + #[arg(short, long)] config_env_file: Option, - /// The specific gateway specified by ID. - #[arg(long, short = 'g', alias = "gateway", global = true)] - entry_gateway: Option, - - /// The address of the gateway to probe directly (bypasses directory lookup) - /// Supports formats: IP (192.168.66.5), IP:PORT (192.168.66.5:8080), HOST:PORT (localhost:30004) - #[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 exit gateway (base58 encoded) - /// When provided, skips HTTP API query - use for localnet testing - #[arg(long, global = true)] - exit_gateway_identity: 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, - - /// Default LP control port when deriving LP address from gateway IP - #[arg(long, global = true, default_value = "41264")] - lp_port: u16, - - /// Identity of the node to test - #[arg(long, short, value_parser = validate_node_identity, global = true)] - node: Option, - - #[arg(long, global = true)] - min_gateway_mixnet_performance: Option, - - // this was a dead field - // #[arg(long, global = true)] - // min_gateway_vpn_performance: Option, - #[arg(long, global = true)] - only_wireguard: bool, - - #[arg(long, global = true)] - only_lp_registration: bool, - - /// Test WireGuard via LP registration (no mixnet) - uses nested session forwarding - #[arg(long, global = true)] - test_lp_wg: bool, - - /// Test mode - explicitly specify which tests to run - /// - /// Modes: - /// mixnet - Traditional mixnet testing (entry/exit pings + WireGuard via authenticator) - /// single-hop - LP registration + WireGuard on single gateway (no mixnet) - /// two-hop - Entry LP + Exit LP (nested forwarding) + WireGuard tunnel - /// lp-only - LP registration only (no WireGuard) - /// - /// If not specified, mode is inferred from other flags: - /// --only-lp-registration → lp-only - /// --test-lp-wg with exit gateway → two-hop - /// --test-lp-wg without exit → single-hop - /// otherwise → mixnet - #[arg(long, global = true, value_name = "MODE")] - mode: Option, - /// Disable logging during probe - #[arg(long, global = true)] - ignore_egress_epoch_role: bool, - - #[arg(long, global = true)] + #[arg(long)] no_log: bool, - - /// Arguments to be appended to the wireguard config enabling amnezia-wg configuration - #[arg(long, short, global = true)] - amnezia_args: Option, - - /// Arguments to manage netstack downloads - #[command(flatten)] - netstack_args: NetstackArgs, - - /// Arguments to manage credentials - #[command(flatten)] - credential_args: CredentialArgs, - - /// Arguments to configure socks5 probe - #[command(flatten)] - socks5_args: Socks5Args, } -const DEFAULT_CONFIG_DIR: &str = "/tmp/nym-gateway-probe/config/"; - +#[allow(clippy::large_enum_variant)] #[derive(Subcommand, Debug)] enum Commands { - /// Run the probe locally + /// Run the probe on an unannounced gateway. IP must be provided. Bypasses directory lookup RunLocal { - /// Provide a mnemonic to get credentials (optional when using --use-mock-ecash) - #[arg(long)] - mnemonic: Option, - + /// Directory for credential and mixnet storage #[arg(long)] config_dir: Option, - /// Use mock ecash credentials for testing (requires gateway with --lp-use-mock-ecash) + /// The address of the gateway + /// Supports formats: IP (192.168.66.5), IP:PORT (192.168.66.5:8080), HOST:PORT (localhost:30004) #[arg(long)] - use_mock_ecash: bool, + entry_gateway_ip: String, + + /// The address of the exit gateway. If not provided, entry acts as exit + /// Supports formats: IP (192.168.66.5), IP:PORT (192.168.66.5:8080), HOST:PORT (localhost:30004) + #[arg(long)] + exit_gateway_ip: Option, + + /// Arguments to manage credentials + #[command(flatten)] + credential_mode: CredentialMode, + + #[command(flatten)] + probe_config: ProbeConfig, + }, + + /// Run the probe on a bonded gateway. Uses directory lookup + Run { + /// Directory for credential and mixnet storage + #[arg(long)] + config_dir: Option, + + /// The specific gateway specified by ID. + #[arg(long, short = 'g', alias = "gateway")] + entry_gateway: NodeIdentity, + + /// Optional identity of the exit node to test, if not provided, entry_gateway is used + #[arg(long)] + exit_gateway: Option, + + /// Arguments to manage credentials + #[command(flatten)] + credential_mode: CredentialMode, + + #[command(flatten)] + probe_config: ProbeConfig, + }, + + /// Run the probe by NS agents + RunAgent { + /// The specific gateway specified by ID. + #[arg(long, short = 'g', alias = "gateway")] + entry_gateway: NodeIdentity, + + /// Arguments to manage credentials + #[command(flatten)] + credential_args: CredentialArgs, + + #[command(flatten)] + probe_config: ProbeConfig, }, } @@ -214,266 +112,66 @@ fn setup_logging() { .init(); } -/// Resolve the test mode from explicit --mode arg or infer from legacy flags -fn resolve_test_mode( - mode_arg: Option<&str>, - only_wireguard: bool, - only_lp_registration: bool, - test_lp_wg: bool, - has_exit_gateway: bool, -) -> anyhow::Result { - if let Some(mode_str) = mode_arg { - // Explicit --mode takes priority - mode_str - .parse::() - .map_err(|e| anyhow::anyhow!("{}", e)) - } else { - // Infer from legacy flags - Ok(TestMode::from_flags( - only_wireguard, - only_lp_registration, - test_lp_wg, - has_exit_gateway, - )) - } -} - -/// Convert TestMode back to legacy boolean flags for backward compatibility -fn mode_to_flags(mode: TestMode) -> (bool, bool, bool) { - match mode { - TestMode::Mixnet => (false, false, false), // only_wireguard handled separately - TestMode::SingleHop => (false, false, true), - TestMode::TwoHop => (false, false, true), - TestMode::LpOnly => (false, true, false), - } -} - -#[allow(clippy::todo)] -#[allow(unreachable_code, unused)] -// ^^^^ // NOTE: to be changed by @SW -#[allow(clippy::unwrap_used)] pub(crate) async fn run() -> anyhow::Result { let args = CliArgs::parse(); if !args.no_log { setup_logging(); } debug!("{:?}", nym_bin_common::bin_info_local_vergen!()); - setup_env(args.config_env_file.as_ref()); + setup_env(args.config_env_file.as_ref()); let network = nym_sdk::NymNetworkDetails::new_from_env(); - let nyxd_url = network - .endpoints - .first() - .map(|ep| ep.nyxd_url()) - .ok_or(anyhow::anyhow!("missing nyxd url"))?; + match args.command { + Commands::RunLocal { + config_dir, + entry_gateway_ip, + exit_gateway_ip, + credential_mode, + probe_config, + } => { + info!("Using direct IP query mode for gateway: {entry_gateway_ip}"); + let entry_details = query_gateway_by_ip(entry_gateway_ip) + .await? + .to_testable_node()?; - args.socks5_args.validate_socks5_endpoints().await?; - - // Three resolution modes in priority order: - // 1. Localnet mode: --entry-gateway-identity provided (no HTTP query) - // 2. Direct IP mode: --gateway-ip provided (queries HTTP API) - // 3. Directory mode: uses nym-api directory service - - // Localnet mode: identity provided via CLI, skip HTTP queries entirely - if let Some(kem_key_digest) = &args.entry_gateway_kem_hey_hash_bs58 { - info!("Using localnet mode with CLI-provided 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 - .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 { - lp_addr - } 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}"))?; - SocketAddr::new(ip, args.lp_port) - } else { - anyhow::bail!( - "--entry-lp-address or --gateway-ip required with --entry-gateway-identity" - ); - }; - - let entry_lp_node = TestedNodeLpDetails { - address: entry_lp_addr, - expected_kem_key_hashes, - expected_signing_key_hashes: todo!(), - x25519: x25519_key, - lp_version: todo!(), - }; - let entry_details = TestedNodeDetails::from_cli(identity, entry_lp_node); - - // Parse exit gateway if provided - 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() - .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, - expected_signing_key_hashes: todo!(), - x25519: x25519_key, - lp_version: todo!(), - }; - - Some(TestedNodeDetails::from_cli(identity, exit_lp_node)) - } else { - None - }; - - // Resolve test mode from --mode arg or infer from flags - let has_exit = exit_details.is_some(); - let test_mode = resolve_test_mode( - args.mode.as_deref(), - args.only_wireguard, - args.only_lp_registration, - args.test_lp_wg, - has_exit, - )?; - - // Validate that two-hop mode has required exit gateway - if test_mode.needs_exit_gateway() && !has_exit { - bail!( - "--mode two-hop requires exit gateway \ - (use --exit-gateway-identity and --exit-lp-address)" - ); - } - - info!("Test mode: {}", test_mode); - - // Convert back to flags for backward compatibility with existing probe methods - // only_wireguard is preserved from args since it's orthogonal to mode - // (it means "skip ping tests" in mixnet mode, irrelevant for LP modes) - let (_, only_lp_registration, test_lp_wg) = mode_to_flags(test_mode); - let only_wireguard = args.only_wireguard; - - let mut trial = nym_gateway_probe::Probe::new_localnet( - entry_details, - exit_details, - args.netstack_args, - args.credential_args, - args.socks5_args, - ); - - if let Some(awg_args) = args.amnezia_args { - trial.with_amnezia(&awg_args); - } - - // Localnet mode doesn't need directory, but nyxd_url is still used for credentials - return match &args.command { - Some(Commands::RunLocal { - mnemonic, - config_dir, - use_mock_ecash, - }) => { - let config_dir = config_dir - .clone() - .unwrap_or_else(|| Path::new(DEFAULT_CONFIG_DIR).join(&network.network_name)); - - info!( - "using the following directory for the probe config: {}", - config_dir.display() - ); - - Box::pin(trial.probe_run_locally( - &config_dir, - mnemonic.as_deref(), - None, // No directory in localnet mode - nyxd_url, - args.ignore_egress_epoch_role, - only_wireguard, - only_lp_registration, - test_lp_wg, - args.min_gateway_mixnet_performance, - *use_mock_ecash, - network, - )) - .await - } - None => { - Box::pin(trial.probe( - None, // No directory in localnet mode - nyxd_url, - args.ignore_egress_epoch_role, - only_wireguard, - only_lp_registration, - test_lp_wg, - args.min_gateway_mixnet_performance, - network, - )) - .await - } - }; - } - - // If gateway IP is provided, query it directly without using the directory - let (entry, directory, gateway_node, exit_gateway_node) = - if let Some(gateway_ip) = args.gateway_ip.clone() { - info!("Using direct IP query mode for gateway: {}", gateway_ip); - let gateway_node = query_gateway_by_ip(gateway_ip).await?; - let identity = gateway_node.identity(); - - // Query exit gateway if provided (for LP forwarding tests) - let exit_node = if let Some(exit_gateway_ip) = args.exit_gateway_ip { - info!( - "Using direct IP query mode for exit gateway: {}", - exit_gateway_ip - ); - Some(query_gateway_by_ip(exit_gateway_ip).await?) + // Parse exit gateway if provided + let exit_details = if let Some(ip_address) = exit_gateway_ip { + info!("Using direct IP query mode for exit gateway: {ip_address}"); + Some(query_gateway_by_ip(ip_address).await?.to_testable_node()?) } else { None }; - // Still create the directory for potential secondary lookups, - // but only if API URL is available - let directory = - if let Some(api_url) = network.endpoints.first().and_then(|ep| ep.api_url()) { - Some(NymApiDirectory::new(api_url).await?) - } else { - None - }; + let config_dir = config_dir + .clone() + .unwrap_or_else(|| Path::new(DEFAULT_CONFIG_DIR).join(&network.network_name)); - (identity, directory, Some(gateway_node), exit_node) - } else { - // Original behavior: use directory service + if config_dir.is_file() { + anyhow::bail!("provided configuration directory is a file"); + } + + if !config_dir.exists() { + std::fs::create_dir_all(config_dir.clone())?; + } + + info!( + "using the following directory for the probe config: {}", + config_dir.display() + ); + + let trial = + nym_gateway_probe::Probe::new(entry_details, exit_details, network, probe_config); + + Box::pin(trial.probe_run_locally(&config_dir, credential_mode)).await + } + Commands::Run { + entry_gateway, + exit_gateway, + config_dir, + credential_mode, + probe_config, + } => { let api_url = network .endpoints .first() @@ -481,121 +179,57 @@ pub(crate) async fn run() -> anyhow::Result { .ok_or(anyhow::anyhow!("missing api url"))?; let directory = NymApiDirectory::new(api_url).await?; + let entry_details = directory + .entry_gateway(&entry_gateway)? + .to_testable_node()?; + let exit_details = exit_gateway + .map(|id_key| directory.exit_gateway(&id_key)) + .transpose()? + .map(|node| node.to_testable_node()) + .transpose()?; - let entry = if let Some(gateway) = &args.entry_gateway { - NodeIdentity::from_base58_string(gateway)? - } else { - directory.random_exit_with_ipr()? - }; - - (entry, Some(directory), None, None) - }; - - let test_point = if let Some(node) = args.node { - TestedNode::Custom { - identity: node, - shares_entry: false, - } - } else { - TestedNode::SameAsEntry - }; - - // Resolve test mode from --mode arg or infer from flags - let has_exit = exit_gateway_node.is_some(); - let test_mode = resolve_test_mode( - args.mode.as_deref(), - args.only_wireguard, - args.only_lp_registration, - args.test_lp_wg, - has_exit, - )?; - info!("Test mode: {}", test_mode); - - // Convert back to flags for backward compatibility with existing probe methods - // only_wireguard is preserved from args since it's orthogonal to mode - let (_, only_lp_registration, test_lp_wg) = mode_to_flags(test_mode); - let only_wireguard = args.only_wireguard; - - let mut trial = if let (Some(entry_node), Some(exit_node)) = (&gateway_node, &exit_gateway_node) - { - // Both entry and exit gateways provided (for LP telescoping tests) - info!("Using both entry and exit gateways for LP forwarding test"); - nym_gateway_probe::Probe::new_with_gateways( - entry, - test_point, - args.netstack_args, - args.credential_args, - entry_node.clone(), - exit_node.clone(), - args.socks5_args, - ) - } else if let Some(gw_node) = gateway_node { - // Only entry gateway provided - nym_gateway_probe::Probe::new_with_gateway( - entry, - test_point, - args.netstack_args, - args.credential_args, - gw_node, - args.socks5_args, - ) - } else { - // No direct gateways, use directory lookup - nym_gateway_probe::Probe::new( - entry, - test_point, - args.netstack_args, - args.credential_args, - args.socks5_args, - ) - }; - - if let Some(awg_args) = args.amnezia_args { - trial.with_amnezia(&awg_args); - } - - match args.command { - Some(Commands::RunLocal { - mnemonic, - config_dir, - use_mock_ecash, - }) => { let config_dir = config_dir .clone() .unwrap_or_else(|| Path::new(DEFAULT_CONFIG_DIR).join(&network.network_name)); + if config_dir.is_file() { + anyhow::bail!("provided configuration directory is a file"); + } + + if !config_dir.exists() { + std::fs::create_dir_all(config_dir.clone())?; + } + info!( "using the following directory for the probe config: {}", config_dir.display() ); - Box::pin(trial.probe_run_locally( - &config_dir, - mnemonic.as_deref(), - directory, - nyxd_url, - args.ignore_egress_epoch_role, - only_wireguard, - only_lp_registration, - test_lp_wg, - args.min_gateway_mixnet_performance, - use_mock_ecash, - network, - )) - .await + let trial = + nym_gateway_probe::Probe::new(entry_details, exit_details, network, probe_config); + Box::pin(trial.probe_run(&config_dir, credential_mode)).await } - None => { - Box::pin(trial.probe( - directory, - nyxd_url, - args.ignore_egress_epoch_role, - only_wireguard, - only_lp_registration, - test_lp_wg, - args.min_gateway_mixnet_performance, - network, - )) - .await + Commands::RunAgent { + entry_gateway, + credential_args, + mut probe_config, + } => { + let api_url = network + .endpoints + .first() + .and_then(|ep| ep.api_url()) + .ok_or(anyhow::anyhow!("missing api url"))?; + + let directory = NymApiDirectory::new(api_url).await?; + let entry_details = directory + .entry_gateway(&entry_gateway)? + .to_testable_node()?; + + // Agents run everything + probe_config.test_mode = nym_gateway_probe::config::TestMode::All; + + let trial = nym_gateway_probe::Probe::new(entry_details, None, network, probe_config); + Box::pin(trial.probe_run_agent(credential_args)).await } } } diff --git a/nym-node-status-api/nym-node-status-agent/Cargo.toml b/nym-node-status-api/nym-node-status-agent/Cargo.toml index 9c8daf7007..cfb9cea2c3 100644 --- a/nym-node-status-api/nym-node-status-agent/Cargo.toml +++ b/nym-node-status-api/nym-node-status-agent/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "nym-node-status-agent" -version = "1.1.1" +version = "1.1.2" authors.workspace = true repository.workspace = true homepage.workspace = true diff --git a/nym-node-status-api/nym-node-status-agent/src/cli/run_probe.rs b/nym-node-status-api/nym-node-status-agent/src/cli/run_probe.rs index 8f82428e75..820136b3f0 100644 --- a/nym-node-status-api/nym-node-status-agent/src/cli/run_probe.rs +++ b/nym-node-status-api/nym-node-status-agent/src/cli/run_probe.rs @@ -52,7 +52,7 @@ pub(crate) async fn run_probe( // Run the probe let log = probe.run_and_get_log( - &Some(gateway_identity_key.clone()), + gateway_identity_key.clone(), probe_extra_args, testrun.ticket_materials, ); diff --git a/nym-node-status-api/nym-node-status-agent/src/probe.rs b/nym-node-status-api/nym-node-status-agent/src/probe.rs index 3ee47646b9..610c07c4bd 100644 --- a/nym-node-status-api/nym-node-status-agent/src/probe.rs +++ b/nym-node-status-api/nym-node-status-agent/src/probe.rs @@ -73,16 +73,15 @@ impl GwProbe { pub(crate) fn run_and_get_log( &self, - gateway_key: &Option, + gateway_key: String, probe_extra_args: &Vec, ticket_materials: AttachedTicketMaterials, ) -> String { let mut command = std::process::Command::new(&self.path); command.stdout(std::process::Stdio::piped()); - if let Some(gateway_id) = gateway_key { - command.arg("--gateway").arg(gateway_id); - } + command.arg("run-agent"); + command.arg("--gateway").arg(gateway_key); tracing::info!("Extra args for the probe:"); for arg in probe_extra_args { From e16a337354d4c082ee4588dbf83e3a9b35cb3d9b Mon Sep 17 00:00:00 2001 From: benedettadavico Date: Mon, 2 Feb 2026 10:14:15 +0100 Subject: [PATCH 41/51] bump versions --- Cargo.lock | 14 +++++++------- clients/native/Cargo.toml | 2 +- clients/socks5/Cargo.toml | 2 +- nym-api/Cargo.toml | 2 +- nym-node/Cargo.toml | 2 +- service-providers/network-requester/Cargo.toml | 2 +- tools/nym-cli/Cargo.toml | 2 +- tools/nymvisor/Cargo.toml | 2 +- 8 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2e041700b7..d30b584199 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5318,7 +5318,7 @@ dependencies = [ [[package]] name = "nym-api" -version = "1.1.72" +version = "1.1.73" dependencies = [ "anyhow", "async-trait", @@ -5563,7 +5563,7 @@ dependencies = [ [[package]] name = "nym-cli" -version = "1.1.69" +version = "1.1.70" dependencies = [ "anyhow", "base64 0.22.1", @@ -5646,7 +5646,7 @@ dependencies = [ [[package]] name = "nym-client" -version = "1.1.69" +version = "1.1.70" dependencies = [ "bs58", "clap", @@ -7057,7 +7057,7 @@ dependencies = [ [[package]] name = "nym-network-requester" -version = "1.1.70" +version = "1.1.71" dependencies = [ "addr", "anyhow", @@ -7107,7 +7107,7 @@ dependencies = [ [[package]] name = "nym-node" -version = "1.24.0" +version = "1.25.0" dependencies = [ "anyhow", "arc-swap", @@ -7654,7 +7654,7 @@ dependencies = [ [[package]] name = "nym-socks5-client" -version = "1.1.69" +version = "1.1.70" dependencies = [ "bs58", "clap", @@ -8474,7 +8474,7 @@ dependencies = [ [[package]] name = "nymvisor" -version = "0.1.34" +version = "0.1.35" dependencies = [ "anyhow", "bytes", diff --git a/clients/native/Cargo.toml b/clients/native/Cargo.toml index ec71c0df74..e5b4f09d84 100644 --- a/clients/native/Cargo.toml +++ b/clients/native/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-client" -version = "1.1.69" +version = "1.1.70" authors = ["Dave Hrycyszyn ", "Jędrzej Stuczyński "] description = "Implementation of the Nym Client" edition = "2021" diff --git a/clients/socks5/Cargo.toml b/clients/socks5/Cargo.toml index 5d892e50de..aa09c09ca3 100644 --- a/clients/socks5/Cargo.toml +++ b/clients/socks5/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-socks5-client" -version = "1.1.69" +version = "1.1.70" authors = ["Dave Hrycyszyn "] description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address" edition = "2021" diff --git a/nym-api/Cargo.toml b/nym-api/Cargo.toml index 39e7bd1cc2..635cebdb66 100644 --- a/nym-api/Cargo.toml +++ b/nym-api/Cargo.toml @@ -4,7 +4,7 @@ [package] name = "nym-api" license = "GPL-3.0" -version = "1.1.72" +version = "1.1.73" authors.workspace = true edition = "2021" rust-version.workspace = true diff --git a/nym-node/Cargo.toml b/nym-node/Cargo.toml index 55105c54bf..d2dd9f0951 100644 --- a/nym-node/Cargo.toml +++ b/nym-node/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "nym-node" -version = "1.24.0" +version = "1.25.0" authors.workspace = true repository.workspace = true homepage.workspace = true diff --git a/service-providers/network-requester/Cargo.toml b/service-providers/network-requester/Cargo.toml index 60c4d192c4..8d79940d1a 100644 --- a/service-providers/network-requester/Cargo.toml +++ b/service-providers/network-requester/Cargo.toml @@ -4,7 +4,7 @@ [package] name = "nym-network-requester" license = "GPL-3.0" -version = "1.1.70" +version = "1.1.71" authors.workspace = true edition.workspace = true rust-version = "1.85" diff --git a/tools/nym-cli/Cargo.toml b/tools/nym-cli/Cargo.toml index fcd9893d86..02ec79ce93 100644 --- a/tools/nym-cli/Cargo.toml +++ b/tools/nym-cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-cli" -version = "1.1.69" +version = "1.1.70" authors.workspace = true edition = "2021" license.workspace = true diff --git a/tools/nymvisor/Cargo.toml b/tools/nymvisor/Cargo.toml index 668e532b98..f3371670a9 100644 --- a/tools/nymvisor/Cargo.toml +++ b/tools/nymvisor/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nymvisor" -version = "0.1.34" +version = "0.1.35" authors.workspace = true repository.workspace = true homepage.workspace = true From 7e8faf0ec61de85a91a136ac889948bcbed992c5 Mon Sep 17 00:00:00 2001 From: Simon Wicky Date: Mon, 2 Feb 2026 11:14:04 +0100 Subject: [PATCH 42/51] use local kem key instead of local x25519 (#6402) --- common/nym-lp/src/state_machine.rs | 63 +++++++++++++++++------------- 1 file changed, 35 insertions(+), 28 deletions(-) diff --git a/common/nym-lp/src/state_machine.rs b/common/nym-lp/src/state_machine.rs index 0283f2f244..91fa48cbe7 100644 --- a/common/nym-lp/src/state_machine.rs +++ b/common/nym-lp/src/state_machine.rs @@ -23,6 +23,7 @@ use crate::{ }; use bytes::{Buf, Bytes}; use num_enum::{IntoPrimitive, TryFromPrimitive}; +use nym_kkt::ciphersuite::EncapsulationKey; use std::mem; use tracing::debug; @@ -364,33 +365,44 @@ impl LpStateMachine { result_action = Some(Err(LpError::UnknownSessionId(packet.header.receiver_idx()))); LpState::KKTExchange { session } } else { - use crate::message::LpMessage; // Packet message is already parsed, match on it directly match &packet.message { LpMessage::KKTRequest(kkt_request) if !session.is_initiator() => { // Responder processes KKT request // Convert X25519 public key to KEM format for KKT response - use nym_kkt::ciphersuite::EncapsulationKey; - // Get local X25519 public key by deriving from private key - let local_x25519_public = session.local_x25519_public(); + // Get local KEM key + match session.get_kem_key_handle() { + Err(err) => { + let reason = err.to_string(); + result_action = Some(Err(err)); + LpState::Closed { reason } + } + Ok(local_kem_psq_public) => { + // Convert to libcrux KEM public key + // V1 is X255519 + match libcrux_kem::PublicKey::decode( + libcrux_kem::Algorithm::X25519, + local_kem_psq_public.as_bytes(), + ) { + Ok(libcrux_public_key) => { + let responder_kem_pk = EncapsulationKey::X25519(libcrux_public_key); - // Convert to libcrux KEM public key - match libcrux_kem::PublicKey::decode( - libcrux_kem::Algorithm::X25519, - local_x25519_public.as_bytes(), - ) { - Ok(libcrux_public_key) => { - let responder_kem_pk = EncapsulationKey::X25519(libcrux_public_key); - - match session.process_kkt_request(&kkt_request.0, &responder_kem_pk) { - Ok(kkt_response_message) => { - match session.next_packet(kkt_response_message) { - Ok(response_packet) => { - result_action = Some(Ok(LpAction::SendPacket(response_packet))); - // After KKT exchange, move to Handshaking - LpState::Handshaking { session } + match session.process_kkt_request(&kkt_request.0, &responder_kem_pk) { + Ok(kkt_response_message) => { + match session.next_packet(kkt_response_message) { + Ok(response_packet) => { + result_action = Some(Ok(LpAction::SendPacket(response_packet))); + // After KKT exchange, move to Handshaking + LpState::Handshaking { session } + } + Err(e) => { + let reason = e.to_string(); + result_action = Some(Err(e)); + LpState::Closed { reason } + } + } } Err(e) => { let reason = e.to_string(); @@ -400,18 +412,13 @@ impl LpStateMachine { } } Err(e) => { - let reason = e.to_string(); - result_action = Some(Err(e)); + let reason = format!("Failed to convert X25519 to KEM: {:?}", e); + let err = LpError::Internal(reason.clone()); + result_action = Some(Err(err)); LpState::Closed { reason } } } - } - Err(e) => { - let reason = format!("Failed to convert X25519 to KEM: {:?}", e); - let err = LpError::Internal(reason.clone()); - result_action = Some(Err(err)); - LpState::Closed { reason } - } + }, } } LpMessage::KKTResponse(kkt_response) if session.is_initiator() => { From af04afbe5efd1d425a1e84a9b16b06d2f948db66 Mon Sep 17 00:00:00 2001 From: Simon Wicky Date: Mon, 2 Feb 2026 11:45:56 +0100 Subject: [PATCH 43/51] use rng that is Send (#6404) --- nym-registration-client/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nym-registration-client/src/lib.rs b/nym-registration-client/src/lib.rs index b175e2e94d..5565f9fc63 100644 --- a/nym-registration-client/src/lib.rs +++ b/nym-registration-client/src/lib.rs @@ -276,7 +276,7 @@ impl RegistrationClient { } async fn register_lp(self) -> Result { - let mut rng = rand::thread_rng(); + let mut rng = rand::rngs::OsRng; self.register_lp_with_rng(&mut rng).await } From bd755385ed525d7afca382bb53b7d74ecce16eb6 Mon Sep 17 00:00:00 2001 From: Simon Wicky Date: Tue, 3 Feb 2026 10:25:43 +0100 Subject: [PATCH 44/51] LP-fix : add LP x25519 key to the description (#6408) * add x25519 key in LP description * gateway probe adapt --- .../src/models/described/type_translation.rs | 7 +++++++ .../src/models/described/v2.rs | 1 + nym-gateway-probe/src/common/nodes.rs | 18 +++++++----------- .../src/api/v1/lewes_protocol/models.rs | 10 ++++++++++ nym-node/src/node/mod.rs | 1 + 5 files changed, 26 insertions(+), 11 deletions(-) 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 efdd558ac3..415184c980 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 @@ -210,6 +210,12 @@ pub struct LewesProtocolDetailsV1 { /// LP UDP data address (default: 51264) for Sphinx packets wrapped in LP pub data_port: u16, + #[serde(with = "bs58_x25519_pubkey")] + #[schemars(with = "String")] + #[schema(value_type = String)] + /// LP public key + pub x25519: x25519::PublicKey, + /// Digests of the KEM keys available to this node alongside hashing algorithms used /// for their computation. /// note: digests are hex encoded @@ -457,6 +463,7 @@ impl From enabled: value.enabled, control_port: value.control_port, data_port: value.data_port, + x25519: value.x25519, kem_keys: value .kem_keys .into_iter() diff --git a/nym-api/nym-api-requests/src/models/described/v2.rs b/nym-api/nym-api-requests/src/models/described/v2.rs index 8be3710eed..429fb2ed88 100644 --- a/nym-api/nym-api-requests/src/models/described/v2.rs +++ b/nym-api/nym-api-requests/src/models/described/v2.rs @@ -343,6 +343,7 @@ pub fn mock_nym_node_description(seed: u64) -> NymNodeDescriptionV2 { enabled: true, control_port: 1234, data_port: 2345, + x25519: *x25519.public_key(), kem_keys: kem_hashes_wrapper, signing_keys: signing_keys_hashes_wrapper, }), diff --git a/nym-gateway-probe/src/common/nodes.rs b/nym-gateway-probe/src/common/nodes.rs index bb88848e1a..d929d4285d 100644 --- a/nym-gateway-probe/src/common/nodes.rs +++ b/nym-gateway-probe/src/common/nodes.rs @@ -130,20 +130,16 @@ impl DirectoryNode { .copied() .ok_or_else(|| anyhow!("no ip address known"))?; - let lp_data = match ( - description.lewes_protocol.clone(), - description.host_information.keys.x25519_versioned_noise, - ) { - (Some(lp_data), Some(noise_key)) => Some(TestedNodeLpDetails { + let lp_data = description.lewes_protocol.as_ref().and_then(|lp_data| { + Some(TestedNodeLpDetails { address: SocketAddr::new(ip_address, lp_data.control_port), - expected_kem_key_hashes: lp_data.kem_keys()?, - expected_signing_key_hashes: lp_data.signing_keys()?, - x25519: noise_key.x25519_pubkey, + expected_kem_key_hashes: lp_data.kem_keys().ok()?, + expected_signing_key_hashes: lp_data.signing_keys().ok()?, + x25519: lp_data.x25519, // \/ TODO: proper derivation from build version lp_version: version::CURRENT, - }), - _ => None, - }; + }) + }); Ok(TestedNodeDetails { identity: self.identity(), 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 5dffa99541..17900472b6 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 @@ -1,6 +1,8 @@ // Copyright 2026 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use nym_crypto::asymmetric::x25519; +use nym_crypto::asymmetric::x25519::serde_helpers::bs58_x25519_pubkey; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use std::collections::HashMap; @@ -19,6 +21,12 @@ pub struct LewesProtocol { /// LP UDP data address (default: 51264) for Sphinx packets wrapped in LP pub data_port: u16, + #[serde(with = "bs58_x25519_pubkey")] + #[schemars(with = "String")] + #[schema(value_type = String)] + /// LP public key + pub x25519: x25519::PublicKey, + /// Digests of the KEM keys available to this node alongside hashing algorithms used /// for their computation. /// note: digests are hex encoded @@ -35,6 +43,7 @@ impl LewesProtocol { enabled: bool, control_port: u16, data_port: u16, + x25519: x25519::PublicKey, kem_keys: HashMap>, signing_keys: HashMap>, ) -> Self { @@ -42,6 +51,7 @@ impl LewesProtocol { enabled, control_port, data_port, + x25519, kem_keys, signing_keys, } diff --git a/nym-node/src/node/mod.rs b/nym-node/src/node/mod.rs index abf246c453..f3c6ab940f 100644 --- a/nym-node/src/node/mod.rs +++ b/nym-node/src/node/mod.rs @@ -890,6 +890,7 @@ impl NymNode { self.modes().entry, self.config.gateway_tasks.lp.announced_control_port(), self.config.gateway_tasks.lp.announced_data_port(), + *self.x25519_noise_keys.public_key(), self.compute_kem_key_hashes(), self.compute_signing_key_hashes(), ); From b6d22abc015aa8e0b9be07e9098bdf54ac3d719e Mon Sep 17 00:00:00 2001 From: Simon Wicky Date: Tue, 3 Feb 2026 11:50:18 +0100 Subject: [PATCH 45/51] configurable LP timeouts (#6409) --- nym-registration-client/src/builder/config.rs | 4 +++- nym-registration-client/src/builder/mod.rs | 1 + nym-registration-client/src/config.rs | 3 +++ nym-registration-client/src/lib.rs | 7 +++++-- nym-registration-client/src/lp_client/client.rs | 10 +++++----- nym-registration-client/src/lp_client/config.rs | 16 ++++------------ nym-registration-client/src/lp_client/mod.rs | 2 +- 7 files changed, 22 insertions(+), 21 deletions(-) diff --git a/nym-registration-client/src/builder/config.rs b/nym-registration-client/src/builder/config.rs index 8efedd0847..d54bad6be1 100644 --- a/nym-registration-client/src/builder/config.rs +++ b/nym-registration-client/src/builder/config.rs @@ -17,8 +17,8 @@ use std::{path::PathBuf, sync::Arc, time::Duration}; use tokio_util::sync::CancellationToken; use typed_builder::TypedBuilder; -use crate::config::RegistrationMode; use crate::error::RegistrationClientError; +use crate::{LpRegistrationConfig, config::RegistrationMode}; const VPN_AVERAGE_PACKET_DELAY: Duration = Duration::from_millis(15); const MIXNET_CLIENT_STARTUP_TIMEOUT: Duration = Duration::from_secs(30); @@ -44,6 +44,8 @@ pub struct BuilderConfig { pub cancel_token: CancellationToken, #[cfg(unix)] pub connection_fd_callback: Arc, + #[builder(default)] + pub lp_registration_config: LpRegistrationConfig, } #[derive(Clone, Default, Debug, Eq, PartialEq)] diff --git a/nym-registration-client/src/builder/mod.rs b/nym-registration-client/src/builder/mod.rs index 9e568baa18..01e061319b 100644 --- a/nym-registration-client/src/builder/mod.rs +++ b/nym-registration-client/src/builder/mod.rs @@ -33,6 +33,7 @@ impl RegistrationClientBuilder { entry: self.config.entry_node.clone(), exit: self.config.exit_node.clone(), mode: self.config.mode, + lp_registration_config: self.config.lp_registration_config, }; let cancel_token = self.config.cancel_token.clone(); let (event_tx, event_rx) = mpsc::unbounded(); diff --git a/nym-registration-client/src/config.rs b/nym-registration-client/src/config.rs index b5b59435fb..dd4ca98bb9 100644 --- a/nym-registration-client/src/config.rs +++ b/nym-registration-client/src/config.rs @@ -1,6 +1,8 @@ // Copyright 2025 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only +use crate::LpRegistrationConfig; + use crate::builder::config::NymNodeWithKeys; /// Registration mode for the client @@ -30,4 +32,5 @@ pub struct RegistrationClientConfig { pub(crate) entry: NymNodeWithKeys, pub(crate) exit: NymNodeWithKeys, pub(crate) mode: RegistrationMode, + pub(crate) lp_registration_config: LpRegistrationConfig, } diff --git a/nym-registration-client/src/lib.rs b/nym-registration-client/src/lib.rs index 5565f9fc63..3ff496d7ff 100644 --- a/nym-registration-client/src/lib.rs +++ b/nym-registration-client/src/lib.rs @@ -22,7 +22,9 @@ pub use builder::config::{ }; pub use config::RegistrationMode; pub use error::RegistrationClientError; -pub use lp_client::{LpConfig, LpRegistrationClient, NestedLpSession, error::LpClientError}; +pub use lp_client::{ + LpRegistrationClient, LpRegistrationConfig, NestedLpSession, error::LpClientError, +}; use nym_crypto::aes::cipher::crypto_common::rand_core::{CryptoRng, RngCore}; pub use types::{ LpRegistrationResult, MixnetRegistrationResult, RegistrationResult, WireguardRegistrationResult, @@ -196,11 +198,12 @@ impl RegistrationClient { // 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( + let mut entry_client = LpRegistrationClient::new( entry_lp_keypair.clone(), entry_peer, entry_address, entry_lp_protocol, + self.config.lp_registration_config, ); // Perform handshake with entry gateway (outer session now established) diff --git a/nym-registration-client/src/lp_client/client.rs b/nym-registration-client/src/lp_client/client.rs index c09f2138b9..490a7437ed 100644 --- a/nym-registration-client/src/lp_client/client.rs +++ b/nym-registration-client/src/lp_client/client.rs @@ -3,7 +3,7 @@ //! LP (Lewes Protocol) registration client for direct gateway connections. -use super::config::LpConfig; +use super::config::LpRegistrationConfig; use super::error::{LpClientError, Result}; use crate::lp_client::helpers::{ LpDataDeliverExt, LpDataSendExt, convert_forward_data, try_convert_forward_response, @@ -66,7 +66,7 @@ pub struct LpRegistrationClient { state_machine: Option, /// Configuration for timeouts and TCP parameters. - config: LpConfig, + config: LpRegistrationConfig, /// Persistent TCP stream for the connection. /// Opened on first use, closed after registration. @@ -93,7 +93,7 @@ where gateway_lp_peer: LpRemotePeer, gateway_lp_address: SocketAddr, gateway_supported_lp_protocol_version: u8, - config: LpConfig, + config: LpRegistrationConfig, ) -> Self { let lp_protocol = if gateway_supported_lp_protocol_version > version::CURRENT { warn!( @@ -140,7 +140,7 @@ where gateway_lp_peer, gateway_lp_address, gateway_supported_lp_protocol_version, - LpConfig::default(), + LpRegistrationConfig::default(), ) } @@ -634,7 +634,7 @@ where packet: &LpPacket, send_key: Option<&OuterAeadKey>, recv_key: Option<&OuterAeadKey>, - config: &LpConfig, + config: &LpRegistrationConfig, ) -> Result { // 1. Connect with timeout let mut stream = tokio::time::timeout(config.connect_timeout, S::connect(address)) diff --git a/nym-registration-client/src/lp_client/config.rs b/nym-registration-client/src/lp_client/config.rs index def4ca8f97..506fc559a2 100644 --- a/nym-registration-client/src/lp_client/config.rs +++ b/nym-registration-client/src/lp_client/config.rs @@ -26,8 +26,8 @@ use std::time::Duration; /// - Fail fast enough for good UX (no indefinite hangs) /// - Allow sufficient time for real network conditions /// - Optimize for latency over throughput (small messages) -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct LpConfig { +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct LpRegistrationConfig { /// TCP connection timeout (nym-102). /// /// Maximum time to wait for TCP connection establishment. @@ -69,7 +69,7 @@ pub struct LpConfig { pub tcp_keepalive: Option, } -impl Default for LpConfig { +impl Default for LpRegistrationConfig { fn default() -> Self { Self { // nym-102: Sane timeout defaults for real network conditions @@ -91,7 +91,7 @@ mod tests { #[test] fn test_default_config() { - let config = LpConfig::default(); + let config = LpRegistrationConfig::default(); assert_eq!(config.connect_timeout, Duration::from_secs(10)); assert_eq!(config.handshake_timeout, Duration::from_secs(15)); @@ -100,12 +100,4 @@ mod tests { assert!(config.tcp_nodelay); assert_eq!(config.tcp_keepalive, None); } - - #[test] - fn test_config_clone() { - let config = LpConfig::default(); - let cloned = config.clone(); - - assert_eq!(config, cloned); - } } diff --git a/nym-registration-client/src/lp_client/mod.rs b/nym-registration-client/src/lp_client/mod.rs index d533303d29..492b01ed2e 100644 --- a/nym-registration-client/src/lp_client/mod.rs +++ b/nym-registration-client/src/lp_client/mod.rs @@ -39,6 +39,6 @@ mod nested_session; mod state_machine_helpers; pub use client::LpRegistrationClient; -pub use config::LpConfig; +pub use config::LpRegistrationConfig; pub use error::LpClientError; pub use nested_session::NestedLpSession; From d4882ca276733b04dabd07ab877d067902556d05 Mon Sep 17 00:00:00 2001 From: Simon Wicky Date: Wed, 4 Feb 2026 09:35:44 +0100 Subject: [PATCH 46/51] [LP-fix] expose wg psk for the vpn-client (#6411) * expose wg psk for the vpn-client : store socket addr of nested session as socketaddr * probe fix * nits --- common/nym-lp/src/codec.rs | 49 ++++++++++- common/nym-lp/src/message.rs | 83 ++++++++++++------- common/registration/src/lib.rs | 1 + common/registration/src/lp_messages.rs | 14 +--- gateway/src/node/lp_listener/handler.rs | 5 +- gateway/src/node/lp_listener/registration.rs | 2 + integration-tests/src/lp_registration.rs | 2 +- nym-authenticator-client/src/lib.rs | 1 + nym-gateway-probe/src/common/probe_tests.rs | 14 ++-- nym-registration-client/src/lib.rs | 8 +- .../src/lp_client/client.rs | 24 ++++-- .../src/lp_client/nested_session.rs | 44 ++++++---- 12 files changed, 164 insertions(+), 83 deletions(-) diff --git a/common/nym-lp/src/codec.rs b/common/nym-lp/src/codec.rs index 9c41cacc69..d8eee41844 100644 --- a/common/nym-lp/src/codec.rs +++ b/common/nym-lp/src/codec.rs @@ -757,12 +757,12 @@ mod tests { } #[test] - fn test_forward_packet_encode_decode_roundtrip() { + fn test_forward_packet_encode_decode_roundtrip_v4() { let mut dst = BytesMut::new(); let forward_data = crate::message::ForwardPacketData { target_gateway_identity: [77u8; 32], - target_lp_address: "1.2.3.4:41264".to_string(), + target_lp_address: "1.2.3.4:41264".parse().unwrap(), inner_packet_bytes: vec![0xa, 0xb, 0xc, 0xd], }; @@ -789,7 +789,50 @@ mod tests { if let LpMessage::ForwardPacket(data) = decoded.message { assert_eq!(data.target_gateway_identity, [77u8; 32]); - assert_eq!(data.target_lp_address, "1.2.3.4:41264"); + assert_eq!(data.target_lp_address, "1.2.3.4:41264".parse().unwrap()); + assert_eq!(data.inner_packet_bytes, vec![0xa, 0xb, 0xc, 0xd]); + } else { + panic!("Expected ForwardPacket message"); + } + } + + #[test] + fn test_forward_packet_encode_decode_roundtrip_v6() { + let mut dst = BytesMut::new(); + + let forward_data = crate::message::ForwardPacketData { + target_gateway_identity: [77u8; 32], + target_lp_address: "[dead:beef:4242:c0ff:ee00::1111]:41264".parse().unwrap(), + inner_packet_bytes: vec![0xa, 0xb, 0xc, 0xd], + }; + + let packet = LpPacket { + header: LpHeader { + protocol_version: 1, + reserved: [0u8; 3], + receiver_idx: 999, + counter: 555, + }, + message: LpMessage::ForwardPacket(forward_data), + trailer: [0xff; TRAILER_LEN], + }; + + // Serialize + serialize_lp_packet(&packet, &mut dst, None).unwrap(); + + // Parse back + let decoded = parse_lp_packet(&dst, None).unwrap(); + + // Verify LP protocol handling works correctly + assert_eq!(decoded.header.receiver_idx, 999); + assert!(matches!(decoded.message.typ(), MessageType::ForwardPacket)); + + if let LpMessage::ForwardPacket(data) = decoded.message { + assert_eq!(data.target_gateway_identity, [77u8; 32]); + assert_eq!( + data.target_lp_address, + "[dead:beef:4242:c0ff:ee00::1111]:41264".parse().unwrap() + ); assert_eq!(data.inner_packet_bytes, vec![0xa, 0xb, 0xc, 0xd]); } else { panic!("Expected ForwardPacket message"); diff --git a/common/nym-lp/src/message.rs b/common/nym-lp/src/message.rs index fb836e63f5..0796d0ffcc 100644 --- a/common/nym-lp/src/message.rs +++ b/common/nym-lp/src/message.rs @@ -9,6 +9,7 @@ use nym_crypto::asymmetric::{ed25519, x25519}; use rand::RngCore; use serde::{Deserialize, Serialize}; use std::fmt::{self, Display}; +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; /// Data structure for the ClientHello message #[derive(Debug, Clone, Serialize, Deserialize)] @@ -229,9 +230,8 @@ pub struct ForwardPacketData { /// Target gateway's Ed25519 identity (32 bytes) pub target_gateway_identity: [u8; 32], - // TODO: replace it with `SocketAddr` /// Target gateway's LP address (IP:port string) - pub target_lp_address: String, + pub target_lp_address: SocketAddr, /// Complete inner LP packet bytes (serialized LpPacket) /// This is the CLIENT→EXIT gateway packet, encrypted for exit @@ -241,7 +241,7 @@ pub struct ForwardPacketData { impl ForwardPacketData { pub fn new( target_gateway_identity: ed25519::PublicKey, - target_lp_address: String, + target_lp_address: SocketAddr, inner_packet_bytes: Vec, ) -> Self { ForwardPacketData { @@ -254,20 +254,31 @@ impl ForwardPacketData { fn len(&self) -> usize { // 32 bytes target gateway identity // + - // 4 bytes length of target lp address + // 1 byte length of target lp address type // + - // target_lp_address.len() + // {4,16} target_lp_address IPv{4,6} + // + + // 2 bytes target_lp_address port // + // 4 bytes of length of inner packet bytes // + // inner_packet_bytes.len() - 32 + 4 + self.target_lp_address.len() + 4 + self.inner_packet_bytes.len() + match self.target_lp_address { + SocketAddr::V4(_) => 32 + 1 + 4 + 2 + 4 + self.inner_packet_bytes.len(), + SocketAddr::V6(_) => 32 + 1 + 16 + 2 + 4 + self.inner_packet_bytes.len(), + } } fn encode(&self, dst: &mut BytesMut) { + let (is_ipv6, ip_bytes) = match &self.target_lp_address { + SocketAddr::V4(address) => (false, address.ip().octets().to_vec()), + SocketAddr::V6(address) => (true, address.ip().octets().to_vec()), + }; + dst.put_slice(&self.target_gateway_identity); - dst.put_u16_le(self.target_lp_address.len() as u16); - dst.put_slice(self.target_lp_address.as_bytes()); + dst.put_u8(is_ipv6 as u8); // IP type , 0 for ipv4 + dst.put_slice(&ip_bytes); // IP bytes + dst.put_u16_le(self.target_lp_address.port()); // Port dst.put_u32_le(self.inner_packet_bytes.len() as u32); dst.put_slice(&self.inner_packet_bytes); } @@ -279,42 +290,56 @@ impl ForwardPacketData { } pub fn decode(bytes: &[u8]) -> Result { - // smallest possible packet with empty address and empty data - if bytes.len() < 38 { + // smallest possible packet with ipv4 and empty data + if bytes.len() < 43 { + // 32 + 1 + 4 + 2 + 4 + 0 return Err(LpError::DeserializationError(format!( - "Too few bytes to deserialise ForwardPacketData[1]. got {}", + "Too few bytes to deserialise ForwardPacketData. got {}", bytes.len() ))); } // SAFETY: we ensured we have sufficient data #[allow(clippy::unwrap_used)] let target_gateway_identity = bytes[0..32].try_into().unwrap(); - let target_lp_address_len = u16::from_le_bytes([bytes[32], bytes[33]]); + let target_lp_address_is_ipv6 = bytes[32] != 0; - // smallest possible packet with empty data - if bytes[34..].len() < 4 + target_lp_address_len as usize { - return Err(LpError::DeserializationError(format!( - "Too few bytes to deserialise ForwardPacketData[2]. got {}", - bytes.len() - ))); - } + let (target_lp_address, next_index) = if target_lp_address_is_ipv6 { + // IPv6, first check we have actually enough bytes + // smallest possible packet with ipv6 and empty data + if bytes.len() < 55 { + // 32 + 1 + 16 + 2 + 4 + 0 + return Err(LpError::DeserializationError(format!( + "Too few bytes to deserialise ipv6 ForwardPacketData. got {}", + bytes.len() + ))); + } + // SAFETY: we ensured we have sufficient data, and the length is correct for casting + #[allow(clippy::unwrap_used)] + let ipv6 = IpAddr::V6(Ipv6Addr::from_octets(bytes[33..49].try_into().unwrap())); + let port = u16::from_le_bytes([bytes[49], bytes[50]]); + (SocketAddr::new(ipv6, port), 51) + } else { + // IPv4. Length check done at the start + // SAFETY: we ensured we have sufficient data, and the length is correct for casting + #[allow(clippy::unwrap_used)] + let ipv4 = IpAddr::V4(Ipv4Addr::from_octets(bytes[33..37].try_into().unwrap())); + let port = u16::from_le_bytes([bytes[37], bytes[38]]); + (SocketAddr::new(ipv4, port), 39) + }; - let target_lp_address = - String::from_utf8_lossy(&bytes[34..34 + target_lp_address_len as usize]).to_string(); let inner_packet_bytes_len = u32::from_le_bytes([ - bytes[34 + target_lp_address_len as usize], - bytes[34 + target_lp_address_len as usize + 1], - bytes[34 + target_lp_address_len as usize + 2], - bytes[34 + target_lp_address_len as usize + 3], + bytes[next_index], + bytes[next_index + 1], + bytes[next_index + 2], + bytes[next_index + 3], ]); - if bytes[34 + target_lp_address_len as usize + 4..].len() != inner_packet_bytes_len as usize - { + if bytes[next_index + 4..].len() != inner_packet_bytes_len as usize { return Err(LpError::DeserializationError(format!( "Expected {inner_packet_bytes_len} bytes to deserialise inner packet bytes of ForwardPacketData. got {}", - bytes[34 + target_lp_address_len as usize + 4..].len() + bytes[next_index + 4..].len() ))); } - let inner_packet_bytes = bytes[34 + target_lp_address_len as usize + 4..].to_vec(); + let inner_packet_bytes = bytes[next_index + 4..].to_vec(); Ok(ForwardPacketData { target_gateway_identity, diff --git a/common/registration/src/lib.rs b/common/registration/src/lib.rs index 777e97fed2..2beb0632ed 100644 --- a/common/registration/src/lib.rs +++ b/common/registration/src/lib.rs @@ -31,6 +31,7 @@ pub struct NymNodeInformation { pub struct WireguardConfiguration { #[serde(with = "bs58_x25519_pubkey")] pub public_key: x25519::PublicKey, + pub psk: Option<[u8; 32]>, pub endpoint: SocketAddr, pub private_ipv4: Ipv4Addr, pub private_ipv6: Ipv6Addr, diff --git a/common/registration/src/lp_messages.rs b/common/registration/src/lp_messages.rs index 9a7bcc59fb..ff64fc3746 100644 --- a/common/registration/src/lp_messages.rs +++ b/common/registration/src/lp_messages.rs @@ -17,7 +17,6 @@ use crate::mixnet::{ use crate::serialisation::{BincodeError, BincodeOptions, lp_bincode_serializer}; use nym_authenticator_requests::models::BandwidthClaim; use nym_credentials_interface::TicketType; -use nym_crypto::aes::cipher::crypto_common::rand_core::{CryptoRng, RngCore}; use serde::{Deserialize, Serialize}; use tracing::error; @@ -133,17 +132,11 @@ impl LpRegistrationRequest { } /// Create new dVPN registration initialisation request - pub fn new_initial_dvpn( - rng: &mut R, + pub fn new_initial_dvpn( wg_public_key: nym_wireguard_types::PeerPublicKey, + psk: [u8; 32], ticket_type: TicketType, - ) -> Self - where - R: RngCore + CryptoRng, - { - let mut psk = [0u8; 32]; - rng.fill_bytes(&mut psk); - + ) -> Self { Self::new(LpRegistrationRequestData::Dvpn { data: Box::new(LpDvpnRegistrationRequestMessage { content: LpDvpnRegistrationRequestMessageContent::InitialRequest( @@ -458,6 +451,7 @@ mod tests { public_key: nym_crypto::asymmetric::x25519::PublicKey::from( nym_sphinx::PublicKey::from([1u8; 32]), ), + psk: Some([42u8; 32]), private_ipv4: Ipv4Addr::new(10, 0, 0, 1), private_ipv6: Ipv6Addr::new(0xfc00, 0, 0, 0, 0, 0, 0, 1), endpoint: "192.168.1.1:8080".parse().expect("Valid test endpoint"), diff --git a/gateway/src/node/lp_listener/handler.rs b/gateway/src/node/lp_listener/handler.rs index db9e77bdf3..599e3796ed 100644 --- a/gateway/src/node/lp_listener/handler.rs +++ b/gateway/src/node/lp_listener/handler.rs @@ -1002,10 +1002,7 @@ where let start = std::time::Instant::now(); // Parse target gateway address - let target_addr: SocketAddr = forward_data.target_lp_address.parse().map_err(|e| { - inc!("lp_forward_failed"); - GatewayError::LpProtocolError(format!("Invalid target address: {}", e)) - })?; + let target_addr = forward_data.target_lp_address; // Check if we need to open a new connection let need_new_connection = match &self.exit_stream { diff --git a/gateway/src/node/lp_listener/registration.rs b/gateway/src/node/lp_listener/registration.rs index 577ab9a460..421dc14260 100644 --- a/gateway/src/node/lp_listener/registration.rs +++ b/gateway/src/node/lp_listener/registration.rs @@ -117,6 +117,7 @@ impl LpHandlerState { LpRegistrationResponse::success_dvpn( WireguardConfiguration { public_key: *self.keypair().public_key(), + psk: None, // TODO: according to @SW this is most likely very wrong endpoint: self.wireguard_config().bind_address, private_ipv4: peer_private_ipv4, @@ -486,6 +487,7 @@ impl LpHandlerState { ticket_type, wireguard_config: WireguardConfiguration { public_key: gateway_pubkey, + psk: None, endpoint: gateway_endpoint, private_ipv4: client_ipv4, private_ipv6: client_ipv6, diff --git a/integration-tests/src/lp_registration.rs b/integration-tests/src/lp_registration.rs index 80afb54004..81112582ed 100644 --- a/integration-tests/src/lp_registration.rs +++ b/integration-tests/src/lp_registration.rs @@ -705,7 +705,7 @@ mod tests { // technically we should use different ephemeral keys than we had for the entry // but crypto is going to work the same let mut nested_session = NestedLpSession::new( - exit.base.socket_addr.to_string(), + exit.base.socket_addr, client_data.base.peer.ed25519().clone(), exit.base.peer.as_remote(), exit.base.lp_version, diff --git a/nym-authenticator-client/src/lib.rs b/nym-authenticator-client/src/lib.rs index b214264cd4..3b36c647e6 100644 --- a/nym-authenticator-client/src/lib.rs +++ b/nym-authenticator-client/src/lib.rs @@ -350,6 +350,7 @@ impl AuthenticatorClient { let gateway_data = WireguardConfiguration { public_key: registered_data.pub_key().inner().into(), + psk: None, // Mixnet-based regsitration does not have psk endpoint: SocketAddr::new(self.ip_addr, registered_data.wg_port()), private_ipv4: registered_data.private_ips().ipv4, private_ipv6: registered_data.private_ips().ipv6, diff --git a/nym-gateway-probe/src/common/probe_tests.rs b/nym-gateway-probe/src/common/probe_tests.rs index ddeab981fd..fa0a9aa2c3 100644 --- a/nym-gateway-probe/src/common/probe_tests.rs +++ b/nym-gateway-probe/src/common/probe_tests.rs @@ -244,6 +244,12 @@ pub async fn lp_registration_probe( info!("LP registration successful! Received gateway data:"); info!(" - Gateway public key: {:?}", gateway_data.public_key); + info!( + " - PSK: {:?}", + gateway_data + .psk + .map(|k| general_purpose::STANDARD.encode(k)) + ); info!(" - Private IPv4: {}", gateway_data.private_ipv4); info!(" - Private IPv6: {}", gateway_data.private_ipv6); info!(" - Endpoint: {}", gateway_data.endpoint); @@ -332,12 +338,8 @@ pub async fn wg_probe_lp( // STEP 2: Use nested session to register with exit gateway via forwarding info!("Registering with exit gateway via entry forwarding..."); - let mut nested_session = NestedLpSession::new( - exit_address.to_string(), - exit_lp_keypair, - exit_peer, - exit_lp_version, - ); + let mut nested_session = + NestedLpSession::new(exit_address, exit_lp_keypair, exit_peer, exit_lp_version); let exit_gateway_pubkey = exit_gateway.identity; diff --git a/nym-registration-client/src/lib.rs b/nym-registration-client/src/lib.rs index 3ff496d7ff..dc7b81fdaf 100644 --- a/nym-registration-client/src/lib.rs +++ b/nym-registration-client/src/lib.rs @@ -220,12 +220,8 @@ impl RegistrationClient { // 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_address.to_string(), - exit_lp_keypair, - exit_peer, - exit_lp_protocol, - ); + let mut nested_session = + NestedLpSession::new(exit_address, exit_lp_keypair, exit_peer, exit_lp_protocol); // Perform handshake and registration with exit gateway (all via entry forwarding) let exit_gateway_data = nested_session diff --git a/nym-registration-client/src/lp_client/client.rs b/nym-registration-client/src/lp_client/client.rs index 490a7437ed..99a6d08a1f 100644 --- a/nym-registration-client/src/lp_client/client.rs +++ b/nym-registration-client/src/lp_client/client.rs @@ -858,7 +858,9 @@ where { // 1. Build registration request let wg_public_key = PeerPublicKey::from(*wg_keypair.public_key()); - let request = LpRegistrationRequest::new_initial_dvpn(rng, wg_public_key, ticket_type); + let mut psk = [0u8; 32]; + rng.fill_bytes(&mut psk); + let request = LpRegistrationRequest::new_initial_dvpn(wg_public_key, psk, ticket_type); tracing::trace!("Built dVPN registration request: {request:?}"); @@ -887,12 +889,12 @@ where }; // 7. check response to the initial request - match dvpn_response.content { + let final_response = match dvpn_response.content { LpDvpnRegistrationResponseMessageContent::RegistrationFailure(res) => { let reason = res.error; // the registration has failed tracing::warn!("Gateway rejected registration: {reason}"); - Err(LpClientError::RegistrationRejected { reason }) + return Err(LpClientError::RegistrationRejected { reason }); } LpDvpnRegistrationResponseMessageContent::CompletedRegistration(res) => { // we have already registered with this gateway before, the gateway has updated the psk and sent us the config @@ -900,7 +902,7 @@ where "LP registration successful! Allocated bandwidth: {} bytes", res.available_bandwidth ); - Ok(res.config) + res.config } LpDvpnRegistrationResponseMessageContent::RequiresCredential(_) => { // we're registering for the first time with this gateway - we need to attach a credential @@ -911,9 +913,17 @@ where bandwidth_controller, ticket_type, ) - .await + .await? } - } + }; + + Ok(WireguardConfiguration { + public_key: final_response.public_key, + psk: Some(psk), + endpoint: SocketAddr::new(self.gateway_lp_address.ip(), final_response.endpoint.port()), + private_ipv4: final_response.private_ipv4, + private_ipv6: final_response.private_ipv6, + }) } /// Register with automatic retry on network failure. @@ -1054,7 +1064,7 @@ where &mut self, forward_data: ForwardPacketData, ) -> Result> { - let target_address = forward_data.target_lp_address.clone(); + let target_address = forward_data.target_lp_address; tracing::debug!( "Sending ForwardPacket to {target_address} ({} inner bytes, persistent connection)", diff --git a/nym-registration-client/src/lp_client/nested_session.rs b/nym-registration-client/src/lp_client/nested_session.rs index a8e7a1be9b..887338f2e0 100644 --- a/nym-registration-client/src/lp_client/nested_session.rs +++ b/nym-registration-client/src/lp_client/nested_session.rs @@ -41,6 +41,7 @@ use nym_registration_common::{ }; use nym_wireguard_types::PeerPublicKey; use rand::{CryptoRng, RngCore}; +use std::net::SocketAddr; use std::sync::Arc; use std::time::{SystemTime, UNIX_EPOCH}; use tracing::warn; @@ -67,7 +68,7 @@ use tracing::warn; /// ``` pub struct NestedLpSession { /// Exit gateway's LP address (e.g., "2.2.2.2:41264") - exit_address: String, + exit_address: SocketAddr, /// Encapsulates all the client keys needed for the Lewes Protocol. lp_local_peer: LpLocalPeer, @@ -92,7 +93,7 @@ impl NestedLpSession { /// * `gateway_lp_peer` - Encapsulates all the gateway keys needed for the Lewes Protocol /// * `gateway_supported_lp_protocol_version` - Gateway's LP protocol version pub fn new( - exit_address: String, + exit_address: SocketAddr, client_keypair: Arc, gateway_lp_peer: LpRemotePeer, gateway_supported_lp_protocol_version: u8, @@ -145,16 +146,13 @@ impl NestedLpSession { /// Attempt to wrap the provided `LpData` into a `ForwardPacketData` /// using the inner state machine. fn prepare_forward_packet(&mut self, data: LpData) -> Result { - let target_gateway_identity = self.gateway_lp_peer.ed25519(); - let target_lp_address = self.exit_address.clone(); - let state_machine = self.state_machine_mut()?; let inner_packet_bytes = prepare_serialised_send_packet(data, state_machine)?; - Ok(ForwardPacketData { - target_gateway_identity: target_gateway_identity.to_bytes(), - target_lp_address, + Ok(ForwardPacketData::new( + self.gateway_lp_peer.ed25519(), + self.exit_address, inner_packet_bytes, - }) + )) } /// Attempt to recover received `LpData` from the received `LpPacket` @@ -224,7 +222,7 @@ impl NestedLpSession { let client_hello_bytes = serialize_packet(&client_hello_packet, None)?; let forward_packet_data = ForwardPacketData::new( self.gateway_lp_peer.ed25519(), - self.exit_address.clone(), + self.exit_address, client_hello_bytes, ); @@ -498,7 +496,10 @@ impl NestedLpSession { // Step 2: Build registration request let wg_public_key = PeerPublicKey::from(*wg_keypair.public_key()); - let request = LpRegistrationRequest::new_initial_dvpn(rng, wg_public_key, ticket_type); + let mut psk = [0u8; 32]; + rng.fill_bytes(&mut psk); + + let request = LpRegistrationRequest::new_initial_dvpn(wg_public_key, psk, ticket_type); // Step 3: Serialize the request let send_data = request.to_lp_data()?; @@ -528,12 +529,12 @@ impl NestedLpSession { }; // Step 9: check response to the initial request - match dvpn_response.content { + let final_response = match dvpn_response.content { LpDvpnRegistrationResponseMessageContent::RegistrationFailure(res) => { let reason = res.error; // the registration has failed tracing::warn!("Gateway rejected registration: {reason}"); - Err(LpClientError::RegistrationRejected { reason }) + return Err(LpClientError::RegistrationRejected { reason }); } LpDvpnRegistrationResponseMessageContent::CompletedRegistration(res) => { // we have already registered with this gateway before, the gateway has updated the psk and sent us the config @@ -541,7 +542,7 @@ impl NestedLpSession { "LP registration successful! Allocated bandwidth: {} bytes", res.available_bandwidth ); - Ok(res.config) + res.config } LpDvpnRegistrationResponseMessageContent::RequiresCredential(_) => { // we're registering for the first time with this gateway - we need to attach a credential @@ -553,9 +554,18 @@ impl NestedLpSession { bandwidth_controller, ticket_type, ) - .await + .await? } - } + }; + + // JS/SW TODO Adapt this to new gateway response + Ok(WireguardConfiguration { + public_key: final_response.public_key, + psk: Some(psk), + endpoint: SocketAddr::new(self.exit_address.ip(), final_response.endpoint.port()), + private_ipv4: final_response.private_ipv4, + private_ipv6: final_response.private_ipv6, + }) } /// Performs handshake and registration with the exit gateway via forwarding, @@ -683,7 +693,7 @@ impl NestedLpSession { let packet_bytes = serialize_packet(packet, send_key.as_ref())?; let forward_data = ForwardPacketData::new( self.gateway_lp_peer.ed25519(), - self.exit_address.clone(), + self.exit_address, packet_bytes, ); let response_bytes = outer_client From 88a4633bc43d1bd22895168bd5af3a1092fbf8fd Mon Sep 17 00:00:00 2001 From: Simon Wicky Date: Thu, 5 Feb 2026 10:51:37 +0100 Subject: [PATCH 47/51] [LP fix] Registration client with fallback (#6419) * don't start mixnet client for lp reg, with fallback * tweaks * add logging --- nym-registration-client/src/builder/config.rs | 59 +++- nym-registration-client/src/builder/mod.rs | 71 ++++- nym-registration-client/src/clients/lp.rs | 200 +++++++++++++ nym-registration-client/src/clients/mixnet.rs | 148 ++++++++++ nym-registration-client/src/clients/mod.rs | 8 + nym-registration-client/src/config.rs | 16 +- nym-registration-client/src/error.rs | 6 + nym-registration-client/src/lib.rs | 275 +----------------- 8 files changed, 481 insertions(+), 302 deletions(-) create mode 100644 nym-registration-client/src/clients/lp.rs create mode 100644 nym-registration-client/src/clients/mixnet.rs create mode 100644 nym-registration-client/src/clients/mod.rs diff --git a/nym-registration-client/src/builder/config.rs b/nym-registration-client/src/builder/config.rs index d54bad6be1..302227fd34 100644 --- a/nym-registration-client/src/builder/config.rs +++ b/nym-registration-client/src/builder/config.rs @@ -17,8 +17,8 @@ use std::{path::PathBuf, sync::Arc, time::Duration}; use tokio_util::sync::CancellationToken; use typed_builder::TypedBuilder; -use crate::error::RegistrationClientError; use crate::{LpRegistrationConfig, config::RegistrationMode}; +use crate::{config::RegistrationClientConfig, error::RegistrationClientError}; const VPN_AVERAGE_PACKET_DELAY: Duration = Duration::from_millis(15); const MIXNET_CLIENT_STARTUP_TIMEOUT: Duration = Duration::from_secs(30); @@ -31,19 +31,29 @@ pub struct NymNodeWithKeys { #[derive(TypedBuilder)] pub struct BuilderConfig { + // For future reference + // Common options pub entry_node: NymNodeWithKeys, pub exit_node: NymNodeWithKeys, pub data_path: Option, + pub mode: RegistrationMode, + pub cancel_token: CancellationToken, + + // Toggle + #[builder(default)] + pub enable_lp_regitration: bool, + + // Mixnet based only option pub mixnet_client_config: MixnetClientConfig, #[builder(default = MIXNET_CLIENT_STARTUP_TIMEOUT)] pub mixnet_client_startup_timeout: Duration, - pub mode: RegistrationMode, pub user_agent: UserAgent, pub custom_topology_provider: Box, pub network_env: NymNetworkDetails, - pub cancel_token: CancellationToken, #[cfg(unix)] pub connection_fd_callback: Arc, + + // LP based only option #[builder(default)] pub lp_registration_config: LpRegistrationConfig, } @@ -77,29 +87,36 @@ impl BuilderConfig { // Mixnet mode uses 5-hop configuration RegistrationMode::Mixnet => mixnet_debug_config(&self.mixnet_client_config), // Wireguard and LP both use 2-hop configuration - RegistrationMode::Wireguard | RegistrationMode::Lp => { - two_hop_debug_config(&self.mixnet_client_config) - } + RegistrationMode::Wireguard => two_hop_debug_config(&self.mixnet_client_config), } } - pub async fn setup_storage( + pub fn registration_client_config(&self) -> RegistrationClientConfig { + RegistrationClientConfig { + entry: self.entry_node.clone(), + exit: self.exit_node.clone(), + mode: self.mode, + lp_registration_config: self.lp_registration_config, + } + } + + pub async fn setup_mixnet_client_storage( &self, ) -> Result, RegistrationClientError> { if let Some(path) = &self.data_path { tracing::debug!("Using custom key storage path: {}", path.display()); let storage_paths = StoragePaths::new_from_dir(path) - .map_err(|err| RegistrationClientError::BuildMixnetClient(Box::new(err)))?; + .map_err(|err| RegistrationClientError::StorageInitialization(Box::new(err)))?; let mixnet_client_storage = storage_paths .initialise_persistent_storage(&self.mixnet_client_debug_config()) .await - .map_err(|err| RegistrationClientError::BuildMixnetClient(Box::new(err)))?; + .map_err(|err| RegistrationClientError::StorageInitialization(Box::new(err)))?; let credential_storage = storage_paths .persistent_credential_storage() .await - .map_err(|err| RegistrationClientError::BuildMixnetClient(Box::new(err)))?; + .map_err(|err| RegistrationClientError::StorageInitialization(Box::new(err)))?; Ok(Some((mixnet_client_storage, credential_storage))) } else { @@ -107,6 +124,26 @@ impl BuilderConfig { } } + pub async fn setup_credential_storage( + &self, + ) -> Result, RegistrationClientError> { + if let Some(path) = &self.data_path { + tracing::debug!("Using custom credential storage path: {}", path.display()); + + let storage_paths = StoragePaths::new_from_dir(path) + .map_err(|err| RegistrationClientError::StorageInitialization(Box::new(err)))?; + + let credential_storage = storage_paths + .persistent_credential_storage() + .await + .map_err(|err| RegistrationClientError::StorageInitialization(Box::new(err)))?; + + Ok(Some(credential_storage)) + } else { + Ok(None) + } + } + pub async fn build_and_connect_mixnet_client( self, builder: MixnetClientBuilder, @@ -123,7 +160,7 @@ impl BuilderConfig { let debug_config = self.mixnet_client_debug_config(); let remember_me = match self.mode { RegistrationMode::Mixnet => RememberMe::new_mixnet(), - RegistrationMode::Wireguard | RegistrationMode::Lp => RememberMe::new_vpn(), + RegistrationMode::Wireguard => RememberMe::new_vpn(), }; let identity = self.entry_node.node.identity.to_string(); diff --git a/nym-registration-client/src/builder/mod.rs b/nym-registration-client/src/builder/mod.rs index 01e061319b..397a943d86 100644 --- a/nym-registration-client/src/builder/mod.rs +++ b/nym-registration-client/src/builder/mod.rs @@ -13,7 +13,12 @@ use nym_validator_client::{ nyxd::{Config as NyxdClientConfig, NyxdClient}, }; -use crate::{RegistrationClient, config::RegistrationClientConfig, error::RegistrationClientError}; +use crate::{ + RegistrationClient, + clients::{LpBasedRegistrationClient, MixnetBasedRegistrationClient}, + config::RegistrationMode, + error::RegistrationClientError, +}; use config::BuilderConfig; pub(crate) mod config; @@ -27,14 +32,38 @@ impl RegistrationClientBuilder { Self { config } } + pub fn use_lp(&self) -> bool { + let lp_enabled = self.config.enable_lp_regitration; + let lp_info_available = self.config.entry_node.node.lp_data.is_some() + && self.config.exit_node.node.lp_data.is_some(); + // To remove when LP supports Mixnet registration + let wireguard_mode = self.config.mode == RegistrationMode::Wireguard; + let use_lp = lp_enabled && lp_info_available && wireguard_mode; + if !use_lp && lp_enabled { + tracing::warn!( + "LP is enabled but can't be used: Missing LP information: {lp_info_available}, wireguard mode: {wireguard_mode}" + ); + } + use_lp + } + pub async fn build(self) -> Result { - let storage = self.config.setup_storage().await?; - let config = RegistrationClientConfig { - entry: self.config.entry_node.clone(), - exit: self.config.exit_node.clone(), - mode: self.config.mode, - lp_registration_config: self.config.lp_registration_config, - }; + if self.use_lp() { + tracing::debug!("Using LP for registration"); + Ok(RegistrationClient::Lp(Box::new(self.build_lp().await?))) + } else { + tracing::debug!("Using Mixnet for registration"); + Ok(RegistrationClient::Mixnet(Box::new( + self.build_mixnet().await?, + ))) + } + } + + pub(crate) async fn build_mixnet( + self, + ) -> Result { + let storage = self.config.setup_mixnet_client_storage().await?; + let config = self.config.registration_client_config(); let cancel_token = self.config.cancel_token.clone(); let (event_tx, event_rx) = mpsc::unbounded(); @@ -84,7 +113,7 @@ impl RegistrationClientBuilder { }; let mixnet_client_address = *mixnet_client.nym_address(); - Ok(RegistrationClient { + Ok(MixnetBasedRegistrationClient { mixnet_client, config, cancel_token, @@ -93,6 +122,30 @@ impl RegistrationClientBuilder { event_rx, }) } + + async fn build_lp(self) -> Result { + let storage = self.config.setup_credential_storage().await?; + let config = self.config.registration_client_config(); + + let nyxd_client = get_nyxd_client(&self.config.network_env)?; + + let bandwidth_controller: Box = + if let Some(credential_storage) = storage { + Box::new(BandwidthController::new(credential_storage, nyxd_client)) + } else { + Box::new(BandwidthController::new( + EphemeralCredentialStorage::default(), + nyxd_client, + )) + }; + + Ok(LpBasedRegistrationClient { + config, + bandwidth_controller, + cancel_token: self.config.cancel_token.clone(), + fallback_client_builder: Some(self), + }) + } } // temporary while we use the legacy bandwidth-controller diff --git a/nym-registration-client/src/clients/lp.rs b/nym-registration-client/src/clients/lp.rs new file mode 100644 index 0000000000..a65a44cd39 --- /dev/null +++ b/nym-registration-client/src/clients/lp.rs @@ -0,0 +1,200 @@ +// Copyright 2026 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::builder::RegistrationClientBuilder; +use crate::config::RegistrationClientConfig; +use crate::config::RegistrationMode; +use crate::error::RegistrationClientError; +use crate::lp_client::helpers::to_lp_remote_peer; +use crate::lp_client::{LpRegistrationClient, NestedLpSession}; +use crate::types::{LpRegistrationResult, RegistrationResult}; + +use nym_bandwidth_controller::BandwidthTicketProvider; +use nym_credentials_interface::TicketType; +use nym_crypto::aes::cipher::crypto_common::rand_core::{CryptoRng, RngCore}; +use nym_crypto::asymmetric::ed25519; + +use rand::rngs::OsRng; +use std::sync::Arc; +use tokio::net::TcpStream; +use tokio_util::sync::CancellationToken; + +pub struct LpBasedRegistrationClient { + pub(crate) config: RegistrationClientConfig, + pub(crate) bandwidth_controller: Box, + pub(crate) cancel_token: CancellationToken, + // While we allow a fallback, we need to be able to build it + pub(crate) fallback_client_builder: Option, +} + +impl LpBasedRegistrationClient { + // create dedicated method taking RNG instance for tests + async fn register_wg_with_rng( + self, + rng: &mut R, + ) -> Result + where + R: RngCore + CryptoRng, + { + // Extract and validate LP data + let entry_lp_data = self.config.entry.node.lp_data.ok_or( + RegistrationClientError::LpRegistrationNotPossible { + node_id: self.config.entry.node.identity.to_base58_string(), + }, + )?; + + let exit_lp_data = self.config.exit.node.lp_data.ok_or( + RegistrationClientError::LpRegistrationNotPossible { + node_id: self.config.exit.node.identity.to_base58_string(), + }, + )?; + + let entry_lp_protocol = entry_lp_data.lp_protocol_version; + let exit_lp_protocol = exit_lp_data.lp_protocol_version; + + 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)); + + 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); + + // 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( + entry_lp_keypair.clone(), + entry_peer, + entry_address, + entry_lp_protocol, + self.config.lp_registration_config, + ); + + // 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_address, + source: Box::new(source), + } + })?; + + tracing::info!("Outer session with entry gateway established"); + + // 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_address, exit_lp_keypair, exit_peer, exit_lp_protocol); + + // Perform handshake and registration with exit gateway (all via entry forwarding) + let exit_gateway_data = nested_session + .handshake_and_register_dvpn::( + &mut entry_client, + rng, + &self.config.exit.keys, + &self.config.exit.node.identity, + &*self.bandwidth_controller, + TicketType::V1WireguardExit, + ) + .await + .map_err(|source| RegistrationClientError::ExitGatewayRegisterLp { + gateway_id: self.config.exit.node.identity.to_base58_string(), + lp_address: exit_address, + source: Box::new(source), + })?; + + tracing::info!("Exit gateway registration completed via forwarding"); + + // STEP 3: Register with entry gateway (packet-per-connection) + tracing::info!("Registering with entry gateway"); + let entry_gateway_data = entry_client + .register_dvpn( + rng, + &self.config.entry.keys, + &self.config.entry.node.identity, + &*self.bandwidth_controller, + TicketType::V1WireguardEntry, + ) + .await + .map_err(|source| RegistrationClientError::EntryGatewayRegisterLp { + gateway_id: self.config.entry.node.identity.to_base58_string(), + lp_address: entry_address, + source: Box::new(source), + })?; + + tracing::info!("Entry gateway registration successful"); + + tracing::info!("LP registration successful for both gateways"); + + // LP is registration-only (packet-per-connection model). + // All data flows through WireGuard after this point. + // Each LP packet used its own TCP connection which was closed after the exchange. + // Exit registration was completed via forwarding through entry gateway. + Ok(RegistrationResult::Lp(Box::new(LpRegistrationResult { + entry_gateway_data, + exit_gateway_data, + bw_controller: self.bandwidth_controller, + }))) + } + + async fn register_wg(self) -> Result { + let mut rng = rand::rngs::OsRng; + + self.register_wg_with_rng(&mut rng).await + } + + pub(crate) async fn register(mut self) -> Result { + let fallback = self.fallback_client_builder.take(); + match &self.config.mode { + RegistrationMode::Mixnet => { + if let Some(fallback) = fallback { + register_with_fallback(fallback).await + } else { + Err(RegistrationClientError::UnsupportedMode) + } + } + RegistrationMode::Wireguard => { + let lp_registration_result = self + .cancel_token + .clone() + .run_until_cancelled(self.register_wg()) + .await; + match lp_registration_result { + // Everything went fine + Some(Ok(res)) => Ok(res), + + // LP reg failed, try fallback if we have one + Some(Err(e)) => { + tracing::error!("LP registration failed : {e}"); + if let Some(fallback) = fallback { + tracing::info!("Registering with fallback"); + register_with_fallback(fallback).await + } else { + Err(e) + } + } + + // Cancelled registration + None => Err(RegistrationClientError::Cancelled), + } + } + } + } +} + +async fn register_with_fallback( + client_builder: RegistrationClientBuilder, +) -> Result { + // This is forcefully building a mixnet based client + let fallback_client = client_builder.build_mixnet().await?; + fallback_client.register().await +} diff --git a/nym-registration-client/src/clients/mixnet.rs b/nym-registration-client/src/clients/mixnet.rs new file mode 100644 index 0000000000..8067afc698 --- /dev/null +++ b/nym-registration-client/src/clients/mixnet.rs @@ -0,0 +1,148 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::config::RegistrationClientConfig; +use crate::config::RegistrationMode; +use crate::error::RegistrationClientError; +use crate::types::{MixnetRegistrationResult, RegistrationResult, WireguardRegistrationResult}; +use nym_authenticator_client::{AuthClientMixnetListener, AuthenticatorClient}; +use nym_bandwidth_controller::BandwidthTicketProvider; +use nym_credentials_interface::TicketType; +use nym_ip_packet_client::IprClientConnect; +use nym_registration_common::AssignedAddresses; +use nym_sdk::mixnet::{EventReceiver, MixnetClient, Recipient}; +use tokio_util::sync::CancellationToken; + +pub struct MixnetBasedRegistrationClient { + pub(crate) mixnet_client: MixnetClient, + pub(crate) config: RegistrationClientConfig, + pub(crate) mixnet_client_address: Recipient, + pub(crate) bandwidth_controller: Box, + pub(crate) cancel_token: CancellationToken, + pub(crate) event_rx: EventReceiver, +} + +impl MixnetBasedRegistrationClient { + async fn register_mix_exit(self) -> Result { + let entry_mixnet_gateway_ip = self.config.entry.node.ip_address; + + let exit_mixnet_gateway_ip = self.config.exit.node.ip_address; + + let ipr_address = self.config.exit.node.ipr_address.ok_or( + RegistrationClientError::NoIpPacketRouterAddress { + node_id: self.config.exit.node.identity.to_base58_string(), + }, + )?; + let mut ipr_client = IprClientConnect::new(self.mixnet_client, self.cancel_token.clone()); + let interface_addresses = ipr_client + .connect(ipr_address) + .await + .map_err(RegistrationClientError::ConnectToIpPacketRouter)?; + + Ok(RegistrationResult::Mixnet(Box::new( + MixnetRegistrationResult { + mixnet_client: ipr_client.into_mixnet_client(), + assigned_addresses: AssignedAddresses { + interface_addresses, + exit_mix_address: ipr_address, + mixnet_client_address: self.mixnet_client_address, + entry_mixnet_gateway_ip, + exit_mixnet_gateway_ip, + }, + event_rx: self.event_rx, + }, + ))) + } + + async fn register_wg(self) -> Result { + let entry_auth_address = self.config.entry.node.authenticator_address.ok_or( + RegistrationClientError::AuthenticationNotPossible { + node_id: self.config.entry.node.identity.to_base58_string(), + }, + )?; + + let exit_auth_address = self.config.exit.node.authenticator_address.ok_or( + RegistrationClientError::AuthenticationNotPossible { + node_id: self.config.exit.node.identity.to_base58_string(), + }, + )?; + + let entry_version = self.config.entry.node.version; + tracing::debug!("Entry gateway version: {entry_version}"); + let exit_version = self.config.exit.node.version; + tracing::debug!("Exit gateway version: {exit_version}"); + + // Start the auth client mixnet listener, which will listen for incoming messages from the + // mixnet and rebroadcast them to the auth clients. + let mixnet_listener = + AuthClientMixnetListener::new(self.mixnet_client, self.cancel_token.clone()).start(); + + let mut entry_auth_client = AuthenticatorClient::new( + mixnet_listener.subscribe(), + mixnet_listener.mixnet_sender(), + self.mixnet_client_address, + entry_auth_address, + entry_version, + self.config.entry.keys, + self.config.entry.node.ip_address, + ); + + let mut exit_auth_client = AuthenticatorClient::new( + mixnet_listener.subscribe(), + mixnet_listener.mixnet_sender(), + self.mixnet_client_address, + exit_auth_address, + exit_version, + self.config.exit.keys, + self.config.exit.node.ip_address, + ); + + let entry_fut = entry_auth_client + .register_wireguard(&*self.bandwidth_controller, TicketType::V1WireguardEntry); + let exit_fut = exit_auth_client + .register_wireguard(&*self.bandwidth_controller, TicketType::V1WireguardExit); + + let (entry, exit) = Box::pin(async { tokio::join!(entry_fut, exit_fut) }).await; + + let entry = entry.map_err(|source| { + RegistrationClientError::from_authenticator_error( + source, + self.config.entry.node.identity.to_base58_string(), + entry_auth_address, + true, // is entry + ) + })?; + let exit = exit.map_err(|source| { + RegistrationClientError::from_authenticator_error( + source, + self.config.exit.node.identity.to_base58_string(), + exit_auth_address, + false, // is exit (not entry) + ) + })?; + + Ok(RegistrationResult::Wireguard(Box::new( + WireguardRegistrationResult { + entry_gateway_client: entry_auth_client, + exit_gateway_client: exit_auth_client, + entry_gateway_data: entry, + exit_gateway_data: exit, + authenticator_listener_handle: mixnet_listener, + bw_controller: self.bandwidth_controller, + }, + ))) + } + + pub(crate) async fn register(self) -> Result { + self.cancel_token + .clone() + .run_until_cancelled(async { + match self.config.mode { + RegistrationMode::Mixnet => self.register_mix_exit().await, + RegistrationMode::Wireguard => self.register_wg().await, + } + }) + .await + .ok_or(RegistrationClientError::Cancelled)? + } +} diff --git a/nym-registration-client/src/clients/mod.rs b/nym-registration-client/src/clients/mod.rs new file mode 100644 index 0000000000..fd4a16f3d6 --- /dev/null +++ b/nym-registration-client/src/clients/mod.rs @@ -0,0 +1,8 @@ +// Copyright 2026 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +pub(crate) mod lp; +pub(crate) mod mixnet; + +pub use lp::LpBasedRegistrationClient; +pub use mixnet::MixnetBasedRegistrationClient; diff --git a/nym-registration-client/src/config.rs b/nym-registration-client/src/config.rs index dd4ca98bb9..b045049021 100644 --- a/nym-registration-client/src/config.rs +++ b/nym-registration-client/src/config.rs @@ -10,22 +10,8 @@ use crate::builder::config::NymNodeWithKeys; pub enum RegistrationMode { /// 5-hop mixnet with IPR (IP Packet Router) Mixnet, - /// 2-hop WireGuard with authenticator + /// 2-hop WireGuard Wireguard, - /// 2-hop WireGuard with LP (Lewes Protocol) - Lp, -} - -impl RegistrationMode { - /// Legacy method for backward compatibility - #[deprecated(note = "use explicit enum variant instead")] - pub fn legacy_two_hop(use_two_hop: bool) -> RegistrationMode { - if use_two_hop { - Self::Wireguard - } else { - Self::Mixnet - } - } } pub struct RegistrationClientConfig { diff --git a/nym-registration-client/src/error.rs b/nym-registration-client/src/error.rs index 449d6d4608..a6a85cc5cd 100644 --- a/nym-registration-client/src/error.rs +++ b/nym-registration-client/src/error.rs @@ -3,9 +3,15 @@ #[derive(thiserror::Error, Debug)] pub enum RegistrationClientError { + #[error("trying to register in mixnet mode with LP and no fallback. This shouldn't happen")] + UnsupportedMode, + #[error("failed to build mixnet client")] BuildMixnetClient(#[source] Box), + #[error("failed to initialize storage")] + StorageInitialization(#[source] Box), + #[error("failed to connect to mixnet")] ConnectToMixnet(#[source] Box), diff --git a/nym-registration-client/src/lib.rs b/nym-registration-client/src/lib.rs index dc7b81fdaf..ca00e270a0 100644 --- a/nym-registration-client/src/lib.rs +++ b/nym-registration-client/src/lib.rs @@ -1,20 +1,6 @@ // Copyright 2025 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -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; -use nym_crypto::asymmetric::ed25519; -use nym_ip_packet_client::IprClientConnect; -use nym_registration_common::AssignedAddresses; -use nym_sdk::mixnet::{EventReceiver, MixnetClient, Recipient}; -use rand::rngs::OsRng; -use std::sync::Arc; -use tokio::net::TcpStream; -use tokio_util::sync::CancellationToken; - pub use builder::RegistrationClientBuilder; pub use builder::config::{ BuilderConfig as RegistrationClientBuilderConfig, MixnetClientConfig, @@ -25,272 +11,27 @@ pub use error::RegistrationClientError; pub use lp_client::{ LpRegistrationClient, LpRegistrationConfig, NestedLpSession, error::LpClientError, }; -use nym_crypto::aes::cipher::crypto_common::rand_core::{CryptoRng, RngCore}; pub use types::{ LpRegistrationResult, MixnetRegistrationResult, RegistrationResult, WireguardRegistrationResult, }; mod builder; +mod clients; mod config; mod error; mod lp_client; mod types; -pub struct RegistrationClient { - mixnet_client: MixnetClient, - config: RegistrationClientConfig, - mixnet_client_address: Recipient, - bandwidth_controller: Box, - cancel_token: CancellationToken, - event_rx: EventReceiver, +pub enum RegistrationClient { + Mixnet(Box), + Lp(Box), } impl RegistrationClient { - async fn register_mix_exit(self) -> Result { - let entry_mixnet_gateway_ip = self.config.entry.node.ip_address; - - let exit_mixnet_gateway_ip = self.config.exit.node.ip_address; - - let ipr_address = self.config.exit.node.ipr_address.ok_or( - RegistrationClientError::NoIpPacketRouterAddress { - node_id: self.config.exit.node.identity.to_base58_string(), - }, - )?; - let mut ipr_client = IprClientConnect::new(self.mixnet_client, self.cancel_token.clone()); - let interface_addresses = ipr_client - .connect(ipr_address) - .await - .map_err(RegistrationClientError::ConnectToIpPacketRouter)?; - - Ok(RegistrationResult::Mixnet(Box::new( - MixnetRegistrationResult { - mixnet_client: ipr_client.into_mixnet_client(), - assigned_addresses: AssignedAddresses { - interface_addresses, - exit_mix_address: ipr_address, - mixnet_client_address: self.mixnet_client_address, - entry_mixnet_gateway_ip, - exit_mixnet_gateway_ip, - }, - event_rx: self.event_rx, - }, - ))) - } - - async fn register_wg(self) -> Result { - let entry_auth_address = self.config.entry.node.authenticator_address.ok_or( - RegistrationClientError::AuthenticationNotPossible { - node_id: self.config.entry.node.identity.to_base58_string(), - }, - )?; - - let exit_auth_address = self.config.exit.node.authenticator_address.ok_or( - RegistrationClientError::AuthenticationNotPossible { - node_id: self.config.exit.node.identity.to_base58_string(), - }, - )?; - - let entry_version = self.config.entry.node.version; - tracing::debug!("Entry gateway version: {entry_version}"); - let exit_version = self.config.exit.node.version; - tracing::debug!("Exit gateway version: {exit_version}"); - - // Start the auth client mixnet listener, which will listen for incoming messages from the - // mixnet and rebroadcast them to the auth clients. - let mixnet_listener = - AuthClientMixnetListener::new(self.mixnet_client, self.cancel_token.clone()).start(); - - let mut entry_auth_client = AuthenticatorClient::new( - mixnet_listener.subscribe(), - mixnet_listener.mixnet_sender(), - self.mixnet_client_address, - entry_auth_address, - entry_version, - self.config.entry.keys, - self.config.entry.node.ip_address, - ); - - let mut exit_auth_client = AuthenticatorClient::new( - mixnet_listener.subscribe(), - mixnet_listener.mixnet_sender(), - self.mixnet_client_address, - exit_auth_address, - exit_version, - self.config.exit.keys, - self.config.exit.node.ip_address, - ); - - let entry_fut = entry_auth_client - .register_wireguard(&*self.bandwidth_controller, TicketType::V1WireguardEntry); - let exit_fut = exit_auth_client - .register_wireguard(&*self.bandwidth_controller, TicketType::V1WireguardExit); - - let (entry, exit) = Box::pin(async { tokio::join!(entry_fut, exit_fut) }).await; - - let entry = entry.map_err(|source| { - RegistrationClientError::from_authenticator_error( - source, - self.config.entry.node.identity.to_base58_string(), - entry_auth_address, - true, // is entry - ) - })?; - let exit = exit.map_err(|source| { - RegistrationClientError::from_authenticator_error( - source, - self.config.exit.node.identity.to_base58_string(), - exit_auth_address, - false, // is exit (not entry) - ) - })?; - - Ok(RegistrationResult::Wireguard(Box::new( - WireguardRegistrationResult { - entry_gateway_client: entry_auth_client, - exit_gateway_client: exit_auth_client, - entry_gateway_data: entry, - exit_gateway_data: exit, - authenticator_listener_handle: mixnet_listener, - bw_controller: self.bandwidth_controller, - }, - ))) - } - - // create dedicated method taking RNG instance for tests - async fn register_lp_with_rng( - self, - rng: &mut R, - ) -> Result - where - R: RngCore + CryptoRng, - { - // Extract and validate LP data - let entry_lp_data = self.config.entry.node.lp_data.ok_or( - RegistrationClientError::LpRegistrationNotPossible { - node_id: self.config.entry.node.identity.to_base58_string(), - }, - )?; - - let exit_lp_data = self.config.exit.node.lp_data.ok_or( - RegistrationClientError::LpRegistrationNotPossible { - node_id: self.config.exit.node.identity.to_base58_string(), - }, - )?; - - let entry_lp_protocol = entry_lp_data.lp_protocol_version; - let exit_lp_protocol = exit_lp_data.lp_protocol_version; - - 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)); - - 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); - - // 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( - entry_lp_keypair.clone(), - entry_peer, - entry_address, - entry_lp_protocol, - self.config.lp_registration_config, - ); - - // 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_address, - source: Box::new(source), - } - })?; - - tracing::info!("Outer session with entry gateway established"); - - // 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_address, exit_lp_keypair, exit_peer, exit_lp_protocol); - - // Perform handshake and registration with exit gateway (all via entry forwarding) - let exit_gateway_data = nested_session - .handshake_and_register_dvpn::( - &mut entry_client, - rng, - &self.config.exit.keys, - &self.config.exit.node.identity, - &*self.bandwidth_controller, - TicketType::V1WireguardExit, - ) - .await - .map_err(|source| RegistrationClientError::ExitGatewayRegisterLp { - gateway_id: self.config.exit.node.identity.to_base58_string(), - lp_address: exit_address, - source: Box::new(source), - })?; - - tracing::info!("Exit gateway registration completed via forwarding"); - - // STEP 3: Register with entry gateway (packet-per-connection) - tracing::info!("Registering with entry gateway"); - let entry_gateway_data = entry_client - .register_dvpn( - rng, - &self.config.entry.keys, - &self.config.entry.node.identity, - &*self.bandwidth_controller, - TicketType::V1WireguardEntry, - ) - .await - .map_err(|source| RegistrationClientError::EntryGatewayRegisterLp { - gateway_id: self.config.entry.node.identity.to_base58_string(), - lp_address: entry_address, - source: Box::new(source), - })?; - - tracing::info!("Entry gateway registration successful"); - - tracing::info!("LP registration successful for both gateways"); - - // LP is registration-only (packet-per-connection model). - // All data flows through WireGuard after this point. - // Each LP packet used its own TCP connection which was closed after the exchange. - // Exit registration was completed via forwarding through entry gateway. - Ok(RegistrationResult::Lp(Box::new(LpRegistrationResult { - entry_gateway_data, - exit_gateway_data, - bw_controller: self.bandwidth_controller, - }))) - } - - async fn register_lp(self) -> Result { - let mut rng = rand::rngs::OsRng; - - self.register_lp_with_rng(&mut rng).await - } - pub async fn register(self) -> Result { - self.cancel_token - .clone() - .run_until_cancelled(async { - match self.config.mode { - RegistrationMode::Mixnet => self.register_mix_exit().await, - RegistrationMode::Wireguard => self.register_wg().await, - RegistrationMode::Lp => self.register_lp().await, - } - }) - .await - .ok_or(RegistrationClientError::Cancelled)? + match self { + RegistrationClient::Mixnet(client) => client.register().await, + RegistrationClient::Lp(client) => client.register().await, + } } } From b19e82d4f7fb47896d75e0a52f9eecc49867d6c9 Mon Sep 17 00:00:00 2001 From: Simon Wicky Date: Thu, 5 Feb 2026 14:43:18 +0100 Subject: [PATCH 48/51] revert mixnet-based client fautly changes from LP (#6420) --- nym-registration-client/src/clients/mixnet.rs | 182 +++++++++++++----- 1 file changed, 133 insertions(+), 49 deletions(-) diff --git a/nym-registration-client/src/clients/mixnet.rs b/nym-registration-client/src/clients/mixnet.rs index 8067afc698..859eee94ea 100644 --- a/nym-registration-client/src/clients/mixnet.rs +++ b/nym-registration-client/src/clients/mixnet.rs @@ -5,6 +5,7 @@ use crate::config::RegistrationClientConfig; use crate::config::RegistrationMode; use crate::error::RegistrationClientError; use crate::types::{MixnetRegistrationResult, RegistrationResult, WireguardRegistrationResult}; +use nym_authenticator_client::AuthClientMixnetListenerHandle; use nym_authenticator_client::{AuthClientMixnetListener, AuthenticatorClient}; use nym_bandwidth_controller::BandwidthTicketProvider; use nym_credentials_interface::TicketType; @@ -22,22 +23,66 @@ pub struct MixnetBasedRegistrationClient { pub(crate) event_rx: EventReceiver, } +enum MixnetClientHandle { + Authenticator(AuthClientMixnetListenerHandle), + Sdk(Box), +} + +impl MixnetClientHandle { + async fn stop(self) { + match self { + Self::Authenticator(handle) => handle.stop().await, + Self::Sdk(handle) => handle.disconnect().await, + } + } +} +// Bundle of an actual error and the underlying mixnet client so it can be shutdown correctly if needed +struct RegistrationError { + mixnet_client_handle: MixnetClientHandle, + source: crate::RegistrationClientError, +} + impl MixnetBasedRegistrationClient { - async fn register_mix_exit(self) -> Result { + async fn register_mix_exit(self) -> Result { let entry_mixnet_gateway_ip = self.config.entry.node.ip_address; let exit_mixnet_gateway_ip = self.config.exit.node.ip_address; - let ipr_address = self.config.exit.node.ipr_address.ok_or( - RegistrationClientError::NoIpPacketRouterAddress { - node_id: self.config.exit.node.identity.to_base58_string(), - }, - )?; - let mut ipr_client = IprClientConnect::new(self.mixnet_client, self.cancel_token.clone()); - let interface_addresses = ipr_client - .connect(ipr_address) + let Some(ipr_address) = self.config.exit.node.ipr_address else { + return Err(RegistrationError { + mixnet_client_handle: MixnetClientHandle::Sdk(Box::new(self.mixnet_client)), + source: RegistrationClientError::NoIpPacketRouterAddress { + node_id: self.config.exit.node.identity.to_base58_string(), + }, + }); + }; + + let mut ipr_client = + IprClientConnect::new(self.mixnet_client, self.cancel_token.child_token()); + + let interface_addresses = match self + .cancel_token + .run_until_cancelled(ipr_client.connect(ipr_address)) .await - .map_err(RegistrationClientError::ConnectToIpPacketRouter)?; + { + Some(Ok(addr)) => addr, + Some(Err(e)) => { + return Err(RegistrationError { + mixnet_client_handle: MixnetClientHandle::Sdk(Box::new( + ipr_client.into_mixnet_client(), + )), + source: RegistrationClientError::ConnectToIpPacketRouter(e), + }); + } + None => { + return Err(RegistrationError { + mixnet_client_handle: MixnetClientHandle::Sdk(Box::new( + ipr_client.into_mixnet_client(), + )), + source: RegistrationClientError::Cancelled, + }); + } + }; Ok(RegistrationResult::Mixnet(Box::new( MixnetRegistrationResult { @@ -54,18 +99,24 @@ impl MixnetBasedRegistrationClient { ))) } - async fn register_wg(self) -> Result { - let entry_auth_address = self.config.entry.node.authenticator_address.ok_or( - RegistrationClientError::AuthenticationNotPossible { - node_id: self.config.entry.node.identity.to_base58_string(), - }, - )?; + async fn register_wg(self) -> Result { + let Some(entry_auth_address) = self.config.entry.node.authenticator_address else { + return Err(RegistrationError { + mixnet_client_handle: MixnetClientHandle::Sdk(Box::new(self.mixnet_client)), + source: RegistrationClientError::AuthenticationNotPossible { + node_id: self.config.entry.node.identity.to_base58_string(), + }, + }); + }; - let exit_auth_address = self.config.exit.node.authenticator_address.ok_or( - RegistrationClientError::AuthenticationNotPossible { - node_id: self.config.exit.node.identity.to_base58_string(), - }, - )?; + let Some(exit_auth_address) = self.config.exit.node.authenticator_address else { + return Err(RegistrationError { + mixnet_client_handle: MixnetClientHandle::Sdk(Box::new(self.mixnet_client)), + source: RegistrationClientError::AuthenticationNotPossible { + node_id: self.config.exit.node.identity.to_base58_string(), + }, + }); + }; let entry_version = self.config.entry.node.version; tracing::debug!("Entry gateway version: {entry_version}"); @@ -75,7 +126,8 @@ impl MixnetBasedRegistrationClient { // Start the auth client mixnet listener, which will listen for incoming messages from the // mixnet and rebroadcast them to the auth clients. let mixnet_listener = - AuthClientMixnetListener::new(self.mixnet_client, self.cancel_token.clone()).start(); + AuthClientMixnetListener::new(self.mixnet_client, self.cancel_token.child_token()) + .start(); let mut entry_auth_client = AuthenticatorClient::new( mixnet_listener.subscribe(), @@ -102,24 +154,50 @@ impl MixnetBasedRegistrationClient { let exit_fut = exit_auth_client .register_wireguard(&*self.bandwidth_controller, TicketType::V1WireguardExit); - let (entry, exit) = Box::pin(async { tokio::join!(entry_fut, exit_fut) }).await; + let (entry, exit) = match Box::pin( + self.cancel_token + .run_until_cancelled(async { tokio::join!(entry_fut, exit_fut) }), + ) + .await + { + Some((entry, exit)) => (entry, exit), + None => { + return Err(RegistrationError { + mixnet_client_handle: MixnetClientHandle::Authenticator(mixnet_listener), + source: RegistrationClientError::Cancelled, + }); + } + }; - let entry = entry.map_err(|source| { - RegistrationClientError::from_authenticator_error( - source, - self.config.entry.node.identity.to_base58_string(), - entry_auth_address, - true, // is entry - ) - })?; - let exit = exit.map_err(|source| { - RegistrationClientError::from_authenticator_error( - source, - self.config.exit.node.identity.to_base58_string(), - exit_auth_address, - false, // is exit (not entry) - ) - })?; + let entry = match entry { + Ok(entry) => entry, + Err(source) => { + return Err(RegistrationError { + mixnet_client_handle: MixnetClientHandle::Authenticator(mixnet_listener), + source: RegistrationClientError::from_authenticator_error( + source, + self.config.entry.node.identity.to_base58_string(), + entry_auth_address, + true, + ), + }); + } + }; + + let exit = match exit { + Ok(exit) => exit, + Err(source) => { + return Err(RegistrationError { + mixnet_client_handle: MixnetClientHandle::Authenticator(mixnet_listener), + source: RegistrationClientError::from_authenticator_error( + source, + self.config.exit.node.identity.to_base58_string(), + exit_auth_address, + false, + ), + }); + } + }; Ok(RegistrationResult::Wireguard(Box::new( WireguardRegistrationResult { @@ -134,15 +212,21 @@ impl MixnetBasedRegistrationClient { } pub(crate) async fn register(self) -> Result { - self.cancel_token - .clone() - .run_until_cancelled(async { - match self.config.mode { - RegistrationMode::Mixnet => self.register_mix_exit().await, - RegistrationMode::Wireguard => self.register_wg().await, - } - }) - .await - .ok_or(RegistrationClientError::Cancelled)? + let registration_result = match self.config.mode { + RegistrationMode::Mixnet => self.register_mix_exit().await, + RegistrationMode::Wireguard => self.register_wg().await, + }; + + // If we failed to register, shut down the mixnet client and wait for it to exit + match registration_result { + Ok(result) => Ok(result), + Err(error) => { + tracing::debug!("Registration failed"); + tracing::debug!("Shutting down mixnet client"); + error.mixnet_client_handle.stop().await; + tracing::debug!("Mixnet client stopped"); + Err(error.source) + } + } } } From a151a03181286acc005baa3c8f7ccbf6cd175241 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Thu, 5 Feb 2026 14:47:37 +0000 Subject: [PATCH 49/51] Lp/ip pool fixes (#6412) * squashing Lp/ip pool fixes#6412 removed unused imports gateway probe fixes PSK injection + test fixes cleanup minus PSK injection combine with lp reg moved authenticator peer registration to centralised location bugfix: ensure IpPool never allocates gateway ip ip pool allocation tests * review fixes * test fixes --- Cargo.lock | 5 + Cargo.toml | 1 + common/authenticator-requests/src/lib.rs | 1 + common/authenticator-requests/src/response.rs | 12 + common/authenticator-requests/src/util.rs | 32 + .../src/v4/registration.rs | 26 +- .../src/v5/registration.rs | 26 +- .../src/v6/registration.rs | 28 +- common/credential-verification/src/lib.rs | 9 + .../src/upgrade_mode.rs | 37 ++ common/registration/src/lib.rs | 16 + common/registration/src/lp_messages.rs | 57 +- .../src/lib.rs | 4 +- common/test-utils/src/helpers.rs | 20 +- .../tests/src/v2/peer_controller.rs | 14 +- common/wireguard/Cargo.toml | 3 + common/wireguard/src/error.rs | 4 +- common/wireguard/src/ip_pool/mod.rs | 391 +++++++++-- common/wireguard/src/lib.rs | 4 +- common/wireguard/src/peer_controller/mock.rs | 12 +- common/wireguard/src/peer_controller/mod.rs | 50 +- gateway/Cargo.toml | 4 +- .../authenticator/mixnet_listener.rs | 627 +----------------- .../authenticator/mod.rs | 13 +- .../authenticator/seen_credential_cache.rs | 38 +- gateway/src/node/lp_listener/handler.rs | 43 +- gateway/src/node/lp_listener/mod.rs | 78 +-- gateway/src/node/lp_listener/registration.rs | 466 +------------ gateway/src/node/mod.rs | 39 +- gateway/src/node/wireguard/error.rs | 39 ++ gateway/src/node/wireguard/mod.rs | 2 + .../new_peer_registration/authenticator.rs | 157 +++++ .../new_peer_registration/helpers.rs | 209 ++++++ .../wireguard/new_peer_registration/lp.rs | 128 ++++ .../wireguard/new_peer_registration/mod.rs | 391 +++++++++++ .../new_peer_registration/pending.rs | 158 +++++ gateway/src/node/wireguard/peer_manager.rs | 469 ++++++++----- integration-tests/src/lp_registration.rs | 63 +- nym-gateway-probe/src/common/netstack.rs | 5 +- nym-gateway-probe/src/common/probe_tests.rs | 7 +- nym-gateway-probe/src/common/wireguard.rs | 17 +- nym-node/src/node/mod.rs | 44 +- .../src/lp_client/client.rs | 25 +- .../src/lp_client/nested_session.rs | 25 +- 44 files changed, 2121 insertions(+), 1678 deletions(-) create mode 100644 gateway/src/node/wireguard/new_peer_registration/authenticator.rs create mode 100644 gateway/src/node/wireguard/new_peer_registration/helpers.rs create mode 100644 gateway/src/node/wireguard/new_peer_registration/lp.rs create mode 100644 gateway/src/node/wireguard/new_peer_registration/mod.rs create mode 100644 gateway/src/node/wireguard/new_peer_registration/pending.rs diff --git a/Cargo.lock b/Cargo.lock index 40e54deb50..dbc4c27625 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6354,6 +6354,7 @@ dependencies = [ name = "nym-gateway" version = "1.1.36" dependencies = [ + "anyhow", "async-trait", "bincode", "bip39", @@ -6392,6 +6393,7 @@ dependencies = [ "nym-sphinx", "nym-statistics-common", "nym-task", + "nym-test-utils", "nym-topology", "nym-upgrade-mode-check", "nym-validator-client", @@ -8365,11 +8367,13 @@ dependencies = [ name = "nym-wireguard" version = "1.20.1" dependencies = [ + "anyhow", "base64 0.22.1", "defguard_wireguard_rs", "futures", "ip_network", "ipnetwork", + "mock_instant", "nym-authenticator-requests", "nym-credential-verification", "nym-credentials-interface", @@ -8380,6 +8384,7 @@ dependencies = [ "nym-network-defaults", "nym-node-metrics", "nym-task", + "nym-test-utils", "nym-wireguard-types", "rand 0.8.5", "thiserror 2.0.17", diff --git a/Cargo.toml b/Cargo.toml index a9a240b504..c6b1bc64a7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -303,6 +303,7 @@ ledger-transport = "0.10.0" ledger-transport-hid = "0.10.0" log = "0.4" mime = "0.3.17" +mock_instant = "0.6.0" moka = { version = "0.12", features = ["future"] } nix = "0.30.1" notify = "5.1.0" diff --git a/common/authenticator-requests/src/lib.rs b/common/authenticator-requests/src/lib.rs index 226c78adee..eb5b14b5df 100644 --- a/common/authenticator-requests/src/lib.rs +++ b/common/authenticator-requests/src/lib.rs @@ -18,6 +18,7 @@ mod util; mod version; pub use error::Error; +pub use util::{authenticator_ipv4_to_ipv6, authenticator_ipv6_to_ipv4}; pub use v6 as latest; pub use version::AuthenticatorVersion; diff --git a/common/authenticator-requests/src/response.rs b/common/authenticator-requests/src/response.rs index 84b6ae8526..467fd25514 100644 --- a/common/authenticator-requests/src/response.rs +++ b/common/authenticator-requests/src/response.rs @@ -7,6 +7,7 @@ use crate::traits::{ TopUpBandwidthResponse, UpgradeModeStatus, }; use crate::{v2, v3, v4, v5, v6}; +use nym_sphinx::addressing::Recipient; #[derive(Debug)] pub enum AuthenticatorResponse { @@ -17,6 +18,17 @@ pub enum AuthenticatorResponse { UpgradeMode(Box), } +pub struct SerialisedResponse { + pub bytes: Vec, + pub reply_to: Option, +} + +impl SerialisedResponse { + pub fn new(bytes: Vec, reply_to: Option) -> Self { + Self { bytes, reply_to } + } +} + impl UpgradeModeStatus for AuthenticatorResponse { fn upgrade_mode_status(&self) -> CurrentUpgradeModeStatus { match self { diff --git a/common/authenticator-requests/src/util.rs b/common/authenticator-requests/src/util.rs index cb1269f10c..451b4a77b5 100644 --- a/common/authenticator-requests/src/util.rs +++ b/common/authenticator-requests/src/util.rs @@ -1,6 +1,38 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use nym_network_defaults::{WG_TUN_DEVICE_IP_ADDRESS_V4, WG_TUN_DEVICE_IP_ADDRESS_V6}; +use std::net::{Ipv4Addr, Ipv6Addr}; + +pub fn authenticator_ipv6_to_ipv4(addr: Ipv6Addr) -> Ipv4Addr { + let before_last_byte = addr.octets()[14]; + let last_byte = addr.octets()[15]; + + Ipv4Addr::new( + WG_TUN_DEVICE_IP_ADDRESS_V4.octets()[0], + WG_TUN_DEVICE_IP_ADDRESS_V4.octets()[1], + before_last_byte, + last_byte, + ) +} + +pub fn authenticator_ipv4_to_ipv6(addr: Ipv4Addr) -> Ipv6Addr { + let before_last_byte = addr.octets()[2]; + let last_byte = addr.octets()[3]; + + let last_bytes = ((before_last_byte as u16) << 8) | last_byte as u16; + Ipv6Addr::new( + WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[0], + WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[1], + WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[2], + WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[3], + WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[4], + WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[5], + WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[6], + last_bytes, + ) +} + #[cfg(test)] pub(crate) mod tests { pub(crate) const CREDENTIAL_BYTES: [u8; 1245] = [ diff --git a/common/authenticator-requests/src/v4/registration.rs b/common/authenticator-requests/src/v4/registration.rs index b1ee074dfd..e718bbab5c 100644 --- a/common/authenticator-requests/src/v4/registration.rs +++ b/common/authenticator-requests/src/v4/registration.rs @@ -2,9 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 use crate::error::Error; +use crate::util::{authenticator_ipv4_to_ipv6, authenticator_ipv6_to_ipv4}; use base64::{Engine, engine::general_purpose}; use nym_credentials_interface::CredentialSpendingData; -use nym_network_defaults::constants::{WG_TUN_DEVICE_IP_ADDRESS_V4, WG_TUN_DEVICE_IP_ADDRESS_V6}; use nym_wireguard_types::PeerPublicKey; use serde::{Deserialize, Serialize}; use std::collections::HashMap; @@ -56,27 +56,11 @@ impl fmt::Display for IpPair { impl From for IpPair { fn from(value: IpAddr) -> Self { - let (before_last_byte, last_byte) = match value { - std::net::IpAddr::V4(ipv4_addr) => (ipv4_addr.octets()[2], ipv4_addr.octets()[3]), - std::net::IpAddr::V6(ipv6_addr) => (ipv6_addr.octets()[14], ipv6_addr.octets()[15]), + let (ipv4, ipv6) = match value { + IpAddr::V4(ipv4) => (ipv4, authenticator_ipv4_to_ipv6(ipv4)), + IpAddr::V6(ipv6_addr) => (authenticator_ipv6_to_ipv4(ipv6_addr), ipv6_addr), }; - let last_bytes = ((before_last_byte as u16) << 8) | last_byte as u16; - let ipv4 = Ipv4Addr::new( - WG_TUN_DEVICE_IP_ADDRESS_V4.octets()[0], - WG_TUN_DEVICE_IP_ADDRESS_V4.octets()[1], - before_last_byte, - last_byte, - ); - let ipv6 = Ipv6Addr::new( - WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[0], - WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[1], - WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[2], - WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[3], - WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[4], - WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[5], - WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[6], - last_bytes, - ); + IpPair::new(ipv4, ipv6) } } diff --git a/common/authenticator-requests/src/v5/registration.rs b/common/authenticator-requests/src/v5/registration.rs index 5154400f93..6000dd6760 100644 --- a/common/authenticator-requests/src/v5/registration.rs +++ b/common/authenticator-requests/src/v5/registration.rs @@ -2,9 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 use crate::error::Error; +use crate::util::{authenticator_ipv4_to_ipv6, authenticator_ipv6_to_ipv4}; use base64::{Engine, engine::general_purpose}; use nym_credentials_interface::CredentialSpendingData; -use nym_network_defaults::constants::{WG_TUN_DEVICE_IP_ADDRESS_V4, WG_TUN_DEVICE_IP_ADDRESS_V6}; use nym_wireguard_types::PeerPublicKey; use serde::{Deserialize, Serialize}; use std::collections::HashMap; @@ -54,27 +54,11 @@ impl fmt::Display for IpPair { impl From for IpPair { fn from(value: IpAddr) -> Self { - let (before_last_byte, last_byte) = match value { - std::net::IpAddr::V4(ipv4_addr) => (ipv4_addr.octets()[2], ipv4_addr.octets()[3]), - std::net::IpAddr::V6(ipv6_addr) => (ipv6_addr.octets()[14], ipv6_addr.octets()[15]), + let (ipv4, ipv6) = match value { + IpAddr::V4(ipv4) => (ipv4, authenticator_ipv4_to_ipv6(ipv4)), + IpAddr::V6(ipv6_addr) => (authenticator_ipv6_to_ipv4(ipv6_addr), ipv6_addr), }; - let last_bytes = ((before_last_byte as u16) << 8) | last_byte as u16; - let ipv4 = Ipv4Addr::new( - WG_TUN_DEVICE_IP_ADDRESS_V4.octets()[0], - WG_TUN_DEVICE_IP_ADDRESS_V4.octets()[1], - before_last_byte, - last_byte, - ); - let ipv6 = Ipv6Addr::new( - WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[0], - WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[1], - WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[2], - WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[3], - WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[4], - WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[5], - WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[6], - last_bytes, - ); + IpPair::new(ipv4, ipv6) } } diff --git a/common/authenticator-requests/src/v6/registration.rs b/common/authenticator-requests/src/v6/registration.rs index 14e7462c05..a433a4e7e9 100644 --- a/common/authenticator-requests/src/v6/registration.rs +++ b/common/authenticator-requests/src/v6/registration.rs @@ -3,13 +3,12 @@ use crate::error::Error; use crate::models::BandwidthClaim; +use crate::util::{authenticator_ipv4_to_ipv6, authenticator_ipv6_to_ipv4}; use base64::{Engine, engine::general_purpose}; -use nym_network_defaults::constants::{WG_TUN_DEVICE_IP_ADDRESS_V4, WG_TUN_DEVICE_IP_ADDRESS_V6}; use nym_wireguard_types::PeerPublicKey; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; -use std::time::SystemTime; use std::{fmt, ops::Deref, str::FromStr}; #[cfg(feature = "verify")] @@ -20,7 +19,6 @@ use nym_crypto::asymmetric::x25519::{PrivateKey, PublicKey}; use sha2::Sha256; pub type PendingRegistrations = HashMap; -pub type PrivateIPs = HashMap; #[cfg(feature = "verify")] pub type HmacSha256 = Hmac; @@ -53,27 +51,11 @@ impl fmt::Display for IpPair { impl From for IpPair { fn from(value: IpAddr) -> Self { - let (before_last_byte, last_byte) = match value { - IpAddr::V4(ipv4_addr) => (ipv4_addr.octets()[2], ipv4_addr.octets()[3]), - IpAddr::V6(ipv6_addr) => (ipv6_addr.octets()[14], ipv6_addr.octets()[15]), + let (ipv4, ipv6) = match value { + IpAddr::V4(ipv4) => (ipv4, authenticator_ipv4_to_ipv6(ipv4)), + IpAddr::V6(ipv6_addr) => (authenticator_ipv6_to_ipv4(ipv6_addr), ipv6_addr), }; - let last_bytes = ((before_last_byte as u16) << 8) | last_byte as u16; - let ipv4 = Ipv4Addr::new( - WG_TUN_DEVICE_IP_ADDRESS_V4.octets()[0], - WG_TUN_DEVICE_IP_ADDRESS_V4.octets()[1], - before_last_byte, - last_byte, - ); - let ipv6 = Ipv6Addr::new( - WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[0], - WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[1], - WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[2], - WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[3], - WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[4], - WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[5], - WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[6], - last_bytes, - ); + IpPair::new(ipv4, ipv6) } } diff --git a/common/credential-verification/src/lib.rs b/common/credential-verification/src/lib.rs index cafc5bfcfd..fbf8152c8c 100644 --- a/common/credential-verification/src/lib.rs +++ b/common/credential-verification/src/lib.rs @@ -22,6 +22,8 @@ pub mod ecash; pub mod error; pub mod upgrade_mode; +const MOCK_BANDWIDTH: i64 = 2024 * 1024 * 1024; + // Histogram buckets for ecash verification duration (in seconds) const ECASH_VERIFICATION_DURATION_BUCKETS: &[f64] = &[0.001, 0.005, 0.01, 0.05, 0.1, 0.5, 1.0, 2.0, 5.0]; @@ -111,6 +113,13 @@ impl CredentialVerifier { } pub async fn verify(&mut self) -> Result { + if self.ecash_verifier.is_mock() { + // if we're in the mock mode (local testing), skip cryptographic verification + // and just return a dummy bandwidth value since we don't have blockchain access + // Return a reasonable test bandwidth value (e.g., 1GB in bytes) + return Ok(MOCK_BANDWIDTH); + } + let start = Instant::now(); nym_metrics::inc!("ecash_verification_attempts"); diff --git a/common/credential-verification/src/upgrade_mode.rs b/common/credential-verification/src/upgrade_mode.rs index 7dec1010f7..93177c688e 100644 --- a/common/credential-verification/src/upgrade_mode.rs +++ b/common/credential-verification/src/upgrade_mode.rs @@ -291,3 +291,40 @@ struct UpgradeModeStateInner { // (and dealing with the async consequences of that) status: UpgradeModeStatus, } + +pub mod testing { + use crate::UpgradeModeState; + use crate::upgrade_mode::{ + CheckRequest, UpgradeModeCheckConfig, UpgradeModeCheckRequestSender, UpgradeModeDetails, + }; + use futures::channel::mpsc::UnboundedReceiver; + use nym_crypto::asymmetric::ed25519; + use std::time::Duration; + + pub fn mock_dummy_upgrade_mode_details() -> (UpgradeModeDetails, UnboundedReceiver) + { + let (um_recheck_tx, um_recheck_rx) = futures::channel::mpsc::unbounded(); + + const DUMMY_ATTESTER_ED25519_PRIVATE_KEY: [u8; 32] = [ + 108, 49, 193, 21, 126, 161, 249, 85, 242, 207, 74, 195, 238, 6, 64, 149, 201, 140, 248, + 163, 122, 170, 79, 198, 87, 85, 36, 29, 243, 92, 64, 161, + ]; + + pub(crate) fn dummy_attester_public_key() -> ed25519::PublicKey { + let private_key = + ed25519::PrivateKey::from_bytes(&DUMMY_ATTESTER_ED25519_PRIVATE_KEY).unwrap(); + private_key.public_key() + } + + let upgrade_mode_state = UpgradeModeState::new(dummy_attester_public_key()); + let upgrade_mode_details = UpgradeModeDetails::new( + UpgradeModeCheckConfig { + // essentially we never want to trigger this in our tests + min_staleness_recheck: Duration::from_nanos(1), + }, + UpgradeModeCheckRequestSender::new(um_recheck_tx), + upgrade_mode_state.clone(), + ); + (upgrade_mode_details, um_recheck_rx) + } +} diff --git a/common/registration/src/lib.rs b/common/registration/src/lib.rs index 2beb0632ed..f297de143d 100644 --- a/common/registration/src/lib.rs +++ b/common/registration/src/lib.rs @@ -27,6 +27,22 @@ pub struct NymNodeInformation { pub version: AuthenticatorVersion, } +#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] +pub struct WireguardRegistrationData { + /// Public x25519 key of this gateway + #[serde(with = "bs58_x25519_pubkey")] + pub public_key: x25519::PublicKey, + + /// Port at which this gateway is accessible for wireguard + pub port: u16, + + /// Ipv4 address assigned to this peer + pub private_ipv4: Ipv4Addr, + + /// Ipv6 address assigned to this peer + pub private_ipv6: Ipv6Addr, +} + #[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] pub struct WireguardConfiguration { #[serde(with = "bs58_x25519_pubkey")] diff --git a/common/registration/src/lp_messages.rs b/common/registration/src/lp_messages.rs index ff64fc3746..c294b87abb 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::WireguardConfiguration; +use crate::WireguardRegistrationData; use crate::dvpn::{ LpDvpnRegistrationFinalisation, LpDvpnRegistrationInitialRequest, LpDvpnRegistrationRequestMessage, LpDvpnRegistrationRequestMessageContent, @@ -16,7 +16,6 @@ use crate::mixnet::{ }; use crate::serialisation::{BincodeError, BincodeOptions, lp_bincode_serializer}; use nym_authenticator_requests::models::BandwidthClaim; -use nym_credentials_interface::TicketType; use serde::{Deserialize, Serialize}; use tracing::error; @@ -135,16 +134,11 @@ impl LpRegistrationRequest { pub fn new_initial_dvpn( wg_public_key: nym_wireguard_types::PeerPublicKey, psk: [u8; 32], - ticket_type: TicketType, ) -> Self { Self::new(LpRegistrationRequestData::Dvpn { data: Box::new(LpDvpnRegistrationRequestMessage { content: LpDvpnRegistrationRequestMessageContent::InitialRequest( - LpDvpnRegistrationInitialRequest { - wg_public_key, - psk, - ticket_type, - }, + LpDvpnRegistrationInitialRequest { wg_public_key, psk }, ), }), }) @@ -180,7 +174,7 @@ impl LpRegistrationRequest { impl LpRegistrationResponse { /// Create a success response with GatewayData (for dVPN mode) - pub fn success_dvpn(config: WireguardConfiguration, available_bandwidth: i64) -> Self { + pub fn success_dvpn(config: WireguardRegistrationData, upgrade_mode: bool) -> Self { Self { status: RegistrationStatus::Completed, response_data: LpRegistrationResponseData::Dvpn { @@ -188,7 +182,7 @@ impl LpRegistrationResponse { content: LpDvpnRegistrationResponseMessageContent::CompletedRegistration( dvpn::CompletedRegistrationResponse { config, - available_bandwidth, + upgrade_mode, }, ), }, @@ -196,16 +190,13 @@ impl LpRegistrationResponse { } } - pub fn success_mixnet(config: LpMixnetGatewayData, available_bandwidth: i64) -> Self { + pub fn success_mixnet(config: LpMixnetGatewayData) -> Self { Self { status: RegistrationStatus::Completed, response_data: LpRegistrationResponseData::Mixnet { data: LpMixnetRegistrationResponseMessage { content: LpMixnetRegistrationResponseMessageContent::CompletedRegistration( - mixnet::CompletedRegistrationResponse { - config, - available_bandwidth, - }, + mixnet::CompletedRegistrationResponse { config }, ), }, }, @@ -284,9 +275,8 @@ impl LpRegistrationResponse { } pub mod dvpn { - use crate::WireguardConfiguration; + use crate::WireguardRegistrationData; use nym_authenticator_requests::models::BandwidthClaim; - use nym_credentials_interface::TicketType; use serde::{Deserialize, Serialize}; // client @@ -310,9 +300,6 @@ pub mod dvpn { /// Preshared key to be used for the connection pub psk: [u8; 32], - - /// Type of the ticket/gateway we're going to register with - pub ticket_type: TicketType, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -350,10 +337,11 @@ pub mod dvpn { pub struct CompletedRegistrationResponse { /// Gateway configuration data for dVPN mode (WireGuard) /// This matches what WireguardRegistrationResult expects - pub config: WireguardConfiguration, + pub config: WireguardRegistrationData, - /// The bandwidth available to this client, - pub available_bandwidth: i64, + /// Flag indicating whether the gateway has detected the system is undergoing the upgrade + /// (thus it will not meter bandwidth) + pub upgrade_mode: bool, } #[derive(Debug, Clone, Copy, Serialize, Deserialize)] @@ -427,9 +415,6 @@ pub mod mixnet { /// /// Contains gateway identity and sphinx key needed for nym address construction. pub config: LpMixnetGatewayData, - - /// The bandwidth available to this client, - pub available_bandwidth: i64, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -446,15 +431,14 @@ mod tests { use std::net::{Ipv4Addr, Ipv6Addr}; // ==================== Helper Functions ==================== - fn create_test_gateway_data() -> WireguardConfiguration { - WireguardConfiguration { + fn create_test_wg_config() -> WireguardRegistrationData { + WireguardRegistrationData { public_key: nym_crypto::asymmetric::x25519::PublicKey::from( nym_sphinx::PublicKey::from([1u8; 32]), ), - psk: Some([42u8; 32]), + port: 1234, private_ipv4: Ipv4Addr::new(10, 0, 0, 1), private_ipv6: Ipv6Addr::new(0xfc00, 0, 0, 0, 0, 0, 0, 1), - endpoint: "192.168.1.1:8080".parse().expect("Valid test endpoint"), } } @@ -499,10 +483,9 @@ mod tests { #[test] fn test_lp_registration_response_success_dvpn() { - let cfg = create_test_gateway_data(); - let allocated_bandwidth = 500_000_000; + let cfg = create_test_wg_config(); - let response = LpRegistrationResponse::success_dvpn(cfg, allocated_bandwidth); + let response = LpRegistrationResponse::success_dvpn(cfg, false); assert!(response.status.is_successful()); let LpRegistrationResponseData::Dvpn { data } = response.response_data else { @@ -515,7 +498,7 @@ mod tests { panic!("unexpected response") }; assert_eq!(complete.config, cfg); - assert_eq!(complete.available_bandwidth, allocated_bandwidth); + assert!(!complete.upgrade_mode); } #[test] @@ -526,10 +509,7 @@ mod tests { let lp_gateway_data = LpMixnetGatewayData { gateway_identity: *valid_key.public_key(), }; - let allocated_bandwidth = 500_000_000; - - let response = - LpRegistrationResponse::success_mixnet(lp_gateway_data.clone(), allocated_bandwidth); + let response = LpRegistrationResponse::success_mixnet(lp_gateway_data.clone()); assert!(response.status.is_successful()); let LpRegistrationResponseData::Mixnet { data } = response.response_data else { @@ -542,6 +522,5 @@ mod tests { panic!("unexpected response") }; assert_eq!(complete.config, lp_gateway_data); - assert_eq!(complete.available_bandwidth, allocated_bandwidth); } } diff --git a/common/service-provider-requests-common/src/lib.rs b/common/service-provider-requests-common/src/lib.rs index 44e54dc2af..c43ea89074 100644 --- a/common/service-provider-requests-common/src/lib.rs +++ b/common/service-provider-requests-common/src/lib.rs @@ -11,7 +11,7 @@ pub enum ProtocolError { InvalidServiceProviderType(u8), } -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] #[repr(u8)] pub enum ServiceProviderType { NetworkRequester = 0, @@ -76,7 +76,7 @@ impl ServiceProviderTypeExt for u8 { } } -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq)] pub struct Protocol { pub version: u8, pub service_provider_type: ServiceProviderType, diff --git a/common/test-utils/src/helpers.rs b/common/test-utils/src/helpers.rs index 29d6c184d3..e96494dfc0 100644 --- a/common/test-utils/src/helpers.rs +++ b/common/test-utils/src/helpers.rs @@ -2,16 +2,16 @@ // SPDX-License-Identifier: Apache-2.0 use crate::traits::Timeboxed; -use rand_chacha::ChaCha20Rng; +use nym_bin_common::logging::tracing_subscriber::EnvFilter; +use nym_bin_common::logging::tracing_subscriber::layer::SubscriberExt; +use nym_bin_common::logging::tracing_subscriber::util::SubscriberInitExt; +use nym_bin_common::logging::{default_tracing_fmt_layer, tracing_subscriber}; use rand_chacha::rand_core::SeedableRng; use std::future::Future; use tokio::task::JoinHandle; use tokio::time::error::Elapsed; -use nym_bin_common::logging::tracing_subscriber::EnvFilter; -use nym_bin_common::logging::tracing_subscriber::layer::SubscriberExt; -use nym_bin_common::logging::tracing_subscriber::util::SubscriberInitExt; -use nym_bin_common::logging::{default_tracing_fmt_layer, tracing_subscriber}; +pub use rand_chacha::ChaCha20Rng as DeterministicRng; pub use rand_chacha::rand_core::{CryptoRng, RngCore}; pub fn leak(val: T) -> &'static mut T { @@ -26,16 +26,16 @@ where tokio::spawn(async move { fut.timeboxed().await }) } -pub fn deterministic_rng() -> ChaCha20Rng { +pub fn deterministic_rng() -> DeterministicRng { seeded_rng([42u8; 32]) } -pub fn seeded_rng(seed: [u8; 32]) -> ChaCha20Rng { - ChaCha20Rng::from_seed(seed) +pub fn seeded_rng(seed: [u8; 32]) -> DeterministicRng { + DeterministicRng::from_seed(seed) } -pub fn u64_seeded_rng(seed: u64) -> ChaCha20Rng { - ChaCha20Rng::seed_from_u64(seed) +pub fn u64_seeded_rng(seed: u64) -> DeterministicRng { + DeterministicRng::seed_from_u64(seed) } // test logger to use during debugging diff --git a/common/wireguard-private-metadata/tests/src/v2/peer_controller.rs b/common/wireguard-private-metadata/tests/src/v2/peer_controller.rs index 15312a1f52..2b29de63c6 100644 --- a/common/wireguard-private-metadata/tests/src/v2/peer_controller.rs +++ b/common/wireguard-private-metadata/tests/src/v2/peer_controller.rs @@ -26,7 +26,7 @@ impl From<&PeerControlRequest> for PeerControlRequestTypeV2 { fn from(req: &PeerControlRequest) -> Self { match req { PeerControlRequest::AddPeer { .. } => PeerControlRequestTypeV2::AddPeer, - PeerControlRequest::AllocatePeerIpPair { .. } => PeerControlRequestTypeV2::AddPeer, + PeerControlRequest::PreAllocateIpPair { .. } => PeerControlRequestTypeV2::AddPeer, PeerControlRequest::RemovePeer { .. } => PeerControlRequestTypeV2::RemovePeer, PeerControlRequest::QueryPeer { .. } => PeerControlRequestTypeV2::QueryPeer, PeerControlRequest::GetClientBandwidthByKey { .. } => { @@ -41,6 +41,7 @@ impl From<&PeerControlRequest> for PeerControlRequestTypeV2 { PeerControlRequest::GetVerifierByIp { ip, .. } => { PeerControlRequestTypeV2::GetVerifierByIp { ip: *ip } } + PeerControlRequest::CheckActivePeer { .. } => unreachable!(), PeerControlRequest::ReleaseIpPair { .. } => unreachable!(), } } @@ -114,7 +115,7 @@ impl MockPeerControllerV2 { ) .unwrap(); } - PeerControlRequest::AllocatePeerIpPair { response_tx, .. } => { + PeerControlRequest::PreAllocateIpPair { response_tx, .. } => { response_tx .send( *response @@ -186,6 +187,15 @@ impl MockPeerControllerV2 { ) .ok(); } + PeerControlRequest::CheckActivePeer { response_tx, .. } => { + response_tx + .send( + *response + .downcast() + .expect("registered response has mismatched type"), + ) + .ok(); + } } } diff --git a/common/wireguard/Cargo.toml b/common/wireguard/Cargo.toml index 3857d1165a..f6b3c0e6fb 100644 --- a/common/wireguard/Cargo.toml +++ b/common/wireguard/Cargo.toml @@ -36,7 +36,10 @@ nym-wireguard-types = { workspace = true } nym-node-metrics = { workspace = true } [dev-dependencies] +anyhow = { workspace = true } nym-gateway-storage = { workspace = true, features = ["mock"] } +mock_instant = { workspace = true } +nym-test-utils = { workspace = true } [features] default = [] diff --git a/common/wireguard/src/error.rs b/common/wireguard/src/error.rs index 7f5437d630..68d465a534 100644 --- a/common/wireguard/src/error.rs +++ b/common/wireguard/src/error.rs @@ -1,6 +1,8 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use crate::IpPoolError; + #[derive(Debug, thiserror::Error)] pub enum Error { #[error("{0}")] @@ -22,7 +24,7 @@ pub enum Error { SystemTime(#[from] std::time::SystemTimeError), #[error("IP pool error: {0}")] - IpPool(String), + IpPool(#[from] IpPoolError), } pub type Result = std::result::Result; diff --git a/common/wireguard/src/ip_pool/mod.rs b/common/wireguard/src/ip_pool/mod.rs index b34a7134c2..7044c8a806 100644 --- a/common/wireguard/src/ip_pool/mod.rs +++ b/common/wireguard/src/ip_pool/mod.rs @@ -1,20 +1,22 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -mod compat; - use defguard_wireguard_rs::host::Peer; use ipnetwork::IpNetwork; use rand::seq::IteratorRandom; use std::collections::HashMap; use std::fmt::{Display, Formatter}; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; -use std::sync::Arc; -use std::time::{Duration, SystemTime}; -use tokio::sync::RwLock; -use tokio::time::Instant; +use std::time::Duration; use tracing::{trace, warn}; +mod compat; + +#[cfg(test)] +use mock_instant::thread_local::Instant; +#[cfg(not(test))] +use std::time::Instant; + // helper to convert peer's allocation into an `IpPair` pub fn allocated_ip_pair(peer: &Peer) -> Option { for allowed_ip in &peer.allowed_ips { @@ -57,17 +59,38 @@ impl Display for IpPair { pub enum AllocationState { /// IP is available for allocation Free, - /// IP is allocated and in use, with timestamp of allocation - Allocated(SystemTime), + + /// The IP has been pre-allocated for a peer, but the corresponding registration has not yet been finalised + PreAllocated { allocated_at: Instant }, + + /// IP is allocated and in use, with timestamp + Allocated { allocated_at: Instant }, +} + +impl AllocationState { + pub fn is_free(&self) -> bool { + matches!(self, AllocationState::Free) + } + + pub fn new_pre_allocated() -> Self { + AllocationState::PreAllocated { + allocated_at: Instant::now(), + } + } + + pub fn new_allocated() -> Self { + AllocationState::Allocated { + allocated_at: Instant::now(), + } + } } /// Thread-safe IP address pool manager /// /// Manages allocation of IPv4/IPv6 address pairs from configured CIDR ranges. /// Ensures collision-free allocation and supports stale cleanup. -#[derive(Clone)] pub struct IpPool { - allocations: Arc>>, + allocations: HashMap, } impl IpPool { @@ -98,7 +121,7 @@ impl IpPool { .iter() .filter_map(|ip| { if let IpAddr::V4(v4) = ip { - Some(v4) + if v4 != ipv4_network { Some(v4) } else { None } } else { None } @@ -109,7 +132,7 @@ impl IpPool { .iter() .filter_map(|ip| { if let IpAddr::V6(v6) = ip { - Some(v6) + if v6 != ipv6_network { Some(v6) } else { None } } else { None } @@ -129,21 +152,18 @@ impl IpPool { allocations.len(), ); - Ok(IpPool { - allocations: Arc::new(RwLock::new(allocations)), - }) + Ok(IpPool { allocations }) } - /// Allocate a free IP pair from the pool + /// Preallocate a free IP pair from the pool /// /// # Errors /// Returns `IpPoolError::NoFreeIp` if no IPs are available - pub async fn allocate(&self) -> Result { - let mut pool = self.allocations.write().await; - + pub fn pre_allocate(&mut self) -> Result { // Find a free IP and allocate it let assignment_start = Instant::now(); - let free_ip = pool + let free_ip = self + .allocations .iter_mut() .filter(|(_, state)| matches!(state, AllocationState::Free)) .choose(&mut rand::thread_rng()) @@ -155,18 +175,42 @@ impl IpPool { } let ip_pair = *free_ip.0; - *free_ip.1 = AllocationState::Allocated(SystemTime::now()); + *free_ip.1 = AllocationState::new_pre_allocated(); tracing::debug!("Allocated IP pair: {ip_pair}"); Ok(ip_pair) } + pub fn confirm_allocation(&mut self, ip_pair: IpPair) -> Result<(), IpPoolError> { + let Some(allocation) = self.allocations.get_mut(&ip_pair) else { + return Err(IpPoolError::UnknownIpPair { ip_pair }); + }; + match allocation { + AllocationState::Free => { + // seems the IpPair has been released before the confirmation, but it has not yet been re-allocated + warn!( + "{ip_pair} seems to have already been released, but has not been allocated to a new peer yet" + ); + *allocation = AllocationState::Allocated { + allocated_at: Instant::now(), + }; + Ok(()) + } + AllocationState::PreAllocated { allocated_at } => { + *allocation = AllocationState::Allocated { + allocated_at: *allocated_at, + }; + Ok(()) + } + AllocationState::Allocated { .. } => Err(IpPoolError::AlreadyUsed { ip_pair }), + } + } + /// Release an IP pair back to the pool /// /// Marks the IP as free for future allocations. - pub async fn release(&self, ip_pair: IpPair) { - let mut pool = self.allocations.write().await; - if let Some(state) = pool.get_mut(&ip_pair) { + pub fn release(&mut self, ip_pair: IpPair) { + if let Some(state) = self.allocations.get_mut(&ip_pair) { *state = AllocationState::Free; tracing::debug!("Released IP pair: {ip_pair}"); } @@ -175,58 +219,67 @@ impl IpPool { /// Mark an IP pair as allocated (used during initialization from database) /// /// This is used when restoring state from the database on gateway startup. - pub async fn mark_used(&self, ip_pair: IpPair) { - let mut pool = self.allocations.write().await; - if let Some(state) = pool.get_mut(&ip_pair) { - *state = AllocationState::Allocated(SystemTime::now()); - tracing::debug!("Marked IP pair as used: {ip_pair}"); - } else { - tracing::warn!("Attempted to mark unknown IP pair as used: {ip_pair}"); + pub fn mark_used(&mut self, ip_pair: IpPair) -> Result<(), IpPoolError> { + let Some(state) = self.allocations.get_mut(&ip_pair) else { + return Err(IpPoolError::UnknownIpPair { ip_pair }); + }; + + if !state.is_free() { + return Err(IpPoolError::AlreadyUsed { ip_pair }); } + tracing::debug!("Marked IP pair as used: {ip_pair}"); + *state = AllocationState::new_allocated(); + Ok(()) } /// Get the number of free IPs in the pool - pub async fn free_count(&self) -> usize { - let pool = self.allocations.read().await; - pool.iter() + pub fn free_count(&self) -> usize { + self.allocations + .iter() .filter(|(_, state)| matches!(state, AllocationState::Free)) .count() } /// Get the number of allocated IPs in the pool - pub async fn allocated_count(&self) -> usize { - let pool = self.allocations.read().await; - pool.iter() - .filter(|(_, state)| matches!(state, AllocationState::Allocated(_))) + pub fn pre_allocated_count(&self) -> usize { + self.allocations + .iter() + .filter(|(_, state)| matches!(state, AllocationState::PreAllocated { .. })) + .count() + } + + /// Get the number of allocated IPs in the pool + pub fn allocated_count(&self) -> usize { + self.allocations + .iter() + .filter(|(_, state)| matches!(state, AllocationState::Allocated { .. })) .count() } /// Get the total pool size - pub async fn total_count(&self) -> usize { - let pool = self.allocations.read().await; - pool.len() + pub fn total_count(&self) -> usize { + self.allocations.len() } /// Clean up stale allocations older than the specified duration /// /// Returns the number of IPs that were freed - pub async fn cleanup_stale(&self, max_age: std::time::Duration) -> usize { - let mut pool = self.allocations.write().await; - let now = SystemTime::now(); + pub fn cleanup_stale(&mut self, max_age: Duration) -> usize { + let now = Instant::now(); let mut freed = 0; - for (_ip, state) in pool.iter_mut() { - if let AllocationState::Allocated(allocated_at) = state - && let Ok(age) = now.duration_since(*allocated_at) - && age > max_age - { - *state = AllocationState::Free; - freed += 1; + for state in self.allocations.values_mut() { + if let AllocationState::PreAllocated { allocated_at, .. } = state { + let age = now.duration_since(*allocated_at); + if age > max_age { + *state = AllocationState::Free; + freed += 1; + } } } if freed > 0 { - tracing::info!("Cleaned up {} stale IP allocations", freed); + tracing::info!("Cleaned up {freed} stale IP allocations"); } freed @@ -234,11 +287,243 @@ impl IpPool { } /// Errors that can occur during IP pool operations -#[derive(Debug, thiserror::Error)] +#[derive(Debug, thiserror::Error, PartialEq, Eq)] pub enum IpPoolError { #[error("No free IP addresses available in pool")] NoFreeIp, + #[error("Attempted to mark an IpPair that is already in used: {ip_pair}")] + AlreadyUsed { ip_pair: IpPair }, + + #[error("Attempted to mark an unknown ip pair: {ip_pair}")] + UnknownIpPair { ip_pair: IpPair }, + #[error("Invalid IP network configuration: {0}")] InvalidNetwork(#[from] ipnetwork::IpNetworkError), } + +#[cfg(test)] +mod tests { + use super::*; + use anyhow::bail; + use mock_instant::thread_local::MockClock; + + // 3 addresses in each pool + fn small_ip_pool() -> IpPool { + IpPool::new( + Ipv4Addr::new(10, 0, 0, 0), + 30, + Ipv6Addr::new(0xfd00, 0, 0, 0, 0, 0, 0, 0), + 126, + ) + .unwrap() + } + + #[test] + fn ip_pool_initial_allocation() -> anyhow::Result<()> { + let base_ipv4_network = Ipv4Addr::new(10, 0, 0, 0); + let base_ipv4_prefix = 24; // 255 addresses + let base_ipv6_network = Ipv6Addr::new(0xfd00, 0, 0, 0, 0, 0, 0, 0); + let base_ipv6_prefix = 112; // 65535 addresses + + // ipv4 pool size < ipv6 pool size + let base_ip_pool = IpPool::new( + base_ipv4_network, + base_ipv4_prefix, + base_ipv6_network, + base_ipv6_prefix, + )?; + let inner = base_ip_pool.allocations; + // minimum of ipv4 and ipv6 allocations + assert_eq!(inner.len(), 255); + + // no ipv4 addresses + let base_ip_pool = IpPool::new(base_ipv4_network, 32, base_ipv6_network, base_ipv6_prefix)?; + let inner = base_ip_pool.allocations; + assert_eq!(inner.len(), 0); + + // no ipv6 addresses + let base_ip_pool = + IpPool::new(base_ipv4_network, base_ipv4_prefix, base_ipv6_network, 128)?; + let inner = base_ip_pool.allocations; + assert_eq!(inner.len(), 0); + + // ipv4 pool size == ipv6 pool size + let base_ip_pool = IpPool::new(base_ipv4_network, 16, base_ipv6_network, base_ipv6_prefix)?; + let inner = base_ip_pool.allocations; + assert_eq!(inner.len(), 65535); + + // ipv4 pool size > ipv6 pool size + let base_ip_pool = IpPool::new(base_ipv4_network, 12, base_ipv6_network, base_ipv6_prefix)?; + let inner = base_ip_pool.allocations; + assert_eq!(inner.len(), 65535); + + Ok(()) + } + + fn ensure_different_allocation(left: IpPair, right: IpPair) -> anyhow::Result<()> { + if left.ipv4 == right.ipv4 || left.ipv6 == right.ipv6 { + bail!("ip allocation overlap") + } + + Ok(()) + } + + #[test] + fn ip_pool_allocation() -> anyhow::Result<()> { + let mut pool = small_ip_pool(); + assert_eq!(pool.allocations.len(), 3); + + let gateway_pair = IpPair { + ipv4: Ipv4Addr::new(10, 0, 0, 0), + ipv6: Ipv6Addr::new(0xfd00, 0, 0, 0, 0, 0, 0, 0), + }; + + assert_eq!(pool.free_count(), 3); + assert_eq!(pool.pre_allocated_count(), 0); + + let allocation1 = pool.pre_allocate()?; + assert_eq!(pool.free_count(), 2); + assert_eq!(pool.pre_allocated_count(), 1); + + let allocation2 = pool.pre_allocate()?; + assert_eq!(pool.free_count(), 1); + assert_eq!(pool.pre_allocated_count(), 2); + + pool.confirm_allocation(allocation1)?; + assert_eq!(pool.free_count(), 1); + assert_eq!(pool.pre_allocated_count(), 1); + assert_eq!(pool.allocated_count(), 1); + + let allocation3 = pool.pre_allocate()?; + assert_eq!(pool.free_count(), 0); + assert_eq!(pool.pre_allocated_count(), 2); + assert_eq!(pool.allocated_count(), 1); + + // make sure each was unique and different from the gateway + ensure_different_allocation(allocation1, allocation2)?; + ensure_different_allocation(allocation1, allocation3)?; + ensure_different_allocation(allocation2, allocation3)?; + + ensure_different_allocation(allocation1, gateway_pair)?; + ensure_different_allocation(allocation2, gateway_pair)?; + ensure_different_allocation(allocation3, gateway_pair)?; + + // allocation 4 will fail as we have run out of addresses + assert_eq!(pool.pre_allocate().unwrap_err(), IpPoolError::NoFreeIp); + + // if pair gets released, it's eligible for allocation again + pool.release(allocation2); + assert_eq!(pool.free_count(), 1); + assert_eq!(pool.pre_allocated_count(), 1); + assert_eq!(pool.allocated_count(), 1); + + let reallocation = pool.pre_allocate()?; + assert_eq!(reallocation, allocation2); + + assert_eq!(pool.free_count(), 0); + assert_eq!(pool.pre_allocated_count(), 2); + assert_eq!(pool.allocated_count(), 1); + + Ok(()) + } + + #[test] + fn ip_pool_mark_used() -> anyhow::Result<()> { + let mut pool = small_ip_pool(); + + let pair1 = IpPair::new( + Ipv4Addr::new(10, 0, 0, 1), + Ipv6Addr::new(0xfd00, 0, 0, 0, 0, 0, 0, 1), + ); + let pair2 = IpPair::new( + Ipv4Addr::new(10, 0, 0, 2), + Ipv6Addr::new(0xfd00, 0, 0, 0, 0, 0, 0, 2), + ); + let pair3 = IpPair::new( + Ipv4Addr::new(10, 0, 0, 3), + Ipv6Addr::new(0xfd00, 0, 0, 0, 0, 0, 0, 3), + ); + + let bad_pair1 = IpPair::new( + Ipv4Addr::new(10, 0, 0, 1), + Ipv6Addr::new(0xfd00, 0, 0, 0, 0, 0, 0, 4), + ); + let bad_pair2 = IpPair::new( + Ipv4Addr::new(10, 0, 0, 4), + Ipv6Addr::new(0xfd00, 0, 0, 0, 0, 0, 0, 1), + ); + + assert!(pool.mark_used(pair1,).is_ok()); + assert_eq!( + pool.mark_used(pair1).unwrap_err(), + IpPoolError::AlreadyUsed { ip_pair: pair1 } + ); + + assert!(pool.mark_used(pair2).is_ok()); + assert!(pool.mark_used(pair3).is_ok()); + + assert_eq!( + pool.mark_used(bad_pair1).unwrap_err(), + IpPoolError::UnknownIpPair { ip_pair: bad_pair1 } + ); + + assert_eq!( + pool.mark_used(bad_pair2,).unwrap_err(), + IpPoolError::UnknownIpPair { ip_pair: bad_pair2 } + ); + + Ok(()) + } + + #[test] + fn ip_pool_cleanup() -> anyhow::Result<()> { + MockClock::set_time(Duration::ZERO); + + let mut pool = small_ip_pool(); + + let age_threshold = Duration::from_secs(1); + + // nothing to cleanup + assert_eq!(pool.cleanup_stale(age_threshold), 0); + + // just allocated + let pair1 = pool.pre_allocate()?; + let pair2 = pool.pre_allocate()?; + assert_eq!(pool.cleanup_stale(age_threshold), 0); + + // advance time to go beyond the allocation threshold + MockClock::advance(Duration::from_millis(1001)); + assert_eq!(pool.cleanup_stale(age_threshold), 2); + + // ensure those pairs are now marked as free + assert!(pool.allocations.get(&pair1).unwrap().is_free()); + assert!(pool.allocations.get(&pair2).unwrap().is_free()); + + pool.pre_allocate()?; + MockClock::advance(Duration::from_millis(500)); + pool.pre_allocate()?; + + assert_eq!(pool.cleanup_stale(age_threshold), 0); + MockClock::advance(Duration::from_millis(501)); + assert_eq!(pool.cleanup_stale(age_threshold), 1); + + MockClock::advance(Duration::from_millis(500)); + assert_eq!(pool.cleanup_stale(age_threshold), 1); + + let mut new_pool = small_ip_pool(); + let pair1 = new_pool.pre_allocate()?; + let pair2 = new_pool.pre_allocate()?; + + // complete allocation for pair2 + new_pool.confirm_allocation(pair2)?; + MockClock::advance(Duration::from_millis(2000)); + + // only pair1 should have got cleaned up + assert_eq!(new_pool.cleanup_stale(age_threshold), 1); + assert!(new_pool.allocations.get(&pair1).unwrap().is_free()); + assert!(!new_pool.allocations.get(&pair2).unwrap().is_free()); + + Ok(()) + } +} diff --git a/common/wireguard/src/lib.rs b/common/wireguard/src/lib.rs index e73da973a2..e64ab3b9e2 100644 --- a/common/wireguard/src/lib.rs +++ b/common/wireguard/src/lib.rs @@ -284,7 +284,7 @@ pub async fn start_wireguard( // Initialize IP pool from configuration info!("Initializing IP pool for WireGuard peer allocation"); - let ip_pool = IpPool::new( + let mut ip_pool = IpPool::new( wireguard_data.inner.config().private_ipv4, wireguard_data.inner.config().private_network_prefix_v4, wireguard_data.inner.config().private_ipv6, @@ -294,7 +294,7 @@ pub async fn start_wireguard( // Mark existing peer IPs as used in the pool for peer in &peers { if let Some(ip_pair) = crate::ip_pool::allocated_ip_pair(peer) { - ip_pool.mark_used(ip_pair).await; + ip_pool.mark_used(ip_pair)?; } } diff --git a/common/wireguard/src/peer_controller/mock.rs b/common/wireguard/src/peer_controller/mock.rs index 89f2821b31..37f9ba1f0a 100644 --- a/common/wireguard/src/peer_controller/mock.rs +++ b/common/wireguard/src/peer_controller/mock.rs @@ -75,6 +75,7 @@ pub enum PeerControlRequestType { ReleaseIpPair { ip_pair: IpPair }, RemovePeer { key: KeyWrapper }, QueryPeer { key: KeyWrapper }, + CheckActivePeer { key: KeyWrapper }, GetClientBandwidthByKey { key: KeyWrapper }, GetClientBandwidthByIp { ip: IpAddr }, GetVerifierByKey { key: KeyWrapper }, @@ -93,6 +94,7 @@ impl PeerControlRequestType { PeerControlRequestType::GetClientBandwidthByIp { .. } => None, PeerControlRequestType::GetVerifierByKey { key } => Some(key.clone()), PeerControlRequestType::GetVerifierByIp { .. } => None, + PeerControlRequestType::CheckActivePeer { key } => Some(key.clone()), } } @@ -107,7 +109,7 @@ impl From<&PeerControlRequest> for PeerControlRequestType { PeerControlRequest::AddPeer { peer, .. } => PeerControlRequestType::AddPeer { public_key: (&peer.public_key).into(), }, - PeerControlRequest::AllocatePeerIpPair { .. } => { + PeerControlRequest::PreAllocateIpPair { .. } => { PeerControlRequestType::AllocatePeerIpPair {} } PeerControlRequest::ReleaseIpPair { ip_pair, .. } => { @@ -131,6 +133,9 @@ impl From<&PeerControlRequest> for PeerControlRequestType { PeerControlRequest::GetVerifierByIp { ip, .. } => { PeerControlRequestType::GetVerifierByIp { ip: *ip } } + PeerControlRequest::CheckActivePeer { key, .. } => { + PeerControlRequestType::CheckActivePeer { key: key.into() } + } } } } @@ -266,7 +271,7 @@ impl MockPeerController { } response_tx.send_downcasted(response.content) } - PeerControlRequest::AllocatePeerIpPair { response_tx, .. } => { + PeerControlRequest::PreAllocateIpPair { response_tx, .. } => { response_tx.send_downcasted(response.content) } PeerControlRequest::ReleaseIpPair { @@ -295,6 +300,9 @@ impl MockPeerController { PeerControlRequest::GetVerifierByIp { response_tx, .. } => { response_tx.send_downcasted(response.content) } + PeerControlRequest::CheckActivePeer { response_tx, .. } => { + response_tx.send_downcasted(response.content) + } } } diff --git a/common/wireguard/src/peer_controller/mod.rs b/common/wireguard/src/peer_controller/mod.rs index e8ba3c4078..9aae772a8f 100644 --- a/common/wireguard/src/peer_controller/mod.rs +++ b/common/wireguard/src/peer_controller/mod.rs @@ -77,7 +77,7 @@ pub enum PeerControlRequest { response_tx: oneshot::Sender, }, /// Attempt to allocate an IP pair from the pool - AllocatePeerIpPair { + PreAllocateIpPair { response_tx: oneshot::Sender, }, /// Attempt to return an IP pair back to the pool @@ -93,6 +93,10 @@ pub enum PeerControlRequest { key: Key, response_tx: oneshot::Sender, }, + CheckActivePeer { + key: Key, + response_tx: oneshot::Sender, + }, GetClientBandwidthByKey { key: Key, response_tx: oneshot::Sender, @@ -118,6 +122,7 @@ pub type AllocatePeerControlResponse = Result; pub type ReleaseIpPairControlResponse = Result<()>; pub type RemovePeerControlResponse = Result<()>; pub type QueryPeerControlResponse = Result>; +pub type CheckActivePeerResponse = Result; pub type GetClientBandwidthControlResponse = Result; pub type QueryVerifierControlResponse = Result>; @@ -216,7 +221,7 @@ impl PeerController { if let Ok(Some(peer)) = self.handle_query_peer_by_key(key).await && let Some(ip_pair) = allocated_ip_pair(&peer) { - self.ip_pool.release(ip_pair).await + self.ip_pool.release(ip_pair) } let ret = self.wg_api.remove_peer(key); @@ -258,6 +263,13 @@ impl PeerController { async fn handle_add_request(&mut self, peer: &Peer) -> Result<()> { nym_metrics::inc!("wg_peer_addition_attempts"); + // confirm ip allocation so that it wouldn't be released for as long as the peer exists + let Some(ip_pair) = allocated_ip_pair(peer) else { + return Err(Error::Internal( + "could not determine ip pair allocated to the peer".to_string(), + )); + }; + // Try to configure WireGuard peer if let Err(e) = self.wg_api.configure_peer(peer) { nym_metrics::inc!("wg_peer_addition_failed"); @@ -289,6 +301,9 @@ impl PeerController { *self.host_information.write().await = host_information; } let public_key = peer.public_key.clone(); + + self.ip_pool.confirm_allocation(ip_pair)?; + tokio::spawn(async move { handle.run().await; debug!("Peer handle shut down for {public_key}"); @@ -302,15 +317,11 @@ impl PeerController { /// /// This only allocates IPs - the caller must handle database storage and /// then call AddPeer with a complete Peer struct. - async fn handle_ip_allocation_request(&mut self) -> Result { + fn handle_ip_allocation_request(&mut self) -> Result { nym_metrics::inc!("wg_ip_allocation_attempts"); // Allocate IP pair from pool - let ip_pair = self - .ip_pool - .allocate() - .await - .map_err(|e| Error::IpPool(e.to_string()))?; + let ip_pair = self.ip_pool.pre_allocate()?; nym_metrics::inc!("wg_ip_allocation_success"); tracing::debug!("Allocated IP pair: {ip_pair}"); @@ -319,8 +330,8 @@ impl PeerController { } /// Return IP pair back to the pool - async fn handle_ip_release(&mut self, ip_pair: IpPair) { - self.ip_pool.release(ip_pair).await + fn handle_ip_release(&mut self, ip_pair: IpPair) { + self.ip_pool.release(ip_pair) } async fn ip_to_key(&self, ip: IpAddr) -> Result> { @@ -359,6 +370,12 @@ impl PeerController { .client_bandwidth()) } + fn check_active_peer(&self, key: Key) -> Result { + // peer is active as long as we still have an entry inside the bandwidth storage manager, + // as it is removed upon peer removal + Ok(self.bw_storage_managers.contains_key(&key)) + } + async fn handle_get_client_bandwidth_by_ip(&self, ip: IpAddr) -> Result { let Some(key) = self.ip_to_key(ip).await? else { return Err(Error::MissingClientKernelEntry(ip.to_string())); @@ -492,16 +509,14 @@ impl PeerController { PeerControlRequest::AddPeer { peer, response_tx } => { response_tx.send(self.handle_add_request(&peer).await).ok(); } - PeerControlRequest::AllocatePeerIpPair { response_tx } => { - response_tx - .send(self.handle_ip_allocation_request().await) - .ok(); + PeerControlRequest::PreAllocateIpPair { response_tx } => { + response_tx.send(self.handle_ip_allocation_request()).ok(); } PeerControlRequest::ReleaseIpPair { response_tx, ip_pair, } => { - self.handle_ip_release(ip_pair).await; + self.handle_ip_release(ip_pair); response_tx.send(Ok(())).ok(); } PeerControlRequest::RemovePeer { key, response_tx } => { @@ -540,6 +555,9 @@ impl PeerController { .send(self.handle_query_verifier_by_ip(ip, *credential).await) .ok(); } + PeerControlRequest::CheckActivePeer { key, response_tx } => { + response_tx.send(self.check_active_peer(key)).ok(); + } } } @@ -558,7 +576,7 @@ impl PeerController { } _ = self.ip_cleanup_interval.next() => { // Periodically cleanup stale IP allocations - let freed = self.ip_pool.cleanup_stale(DEFAULT_IP_STALE_AGE).await; + let freed = self.ip_pool.cleanup_stale(DEFAULT_IP_STALE_AGE); if freed > 0 { nym_metrics::inc_by!("wg_stale_ips_cleaned", freed as u64); info!("Cleaned up {} stale IP allocations", freed); diff --git a/gateway/Cargo.toml b/gateway/Cargo.toml index 8f729975e5..e912053e34 100644 --- a/gateway/Cargo.toml +++ b/gateway/Cargo.toml @@ -89,9 +89,11 @@ bytes = { workspace = true } defguard_wireguard_rs = { workspace = true } [dev-dependencies] +anyhow = { workspace = true } +nym-test-utils = { workspace = true } nym-gateway-storage = { workspace = true, features = ["mock"] } nym-wireguard = { workspace = true, features = ["mock"] } -mock_instant = "0.6.0" +mock_instant = { workspace = true } time = { workspace = true } [lints] diff --git a/gateway/src/node/internal_service_providers/authenticator/mixnet_listener.rs b/gateway/src/node/internal_service_providers/authenticator/mixnet_listener.rs index 3607ff7926..d9591e0090 100644 --- a/gateway/src/node/internal_service_providers/authenticator/mixnet_listener.rs +++ b/gateway/src/node/internal_service_providers/authenticator/mixnet_listener.rs @@ -4,29 +4,15 @@ use crate::node::internal_service_providers::authenticator::{ config::Config, error::AuthenticatorError, seen_credential_cache::SeenCredentialCache, }; -use crate::node::wireguard::PeerManager; -use defguard_wireguard_rs::net::IpAddrMask; -use defguard_wireguard_rs::{host::Peer, key::Key}; +use crate::node::wireguard::{PeerManager, PeerRegistrator}; use futures::StreamExt; -use nym_authenticator_requests::models::BandwidthClaim; use nym_authenticator_requests::traits::UpgradeModeMessage; -use nym_authenticator_requests::{latest, v4::registration::IpPair}; use nym_authenticator_requests::{ - latest::registration::{GatewayClient, PendingRegistrations, PrivateIPs}, request::AuthenticatorRequest, traits::{FinalMessage, InitMessage, QueryBandwidthMessage, TopUpMessage}, v1, v2, v3, v4, v5, v6, AuthenticatorVersion, CURRENT_VERSION, }; -use nym_credential_verification::ecash::traits::EcashManager; use nym_credential_verification::upgrade_mode::UpgradeModeDetails; -use nym_credential_verification::{ - bandwidth_storage_manager::BandwidthStorageManager, BandwidthFlushingBehaviourConfig, - ClientBandwidth, CredentialVerifier, -}; -use nym_credentials_interface::{BandwidthCredential, CredentialSpendingData}; -use nym_crypto::asymmetric::x25519::KeyPair; -use nym_gateway_requests::models::CredentialSpendingRequest; -use nym_gateway_storage::models::PersistedBandwidth; use nym_sdk::mixnet::{ AnonymousSenderTag, InputMessage, MixnetMessageSender, Recipient, TransmissionLane, }; @@ -36,50 +22,28 @@ use nym_task::ShutdownToken; use nym_wireguard::WireguardGatewayData; use nym_wireguard_types::PeerPublicKey; use std::cmp::max; -use std::{ - net::IpAddr, - sync::Arc, - time::{Duration, SystemTime}, -}; -use tokio::sync::RwLock; +use std::time::Duration; use tokio_stream::wrappers::IntervalStream; type AuthenticatorHandleResult = Result<(Vec, Option), AuthenticatorError>; -const DEFAULT_REGISTRATION_TIMEOUT_CHECK: Duration = Duration::from_secs(60); // 1 minute +const DEFAULT_CREDENTIAL_TIMEOUT_CHECK: Duration = Duration::from_secs(60); // 1 minute // we need to be above MINIMUM_REMAINING_BANDWIDTH (500MB) plus we also have to trick the client // its depletion is low enough to not require sending new tickets const DEFAULT_WG_CLIENT_BANDWIDTH_THRESHOLD: i64 = 1024 * 1024 * 1024; -pub(crate) struct RegisteredAndFree { - registration_in_progress: PendingRegistrations, - taken_private_network_ips: PrivateIPs, -} - -impl RegisteredAndFree { - pub(crate) fn new() -> Self { - RegisteredAndFree { - registration_in_progress: Default::default(), - taken_private_network_ips: Default::default(), - } - } -} - pub(crate) struct MixnetListener { // The configuration for the mixnet listener - pub(crate) config: Config, + pub(crate) _config: Config, // The mixnet client that we use to send and receive packets from the mixnet pub(crate) mixnet_client: nym_sdk::mixnet::MixnetClient, - // Registrations awaiting confirmation - pub(crate) registered_and_free: RwLock, - pub(crate) peer_manager: PeerManager, pub(crate) upgrade_mode: UpgradeModeDetails, - pub(crate) ecash_verifier: Arc, + pub(crate) peer_registrator: PeerRegistrator, pub(crate) timeout_check_interval: IntervalStream, @@ -91,18 +55,17 @@ impl MixnetListener { config: Config, wireguard_gateway_data: WireguardGatewayData, mixnet_client: nym_sdk::mixnet::MixnetClient, + peer_registrator: PeerRegistrator, upgrade_mode: UpgradeModeDetails, - ecash_verifier: Arc, ) -> Self { let timeout_check_interval = - IntervalStream::new(tokio::time::interval(DEFAULT_REGISTRATION_TIMEOUT_CHECK)); + IntervalStream::new(tokio::time::interval(DEFAULT_CREDENTIAL_TIMEOUT_CHECK)); MixnetListener { - config, + _config: config, mixnet_client, - registered_and_free: RwLock::new(RegisteredAndFree::new()), peer_manager: PeerManager::new(wireguard_gateway_data), upgrade_mode, - ecash_verifier, + peer_registrator, timeout_check_interval, seen_credential_cache: SeenCredentialCache::new(), } @@ -112,10 +75,6 @@ impl MixnetListener { self.upgrade_mode.enabled() } - fn keypair(&self) -> &Arc { - self.peer_manager.wireguard_gateway_data.keypair() - } - async fn upgrade_mode_bandwidth(&self, peer: PeerPublicKey) -> Result { // if we're undergoing upgrade mode, we don't meter bandwidth, // we simply return MAX of clients current bandwidth and minimum bandwidth before default @@ -129,47 +88,6 @@ impl MixnetListener { )) } - async fn remove_stale_registrations(&self) -> Result<(), AuthenticatorError> { - let mut registered_and_free = self.registered_and_free.write().await; - let registered_values: Vec<_> = registered_and_free - .registration_in_progress - .values() - .cloned() - .collect(); - for reg in registered_values { - let timestamp = registered_and_free - .taken_private_network_ips - .get_mut(®.gateway_data.private_ips) - .ok_or(AuthenticatorError::InternalDataCorruption(format!( - "IPs {} should be present", - reg.gateway_data.private_ips - )))?; - - let duration = SystemTime::now().duration_since(*timestamp).map_err(|_| { - AuthenticatorError::InternalDataCorruption( - "set timestamp shouldn't have been set in the future".to_string(), - ) - })?; - if duration > DEFAULT_REGISTRATION_TIMEOUT_CHECK { - registered_and_free - .registration_in_progress - .remove(®.gateway_data.pub_key()); - registered_and_free - .taken_private_network_ips - .remove(®.gateway_data.private_ips); - self.peer_manager - .release_ip_pair(reg.gateway_data.private_ips.into()) - .await?; - - tracing::debug!( - "Removed stale registration of {}", - reg.gateway_data.pub_key() - ); - } - } - Ok(()) - } - async fn on_initial_request( &mut self, init_message: Box, @@ -177,352 +95,12 @@ impl MixnetListener { request_id: u64, reply_to: Option, ) -> AuthenticatorHandleResult { - let remote_public = init_message.pub_key(); - let nonce: u64 = fastrand::u64(..); - let mut registered_and_free = self.registered_and_free.write().await; - if let Some(registration_data) = registered_and_free - .registration_in_progress - .get(&remote_public) - { - let gateway_data = registration_data.gateway_data.clone(); - let bytes = match AuthenticatorVersion::from(protocol) { - AuthenticatorVersion::V1 => { - v1::response::AuthenticatorResponse::new_pending_registration_success( - v1::registration::RegistrationData { - nonce: registration_data.nonce, - gateway_data: v1::GatewayClient { - pub_key: gateway_data.pub_key, - private_ip: gateway_data.private_ips.ipv4.into(), - mac: v1::ClientMac::new(gateway_data.mac.to_vec()), - }, - wg_port: registration_data.wg_port, - }, - request_id, - reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?, - ) - .to_bytes() - .map_err(AuthenticatorError::response_serialisation)? - } - AuthenticatorVersion::V2 => { - v2::response::AuthenticatorResponse::new_pending_registration_success( - v2::registration::RegistrationData { - nonce: registration_data.nonce, - gateway_data: v2::registration::GatewayClient::new( - self.keypair().private_key(), - remote_public.inner(), - registration_data.gateway_data.private_ips.ipv4.into(), - registration_data.nonce, - ), - wg_port: registration_data.wg_port, - }, - request_id, - reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?, - ) - .to_bytes() - .map_err(AuthenticatorError::response_serialisation)? - } - AuthenticatorVersion::V3 => { - v3::response::AuthenticatorResponse::new_pending_registration_success( - v3::registration::RegistrationData { - nonce: registration_data.nonce, - gateway_data: v3::registration::GatewayClient::new( - self.keypair().private_key(), - remote_public.inner(), - registration_data.gateway_data.private_ips.ipv4.into(), - registration_data.nonce, - ), - wg_port: registration_data.wg_port, - }, - request_id, - reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?, - ) - .to_bytes() - .map_err(AuthenticatorError::response_serialisation)? - } - AuthenticatorVersion::V4 => { - v4::response::AuthenticatorResponse::new_pending_registration_success( - v4::registration::RegistrationData { - nonce: registration_data.nonce, - // convert current to v5 and then v5 to v4 (current as of 28.08.25) - gateway_data: v5::registration::GatewayClient::from( - registration_data.gateway_data.clone(), - ) - .into(), - wg_port: registration_data.wg_port, - }, - request_id, - reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?, - ) - .to_bytes() - .map_err(AuthenticatorError::response_serialisation)? - } - AuthenticatorVersion::V5 => { - v5::response::AuthenticatorResponse::new_pending_registration_success( - v5::registration::RegistrationData { - nonce: registration_data.nonce, - gateway_data: registration_data.gateway_data.clone().into(), - wg_port: registration_data.wg_port, - }, - request_id, - ) - .to_bytes() - .map_err(AuthenticatorError::response_serialisation)? - } - AuthenticatorVersion::V6 => { - v6::response::AuthenticatorResponse::new_pending_registration_success( - v6::registration::RegistrationData { - nonce: registration_data.nonce, - gateway_data: registration_data.gateway_data.clone(), - wg_port: registration_data.wg_port, - }, - request_id, - self.upgrade_mode_enabled(), - ) - .to_bytes() - .map_err(AuthenticatorError::response_serialisation)? - } - AuthenticatorVersion::UNKNOWN => return Err(AuthenticatorError::UnknownVersion), - }; - return Ok((bytes, reply_to)); - } + let response = self + .peer_registrator + .on_initial_authenticator_request(init_message, protocol, request_id, reply_to) + .await?; - let peer = self.peer_manager.query_peer(remote_public).await?; - if let Some(peer) = peer { - let allowed_ipv4 = peer - .allowed_ips - .iter() - .find_map(|ip_mask| match ip_mask.address { - IpAddr::V4(ipv4_addr) => Some(ipv4_addr), - _ => None, - }) - .ok_or(AuthenticatorError::InternalError( - "there should be one private IPv4 in the list".to_string(), - ))?; - let allowed_ipv6 = peer - .allowed_ips - .iter() - .find_map(|ip_mask| match ip_mask.address { - IpAddr::V6(ipv6_addr) => Some(ipv6_addr), - _ => None, - }) - .unwrap_or(IpPair::from(IpAddr::from(allowed_ipv4)).ipv6); - let bytes = match AuthenticatorVersion::from(protocol) { - AuthenticatorVersion::V1 => v1::response::AuthenticatorResponse::new_registered( - v1::registration::RegisteredData { - pub_key: self.keypair().public_key().into(), - private_ip: allowed_ipv4.into(), - wg_port: self.config.authenticator.tunnel_announced_port, - }, - reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?, - request_id, - ) - .to_bytes() - .map_err(AuthenticatorError::response_serialisation)?, - AuthenticatorVersion::V2 => v2::response::AuthenticatorResponse::new_registered( - v2::registration::RegisteredData { - pub_key: self.keypair().public_key().into(), - private_ip: allowed_ipv4.into(), - wg_port: self.config.authenticator.tunnel_announced_port, - }, - reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?, - request_id, - ) - .to_bytes() - .map_err(AuthenticatorError::response_serialisation)?, - AuthenticatorVersion::V3 => v3::response::AuthenticatorResponse::new_registered( - v3::registration::RegisteredData { - pub_key: self.keypair().public_key().into(), - private_ip: allowed_ipv4.into(), - wg_port: self.config.authenticator.tunnel_announced_port, - }, - reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?, - request_id, - ) - .to_bytes() - .map_err(AuthenticatorError::response_serialisation)?, - AuthenticatorVersion::V4 => v4::response::AuthenticatorResponse::new_registered( - v4::registration::RegisteredData { - pub_key: self.keypair().public_key().into(), - private_ips: (allowed_ipv4, allowed_ipv6).into(), - wg_port: self.config.authenticator.tunnel_announced_port, - }, - reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?, - request_id, - ) - .to_bytes() - .map_err(AuthenticatorError::response_serialisation)?, - AuthenticatorVersion::V5 => v5::response::AuthenticatorResponse::new_registered( - v5::registration::RegisteredData { - pub_key: self.keypair().public_key().into(), - private_ips: (allowed_ipv4, allowed_ipv6).into(), - wg_port: self.config.authenticator.tunnel_announced_port, - }, - request_id, - ) - .to_bytes() - .map_err(AuthenticatorError::response_serialisation)?, - AuthenticatorVersion::V6 => v6::response::AuthenticatorResponse::new_registered( - v6::registration::RegisteredData { - pub_key: self.keypair().public_key().into(), - private_ips: (allowed_ipv4, allowed_ipv6).into(), - wg_port: self.config.authenticator.tunnel_announced_port, - }, - request_id, - self.upgrade_mode_enabled(), - ) - .to_bytes() - .map_err(AuthenticatorError::response_serialisation)?, - AuthenticatorVersion::UNKNOWN => return Err(AuthenticatorError::UnknownVersion), - }; - return Ok((bytes, reply_to)); - } - - // mark it as used, even though it's not final - let ip_allocation = self.peer_manager.allocate_peer_ip_pair().await?; - self.registered_and_free - .write() - .await - .taken_private_network_ips - .insert(ip_allocation.into(), SystemTime::now()); - - let gateway_data = GatewayClient::new( - self.keypair().private_key(), - remote_public.inner(), - ip_allocation.into(), - nonce, - ); - let registration_data = latest::registration::RegistrationData { - nonce, - gateway_data: gateway_data.clone(), - wg_port: self.config.authenticator.tunnel_announced_port, - }; - registered_and_free - .registration_in_progress - .insert(remote_public, registration_data.clone()); - let bytes = match AuthenticatorVersion::from(protocol) { - AuthenticatorVersion::V1 => { - v1::response::AuthenticatorResponse::new_pending_registration_success( - v1::registration::RegistrationData { - nonce: registration_data.nonce, - gateway_data: v1::registration::GatewayClient::new( - self.keypair().private_key(), - remote_public.inner(), - ip_allocation.ipv4.into(), - nonce, - ), - wg_port: registration_data.wg_port, - }, - request_id, - reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?, - ) - .to_bytes() - .map_err(AuthenticatorError::response_serialisation)? - } - AuthenticatorVersion::V2 => { - v2::response::AuthenticatorResponse::new_pending_registration_success( - v2::registration::RegistrationData { - nonce: registration_data.nonce, - gateway_data: v2::registration::GatewayClient::new( - self.keypair().private_key(), - remote_public.inner(), - ip_allocation.ipv4.into(), - nonce, - ), - wg_port: registration_data.wg_port, - }, - request_id, - reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?, - ) - .to_bytes() - .map_err(AuthenticatorError::response_serialisation)? - } - AuthenticatorVersion::V3 => { - v3::response::AuthenticatorResponse::new_pending_registration_success( - v3::registration::RegistrationData { - nonce: registration_data.nonce, - gateway_data: v3::registration::GatewayClient::new( - self.keypair().private_key(), - remote_public.inner(), - ip_allocation.ipv4.into(), - nonce, - ), - wg_port: registration_data.wg_port, - }, - request_id, - reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?, - ) - .to_bytes() - .map_err(AuthenticatorError::response_serialisation)? - } - AuthenticatorVersion::V4 => { - v4::response::AuthenticatorResponse::new_pending_registration_success( - v4::registration::RegistrationData { - nonce: registration_data.nonce, - // convert current to v5 and then v5 to v4 (current as of 28.08.25) - gateway_data: v5::registration::GatewayClient::from( - registration_data.gateway_data.clone(), - ) - .into(), - wg_port: registration_data.wg_port, - }, - request_id, - reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?, - ) - .to_bytes() - .map_err(AuthenticatorError::response_serialisation)? - } - AuthenticatorVersion::V5 => { - v5::response::AuthenticatorResponse::new_pending_registration_success( - v5::registration::RegistrationData { - nonce: registration_data.nonce, - gateway_data: registration_data.gateway_data.into(), - wg_port: registration_data.wg_port, - }, - request_id, - ) - .to_bytes() - .map_err(AuthenticatorError::response_serialisation)? - } - AuthenticatorVersion::V6 => { - v6::response::AuthenticatorResponse::new_pending_registration_success( - v6::registration::RegistrationData { - nonce: registration_data.nonce, - gateway_data: registration_data.gateway_data, - wg_port: registration_data.wg_port, - }, - request_id, - self.upgrade_mode_enabled(), - ) - .to_bytes() - .map_err(AuthenticatorError::response_serialisation)? - } - AuthenticatorVersion::UNKNOWN => return Err(AuthenticatorError::UnknownVersion), - }; - - Ok((bytes, reply_to)) - } - - async fn handle_final_credential_claim( - &self, - claim: BandwidthClaim, - client_id: i64, - ) -> Result<(), AuthenticatorError> { - match claim.credential { - BandwidthCredential::ZkNym(zk_nym) => { - // if we got zk-nym, we just try to verify it - credential_verification(self.ecash_verifier.clone(), *zk_nym, client_id).await?; - Ok(()) - } - BandwidthCredential::UpgradeModeJWT { token } => { - // if we're already in the upgrade mode, don't bother validating the token - if self.upgrade_mode_enabled() { - return Ok(()); - } - - self.upgrade_mode.try_enable_via_received_jwt(token).await?; - Ok(()) - } - } + Ok((response.bytes, response.reply_to)) } async fn on_final_request( @@ -532,139 +110,12 @@ impl MixnetListener { request_id: u64, reply_to: Option, ) -> AuthenticatorHandleResult { - let mut registered_and_free = self.registered_and_free.write().await; - let registration_data = registered_and_free - .registration_in_progress - .get(&final_message.gateway_client_pub_key()) - .ok_or(AuthenticatorError::RegistrationNotInProgress)? - .clone(); - - if final_message - .verify(self.keypair().private_key(), registration_data.nonce) - .is_err() - { - return Err(AuthenticatorError::MacVerificationFailure); - } - - let mut peer = Peer::new(Key::new(final_message.gateway_client_pub_key().to_bytes())); - peer.allowed_ips - .push(IpAddrMask::new(final_message.private_ips().ipv4.into(), 32)); - peer.allowed_ips.push(IpAddrMask::new( - final_message.private_ips().ipv6.into(), - 128, - )); - - // ideally credential wouldn't have been required in upgrade mode, - // however, we need some basic information to insert valid wg peer - let Some(credential) = final_message.credential() else { - return Err(AuthenticatorError::NoCredentialReceived); - }; - - let typ = credential.kind; - - let client_id = self - .ecash_verifier - .storage() - .insert_wireguard_peer(&peer, typ.into()) + let response = self + .peer_registrator + .on_final_authenticator_request(final_message, protocol, request_id, reply_to) .await?; - if let Err(err) = self - .handle_final_credential_claim(credential, client_id) - .await - { - self.ecash_verifier - .storage() - .remove_wireguard_peer(&peer.public_key.to_string()) - .await?; - return Err(err); - } - - let public_key = peer.public_key.to_string(); - if let Err(e) = self.peer_manager.add_peer(peer).await { - self.ecash_verifier - .storage() - .remove_wireguard_peer(&public_key) - .await?; - return Err(e.into()); - } - - registered_and_free - .registration_in_progress - .remove(&final_message.gateway_client_pub_key()); - - let bytes = match AuthenticatorVersion::from(protocol) { - AuthenticatorVersion::V1 => v1::response::AuthenticatorResponse::new_registered( - v1::registration::RegisteredData { - pub_key: registration_data.gateway_data.pub_key, - private_ip: registration_data.gateway_data.private_ips.ipv4.into(), - wg_port: registration_data.wg_port, - }, - reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?, - request_id, - ) - .to_bytes() - .map_err(AuthenticatorError::response_serialisation)?, - AuthenticatorVersion::V2 => v2::response::AuthenticatorResponse::new_registered( - v2::registration::RegisteredData { - pub_key: registration_data.gateway_data.pub_key, - private_ip: registration_data.gateway_data.private_ips.ipv4.into(), - wg_port: registration_data.wg_port, - }, - reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?, - request_id, - ) - .to_bytes() - .map_err(AuthenticatorError::response_serialisation)?, - AuthenticatorVersion::V3 => v3::response::AuthenticatorResponse::new_registered( - v3::registration::RegisteredData { - pub_key: registration_data.gateway_data.pub_key, - private_ip: registration_data.gateway_data.private_ips.ipv4.into(), - wg_port: registration_data.wg_port, - }, - reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?, - request_id, - ) - .to_bytes() - .map_err(AuthenticatorError::response_serialisation)?, - AuthenticatorVersion::V4 => v4::response::AuthenticatorResponse::new_registered( - v4::registration::RegisteredData { - pub_key: registration_data.gateway_data.pub_key, - // convert current to v5 and then v5 to v4 (current as of 28.08.25) - private_ips: v5::registration::IpPair::from( - registration_data.gateway_data.private_ips, - ) - .into(), - wg_port: registration_data.wg_port, - }, - reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?, - request_id, - ) - .to_bytes() - .map_err(AuthenticatorError::response_serialisation)?, - AuthenticatorVersion::V5 => v5::response::AuthenticatorResponse::new_registered( - v5::registration::RegisteredData { - pub_key: registration_data.gateway_data.pub_key, - private_ips: registration_data.gateway_data.private_ips.into(), - wg_port: registration_data.wg_port, - }, - request_id, - ) - .to_bytes() - .map_err(AuthenticatorError::response_serialisation)?, - AuthenticatorVersion::V6 => v6::response::AuthenticatorResponse::new_registered( - v6::registration::RegisteredData { - pub_key: registration_data.gateway_data.pub_key, - private_ips: registration_data.gateway_data.private_ips, - wg_port: registration_data.wg_port, - }, - request_id, - self.upgrade_mode_enabled(), - ) - .to_bytes() - .map_err(AuthenticatorError::response_serialisation)?, - AuthenticatorVersion::UNKNOWN => return Err(AuthenticatorError::UnknownVersion), - }; - Ok((bytes, reply_to)) + Ok((response.bytes, response.reply_to)) } async fn on_query_bandwidth_request( @@ -955,9 +406,6 @@ impl MixnetListener { break; }, _ = self.timeout_check_interval.next() => { - if let Err(e) = self.remove_stale_registrations().await { - tracing::error!("Could not clear stale registrations. The registration process might get jammed soon - {e:?}"); - } self.seen_credential_cache.remove_stale(); } msg = self.mixnet_client.next() => { @@ -987,45 +435,6 @@ impl MixnetListener { } } -pub async fn credential_storage_preparation( - ecash_verifier: Arc, - client_id: i64, -) -> Result { - ecash_verifier - .storage() - .create_bandwidth_entry(client_id) - .await?; - let bandwidth = ecash_verifier - .storage() - .get_available_bandwidth(client_id) - .await? - .ok_or(AuthenticatorError::InternalError( - "bandwidth entry should have just been created".to_string(), - ))?; - Ok(bandwidth) -} - -async fn credential_verification( - ecash_verifier: Arc, - credential: CredentialSpendingData, - client_id: i64, -) -> Result { - let bandwidth = credential_storage_preparation(ecash_verifier.clone(), client_id).await?; - let client_bandwidth = ClientBandwidth::new(bandwidth.into()); - let mut verifier = CredentialVerifier::new( - CredentialSpendingRequest::new(credential), - ecash_verifier.clone(), - BandwidthStorageManager::new( - ecash_verifier.storage(), - client_bandwidth, - client_id, - BandwidthFlushingBehaviourConfig::default(), - true, - ), - ); - Ok(verifier.verify().await?) -} - fn deserialize_request( reconstructed: &ReconstructedMessage, ) -> Result { diff --git a/gateway/src/node/internal_service_providers/authenticator/mod.rs b/gateway/src/node/internal_service_providers/authenticator/mod.rs index 95423a66e7..fb50f6ddb0 100644 --- a/gateway/src/node/internal_service_providers/authenticator/mod.rs +++ b/gateway/src/node/internal_service_providers/authenticator/mod.rs @@ -2,13 +2,14 @@ // SPDX-License-Identifier: Apache-2.0 use crate::node::internal_service_providers::authenticator::error::AuthenticatorError; +use crate::node::wireguard::PeerRegistrator; use futures::channel::oneshot; use nym_client_core::{HardcodedTopologyProvider, TopologyProvider}; use nym_credential_verification::upgrade_mode::UpgradeModeDetails; use nym_sdk::{mixnet::Recipient, GatewayTransceiver}; use nym_task::ShutdownTracker; use nym_wireguard::WireguardGatewayData; -use std::{path::Path, sync::Arc}; +use std::path::Path; pub use config::Config; @@ -32,12 +33,12 @@ impl OnStartData { pub struct Authenticator { #[allow(unused)] config: Config, + peer_registrator: PeerRegistrator, upgrade_mode_state: UpgradeModeDetails, wait_for_gateway: bool, custom_topology_provider: Option>, custom_gateway_transceiver: Option>, wireguard_gateway_data: WireguardGatewayData, - ecash_verifier: Arc, shutdown: ShutdownTracker, on_start: Option>, } @@ -45,20 +46,18 @@ pub struct Authenticator { impl Authenticator { pub fn new( config: Config, + peer_registrator: PeerRegistrator, upgrade_mode_state: UpgradeModeDetails, wireguard_gateway_data: WireguardGatewayData, - ecash_verifier: Arc< - dyn nym_credential_verification::ecash::traits::EcashManager + Send + Sync, - >, shutdown: ShutdownTracker, ) -> Self { Self { config, + peer_registrator, upgrade_mode_state, wait_for_gateway: false, custom_topology_provider: None, custom_gateway_transceiver: None, - ecash_verifier, wireguard_gateway_data, shutdown, on_start: None, @@ -134,8 +133,8 @@ impl Authenticator { self.config, self.wireguard_gateway_data, mixnet_client, + self.peer_registrator, self.upgrade_mode_state, - self.ecash_verifier, ); tracing::info!("The address of this client is: {self_address}"); diff --git a/gateway/src/node/internal_service_providers/authenticator/seen_credential_cache.rs b/gateway/src/node/internal_service_providers/authenticator/seen_credential_cache.rs index 0c2253f7c5..e06b66760e 100644 --- a/gateway/src/node/internal_service_providers/authenticator/seen_credential_cache.rs +++ b/gateway/src/node/internal_service_providers/authenticator/seen_credential_cache.rs @@ -2,9 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 #[cfg(test)] -use mock_instant::thread_local::SystemTime; +use mock_instant::thread_local::Instant; #[cfg(not(test))] -use std::time::SystemTime; +use std::time::Instant; use std::{collections::HashMap, time::Duration}; use nym_credentials_interface::CredentialSpendingData; @@ -15,7 +15,7 @@ const SEEN_CREDENTIAL_CACHE_TIME: Duration = Duration::from_secs(60 * 60); // 1 #[derive(Eq, Hash, PartialEq)] struct TimestampedPeerPubKey { peer_pub_key: PeerPublicKey, - timestamp: SystemTime, + timestamp: Instant, } pub(crate) struct SeenCredentialCache { @@ -36,7 +36,7 @@ impl SeenCredentialCache { ) { let value = TimestampedPeerPubKey { peer_pub_key, - timestamp: SystemTime::now(), + timestamp: Instant::now(), }; self.cached_credentials .insert(credential.serial_number_b58(), value); @@ -52,12 +52,9 @@ impl SeenCredentialCache { } pub(crate) fn remove_stale(&mut self) { - let now = SystemTime::now(); + let now = Instant::now(); self.cached_credentials.retain(|_, value| { - let Ok(cache_time) = now.duration_since(value.timestamp) else { - tracing::warn!("Got decreasing consecutive system timestamps"); - return false; - }; + let cache_time = now.duration_since(value.timestamp); cache_time < SEEN_CREDENTIAL_CACHE_TIME }); } @@ -159,30 +156,9 @@ mod test { cache.remove_stale(); assert!(cache.get_peer_pub_key(&credential).is_some()); - MockClock::advance_system_time(SEEN_CREDENTIAL_CACHE_TIME * 2); + MockClock::advance(SEEN_CREDENTIAL_CACHE_TIME * 2); cache.remove_stale(); assert!(cache.get_peer_pub_key(&credential).is_none()); } - - #[test] - fn invalid_time() { - assert!(MockClock::is_thread_local()); - assert!(SystemTime::now().is_thread_local()); - - let mut cache = SeenCredentialCache::new(); - let credential = CredentialSpendingData::try_from_bytes(&CREDENTIAL_BYTES).unwrap(); - let peer_pub_key = PeerPublicKey::from_str(PUB_KEY).unwrap(); - - // set some value for time - MockClock::set_system_time(Duration::from_secs(10)); - cache.insert_credential(credential.clone(), peer_pub_key); - - // then set the time in the past - MockClock::set_system_time(Duration::ZERO); - cache.remove_stale(); - - // invalid time should remove the credential, just in case - assert!(cache.get_peer_pub_key(&credential).is_none()); - } } diff --git a/gateway/src/node/lp_listener/handler.rs b/gateway/src/node/lp_listener/handler.rs index 599e3796ed..c29f9298f1 100644 --- a/gateway/src/node/lp_listener/handler.rs +++ b/gateway/src/node/lp_listener/handler.rs @@ -1241,23 +1241,15 @@ where mod tests { use super::*; use crate::node::lp_listener::{LpConfig, LpDebug}; - use crate::node::wireguard::PeerManager; use crate::node::ActiveClientsStore; use bytes::BytesMut; - use nym_credential_verification::upgrade_mode::{ - UpgradeModeCheckConfig, UpgradeModeCheckRequestSender, UpgradeModeDetails, - }; - use nym_credential_verification::UpgradeModeState; use nym_lp::codec::{parse_lp_packet, serialize_lp_packet}; use nym_lp::message::{ClientHelloData, EncryptedDataPayload, HandshakeData, LpMessage}; use nym_lp::packet::{LpHeader, LpPacket}; use nym_lp::peer::LpLocalPeer; - use nym_wireguard::{PeerControlRequest, WireguardConfig, WireguardGatewayData}; - use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; use std::sync::Arc; use std::time::{SystemTime, UNIX_EPOCH}; use tokio::io::{AsyncRead, AsyncReadExt, AsyncWriteExt}; - use tokio::sync::mpsc::Receiver; // ==================== Test Helpers ==================== /// Create a minimal test state for handler tests @@ -1265,23 +1257,6 @@ mod tests { use nym_crypto::asymmetric::ed25519; use rand::rngs::OsRng; - fn wireguard_data( - keys: Arc, - ) -> (WireguardGatewayData, Receiver) { - // some sensible default values (ports don't matter anyway) - let cfg = WireguardConfig { - bind_address: SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 51822), - private_ipv4: Ipv4Addr::new(10, 1, 0, 1), - private_ipv6: Ipv6Addr::new(0xfc01, 0, 0, 0, 0, 0, 0, 0x1), // fc01::1, - announced_tunnel_port: 51822, - announced_metadata_port: 51830, - private_network_prefix_v4: 16, - private_network_prefix_v6: 112, - }; - - WireguardGatewayData::new(cfg, keys) - } - // Create in-memory storage for testing let storage = nym_gateway_storage::GatewayStorage::init(":memory:", 100) .await @@ -1308,20 +1283,6 @@ mod tests { let id_keys = Arc::new(ed25519::KeyPair::new(&mut OsRng)); let x_keys = Arc::new(id_keys.to_x25519()); - let (wireguard_data, _) = wireguard_data(x_keys.clone()); - - let (um_recheck_tx, _) = futures::channel::mpsc::unbounded(); - - let upgrade_mode_state = UpgradeModeState::new(*id_keys.public_key()); - let upgrade_mode_details = UpgradeModeDetails::new( - UpgradeModeCheckConfig { - // essentially we never want to trigger this in our tests - min_staleness_recheck: Duration::from_nanos(1), - }, - UpgradeModeCheckRequestSender::new(um_recheck_tx), - upgrade_mode_state.clone(), - ); - let lp_peer = LpLocalPeer::new(id_keys, x_keys.clone()).with_kem_psq_key(x_keys); LpHandlerState { @@ -1332,13 +1293,11 @@ mod tests { local_lp_peer: lp_peer, metrics: nym_node_metrics::NymNodeMetrics::default(), active_clients_store: ActiveClientsStore::new(), - upgrade_mode: upgrade_mode_details, outbound_mix_sender: mix_sender, handshake_states: Arc::new(dashmap::DashMap::new()), session_states: Arc::new(dashmap::DashMap::new()), - registrations_in_progress: Default::default(), forward_semaphore, - peer_manager: Arc::new(PeerManager::new(wireguard_data)), + peer_registrator: None, } } diff --git a/gateway/src/node/lp_listener/mod.rs b/gateway/src/node/lp_listener/mod.rs index 0114e28d95..367e16e9bf 100644 --- a/gateway/src/node/lp_listener/mod.rs +++ b/gateway/src/node/lp_listener/mod.rs @@ -68,12 +68,11 @@ // They can be exported via Prometheus format using the metrics endpoint. use crate::error::GatewayError; -use crate::node::lp_listener::registration::RegistrationsInProgress; +use crate::node::wireguard::PeerRegistrator; use crate::node::ActiveClientsStore; use dashmap::DashMap; use nym_config::serde_helpers::de_maybe_port; use nym_credential_verification::ecash::traits::EcashManager; -use nym_credential_verification::upgrade_mode::UpgradeModeDetails; use nym_gateway_storage::GatewayStorage; use nym_lp::state_machine::LpStateMachine; use nym_node_metrics::NymNodeMetrics; @@ -85,7 +84,6 @@ use tokio::net::TcpListener; use tokio::sync::Semaphore; use tracing::*; -use crate::node::wireguard::PeerManager; pub use nym_lp::peer::LpLocalPeer; pub use nym_mixnet_client::forwarder::{ mix_forwarding_channels, MixForwardingReceiver, MixForwardingSender, @@ -184,10 +182,6 @@ pub struct LpDebug { #[serde(with = "humantime_serde")] pub demoted_session_ttl: Duration, - /// Maximum age of in-progress dVPN registration before cleanup (default: 60s) - #[serde(with = "humantime_serde")] - pub pending_registration_ttl: Duration, - /// How often to run the state cleanup task (default: 5 minutes) /// /// The cleanup task scans for and removes stale handshakes and sessions. @@ -257,9 +251,6 @@ impl LpDebug { // 5 minutes - balances memory reclamation with task overhead pub const DEFAULT_STATE_CLEANUP_INTERVAL: Duration = Duration::from_secs(300); - // 1 minute - enough for client to send retrieve credential from its storage and send it across - pub const DEFAULT_PENDING_REGISTRATION_TTL: Duration = Duration::from_secs(60); - // Limits concurrent outbound connections to prevent fd exhaustion pub const DEFAULT_MAX_CONCURRENT_FORWARDS: usize = 1000; } @@ -273,7 +264,6 @@ impl Default for LpDebug { handshake_ttl: Self::DEFAULT_HANDSHAKE_TTL, session_ttl: Self::DEFAULT_SESSION_TTL, demoted_session_ttl: Self::DEFAULT_DEMOTED_SESSION_TTL, - pending_registration_ttl: Self::DEFAULT_PENDING_REGISTRATION_TTL, state_cleanup_interval: Self::DEFAULT_STATE_CLEANUP_INTERVAL, max_concurrent_forwards: Self::DEFAULT_MAX_CONCURRENT_FORWARDS, } @@ -360,12 +350,8 @@ pub struct LpHandlerState { /// Active clients tracking pub active_clients_store: ActiveClientsStore, - /// Current state of the Upgrade Mode as perceived by this gateway - pub upgrade_mode: UpgradeModeDetails, - - /// WireGuard gateway data (contains keypair and config) - /// alongside helpers for managing peers - pub peer_manager: Arc, + /// Handle registering new wireguard peers + pub peer_registrator: Option, /// LP configuration (for timestamp validation, etc.) pub lp_config: LpConfig, @@ -399,10 +385,6 @@ pub struct LpHandlerState { /// to rekey without re-authentication. pub session_states: Arc>>, - /// In-progress dVPN registrations that require additional data (e.g. credentials) - /// to finalise. - pub registrations_in_progress: RegistrationsInProgress, - /// Semaphore limiting concurrent forward connections /// /// Prevents file descriptor exhaustion when forwarding LP packets during @@ -577,31 +559,26 @@ impl LpListener { /// /// The task automatically stops when the shutdown signal is received. fn spawn_state_cleanup_task(&self) -> tokio::task::JoinHandle<()> { - let peer_manager = Arc::clone(&self.handler_state.peer_manager); let handshake_states = Arc::clone(&self.handler_state.handshake_states); let session_states = Arc::clone(&self.handler_state.session_states); - let pending_registrations = self.handler_state.registrations_in_progress.clone(); let dbg_cfg = self.handler_state.lp_config.debug; let handshake_ttl = dbg_cfg.handshake_ttl; let session_ttl = dbg_cfg.session_ttl; let demoted_session_ttl = dbg_cfg.demoted_session_ttl; - let pending_reg_ttl = dbg_cfg.pending_registration_ttl; let interval = dbg_cfg.state_cleanup_interval; let shutdown = self.shutdown.clone_shutdown_token(); let metrics = self.handler_state.metrics.clone(); info!( - "Starting LP state cleanup task (handshake_ttl={}s, session_ttl={}s, demoted_ttl={}s, reg_ttl={}s, interval={}s)", - handshake_ttl.as_secs(), session_ttl.as_secs(), demoted_session_ttl.as_secs(),pending_reg_ttl.as_secs(), interval.as_secs() + "Starting LP state cleanup task (handshake_ttl={}s, session_ttl={}s, demoted_ttl={}s, interval={}s)", + handshake_ttl.as_secs(), session_ttl.as_secs(), demoted_session_ttl.as_secs(), interval.as_secs() ); self.shutdown.try_spawn_named( cleanup_task::cleanup_loop( - peer_manager, handshake_states, session_states, - pending_registrations, dbg_cfg, shutdown, metrics, @@ -619,33 +596,27 @@ impl LpListener { } pub(crate) mod cleanup_task { - use crate::node::lp_listener::registration::RegistrationsInProgress; use crate::node::lp_listener::{LpDebug, TimestampedState}; - use crate::node::wireguard::PeerManager; use dashmap::DashMap; use nym_lp::state_machine::LpStateBare; use nym_lp::LpStateMachine; use nym_metrics::inc_by; use nym_node_metrics::NymNodeMetrics; use std::sync::Arc; - use tracing::{debug, error, info}; + use tracing::{debug, info}; async fn perform_cleanup( - peer_manager: &PeerManager, handshake_states: &Arc>>, session_states: &Arc>>, - registrations_in_progress: &RegistrationsInProgress, cfg: LpDebug, ) { let handshake_ttl = cfg.handshake_ttl; let session_ttl = cfg.session_ttl; let demoted_session_ttl = cfg.demoted_session_ttl; - let pending_registration_ttl = cfg.pending_registration_ttl; let start = std::time::Instant::now(); let mut hs_removed = 0u64; let mut ss_removed = 0u64; - let mut pending_reg_removed = 0u64; let mut demoted_removed = 0u64; // Remove stale handshakes (based on age since creation) @@ -680,33 +651,10 @@ pub(crate) mod cleanup_task { } }); - // Remove stale registrations (based on time since last activity) - let mut reg_guard = registrations_in_progress.lock().await; - let mut stale_registrations = Vec::new(); - for (k, timestamped) in reg_guard.iter() { - if timestamped.age() > pending_registration_ttl { - stale_registrations.push(*k) - } - } - - for to_remove in stale_registrations { - pending_reg_removed += 1; - - // SAFETY: we never dropped the guard and the entry existed - #[allow(clippy::unwrap_used)] - let entry = reg_guard.remove(&to_remove).unwrap(); - if let Err(err) = peer_manager - .release_ip_pair(entry.state.allocated_ip_pair()) - .await - { - error!("failed to release allocated ip pair: {err}") - } - } - - if hs_removed > 0 || ss_removed > 0 || demoted_removed > 0 || pending_reg_removed > 0 { + if hs_removed > 0 || ss_removed > 0 || demoted_removed > 0 { let duration = start.elapsed(); info!( - "LP state cleanup: removed {hs_removed} handshakes, {pending_reg_removed} pending registrations, {ss_removed} sessions, {demoted_removed} demoted (took {:.3}s)", + "LP state cleanup: removed {hs_removed} handshakes, {ss_removed} sessions, {demoted_removed} demoted (took {:.3}s)", duration.as_secs_f64() ); @@ -720,12 +668,6 @@ pub(crate) mod cleanup_task { if demoted_removed > 0 { inc_by!("lp_states_cleanup_demoted_removed", demoted_removed as i64); } - if pending_reg_removed > 0 { - inc_by!( - "lp_states_cleanup_pending_registrations_removed", - pending_reg_removed as i64 - ); - } } } @@ -737,10 +679,8 @@ pub(crate) mod cleanup_task { /// Demoted sessions (ReadOnlyTransport) use shorter TTL since they /// only need to drain in-flight packets after subsession promotion. pub(crate) async fn cleanup_loop( - peer_manager: Arc, handshake_states: Arc>>, session_states: Arc>>, - registrations_in_progress: RegistrationsInProgress, cfg: LpDebug, shutdown: nym_task::ShutdownToken, _metrics: NymNodeMetrics, @@ -757,7 +697,7 @@ pub(crate) mod cleanup_task { break; } _ = cleanup_interval.tick() => { - perform_cleanup(&peer_manager, &handshake_states, &session_states, ®istrations_in_progress, cfg).await; + perform_cleanup(&handshake_states, &session_states, cfg).await; } } } diff --git a/gateway/src/node/lp_listener/registration.rs b/gateway/src/node/lp_listener/registration.rs index 421dc14260..7f2e7b875c 100644 --- a/gateway/src/node/lp_listener/registration.rs +++ b/gateway/src/node/lp_listener/registration.rs @@ -1,22 +1,8 @@ // Copyright 2025 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use super::{LpHandlerState, ReceiverIndex, TimestampedState}; -use crate::error::GatewayError; -use defguard_wireguard_rs::host::Peer; -use defguard_wireguard_rs::key::Key; -use nym_authenticator_requests::models::BandwidthClaim; -use nym_credential_verification::ecash::traits::EcashManager; -use nym_credential_verification::{ - bandwidth_storage_manager::BandwidthStorageManager, BandwidthFlushingBehaviourConfig, - ClientBandwidth, CredentialVerifier, -}; -use nym_credentials_interface::{BandwidthCredential, CredentialSpendingData, TicketType}; -use nym_crypto::asymmetric::encryption::KeyPair; -use nym_gateway_requests::models::CredentialSpendingRequest; -use nym_gateway_storage::models::PersistedBandwidth; -use nym_gateway_storage::traits::BandwidthGatewayStorage; -use nym_metrics::{add_histogram_obs, inc, inc_by}; +use crate::node::lp_listener::{LpHandlerState, ReceiverIndex}; +use nym_metrics::{add_histogram_obs, inc}; use nym_registration_common::dvpn::{ LpDvpnRegistrationFinalisation, LpDvpnRegistrationInitialRequest, LpDvpnRegistrationRequestMessage, LpDvpnRegistrationRequestMessageContent, @@ -24,15 +10,8 @@ use nym_registration_common::dvpn::{ use nym_registration_common::mixnet::LpMixnetRegistrationRequestMessage; use nym_registration_common::{ LpRegistrationRequest, LpRegistrationRequestData, LpRegistrationResponse, RegistrationMode, - RegistrationStatus, WireguardConfiguration, + RegistrationStatus, }; -use nym_wireguard::peer_controller::IpPair; -use nym_wireguard::WireguardConfig; -use nym_wireguard_types::PeerPublicKey; -use std::collections::HashMap; -use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; -use std::sync::Arc; -use tokio::sync::{Mutex, MutexGuard}; use tracing::*; // Histogram buckets for LP registration duration tracking @@ -49,225 +28,28 @@ const LP_REGISTRATION_DURATION_BUCKETS: &[f64] = &[ 30.0, // 30s ]; -// Histogram buckets for WireGuard peer controller channel latency -// Measures time to send request and receive response from peer controller -// Expected: 1ms-100ms for normal operations, up to 2s for slow conditions -const WG_CONTROLLER_LATENCY_BUCKETS: &[f64] = &[ - 0.001, // 1ms - 0.005, // 5ms - 0.01, // 10ms - 0.05, // 50ms - 0.1, // 100ms - 0.25, // 250ms - 0.5, // 500ms - 1.0, // 1s - 2.0, // 2s -]; - -#[derive(Clone, Copy)] -pub struct PendingRegistrationState { - client_id: i64, - peer_key: PeerPublicKey, - ticket_type: TicketType, - wireguard_config: WireguardConfiguration, -} - -impl PendingRegistrationState { - pub(crate) fn allocated_ip_pair(&self) -> IpPair { - IpPair::new( - self.wireguard_config.private_ipv4, - self.wireguard_config.private_ipv6, - ) - } -} - -#[derive(Clone, Default)] -pub struct RegistrationsInProgress { - /// Wrapped in TimestampedState for TTL-based cleanup of stale data. - inner: Arc>>>, -} - -impl RegistrationsInProgress { - pub async fn lock( - &self, - ) -> MutexGuard<'_, HashMap>> { - self.inner.lock().await - } -} - impl LpHandlerState { - fn upgrade_mode_enabled(&self) -> bool { - self.upgrade_mode.enabled() - } - - fn keypair(&self) -> &Arc { - self.peer_manager.wireguard_gateway_data.keypair() - } - - fn wireguard_config(&self) -> WireguardConfig { - self.peer_manager.wireguard_gateway_data.config() - } - - fn successful_dvpn_registration( - &self, - peer_private_ipv4: Ipv4Addr, - peer_private_ipv6: Ipv6Addr, - bandwidth: i64, - ) -> LpRegistrationResponse { - LpRegistrationResponse::success_dvpn( - WireguardConfiguration { - public_key: *self.keypair().public_key(), - psk: None, - // TODO: according to @SW this is most likely very wrong - endpoint: self.wireguard_config().bind_address, - private_ipv4: peer_private_ipv4, - private_ipv6: peer_private_ipv6, - }, - bandwidth, - ) - } - - /// Check if WG peer already registered, return cached response if so. - /// - /// This enables idempotent registration: if a client retries registration - /// with the same WG public key (e.g., after network failure), we return - /// the existing registration data instead of re-processing. This prevents - /// wasting credentials on network issues. - async fn check_existing_dvpn_registration( - &self, - public_key: PeerPublicKey, - ) -> Option { - // Look up existing peer - let Ok(maybe_peer) = self.peer_manager.query_peer(public_key).await else { - return Some(LpRegistrationResponse::error( - "iternal failure: failed to resolve peer information", - RegistrationMode::Dvpn, - )); - }; - - let peer = maybe_peer?; - - // Extract IPv4 and IPv6 from allowed_ips - let mut private_ipv4 = None; - let mut private_ipv6 = None; - for ip_mask in &peer.allowed_ips { - match ip_mask.address { - IpAddr::V4(v4) => private_ipv4 = Some(v4), - IpAddr::V6(v6) => private_ipv6 = Some(v6), - } - if private_ipv4.is_some() && private_ipv6.is_some() { - break; - } - } - - // Incomplete data, treat as new registration - let (Some(private_ipv4), Some(private_ipv6)) = (private_ipv4, private_ipv6) else { - return None; - }; - - // Get current bandwidth - let Ok(bandwidth) = self.peer_manager.query_client_bandwidth(public_key).await else { - return Some(LpRegistrationResponse::error( - "iternal failure: failed to resolve peer bandwidth", - RegistrationMode::Dvpn, - )); - }; - - Some(self.successful_dvpn_registration( - private_ipv4, - private_ipv6, - bandwidth.available().await, - )) - } - - /// In the case of an already registered WG peer, update its PSK. - async fn update_peer_psk(&self, peer: PeerPublicKey, psk: Key) -> Result<(), GatewayError> { - let encoded_psk = psk.to_lower_hex(); - self.storage - .update_peer_psk(&peer.to_string(), Some(&encoded_psk)) - .await?; - - // TODO: do we have to go through a peer manager to also update PSK if a peer is currently active? - // seems like an edge case. maybe we should force disconnect here? - Ok(()) - } - async fn process_dvpn_initial_registration( &self, sender: ReceiverIndex, request: LpDvpnRegistrationInitialRequest, ) -> LpRegistrationResponse { - let wg_key_str = request.wg_public_key.to_string(); - - // check for an existing registration (same WG key already registered) - // This allows clients to retry registration after network failures - // or to re-use gateway without spending additional bandwidth - if let Some(existing_response) = self - .check_existing_dvpn_registration(request.wg_public_key) - .await - { - // if there already exists registration for this client, update the psk and return the peer data - if let Err(err) = self - .update_peer_psk(request.wg_public_key, Key::new(request.psk)) - .await - { - return LpRegistrationResponse::error( - format!("WireGuard peer PSK update failed: {err}"), - RegistrationMode::Dvpn, - ); - } - info!("LP dVPN re-registration for existing peer {wg_key_str} (idempotent)",); - inc!("lp_registration_dvpn_idempotent"); - return existing_response; - } - - // TODO: this could be a source of some issue as we pre-allocate ip before validating credentials - // (but we do the same in the authenticator anyway...) - if let Err(err) = self - .register_wg_peer( - sender, - request.wg_public_key, - request.ticket_type, - Key::new(request.psk), - ) - .await - { + let Some(registrator) = self.peer_registrator.as_ref() else { return LpRegistrationResponse::error( - format!("WireGuard peer IP allocation failed: {err}"), + "dVPN via LP is not enabled on this node", RegistrationMode::Dvpn, ); - } + }; - LpRegistrationResponse::request_dvpn_credential() - } - - // TODO: dedup - async fn handle_final_credential_claim( - &self, - claim: BandwidthClaim, - client_id: i64, - ) -> Result { - match claim.credential { - BandwidthCredential::ZkNym(zk_nym) => { - // if we got zk-nym, we just try to verify it - let bandwidth = - credential_verification(self.ecash_verifier.clone(), *zk_nym, client_id) - .await?; - Ok(bandwidth) - } - BandwidthCredential::UpgradeModeJWT { token } => { - // TODO: move - const UM_BANDWIDTH: i64 = 1024 * 1024 * 1024; - - // if we're already in the upgrade mode, don't bother validating the token - if self.upgrade_mode_enabled() { - return Ok(UM_BANDWIDTH); - } - - self.upgrade_mode.try_enable_via_received_jwt(token).await?; - Ok(UM_BANDWIDTH) - } - } + registrator + .on_initial_lp_request(request, sender) + .await + .unwrap_or_else(|err| { + LpRegistrationResponse::error( + format!("LP registration has failed: {err}"), + RegistrationMode::Dvpn, + ) + }) } async fn process_dvpn_registration_finalisation( @@ -275,63 +57,22 @@ impl LpHandlerState { sender: ReceiverIndex, request: LpDvpnRegistrationFinalisation, ) -> LpRegistrationResponse { - // see if we still have the pending registration - // (e.g. it's illegal for client to request registration and only finalise it, - // for example the next day; we can't keep the data forever) - let Some(pending) = self - .registrations_in_progress - .lock() - .await - .get(&sender) - .map(|pending| pending.state) - else { + let Some(registrator) = self.peer_registrator.as_ref() else { return LpRegistrationResponse::error( - "no pending registration", + "dVPN via LP is not enabled on this node", RegistrationMode::Dvpn, ); }; - if pending.ticket_type != request.credential.kind { - return LpRegistrationResponse::error( - format!( - "inconsistent ticket type. used {} for initial request and {} for finalisation", - pending.ticket_type, request.credential.kind - ), - RegistrationMode::Dvpn, - ); - } - - let client_id = pending.client_id; - - let allocated_bandwidth = match self - .handle_final_credential_claim(request.credential, client_id) + registrator + .on_final_lp_request(request, sender) .await - { - Ok(bandwidth) => bandwidth, - Err(err) => { - // Credential verification failed, remove the peer - warn!("LP credential verification failed for client {client_id}: {err}"); - inc!("lp_registration_dvpn_failed"); - if let Err(remove_err) = self - .storage - .remove_wireguard_peer(&pending.peer_key.to_string()) - .await - { - error!( - "Failed to remove peer after credential verification failure: {remove_err}" - ); - } - self.registrations_in_progress.lock().await.remove(&sender); - return LpRegistrationResponse::error( - format!("Credential verification failed: {err}"), + .unwrap_or_else(|err| { + LpRegistrationResponse::error( + format!("LP registration has failed: {err}"), RegistrationMode::Dvpn, - ); - } - }; - - info!("LP dVPN registration successful (client_id: {client_id})"); - inc!("lp_registration_dvpn_success"); - LpRegistrationResponse::success_dvpn(pending.wireguard_config, allocated_bandwidth) + ) + }) } async fn process_dvpn_registration( @@ -415,163 +156,4 @@ impl LpHandlerState { result } - - /// Register a WireGuard peer and return gateway data along with the client_id - async fn register_wg_peer( - &self, - sender: ReceiverIndex, - peer_key: PeerPublicKey, - ticket_type: nym_credentials_interface::TicketType, - psk: Key, - ) -> Result<(), GatewayError> { - // Allocate IPs from centralized pool managed by PeerController - let defguard_key = Key::new(peer_key.to_bytes()); - - // Request IP allocation from PeerController - let ip_pair = self.peer_manager.allocate_peer_ip_pair().await?; - - let client_ipv4 = ip_pair.ipv4; - let client_ipv6 = ip_pair.ipv6; - - info!("Allocated IPs for peer {peer_key}: {client_ipv4} / {client_ipv6}"); - - // Create WireGuard peer with allocated IPs - let mut peer = Peer::new(defguard_key); - peer.endpoint = None; - peer.allowed_ips = vec![ - format!("{client_ipv4}/32").parse()?, - format!("{client_ipv6}/128").parse()?, - ]; - peer.persistent_keepalive_interval = Some(25); - peer.preshared_key = Some(psk); - - // Store peer in database FIRST (before adding to controller) - // This ensures bandwidth storage exists when controller's generate_bandwidth_manager() is called - let client_id = self - .storage - .insert_wireguard_peer(&peer, ticket_type.into()) - .await - .map_err(|e| { - error!("Failed to store WireGuard peer in database: {}", e); - GatewayError::InternalError(format!("Failed to store peer: {}", e)) - })?; - - // Create bandwidth entry for the client - // This must happen BEFORE AddPeer because generate_bandwidth_manager() expects it to exist - credential_storage_preparation(self.ecash_verifier.clone(), client_id).await?; - - // Now send peer to WireGuard controller and track latency - let controller_start = std::time::Instant::now(); - let result = self.peer_manager.add_peer(peer).await; - - // Record peer controller channel latency - let latency = controller_start.elapsed().as_secs_f64(); - add_histogram_obs!( - "wg_peer_controller_channel_latency_seconds", - latency, - WG_CONTROLLER_LATENCY_BUCKETS - ); - - result?; - - // Get gateway's actual WireGuard public key - let gateway_pubkey = *self.keypair().public_key(); - - // Get gateway's WireGuard endpoint from config - let gateway_endpoint = self.wireguard_config().bind_address; - self.registrations_in_progress.lock().await.insert( - sender, - TimestampedState::new(PendingRegistrationState { - client_id, - peer_key, - ticket_type, - wireguard_config: WireguardConfiguration { - public_key: gateway_pubkey, - psk: None, - endpoint: gateway_endpoint, - private_ipv4: client_ipv4, - private_ipv6: client_ipv6, - }, - }), - ); - Ok(()) - } -} - -// TODO: dedup -/// Prepare bandwidth storage for a client -async fn credential_storage_preparation( - ecash_verifier: Arc, - client_id: i64, -) -> Result { - // Check if bandwidth entry already exists (idempotent) - let existing_bandwidth = ecash_verifier - .storage() - .get_available_bandwidth(client_id) - .await?; - - // Only create if it doesn't exist - if existing_bandwidth.is_none() { - ecash_verifier - .storage() - .create_bandwidth_entry(client_id) - .await?; - } - - let bandwidth = ecash_verifier - .storage() - .get_available_bandwidth(client_id) - .await? - .ok_or_else(|| GatewayError::InternalError("bandwidth entry should exist".to_string()))?; - Ok(bandwidth) -} - -// TODO: dedup -/// Verify credential and allocate bandwidth using CredentialVerifier -async fn credential_verification( - ecash_verifier: Arc, - credential: CredentialSpendingData, - client_id: i64, -) -> Result { - let bandwidth = credential_storage_preparation(ecash_verifier.clone(), client_id).await?; - let client_bandwidth = ClientBandwidth::new(bandwidth.into()); - let mut verifier = CredentialVerifier::new( - CredentialSpendingRequest::new(credential), - ecash_verifier.clone(), - BandwidthStorageManager::new( - ecash_verifier.storage(), - client_bandwidth, - client_id, - BandwidthFlushingBehaviourConfig::default(), - true, - ), - ); - - // Track credential verification attempts - inc!("lp_credential_verification_attempts"); - - // For mock ecash mode (local testing), skip cryptographic verification - // and just return a dummy bandwidth value since we don't have blockchain access - let allocated = if ecash_verifier.is_mock() { - // Return a reasonable test bandwidth value (e.g., 1GB in bytes) - const MOCK_BANDWIDTH: i64 = 1024 * 1024 * 1024; - inc!("lp_credential_verification_success"); - inc_by!("lp_bandwidth_allocated_bytes_total", MOCK_BANDWIDTH); - Ok::(MOCK_BANDWIDTH) - } else { - match verifier.verify().await { - Ok(allocated) => { - inc!("lp_credential_verification_success"); - // Track allocated bandwidth - inc_by!("lp_bandwidth_allocated_bytes_total", allocated); - Ok(allocated) - } - Err(e) => { - inc!("lp_credential_verification_failed"); - Err(e.into()) - } - } - }?; - - Ok(allocated) } diff --git a/gateway/src/node/mod.rs b/gateway/src/node/mod.rs index a8a613f2ec..d8376289a3 100644 --- a/gateway/src/node/mod.rs +++ b/gateway/src/node/mod.rs @@ -38,7 +38,7 @@ use tracing::*; use zeroize::Zeroizing; pub use crate::node::upgrade_mode::watcher::UpgradeModeWatcher; -use crate::node::wireguard::PeerManager; +use crate::node::wireguard::{PeerManager, PeerRegistrator}; pub use client_handling::active_clients::ActiveClientsStore; pub use lp_listener::LpConfig; pub use nym_credential_verification::upgrade_mode::UpgradeModeCheckRequestSender; @@ -299,6 +299,22 @@ impl GatewayTasksBuilder { } } + pub async fn build_peer_registrator( + &mut self, + upgrade_mode_details: UpgradeModeDetails, + ) -> Result, GatewayError> { + let Some(wireguard_data) = &self.wireguard_data else { + return Ok(None); + }; + + let peer_manager = PeerManager::new(wireguard_data.inner.clone()); + Ok(Some(PeerRegistrator::new( + self.ecash_manager().await?, + peer_manager, + upgrade_mode_details, + ))) + } + pub async fn build_websocket_listener( &mut self, active_clients_store: ActiveClientsStore, @@ -330,19 +346,9 @@ impl GatewayTasksBuilder { pub async fn build_lp_listener( &mut self, - upgrade_mode_common_state: UpgradeModeDetails, + peer_registrator: Option, active_clients_store: ActiveClientsStore, ) -> Result { - // Get WireGuard peer controller if available - let Some(wireguard_data) = &self.wireguard_data else { - return Err(GatewayError::InternalWireguardError( - "wireguard not set".to_string(), - )); - }; - - // TODO: combine this `PeerManager` with the one used within the authenticator - let peer_manager = Arc::new(PeerManager::new(wireguard_data.inner.clone())); - let handler_state = lp_listener::LpHandlerState { ecash_verifier: self.ecash_manager().await?, storage: self.storage.clone(), @@ -353,13 +359,11 @@ impl GatewayTasksBuilder { .with_kem_psq_key(self.kem_psq_keys.clone()), metrics: self.metrics.clone(), active_clients_store, - upgrade_mode: upgrade_mode_common_state, - peer_manager, + peer_registrator, lp_config: self.config.lp, outbound_mix_sender: self.mix_packet_sender.clone(), handshake_states: Arc::new(dashmap::DashMap::new()), session_states: Arc::new(dashmap::DashMap::new()), - registrations_in_progress: Default::default(), forward_semaphore: Arc::new(Semaphore::new( self.config.lp.debug.max_concurrent_forwards, )), @@ -495,11 +499,10 @@ impl GatewayTasksBuilder { pub async fn build_wireguard_authenticator( &mut self, + peer_registrator: PeerRegistrator, upgrade_mode_common: UpgradeModeDetails, topology_provider: Box, ) -> Result, GatewayError> { - let ecash_manager = self.ecash_manager().await?; - let Some(opts) = &self.authenticator_opts else { return Err(GatewayError::UnspecifiedAuthenticatorConfig); }; @@ -519,9 +522,9 @@ impl GatewayTasksBuilder { let mut authenticator_server = Authenticator::new( opts.config.clone(), + peer_registrator, upgrade_mode_common, wireguard_data.inner.clone(), - ecash_manager, self.shutdown_tracker.clone(), ) .with_custom_gateway_transceiver(transceiver) diff --git a/gateway/src/node/wireguard/error.rs b/gateway/src/node/wireguard/error.rs index de0a6aaa9f..3b32f12257 100644 --- a/gateway/src/node/wireguard/error.rs +++ b/gateway/src/node/wireguard/error.rs @@ -1,6 +1,7 @@ // Copyright 2026 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use nym_credential_verification::upgrade_mode::UpgradeModeEnableError; use thiserror::Error; #[derive(Debug, Error)] @@ -10,10 +11,48 @@ pub enum GatewayWireguardError { #[error("peers can't be interacted with anymore")] PeerInteractionStopped, + + #[error("registration is not in progress for the provided peer key")] + RegistrationNotInProgress, + + #[error("missing reply_to for old client")] + MissingReplyToForOldClient, + + #[error("unknown version number")] + UnknownAuthenticatorVersion, + + #[error("unsupported authenticator version")] + UnsupportedAuthenticatorVersion, + + #[error("mac does not verify")] + AuthenticatorMacVerificationFailure, + + #[error("no credential received")] + MissingAuthenticatorCredential, + + #[error(transparent)] + UpgradeModeEnable(#[from] UpgradeModeEnableError), + + #[error("credential verification failed: {0}")] + CredentialVerificationError(#[from] nym_credential_verification::Error), + + #[error(transparent)] + GatewayStorageError(#[from] nym_gateway_storage::error::GatewayStorageError), + + #[error("failed to serialise authenticator response packet: {source}")] + AuthenticatorResponseSerialisationFailure { source: Box }, } impl GatewayWireguardError { pub fn internal(message: impl Into) -> Self { GatewayWireguardError::InternalError(message.into()) } + + pub fn authenticator_response_serialisation( + source: impl Into>, + ) -> Self { + GatewayWireguardError::AuthenticatorResponseSerialisationFailure { + source: source.into(), + } + } } diff --git a/gateway/src/node/wireguard/mod.rs b/gateway/src/node/wireguard/mod.rs index 7c9a02b8e1..b65654a9d1 100644 --- a/gateway/src/node/wireguard/mod.rs +++ b/gateway/src/node/wireguard/mod.rs @@ -2,7 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 pub mod error; +pub mod new_peer_registration; pub mod peer_manager; pub use error::GatewayWireguardError; +pub use new_peer_registration::PeerRegistrator; pub use peer_manager::PeerManager; diff --git a/gateway/src/node/wireguard/new_peer_registration/authenticator.rs b/gateway/src/node/wireguard/new_peer_registration/authenticator.rs new file mode 100644 index 0000000000..45da0e7ae4 --- /dev/null +++ b/gateway/src/node/wireguard/new_peer_registration/authenticator.rs @@ -0,0 +1,157 @@ +// Copyright 2026 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::node::wireguard::new_peer_registration::helpers::build_final_authenticator_response; +use crate::node::wireguard::new_peer_registration::pending::{ + PendingRegistration, PendingRegistrationData, +}; +use crate::node::wireguard::{GatewayWireguardError, PeerRegistrator}; +use defguard_wireguard_rs::host::Peer; +use nym_authenticator_requests::authenticator_ipv4_to_ipv6; +use nym_authenticator_requests::response::SerialisedResponse; +use nym_registration_common::WireguardRegistrationData; +use nym_sdk::mixnet::Recipient; +use nym_service_provider_requests_common::Protocol; +use nym_wireguard::peer_controller::IpPair; +use nym_wireguard_types::PeerPublicKey; +use std::net::IpAddr; +use std::time::Instant; + +impl PeerRegistrator { + fn authenticator_peer_to_final_response( + &self, + peer: Peer, + protocol: Protocol, + request_id: u64, + reply_to: Option, + ) -> Result { + let allowed_ipv4 = peer + .allowed_ips + .iter() + .find_map(|ip_mask| match ip_mask.address { + IpAddr::V4(ipv4_addr) => Some(ipv4_addr), + _ => None, + }) + .ok_or(GatewayWireguardError::internal( + "there should be one private IPv4 in the list", + ))?; + let allowed_ipv6 = peer + .allowed_ips + .iter() + .find_map(|ip_mask| match ip_mask.address { + IpAddr::V6(ipv6_addr) => Some(ipv6_addr), + _ => None, + }) + .unwrap_or(authenticator_ipv4_to_ipv6(allowed_ipv4)); + + let ip_allocation = IpPair::new(allowed_ipv4, allowed_ipv6); + let wg_port = self.wg_port(); + let local_pub_key = (*self.keypair().public_key()).into(); + let upgrade_mode_enabled = self.upgrade_mode_enabled(); + + build_final_authenticator_response( + ip_allocation, + wg_port, + local_pub_key, + upgrade_mode_enabled, + request_id, + protocol.into(), + reply_to, + ) + } + + pub(super) async fn check_pending_authenticator_registration( + &self, + protocol: Protocol, + request_id: u64, + remote_public: PeerPublicKey, + reply_to: Option, + ) -> Result, GatewayWireguardError> { + let Some(pending_registration) = self + .pending_registrations + .check_authenticator(&remote_public) + .await + else { + return Ok(None); + }; + + Ok(Some( + pending_registration.to_pending_authenticator_response( + self.keypair().private_key(), + self.upgrade_mode_enabled(), + request_id, + protocol.into(), + reply_to, + )?, + )) + } + + pub(super) async fn check_existing_authenticator_peer( + &self, + protocol: Protocol, + request_id: u64, + remote_public: PeerPublicKey, + reply_to: Option, + ) -> Result, GatewayWireguardError> { + let Some(peer) = self.peer_manager.query_peer(remote_public).await? else { + return Ok(None); + }; + Ok(Some(self.authenticator_peer_to_final_response( + peer, protocol, request_id, reply_to, + )?)) + } + + pub(super) fn new_pending_authenticator( + &self, + peer: PeerPublicKey, + ip_allocation: IpPair, + ) -> PendingRegistration { + let nonce: u64 = fastrand::u64(..); + + PendingRegistration { + requested_on: Instant::now(), + data: PendingRegistrationData { + nonce, + peer_key: peer, + psk: None, + wireguard_config: WireguardRegistrationData { + public_key: *self.keypair().public_key(), + port: self.wg_port(), + private_ipv4: ip_allocation.ipv4, + private_ipv6: ip_allocation.ipv6, + }, + }, + } + } + + pub(super) async fn process_fresh_initial_authenticator_registration( + &self, + protocol: Protocol, + request_id: u64, + remote_public: PeerPublicKey, + reply_to: Option, + ) -> Result { + // 1. allocate ip pair + let ip_allocation = self.peer_manager.preallocate_peer_ip_pair().await?; + + let pending = self.new_pending_authenticator(remote_public, ip_allocation); + + // 2. construct response + let response = pending.to_pending_authenticator_response( + self.keypair().private_key(), + self.upgrade_mode_enabled(), + request_id, + protocol.into(), + reply_to, + )?; + + // 3. insert pending data into cache + self.pending_registrations + .authenticator + .write() + .await + .insert(remote_public, pending); + + Ok(response) + } +} diff --git a/gateway/src/node/wireguard/new_peer_registration/helpers.rs b/gateway/src/node/wireguard/new_peer_registration/helpers.rs new file mode 100644 index 0000000000..c301d25c76 --- /dev/null +++ b/gateway/src/node/wireguard/new_peer_registration/helpers.rs @@ -0,0 +1,209 @@ +// Copyright 2026 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::node::wireguard::GatewayWireguardError; +use nym_authenticator_requests::response::SerialisedResponse; +use nym_authenticator_requests::{v1, v2, v3, v4, v5, v6, AuthenticatorVersion}; +use nym_crypto::asymmetric::x25519; +use nym_sdk::mixnet::Recipient; +use nym_wireguard::ip_pool::IpPair; +use nym_wireguard_types::PeerPublicKey; + +#[allow(clippy::too_many_arguments)] +pub(crate) fn build_pending_authenticator_response( + ip_allocation: IpPair, + wg_port: u16, + local_key: &x25519::PrivateKey, + peer_key: PeerPublicKey, + upgrade_mode_enabled: bool, + nonce: u64, + request_id: u64, + version: AuthenticatorVersion, + reply_to: Option, +) -> Result { + let private_ipv4 = ip_allocation.ipv4; + let private_ipv6 = ip_allocation.ipv6; + + let bytes = match version { + AuthenticatorVersion::V1 => Err(GatewayWireguardError::UnsupportedAuthenticatorVersion), + AuthenticatorVersion::V2 => { + v2::response::AuthenticatorResponse::new_pending_registration_success( + v2::registration::RegistrationData { + nonce, + gateway_data: v2::registration::GatewayClient::new( + local_key, + peer_key.inner(), + private_ipv4.into(), + nonce, + ), + wg_port, + }, + request_id, + reply_to.ok_or(GatewayWireguardError::MissingReplyToForOldClient)?, + ) + .to_bytes() + .map_err(GatewayWireguardError::authenticator_response_serialisation) + } + AuthenticatorVersion::V3 => { + v3::response::AuthenticatorResponse::new_pending_registration_success( + v3::registration::RegistrationData { + nonce, + gateway_data: v3::registration::GatewayClient::new( + local_key, + peer_key.inner(), + private_ipv4.into(), + nonce, + ), + wg_port, + }, + request_id, + reply_to.ok_or(GatewayWireguardError::MissingReplyToForOldClient)?, + ) + .to_bytes() + .map_err(GatewayWireguardError::authenticator_response_serialisation) + } + AuthenticatorVersion::V4 => { + v4::response::AuthenticatorResponse::new_pending_registration_success( + v4::registration::RegistrationData { + nonce, + gateway_data: v4::registration::GatewayClient::new( + local_key, + peer_key.inner(), + v4::registration::IpPair::new(private_ipv4, private_ipv6), + nonce, + ), + wg_port, + }, + request_id, + reply_to.ok_or(GatewayWireguardError::MissingReplyToForOldClient)?, + ) + .to_bytes() + .map_err(GatewayWireguardError::authenticator_response_serialisation) + } + AuthenticatorVersion::V5 => { + v5::response::AuthenticatorResponse::new_pending_registration_success( + v5::registration::RegistrationData { + nonce, + gateway_data: v5::registration::GatewayClient::new( + local_key, + peer_key.inner(), + v5::registration::IpPair::new(private_ipv4, private_ipv6), + nonce, + ), + wg_port, + }, + request_id, + ) + .to_bytes() + .map_err(GatewayWireguardError::authenticator_response_serialisation) + } + AuthenticatorVersion::V6 => { + v6::response::AuthenticatorResponse::new_pending_registration_success( + v6::registration::RegistrationData { + nonce, + gateway_data: v6::registration::GatewayClient::new( + local_key, + peer_key.inner(), + v6::registration::IpPair::new(private_ipv4, private_ipv6), + nonce, + ), + wg_port, + }, + request_id, + upgrade_mode_enabled, + ) + .to_bytes() + .map_err(GatewayWireguardError::authenticator_response_serialisation) + } + AuthenticatorVersion::UNKNOWN => { + return Err(GatewayWireguardError::UnknownAuthenticatorVersion) + } + }?; + + Ok(nym_authenticator_requests::response::SerialisedResponse::new(bytes, reply_to)) +} + +pub(crate) fn build_final_authenticator_response( + ip_allocation: IpPair, + wg_port: u16, + pub_key: PeerPublicKey, + upgrade_mode_enabled: bool, + request_id: u64, + version: AuthenticatorVersion, + reply_to: Option, +) -> Result { + let private_ipv4 = ip_allocation.ipv4; + let private_ipv6 = ip_allocation.ipv6; + + let bytes = match version { + AuthenticatorVersion::V1 => v1::response::AuthenticatorResponse::new_registered( + v1::registration::RegisteredData { + pub_key, + private_ip: private_ipv4.into(), + wg_port, + }, + reply_to.ok_or(GatewayWireguardError::MissingReplyToForOldClient)?, + request_id, + ) + .to_bytes() + .map_err(GatewayWireguardError::authenticator_response_serialisation)?, + AuthenticatorVersion::V2 => v2::response::AuthenticatorResponse::new_registered( + v2::registration::RegisteredData { + pub_key, + private_ip: private_ipv4.into(), + wg_port, + }, + reply_to.ok_or(GatewayWireguardError::MissingReplyToForOldClient)?, + request_id, + ) + .to_bytes() + .map_err(GatewayWireguardError::authenticator_response_serialisation)?, + AuthenticatorVersion::V3 => v3::response::AuthenticatorResponse::new_registered( + v3::registration::RegisteredData { + pub_key, + private_ip: private_ipv4.into(), + wg_port, + }, + reply_to.ok_or(GatewayWireguardError::MissingReplyToForOldClient)?, + request_id, + ) + .to_bytes() + .map_err(GatewayWireguardError::authenticator_response_serialisation)?, + AuthenticatorVersion::V4 => v4::response::AuthenticatorResponse::new_registered( + v4::registration::RegisteredData { + pub_key, + private_ips: v4::registration::IpPair::new(private_ipv4, private_ipv6), + wg_port, + }, + reply_to.ok_or(GatewayWireguardError::MissingReplyToForOldClient)?, + request_id, + ) + .to_bytes() + .map_err(GatewayWireguardError::authenticator_response_serialisation)?, + AuthenticatorVersion::V5 => v5::response::AuthenticatorResponse::new_registered( + v5::registration::RegisteredData { + pub_key, + private_ips: v5::registration::IpPair::new(private_ipv4, private_ipv6), + wg_port, + }, + request_id, + ) + .to_bytes() + .map_err(GatewayWireguardError::authenticator_response_serialisation)?, + AuthenticatorVersion::V6 => v6::response::AuthenticatorResponse::new_registered( + v6::registration::RegisteredData { + pub_key, + private_ips: v6::registration::IpPair::new(private_ipv4, private_ipv6), + wg_port, + }, + request_id, + upgrade_mode_enabled, + ) + .to_bytes() + .map_err(GatewayWireguardError::authenticator_response_serialisation)?, + AuthenticatorVersion::UNKNOWN => { + return Err(GatewayWireguardError::UnknownAuthenticatorVersion) + } + }; + Ok(SerialisedResponse::new(bytes, reply_to)) +} diff --git a/gateway/src/node/wireguard/new_peer_registration/lp.rs b/gateway/src/node/wireguard/new_peer_registration/lp.rs new file mode 100644 index 0000000000..dcc505ab94 --- /dev/null +++ b/gateway/src/node/wireguard/new_peer_registration/lp.rs @@ -0,0 +1,128 @@ +// Copyright 2026 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::node::lp_listener::ReceiverIndex; +use crate::node::wireguard::new_peer_registration::pending::{ + PendingRegistration, PendingRegistrationData, +}; +use crate::node::wireguard::{GatewayWireguardError, PeerRegistrator}; +use defguard_wireguard_rs::host::Peer; +use defguard_wireguard_rs::key::Key; +use nym_registration_common::{LpRegistrationResponse, WireguardRegistrationData}; +use nym_wireguard::ip_pool::{allocated_ip_pair, IpPair}; +use nym_wireguard_types::PeerPublicKey; +use std::time::Instant; + +impl PeerRegistrator { + /// In the case of an already registered WG peer, update its PSK. + pub(super) async fn update_peer_psk( + &self, + peer: PeerPublicKey, + psk: Key, + ) -> Result<(), GatewayWireguardError> { + // 1. check if the peer is currently being handled + if self.peer_manager.check_active_peer(peer).await? { + // 2. if so, force disconnect it (as we're handling new request from the same peer) + self.peer_manager.remove_peer(peer).await?; + } + + // 3. update the on-disk PSK + let encoded_psk = psk.to_lower_hex(); + self.ecash_verifier + .storage() + .update_peer_psk(&peer.to_string(), Some(&encoded_psk)) + .await?; + + Ok(()) + } + + fn lp_peer_to_final_response( + &self, + peer: Peer, + ) -> Result, GatewayWireguardError> { + // Incomplete data, treat as new registration + let Some(allocated_ips) = allocated_ip_pair(&peer) else { + return Ok(None); + }; + + Ok(Some(LpRegistrationResponse::success_dvpn( + WireguardRegistrationData { + public_key: *self.keypair().public_key(), + port: self.wg_port(), + private_ipv4: allocated_ips.ipv4, + private_ipv6: allocated_ips.ipv6, + }, + self.upgrade_mode_enabled(), + ))) + } + + pub(super) async fn check_pending_lp_registration( + &self, + sender: ReceiverIndex, + ) -> Result, GatewayWireguardError> { + let Some(pending_registration) = self.pending_registrations.check_lp(sender).await else { + return Ok(None); + }; + + Ok(Some(pending_registration.to_pending_lp_response())) + } + + pub(super) async fn check_existing_lp_peer( + &self, + remote_public: PeerPublicKey, + ) -> Result, GatewayWireguardError> { + let Some(peer) = self.peer_manager.query_peer(remote_public).await? else { + return Ok(None); + }; + + self.lp_peer_to_final_response(peer) + } + + pub(super) fn new_pending_lp( + &self, + peer: PeerPublicKey, + psk: Key, + ip_allocation: IpPair, + ) -> PendingRegistration { + let nonce: u64 = fastrand::u64(..); + + PendingRegistration { + requested_on: Instant::now(), + data: PendingRegistrationData { + nonce, + peer_key: peer, + psk: Some(psk), + wireguard_config: WireguardRegistrationData { + public_key: *self.keypair().public_key(), + port: self.wg_port(), + private_ipv4: ip_allocation.ipv4, + private_ipv6: ip_allocation.ipv6, + }, + }, + } + } + + pub(super) async fn process_fresh_initial_lp_registration( + &self, + sender: ReceiverIndex, + remote_public: PeerPublicKey, + psk: Key, + ) -> Result { + // 1. allocate ip pair + let ip_allocation = self.peer_manager.preallocate_peer_ip_pair().await?; + + let pending = self.new_pending_lp(remote_public, psk, ip_allocation); + + // 2. construct response + let response = pending.to_pending_lp_response(); + + // 3. insert pending data into cache + self.pending_registrations + .lp + .write() + .await + .insert(sender, pending); + + Ok(response) + } +} diff --git a/gateway/src/node/wireguard/new_peer_registration/mod.rs b/gateway/src/node/wireguard/new_peer_registration/mod.rs new file mode 100644 index 0000000000..43dcce7bb0 --- /dev/null +++ b/gateway/src/node/wireguard/new_peer_registration/mod.rs @@ -0,0 +1,391 @@ +// Copyright 2026 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +//! Unification of Nym registration flow +//! In general the registration has the following structure: +//! 1. Initial request message is received +//! 1.1. We check if the peer has already registered before -> if so, we returned the past information +//! 1.2. We check if the peer already has a pending registration -> if so, we return the past information +//! 1.3. We pre-allocated [`nym_wireguard::ip_pool::IpPair`] and save time-sensitive pending registration. +//! If it does not complete within specified time interval, the information is going to get removed. +//! 2. Finalisation request message is received, where credential has to be attached is verified. +//! Upon successful completion, pending registration is transformed into a properly inserted peer. + +use crate::node::lp_listener::ReceiverIndex; +use crate::node::wireguard::new_peer_registration::pending::{ + PendingRegistration, PendingRegistrations, +}; +use crate::node::wireguard::{GatewayWireguardError, PeerManager}; +use defguard_wireguard_rs::host::Peer; +use defguard_wireguard_rs::key::Key; +use defguard_wireguard_rs::net::IpAddrMask; +use nym_authenticator_requests::models::BandwidthClaim; +use nym_authenticator_requests::response::SerialisedResponse; +use nym_authenticator_requests::traits::{FinalMessage, InitMessage}; +use nym_credential_verification::bandwidth_storage_manager::BandwidthStorageManager; +use nym_credential_verification::ecash::traits::EcashManager; +use nym_credential_verification::upgrade_mode::UpgradeModeDetails; +use nym_credential_verification::{ + BandwidthFlushingBehaviourConfig, ClientBandwidth, CredentialVerifier, +}; +use nym_credentials_interface::{BandwidthCredential, CredentialSpendingData}; +use nym_crypto::asymmetric::x25519; +use nym_gateway_requests::models::CredentialSpendingRequest; +use nym_gateway_storage::models::PersistedBandwidth; +use nym_registration_common::dvpn::{ + LpDvpnRegistrationFinalisation, LpDvpnRegistrationInitialRequest, +}; +use nym_registration_common::LpRegistrationResponse; +use nym_sdk::mixnet::Recipient; +use nym_service_provider_requests_common::Protocol; +use nym_task::ShutdownToken; +use nym_wireguard::WireguardConfig; +use std::sync::Arc; +use std::time::Duration; +use tokio::time::{interval_at, Instant}; +use tracing::trace; + +mod authenticator; +mod helpers; +mod lp; +mod pending; + +#[derive(Clone)] +pub struct PeerRegistrator { + /// Handle for the structure managing verification of the ecash credentials for the bandwidth control + pub(crate) ecash_verifier: Arc, + + /// Handle for communication with the [`nym_wireguard::peer_controller::PeerController`] + pub(crate) peer_manager: PeerManager, + + /// Information about the current state of the upgrade mode as well as a handle + /// to remotely trigger the recheck + pub(crate) upgrade_mode: UpgradeModeDetails, + + /// Registrations in progress + pub(crate) pending_registrations: PendingRegistrations, +} + +impl PeerRegistrator { + pub fn new( + ecash_verifier: Arc, + peer_manager: PeerManager, + upgrade_mode: UpgradeModeDetails, + ) -> Self { + PeerRegistrator { + ecash_verifier, + peer_manager, + upgrade_mode, + pending_registrations: Default::default(), + } + } + + pub fn cleanup_task(&self, shutdown_token: ShutdownToken) -> StaleRegistrationRemover { + StaleRegistrationRemover { + pending_registrations: self.pending_registrations.clone(), + shutdown_token, + } + } + + fn upgrade_mode_enabled(&self) -> bool { + self.upgrade_mode.enabled() + } + + fn keypair(&self) -> &Arc { + self.peer_manager.wireguard_gateway_data.keypair() + } + + fn wireguard_config(&self) -> WireguardConfig { + self.peer_manager.wireguard_gateway_data.config() + } + + fn wg_port(&self) -> u16 { + self.wireguard_config().announced_tunnel_port + } + + pub async fn credential_storage_preparation( + &self, + client_id: i64, + ) -> Result { + self.ecash_verifier + .storage() + .create_bandwidth_entry(client_id) + .await?; + + self.ecash_verifier + .storage() + .get_available_bandwidth(client_id) + .await? + .ok_or(GatewayWireguardError::internal( + "missing bandwidth entry after it has just been created", + )) + } + + async fn credential_verification( + &self, + credential: CredentialSpendingData, + client_id: i64, + ) -> Result { + let bandwidth = self.credential_storage_preparation(client_id).await?; + let client_bandwidth = ClientBandwidth::new(bandwidth.into()); + let mut verifier = CredentialVerifier::new( + CredentialSpendingRequest::new(credential), + self.ecash_verifier.clone(), + BandwidthStorageManager::new( + self.ecash_verifier.storage(), + client_bandwidth, + client_id, + BandwidthFlushingBehaviourConfig::default(), + true, + ), + ); + + Ok(verifier.verify().await?) + } + + async fn handle_final_credential_claim( + &self, + claim: BandwidthClaim, + client_id: i64, + ) -> Result<(), GatewayWireguardError> { + match claim.credential { + BandwidthCredential::ZkNym(zk_nym) => { + // if we got zk-nym, we just try to verify it + self.credential_verification(*zk_nym, client_id).await?; + Ok(()) + } + BandwidthCredential::UpgradeModeJWT { token } => { + // if we're already in the upgrade mode, don't bother validating the token + if self.upgrade_mode_enabled() { + return Ok(()); + } + + self.upgrade_mode.try_enable_via_received_jwt(token).await?; + Ok(()) + } + } + } + + /// Attempt to process new peer by: + /// 1. retrieving previous IP allocation + /// 2. inserting it into the storage + /// 3. verifying bandwidth claim and increasing the allowance + /// 4. spawning the peer handler + async fn process_new_peer( + &self, + pending: PendingRegistration, + credential: BandwidthClaim, + ) -> Result<(), GatewayWireguardError> { + // 1. create peer based on the cached registration information + let defguard_key = Key::new(pending.data.peer_key.to_bytes()); + let mut peer = Peer::new(defguard_key); + if let Some(psk) = pending.data.psk { + peer.preshared_key = Some(psk); + } + let private_ipv4 = pending.data.wireguard_config.private_ipv4; + let private_ipv6 = pending.data.wireguard_config.private_ipv6; + peer.allowed_ips = vec![ + IpAddrMask::new(private_ipv4.into(), 32), + IpAddrMask::new(private_ipv6.into(), 128), + ]; + + let typ = credential.kind; + + // 2. attempt to pre-insert peer into the storage + let client_id = self + .ecash_verifier + .storage() + .insert_wireguard_peer(&peer, typ.into()) + .await?; + + // 3. verify the credential + if let Err(err) = self + .handle_final_credential_claim(credential, client_id) + .await + { + // 3.1. on failure -> remove the inserted peer + self.ecash_verifier + .storage() + .remove_wireguard_peer(&peer.public_key.to_string()) + .await?; + return Err(err); + } + + // 4. attempt to start the actual handle for the peer + let public_key = peer.public_key.to_string(); + if let Err(err) = self.peer_manager.add_peer(peer).await { + // 4.1. on failure -> remove the inserted peer (from the storage) + self.ecash_verifier + .storage() + .remove_wireguard_peer(&public_key) + .await?; + return Err(err); + } + + Ok(()) + } + + pub(crate) async fn on_initial_authenticator_request( + &mut self, + init_message: Box, + protocol: Protocol, + request_id: u64, + reply_to: Option, + ) -> Result { + let remote_public = init_message.pub_key(); + + // 1. check if there's any pending registration already in progress, + // if so, return the same data again without additional processing + if let Some(pending_registration) = self + .check_pending_authenticator_registration(protocol, request_id, remote_public, reply_to) + .await? + { + return Ok(pending_registration); + } + + // 2. check if there is already a peer associated with this sender, + // if so, retrieve the "final" data without additional processing + if let Some(existing_registration) = self + .check_existing_authenticator_peer(protocol, request_id, remote_public, reply_to) + .await? + { + return Ok(existing_registration); + } + + // 3. process fresh registration request + self.process_fresh_initial_authenticator_registration( + protocol, + request_id, + remote_public, + reply_to, + ) + .await + } + + pub(crate) async fn on_final_authenticator_request( + &mut self, + final_message: Box, + protocol: Protocol, + request_id: u64, + reply_to: Option, + ) -> Result { + let peer = final_message.gateway_client_pub_key(); + // 1. check if there's any pending registration associated with this peer + let pending_data = self + .pending_registrations + .check_authenticator(&peer) + .await + .ok_or(GatewayWireguardError::RegistrationNotInProgress)? + .clone(); + + // 2. verify the correctness of the received request based on the prior nonce + if final_message + .verify(self.keypair().private_key(), pending_data.data.nonce) + .is_err() + { + return Err(GatewayWireguardError::AuthenticatorMacVerificationFailure); + } + + // 3. ensure we have received a credential + let Some(credential) = final_message.credential() else { + return Err(GatewayWireguardError::MissingAuthenticatorCredential); + }; + + // 4. prepare new peer information and verify the credential + self.process_new_peer(pending_data.clone(), credential) + .await?; + + // 5. remove pending registration + self.pending_registrations.remove_authenticator(&peer).await; + + // 6. construct and return the response + pending_data.to_registered_authenticator_response( + self.upgrade_mode_enabled(), + request_id, + protocol.into(), + reply_to, + ) + } + + pub(crate) async fn on_initial_lp_request( + &self, + init_msg: LpDvpnRegistrationInitialRequest, + sender: ReceiverIndex, + ) -> Result { + let remote_public = init_msg.wg_public_key; + let psk = Key::new(init_msg.psk); + + // 1. check if there's any pending registration already in progress, + // if so, return the same data again without additional processing, + // but update stored PSK + if let Some(pending_registration) = self.check_pending_lp_registration(sender).await? { + self.update_peer_psk(remote_public, psk).await?; + return Ok(pending_registration); + } + + // 2. check if there is already a peer associated with this sender, + // if so, retrieve the "final" data without additional processing, + // but do update stored PSK + if let Some(existing_registration) = self.check_existing_lp_peer(remote_public).await? { + self.update_peer_psk(remote_public, psk).await?; + return Ok(existing_registration); + } + + // 3. process fresh registration request + self.process_fresh_initial_lp_registration(sender, remote_public, psk) + .await + } + + pub(crate) async fn on_final_lp_request( + &self, + final_msg: LpDvpnRegistrationFinalisation, + sender: ReceiverIndex, + ) -> Result { + // 1. check if there's any pending registration associated with this peer + let pending_data = self + .pending_registrations + .check_lp(sender) + .await + .ok_or(GatewayWireguardError::RegistrationNotInProgress)? + .clone(); + + let credential = final_msg.credential; + + // 2. prepare new peer information and verify the credential + self.process_new_peer(pending_data.clone(), credential) + .await?; + + // 3 remove pending registration + self.pending_registrations.remove_lp(sender).await; + + // 4. construct and return the response + Ok(pending_data.to_registered_lp_response(self.upgrade_mode_enabled())) + } +} + +pub struct StaleRegistrationRemover { + pending_registrations: PendingRegistrations, + shutdown_token: ShutdownToken, +} + +impl StaleRegistrationRemover { + // TODO: make it configurable + const STALE_REG_CHECK_INTERVAL: Duration = Duration::from_secs(60); + + pub async fn run(&self) { + let start = Instant::now() + Self::STALE_REG_CHECK_INTERVAL; + let mut interval = interval_at(start, Self::STALE_REG_CHECK_INTERVAL); + loop { + tokio::select! { + biased; + _ = self.shutdown_token.cancelled() => { + trace!("StaleRegistrationRemover: received shutdown"); + break + } + _ = interval.tick() => { + self.pending_registrations.remove_stale_registrations().await + } + } + } + } +} diff --git a/gateway/src/node/wireguard/new_peer_registration/pending.rs b/gateway/src/node/wireguard/new_peer_registration/pending.rs new file mode 100644 index 0000000000..cb933514db --- /dev/null +++ b/gateway/src/node/wireguard/new_peer_registration/pending.rs @@ -0,0 +1,158 @@ +// Copyright 2026 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::node::lp_listener::ReceiverIndex; +use crate::node::wireguard::new_peer_registration::helpers::{ + build_final_authenticator_response, build_pending_authenticator_response, +}; +use crate::node::wireguard::GatewayWireguardError; +use defguard_wireguard_rs::key::Key; +use nym_authenticator_requests::AuthenticatorVersion; +use nym_crypto::asymmetric::x25519; +use nym_registration_common::{LpRegistrationResponse, WireguardRegistrationData}; +use nym_sdk::mixnet::Recipient; +use nym_wireguard::ip_pool::IpPair; +use nym_wireguard_types::PeerPublicKey; +use std::collections::HashMap; +use std::sync::Arc; +use std::time::{Duration, Instant}; +use tokio::sync::RwLock; + +const DEFAULT_PENDING_REGISTRATION_TTL: Duration = Duration::from_secs(120); // 2 minutes + +#[derive(Clone)] +pub struct PendingRegistration { + pub(super) requested_on: Instant, + pub(super) data: PendingRegistrationData, +} + +#[derive(Clone)] +pub struct PendingRegistrationData { + pub(super) nonce: u64, + + pub(super) peer_key: PeerPublicKey, + + // will not be set if registering via the Authenticator + pub(super) psk: Option, + + pub(super) wireguard_config: WireguardRegistrationData, +} + +impl PendingRegistration { + pub(crate) fn to_pending_authenticator_response( + &self, + local_key: &x25519::PrivateKey, + upgrade_mode_enabled: bool, + request_id: u64, + version: AuthenticatorVersion, + reply_to: Option, + ) -> Result + { + let nonce = self.data.nonce; + let remote_public = self.data.peer_key; + let wg_port = self.data.wireguard_config.port; + let ip_allocation = IpPair::new( + self.data.wireguard_config.private_ipv4, + self.data.wireguard_config.private_ipv6, + ); + + build_pending_authenticator_response( + ip_allocation, + wg_port, + local_key, + remote_public, + upgrade_mode_enabled, + nonce, + request_id, + version, + reply_to, + ) + } + + pub(crate) fn to_registered_authenticator_response( + &self, + upgrade_mode_enabled: bool, + request_id: u64, + version: AuthenticatorVersion, + reply_to: Option, + ) -> Result + { + let wg_port = self.data.wireguard_config.port; + let local_pub_key = self.data.wireguard_config.public_key.into(); + + let ip_allocation = IpPair::new( + self.data.wireguard_config.private_ipv4, + self.data.wireguard_config.private_ipv6, + ); + + build_final_authenticator_response( + ip_allocation, + wg_port, + local_pub_key, + upgrade_mode_enabled, + request_id, + version, + reply_to, + ) + } + + pub(crate) fn to_pending_lp_response(&self) -> LpRegistrationResponse { + LpRegistrationResponse::request_dvpn_credential() + } + + pub(crate) fn to_registered_lp_response( + &self, + upgrade_mode_enabled: bool, + ) -> LpRegistrationResponse { + LpRegistrationResponse::success_dvpn(self.data.wireguard_config, upgrade_mode_enabled) + } +} + +#[derive(Clone, Default)] +pub(crate) struct PendingRegistrations { + // TODO: unify those, somehow, later + /// Registrations in progress received from the Authenticator service provider via the + /// [`crate::node::internal_service_providers::authenticator::mixnet_listener::MixnetListener`] + pub(crate) authenticator: Arc>>, + + /// Registrations in progress received from the LP Listener via the + /// [`crate::node::lp_listener::handler::LpConnectionHandler`] and handle through + /// [`crate::node::lp_listener::registration::LpHandlerState`] + pub(crate) lp: Arc>>, +} + +impl PendingRegistrations { + pub(crate) async fn check_authenticator( + &self, + peer: &PeerPublicKey, + ) -> Option { + self.authenticator.read().await.get(peer).cloned() + } + + pub(crate) async fn remove_authenticator(&self, peer: &PeerPublicKey) { + self.authenticator.write().await.remove(peer); + } + + pub(crate) async fn remove_lp(&self, receiver_index: ReceiverIndex) { + self.lp.write().await.remove(&receiver_index); + } + + pub(crate) async fn check_lp( + &self, + receiver_index: ReceiverIndex, + ) -> Option { + self.lp.read().await.get(&receiver_index).cloned() + } + + pub(crate) async fn remove_stale_registrations(&self) { + // note: `IpPool` will release stale pre-allocated addresses by itself during the cleanup, + // so there's no need to send explicit messages over + let now = Instant::now(); + self.authenticator.write().await.retain(|_, pending| { + now.duration_since(pending.requested_on) < DEFAULT_PENDING_REGISTRATION_TTL + }); + self.lp.write().await.retain(|_, pending| { + now.duration_since(pending.requested_on) < DEFAULT_PENDING_REGISTRATION_TTL + }); + } +} diff --git a/gateway/src/node/wireguard/peer_manager.rs b/gateway/src/node/wireguard/peer_manager.rs index 0921b758ca..ffb25840e9 100644 --- a/gateway/src/node/wireguard/peer_manager.rs +++ b/gateway/src/node/wireguard/peer_manager.rs @@ -6,11 +6,28 @@ use defguard_wireguard_rs::{host::Peer, key::Key}; use futures::channel::oneshot; use nym_credential_verification::{ClientBandwidth, TicketVerifier}; use nym_credentials_interface::CredentialSpendingData; +use nym_metrics::add_histogram_obs; use nym_wireguard::peer_controller::IpPair; use nym_wireguard::{peer_controller::PeerControlRequest, WireguardGatewayData}; use nym_wireguard_types::PeerPublicKey; +use std::time::Instant; use tracing::error; +// Histogram buckets for WireGuard peer controller channel latency +// Measures time to send request and receive response from peer controller +// Expected: 1ms-100ms for normal operations, up to 2s for slow conditions +const WG_CONTROLLER_LATENCY_BUCKETS: &[f64] = &[ + 0.001, // 1ms + 0.005, // 5ms + 0.01, // 10ms + 0.05, // 50ms + 0.1, // 100ms + 0.25, // 250ms + 0.5, // 500ms + 1.0, // 1s + 2.0, // 2s +]; + #[derive(Clone)] pub struct PeerManager { pub(crate) wireguard_gateway_data: WireguardGatewayData, @@ -23,16 +40,17 @@ impl PeerManager { } } - pub async fn allocate_peer_ip_pair(&self) -> Result { + pub async fn preallocate_peer_ip_pair(&self) -> Result { + let controller_start = Instant::now(); let (response_tx, response_rx) = oneshot::channel(); - let msg = PeerControlRequest::AllocatePeerIpPair { response_tx }; + let msg = PeerControlRequest::PreAllocateIpPair { response_tx }; self.wireguard_gateway_data .peer_tx() .send(msg) .await .map_err(|_| GatewayWireguardError::PeerInteractionStopped)?; - response_rx + let res = response_rx .await .map_err(|e| { GatewayWireguardError::InternalError(format!( @@ -42,7 +60,16 @@ impl PeerManager { .map_err(|e| { error!("Failed to allocate IPs from pool: {e}"); GatewayWireguardError::InternalError(format!("Failed to allocate IPs: {e}")) - }) + }); + + let latency = controller_start.elapsed().as_secs_f64(); + add_histogram_obs!( + "wg_peer_controller_channel_latency_seconds", + latency, + WG_CONTROLLER_LATENCY_BUCKETS + ); + + res } pub async fn release_ip_pair(&self, ip_pair: IpPair) -> Result<(), GatewayWireguardError> { @@ -70,6 +97,7 @@ impl PeerManager { } pub async fn add_peer(&self, peer: Peer) -> Result<(), GatewayWireguardError> { + let controller_start = Instant::now(); let (response_tx, response_rx) = oneshot::channel(); let msg = PeerControlRequest::AddPeer { peer, response_tx }; self.wireguard_gateway_data @@ -78,17 +106,27 @@ impl PeerManager { .await .map_err(|_| GatewayWireguardError::PeerInteractionStopped)?; - response_rx + let res = response_rx .await .map_err(|_| GatewayWireguardError::internal("no response for add peer".to_string()))? .map_err(|err| { GatewayWireguardError::internal(format!( "adding peer could not be performed: {err:?}" )) - }) + }); + + let latency = controller_start.elapsed().as_secs_f64(); + add_histogram_obs!( + "wg_peer_controller_channel_latency_seconds", + latency, + WG_CONTROLLER_LATENCY_BUCKETS + ); + + res } - pub async fn _remove_peer(&self, pub_key: PeerPublicKey) -> Result<(), GatewayWireguardError> { + pub async fn remove_peer(&self, pub_key: PeerPublicKey) -> Result<(), GatewayWireguardError> { + let controller_start = Instant::now(); let key = Key::new(pub_key.to_bytes()); let (response_tx, response_rx) = oneshot::channel(); let msg = PeerControlRequest::RemovePeer { key, response_tx }; @@ -98,20 +136,63 @@ impl PeerManager { .await .map_err(|_| GatewayWireguardError::PeerInteractionStopped)?; - response_rx + let res = response_rx .await .map_err(|_| GatewayWireguardError::internal("no response for remove peer"))? .map_err(|err| { GatewayWireguardError::InternalError(format!( "removing peer could not be performed: {err:?}" )) - }) + }); + + let latency = controller_start.elapsed().as_secs_f64(); + add_histogram_obs!( + "wg_peer_controller_channel_latency_seconds", + latency, + WG_CONTROLLER_LATENCY_BUCKETS + ); + + res + } + + pub async fn check_active_peer( + &self, + pub_key: PeerPublicKey, + ) -> Result { + let controller_start = Instant::now(); + let key = Key::new(pub_key.to_bytes()); + let (response_tx, response_rx) = oneshot::channel(); + let msg = PeerControlRequest::CheckActivePeer { key, response_tx }; + self.wireguard_gateway_data + .peer_tx() + .send(msg) + .await + .map_err(|_| GatewayWireguardError::PeerInteractionStopped)?; + + let res = response_rx + .await + .map_err(|_| GatewayWireguardError::internal("no response for check active peer"))? + .map_err(|err| { + GatewayWireguardError::InternalError(format!( + "check active peer could not be performed: {err:?}" + )) + }); + + let latency = controller_start.elapsed().as_secs_f64(); + add_histogram_obs!( + "wg_peer_controller_channel_latency_seconds", + latency, + WG_CONTROLLER_LATENCY_BUCKETS + ); + + res } pub async fn query_peer( &self, public_key: PeerPublicKey, ) -> Result, GatewayWireguardError> { + let controller_start = Instant::now(); let key = Key::new(public_key.to_bytes()); let (response_tx, response_rx) = oneshot::channel(); let msg = PeerControlRequest::QueryPeer { key, response_tx }; @@ -121,14 +202,23 @@ impl PeerManager { .await .map_err(|_| GatewayWireguardError::PeerInteractionStopped)?; - response_rx + let res = response_rx .await .map_err(|_| GatewayWireguardError::internal("no response for query peer".to_string()))? .map_err(|err| { GatewayWireguardError::internal(format!( "querying peer could not be performed: {err:?}" )) - }) + }); + + let latency = controller_start.elapsed().as_secs_f64(); + add_histogram_obs!( + "wg_peer_controller_channel_latency_seconds", + latency, + WG_CONTROLLER_LATENCY_BUCKETS + ); + + res } pub async fn query_bandwidth( @@ -143,6 +233,7 @@ impl PeerManager { &self, key: PeerPublicKey, ) -> Result { + let controller_start = Instant::now(); let key = Key::new(key.to_bytes()); let (response_tx, response_rx) = oneshot::channel(); let msg = PeerControlRequest::GetClientBandwidthByKey { key, response_tx }; @@ -152,14 +243,23 @@ impl PeerManager { .await .map_err(|_| GatewayWireguardError::PeerInteractionStopped)?; - response_rx + let res = response_rx .await .map_err(|_| GatewayWireguardError::internal("no response for query client bandwidth"))? .map_err(|err| { GatewayWireguardError::internal(format!( "querying client bandwidth could not be performed: {err:?}" )) - }) + }); + + let latency = controller_start.elapsed().as_secs_f64(); + add_histogram_obs!( + "wg_peer_controller_channel_latency_seconds", + latency, + WG_CONTROLLER_LATENCY_BUCKETS + ); + + res } pub async fn query_verifier_by_key( @@ -167,6 +267,7 @@ impl PeerManager { key: PeerPublicKey, credential: CredentialSpendingData, ) -> Result, GatewayWireguardError> { + let controller_start = Instant::now(); let key = Key::new(key.to_bytes()); let (response_tx, response_rx) = oneshot::channel(); let msg = PeerControlRequest::GetVerifierByKey { @@ -180,7 +281,7 @@ impl PeerManager { .await .map_err(|_| GatewayWireguardError::PeerInteractionStopped)?; - response_rx + let res = response_rx .await .map_err(|_| { GatewayWireguardError::internal("no response for query verifier".to_string()) @@ -189,31 +290,39 @@ impl PeerManager { GatewayWireguardError::internal(format!( "querying verifier could not be performed: {err:?}" )) - }) + }); + + let latency = controller_start.elapsed().as_secs_f64(); + add_histogram_obs!( + "wg_peer_controller_channel_latency_seconds", + latency, + WG_CONTROLLER_LATENCY_BUCKETS + ); + + res } } #[cfg(test)] mod tests { - use std::{str::FromStr, sync::Arc}; - + use super::*; + use crate::node::wireguard::PeerRegistrator; + use crate::nym_authenticator::config::Authenticator; + use defguard_wireguard_rs::net::IpAddrMask; + use nym_credential_verification::upgrade_mode::testing::mock_dummy_upgrade_mode_details; use nym_credential_verification::{ bandwidth_storage_manager::BandwidthStorageManager, ecash::MockEcashManager, }; use nym_credentials_interface::Bandwidth; use nym_crypto::asymmetric::x25519::KeyPair; use nym_gateway_storage::traits::{mock::MockGatewayStorage, BandwidthGatewayStorage}; + use nym_task::ShutdownManager; + use nym_test_utils::helpers::{deterministic_rng, DeterministicRng, RngCore}; use nym_wireguard::peer_controller::{start_controller, stop_controller}; - use rand::rngs::OsRng; + use std::{str::FromStr, sync::Arc}; use time::{Duration, OffsetDateTime}; use tokio::sync::RwLock; - use crate::nym_authenticator::{ - config::Authenticator, mixnet_listener::credential_storage_preparation, - }; - - use super::*; - const CREDENTIAL_BYTES: [u8; 1245] = [ 0, 0, 4, 133, 96, 179, 223, 185, 136, 23, 213, 166, 59, 203, 66, 69, 209, 181, 227, 254, 16, 102, 98, 237, 59, 119, 170, 111, 31, 194, 51, 59, 120, 17, 115, 229, 79, 91, 11, 139, @@ -279,141 +388,202 @@ mod tests { 0, 0, 0, 0, 0, 1, ]; - #[tokio::test] - async fn add_peer() { - let (wireguard_data, request_rx) = WireguardGatewayData::new( - Authenticator::default().into(), - Arc::new(KeyPair::new(&mut OsRng)), - ); - let peer_manager = PeerManager::new(wireguard_data); - let (storage, task_manager) = start_controller( - peer_manager.wireguard_gateway_data.peer_tx().clone(), - request_rx, - ); - let peer = Peer::default(); - let ecash_manager = MockEcashManager::new(Box::new(storage.clone())); - - assert!(peer_manager.add_peer(peer.clone()).await.is_err()); - - let client_id = storage - .insert_wireguard_peer(&peer, FromStr::from_str("entry_wireguard").unwrap()) - .await - .unwrap(); - assert!(peer_manager.add_peer(peer.clone()).await.is_err()); - - credential_storage_preparation(Arc::new(ecash_manager), client_id) - .await - .unwrap(); - peer_manager.add_peer(peer.clone()).await.unwrap(); - - stop_controller(task_manager).await; + struct TestSetup { + rng: DeterministicRng, + _ecash_manager: Arc, + storage: Arc>, + peer_registrator: PeerRegistrator, + peer_manager: PeerManager, + task_manager: ShutdownManager, } - async fn helper_add_peer( - storage: &Arc>, - peer_manager: &mut PeerManager, - ) -> i64 { - let peer = Peer::default(); - let ecash_manager = MockEcashManager::new(Box::new(storage.clone())); - let client_id = storage + struct GeneratedPeer { + peer: Peer, + client_id: i64, + } + + impl GeneratedPeer { + fn key(&self) -> PeerPublicKey { + PeerPublicKey::from_str(self.peer.public_key.to_string().as_str()).unwrap() + } + } + + impl TestSetup { + fn new() -> TestSetup { + let mut rng = deterministic_rng(); + let (wireguard_data, request_rx) = WireguardGatewayData::new( + Authenticator::default().into(), + Arc::new(KeyPair::new(&mut rng)), + ); + + let (upgrade_mode_details, _) = mock_dummy_upgrade_mode_details(); + let peer_manager = PeerManager::new(wireguard_data); + + let (storage, task_manager) = start_controller( + peer_manager.wireguard_gateway_data.peer_tx().clone(), + request_rx, + ); + + let ecash_manager = Arc::new(MockEcashManager::new(Box::new(storage.clone()))); + let peer_registrator = PeerRegistrator::new( + ecash_manager.clone(), + peer_manager.clone(), + upgrade_mode_details, + ); + + TestSetup { + rng, + _ecash_manager: ecash_manager, + storage, + peer_registrator, + peer_manager, + task_manager, + } + } + + async fn peer_with_pre_allocated_ip(&mut self) -> Peer { + let mut peer = Peer::default(); + let mut key = [0u8; 32]; + self.rng.fill_bytes(&mut key); + peer.public_key = Key::new(key); + + let allocation = self.peer_manager.preallocate_peer_ip_pair().await.unwrap(); + peer.allowed_ips = vec![ + IpAddrMask::new(allocation.ipv4.into(), 32), + IpAddrMask::new(allocation.ipv6.into(), 128), + ]; + + peer + } + + async fn _add_peer(&self, peer: &Peer) -> i64 { + let client_id = self + .storage + .insert_wireguard_peer(peer, FromStr::from_str("entry_wireguard").unwrap()) + .await + .unwrap(); + self.peer_registrator + .credential_storage_preparation(client_id) + .await + .unwrap(); + self.peer_manager.add_peer(peer.clone()).await.unwrap(); + client_id + } + + async fn add_peer(&mut self) -> GeneratedPeer { + let peer = self.peer_with_pre_allocated_ip().await; + let client_id = self._add_peer(&peer).await; + + GeneratedPeer { peer, client_id } + } + + async fn finish(self) { + stop_controller(self.task_manager).await + } + } + + #[tokio::test] + async fn assign_peer_ip() -> anyhow::Result<()> { + let test = TestSetup::new(); + + let ip_pair1 = test.peer_manager.preallocate_peer_ip_pair().await?; + let ip_pair2 = test.peer_manager.preallocate_peer_ip_pair().await?; + assert_ne!(ip_pair1, ip_pair2); + + test.finish().await; + + Ok(()) + } + + #[tokio::test] + async fn add_peer() { + let mut test = TestSetup::new(); + let peer = test.peer_with_pre_allocated_ip().await; + + assert!(test.peer_manager.add_peer(peer.clone()).await.is_err()); + + let client_id = test + .storage .insert_wireguard_peer(&peer, FromStr::from_str("entry_wireguard").unwrap()) .await .unwrap(); - credential_storage_preparation(Arc::new(ecash_manager), client_id) + assert!(test.peer_manager.add_peer(peer.clone()).await.is_err()); + + test.peer_registrator + .credential_storage_preparation(client_id) .await .unwrap(); - peer_manager.add_peer(peer.clone()).await.unwrap(); + test.peer_manager.add_peer(peer.clone()).await.unwrap(); - client_id + test.finish().await } #[tokio::test] async fn remove_peer() { - let (wireguard_data, request_rx) = WireguardGatewayData::new( - Authenticator::default().into(), - Arc::new(KeyPair::new(&mut OsRng)), - ); - let mut peer_manager = PeerManager::new(wireguard_data); - let key = Key::default(); - let public_key = PeerPublicKey::from_str(&key.to_string()).unwrap(); - let (storage, task_manager) = start_controller( - peer_manager.wireguard_gateway_data.peer_tx().clone(), - request_rx, - ); + let mut test = TestSetup::new(); + let peer = test.add_peer().await; + let public_key = peer.key(); - helper_add_peer(&storage, &mut peer_manager).await; - peer_manager._remove_peer(public_key).await.unwrap(); + test.peer_manager.remove_peer(public_key).await.unwrap(); - stop_controller(task_manager).await; + test.finish().await } #[tokio::test] async fn query_peer() { - let (wireguard_data, request_rx) = WireguardGatewayData::new( - Authenticator::default().into(), - Arc::new(KeyPair::new(&mut OsRng)), - ); - let mut peer_manager = PeerManager::new(wireguard_data); - let key = Key::default(); - let public_key = PeerPublicKey::from_str(&key.to_string()).unwrap(); - let (storage, task_manager) = start_controller( - peer_manager.wireguard_gateway_data.peer_tx().clone(), - request_rx, - ); + let mut test = TestSetup::new(); + let peer = test.peer_with_pre_allocated_ip().await; + let public_key = PeerPublicKey::from_str(peer.public_key.to_string().as_str()).unwrap(); - assert!(peer_manager.query_peer(public_key).await.unwrap().is_none()); + assert!(test + .peer_manager + .query_peer(public_key) + .await + .unwrap() + .is_none()); - helper_add_peer(&storage, &mut peer_manager).await; - let peer = peer_manager.query_peer(public_key).await.unwrap().unwrap(); - assert_eq!(peer.public_key, key); + test._add_peer(&peer).await; + let peer_query = test + .peer_manager + .query_peer(public_key) + .await + .unwrap() + .unwrap(); + assert_eq!(peer.public_key, peer_query.public_key); - stop_controller(task_manager).await; + test.finish().await } #[tokio::test] async fn query_bandwidth() { - let (wireguard_data, request_rx) = WireguardGatewayData::new( - Authenticator::default().into(), - Arc::new(KeyPair::new(&mut OsRng)), - ); - let mut peer_manager = PeerManager::new(wireguard_data); - let key = Key::default(); - let public_key = PeerPublicKey::from_str(&key.to_string()).unwrap(); - let (storage, task_manager) = start_controller( - peer_manager.wireguard_gateway_data.peer_tx().clone(), - request_rx, - ); + let mut test = TestSetup::new(); + let peer = test.peer_with_pre_allocated_ip().await; + let public_key = PeerPublicKey::from_str(peer.public_key.to_string().as_str()).unwrap(); - assert!(peer_manager.query_bandwidth(public_key).await.is_err()); + assert!(test.peer_manager.query_bandwidth(public_key).await.is_err()); - helper_add_peer(&storage, &mut peer_manager).await; - let available_bandwidth = peer_manager.query_bandwidth(public_key).await.unwrap(); + test._add_peer(&peer).await; + let available_bandwidth = test.peer_manager.query_bandwidth(public_key).await.unwrap(); assert_eq!(available_bandwidth, 0); - stop_controller(task_manager).await; + test.finish().await } #[tokio::test] async fn query_client_bandwidth() { - let (wireguard_data, request_rx) = WireguardGatewayData::new( - Authenticator::default().into(), - Arc::new(KeyPair::new(&mut OsRng)), - ); - let mut peer_manager = PeerManager::new(wireguard_data); - let key = Key::default(); - let public_key = PeerPublicKey::from_str(&key.to_string()).unwrap(); - let (storage, task_manager) = start_controller( - peer_manager.wireguard_gateway_data.peer_tx().clone(), - request_rx, - ); + let mut test = TestSetup::new(); + let peer = test.peer_with_pre_allocated_ip().await; + let public_key = PeerPublicKey::from_str(peer.public_key.to_string().as_str()).unwrap(); - assert!(peer_manager + assert!(test + .peer_manager .query_client_bandwidth(public_key) .await .is_err()); - helper_add_peer(&storage, &mut peer_manager).await; - let available_bandwidth = peer_manager + test._add_peer(&peer).await; + let available_bandwidth = test + .peer_manager .query_client_bandwidth(public_key) .await .unwrap() @@ -421,64 +591,51 @@ mod tests { .await; assert_eq!(available_bandwidth, 0); - stop_controller(task_manager).await; + test.finish().await } #[tokio::test] async fn query_verifier() { - let (wireguard_data, request_rx) = WireguardGatewayData::new( - Authenticator::default().into(), - Arc::new(KeyPair::new(&mut OsRng)), - ); - let mut peer_manager = PeerManager::new(wireguard_data); - let key = Key::default(); - let public_key = PeerPublicKey::from_str(&key.to_string()).unwrap(); - let (storage, task_manager) = start_controller( - peer_manager.wireguard_gateway_data.peer_tx().clone(), - request_rx, - ); + let mut test = TestSetup::new(); + let peer = test.peer_with_pre_allocated_ip().await; + let public_key = PeerPublicKey::from_str(peer.public_key.to_string().as_str()).unwrap(); + let credential = CredentialSpendingData::try_from_bytes(&CREDENTIAL_BYTES).unwrap(); - assert!(peer_manager + assert!(test + .peer_manager .query_verifier_by_key(public_key, credential.clone()) .await .is_err()); - helper_add_peer(&storage, &mut peer_manager).await; - peer_manager + test._add_peer(&peer).await; + test.peer_manager .query_verifier_by_key(public_key, credential) .await .unwrap(); - stop_controller(task_manager).await; + test.finish().await } #[tokio::test] async fn increase_decrease_bandwidth() { - let (wireguard_data, request_rx) = WireguardGatewayData::new( - Authenticator::default().into(), - Arc::new(KeyPair::new(&mut OsRng)), - ); - let mut peer_manager = PeerManager::new(wireguard_data); - let key = Key::default(); - let public_key = PeerPublicKey::from_str(&key.to_string()).unwrap(); + let mut test = TestSetup::new(); + let peer = test.add_peer().await; + let public_key = peer.key(); + let top_up = 42; let consume = 4; - let (storage, task_manager) = start_controller( - peer_manager.wireguard_gateway_data.peer_tx().clone(), - request_rx, - ); - let client_id = helper_add_peer(&storage, &mut peer_manager).await; - let client_bandwidth = peer_manager - .query_client_bandwidth(public_key) + let client_bandwidth = test + .peer_manager + .query_client_bandwidth(peer.key()) .await .unwrap(); let mut bw_manager = BandwidthStorageManager::new( - Box::new(storage), + Box::new(test.storage.clone()), client_bandwidth.clone(), - client_id, + peer.client_id, Default::default(), true, ); @@ -494,7 +651,7 @@ mod tests { assert_eq!(client_bandwidth.available().await, top_up); assert_eq!( - peer_manager.query_bandwidth(public_key).await.unwrap(), + test.peer_manager.query_bandwidth(public_key).await.unwrap(), top_up ); @@ -502,10 +659,10 @@ mod tests { let remaining = top_up - consume; assert_eq!(client_bandwidth.available().await, remaining); assert_eq!( - peer_manager.query_bandwidth(public_key).await.unwrap(), + test.peer_manager.query_bandwidth(public_key).await.unwrap(), remaining ); - stop_controller(task_manager).await; + test.finish().await } } diff --git a/integration-tests/src/lp_registration.rs b/integration-tests/src/lp_registration.rs index 81112582ed..15074efc0e 100644 --- a/integration-tests/src/lp_registration.rs +++ b/integration-tests/src/lp_registration.rs @@ -5,11 +5,8 @@ mod tests { use anyhow::Context; use nym_bandwidth_controller::mock::MockBandwidthController; - use nym_credential_verification::UpgradeModeState; use nym_credential_verification::ecash::MockEcashManager; - use nym_credential_verification::upgrade_mode::{ - UpgradeModeCheckConfig, UpgradeModeCheckRequestSender, UpgradeModeDetails, - }; + use nym_credential_verification::upgrade_mode::testing::mock_dummy_upgrade_mode_details; use nym_credentials_interface::TicketType; use nym_crypto::asymmetric::{ed25519, x25519}; use nym_gateway::GatewayError; @@ -18,7 +15,7 @@ mod tests { LpDebug, LpHandlerState, LpLocalPeer, MixForwardingReceiver, PeerControlRequest, WireguardGatewayData, mix_forwarding_channels, }; - use nym_gateway::node::wireguard::PeerManager; + use nym_gateway::node::wireguard::{PeerManager, PeerRegistrator}; use nym_gateway::node::{ActiveClientsStore, GatewayStorage, LpConfig}; use nym_registration_client::{LpClientError, LpRegistrationClient}; use nym_test_utils::helpers::{CryptoRng, RngCore, u64_seeded_rng}; @@ -166,10 +163,9 @@ mod tests { .unwrap() } - async fn allocate_ip_pair(&mut self) -> IpPair { + fn pre_allocate_ip_pair(&mut self) -> IpPair { self.ip_pool - .allocate() - .await + .pre_allocate() .expect("unexpected ip allocation failure!") } @@ -181,17 +177,6 @@ mod tests { Ok(GatewayStorage::from_connection_pool(conn_pool, 100).await?) } - const DUMMY_ATTESTER_ED25519_PRIVATE_KEY: [u8; 32] = [ - 108, 49, 193, 21, 126, 161, 249, 85, 242, 207, 74, 195, 238, 6, 64, 149, 201, 140, 248, - 163, 122, 170, 79, 198, 87, 85, 36, 29, 243, 92, 64, 161, - ]; - - pub(crate) fn dummy_attester_public_key() -> ed25519::PublicKey { - let private_key = - ed25519::PrivateKey::from_bytes(&Self::DUMMY_ATTESTER_ED25519_PRIVATE_KEY).unwrap(); - private_key.public_key() - } - async fn mock(rng: &mut (impl RngCore + CryptoRng)) -> anyhow::Result { let base = Party::generate(rng); @@ -217,31 +202,24 @@ mod tests { // create wireguard data let (wireguard_data, peer_request_rx) = Self::wireguard_data(&base); - let (um_recheck_tx, um_recheck_rx) = futures::channel::mpsc::unbounded(); - - // TODO: use it if we ever want to test UM - let _ = um_recheck_rx; + let (upgrade_mode_details, _) = mock_dummy_upgrade_mode_details(); // mock the wg peer controller let (mock_peer_controller, peer_controller_state) = mock_peer_controller(peer_request_rx); - let upgrade_mode_state = UpgradeModeState::new(Self::dummy_attester_public_key()); - let upgrade_mode_details = UpgradeModeDetails::new( - UpgradeModeCheckConfig { - // essentially we never want to trigger this in our tests - min_staleness_recheck: Duration::from_nanos(1), - }, - UpgradeModeCheckRequestSender::new(um_recheck_tx), - upgrade_mode_state.clone(), - ); - // registering particular responses for peer controller is up to given test - let peer_manager = Arc::new(PeerManager::new(wireguard_data)); + let ecash_verifier = Arc::new(ecash_verifier); + + let peer_registrator = PeerRegistrator::new( + ecash_verifier.clone(), + PeerManager::new(wireguard_data), + upgrade_mode_details, + ); let lp_state = LpHandlerState { // use mock instance of ecash verifier - ecash_verifier: Arc::new(ecash_verifier), + ecash_verifier, // use in-memory database (no need for persistency) storage, @@ -253,10 +231,6 @@ mod tests { // no clients at the beginning active_clients_store: ActiveClientsStore::new(), - // handles required for wg registration - upgrade_mode: upgrade_mode_details, - peer_manager, - // use default lp config (with enabled flag) lp_config, @@ -269,9 +243,10 @@ mod tests { // we start with empty state session_states: Arc::new(Default::default()), - // sensible default value for tests - registrations_in_progress: Default::default(), forward_semaphore, + + // handles for dealing with new peers + peer_registrator: Some(peer_registrator), }; Ok(Gateway { @@ -444,7 +419,7 @@ mod tests { // 3. register all needed responses for the dvpn registration that will reach the peer controller // 1) peer registration - ip pair allocation - let ip_pair = entry.allocate_ip_pair().await; + let ip_pair = entry.pre_allocate_ip_pair(); let reg_res = Ok::<_, nym_wireguard::Error>(ip_pair); entry @@ -617,7 +592,7 @@ mod tests { // 4. register all needed responses for the dvpn registration that will reach the peer controller // 1) peer registration - ip pair allocation - let entry_ip_pair = entry.allocate_ip_pair().await; + let entry_ip_pair = entry.pre_allocate_ip_pair(); let reg_res = Ok::<_, nym_wireguard::Error>(entry_ip_pair); entry @@ -668,7 +643,7 @@ mod tests { // 10. register all needed responses for the dvpn registration that will reach the peer controller // 1) peer registration - ip pair allocation - let exit_ip_pair = exit.allocate_ip_pair().await; + let exit_ip_pair = exit.pre_allocate_ip_pair(); let reg_res = Ok::<_, nym_wireguard::Error>(exit_ip_pair); exit.register_peer_controller_response( diff --git a/nym-gateway-probe/src/common/netstack.rs b/nym-gateway-probe/src/common/netstack.rs index c7dff8a5cb..7b6f473bed 100644 --- a/nym-gateway-probe/src/common/netstack.rs +++ b/nym-gateway-probe/src/common/netstack.rs @@ -5,6 +5,7 @@ use crate::config::NetstackArgs; use anyhow::Context; use serde::Deserialize; use std::ffi::{CStr, CString}; +use std::net::SocketAddr; mod sys { use std::ffi::{c_char, c_void}; @@ -223,14 +224,14 @@ pub struct TwoHopNetstackRequestGo { pub entry_wg_ip: String, pub entry_private_key: String, pub entry_public_key: String, - pub entry_endpoint: String, + pub entry_endpoint: SocketAddr, pub entry_awg_args: String, // Exit tunnel configuration (connects via forwarder through entry) pub exit_wg_ip: String, pub exit_private_key: String, pub exit_public_key: String, - pub exit_endpoint: String, + pub exit_endpoint: SocketAddr, pub exit_awg_args: String, // Test parameters diff --git a/nym-gateway-probe/src/common/probe_tests.rs b/nym-gateway-probe/src/common/probe_tests.rs index fa0a9aa2c3..0ee8140f77 100644 --- a/nym-gateway-probe/src/common/probe_tests.rs +++ b/nym-gateway-probe/src/common/probe_tests.rs @@ -299,9 +299,6 @@ pub async fn wg_probe_lp( let entry_lp_version = entry_lp_data.lp_version; let exit_lp_version = exit_lp_data.lp_version; - let entry_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(); @@ -405,9 +402,9 @@ pub async fn wg_probe_lp( // Build WireGuard endpoint addresses // Entry endpoint uses entry_ip (host-reachable) + port from registration - let entry_wg_endpoint = format!("{}:{}", entry_ip, entry_gateway_data.endpoint.port()); + let entry_wg_endpoint = entry_gateway_data.endpoint; // Exit endpoint uses exit_ip + port from registration (forwarded via entry) - let exit_wg_endpoint = format!("{}:{}", exit_ip, exit_gateway_data.endpoint.port()); + let exit_wg_endpoint = exit_gateway_data.endpoint; info!("Two-hop WireGuard configuration:"); info!(" Entry gateway:"); diff --git a/nym-gateway-probe/src/common/wireguard.rs b/nym-gateway-probe/src/common/wireguard.rs index 20a0e7ec8d..2ded0a0ae3 100644 --- a/nym-gateway-probe/src/common/wireguard.rs +++ b/nym-gateway-probe/src/common/wireguard.rs @@ -7,6 +7,7 @@ //! that is shared between different test modes (authenticator-based and LP-based). use nym_config::defaults::{WG_METADATA_PORT, WG_TUN_DEVICE_IP_ADDRESS_V4}; +use std::net::SocketAddr; use tracing::{error, info}; use crate::NetstackArgs; @@ -185,7 +186,7 @@ pub struct TwoHopWgTunnelConfig { /// Entry gateway's WireGuard public key (hex encoded) pub entry_public_key_hex: String, /// Entry WireGuard endpoint address (entry_gateway_ip:port) - pub entry_endpoint: String, + pub entry_endpoint: SocketAddr, /// Entry Amnezia WG args (empty for standard WG) pub entry_awg_args: String, @@ -197,7 +198,7 @@ pub struct TwoHopWgTunnelConfig { /// Exit gateway's WireGuard public key (hex encoded) pub exit_public_key_hex: String, /// Exit WireGuard endpoint address (exit_gateway_ip:port, forwarded via entry) - pub exit_endpoint: String, + pub exit_endpoint: SocketAddr, /// Exit Amnezia WG args (empty for standard WG) pub exit_awg_args: String, } @@ -209,24 +210,24 @@ impl TwoHopWgTunnelConfig { entry_private_ipv4: impl Into, entry_private_key_hex: impl Into, entry_public_key_hex: impl Into, - entry_endpoint: impl Into, + entry_endpoint: SocketAddr, entry_awg_args: impl Into, exit_private_ipv4: impl Into, exit_private_key_hex: impl Into, exit_public_key_hex: impl Into, - exit_endpoint: impl Into, + exit_endpoint: SocketAddr, exit_awg_args: impl Into, ) -> Self { Self { entry_private_ipv4: entry_private_ipv4.into(), entry_private_key_hex: entry_private_key_hex.into(), entry_public_key_hex: entry_public_key_hex.into(), - entry_endpoint: entry_endpoint.into(), + entry_endpoint, entry_awg_args: entry_awg_args.into(), exit_private_ipv4: exit_private_ipv4.into(), exit_private_key_hex: exit_private_key_hex.into(), exit_public_key_hex: exit_public_key_hex.into(), - exit_endpoint: exit_endpoint.into(), + exit_endpoint, exit_awg_args: exit_awg_args.into(), } } @@ -256,14 +257,14 @@ pub fn run_two_hop_tunnel_tests( entry_wg_ip: config.entry_private_ipv4.clone(), entry_private_key: config.entry_private_key_hex.clone(), entry_public_key: config.entry_public_key_hex.clone(), - entry_endpoint: config.entry_endpoint.clone(), + entry_endpoint: config.entry_endpoint, entry_awg_args: config.entry_awg_args.clone(), // Exit tunnel config exit_wg_ip: config.exit_private_ipv4.clone(), exit_private_key: config.exit_private_key_hex.clone(), exit_public_key: config.exit_public_key_hex.clone(), - exit_endpoint: config.exit_endpoint.clone(), + exit_endpoint: config.exit_endpoint, exit_awg_args: config.exit_awg_args.clone(), // Test parameters (use IPv4 config) diff --git a/nym-node/src/node/mod.rs b/nym-node/src/node/mod.rs index f3c6ab940f..a61c522846 100644 --- a/nym-node/src/node/mod.rs +++ b/nym-node/src/node/mod.rs @@ -673,6 +673,26 @@ impl NymNode { let upgrade_mode_common_state = gateway_tasks_builder.build_upgrade_mode_common_state(upgrade_check_request_sender); + // Set WireGuard data early so other builders can access it + if self.config.wireguard.enabled { + let Some(wg_data) = self.wireguard.take() else { + return Err(NymNodeError::WireguardDataUnavailable); + }; + gateway_tasks_builder.set_wireguard_data(wg_data.into()); + } + + let wg_peer_registrator = gateway_tasks_builder + .build_peer_registrator(upgrade_mode_common_state.clone()) + .await?; + + if let Some(wg_peer_registrator) = wg_peer_registrator.as_ref() { + let cleanup_task = wg_peer_registrator.cleanup_task(self.shutdown_token()); + self.shutdown_tracker().try_spawn_named( + async move { cleanup_task.run().await }, + "StaleRegistrationRemover", + ); + }; + // if we're running in entry mode, start the websocket if self.modes().entry { info!( @@ -688,15 +708,6 @@ impl NymNode { self.shutdown_tracker() .try_spawn_named(async move { websocket.run().await }, "EntryWebsocket"); - // Set WireGuard data early so LP listener can access it - // (LP listener needs wg_peer_controller for dVPN registrations) - if self.config.wireguard.enabled { - let Some(wg_data) = self.wireguard.take() else { - return Err(NymNodeError::WireguardDataUnavailable); - }; - gateway_tasks_builder.set_wireguard_data(wg_data.into()); - } - // Start LP listener if enabled info!( "starting the LP listener on {} (data handler on: {})", @@ -704,10 +715,7 @@ impl NymNode { self.config.gateway_tasks.lp.data_bind_address, ); let mut lp_listener = gateway_tasks_builder - .build_lp_listener( - upgrade_mode_common_state.clone(), - active_clients_store.clone(), - ) + .build_lp_listener(wg_peer_registrator.clone(), active_clients_store.clone()) .await?; self.shutdown_tracker() .try_spawn_named(async move { lp_listener.run().await }, "LpListener"); @@ -747,8 +755,16 @@ impl NymNode { gateway_tasks_builder.set_authenticator_opts(config.auth_opts); + let Some(peer_registrator) = wg_peer_registrator else { + return Err(NymNodeError::WireguardDataUnavailable); + }; + let authenticator = gateway_tasks_builder - .build_wireguard_authenticator(upgrade_mode_common_state.clone(), topology_provider) + .build_wireguard_authenticator( + peer_registrator, + upgrade_mode_common_state.clone(), + topology_provider, + ) .await?; let started_authenticator = authenticator.start_service_provider().await?; active_clients_store.insert_embedded(started_authenticator.handle); diff --git a/nym-registration-client/src/lp_client/client.rs b/nym-registration-client/src/lp_client/client.rs index 99a6d08a1f..1ae732dfb6 100644 --- a/nym-registration-client/src/lp_client/client.rs +++ b/nym-registration-client/src/lp_client/client.rs @@ -25,6 +25,7 @@ use nym_lp_transport::traits::LpTransport; use nym_registration_common::dvpn::LpDvpnRegistrationResponseMessageContent; use nym_registration_common::{ LpRegistrationRequest, LpRegistrationResponse, WireguardConfiguration, + WireguardRegistrationData, }; use nym_wireguard_types::PeerPublicKey; use rand::{CryptoRng, RngCore}; @@ -748,7 +749,7 @@ where gateway_identity: ed25519::PublicKey, bandwidth_controller: &dyn BandwidthTicketProvider, ticket_type: TicketType, - ) -> Result { + ) -> Result { tracing::debug!("Acquiring bandwidth credential for registration"); // 1. Get bandwidth credential from controller @@ -805,14 +806,7 @@ where tracing::warn!("Gateway rejected registration: {reason}"); Err(LpClientError::RegistrationRejected { reason }) } - LpDvpnRegistrationResponseMessageContent::CompletedRegistration(res) => { - // we have managed to complete the registration - tracing::info!( - "LP registration successful! Allocated bandwidth: {} bytes", - res.available_bandwidth - ); - Ok(res.config) - } + LpDvpnRegistrationResponseMessageContent::CompletedRegistration(res) => Ok(res.config), LpDvpnRegistrationResponseMessageContent::RequiresCredential(_) => { Err(LpClientError::unexpected_response( "received request for additional dvpn data after sending credential!", @@ -860,7 +854,7 @@ where let wg_public_key = PeerPublicKey::from(*wg_keypair.public_key()); let mut psk = [0u8; 32]; rng.fill_bytes(&mut psk); - let request = LpRegistrationRequest::new_initial_dvpn(wg_public_key, psk, ticket_type); + let request = LpRegistrationRequest::new_initial_dvpn(wg_public_key, psk); tracing::trace!("Built dVPN registration request: {request:?}"); @@ -896,14 +890,7 @@ where tracing::warn!("Gateway rejected registration: {reason}"); return Err(LpClientError::RegistrationRejected { reason }); } - LpDvpnRegistrationResponseMessageContent::CompletedRegistration(res) => { - // we have already registered with this gateway before, the gateway has updated the psk and sent us the config - tracing::info!( - "LP registration successful! Allocated bandwidth: {} bytes", - res.available_bandwidth - ); - res.config - } + LpDvpnRegistrationResponseMessageContent::CompletedRegistration(res) => res.config, LpDvpnRegistrationResponseMessageContent::RequiresCredential(_) => { // we're registering for the first time with this gateway - we need to attach a credential @@ -920,7 +907,7 @@ where Ok(WireguardConfiguration { public_key: final_response.public_key, psk: Some(psk), - endpoint: SocketAddr::new(self.gateway_lp_address.ip(), final_response.endpoint.port()), + endpoint: SocketAddr::new(self.gateway_lp_address.ip(), final_response.port), private_ipv4: final_response.private_ipv4, private_ipv6: final_response.private_ipv6, }) diff --git a/nym-registration-client/src/lp_client/nested_session.rs b/nym-registration-client/src/lp_client/nested_session.rs index 887338f2e0..631206785c 100644 --- a/nym-registration-client/src/lp_client/nested_session.rs +++ b/nym-registration-client/src/lp_client/nested_session.rs @@ -38,6 +38,7 @@ use nym_lp_transport::traits::LpTransport; use nym_registration_common::dvpn::LpDvpnRegistrationResponseMessageContent; use nym_registration_common::{ LpRegistrationRequest, LpRegistrationResponse, WireguardConfiguration, + WireguardRegistrationData, }; use nym_wireguard_types::PeerPublicKey; use rand::{CryptoRng, RngCore}; @@ -372,7 +373,7 @@ impl NestedLpSession { gateway_identity: ed25519::PublicKey, bandwidth_controller: &dyn BandwidthTicketProvider, ticket_type: TicketType, - ) -> Result + ) -> Result where S: LpTransport + Unpin, { @@ -433,14 +434,7 @@ impl NestedLpSession { tracing::warn!("Gateway rejected registration: {reason}"); Err(LpClientError::RegistrationRejected { reason }) } - LpDvpnRegistrationResponseMessageContent::CompletedRegistration(res) => { - // we have managed to complete the registration - tracing::info!( - "LP registration successful! Allocated bandwidth: {} bytes", - res.available_bandwidth - ); - Ok(res.config) - } + LpDvpnRegistrationResponseMessageContent::CompletedRegistration(res) => Ok(res.config), LpDvpnRegistrationResponseMessageContent::RequiresCredential(_) => { Err(LpClientError::unexpected_response( "received request for additional dvpn data after sending credential!", @@ -499,7 +493,7 @@ impl NestedLpSession { let mut psk = [0u8; 32]; rng.fill_bytes(&mut psk); - let request = LpRegistrationRequest::new_initial_dvpn(wg_public_key, psk, ticket_type); + let request = LpRegistrationRequest::new_initial_dvpn(wg_public_key, psk); // Step 3: Serialize the request let send_data = request.to_lp_data()?; @@ -536,14 +530,7 @@ impl NestedLpSession { tracing::warn!("Gateway rejected registration: {reason}"); return Err(LpClientError::RegistrationRejected { reason }); } - LpDvpnRegistrationResponseMessageContent::CompletedRegistration(res) => { - // we have already registered with this gateway before, the gateway has updated the psk and sent us the config - tracing::info!( - "LP registration successful! Allocated bandwidth: {} bytes", - res.available_bandwidth - ); - res.config - } + LpDvpnRegistrationResponseMessageContent::CompletedRegistration(res) => res.config, LpDvpnRegistrationResponseMessageContent::RequiresCredential(_) => { // we're registering for the first time with this gateway - we need to attach a credential @@ -562,7 +549,7 @@ impl NestedLpSession { Ok(WireguardConfiguration { public_key: final_response.public_key, psk: Some(psk), - endpoint: SocketAddr::new(self.exit_address.ip(), final_response.endpoint.port()), + endpoint: SocketAddr::new(self.exit_address.ip(), final_response.port), private_ipv4: final_response.private_ipv4, private_ipv6: final_response.private_ipv6, }) From 801dcdda1e4622a75cbe39a2d559baac70977b22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Fri, 6 Feb 2026 08:41:19 +0000 Subject: [PATCH 50/51] do not run LP (#6422) --- .../validator-client/src/client.rs | 62 ++++++------ .../validator-client/src/nym_api/mod.rs | 96 +++++++++---------- .../mixnet/query/query_all_gateways.rs | 2 +- .../mixnet/query/query_all_mixnodes.rs | 2 +- nym-api/src/nym_nodes/handlers/v1.rs | 2 +- nym-api/src/nym_nodes/handlers/v2.rs | 34 ++++--- nym-gateway-probe/src/common/nodes.rs | 75 +++++++-------- nym-node/src/node/http/router/mod.rs | 11 +-- nym-node/src/node/mod.rs | 93 ++++++++---------- nym-registration-client/src/builder/mod.rs | 55 +++++------ nym-registration-client/src/clients/lp.rs | 2 + nym-registration-client/src/clients/mod.rs | 6 +- nym-registration-client/src/lib.rs | 4 +- .../src/lp_client/helpers.rs | 2 + wasm/mix-fetch/src/helpers.rs | 2 +- 15 files changed, 222 insertions(+), 226 deletions(-) diff --git a/common/client-libs/validator-client/src/client.rs b/common/client-libs/validator-client/src/client.rs index 43ab5808a7..d461157f52 100644 --- a/common/client-libs/validator-client/src/client.rs +++ b/common/client-libs/validator-client/src/client.rs @@ -20,7 +20,7 @@ use nym_api_requests::ecash::{ }; use nym_api_requests::models::{ ApiHealthResponse, GatewayCoreStatusResponse, HistoricalPerformanceResponse, - MixnodeCoreStatusResponse, NymNodeDescriptionV1, NymNodeDescriptionV2, + MixnodeCoreStatusResponse, NymNodeDescriptionV1, }; use nym_api_requests::nym_nodes::{ NodesByAddressesResponse, SemiSkimmedNodesWithMetadata, SkimmedNode, SkimmedNodesWithMetadata, @@ -273,18 +273,18 @@ impl Client { Ok(history) } - #[deprecated(note = "use get_all_cached_described_nodes_v2 instead")] + // #[deprecated(note = "use get_all_cached_described_nodes_v2 instead")] pub async fn get_all_cached_described_nodes( &self, ) -> Result, ValidatorClientError> { Ok(self.nym_api.get_all_described_nodes().await?) } - pub async fn get_all_cached_described_nodes_v2( - &self, - ) -> Result, ValidatorClientError> { - Ok(self.nym_api.get_all_described_nodes_v2().await?) - } + // pub async fn get_all_cached_described_nodes_v2( + // &self, + // ) -> Result, ValidatorClientError> { + // Ok(self.nym_api.get_all_described_nodes_v2().await?) + // } pub async fn get_all_cached_bonded_nym_nodes( &self, @@ -473,7 +473,7 @@ impl NymApiClient { Ok(self.nym_api.health().await?) } - #[deprecated(note = "use .get_all_described_nodes_v2 instead")] + // #[deprecated(note = "use .get_all_described_nodes_v2 instead")] pub async fn get_all_described_nodes( &self, ) -> Result, ValidatorClientError> { @@ -495,29 +495,29 @@ impl NymApiClient { Ok(descriptions) } - pub async fn get_all_described_nodes_v2( - &self, - ) -> Result, ValidatorClientError> { - // TODO: deal with paging in macro or some helper function or something, because it's the same pattern everywhere - let mut page = 0; - let mut descriptions = Vec::new(); - - loop { - let mut res = self - .nym_api - .get_nodes_described_v2(Some(page), None) - .await?; - - descriptions.append(&mut res.data); - if descriptions.len() < res.pagination.total { - page += 1 - } else { - break; - } - } - - Ok(descriptions) - } + // pub async fn get_all_described_nodes_v2( + // &self, + // ) -> Result, ValidatorClientError> { + // // TODO: deal with paging in macro or some helper function or something, because it's the same pattern everywhere + // let mut page = 0; + // let mut descriptions = Vec::new(); + // + // loop { + // let mut res = self + // .nym_api + // .get_nodes_described_v2(Some(page), None) + // .await?; + // + // descriptions.append(&mut res.data); + // if descriptions.len() < res.pagination.total { + // page += 1 + // } else { + // break; + // } + // } + // + // Ok(descriptions) + // } pub async fn get_all_bonded_nym_nodes( &self, diff --git a/common/client-libs/validator-client/src/nym_api/mod.rs b/common/client-libs/validator-client/src/nym_api/mod.rs index cf71f706a4..08bcd7e7f3 100644 --- a/common/client-libs/validator-client/src/nym_api/mod.rs +++ b/common/client-libs/validator-client/src/nym_api/mod.rs @@ -17,7 +17,7 @@ use nym_api_requests::ecash::VerificationKeyResponse; use nym_api_requests::models::{ AnnotationResponse, ApiHealthResponse, BinaryBuildInformationOwned, ChainBlocksStatusResponse, ChainStatusResponse, KeyRotationInfoResponse, NodePerformanceResponse, NodeRefreshBody, - NymNodeDescriptionV1, NymNodeDescriptionV2, PerformanceHistoryResponse, RewardedSetResponse, + NymNodeDescriptionV1, PerformanceHistoryResponse, RewardedSetResponse, SignerInformationResponse, }; use nym_api_requests::nym_nodes::{ @@ -117,7 +117,7 @@ pub trait NymApiClientExt: ApiClient { } #[tracing::instrument(level = "debug", skip_all)] - #[deprecated(note = "use .get_nodes_described_v2 instead")] + // #[deprecated(note = "use .get_nodes_described_v2 instead")] async fn get_nodes_described( &self, page: Option, @@ -144,32 +144,32 @@ pub trait NymApiClientExt: ApiClient { .await } - #[tracing::instrument(level = "debug", skip_all)] - async fn get_nodes_described_v2( - &self, - page: Option, - per_page: Option, - ) -> Result, NymAPIError> { - let mut params = Vec::new(); - - if let Some(page) = page { - params.push(("page", page.to_string())) - } - - if let Some(per_page) = per_page { - params.push(("per_page", per_page.to_string())) - } - - self.get_json( - &[ - routes::V2_API_VERSION, - routes::NYM_NODES_ROUTES, - routes::NYM_NODES_DESCRIBED, - ], - ¶ms, - ) - .await - } + // #[tracing::instrument(level = "debug", skip_all)] + // async fn get_nodes_described_v2( + // &self, + // page: Option, + // per_page: Option, + // ) -> Result, NymAPIError> { + // let mut params = Vec::new(); + // + // if let Some(page) = page { + // params.push(("page", page.to_string())) + // } + // + // if let Some(per_page) = per_page { + // params.push(("per_page", per_page.to_string())) + // } + // + // self.get_json( + // &[ + // routes::V2_API_VERSION, + // routes::NYM_NODES_ROUTES, + // routes::NYM_NODES_DESCRIBED, + // ], + // ¶ms, + // ) + // .await + // } async fn get_current_rewarded_set(&self) -> Result { self.get_rewarded_set().await @@ -302,8 +302,8 @@ pub trait NymApiClientExt: ApiClient { Ok(SkimmedNodesWithMetadata::new(nodes, metadata)) } - #[deprecated(note = "use .get_all_described_nodes_v2 instead")] - #[allow(deprecated)] + // #[deprecated(note = "use .get_all_described_nodes_v2 instead")] + // #[allow(deprecated)] async fn get_all_described_nodes(&self) -> Result, NymAPIError> { // TODO: deal with paging in macro or some helper function or something, because it's the same pattern everywhere let mut page = 0; @@ -323,24 +323,24 @@ pub trait NymApiClientExt: ApiClient { Ok(descriptions) } - async fn get_all_described_nodes_v2(&self) -> Result, NymAPIError> { - // TODO: deal with paging in macro or some helper function or something, because it's the same pattern everywhere - let mut page = 0; - let mut descriptions = Vec::new(); - - loop { - let mut res = self.get_nodes_described_v2(Some(page), None).await?; - - descriptions.append(&mut res.data); - if descriptions.len() < res.pagination.total { - page += 1 - } else { - break; - } - } - - Ok(descriptions) - } + // async fn (&self) -> Result, NymAPIError> { + // // TODO: deal with paging in macro or some helper function or something, because it's the same pattern everywhere + // let mut page = 0; + // let mut descriptions = Vec::new(); + // + // loop { + // let mut res = self.get_nodes_described_v2(Some(page), None).await?; + // + // descriptions.append(&mut res.data); + // if descriptions.len() < res.pagination.total { + // page += 1 + // } else { + // break; + // } + // } + // + // Ok(descriptions) + // } #[tracing::instrument(level = "debug", skip_all)] async fn get_nym_nodes( diff --git a/common/commands/src/validator/mixnet/query/query_all_gateways.rs b/common/commands/src/validator/mixnet/query/query_all_gateways.rs index 516a533315..f03fb61d28 100644 --- a/common/commands/src/validator/mixnet/query/query_all_gateways.rs +++ b/common/commands/src/validator/mixnet/query/query_all_gateways.rs @@ -14,7 +14,7 @@ pub struct Args { } pub async fn query(args: Args, client: &QueryClientWithNyxd) { - match client.get_all_cached_described_nodes_v2().await { + match client.get_all_cached_described_nodes().await { Ok(res) => match args.identity_key { Some(identity_key) => { let node = res.iter().find(|node| { diff --git a/common/commands/src/validator/mixnet/query/query_all_mixnodes.rs b/common/commands/src/validator/mixnet/query/query_all_mixnodes.rs index b1a81780b9..ef5b4a9548 100644 --- a/common/commands/src/validator/mixnet/query/query_all_mixnodes.rs +++ b/common/commands/src/validator/mixnet/query/query_all_mixnodes.rs @@ -14,7 +14,7 @@ pub struct Args { } pub async fn query(args: Args, client: &QueryClientWithNyxd) { - match client.get_all_cached_described_nodes_v2().await { + match client.get_all_cached_described_nodes().await { Ok(res) => match args.identity_key { Some(identity_key) => { let node = res.iter().find(|node| { diff --git a/nym-api/src/nym_nodes/handlers/v1.rs b/nym-api/src/nym_nodes/handlers/v1.rs index 36834a5fc4..02878bcaa6 100644 --- a/nym-api/src/nym_nodes/handlers/v1.rs +++ b/nym-api/src/nym_nodes/handlers/v1.rs @@ -231,7 +231,7 @@ async fn get_bonded_nodes( ), params(PaginationRequest) )] -#[deprecated(note = "use '/v2/nym-nodes/described' instead")] +// #[deprecated(note = "use '/v2/nym-nodes/described' instead")] async fn get_described_nodes( State(state): State, Query(pagination): Query, diff --git a/nym-api/src/nym_nodes/handlers/v2.rs b/nym-api/src/nym_nodes/handlers/v2.rs index ee49e575fa..502f2c6e1c 100644 --- a/nym-api/src/nym_nodes/handlers/v2.rs +++ b/nym-api/src/nym_nodes/handlers/v2.rs @@ -1,14 +1,14 @@ // Copyright 2026 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::node_status_api::models::AxumResult; +use crate::node_status_api::models::{AxumErrorResponse, AxumResult}; use crate::support::http::helpers::PaginationRequest; use crate::support::http::state::AppState; use axum::extract::{Query, State}; use axum::routing::get; use axum::Router; use nym_api_requests::models::NymNodeDescriptionV2; -use nym_api_requests::pagination::{PaginatedResponse, Pagination}; +use nym_api_requests::pagination::PaginatedResponse; use nym_http_api_common::FormattedResponse; use tower_http::compression::CompressionLayer; @@ -36,19 +36,23 @@ async fn get_described_nodes( State(state): State, Query(pagination): Query, ) -> AxumResult>> { - // TODO: implement it + let _ = state; let _ = pagination; - let output = pagination.output.unwrap_or_default(); + Err(AxumErrorResponse::not_implemented()) - let cache = state.described_nodes_cache.get().await?; - let descriptions = cache.all_nodes().cloned().collect::>(); - - Ok(output.to_response(PaginatedResponse { - pagination: Pagination { - total: descriptions.len(), - page: 0, - size: descriptions.len(), - }, - data: descriptions, - })) + // // TODO: implement it + // let _ = pagination; + // let output = pagination.output.unwrap_or_default(); + // + // let cache = state.described_nodes_cache.get().await?; + // let descriptions = cache.all_nodes().cloned().collect::>(); + // + // Ok(output.to_response(PaginatedResponse { + // pagination: Pagination { + // total: descriptions.len(), + // page: 0, + // size: descriptions.len(), + // }, + // data: descriptions, + // })) } diff --git a/nym-gateway-probe/src/common/nodes.rs b/nym-gateway-probe/src/common/nodes.rs index d929d4285d..4e79e84ce6 100644 --- a/nym-gateway-probe/src/common/nodes.rs +++ b/nym-gateway-probe/src/common/nodes.rs @@ -3,9 +3,9 @@ use anyhow::{Context, anyhow, bail}; use nym_api_requests::models::{ - AuthenticatorDetailsV2, DeclaredRolesV2, DescribedNodeTypeV2, HostInformationV2, - IpPacketRouterDetailsV2, NetworkRequesterDetailsV2, NymNodeDataV2, - OffsetDateTimeJsonSchemaWrapper, WebSocketsV2, WireguardDetailsV2, + AuthenticatorDetailsV1, DeclaredRolesV1, DescribedNodeTypeV1, HostInformationV1, + IpPacketRouterDetailsV1, NetworkRequesterDetailsV1, NymNodeDataV1, + OffsetDateTimeJsonSchemaWrapper, WebSocketsV1, WireguardDetailsV1, }; use nym_authenticator_requests::AuthenticatorVersion; use nym_bin_common::build_information::BinaryBuildInformationOwned; @@ -13,14 +13,13 @@ use nym_crypto::asymmetric::x25519; use nym_http_api_client::UserAgent; use nym_kkt_ciphersuite::SignatureScheme; use nym_kkt_ciphersuite::{KEM, KEMKeyDigests}; -use nym_lp::packet::version; use nym_network_defaults::DEFAULT_NYM_NODE_HTTP_PORT; use nym_node_requests::api::client::NymNodeApiClientExt; use nym_node_requests::api::v1::node::models::AuxiliaryDetails as NodeAuxiliaryDetails; use nym_sdk::mixnet::NodeIdentity; use nym_sdk::mixnet::Recipient; use nym_validator_client::client::NymApiClientExt; -use nym_validator_client::models::NymNodeDescriptionV2; +use nym_validator_client::models::NymNodeDescriptionV1; use rand::prelude::IteratorRandom; use std::collections::HashMap; use std::net::{IpAddr, SocketAddr}; @@ -91,7 +90,7 @@ use url::Url; #[derive(Clone)] pub struct DirectoryNode { - described: NymNodeDescriptionV2, + described: NymNodeDescriptionV1, } impl DirectoryNode { @@ -130,16 +129,16 @@ impl DirectoryNode { .copied() .ok_or_else(|| anyhow!("no ip address known"))?; - let lp_data = description.lewes_protocol.as_ref().and_then(|lp_data| { - Some(TestedNodeLpDetails { - address: SocketAddr::new(ip_address, lp_data.control_port), - expected_kem_key_hashes: lp_data.kem_keys().ok()?, - expected_signing_key_hashes: lp_data.signing_keys().ok()?, - x25519: lp_data.x25519, - // \/ TODO: proper derivation from build version - lp_version: version::CURRENT, - }) - }); + // let lp_data = description.lewes_protocol.as_ref().and_then(|lp_data| { + // Some(TestedNodeLpDetails { + // address: SocketAddr::new(ip_address, lp_data.control_port), + // expected_kem_key_hashes: lp_data.kem_keys().ok()?, + // expected_signing_key_hashes: lp_data.signing_keys().ok()?, + // x25519: lp_data.x25519, + // // \/ TODO: proper derivation from build version + // lp_version: version::CURRENT, + // }) + // }); Ok(TestedNodeDetails { identity: self.identity(), @@ -148,7 +147,7 @@ impl DirectoryNode { authenticator_address, authenticator_version, ip_address: Some(ip_address), - lp_data, + lp_data: None, }) } } @@ -238,11 +237,11 @@ pub async fn query_gateway_by_ip(address: String) -> anyhow::Result anyhow::Result = - nr_result.map(|nr| NetworkRequesterDetailsV2 { + let network_requester: Option = + nr_result.map(|nr| NetworkRequesterDetailsV1 { address: nr.address, uses_exit_policy: false, // Field not availabe, to change if it becomes useful here }); - let ip_packet_router: Option = - ipr_result.map(|ipr| IpPacketRouterDetailsV2 { + let ip_packet_router: Option = + ipr_result.map(|ipr| IpPacketRouterDetailsV1 { address: ipr.address, }); - let authenticator: Option = - authenticator_result.map(|auth| AuthenticatorDetailsV2 { + let authenticator: Option = + authenticator_result.map(|auth| AuthenticatorDetailsV1 { address: auth.address, }); #[allow(deprecated)] - let wireguard: Option = - wireguard_result.map(|wg| WireguardDetailsV2 { + let wireguard: Option = + wireguard_result.map(|wg| WireguardDetailsV1 { port: wg.tunnel_port, // Use tunnel_port for deprecated port field tunnel_port: wg.tunnel_port, metadata_port: wg.metadata_port, @@ -285,14 +284,14 @@ pub async fn query_gateway_by_ip(address: String) -> anyhow::Result anyhow::Result Self { - self.api.v1_config.lewes_protocol.details = Some(lewes_protocol); - self - } + // #[must_use] + // pub fn with_lewes_protocol(mut self, lewes_protocol: LewesProtocol) -> Self { + // self.api.v1_config.lewes_protocol.details = Some(lewes_protocol); + // self + // } } pub struct NymNodeRouter { diff --git a/nym-node/src/node/mod.rs b/nym-node/src/node/mod.rs index a61c522846..460889d3db 100644 --- a/nym-node/src/node/mod.rs +++ b/nym-node/src/node/mod.rs @@ -42,7 +42,6 @@ use nym_bin_common::bin_info; use nym_credential_verification::UpgradeModeState; use nym_crypto::asymmetric::{ed25519, x25519}; use nym_gateway::node::{ActiveClientsStore, GatewayTasksBuilder, UpgradeModeCheckRequestSender}; -use nym_kkt::key_utils::produce_key_digests; use nym_mixnet_client::client::ActiveConnections; use nym_mixnet_client::forwarder::MixForwardingSender; use nym_network_requester::{ @@ -51,9 +50,6 @@ use nym_network_requester::{ }; use nym_node_metrics::NymNodeMetrics; use nym_node_metrics::events::MetricEventsSender; -use nym_node_requests::api::v1::lewes_protocol::models::{ - LPHashFunction, LPKEM, LPSignatureScheme, -}; use nym_node_requests::api::v1::node::models::{AnnouncePorts, NodeDescription}; use nym_noise::config::{NoiseConfig, NoiseNetworkView}; use nym_noise_keys::VersionedNoiseKeyV1; @@ -66,7 +62,6 @@ use nym_verloc::{self, measurements::VerlocMeasurer}; use nym_wireguard::{WireguardGatewayData, peer_controller::PeerControlRequest}; use rand::rngs::OsRng; use rand::{CryptoRng, RngCore}; -use std::collections::HashMap; use std::net::SocketAddr; use std::ops::Deref; use std::path::Path; @@ -714,11 +709,13 @@ impl NymNode { self.config.gateway_tasks.lp.control_bind_address, self.config.gateway_tasks.lp.data_bind_address, ); - let mut lp_listener = gateway_tasks_builder + let lp_listener = gateway_tasks_builder .build_lp_listener(wg_peer_registrator.clone(), active_clients_store.clone()) .await?; - self.shutdown_tracker() - .try_spawn_named(async move { lp_listener.run().await }, "LpListener"); + // make sure lp listener can be built, but do not start it + let _ = lp_listener; + // self.shutdown_tracker() + // .try_spawn_named(async move { lp_listener.run().await }, "LpListener"); } else { info!("node not running in entry mode: the websocket and LP will remain closed"); } @@ -794,40 +791,40 @@ impl NymNode { Ok(()) } - - fn compute_kem_key_hashes(&self) -> HashMap> { - let kem = LPKEM::X25519; - - let kem_key_bytes = self.entry_gateway.psq_kem_key.public_key().as_bytes(); - - // convert from `nym_kkt_ciphersuite` types into `nym_nodes_requests` - let digests = produce_key_digests(kem_key_bytes.as_ref()) - .into_iter() - .map(|(f, digest)| (f.into(), hex::encode(&digest))) - .collect(); - - let mut hashes = HashMap::new(); - hashes.insert(kem, digests); - hashes - } - - fn compute_signing_key_hashes( - &self, - ) -> HashMap> { - let scheme = LPSignatureScheme::Ed25519; - - let kem_key_bytes = self.ed25519_identity_keys.public_key().as_bytes(); - - // convert from `nym_kkt_ciphersuite` types into `nym_nodes_requests` - let digests = produce_key_digests(kem_key_bytes.as_ref()) - .into_iter() - .map(|(f, digest)| (f.into(), hex::encode(&digest))) - .collect(); - - let mut hashes = HashMap::new(); - hashes.insert(scheme, digests); - hashes - } + // + // fn compute_kem_key_hashes(&self) -> HashMap> { + // let kem = LPKEM::X25519; + // + // let kem_key_bytes = self.entry_gateway.psq_kem_key.public_key().as_bytes(); + // + // // convert from `nym_kkt_ciphersuite` types into `nym_nodes_requests` + // let digests = produce_key_digests(kem_key_bytes.as_ref()) + // .into_iter() + // .map(|(f, digest)| (f.into(), hex::encode(&digest))) + // .collect(); + // + // let mut hashes = HashMap::new(); + // hashes.insert(kem, digests); + // hashes + // } + // + // fn compute_signing_key_hashes( + // &self, + // ) -> HashMap> { + // let scheme = LPSignatureScheme::Ed25519; + // + // let kem_key_bytes = self.ed25519_identity_keys.public_key().as_bytes(); + // + // // convert from `nym_kkt_ciphersuite` types into `nym_nodes_requests` + // let digests = produce_key_digests(kem_key_bytes.as_ref()) + // .into_iter() + // .map(|(f, digest)| (f.into(), hex::encode(&digest))) + // .collect(); + // + // let mut hashes = HashMap::new(); + // hashes.insert(scheme, digests); + // hashes + // } pub(crate) async fn build_http_server(&self) -> Result { let auxiliary_details = api_requests::v1::node::models::AuxiliaryDetails { @@ -902,15 +899,6 @@ impl NymNode { policy: None, }; - let lp_details = api_requests::v1::lewes_protocol::models::LewesProtocol::new( - self.modes().entry, - self.config.gateway_tasks.lp.announced_control_port(), - self.config.gateway_tasks.lp.announced_data_port(), - *self.x25519_noise_keys.public_key(), - self.compute_kem_key_hashes(), - self.compute_signing_key_hashes(), - ); - let mut config = HttpServerConfig::new() .with_landing_page_assets(self.config.http.landing_page_assets_path.as_ref()) .with_mixnode_details(mixnode_details) @@ -921,8 +909,7 @@ impl NymNode { .with_used_exit_policy(exit_policy_details) .with_description(self.description.clone()) .with_auxiliary_details(auxiliary_details) - .with_prometheus_bearer_token(self.config.http.access_token.clone()) - .with_lewes_protocol(lp_details); + .with_prometheus_bearer_token(self.config.http.access_token.clone()); if self.config.http.expose_system_info { config = config.with_system_info(get_system_info( diff --git a/nym-registration-client/src/builder/mod.rs b/nym-registration-client/src/builder/mod.rs index 397a943d86..37e66a03df 100644 --- a/nym-registration-client/src/builder/mod.rs +++ b/nym-registration-client/src/builder/mod.rs @@ -14,9 +14,7 @@ use nym_validator_client::{ }; use crate::{ - RegistrationClient, - clients::{LpBasedRegistrationClient, MixnetBasedRegistrationClient}, - config::RegistrationMode, + RegistrationClient, clients::MixnetBasedRegistrationClient, config::RegistrationMode, error::RegistrationClientError, }; use config::BuilderConfig; @@ -50,7 +48,10 @@ impl RegistrationClientBuilder { pub async fn build(self) -> Result { if self.use_lp() { tracing::debug!("Using LP for registration"); - Ok(RegistrationClient::Lp(Box::new(self.build_lp().await?))) + Err(RegistrationClientError::LpRegistrationNotPossible { + node_id: "any".to_string(), + }) + // Ok(RegistrationClient::Lp(Box::new(self.build_lp().await?))) } else { tracing::debug!("Using Mixnet for registration"); Ok(RegistrationClient::Mixnet(Box::new( @@ -123,29 +124,29 @@ impl RegistrationClientBuilder { }) } - async fn build_lp(self) -> Result { - let storage = self.config.setup_credential_storage().await?; - let config = self.config.registration_client_config(); - - let nyxd_client = get_nyxd_client(&self.config.network_env)?; - - let bandwidth_controller: Box = - if let Some(credential_storage) = storage { - Box::new(BandwidthController::new(credential_storage, nyxd_client)) - } else { - Box::new(BandwidthController::new( - EphemeralCredentialStorage::default(), - nyxd_client, - )) - }; - - Ok(LpBasedRegistrationClient { - config, - bandwidth_controller, - cancel_token: self.config.cancel_token.clone(), - fallback_client_builder: Some(self), - }) - } + // async fn build_lp(self) -> Result { + // let storage = self.config.setup_credential_storage().await?; + // let config = self.config.registration_client_config(); + // + // let nyxd_client = get_nyxd_client(&self.config.network_env)?; + // + // let bandwidth_controller: Box = + // if let Some(credential_storage) = storage { + // Box::new(BandwidthController::new(credential_storage, nyxd_client)) + // } else { + // Box::new(BandwidthController::new( + // EphemeralCredentialStorage::default(), + // nyxd_client, + // )) + // }; + // + // Ok(LpBasedRegistrationClient { + // config, + // bandwidth_controller, + // cancel_token: self.config.cancel_token.clone(), + // fallback_client_builder: Some(self), + // }) + // } } // temporary while we use the legacy bandwidth-controller diff --git a/nym-registration-client/src/clients/lp.rs b/nym-registration-client/src/clients/lp.rs index a65a44cd39..3f3ce0abbc 100644 --- a/nym-registration-client/src/clients/lp.rs +++ b/nym-registration-client/src/clients/lp.rs @@ -1,6 +1,8 @@ // Copyright 2026 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +#![allow(dead_code)] + use crate::builder::RegistrationClientBuilder; use crate::config::RegistrationClientConfig; use crate::config::RegistrationMode; diff --git a/nym-registration-client/src/clients/mod.rs b/nym-registration-client/src/clients/mod.rs index fd4a16f3d6..3a96c164de 100644 --- a/nym-registration-client/src/clients/mod.rs +++ b/nym-registration-client/src/clients/mod.rs @@ -1,8 +1,10 @@ // Copyright 2026 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -pub(crate) mod lp; -pub(crate) mod mixnet; +#![allow(unused)] + +pub mod lp; +pub mod mixnet; pub use lp::LpBasedRegistrationClient; pub use mixnet::MixnetBasedRegistrationClient; diff --git a/nym-registration-client/src/lib.rs b/nym-registration-client/src/lib.rs index ca00e270a0..3ae9cb64fa 100644 --- a/nym-registration-client/src/lib.rs +++ b/nym-registration-client/src/lib.rs @@ -24,14 +24,14 @@ mod types; pub enum RegistrationClient { Mixnet(Box), - Lp(Box), + // Lp(Box), } impl RegistrationClient { pub async fn register(self) -> Result { match self { RegistrationClient::Mixnet(client) => client.register().await, - RegistrationClient::Lp(client) => client.register().await, + // RegistrationClient::Lp(client) => client.register().await, } } } diff --git a/nym-registration-client/src/lp_client/helpers.rs b/nym-registration-client/src/lp_client/helpers.rs index e972b21b45..3eb53706fd 100644 --- a/nym-registration-client/src/lp_client/helpers.rs +++ b/nym-registration-client/src/lp_client/helpers.rs @@ -1,6 +1,8 @@ // Copyright 2026 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +#![allow(dead_code)] + use crate::LpClientError; use nym_crypto::asymmetric::ed25519; use nym_lp::message::ForwardPacketData; diff --git a/wasm/mix-fetch/src/helpers.rs b/wasm/mix-fetch/src/helpers.rs index 27b8513e70..9476d61715 100644 --- a/wasm/mix-fetch/src/helpers.rs +++ b/wasm/mix-fetch/src/helpers.rs @@ -25,7 +25,7 @@ pub(crate) async fn get_network_requester( url::Url::parse(&nym_api_url.unwrap_or(NYM_API_URL.to_string()))?, None, ); - let nodes = client.get_all_described_nodes_v2().await?; + let nodes = client.get_all_described_nodes().await?; let providers: Vec<_> = nodes .iter() .filter_map(|node| { From 1ecb457c66ccabc4877e3822566f266c7331d29e Mon Sep 17 00:00:00 2001 From: benedettadavico Date: Tue, 10 Feb 2026 10:30:45 +0100 Subject: [PATCH 51/51] update changelog --- CHANGELOG.md | 80 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e0ea936754..d58c32a7e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,86 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https:// ## [Unreleased] +## [2026.3-parmigiano] (2026-02-10) + +- chore: disable LP on parmigiano branch ([#6422]) +- revert mixnet-based client fautly changes from LP ([#6420]) +- [LP fix] Registration client with fallback ([#6419]) +- Lp/ip pool fixes ([#6412]) +- [LP-fix] expose wg psk for the vpn-client ([#6411]) +- LP-fix : configurable LP timeouts ([#6409]) +- LP-fix : add LP x25519 key to the description ([#6408]) +- use rng that is Send ([#6404]) +- use local kem key instead of local x25519 ([#6402]) +- [LP Gateway Probe] CLI and behavior improvements ([#6400]) +- lp: attempt to negotiate (and use) protocol version ([#6399]) +- bugfix: use correct reserved bytes when parsing LpHeader ([#6398]) +- Lp/bugfix/share ip allocation ([#6395]) +- feat: use hex-encoding for lp key digests ([#6394]) +- Add socks5 test to gateway-probe ([#6393]) +- [LP Gateway probe] Improve file structure ([#6391]) +- Reduce the size of `HttpClientError` ([#6390]) +- Lp/two step dvpn reg ([#6386]) +- Add extra configured nym api url to env ([#6382]) +- Lp/dvpn psk injection ([#6378]) +- LP: include signing key digests to LP responses ([#6373]) +- Lp/use noise x25519 ([#6372]) +- Topology fallback ([#6363]) +- NS API socks5 support ([#6361]) +- LP: modified LPRemotePeer to dynamically choose required KEM key hash ([#6358]) +- Fix KKT Integration into LP ([#6357]) +- LP: mixnet reg fixes ([#6356]) +- LP: announced KEM key hashes ([#6349]) +- revert faulty drop changes ([#6346]) +- small qol changes ([#6340]) +- Apply configured api urls via env ([#6337]) +- lp chore: make sure to take reserved bytes straight from the header ([#6336]) +- LP: x25519/ed22519 cleanup round ([#6335]) +- Lp/encrypted kkt ([#6331]) +- ensure packets with incompatible versions are rejected ([#6326]) +- standarise lp serialisation: ([#6324]) +- Upgrade to def_guard_wireguard v0.8.0 ([#6315]) +- Max/crates io prep v2 ([#6270]) + +[#6422]: https://github.com/nymtech/nym/pull/6422 +[#6420]: https://github.com/nymtech/nym/pull/6420 +[#6419]: https://github.com/nymtech/nym/pull/6419 +[#6412]: https://github.com/nymtech/nym/pull/6412 +[#6411]: https://github.com/nymtech/nym/pull/6411 +[#6409]: https://github.com/nymtech/nym/pull/6409 +[#6408]: https://github.com/nymtech/nym/pull/6408 +[#6404]: https://github.com/nymtech/nym/pull/6404 +[#6402]: https://github.com/nymtech/nym/pull/6402 +[#6400]: https://github.com/nymtech/nym/pull/6400 +[#6399]: https://github.com/nymtech/nym/pull/6399 +[#6398]: https://github.com/nymtech/nym/pull/6398 +[#6395]: https://github.com/nymtech/nym/pull/6395 +[#6394]: https://github.com/nymtech/nym/pull/6394 +[#6393]: https://github.com/nymtech/nym/pull/6393 +[#6391]: https://github.com/nymtech/nym/pull/6391 +[#6390]: https://github.com/nymtech/nym/pull/6390 +[#6386]: https://github.com/nymtech/nym/pull/6386 +[#6382]: https://github.com/nymtech/nym/pull/6382 +[#6378]: https://github.com/nymtech/nym/pull/6378 +[#6373]: https://github.com/nymtech/nym/pull/6373 +[#6372]: https://github.com/nymtech/nym/pull/6372 +[#6363]: https://github.com/nymtech/nym/pull/6363 +[#6361]: https://github.com/nymtech/nym/pull/6361 +[#6358]: https://github.com/nymtech/nym/pull/6358 +[#6357]: https://github.com/nymtech/nym/pull/6357 +[#6356]: https://github.com/nymtech/nym/pull/6356 +[#6349]: https://github.com/nymtech/nym/pull/6349 +[#6346]: https://github.com/nymtech/nym/pull/6346 +[#6340]: https://github.com/nymtech/nym/pull/6340 +[#6337]: https://github.com/nymtech/nym/pull/6337 +[#6336]: https://github.com/nymtech/nym/pull/6336 +[#6335]: https://github.com/nymtech/nym/pull/6335 +[#6331]: https://github.com/nymtech/nym/pull/6331 +[#6326]: https://github.com/nymtech/nym/pull/6326 +[#6324]: https://github.com/nymtech/nym/pull/6324 +[#6315]: https://github.com/nymtech/nym/pull/6315 +[#6270]: https://github.com/nymtech/nym/pull/6270 + ## [2026.2-oscypek] (2026-01-27) - bugfix: downgrade gateway protocol to clients proposed version ([#6377])