Skip to content

[Feature] Noise XKpsk3 integration (2025 version) #5692

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 21 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
d21eff3
add noise lib
simonwicky May 20, 2025
6cf8d79
adding proper noise key announcing on nodes
simonwicky May 20, 2025
eabad28
add semi-skimmed endpoint to distribute noise key
simonwicky May 20, 2025
ddf7c3b
noise handshake common
simonwicky May 20, 2025
1ee8a5a
noise handshake responder side
simonwicky May 20, 2025
8f950ee
noise handshake initiator side
simonwicky May 20, 2025
690a189
enable noise by announcing keys
simonwicky May 20, 2025
bfa52f7
fix wasm client by conditionnally import mixnet client in client-core
simonwicky May 20, 2025
cb17314
additional Polish; missing features, extra test, etc
jstuczyn May 20, 2025
c6675bb
some comments and minor improvements for future versions
simonwicky May 20, 2025
cc28575
appease the clippy god
simonwicky May 20, 2025
7e2e122
appease the clippy god
simonwicky May 20, 2025
162b742
resolve non stream-related PR comments
simonwicky May 20, 2025
cc4dcff
fix asyncread and asyncwrite op following PR comment
simonwicky May 20, 2025
cd61e7c
add active_only option for semi-skimmed node build_response
simonwicky May 20, 2025
32fc3c1
improve noisestream creation and test noisepatterns
simonwicky May 20, 2025
7c10417
restore start_send use
simonwicky May 20, 2025
4188c9f
change buffer allocation method and use connection timeout
simonwicky May 20, 2025
48045f7
add multiple output for semi-skimmed endpoint
simonwicky May 20, 2025
a66b2ae
Bump ns-api version
benedettadavico May 20, 2025
8125c0a
backwards compatibility for mixnodes announced keys
simonwicky May 21, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 7 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ members = [
"common/commands",
"common/config",
"common/cosmwasm-smart-contracts/coconut-dkg",
"common/cosmwasm-smart-contracts/contracts-common", "common/cosmwasm-smart-contracts/easy_addr",
"common/cosmwasm-smart-contracts/contracts-common",
"common/cosmwasm-smart-contracts/easy_addr",
"common/cosmwasm-smart-contracts/ecash-contract",
"common/cosmwasm-smart-contracts/group-contract",
"common/cosmwasm-smart-contracts/mixnet-contract",
Expand Down Expand Up @@ -64,6 +65,8 @@ members = [
"common/nym-id",
"common/nym-metrics",
"common/nym_offline_compact_ecash",
"common/nymnoise",
"common/nymnoise/keys",
"common/nymsphinx",
"common/nymsphinx/acknowledgements",
"common/nymsphinx/addressing",
Expand Down Expand Up @@ -131,7 +134,8 @@ members = [
"tools/internal/testnet-manager",
"tools/internal/testnet-manager",
"tools/internal/testnet-manager/dkg-bypass-contract",
"tools/internal/testnet-manager/dkg-bypass-contract", "tools/internal/validator-status-check",
"tools/internal/testnet-manager/dkg-bypass-contract",
"tools/internal/validator-status-check",
"tools/nym-cli",
"tools/nym-id-cli",
"tools/nym-nr-query",
Expand Down Expand Up @@ -305,6 +309,7 @@ serde_with = "3.9.0"
serde_yaml = "0.9.25"
sha2 = "0.10.9"
si-scale = "0.2.3"
snow = "0.9.6"
sphinx-packet = "=0.6.0"
sqlx = "0.7.4"
strum = "0.26"
Expand Down
4 changes: 3 additions & 1 deletion common/client-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ nym-sphinx = { path = "../nymsphinx" }
nym-statistics-common = { path = "../statistics" }
nym-pemstore = { path = "../pemstore" }
nym-topology = { path = "../topology", features = ["persistence"] }
nym-mixnet-client = { path = "../client-libs/mixnet-client", default-features = false }
nym-validator-client = { path = "../client-libs/validator-client", default-features = false }
nym-task = { path = "../task" }
nym-credentials-interface = { path = "../credentials-interface" }
Expand All @@ -57,6 +56,9 @@ nym-client-core-surb-storage = { path = "./surb-storage" }
nym-client-core-gateways-storage = { path = "./gateways-storage" }
nym-ecash-time = { path = "../ecash-time" }

[target."cfg(not(target_arch = \"wasm32\"))".dependencies]
nym-mixnet-client = { path = "../client-libs/mixnet-client", default-features = false }

### For serving prometheus metrics
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.hyper]
workspace = true
Expand Down
7 changes: 6 additions & 1 deletion common/client-libs/mixnet-client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,14 @@ tokio-util = { workspace = true, features = ["codec"], optional = true }
tokio-stream = { workspace = true }

# internal
nym-noise = { path = "../../nymnoise" }
nym-sphinx = { path = "../../nymsphinx" }
nym-task = { path = "../../task", optional = true }

[features]
default = ["client"]
client = ["tokio-util", "nym-task", "tokio/net", "tokio/rt"]
client = ["tokio-util", "nym-task", "tokio/net", "tokio/rt"]

[dev-dependencies]
nym-crypto = { path = "../../crypto" }
rand = { workspace = true }
40 changes: 37 additions & 3 deletions common/client-libs/mixnet-client/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

use dashmap::DashMap;
use futures::StreamExt;
use nym_noise::config::NoiseConfig;
use nym_noise::upgrade_noise_initiator;
use nym_sphinx::addressing::nodes::NymNodeRoutingAddress;
use nym_sphinx::framing::codec::NymCodec;
use nym_sphinx::framing::packet::FramedNymPacket;
Expand Down Expand Up @@ -59,6 +61,7 @@ pub trait SendWithoutResponse {

pub struct Client {
active_connections: ActiveConnections,
noise_config: NoiseConfig,
connections_count: Arc<AtomicUsize>,
config: Config,
}
Expand Down Expand Up @@ -104,6 +107,7 @@ impl ConnectionSender {

struct ManagedConnection {
address: SocketAddr,
noise_config: NoiseConfig,
message_receiver: ReceiverStream<FramedNymPacket>,
connection_timeout: Duration,
current_reconnection: Arc<AtomicU32>,
Expand All @@ -112,12 +116,14 @@ struct ManagedConnection {
impl ManagedConnection {
fn new(
address: SocketAddr,
noise_config: NoiseConfig,
message_receiver: mpsc::Receiver<FramedNymPacket>,
connection_timeout: Duration,
current_reconnection: Arc<AtomicU32>,
) -> Self {
ManagedConnection {
address,
noise_config,
message_receiver: ReceiverStream::new(message_receiver),
connection_timeout,
current_reconnection,
Expand All @@ -132,9 +138,21 @@ impl ManagedConnection {
Ok(stream_res) => match stream_res {
Ok(stream) => {
debug!("Managed to establish connection to {}", self.address);
// if we managed to connect, reset the reconnection count (whatever it might have been)

let noise_stream =
match upgrade_noise_initiator(stream, &self.noise_config).await {
Ok(noise_stream) => noise_stream,
Err(err) => {
error!("Failed to perform Noise handshake with {address} - {err}");
// we failed to finish the noise handshake - increase reconnection attempt
self.current_reconnection.fetch_add(1, Ordering::SeqCst);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wouldn't we end up in a constant reconnection loop if receiver doesn't support noise?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error message is a bit misleading tbh, my bad.
If the receiver doesn't support Noise, there won't be any handshake, and we will just return the TcpStream wrapped in a Connection for compatibility

return;
}
};
// if we managed to connect AND do the noise handshake, reset the reconnection count (whatever it might have been)
self.current_reconnection.store(0, Ordering::Release);
Framed::new(stream, NymCodec)
debug!("Noise initiator handshake completed for {:?}", address);
Framed::new(noise_stream, NymCodec)
}
Err(err) => {
debug!("failed to establish connection to {address} (err: {err})",);
Expand Down Expand Up @@ -167,9 +185,14 @@ impl ManagedConnection {
}

impl Client {
pub fn new(config: Config, connections_count: Arc<AtomicUsize>) -> Client {
pub fn new(
config: Config,
noise_config: NoiseConfig,
connections_count: Arc<AtomicUsize>,
) -> Client {
Client {
active_connections: Default::default(),
noise_config,
connections_count,
config,
}
Expand Down Expand Up @@ -224,6 +247,7 @@ impl Client {
let initial_connection_timeout = self.config.initial_connection_timeout;

let connections_count = self.connections_count.clone();
let noise_config = self.noise_config.clone();
tokio::spawn(async move {
// before executing the manager, wait for what was specified, if anything
if let Some(backoff) = backoff {
Expand All @@ -234,6 +258,7 @@ impl Client {
connections_count.fetch_add(1, Ordering::SeqCst);
ManagedConnection::new(
address.into(),
noise_config,
receiver,
initial_connection_timeout,
current_reconnection_attempt,
Expand Down Expand Up @@ -302,15 +327,24 @@ impl SendWithoutResponse for Client {
#[cfg(test)]
mod tests {
use super::*;
use nym_crypto::asymmetric::x25519;
use nym_noise::config::NoiseNetworkView;
use rand::rngs::OsRng;

fn dummy_client() -> Client {
let mut rng = OsRng; //for test only, so we don't care if rng source isn't crypto grade
Client::new(
Config {
initial_reconnection_backoff: Duration::from_millis(10_000),
maximum_reconnection_backoff: Duration::from_millis(300_000),
initial_connection_timeout: Duration::from_millis(1_500),
maximum_connection_buffer_size: 128,
},
NoiseConfig::new(
Arc::new(x25519::KeyPair::new(&mut rng)),
NoiseNetworkView::new_empty(),
Duration::from_millis(1_500),
),
Default::default(),
)
}
Expand Down
27 changes: 26 additions & 1 deletion common/client-libs/validator-client/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ use nym_api_requests::models::{
NymNodeDescription, RewardEstimationResponse, StakeSaturationResponse,
};
use nym_api_requests::models::{LegacyDescribedGateway, MixNodeBondAnnotated};
use nym_api_requests::nym_nodes::{NodesByAddressesResponse, SkimmedNode};
use nym_api_requests::nym_nodes::{NodesByAddressesResponse, SemiSkimmedNode, SkimmedNode};
use nym_coconut_dkg_common::types::EpochId;
use nym_http_api_client::UserAgent;
use nym_mixnet_contract_common::EpochRewardedSet;
Expand Down Expand Up @@ -524,6 +524,31 @@ impl NymApiClient {
Ok(nodes)
}

/// retrieve expanded information for all bonded nodes on the network
pub async fn get_all_expanded_nodes(
&self,
) -> Result<Vec<SemiSkimmedNode>, ValidatorClientError> {
// TODO: deal with paging in macro or some helper function or something, because it's the same pattern everywhere
let mut page = 0;
let mut nodes = Vec::new();

loop {
let mut res = self
.nym_api
.get_expanded_nodes(false, Some(page), None)
.await?;

nodes.append(&mut res.nodes.data);
if nodes.len() < res.nodes.pagination.total {
page += 1
} else {
break;
}
}

Ok(nodes)
}

pub async fn health(&self) -> Result<ApiHealthResponse, ValidatorClientError> {
Ok(self.nym_api.health().await?)
}
Expand Down
34 changes: 34 additions & 0 deletions common/client-libs/validator-client/src/nym_api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ use nym_api_requests::models::{
};
use nym_api_requests::nym_nodes::{
NodesByAddressesRequestBody, NodesByAddressesResponse, PaginatedCachedNodesResponse,
SemiSkimmedNode,
};
use nym_api_requests::pagination::PaginatedResponse;
pub use nym_api_requests::{
Expand Down Expand Up @@ -474,6 +475,39 @@ pub trait NymApiClientExt: ApiClient {
.await
}

#[instrument(level = "debug", skip(self))]
async fn get_expanded_nodes(
&self,
no_legacy: bool,
page: Option<u32>,
per_page: Option<u32>,
) -> Result<PaginatedCachedNodesResponse<SemiSkimmedNode>, NymAPIError> {
let mut params = Vec::new();

if no_legacy {
params.push(("no_legacy", "true".to_string()))
}

if let Some(page) = page {
params.push(("page", page.to_string()))
}

if let Some(per_page) = per_page {
params.push(("per_page", per_page.to_string()))
}

self.get_json(
&[
routes::API_VERSION,
"unstable",
routes::NYM_NODES_ROUTES,
"semi-skimmed",
],
&params,
)
.await
}

#[deprecated]
#[instrument(level = "debug", skip(self))]
async fn get_active_mixnodes(&self) -> Result<Vec<MixNodeDetails>, NymAPIError> {
Expand Down
4 changes: 4 additions & 0 deletions common/crypto/src/asymmetric/x25519/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,10 @@ impl PrivateKey {
self.0.to_bytes()
}

pub fn as_bytes(&self) -> &[u8; PRIVATE_KEY_SIZE] {
self.0.as_bytes()
}

pub fn from_bytes(b: &[u8]) -> Result<Self, KeyRecoveryError> {
if b.len() != PRIVATE_KEY_SIZE {
return Err(KeyRecoveryError::InvalidSizePrivateKey {
Expand Down
Loading
Loading