From 61dd59ff9dffd704ffc2681b5534d3fd8d1463b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Tue, 13 May 2025 15:31:36 +0100 Subject: [PATCH] most of nym-node changes --- nym-api/nym-api-requests/src/models.rs | 2 +- nym-node/nym-node-requests/src/api/mod.rs | 16 +++++----- .../src/api/v1/node/models.rs | 13 +++++--- nym-node/src/config/mod.rs | 30 +++++++++++++++++++ .../src/config/old_configs/old_config_v9.rs | 1 + .../router/api/v1/node/host_information.rs | 17 +++++++++-- nym-node/src/node/key_rotation/active_keys.rs | 13 ++++---- nym-node/src/node/key_rotation/controller.rs | 27 +++++++++-------- nym-node/src/node/key_rotation/manager.rs | 2 +- nym-node/src/node/mod.rs | 4 ++- .../src/node/replay_protection/bloomfilter.rs | 4 +-- nym-node/src/throughput_tester/client.rs | 19 ++++++++++-- 12 files changed, 106 insertions(+), 42 deletions(-) diff --git a/nym-api/nym-api-requests/src/models.rs b/nym-api/nym-api-requests/src/models.rs index 8f7d4572b5..61ff74d54e 100644 --- a/nym-api/nym-api-requests/src/models.rs +++ b/nym-api/nym-api-requests/src/models.rs @@ -933,7 +933,7 @@ impl From for HostKeys { fn from(value: nym_node_requests::api::v1::node::models::HostKeys) -> Self { HostKeysDeHelper { ed25519: value.ed25519_identity, - current_x25519_sphinx_key: value.current_x25519_sphinx_key.into(), + current_x25519_sphinx_key: value.primary_x25519_sphinx_key.into(), pre_announced_x25519_sphinx_key: value.pre_announced_x25519_sphinx_key.map(Into::into), x25519_noise: value.x25519_noise, } diff --git a/nym-node/nym-node-requests/src/api/mod.rs b/nym-node/nym-node-requests/src/api/mod.rs index 990fd95bb6..67b894ed36 100644 --- a/nym-node/nym-node-requests/src/api/mod.rs +++ b/nym-node/nym-node-requests/src/api/mod.rs @@ -136,7 +136,7 @@ mod tests { hostname: Some("foomp.com".to_string()), keys: crate::api::v1::node::models::HostKeys { ed25519_identity: *ed22519.public_key(), - current_x25519_sphinx_key: SphinxKey { + primary_x25519_sphinx_key: SphinxKey { rotation_id: current_rotation_id, public_key: *x25519_sphinx.public_key(), }, @@ -154,7 +154,7 @@ mod tests { hostname: Some("foomp.com".to_string()), keys: crate::api::v1::node::models::HostKeys { ed25519_identity: *ed22519.public_key(), - current_x25519_sphinx_key: SphinxKey { + primary_x25519_sphinx_key: SphinxKey { rotation_id: current_rotation_id, public_key: *x25519_sphinx.public_key(), }, @@ -174,7 +174,7 @@ mod tests { hostname: Some("foomp.com".to_string()), keys: crate::api::v1::node::models::HostKeys { ed25519_identity: *ed22519.public_key(), - current_x25519_sphinx_key: SphinxKey { + primary_x25519_sphinx_key: SphinxKey { rotation_id: current_rotation_id, public_key: *x25519_sphinx.public_key(), }, @@ -195,7 +195,7 @@ mod tests { hostname: Some("foomp.com".to_string()), keys: crate::api::v1::node::models::HostKeys { ed25519_identity: *ed22519.public_key(), - current_x25519_sphinx_key: SphinxKey { + primary_x25519_sphinx_key: SphinxKey { rotation_id: current_rotation_id, public_key: *x25519_sphinx.public_key(), }, @@ -236,7 +236,7 @@ mod tests { hostname: Some("foomp.com".to_string()), keys: HostKeys { ed25519_identity: *ed22519.public_key(), - current_x25519_sphinx_key: SphinxKey { + primary_x25519_sphinx_key: SphinxKey { rotation_id: u32::MAX, public_key: *x25519_sphinx.public_key(), }, @@ -293,7 +293,7 @@ mod tests { hostname: legacy_info_no_noise.hostname.clone(), keys: crate::api::v1::node::models::HostKeys { ed25519_identity: legacy_info_no_noise.keys.ed25519_identity.parse().unwrap(), - current_x25519_sphinx_key: SphinxKey { + primary_x25519_sphinx_key: SphinxKey { rotation_id: u32::MAX, public_key: *x25519_sphinx.public_key(), }, @@ -308,7 +308,7 @@ mod tests { hostname: legacy_info_noise.hostname.clone(), keys: crate::api::v1::node::models::HostKeys { ed25519_identity: legacy_info_noise.keys.ed25519_identity.parse().unwrap(), - current_x25519_sphinx_key: SphinxKey { + primary_x25519_sphinx_key: SphinxKey { rotation_id: u32::MAX, public_key: *x25519_sphinx.public_key(), }, @@ -365,7 +365,7 @@ mod tests { hostname: legacy_info.hostname.clone(), keys: crate::api::v1::node::models::HostKeys { ed25519_identity: legacy_info.keys.ed25519.parse().unwrap(), - current_x25519_sphinx_key: SphinxKey { + primary_x25519_sphinx_key: SphinxKey { rotation_id: u32::MAX, public_key: *x25519_sphinx.public_key(), }, diff --git a/nym-node/nym-node-requests/src/api/v1/node/models.rs b/nym-node/nym-node-requests/src/api/v1/node/models.rs index 862852826b..87204f88de 100644 --- a/nym-node/nym-node-requests/src/api/v1/node/models.rs +++ b/nym-node/nym-node-requests/src/api/v1/node/models.rs @@ -131,12 +131,17 @@ pub struct HostKeys { #[cfg_attr(feature = "openapi", schema(value_type = String))] pub ed25519_identity: ed25519::PublicKey, + #[deprecated(note = "use explicit primary_x25519_sphinx_key instead")] + #[serde(alias = "x25519")] + #[serde(with = "bs58_x25519_pubkey")] + #[schemars(with = "String")] + #[cfg_attr(feature = "openapi", schema(value_type = String))] + pub x25519_sphinx: x25519::PublicKey, + /// Current, active, x25519 sphinx key clients are expected to use when constructing packets /// with this node in the route. - #[serde(alias = "x25519")] - #[serde(alias = "x25519_sphinx")] #[serde(deserialize_with = "de_sphinx_key")] - pub current_x25519_sphinx_key: SphinxKey, + pub primary_x25519_sphinx_key: SphinxKey, /// Pre-announced x25519 sphinx key clients will use during the following key rotation #[serde(default)] @@ -223,7 +228,7 @@ impl From for LegacyHostKeysV3 { fn from(value: HostKeys) -> Self { LegacyHostKeysV3 { ed25519_identity: value.ed25519_identity, - x25519_sphinx: value.current_x25519_sphinx_key.public_key, + x25519_sphinx: value.primary_x25519_sphinx_key.public_key, x25519_noise: value.x25519_noise, } } diff --git a/nym-node/src/config/mod.rs b/nym-node/src/config/mod.rs index f86eefba5b..9074f5697b 100644 --- a/nym-node/src/config/mod.rs +++ b/nym-node/src/config/mod.rs @@ -529,6 +529,9 @@ pub struct Mixnet { /// Settings for controlling replay detection pub replay_protection: ReplayProtection, + #[serde(default)] + pub key_rotation: KeyRotation, + #[serde(default)] pub debug: MixnetDebug, } @@ -717,6 +720,32 @@ impl Default for ReplayProtectionDebug { } } +#[derive(Debug, Default, Copy, Clone, Deserialize, PartialEq, Serialize)] +#[serde(default)] +pub struct KeyRotation { + pub debug: KeyRotationDebug, +} + +#[derive(Debug, Copy, Clone, Deserialize, PartialEq, Serialize)] +#[serde(default)] +pub struct KeyRotationDebug { + /// Specifies how often the node should poll for any changes in the key rotation global state. + #[serde(with = "humantime_serde")] + pub rotation_state_poling_interval: Duration, +} + +impl KeyRotationDebug { + pub const DEFAULT_ROTATION_STATE_POLLING_INTERVAL: Duration = Duration::from_secs(4 * 60 * 60); +} + +impl Default for KeyRotationDebug { + fn default() -> Self { + KeyRotationDebug { + rotation_state_poling_interval: Self::DEFAULT_ROTATION_STATE_POLLING_INTERVAL, + } + } +} + impl MixnetDebug { // given that genuine clients are using mean delay of 50ms, // the probability of them delaying for over 10s is 10^-87 @@ -767,6 +796,7 @@ impl Mixnet { nym_api_urls, nyxd_urls, replay_protection: ReplayProtection::new_default(data_dir), + key_rotation: Default::default(), debug: Default::default(), } } diff --git a/nym-node/src/config/old_configs/old_config_v9.rs b/nym-node/src/config/old_configs/old_config_v9.rs index 23c96a5cb8..058edfc61e 100644 --- a/nym-node/src/config/old_configs/old_config_v9.rs +++ b/nym-node/src/config/old_configs/old_config_v9.rs @@ -1403,6 +1403,7 @@ pub async fn try_upgrade_config_v9>( .bloomfilter_disk_flushing_rate, }, }, + key_rotation: Default::default(), debug: MixnetDebug { maximum_forward_packet_delay: old_cfg.mixnet.debug.maximum_forward_packet_delay, packet_forwarding_initial_backoff: old_cfg diff --git a/nym-node/src/node/http/router/api/v1/node/host_information.rs b/nym-node/src/node/http/router/api/v1/node/host_information.rs index c6a7dc0fbb..32a8c9ea6e 100644 --- a/nym-node/src/node/http/router/api/v1/node/host_information.rs +++ b/nym-node/src/node/http/router/api/v1/node/host_information.rs @@ -31,18 +31,29 @@ pub(crate) async fn host_information( let pre_announced = match state.x25519_sphinx_keys.secondary() { None => None, Some(secondary_key) => { - todo!() + if secondary_key.rotation_id() == primary_key.rotation_id() + 1 { + Some(api_requests::v1::node::models::SphinxKey { + rotation_id: secondary_key.rotation_id(), + public_key: secondary_key.x25519_pubkey(), + }) + } else { + None + } } }; + let primary_pubkey = primary_key.x25519_pubkey(); + + #[allow(deprecated)] let host_info = api_requests::v1::node::models::HostInformation { ip_address: state.static_information.ip_addresses.clone(), hostname: state.static_information.hostname.clone(), keys: api_requests::v1::node::models::HostKeys { ed25519_identity: *state.static_information.ed25519_identity_keys.public_key(), - current_x25519_sphinx_key: api_requests::v1::node::models::SphinxKey { + x25519_sphinx: primary_pubkey, + primary_x25519_sphinx_key: api_requests::v1::node::models::SphinxKey { rotation_id: primary_key.rotation_id(), - public_key: primary_key.x25519_pubkey(), + public_key: primary_pubkey, }, x25519_noise: state.static_information.x25519_noise_key, pre_announced_x25519_sphinx_key: pre_announced, diff --git a/nym-node/src/node/key_rotation/active_keys.rs b/nym-node/src/node/key_rotation/active_keys.rs index cf637a1fa1..8532cddfc8 100644 --- a/nym-node/src/node/key_rotation/active_keys.rs +++ b/nym-node/src/node/key_rotation/active_keys.rs @@ -35,13 +35,12 @@ impl ActiveSphinxKeys { primary: SphinxPrivateKey, secondary: Option, ) -> Self { - todo!() - // ActiveSphinxKeys { - // inner: Arc::new(ActiveSphinxKeysInner { - // primary_key: ArcSwap::from_pointee(primary), - // secondary_key: ArcSwapOption::from_pointee(secondary), - // }), - // } + ActiveSphinxKeys { + inner: Arc::new(ActiveSphinxKeysInner { + primary_key: ArcSwap::from_pointee(primary), + secondary_key: ArcSwapOption::from_pointee(secondary), + }), + } } pub(crate) fn even(&self) -> Option { diff --git a/nym-node/src/node/key_rotation/controller.rs b/nym-node/src/node/key_rotation/controller.rs index e4fd372f30..07d4229725 100644 --- a/nym-node/src/node/key_rotation/controller.rs +++ b/nym-node/src/node/key_rotation/controller.rs @@ -6,7 +6,6 @@ use crate::node::key_rotation::manager::SphinxKeyManager; use crate::node::nym_apis_client::NymApisClient; use crate::node::replay_protection::manager::ReplayProtectionBloomfiltersManager; use futures::pin_mut; -use nym_node_metrics::NymNodeMetrics; use nym_task::ShutdownToken; use nym_validator_client::models::{KeyRotationInfoResponse, KeyRotationState}; use std::time::Duration; @@ -14,7 +13,7 @@ use time::OffsetDateTime; use tokio::time::{interval, sleep, Instant}; use tracing::{error, info, trace, warn}; -struct RotationConfig { +pub(crate) struct RotationConfig { epoch_duration: Duration, rotation_state: KeyRotationState, } @@ -78,20 +77,24 @@ enum KeyRotationActionState { impl KeyRotationController { pub(crate) fn new( config: &Config, + rotation_config: RotationConfig, client: NymApisClient, replay_protection_manager: ReplayProtectionBloomfiltersManager, - metrics: NymNodeMetrics, managed_keys: SphinxKeyManager, shutdown_token: ShutdownToken, ) -> Self { - todo!() - // KeyRotationController { - // regular_polling_interval, - // replay_protection_manager, - // client, - // managed_keys, - // shutdown_token, - // } + KeyRotationController { + regular_polling_interval: config + .mixnet + .key_rotation + .debug + .rotation_state_poling_interval, + rotation_config, + replay_protection_manager, + client, + managed_keys, + shutdown_token, + } } async fn determine_next_action(&self) -> NextAction { @@ -234,7 +237,7 @@ impl KeyRotationController { polling_interval.reset(); let mut next_action = self.determine_next_action().await; - let mut state_update_future = sleep(next_action.until_deadline()); + let state_update_future = sleep(next_action.until_deadline()); pin_mut!(state_update_future); while !self.shutdown_token.is_cancelled() { diff --git a/nym-node/src/node/key_rotation/manager.rs b/nym-node/src/node/key_rotation/manager.rs index d622be7677..090f0cbb77 100644 --- a/nym-node/src/node/key_rotation/manager.rs +++ b/nym-node/src/node/key_rotation/manager.rs @@ -180,7 +180,7 @@ impl SphinxKeyManager { // immediately rotate keys (but leave 1h grace period for current primary, i.e. set it as secondary) if primary.rotation_id() != current_rotation_id { warn!("this node has been inactive for more than a key rotation duration. the current primary key was generated for rotation {} while the current rotation is {current_rotation_id}. new key will be generated now.", primary.rotation_id()); - let mut this = SphinxKeyManager { + let this = SphinxKeyManager { keys: ActiveSphinxKeys::new_loaded(primary, None), primary_key_path: primary_key_path.as_ref().to_path_buf(), secondary_key_path: secondary_key_path.as_ref().to_path_buf(), diff --git a/nym-node/src/node/mod.rs b/nym-node/src/node/mod.rs index ddd43d79b0..3620a9348a 100644 --- a/nym-node/src/node/mod.rs +++ b/nym-node/src/node/mod.rs @@ -1003,11 +1003,13 @@ impl NymNode { replay_protection_manager: ReplayProtectionBloomfiltersManager, ) -> Result<(), NymNodeError> { let managed_keys = self.take_managed_sphinx_keys()?; + let rotation_state = nym_apis_client.get_key_rotation_info().await?; + let rotation_controller = KeyRotationController::new( &self.config, + rotation_state.into(), nym_apis_client, replay_protection_manager, - self.metrics.clone(), managed_keys, self.shutdown_manager.clone_token("key-rotation-controller"), ); diff --git a/nym-node/src/node/replay_protection/bloomfilter.rs b/nym-node/src/node/replay_protection/bloomfilter.rs index 47b12e0b31..dda30f5534 100644 --- a/nym-node/src/node/replay_protection/bloomfilter.rs +++ b/nym-node/src/node/replay_protection/bloomfilter.rs @@ -210,7 +210,7 @@ impl ReplayProtectionBloomfilters { })?; let id = guard.primary.metadata.rotation_id; - let bytes = guard.primary.data.to_bytes(); + let bytes = guard.primary.bytes(); Ok((bytes, id)) } @@ -233,7 +233,7 @@ impl ReplayProtectionBloomfilters { }; let id = secondary.metadata.rotation_id; - let bytes = secondary.data.to_bytes(); + let bytes = secondary.bytes(); Ok(Some((bytes, id))) } } diff --git a/nym-node/src/throughput_tester/client.rs b/nym-node/src/throughput_tester/client.rs index 02a82b4942..3410dd9814 100644 --- a/nym-node/src/throughput_tester/client.rs +++ b/nym-node/src/throughput_tester/client.rs @@ -15,7 +15,7 @@ use nym_crypto::asymmetric::x25519; use nym_sphinx_addressing::nodes::NymNodeRoutingAddress; use nym_sphinx_framing::codec::{NymCodec, NymCodecError}; use nym_sphinx_framing::packet::FramedNymPacket; -use nym_sphinx_params::PacketSize; +use nym_sphinx_params::{PacketSize, SphinxKeyRotation}; use nym_sphinx_routing::generate_hop_delays; use nym_sphinx_types::constants::{ EXPANDED_SHARED_SECRET_HKDF_INFO, EXPANDED_SHARED_SECRET_HKDF_SALT, @@ -101,6 +101,7 @@ pub(crate) struct ThroughputTestingClient { listener: TcpListener, forward_connection: Framed, payload_key: PayloadKey, + key_rotation: SphinxKeyRotation, } fn rederive_lioness_payload_key(shared_secret: &[u8; 32]) -> PayloadKey { @@ -195,6 +196,12 @@ impl ThroughputTestingClient { } }; + let key_rotation = if loaded_private.is_even_rotation() { + SphinxKeyRotation::EvenRotation + } else { + SphinxKeyRotation::OddRotation + }; + Ok(ThroughputTestingClient { stats, last_received_update: Instant::now(), @@ -210,6 +217,7 @@ impl ThroughputTestingClient { listener, forward_connection: Framed::new(forward_connection, NymCodec), payload_key, + key_rotation, }) } @@ -256,8 +264,13 @@ impl ThroughputTestingClient { packet_bytes.append(&mut payload_bytes); let forward_packet = NymPacket::sphinx_from_bytes(&packet_bytes)?; - todo!() - // Ok(FramedNymPacket::new(forward_packet, Default::default())) + // let key_rotation = if self.s + + Ok(FramedNymPacket::new( + forward_packet, + Default::default(), + self.key_rotation, + )) } async fn send_packets(&mut self) -> anyhow::Result<()> {