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::path::Path;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tokio::sync::mpsc::Sender;
|
use tokio::sync::mpsc::Sender;
|
||||||
|
use tokio::sync::Mutex;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
#[cfg(all(
|
#[cfg(all(
|
||||||
@@ -195,6 +196,10 @@ pub struct BaseClientBuilder<C, S: MixnetClientStorage> {
|
|||||||
connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
|
connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
|
||||||
|
|
||||||
derivation_material: Option<DerivationMaterial>,
|
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>
|
impl<C, S> BaseClientBuilder<C, S>
|
||||||
@@ -220,6 +225,7 @@ where
|
|||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
connection_fd_callback: None,
|
connection_fd_callback: None,
|
||||||
derivation_material: None,
|
derivation_material: None,
|
||||||
|
shared_derivation_material: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -232,6 +238,18 @@ where
|
|||||||
self
|
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]
|
#[must_use]
|
||||||
pub fn with_forget_me(mut self, forget_me: &ForgetMe) -> Self {
|
pub fn with_forget_me(mut self, forget_me: &ForgetMe) -> Self {
|
||||||
self.config.debug.forget_me = *forget_me;
|
self.config.debug.forget_me = *forget_me;
|
||||||
@@ -704,6 +722,7 @@ where
|
|||||||
key_store: &S::KeyStore,
|
key_store: &S::KeyStore,
|
||||||
details_store: &S::GatewaysDetailsStore,
|
details_store: &S::GatewaysDetailsStore,
|
||||||
derivation_material: Option<DerivationMaterial>,
|
derivation_material: Option<DerivationMaterial>,
|
||||||
|
shared_derivation_material: Option<Arc<Mutex<DerivationMaterial>>>,
|
||||||
) -> Result<InitialisationResult, ClientCoreError>
|
) -> Result<InitialisationResult, ClientCoreError>
|
||||||
where
|
where
|
||||||
<S::KeyStore as KeyStore>::StorageError: Sync + Send,
|
<S::KeyStore as KeyStore>::StorageError: Sync + Send,
|
||||||
@@ -713,12 +732,24 @@ where
|
|||||||
if key_store.load_keys().await.is_err() {
|
if key_store.load_keys().await.is_err() {
|
||||||
info!("could not find valid client keys - a new set will be generated");
|
info!("could not find valid client keys - a new set will be generated");
|
||||||
let mut rng = OsRng;
|
let mut rng = OsRng;
|
||||||
let keys = if let Some(derivation_material) = derivation_material {
|
|
||||||
ClientKeys::from_master_key(&mut rng, &derivation_material)
|
// Key generation priority: individual derivation material > shared derivation material > random generation
|
||||||
.map_err(|_| ClientCoreError::HkdfDerivationError {})?
|
let keys = match (derivation_material, shared_derivation_material) {
|
||||||
} else {
|
// Individual derivation material takes precedence if provided
|
||||||
ClientKeys::generate_new(&mut rng)
|
(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?;
|
store_client_keys(keys, key_store).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -741,6 +772,7 @@ where
|
|||||||
self.client_store.key_store(),
|
self.client_store.key_store(),
|
||||||
self.client_store.gateway_details_store(),
|
self.client_store.gateway_details_store(),
|
||||||
self.derivation_material,
|
self.derivation_material,
|
||||||
|
self.shared_derivation_material,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ use hkdf::{
|
|||||||
},
|
},
|
||||||
Hkdf,
|
Hkdf,
|
||||||
};
|
};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use sha2::{Sha256, Sha512};
|
use sha2::{Sha256, Sha512};
|
||||||
|
|
||||||
pub use hkdf::InvalidLength;
|
pub use hkdf::InvalidLength;
|
||||||
@@ -60,7 +61,7 @@ where
|
|||||||
/// // Prepare for the next derivation
|
/// // Prepare for the next derivation
|
||||||
/// let next_material = material.next();
|
/// let next_material = material.next();
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(ZeroizeOnDrop)]
|
#[derive(ZeroizeOnDrop, Serialize, Deserialize)]
|
||||||
pub struct DerivationMaterial {
|
pub struct DerivationMaterial {
|
||||||
master_key: [u8; 32],
|
master_key: [u8; 32],
|
||||||
index: u32,
|
index: u32,
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use log::{info, warn};
|
|||||||
use nym_bin_common::bin_info;
|
use nym_bin_common::bin_info;
|
||||||
use nym_client_core::config::ForgetMe;
|
use nym_client_core::config::ForgetMe;
|
||||||
use nym_crypto::asymmetric::ed25519::PrivateKey;
|
use nym_crypto::asymmetric::ed25519::PrivateKey;
|
||||||
|
use nym_crypto::hkdf::DerivationMaterial;
|
||||||
use nym_network_defaults::setup_env;
|
use nym_network_defaults::setup_env;
|
||||||
use nym_network_defaults::var_names::NYM_API;
|
use nym_network_defaults::var_names::NYM_API;
|
||||||
use nym_sdk::mixnet::{self, MixnetClient};
|
use nym_sdk::mixnet::{self, MixnetClient};
|
||||||
@@ -13,6 +14,7 @@ use nym_sphinx::chunking::monitoring;
|
|||||||
use nym_topology::{HardcodedTopologyProvider, NymTopology, NymTopologyMetadata};
|
use nym_topology::{HardcodedTopologyProvider, NymTopology, NymTopologyMetadata};
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
use std::path::PathBuf;
|
||||||
use std::sync::LazyLock;
|
use std::sync::LazyLock;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use std::{
|
use std::{
|
||||||
@@ -21,6 +23,7 @@ use std::{
|
|||||||
str::FromStr,
|
str::FromStr,
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
|
use tokio::sync::Mutex;
|
||||||
use tokio::sync::OnceCell;
|
use tokio::sync::OnceCell;
|
||||||
use tokio::{signal::ctrl_c, sync::RwLock};
|
use tokio::{signal::ctrl_c, sync::RwLock};
|
||||||
use tokio_util::sync::CancellationToken;
|
use tokio_util::sync::CancellationToken;
|
||||||
@@ -45,6 +48,7 @@ async fn make_clients(
|
|||||||
n_clients: usize,
|
n_clients: usize,
|
||||||
lifetime: u64,
|
lifetime: u64,
|
||||||
topology: NymTopology,
|
topology: NymTopology,
|
||||||
|
derivation_material: Option<Arc<Mutex<DerivationMaterial>>>,
|
||||||
) {
|
) {
|
||||||
loop {
|
loop {
|
||||||
let spawned_clients = clients.read().await.len();
|
let spawned_clients = clients.read().await.len();
|
||||||
@@ -71,7 +75,12 @@ async fn make_clients(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
info!("Spawning new client");
|
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,
|
Ok(client) => client,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
warn!("{}, moving on", 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 net = mixnet::NymNetworkDetails::new_from_env();
|
||||||
let topology_provider = Box::new(HardcodedTopologyProvider::new(topology));
|
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)
|
.network_details(net)
|
||||||
.custom_topology_provider(topology_provider)
|
.custom_topology_provider(topology_provider)
|
||||||
.debug_config(mixnet_debug_config(0))
|
.debug_config(mixnet_debug_config(0))
|
||||||
.with_forget_me(ForgetMe::new_all())
|
.with_forget_me(ForgetMe::new_all());
|
||||||
// .enable_credentials_mode()
|
|
||||||
.build()?;
|
// 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?;
|
let client = mixnet_client.connect_to_mixnet().await?;
|
||||||
Ok(client)
|
Ok(client)
|
||||||
@@ -138,6 +160,12 @@ struct Args {
|
|||||||
|
|
||||||
#[arg(long, env = "DATABASE_URL")]
|
#[arg(long, env = "DATABASE_URL")]
|
||||||
database_url: Option<String>,
|
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<()> {
|
fn generate_key_pair() -> Result<()> {
|
||||||
@@ -214,12 +242,24 @@ async fn main() -> Result<()> {
|
|||||||
|
|
||||||
MIXNET_TIMEOUT.set(args.mixnet_timeout).ok();
|
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);
|
let spawn_clients = Arc::clone(&clients);
|
||||||
tokio::spawn(make_clients(
|
tokio::spawn(make_clients(
|
||||||
spawn_clients,
|
spawn_clients,
|
||||||
args.n_clients,
|
args.n_clients,
|
||||||
args.client_lifetime,
|
args.client_lifetime,
|
||||||
TOPOLOGY.get().expect("Topology not set yet!").clone(),
|
TOPOLOGY.get().expect("Topology not set yet!").clone(),
|
||||||
|
derivation_material,
|
||||||
));
|
));
|
||||||
|
|
||||||
let clients_server = clients.clone();
|
let clients_server = clients.clone();
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ use std::path::Path;
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use tokio::sync::Mutex;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
use zeroize::Zeroizing;
|
use zeroize::Zeroizing;
|
||||||
|
|
||||||
@@ -67,6 +68,9 @@ pub struct MixnetClientBuilder<S: MixnetClientStorage = Ephemeral> {
|
|||||||
forget_me: ForgetMe,
|
forget_me: ForgetMe,
|
||||||
remember_me: RememberMe,
|
remember_me: RememberMe,
|
||||||
derivation_material: Option<DerivationMaterial>,
|
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> {
|
impl MixnetClientBuilder<Ephemeral> {
|
||||||
@@ -106,6 +110,7 @@ impl MixnetClientBuilder<OnDiskPersistent> {
|
|||||||
forget_me: Default::default(),
|
forget_me: Default::default(),
|
||||||
remember_me: Default::default(),
|
remember_me: Default::default(),
|
||||||
derivation_material: None,
|
derivation_material: None,
|
||||||
|
shared_derivation_material: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -140,6 +145,7 @@ where
|
|||||||
forget_me: Default::default(),
|
forget_me: Default::default(),
|
||||||
remember_me: Default::default(),
|
remember_me: Default::default(),
|
||||||
derivation_material: None,
|
derivation_material: None,
|
||||||
|
shared_derivation_material: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -163,6 +169,7 @@ where
|
|||||||
forget_me: self.forget_me,
|
forget_me: self.forget_me,
|
||||||
remember_me: self.remember_me,
|
remember_me: self.remember_me,
|
||||||
derivation_material: self.derivation_material,
|
derivation_material: self.derivation_material,
|
||||||
|
shared_derivation_material: self.shared_derivation_material,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,6 +179,18 @@ where
|
|||||||
self
|
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.
|
/// Change the underlying storage of this builder to use default implementation of on-disk disk_persistence.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn set_default_storage(
|
pub fn set_default_storage(
|
||||||
@@ -335,6 +354,7 @@ where
|
|||||||
client.forget_me = self.forget_me;
|
client.forget_me = self.forget_me;
|
||||||
client.remember_me = self.remember_me;
|
client.remember_me = self.remember_me;
|
||||||
client.derivation_material = self.derivation_material;
|
client.derivation_material = self.derivation_material;
|
||||||
|
client.shared_derivation_material = self.shared_derivation_material;
|
||||||
Ok(client)
|
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
|
/// The derivation material to use for the client keys, its up to the caller to save this for rederivation later
|
||||||
derivation_material: Option<DerivationMaterial>,
|
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>
|
impl<S> DisconnectedMixnetClient<S>
|
||||||
@@ -451,6 +476,7 @@ where
|
|||||||
forget_me,
|
forget_me,
|
||||||
remember_me,
|
remember_me,
|
||||||
derivation_material: None,
|
derivation_material: None,
|
||||||
|
shared_derivation_material: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -678,7 +704,8 @@ where
|
|||||||
.with_wait_for_gateway(self.wait_for_gateway)
|
.with_wait_for_gateway(self.wait_for_gateway)
|
||||||
.with_forget_me(&self.forget_me)
|
.with_forget_me(&self.forget_me)
|
||||||
.with_remember_me(&self.remember_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 {
|
if let Some(user_agent) = self.user_agent {
|
||||||
base_builder = base_builder.with_user_agent(user_agent);
|
base_builder = base_builder.with_user_agent(user_agent);
|
||||||
|
|||||||
Reference in New Issue
Block a user