Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7712d15e79 | |||
| eb886c0860 |
@@ -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?;
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user