clippy and formatting issues
This commit is contained in:
Generated
+3
-2
@@ -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| {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
@@ -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());
|
||||
|
||||
@@ -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()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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},
|
||||
};
|
||||
|
||||
@@ -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(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user