diff --git a/Cargo.lock b/Cargo.lock index cf99c2a815..475d2e657c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4622,6 +4622,7 @@ dependencies = [ "hmac", "nym-credentials-interface", "nym-crypto", + "nym-network-defaults", "nym-service-provider-requests-common", "nym-sphinx", "nym-wireguard-types", diff --git a/common/authenticator-requests/Cargo.toml b/common/authenticator-requests/Cargo.toml index 4dd8570281..cb7236e700 100644 --- a/common/authenticator-requests/Cargo.toml +++ b/common/authenticator-requests/Cargo.toml @@ -17,6 +17,7 @@ thiserror = { 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" } diff --git a/common/authenticator-requests/src/lib.rs b/common/authenticator-requests/src/lib.rs index dd98cc9f7f..7fd3d7d1fe 100644 --- a/common/authenticator-requests/src/lib.rs +++ b/common/authenticator-requests/src/lib.rs @@ -4,13 +4,14 @@ pub mod v1; pub mod v2; pub mod v3; +pub mod v4; mod error; pub use error::Error; -pub use v3 as latest; +pub use v4 as latest; -pub const CURRENT_VERSION: u8 = 3; +pub const CURRENT_VERSION: u8 = 4; fn make_bincode_serializer() -> impl bincode::Options { use bincode::Options; diff --git a/common/authenticator-requests/src/v3/conversion.rs b/common/authenticator-requests/src/v3/conversion.rs index 15659271b2..4b43715966 100644 --- a/common/authenticator-requests/src/v3/conversion.rs +++ b/common/authenticator-requests/src/v3/conversion.rs @@ -9,7 +9,7 @@ impl From for v3::request::AuthenticatorReque fn from(authenticator_request: v2::request::AuthenticatorRequest) -> Self { Self { protocol: Protocol { - version: 2, + version: 3, service_provider_type: ServiceProviderType::Authenticator, }, data: authenticator_request.data.into(), diff --git a/common/authenticator-requests/src/v4/conversion.rs b/common/authenticator-requests/src/v4/conversion.rs new file mode 100644 index 0000000000..183b3440c3 --- /dev/null +++ b/common/authenticator-requests/src/v4/conversion.rs @@ -0,0 +1,200 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use nym_service_provider_requests_common::{Protocol, ServiceProviderType}; + +use crate::{v3, v4}; + +impl From for v4::request::AuthenticatorRequest { + fn from(authenticator_request: v3::request::AuthenticatorRequest) -> Self { + Self { + protocol: Protocol { + version: 4, + service_provider_type: ServiceProviderType::Authenticator, + }, + data: authenticator_request.data.into(), + reply_to: authenticator_request.reply_to, + request_id: authenticator_request.request_id, + } + } +} + +impl From for v4::request::AuthenticatorRequestData { + fn from(authenticator_request_data: v3::request::AuthenticatorRequestData) -> Self { + match authenticator_request_data { + v3::request::AuthenticatorRequestData::Initial(init_msg) => { + v4::request::AuthenticatorRequestData::Initial(init_msg.into()) + } + v3::request::AuthenticatorRequestData::Final(gw_client) => { + v4::request::AuthenticatorRequestData::Final(gw_client.into()) + } + v3::request::AuthenticatorRequestData::QueryBandwidth(pub_key) => { + v4::request::AuthenticatorRequestData::QueryBandwidth(pub_key) + } + v3::request::AuthenticatorRequestData::TopUpBandwidth(top_up_message) => { + v4::request::AuthenticatorRequestData::TopUpBandwidth(top_up_message.into()) + } + } + } +} + +impl From for v4::registration::InitMessage { + fn from(init_msg: v3::registration::InitMessage) -> Self { + Self { + pub_key: init_msg.pub_key, + } + } +} + +impl From> for Box { + fn from(gw_client: Box) -> Self { + Box::new(v4::registration::FinalMessage { + gateway_client: gw_client.gateway_client.into(), + credential: gw_client.credential, + }) + } +} + +impl From> for Box { + fn from(top_up_message: Box) -> Self { + Box::new(v4::topup::TopUpMessage { + pub_key: top_up_message.pub_key, + credential: top_up_message.credential, + }) + } +} + +impl From for v4::registration::GatewayClient { + fn from(gw_client: v3::registration::GatewayClient) -> Self { + Self { + pub_key: gw_client.pub_key, + private_ips: gw_client.private_ip.into(), + mac: gw_client.mac.into(), + } + } +} + +impl From for v3::registration::GatewayClient { + fn from(gw_client: v4::registration::GatewayClient) -> Self { + Self { + pub_key: gw_client.pub_key, + private_ip: gw_client.private_ips.ipv4.into(), + mac: gw_client.mac.into(), + } + } +} + +impl From for v4::registration::ClientMac { + fn from(mac: v3::registration::ClientMac) -> Self { + Self::new(mac.to_vec()) + } +} + +impl From for v3::registration::ClientMac { + fn from(mac: v4::registration::ClientMac) -> Self { + Self::new(mac.to_vec()) + } +} + +impl TryFrom for v3::response::AuthenticatorResponse { + type Error = crate::Error; + + fn try_from( + authenticator_response: v4::response::AuthenticatorResponse, + ) -> Result { + Ok(Self { + data: authenticator_response.data.try_into()?, + reply_to: authenticator_response.reply_to, + protocol: authenticator_response.protocol, + }) + } +} + +impl TryFrom for v3::response::AuthenticatorResponseData { + type Error = crate::Error; + + fn try_from( + authenticator_response_data: v4::response::AuthenticatorResponseData, + ) -> Result { + match authenticator_response_data { + v4::response::AuthenticatorResponseData::PendingRegistration( + pending_registration_response, + ) => Ok( + v3::response::AuthenticatorResponseData::PendingRegistration( + pending_registration_response.into(), + ), + ), + v4::response::AuthenticatorResponseData::Registered(registered_response) => Ok( + v3::response::AuthenticatorResponseData::Registered(registered_response.into()), + ), + v4::response::AuthenticatorResponseData::RemainingBandwidth( + remaining_bandwidth_response, + ) => Ok(v3::response::AuthenticatorResponseData::RemainingBandwidth( + remaining_bandwidth_response.into(), + )), + v4::response::AuthenticatorResponseData::TopUpBandwidth(_) => { + Err(Self::Error::Conversion( + "a v3 request couldn't produce a v4 only type of response".to_string(), + )) + } + } + } +} + +impl From for v3::response::PendingRegistrationResponse { + fn from(value: v4::response::PendingRegistrationResponse) -> Self { + Self { + request_id: value.request_id, + reply_to: value.reply_to, + reply: value.reply.into(), + } + } +} + +impl From for v3::response::RegisteredResponse { + fn from(value: v4::response::RegisteredResponse) -> Self { + Self { + request_id: value.request_id, + reply_to: value.reply_to, + reply: value.reply.into(), + } + } +} + +impl From for v3::response::RemainingBandwidthResponse { + fn from(value: v4::response::RemainingBandwidthResponse) -> Self { + Self { + request_id: value.request_id, + reply_to: value.reply_to, + reply: value.reply.map(Into::into), + } + } +} + +impl From for v3::registration::RegistrationData { + fn from(value: v4::registration::RegistrationData) -> Self { + Self { + nonce: value.nonce, + gateway_data: value.gateway_data.into(), + wg_port: value.wg_port, + } + } +} + +impl From for v3::registration::RegistredData { + fn from(value: v4::registration::RegistredData) -> Self { + Self { + pub_key: value.pub_key, + private_ip: value.private_ips.ipv4.into(), + wg_port: value.wg_port, + } + } +} + +impl From for v3::registration::RemainingBandwidthData { + fn from(value: v4::registration::RemainingBandwidthData) -> Self { + Self { + available_bandwidth: value.available_bandwidth, + } + } +} diff --git a/common/authenticator-requests/src/v4/mod.rs b/common/authenticator-requests/src/v4/mod.rs new file mode 100644 index 0000000000..5b553a95c7 --- /dev/null +++ b/common/authenticator-requests/src/v4/mod.rs @@ -0,0 +1,10 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +pub mod conversion; +pub mod registration; +pub mod request; +pub mod response; +pub mod topup; + +pub const VERSION: u8 = 4; diff --git a/common/authenticator-requests/src/v4/registration.rs b/common/authenticator-requests/src/v4/registration.rs new file mode 100644 index 0000000000..1121d2c1f1 --- /dev/null +++ b/common/authenticator-requests/src/v4/registration.rs @@ -0,0 +1,258 @@ +// -2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::error::Error; +use base64::{engine::general_purpose, Engine}; +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; +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; +use std::time::SystemTime; +use std::{fmt, ops::Deref, str::FromStr}; + +#[cfg(feature = "verify")] +use hmac::{Hmac, Mac}; +#[cfg(feature = "verify")] +use nym_crypto::asymmetric::encryption::PrivateKey; +#[cfg(feature = "verify")] +use sha2::Sha256; + +pub type PendingRegistrations = HashMap; +pub type PrivateIPs = HashMap; + +#[cfg(feature = "verify")] +pub type HmacSha256 = Hmac; + +pub type Nonce = u64; +pub type Taken = Option; + +pub const BANDWIDTH_CAP_PER_DAY: u64 = 1024 * 1024 * 1024; // 1 GB + +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub struct IpPair { + pub ipv4: Ipv4Addr, + pub ipv6: Ipv6Addr, +} + +impl IpPair { + pub fn new(ipv4: Ipv4Addr, ipv6: Ipv6Addr) -> Self { + IpPair { ipv4, ipv6 } + } +} + +impl fmt::Display for IpPair { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "({}, {})", self.ipv4, self.ipv6) + } +} + +impl From for IpPair { + fn from(value: IpAddr) -> Self { + let transformed_ipv4 = WG_TUN_DEVICE_IP_ADDRESS_V4; + let transformed_ipv6 = WG_TUN_DEVICE_IP_ADDRESS_V6; + let (ipv4, ipv6) = match value { + std::net::IpAddr::V4(ipv4_addr) => (ipv4_addr, transformed_ipv6), + std::net::IpAddr::V6(ipv6_addr) => (transformed_ipv4, ipv6_addr), + }; + IpPair::new(ipv4, ipv6) + } +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct InitMessage { + /// Base64 encoded x25519 public key + pub pub_key: PeerPublicKey, +} + +impl InitMessage { + pub fn new(pub_key: PeerPublicKey) -> Self { + InitMessage { pub_key } + } +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct FinalMessage { + /// Gateway client data + pub gateway_client: GatewayClient, + + /// Ecash credential + pub credential: Option, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct RegistrationData { + pub nonce: u64, + pub gateway_data: GatewayClient, + pub wg_port: u16, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct RegistredData { + pub pub_key: PeerPublicKey, + pub private_ips: IpPair, + pub wg_port: u16, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct RemainingBandwidthData { + pub available_bandwidth: i64, +} + +/// Client that wants to register sends its PublicKey bytes mac digest encrypted with a DH shared secret. +/// Gateway/Nym node can then verify pub_key payload using the same process +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct GatewayClient { + /// Base64 encoded x25519 public key + pub pub_key: PeerPublicKey, + + /// Assigned private IPs (v4 and v6) + pub private_ips: IpPair, + + /// Sha256 hmac on the data (alongside the prior nonce) + pub mac: ClientMac, +} + +impl GatewayClient { + #[cfg(feature = "verify")] + pub fn new( + local_secret: &PrivateKey, + remote_public: x25519_dalek::PublicKey, + private_ips: IpPair, + nonce: u64, + ) -> Self { + // convert from 1.0 x25519-dalek private key into 2.0 x25519-dalek + #[allow(clippy::expect_used)] + let static_secret = x25519_dalek::StaticSecret::from(local_secret.to_bytes()); + let local_public: x25519_dalek::PublicKey = (&static_secret).into(); + + let dh = static_secret.diffie_hellman(&remote_public); + + // TODO: change that to use our nym_crypto::hmac module instead + #[allow(clippy::expect_used)] + let mut mac = HmacSha256::new_from_slice(dh.as_bytes()) + .expect("x25519 shared secret is always 32 bytes long"); + + mac.update(local_public.as_bytes()); + mac.update(private_ips.to_string().as_bytes()); + mac.update(&nonce.to_le_bytes()); + + GatewayClient { + pub_key: PeerPublicKey::new(local_public), + private_ips, + mac: ClientMac(mac.finalize().into_bytes().to_vec()), + } + } + + // Reusable secret should be gateways Wireguard PK + // Client should perform this step when generating its payload, using its own WG PK + #[cfg(feature = "verify")] + pub fn verify(&self, gateway_key: &PrivateKey, nonce: u64) -> Result<(), Error> { + // convert from 1.0 x25519-dalek private key into 2.0 x25519-dalek + #[allow(clippy::expect_used)] + let static_secret = x25519_dalek::StaticSecret::from(gateway_key.to_bytes()); + + let dh = static_secret.diffie_hellman(&self.pub_key); + + // TODO: change that to use our nym_crypto::hmac module instead + #[allow(clippy::expect_used)] + let mut mac = HmacSha256::new_from_slice(dh.as_bytes()) + .expect("x25519 shared secret is always 32 bytes long"); + + mac.update(self.pub_key.as_bytes()); + mac.update(self.private_ips.to_string().as_bytes()); + mac.update(&nonce.to_le_bytes()); + + mac.verify_slice(&self.mac) + .map_err(|source| Error::FailedClientMacVerification { + client: self.pub_key.to_string(), + source, + }) + } + + pub fn pub_key(&self) -> PeerPublicKey { + self.pub_key + } +} + +// TODO: change the inner type into generic array of size HmacSha256::OutputSize +// TODO2: rely on our internal crypto/hmac +#[derive(Debug, Clone)] +pub struct ClientMac(Vec); + +impl fmt::Display for ClientMac { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", general_purpose::STANDARD.encode(&self.0)) + } +} + +impl ClientMac { + #[allow(dead_code)] + pub fn new(mac: Vec) -> Self { + ClientMac(mac) + } +} + +impl Deref for ClientMac { + type Target = Vec; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl FromStr for ClientMac { + type Err = Error; + + fn from_str(s: &str) -> Result { + let mac_bytes: Vec = + general_purpose::STANDARD + .decode(s) + .map_err(|source| Error::MalformedClientMac { + mac: s.to_string(), + source, + })?; + + Ok(ClientMac(mac_bytes)) + } +} + +impl Serialize for ClientMac { + fn serialize(&self, serializer: S) -> Result { + let encoded_key = general_purpose::STANDARD.encode(self.0.clone()); + serializer.serialize_str(&encoded_key) + } +} + +impl<'de> Deserialize<'de> for ClientMac { + fn deserialize>(deserializer: D) -> Result { + let encoded_key = String::deserialize(deserializer)?; + ClientMac::from_str(&encoded_key).map_err(serde::de::Error::custom) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use nym_crypto::asymmetric::encryption; + + #[test] + #[cfg(feature = "verify")] + fn client_request_roundtrip() { + let mut rng = rand::thread_rng(); + + let gateway_key_pair = encryption::KeyPair::new(&mut rng); + let client_key_pair = encryption::KeyPair::new(&mut rng); + + let nonce = 1234567890; + + let client = GatewayClient::new( + client_key_pair.private_key(), + x25519_dalek::PublicKey::from(gateway_key_pair.public_key().to_bytes()), + IpPair::new("10.0.0.42".parse().unwrap(), "fc00::42".parse().unwrap()), + nonce, + ); + assert!(client.verify(gateway_key_pair.private_key(), nonce).is_ok()) + } +} diff --git a/common/authenticator-requests/src/v4/request.rs b/common/authenticator-requests/src/v4/request.rs new file mode 100644 index 0000000000..aa4862b057 --- /dev/null +++ b/common/authenticator-requests/src/v4/request.rs @@ -0,0 +1,136 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use super::{ + registration::{FinalMessage, InitMessage}, + topup::TopUpMessage, +}; +use nym_service_provider_requests_common::{Protocol, ServiceProviderType}; +use nym_sphinx::addressing::Recipient; +use nym_wireguard_types::PeerPublicKey; +use serde::{Deserialize, Serialize}; + +use crate::make_bincode_serializer; + +use super::VERSION; + +fn generate_random() -> u64 { + use rand::RngCore; + let mut rng = rand::rngs::OsRng; + rng.next_u64() +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct AuthenticatorRequest { + pub protocol: Protocol, + pub data: AuthenticatorRequestData, + pub reply_to: Recipient, + pub request_id: u64, +} + +impl AuthenticatorRequest { + pub fn from_reconstructed_message( + message: &nym_sphinx::receiver::ReconstructedMessage, + ) -> Result { + use bincode::Options; + make_bincode_serializer().deserialize(&message.message) + } + + pub fn new_initial_request(init_message: InitMessage, reply_to: Recipient) -> (Self, u64) { + let request_id = generate_random(); + ( + Self { + protocol: Protocol { + service_provider_type: ServiceProviderType::Authenticator, + version: VERSION, + }, + data: AuthenticatorRequestData::Initial(init_message), + reply_to, + request_id, + }, + request_id, + ) + } + + pub fn new_final_request(final_message: FinalMessage, reply_to: Recipient) -> (Self, u64) { + let request_id = generate_random(); + ( + Self { + protocol: Protocol { + service_provider_type: ServiceProviderType::Authenticator, + version: VERSION, + }, + data: AuthenticatorRequestData::Final(Box::new(final_message)), + reply_to, + request_id, + }, + request_id, + ) + } + + pub fn new_query_request(peer_public_key: PeerPublicKey, reply_to: Recipient) -> (Self, u64) { + let request_id = generate_random(); + ( + Self { + protocol: Protocol { + service_provider_type: ServiceProviderType::Authenticator, + version: VERSION, + }, + data: AuthenticatorRequestData::QueryBandwidth(peer_public_key), + reply_to, + request_id, + }, + request_id, + ) + } + + pub fn new_topup_request(top_up_message: TopUpMessage, reply_to: Recipient) -> (Self, u64) { + let request_id = generate_random(); + ( + Self { + protocol: Protocol { + service_provider_type: ServiceProviderType::Authenticator, + version: VERSION, + }, + data: AuthenticatorRequestData::TopUpBandwidth(Box::new(top_up_message)), + reply_to, + request_id, + }, + request_id, + ) + } + + pub fn to_bytes(&self) -> Result, bincode::Error> { + use bincode::Options; + make_bincode_serializer().serialize(self) + } +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum AuthenticatorRequestData { + Initial(InitMessage), + Final(Box), + QueryBandwidth(PeerPublicKey), + TopUpBandwidth(Box), +} + +#[cfg(test)] +mod tests { + use super::*; + use std::str::FromStr; + + #[test] + fn check_first_bytes_protocol() { + let version = 4; + let data = AuthenticatorRequest { + protocol: Protocol { version, service_provider_type: ServiceProviderType::Authenticator }, + data: AuthenticatorRequestData::Initial(InitMessage::new( + PeerPublicKey::from_str("yvNUDpT5l7W/xDhiu6HkqTHDQwbs/B3J5UrLmORl1EQ=").unwrap(), + )), + reply_to: Recipient::try_from_base58_string("D1rrpsysCGCYXy9saP8y3kmNpGtJZUXN9SvFoUcqAsM9.9Ssso1ea5NfkbMASdiseDSjTN1fSWda5SgEVjdSN4CvV@GJqd3ZxpXWSNxTfx7B1pPtswpetH4LnJdFeLeuY5KUuN").unwrap(), + request_id: 1, + }; + let bytes = *data.to_bytes().unwrap().first_chunk::<2>().unwrap(); + assert_eq!(bytes, [version, ServiceProviderType::Authenticator as u8]); + } +} diff --git a/common/authenticator-requests/src/v4/response.rs b/common/authenticator-requests/src/v4/response.rs new file mode 100644 index 0000000000..370fc64671 --- /dev/null +++ b/common/authenticator-requests/src/v4/response.rs @@ -0,0 +1,157 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use super::registration::{RegistrationData, RegistredData, RemainingBandwidthData}; +use nym_service_provider_requests_common::{Protocol, ServiceProviderType}; +use nym_sphinx::addressing::Recipient; +use serde::{Deserialize, Serialize}; + +use crate::make_bincode_serializer; + +use super::VERSION; + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct AuthenticatorResponse { + pub protocol: Protocol, + pub data: AuthenticatorResponseData, + pub reply_to: Recipient, +} + +impl AuthenticatorResponse { + pub fn new_pending_registration_success( + registration_data: RegistrationData, + request_id: u64, + reply_to: Recipient, + ) -> Self { + Self { + protocol: Protocol { + service_provider_type: ServiceProviderType::Authenticator, + version: VERSION, + }, + data: AuthenticatorResponseData::PendingRegistration(PendingRegistrationResponse { + reply: registration_data, + reply_to, + request_id, + }), + reply_to, + } + } + + pub fn new_registered( + registred_data: RegistredData, + reply_to: Recipient, + request_id: u64, + ) -> Self { + Self { + protocol: Protocol { + service_provider_type: ServiceProviderType::Authenticator, + version: VERSION, + }, + data: AuthenticatorResponseData::Registered(RegisteredResponse { + reply: registred_data, + reply_to, + request_id, + }), + reply_to, + } + } + + pub fn new_remaining_bandwidth( + remaining_bandwidth_data: Option, + reply_to: Recipient, + request_id: u64, + ) -> Self { + Self { + protocol: Protocol { + service_provider_type: ServiceProviderType::Authenticator, + version: VERSION, + }, + data: AuthenticatorResponseData::RemainingBandwidth(RemainingBandwidthResponse { + reply: remaining_bandwidth_data, + reply_to, + request_id, + }), + reply_to, + } + } + + pub fn new_topup_bandwidth( + remaining_bandwidth_data: RemainingBandwidthData, + reply_to: Recipient, + request_id: u64, + ) -> Self { + Self { + protocol: Protocol { + service_provider_type: ServiceProviderType::Authenticator, + version: VERSION, + }, + data: AuthenticatorResponseData::TopUpBandwidth(TopUpBandwidthResponse { + reply: remaining_bandwidth_data, + reply_to, + request_id, + }), + reply_to, + } + } + + pub fn recipient(&self) -> Recipient { + self.reply_to + } + + pub fn to_bytes(&self) -> Result, bincode::Error> { + use bincode::Options; + make_bincode_serializer().serialize(self) + } + + pub fn from_reconstructed_message( + message: &nym_sphinx::receiver::ReconstructedMessage, + ) -> Result { + use bincode::Options; + make_bincode_serializer().deserialize(&message.message) + } + + pub fn id(&self) -> Option { + match &self.data { + AuthenticatorResponseData::PendingRegistration(response) => Some(response.request_id), + AuthenticatorResponseData::Registered(response) => Some(response.request_id), + AuthenticatorResponseData::RemainingBandwidth(response) => Some(response.request_id), + AuthenticatorResponseData::TopUpBandwidth(response) => Some(response.request_id), + } + } +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum AuthenticatorResponseData { + PendingRegistration(PendingRegistrationResponse), + Registered(RegisteredResponse), + RemainingBandwidth(RemainingBandwidthResponse), + TopUpBandwidth(TopUpBandwidthResponse), +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct PendingRegistrationResponse { + pub request_id: u64, + pub reply_to: Recipient, + pub reply: RegistrationData, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct RegisteredResponse { + pub request_id: u64, + pub reply_to: Recipient, + pub reply: RegistredData, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct RemainingBandwidthResponse { + pub request_id: u64, + pub reply_to: Recipient, + pub reply: Option, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct TopUpBandwidthResponse { + pub request_id: u64, + pub reply_to: Recipient, + pub reply: RemainingBandwidthData, +} diff --git a/common/authenticator-requests/src/v4/topup.rs b/common/authenticator-requests/src/v4/topup.rs new file mode 100644 index 0000000000..31a61a0659 --- /dev/null +++ b/common/authenticator-requests/src/v4/topup.rs @@ -0,0 +1,15 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use nym_credentials_interface::CredentialSpendingData; +use nym_wireguard_types::PeerPublicKey; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct TopUpMessage { + /// Base64 encoded x25519 public key + pub pub_key: PeerPublicKey, + + /// Ecash credential + pub credential: CredentialSpendingData, +} diff --git a/common/ip-packet-requests/src/v6/request.rs b/common/ip-packet-requests/src/v6/request.rs index 0f89731e27..67f1d09b0f 100644 --- a/common/ip-packet-requests/src/v6/request.rs +++ b/common/ip-packet-requests/src/v6/request.rs @@ -248,7 +248,7 @@ mod tests { data: IpPacketRequestData::StaticConnect( StaticConnectRequest { request_id: 123, - ips: IpPair::new(Ipv4Addr::from_str("10.0.0.1").unwrap(), Ipv6Addr::from_str("2001:db8:a160::1").unwrap()), + ips: IpPair::new(Ipv4Addr::from_str("10.0.0.1").unwrap(), Ipv6Addr::from_str("fc00::1").unwrap()), reply_to: Recipient::try_from_base58_string("D1rrpsysCGCYXy9saP8y3kmNpGtJZUXN9SvFoUcqAsM9.9Ssso1ea5NfkbMASdiseDSjTN1fSWda5SgEVjdSN4CvV@GJqd3ZxpXWSNxTfx7B1pPtswpetH4LnJdFeLeuY5KUuN").unwrap(), reply_to_hops: None, reply_to_avg_mix_delays: None, diff --git a/common/ip-packet-requests/src/v7/request.rs b/common/ip-packet-requests/src/v7/request.rs index 73266320e3..dc125068b7 100644 --- a/common/ip-packet-requests/src/v7/request.rs +++ b/common/ip-packet-requests/src/v7/request.rs @@ -429,7 +429,7 @@ mod tests { SignedStaticConnectRequest { request: StaticConnectRequest { request_id: 123, - ips: IpPair::new(Ipv4Addr::from_str("10.0.0.1").unwrap(), Ipv6Addr::from_str("2001:db8:a160::1").unwrap()), + ips: IpPair::new(Ipv4Addr::from_str("10.0.0.1").unwrap(), Ipv6Addr::from_str("fc00::1").unwrap()), reply_to: Recipient::try_from_base58_string("D1rrpsysCGCYXy9saP8y3kmNpGtJZUXN9SvFoUcqAsM9.9Ssso1ea5NfkbMASdiseDSjTN1fSWda5SgEVjdSN4CvV@GJqd3ZxpXWSNxTfx7B1pPtswpetH4LnJdFeLeuY5KUuN").unwrap(), reply_to_hops: None, reply_to_avg_mix_delays: None, diff --git a/common/network-defaults/src/constants.rs b/common/network-defaults/src/constants.rs index 91bb5c7931..6723b45e41 100644 --- a/common/network-defaults/src/constants.rs +++ b/common/network-defaults/src/constants.rs @@ -45,13 +45,15 @@ pub mod nyx { } pub mod wireguard { - use std::net::{IpAddr, Ipv4Addr}; + use std::net::{Ipv4Addr, Ipv6Addr}; pub const WG_PORT: u16 = 51822; // The interface used to route traffic pub const WG_TUN_BASE_NAME: &str = "nymwg"; pub const WG_TUN_DEVICE_ADDRESS: &str = "10.1.0.1"; - pub const WG_TUN_DEVICE_IP_ADDRESS: IpAddr = IpAddr::V4(Ipv4Addr::new(10, 1, 0, 1)); - pub const WG_TUN_DEVICE_NETMASK: &str = "255.255.255.0"; + pub const WG_TUN_DEVICE_IP_ADDRESS_V4: Ipv4Addr = Ipv4Addr::new(10, 1, 0, 1); + pub const WG_TUN_DEVICE_NETMASK_V4: u8 = 16; + pub const WG_TUN_DEVICE_IP_ADDRESS_V6: Ipv6Addr = Ipv6Addr::new(0xfc01, 0, 0, 0, 0, 0, 0, 0x1); // fc01::1 + pub const WG_TUN_DEVICE_NETMASK_V6: u8 = 112; } diff --git a/common/wireguard-types/src/config.rs b/common/wireguard-types/src/config.rs index 16e7397c6f..fda6fc70f3 100644 --- a/common/wireguard-types/src/config.rs +++ b/common/wireguard-types/src/config.rs @@ -1,7 +1,7 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use std::net::{IpAddr, SocketAddr}; +use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr}; #[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)] pub struct Config { @@ -9,15 +9,23 @@ pub struct Config { /// default: `0.0.0.0:51822` pub bind_address: SocketAddr, - /// Private IP address of the wireguard gateway. + /// Private IPv4 address of the wireguard gateway. /// default: `10.1.0.1` - pub private_ip: IpAddr, + pub private_ipv4: Ipv4Addr, + + /// Private IPv6 address of the wireguard gateway. + /// default: `fc01::1` + pub private_ipv6: Ipv6Addr, /// Port announced to external clients wishing to connect to the wireguard interface. /// Useful in the instances where the node is behind a proxy. pub announced_port: u16, - /// The prefix denoting the maximum number of the clients that can be connected via Wireguard. - /// The maximum value for IPv4 is 32 and for IPv6 is 128 - pub private_network_prefix: u8, + /// The prefix denoting the maximum number of the clients that can be connected via Wireguard using IPv4. + /// The maximum value for IPv4 is 32 + pub private_network_prefix_v4: u8, + + /// The prefix denoting the maximum number of the clients that can be connected via Wireguard using IPv6. + /// The maximum value for IPv6 is 128 + pub private_network_prefix_v6: u8, } diff --git a/common/wireguard/src/lib.rs b/common/wireguard/src/lib.rs index ecd1576926..a6ae284170 100644 --- a/common/wireguard/src/lib.rs +++ b/common/wireguard/src/lib.rs @@ -10,13 +10,13 @@ use defguard_wireguard_rs::WGApi; #[cfg(target_os = "linux")] use defguard_wireguard_rs::{host::Peer, key::Key, net::IpAddrMask}; use nym_crypto::asymmetric::encryption::KeyPair; +#[cfg(target_os = "linux")] +use nym_network_defaults::constants::WG_TUN_BASE_NAME; use nym_wireguard_types::Config; use peer_controller::PeerControlRequest; use std::sync::Arc; use tokio::sync::mpsc::{self, Receiver, Sender}; -const WG_TUN_NAME: &str = "nymwg"; - pub(crate) mod error; pub mod peer_controller; pub mod peer_handle; @@ -93,7 +93,7 @@ pub async fn start_wireguard use std::collections::HashMap; use tokio::sync::RwLock; - let ifname = String::from(WG_TUN_NAME); + let ifname = String::from(WG_TUN_BASE_NAME); let wg_api = defguard_wireguard_rs::WGApi::new(ifname.clone(), false)?; let mut peer_bandwidth_managers = HashMap::with_capacity(all_peers.len()); let peers = all_peers @@ -124,23 +124,41 @@ 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_ip.to_string(), + address: wireguard_data.inner.config().private_ipv4.to_string(), port: wireguard_data.inner.config().announced_port as u32, peers, mtu: None, }; wg_api.configure_interface(&interface_config)?; + std::process::Command::new("ip") + .args([ + "-6", + "addr", + "add", + &format!( + "{}/{}", + wireguard_data.inner.config().private_ipv6, + wireguard_data.inner.config().private_network_prefix_v6 + ), + "dev", + (&ifname), + ]) + .output()?; // Use a dummy peer to create routing rule for the entire network space let mut catch_all_peer = Peer::new(Key::new([0; 32])); - let network = IpNetwork::new_truncate( - wireguard_data.inner.config().private_ip, - wireguard_data.inner.config().private_network_prefix, + let network_v4 = IpNetwork::new_truncate( + wireguard_data.inner.config().private_ipv4, + wireguard_data.inner.config().private_network_prefix_v4, )?; - catch_all_peer.set_allowed_ips(vec![IpAddrMask::new( - network.network_address(), - network.netmask(), - )]); + let network_v6 = IpNetwork::new_truncate( + wireguard_data.inner.config().private_ipv6, + wireguard_data.inner.config().private_network_prefix_v6, + )?; + catch_all_peer.set_allowed_ips(vec![ + IpAddrMask::new(network_v4.network_address(), network_v4.netmask()), + IpAddrMask::new(network_v6.network_address(), network_v6.netmask()), + ]); wg_api.configure_peer_routing(&[catch_all_peer])?; let host = wg_api.read_interface_data()?; diff --git a/documentation/docs/pages/operators/nodes/nym-node/configuration.mdx b/documentation/docs/pages/operators/nodes/nym-node/configuration.mdx index 8709742093..7c3943c097 100644 --- a/documentation/docs/pages/operators/nodes/nym-node/configuration.mdx +++ b/documentation/docs/pages/operators/nodes/nym-node/configuration.mdx @@ -389,7 +389,7 @@ ip addr show nymtun0 link/none inet 10.0.0.1/16 scope global nymtun0 valid_lft forever preferred_lft forever - inet6 2001:db8:a160::1/112 scope global + inet6 fc00::1/112 scope global valid_lft forever preferred_lft forever inet6 fe80::ad08:d167:5700:8c7c/64 scope link stable-privacy valid_lft forever preferred_lft forever` diff --git a/nym-node/src/cli/helpers.rs b/nym-node/src/cli/helpers.rs index cabbd6d621..4ca33dbed3 100644 --- a/nym-node/src/cli/helpers.rs +++ b/nym-node/src/cli/helpers.rs @@ -265,14 +265,6 @@ pub(crate) struct WireguardArgs { )] pub(crate) wireguard_bind_address: Option, - /// Private IP address of the wireguard gateway. - /// default: `10.1.0.1` - #[clap( - long, - env = NYMNODE_WG_IP_ARG, - )] - pub(crate) wireguard_private_ip: Option, - /// Port announced to external clients wishing to connect to the wireguard interface. /// Useful in the instances where the node is behind a proxy. #[clap( @@ -312,12 +304,8 @@ impl WireguardArgs { section.announced_port = announced_port } - if let Some(private_ip) = self.wireguard_private_ip { - section.private_ip = private_ip - } - if let Some(private_network_prefix) = self.wireguard_private_network_prefix { - section.private_network_prefix = private_network_prefix + section.private_network_prefix_v4 = private_network_prefix } section diff --git a/nym-node/src/config/entry_gateway.rs b/nym-node/src/config/entry_gateway.rs index f7c0a51c06..8ab330e090 100644 --- a/nym-node/src/config/entry_gateway.rs +++ b/nym-node/src/config/entry_gateway.rs @@ -181,9 +181,11 @@ pub fn ephemeral_entry_gateway_config( config: super::Wireguard { enabled: config.wireguard.enabled, bind_address: config.wireguard.bind_address, - private_ip: config.wireguard.private_ip, + private_ipv4: config.wireguard.private_ipv4, + private_ipv6: config.wireguard.private_ipv6, announced_port: config.wireguard.announced_port, - private_network_prefix: config.wireguard.private_network_prefix, + private_network_prefix_v4: config.wireguard.private_network_prefix_v4, + private_network_prefix_v6: config.wireguard.private_network_prefix_v6, storage_paths: config.wireguard.storage_paths.clone(), }, custom_mixnet_path: None, diff --git a/nym-node/src/config/exit_gateway.rs b/nym-node/src/config/exit_gateway.rs index 0fc6b9e8ee..9cc640e701 100644 --- a/nym-node/src/config/exit_gateway.rs +++ b/nym-node/src/config/exit_gateway.rs @@ -278,9 +278,11 @@ pub fn ephemeral_exit_gateway_config( config: super::Wireguard { enabled: config.wireguard.enabled, bind_address: config.wireguard.bind_address, - private_ip: config.wireguard.private_ip, + private_ipv4: config.wireguard.private_ipv4, + private_ipv6: config.wireguard.private_ipv6, announced_port: config.wireguard.announced_port, - private_network_prefix: config.wireguard.private_network_prefix, + private_network_prefix_v4: config.wireguard.private_network_prefix_v4, + private_network_prefix_v6: config.wireguard.private_network_prefix_v6, storage_paths: config.wireguard.storage_paths.clone(), }, custom_mixnet_path: None, diff --git a/nym-node/src/config/mod.rs b/nym-node/src/config/mod.rs index 65ca8983d7..626c9cf098 100644 --- a/nym-node/src/config/mod.rs +++ b/nym-node/src/config/mod.rs @@ -10,7 +10,9 @@ use clap::ValueEnum; use nym_bin_common::logging::LoggingSettings; use nym_config::defaults::{ mainnet, var_names, DEFAULT_MIX_LISTENING_PORT, DEFAULT_NYM_NODE_HTTP_PORT, WG_PORT, + WG_TUN_DEVICE_IP_ADDRESS_V4, WG_TUN_DEVICE_IP_ADDRESS_V6, }; +use nym_config::defaults::{WG_TUN_DEVICE_NETMASK_V4, WG_TUN_DEVICE_NETMASK_V6}; use nym_config::helpers::inaddr_any; use nym_config::serde_helpers::de_maybe_port; use nym_config::serde_helpers::de_maybe_stringified; @@ -21,7 +23,7 @@ use nym_config::{ use serde::{Deserialize, Serialize}; use std::env; use std::fmt::{Display, Formatter}; -use std::net::{IpAddr, Ipv4Addr, SocketAddr}; +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; use std::path::{Path, PathBuf}; use std::time::Duration; use tracing::{debug, error}; @@ -43,9 +45,6 @@ pub use crate::config::mixnode::MixnodeConfig; const DEFAULT_NYMNODES_DIR: &str = "nym-nodes"; -pub const DEFAULT_WIREGUARD_PORT: u16 = WG_PORT; -pub const DEFAULT_WIREGUARD_IP: IpAddr = IpAddr::V4(Ipv4Addr::new(10, 1, 0, 1)); -pub const DEFAULT_WIREGUARD_PREFIX: u8 = 16; pub const DEFAULT_HTTP_PORT: u16 = DEFAULT_NYM_NODE_HTTP_PORT; pub const DEFAULT_MIXNET_PORT: u16 = DEFAULT_MIX_LISTENING_PORT; @@ -518,17 +517,25 @@ pub struct Wireguard { /// default: `0.0.0.0:51822` pub bind_address: SocketAddr, - /// Private IP address of the wireguard gateway. + /// Private IPv4 address of the wireguard gateway. /// default: `10.1.0.1` - pub private_ip: IpAddr, + pub private_ipv4: Ipv4Addr, + + /// Private IPv6 address of the wireguard gateway. + /// default: `fc01::1` + pub private_ipv6: Ipv6Addr, /// Port announced to external clients wishing to connect to the wireguard interface. /// Useful in the instances where the node is behind a proxy. pub announced_port: u16, - /// The prefix denoting the maximum number of the clients that can be connected via Wireguard. - /// The maximum value for IPv4 is 32 and for IPv6 is 128 - pub private_network_prefix: u8, + /// The prefix denoting the maximum number of the clients that can be connected via Wireguard using IPv4. + /// The maximum value for IPv4 is 32 + pub private_network_prefix_v4: u8, + + /// The prefix denoting the maximum number of the clients that can be connected via Wireguard using IPv6. + /// The maximum value for IPv6 is 128 + pub private_network_prefix_v6: u8, /// Paths for wireguard keys, client registries, etc. pub storage_paths: persistence::WireguardPaths, @@ -538,13 +545,12 @@ impl Wireguard { pub fn new_default>(data_dir: P) -> Self { Wireguard { enabled: false, - bind_address: SocketAddr::new( - IpAddr::V4(Ipv4Addr::UNSPECIFIED), - DEFAULT_WIREGUARD_PORT, - ), - private_ip: DEFAULT_WIREGUARD_IP, - announced_port: DEFAULT_WIREGUARD_PORT, - private_network_prefix: DEFAULT_WIREGUARD_PREFIX, + bind_address: SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), WG_PORT), + private_ipv4: WG_TUN_DEVICE_IP_ADDRESS_V4, + private_ipv6: WG_TUN_DEVICE_IP_ADDRESS_V6, + announced_port: WG_PORT, + private_network_prefix_v4: WG_TUN_DEVICE_NETMASK_V4, + private_network_prefix_v6: WG_TUN_DEVICE_NETMASK_V6, storage_paths: persistence::WireguardPaths::new(data_dir), } } @@ -554,9 +560,11 @@ impl From for nym_wireguard_types::Config { fn from(value: Wireguard) -> Self { nym_wireguard_types::Config { bind_address: value.bind_address, - private_ip: value.private_ip, + private_ipv4: value.private_ipv4, + private_ipv6: value.private_ipv6, announced_port: value.announced_port, - private_network_prefix: value.private_network_prefix, + private_network_prefix_v4: value.private_network_prefix_v4, + private_network_prefix_v6: value.private_network_prefix_v6, } } } @@ -565,9 +573,11 @@ impl From for nym_authenticator::config::Authenticator { fn from(value: Wireguard) -> Self { nym_authenticator::config::Authenticator { bind_address: value.bind_address, - private_ip: value.private_ip, + private_ipv4: value.private_ipv4, + private_ipv6: value.private_ipv6, announced_port: value.announced_port, - private_network_prefix: value.private_network_prefix, + private_network_prefix_v4: value.private_network_prefix_v4, + private_network_prefix_v6: value.private_network_prefix_v6, } } } diff --git a/nym-node/src/config/old_configs/mod.rs b/nym-node/src/config/old_configs/mod.rs index 3b1f589858..58040fde6b 100644 --- a/nym-node/src/config/old_configs/mod.rs +++ b/nym-node/src/config/old_configs/mod.rs @@ -5,8 +5,10 @@ mod old_config_v1; mod old_config_v2; mod old_config_v3; mod old_config_v4; +mod old_config_v5; pub use old_config_v1::try_upgrade_config_v1; pub use old_config_v2::try_upgrade_config_v2; pub use old_config_v3::try_upgrade_config_v3; pub use old_config_v4::try_upgrade_config_v4; +pub use old_config_v5::try_upgrade_config_v5; diff --git a/nym-node/src/config/old_configs/old_config_v4.rs b/nym-node/src/config/old_configs/old_config_v4.rs index 95fd2714ec..8e8e3758d0 100644 --- a/nym-node/src/config/old_configs/old_config_v4.rs +++ b/nym-node/src/config/old_configs/old_config_v4.rs @@ -4,12 +4,6 @@ #![allow(dead_code)] use crate::{config::*, error::KeyIOFailure}; -use entry_gateway::{Debug as EntryGatewayConfigDebug, ZkNymTicketHandlerDebug}; -use exit_gateway::{ - Debug as ExitGatewayConfigDebug, IpPacketRouter, IpPacketRouterDebug, NetworkRequester, - NetworkRequesterDebug, -}; -use mixnode::{Verloc, VerlocDebug}; use nym_client_core_config_types::{ disk_persistence::{ClientKeysPaths, CommonClientPaths}, DebugConfig as ClientDebugConfig, @@ -22,6 +16,7 @@ use nym_network_requester::{ }; use nym_pemstore::{store_key, store_keypair}; use nym_sphinx_acknowledgements::AckKey; +use old_configs::old_config_v5::*; use persistence::*; use rand::rngs::OsRng; use serde::{Deserialize, Serialize}; @@ -93,12 +88,12 @@ pub enum NodeModeV4 { ExitGateway, } -impl From for NodeMode { +impl From for NodeModeV5 { fn from(config: NodeModeV4) -> Self { match config { - NodeModeV4::Mixnode => NodeMode::Mixnode, - NodeModeV4::EntryGateway => NodeMode::EntryGateway, - NodeModeV4::ExitGateway => NodeMode::ExitGateway, + NodeModeV4::Mixnode => NodeModeV5::Mixnode, + NodeModeV4::EntryGateway => NodeModeV5::EntryGateway, + NodeModeV4::ExitGateway => NodeModeV5::ExitGateway, } } } @@ -1068,7 +1063,7 @@ pub async fn initialise( pub async fn try_upgrade_config_v4>( path: P, prev_config: Option, -) -> Result { +) -> Result { tracing::debug!("Updating from 1.1.5"); let old_cfg = if let Some(prev_config) = prev_config { prev_config @@ -1094,21 +1089,21 @@ pub async fn try_upgrade_config_v4>( .ok_or(NymNodeError::DataDirDerivationFailure)?, ); - let cfg = Config { + let cfg = ConfigV5 { save_path: old_cfg.save_path, id: old_cfg.id, mode: old_cfg.mode.into(), - host: Host { + host: HostV5 { public_ips: old_cfg.host.public_ips, hostname: old_cfg.host.hostname, location: old_cfg.host.location, }, - mixnet: Mixnet { + mixnet: MixnetV5 { bind_address: old_cfg.mixnet.bind_address, announce_port: old_cfg.mixnet.announce_port, nym_api_urls: old_cfg.mixnet.nym_api_urls, nyxd_urls: old_cfg.mixnet.nyxd_urls, - debug: MixnetDebug { + debug: MixnetDebugV5 { packet_forwarding_initial_backoff: old_cfg .mixnet .debug @@ -1122,8 +1117,8 @@ pub async fn try_upgrade_config_v4>( unsafe_disable_noise: old_cfg.mixnet.debug.unsafe_disable_noise, }, }, - storage_paths: NymNodePaths { - keys: KeysPaths { + storage_paths: NymNodePathsV5 { + keys: KeysPathsV5 { private_ed25519_identity_key_file: old_cfg .storage_paths .keys @@ -1151,7 +1146,7 @@ pub async fn try_upgrade_config_v4>( }, description: old_cfg.storage_paths.description, }, - http: Http { + http: HttpV5 { bind_address: old_cfg.http.bind_address, landing_page_assets_path: old_cfg.http.landing_page_assets_path, access_token: old_cfg.http.access_token, @@ -1159,13 +1154,13 @@ pub async fn try_upgrade_config_v4>( expose_system_hardware: old_cfg.http.expose_system_hardware, expose_crypto_hardware: old_cfg.http.expose_crypto_hardware, }, - wireguard: Wireguard { + wireguard: WireguardV5 { enabled: old_cfg.wireguard.enabled, bind_address: old_cfg.wireguard.bind_address, private_ip: old_cfg.wireguard.private_ip, announced_port: old_cfg.wireguard.announced_port, private_network_prefix: old_cfg.wireguard.private_network_prefix, - storage_paths: WireguardPaths { + storage_paths: WireguardPathsV5 { private_diffie_hellman_key_file: old_cfg .wireguard .storage_paths @@ -1176,12 +1171,12 @@ pub async fn try_upgrade_config_v4>( .public_diffie_hellman_key_file, }, }, - mixnode: MixnodeConfig { - storage_paths: MixnodePaths {}, - verloc: Verloc { + mixnode: MixnodeConfigV5 { + storage_paths: MixnodePathsV5 {}, + verloc: VerlocV5 { bind_address: old_cfg.mixnode.verloc.bind_address, announce_port: old_cfg.mixnode.verloc.announce_port, - debug: VerlocDebug { + debug: VerlocDebugV5 { packets_per_node: old_cfg.mixnode.verloc.debug.packets_per_node, connection_timeout: old_cfg.mixnode.verloc.debug.connection_timeout, packet_timeout: old_cfg.mixnode.verloc.debug.packet_timeout, @@ -1191,17 +1186,17 @@ pub async fn try_upgrade_config_v4>( retry_timeout: old_cfg.mixnode.verloc.debug.retry_timeout, }, }, - debug: mixnode::Debug { + debug: DebugV5 { node_stats_logging_delay: old_cfg.mixnode.debug.node_stats_logging_delay, node_stats_updating_delay: old_cfg.mixnode.debug.node_stats_updating_delay, }, }, - entry_gateway: EntryGatewayConfig { - storage_paths: EntryGatewayPaths { + entry_gateway: EntryGatewayConfigV5 { + storage_paths: EntryGatewayPathsV5 { clients_storage: old_cfg.entry_gateway.storage_paths.clients_storage, stats_storage: entry_gateway_paths.stats_storage, cosmos_mnemonic: old_cfg.entry_gateway.storage_paths.cosmos_mnemonic, - authenticator: AuthenticatorPaths { + authenticator: AuthenticatorPathsV5 { private_ed25519_identity_key_file: old_cfg .entry_gateway .storage_paths @@ -1243,9 +1238,9 @@ pub async fn try_upgrade_config_v4>( bind_address: old_cfg.entry_gateway.bind_address, announce_ws_port: old_cfg.entry_gateway.announce_ws_port, announce_wss_port: old_cfg.entry_gateway.announce_wss_port, - debug: EntryGatewayConfigDebug { + debug: EntryGatewayConfigDebugV5 { message_retrieval_limit: old_cfg.entry_gateway.debug.message_retrieval_limit, - zk_nym_tickets: ZkNymTicketHandlerDebug { + zk_nym_tickets: ZkNymTicketHandlerDebugV5 { revocation_bandwidth_penalty: old_cfg .entry_gateway .debug @@ -1270,11 +1265,11 @@ pub async fn try_upgrade_config_v4>( }, }, }, - exit_gateway: ExitGatewayConfig { - storage_paths: ExitGatewayPaths { + exit_gateway: ExitGatewayConfigV5 { + storage_paths: ExitGatewayPathsV5 { clients_storage: old_cfg.exit_gateway.storage_paths.clients_storage, stats_storage: exit_gateway_paths.stats_storage, - network_requester: NetworkRequesterPaths { + network_requester: NetworkRequesterPathsV5 { private_ed25519_identity_key_file: old_cfg .exit_gateway .storage_paths @@ -1311,7 +1306,7 @@ pub async fn try_upgrade_config_v4>( .network_requester .gateway_registrations, }, - ip_packet_router: IpPacketRouterPaths { + ip_packet_router: IpPacketRouterPathsV5 { private_ed25519_identity_key_file: old_cfg .exit_gateway .storage_paths @@ -1348,7 +1343,7 @@ pub async fn try_upgrade_config_v4>( .ip_packet_router .gateway_registrations, }, - authenticator: AuthenticatorPaths { + authenticator: AuthenticatorPathsV5 { private_ed25519_identity_key_file: old_cfg .exit_gateway .storage_paths @@ -1388,8 +1383,8 @@ pub async fn try_upgrade_config_v4>( }, open_proxy: old_cfg.exit_gateway.open_proxy, upstream_exit_policy_url: old_cfg.exit_gateway.upstream_exit_policy_url, - network_requester: NetworkRequester { - debug: NetworkRequesterDebug { + network_requester: NetworkRequesterV5 { + debug: NetworkRequesterDebugV5 { enabled: old_cfg.exit_gateway.network_requester.debug.enabled, disable_poisson_rate: old_cfg .exit_gateway @@ -1399,8 +1394,8 @@ pub async fn try_upgrade_config_v4>( client_debug: old_cfg.exit_gateway.network_requester.debug.client_debug, }, }, - ip_packet_router: IpPacketRouter { - debug: IpPacketRouterDebug { + ip_packet_router: IpPacketRouterV5 { + debug: IpPacketRouterDebugV5 { enabled: old_cfg.exit_gateway.ip_packet_router.debug.enabled, disable_poisson_rate: old_cfg .exit_gateway @@ -1410,12 +1405,18 @@ pub async fn try_upgrade_config_v4>( client_debug: old_cfg.exit_gateway.ip_packet_router.debug.client_debug, }, }, - debug: ExitGatewayConfigDebug { + debug: ExitGatewayDebugV5 { message_retrieval_limit: old_cfg.exit_gateway.debug.message_retrieval_limit, }, }, - authenticator: Default::default(), - logging: LoggingSettings {}, + authenticator: AuthenticatorV5 { + debug: AuthenticatorDebugV5 { + enabled: old_cfg.authenticator.debug.enabled, + disable_poisson_rate: old_cfg.authenticator.debug.disable_poisson_rate, + client_debug: old_cfg.authenticator.debug.client_debug, + }, + }, + logging: LoggingSettingsV5 {}, }; Ok(cfg) diff --git a/nym-node/src/config/old_configs/old_config_v5.rs b/nym-node/src/config/old_configs/old_config_v5.rs new file mode 100644 index 0000000000..936882f113 --- /dev/null +++ b/nym-node/src/config/old_configs/old_config_v5.rs @@ -0,0 +1,1415 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +#![allow(dead_code)] + +use crate::{config::*, error::KeyIOFailure}; +use entry_gateway::{Debug as EntryGatewayConfigDebug, ZkNymTicketHandlerDebug}; +use exit_gateway::{ + Debug as ExitGatewayConfigDebug, IpPacketRouter, IpPacketRouterDebug, NetworkRequester, + NetworkRequesterDebug, +}; +use mixnode::{Verloc, VerlocDebug}; +use nym_client_core_config_types::{ + disk_persistence::{ClientKeysPaths, CommonClientPaths}, + DebugConfig as ClientDebugConfig, +}; +use nym_config::{defaults::TICKETBOOK_VALIDITY_DAYS, serde_helpers::de_maybe_port}; +use nym_crypto::asymmetric::{ed25519, x25519}; +use nym_network_requester::{ + set_active_gateway, setup_fs_gateways_storage, store_gateway_details, CustomGatewayDetails, + GatewayDetails, +}; +use nym_pemstore::{store_key, store_keypair}; +use nym_sphinx_acknowledgements::AckKey; +use persistence::*; +use rand::rngs::OsRng; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct WireguardPathsV5 { + pub private_diffie_hellman_key_file: PathBuf, + pub public_diffie_hellman_key_file: PathBuf, +} + +impl WireguardPathsV5 { + pub fn new>(data_dir: P) -> Self { + let data_dir = data_dir.as_ref(); + WireguardPathsV5 { + private_diffie_hellman_key_file: data_dir + .join(persistence::DEFAULT_X25519_WG_DH_KEY_FILENAME), + public_diffie_hellman_key_file: data_dir + .join(persistence::DEFAULT_X25519_WG_PUBLIC_DH_KEY_FILENAME), + } + } + + pub fn x25519_wireguard_storage_paths(&self) -> nym_pemstore::KeyPairPath { + nym_pemstore::KeyPairPath::new( + &self.private_diffie_hellman_key_file, + &self.public_diffie_hellman_key_file, + ) + } +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct WireguardV5 { + /// Specifies whether the wireguard service is enabled on this node. + pub enabled: bool, + + /// Socket address this node will use for binding its wireguard interface. + /// default: `0.0.0.0:51822` + pub bind_address: SocketAddr, + + /// Ip address of the private wireguard network. + /// default: `10.1.0.0` + pub private_ip: IpAddr, + + /// Port announced to external clients wishing to connect to the wireguard interface. + /// Useful in the instances where the node is behind a proxy. + pub announced_port: u16, + + /// The prefix denoting the maximum number of the clients that can be connected via Wireguard. + /// The maximum value for IPv4 is 32 and for IPv6 is 128 + pub private_network_prefix: u8, + + /// Paths for wireguard keys, client registries, etc. + pub storage_paths: WireguardPathsV5, +} + +// a temporary solution until all "types" are run at the same time +#[derive(Debug, Default, Serialize, Deserialize, ValueEnum, Clone, Copy)] +#[serde(rename_all = "snake_case")] +pub enum NodeModeV5 { + #[default] + #[clap(alias = "mix")] + Mixnode, + + #[clap(alias = "entry", alias = "gateway")] + EntryGateway, + + #[clap(alias = "exit")] + ExitGateway, +} + +impl From for NodeMode { + fn from(config: NodeModeV5) -> Self { + match config { + NodeModeV5::Mixnode => NodeMode::Mixnode, + NodeModeV5::EntryGateway => NodeMode::EntryGateway, + NodeModeV5::ExitGateway => NodeMode::ExitGateway, + } + } +} + +// TODO: this is very much a WIP. we need proper ssl certificate support here +#[derive(Debug, Clone, Default, Deserialize, PartialEq, Serialize)] +#[serde(default)] +#[serde(deny_unknown_fields)] +pub struct HostV5 { + /// Ip address(es) of this host, such as 1.1.1.1 that external clients will use for connections. + /// If no values are provided, when this node gets included in the network, + /// its ip addresses will be populated by whatever value is resolved by associated nym-api. + pub public_ips: Vec, + + /// Optional hostname of this node, for example nymtech.net. + // TODO: this is temporary. to be replaced by pulling the data directly from the certs. + #[serde(deserialize_with = "de_maybe_stringified")] + pub hostname: Option, + + /// Optional ISO 3166 alpha-2 two-letter country code of the node's **physical** location + #[serde(deserialize_with = "de_maybe_stringified")] + pub location: Option, +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)] +#[serde(default)] +#[serde(deny_unknown_fields)] +pub struct MixnetDebugV5 { + /// Initial value of an exponential backoff to reconnect to dropped TCP connection when + /// forwarding sphinx packets. + #[serde(with = "humantime_serde")] + pub packet_forwarding_initial_backoff: Duration, + + /// Maximum value of an exponential backoff to reconnect to dropped TCP connection when + /// forwarding sphinx packets. + #[serde(with = "humantime_serde")] + pub packet_forwarding_maximum_backoff: Duration, + + /// Timeout for establishing initial connection when trying to forward a sphinx packet. + #[serde(with = "humantime_serde")] + pub initial_connection_timeout: Duration, + + /// Maximum number of packets that can be stored waiting to get sent to a particular connection. + pub maximum_connection_buffer_size: usize, + + /// Specifies whether this node should **NOT** use noise protocol in the connections (currently not implemented) + pub unsafe_disable_noise: bool, +} + +impl MixnetDebugV5 { + const DEFAULT_PACKET_FORWARDING_INITIAL_BACKOFF: Duration = Duration::from_millis(10_000); + const DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF: Duration = Duration::from_millis(300_000); + const DEFAULT_INITIAL_CONNECTION_TIMEOUT: Duration = Duration::from_millis(1_500); + const DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE: usize = 2000; +} + +impl Default for MixnetDebugV5 { + fn default() -> Self { + MixnetDebugV5 { + packet_forwarding_initial_backoff: Self::DEFAULT_PACKET_FORWARDING_INITIAL_BACKOFF, + packet_forwarding_maximum_backoff: Self::DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF, + initial_connection_timeout: Self::DEFAULT_INITIAL_CONNECTION_TIMEOUT, + maximum_connection_buffer_size: Self::DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE, + // to be changed by @SW once the implementation is there + unsafe_disable_noise: true, + } + } +} + +impl Default for MixnetV5 { + fn default() -> Self { + // SAFETY: + // our hardcoded values should always be valid + #[allow(clippy::expect_used)] + // is if there's anything set in the environment, otherwise fallback to mainnet + let nym_api_urls = if let Ok(env_value) = env::var(var_names::NYM_API) { + parse_urls(&env_value) + } else { + vec![mainnet::NYM_API.parse().expect("Invalid default API URL")] + }; + + #[allow(clippy::expect_used)] + let nyxd_urls = if let Ok(env_value) = env::var(var_names::NYXD) { + parse_urls(&env_value) + } else { + vec![mainnet::NYXD_URL.parse().expect("Invalid default nyxd URL")] + }; + + MixnetV5 { + bind_address: SocketAddr::new(inaddr_any(), DEFAULT_MIXNET_PORT), + announce_port: None, + nym_api_urls, + nyxd_urls, + debug: Default::default(), + } + } +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)] +#[serde(default)] +#[serde(deny_unknown_fields)] +pub struct MixnetV5 { + /// Address this node will bind to for listening for mixnet packets + /// default: `0.0.0.0:1789` + pub bind_address: SocketAddr, + + /// If applicable, custom port announced in the self-described API that other clients and nodes + /// will use. + /// Useful when the node is behind a proxy. + #[serde(deserialize_with = "de_maybe_port")] + pub announce_port: Option, + + /// Addresses to nym APIs from which the node gets the view of the network. + pub nym_api_urls: Vec, + + /// Addresses to nyxd which the node uses to interact with the nyx chain. + pub nyxd_urls: Vec, + + #[serde(default)] + pub debug: MixnetDebugV5, +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct KeysPathsV5 { + /// Path to file containing ed25519 identity private key. + pub private_ed25519_identity_key_file: PathBuf, + + /// Path to file containing ed25519 identity public key. + pub public_ed25519_identity_key_file: PathBuf, + + /// Path to file containing x25519 sphinx private key. + pub private_x25519_sphinx_key_file: PathBuf, + + /// Path to file containing x25519 sphinx public key. + pub public_x25519_sphinx_key_file: PathBuf, + + /// Path to file containing x25519 noise private key. + pub private_x25519_noise_key_file: PathBuf, + + /// Path to file containing x25519 noise public key. + pub public_x25519_noise_key_file: PathBuf, +} + +impl KeysPathsV5 { + pub fn new>(data_dir: P) -> Self { + let data_dir = data_dir.as_ref(); + + KeysPathsV5 { + private_ed25519_identity_key_file: data_dir + .join(DEFAULT_ED25519_PRIVATE_IDENTITY_KEY_FILENAME), + public_ed25519_identity_key_file: data_dir + .join(DEFAULT_ED25519_PUBLIC_IDENTITY_KEY_FILENAME), + private_x25519_sphinx_key_file: data_dir + .join(DEFAULT_X25519_PRIVATE_SPHINX_KEY_FILENAME), + public_x25519_sphinx_key_file: data_dir.join(DEFAULT_X25519_PUBLIC_SPHINX_KEY_FILENAME), + private_x25519_noise_key_file: data_dir.join(DEFAULT_X25519_PRIVATE_NOISE_KEY_FILENAME), + public_x25519_noise_key_file: data_dir.join(DEFAULT_X25519_PUBLIC_NOISE_KEY_FILENAME), + } + } + + pub fn ed25519_identity_storage_paths(&self) -> nym_pemstore::KeyPairPath { + nym_pemstore::KeyPairPath::new( + &self.private_ed25519_identity_key_file, + &self.public_ed25519_identity_key_file, + ) + } + + pub fn x25519_sphinx_storage_paths(&self) -> nym_pemstore::KeyPairPath { + nym_pemstore::KeyPairPath::new( + &self.private_x25519_sphinx_key_file, + &self.public_x25519_sphinx_key_file, + ) + } + + pub fn x25519_noise_storage_paths(&self) -> nym_pemstore::KeyPairPath { + nym_pemstore::KeyPairPath::new( + &self.private_x25519_noise_key_file, + &self.public_x25519_noise_key_file, + ) + } +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct NymNodePathsV5 { + pub keys: KeysPathsV5, + + /// Path to a file containing basic node description: human-readable name, website, details, etc. + pub description: PathBuf, +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)] +#[serde(default)] +#[serde(deny_unknown_fields)] +pub struct HttpV5 { + /// Socket address this node will use for binding its http API. + /// default: `0.0.0.0:8080` + pub bind_address: SocketAddr, + + /// Path to assets directory of custom landing page of this node. + #[serde(deserialize_with = "de_maybe_stringified")] + pub landing_page_assets_path: Option, + + /// An optional bearer token for accessing certain http endpoints. + /// Currently only used for obtaining mixnode's stats. + #[serde(default)] + pub access_token: Option, + + /// Specify whether basic system information should be exposed. + /// default: true + pub expose_system_info: bool, + + /// Specify whether basic system hardware information should be exposed. + /// This option is superseded by `expose_system_info` + /// default: true + pub expose_system_hardware: bool, + + /// Specify whether detailed system crypto hardware information should be exposed. + /// This option is superseded by `expose_system_hardware` + /// default: true + pub expose_crypto_hardware: bool, +} + +impl Default for HttpV5 { + fn default() -> Self { + HttpV5 { + bind_address: SocketAddr::new(inaddr_any(), DEFAULT_HTTP_PORT), + landing_page_assets_path: None, + access_token: None, + expose_system_info: true, + expose_system_hardware: true, + expose_crypto_hardware: true, + } + } +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct MixnodePathsV5 {} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct DebugV5 { + /// Delay between each subsequent node statistics being logged to the console + #[serde(with = "humantime_serde")] + pub node_stats_logging_delay: Duration, + + /// Delay between each subsequent node statistics being updated + #[serde(with = "humantime_serde")] + pub node_stats_updating_delay: Duration, +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct VerlocDebugV5 { + /// Specifies number of echo packets sent to each node during a measurement run. + pub packets_per_node: usize, + + /// Specifies maximum amount of time to wait for the connection to get established. + #[serde(with = "humantime_serde")] + pub connection_timeout: Duration, + + /// Specifies maximum amount of time to wait for the reply packet to arrive before abandoning the test. + #[serde(with = "humantime_serde")] + pub packet_timeout: Duration, + + /// Specifies delay between subsequent test packets being sent (after receiving a reply). + #[serde(with = "humantime_serde")] + pub delay_between_packets: Duration, + + /// Specifies number of nodes being tested at once. + pub tested_nodes_batch_size: usize, + + /// Specifies delay between subsequent test runs. + #[serde(with = "humantime_serde")] + pub testing_interval: Duration, + + /// Specifies delay between attempting to run the measurement again if the previous run failed + /// due to being unable to get the list of nodes. + #[serde(with = "humantime_serde")] + pub retry_timeout: Duration, +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct VerlocV5 { + /// Socket address this node will use for binding its verloc API. + /// default: `0.0.0.0:1790` + pub bind_address: SocketAddr, + + #[serde(deserialize_with = "de_maybe_port")] + pub announce_port: Option, + + #[serde(default)] + pub debug: VerlocDebugV5, +} + +impl VerlocDebugV5 { + const DEFAULT_PACKETS_PER_NODE: usize = 100; + const DEFAULT_CONNECTION_TIMEOUT: Duration = Duration::from_millis(5000); + const DEFAULT_PACKET_TIMEOUT: Duration = Duration::from_millis(1500); + const DEFAULT_DELAY_BETWEEN_PACKETS: Duration = Duration::from_millis(50); + const DEFAULT_BATCH_SIZE: usize = 50; + const DEFAULT_TESTING_INTERVAL: Duration = Duration::from_secs(60 * 60 * 12); + const DEFAULT_RETRY_TIMEOUT: Duration = Duration::from_secs(60 * 30); +} + +impl Default for VerlocDebugV5 { + fn default() -> Self { + VerlocDebugV5 { + packets_per_node: Self::DEFAULT_PACKETS_PER_NODE, + connection_timeout: Self::DEFAULT_CONNECTION_TIMEOUT, + packet_timeout: Self::DEFAULT_PACKET_TIMEOUT, + delay_between_packets: Self::DEFAULT_DELAY_BETWEEN_PACKETS, + tested_nodes_batch_size: Self::DEFAULT_BATCH_SIZE, + testing_interval: Self::DEFAULT_TESTING_INTERVAL, + retry_timeout: Self::DEFAULT_RETRY_TIMEOUT, + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct MixnodeConfigV5 { + pub storage_paths: MixnodePathsV5, + + pub verloc: VerlocV5, + + #[serde(default)] + pub debug: DebugV5, +} + +impl DebugV5 { + const DEFAULT_NODE_STATS_LOGGING_DELAY: Duration = Duration::from_millis(60_000); + const DEFAULT_NODE_STATS_UPDATING_DELAY: Duration = Duration::from_millis(30_000); +} + +impl Default for DebugV5 { + fn default() -> Self { + DebugV5 { + node_stats_logging_delay: Self::DEFAULT_NODE_STATS_LOGGING_DELAY, + node_stats_updating_delay: Self::DEFAULT_NODE_STATS_UPDATING_DELAY, + } + } +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct EntryGatewayPathsV5 { + /// Path to sqlite database containing all persistent data: messages for offline clients, + /// derived shared keys and available client bandwidths. + pub clients_storage: PathBuf, + + pub stats_storage: PathBuf, + + /// Path to file containing cosmos account mnemonic used for zk-nym redemption. + pub cosmos_mnemonic: PathBuf, + + pub authenticator: AuthenticatorPathsV5, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(default)] +pub struct ZkNymTicketHandlerDebugV5 { + /// Specifies the multiplier for revoking a malformed/double-spent ticket + /// (if it has to go all the way to the nym-api for verification) + /// e.g. if one ticket grants 100Mb and `revocation_bandwidth_penalty` is set to 1.5, + /// the client will lose 150Mb + pub revocation_bandwidth_penalty: f32, + + /// Specifies the interval for attempting to resolve any failed, pending operations, + /// such as ticket verification or redemption. + #[serde(with = "humantime_serde")] + pub pending_poller: Duration, + + pub minimum_api_quorum: f32, + + /// Specifies the minimum number of tickets this gateway will attempt to redeem. + pub minimum_redemption_tickets: usize, + + /// Specifies the maximum time between two subsequent tickets redemptions. + /// That's required as nym-apis will purge all ticket information for tickets older than maximum validity. + #[serde(with = "humantime_serde")] + pub maximum_time_between_redemption: Duration, +} + +impl ZkNymTicketHandlerDebugV5 { + pub const DEFAULT_REVOCATION_BANDWIDTH_PENALTY: f32 = 10.0; + pub const DEFAULT_PENDING_POLLER: Duration = Duration::from_secs(300); + pub const DEFAULT_MINIMUM_API_QUORUM: f32 = 0.8; + pub const DEFAULT_MINIMUM_REDEMPTION_TICKETS: usize = 100; + + // use min(4/5 of max validity, validity - 1), but making sure it's no greater than 1 day + // ASSUMPTION: our validity period is AT LEAST 2 days + // + // this could have been a constant, but it's more readable as a function + pub const fn default_maximum_time_between_redemption() -> Duration { + let desired_secs = TICKETBOOK_VALIDITY_DAYS * (86400 * 4) / 5; + let desired_secs_alt = (TICKETBOOK_VALIDITY_DAYS - 1) * 86400; + + // can't use `min` in const context + let target_secs = if desired_secs < desired_secs_alt { + desired_secs + } else { + desired_secs_alt + }; + + assert!( + target_secs > 86400, + "the maximum time between redemption can't be lower than 1 day!" + ); + Duration::from_secs(target_secs as u64) + } +} + +impl Default for ZkNymTicketHandlerDebugV5 { + fn default() -> Self { + ZkNymTicketHandlerDebugV5 { + revocation_bandwidth_penalty: Self::DEFAULT_REVOCATION_BANDWIDTH_PENALTY, + pending_poller: Self::DEFAULT_PENDING_POLLER, + minimum_api_quorum: Self::DEFAULT_MINIMUM_API_QUORUM, + minimum_redemption_tickets: Self::DEFAULT_MINIMUM_REDEMPTION_TICKETS, + maximum_time_between_redemption: Self::default_maximum_time_between_redemption(), + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct EntryGatewayConfigDebugV5 { + /// Number of messages from offline client that can be pulled at once (i.e. with a single SQL query) from the storage. + pub message_retrieval_limit: i64, + pub zk_nym_tickets: ZkNymTicketHandlerDebugV5, +} + +impl EntryGatewayConfigDebugV5 { + const DEFAULT_MESSAGE_RETRIEVAL_LIMIT: i64 = 100; +} + +impl Default for EntryGatewayConfigDebugV5 { + fn default() -> Self { + EntryGatewayConfigDebugV5 { + message_retrieval_limit: Self::DEFAULT_MESSAGE_RETRIEVAL_LIMIT, + zk_nym_tickets: Default::default(), + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct EntryGatewayConfigV5 { + pub storage_paths: EntryGatewayPathsV5, + + /// Indicates whether this gateway is accepting only coconut credentials for accessing the mixnet + /// or if it also accepts non-paying clients + pub enforce_zk_nyms: bool, + + /// Socket address this node will use for binding its client websocket API. + /// default: `0.0.0.0:9000` + pub bind_address: SocketAddr, + + /// Custom announced port for listening for websocket client traffic. + /// If unspecified, the value from the `bind_address` will be used instead + /// default: None + #[serde(deserialize_with = "de_maybe_port")] + pub announce_ws_port: Option, + + /// If applicable, announced port for listening for secure websocket client traffic. + /// (default: None) + #[serde(deserialize_with = "de_maybe_port")] + pub announce_wss_port: Option, + + #[serde(default)] + pub debug: EntryGatewayConfigDebugV5, +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct NetworkRequesterPathsV5 { + /// Path to file containing network requester ed25519 identity private key. + pub private_ed25519_identity_key_file: PathBuf, + + /// Path to file containing network requester ed25519 identity public key. + pub public_ed25519_identity_key_file: PathBuf, + + /// Path to file containing network requester x25519 diffie hellman private key. + pub private_x25519_diffie_hellman_key_file: PathBuf, + + /// Path to file containing network requester x25519 diffie hellman public key. + pub public_x25519_diffie_hellman_key_file: PathBuf, + + /// Path to file containing key used for encrypting and decrypting the content of an + /// acknowledgement so that nobody besides the client knows which packet it refers to. + pub ack_key_file: PathBuf, + + /// Path to the persistent store for received reply surbs, unused encryption keys and used sender tags. + pub reply_surb_database: PathBuf, + + /// Normally this is a path to the file containing information about gateways used by this client, + /// i.e. details such as their public keys, owner addresses or the network information. + /// but in this case it just has the basic information of "we're using custom gateway". + /// Due to how clients are started up, this file has to exist. + pub gateway_registrations: PathBuf, + // it's possible we might have to add credential storage here for return tickets +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct IpPacketRouterPathsV5 { + /// Path to file containing ip packet router ed25519 identity private key. + pub private_ed25519_identity_key_file: PathBuf, + + /// Path to file containing ip packet router ed25519 identity public key. + pub public_ed25519_identity_key_file: PathBuf, + + /// Path to file containing ip packet router x25519 diffie hellman private key. + pub private_x25519_diffie_hellman_key_file: PathBuf, + + /// Path to file containing ip packet router x25519 diffie hellman public key. + pub public_x25519_diffie_hellman_key_file: PathBuf, + + /// Path to file containing key used for encrypting and decrypting the content of an + /// acknowledgement so that nobody besides the client knows which packet it refers to. + pub ack_key_file: PathBuf, + + /// Path to the persistent store for received reply surbs, unused encryption keys and used sender tags. + pub reply_surb_database: PathBuf, + + /// Normally this is a path to the file containing information about gateways used by this client, + /// i.e. details such as their public keys, owner addresses or the network information. + /// but in this case it just has the basic information of "we're using custom gateway". + /// Due to how clients are started up, this file has to exist. + pub gateway_registrations: PathBuf, + // it's possible we might have to add credential storage here for return tickets +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct AuthenticatorPathsV5 { + /// Path to file containing authenticator ed25519 identity private key. + pub private_ed25519_identity_key_file: PathBuf, + + /// Path to file containing authenticator ed25519 identity public key. + pub public_ed25519_identity_key_file: PathBuf, + + /// Path to file containing authenticator x25519 diffie hellman private key. + pub private_x25519_diffie_hellman_key_file: PathBuf, + + /// Path to file containing authenticator x25519 diffie hellman public key. + pub public_x25519_diffie_hellman_key_file: PathBuf, + + /// Path to file containing key used for encrypting and decrypting the content of an + /// acknowledgement so that nobody besides the client knows which packet it refers to. + pub ack_key_file: PathBuf, + + /// Path to the persistent store for received reply surbs, unused encryption keys and used sender tags. + pub reply_surb_database: PathBuf, + + /// Normally this is a path to the file containing information about gateways used by this client, + /// i.e. details such as their public keys, owner addresses or the network information. + /// but in this case it just has the basic information of "we're using custom gateway". + /// Due to how clients are started up, this file has to exist. + pub gateway_registrations: PathBuf, + // it's possible we might have to add credential storage here for return tickets +} + +impl AuthenticatorPathsV5 { + pub fn new>(data_dir: P) -> Self { + let data_dir = data_dir.as_ref(); + AuthenticatorPathsV5 { + private_ed25519_identity_key_file: data_dir + .join(DEFAULT_ED25519_AUTH_PRIVATE_IDENTITY_KEY_FILENAME), + public_ed25519_identity_key_file: data_dir + .join(DEFAULT_ED25519_AUTH_PUBLIC_IDENTITY_KEY_FILENAME), + private_x25519_diffie_hellman_key_file: data_dir + .join(DEFAULT_X25519_AUTH_PRIVATE_DH_KEY_FILENAME), + public_x25519_diffie_hellman_key_file: data_dir + .join(DEFAULT_X25519_AUTH_PUBLIC_DH_KEY_FILENAME), + ack_key_file: data_dir.join(DEFAULT_AUTH_ACK_KEY_FILENAME), + reply_surb_database: data_dir.join(DEFAULT_AUTH_REPLY_SURB_DB_FILENAME), + gateway_registrations: data_dir.join(DEFAULT_AUTH_GATEWAYS_DB_FILENAME), + } + } + + pub fn to_common_client_paths(&self) -> CommonClientPaths { + CommonClientPaths { + keys: ClientKeysPaths { + private_identity_key_file: self.private_ed25519_identity_key_file.clone(), + public_identity_key_file: self.public_ed25519_identity_key_file.clone(), + private_encryption_key_file: self.private_x25519_diffie_hellman_key_file.clone(), + public_encryption_key_file: self.public_x25519_diffie_hellman_key_file.clone(), + ack_key_file: self.ack_key_file.clone(), + }, + gateway_registrations: self.gateway_registrations.clone(), + + // not needed for embedded providers + credentials_database: Default::default(), + reply_surb_database: self.reply_surb_database.clone(), + } + } + + pub fn ed25519_identity_storage_paths(&self) -> nym_pemstore::KeyPairPath { + nym_pemstore::KeyPairPath::new( + &self.private_ed25519_identity_key_file, + &self.public_ed25519_identity_key_file, + ) + } + + pub fn x25519_diffie_hellman_storage_paths(&self) -> nym_pemstore::KeyPairPath { + nym_pemstore::KeyPairPath::new( + &self.private_x25519_diffie_hellman_key_file, + &self.public_x25519_diffie_hellman_key_file, + ) + } +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct ExitGatewayPathsV5 { + pub clients_storage: PathBuf, + + pub stats_storage: PathBuf, + + pub network_requester: NetworkRequesterPathsV5, + + pub ip_packet_router: IpPacketRouterPathsV5, + + pub authenticator: AuthenticatorPathsV5, +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)] +pub struct AuthenticatorV5 { + #[serde(default)] + pub debug: AuthenticatorDebugV5, +} + +#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)] +#[serde(default)] +pub struct AuthenticatorDebugV5 { + /// Specifies whether authenticator service is enabled in this process. + /// This is only here for debugging purposes as exit gateway should always run + /// the authenticator. + pub enabled: bool, + + /// Disable Poisson sending rate. + /// This is equivalent to setting client_debug.traffic.disable_main_poisson_packet_distribution = true + /// (or is it (?)) + pub disable_poisson_rate: bool, + + /// Shared detailed client configuration options + #[serde(flatten)] + pub client_debug: ClientDebugConfig, +} + +impl Default for AuthenticatorDebugV5 { + fn default() -> Self { + AuthenticatorDebugV5 { + enabled: true, + disable_poisson_rate: true, + client_debug: Default::default(), + } + } +} + +#[allow(clippy::derivable_impls)] +impl Default for AuthenticatorV5 { + fn default() -> Self { + AuthenticatorV5 { + debug: Default::default(), + } + } +} + +#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)] +#[serde(default)] +pub struct IpPacketRouterDebugV5 { + /// Specifies whether ip packet routing service is enabled in this process. + /// This is only here for debugging purposes as exit gateway should always run **both** + /// network requester and an ip packet router. + pub enabled: bool, + + /// Disable Poisson sending rate. + /// This is equivalent to setting client_debug.traffic.disable_main_poisson_packet_distribution = true + /// (or is it (?)) + pub disable_poisson_rate: bool, + + /// Shared detailed client configuration options + #[serde(flatten)] + pub client_debug: ClientDebugConfig, +} + +impl Default for IpPacketRouterDebugV5 { + fn default() -> Self { + IpPacketRouterDebugV5 { + enabled: true, + disable_poisson_rate: true, + client_debug: Default::default(), + } + } +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)] +pub struct IpPacketRouterV5 { + #[serde(default)] + pub debug: IpPacketRouterDebugV5, +} + +#[allow(clippy::derivable_impls)] +impl Default for IpPacketRouterV5 { + fn default() -> Self { + IpPacketRouterV5 { + debug: Default::default(), + } + } +} + +#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)] +pub struct NetworkRequesterDebugV5 { + /// Specifies whether network requester service is enabled in this process. + /// This is only here for debugging purposes as exit gateway should always run **both** + /// network requester and an ip packet router. + pub enabled: bool, + + /// Disable Poisson sending rate. + /// This is equivalent to setting client_debug.traffic.disable_main_poisson_packet_distribution = true + /// (or is it (?)) + pub disable_poisson_rate: bool, + + /// Shared detailed client configuration options + #[serde(flatten)] + pub client_debug: ClientDebugConfig, +} + +impl Default for NetworkRequesterDebugV5 { + fn default() -> Self { + NetworkRequesterDebugV5 { + enabled: true, + disable_poisson_rate: true, + client_debug: Default::default(), + } + } +} + +#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)] +pub struct NetworkRequesterV5 { + #[serde(default)] + pub debug: NetworkRequesterDebugV5, +} + +#[allow(clippy::derivable_impls)] +impl Default for NetworkRequesterV5 { + fn default() -> Self { + NetworkRequesterV5 { + debug: Default::default(), + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct ExitGatewayDebugV5 { + /// Number of messages from offline client that can be pulled at once (i.e. with a single SQL query) from the storage. + pub message_retrieval_limit: i64, +} + +impl ExitGatewayDebugV5 { + const DEFAULT_MESSAGE_RETRIEVAL_LIMIT: i64 = 100; +} + +impl Default for ExitGatewayDebugV5 { + fn default() -> Self { + ExitGatewayDebugV5 { + message_retrieval_limit: Self::DEFAULT_MESSAGE_RETRIEVAL_LIMIT, + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct ExitGatewayConfigV5 { + pub storage_paths: ExitGatewayPathsV5, + + /// specifies whether this exit node should run in 'open-proxy' mode + /// and thus would attempt to resolve **ANY** request it receives. + pub open_proxy: bool, + + /// Specifies the url for an upstream source of the exit policy used by this node. + pub upstream_exit_policy_url: Url, + + pub network_requester: NetworkRequesterV5, + + pub ip_packet_router: IpPacketRouterV5, + + #[serde(default)] + pub debug: ExitGatewayDebugV5, +} + +#[derive(Debug, Default, Copy, Clone, Deserialize, PartialEq, Eq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct LoggingSettingsV5 { + // well, we need to implement something here at some point... +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct ConfigV5 { + // additional metadata holding on-disk location of this config file + #[serde(skip)] + pub(crate) save_path: Option, + + /// Human-readable ID of this particular node. + pub id: String, + + /// Current mode of this nym-node. + /// Expect this field to be changed in the future to allow running the node in multiple modes (i.e. mixnode + gateway) + pub mode: NodeModeV5, + + pub host: HostV5, + + pub mixnet: MixnetV5, + + /// Storage paths to persistent nym-node data, such as its long term keys. + pub storage_paths: NymNodePathsV5, + + #[serde(default)] + pub http: HttpV5, + + pub wireguard: WireguardV5, + + pub mixnode: MixnodeConfigV5, + + pub entry_gateway: EntryGatewayConfigV5, + + pub exit_gateway: ExitGatewayConfigV5, + + pub authenticator: AuthenticatorV5, + + #[serde(default)] + pub logging: LoggingSettingsV5, +} + +impl NymConfigTemplate for ConfigV5 { + fn template(&self) -> &'static str { + CONFIG_TEMPLATE + } +} + +impl ConfigV5 { + pub fn save(&self) -> Result<(), NymNodeError> { + let save_location = self.save_location(); + debug!( + "attempting to save config file to '{}'", + save_location.display() + ); + save_formatted_config_to_file(self, &save_location).map_err(|source| { + NymNodeError::ConfigSaveFailure { + id: self.id.clone(), + path: save_location, + source, + } + }) + } + + pub fn save_location(&self) -> PathBuf { + self.save_path + .clone() + .unwrap_or(self.default_save_location()) + } + + pub fn default_save_location(&self) -> PathBuf { + default_config_filepath(&self.id) + } + + pub fn default_data_directory>(config_path: P) -> Result { + let config_path = config_path.as_ref(); + + // we got a proper path to the .toml file + let Some(config_dir) = config_path.parent() else { + error!( + "'{}' does not have a parent directory. Have you pointed to the fs root?", + config_path.display() + ); + return Err(NymNodeError::DataDirDerivationFailure); + }; + + let Some(config_dir_name) = config_dir.file_name() else { + error!( + "could not obtain parent directory name of '{}'. Have you used relative paths?", + config_path.display() + ); + return Err(NymNodeError::DataDirDerivationFailure); + }; + + if config_dir_name != DEFAULT_CONFIG_DIR { + error!( + "the parent directory of '{}' ({}) is not {DEFAULT_CONFIG_DIR}. currently this is not supported", + config_path.display(), config_dir_name.to_str().unwrap_or("UNKNOWN") + ); + return Err(NymNodeError::DataDirDerivationFailure); + } + + let Some(node_dir) = config_dir.parent() else { + error!( + "'{}' does not have a parent directory. Have you pointed to the fs root?", + config_dir.display() + ); + return Err(NymNodeError::DataDirDerivationFailure); + }; + + Ok(node_dir.join(DEFAULT_DATA_DIR)) + } + + // simple wrapper that reads config file and assigns path location + fn read_from_path>(path: P) -> Result { + let path = path.as_ref(); + let mut loaded: ConfigV5 = + read_config_from_toml_file(path).map_err(|source| NymNodeError::ConfigLoadFailure { + path: path.to_path_buf(), + source, + })?; + loaded.save_path = Some(path.to_path_buf()); + debug!("loaded config file from {}", path.display()); + Ok(loaded) + } + + pub fn read_from_toml_file>(path: P) -> Result { + Self::read_from_path(path) + } +} + +pub async fn initialise( + paths: &AuthenticatorPaths, + public_key: nym_crypto::asymmetric::identity::PublicKey, +) -> Result<(), NymNodeError> { + let mut rng = OsRng; + let ed25519_keys = ed25519::KeyPair::new(&mut rng); + let x25519_keys = x25519::KeyPair::new(&mut rng); + let aes128ctr_key = AckKey::new(&mut rng); + let gateway_details = GatewayDetails::Custom(CustomGatewayDetails::new(public_key)).into(); + + store_keypair(&ed25519_keys, &paths.ed25519_identity_storage_paths()).map_err(|e| { + KeyIOFailure::KeyPairStoreFailure { + keys: "ed25519-identity".to_string(), + paths: paths.ed25519_identity_storage_paths(), + err: e, + } + })?; + store_keypair(&x25519_keys, &paths.x25519_diffie_hellman_storage_paths()).map_err(|e| { + KeyIOFailure::KeyPairStoreFailure { + keys: "x25519-dh".to_string(), + paths: paths.x25519_diffie_hellman_storage_paths(), + err: e, + } + })?; + store_key(&aes128ctr_key, &paths.ack_key_file).map_err(|e| KeyIOFailure::KeyStoreFailure { + key: "ack".to_string(), + path: paths.ack_key_file.clone(), + err: e, + })?; + + // insert all required information into the gateways store + // (I hate that we have to do it, but that's currently the simplest thing to do) + let storage = setup_fs_gateways_storage(&paths.gateway_registrations).await?; + store_gateway_details(&storage, &gateway_details).await?; + set_active_gateway(&storage, &gateway_details.gateway_id().to_base58_string()).await?; + + Ok(()) +} + +pub async fn try_upgrade_config_v5>( + path: P, + prev_config: Option, +) -> Result { + tracing::debug!("Updating from 1.1.6"); + let old_cfg = if let Some(prev_config) = prev_config { + prev_config + } else { + ConfigV5::read_from_path(&path)? + }; + + let (private_ipv4, private_ipv6) = match old_cfg.wireguard.private_ip { + IpAddr::V4(ipv4_addr) => (ipv4_addr, WG_TUN_DEVICE_IP_ADDRESS_V6), + IpAddr::V6(ipv6_addr) => (WG_TUN_DEVICE_IP_ADDRESS_V4, ipv6_addr), + }; + + let cfg = Config { + save_path: old_cfg.save_path, + id: old_cfg.id, + mode: old_cfg.mode.into(), + host: Host { + public_ips: old_cfg.host.public_ips, + hostname: old_cfg.host.hostname, + location: old_cfg.host.location, + }, + mixnet: Mixnet { + bind_address: old_cfg.mixnet.bind_address, + announce_port: old_cfg.mixnet.announce_port, + nym_api_urls: old_cfg.mixnet.nym_api_urls, + nyxd_urls: old_cfg.mixnet.nyxd_urls, + debug: MixnetDebug { + packet_forwarding_initial_backoff: old_cfg + .mixnet + .debug + .packet_forwarding_initial_backoff, + packet_forwarding_maximum_backoff: old_cfg + .mixnet + .debug + .packet_forwarding_maximum_backoff, + initial_connection_timeout: old_cfg.mixnet.debug.initial_connection_timeout, + maximum_connection_buffer_size: old_cfg.mixnet.debug.maximum_connection_buffer_size, + unsafe_disable_noise: old_cfg.mixnet.debug.unsafe_disable_noise, + }, + }, + storage_paths: NymNodePaths { + keys: KeysPaths { + private_ed25519_identity_key_file: old_cfg + .storage_paths + .keys + .private_ed25519_identity_key_file, + public_ed25519_identity_key_file: old_cfg + .storage_paths + .keys + .public_ed25519_identity_key_file, + private_x25519_sphinx_key_file: old_cfg + .storage_paths + .keys + .private_x25519_sphinx_key_file, + public_x25519_sphinx_key_file: old_cfg + .storage_paths + .keys + .public_x25519_sphinx_key_file, + private_x25519_noise_key_file: old_cfg + .storage_paths + .keys + .private_x25519_noise_key_file, + public_x25519_noise_key_file: old_cfg + .storage_paths + .keys + .public_x25519_noise_key_file, + }, + description: old_cfg.storage_paths.description, + }, + http: Http { + bind_address: old_cfg.http.bind_address, + landing_page_assets_path: old_cfg.http.landing_page_assets_path, + access_token: old_cfg.http.access_token, + expose_system_info: old_cfg.http.expose_system_info, + expose_system_hardware: old_cfg.http.expose_system_hardware, + expose_crypto_hardware: old_cfg.http.expose_crypto_hardware, + }, + wireguard: Wireguard { + enabled: old_cfg.wireguard.enabled, + bind_address: old_cfg.wireguard.bind_address, + private_ipv4, + private_ipv6, + announced_port: old_cfg.wireguard.announced_port, + private_network_prefix_v4: old_cfg.wireguard.private_network_prefix, + private_network_prefix_v6: WG_TUN_DEVICE_NETMASK_V6, + storage_paths: WireguardPaths { + private_diffie_hellman_key_file: old_cfg + .wireguard + .storage_paths + .private_diffie_hellman_key_file, + public_diffie_hellman_key_file: old_cfg + .wireguard + .storage_paths + .public_diffie_hellman_key_file, + }, + }, + mixnode: MixnodeConfig { + storage_paths: MixnodePaths {}, + verloc: Verloc { + bind_address: old_cfg.mixnode.verloc.bind_address, + announce_port: old_cfg.mixnode.verloc.announce_port, + debug: VerlocDebug { + packets_per_node: old_cfg.mixnode.verloc.debug.packets_per_node, + connection_timeout: old_cfg.mixnode.verloc.debug.connection_timeout, + packet_timeout: old_cfg.mixnode.verloc.debug.packet_timeout, + delay_between_packets: old_cfg.mixnode.verloc.debug.delay_between_packets, + tested_nodes_batch_size: old_cfg.mixnode.verloc.debug.tested_nodes_batch_size, + testing_interval: old_cfg.mixnode.verloc.debug.testing_interval, + retry_timeout: old_cfg.mixnode.verloc.debug.retry_timeout, + }, + }, + debug: mixnode::Debug { + node_stats_logging_delay: old_cfg.mixnode.debug.node_stats_logging_delay, + node_stats_updating_delay: old_cfg.mixnode.debug.node_stats_updating_delay, + }, + }, + entry_gateway: EntryGatewayConfig { + storage_paths: EntryGatewayPaths { + clients_storage: old_cfg.entry_gateway.storage_paths.clients_storage, + stats_storage: old_cfg.entry_gateway.storage_paths.stats_storage, + cosmos_mnemonic: old_cfg.entry_gateway.storage_paths.cosmos_mnemonic, + authenticator: AuthenticatorPaths { + private_ed25519_identity_key_file: old_cfg + .entry_gateway + .storage_paths + .authenticator + .private_ed25519_identity_key_file, + public_ed25519_identity_key_file: old_cfg + .entry_gateway + .storage_paths + .authenticator + .public_ed25519_identity_key_file, + private_x25519_diffie_hellman_key_file: old_cfg + .entry_gateway + .storage_paths + .authenticator + .private_x25519_diffie_hellman_key_file, + public_x25519_diffie_hellman_key_file: old_cfg + .entry_gateway + .storage_paths + .authenticator + .public_x25519_diffie_hellman_key_file, + ack_key_file: old_cfg + .entry_gateway + .storage_paths + .authenticator + .ack_key_file, + reply_surb_database: old_cfg + .entry_gateway + .storage_paths + .authenticator + .reply_surb_database, + gateway_registrations: old_cfg + .entry_gateway + .storage_paths + .authenticator + .gateway_registrations, + }, + }, + enforce_zk_nyms: old_cfg.entry_gateway.enforce_zk_nyms, + bind_address: old_cfg.entry_gateway.bind_address, + announce_ws_port: old_cfg.entry_gateway.announce_ws_port, + announce_wss_port: old_cfg.entry_gateway.announce_wss_port, + debug: EntryGatewayConfigDebug { + message_retrieval_limit: old_cfg.entry_gateway.debug.message_retrieval_limit, + zk_nym_tickets: ZkNymTicketHandlerDebug { + revocation_bandwidth_penalty: old_cfg + .entry_gateway + .debug + .zk_nym_tickets + .revocation_bandwidth_penalty, + pending_poller: old_cfg.entry_gateway.debug.zk_nym_tickets.pending_poller, + minimum_api_quorum: old_cfg + .entry_gateway + .debug + .zk_nym_tickets + .minimum_api_quorum, + minimum_redemption_tickets: old_cfg + .entry_gateway + .debug + .zk_nym_tickets + .minimum_redemption_tickets, + maximum_time_between_redemption: old_cfg + .entry_gateway + .debug + .zk_nym_tickets + .maximum_time_between_redemption, + }, + }, + }, + exit_gateway: ExitGatewayConfig { + storage_paths: ExitGatewayPaths { + clients_storage: old_cfg.exit_gateway.storage_paths.clients_storage, + stats_storage: old_cfg.exit_gateway.storage_paths.stats_storage, + network_requester: NetworkRequesterPaths { + private_ed25519_identity_key_file: old_cfg + .exit_gateway + .storage_paths + .network_requester + .private_ed25519_identity_key_file, + public_ed25519_identity_key_file: old_cfg + .exit_gateway + .storage_paths + .network_requester + .public_ed25519_identity_key_file, + private_x25519_diffie_hellman_key_file: old_cfg + .exit_gateway + .storage_paths + .network_requester + .private_x25519_diffie_hellman_key_file, + public_x25519_diffie_hellman_key_file: old_cfg + .exit_gateway + .storage_paths + .network_requester + .public_x25519_diffie_hellman_key_file, + ack_key_file: old_cfg + .exit_gateway + .storage_paths + .network_requester + .ack_key_file, + reply_surb_database: old_cfg + .exit_gateway + .storage_paths + .network_requester + .reply_surb_database, + gateway_registrations: old_cfg + .exit_gateway + .storage_paths + .network_requester + .gateway_registrations, + }, + ip_packet_router: IpPacketRouterPaths { + private_ed25519_identity_key_file: old_cfg + .exit_gateway + .storage_paths + .ip_packet_router + .private_ed25519_identity_key_file, + public_ed25519_identity_key_file: old_cfg + .exit_gateway + .storage_paths + .ip_packet_router + .public_ed25519_identity_key_file, + private_x25519_diffie_hellman_key_file: old_cfg + .exit_gateway + .storage_paths + .ip_packet_router + .private_x25519_diffie_hellman_key_file, + public_x25519_diffie_hellman_key_file: old_cfg + .exit_gateway + .storage_paths + .ip_packet_router + .public_x25519_diffie_hellman_key_file, + ack_key_file: old_cfg + .exit_gateway + .storage_paths + .ip_packet_router + .ack_key_file, + reply_surb_database: old_cfg + .exit_gateway + .storage_paths + .ip_packet_router + .reply_surb_database, + gateway_registrations: old_cfg + .exit_gateway + .storage_paths + .ip_packet_router + .gateway_registrations, + }, + authenticator: AuthenticatorPaths { + private_ed25519_identity_key_file: old_cfg + .exit_gateway + .storage_paths + .authenticator + .private_ed25519_identity_key_file, + public_ed25519_identity_key_file: old_cfg + .exit_gateway + .storage_paths + .authenticator + .public_ed25519_identity_key_file, + private_x25519_diffie_hellman_key_file: old_cfg + .exit_gateway + .storage_paths + .authenticator + .private_x25519_diffie_hellman_key_file, + public_x25519_diffie_hellman_key_file: old_cfg + .exit_gateway + .storage_paths + .authenticator + .public_x25519_diffie_hellman_key_file, + ack_key_file: old_cfg + .exit_gateway + .storage_paths + .authenticator + .ack_key_file, + reply_surb_database: old_cfg + .exit_gateway + .storage_paths + .authenticator + .reply_surb_database, + gateway_registrations: old_cfg + .exit_gateway + .storage_paths + .authenticator + .gateway_registrations, + }, + }, + open_proxy: old_cfg.exit_gateway.open_proxy, + upstream_exit_policy_url: old_cfg.exit_gateway.upstream_exit_policy_url, + network_requester: NetworkRequester { + debug: NetworkRequesterDebug { + enabled: old_cfg.exit_gateway.network_requester.debug.enabled, + disable_poisson_rate: old_cfg + .exit_gateway + .network_requester + .debug + .disable_poisson_rate, + client_debug: old_cfg.exit_gateway.network_requester.debug.client_debug, + }, + }, + ip_packet_router: IpPacketRouter { + debug: IpPacketRouterDebug { + enabled: old_cfg.exit_gateway.ip_packet_router.debug.enabled, + disable_poisson_rate: old_cfg + .exit_gateway + .ip_packet_router + .debug + .disable_poisson_rate, + client_debug: old_cfg.exit_gateway.ip_packet_router.debug.client_debug, + }, + }, + debug: ExitGatewayConfigDebug { + message_retrieval_limit: old_cfg.exit_gateway.debug.message_retrieval_limit, + }, + }, + authenticator: Default::default(), + logging: LoggingSettings {}, + }; + + Ok(cfg) +} diff --git a/nym-node/src/config/template.rs b/nym-node/src/config/template.rs index a43b942caf..58edfae032 100644 --- a/nym-node/src/config/template.rs +++ b/nym-node/src/config/template.rs @@ -123,15 +123,23 @@ bind_address = '{{ wireguard.bind_address }}' # Private IP address of the wireguard gateway. # default: `10.1.0.1` -private_ip = '{{ wireguard.private_ip }}' +private_ipv4 = '{{ wireguard.private_ipv4 }}' + +# Private IP address of the wireguard gateway. +# default: `fc01::1` +private_ipv6 = '{{ wireguard.private_ipv6 }}' # Port announced to external clients wishing to connect to the wireguard interface. # Useful in the instances where the node is behind a proxy. announced_port = {{ wireguard.announced_port }} -# The prefix denoting the maximum number of the clients that can be connected via Wireguard. -# The maximum value for IPv4 is 32 and for IPv6 is 128 -private_network_prefix = {{ wireguard.private_network_prefix }} +# The prefix denoting the maximum number of the clients that can be connected via Wireguard using IPv4. +# The maximum value for IPv4 is 32 +private_network_prefix_v4 = {{ wireguard.private_network_prefix_v4 }} + +# The prefix denoting the maximum number of the clients that can be connected via Wireguard using IPv6. +# The maximum value for IPv6 is 128 +private_network_prefix_v6 = {{ wireguard.private_network_prefix_v6 }} [wireguard.storage_paths] # Path to file containing wireguard x25519 diffie hellman private key. diff --git a/nym-node/src/config/upgrade_helpers.rs b/nym-node/src/config/upgrade_helpers.rs index 29352ec525..bae75192ec 100644 --- a/nym-node/src/config/upgrade_helpers.rs +++ b/nym-node/src/config/upgrade_helpers.rs @@ -11,7 +11,8 @@ async fn try_upgrade_config(path: &Path) -> Result<(), NymNodeError> { let cfg = try_upgrade_config_v1(path, None).await.ok(); let cfg = try_upgrade_config_v2(path, cfg).await.ok(); let cfg = try_upgrade_config_v3(path, cfg).await.ok(); - match try_upgrade_config_v4(path, cfg).await { + let cfg = try_upgrade_config_v4(path, cfg).await.ok(); + match try_upgrade_config_v5(path, cfg).await { Ok(cfg) => cfg.save(), Err(e) => { tracing::error!("Failed to finish upgrade - {e}"); diff --git a/nym-node/src/env.rs b/nym-node/src/env.rs index d9b03a4b3e..b4662fa69e 100644 --- a/nym-node/src/env.rs +++ b/nym-node/src/env.rs @@ -43,7 +43,6 @@ pub mod vars { // wireguard: pub const NYMNODE_WG_ENABLED_ARG: &str = "NYMNODE_WG_ENABLED"; pub const NYMNODE_WG_BIND_ADDRESS_ARG: &str = "NYMNODE_WG_BIND_ADDRESS"; - pub const NYMNODE_WG_IP_ARG: &str = "NYMNODE_WG_IP"; pub const NYMNODE_WG_ANNOUNCED_PORT_ARG: &str = "NYMNODE_WG_ANNOUNCED_PORT"; pub const NYMNODE_WG_PRIVATE_NETWORK_PREFIX_ARG: &str = "NYMNODE_WG_PRIVATE_NETWORK_PREFIX"; diff --git a/service-providers/authenticator/src/authenticator.rs b/service-providers/authenticator/src/authenticator.rs index f36ed25267..1023059d35 100644 --- a/service-providers/authenticator/src/authenticator.rs +++ b/service-providers/authenticator/src/authenticator.rs @@ -143,17 +143,17 @@ impl Authenticator { 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_ip, - self.config.authenticator.private_network_prefix, + 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| { + .map(|ip: IpAddr| { if used_private_network_ips.contains(&ip) { - (ip, Some(now)) + (ip.into(), Some(now)) } else { - (ip, None) + (ip.into(), None) } }) .collect(); diff --git a/service-providers/authenticator/src/cli/request.rs b/service-providers/authenticator/src/cli/request.rs index 2400e9fc2f..eca46a1892 100644 --- a/service-providers/authenticator/src/cli/request.rs +++ b/service-providers/authenticator/src/cli/request.rs @@ -6,14 +6,14 @@ use crate::cli::{override_config, OverrideConfig}; use crate::cli::{try_load_current_config, version_check}; use clap::{Args, Subcommand}; use nym_authenticator_requests::latest::{ - registration::{ClientMac, FinalMessage, GatewayClient, InitMessage}, + registration::{ClientMac, FinalMessage, GatewayClient, InitMessage, IpPair}, request::{AuthenticatorRequest, AuthenticatorRequestData}, }; use nym_client_core::cli_helpers::client_run::CommonClientRunArgs; use nym_sdk::mixnet::{MixnetMessageSender, Recipient, TransmissionLane}; use nym_task::TaskHandle; use nym_wireguard_types::PeerPublicKey; -use std::net::IpAddr; +use std::net::{Ipv4Addr, Ipv6Addr}; use std::str::FromStr; use std::time::Duration; use tokio::time::sleep; @@ -55,7 +55,8 @@ pub(crate) struct Initial { #[derive(Args, Clone, Debug)] pub(crate) struct Final { pub_key: String, - private_ip: String, + private_ipv4: String, + private_ipv6: String, mac: String, } @@ -75,7 +76,10 @@ impl TryFrom for AuthenticatorRequestData { RequestType::Final(req) => AuthenticatorRequestData::Final(Box::new(FinalMessage { gateway_client: GatewayClient { pub_key: PeerPublicKey::from_str(&req.pub_key)?, - private_ip: IpAddr::from_str(&req.private_ip)?, + private_ips: IpPair::new( + Ipv4Addr::from_str(&req.private_ipv4)?, + Ipv6Addr::from_str(&req.private_ipv6)?, + ), mac: ClientMac::from_str(&req.mac)?, }, credential: None, diff --git a/service-providers/authenticator/src/config/mod.rs b/service-providers/authenticator/src/config/mod.rs index eb5df8479c..4b2d11fae9 100644 --- a/service-providers/authenticator/src/config/mod.rs +++ b/service-providers/authenticator/src/config/mod.rs @@ -8,13 +8,16 @@ use nym_config::{ must_get_home, save_formatted_config_to_file, NymConfigTemplate, OptionalSet, DEFAULT_CONFIG_DIR, DEFAULT_CONFIG_FILENAME, DEFAULT_DATA_DIR, NYM_DIR, }; -use nym_network_defaults::WG_PORT; +use nym_network_defaults::{ + WG_PORT, WG_TUN_DEVICE_IP_ADDRESS_V4, WG_TUN_DEVICE_IP_ADDRESS_V6, WG_TUN_DEVICE_NETMASK_V4, + WG_TUN_DEVICE_NETMASK_V6, +}; use nym_service_providers_common::DEFAULT_SERVICE_PROVIDERS_DIR; pub use persistence::AuthenticatorPaths; use serde::{Deserialize, Serialize}; use std::{ io, - net::{IpAddr, Ipv4Addr, SocketAddr}, + net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}, path::{Path, PathBuf}, str::FromStr, }; @@ -192,24 +195,34 @@ pub struct Authenticator { /// Private IP address of the wireguard gateway. /// default: `10.1.0.1` - pub private_ip: IpAddr, + pub private_ipv4: Ipv4Addr, + + /// Private IP address of the wireguard gateway. + /// default: `fc01::1` + pub private_ipv6: Ipv6Addr, /// Port announced to external clients wishing to connect to the wireguard interface. /// Useful in the instances where the node is behind a proxy. pub announced_port: u16, - /// The prefix denoting the maximum number of the clients that can be connected via Wireguard. - /// The maximum value for IPv4 is 32 and for IPv6 is 128 - pub private_network_prefix: u8, + /// The prefix denoting the maximum number of the clients that can be connected via Wireguard using IPv4. + /// The maximum value for IPv4 is 32 + pub private_network_prefix_v4: u8, + + /// The prefix denoting the maximum number of the clients that can be connected via Wireguard using IPv6. + /// The maximum value for IPv6 is 128 + pub private_network_prefix_v6: u8, } impl Default for Authenticator { fn default() -> Self { Self { - bind_address: SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 51822), - private_ip: IpAddr::V4(Ipv4Addr::new(10, 1, 0, 1)), + bind_address: SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), WG_PORT), + private_ipv4: WG_TUN_DEVICE_IP_ADDRESS_V4, + private_ipv6: WG_TUN_DEVICE_IP_ADDRESS_V6, announced_port: WG_PORT, - private_network_prefix: 16, + private_network_prefix_v4: WG_TUN_DEVICE_NETMASK_V4, + private_network_prefix_v6: WG_TUN_DEVICE_NETMASK_V6, } } } @@ -218,9 +231,11 @@ impl From for nym_wireguard_types::Config { fn from(value: Authenticator) -> Self { nym_wireguard_types::Config { bind_address: value.bind_address, - private_ip: value.private_ip, + private_ipv4: value.private_ipv4, + private_ipv6: value.private_ipv6, announced_port: value.announced_port, - private_network_prefix: value.private_network_prefix, + private_network_prefix_v4: value.private_network_prefix_v4, + private_network_prefix_v6: value.private_network_prefix_v6, } } } diff --git a/service-providers/authenticator/src/mixnet_listener.rs b/service-providers/authenticator/src/mixnet_listener.rs index 8fc5c62582..e4c68eb484 100644 --- a/service-providers/authenticator/src/mixnet_listener.rs +++ b/service-providers/authenticator/src/mixnet_listener.rs @@ -11,17 +11,15 @@ use defguard_wireguard_rs::net::IpAddrMask; use defguard_wireguard_rs::{host::Peer, key::Key}; use futures::StreamExt; use nym_authenticator_requests::{ - v1, v2, - v3::{ - self, + latest::{ registration::{ - FinalMessage, GatewayClient, InitMessage, PendingRegistrations, PrivateIPs, + FinalMessage, GatewayClient, InitMessage, IpPair, PendingRegistrations, PrivateIPs, RegistrationData, RegistredData, RemainingBandwidthData, }, request::{AuthenticatorRequest, AuthenticatorRequestData}, response::AuthenticatorResponse, }, - CURRENT_VERSION, + v1, v2, v3, v4, CURRENT_VERSION, }; use nym_credential_verification::{ bandwidth_storage_manager::BandwidthStorageManager, ecash::EcashManager, @@ -116,10 +114,10 @@ impl MixnetListener { for reg in registred_values { let ip = registred_and_free .free_private_network_ips - .get_mut(®.gateway_data.private_ip) + .get_mut(®.gateway_data.private_ips) .ok_or(AuthenticatorError::InternalDataCorruption(format!( - "IP {} should be present", - reg.gateway_data.private_ip + "IPs {} should be present", + reg.gateway_data.private_ips )))?; let Some(timestamp) = ip else { @@ -173,15 +171,29 @@ impl MixnetListener { let peer = self.peer_manager.query_peer(remote_public).await?; if let Some(peer) = peer { - let Some(allowed_ip) = peer.allowed_ips.first() else { + let private_ipv4 = peer + .allowed_ips + .iter() + .find_map(|ip_mask| match ip_mask.ip { + std::net::IpAddr::V4(ipv4_addr) => Some(ipv4_addr), + _ => None, + }); + let private_ipv6 = peer + .allowed_ips + .iter() + .find_map(|ip_mask| match ip_mask.ip { + std::net::IpAddr::V6(ipv6_addr) => Some(ipv6_addr), + _ => None, + }); + let (Some(allowed_ipv4), Some(allowed_ipv6)) = (private_ipv4, private_ipv6) else { return Err(AuthenticatorError::InternalError( - "private ip list should not be empty".to_string(), + "there should be one private IP pair in the list".to_string(), )); }; return Ok(AuthenticatorResponse::new_registered( RegistredData { pub_key: PeerPublicKey::new(self.keypair().public_key().to_bytes().into()), - private_ip: allowed_ip.ip, + private_ips: IpPair::new(allowed_ipv4, allowed_ipv6), wg_port: self.config.authenticator.announced_port, }, reply_to, @@ -241,8 +253,14 @@ impl MixnetListener { } let mut peer = Peer::new(Key::new(final_message.gateway_client.pub_key.to_bytes())); - peer.allowed_ips - .push(IpAddrMask::new(final_message.gateway_client.private_ip, 32)); + peer.allowed_ips.push(IpAddrMask::new( + final_message.gateway_client.private_ips.ipv4.into(), + 32, + )); + peer.allowed_ips.push(IpAddrMask::new( + final_message.gateway_client.private_ips.ipv6.into(), + 128, + )); // If gateway does ecash verification and client sends a credential, we do the additional // credential verification. Later this will become mandatory. @@ -284,7 +302,7 @@ impl MixnetListener { Ok(AuthenticatorResponse::new_registered( RegistredData { pub_key: registration_data.gateway_data.pub_key, - private_ip: registration_data.gateway_data.private_ip, + private_ips: registration_data.gateway_data.private_ips, wg_port: registration_data.wg_port, }, reply_to, @@ -507,6 +525,7 @@ fn deserialize_request(reconstructed: &ReconstructedMessage) -> Result v1::request::AuthenticatorRequest::from_reconstructed_message(reconstructed) .map_err(|err| AuthenticatorError::FailedToDeserializeTaggedPacket { source: err }) .map(Into::::into) + .map(Into::::into) .map(Into::into), [2, request_type] => { if request_type == ServiceProviderType::Authenticator as u8 { @@ -514,6 +533,7 @@ fn deserialize_request(reconstructed: &ReconstructedMessage) -> Result::into) .map(Into::into) } else { Err(AuthenticatorError::InvalidPacketType(request_type)) @@ -525,6 +545,17 @@ fn deserialize_request(reconstructed: &ReconstructedMessage) -> Result { + if request_type == ServiceProviderType::Authenticator as u8 { + v4::request::AuthenticatorRequest::from_reconstructed_message(reconstructed) + .map_err(|err| AuthenticatorError::FailedToDeserializeTaggedPacket { + source: err, + }) } else { Err(AuthenticatorError::InvalidPacketType(request_type)) } diff --git a/service-providers/ip-packet-router/src/constants.rs b/service-providers/ip-packet-router/src/constants.rs index 311087ad2a..73e744890d 100644 --- a/service-providers/ip-packet-router/src/constants.rs +++ b/service-providers/ip-packet-router/src/constants.rs @@ -5,7 +5,7 @@ use std::time::Duration; pub const TUN_BASE_NAME: &str = "nymtun"; pub const TUN_DEVICE_ADDRESS_V4: Ipv4Addr = Ipv4Addr::new(10, 0, 0, 1); pub const TUN_DEVICE_NETMASK_V4: Ipv4Addr = Ipv4Addr::new(255, 255, 0, 0); -pub const TUN_DEVICE_ADDRESS_V6: Ipv6Addr = Ipv6Addr::new(0x2001, 0xdb8, 0xa160, 0, 0, 0, 0, 0x1); // 2001:db8:a160::1 +pub const TUN_DEVICE_ADDRESS_V6: Ipv6Addr = Ipv6Addr::new(0xfc00, 0, 0, 0, 0, 0, 0, 0x1); // fc00::1 pub const TUN_DEVICE_NETMASK_V6: &str = "112";