Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7712d15e79 | |||
| eb886c0860 | |||
| d6b3d7fc0a | |||
| ac273480f8 |
@@ -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?;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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