Compare commits

...

2 Commits

Author SHA1 Message Date
durch 7712d15e79 Remove unwraps 2025-06-12 17:30:41 +02:00
durch eb886c0860 Add shared derivation material support for thread-safe key generation
- Add serialization support to DerivationMaterial for persistence/sharing
- Implement shared derivation material in BaseClientBuilder with Arc<Mutex<>> for thread safety
- Add network monitor support for deterministic key generation from file
- Extend MixnetClientBuilder with shared derivation material functionality
- Add comprehensive documentation explaining thread-safe sharing and key generation priority

This enables multiple clients to derive keys from the same source material while maintaining
thread safety, particularly useful for network monitoring where consistent client identities
are needed across multiple instances.
2025-06-12 17:24:32 +02:00
4 changed files with 113 additions and 13 deletions
@@ -63,6 +63,7 @@ use std::os::raw::c_int as RawFd;
use std::path::Path;
use std::sync::Arc;
use tokio::sync::mpsc::Sender;
use tokio::sync::Mutex;
use url::Url;
#[cfg(all(
@@ -195,6 +196,10 @@ pub struct BaseClientBuilder<C, S: MixnetClientStorage> {
connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
derivation_material: Option<DerivationMaterial>,
// Shared derivation material wrapped in Arc<Mutex<>> for thread-safe access
// across multiple clients. This allows multiple clients to share the same
// derivation source while maintaining safe concurrent access.
shared_derivation_material: Option<Arc<Mutex<DerivationMaterial>>>,
}
impl<C, S> BaseClientBuilder<C, S>
@@ -220,6 +225,7 @@ where
#[cfg(unix)]
connection_fd_callback: None,
derivation_material: None,
shared_derivation_material: None,
}
}
@@ -232,6 +238,18 @@ where
self
}
/// Set shared derivation material for thread-safe sharing across multiple clients.
/// This is useful when multiple clients need to derive keys from the same source
/// while ensuring thread-safe access through Arc<Mutex<>>.
#[must_use]
pub fn with_shared_derivation_material(
mut self,
derivation_material: Option<Arc<Mutex<DerivationMaterial>>>,
) -> Self {
self.shared_derivation_material = derivation_material;
self
}
#[must_use]
pub fn with_forget_me(mut self, forget_me: &ForgetMe) -> Self {
self.config.debug.forget_me = *forget_me;
@@ -704,6 +722,7 @@ where
key_store: &S::KeyStore,
details_store: &S::GatewaysDetailsStore,
derivation_material: Option<DerivationMaterial>,
shared_derivation_material: Option<Arc<Mutex<DerivationMaterial>>>,
) -> Result<InitialisationResult, ClientCoreError>
where
<S::KeyStore as KeyStore>::StorageError: Sync + Send,
@@ -713,12 +732,24 @@ where
if key_store.load_keys().await.is_err() {
info!("could not find valid client keys - a new set will be generated");
let mut rng = OsRng;
let keys = if let Some(derivation_material) = derivation_material {
ClientKeys::from_master_key(&mut rng, &derivation_material)
.map_err(|_| ClientCoreError::HkdfDerivationError {})?
} else {
ClientKeys::generate_new(&mut rng)
// Key generation priority: individual derivation material > shared derivation material > random generation
let keys = match (derivation_material, shared_derivation_material) {
// Individual derivation material takes precedence if provided
(Some(derivation_material), _) => {
ClientKeys::from_master_key(&mut rng, &derivation_material)
.map_err(|_| ClientCoreError::HkdfDerivationError {})?
}
// Use shared derivation material if no individual material is provided
(None, Some(shared_derivation_material)) => {
let shared_derivation_material = shared_derivation_material.lock().await;
ClientKeys::from_master_key(&mut rng, &shared_derivation_material)
.map_err(|_| ClientCoreError::HkdfDerivationError {})?
}
// Fall back to random key generation if no derivation material is available
(None, None) => ClientKeys::generate_new(&mut rng),
};
store_client_keys(keys, key_store).await?;
}
@@ -741,6 +772,7 @@ where
self.client_store.key_store(),
self.client_store.gateway_details_store(),
self.derivation_material,
self.shared_derivation_material,
)
.await?;
+2 -1
View File
@@ -8,6 +8,7 @@ use hkdf::{
},
Hkdf,
};
use serde::{Deserialize, Serialize};
use sha2::{Sha256, Sha512};
pub use hkdf::InvalidLength;
@@ -60,7 +61,7 @@ where
/// // Prepare for the next derivation
/// let next_material = material.next();
/// ```
#[derive(ZeroizeOnDrop)]
#[derive(ZeroizeOnDrop, Serialize, Deserialize)]
pub struct DerivationMaterial {
master_key: [u8; 32],
index: u32,
+46 -6
View File
@@ -6,6 +6,7 @@ use log::{info, warn};
use nym_bin_common::bin_info;
use nym_client_core::config::ForgetMe;
use nym_crypto::asymmetric::ed25519::PrivateKey;
use nym_crypto::hkdf::DerivationMaterial;
use nym_network_defaults::setup_env;
use nym_network_defaults::var_names::NYM_API;
use nym_sdk::mixnet::{self, MixnetClient};
@@ -13,6 +14,7 @@ use nym_sphinx::chunking::monitoring;
use nym_topology::{HardcodedTopologyProvider, NymTopology, NymTopologyMetadata};
use std::fs::File;
use std::io::Write;
use std::path::PathBuf;
use std::sync::LazyLock;
use std::time::Duration;
use std::{
@@ -21,6 +23,7 @@ use std::{
str::FromStr,
sync::Arc,
};
use tokio::sync::Mutex;
use tokio::sync::OnceCell;
use tokio::{signal::ctrl_c, sync::RwLock};
use tokio_util::sync::CancellationToken;
@@ -45,6 +48,7 @@ async fn make_clients(
n_clients: usize,
lifetime: u64,
topology: NymTopology,
derivation_material: Option<Arc<Mutex<DerivationMaterial>>>,
) {
loop {
let spawned_clients = clients.read().await.len();
@@ -71,7 +75,12 @@ async fn make_clients(
}
}
info!("Spawning new client");
let client = match make_client(topology.clone()).await {
let client = match make_client(
topology.clone(),
derivation_material.as_ref().map(Arc::clone),
)
.await
{
Ok(client) => client,
Err(err) => {
warn!("{}, moving on", err);
@@ -85,16 +94,29 @@ async fn make_clients(
}
}
async fn make_client(topology: NymTopology) -> Result<MixnetClient> {
/// Creates a new mixnet client, optionally using shared derivation material
/// for deterministic key generation. This allows multiple monitor clients
/// to derive keys from the same source while maintaining thread safety.
async fn make_client(
topology: NymTopology,
derivation_material: Option<Arc<Mutex<DerivationMaterial>>>,
) -> Result<MixnetClient> {
let net = mixnet::NymNetworkDetails::new_from_env();
let topology_provider = Box::new(HardcodedTopologyProvider::new(topology));
let mixnet_client = mixnet::MixnetClientBuilder::new_ephemeral()
let mut mixnet_client = mixnet::MixnetClientBuilder::new_ephemeral()
.network_details(net)
.custom_topology_provider(topology_provider)
.debug_config(mixnet_debug_config(0))
.with_forget_me(ForgetMe::new_all())
// .enable_credentials_mode()
.build()?;
.with_forget_me(ForgetMe::new_all());
// Configure the client with shared derivation material if available
// This ensures all monitor clients use the same key derivation source
if let Some(derivation_material) = derivation_material {
mixnet_client =
mixnet_client.with_shared_derivation_material(Arc::clone(&derivation_material));
}
let mixnet_client = mixnet_client.build()?;
let client = mixnet_client.connect_to_mixnet().await?;
Ok(client)
@@ -138,6 +160,12 @@ struct Args {
#[arg(long, env = "DATABASE_URL")]
database_url: Option<String>,
/// Path to a JSON file containing serialized DerivationMaterial for deterministic
/// client key generation. When provided, all monitor clients will derive keys
/// from this shared material instead of generating random keys.
#[arg(long, env = "DERIVATION_MATERIAL_PATH")]
derivation_material_path: Option<PathBuf>,
}
fn generate_key_pair() -> Result<()> {
@@ -214,12 +242,24 @@ async fn main() -> Result<()> {
MIXNET_TIMEOUT.set(args.mixnet_timeout).ok();
// Load shared derivation material from file if provided
// This enables deterministic key generation across all monitor clients
let derivation_material = args
.derivation_material_path
.map(|derivation_material_path| {
let file = File::open(derivation_material_path)?;
let derivation_material: DerivationMaterial = serde_json::from_reader(file)?;
Ok::<_, anyhow::Error>(Arc::new(Mutex::new(derivation_material)))
})
.transpose()?;
let spawn_clients = Arc::clone(&clients);
tokio::spawn(make_clients(
spawn_clients,
args.n_clients,
args.client_lifetime,
TOPOLOGY.get().expect("Topology not set yet!").clone(),
derivation_material,
));
let clients_server = clients.clone();
+28 -1
View File
@@ -39,6 +39,7 @@ use std::path::Path;
use std::path::PathBuf;
#[cfg(unix)]
use std::sync::Arc;
use tokio::sync::Mutex;
use url::Url;
use zeroize::Zeroizing;
@@ -67,6 +68,9 @@ pub struct MixnetClientBuilder<S: MixnetClientStorage = Ephemeral> {
forget_me: ForgetMe,
remember_me: RememberMe,
derivation_material: Option<DerivationMaterial>,
// Shared derivation material for thread-safe access across multiple clients
// Wrapped in Arc<Mutex<>> to allow concurrent access while maintaining safety
shared_derivation_material: Option<Arc<Mutex<DerivationMaterial>>>,
}
impl MixnetClientBuilder<Ephemeral> {
@@ -106,6 +110,7 @@ impl MixnetClientBuilder<OnDiskPersistent> {
forget_me: Default::default(),
remember_me: Default::default(),
derivation_material: None,
shared_derivation_material: None,
})
}
}
@@ -140,6 +145,7 @@ where
forget_me: Default::default(),
remember_me: Default::default(),
derivation_material: None,
shared_derivation_material: None,
}
}
@@ -163,6 +169,7 @@ where
forget_me: self.forget_me,
remember_me: self.remember_me,
derivation_material: self.derivation_material,
shared_derivation_material: self.shared_derivation_material,
}
}
@@ -172,6 +179,18 @@ where
self
}
/// Set shared derivation material for deterministic key generation across multiple clients.
/// This allows multiple client instances to derive keys from the same source material
/// while ensuring thread-safe access through Arc<Mutex<>>.
#[must_use]
pub fn with_shared_derivation_material(
mut self,
derivation_material: Arc<Mutex<DerivationMaterial>>,
) -> Self {
self.shared_derivation_material = Some(derivation_material);
self
}
/// Change the underlying storage of this builder to use default implementation of on-disk disk_persistence.
#[must_use]
pub fn set_default_storage(
@@ -335,6 +354,7 @@ where
client.forget_me = self.forget_me;
client.remember_me = self.remember_me;
client.derivation_material = self.derivation_material;
client.shared_derivation_material = self.shared_derivation_material;
Ok(client)
}
}
@@ -394,6 +414,11 @@ where
/// The derivation material to use for the client keys, its up to the caller to save this for rederivation later
derivation_material: Option<DerivationMaterial>,
/// Shared derivation material that can be safely accessed across multiple threads/clients.
/// This is useful when multiple clients need to derive keys from the same source while
/// maintaining thread safety through Arc<Mutex<>> wrapping.
shared_derivation_material: Option<Arc<Mutex<DerivationMaterial>>>,
}
impl<S> DisconnectedMixnetClient<S>
@@ -451,6 +476,7 @@ where
forget_me,
remember_me,
derivation_material: None,
shared_derivation_material: None,
})
}
@@ -678,7 +704,8 @@ where
.with_wait_for_gateway(self.wait_for_gateway)
.with_forget_me(&self.forget_me)
.with_remember_me(&self.remember_me)
.with_derivation_material(self.derivation_material);
.with_derivation_material(self.derivation_material)
.with_shared_derivation_material(self.shared_derivation_material);
if let Some(user_agent) = self.user_agent {
base_builder = base_builder.with_user_agent(user_agent);