wip: putting new sphinx keys to self described endpoints

This commit is contained in:
Jędrzej Stuczyński
2025-05-07 15:06:18 +01:00
parent 440a0dc6a4
commit 3a01d6bf2d
14 changed files with 472 additions and 128 deletions
@@ -3,6 +3,7 @@
use crate::node::http::api::api_requests;
use crate::node::http::state::AppState;
use crate::node::key_rotation::active_keys::SphinxKeyGuard;
use axum::extract::{Query, State};
use nym_http_api_common::{FormattedResponse, OutputParams};
use nym_node_requests::api::{v1::node::models::SignedHostInformation, SignedDataHostInfo};
@@ -27,13 +28,25 @@ pub(crate) async fn host_information(
) -> HostInformationResponse {
let output = output.output.unwrap_or_default();
let primary_key = state.x25519_sphinx_keys.primary();
let pre_announced = match state.x25519_sphinx_keys.secondary() {
None => None,
Some(secondary_key) => {
todo!()
}
};
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(),
x25519_sphinx: state.x25519_sphinx_keys.primary().x25519_pubkey(),
current_x25519_sphinx_key: api_requests::v1::node::models::SphinxKey {
rotation_id: primary_key.rotation_id(),
public_key: primary_key.x25519_pubkey(),
},
x25519_noise: state.static_information.x25519_noise_key,
pre_announced_x25519_sphinx_key: pre_announced,
},
};
@@ -13,8 +13,14 @@ pub(crate) struct ActiveSphinxKeys {
}
struct ActiveSphinxKeysInner {
/// Key that's currently used as the default when processing packets with no explicit rotation information
primary_key: ArcSwap<SphinxPrivateKey>,
/// Optionally, a key from the previous rotation during the overlap period when the keys are rotated.
secondary_key: ArcSwapOption<SphinxPrivateKey>,
/// Optionally, a key for the upcoming rotation that's being pre-announced to other network entities
pre_announced_key: ArcSwapOption<SphinxPrivateKey>,
}
impl ActiveSphinxKeys {
+30 -1
View File
@@ -2,25 +2,54 @@
// SPDX-License-Identifier: Apache-2.0
use crate::node::key_rotation::manager::SphinxKeyManager;
use crate::node::nym_apis_client::NymApisClient;
use nym_task::ShutdownToken;
use tokio::io::AsyncWriteExt;
use std::time::Duration;
use tokio::time::interval;
use tracing::{info, trace};
pub(crate) struct KeyRotationController {
// regular polling rate to catch any changes in the system config. they shouldn't happen too often
// so the requests can be sent quite infrequently
regular_polling_interval: Duration,
client: NymApisClient,
managed_keys: SphinxKeyManager,
shutdown_token: ShutdownToken,
}
enum KeyRotationActionState {
// perform key-rotation and pre-announce new key to the nym-api(s)
PreAnnounce,
// remove the old key and purge associated data like the replay detection bloomfilter
PurgeOld,
}
impl KeyRotationController {
pub(crate) fn new(client: NymApisClient, shutdown_token: ShutdownToken) -> Self {
todo!()
}
async fn regular_poll(&self) {
todo!()
}
pub(crate) async fn run(&self) {
info!("starting sphinx key rotation controller");
let mut polling_interval = interval(self.regular_polling_interval);
polling_interval.reset();
while !self.shutdown_token.is_cancelled() {
tokio::select! {
biased;
_ = self.shutdown_token.cancelled() => {
trace!("KeyRotationController: Received shutdown");
}
_ = polling_interval.tick() => {
self.regular_poll().await;
}
// TODO:
}
}
+1 -1
View File
@@ -5,8 +5,8 @@ use crate::error::{KeyIOFailure, NymNodeError};
use crate::node::helpers::{load_key, store_key};
use crate::node::key_rotation::active_keys::ActiveSphinxKeys;
use crate::node::key_rotation::key::SphinxPrivateKey;
use nym_crypto::aes::cipher::crypto_common::rand_core::{CryptoRng, RngCore};
use rand::rngs::OsRng;
use rand::{CryptoRng, RngCore};
use std::fs;
use std::path::{Path, PathBuf};
use tracing::{trace, warn};
+1 -1
View File
@@ -2,6 +2,6 @@
// SPDX-License-Identifier: GPL-3.0-only
pub(crate) mod active_keys;
mod controller;
pub(crate) mod controller;
pub(crate) mod key;
pub(crate) mod manager;
+52 -12
View File
@@ -12,12 +12,19 @@ use nym_validator_client::nym_api::error::NymAPIError;
use nym_validator_client::NymApiClient;
use rand::prelude::SliceRandom;
use rand::thread_rng;
use std::sync::Arc;
use std::time::Duration;
use tokio::sync::RwLock;
use tokio::time::timeout;
use tracing::warn;
use url::Url;
#[derive(Clone)]
pub struct NymApisClient {
inner: Arc<RwLock<InnerClient>>,
}
struct InnerClient {
active_client: NymApiClient,
available_urls: Vec<Url>,
currently_used_api: usize,
@@ -38,24 +45,57 @@ impl NymApisClient {
.build()?;
Ok(NymApisClient {
active_client: NymApiClient {
nym_api: active_client,
},
available_urls: urls,
currently_used_api: 0,
inner: Arc::new(RwLock::new(InnerClient {
active_client: NymApiClient {
nym_api: active_client,
},
available_urls: urls,
currently_used_api: 0,
})),
})
}
fn use_next_endpoint(&mut self) {
if self.available_urls.len() == 1 {
async fn use_next_endpoint(&mut self) {
let mut guard = self.inner.write().await;
if guard.available_urls.len() == 1 {
return;
}
self.currently_used_api = (self.currently_used_api + 1) % self.available_urls.len();
self.active_client
.change_nym_api(self.available_urls[self.currently_used_api].clone())
let next_index = (guard.currently_used_api + 1) % guard.available_urls.len();
let next = guard.available_urls[next_index].clone();
guard.currently_used_api = next_index;
guard.active_client.change_nym_api(next)
}
pub(crate) async fn query_exhaustively<R, T>(
&self,
req: R,
timeout_duration: Duration,
) -> Result<T, NymNodeError>
where
R: AsyncFn(Client) -> Result<T, NymAPIError>,
{
self.inner
.read()
.await
.query_exhaustively(req, timeout_duration)
.await
}
pub(crate) async fn broadcast_force_refresh(&self, private_key: &ed25519::PrivateKey) {
self.inner
.read()
.await
.broadcast_force_refresh(private_key)
.await;
}
pub(crate) async fn broadcast_key_rotation(&self) {
self.inner.read().await.broadcast_key_rotation().await;
}
}
impl InnerClient {
// currently there are no cases without json body, but for those we'd just need to slightly adjust the signature
async fn broadcast<B, R>(&self, request_body: &B, req: R, timeout_duration: Duration)
where
@@ -115,11 +155,11 @@ impl NymApisClient {
}
pub(crate) async fn broadcast_key_rotation(&self) {
//
todo!()
}
}
impl AsRef<NymApiClient> for NymApisClient {
impl AsRef<NymApiClient> for InnerClient {
fn as_ref(&self) -> &NymApiClient {
&self.active_client
}
@@ -33,41 +33,41 @@ struct ReplayProtectionBackgroundTaskConfig {
impl From<&Config> for ReplayProtectionBackgroundTaskConfig {
fn from(config: &Config) -> Self {
ReplayProtectionBackgroundTaskConfig {
current_bloomfilter_path: config
.mixnet
.replay_protection
.storage_paths
.current_bloomfilter_filepath(),
current_bloomfilter_temp_flush_path: config
.mixnet
.replay_protection
.storage_paths
.current_bloomfilter_being_flushed_filepath(),
false_positive_rate: config.mixnet.replay_protection.debug.false_positive_rate,
filter_reset_rate: config.mixnet.replay_protection.debug.bloomfilter_reset_rate,
disk_flushing_rate: config
.mixnet
.replay_protection
.debug
.bloomfilter_disk_flushing_rate,
bloomfilter_size_multiplier: config
.mixnet
.replay_protection
.debug
.bloomfilter_size_multiplier,
minimum_bloomfilter_packets_per_second: config
.mixnet
.replay_protection
.debug
.bloomfilter_minimum_packets_per_second_size,
}
todo!()
// ReplayProtectionBackgroundTaskConfig {
// current_bloomfilter_path: config
// .mixnet
// .replay_protection
// .storage_paths
// .current_bloomfilter_filepath(),
// current_bloomfilter_temp_flush_path: config
// .mixnet
// .replay_protection
// .storage_paths
// .current_bloomfilter_being_flushed_filepath(),
// false_positive_rate: config.mixnet.replay_protection.debug.false_positive_rate,
// filter_reset_rate: config.mixnet.replay_protection.debug.bloomfilter_reset_rate,
// disk_flushing_rate: config
// .mixnet
// .replay_protection
// .debug
// .bloomfilter_disk_flushing_rate,
// bloomfilter_size_multiplier: config
// .mixnet
// .replay_protection
// .debug
// .bloomfilter_size_multiplier,
// minimum_bloomfilter_packets_per_second: config
// .mixnet
// .replay_protection
// .debug
// .bloomfilter_minimum_packets_per_second_size,
// }
}
}
// background task responsible for periodically flushing the bloomfilter to disk
// as well as clearing it up on the specified timer
// (in the future this will be enforced by key rotation)
// background task responsible for periodically flushing the bloomfilters to disk
// it no longer removes them on the timer as it's now responsibility of the key rotation controller
pub struct ReplayProtectionBackgroundTask {
config: ReplayProtectionBackgroundTaskConfig,
last_reset: LastResetData,