Compare commits

...

4 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
import this d6b3d7fc0a [DOCs/operators]: Release notes for v2025.11 cheddar (#5852)
* bump up version

* add dev features

* add operator updates

* add updated stats

* update prebuild
2025-06-12 11:19:00 +00:00
dynco-nym ac273480f8 Fix CI version check (#5851)
* Fix version

* Test .rc version

* Undo cargo.toml version

* Remove comment

* Apply to statistics service
2025-06-12 11:17:56 +02:00
9 changed files with 174 additions and 23 deletions
@@ -44,8 +44,10 @@ jobs:
echo "Tag is empty"
exit 1
fi
# first, list all tags for logging purposes
curl -su ${{ secrets.HARBOR_ROBOT_USERNAME }}:${{ secrets.HARBOR_ROBOT_SECRET }} "$registry/v2/$repo_name/tags/list" | jq
exists=$(curl -su ${{ secrets.HARBOR_ROBOT_USERNAME }}:${{ secrets.HARBOR_ROBOT_SECRET }} "$registry/v2/$repo_name/tags/list" | jq --arg tag $TAG '.tags | contains([$tag])' )
# check if there's a matching tag
exists=$(curl -su ${{ secrets.HARBOR_ROBOT_USERNAME }}:${{ secrets.HARBOR_ROBOT_SECRET }} "$registry/v2/$repo_name/tags/list" | jq -r --arg tag "$TAG" 'any(.tags[]; . == $tag)' )
if [[ $exists = "true" ]]; then
echo "Version '$TAG' defined in Cargo.toml ALREADY EXISTS as tag in harbor repo"
exit 1
@@ -53,5 +55,5 @@ jobs:
echo "Version '$TAG' doesn't exist on the remote"
else
echo "Unknown output '$exists'"
exit 1
exit 2
fi
@@ -44,8 +44,10 @@ jobs:
echo "Tag is empty"
exit 1
fi
# first, list all tags for logging purposes
curl -su ${{ secrets.HARBOR_ROBOT_USERNAME }}:${{ secrets.HARBOR_ROBOT_SECRET }} "$registry/v2/$repo_name/tags/list" | jq
exists=$(curl -su ${{ secrets.HARBOR_ROBOT_USERNAME }}:${{ secrets.HARBOR_ROBOT_SECRET }} "$registry/v2/$repo_name/tags/list" | jq --arg tag $TAG '.tags | contains([$tag])' )
# check if there's a matching tag
exists=$(curl -su ${{ secrets.HARBOR_ROBOT_USERNAME }}:${{ secrets.HARBOR_ROBOT_SECRET }} "$registry/v2/$repo_name/tags/list" | jq -r --arg tag "$TAG" 'any(.tags[]; . == $tag)' )
if [[ $exists = "true" ]]; then
echo "Version '$TAG' defined in Cargo.toml ALREADY EXISTS as tag in harbor repo"
exit 1
@@ -53,5 +55,5 @@ jobs:
echo "Version '$TAG' doesn't exist on the remote"
else
echo "Unknown output '$exists'"
exit 1
exit 2
fi
@@ -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,
@@ -1 +1 @@
Friday, June 6th 2025, 09:33:10 UTC
Thursday, June 12th 2025, 11:03:30 UTC
@@ -47,6 +47,53 @@ This page displays a full list of all the changes during our release cycle from
<VarInfo />
## `v2025.11-cheddar`
- [Release Binaries](https://github.com/nymtech/nym/releases/tag/nym-binaries-v2025.11-cheddar)
- [`nym-node`](nodes/nym-node.mdx) version `1.13.0`
```sh
nym-node
Binary Name: nym-node
Build Timestamp: 2025-06-10T09:41:31.291089877Z
Build Version: 1.13.0
Commit SHA: ef220882d4bcf0a8a703ea62f9273e017c9ebb51
Commit Date: 2025-06-10T11:39:20.000000000+02:00
Commit Branch: HEAD
rustc Version: 1.86.0
rustc Channel: stable
cargo Profile: release
```
### Operators Updates & Tools
- **Nym Improvement Proposals**: NIPs are being introduced, allowing us to propose changes to the Nym network and ecosystem, and decentralise governance
- **NIP1** will outline the governance process: Its draft will be published soon for discussion
- **NIP2** will be about reducing the stake saturation point variable, which would make reward distribution across the network more equitable
- **Join the Operator AMA next Tuesday** for an in-depth overview of what this all means (feat Nym Chief Scientist Claudia Diaz)
- Keep an eye on our channels for information about how to participate in the upcoming voting (no need for forum registration, we are building something new!)
### Features
- [Track wireguard credential retries](https://github.com/nymtech/nym/pull/5783)
- [Swap a decode into a `fromrow` to please future `postgres` feature](https://github.com/nymtech/nym/pull/5785): This PR change a sqlx derive from `Decode` to `FromRow` because a future PR will bring the `postgres` feature and it doesn't like that `Decode` there
- [Fix contains ticketbook function that always returned true](https://github.com/nymtech/nym/pull/5787)
- [QoL: `RequestPath` trait for `http-api-client`](https://github.com/nymtech/nym/pull/5788): Those `PathSegments` in `ApiClient` weren't very user-friendly. Instead we've defined a `RequestPath` trait that allows you to also pass a good old `&str` or `String` and internally it does exactly the same sanitization as before. `PathSegments` are also fully supported to not break any existing compatibility.
- [Nym Statistics API](https://github.com/nymtech/nym/pull/5800):
- Types for `nym-vpn-client`: It introduces `VpnClientStatsReport` which will be used by nym vpn clients to report statistics. Those types are in the monorepo because they're used by the `num-statistics-API`
- Nym Statistics API: Basically a `REST API` with a `postgres` backend. In this first iteration, it only accepts `VpnClientStatsReport` and stores them in its database. It optionally connects to the Nym API to confirm reports are coming from the network and attach country code to them if it's the case (exit node country code).
- [Resolve 1.87 clippy warnings](https://github.com/nymtech/nym/pull/5802)
- [Adjusted wallet storybook mocks to fix the build](https://github.com/nymtech/nym/pull/5804)
- [Set cached storage counters to 0](https://github.com/nymtech/nym/pull/5812)
- [No autoremoval of peers](https://github.com/nymtech/nym/pull/5831)
## `v2025.10-brie`
- [Release Binaries](https://github.com/nymtech/nym/releases/tag/nym-binaries-v2025.10-brie)
@@ -18,12 +18,12 @@ This documentation page provides a guide on how to set up and run a [NYM NODE](.
## Current version
```sh
nym-node
ym-node
Binary Name: nym-node
Build Timestamp: 2025-05-27T10:13:18.511075453Z
Build Version: 1.12.0
Commit SHA: 1c6db86259d08d80e8bcfbc4fcc71ccb147fcfd0
Commit Date: 2025-05-27T12:11:13.000000000+02:00
Build Timestamp: 2025-06-10T09:41:31.291089877Z
Build Version: 1.13.0
Commit SHA: ef220882d4bcf0a8a703ea62f9273e017c9ebb51
Commit Date: 2025-06-10T11:39:20.000000000+02:00
Commit Branch: HEAD
rustc Version: 1.86.0
rustc Channel: stable
+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);