clippy and formatting issues

This commit is contained in:
Jędrzej Stuczyński
2026-02-26 11:40:17 +00:00
parent c93b056e36
commit f9727ce0a0
28 changed files with 535 additions and 1111 deletions
Generated
+3 -2
View File
@@ -5454,7 +5454,6 @@ dependencies = [
"nym-serde-helpers",
"nym-test-utils",
"nym-ticketbooks-merkle",
"rand_chacha 0.3.1",
"schemars 0.8.22",
"serde",
"serde_json",
@@ -6521,6 +6520,7 @@ dependencies = [
"nym-validator-client",
"pnet_packet",
"rand 0.8.5",
"rand 0.9.2",
"reqwest 0.13.1",
"serde",
"serde_json",
@@ -6825,7 +6825,6 @@ name = "nym-kkt"
version = "0.1.0"
dependencies = [
"anyhow",
"criterion",
"libcrux-chacha20poly1305",
"libcrux-ecdh",
"libcrux-kem",
@@ -6923,6 +6922,7 @@ dependencies = [
"nym-topology",
"nym-validator-client",
"rand 0.8.5",
"rand 0.9.2",
"rand_chacha 0.3.1",
"serde",
"serde_json",
@@ -7216,6 +7216,7 @@ dependencies = [
"nym-sphinx-types",
"nym-statistics-common",
"nym-task",
"nym-test-utils",
"nym-topology",
"nym-types",
"nym-validator-client",
@@ -14,7 +14,7 @@ pub struct Args {
}
pub async fn query(args: Args, client: &QueryClientWithNyxd) {
match client.get_all_cached_described_nodes().await {
match client.get_all_cached_described_nodes_v2().await {
Ok(res) => match args.identity_key {
Some(identity_key) => {
let node = res.iter().find(|node| {
@@ -14,7 +14,7 @@ pub struct Args {
}
pub async fn query(args: Args, client: &QueryClientWithNyxd) {
match client.get_all_cached_described_nodes().await {
match client.get_all_cached_described_nodes_v2().await {
Ok(res) => match args.identity_key {
Some(identity_key) => {
let node = res.iter().find(|node| {
-5
View File
@@ -30,12 +30,7 @@ libcrux-ml-kem = { workspace = true }
[dev-dependencies]
rand_chacha = "0.9.0"
anyhow = { workspace = true }
criterion = { workspace = true }
[[bench]]
name = "benches"
harness = false
[lints]
workspace = true
-413
View File
@@ -1,413 +0,0 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
// fine in benchmarking code
#![allow(clippy::expect_used)]
#![allow(clippy::unwrap_used)]
use criterion::{Criterion, criterion_group, criterion_main};
use nym_kkt::{
ciphersuite::{Ciphersuite, EncapsulationKey, HashFunction, KEM, SignatureScheme},
context::KKTMode,
frame::KKTFrame,
key_utils::{
generate_keypair_libcrux, generate_keypair_mceliece, generate_keypair_mlkem,
hash_encapsulation_key,
},
session::{
initiator_ingest_response, initiator_process, responder_ingest_message, responder_process,
},
};
use rand09::prelude::*;
pub fn gen_mlkem768_keypair(c: &mut Criterion) {
c.bench_function("Generate MlKem768 Keypair", |b| {
b.iter(|| {
libcrux_kem::key_gen(libcrux_kem::Algorithm::MlKem768, &mut rand09::rng()).unwrap()
});
});
}
pub fn kkt_benchmark(c: &mut Criterion) {
let mut rng = rand09::rng();
let mut secret_initiator: [u8; 32] = [0u8; 32];
rng.fill_bytes(&mut secret_initiator);
let mut secret_responder: [u8; 32] = [0u8; 32];
rng.fill_bytes(&mut secret_responder);
for kem in [KEM::MlKem768, KEM::XWing, KEM::X25519, KEM::McEliece] {
for hash_function in [
HashFunction::Blake3,
HashFunction::SHA256,
HashFunction::Shake128,
HashFunction::Shake256,
] {
let ciphersuite = Ciphersuite::resolve_ciphersuite(
kem,
hash_function,
SignatureScheme::Ed25519,
None,
)
.unwrap();
// generate kem public keys
let (responder_kem_public_key, initiator_kem_public_key) = match kem {
KEM::MlKem768 => (
EncapsulationKey::MlKem768(generate_keypair_mlkem(&mut rng).1),
EncapsulationKey::MlKem768(generate_keypair_mlkem(&mut rng).1),
),
KEM::XWing => (
EncapsulationKey::XWing(generate_keypair_libcrux(&mut rng, kem).unwrap().1),
EncapsulationKey::XWing(generate_keypair_libcrux(&mut rng, kem).unwrap().1),
),
KEM::X25519 => (
EncapsulationKey::X25519(generate_keypair_libcrux(&mut rng, kem).unwrap().1),
EncapsulationKey::X25519(generate_keypair_libcrux(&mut rng, kem).unwrap().1),
),
KEM::McEliece => (
EncapsulationKey::McEliece(generate_keypair_mceliece(&mut rng).1),
EncapsulationKey::McEliece(generate_keypair_mceliece(&mut rng).1),
),
};
let i_kem_key_bytes = initiator_kem_public_key.encode();
let r_kem_key_bytes = responder_kem_public_key.encode();
let i_dir_hash = hash_encapsulation_key(
&ciphersuite.hash_function(),
ciphersuite.hash_len(),
&i_kem_key_bytes,
);
let r_dir_hash = hash_encapsulation_key(
&ciphersuite.hash_function(),
ciphersuite.hash_len(),
&r_kem_key_bytes,
);
// Anonymous Initiator, OneWay
{
c.bench_function(
&format!("{kem}, {hash_function} | Anonymous Initiator: Generate Request",),
|b| {
b.iter(|| {
initiator_process(&mut rng, KKTMode::OneWay, ciphersuite, None).unwrap()
});
},
);
let (mut i_context, i_frame) =
initiator_process(&mut rng, KKTMode::OneWay, ciphersuite, None).unwrap();
c.bench_function(
&format!(
"{kem}, {hash_function} | Anonymous Initiator: Encode Frame - Request",
),
|b| b.iter(|| i_frame.to_bytes()),
);
let i_frame_bytes = i_frame.to_bytes();
c.bench_function(
&format!(
"{kem}, {hash_function} | Anonymous Initiator: Decode Frame - Request",
),
|b| b.iter(|| KKTFrame::from_bytes(&i_frame_bytes).unwrap()),
);
let (i_frame_r, r_context) = KKTFrame::from_bytes(&i_frame_bytes).unwrap();
c.bench_function(
&format!(
"{kem}, {hash_function} | Anonymous Initiator: Responder Ingest Frame",
),
|b| {
b.iter(|| responder_ingest_message(&r_context, None, &i_frame_r).unwrap());
},
);
let (mut r_context, _) =
responder_ingest_message(&r_context, None, &i_frame_r).unwrap();
c.bench_function(
&format!(
"{kem}, {hash_function} | Anonymous Initiator: Responder Generate Response",
),
|b| {
b.iter(|| {
responder_process(
&mut r_context,
i_frame_r.session_id(),
&responder_kem_public_key,
)
.unwrap()
});
},
);
let r_frame = responder_process(
&mut r_context,
i_frame_r.session_id(),
&responder_kem_public_key,
)
.unwrap();
c.bench_function(
&format!(
"{kem}, {hash_function} | Anonymous Initiator: Responder Encode Frame",
),
|b| b.iter(|| r_frame.to_bytes()),
);
c.bench_function(
&format!(
"{kem}, {hash_function} | Anonymous Initiator: Initiator Ingest Response",
),
|b| {
b.iter(|| {
initiator_ingest_response(
&mut i_context,
&r_frame,
&r_frame.context().unwrap(),
&r_dir_hash,
)
.unwrap()
});
},
);
let obtained_key = initiator_ingest_response(
&mut i_context,
&r_frame,
&r_frame.context().unwrap(),
&r_dir_hash,
)
.unwrap();
assert_eq!(obtained_key.encode(), r_kem_key_bytes)
}
// Initiator, OneWay
{
let (mut i_context, i_frame) =
initiator_process(&mut rng, KKTMode::OneWay, ciphersuite, None).unwrap();
c.bench_function(
&format!("{kem}, {hash_function} | Initiator OneWay: Generate Request",),
|b| {
b.iter(|| {
initiator_process(&mut rng, KKTMode::OneWay, ciphersuite, None).unwrap()
});
},
);
c.bench_function(
&format!("{kem}, {hash_function} | Initiator OneWay: Encode Frame - Request",),
|b| b.iter(|| i_frame.to_bytes()),
);
let i_frame_bytes = i_frame.to_bytes();
c.bench_function(
&format!("{kem}, {hash_function} | Initiator OneWay: Decode Frame - Request",),
|b| b.iter(|| KKTFrame::from_bytes(&i_frame_bytes).unwrap()),
);
let (i_frame_r, r_context) = KKTFrame::from_bytes(&i_frame_bytes).unwrap();
c.bench_function(
&format!("{kem}, {hash_function} | Initiator OneWay: Responder Ingest Frame",),
|b| {
b.iter(|| responder_ingest_message(&r_context, None, &i_frame_r).unwrap());
},
);
let (mut r_context, r_obtained_key) =
responder_ingest_message(&r_context, None, &i_frame_r).unwrap();
assert!(r_obtained_key.is_none());
c.bench_function(
&format!(
"{kem}, {hash_function} | Initiator OneWay: Responder Generate Response",
),
|b| {
b.iter(|| {
responder_process(
&mut r_context,
i_frame_r.session_id(),
&responder_kem_public_key,
)
.unwrap()
});
},
);
let r_frame = responder_process(
&mut r_context,
i_frame_r.session_id(),
&responder_kem_public_key,
)
.unwrap();
c.bench_function(
&format!("{kem}, {hash_function} | Initiator OneWay: Responder Encode Frame",),
|b| {
b.iter(|| r_frame.to_bytes());
},
);
c.bench_function(
&format!(
"{kem}, {hash_function} | Initiator OneWay: Initiator Ingest Response",
),
|b| {
b.iter(|| {
initiator_ingest_response(
&mut i_context,
&r_frame,
&r_frame.context().unwrap(),
&r_dir_hash,
)
.unwrap()
});
},
);
let i_obtained_key = initiator_ingest_response(
&mut i_context,
&r_frame,
&r_frame.context().unwrap(),
&r_dir_hash,
)
.unwrap();
assert_eq!(i_obtained_key.encode(), r_kem_key_bytes)
}
// Initiator, Mutual
{
c.bench_function(
&format!("{kem}, {hash_function} | Initiator Mutual: Generate Request",),
|b| {
b.iter(|| {
initiator_process(
&mut rng,
KKTMode::Mutual,
ciphersuite,
Some(&initiator_kem_public_key),
)
.unwrap()
});
},
);
let (mut i_context, i_frame) = initiator_process(
&mut rng,
KKTMode::Mutual,
ciphersuite,
Some(&initiator_kem_public_key),
)
.unwrap();
c.bench_function(
&format!("{kem}, {hash_function} | Initiator Mutual: Encode Frame - Request",),
|b| {
b.iter(|| i_frame.to_bytes());
},
);
let i_frame_bytes = i_frame.to_bytes();
c.bench_function(
&format!("{kem}, {hash_function} | Initiator Mutual: Decode Frame - Request",),
|b| {
b.iter(|| KKTFrame::from_bytes(&i_frame_bytes).unwrap());
},
);
let (i_frame_r, r_context) = KKTFrame::from_bytes(&i_frame_bytes).unwrap();
c.bench_function(
&format!("{kem}, {hash_function} | Initiator Mutual: Responder Ingest Frame",),
|b| {
b.iter(|| {
responder_ingest_message(&r_context, Some(&i_dir_hash), &i_frame_r)
.unwrap()
});
},
);
let (mut r_context, r_obtained_key) =
responder_ingest_message(&r_context, Some(&i_dir_hash), &i_frame_r).unwrap();
assert_eq!(r_obtained_key.unwrap().encode(), i_kem_key_bytes);
c.bench_function(
&format!(
"{kem}, {hash_function} | Initiator Mutual: Responder Generate Response",
),
|b| {
b.iter(|| {
responder_process(
&mut r_context,
i_frame_r.session_id(),
&responder_kem_public_key,
)
.unwrap()
});
},
);
let r_frame = responder_process(
&mut r_context,
i_frame_r.session_id(),
&responder_kem_public_key,
)
.unwrap();
c.bench_function(
&format!("{kem}, {hash_function} | Initiator Mutual: Responder Encode Frame",),
|b| {
b.iter(|| {
r_frame.to_bytes();
});
},
);
c.bench_function(
&format!(
"{kem}, {hash_function} | Initiator Mutual: Initiator Ingest Response",
),
|b| {
b.iter(|| {
initiator_ingest_response(
&mut i_context,
&r_frame,
&r_frame.context().unwrap(),
&r_dir_hash,
)
.unwrap()
});
},
);
let obtained_key = initiator_ingest_response(
&mut i_context,
&r_frame,
&r_frame.context().unwrap(),
&r_dir_hash,
)
.unwrap();
assert_eq!(obtained_key.encode(), r_kem_key_bytes)
}
}
}
}
criterion_group!(benches, gen_mlkem768_keypair, kkt_benchmark);
criterion_main!(benches);
+4 -4
View File
@@ -106,7 +106,7 @@ mod test {
)
.unwrap();
let response = responder.process_request(request).unwrap();
let response = responder.process_request(request.request).unwrap();
let result = initiator.process_response(response.response).unwrap();
@@ -133,7 +133,7 @@ mod test {
)
.unwrap();
let processed_request = responder.process_request(request).unwrap();
let processed_request = responder.process_request(request.request).unwrap();
// if we keep unverified keys, this should change
assert!(processed_request.remote_encapsulation_key.is_none());
@@ -166,7 +166,7 @@ mod test {
)
.unwrap();
let processed_request = responder.process_request(request).unwrap();
let processed_request = responder.process_request(request.request).unwrap();
let processed_response = initiator
.process_response(processed_request.response)
@@ -195,7 +195,7 @@ mod test {
)
.unwrap();
let processed_request = responder.process_request(request).unwrap();
let processed_request = responder.process_request(request.request).unwrap();
// if we keep unverified keys, this should change
assert!(processed_request.remote_encapsulation_key.is_none());
+39 -8
View File
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
//! Post-Quantum Re-Key Protocol
/// This module implements a stateless post-quantum re-keying protocol in one round-trip.
/// We currently support MlKem768 and XWing.
///
@@ -16,6 +17,7 @@ use libcrux_kem::*;
use nym_crypto::hkdf::blake3::derive_key_blake3;
use nym_kkt_ciphersuite::{KEM, mceliece, ml_kem768, x25519, xwing};
use rand09::{CryptoRng, RngCore};
use std::fmt::{Debug, Formatter};
use zeroize::Zeroize;
use crate::error::KKTError;
@@ -29,6 +31,26 @@ pub struct RekeyInitiator {
salt: [u8; 32],
}
impl Debug for RekeyInitiator {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let key_typ = match self.decapsulation_key {
PrivateKey::X25519(_) => "x25519",
PrivateKey::P256(_) => "p256",
PrivateKey::MlKem512(_) => "ml512",
PrivateKey::MlKem768(_) => "mlkem768",
PrivateKey::X25519MlKem768Draft00(_) => "x25519-mlkem768",
PrivateKey::XWingKemDraft06(_) => "xwing",
PrivateKey::MlKem1024(_) => "ml1024",
};
f.debug_struct("RekeyInitiator")
.field("algorithm", &self.algorithm)
.field("decapsulation_key", &key_typ)
.field("salt", &self.salt)
.finish()
}
}
impl RekeyInitiator {
/// The Initiator generates an ephemeral KEM keypair and a 32-byte salt.
/// The Initiator keeps the decapsulation key and generates a request message.
@@ -204,6 +226,7 @@ where
#[cfg(test)]
mod tests {
use crate::error::KKTError;
use crate::rekey::{RekeyInitiator, responder_process};
use nym_kkt_ciphersuite::KEM;
@@ -211,16 +234,24 @@ mod tests {
fn rekey_test() {
let mut rng = rand09::rng();
for kem in [KEM::MlKem768] {
let (rekey_state, request_message) =
RekeyInitiator::generate_request(&mut rng, kem).unwrap();
let (rekey_state, request_message) =
RekeyInitiator::generate_request(&mut rng, KEM::MlKem768).unwrap();
let (responder_secret, response_message) =
responder_process(&mut rng, request_message).unwrap();
let (responder_secret, response_message) =
responder_process(&mut rng, request_message).unwrap();
let initiator_secret = rekey_state.finalize(&response_message).unwrap();
let initiator_secret = rekey_state.finalize(&response_message).unwrap();
assert_eq!(initiator_secret, responder_secret);
}
assert_eq!(initiator_secret, responder_secret);
// mceliece should fail
let err = RekeyInitiator::generate_request(&mut rng, KEM::McEliece).unwrap_err();
assert_eq!(
err.to_string(),
KKTError::UnsupportedAlgorithm {
info: "McEliece is not supported for re-keying",
}
.to_string()
)
}
}
+1 -1
View File
@@ -18,7 +18,7 @@ pub use nym_kkt_ciphersuite::{
Ciphersuite, HashFunction, HashLength, KEM, KEMKeyDigests, SignatureScheme,
};
pub use nym_lp_packet::{
EncryptedLpPacket, LpMessage, LpPacket, OuterHeader,
EncryptedLpPacket, InnerHeader, LpHeader, LpMessage, LpPacket, MessageType, OuterHeader,
error::MalformedLpPacketError,
message::{ApplicationData, ExpectedResponseSize, ForwardPacketData},
};
+5 -5
View File
@@ -2,13 +2,15 @@
// SPDX-License-Identifier: Apache-2.0
use crate::LpError;
use nym_crypto::asymmetric::x25519;
use nym_kkt_ciphersuite::{Ciphersuite, KEM, KEMKeyDigests};
use std::collections::BTreeMap;
use std::fmt::Debug;
use std::sync::Arc;
pub use libcrux_psq::handshake::types::{DHKeyPair, DHPublicKey};
pub use nym_kkt::key_utils::{
generate_keypair_mceliece, generate_keypair_mlkem, generate_lp_keypair_x25519,
};
pub use nym_kkt::keys::KEMKeys;
/// Representation of a local Lewes Protocol peer
@@ -85,11 +87,9 @@ pub struct LpRemotePeer {
}
impl LpRemotePeer {
pub fn new(x25519_public: x25519::PublicKey) -> Self {
// TODO: make nicer conversion (without cloning) + error handling
let responder_x25519_public_key = DHPublicKey::from_bytes(x25519_public.as_bytes());
pub fn new(x25519_public: DHPublicKey) -> Self {
LpRemotePeer {
x25519_public: responder_x25519_public_key,
x25519_public,
expected_kem_key_digests: Default::default(),
}
}
+2 -1
View File
@@ -12,6 +12,7 @@ use std::collections::BTreeMap;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
pub use lp_messages::*;
use nym_crypto::asymmetric::x25519::DHPublicKey;
pub use serialisation::BincodeError;
mod lp_messages;
@@ -57,7 +58,7 @@ pub struct WireguardConfiguration {
pub struct NymNodeLPInformation {
pub address: SocketAddr,
pub expected_kem_key_hashes: BTreeMap<KEM, KEMKeyDigests>,
pub x25519: x25519::PublicKey,
pub x25519: DHPublicKey,
// to be inferred from node's version
pub ciphersuite: Ciphersuite,
+1 -1
View File
@@ -122,7 +122,7 @@ impl LpDataHandler {
) -> Result<(), LpHandlerError> {
inc!("lp_data_packets_received");
let _ = OuterHeader::parse(&packet)?;
let _ = OuterHeader::parse(packet)?;
trace!(
"received {} bytes from {src_addr} on the unimplemented LP Data endpoint",
packet.len()
+3 -4
View File
@@ -59,10 +59,9 @@ pub enum LpHandlerError {
impl LpHandlerError {
pub fn is_connection_closed(&self) -> bool {
match self {
LpHandlerError::LpTransportError(transport_err) => match transport_err {
LpTransportError::ConnectionClosed => true,
_ => false,
},
LpHandlerError::LpTransportError(transport_err) => {
matches!(transport_err, LpTransportError::ConnectionClosed)
}
_ => false,
}
}
+67 -313
View File
@@ -624,35 +624,6 @@ where
Ok(())
}
// only used in tests
#[cfg(test)]
async fn send_lp_packet(&mut self, packet: LpPacket) -> Result<(), LpHandlerError> {
let receiver_index = self.bound_receiver_index()?;
let mut session_entry = self
.state
.session_states
.get_mut(&receiver_index)
.ok_or_else(|| LpHandlerError::MissingLpSession { receiver_index })?;
// Access session via state machine for subsession support
let session = session_entry.value_mut().state.session_mut()?;
let mut packet_buf = BytesMut::new();
serialize_lp_packet(&packet, &mut packet_buf, Some(outer_key)).map_err(|e| {
LpHandlerError::LpProtocolError(format!("Failed to serialize packet: {e}",))
})?;
self.stream
.send_length_prefixed_transport_packet(&packet_buf)
.await?;
// Track bytes sent (4 byte header + packet data)
self.stats.record_bytes_sent(4 + packet_buf.len());
Ok(())
}
/// Emit connection lifecycle metrics
fn emit_lifecycle_metrics(&self, graceful: bool) {
use nym_metrics::inc_by;
@@ -689,17 +660,18 @@ mod tests {
use super::*;
use crate::node::lp_listener::{LpConfig, LpDebug};
use crate::node::ActiveClientsStore;
use bytes::BytesMut;
use nym_lp::peer::LpLocalPeer;
use nym_lp::SessionsMock;
use nym_lp::peer::{generate_keypair_mceliece, generate_keypair_mlkem, KEMKeys, LpLocalPeer};
use nym_lp::{sessions_for_tests, Ciphersuite, SessionManager};
use nym_test_utils::helpers::{deterministic_rng, deterministic_rng_09};
use std::sync::Arc;
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWriteExt};
// ==================== Test Helpers ====================
/// Create a minimal test state for handler tests
async fn create_minimal_test_state() -> LpHandlerState {
use nym_crypto::asymmetric::ed25519;
use rand::rngs::OsRng;
let mut rng = deterministic_rng();
let mut rng09 = deterministic_rng_09();
// Create in-memory storage for testing
let storage = nym_gateway_storage::GatewayStorage::init(":memory:", 100)
@@ -723,10 +695,14 @@ mod tests {
// Create mix forwarding channel (unused in tests but required by struct)
let (mix_sender, _mix_receiver) = nym_mixnet_client::forwarder::mix_forwarding_channels();
let id_keys = Arc::new(ed25519::KeyPair::new(&mut OsRng));
let x_keys = Arc::new(id_keys.to_x25519());
let id_keys = Arc::new(ed25519::KeyPair::new(&mut rng));
let x_keys = Arc::new(id_keys.to_x25519().try_into().unwrap());
let lp_peer = LpLocalPeer::new(id_keys, x_keys.clone()).with_kem_psq_key(x_keys);
let kem_keys = KEMKeys::new(
generate_keypair_mceliece(&mut rng09),
generate_keypair_mlkem(&mut rng09),
);
let lp_peer = LpLocalPeer::new(Ciphersuite::default(), x_keys).with_kem_keys(kem_keys);
LpHandlerState {
lp_config,
@@ -739,173 +715,24 @@ mod tests {
outbound_mix_sender: mix_sender,
session_states: Arc::new(dashmap::DashMap::new()),
peer_registrator: None,
forward_semaphore,
}
}
fn add_dummy_lp_state(handler: &mut LpConnectionHandler, session: LpSession) {
let id = session.receiver_index();
let state_machine = LpStateMachine::new(session);
handler.bound_receiver_idx = Some(id);
handler
.state
.session_states
.insert(id, TimestampedState::new(state_machine));
}
/// Helper to write an LP packet to a stream with proper framing
async fn write_lp_packet_to_stream<W: AsyncWriteExt + Unpin>(
stream: &mut W,
packet: &LpPacket,
) -> Result<(), std::io::Error> {
let mut packet_buf = BytesMut::new();
serialize_lp_packet(packet, &mut packet_buf, None)
.map_err(|e| std::io::Error::other(e.to_string()))?;
// Write length prefix
let len = packet_buf.len() as u32;
stream.write_all(&len.to_be_bytes()).await?;
// Write packet data
stream.write_all(&packet_buf).await?;
stream.flush().await?;
Ok(())
}
/// Helper to read an LP packet from a stream with proper framing
async fn read_lp_packet_from_stream<R: AsyncRead + Unpin>(
stream: &mut R,
outer_aead_key: &OuterAeadKey,
) -> Result<LpPacket, std::io::Error> {
// Read length prefix
let mut len_buf = [0u8; 4];
stream.read_exact(&mut len_buf).await?;
let packet_len = u32::from_be_bytes(len_buf) as usize;
// Read packet data
let mut packet_buf = vec![0u8; packet_len];
stream.read_exact(&mut packet_buf).await?;
// Parse packet
parse_lp_packet(&packet_buf, Some(outer_aead_key))
.map_err(|e| std::io::Error::other(e.to_string()))
}
// ==================== Existing Tests ====================
#[test]
fn test_validate_timestamp_current() {
use std::time::{SystemTime, UNIX_EPOCH};
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
// Current timestamp should always pass
assert!(
LpConnectionHandler::<TcpStream>::validate_timestamp(now, Duration::from_secs(30))
.is_ok()
);
}
#[test]
fn test_validate_timestamp_within_tolerance() {
use std::time::{SystemTime, UNIX_EPOCH};
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
// 10 seconds old, tolerance 30s -> should pass
let old_timestamp = now - 10;
assert!(LpConnectionHandler::<TcpStream>::validate_timestamp(
old_timestamp,
Duration::from_secs(30)
)
.is_ok());
// 10 seconds in future, tolerance 30s -> should pass
let future_timestamp = now + 10;
assert!(LpConnectionHandler::<TcpStream>::validate_timestamp(
future_timestamp,
Duration::from_secs(30)
)
.is_ok());
}
#[test]
fn test_validate_timestamp_too_old() {
use std::time::{SystemTime, UNIX_EPOCH};
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
// 60 seconds old, tolerance 30s -> should fail
let old_timestamp = now - 60;
let result = LpConnectionHandler::<TcpStream>::validate_timestamp(
old_timestamp,
Duration::from_secs(30),
);
assert!(result.is_err());
assert!(format!("{:?}", result).contains("too old"));
}
#[test]
fn test_validate_timestamp_too_far_future() {
use std::time::{SystemTime, UNIX_EPOCH};
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
// 60 seconds in future, tolerance 30s -> should fail
let future_timestamp = now + 60;
let result = LpConnectionHandler::<TcpStream>::validate_timestamp(
future_timestamp,
Duration::from_secs(30),
);
assert!(result.is_err());
assert!(format!("{:?}", result).contains("too future"));
}
#[test]
fn test_validate_timestamp_boundary() {
use std::time::{SystemTime, UNIX_EPOCH};
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
// Exactly at tolerance boundary -> should pass
let boundary_timestamp = now - 30;
assert!(LpConnectionHandler::<TcpStream>::validate_timestamp(
boundary_timestamp,
Duration::from_secs(30)
)
.is_ok());
// Just beyond boundary -> should fail
let beyond_timestamp = now - 31;
assert!(LpConnectionHandler::<TcpStream>::validate_timestamp(
beyond_timestamp,
Duration::from_secs(30)
)
.is_err());
}
// ==================== Packet I/O Tests ====================
#[tokio::test]
async fn test_receive_raw_packet_valid() {
use tokio::net::{TcpListener, TcpStream};
let (init, resp) = sessions_for_tests();
let mut init_sm = SessionManager::new();
let mut resp_sm = SessionManager::new();
resp_sm.create_session_state_machine(resp).unwrap();
let id = init_sm.create_session_state_machine(init).unwrap();
// Bind to localhost
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = listener.local_addr().unwrap();
@@ -916,69 +743,38 @@ mod tests {
let state = create_minimal_test_state().await;
let mut handler = LpConnectionHandler::new(stream, remote_addr, state);
// Two-phase: receive raw bytes + header, then parse full packet
let (raw_bytes, header) = handler.receive_raw_packet().await?;
let packet = parse_lp_packet(&raw_bytes, None).map_err(|e| {
LpHandlerError::LpProtocolError(format!("Failed to parse packet: {}", e))
})?;
Ok::<_, LpHandlerError>((header, packet))
let packet = handler.receive_raw_packet().await?;
let header = packet.outer_header();
assert_eq!(packet.outer_header().receiver_idx, id);
let Some(LpAction::DeliverData(data)) = resp_sm.receive_packet(id, packet).unwrap()
else {
panic!("illegal state")
};
Ok::<_, LpHandlerError>((header, data))
});
// Connect as client
let mut client_stream = TcpStream::connect(addr).await.unwrap();
// Send a valid packet from client side
let packet = LpPacket::new(
LpHeader {
protocol_version: 1,
reserved: [0u8; 3],
receiver_idx: 42,
counter: 0,
},
LpMessage::Busy,
);
write_lp_packet_to_stream(&mut client_stream, &packet)
let LpAction::SendPacket(packet) = init_sm
.send_data(id, LpData::new_opaque(b"foomp".to_vec()))
.unwrap()
else {
panic!("illegal state")
};
client_stream
.send_length_prefixed_transport_packet(&packet)
.await
.unwrap();
// Handler should receive and parse it correctly
// Note: header is OuterHeader (receiver_idx + counter only), not LpHeader
let (header, received) = server_task.await.unwrap().unwrap();
assert_eq!(header.receiver_idx, 42);
assert_eq!(header.receiver_idx, id);
assert_eq!(header.counter, 0);
assert_eq!(received.header().protocol_version, 1);
assert_eq!(received.header().receiver_idx, 42);
assert_eq!(received.header().counter, 0);
}
#[tokio::test]
async fn test_receive_raw_packet_exceeds_max_size() {
use tokio::net::{TcpListener, TcpStream};
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = listener.local_addr().unwrap();
let server_task = tokio::spawn(async move {
let (stream, remote_addr) = listener.accept().await.unwrap();
let state = create_minimal_test_state().await;
let mut handler = LpConnectionHandler::new(stream, remote_addr, state);
handler.receive_raw_packet().await
});
let mut client_stream = TcpStream::connect(addr).await.unwrap();
// Send a packet size that exceeds MAX_PACKET_SIZE (64KB)
let oversized_len: u32 = 70000; // > 65536
client_stream
.write_all(&oversized_len.to_be_bytes())
.await
.unwrap();
client_stream.flush().await.unwrap();
// Handler should reject it
let result = server_task.await.unwrap();
assert!(result.is_err());
let err_msg = format!("{:?}", result.unwrap_err());
assert!(err_msg.contains("exceeds maximum"));
assert_eq!(received.content.as_ref(), b"foomp");
}
#[tokio::test]
@@ -988,88 +784,46 @@ mod tests {
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = listener.local_addr().unwrap();
let receiver_idx = 99;
let sessions = SessionsMock::mock_post_handshake(receiver_idx);
let init = sessions.initiator;
let resp = sessions.responder;
let (init, resp) = sessions_for_tests();
let mut init_sm = SessionManager::new();
let mut resp_sm = SessionManager::new();
resp_sm.create_session_state_machine(resp).unwrap();
let id = init_sm.create_session_state_machine(init).unwrap();
let server_task = tokio::spawn(async move {
let (stream, remote_addr) = listener.accept().await.unwrap();
let state = create_minimal_test_state().await;
let mut handler = LpConnectionHandler::new(stream, remote_addr, state);
add_dummy_lp_state(&mut handler, resp);
let (mut stream, _) = listener.accept().await.unwrap();
let packet = LpPacket::new(
LpHeader {
protocol_version: 1,
reserved: [0u8; 3],
receiver_idx,
counter: 5,
},
LpMessage::Busy,
);
handler.send_lp_packet(packet).await
let LpAction::SendPacket(packet) = resp_sm
.send_data(id, LpData::new_opaque(b"foomp".to_vec()))
.unwrap()
else {
panic!("illegal state")
};
stream
.send_length_prefixed_transport_packet(&packet)
.await
.unwrap();
});
let mut client_stream = TcpStream::connect(addr).await.unwrap();
// Wait for server to send
server_task.await.unwrap().unwrap();
server_task.await.unwrap();
// Client should receive it correctly
let received = read_lp_packet_from_stream(&mut client_stream, init.outer_aead_key())
let received = client_stream
.receive_length_prefixed_transport_packet()
.await
.unwrap();
assert_eq!(received.header().receiver_idx, receiver_idx);
assert_eq!(received.header().counter, 5);
}
let header = received.outer_header();
let Some(LpAction::DeliverData(data)) = init_sm.receive_packet(id, received).unwrap()
else {
panic!("illegal state")
};
#[tokio::test]
async fn test_send_receive_encrypted_data_message() {
use tokio::net::{TcpListener, TcpStream};
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = listener.local_addr().unwrap();
let receiver_idx = 200;
let sessions = SessionsMock::mock_post_handshake(receiver_idx);
let init = sessions.initiator;
let resp = sessions.responder;
let encrypted_payload = vec![42u8; 256];
let expected_payload = encrypted_payload.clone();
let server_task = tokio::spawn(async move {
let (stream, remote_addr) = listener.accept().await.unwrap();
let state = create_minimal_test_state().await;
let mut handler = LpConnectionHandler::new(stream, remote_addr, state);
add_dummy_lp_state(&mut handler, resp);
let packet = LpPacket::new(
LpHeader {
protocol_version: 1,
reserved: [0u8; 3],
receiver_idx,
counter: 20,
},
LpMessage::ApplicationData(ApplicationData(encrypted_payload)),
);
handler.send_lp_packet(packet).await
});
let mut client_stream = TcpStream::connect(addr).await.unwrap();
server_task.await.unwrap().unwrap();
let received = read_lp_packet_from_stream(&mut client_stream, init.outer_aead_key())
.await
.unwrap();
assert_eq!(received.header().receiver_idx, 200);
assert_eq!(received.header().counter, 20);
match received.message() {
LpMessage::ApplicationData(data) => {
assert_eq!(data, &ApplicationData(expected_payload))
}
_ => panic!("Expected EncryptedData message"),
}
assert_eq!(header.receiver_idx, id);
assert_eq!(header.counter, 0);
assert_eq!(data.content.as_ref(), b"foomp");
}
}
+170 -168
View File
@@ -9,12 +9,11 @@ mod tests {
use nym_credential_verification::upgrade_mode::testing::mock_dummy_upgrade_mode_details;
use nym_credentials_interface::TicketType;
use nym_crypto::asymmetric::{ed25519, x25519};
use nym_gateway::GatewayError;
use nym_gateway::node::lp_listener::error::LpHandlerError;
use nym_gateway::node::lp_listener::handler::LpConnectionHandler;
use nym_gateway::node::lp_listener::{
DHKeyPair, KEMKeys, LpDebug, LpHandlerState, LpLocalPeer, MixForwardingReceiver,
PeerControlRequest, WireguardGatewayData, mix_forwarding_channels,
KEMKeys, LpDebug, LpHandlerState, LpLocalPeer, MixForwardingReceiver, PeerControlRequest,
WireguardGatewayData, mix_forwarding_channels,
};
use nym_gateway::node::wireguard::{PeerManager, PeerRegistrator};
use nym_gateway::node::{ActiveClientsStore, GatewayStorage, LpConfig};
@@ -23,7 +22,7 @@ mod tests {
};
use nym_kkt_ciphersuite::Ciphersuite;
use nym_registration_client::{LpClientError, LpRegistrationClient};
use nym_test_utils::helpers::{CryptoRng09, RngCore09, seeded_rng};
use nym_test_utils::helpers::{CryptoRng09, seeded_rng};
use nym_test_utils::mocks::async_read_write::MockIOStream;
use nym_test_utils::traits::Timeboxed;
use nym_wireguard::peer_controller::IpPair;
@@ -35,7 +34,6 @@ mod tests {
use std::mem;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
use std::sync::Arc;
use std::time::Duration;
use tokio::sync::Semaphore;
use tokio::sync::mpsc::Receiver;
use tokio::task::JoinHandle;
@@ -61,7 +59,7 @@ mod tests {
}
impl Party {
fn generate(rng: &mut (impl RngCore09 + CryptoRng09)) -> Self {
fn generate(rng: &mut impl CryptoRng09) -> Self {
let mut ip = [0u8; 4];
let mut port = [0u8; 2];
@@ -100,7 +98,7 @@ mod tests {
}
impl Client {
fn mock(rng: &mut (impl RngCore09 + CryptoRng09)) -> Self {
fn mock(rng: &mut impl CryptoRng09) -> Self {
Client {
base: Party::generate(rng),
ticket_provider: Default::default(),
@@ -194,7 +192,7 @@ mod tests {
Ok(GatewayStorage::from_connection_pool(conn_pool, 100).await?)
}
async fn mock(rng: &mut (impl RngCore09 + CryptoRng09)) -> anyhow::Result<Self> {
async fn mock(rng: &mut impl CryptoRng09) -> anyhow::Result<Self> {
let base = Party::generate(rng);
// 1. create in-memory gateway storage
@@ -574,199 +572,203 @@ mod tests {
async fn test_basic_lp_exit_registration() -> anyhow::Result<()> {
// nym_test_utils::helpers::setup_test_logger();
let TODO = "test with different kems";
let ciphersuite = Ciphersuite::default();
todo!("doesn't work with mceliece");
// initialise random, but deterministic, keys, addresses, etc. for the parties
let mut client_rng = u64_seeded_rng_09(0);
let mut entry_rng = u64_seeded_rng_09(1);
let mut exit_rng = u64_seeded_rng_09(2);
for kem in KEM::iter() {
let ciphersuite = Ciphersuite::default().with_kem(kem);
let client_data = Client::mock(&mut client_rng);
let client_key = *client_data.base.x25519_wg_keys.public_key();
let mut entry = Gateway::mock(&mut entry_rng).await?;
let mut exit = Gateway::mock(&mut exit_rng).await?;
// initialise random, but deterministic, keys, addresses, etc. for the parties
let mut client_rng = u64_seeded_rng_09(0);
let mut entry_rng = u64_seeded_rng_09(1);
let mut exit_rng = u64_seeded_rng_09(2);
let mut entry_client = LpRegistrationClient::<MockIOStream>::new_with_default_config(
client_data.base.peer.x25519().clone(),
entry.base.peer.as_remote(),
entry.base.socket_addr,
ciphersuite,
entry.base.lp_version,
);
let client_data = Client::mock(&mut client_rng);
let client_key = *client_data.base.x25519_wg_keys.public_key();
let mut entry = Gateway::mock(&mut entry_rng).await?;
let mut exit = Gateway::mock(&mut exit_rng).await?;
// START: ENTRY SETUP
//
// 1. establish mock connection between client and gateway and retrieve gateway's handle
entry_client.ensure_connected().await?;
let entry_conn = entry_client
.connection()
.as_ref()
.context("mock connection has failed!")?
.try_get_remote_handle();
entry_conn.set_id(1);
let mut entry_client =
LpRegistrationClient::<MockIOStream>::new_with_default_config(
client_data.base.peer.x25519().clone(),
entry.base.peer.as_remote(),
entry.base.socket_addr,
ciphersuite,
entry.base.lp_version,
);
// 2. create handler for the client connection (entry)
entry.create_lp_handler(entry_conn, client_data.base.socket_addr);
// START: ENTRY SETUP
//
// 1. establish mock connection between client and gateway and retrieve gateway's handle
entry_client.ensure_connected().await?;
let entry_conn = entry_client
.connection()
.as_ref()
.context("mock connection has failed!")?
.try_get_remote_handle();
entry_conn.set_id(1);
// 3. pre-establish connection between entry and exit
let exit_conn = entry
.establish_forwarding_channel(exit.base.socket_addr)
.await?;
exit_conn.set_id(255);
// 2. create handler for the client connection (entry)
entry.create_lp_handler(entry_conn, client_data.base.socket_addr);
// 4. register all needed responses for the dvpn registration that will reach the peer controller
// 1) peer registration - ip pair allocation
let entry_ip_pair = entry.pre_allocate_ip_pair();
let reg_res = Ok::<_, nym_wireguard::Error>(entry_ip_pair);
// 3. pre-establish connection between entry and exit
let exit_conn = entry
.establish_forwarding_channel(exit.base.socket_addr)
.await?;
exit_conn.set_id(255);
entry
.register_peer_controller_response(
// 4. register all needed responses for the dvpn registration that will reach the peer controller
// 1) peer registration - ip pair allocation
let entry_ip_pair = entry.pre_allocate_ip_pair();
let reg_res = Ok::<_, nym_wireguard::Error>(entry_ip_pair);
entry
.register_peer_controller_response(
PeerControlRequestType::AllocatePeerIpPair {},
reg_res,
)
.await;
// 2) new peer inclusion - in non-mock system it would spawn handlers,
// here we'll just set a flag and say it's all fine
let public_key = client_key.to_wg_key();
let add_res = Ok::<_, nym_wireguard::Error>(());
entry
.register_peer_controller_response(
PeerControlRequestType::AddPeer { public_key },
add_res,
)
.await;
// 3) peer query - check for prior registrations
let query_res = Ok::<_, nym_wireguard::Error>(Option::<DefguardPeer>::None);
let key = client_key.to_wg_key();
entry
.register_peer_controller_response(
PeerControlRequestType::QueryPeer { key },
query_res,
)
.await;
// 5. spawn peer controller to be able to handle dvpn registration requests
entry.spawn_peer_controller();
// 6. finally spawn the handler
entry.spawn_lp_handler();
// 7. perform client handshake (with the entry)
entry_client.perform_handshake().timeboxed().await??;
// END: ENTRY SETUP
//
// START: EXIT SETUP:
// 8. create handler for the forwarding channel (exit)
exit.create_lp_handler(exit_conn, client_data.base.socket_addr);
// 9. spawn the handler
exit.spawn_lp_handler();
// 10. register all needed responses for the dvpn registration that will reach the peer controller
// 1) peer registration - ip pair allocation
let exit_ip_pair = exit.pre_allocate_ip_pair();
let reg_res = Ok::<_, nym_wireguard::Error>(exit_ip_pair);
exit.register_peer_controller_response(
PeerControlRequestType::AllocatePeerIpPair {},
reg_res,
)
.await;
// 2) new peer inclusion - in non-mock system it would spawn handlers,
// here we'll just set a flag and say it's all fine
let public_key = client_key.to_wg_key();
let add_res = Ok::<_, nym_wireguard::Error>(());
entry
.register_peer_controller_response(
// 2) new peer inclusion - in non-mock system it would spawn handlers,
// here we'll just set a flag and say it's all fine
let public_key = client_key.to_wg_key();
let add_res = Ok::<_, nym_wireguard::Error>(());
exit.register_peer_controller_response(
PeerControlRequestType::AddPeer { public_key },
add_res,
)
.await;
// 3) peer query - check for prior registrations
let query_res = Ok::<_, nym_wireguard::Error>(Option::<DefguardPeer>::None);
let key = client_key.to_wg_key();
entry
.register_peer_controller_response(
// 3) peer query - check for prior registrations
let query_res = Ok::<_, nym_wireguard::Error>(Option::<DefguardPeer>::None);
let key = client_key.to_wg_key();
exit.register_peer_controller_response(
PeerControlRequestType::QueryPeer { key },
query_res,
)
.await;
// 5. spawn peer controller to be able to handle dvpn registration requests
entry.spawn_peer_controller();
// 11. spawn peer controller to be able to handle dvpn registration requests
exit.spawn_peer_controller();
// 6. finally spawn the handler
entry.spawn_lp_handler();
// END: EXIT SETUP
// 7. perform client handshake (with the entry)
entry_client.perform_handshake().timeboxed().await??;
// 12. create nested session to register with exit via forwarding
// technically we should use different ephemeral keys than we had for the entry
// but crypto is going to work the same
let mut nested_session = NestedLpSession::new(
exit.base.socket_addr,
client_data.base.peer.x25519().clone(),
exit.base.peer.as_remote(),
ciphersuite,
exit.base.lp_version,
);
// END: ENTRY SETUP
//
// START: EXIT SETUP:
// 8. create handler for the forwarding channel (exit)
exit.create_lp_handler(exit_conn, client_data.base.socket_addr);
// 13. Perform handshake and registration with exit gateway (all via entry forwarding)
nested_session.perform_handshake(&mut entry_client).await?;
// 9. spawn the handler
exit.spawn_lp_handler();
let exit_registration_result = nested_session
.register_dvpn(
&mut entry_client,
&mut client_rng,
&client_data.base.x25519_wg_keys,
exit.base.identity.public_key(),
&client_data.ticket_provider,
TicketType::V1WireguardExit,
)
.timeboxed()
.await??;
// 10. register all needed responses for the dvpn registration that will reach the peer controller
// 1) peer registration - ip pair allocation
let exit_ip_pair = exit.pre_allocate_ip_pair();
let reg_res = Ok::<_, nym_wireguard::Error>(exit_ip_pair);
// 14. complete registration with the entry
let entry_registration_result = entry_client
.register_dvpn(
&mut client_rng,
&client_data.base.x25519_wg_keys,
entry.base.identity.public_key(),
&client_data.ticket_provider,
TicketType::V1WireguardEntry,
)
.timeboxed()
.await??;
exit.register_peer_controller_response(
PeerControlRequestType::AllocatePeerIpPair {},
reg_res,
)
.await;
// 15. verify all registration results
let peers_guard = entry.mock_peer_controller_state.peers.read().await;
let entry_peer = peers_guard.get_by_x25519_key(&client_key).unwrap().clone();
drop(peers_guard);
assert!(entry_peer.add_success);
// 2) new peer inclusion - in non-mock system it would spawn handlers,
// here we'll just set a flag and say it's all fine
let public_key = client_key.to_wg_key();
let add_res = Ok::<_, nym_wireguard::Error>(());
exit.register_peer_controller_response(
PeerControlRequestType::AddPeer { public_key },
add_res,
)
.await;
let peers_guard = exit.mock_peer_controller_state.peers.read().await;
let exit_peer = peers_guard.get_by_x25519_key(&client_key).unwrap().clone();
drop(peers_guard);
assert!(exit_peer.add_success);
// 3) peer query - check for prior registrations
let query_res = Ok::<_, nym_wireguard::Error>(Option::<DefguardPeer>::None);
let key = client_key.to_wg_key();
exit.register_peer_controller_response(
PeerControlRequestType::QueryPeer { key },
query_res,
)
.await;
assert_eq!(entry_registration_result.private_ipv4, entry_ip_pair.ipv4);
assert_eq!(entry_registration_result.private_ipv6, entry_ip_pair.ipv6);
assert_eq!(
entry_registration_result.public_key,
*entry.base.x25519_wg_keys.public_key()
);
// 11. spawn peer controller to be able to handle dvpn registration requests
exit.spawn_peer_controller();
assert_eq!(exit_registration_result.private_ipv4, exit_ip_pair.ipv4);
assert_eq!(exit_registration_result.private_ipv6, exit_ip_pair.ipv6);
assert_eq!(
exit_registration_result.public_key,
*exit.base.x25519_wg_keys.public_key()
);
// END: EXIT SETUP
// 12. create nested session to register with exit via forwarding
// technically we should use different ephemeral keys than we had for the entry
// but crypto is going to work the same
let mut nested_session = NestedLpSession::new(
exit.base.socket_addr,
client_data.base.peer.x25519().clone(),
exit.base.peer.as_remote(),
ciphersuite,
exit.base.lp_version,
);
// 13. Perform handshake and registration with exit gateway (all via entry forwarding)
nested_session.perform_handshake(&mut entry_client).await?;
let exit_registration_result = nested_session
.register_dvpn(
&mut entry_client,
&mut client_rng,
&client_data.base.x25519_wg_keys,
exit.base.identity.public_key(),
&client_data.ticket_provider,
TicketType::V1WireguardExit,
)
.timeboxed()
.await??;
// 14. complete registration with the entry
let entry_registration_result = entry_client
.register_dvpn(
&mut client_rng,
&client_data.base.x25519_wg_keys,
entry.base.identity.public_key(),
&client_data.ticket_provider,
TicketType::V1WireguardEntry,
)
.timeboxed()
.await??;
// 15. verify all registration results
let peers_guard = entry.mock_peer_controller_state.peers.read().await;
let entry_peer = peers_guard.get_by_x25519_key(&client_key).unwrap().clone();
drop(peers_guard);
assert!(entry_peer.add_success);
let peers_guard = exit.mock_peer_controller_state.peers.read().await;
let exit_peer = peers_guard.get_by_x25519_key(&client_key).unwrap().clone();
drop(peers_guard);
assert!(exit_peer.add_success);
assert_eq!(entry_registration_result.private_ipv4, entry_ip_pair.ipv4);
assert_eq!(entry_registration_result.private_ipv6, entry_ip_pair.ipv6);
assert_eq!(
entry_registration_result.public_key,
*entry.base.x25519_wg_keys.public_key()
);
assert_eq!(exit_registration_result.private_ipv4, exit_ip_pair.ipv4);
assert_eq!(exit_registration_result.private_ipv6, exit_ip_pair.ipv6);
assert_eq!(
exit_registration_result.public_key,
*exit.base.x25519_wg_keys.public_key()
);
// 16. stop the gateway task and finish the test
entry.stop_tasks().await?;
exit.stop_tasks().await?;
// 16. stop the gateway task and finish the test
entry.stop_tasks().await?;
exit.stop_tasks().await?;
}
Ok(())
}
-1
View File
@@ -52,7 +52,6 @@ nym-ecash-signer-check-types = { workspace = true }
nym-kkt-ciphersuite = { workspace = true }
[dev-dependencies]
rand_chacha = { workspace = true }
nym-crypto = { workspace = true, features = ["rand"] }
nym-test-utils = { workspace = true }
+2 -8
View File
@@ -790,13 +790,7 @@ mod tests {
use nym_compact_ecash::scheme::keygen::KeyPairUser;
use nym_compact_ecash::withdrawal_request;
use nym_ecash_time::{ecash_today_date, EcashTime};
use rand_chacha::rand_core::SeedableRng;
use rand_chacha::ChaCha20Rng;
pub fn test_rng() -> ChaCha20Rng {
let dummy_seed = [42u8; 32];
ChaCha20Rng::from_seed(dummy_seed)
}
use nym_test_utils::helpers::deterministic_rng;
// had some issues with `Date` and serde...
// so might as well leave this unit test in case we do something to the helper
@@ -821,7 +815,7 @@ mod tests {
#[test]
fn decoding_attribute_commitments() {
let mut rng = test_rng();
let mut rng = deterministic_rng();
let keys = ed25519::KeyPair::new(&mut rng);
let dummy_sig = keys.private_key().sign("foomp");
let dummy_keypair = KeyPairUser::new();
@@ -209,6 +209,16 @@ pub struct LewesProtocolDetailsV1 {
pub signature: ed25519::Signature,
}
impl LewesProtocolDetailsV1 {
pub fn verify(&self, key: &ed25519::PublicKey) -> bool {
let Ok(plaintext) = serde_json::to_string(&self.content) else {
return false;
};
key.verify(plaintext, &self.signature).is_ok()
}
}
#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema, PartialEq)]
pub struct LewesProtocolDetailsDataV1 {
/// Helper field that specifies whether the LP listener(s) is enabled on this node.
@@ -572,10 +582,44 @@ impl TryFrom<LPSignatureScheme> for SignatureScheme {
#[cfg(test)]
mod tests {
use super::*;
use nym_node_requests::api::SignedLewesProtocol;
#[test]
fn signature_validity_after_conversion() {
use nym_node_requests::api::v1::lewes_protocol::models::{LPHashFunction, LPKEM};
let signing_key = ed25519::PrivateKey::from_bytes(&[42u8; 32]).unwrap();
let verification_key = signing_key.public_key();
let x25519_key = x25519::DHPublicKey::from_bytes(&[42u8; 32]);
let mut dummy_kems = BTreeMap::new();
for kem in [LPKEM::McEliece, LPKEM::McEliece] {
let mut kem_digests = BTreeMap::new();
for sf in [
LPHashFunction::Blake3,
LPHashFunction::Shake128,
LPHashFunction::Shake256,
LPHashFunction::Sha256,
] {
kem_digests.insert(sf, "0xdeadbeef".to_string());
}
dummy_kems.insert(kem, kem_digests);
}
// make sure the serialisation stays the same and signature is still valid
todo!()
let dummy_lp = nym_node_requests::api::v1::lewes_protocol::models::LewesProtocol {
enabled: false,
control_port: 123,
data_port: 345,
x25519: x25519_key,
kem_keys: dummy_kems,
};
let dummy_signed_lp = SignedLewesProtocol::new(dummy_lp, &signing_key).unwrap();
// sanity check
assert!(dummy_signed_lp.verify(&verification_key));
let converted = LewesProtocolDetailsV1::from(dummy_signed_lp);
assert!(converted.verify(&verification_key));
}
}
@@ -247,7 +247,7 @@ impl From<NymNodeDescriptionV1> for NymNodeDescriptionV2 {
#[cfg(test)]
pub fn mock_nym_node_description(seed: u64) -> NymNodeDescriptionV2 {
use crate::models::{LPHashFunction, LPSignatureScheme, LPKEM};
use nym_node_requests::api::v1::lewes_protocol::models::{LPHashFunction, LPKEM};
use nym_test_utils::helpers::{u64_seeded_rng, RngCore};
let mut rng = u64_seeded_rng(seed);
@@ -257,22 +257,33 @@ pub fn mock_nym_node_description(seed: u64) -> NymNodeDescriptionV2 {
// just reuse the same x25519 key for everything - this is just a data mock
let x25519 = x25519::KeyPair::new(&mut rng);
let mut kem_hashes_wrapper = std::collections::HashMap::new();
let mut signing_keys_hashes_wrapper = std::collections::HashMap::new();
let mut kem_hashes = std::collections::HashMap::new();
let mut signing_keys_hashes = std::collections::HashMap::new();
let mut dummy_kems = std::collections::BTreeMap::new();
for kem in [LPKEM::McEliece, LPKEM::McEliece] {
let mut kem_digests = std::collections::BTreeMap::new();
for (i, sf) in [
LPHashFunction::Blake3,
LPHashFunction::Shake128,
LPHashFunction::Shake256,
LPHashFunction::Sha256,
]
.iter()
.enumerate()
{
kem_digests.insert(*sf, hex::encode([((seed + i as u64) % 256) as u8; 32]));
}
dummy_kems.insert(kem, kem_digests);
}
kem_hashes.insert(
LPHashFunction::Sha256,
hex::encode([(seed % 256) as u8; 32]),
);
kem_hashes_wrapper.insert(LPKEM::X25519, kem_hashes);
signing_keys_hashes.insert(
LPHashFunction::Sha256,
hex::encode([(seed % 256) as u8; 32]),
);
signing_keys_hashes_wrapper.insert(LPSignatureScheme::Ed25519, signing_keys_hashes);
// make sure the serialisation stays the same and signature is still valid
let dummy_lp = nym_node_requests::api::v1::lewes_protocol::models::LewesProtocol {
enabled: false,
control_port: 123,
data_port: 345,
x25519: (*x25519.public_key()).into(),
kem_keys: dummy_kems,
};
let dummy_signed_lp =
nym_node_requests::api::SignedLewesProtocol::new(dummy_lp, ed25519.private_key()).unwrap();
NymNodeDescriptionV2 {
node_id: rng.next_u32(),
@@ -339,14 +350,7 @@ pub fn mock_nym_node_description(seed: u64) -> NymNodeDescriptionV2 {
metadata_port: 456,
public_key: x25519.public_key().to_base58_string(),
}),
lewes_protocol: Some(LewesProtocolDetailsV1 {
enabled: true,
control_port: 1234,
data_port: 2345,
x25519: *x25519.public_key(),
kem_keys: kem_hashes_wrapper,
signing_keys: signing_keys_hashes_wrapper,
}),
lewes_protocol: Some(dummy_signed_lp.into()),
mixnet_websockets: WebSocketsV2 {
ws_port: 9000,
wss_port: None,
+1
View File
@@ -22,6 +22,7 @@ hex.workspace = true
tracing.workspace = true
pnet_packet.workspace = true
rand.workspace = true
rand09.workspace = true
reqwest = { workspace = true, features = ["socks"] }
serde.workspace = true
serde_json.workspace = true
-10
View File
@@ -1,12 +1,9 @@
// Copyright 2026 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::common::nodes::TestedNodeLpDetails;
use nym_crypto::asymmetric::ed25519;
use nym_ip_packet_requests::v8::response::{
ControlResponse, DataResponse, InfoLevel, IpPacketResponse, IpPacketResponseData,
};
use nym_lp::peer::LpRemotePeer;
use nym_sdk::{
DebugConfig, NymApiTopologyProvider, NymApiTopologyProviderConfig, NymNetworkDetails,
TopologyProvider, mixnet::ReconstructedMessage,
@@ -15,13 +12,6 @@ use nym_topology::NymTopology;
use tracing::*;
use url::Url;
pub fn to_lp_remote_peer(identity: ed25519::PublicKey, data: TestedNodeLpDetails) -> LpRemotePeer {
LpRemotePeer::new(identity, data.x25519).with_key_digests(
data.expected_kem_key_hashes,
data.expected_signing_key_hashes,
)
}
pub fn mixnet_debug_config(
min_gateway_performance: Option<u8>,
ignore_egress_epoch_role: bool,
+38 -32
View File
@@ -3,25 +3,25 @@
use anyhow::{Context, anyhow, bail};
use nym_api_requests::models::{
AuthenticatorDetailsV1, DeclaredRolesV1, DescribedNodeTypeV1, HostInformationV1,
IpPacketRouterDetailsV1, NetworkRequesterDetailsV1, NymNodeDataV1,
OffsetDateTimeJsonSchemaWrapper, WebSocketsV1, WireguardDetailsV1,
AuthenticatorDetailsV2, DeclaredRolesV2, DescribedNodeTypeV2, HostInformationV2,
IpPacketRouterDetailsV2, NetworkRequesterDetailsV2, NymNodeDataV2,
OffsetDateTimeJsonSchemaWrapper, WebSocketsV2, WireguardDetailsV2,
};
use nym_authenticator_requests::AuthenticatorVersion;
use nym_bin_common::build_information::BinaryBuildInformationOwned;
use nym_crypto::asymmetric::x25519;
use nym_http_api_client::UserAgent;
use nym_kkt_ciphersuite::SignatureScheme;
use nym_kkt_ciphersuite::Ciphersuite;
use nym_kkt_ciphersuite::{KEM, KEMKeyDigests};
use nym_lp::peer::{DHPublicKey, LpRemotePeer};
use nym_network_defaults::DEFAULT_NYM_NODE_HTTP_PORT;
use nym_node_requests::api::client::NymNodeApiClientExt;
use nym_node_requests::api::v1::node::models::AuxiliaryDetails as NodeAuxiliaryDetails;
use nym_sdk::mixnet::NodeIdentity;
use nym_sdk::mixnet::Recipient;
use nym_validator_client::client::NymApiClientExt;
use nym_validator_client::models::NymNodeDescriptionV1;
use nym_validator_client::models::NymNodeDescriptionV2;
use rand::prelude::IteratorRandom;
use std::collections::HashMap;
use std::collections::{BTreeMap, HashMap};
use std::net::{IpAddr, SocketAddr};
use std::time::Duration;
use time::OffsetDateTime;
@@ -90,7 +90,7 @@ use url::Url;
#[derive(Clone)]
pub struct DirectoryNode {
described: NymNodeDescriptionV1,
described: NymNodeDescriptionV2,
}
impl DirectoryNode {
@@ -237,11 +237,11 @@ pub async fn query_gateway_by_ip(address: String) -> anyhow::Result<DirectoryNod
.await
.inspect_err(|e| error!("Failed to get wireguard information : {e}"))
.ok();
// let lp_result = client
// .get_lewes_protocol()
// .await
// .inspect_err(|e| error!("Failed to get LP information : {e}"))
// .ok();
let lp_result = client
.get_lewes_protocol()
.await
.inspect_err(|e| error!("Failed to get LP information : {e}"))
.ok();
// Check required fields
let host_info = host_info_result.context("Failed to get host information")?;
@@ -261,22 +261,22 @@ pub async fn query_gateway_by_ip(address: String) -> anyhow::Result<DirectoryNod
}
// Convert to our internal types
let network_requester: Option<NetworkRequesterDetailsV1> =
nr_result.map(|nr| NetworkRequesterDetailsV1 {
let network_requester: Option<NetworkRequesterDetailsV2> =
nr_result.map(|nr| NetworkRequesterDetailsV2 {
address: nr.address,
uses_exit_policy: false, // Field not availabe, to change if it becomes useful here
});
let ip_packet_router: Option<IpPacketRouterDetailsV1> =
ipr_result.map(|ipr| IpPacketRouterDetailsV1 {
let ip_packet_router: Option<IpPacketRouterDetailsV2> =
ipr_result.map(|ipr| IpPacketRouterDetailsV2 {
address: ipr.address,
});
let authenticator: Option<AuthenticatorDetailsV1> =
authenticator_result.map(|auth| AuthenticatorDetailsV1 {
let authenticator: Option<AuthenticatorDetailsV2> =
authenticator_result.map(|auth| AuthenticatorDetailsV2 {
address: auth.address,
});
#[allow(deprecated)]
let wireguard: Option<WireguardDetailsV1> =
wireguard_result.map(|wg| WireguardDetailsV1 {
let wireguard: Option<WireguardDetailsV2> =
wireguard_result.map(|wg| WireguardDetailsV2 {
port: wg.tunnel_port, // Use tunnel_port for deprecated port field
tunnel_port: wg.tunnel_port,
metadata_port: wg.metadata_port,
@@ -284,14 +284,14 @@ pub async fn query_gateway_by_ip(address: String) -> anyhow::Result<DirectoryNod
});
// Construct NymNodeData
let node_data = NymNodeDataV1 {
let node_data = NymNodeDataV2 {
last_polled: OffsetDateTimeJsonSchemaWrapper(OffsetDateTime::now_utc()),
host_information: HostInformationV1 {
host_information: HostInformationV2 {
ip_address: host_info.data.ip_address,
hostname: host_info.data.hostname,
keys: host_info.data.keys.into(),
},
declared_role: DeclaredRolesV1 {
declared_role: DeclaredRolesV2 {
mixnode: roles.mixnode_enabled,
entry: roles.gateway_enabled,
exit_nr: roles.network_requester_enabled,
@@ -314,17 +314,17 @@ pub async fn query_gateway_by_ip(address: String) -> anyhow::Result<DirectoryNod
ip_packet_router,
authenticator,
wireguard,
// lewes_protocol: lp_result.map(Into::into),
mixnet_websockets: WebSocketsV1 {
lewes_protocol: lp_result.map(Into::into),
mixnet_websockets: WebSocketsV2 {
ws_port: websockets.ws_port,
wss_port: websockets.wss_port,
},
};
// Create NymNodeDescription
let described = NymNodeDescriptionV1 {
let described = NymNodeDescriptionV2 {
node_id: 0, // We don't have a node_id from direct query
contract_node_type: DescribedNodeTypeV1::NymNode, // All new nodes are NymNode type
contract_node_type: DescribedNodeTypeV2::NymNode, // All new nodes are NymNode type
description: node_data,
};
@@ -361,7 +361,7 @@ impl NymApiDirectory {
debug!("Fetching all described nodes from nym-api...");
let described_nodes = api_client
.get_all_described_nodes()
.get_all_described_nodes_v2()
.await
.context("nym api query failure")?;
@@ -482,8 +482,14 @@ pub struct TestedNodeDetails {
#[derive(Debug, Clone)]
pub struct TestedNodeLpDetails {
pub address: SocketAddr,
pub expected_kem_key_hashes: HashMap<KEM, KEMKeyDigests>,
pub expected_signing_key_hashes: HashMap<SignatureScheme, KEMKeyDigests>,
pub x25519: x25519::PublicKey,
pub expected_kem_key_hashes: BTreeMap<KEM, KEMKeyDigests>,
pub x25519: DHPublicKey,
pub lp_version: u8,
pub ciphersuite: Ciphersuite,
}
impl TestedNodeLpDetails {
pub fn into_remote_peer(self) -> LpRemotePeer {
LpRemotePeer::new(self.x25519).with_key_digests(self.expected_kem_key_hashes)
}
}
+37 -27
View File
@@ -28,10 +28,12 @@ use nym_credentials_interface::{CredentialSpendingData, TicketType};
use nym_crypto::asymmetric::{ed25519, x25519};
use nym_ip_packet_client::IprClientConnect;
use nym_ip_packet_requests::{IpPair, codec::MultiIpPacketCodec};
use nym_lp::peer::DHKeyPair;
use nym_registration_client::{LpRegistrationClient, NestedLpSession};
use nym_sdk::NymNetworkDetails;
use nym_sdk::mixnet::{MixnetClient, MixnetClientBuilder, NodeIdentity, Recipient, Socks5};
use nym_topology::{HardcodedTopologyProvider, NymTopology};
use rand09::SeedableRng;
use std::{
net::{IpAddr, Ipv4Addr, Ipv6Addr},
sync::Arc,
@@ -163,23 +165,25 @@ pub async fn lp_registration_probe(
) -> anyhow::Result<LpProbeResults> {
let lp_address = gateway_lp_data.address;
let lp_version = gateway_lp_data.lp_version;
let peer = helpers::to_lp_remote_peer(gateway_identity, gateway_lp_data);
let lp_ciphersuite = gateway_lp_data.ciphersuite;
let peer = gateway_lp_data.into_remote_peer();
info!("Starting LP registration probe for gateway at {lp_address}");
let mut lp_outcome = LpProbeResults::default();
// Generate Ed25519 keypair for this connection (X25519 will be derived internally by LP)
let mut rng = rand::thread_rng();
let client_ed25519_keypair = std::sync::Arc::new(ed25519::KeyPair::new(&mut rng));
let mut rng09 = rand09::rngs::StdRng::from_os_rng();
let client_x25519_keypair = Arc::new(DHKeyPair::new(&mut rng09));
// Step 0: Derive X25519 keys from Ed25519 for the gateways
// Create LP registration client (uses Ed25519 keys directly, derives X25519 internally)
let mut client = LpRegistrationClient::<TcpStream>::new_with_default_config(
client_ed25519_keypair,
client_x25519_keypair,
peer,
lp_address,
lp_ciphersuite,
lp_version,
);
@@ -209,23 +213,13 @@ pub async fn lp_registration_probe(
let wg_keypair = nym_crypto::asymmetric::x25519::KeyPair::new(&mut rng);
// Convert gateway identity to ed25519 public key
let gateway_ed25519_pubkey = match nym_crypto::asymmetric::ed25519::PublicKey::from_bytes(
&gateway_identity.to_bytes(),
) {
Ok(key) => key,
Err(e) => {
let error_msg = format!("Failed to convert gateway identity: {}", e);
error!("{}", error_msg);
lp_outcome.error = Some(error_msg);
return Ok(lp_outcome);
}
};
let gateway_ed25519_pubkey = gateway_identity;
// Register using the new packet-per-connection API (returns GatewayData directly)
let ticket_type = TicketType::V1WireguardEntry;
let gateway_data = match client
.register_dvpn(
&mut rng,
&mut rng09,
&wg_keypair,
&gateway_ed25519_pubkey,
bandwidth_controller,
@@ -299,21 +293,26 @@ pub async fn wg_probe_lp(
let entry_lp_version = entry_lp_data.lp_version;
let exit_lp_version = exit_lp_data.lp_version;
let entry_lp_ciphersuite = entry_lp_data.ciphersuite;
let exit_lp_ciphersuite = exit_lp_data.ciphersuite;
info!("Starting LP-based WireGuard probe (entry→exit via forwarding)");
let mut wg_outcome = WgProbeResults::default();
// Generate Ed25519 keypairs for LP protocol
let mut rng = rand::thread_rng();
let entry_lp_keypair = Arc::new(ed25519::KeyPair::new(&mut rng));
let exit_lp_keypair = Arc::new(ed25519::KeyPair::new(&mut rng));
// Generate x25519 keypairs for LP protocol
let mut rng09 = rand09::rngs::StdRng::from_os_rng();
let entry_lp_keypair = Arc::new(DHKeyPair::new(&mut rng09));
let exit_lp_keypair = Arc::new(DHKeyPair::new(&mut rng09));
// Generate WireGuard keypairs for VPN registration
let mut rng = rand::rngs::OsRng;
let entry_wg_keypair = x25519::KeyPair::new(&mut rng);
let exit_wg_keypair = x25519::KeyPair::new(&mut rng);
let entry_peer = helpers::to_lp_remote_peer(entry_gateway.identity, entry_lp_data);
let exit_peer = helpers::to_lp_remote_peer(exit_gateway.identity, exit_lp_data);
let entry_peer = entry_lp_data.into_remote_peer();
let exit_peer = exit_lp_data.into_remote_peer();
// STEP 1: Establish outer LP session with entry gateway
// LpRegistrationClient uses packet-per-connection model - connect() is gone,
@@ -323,6 +322,7 @@ pub async fn wg_probe_lp(
entry_lp_keypair,
entry_peer,
entry_address,
entry_lp_ciphersuite,
entry_lp_version,
);
@@ -335,16 +335,26 @@ pub async fn wg_probe_lp(
// STEP 2: Use nested session to register with exit gateway via forwarding
info!("Registering with exit gateway via entry forwarding...");
let mut nested_session =
NestedLpSession::new(exit_address, exit_lp_keypair, exit_peer, exit_lp_version);
let mut nested_session = NestedLpSession::new(
exit_address,
exit_lp_keypair,
exit_peer,
exit_lp_ciphersuite,
exit_lp_version,
);
let exit_gateway_pubkey = exit_gateway.identity;
// Perform handshake and registration with exit gateway via forwarding
if let Err(err) = nested_session.perform_handshake(&mut entry_client).await {
error!("Failed to perform handshake with exit gateway: {err}");
return Ok(wg_outcome);
};
let exit_gateway_data = match nested_session
.handshake_and_register_dvpn(
.register_dvpn(
&mut entry_client,
&mut rng,
&mut rng09,
&exit_wg_keypair,
&exit_gateway_pubkey,
bandwidth_controller,
@@ -370,7 +380,7 @@ pub async fn wg_probe_lp(
// Use packet-per-connection register() which returns GatewayData directly
let entry_gateway_data = match entry_client
.register_dvpn(
&mut rng,
&mut rng09,
&entry_wg_keypair,
&entry_gateway_pubkey,
bandwidth_controller,
+1
View File
@@ -134,6 +134,7 @@ cargo_metadata = { workspace = true }
[dev-dependencies]
criterion = { workspace = true, features = ["async_tokio"] }
nym-test-utils = { workspace = true }
[features]
tokio-console = ["console-subscriber", "nym-task/tokio-tracing"]
+3 -4
View File
@@ -134,7 +134,6 @@ impl Display for ErrorResponse {
#[allow(deprecated)]
#[cfg(test)]
mod tests {
use super::*;
use crate::api::v1::node::models::{HostKeys, SphinxKey};
use nym_crypto::asymmetric::{ed25519, x25519};
@@ -243,7 +242,7 @@ mod tests {
#[test]
fn dummy_legacy_v3_signed_host_verification() {
let mut rng = rand_chacha::ChaCha20Rng::from_seed([0u8; 32]);
let mut rng = deterministic_rng();
let ed22519 = ed25519::KeyPair::new(&mut rng);
let x25519_sphinx = x25519::KeyPair::new(&mut rng);
let x25519_noise = x25519::KeyPair::new(&mut rng);
@@ -337,7 +336,7 @@ mod tests {
#[test]
fn dummy_legacy_v2_signed_host_verification() {
let mut rng = rand_chacha::ChaCha20Rng::from_seed([0u8; 32]);
let mut rng = deterministic_rng();
let ed22519 = ed25519::KeyPair::new(&mut rng);
let x25519_sphinx = x25519::KeyPair::new(&mut rng);
let x25519_noise = x25519::KeyPair::new(&mut rng);
@@ -426,7 +425,7 @@ mod tests {
#[test]
fn dummy_legacy_v1_signed_host_verification() {
let mut rng = rand_chacha::ChaCha20Rng::from_seed([0u8; 32]);
let mut rng = deterministic_rng();
let ed22519 = ed25519::KeyPair::new(&mut rng);
let x25519_sphinx = x25519::KeyPair::new(&mut rng);
+2 -5
View File
@@ -113,14 +113,11 @@ impl PemStorableKey for SphinxPrivateKey {
#[cfg(test)]
mod tests {
use super::*;
use rand::SeedableRng;
use rand_chacha::ChaCha20Rng;
use nym_test_utils::helpers::deterministic_rng;
#[test]
fn private_key_bytes_convertion() {
// Set up a deterministic RNG.
let seed = [42u8; 32];
let mut rng = ChaCha20Rng::from_seed(seed);
let mut rng = deterministic_rng();
let key = SphinxPrivateKey {
rotation_id: 42,
+1
View File
@@ -15,6 +15,7 @@ anyhow = { workspace = true }
bytes = { workspace = true }
clap = { workspace = true, features = ["derive"] }
rand = { workspace = true }
rand09 = { workspace = true }
rand_chacha = { workspace = true }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
+74 -68
View File
@@ -20,7 +20,8 @@ use nym_sphinx_anonymous_replies::requests::{AnonymousSenderTag, RepliableMessag
use nym_sphinx_anonymous_replies::{ReplySurb, SurbEncryptionKey};
use nym_sphinx_framing::codec::NymCodec;
use nym_sphinx_framing::packet::FramedNymPacket;
use rand_chacha::rand_core::SeedableRng;
use rand::SeedableRng;
use rand09::SeedableRng as SeedableRng09;
use rand_chacha::ChaCha8Rng;
use std::net::SocketAddr;
use std::sync::Arc;
@@ -33,7 +34,7 @@ use tracing::{debug, info, trace};
use crate::topology::{GatewayInfo, SpeedtestTopology};
use nym_ip_packet_requests::v8::request::IpPacketRequest;
use nym_lp::packet::version;
use nym_lp::peer::LpRemotePeer;
use nym_lp::peer::{DHKeyPair, LpRemotePeer};
use nym_sphinx::forwarding::packet::MixPacket;
/// Conv ID for KCP - hash of source and destination addresses
@@ -51,6 +52,8 @@ pub struct SpeedtestClient {
identity_keypair: Arc<ed25519::KeyPair>,
/// Client's x25519 encryption keypair (for SURBs)
encryption_keypair: Arc<x25519::KeyPair>,
/// Client's LP keypair
lp_keypair: Arc<DHKeyPair>,
/// Target gateway
gateway: GatewayInfo,
/// Network topology for routing
@@ -86,11 +89,14 @@ impl SpeedtestClient {
pub fn new(gateway: GatewayInfo, topology: Arc<SpeedtestTopology>) -> Self {
let identity_keypair = Arc::new(ed25519::KeyPair::new(&mut rand::rngs::OsRng));
let encryption_keypair = Arc::new(x25519::KeyPair::new(&mut rand::rngs::OsRng));
let mut rng09 = rand09::rngs::StdRng::from_os_rng();
let lp_keypair = DHKeyPair::new(&mut rng09);
let rng = ChaCha8Rng::from_entropy();
Self {
identity_keypair,
encryption_keypair,
lp_keypair: Arc::new(lp_keypair),
gateway,
topology,
socket: None,
@@ -118,16 +124,14 @@ impl SpeedtestClient {
self.gateway.lp_address
);
let gw_peer = LpRemotePeer::new(self.gateway.identity, self.gateway.identity.to_x25519()?)
.with_key_digests(
self.gateway.kem_key_hashes.clone(),
self.gateway.signing_key_hashes.clone(),
);
let gw_peer = LpRemotePeer::new(self.gateway.lp_key)
.with_key_digests(self.gateway.kem_key_hashes.clone());
let mut lp_client = LpRegistrationClient::<TcpStream>::new_with_default_config(
self.identity_keypair.clone(),
self.lp_keypair.clone(),
gw_peer,
self.gateway.lp_address,
self.gateway.ciphersuite,
self.gateway.lp_version,
);
@@ -165,16 +169,14 @@ impl SpeedtestClient {
self.gateway.lp_address
);
let gw_peer = LpRemotePeer::new(self.gateway.identity, self.gateway.identity.to_x25519()?)
.with_key_digests(
self.gateway.kem_key_hashes.clone(),
self.gateway.signing_key_hashes.clone(),
);
let gw_peer = LpRemotePeer::new(self.gateway.lp_key)
.with_key_digests(self.gateway.kem_key_hashes.clone());
let mut lp_client = LpRegistrationClient::new_with_default_config(
self.identity_keypair.clone(),
self.lp_keypair.clone(),
gw_peer,
self.gateway.lp_address,
self.gateway.ciphersuite,
self.gateway.lp_version,
);
@@ -440,61 +442,65 @@ impl SpeedtestClient {
if !self.has_lp_session() {
bail!("LP session not initialized - call init_lp_session() first");
}
let _ = payload;
let _ = num_surbs;
bail!("lp transport channel is not yet implemented");
let prepared = self.prepare_sphinx_fragments(payload, num_surbs).await?;
// Now get mutable references after prepare_sphinx_fragments is done
let lp_client = self.lp_client.as_mut().unwrap(); // safe: checked above
let socket = self.socket.as_ref().context("socket not initialized")?;
let lp_data_address = self.gateway.lp_data_address;
let mut total_sent = 0usize;
let fragment_count = prepared.fragments.len();
for fragment in prepared.fragments {
let nym_packet = NymPacket::sphinx_build(
false,
PacketSize::RegularPacket.payload_size(),
fragment.into_bytes(),
&prepared.route,
&prepared.destination,
&prepared.delays,
)?;
// Wrap in MixPacket v2: packet_type || key_rotation || next_hop || sphinx_data
let mix_packet = MixPacket::new(
prepared.first_hop_addr,
nym_packet,
PacketType::Mix,
SphinxKeyRotation::Unknown,
);
let mix_bytes = mix_packet
.into_v2_bytes()
.context("failed to serialize MixPacket")?;
// Wrap in LP for UDP data plane
let lp_packet = lp_client
.wrap_data(&mix_bytes)
.context("failed to wrap in LP")?;
// Send to gateway's LP data port (51264) with timeout
tokio::time::timeout(
Duration::from_secs(5),
socket.send_to(&lp_packet, lp_data_address),
)
.await
.context("UDP send timed out")?
.context("UDP send failed")?;
total_sent += lp_packet.len();
}
info!(
"Sent {} bytes via LP ({} fragments, {} SURBs) to {}",
total_sent, fragment_count, num_surbs, lp_data_address
);
Ok(prepared.encryption_keys)
// leave the code for future reference
// let prepared = self.prepare_sphinx_fragments(payload, num_surbs).await?;
//
// // Now get mutable references after prepare_sphinx_fragments is done
// let lp_client = self.lp_client.as_mut().unwrap(); // safe: checked above
// let socket = self.socket.as_ref().context("socket not initialized")?;
// let lp_data_address = self.gateway.lp_data_address;
//
// let mut total_sent = 0usize;
// let fragment_count = prepared.fragments.len();
//
// for fragment in prepared.fragments {
// let nym_packet = NymPacket::sphinx_build(
// false,
// PacketSize::RegularPacket.payload_size(),
// fragment.into_bytes(),
// &prepared.route,
// &prepared.destination,
// &prepared.delays,
// )?;
//
// // Wrap in MixPacket v2: packet_type || key_rotation || next_hop || sphinx_data
// let mix_packet = MixPacket::new(
// prepared.first_hop_addr,
// nym_packet,
// PacketType::Mix,
// SphinxKeyRotation::Unknown,
// );
//
// let mix_bytes = mix_packet
// .into_v2_bytes()
// .context("failed to serialize MixPacket")?;
//
// // Wrap in LP for UDP data plane
// let lp_packet = lp_client
// .wrap_data(&mix_bytes)
// .context("failed to wrap in LP")?;
//
// // Send to gateway's LP data port (51264) with timeout
// tokio::time::timeout(
// Duration::from_secs(5),
// socket.send_to(&lp_packet, lp_data_address),
// )
// .await
// .context("UDP send timed out")?
// .context("UDP send failed")?;
// total_sent += lp_packet.len();
// }
//
// info!(
// "Sent {} bytes via LP ({} fragments, {} SURBs) to {}",
// total_sent, fragment_count, num_surbs, lp_data_address
// );
//
// Ok(prepared.encryption_keys)
}
/// Receive UDP data with timeout
+6 -4
View File
@@ -10,13 +10,14 @@ use nym_api_requests::models::{LPHashFunction, LPKEM};
use nym_api_requests::nym_nodes::SkimmedNode;
use nym_crypto::asymmetric::ed25519;
use nym_http_api_client::UserAgent;
use nym_kkt_ciphersuite::{KEMKeyDigests, SignatureScheme, SigningKeyDigests, KEM};
use nym_kkt_ciphersuite::{Ciphersuite, KEMKeyDigests, SignatureScheme, KEM};
use nym_lp::peer::DHPublicKey;
use nym_sphinx_types::Node as SphinxNode;
use nym_topology::{NymRouteProvider, NymTopology, NymTopologyMetadata};
use nym_validator_client::nym_api::NymApiClientExt;
use rand::prelude::IteratorRandom;
use rand::{CryptoRng, Rng};
use std::collections::HashMap;
use std::collections::{BTreeMap, HashMap};
use std::net::SocketAddr;
use tracing::{debug, info};
use url::Url;
@@ -30,8 +31,9 @@ const LP_DATA_PORT: u16 = 51264;
#[derive(Debug, Clone)]
pub struct GatewayInfo {
pub identity: ed25519::PublicKey,
pub kem_key_hashes: HashMap<KEM, KEMKeyDigests>,
pub signing_key_hashes: HashMap<SignatureScheme, SigningKeyDigests>,
pub lp_key: DHPublicKey,
pub kem_key_hashes: BTreeMap<KEM, KEMKeyDigests>,
pub ciphersuite: Ciphersuite,
pub sphinx_key: nym_crypto::asymmetric::x25519::PublicKey,
/// Mix host (IP:port for Sphinx mixing)