[Feature] Noise XKpsk3 integration (2025 version) (#5692)

* grand Noise squaheroo

* fix merge conflicts and adapt code for the key rotation changes (#5824)

* remove file that should have been ignored
This commit is contained in:
Simon Wicky
2025-06-05 11:34:55 +02:00
committed by GitHub
parent be16fddc75
commit 8ba58ba11e
45 changed files with 2077 additions and 94 deletions
@@ -55,7 +55,7 @@ pub(crate) async fn host_information(
rotation_id: primary_key.rotation_id(),
public_key: primary_pubkey,
},
x25519_noise: state.static_information.x25519_noise_key,
x25519_versioned_noise: state.static_information.x25519_versioned_noise_key,
pre_announced_x25519_sphinx_key: pre_announced,
},
};
+3 -2
View File
@@ -4,8 +4,9 @@
use crate::node::http::state::load::CachedNodeLoad;
use crate::node::http::state::metrics::MetricsAppState;
use crate::node::key_rotation::active_keys::ActiveSphinxKeys;
use nym_crypto::asymmetric::{ed25519, x25519};
use nym_crypto::asymmetric::ed25519;
use nym_node_metrics::NymNodeMetrics;
use nym_noise_keys::VersionedNoiseKey;
use nym_verloc::measurements::SharedVerlocStats;
use std::net::IpAddr;
use std::sync::Arc;
@@ -17,7 +18,7 @@ pub mod metrics;
pub(crate) struct StaticNodeInformation {
pub(crate) ed25519_identity_keys: Arc<ed25519::KeyPair>,
pub(crate) x25519_noise_key: Option<x25519::PublicKey>,
pub(crate) x25519_versioned_noise_key: Option<VersionedNoiseKey>,
pub(crate) ip_addresses: Vec<IpAddr>,
pub(crate) hostname: Option<String>,
}
+28 -9
View File
@@ -3,6 +3,8 @@
use crate::node::mixnet::shared::SharedData;
use futures::StreamExt;
use nym_noise::connection::Connection;
use nym_noise::upgrade_noise_responder;
use nym_sphinx_forwarding::packet::MixPacket;
use nym_sphinx_framing::codec::NymCodec;
use nym_sphinx_framing::packet::FramedNymPacket;
@@ -72,7 +74,6 @@ impl PendingReplayCheckPackets {
pub(crate) struct ConnectionHandler {
shared: SharedData,
mixnet_connection: Framed<TcpStream, NymCodec>,
remote_address: SocketAddr,
// packets pending for replay detection
@@ -89,11 +90,7 @@ impl Drop for ConnectionHandler {
}
impl ConnectionHandler {
pub(crate) fn new(
shared: &SharedData,
tcp_stream: TcpStream,
remote_address: SocketAddr,
) -> Self {
pub(crate) fn new(shared: &SharedData, remote_address: SocketAddr) -> Self {
let shutdown = shared.shutdown.child_token(remote_address.to_string());
shared.metrics.network.new_active_ingress_mixnet_client();
@@ -104,11 +101,11 @@ impl ConnectionHandler {
replay_protection_filter: shared.replay_protection_filter.clone(),
mixnet_forwarder: shared.mixnet_forwarder.clone(),
final_hop: shared.final_hop.clone(),
noise_config: shared.noise_config.clone(),
metrics: shared.metrics.clone(),
shutdown,
},
remote_address,
mixnet_connection: Framed::new(tcp_stream, NymCodec),
pending_packets: PendingReplayCheckPackets::new(),
}
}
@@ -466,7 +463,29 @@ impl ConnectionHandler {
remote = %self.remote_address
)
)]
pub(crate) async fn handle_stream(&mut self) {
pub(crate) async fn handle_connection(&mut self, socket: TcpStream) {
let noise_stream = match upgrade_noise_responder(socket, &self.shared.noise_config).await {
Ok(noise_stream) => noise_stream,
Err(err) => {
error!(
"Failed to perform Noise handshake with {:?} - {err}",
self.remote_address
);
return;
}
};
debug!(
"Noise responder handshake completed for {:?}",
self.remote_address
);
self.handle_stream(Framed::new(noise_stream, NymCodec))
.await
}
pub(crate) async fn handle_stream(
&mut self,
mut mixnet_connection: Framed<Connection<TcpStream>, NymCodec>,
) {
loop {
tokio::select! {
biased;
@@ -474,7 +493,7 @@ impl ConnectionHandler {
trace!("connection handler: received shutdown");
break
}
maybe_framed_nym_packet = self.mixnet_connection.next() => {
maybe_framed_nym_packet = mixnet_connection.next() => {
match maybe_framed_nym_packet {
Some(Ok(packet)) => self.handle_received_nym_packet(packet).await,
Some(Err(err)) => {
+10 -2
View File
@@ -10,6 +10,7 @@ use nym_gateway::node::GatewayStorageError;
use nym_mixnet_client::forwarder::{MixForwardingSender, PacketToForward};
use nym_node_metrics::mixnet::PacketKind;
use nym_node_metrics::NymNodeMetrics;
use nym_noise::config::NoiseConfig;
use nym_sphinx_forwarding::packet::MixPacket;
use nym_sphinx_framing::processing::{
MixPacketVersion, MixProcessingResult, MixProcessingResultData, PacketProcessingError,
@@ -74,6 +75,9 @@ pub(crate) struct SharedData {
// data specific to the final hop (gateway) processing
pub(super) final_hop: SharedFinalHopData,
// for establishing a Noise connection
pub(super) noise_config: NoiseConfig,
pub(super) metrics: NymNodeMetrics,
pub(super) shutdown: ShutdownToken,
}
@@ -86,12 +90,14 @@ fn convert_to_metrics_version(processed: MixPacketVersion) -> PacketKind {
}
impl SharedData {
#[allow(clippy::too_many_arguments)]
pub(crate) fn new(
processing_config: ProcessingConfig,
sphinx_keys: ActiveSphinxKeys,
replay_protection_filter: ReplayProtectionBloomfilters,
mixnet_forwarder: MixForwardingSender,
final_hop: SharedFinalHopData,
noise_config: NoiseConfig,
metrics: NymNodeMetrics,
shutdown: ShutdownToken,
) -> Self {
@@ -101,6 +107,7 @@ impl SharedData {
replay_protection_filter,
mixnet_forwarder,
final_hop,
noise_config,
metrics,
shutdown,
}
@@ -163,8 +170,9 @@ impl SharedData {
match accepted {
Ok((socket, remote_addr)) => {
debug!("accepted incoming mixnet connection from: {remote_addr}");
let mut handler = ConnectionHandler::new(self, socket, remote_addr);
let join_handle = tokio::spawn(async move { handler.handle_stream().await });
let mut handler = ConnectionHandler::new(self, remote_addr);
let join_handle =
tokio::spawn(async move { handler.handle_connection(socket).await });
self.log_connected_clients();
Some(join_handle)
}
+27 -3
View File
@@ -50,6 +50,8 @@ use nym_network_requester::{
use nym_node_metrics::events::MetricEventsSender;
use nym_node_metrics::NymNodeMetrics;
use nym_node_requests::api::v1::node::models::{AnnouncePorts, NodeDescription};
use nym_noise::config::{NoiseConfig, NoiseNetworkView};
use nym_noise_keys::VersionedNoiseKey;
use nym_sphinx_acknowledgements::AckKey;
use nym_sphinx_addressing::Recipient;
use nym_task::{ShutdownManager, ShutdownToken, TaskClient};
@@ -800,16 +802,19 @@ impl NymNode {
config.api.v1_config.node.roles.ip_packet_router_enabled = true;
}
let x25519_noise_key = if self.config.mixnet.debug.unsafe_disable_noise {
let x25519_versioned_noise_key = if self.config.mixnet.debug.unsafe_disable_noise {
None
} else {
Some(*self.x25519_noise_keys.public_key())
Some(VersionedNoiseKey {
supported_version: nym_noise::LATEST_NOISE_VERSION,
x25519_pubkey: *self.x25519_noise_keys.public_key(),
})
};
let app_state = AppState::new(
StaticNodeInformation {
ed25519_identity_keys: self.ed25519_identity_keys.clone(),
x25519_noise_key,
x25519_versioned_noise_key,
ip_addresses: self.config.host.public_ips.clone(),
hostname: self.config.host.hostname.clone(),
},
@@ -1031,6 +1036,7 @@ impl NymNode {
active_clients_store: &ActiveClientsStore,
replay_protection_bloomfilter: ReplayProtectionBloomfilters,
routing_filter: F,
noise_config: NoiseConfig,
shutdown: ShutdownToken,
) -> Result<(MixForwardingSender, ActiveConnections), NymNodeError>
where
@@ -1054,6 +1060,7 @@ impl NymNode {
);
let mixnet_client = nym_mixnet_client::Client::new(
mixnet_client_config,
noise_config.clone(),
self.metrics
.network
.active_egress_mixnet_connections_counter(),
@@ -1080,6 +1087,7 @@ impl NymNode {
replay_protection_bloomfilter,
mix_packet_sender.clone(),
final_hop_data,
noise_config,
self.metrics.clone(),
shutdown,
);
@@ -1089,10 +1097,18 @@ impl NymNode {
}
pub(crate) async fn run_minimal_mixnet_processing(self) -> Result<(), NymNodeError> {
let noise_config = nym_noise::config::NoiseConfig::new(
self.x25519_noise_keys.clone(),
NoiseNetworkView::new_empty(),
self.config.mixnet.debug.initial_connection_timeout,
)
.with_unsafe_disabled(true);
self.start_mixnet_listener(
&ActiveClientsStore::new(),
ReplayProtectionBloomfilters::new_disabled(),
OpenFilter,
noise_config,
self.shutdown_manager.clone_token("mixnet-traffic"),
)
.await?;
@@ -1137,11 +1153,19 @@ impl NymNode {
let bloomfilters_manager = self.setup_replay_detection().await?;
let noise_config = nym_noise::config::NoiseConfig::new(
self.x25519_noise_keys.clone(),
network_refresher.noise_view(),
self.config.mixnet.debug.initial_connection_timeout,
)
.with_unsafe_disabled(self.config.mixnet.debug.unsafe_disable_noise);
let (mix_packet_sender, active_egress_mixnet_connections) = self
.start_mixnet_listener(
&active_clients_store,
bloomfilters_manager.bloomfilters(),
network_refresher.routing_filter(),
noise_config,
self.shutdown_manager.clone_token("mixnet-traffic"),
)
.await?;
+44 -13
View File
@@ -8,6 +8,7 @@ use async_trait::async_trait;
use nym_crypto::asymmetric::ed25519;
use nym_gateway::node::UserAgent;
use nym_node_metrics::prometheus_wrapper::{PrometheusMetric, PROMETHEUS_METRICS};
use nym_noise::config::NoiseNetworkView;
use nym_task::ShutdownToken;
use nym_topology::node::RoutingNode;
use nym_topology::{
@@ -16,10 +17,10 @@ use nym_topology::{
};
use nym_validator_client::nym_api::NymApiClientExt;
use nym_validator_client::nym_nodes::{
NodesByAddressesResponse, SkimmedNode, SkimmedNodesWithMetadata,
NodesByAddressesResponse, SemiSkimmedNode, SemiSkimmedNodesWithMetadata,
};
use nym_validator_client::{NymApiClient, ValidatorClientError};
use std::collections::HashSet;
use std::collections::{HashMap, HashSet};
use std::net::{IpAddr, SocketAddr};
use std::ops::Deref;
use std::sync::Arc;
@@ -63,10 +64,12 @@ impl NodesQuerier {
res
}
async fn current_nymnodes(&mut self) -> Result<SkimmedNodesWithMetadata, ValidatorClientError> {
async fn current_nymnodes(
&mut self,
) -> Result<SemiSkimmedNodesWithMetadata, ValidatorClientError> {
let res = self
.client
.get_all_basic_nodes_with_metadata()
.get_all_expanded_nodes()
.await
.inspect_err(|err| error!("failed to get network nodes: {err}"));
@@ -150,13 +153,19 @@ impl TopologyProvider for CachedTopologyProvider {
network_guard.rewarded_set.clone(),
Vec::new(),
)
.with_additional_nodes(network_guard.network_nodes.iter().filter(|node| {
if node.supported_roles.mixnode {
node.performance.round_to_integer() >= self.min_mix_performance
} else {
true
}
}));
.with_additional_nodes(
network_guard
.network_nodes
.iter()
.map(|node| &node.basic)
.filter(|node| {
if node.supported_roles.mixnode {
node.performance.round_to_integer() >= self.min_mix_performance
} else {
true
}
}),
);
if !topology.has_node(self.gateway_node.identity_key) {
debug!("{self_node} didn't exist in topology. inserting it.",);
@@ -188,7 +197,7 @@ impl CachedNetwork {
struct CachedNetworkInner {
rewarded_set: EpochRewardedSet,
topology_metadata: NymTopologyMetadata,
network_nodes: Vec<SkimmedNode>,
network_nodes: Vec<SemiSkimmedNode>,
}
pub struct NetworkRefresher {
@@ -199,6 +208,7 @@ pub struct NetworkRefresher {
network: CachedNetwork,
routing_filter: NetworkRoutingFilter,
noise_view: NoiseNetworkView,
}
impl NetworkRefresher {
@@ -226,6 +236,7 @@ impl NetworkRefresher {
shutdown_token,
network: CachedNetwork::new_empty(),
routing_filter: NetworkRoutingFilter::new_empty(testnet),
noise_view: NoiseNetworkView::new_empty(),
};
this.obtain_initial_network().await?;
@@ -282,7 +293,7 @@ impl NetworkRefresher {
// collect all known/allowed nodes information
let known_nodes = nodes
.iter()
.flat_map(|n| n.ip_addresses.iter())
.flat_map(|n| n.basic.ip_addresses.iter())
.copied()
.collect::<HashSet<_>>();
@@ -305,6 +316,22 @@ impl NetworkRefresher {
self.routing_filter.resolved.swap_denied(current_denied);
self.routing_filter.pending.clear().await;
//update noise Noise Nodes
let noise_nodes = nodes
.iter()
.filter(|n| n.x25519_noise_versioned_key.is_some())
.flat_map(|n| {
n.basic.ip_addresses.iter().map(|ip_addr| {
(
SocketAddr::new(*ip_addr, n.basic.mix_port),
#[allow(clippy::unwrap_used)]
n.x25519_noise_versioned_key.unwrap(), // SAFETY : we filtered out nodes where this option can be None
)
})
})
.collect::<HashMap<_, _>>();
self.noise_view.swap_view(noise_nodes);
let mut network_guard = self.network.inner.write().await;
network_guard.topology_metadata =
NymTopologyMetadata::new(metadata.rotation_id, metadata.absolute_epoch_id);
@@ -340,6 +367,10 @@ impl NetworkRefresher {
self.network.clone()
}
pub(crate) fn noise_view(&self) -> NoiseNetworkView {
self.noise_view.clone()
}
pub(crate) async fn run(&mut self) {
let mut full_refresh_interval = interval(self.full_refresh_interval);
full_refresh_interval.reset();