Compare commits

..

10 Commits

Author SHA1 Message Date
benedettadavico ecd678a849 ... 2026-01-26 18:52:36 +01:00
benedettadavico 6c6e473607 aaaa 2026-01-26 18:24:23 +01:00
benedettadavico d52d728ab8 loggggggging 2026-01-26 15:22:32 +01:00
benedetta davico b513a99498 Merge pull request #6364 from nymtech/ack-fix
bugfix: ack fix
2026-01-23 17:36:04 +01:00
benedettadavico b5d1e6a93f ack fix 2026-01-23 17:24:48 +01:00
Tommy Verrall b949d0fb01 Merge pull request #6348 from nymtech/cherry-pick/api-urls-oscypek
Cherry pick/api urls oscypek
2026-01-21 14:52:34 +01:00
jmwample 52c47a950e env feature locking to protect contracts 2026-01-21 12:45:23 +01:00
jmwample 377c22f283 minor fixes 2026-01-21 12:45:23 +01:00
jmwample 036ae5c6dc apply configured api urls via env 2026-01-21 12:45:22 +01:00
benedettadavico fb85de9ab6 bump versions 2026-01-16 10:12:01 +01:00
55 changed files with 2423 additions and 3257 deletions
Generated
+446 -757
View File
File diff suppressed because it is too large Load Diff
+2 -1
View File
@@ -256,7 +256,8 @@ ctr = "0.9.1"
cupid = "0.6.1"
curve25519-dalek = "4.1.3"
dashmap = "5.5.3"
defguard_wireguard_rs = "0.8.0"
# We want https://github.com/DefGuard/wireguard-rs/pull/64 , but there's no crates.io release being pushed out anymore
defguard_wireguard_rs = { git = "https://github.com/DefGuard/wireguard-rs.git", rev = "v0.4.7" }
digest = "0.10.7"
dirs = "6.0"
dotenvy = "0.15.6"
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-client"
version = "1.1.68"
version = "1.1.69"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
description = "Implementation of the Nym Client"
edition = "2021"
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-socks5-client"
version = "1.1.68"
version = "1.1.69"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address"
edition = "2021"
+3 -37
View File
@@ -20,7 +20,6 @@ pub use serde_helpers::*;
#[cfg(feature = "sphinx")]
use nym_sphinx_types::{DESTINATION_ADDRESS_LENGTH, DestinationAddressBytes};
use crate::asymmetric::x25519;
#[cfg(feature = "rand")]
use rand::{CryptoRng, Rng, RngCore};
#[cfg(feature = "serde")]
@@ -111,18 +110,6 @@ impl KeyPair {
index: fake_index(pub_bytes),
})
}
/// Converts this Ed25519 keypair to an X25519 keypair for ECDH.
///
/// Uses the standard ed25519→x25519 conversion via SHA-512 hash and clamping.
/// This is the same approach as libsodium's `crypto_sign_ed25519_sk_to_curve25519`.
///
/// # Returns
/// The converted X25519 keypair
pub fn to_x25519(&self) -> x25519::KeyPair {
let private_key = self.private_key.to_x25519();
x25519::KeyPair::from(private_key)
}
}
/// Reduces a byte slice into a u32 value by XOR-ing all its bytes into a 4-byte accumulator.
@@ -149,16 +136,6 @@ impl From<PrivateKey> for KeyPair {
}
}
impl From<(PrivateKey, PublicKey)> for KeyPair {
fn from((private_key, public_key): (PrivateKey, PublicKey)) -> Self {
KeyPair {
private_key,
public_key,
index: fake_index(public_key.to_bytes().as_ref()),
}
}
}
impl PemStorableKeyPair for KeyPair {
type PrivatePemKey = PrivateKey;
type PublicPemKey = PublicKey;
@@ -208,25 +185,14 @@ impl PublicKey {
}
/// Convert this public key to a byte array.
#[inline]
pub fn to_bytes(self) -> [u8; PUBLIC_KEY_LENGTH] {
self.0.to_bytes()
}
/// View this public key as a byte array.
#[inline]
pub fn as_bytes(&self) -> &[u8; PUBLIC_KEY_LENGTH] {
self.0.as_bytes()
}
#[inline]
pub fn from_bytes(b: &[u8]) -> Result<Self, Ed25519RecoveryError> {
Self::from_byte_array(b.try_into()?)
}
#[inline]
pub fn from_byte_array(b: &[u8; PUBLIC_KEY_LENGTH]) -> Result<Self, Ed25519RecoveryError> {
Ok(PublicKey(ed25519_dalek::VerifyingKey::from_bytes(b)?))
Ok(PublicKey(ed25519_dalek::VerifyingKey::from_bytes(
b.try_into()?,
)?))
}
pub fn to_base58_string(self) -> String {
+1 -45
View File
@@ -4,7 +4,6 @@
use base64::Engine;
use nym_pemstore::traits::{PemStorableKey, PemStorableKeyPair};
use std::fmt::{self, Debug, Display, Formatter};
use std::ops::Deref;
use std::str::FromStr;
use thiserror::Error;
use zeroize::{Zeroize, ZeroizeOnDrop};
@@ -57,15 +56,6 @@ pub struct KeyPair {
pub(crate) public_key: PublicKey,
}
impl Debug for KeyPair {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_struct("KeyPair")
.field("private_key", &"<redacted>")
.field("public_key", &self.public_key.to_base58_string())
.finish()
}
}
impl KeyPair {
#[cfg(feature = "rand")]
pub fn new<R: RngCore + CryptoRng>(rng: &mut R) -> Self {
@@ -103,15 +93,6 @@ impl From<PrivateKey> for KeyPair {
}
}
impl From<(PrivateKey, PublicKey)> for KeyPair {
fn from((private_key, public_key): (PrivateKey, PublicKey)) -> Self {
KeyPair {
private_key,
public_key,
}
}
}
impl PemStorableKeyPair for KeyPair {
type PrivatePemKey = PrivateKey;
type PublicPemKey = PublicKey;
@@ -135,13 +116,6 @@ impl PemStorableKeyPair for KeyPair {
#[derive(PartialEq, Eq, Hash, Copy, Clone)]
pub struct PublicKey(x25519_dalek::PublicKey);
impl Deref for PublicKey {
type Target = x25519_dalek::PublicKey;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl Display for PublicKey {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
Display::fmt(&self.to_base58_string(), f)
@@ -155,17 +129,14 @@ impl Debug for PublicKey {
}
impl PublicKey {
#[inline]
pub fn to_bytes(self) -> [u8; PUBLIC_KEY_SIZE] {
*self.0.as_bytes()
}
#[inline]
pub fn as_bytes(&self) -> &[u8; PUBLIC_KEY_SIZE] {
self.0.as_bytes()
}
#[inline]
pub fn from_bytes(b: &[u8]) -> Result<Self, KeyRecoveryError> {
if b.len() != PUBLIC_KEY_SIZE {
return Err(KeyRecoveryError::InvalidSizePublicKey {
@@ -175,12 +146,7 @@ impl PublicKey {
}
let mut bytes = [0; PUBLIC_KEY_SIZE];
bytes.copy_from_slice(&b[..PUBLIC_KEY_SIZE]);
Ok(Self::from_byte_array(&bytes))
}
#[inline]
pub fn from_byte_array(b: &[u8; PUBLIC_KEY_SIZE]) -> Self {
Self(x25519_dalek::PublicKey::from(*b))
Ok(Self(x25519_dalek::PublicKey::from(bytes)))
}
pub fn to_base58_string(self) -> String {
@@ -208,12 +174,6 @@ impl PublicKey {
}
}
impl From<[u8; PUBLIC_KEY_SIZE]> for PublicKey {
fn from(bytes: [u8; PUBLIC_KEY_SIZE]) -> Self {
PublicKey(x25519_dalek::PublicKey::from(bytes))
}
}
impl FromStr for PublicKey {
type Err = KeyRecoveryError;
@@ -336,10 +296,6 @@ impl PrivateKey {
Ok(Self(x25519_dalek::StaticSecret::from(bytes)))
}
pub fn from_secret(secret: [u8; PRIVATE_KEY_SIZE]) -> Self {
Self(x25519_dalek::StaticSecret::from(secret))
}
pub fn to_base58_string(&self) -> String {
bs58::encode(&self.to_bytes()).into_string()
}
+3 -1
View File
@@ -12,6 +12,8 @@ dotenvy = { workspace = true, optional = true }
log = { workspace = true, optional = true }
schemars = { workspace = true, features = ["preserve_order"], optional = true }
serde = { workspace = true, features = ["derive"], optional = true }
serde_json = {workspace = true, optional = true }
tracing = {workspace = true, optional = true }
url = { workspace = true, optional = true }
utoipa = { workspace = true, optional = true }
@@ -20,7 +22,7 @@ utoipa = { workspace = true, optional = true }
[features]
default = ["env", "network"]
env = ["dotenvy", "log"]
env = ["dotenvy", "log", "serde_json", "tracing"]
network = ["schemars", "serde", "url"]
utoipa = [ "dep:utoipa" ]
+12
View File
@@ -72,6 +72,13 @@ pub const NYM_VPN_APIS: &[ApiUrlConst] = &[
},
];
#[cfg(feature = "env")]
fn serialize_api_urls(urls: &[ApiUrlConst]) -> String {
serde_json::to_string(urls)
.inspect_err(|e| tracing::warn!("failed to serialize nym_api_urls for env: {e}"))
.unwrap_or_default()
}
// I'm making clippy mad on purpose, because that url HAS TO be updated and deployed before merging
pub const EXIT_POLICY_URL: &str =
"https://nymtech.net/.wellknown/network-requester/exit-policy.txt";
@@ -162,9 +169,11 @@ pub fn export_to_env() {
);
set_var_to_default(var_names::NYXD, NYXD_URL);
set_var_to_default(var_names::NYM_API, NYM_API);
set_var_to_default(var_names::NYM_APIS, &serialize_api_urls(NYM_APIS));
set_var_to_default(var_names::NYXD_WEBSOCKET, NYXD_WS);
set_var_to_default(var_names::EXIT_POLICY_URL, EXIT_POLICY_URL);
set_var_to_default(var_names::NYM_VPN_API, NYM_VPN_API);
set_var_to_default(var_names::NYM_VPN_APIS, &serialize_api_urls(NYM_VPN_APIS));
set_var_to_default(
var_names::UPGRADE_MODE_ATTESTATION_URL,
UPGRADE_MODE_ATTESTATION_URL,
@@ -211,6 +220,9 @@ pub fn export_to_env_if_not_set() {
);
set_var_conditionally_to_default(var_names::NYXD, NYXD_URL);
set_var_conditionally_to_default(var_names::NYM_API, NYM_API);
set_var_conditionally_to_default(var_names::NYM_APIS, &serialize_api_urls(NYM_APIS));
set_var_conditionally_to_default(var_names::NYM_VPN_API, NYM_VPN_API);
set_var_conditionally_to_default(var_names::NYM_VPN_APIS, &serialize_api_urls(NYM_VPN_APIS));
set_var_conditionally_to_default(var_names::NYXD_WEBSOCKET, NYXD_WS);
set_var_conditionally_to_default(var_names::EXIT_POLICY_URL, EXIT_POLICY_URL);
set_var_conditionally_to_default(
+51 -13
View File
@@ -7,6 +7,13 @@ use serde::{Deserialize, Serialize};
use std::ops::Not;
use url::Url;
#[cfg(feature = "env")]
use crate::var_names;
#[cfg(feature = "env")]
use std::env::{VarError, var};
#[cfg(feature = "env")]
use std::ffi::OsStr;
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize, JsonSchema)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub struct ChainDetails {
@@ -55,7 +62,7 @@ pub struct ApiUrl {
pub front_hosts: Option<Vec<String>>,
}
#[derive(Copy, Clone)]
#[derive(Copy, Clone, Debug, Serialize)]
pub struct ApiUrlConst<'a> {
pub url: &'a str,
pub front_hosts: Option<&'a [&'a str]>,
@@ -106,10 +113,6 @@ impl NymNetworkDetails {
#[cfg(feature = "env")]
pub fn new_from_env() -> Self {
use crate::var_names;
use std::env::{VarError, var};
use std::ffi::OsStr;
fn get_optional_env<K: AsRef<OsStr>>(env: K) -> Option<String> {
match var(env) {
Ok(var) => {
@@ -125,6 +128,11 @@ impl NymNetworkDetails {
}
let nym_api = var(var_names::NYM_API).expect("nym api not set");
let nym_api_urls = try_parse_api_urls(var_names::NYM_APIS).unwrap_or(vec![ApiUrl {
url: nym_api.clone(),
front_hosts: None,
}]);
let nym_vpn_api_urls = try_parse_api_urls(var_names::NYM_VPN_APIS);
NymNetworkDetails::new_empty()
.with_network_name(var(var_names::NETWORK_NAME).expect("network name not set"))
@@ -161,10 +169,8 @@ impl NymNetworkDetails {
.with_multisig_contract(get_optional_env(var_names::MULTISIG_CONTRACT_ADDRESS))
.with_coconut_dkg_contract(get_optional_env(var_names::COCONUT_DKG_CONTRACT_ADDRESS))
.with_nym_vpn_api_url(get_optional_env(var_names::NYM_VPN_API))
.with_nym_api_urls(Some(vec![ApiUrl {
url: nym_api,
front_hosts: None,
}]))
.with_nym_vpn_api_urls(nym_vpn_api_urls)
.with_nym_api_urls(nym_api_urls)
}
pub fn new_mainnet() -> Self {
@@ -218,6 +224,9 @@ impl NymNetworkDetails {
}
}
unsafe {
let nym_api_urls = self.nym_api_urls();
let nym_vpn_api_urls = self.nym_vpn_api_urls();
set_var(var_names::NETWORK_NAME, self.network_name);
set_var(var_names::BECH32_PREFIX, self.chain_details.bech32_account_prefix);
@@ -243,9 +252,10 @@ impl NymNetworkDetails {
set_optional_var(var_names::COCONUT_DKG_CONTRACT_ADDRESS, self.contracts.coconut_dkg_contract_address);
set_optional_var(var_names::NYM_VPN_API, self.nym_vpn_api_url);
set_optional_var(var_names::NYM_VPN_APIS, nym_vpn_api_urls);
set_optional_var(var_names::NYM_APIS, nym_api_urls);
}
}
pub fn default_gas_price_amount(&self) -> f64 {
@@ -355,8 +365,14 @@ impl NymNetworkDetails {
}
#[must_use]
pub fn with_nym_api_urls(mut self, urls: Option<Vec<ApiUrl>>) -> Self {
self.nym_api_urls = urls;
pub fn with_nym_api_urls(mut self, urls: Vec<ApiUrl>) -> Self {
self.nym_api_urls = Some(urls);
self
}
#[must_use]
pub fn with_nym_vpn_api_urls(mut self, urls: Option<Vec<ApiUrl>>) -> Self {
self.nym_vpn_api_urls = urls;
self
}
@@ -366,6 +382,28 @@ impl NymNetworkDetails {
.expect("the provided nym-vpn api url is invalid!")
})
}
#[cfg(feature = "env")]
fn nym_api_urls(&self) -> Option<String> {
serde_json::to_string(self.nym_api_urls.as_deref()?)
.inspect_err(|e| tracing::warn!("failed to serialize nym_api_urls for env: {e}"))
.ok()
}
#[cfg(feature = "env")]
fn nym_vpn_api_urls(&self) -> Option<String> {
serde_json::to_string(self.nym_vpn_api_urls.as_deref()?)
.inspect_err(|e| tracing::warn!("failed to serialize nym_vpn_api_urls for env: {e}"))
.ok()
}
}
#[cfg(feature = "env")]
fn try_parse_api_urls(k: impl AsRef<OsStr>) -> Option<Vec<ApiUrl>> {
let raw = var(k).ok()?;
serde_json::from_str(&raw)
.inspect_err(|e| tracing::warn!("failed to parse api urls from env \"{raw:?}\": {e}"))
.ok()
}
#[derive(Debug, Copy, Serialize, Deserialize, Clone, PartialEq, Eq)]
+2
View File
@@ -21,9 +21,11 @@ pub const COCONUT_DKG_CONTRACT_ADDRESS: &str = "COCONUT_DKG_CONTRACT_ADDRESS";
pub const REWARDING_VALIDATOR_ADDRESS: &str = "REWARDING_VALIDATOR_ADDRESS";
pub const NYXD: &str = "NYXD";
pub const NYM_API: &str = "NYM_API";
pub const NYM_APIS: &str = "NYM_APIS";
pub const NYXD_WEBSOCKET: &str = "NYXD_WS";
pub const EXIT_POLICY_URL: &str = "EXIT_POLICY";
pub const NYM_VPN_API: &str = "NYM_VPN_API";
pub const NYM_VPN_APIS: &str = "NYM_VPN_APIS";
pub const CLIENT_STATS_COLLECTION_PROVIDER: &str = "CLIENT_STATS_COLLECTION_PROVIDER";
pub const UPGRADE_MODE_ATTESTATION_URL: &str = "UPGRADE_MODE_ATTESTATION_URL";
pub const UPGRADE_MODE_ATTESTER_ED25519_BS58_PUBKEY: &str = "UPGRADE_MODE_ATTESTER_ED25519_PUBKEY";
+3 -8
View File
@@ -8,29 +8,24 @@ license.workspace = true
[dependencies]
blake3 = { workspace = true }
thiserror = { workspace = true }
num_enum = { workspace = true }
# internal
nym-crypto = { path = "../crypto", features = ["asymmetric", "serde"] }
libcrux-traits = { git = "https://github.com/cryspen/libcrux" }
libcrux-kem = { git = "https://github.com/cryspen/libcrux" }
libcrux-psq = { git = "https://github.com/cryspen/libcrux", features = ["test-utils"] }
libcrux-sha3 = { git = "https://github.com/cryspen/libcrux" }
libcrux-ml-kem = { git = "https://github.com/cryspen/libcrux" }
libcrux-ecdh = { git = "https://github.com/cryspen/libcrux", features = ["codec"] }
libcrux-chacha20poly1305 = { git = "https://github.com/cryspen/libcrux" }
#rand = "0.9.2"
rand = "0.9.2"
zeroize = { workspace = true, features = ["zeroize_derive"] }
classic-mceliece-rust = { git = "https://github.com/georgio/classic-mceliece-rust", features = ["mceliece460896f", "zeroize"] }
[dev-dependencies]
rand_chacha = "0.9.0"
anyhow = { workspace = true }
criterion = { workspace = true }
[[bench]]
name = "benches"
harness = false
+82 -40
View File
@@ -48,7 +48,6 @@ pub fn kkt_benchmark(c: &mut Criterion) {
let mut secret_responder: [u8; 32] = [0u8; 32];
rng.fill_bytes(&mut secret_responder);
let responder_ed25519_keypair = ed25519::KeyPair::from_secret(secret_responder, 1);
for kem in [KEM::MlKem768, KEM::XWing, KEM::X25519, KEM::McEliece] {
for hash_function in [
@@ -105,7 +104,10 @@ pub fn kkt_benchmark(c: &mut Criterion) {
// Anonymous Initiator, OneWay
{
c.bench_function(
&format!("{kem}, {hash_function} | Anonymous Initiator: Generate Request",),
&format!(
"{}, {} | Anonymous Initiator: Generate Request",
kem, hash_function
),
|b| {
b.iter(|| anonymous_initiator_process(&mut rng, ciphersuite).unwrap());
},
@@ -116,7 +118,8 @@ pub fn kkt_benchmark(c: &mut Criterion) {
c.bench_function(
&format!(
"{kem}, {hash_function} | Anonymous Initiator: Encode Frame - Request",
"{}, {} | Anonymous Initiator: Encode Frame - Request",
kem, hash_function
),
|b| b.iter(|| i_frame.to_bytes()),
);
@@ -125,7 +128,8 @@ pub fn kkt_benchmark(c: &mut Criterion) {
c.bench_function(
&format!(
"{kem}, {hash_function} | Anonymous Initiator: Decode Frame - Request",
"{}, {} | Anonymous Initiator: Decode Frame - Request",
kem, hash_function
),
|b| b.iter(|| KKTFrame::from_bytes(&i_frame_bytes).unwrap()),
);
@@ -134,7 +138,8 @@ pub fn kkt_benchmark(c: &mut Criterion) {
c.bench_function(
&format!(
"{kem}, {hash_function} | Anonymous Initiator: Responder Ingest Frame",
"{}, {} | Anonymous Initiator: Responder Ingest Frame",
kem, hash_function
),
|b| {
b.iter(|| {
@@ -148,13 +153,14 @@ pub fn kkt_benchmark(c: &mut Criterion) {
c.bench_function(
&format!(
"{kem}, {hash_function} | Anonymous Initiator: Responder Generate Response",
"{}, {} | Anonymous Initiator: Responder Generate Response",
kem, hash_function
),
|b| {
b.iter(|| {
responder_process(
&mut r_context,
i_frame_r.session_id(),
i_frame_r.session_id_ref(),
responder_ed25519_keypair.private_key(),
&responder_kem_public_key,
)
@@ -164,7 +170,7 @@ pub fn kkt_benchmark(c: &mut Criterion) {
);
let r_frame = responder_process(
&mut r_context,
i_frame_r.session_id(),
i_frame_r.session_id_ref(),
responder_ed25519_keypair.private_key(),
&responder_kem_public_key,
)
@@ -172,23 +178,26 @@ pub fn kkt_benchmark(c: &mut Criterion) {
c.bench_function(
&format!(
"{kem}, {hash_function} | Anonymous Initiator: Responder Encode Frame",
"{}, {} | Anonymous Initiator: Responder Encode Frame",
kem, hash_function
),
|b| b.iter(|| r_frame.to_bytes()),
);
let r_bytes = r_frame.to_bytes();
c.bench_function(
&format!(
"{kem}, {hash_function} | Anonymous Initiator: Initiator Ingest Response",
"{}, {} | Anonymous Initiator: Initiator Ingest Response",
kem, hash_function
),
|b| {
b.iter(|| {
initiator_ingest_response(
&mut i_context,
&r_frame,
&r_frame.context().unwrap(),
responder_ed25519_keypair.public_key(),
&r_dir_hash,
&r_bytes,
)
.unwrap()
});
@@ -197,10 +206,9 @@ pub fn kkt_benchmark(c: &mut Criterion) {
let obtained_key = initiator_ingest_response(
&mut i_context,
&r_frame,
&r_frame.context().unwrap(),
responder_ed25519_keypair.public_key(),
&r_dir_hash,
&r_bytes,
)
.unwrap();
@@ -218,7 +226,10 @@ pub fn kkt_benchmark(c: &mut Criterion) {
.unwrap();
c.bench_function(
&format!("{kem}, {hash_function} | Initiator OneWay: Generate Request",),
&format!(
"{}, {} | Initiator OneWay: Generate Request",
kem, hash_function
),
|b| {
b.iter(|| {
initiator_process(
@@ -234,21 +245,30 @@ pub fn kkt_benchmark(c: &mut Criterion) {
);
c.bench_function(
&format!("{kem}, {hash_function} | Initiator OneWay: Encode Frame - Request",),
&format!(
"{}, {} | Initiator OneWay: Encode Frame - Request",
kem, hash_function
),
|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",),
&format!(
"{}, {} | Initiator OneWay: Decode Frame - Request",
kem, hash_function
),
|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",),
&format!(
"{}, {} | Initiator OneWay: Responder Ingest Frame",
kem, hash_function
),
|b| {
b.iter(|| {
responder_ingest_message(
@@ -274,13 +294,14 @@ pub fn kkt_benchmark(c: &mut Criterion) {
c.bench_function(
&format!(
"{kem}, {hash_function} | Initiator OneWay: Responder Generate Response",
"{}, {} | Initiator OneWay: Responder Generate Response",
kem, hash_function
),
|b| {
b.iter(|| {
responder_process(
&mut r_context,
i_frame_r.session_id(),
i_frame_r.session_id_ref(),
responder_ed25519_keypair.private_key(),
&responder_kem_public_key,
)
@@ -291,31 +312,36 @@ pub fn kkt_benchmark(c: &mut Criterion) {
let r_frame = responder_process(
&mut r_context,
i_frame_r.session_id(),
i_frame_r.session_id_ref(),
responder_ed25519_keypair.private_key(),
&responder_kem_public_key,
)
.unwrap();
c.bench_function(
&format!("{kem}, {hash_function} | Initiator OneWay: Responder Encode Frame",),
&format!(
"{}, {} | Initiator OneWay: Responder Encode Frame",
kem, hash_function
),
|b| {
b.iter(|| r_frame.to_bytes());
},
);
let r_bytes = r_frame.to_bytes();
c.bench_function(
&format!(
"{kem}, {hash_function} | Initiator OneWay: Initiator Ingest Response",
"{}, {} | Initiator OneWay: Initiator Ingest Response",
kem, hash_function
),
|b| {
b.iter(|| {
initiator_ingest_response(
&mut i_context,
&r_frame,
&r_frame.context().unwrap(),
responder_ed25519_keypair.public_key(),
&r_dir_hash,
&r_bytes,
)
.unwrap()
});
@@ -324,10 +350,9 @@ pub fn kkt_benchmark(c: &mut Criterion) {
let i_obtained_key = initiator_ingest_response(
&mut i_context,
&r_frame,
&r_frame.context().unwrap(),
responder_ed25519_keypair.public_key(),
&r_dir_hash,
&r_bytes,
)
.unwrap();
@@ -337,7 +362,10 @@ pub fn kkt_benchmark(c: &mut Criterion) {
// Initiator, Mutual
{
c.bench_function(
&format!("{kem}, {hash_function} | Initiator Mutual: Generate Request",),
&format!(
"{}, {} | Initiator Mutual: Generate Request",
kem, hash_function
),
|b| {
b.iter(|| {
initiator_process(
@@ -362,7 +390,10 @@ pub fn kkt_benchmark(c: &mut Criterion) {
.unwrap();
c.bench_function(
&format!("{kem}, {hash_function} | Initiator Mutual: Encode Frame - Request",),
&format!(
"{}, {} | Initiator Mutual: Encode Frame - Request",
kem, hash_function
),
|b| {
b.iter(|| i_frame.to_bytes());
},
@@ -371,7 +402,10 @@ pub fn kkt_benchmark(c: &mut Criterion) {
let i_frame_bytes = i_frame.to_bytes();
c.bench_function(
&format!("{kem}, {hash_function} | Initiator Mutual: Decode Frame - Request",),
&format!(
"{}, {} | Initiator Mutual: Decode Frame - Request",
kem, hash_function
),
|b| {
b.iter(|| KKTFrame::from_bytes(&i_frame_bytes).unwrap());
},
@@ -380,7 +414,10 @@ pub fn kkt_benchmark(c: &mut Criterion) {
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",),
&format!(
"{}, {} | Initiator Mutual: Responder Ingest Frame",
kem, hash_function
),
|b| {
b.iter(|| {
responder_ingest_message(
@@ -406,13 +443,14 @@ pub fn kkt_benchmark(c: &mut Criterion) {
c.bench_function(
&format!(
"{kem}, {hash_function} | Initiator Mutual: Responder Generate Response",
"{}, {} | Initiator Mutual: Responder Generate Response",
kem, hash_function
),
|b| {
b.iter(|| {
responder_process(
&mut r_context,
i_frame_r.session_id(),
i_frame_r.session_id_ref(),
responder_ed25519_keypair.private_key(),
&responder_kem_public_key,
)
@@ -423,14 +461,17 @@ pub fn kkt_benchmark(c: &mut Criterion) {
let r_frame = responder_process(
&mut r_context,
i_frame_r.session_id(),
i_frame_r.session_id_ref(),
responder_ed25519_keypair.private_key(),
&responder_kem_public_key,
)
.unwrap();
c.bench_function(
&format!("{kem}, {hash_function} | Initiator Mutual: Responder Encode Frame",),
&format!(
"{}, {} | Initiator Mutual: Responder Encode Frame",
kem, hash_function
),
|b| {
b.iter(|| {
r_frame.to_bytes();
@@ -438,18 +479,20 @@ pub fn kkt_benchmark(c: &mut Criterion) {
},
);
let r_bytes = r_frame.to_bytes();
c.bench_function(
&format!(
"{kem}, {hash_function} | Initiator Mutual: Initiator Ingest Response",
"{}, {} | Initiator Mutual: Initiator Ingest Response",
kem, hash_function
),
|b| {
b.iter(|| {
initiator_ingest_response(
&mut i_context,
&r_frame,
&r_frame.context().unwrap(),
responder_ed25519_keypair.public_key(),
&r_dir_hash,
&r_bytes,
)
.unwrap()
});
@@ -458,10 +501,9 @@ pub fn kkt_benchmark(c: &mut Criterion) {
let obtained_key = initiator_ingest_response(
&mut i_context,
&r_frame,
&r_frame.context().unwrap(),
responder_ed25519_keypair.public_key(),
&r_dir_hash,
&r_bytes,
)
.unwrap();
+55 -45
View File
@@ -8,12 +8,12 @@ use nym_crypto::asymmetric::ed25519;
use crate::error::KKTError;
pub const HASH_LEN_256: usize = 32;
pub const HASH_LEN_256: u8 = 32;
pub const CIPHERSUITE_ENCODING_LEN: usize = 4;
pub const CURVE25519_KEY_LEN: usize = 32;
#[derive(Clone, Copy, Debug, PartialEq)]
#[derive(Clone, Copy, Debug)]
pub enum HashFunction {
Blake3,
SHAKE128,
@@ -87,7 +87,7 @@ impl<'a> EncapsulationKey<'a> {
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
#[derive(Clone, Copy, Debug)]
pub enum SignatureScheme {
Ed25519,
}
@@ -99,7 +99,7 @@ impl Display for SignatureScheme {
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
#[derive(Clone, Copy, Debug)]
pub enum KEM {
MlKem768,
XWing,
@@ -118,7 +118,7 @@ impl Display for KEM {
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
#[derive(Clone, Copy, Debug)]
pub struct Ciphersuite {
hash_function: HashFunction,
signature_scheme: SignatureScheme,
@@ -172,7 +172,7 @@ impl Ciphersuite {
l
}
}
None => HASH_LEN_256 as u8,
None => HASH_LEN_256,
};
Ok(Self {
hash_function,
@@ -203,7 +203,7 @@ impl Ciphersuite {
},
})
}
pub fn encode(&self) -> [u8; CIPHERSUITE_ENCODING_LEN] {
pub fn encode(&self) -> [u8; 4] {
// [kem, hash, hashlen, sig]
[
match self.kem {
@@ -218,8 +218,8 @@ impl Ciphersuite {
HashFunction::SHAKE128 => 2,
HashFunction::SHA256 => 3,
},
match self.hash_length as usize {
HASH_LEN_256 => 0u8,
match self.hash_length {
HASH_LEN_256 => 0,
_ => self.hash_length,
},
match self.signature_scheme {
@@ -227,45 +227,55 @@ impl Ciphersuite {
},
]
}
pub fn decode(encoding: [u8; CIPHERSUITE_ENCODING_LEN]) -> Result<Self, KKTError> {
let kem = match encoding[0] {
0 => KEM::XWing,
1 => KEM::MlKem768,
2 => KEM::McEliece,
255 => KEM::X25519,
_ => {
return Err(KKTError::CiphersuiteDecodingError {
info: format!("Undefined KEM: {}", encoding[0]),
});
}
};
let hash_function = match encoding[1] {
0 => HashFunction::Blake3,
1 => HashFunction::SHAKE256,
2 => HashFunction::SHAKE128,
3 => HashFunction::SHA256,
_ => {
return Err(KKTError::CiphersuiteDecodingError {
info: format!("Undefined Hash Function: {}", encoding[1]),
});
}
};
pub fn decode(encoding: &[u8]) -> Result<Self, KKTError> {
if encoding.len() == 4 {
let kem = match encoding[0] {
0 => KEM::XWing,
1 => KEM::MlKem768,
2 => KEM::McEliece,
255 => KEM::X25519,
_ => {
return Err(KKTError::CiphersuiteDecodingError {
info: format!("Undefined KEM: {}", encoding[0]),
});
}
};
let hash_function = match encoding[1] {
0 => HashFunction::Blake3,
1 => HashFunction::SHAKE256,
2 => HashFunction::SHAKE128,
3 => HashFunction::SHA256,
_ => {
return Err(KKTError::CiphersuiteDecodingError {
info: format!("Undefined Hash Function: {}", encoding[1]),
});
}
};
let custom_hash_length = match encoding[2] {
0 => None,
_ => Some(encoding[2]),
};
let custom_hash_length = match encoding[2] {
0 => None,
_ => Some(encoding[2]),
};
let signature_scheme = match encoding[3] {
0 => SignatureScheme::Ed25519,
_ => {
return Err(KKTError::CiphersuiteDecodingError {
info: format!("Undefined Signature Scheme: {}", encoding[3]),
});
}
};
let signature_scheme = match encoding[3] {
0 => SignatureScheme::Ed25519,
_ => {
return Err(KKTError::CiphersuiteDecodingError {
info: format!("Undefined Signature Scheme: {}", encoding[3]),
});
}
};
Self::resolve_ciphersuite(kem, hash_function, signature_scheme, custom_hash_length)
Self::resolve_ciphersuite(kem, hash_function, signature_scheme, custom_hash_length)
} else {
Err(KKTError::CiphersuiteDecodingError {
info: format!(
"Incorrect Encoding Length: actual: {} != expected: {}",
encoding.len(),
CIPHERSUITE_ENCODING_LEN
),
})
}
}
}
+117 -100
View File
@@ -1,25 +1,22 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::ciphersuite::CIPHERSUITE_ENCODING_LEN;
use crate::{KKT_VERSION, ciphersuite::Ciphersuite, error::KKTError, frame::KKT_SESSION_ID_LEN};
use num_enum::{IntoPrimitive, TryFromPrimitive};
use std::fmt::Display;
use crate::{KKT_VERSION, ciphersuite::Ciphersuite, error::KKTError, frame::KKT_SESSION_ID_LEN};
pub const KKT_CONTEXT_LEN: usize = 7;
// bitmask used: 0b1110_0000
#[derive(Clone, Copy, PartialEq, Debug, IntoPrimitive, TryFromPrimitive)]
#[repr(u8)]
#[derive(Clone, Copy, PartialEq, Debug)]
pub enum KKTStatus {
Ok = 0b0000_0000,
InvalidRequestFormat = 0b0010_0000,
InvalidResponseFormat = 0b0100_0000,
InvalidSignature = 0b0110_0000,
UnsupportedCiphersuite = 0b1000_0000,
UnsupportedKKTVersion = 0b1010_0000,
InvalidKey = 0b1100_0000,
Timeout = 0b1110_0000,
Ok,
InvalidRequestFormat,
InvalidResponseFormat,
InvalidSignature,
UnsupportedCiphersuite,
UnsupportedKKTVersion,
InvalidKey,
Timeout,
}
impl Display for KKTStatus {
@@ -36,25 +33,20 @@ impl Display for KKTStatus {
})
}
}
// bitmask used: 0b0000_0011
#[derive(Clone, Copy, PartialEq, Debug, IntoPrimitive, TryFromPrimitive)]
#[repr(u8)]
#[derive(Clone, Copy, PartialEq, Debug)]
pub enum KKTRole {
Initiator = 0b0000_0000,
Responder = 0b0000_0001,
AnonymousInitiator = 0b0000_0010,
Initiator,
AnonymousInitiator,
Responder,
}
// bitmask used: 0b0001_1100
#[derive(Clone, Copy, PartialEq, Debug, IntoPrimitive, TryFromPrimitive)]
#[repr(u8)]
#[derive(Clone, Copy, PartialEq, Debug)]
pub enum KKTMode {
OneWay = 0b0000_0000,
Mutual = 0b0000_0100,
OneWay,
Mutual,
}
#[derive(Copy, Clone, Debug, PartialEq)]
#[derive(Copy, Clone, Debug)]
pub struct KKTContext {
version: u8,
message_sequence: u8,
@@ -135,14 +127,11 @@ impl KKTContext {
}
}
pub const fn header_len(&self) -> usize {
pub fn header_len(&self) -> usize {
KKT_CONTEXT_LEN
}
pub const fn session_id_len(&self) -> usize {
// note: if anyone decides to update this function and changes the constant value,
// you will have to adjust encoding/decoding functions
pub fn session_id_len(&self) -> usize {
// match self.role {
// KKTRole::Initiator | KKTRole::Responder => SESSION_ID_LENGTH,
// It doesn't make sense to send a session_id if we send messages in the clear
@@ -155,87 +144,115 @@ impl KKTContext {
self.body_len() + self.signature_len() + self.header_len() + self.session_id_len()
}
pub fn encode(&self) -> Result<[u8; KKT_CONTEXT_LEN], KKTError> {
let mut header_bytes = [0u8; KKT_CONTEXT_LEN];
pub fn encode(&self) -> Result<Vec<u8>, KKTError> {
let mut header_bytes: Vec<u8> = Vec::with_capacity(KKT_CONTEXT_LEN);
if self.message_sequence >= 1 << 4 {
return Err(KKTError::MessageCountLimitReached);
}
let ciphersuite_bytes = self.ciphersuite.encode();
header_bytes.push((KKT_VERSION << 4) + self.message_sequence);
header_bytes[0] = (KKT_VERSION << 4) + self.message_sequence;
header_bytes[1] = u8::from(self.status) + u8::from(self.mode) + u8::from(self.role);
header_bytes.push(
match self.status {
KKTStatus::Ok => 0,
KKTStatus::InvalidRequestFormat => 0b0010_0000,
KKTStatus::InvalidResponseFormat => 0b0100_0000,
KKTStatus::InvalidSignature => 0b0110_0000,
KKTStatus::UnsupportedCiphersuite => 0b1000_0000,
KKTStatus::UnsupportedKKTVersion => 0b1010_0000,
KKTStatus::InvalidKey => 0b1100_0000,
KKTStatus::Timeout => 0b1110_0000,
} + match self.mode {
KKTMode::OneWay => 0,
KKTMode::Mutual => 0b0000_0100,
} + match self.role {
KKTRole::Initiator => 0,
KKTRole::Responder => 1,
KKTRole::AnonymousInitiator => 2,
},
);
let mut i = 2;
for b in ciphersuite_bytes.into_iter() {
header_bytes[i] = b;
i += 1;
}
header_bytes[i] = 0;
header_bytes.extend_from_slice(&self.ciphersuite.encode());
header_bytes.push(0);
Ok(header_bytes)
}
pub fn try_decode(header_bytes: [u8; KKT_CONTEXT_LEN]) -> Result<Self, KKTError> {
let kkt_version = (header_bytes[0] & 0b1111_0000) >> 4;
let message_sequence_counter = header_bytes[0] & 0b0000_1111;
pub fn try_decode(header_bytes: &[u8]) -> Result<Self, KKTError> {
if header_bytes.len() == KKT_CONTEXT_LEN {
let kkt_version = header_bytes[0] & 0b1111_0000;
// We only check if stuff is valid here, not necessarily if it's compatible
let message_sequence_counter = header_bytes[0] & 0b0000_1111;
if kkt_version > KKT_VERSION {
return Err(KKTError::FrameDecodingError {
info: format!("Header - Invalid KKT Version: {kkt_version}"),
});
}
// We only check if stuff is valid here, not necessarily if it's compatible
let raw_kkt_status = header_bytes[1] & 0b1110_0000;
let raw_kkt_role = header_bytes[1] & 0b0000_0011;
let raw_kkt_mode = header_bytes[1] & 0b0001_1100;
let status =
KKTStatus::try_from(raw_kkt_status).map_err(|_| KKTError::FrameDecodingError {
info: format!("Header - Invalid KKT Status: {raw_kkt_status}"),
})?;
let role = KKTRole::try_from(raw_kkt_role).map_err(|_| KKTError::FrameDecodingError {
info: format!("Header - Invalid KKT Role: {raw_kkt_role}"),
})?;
let mode = KKTMode::try_from(raw_kkt_mode).map_err(|_| KKTError::FrameDecodingError {
info: format!("Header - Invalid KKT Mode: {raw_kkt_mode}"),
})?;
let ciphersuite_bytes = header_bytes[2..6].try_into().map_err(|_| {
KKTError::CiphersuiteDecodingError {
info: format!(
"Incorrect Encoding Length: actual: 4 != expected: {CIPHERSUITE_ENCODING_LEN}",
),
if (kkt_version >> 4) > KKT_VERSION {
return Err(KKTError::FrameDecodingError {
info: format!("Header - Invalid KKT Version: {}", kkt_version >> 4),
});
}
})?;
Ok(KKTContext {
version: kkt_version,
status,
mode,
role,
ciphersuite: Ciphersuite::decode(ciphersuite_bytes)?,
message_sequence: message_sequence_counter,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn kkt_context_encoding() {
let valid_context = KKTContext::new(
KKTRole::Initiator,
KKTMode::Mutual,
Ciphersuite::decode([255, 1, 0, 0]).unwrap(),
)
.unwrap();
let encoded = valid_context.encode().unwrap();
let decoded = KKTContext::try_decode(encoded).unwrap();
assert_eq!(decoded, valid_context);
let status = match header_bytes[1] & 0b1110_0000 {
0 => KKTStatus::Ok,
0b0010_0000 => KKTStatus::InvalidRequestFormat,
0b0100_0000 => KKTStatus::InvalidResponseFormat,
0b0110_0000 => KKTStatus::InvalidSignature,
0b1000_0000 => KKTStatus::UnsupportedCiphersuite,
0b1010_0000 => KKTStatus::UnsupportedKKTVersion,
0b1100_0000 => KKTStatus::InvalidKey,
0b1110_0000 => KKTStatus::Timeout,
_ => {
return Err(KKTError::FrameDecodingError {
info: format!(
"Header - Invalid KKT Status: {}",
header_bytes[1] & 0b1110_0000
),
});
}
};
let role = match header_bytes[1] & 0b0000_0011 {
0 => KKTRole::Initiator,
1 => KKTRole::Responder,
2 => KKTRole::AnonymousInitiator,
_ => {
return Err(KKTError::FrameDecodingError {
info: format!(
"Header - Invalid KKT Role: {}",
header_bytes[1] & 0b0000_0011
),
});
}
};
let mode = match (header_bytes[1] & 0b0001_1100) >> 2 {
0 => KKTMode::OneWay,
1 => KKTMode::Mutual,
_ => {
return Err(KKTError::FrameDecodingError {
info: format!(
"Header - Invalid KKT Mode: {}",
(header_bytes[1] & 0b0001_1100) >> 2
),
});
}
};
Ok(KKTContext {
version: kkt_version,
status,
mode,
role,
ciphersuite: Ciphersuite::decode(&header_bytes[2..6])?,
message_sequence: message_sequence_counter,
})
} else {
Err(KKTError::FrameDecodingError {
info: format!(
"Header - Invalid Header Length: actual: {} != expected: {}",
header_bytes.len(),
KKT_CONTEXT_LEN
),
})
}
}
}
+68 -230
View File
@@ -1,257 +1,95 @@
// Copyright 2025-2026 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use core::hash;
use crate::kkt::KKT_INITIAL_FRAME_AAD;
use crate::{
ciphersuite::CURVE25519_KEY_LEN, context::KKTContext, error::KKTError, frame::KKTFrame,
use blake3::{Hash, Hasher};
use curve25519_dalek::digest::DynDigest;
use libcrux_psq::traits::Ciphertext;
use nym_crypto::symmetric::aead::{AeadKey, Nonce};
use nym_crypto::{
aes::Aes256,
asymmetric::x25519::{self, PrivateKey, PublicKey},
generic_array::GenericArray,
Aes256GcmSiv,
};
use blake3::Hasher;
use libcrux_chacha20poly1305::{NONCE_LEN, TAG_LEN};
use nym_crypto::asymmetric::x25519;
use rand::{CryptoRng, RngCore};
// use rand::{CryptoRng, RngCore};
use zeroize::Zeroize;
#[derive(Clone, Copy, Zeroize)]
pub struct KKTSessionSecret([u8; 32]);
use nym_crypto::aes::cipher::crypto_common::rand_core::{CryptoRng, RngCore};
impl KKTSessionSecret {
pub fn new<R>(rng: &mut R, remote_public_key: &x25519::PublicKey) -> (Self, x25519::PublicKey)
where
R: RngCore + CryptoRng,
{
let mut private_key_bytes = [0u8; x25519::PRIVATE_KEY_SIZE];
rng.fill_bytes(&mut private_key_bytes);
use crate::error::KKTError;
let ephemeral_private_key = x25519::PrivateKey::from_secret(private_key_bytes);
let ephemeral_public_key = x25519::PublicKey::from(&ephemeral_private_key);
(
Self::derive(&ephemeral_private_key, remote_public_key),
ephemeral_public_key,
)
}
pub fn from_bytes(secret: [u8; 32]) -> Self {
Self(secret)
}
fn try_derive(private_key: &x25519::PrivateKey, public_key: &[u8]) -> Result<Self, KKTError> {
let mut pub_key: [u8; 32] = [0u8; 32];
pub_key.copy_from_slice(&public_key[0..CURVE25519_KEY_LEN]);
// Todo: check validity of pk...
let pk = x25519::PublicKey::from(pub_key);
Ok(Self::derive(private_key, &pk))
}
pub fn derive(private_key: &x25519::PrivateKey, public_key: &x25519::PublicKey) -> Self {
let mut shared_secret = private_key.diffie_hellman(public_key);
let mut hasher = Hasher::new();
hasher.update(&shared_secret);
shared_secret.zeroize();
Self(hasher.finalize().as_bytes().to_owned())
}
pub fn as_bytes(&self) -> &[u8; 32] {
&self.0
}
}
pub fn encrypt_initial_kkt_frame<R>(
fn generate_round_trip_symmetric_key<R>(
rng: &mut R,
remote_public_key: &x25519::PublicKey,
kkt_frame: &KKTFrame,
) -> Result<(KKTSessionSecret, Vec<u8>), KKTError>
remote_public_key: &PublicKey,
) -> ([u8; 64], [u8; 32])
where
R: CryptoRng + RngCore,
{
let (session_secret_key, ephemeral_public_key) = KKTSessionSecret::new(rng, remote_public_key);
let mut s = x25519::PrivateKey::new(rng);
let gs = s.public_key();
let mut encrypted_frame =
encrypt_kkt_frame(rng, &session_secret_key, kkt_frame, KKT_INITIAL_FRAME_AAD)?;
let mut gbs = s.diffie_hellman(remote_public_key);
s.zeroize();
let mut output_buffer = Vec::with_capacity(encrypted_frame.len() + CURVE25519_KEY_LEN);
output_buffer.extend_from_slice(ephemeral_public_key.as_bytes());
output_buffer.append(&mut encrypted_frame);
let mut message: [u8; 64] = [0u8; 64];
message[0..32].clone_from_slice(gs.as_bytes());
// [ 32 | 12 | ciphertext | 16];
// [eph_pub_key | nonce | ciphertext | tag];
Ok((session_secret_key, output_buffer))
let mut hasher = Hasher::new();
hasher.update(&gbs);
gbs.zeroize();
let key: [u8; 32] = hasher.finalize().as_bytes().to_owned();
hasher.update(remote_public_key.as_bytes());
hasher.update(gs.as_bytes());
hasher.finalize_into_reset(&mut message[32..64]);
(message, key)
}
pub fn decrypt_initial_kkt_frame(
responder_private_key: &x25519::PrivateKey,
encrypted_frame_bytes: &[u8],
) -> Result<(KKTSessionSecret, KKTFrame, KKTContext), KKTError> {
if encrypted_frame_bytes.len() < CURVE25519_KEY_LEN + TAG_LEN + NONCE_LEN {
Err(KKTError::AEADError {
info: "Encrypted KKT Frame is too short.",
})
fn extract_shared_secret(b: &PrivateKey, message: &[u8; 64]) -> Result<[u8; 32], KKTError> {
let gs = PublicKey::from_bytes(&message[0..32])?;
let mut gsb = b.diffie_hellman(&gs);
let mut hasher = Hasher::new();
hasher.update(&gsb);
gsb.zeroize();
let key: [u8; 32] = hasher.finalize().as_bytes().to_owned();
hasher.update(b.public_key().as_bytes());
hasher.update(gs.as_bytes());
// This runs in constant time
if hasher.finalize() == message[32..64] {
Ok(key)
} else {
let shared_secret = KKTSessionSecret::try_derive(
responder_private_key,
&encrypted_frame_bytes[0..CURVE25519_KEY_LEN],
)?;
let (kkt_frame, kkt_context) = decrypt_kkt_frame(
&shared_secret,
&encrypted_frame_bytes[CURVE25519_KEY_LEN..],
KKT_INITIAL_FRAME_AAD,
)?;
Ok((shared_secret, kkt_frame, kkt_context))
Err(KKTError::X25519Error {
info: format!("Symmetric Key Hash Validation Error"),
})
}
}
pub fn encrypt_kkt_frame<R>(
rng: &mut R,
secret_key: &KKTSessionSecret,
kkt_frame: &KKTFrame,
aad: &[u8],
) -> Result<Vec<u8>, KKTError>
where
R: CryptoRng + RngCore,
{
let kkt_frame_bytes = kkt_frame.to_bytes();
fn encrypt(mut key: [u8; 32], message: &[u8]) -> Result<Vec<u8>, KKTError> {
// The empty nonce is fine since we use the key once.
let nonce = Nonce::<Aes256GcmSiv>::from_slice(&[]);
// generate nonce
let mut nonce: [u8; NONCE_LEN] = [0u8; NONCE_LEN];
rng.fill_bytes(&mut nonce);
let ciphertext =
nym_crypto::symmetric::aead::encrypt::<Aes256GcmSiv>(&key.into(), nonce, message)?;
let mut ciphertext = encrypt(secret_key.as_bytes(), &kkt_frame_bytes, aad, &nonce)?;
key.zeroize();
// [ 12 | ciphertext | 16];
// [nonce | ciphertext | tag];
let mut output_buffer: Vec<u8> =
Vec::with_capacity(NONCE_LEN + kkt_frame_bytes.len() + TAG_LEN);
output_buffer.extend_from_slice(&nonce);
output_buffer.append(&mut ciphertext);
Ok(output_buffer)
Ok(ciphertext)
}
// kkt_frame_bytes should look like this
// [ 12 | ciphertext | 16];
// [nonce | ciphertext | tag];
pub fn decrypt_kkt_frame(
secret_key: &KKTSessionSecret,
kkt_frame_bytes: &[u8],
aad: &[u8],
) -> Result<(KKTFrame, KKTContext), KKTError> {
let mut nonce: [u8; NONCE_LEN] = [0u8; NONCE_LEN];
nonce.copy_from_slice(&kkt_frame_bytes[0..NONCE_LEN]);
fn decrypt(key: [u8; 32], ciphertext: Vec<u8>) -> Vec<u8> {
// The empty nonce is fine since we use the key once.
let nonce = Nonce::<Aes256>::from_slice(&[]);
let plaintext = decrypt(
secret_key.as_bytes(),
&kkt_frame_bytes[NONCE_LEN..],
aad,
&nonce,
)?;
let ciphertext =
nym_crypto::symmetric::aead::encrypt::<Aes256GcmSiv>(&key.into(), nonce, message)?;
KKTFrame::from_bytes(&plaintext)
}
fn encrypt(
secret_key: &[u8; 32],
plaintext: &[u8],
aad: &[u8],
nonce: &[u8; NONCE_LEN],
) -> Result<Vec<u8>, KKTError> {
let mut output_buffer = vec![0; plaintext.len() + TAG_LEN];
libcrux_chacha20poly1305::encrypt(secret_key, plaintext, &mut output_buffer, aad, nonce)?;
Ok(output_buffer)
}
fn decrypt(
secret_key: &[u8; 32],
ciphertext: &[u8],
aad: &[u8],
nonce: &[u8; NONCE_LEN],
) -> Result<Vec<u8>, KKTError> {
let mut output_buffer = vec![0; ciphertext.len() - TAG_LEN];
libcrux_chacha20poly1305::decrypt(secret_key, &mut output_buffer, ciphertext, aad, nonce)?;
Ok(output_buffer)
}
#[cfg(test)]
mod test {
use crate::ciphersuite::Ciphersuite;
use crate::context::{KKTContext, KKTMode, KKTRole};
use crate::encryption::{decrypt_kkt_frame, encrypt_kkt_frame};
use crate::frame::{KKT_SESSION_ID_LEN, KKTFrame};
use crate::{
ciphersuite::HASH_LEN_256,
encryption::{KKTSessionSecret, decrypt, encrypt},
key_utils::generate_keypair_x25519,
};
use rand::{RngCore, SeedableRng, rng};
use rand_chacha::ChaCha20Rng;
#[test]
fn test_keygen() {
let mut rng = rng();
let responder_x25519_keypair = generate_keypair_x25519(&mut rng);
let (session_secret_key, ephemeral_public_key) =
KKTSessionSecret::new(&mut rng, responder_x25519_keypair.public_key());
let shared_secret = KKTSessionSecret::try_derive(
responder_x25519_keypair.private_key(),
ephemeral_public_key.as_bytes().as_slice(),
)
.unwrap();
assert_eq!(shared_secret.as_bytes(), session_secret_key.as_bytes())
}
#[test]
fn test_encryption() {
let mut rng = rng();
let mut secret_key = [0u8; HASH_LEN_256];
rng.fill_bytes(&mut secret_key);
let mut plaintext = vec![0; 100];
rng.fill_bytes(&mut plaintext);
let mut nonce = [0; 12];
rng.fill_bytes(&mut nonce);
let mut aad = vec![0; 124];
rng.fill_bytes(&mut aad);
let ciphertext = encrypt(&secret_key, &plaintext, &aad, &nonce).unwrap();
let o_plaintext = decrypt(&secret_key, &ciphertext, &aad, &nonce).unwrap();
assert_eq!(o_plaintext, plaintext)
}
#[test]
fn kkt_frame_encryption() -> anyhow::Result<()> {
let mut rng = ChaCha20Rng::seed_from_u64(42);
let session_key = KKTSessionSecret::from_bytes([42u8; 32]);
let aad = b"my-amazing-aad";
let valid_context = KKTContext::new(
KKTRole::Initiator,
KKTMode::Mutual,
Ciphersuite::decode([255, 1, 0, 0])?,
)?;
let dummy_frame = KKTFrame::new(
valid_context.encode()?,
&[2u8; 32],
[3u8; KKT_SESSION_ID_LEN],
&[4u8; 64],
);
let ciphertext = encrypt_kkt_frame(&mut rng, &session_key, &dummy_frame, aad.as_slice())?;
let (frame, context) = decrypt_kkt_frame(&session_key, &ciphertext, aad.as_slice())?;
assert_eq!(dummy_frame, frame);
assert_eq!(context, valid_context);
Ok(())
}
key.zeroize();
Ok(ciphertext)
}
-28
View File
@@ -1,7 +1,6 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use std::fmt::Debug;
use thiserror::Error;
use crate::context::KKTStatus;
@@ -45,9 +44,6 @@ pub enum KKTError {
#[error("{}", info)]
X25519Error { info: &'static str },
#[error("{}", info)]
AEADError { info: &'static str },
#[error("Generic libcrux error")]
LibcruxError,
}
@@ -91,27 +87,3 @@ impl From<libcrux_ecdh::Error> for KKTError {
}
}
}
impl From<libcrux_chacha20poly1305::AeadError> for KKTError {
fn from(err: libcrux_chacha20poly1305::AeadError) -> Self {
KKTError::KEMError {
info: match err {
libcrux_chacha20poly1305::AeadError::PlaintextTooLarge => {
"Plaintext is longer than u32::MAX"
}
libcrux_chacha20poly1305::AeadError::CiphertextTooLarge => {
"Ciphertext is longer than u32::MAX"
}
libcrux_chacha20poly1305::AeadError::AadTooLarge => "Aad is longer than u32::MAX",
libcrux_chacha20poly1305::AeadError::CiphertextTooShort => {
"The provided destination ciphertext does not fit the ciphertext and tag"
}
libcrux_chacha20poly1305::AeadError::PlaintextTooShort => {
"The provided destination plaintext is too short to fit the decrypted plaintext"
}
libcrux_chacha20poly1305::AeadError::InvalidCiphertext => {
"The ciphertext is not a valid encryption under the given key and nonce."
}
},
}
}
}
+53 -79
View File
@@ -14,12 +14,9 @@ use crate::{
pub const KKT_SESSION_ID_LEN: usize = 16;
pub type KKTSessionId = [u8; KKT_SESSION_ID_LEN];
#[derive(Debug, PartialEq, Clone)]
pub struct KKTFrame {
context: [u8; KKT_CONTEXT_LEN],
session_id: KKTSessionId,
context: Vec<u8>,
session_id: Vec<u8>,
body: Vec<u8>,
signature: Vec<u8>,
}
@@ -30,31 +27,20 @@ pub struct KKTFrame {
// if coming from responder => body has the responder's kem public key and the signature is over the context + body + session_id.
impl KKTFrame {
pub fn new(
context: [u8; KKT_CONTEXT_LEN],
body: &[u8],
session_id: [u8; KKT_SESSION_ID_LEN],
signature: &[u8],
) -> Self {
pub fn new(context: &[u8], body: &[u8], session_id: &[u8], signature: &[u8]) -> Self {
Self {
context,
context: Vec::from(context),
body: Vec::from(body),
session_id,
session_id: Vec::from(session_id),
signature: Vec::from(signature),
}
}
pub fn context_ref(&self) -> &[u8] {
&self.context
}
pub fn context(&self) -> Result<KKTContext, KKTError> {
KKTContext::try_decode(self.context)
}
pub fn signature_ref(&self) -> &[u8] {
&self.signature
}
pub fn body_ref(&self) -> &[u8] {
&self.body
}
@@ -62,10 +48,6 @@ impl KKTFrame {
pub fn session_id_ref(&self) -> &[u8] {
&self.session_id
}
pub fn session_id(&self) -> [u8; KKT_SESSION_ID_LEN] {
self.session_id
}
pub fn signature_mut(&mut self) -> &mut [u8] {
&mut self.signature
}
@@ -91,65 +73,57 @@ impl KKTFrame {
}
pub fn from_bytes(bytes: &[u8]) -> Result<(Self, KKTContext), KKTError> {
let len = bytes.len();
if bytes.len() < KKT_CONTEXT_LEN {
return Err(KKTError::FrameDecodingError {
Err(KKTError::FrameDecodingError {
info: format!(
"Frame is shorter than expected context length: actual {len} != expected {KKT_CONTEXT_LEN}",
"Frame is shorter than expected context length: actual {} != expected {}",
bytes.len(),
KKT_CONTEXT_LEN
),
});
})
} else {
let context_bytes = Vec::from(&bytes[0..KKT_CONTEXT_LEN]);
let context = KKTContext::try_decode(&context_bytes)?;
let (mut session_id, mut body, mut signature): (Vec<u8>, Vec<u8>, Vec<u8>) =
(vec![], vec![], vec![]);
if bytes.len() == context.full_message_len() {
if context.body_len() > 0 {
body.extend_from_slice(
&bytes[KKT_CONTEXT_LEN..KKT_CONTEXT_LEN + context.body_len()],
);
}
if context.session_id_len() > 0 {
session_id.extend_from_slice(
&bytes[KKT_CONTEXT_LEN + context.body_len()
..KKT_CONTEXT_LEN + context.body_len() + context.session_id_len()],
);
}
if context.signature_len() > 0 {
signature.extend_from_slice(
&bytes[KKT_CONTEXT_LEN + context.body_len() + context.session_id_len()
..KKT_CONTEXT_LEN
+ context.body_len()
+ context.session_id_len()
+ context.signature_len()],
);
}
Ok((
KKTFrame::new(&context_bytes, &body, &session_id, &signature),
context,
))
} else {
Err(KKTError::FrameDecodingError {
info: format!(
"Frame is shorter than expected: actual {} != expected {}",
bytes.len(),
context.full_message_len()
),
})
}
}
// SAFETY: we're using exactly KKT_CONTEXT_LEN bytes
#[allow(clippy::unwrap_used)]
let context_bytes = bytes[0..KKT_CONTEXT_LEN].try_into().unwrap();
let context = KKTContext::try_decode(context_bytes)?;
if bytes.len() != context.full_message_len() {
return Err(KKTError::FrameDecodingError {
info: format!(
"Frame is shorter than expected: actual {len} != expected {}",
context.full_message_len()
),
});
}
let mut body = Vec::new();
let mut signature = Vec::new();
// decode body
if context.body_len() > 0 {
let body_bytes = &bytes[KKT_CONTEXT_LEN..KKT_CONTEXT_LEN + context.body_len()];
body.extend_from_slice(body_bytes);
}
let session_bytes = &bytes[KKT_CONTEXT_LEN + context.body_len()
..KKT_CONTEXT_LEN + context.body_len() + KKT_SESSION_ID_LEN];
// SAFETY: we're using exactly KKT_SESSION_ID_LEN bytes and we checked for sufficient bytes
#[allow(clippy::unwrap_used)]
let session_id = session_bytes.try_into().unwrap();
// // old code left for reference if session id becomes variable in length:
// if context.session_id_len() > 0 {
// session_id.extend_from_slice(
// &bytes[KKT_CONTEXT_LEN + context.body_len()
// ..KKT_CONTEXT_LEN + context.body_len() + context.session_id_len()],
// );
// }
// decode signature
if context.signature_len() > 0 {
let signature_bytes = &bytes[KKT_CONTEXT_LEN + context.body_len() + KKT_SESSION_ID_LEN
..KKT_CONTEXT_LEN
+ context.body_len()
+ KKT_SESSION_ID_LEN
+ context.signature_len()];
signature.extend_from_slice(signature_bytes);
}
Ok((
KKTFrame::new(context_bytes, &body, session_id, &signature),
context,
))
}
}
+11 -37
View File
@@ -1,53 +1,27 @@
use crate::ciphersuite::HashFunction;
use crate::{
ciphersuite::{HashFunction, KEM},
error::KKTError,
};
use classic_mceliece_rust::keypair_boxed;
use libcrux_kem::{Algorithm, key_gen};
use libcrux_sha3;
use rand::{CryptoRng, RngCore};
pub fn generate_keypair_ed25519<R>(
rng: &mut R,
index: Option<u32>,
) -> nym_crypto::asymmetric::ed25519::KeyPair
where
R: RngCore + CryptoRng,
{
let mut secret_initiator: [u8; 32] = [0u8; 32];
rng.fill_bytes(&mut secret_initiator);
nym_crypto::asymmetric::ed25519::KeyPair::from_secret(secret_initiator, index.unwrap_or(0))
}
pub fn generate_keypair_x25519<R>(rng: &mut R) -> nym_crypto::asymmetric::x25519::KeyPair
where
R: RngCore + CryptoRng,
{
let mut secret_initiator: [u8; 32] = [0u8; 32];
rng.fill_bytes(&mut secret_initiator);
let private_key = nym_crypto::asymmetric::x25519::PrivateKey::from_secret(secret_initiator);
private_key.into()
}
// (decapsulation_key, encapsulation_key)
pub fn generate_keypair_libcrux<R>(
rng: &mut R,
kem: crate::ciphersuite::KEM,
) -> Result<(libcrux_kem::PrivateKey, libcrux_kem::PublicKey), crate::error::KKTError>
kem: KEM,
) -> Result<(libcrux_kem::PrivateKey, libcrux_kem::PublicKey), KKTError>
where
R: RngCore + CryptoRng,
{
match kem {
crate::ciphersuite::KEM::MlKem768 => {
Ok(libcrux_kem::key_gen(libcrux_kem::Algorithm::MlKem768, rng)?)
}
crate::ciphersuite::KEM::XWing => Ok(libcrux_kem::key_gen(
libcrux_kem::Algorithm::XWingKemDraft06,
rng,
)?),
crate::ciphersuite::KEM::X25519 => {
Ok(libcrux_kem::key_gen(libcrux_kem::Algorithm::X25519, rng)?)
}
_ => Err(crate::error::KKTError::KEMError {
KEM::MlKem768 => Ok(key_gen(Algorithm::MlKem768, rng)?),
KEM::XWing => Ok(key_gen(Algorithm::XWingKemDraft06, rng)?),
KEM::X25519 => Ok(key_gen(Algorithm::X25519, rng)?),
_ => Err(KKTError::KEMError {
info: "Key Generation Error: Unsupported Libcrux Algorithm",
}),
}
+42 -140
View File
@@ -8,14 +8,14 @@
//!
//! The underlying KKT protocol is implemented in the `session` module.
use nym_crypto::asymmetric::{ed25519, x25519};
use nym_crypto::asymmetric::ed25519;
use rand::{CryptoRng, RngCore};
use crate::{
ciphersuite::{Ciphersuite, EncapsulationKey},
context::{KKTContext, KKTMode},
encryption::{decrypt_initial_kkt_frame, decrypt_kkt_frame, encrypt_kkt_frame},
error::KKTError,
frame::KKTFrame,
};
// Re-export core session functions for advanced use cases
@@ -24,13 +24,7 @@ pub use crate::session::{
responder_ingest_message, responder_process,
};
use crate::encryption::{KKTSessionSecret, encrypt_initial_kkt_frame};
use crate::frame::KKTFrame;
pub(crate) const KKT_RESPONSE_AAD: &[u8] = b"KKT_Response";
pub(crate) const KKT_INITIAL_FRAME_AAD: &[u8] = b"KKT_INITIAL_FRAME";
/// Perform an *Encrypted* request for a KEM public key from a responder (OneWay mode).
/// Request a KEM public key from a responder (OneWay mode).
///
/// This is the client-side operation that initiates a KKT exchange.
/// The request will be signed with the provided signing key.
@@ -39,20 +33,17 @@ pub(crate) const KKT_INITIAL_FRAME_AAD: &[u8] = b"KKT_INITIAL_FRAME";
/// * `rng` - Random number generator
/// * `ciphersuite` - Negotiated ciphersuite (KEM, hash, signature algorithms)
/// * `signing_key` - Client's Ed25519 signing key for authentication
/// * `responder_dh_public_key` - Responder's long-term x25519 Diffie-Hellman public key
///
/// # Returns
/// * `KKTSessionSecret` - Session Secret Key to use when decrypting responses
/// * `KKTContext` - Context to use when validating the response
/// * `Vec<u8>` - Contains the client's ephemeral public key and encrypted and signed bytes to send to responder
/// * `KKTFrame` - Signed request frame to send to responder
///
/// # Example
/// ```ignore
/// let (session_secret, context, request_frame) = request_kem_key(
/// let (context, request_frame) = request_kem_key(
/// &mut rng,
/// ciphersuite,
/// client_signing_key,
/// responder_dh_public_key,
/// )?;
/// // Send request_frame to gateway
/// ```
@@ -60,21 +51,13 @@ pub fn request_kem_key<R: CryptoRng + RngCore>(
rng: &mut R,
ciphersuite: Ciphersuite,
signing_key: &ed25519::PrivateKey,
responder_dh_public_key: &x25519::PublicKey,
) -> Result<(KKTSessionSecret, KKTContext, Vec<u8>), KKTError> {
) -> Result<(KKTContext, KKTFrame), KKTError> {
// OneWay mode: client only wants responder's KEM key
// None: client doesn't send their own KEM key
let (initiator_context, initiator_frame) =
initiator_process(rng, KKTMode::OneWay, ciphersuite, signing_key, None)?;
// Generate the session's shared secret and encrypt the Initiator's request
let (session_secret, encrypted_request_bytes) =
encrypt_initial_kkt_frame(rng, responder_dh_public_key, &initiator_frame)?;
Ok((session_secret, initiator_context, encrypted_request_bytes))
initiator_process(rng, KKTMode::OneWay, ciphersuite, signing_key, None)
}
/// Decrypt, validate an *Encrypted* KKT response and extract the responder's KEM public key.
/// Validate a KKT response and extract the responder's KEM public key.
///
/// This is the client-side operation that processes the gateway's response.
/// It verifies the signature and validates the key hash against the expected value
@@ -82,7 +65,6 @@ pub fn request_kem_key<R: CryptoRng + RngCore>(
///
/// # Arguments
/// * `context` - Context from the initial request
/// * `session_secret` - Session Secret Key (generated with request)
/// * `responder_vk` - Responder's Ed25519 verification key (from directory)
/// * `expected_key_hash` - Expected hash of responder's KEM key (from directory)
/// * `response_bytes` - Serialized response frame from responder
@@ -94,8 +76,7 @@ pub fn request_kem_key<R: CryptoRng + RngCore>(
/// ```ignore
/// let gateway_kem_key = validate_kem_response(
/// &mut context,
/// &session_secret,
/// &gateway_verification_key,
/// gateway_verification_key,
/// &expected_hash_from_directory,
/// &response_bytes,
/// )?;
@@ -103,44 +84,23 @@ pub fn request_kem_key<R: CryptoRng + RngCore>(
/// ```
pub fn validate_kem_response<'a>(
context: &mut KKTContext,
session_secret: &KKTSessionSecret,
responder_vk: &ed25519::PublicKey,
expected_key_hash: &[u8],
encrypted_response_bytes: &[u8],
response_bytes: &[u8],
) -> Result<EncapsulationKey<'a>, KKTError> {
let (responder_frame, responder_context) =
decrypt_kkt_response_frame(session_secret, encrypted_response_bytes)?;
initiator_ingest_response(
context,
&responder_frame,
&responder_context,
responder_vk,
expected_key_hash,
)
initiator_ingest_response(context, responder_vk, expected_key_hash, response_bytes)
}
/// Decrypts and validates an *Encrypted* KKT response
///
/// This is the client-side operation that processes the gateway's response.
pub fn decrypt_kkt_response_frame(
session_secret: &KKTSessionSecret,
frame_ciphertext: &[u8],
) -> Result<(KKTFrame, KKTContext), KKTError> {
decrypt_kkt_frame(session_secret, frame_ciphertext, KKT_RESPONSE_AAD)
}
/// Handle an *Encrypted* KKT request and generate a signed response with the responder's KEM key.
/// Handle a KKT request and generate a signed response with the responder's KEM key.
///
/// This is the gateway-side operation that processes a client's KKT request.
/// It validates the request signature (if authenticated) and responds with
/// the gateway's KEM public key, signed for authenticity.
///
/// # Arguments
/// * `encrypted_request_bytes` - encrypted KEM request
/// * `request_frame` - Request frame received from initiator
/// * `initiator_vk` - Initiator's Ed25519 verification key (None for anonymous)
/// * `responder_signing_key` - Gateway's Ed25519 signing key
/// * `responder_dh_public_key` - Gateway's long-term x25519 Diffie-Hellman private key
/// * `responder_kem_key` - Gateway's KEM public key to send
///
/// # Returns
@@ -156,40 +116,31 @@ pub fn decrypt_kkt_response_frame(
/// )?;
/// // Send response_frame back to client
/// ```
pub fn handle_kem_request<'a, R>(
rng: &mut R,
encrypted_request_bytes: &[u8],
pub fn handle_kem_request<'a>(
request_frame: &KKTFrame,
initiator_vk: Option<&ed25519::PublicKey>,
responder_signing_key: &ed25519::PrivateKey,
responder_dh_private_key: &x25519::PrivateKey,
responder_kem_key: &EncapsulationKey<'a>,
) -> Result<Vec<u8>, KKTError>
where
R: RngCore + CryptoRng,
{
// Compute the session's shared secret, decrypt and parse context from the request frame
let (session_secret, request_frame, initiator_context) =
decrypt_initial_kkt_frame(responder_dh_private_key, encrypted_request_bytes)?;
) -> Result<KKTFrame, KKTError> {
// Parse context from the request frame
let request_bytes = request_frame.to_bytes();
let (_, request_context) = KKTFrame::from_bytes(&request_bytes)?;
// Validate the request (verifies signature if initiator_vk provided)
let (mut response_context, _) = responder_ingest_message(
&initiator_context,
&request_context,
initiator_vk,
None, // Not checking initiator's KEM key in OneWay mode
&request_frame,
request_frame,
)?;
// Generate signed response with our KEM public key
let responder_frame = responder_process(
responder_process(
&mut response_context,
request_frame.session_id(),
request_frame.session_id_ref(),
responder_signing_key,
responder_kem_key,
)?;
// Encrypt the responder's response with the session's shared secret
encrypt_kkt_frame(rng, &session_secret, &responder_frame, KKT_RESPONSE_AAD)
)
}
#[cfg(test)]
@@ -200,13 +151,6 @@ mod tests {
key_utils::{generate_keypair_libcrux, hash_encapsulation_key},
};
fn random_x25519_key() -> x25519::PrivateKey {
let mut bytes = [0u8; 32];
let mut rng = rand::rng();
rng.fill_bytes(&mut bytes);
x25519::PrivateKey::from_secret(bytes)
}
#[test]
fn test_kkt_wrappers_oneway_authenticated() {
let mut rng = rand::rng();
@@ -214,14 +158,11 @@ mod tests {
// Generate Ed25519 keypairs for both parties
let mut initiator_secret = [0u8; 32];
rng.fill_bytes(&mut initiator_secret);
let ed25519_init = ed25519::KeyPair::from_secret(initiator_secret, 0);
let initiator_keypair = ed25519::KeyPair::from_secret(initiator_secret, 0);
let mut responder_secret = [0u8; 32];
rng.fill_bytes(&mut responder_secret);
let ed25519_resp = ed25519::KeyPair::from_secret(responder_secret, 1);
let x25519_resp_priv = random_x25519_key();
let x25519_resp_pub = x25519::PublicKey::from(&x25519_resp_priv);
let responder_keypair = ed25519::KeyPair::from_secret(responder_secret, 1);
// Generate responder's KEM keypair (X25519 for testing)
let (_, responder_kem_pk) = generate_keypair_libcrux(&mut rng, KEM::X25519).unwrap();
@@ -244,21 +185,14 @@ mod tests {
);
// Client: Request KEM key
let (session_key, mut context, request_frame_ciphertext) = request_kem_key(
&mut rng,
ciphersuite,
ed25519_init.private_key(),
&x25519_resp_pub,
)
.unwrap();
let (mut context, request_frame) =
request_kem_key(&mut rng, ciphersuite, initiator_keypair.private_key()).unwrap();
// Gateway: Handle request
let response_frame_ciphertext = handle_kem_request(
&mut rng,
&request_frame_ciphertext,
Some(ed25519_init.public_key()), // Authenticated
ed25519_resp.private_key(),
&x25519_resp_priv,
let response_frame = handle_kem_request(
&request_frame,
Some(initiator_keypair.public_key()), // Authenticated
responder_keypair.private_key(),
&responder_kem_key,
)
.unwrap();
@@ -266,10 +200,9 @@ mod tests {
// Client: Validate response
let obtained_key = validate_kem_response(
&mut context,
&session_key,
ed25519_resp.public_key(),
responder_keypair.public_key(),
&key_hash,
&response_frame_ciphertext,
&response_frame.to_bytes(),
)
.unwrap();
@@ -289,9 +222,6 @@ mod tests {
let (_, responder_kem_pk) = generate_keypair_libcrux(&mut rng, KEM::X25519).unwrap();
let responder_kem_key = EncapsulationKey::X25519(responder_kem_pk);
let x25519_resp_priv = random_x25519_key();
let x25519_resp_pub = x25519::PublicKey::from(&x25519_resp_priv);
let ciphersuite = Ciphersuite::resolve_ciphersuite(
KEM::X25519,
HashFunction::Blake3,
@@ -310,17 +240,11 @@ mod tests {
let (mut context, request_frame) =
anonymous_initiator_process(&mut rng, ciphersuite).unwrap();
// Generate the session's shared secret and encrypt the Initiator's request
let (session_secret, encrypted_request_bytes) =
encrypt_initial_kkt_frame(&mut rng, &x25519_resp_pub, &request_frame).unwrap();
// Gateway: Handle anonymous request
let response_frame = handle_kem_request(
&mut rng,
&encrypted_request_bytes,
&request_frame,
None, // Anonymous - no verification key
responder_keypair.private_key(),
&x25519_resp_priv,
&responder_kem_key,
)
.unwrap();
@@ -328,10 +252,9 @@ mod tests {
// Initiator: Validate response
let obtained_key = validate_kem_response(
&mut context,
&session_secret,
responder_keypair.public_key(),
&key_hash,
&response_frame,
&response_frame.to_bytes(),
)
.unwrap();
@@ -350,9 +273,6 @@ mod tests {
rng.fill_bytes(&mut responder_secret);
let responder_keypair = ed25519::KeyPair::from_secret(responder_secret, 1);
let x25519_resp_priv = random_x25519_key();
let x25519_resp_pub = x25519::PublicKey::from(&x25519_resp_priv);
// Different keypair for wrong signature
let mut wrong_secret = [0u8; 32];
rng.fill_bytes(&mut wrong_secret);
@@ -369,21 +289,14 @@ mod tests {
)
.unwrap();
let (_session_key, _context, request_frame_ciphertext) = request_kem_key(
&mut rng,
ciphersuite,
initiator_keypair.private_key(),
&x25519_resp_pub,
)
.unwrap();
let (_context, request_frame) =
request_kem_key(&mut rng, ciphersuite, initiator_keypair.private_key()).unwrap();
// Gateway handles request but we provide WRONG verification key
let result = handle_kem_request(
&mut rng,
&request_frame_ciphertext,
&request_frame,
Some(wrong_keypair.public_key()), // Wrong key!
responder_keypair.private_key(),
&x25519_resp_priv,
&responder_kem_key,
);
@@ -403,9 +316,6 @@ mod tests {
rng.fill_bytes(&mut responder_secret);
let responder_keypair = ed25519::KeyPair::from_secret(responder_secret, 1);
let x25519_resp_priv = random_x25519_key();
let x25519_resp_pub = x25519::PublicKey::from(&x25519_resp_priv);
let (_, responder_kem_pk) = generate_keypair_libcrux(&mut rng, KEM::X25519).unwrap();
let responder_kem_key = EncapsulationKey::X25519(responder_kem_pk);
@@ -420,20 +330,13 @@ mod tests {
// Use WRONG hash
let wrong_hash = [0u8; 32];
let (session_key, mut context, request_frame) = request_kem_key(
&mut rng,
ciphersuite,
initiator_keypair.private_key(),
&x25519_resp_pub,
)
.unwrap();
let (mut context, request_frame) =
request_kem_key(&mut rng, ciphersuite, initiator_keypair.private_key()).unwrap();
let response_frame = handle_kem_request(
&mut rng,
&request_frame,
Some(initiator_keypair.public_key()),
responder_keypair.private_key(),
&x25519_resp_priv,
&responder_kem_key,
)
.unwrap();
@@ -441,10 +344,9 @@ mod tests {
// Client validates with WRONG hash
let result = validate_kem_response(
&mut context,
&session_key,
responder_keypair.public_key(),
&wrong_hash, // Wrong!
&response_frame,
&response_frame.to_bytes(),
);
// Should fail hash validation
+23 -288
View File
@@ -3,31 +3,28 @@
pub mod ciphersuite;
pub mod context;
pub mod encryption;
// pub mod encryption;
pub mod error;
pub mod frame;
pub mod key_utils;
pub mod kkt;
pub mod session;
// pub mod psq;
// This must be less than 4 bits
pub const KKT_VERSION: u8 = 1;
const _: () = assert!(KKT_VERSION < 1 << 4);
#[cfg(test)]
mod test {
use crate::kkt::KKT_RESPONSE_AAD;
use nym_crypto::asymmetric::ed25519;
use rand::prelude::*;
use crate::{
ciphersuite::{Ciphersuite, EncapsulationKey, HashFunction, KEM},
encryption::{
decrypt_initial_kkt_frame, decrypt_kkt_frame, encrypt_initial_kkt_frame,
encrypt_kkt_frame,
},
frame::KKTFrame,
key_utils::{
generate_keypair_ed25519, generate_keypair_libcrux, generate_keypair_mceliece,
generate_keypair_x25519, hash_encapsulation_key,
},
key_utils::{generate_keypair_libcrux, generate_keypair_mceliece, hash_encapsulation_key},
session::{
anonymous_initiator_process, initiator_ingest_response, initiator_process,
responder_ingest_message, responder_process,
@@ -39,9 +36,13 @@ mod test {
let mut rng = rand::rng();
// generate ed25519 keys
let initiator_ed25519_keypair = generate_keypair_ed25519(&mut rng, Some(0));
let responder_ed25519_keypair = generate_keypair_ed25519(&mut rng, Some(1));
let mut secret_initiator: [u8; 32] = [0u8; 32];
rng.fill_bytes(&mut secret_initiator);
let initiator_ed25519_keypair = ed25519::KeyPair::from_secret(secret_initiator, 0);
let mut secret_responder: [u8; 32] = [0u8; 32];
rng.fill_bytes(&mut secret_responder);
let responder_ed25519_keypair = ed25519::KeyPair::from_secret(secret_responder, 1);
for kem in [KEM::MlKem768, KEM::XWing, KEM::X25519, KEM::McEliece] {
for hash_function in [
HashFunction::Blake3,
@@ -116,7 +117,7 @@ mod test {
let r_frame = responder_process(
&mut r_context,
i_frame_r.session_id(),
i_frame_r.session_id_ref(),
responder_ed25519_keypair.private_key(),
&responder_kem_public_key,
)
@@ -124,18 +125,15 @@ mod test {
let r_bytes = r_frame.to_bytes();
let (i_frame_r, i_context_r) = KKTFrame::from_bytes(&r_bytes).unwrap();
let i_obtained_key = initiator_ingest_response(
let obtained_key = initiator_ingest_response(
&mut i_context,
&i_frame_r,
&i_context_r,
responder_ed25519_keypair.public_key(),
&r_dir_hash,
&r_bytes,
)
.unwrap();
assert_eq!(i_obtained_key.encode(), r_kem_key_bytes)
assert_eq!(obtained_key.encode(), r_kem_key_bytes)
}
// Initiator, OneWay
{
@@ -164,7 +162,7 @@ mod test {
let r_frame = responder_process(
&mut r_context,
i_frame_r.session_id(),
i_frame_r.session_id_ref(),
responder_ed25519_keypair.private_key(),
&responder_kem_public_key,
)
@@ -172,14 +170,11 @@ mod test {
let r_bytes = r_frame.to_bytes();
let (i_frame_r, i_context_r) = KKTFrame::from_bytes(&r_bytes).unwrap();
let i_obtained_key = initiator_ingest_response(
&mut i_context,
&i_frame_r,
&i_context_r,
responder_ed25519_keypair.public_key(),
&r_dir_hash,
&r_bytes,
)
.unwrap();
@@ -213,7 +208,7 @@ mod test {
let r_frame = responder_process(
&mut r_context,
i_frame_r.session_id(),
i_frame_r.session_id_ref(),
responder_ed25519_keypair.private_key(),
&responder_kem_public_key,
)
@@ -221,275 +216,15 @@ mod test {
let r_bytes = r_frame.to_bytes();
let (i_frame_r, i_context_r) = KKTFrame::from_bytes(&r_bytes).unwrap();
let i_obtained_key = initiator_ingest_response(
let obtained_key = initiator_ingest_response(
&mut i_context,
&i_frame_r,
&i_context_r,
responder_ed25519_keypair.public_key(),
&r_dir_hash,
&r_bytes,
)
.unwrap();
assert_eq!(i_obtained_key.encode(), r_kem_key_bytes)
}
}
}
}
#[test]
fn test_kkt_psq_e2e_encrypted() {
let mut rng = rand::rng();
// generate ed25519 keys
let initiator_ed25519_keypair = generate_keypair_ed25519(&mut rng, Some(0));
let responder_ed25519_keypair = generate_keypair_ed25519(&mut rng, Some(1));
// generate responder x25519 keys
let responder_x25519_keypair = generate_keypair_x25519(&mut rng);
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,
crate::ciphersuite::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_libcrux(&mut rng, kem).unwrap().1,
),
EncapsulationKey::MlKem768(
generate_keypair_libcrux(&mut rng, kem).unwrap().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
{
let (mut i_context, i_frame) =
anonymous_initiator_process(&mut rng, ciphersuite).unwrap();
// encryption - initiator frame
let (i_session_secret, i_bytes) = encrypt_initial_kkt_frame(
&mut rng,
responder_x25519_keypair.public_key(),
&i_frame,
)
.unwrap();
// decryption - initiator frame
let (r_session_secret, i_frame_r, i_context_r) =
decrypt_initial_kkt_frame(responder_x25519_keypair.private_key(), &i_bytes)
.unwrap();
let (mut r_context, _) =
responder_ingest_message(&i_context_r, None, None, &i_frame_r).unwrap();
let r_frame = responder_process(
&mut r_context,
i_frame_r.session_id(),
responder_ed25519_keypair.private_key(),
&responder_kem_public_key,
)
.unwrap();
// encryption - responder frame
let r_bytes =
encrypt_kkt_frame(&mut rng, &r_session_secret, &r_frame, KKT_RESPONSE_AAD)
.unwrap();
// decryption - responder frame
let (i_frame_r, i_context_r) =
decrypt_kkt_frame(&i_session_secret, &r_bytes, KKT_RESPONSE_AAD).unwrap();
let i_obtained_key = initiator_ingest_response(
&mut i_context,
&i_frame_r,
&i_context_r,
responder_ed25519_keypair.public_key(),
&r_dir_hash,
)
.unwrap();
assert_eq!(i_obtained_key.encode(), r_kem_key_bytes)
}
// Initiator, OneWay
{
let (mut i_context, i_frame) = initiator_process(
&mut rng,
crate::context::KKTMode::OneWay,
ciphersuite,
initiator_ed25519_keypair.private_key(),
None,
)
.unwrap();
// encryption - initiator frame
let (i_session_secret, i_bytes) = encrypt_initial_kkt_frame(
&mut rng,
responder_x25519_keypair.public_key(),
&i_frame,
)
.unwrap();
// decryption - initiator frame
let (r_session_secret, i_frame_r, r_context) =
decrypt_initial_kkt_frame(responder_x25519_keypair.private_key(), &i_bytes)
.unwrap();
let (mut r_context, r_obtained_key) = responder_ingest_message(
&r_context,
Some(initiator_ed25519_keypair.public_key()),
None,
&i_frame_r,
)
.unwrap();
assert!(r_obtained_key.is_none());
let r_frame = responder_process(
&mut r_context,
i_frame_r.session_id(),
responder_ed25519_keypair.private_key(),
&responder_kem_public_key,
)
.unwrap();
// encryption - responder frame
let r_bytes =
encrypt_kkt_frame(&mut rng, &r_session_secret, &r_frame, KKT_RESPONSE_AAD)
.unwrap();
// decryption - responder frame
let (i_frame_r, i_context_r) =
decrypt_kkt_frame(&i_session_secret, &r_bytes, KKT_RESPONSE_AAD).unwrap();
let i_obtained_key = initiator_ingest_response(
&mut i_context,
&i_frame_r,
&i_context_r,
responder_ed25519_keypair.public_key(),
&r_dir_hash,
)
.unwrap();
assert_eq!(i_obtained_key.encode(), r_kem_key_bytes)
}
// Initiator, Mutual
{
let (mut i_context, i_frame) = initiator_process(
&mut rng,
crate::context::KKTMode::Mutual,
ciphersuite,
initiator_ed25519_keypair.private_key(),
Some(&initiator_kem_public_key),
)
.unwrap();
// encryption - initiator frame
let (i_session_secret, i_bytes) = encrypt_initial_kkt_frame(
&mut rng,
responder_x25519_keypair.public_key(),
&i_frame,
)
.unwrap();
// decryption - initiator frame
let (r_session_secret, i_frame_r, i_context_r) =
decrypt_initial_kkt_frame(responder_x25519_keypair.private_key(), &i_bytes)
.unwrap();
let (mut r_context, r_obtained_key) = responder_ingest_message(
&i_context_r,
Some(initiator_ed25519_keypair.public_key()),
Some(&i_dir_hash),
&i_frame_r,
)
.unwrap();
assert_eq!(r_obtained_key.unwrap().encode(), i_kem_key_bytes);
let r_frame = responder_process(
&mut r_context,
i_frame_r.session_id(),
responder_ed25519_keypair.private_key(),
&responder_kem_public_key,
)
.unwrap();
// encryption - responder frame
let r_bytes =
encrypt_kkt_frame(&mut rng, &r_session_secret, &r_frame, KKT_RESPONSE_AAD)
.unwrap();
// decryption - responder frame
let (i_frame_r, i_context_r) =
decrypt_kkt_frame(&i_session_secret, &r_bytes, KKT_RESPONSE_AAD).unwrap();
let i_obtained_key = initiator_ingest_response(
&mut i_context,
&i_frame_r,
&i_context_r,
responder_ed25519_keypair.public_key(),
&r_dir_hash,
)
.unwrap();
assert_eq!(i_obtained_key.encode(), r_kem_key_bytes)
assert_eq!(obtained_key.encode(), r_kem_key_bytes)
}
}
}
+18 -14
View File
@@ -1,7 +1,6 @@
use nym_crypto::asymmetric::ed25519::{self, Signature};
use rand::{CryptoRng, RngCore};
use crate::frame::KKTSessionId;
use crate::{
ciphersuite::{Ciphersuite, EncapsulationKey},
context::{KKTContext, KKTMode, KKTRole, KKTStatus},
@@ -52,7 +51,7 @@ where
Ok((
context,
KKTFrame::new(context_bytes, body, session_id, &signature),
KKTFrame::new(&context_bytes, body, &session_id, &signature),
))
}
@@ -69,38 +68,43 @@ where
let mut session_id = [0u8; KKT_SESSION_ID_LEN];
rng.fill_bytes(&mut session_id);
Ok((context, KKTFrame::new(context_bytes, &[], session_id, &[])))
Ok((
context,
KKTFrame::new(&context_bytes, &[], &session_id, &[]),
))
}
pub fn initiator_ingest_response<'a>(
own_context: &mut KKTContext,
remote_frame: &KKTFrame,
remote_context: &KKTContext,
remote_verification_key: &ed25519::PublicKey,
expected_hash: &[u8],
message_bytes: &[u8],
) -> Result<EncapsulationKey<'a>, KKTError> {
check_compatibility(own_context, remote_context)?;
// sizes have to be correct
let (frame, remote_context) = KKTFrame::from_bytes(message_bytes)?;
check_compatibility(own_context, &remote_context)?;
match remote_context.status() {
KKTStatus::Ok => {
let mut bytes_to_verify: Vec<u8> = Vec::with_capacity(
remote_context.full_message_len() - remote_context.signature_len(),
);
bytes_to_verify.extend_from_slice(&remote_context.encode()?);
bytes_to_verify.extend_from_slice(remote_frame.body_ref());
bytes_to_verify.extend_from_slice(remote_frame.session_id_ref());
bytes_to_verify.extend_from_slice(frame.body_ref());
bytes_to_verify.extend_from_slice(frame.session_id_ref());
match Signature::from_bytes(remote_frame.signature_ref()) {
match Signature::from_bytes(frame.signature_ref()) {
Ok(sig) => match remote_verification_key.verify(bytes_to_verify, &sig) {
Ok(()) => {
let received_encapsulation_key = EncapsulationKey::decode(
own_context.ciphersuite().kem(),
remote_frame.body_ref(),
frame.body_ref(),
)?;
match validate_encapsulation_key(
&own_context.ciphersuite().hash_function(),
own_context.ciphersuite().hash_len(),
remote_frame.body_ref(),
frame.body_ref(),
expected_hash,
) {
true => Ok(received_encapsulation_key),
@@ -202,7 +206,7 @@ pub fn responder_ingest_message<'a>(
pub fn responder_process<'a>(
own_context: &mut KKTContext,
session_id: KKTSessionId,
session_id: &[u8],
signing_key: &ed25519::PrivateKey,
encapsulation_key: &EncapsulationKey<'a>,
) -> Result<KKTFrame, KKTError> {
@@ -214,11 +218,11 @@ pub fn responder_process<'a>(
Vec::with_capacity(own_context.full_message_len() - own_context.signature_len());
bytes_to_sign.extend_from_slice(&own_context.encode()?);
bytes_to_sign.extend_from_slice(&body);
bytes_to_sign.extend_from_slice(&session_id);
bytes_to_sign.extend_from_slice(session_id);
let signature = signing_key.sign(bytes_to_sign).to_bytes();
Ok(KKTFrame::new(context_bytes, &body, session_id, &signature))
Ok(KKTFrame::new(&context_bytes, &body, session_id, &signature))
}
fn check_compatibility(
+4 -1
View File
@@ -5,6 +5,7 @@ edition = { workspace = true }
license = { workspace = true }
[dependencies]
bincode = { workspace = true }
thiserror = { workspace = true }
parking_lot = { workspace = true }
snow = { workspace = true }
@@ -13,7 +14,9 @@ serde = { workspace = true }
bytes = { workspace = true }
dashmap = { workspace = true }
sha2 = { workspace = true }
ansi_term = { workspace = true }
tracing = { workspace = true }
utoipa = { workspace = true, features = ["macros", "non_strict_integers"] }
rand = { workspace = true }
# rand 0.9 for KKT integration (nym-kkt uses rand 0.9)
rand09 = { package = "rand", version = "0.9.2" }
@@ -21,6 +24,7 @@ rand09 = { package = "rand", version = "0.9.2" }
nym-crypto = { path = "../crypto", features = ["hashing", "asymmetric"] }
nym-kkt = { path = "../nym-kkt" }
nym-lp-common = { path = "../nym-lp-common" }
nym-sphinx = { path = "../nymsphinx" }
# libcrux dependencies for PSQ (Post-Quantum PSK derivation)
libcrux-psq = { git = "https://github.com/cryspen/libcrux", features = [
@@ -36,7 +40,6 @@ zeroize = { workspace = true, features = ["zeroize_derive"] }
[dev-dependencies]
criterion = { version = "0.5", features = ["html_reports"] }
rand_chacha = "0.3"
nym-crypto = { path = "../crypto", features = ["rand"] }
[[bench]]
+150 -35
View File
@@ -2,8 +2,13 @@
// SPDX-License-Identifier: Apache-2.0
use crate::LpError;
use crate::message::{LpMessage, MessageType};
use crate::message::{
ClientHelloData, EncryptedDataPayload, ForwardPacketData, HandshakeData, KKTRequestData,
KKTResponseData, LpMessage, MessageType, SubsessionKK1Data, SubsessionKK2Data,
SubsessionReadyData,
};
use crate::packet::{LpHeader, LpPacket, OuterHeader, TRAILER_LEN};
use crate::serialisation::{BincodeOptions, lp_bincode_serializer};
use bytes::{BufMut, BytesMut};
use chacha20poly1305::{
ChaCha20Poly1305, Key, Nonce, Tag,
@@ -91,7 +96,87 @@ fn parse_message_from_type_and_content(
let message_type = MessageType::from_u32(msg_type_raw)
.ok_or_else(|| LpError::invalid_message_type(msg_type_raw))?;
LpMessage::decode_content(content, message_type)
match message_type {
MessageType::Busy => {
if !content.is_empty() {
return Err(LpError::InvalidPayloadSize {
expected: 0,
actual: content.len(),
});
}
Ok(LpMessage::Busy)
}
MessageType::Handshake => Ok(LpMessage::Handshake(HandshakeData(content.to_vec()))),
MessageType::EncryptedData => Ok(LpMessage::EncryptedData(EncryptedDataPayload(
content.to_vec(),
))),
MessageType::ClientHello => {
let data = ClientHelloData::decode(content)?;
Ok(LpMessage::ClientHello(data))
}
MessageType::KKTRequest => Ok(LpMessage::KKTRequest(KKTRequestData(content.to_vec()))),
MessageType::KKTResponse => Ok(LpMessage::KKTResponse(KKTResponseData(content.to_vec()))),
MessageType::ForwardPacket => {
let data: ForwardPacketData = lp_bincode_serializer()
.deserialize(content)
.map_err(|e| LpError::DeserializationError(e.to_string()))?;
Ok(LpMessage::ForwardPacket(data))
}
MessageType::Collision => {
if !content.is_empty() {
return Err(LpError::InvalidPayloadSize {
expected: 0,
actual: content.len(),
});
}
Ok(LpMessage::Collision)
}
MessageType::Ack => {
if !content.is_empty() {
return Err(LpError::InvalidPayloadSize {
expected: 0,
actual: content.len(),
});
}
Ok(LpMessage::Ack)
}
MessageType::SubsessionRequest => {
if !content.is_empty() {
return Err(LpError::InvalidPayloadSize {
expected: 0,
actual: content.len(),
});
}
Ok(LpMessage::SubsessionRequest)
}
MessageType::SubsessionKK1 => {
let data: SubsessionKK1Data = lp_bincode_serializer()
.deserialize(content)
.map_err(|e| LpError::DeserializationError(e.to_string()))?;
Ok(LpMessage::SubsessionKK1(data))
}
MessageType::SubsessionKK2 => {
let data: SubsessionKK2Data = lp_bincode_serializer()
.deserialize(content)
.map_err(|e| LpError::DeserializationError(e.to_string()))?;
Ok(LpMessage::SubsessionKK2(data))
}
MessageType::SubsessionReady => {
let data: SubsessionReadyData = lp_bincode_serializer()
.deserialize(content)
.map_err(|e| LpError::DeserializationError(e.to_string()))?;
Ok(LpMessage::SubsessionReady(data))
}
MessageType::SubsessionAbort => {
// Empty signal message - no content to deserialize
if !content.is_empty() {
return Err(LpError::DeserializationError(
"SubsessionAbort should have no payload".to_string(),
));
}
Ok(LpMessage::SubsessionAbort)
}
}
}
/// Parse only the outer header from raw packet bytes.
@@ -164,7 +249,7 @@ pub fn parse_lp_packet(src: &[u8], outer_key: Option<&OuterAeadKey>) -> Result<L
let header = LpHeader {
protocol_version,
reserved: [0u8; 3],
reserved: 0,
receiver_idx: outer_header.receiver_idx,
counter: outer_header.counter,
};
@@ -208,7 +293,7 @@ pub fn parse_lp_packet(src: &[u8], outer_key: Option<&OuterAeadKey>) -> Result<L
let header = LpHeader {
protocol_version,
reserved: [0u8; 3],
reserved: 0,
receiver_idx: outer_header.receiver_idx,
counter: outer_header.counter,
};
@@ -321,8 +406,6 @@ mod tests {
use crate::message::{EncryptedDataPayload, HandshakeData, LpMessage, MessageType};
use crate::packet::{LpHeader, LpPacket, TRAILER_LEN};
use bytes::BytesMut;
use nym_crypto::asymmetric::{ed25519, x25519};
use rand::thread_rng;
// With unified format, outer header (receiver_idx + counter) is always first
// and is the only cleartext portion for encrypted packets
@@ -338,7 +421,7 @@ mod tests {
let packet = LpPacket {
header: LpHeader {
protocol_version: 1,
reserved: [0u8; 3],
reserved: 0,
receiver_idx: 42,
counter: 123,
},
@@ -369,7 +452,7 @@ mod tests {
let packet = LpPacket {
header: LpHeader {
protocol_version: 1,
reserved: [0u8; 3],
reserved: 0,
receiver_idx: 42,
counter: 123,
},
@@ -407,7 +490,7 @@ mod tests {
let packet = LpPacket {
header: LpHeader {
protocol_version: 1,
reserved: [0u8; 3],
reserved: 0,
receiver_idx: 42,
counter: 123,
},
@@ -605,13 +688,11 @@ mod tests {
fn test_serialize_parse_client_hello() {
use crate::message::ClientHelloData;
let mut rng = thread_rng();
let valid_ed25519 = ed25519::KeyPair::new(&mut rng);
let mut dst = BytesMut::new();
// Create ClientHelloData
let client_key = x25519::PublicKey::from_byte_array(&[42u8; 32]);
let client_ed25519_key = *valid_ed25519.public_key();
let client_key = [42u8; 32];
let client_ed25519_key = [43u8; 32];
let salt = [99u8; 32];
let hello_data = ClientHelloData {
receiver_index: 12345,
@@ -624,7 +705,7 @@ mod tests {
let packet = LpPacket {
header: LpHeader {
protocol_version: 1,
reserved: [0u8; 3],
reserved: 0,
receiver_idx: 42,
counter: 123,
},
@@ -665,11 +746,8 @@ mod tests {
.as_secs();
// Create ClientHelloData with fresh salt
let mut rng = thread_rng();
let valid_ed25519 = ed25519::KeyPair::new(&mut rng);
let client_key = x25519::PublicKey::from_byte_array(&[7u8; 32]);
let client_ed25519_key = *valid_ed25519.public_key();
let client_key = [7u8; 32];
let client_ed25519_key = [8u8; 32];
let hello_data =
ClientHelloData::new_with_fresh_salt(client_key, client_ed25519_key, timestamp);
@@ -677,7 +755,7 @@ mod tests {
let packet = LpPacket {
header: LpHeader {
protocol_version: 1,
reserved: [0u8; 3],
reserved: 0,
receiver_idx: 100,
counter: 200,
},
@@ -756,6 +834,43 @@ mod tests {
}
}
#[test]
fn test_client_hello_different_protocol_versions() {
use crate::message::ClientHelloData;
for version in [0u8, 1, 2, 255] {
let mut dst = BytesMut::new();
let hello_data = ClientHelloData {
receiver_index: version as u32,
client_lp_public_key: [version; 32],
client_ed25519_public_key: [version.wrapping_add(2); 32],
salt: [version.wrapping_add(1); 32],
};
let packet = LpPacket {
header: LpHeader {
protocol_version: 1,
reserved: 0,
receiver_idx: version as u32,
counter: version as u64,
},
message: LpMessage::ClientHello(hello_data.clone()),
trailer: [version; TRAILER_LEN],
};
serialize_lp_packet(&packet, &mut dst, None).unwrap();
let decoded = parse_lp_packet(&dst, None).unwrap();
match decoded.message {
LpMessage::ClientHello(decoded_data) => {
assert_eq!(decoded_data.client_lp_public_key, [version; 32]);
}
_ => panic!("Expected ClientHello message for version {}", version),
}
}
}
#[test]
fn test_forward_packet_encode_decode_roundtrip() {
let mut dst = BytesMut::new();
@@ -769,7 +884,7 @@ mod tests {
let packet = LpPacket {
header: LpHeader {
protocol_version: 1,
reserved: [0u8; 3],
reserved: 0,
receiver_idx: 999,
counter: 555,
},
@@ -807,7 +922,7 @@ mod tests {
let packet = LpPacket {
header: LpHeader {
protocol_version: 1,
reserved: [0u8; 3],
reserved: 0,
receiver_idx: 12345,
counter: 999,
},
@@ -836,7 +951,7 @@ mod tests {
let packet = LpPacket {
header: LpHeader {
protocol_version: 1,
reserved: [0u8; 3],
reserved: 0,
receiver_idx: 12345,
counter: 999,
},
@@ -883,7 +998,7 @@ mod tests {
let packet = LpPacket {
header: LpHeader {
protocol_version: 1,
reserved: [0u8; 3],
reserved: 0,
receiver_idx: 12345,
counter: 999,
},
@@ -912,7 +1027,7 @@ mod tests {
let packet = LpPacket {
header: LpHeader {
protocol_version: 1,
reserved: [0u8; 3],
reserved: 0,
receiver_idx: 12345,
counter: 999,
},
@@ -941,7 +1056,7 @@ mod tests {
let packet1 = LpPacket {
header: LpHeader {
protocol_version: 1,
reserved: [0u8; 3],
reserved: 0,
receiver_idx: 12345,
counter: 1,
},
@@ -952,7 +1067,7 @@ mod tests {
let packet2 = LpPacket {
header: LpHeader {
protocol_version: 1,
reserved: [0u8; 3],
reserved: 0,
receiver_idx: 12345,
counter: 2, // Different counter
},
@@ -987,7 +1102,7 @@ mod tests {
let packet = LpPacket {
header: LpHeader {
protocol_version: 1,
reserved: [0u8; 3],
reserved: 0,
receiver_idx: 12345,
counter: 999,
},
@@ -1013,7 +1128,7 @@ mod tests {
let packet = LpPacket {
header: LpHeader {
protocol_version: 1,
reserved: [0u8; 3],
reserved: 0,
receiver_idx: 54321,
counter: 12345678,
},
@@ -1046,7 +1161,7 @@ mod tests {
let packet = LpPacket {
header: LpHeader {
protocol_version: 1,
reserved: [0u8; 3],
reserved: 0,
receiver_idx: 99999,
counter: 2,
},
@@ -1076,7 +1191,7 @@ mod tests {
let packet = LpPacket {
header: LpHeader {
protocol_version: 1,
reserved: [0u8; 3],
reserved: 0,
receiver_idx: 42,
counter: 100,
},
@@ -1105,7 +1220,7 @@ mod tests {
let packet = LpPacket {
header: LpHeader {
protocol_version: 1,
reserved: [0u8; 3],
reserved: 0,
receiver_idx: 123,
counter: 456,
},
@@ -1138,7 +1253,7 @@ mod tests {
let packet = LpPacket {
header: LpHeader {
protocol_version: 1,
reserved: [0u8; 3],
reserved: 0,
receiver_idx: 789,
counter: 1000,
},
@@ -1171,7 +1286,7 @@ mod tests {
let packet = LpPacket {
header: LpHeader {
protocol_version: 1,
reserved: [0u8; 3],
reserved: 0,
receiver_idx: 42,
counter: 200,
},
@@ -1226,7 +1341,7 @@ mod tests {
let packet = LpPacket {
header: LpHeader {
protocol_version: 1,
reserved: [0u8; 3],
reserved: 0,
receiver_idx: 54321,
counter: 999,
},
-8
View File
@@ -82,12 +82,4 @@ pub enum LpError {
/// Outer AEAD authentication tag verification failed.
#[error("AEAD authentication tag verification failed")]
AeadTagMismatch,
/// Received an LP packet with an incompatible, future, version
#[error("incompatible LP packet version. got: {got}, highest supported: {highest_supported}")]
IncompatibleFuturePacketVersion { got: u8, highest_supported: u8 },
/// Received an LP packet with an incompatible, legacy, version
#[error("incompatible LP packet version. got: {got}, lowest supported: {lowest_supported}")]
IncompatibleLegacyPacketVersion { got: u8, lowest_supported: u8 },
}
+200
View File
@@ -0,0 +1,200 @@
use std::fmt::{self, Display, Formatter};
use std::ops::Deref;
use std::str::FromStr;
use nym_sphinx::{PrivateKey as SphinxPrivateKey, PublicKey as SphinxPublicKey};
use serde::Serialize;
use utoipa::ToSchema;
use crate::LpError;
#[derive(Clone)]
pub struct PrivateKey(SphinxPrivateKey);
impl fmt::Debug for PrivateKey {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_tuple("PrivateKey").field(&"[REDACTED]").finish()
}
}
impl Deref for PrivateKey {
type Target = SphinxPrivateKey;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl Default for PrivateKey {
fn default() -> Self {
Self::new()
}
}
impl PrivateKey {
pub fn new() -> Self {
let private_key = SphinxPrivateKey::random();
Self(private_key)
}
pub fn to_base58_string(&self) -> String {
bs58::encode(self.0.to_bytes()).into_string()
}
pub fn from_base58_string(s: &str) -> Result<Self, LpError> {
let bytes: [u8; 32] = bs58::decode(s).into_vec()?.try_into().unwrap();
Ok(PrivateKey(SphinxPrivateKey::from(bytes)))
}
pub fn from_bytes(bytes: &[u8; 32]) -> Self {
PrivateKey(SphinxPrivateKey::from(*bytes))
}
pub fn public_key(&self) -> PublicKey {
let public_key = SphinxPublicKey::from(&self.0);
PublicKey(public_key)
}
}
#[derive(Clone)]
pub struct PublicKey(SphinxPublicKey);
impl fmt::Debug for PublicKey {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_tuple("PublicKey")
.field(&self.to_base58_string())
.finish()
}
}
impl Deref for PublicKey {
type Target = SphinxPublicKey;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl PublicKey {
pub fn to_base58_string(&self) -> String {
bs58::encode(self.0.as_bytes()).into_string()
}
pub fn from_base58_string(s: &str) -> Result<Self, LpError> {
let bytes: [u8; 32] = bs58::decode(s).into_vec()?.try_into().unwrap();
Ok(PublicKey(SphinxPublicKey::from(bytes)))
}
pub fn from_bytes(bytes: &[u8; 32]) -> Result<Self, LpError> {
Ok(PublicKey(SphinxPublicKey::from(*bytes)))
}
pub fn as_bytes(&self) -> &[u8; 32] {
self.0.as_bytes()
}
}
impl Default for PublicKey {
fn default() -> Self {
let private_key = PrivateKey::default();
private_key.public_key()
}
}
pub struct Keypair {
private_key: PrivateKey,
public_key: PublicKey,
}
impl Default for Keypair {
fn default() -> Self {
Self::new()
}
}
impl Keypair {
pub fn new() -> Self {
let private_key = PrivateKey::default();
let public_key = private_key.public_key();
Self {
private_key,
public_key,
}
}
pub fn from_private_key(private_key: PrivateKey) -> Self {
let public_key = private_key.public_key();
Self {
private_key,
public_key,
}
}
pub fn from_keys(private_key: PrivateKey, public_key: PublicKey) -> Self {
Self {
private_key,
public_key,
}
}
pub fn private_key(&self) -> &PrivateKey {
&self.private_key
}
pub fn public_key(&self) -> &PublicKey {
&self.public_key
}
}
impl From<KeypairReadable> for Keypair {
fn from(keypair: KeypairReadable) -> Self {
Self {
private_key: PrivateKey::from_base58_string(&keypair.private).unwrap(),
public_key: PublicKey::from_base58_string(&keypair.public).unwrap(),
}
}
}
impl From<&Keypair> for KeypairReadable {
fn from(keypair: &Keypair) -> Self {
Self {
private: keypair.private_key.to_base58_string(),
public: keypair.public_key.to_base58_string(),
}
}
}
impl FromStr for PrivateKey {
type Err = LpError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
PrivateKey::from_base58_string(s)
}
}
impl Display for PrivateKey {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.to_base58_string())
}
}
impl Display for PublicKey {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.to_base58_string())
}
}
#[derive(Serialize, serde::Deserialize, Clone, ToSchema, Debug)]
pub struct KeypairReadable {
private: String,
public: String,
}
impl KeypairReadable {
pub fn private_key(&self) -> Result<PrivateKey, LpError> {
PrivateKey::from_base58_string(&self.private)
}
pub fn public_key(&self) -> Result<PublicKey, LpError> {
PublicKey::from_base58_string(&self.public)
}
}
+102 -133
View File
@@ -37,10 +37,9 @@
//! ).unwrap();
//!
//! // Client: Create request
//! let (session_secret, client_context, request_data) = create_request(
//! let (client_context, request_data) = create_request(
//! ciphersuite,
//! &client_signing_key,
//! &responder_dh_public_key
//! ).unwrap();
//!
//! // Gateway: Handle request
@@ -48,14 +47,12 @@
//! &request_data,
//! Some(&client_verification_key),
//! &gateway_signing_key,
//! &gateway_dh_private_key,
//! &gateway_kem_public_key,
//! ).unwrap();
//!
//! // Client: Process response
//! let gateway_kem_key = process_response(
//! client_context,
//! &session_secret,
//! &gateway_verification_key,
//! &expected_key_hash,
//! &response_data,
@@ -64,10 +61,10 @@
use crate::LpError;
use crate::message::{KKTRequestData, KKTResponseData};
use nym_crypto::asymmetric::{ed25519, x25519};
use nym_crypto::asymmetric::ed25519;
use nym_kkt::ciphersuite::{Ciphersuite, EncapsulationKey};
use nym_kkt::context::KKTContext;
use nym_kkt::encryption::KKTSessionSecret;
use nym_kkt::frame::KKTFrame;
use nym_kkt::kkt::{handle_kem_request, request_kem_key, validate_kem_response};
/// Creates a KKT request to obtain the responder's KEM public key.
@@ -78,10 +75,8 @@ use nym_kkt::kkt::{handle_kem_request, request_kem_key, validate_kem_response};
/// # Arguments
/// * `ciphersuite` - Negotiated ciphersuite (KEM, hash, signature algorithms)
/// * `signing_key` - Client's Ed25519 signing key for authentication
/// * `responder_dh_public_key` - Gateway's x25519 public key (from directory)
///
/// # Returns
/// * `KKTSessionSecret` - Session secret key to encrypt/decrypt KKT messages for this session
/// * `KKTContext` - Context to use when validating the response
/// * `KKTRequestData` - Serialized KKT request frame to send to gateway
///
@@ -90,15 +85,14 @@ use nym_kkt::kkt::{handle_kem_request, request_kem_key, validate_kem_response};
pub fn create_request(
ciphersuite: Ciphersuite,
signing_key: &ed25519::PrivateKey,
responder_dh_public_key: &x25519::PublicKey,
) -> Result<(KKTSessionSecret, KKTContext, KKTRequestData), LpError> {
) -> Result<(KKTContext, KKTRequestData), LpError> {
// Note: Uses rand 0.9's thread_rng() to match nym-kkt's rand version
let mut rng = rand09::rng();
let (session_secret, context, request_bytes) =
request_kem_key(&mut rng, ciphersuite, signing_key, responder_dh_public_key)
.map_err(|e| LpError::KKTError(e.to_string()))?;
let (context, frame) = request_kem_key(&mut rng, ciphersuite, signing_key)
.map_err(|e| LpError::KKTError(e.to_string()))?;
Ok((session_secret, context, KKTRequestData(request_bytes)))
let request_bytes = frame.to_bytes();
Ok((context, KKTRequestData(request_bytes)))
}
/// Processes a KKT response and extracts the responder's KEM public key.
@@ -108,7 +102,6 @@ pub fn create_request(
///
/// # Arguments
/// * `context` - Context from the initial `create_request()` call
/// * `session_secret` - The KKT session secret key from the initial `create_request()` call
/// * `responder_vk` - Responder's Ed25519 verification key (from directory)
/// * `expected_key_hash` - Expected hash of responder's KEM key (from directory)
/// * `response_data` - Serialized KKT response frame from responder
@@ -123,14 +116,12 @@ pub fn create_request(
/// - Key hash doesn't match expected value
pub fn process_response<'a>(
mut context: KKTContext,
session_secret: &KKTSessionSecret,
responder_vk: &ed25519::PublicKey,
expected_key_hash: &[u8],
response_data: &KKTResponseData,
) -> Result<EncapsulationKey<'a>, LpError> {
validate_kem_response(
&mut context,
session_secret,
responder_vk,
expected_key_hash,
&response_data.0,
@@ -148,7 +139,6 @@ pub fn process_response<'a>(
/// * `request_data` - Serialized KKT request frame from initiator
/// * `initiator_vk` - Initiator's Ed25519 verification key (None for anonymous)
/// * `responder_signing_key` - Gateway's Ed25519 signing key
/// * `responder_dh_private_key` - Gateway's x25519 private key
/// * `responder_kem_key` - Gateway's KEM public key to send
///
/// # Returns
@@ -163,21 +153,22 @@ pub fn handle_request<'a>(
request_data: &KKTRequestData,
initiator_vk: Option<&ed25519::PublicKey>,
responder_signing_key: &ed25519::PrivateKey,
responder_dh_private_key: &x25519::PrivateKey,
responder_kem_key: &EncapsulationKey<'a>,
) -> Result<KKTResponseData, LpError> {
let mut rng = rand09::rng();
// Deserialize request frame
let (request_frame, _) = KKTFrame::from_bytes(&request_data.0)
.map_err(|e| LpError::KKTError(format!("Failed to parse KKT request: {}", e)))?;
// Handle the request and generate response
let response_bytes = handle_kem_request(
&mut rng,
&request_data.0,
let response_frame = handle_kem_request(
&request_frame,
initiator_vk,
responder_signing_key,
responder_dh_private_key,
responder_kem_key,
)
.map_err(|e| LpError::KKTError(e.to_string()))?;
let response_bytes = response_frame.to_bytes();
Ok(KKTResponseData(response_bytes))
}
@@ -185,10 +176,7 @@ pub fn handle_request<'a>(
mod tests {
use super::*;
use nym_kkt::ciphersuite::{HashFunction, KEM, SignatureScheme};
use nym_kkt::key_utils::{
generate_keypair_ed25519, generate_keypair_libcrux, generate_keypair_x25519,
hash_encapsulation_key,
};
use nym_kkt::key_utils::{generate_keypair_libcrux, hash_encapsulation_key};
use rand09::RngCore;
#[test]
@@ -196,10 +184,13 @@ mod tests {
let mut rng = rand09::rng();
// Generate Ed25519 keypairs for both parties
let initiator_ed25519_keypair = generate_keypair_ed25519(&mut rng, Some(0));
let responder_ed25519_keypair = generate_keypair_ed25519(&mut rng, Some(1));
let mut initiator_secret = [0u8; 32];
rng.fill_bytes(&mut initiator_secret);
let initiator_keypair = ed25519::KeyPair::from_secret(initiator_secret, 0);
let responder_x25519 = generate_keypair_x25519(&mut rng);
let mut responder_secret = [0u8; 32];
rng.fill_bytes(&mut responder_secret);
let responder_keypair = ed25519::KeyPair::from_secret(responder_secret, 1);
// Generate responder's KEM keypair (X25519 for testing)
let (_, responder_kem_pk) = generate_keypair_libcrux(&mut rng, KEM::X25519).unwrap();
@@ -222,19 +213,14 @@ mod tests {
);
// Client: Create request
let (session_secret, context, request_data) = create_request(
ciphersuite,
initiator_ed25519_keypair.private_key(),
responder_x25519.public_key(),
)
.unwrap();
let (context, request_data) =
create_request(ciphersuite, initiator_keypair.private_key()).unwrap();
// Gateway: Handle request
let response_data = handle_request(
&request_data,
Some(initiator_ed25519_keypair.public_key()),
responder_ed25519_keypair.private_key(),
responder_x25519.private_key(),
Some(initiator_keypair.public_key()),
responder_keypair.private_key(),
&responder_kem_key,
)
.unwrap();
@@ -242,8 +228,7 @@ mod tests {
// Client: Process response
let obtained_key = process_response(
context,
&session_secret,
responder_ed25519_keypair.public_key(),
responder_keypair.public_key(),
&key_hash,
&response_data,
)
@@ -253,71 +238,70 @@ mod tests {
assert_eq!(obtained_key.encode(), responder_kem_key.encode());
}
// #[test]
// fn test_kkt_roundtrip_anonymous() {
// let mut rng = rand09::rng();
#[test]
fn test_kkt_roundtrip_anonymous() {
let mut rng = rand09::rng();
// // Only responder has keys (anonymous initiator)
// // Generate Ed25519 keypairs for both parties
// Only responder has keys (anonymous initiator)
let mut responder_secret = [0u8; 32];
rng.fill_bytes(&mut responder_secret);
let responder_keypair = ed25519::KeyPair::from_secret(responder_secret, 1);
// let responder_ed25519_keypair = generate_keypair_ed25519(&mut rng, Some(1));
let (_, responder_kem_pk) = generate_keypair_libcrux(&mut rng, KEM::X25519).unwrap();
let responder_kem_key = EncapsulationKey::X25519(responder_kem_pk);
// let responder_x25519 = generate_keypair_x25519(&mut rng);
let ciphersuite = Ciphersuite::resolve_ciphersuite(
KEM::X25519,
HashFunction::Blake3,
SignatureScheme::Ed25519,
None,
)
.unwrap();
// let (_, responder_kem_pk) = generate_keypair_libcrux(&mut rng, KEM::X25519).unwrap();
// let responder_kem_key = EncapsulationKey::X25519(responder_kem_pk);
let key_hash = hash_encapsulation_key(
&ciphersuite.hash_function(),
ciphersuite.hash_len(),
&responder_kem_key.encode(),
);
// let ciphersuite = Ciphersuite::resolve_ciphersuite(
// KEM::X25519,
// HashFunction::Blake3,
// SignatureScheme::Ed25519,
// None,
// )
// .unwrap();
// Anonymous initiator - use anonymous_initiator_process directly
use nym_kkt::kkt::anonymous_initiator_process;
let (mut context, request_frame) =
anonymous_initiator_process(&mut rng, ciphersuite).unwrap();
let request_data = KKTRequestData(request_frame.to_bytes());
// let key_hash = hash_encapsulation_key(
// &ciphersuite.hash_function(),
// ciphersuite.hash_len(),
// &responder_kem_key.encode(),
// );
// Gateway: Handle anonymous request
let response_data = handle_request(
&request_data,
None, // Anonymous - no verification key
responder_keypair.private_key(),
&responder_kem_key,
)
.unwrap();
// // Anonymous initiator - use anonymous_initiator_process directly
// use nym_kkt::kkt::anonymous_initiator_process;
// let (mut context, request_frame) =
// anonymous_initiator_process(&mut rng, ciphersuite).unwrap();
// let request_data = KKTRequestData(request_frame.to_bytes());
// Initiator: Validate response
let obtained_key = validate_kem_response(
&mut context,
responder_keypair.public_key(),
&key_hash,
&response_data.0,
)
.unwrap();
// // Gateway: Handle anonymous request
// let response_data = handle_request(
// &request_data,
// None,
// responder_ed25519_keypair.private_key(),
// &responder_x25519_sk,
// &responder_kem_key,
// )
// .unwrap();
// // Initiator: Validate response
// let obtained_key = initiator_ingest_response(
// &mut context,
// responder_ed25519_keypair.public_key(),
// &key_hash,
// &response_data.0,
// )
// .unwrap();
// assert_eq!(obtained_key.encode(), responder_kem_key.encode());
// }
assert_eq!(obtained_key.encode(), responder_kem_key.encode());
}
#[test]
fn test_invalid_signature_rejected() {
let mut rng = rand09::rng();
// Generate Ed25519 keypairs for both parties
let initiator_ed25519_keypair = generate_keypair_ed25519(&mut rng, Some(0));
let responder_ed25519_keypair = generate_keypair_ed25519(&mut rng, Some(1));
let mut initiator_secret = [0u8; 32];
rng.fill_bytes(&mut initiator_secret);
let initiator_keypair = ed25519::KeyPair::from_secret(initiator_secret, 0);
let responder_x25519 = generate_keypair_x25519(&mut rng);
let mut responder_secret = [0u8; 32];
rng.fill_bytes(&mut responder_secret);
let responder_keypair = ed25519::KeyPair::from_secret(responder_secret, 1);
// Different keypair for wrong signature
let mut wrong_secret = [0u8; 32];
@@ -335,19 +319,14 @@ mod tests {
)
.unwrap();
let (_session_secret, _context, request_data) = create_request(
ciphersuite,
initiator_ed25519_keypair.private_key(),
responder_x25519.public_key(),
)
.unwrap();
let (_context, request_data) =
create_request(ciphersuite, initiator_keypair.private_key()).unwrap();
// Gateway handles request but we provide WRONG verification key
let result = handle_request(
&request_data,
Some(wrong_keypair.public_key()), // Wrong key!
responder_ed25519_keypair.private_key(),
responder_x25519.private_key(),
responder_keypair.private_key(),
&responder_kem_key,
);
@@ -364,11 +343,13 @@ mod tests {
fn test_hash_mismatch_rejected() {
let mut rng = rand09::rng();
// Generate Ed25519 keypairs for both parties
let initiator_ed25519_keypair = generate_keypair_ed25519(&mut rng, Some(0));
let responder_ed25519_keypair = generate_keypair_ed25519(&mut rng, Some(1));
let mut initiator_secret = [0u8; 32];
rng.fill_bytes(&mut initiator_secret);
let initiator_keypair = ed25519::KeyPair::from_secret(initiator_secret, 0);
let responder_x25519 = generate_keypair_x25519(&mut rng);
let mut responder_secret = [0u8; 32];
rng.fill_bytes(&mut responder_secret);
let responder_keypair = ed25519::KeyPair::from_secret(responder_secret, 1);
let (_, responder_kem_pk) = generate_keypair_libcrux(&mut rng, KEM::X25519).unwrap();
let responder_kem_key = EncapsulationKey::X25519(responder_kem_pk);
@@ -384,18 +365,13 @@ mod tests {
// Use WRONG hash
let wrong_hash = [0u8; 32];
let (session_secret, context, request_data) = create_request(
ciphersuite,
initiator_ed25519_keypair.private_key(),
responder_x25519.public_key(),
)
.unwrap();
let (context, request_data) =
create_request(ciphersuite, initiator_keypair.private_key()).unwrap();
let response_data = handle_request(
&request_data,
Some(initiator_ed25519_keypair.public_key()),
responder_ed25519_keypair.private_key(),
responder_x25519.private_key(),
Some(initiator_keypair.public_key()),
responder_keypair.private_key(),
&responder_kem_key,
)
.unwrap();
@@ -403,8 +379,7 @@ mod tests {
// Client validates with WRONG hash
let result = process_response(
context,
&session_secret,
responder_ed25519_keypair.public_key(),
responder_keypair.public_key(),
&wrong_hash, // Wrong!
&response_data,
);
@@ -424,9 +399,7 @@ mod tests {
let mut responder_secret = [0u8; 32];
rng.fill_bytes(&mut responder_secret);
let responder_ed25519_keypair = generate_keypair_ed25519(&mut rng, Some(1));
let responder_x25519 = generate_keypair_x25519(&mut rng);
let responder_keypair = ed25519::KeyPair::from_secret(responder_secret, 1);
let (_, responder_kem_pk) = generate_keypair_libcrux(&mut rng, KEM::X25519).unwrap();
let responder_kem_key = EncapsulationKey::X25519(responder_kem_pk);
@@ -437,8 +410,7 @@ mod tests {
let result = handle_request(
&malformed_request,
None,
responder_ed25519_keypair.private_key(),
responder_x25519.private_key(),
responder_keypair.private_key(),
&responder_kem_key,
);
@@ -455,11 +427,13 @@ mod tests {
fn test_malformed_response_rejected() {
let mut rng = rand09::rng();
// Generate Ed25519 keypairs for both parties
let initiator_ed25519_keypair = generate_keypair_ed25519(&mut rng, Some(0));
let responder_ed25519_keypair = generate_keypair_ed25519(&mut rng, Some(1));
let mut initiator_secret = [0u8; 32];
rng.fill_bytes(&mut initiator_secret);
let initiator_keypair = ed25519::KeyPair::from_secret(initiator_secret, 0);
let responder_x25519 = generate_keypair_x25519(&mut rng);
let mut responder_secret = [0u8; 32];
rng.fill_bytes(&mut responder_secret);
let responder_keypair = ed25519::KeyPair::from_secret(responder_secret, 1);
let ciphersuite = Ciphersuite::resolve_ciphersuite(
KEM::X25519,
@@ -469,12 +443,8 @@ mod tests {
)
.unwrap();
let (session_secret, context, _request_data) = create_request(
ciphersuite,
initiator_ed25519_keypair.private_key(),
responder_x25519.public_key(),
)
.unwrap();
let (context, _request_data) =
create_request(ciphersuite, initiator_keypair.private_key()).unwrap();
// Create malformed response data
let malformed_response = KKTResponseData(vec![0xFF; 100]);
@@ -482,8 +452,7 @@ mod tests {
let result = process_response(
context,
&session_secret,
responder_ed25519_keypair.public_key(),
responder_keypair.public_key(),
&key_hash,
&malformed_response,
);
+36 -30
View File
@@ -4,6 +4,7 @@
pub mod codec;
pub mod config;
pub mod error;
pub mod keypair;
pub mod kkt_orchestrator;
pub mod message;
pub mod noise_protocol;
@@ -13,7 +14,6 @@ pub mod replay;
pub mod session;
mod session_integration;
pub mod session_manager;
pub mod state_machine;
pub use config::LpConfig;
pub use error::LpError;
@@ -22,6 +22,11 @@ pub use packet::{BOOTSTRAP_RECEIVER_IDX, LpPacket, OuterHeader};
pub use replay::{ReceivingKeyCounterValidator, ReplayError};
pub use session::{LpSession, generate_fresh_salt};
pub use session_manager::SessionManager;
// Add the new state machine module
pub mod serialisation;
pub mod state_machine;
pub use state_machine::LpStateMachine;
pub const NOISE_PATTERN: &str = "Noise_XKpsk3_25519_ChaChaPoly_SHA256";
@@ -29,14 +34,12 @@ pub const NOISE_PSK_INDEX: u8 = 3;
#[cfg(test)]
pub fn sessions_for_tests() -> (LpSession, LpSession) {
use nym_crypto::asymmetric::{ed25519, x25519};
use std::sync::Arc;
let mut rng = rand::thread_rng();
use crate::keypair::Keypair;
use nym_crypto::asymmetric::ed25519;
// X25519 keypairs for Noise protocol
let keypair_1 = Arc::new(x25519::KeyPair::new(&mut rng));
let keypair_2 = Arc::new(x25519::KeyPair::new(&mut rng));
let keypair_1 = Keypair::default();
let keypair_2 = Keypair::default();
// Use a fixed receiver_index for deterministic tests
let receiver_index: u32 = 12345;
@@ -45,8 +48,6 @@ pub fn sessions_for_tests() -> (LpSession, LpSession) {
let ed25519_keypair_1 = ed25519::KeyPair::from_secret([1u8; 32], 0);
let ed25519_keypair_2 = ed25519::KeyPair::from_secret([2u8; 32], 1);
let ed25519_keypair1_pubkey = *ed25519_keypair_1.public_key();
// Use consistent salt for deterministic tests
let salt = [1u8; 32];
@@ -55,8 +56,11 @@ pub fn sessions_for_tests() -> (LpSession, LpSession) {
let initiator_session = LpSession::new(
receiver_index,
true,
Arc::new(ed25519_keypair_1),
keypair_1.clone(),
(
ed25519_keypair_1.private_key(),
ed25519_keypair_1.public_key(),
),
keypair_1.private_key(),
ed25519_keypair_2.public_key(),
keypair_2.public_key(),
&salt,
@@ -66,9 +70,12 @@ pub fn sessions_for_tests() -> (LpSession, LpSession) {
let responder_session = LpSession::new(
receiver_index,
false,
Arc::new(ed25519_keypair_2),
keypair_2.clone(),
&ed25519_keypair1_pubkey,
(
ed25519_keypair_2.private_key(),
ed25519_keypair_2.public_key(),
),
keypair_2.private_key(),
ed25519_keypair_1.public_key(),
keypair_1.public_key(),
&salt,
)
@@ -84,7 +91,6 @@ mod tests {
use crate::session_manager::SessionManager;
use crate::{LpError, sessions_for_tests};
use bytes::BytesMut;
use std::sync::Arc;
// Import the new standalone functions
use crate::codec::{parse_lp_packet, serialize_lp_packet};
@@ -98,7 +104,7 @@ mod tests {
let packet1 = LpPacket {
header: LpHeader {
protocol_version: 1,
reserved: [0u8; 3],
reserved: 0,
receiver_idx: 42, // Matches session's sending_index assumption for this test
counter: 0,
},
@@ -127,7 +133,7 @@ mod tests {
let packet2 = LpPacket {
header: LpHeader {
protocol_version: 1,
reserved: [0u8; 3],
reserved: 0,
receiver_idx: 42,
counter: 0, // Same counter as before (replay)
},
@@ -157,7 +163,7 @@ mod tests {
let packet3 = LpPacket {
header: LpHeader {
protocol_version: 1,
reserved: [0u8; 3],
reserved: 0,
receiver_idx: 42,
counter: 1, // Incremented counter
},
@@ -200,10 +206,6 @@ mod tests {
let ed25519_keypair_local = ed25519::KeyPair::from_secret([8u8; 32], 0);
let ed25519_keypair_remote = ed25519::KeyPair::from_secret([9u8; 32], 1);
let ed25519_keypair_local_pubkey = *ed25519_keypair_local.public_key();
let x25519_keypair_local_pubkey = ed25519_keypair_local_pubkey.to_x25519().unwrap();
let x25519_keypair_remote_pubkey = ed25519_keypair_remote.public_key().to_x25519().unwrap();
// Use fixed receiver_index for deterministic test
let receiver_index: u32 = 54321;
@@ -214,9 +216,11 @@ mod tests {
let _ = local_manager
.create_session_state_machine(
receiver_index,
Arc::new(ed25519_keypair_local),
(
ed25519_keypair_local.private_key(),
ed25519_keypair_local.public_key(),
),
ed25519_keypair_remote.public_key(),
&x25519_keypair_remote_pubkey,
true,
&salt,
)
@@ -225,9 +229,11 @@ mod tests {
let _ = remote_manager
.create_session_state_machine(
receiver_index,
Arc::new(ed25519_keypair_remote),
&ed25519_keypair_local_pubkey,
&x25519_keypair_local_pubkey,
(
ed25519_keypair_remote.private_key(),
ed25519_keypair_remote.public_key(),
),
ed25519_keypair_local.public_key(),
false,
&salt,
)
@@ -236,7 +242,7 @@ mod tests {
let packet1 = LpPacket {
header: LpHeader {
protocol_version: 1,
reserved: [0u8; 3],
reserved: 0,
receiver_idx: receiver_index,
counter: 0,
},
@@ -269,7 +275,7 @@ mod tests {
let packet2 = LpPacket {
header: LpHeader {
protocol_version: 1,
reserved: [0u8; 3],
reserved: 0,
receiver_idx: receiver_index,
counter: 1,
},
@@ -297,7 +303,7 @@ mod tests {
let packet3 = LpPacket {
header: LpHeader {
protocol_version: 1,
reserved: [0u8; 3],
reserved: 0,
receiver_idx: receiver_index,
counter: 0, // Replay of first packet
},
+78 -321
View File
@@ -1,10 +1,10 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::serialisation::{BincodeOptions, lp_bincode_serializer};
use crate::{BOOTSTRAP_RECEIVER_IDX, LpError};
use bytes::{BufMut, BytesMut};
use num_enum::{IntoPrimitive, TryFromPrimitive};
use nym_crypto::asymmetric::{ed25519, x25519};
use serde::{Deserialize, Serialize};
use std::fmt::{self, Display};
@@ -15,21 +15,16 @@ pub struct ClientHelloData {
/// Auto-generated randomly by the client
pub receiver_index: u32,
/// Client's LP x25519 public key (32 bytes) - derived from Ed25519 key
pub client_lp_public_key: x25519::PublicKey,
pub client_lp_public_key: [u8; 32],
/// Client's Ed25519 public key (32 bytes) - for PSQ authentication
pub client_ed25519_public_key: ed25519::PublicKey,
pub client_ed25519_public_key: [u8; 32],
/// Salt for PSK derivation (32 bytes: 8-byte timestamp + 24-byte nonce)
pub salt: [u8; 32],
}
impl ClientHelloData {
// 4 bytes for receiver index + 32 bytes for client lp key, 32 bytes for client ed25519 key + 32 bytes for salt
pub const LEN: usize = 100;
fn len(&self) -> usize {
Self::LEN
}
fn generate_receiver_index() -> u32 {
loop {
let candidate = rand::random();
@@ -47,8 +42,8 @@ impl ClientHelloData {
/// * `client_lp_public_key` - Client's x25519 public key (derived from Ed25519)
/// * `client_ed25519_public_key` - Client's Ed25519 public key (for PSQ authentication)
pub fn new_with_fresh_salt(
client_lp_public_key: x25519::PublicKey,
client_ed25519_public_key: ed25519::PublicKey,
client_lp_public_key: [u8; 32],
client_ed25519_public_key: [u8; 32],
timestamp: u64,
) -> Self {
// Generate salt: timestamp + nonce
@@ -79,13 +74,14 @@ impl ClientHelloData {
u64::from_le_bytes(timestamp_bytes)
}
pub fn encode(&self, dst: &mut BytesMut) {
dst.put_u32_le(self.receiver_index);
dst.put_slice(self.client_lp_public_key.as_bytes());
dst.put_slice(self.client_ed25519_public_key.as_bytes());
dst.put_slice(&self.salt);
pub fn encode(&self) -> Vec<u8> {
let mut out = Vec::with_capacity(Self::LEN);
out.put_u32_le(self.receiver_index);
out.put_slice(&self.client_lp_public_key);
out.put_slice(&self.client_ed25519_public_key);
out.put_slice(&self.salt);
out
}
pub fn decode(b: &[u8]) -> Result<Self, LpError> {
if b.len() != Self::LEN {
return Err(LpError::DeserializationError(format!(
@@ -97,15 +93,10 @@ impl ClientHelloData {
// SAFETY: we checked for valid byte lengths
#[allow(clippy::unwrap_used)]
let client_lp_public_key_bytes = b[4..36].try_into().unwrap();
let client_ed25519_public_key_bytes = b[36..68].try_into().unwrap();
Ok(ClientHelloData {
receiver_index: u32::from_le_bytes([b[0], b[1], b[2], b[3]]),
client_lp_public_key: x25519::PublicKey::from_byte_array(client_lp_public_key_bytes),
client_ed25519_public_key: ed25519::PublicKey::from_byte_array(
client_ed25519_public_key_bytes,
)?,
client_lp_public_key: b[4..36].try_into().unwrap(),
client_ed25519_public_key: b[36..68].try_into().unwrap(),
salt: b[68..].try_into().unwrap(),
})
}
@@ -150,80 +141,23 @@ impl MessageType {
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HandshakeData(pub Vec<u8>);
impl HandshakeData {
fn len(&self) -> usize {
self.0.len()
}
fn encode(&self, dst: &mut BytesMut) {
dst.put_slice(&self.0);
}
fn decode(bytes: &[u8]) -> Result<Self, LpError> {
Ok(HandshakeData(bytes.to_vec()))
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EncryptedDataPayload(pub Vec<u8>);
impl EncryptedDataPayload {
fn len(&self) -> usize {
self.0.len()
}
fn encode(&self, dst: &mut BytesMut) {
dst.put_slice(&self.0);
}
fn decode(bytes: &[u8]) -> Result<Self, LpError> {
Ok(EncryptedDataPayload(bytes.to_vec()))
}
}
/// KKT request frame data (serialized KKTFrame bytes)
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct KKTRequestData(pub Vec<u8>);
impl KKTRequestData {
fn len(&self) -> usize {
self.0.len()
}
fn encode(&self, dst: &mut BytesMut) {
dst.put_slice(&self.0);
}
fn decode(bytes: &[u8]) -> Result<Self, LpError> {
Ok(KKTRequestData(bytes.to_vec()))
}
}
/// KKT response frame data (serialized KKTFrame bytes)
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct KKTResponseData(pub Vec<u8>);
impl KKTResponseData {
fn len(&self) -> usize {
self.0.len()
}
fn encode(&self, dst: &mut BytesMut) {
dst.put_slice(&self.0);
}
fn decode(bytes: &[u8]) -> Result<Self, LpError> {
Ok(KKTResponseData(bytes.to_vec()))
}
}
/// Packet forwarding request with embedded inner LP packet
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ForwardPacketData {
/// Target gateway's Ed25519 identity (32 bytes)
pub target_gateway_identity: [u8; 32],
// TODO: replace it with `SocketAddr`
/// Target gateway's LP address (IP:port string)
pub target_lp_address: String,
@@ -232,157 +166,27 @@ pub struct ForwardPacketData {
pub inner_packet_bytes: Vec<u8>,
}
impl ForwardPacketData {
fn len(&self) -> usize {
// 32 bytes target gateway identity
// +
// 4 bytes length of target lp address
// +
// target_lp_address.len()
// +
// 4 bytes of length of inner packet bytes
// +
// inner_packet_bytes.len()
32 + 4 + self.target_lp_address.len() + 4 + self.inner_packet_bytes.len()
}
fn encode(&self, dst: &mut BytesMut) {
dst.put_slice(&self.target_gateway_identity);
dst.put_u16_le(self.target_lp_address.len() as u16);
dst.put_slice(self.target_lp_address.as_bytes());
dst.put_u32_le(self.inner_packet_bytes.len() as u32);
dst.put_slice(&self.inner_packet_bytes);
}
pub fn to_bytes(&self) -> Vec<u8> {
let mut buf = BytesMut::new();
self.encode(&mut buf);
buf.into()
}
pub fn decode(bytes: &[u8]) -> Result<Self, LpError> {
// smallest possible packet with empty address and empty data
if bytes.len() < 38 {
return Err(LpError::DeserializationError(format!(
"Too few bytes to deserialise ForwardPacketData[1]. got {}",
bytes.len()
)));
}
// SAFETY: we ensured we have sufficient data
#[allow(clippy::unwrap_used)]
let target_gateway_identity = bytes[0..32].try_into().unwrap();
let target_lp_address_len = u16::from_le_bytes([bytes[32], bytes[33]]);
// smallest possible packet with empty data
if bytes[34..].len() < 4 + target_lp_address_len as usize {
return Err(LpError::DeserializationError(format!(
"Too few bytes to deserialise ForwardPacketData[2]. got {}",
bytes.len()
)));
}
let target_lp_address =
String::from_utf8_lossy(&bytes[34..34 + target_lp_address_len as usize]).to_string();
let inner_packet_bytes_len = u32::from_le_bytes([
bytes[34 + target_lp_address_len as usize],
bytes[34 + target_lp_address_len as usize + 1],
bytes[34 + target_lp_address_len as usize + 2],
bytes[34 + target_lp_address_len as usize + 3],
]);
if bytes[34 + target_lp_address_len as usize + 4..].len() != inner_packet_bytes_len as usize
{
return Err(LpError::DeserializationError(format!(
"Expected {inner_packet_bytes_len} bytes to deserialise inner packet bytes of ForwardPacketData. got {}",
bytes[34 + target_lp_address_len as usize + 4..].len()
)));
}
let inner_packet_bytes = bytes[34 + target_lp_address_len as usize + 4..].to_vec();
Ok(ForwardPacketData {
target_gateway_identity,
target_lp_address,
inner_packet_bytes,
})
}
}
/// Subsession KK1 message - first message of Noise KK handshake
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct SubsessionKK1Data {
/// Noise KK first message payload (ephemeral key + encrypted static)
pub payload: Vec<u8>,
}
impl SubsessionKK1Data {
fn len(&self) -> usize {
self.payload.len()
}
fn encode(&self, dst: &mut BytesMut) {
dst.put_slice(&self.payload);
}
fn decode(bytes: &[u8]) -> Result<Self, LpError> {
Ok(SubsessionKK1Data {
payload: bytes.to_vec(),
})
}
}
/// Subsession KK2 message - second message of Noise KK handshake
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct SubsessionKK2Data {
/// Noise KK second message payload (ephemeral key + encrypted response)
pub payload: Vec<u8>,
}
impl SubsessionKK2Data {
fn len(&self) -> usize {
self.payload.len()
}
fn encode(&self, dst: &mut BytesMut) {
dst.put_slice(&self.payload);
}
fn decode(bytes: &[u8]) -> Result<Self, LpError> {
Ok(SubsessionKK2Data {
payload: bytes.to_vec(),
})
}
}
/// Subsession ready confirmation with new session index
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct SubsessionReadyData {
/// New subsession's receiver index for routing
pub receiver_index: u32,
}
impl SubsessionReadyData {
pub const LEN: usize = 4;
fn len(&self) -> usize {
Self::LEN
}
fn encode(&self, dst: &mut BytesMut) {
dst.put_u32_le(self.receiver_index);
}
fn decode(bytes: &[u8]) -> Result<Self, LpError> {
if bytes.len() != 4 {
return Err(LpError::DeserializationError(format!(
"Expected 4 bytes to deserialise SubsessionReadyData. got {}",
bytes.len()
)));
}
Ok(SubsessionReadyData {
receiver_index: u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]),
})
}
}
#[derive(Debug, Clone)]
pub enum LpMessage {
Busy,
@@ -471,18 +275,23 @@ impl LpMessage {
pub fn len(&self) -> usize {
match self {
LpMessage::Busy => 0,
LpMessage::Handshake(payload) => payload.len(),
LpMessage::EncryptedData(payload) => payload.len(),
LpMessage::ClientHello(payload) => payload.len(),
LpMessage::KKTRequest(payload) => payload.len(),
LpMessage::KKTResponse(payload) => payload.len(),
LpMessage::ForwardPacket(payload) => payload.len(),
LpMessage::Handshake(payload) => payload.0.len(),
LpMessage::EncryptedData(payload) => payload.0.len(),
// 4 bytes receiver_index + 32 bytes x25519 key + 32 bytes ed25519 key + 32 bytes salt
LpMessage::ClientHello(_) => ClientHelloData::LEN,
LpMessage::KKTRequest(payload) => payload.0.len(),
LpMessage::KKTResponse(payload) => payload.0.len(),
LpMessage::ForwardPacket(data) => {
32 + data.target_lp_address.len() + data.inner_packet_bytes.len() + 10
}
LpMessage::Collision => 0,
LpMessage::Ack => 0,
LpMessage::SubsessionRequest => 0,
LpMessage::SubsessionKK1(payload) => payload.len(),
LpMessage::SubsessionKK2(payload) => payload.len(),
LpMessage::SubsessionReady(payload) => payload.len(),
// Variable length: bincode overhead (~8 bytes for Vec length) + payload
LpMessage::SubsessionKK1(data) => 8 + data.payload.len(),
LpMessage::SubsessionKK2(data) => 8 + data.payload.len(),
// 4 bytes u32 + bincode overhead (~4 bytes)
LpMessage::SubsessionReady(_) => 8,
LpMessage::SubsessionAbort => 0,
}
}
@@ -509,90 +318,51 @@ impl LpMessage {
pub fn encode_content(&self, dst: &mut BytesMut) {
match self {
LpMessage::Busy => { /* No content */ }
LpMessage::Handshake(payload) => payload.encode(dst),
LpMessage::EncryptedData(payload) => payload.encode(dst),
LpMessage::ClientHello(data) => data.encode(dst),
LpMessage::KKTRequest(payload) => payload.encode(dst),
LpMessage::KKTResponse(payload) => payload.encode(dst),
LpMessage::ForwardPacket(data) => data.encode(dst),
LpMessage::Handshake(payload) => {
dst.put_slice(&payload.0);
}
LpMessage::EncryptedData(payload) => {
dst.put_slice(&payload.0);
}
LpMessage::ClientHello(data) => {
dst.put_slice(&data.encode());
}
LpMessage::KKTRequest(payload) => {
dst.put_slice(&payload.0);
}
LpMessage::KKTResponse(payload) => {
dst.put_slice(&payload.0);
}
LpMessage::ForwardPacket(data) => {
let serialized = lp_bincode_serializer()
.serialize(data)
.expect("Failed to serialize ForwardPacketData");
dst.put_slice(&serialized);
}
LpMessage::Collision => { /* No content */ }
LpMessage::Ack => { /* No content */ }
LpMessage::SubsessionRequest => { /* No content - signal only */ }
LpMessage::SubsessionKK1(data) => data.encode(dst),
LpMessage::SubsessionKK2(data) => data.encode(dst),
LpMessage::SubsessionReady(data) => data.encode(dst),
LpMessage::SubsessionKK1(data) => {
let serialized = lp_bincode_serializer()
.serialize(data)
.expect("Failed to serialize SubsessionKK1Data");
dst.put_slice(&serialized);
}
LpMessage::SubsessionKK2(data) => {
let serialized = lp_bincode_serializer()
.serialize(data)
.expect("Failed to serialize SubsessionKK2Data");
dst.put_slice(&serialized);
}
LpMessage::SubsessionReady(data) => {
let serialized = lp_bincode_serializer()
.serialize(data)
.expect("Failed to serialize SubsessionReadyData");
dst.put_slice(&serialized);
}
LpMessage::SubsessionAbort => { /* No content - signal only */ }
}
}
/// Parse message from its type and content bytes.
///
/// Used when decrypting outer-encrypted packets where the message type
/// was encrypted along with the content.
pub fn decode_content(content: &[u8], message_type: MessageType) -> Result<Self, LpError> {
match message_type {
MessageType::Busy => {
content.ensure_empty()?;
Ok(LpMessage::Busy)
}
MessageType::Handshake => Ok(LpMessage::Handshake(HandshakeData::decode(content)?)),
MessageType::EncryptedData => Ok(LpMessage::EncryptedData(
EncryptedDataPayload::decode(content)?,
)),
MessageType::ClientHello => {
Ok(LpMessage::ClientHello(ClientHelloData::decode(content)?))
}
MessageType::KKTRequest => Ok(LpMessage::KKTRequest(KKTRequestData::decode(content)?)),
MessageType::KKTResponse => {
Ok(LpMessage::KKTResponse(KKTResponseData::decode(content)?))
}
MessageType::ForwardPacket => Ok(LpMessage::ForwardPacket(ForwardPacketData::decode(
content,
)?)),
MessageType::Collision => {
content.ensure_empty()?;
Ok(LpMessage::Collision)
}
MessageType::Ack => {
content.ensure_empty()?;
Ok(LpMessage::Ack)
}
MessageType::SubsessionRequest => {
content.ensure_empty()?;
Ok(LpMessage::SubsessionRequest)
}
MessageType::SubsessionKK1 => Ok(LpMessage::SubsessionKK1(SubsessionKK1Data::decode(
content,
)?)),
MessageType::SubsessionKK2 => Ok(LpMessage::SubsessionKK2(SubsessionKK2Data::decode(
content,
)?)),
MessageType::SubsessionReady => Ok(LpMessage::SubsessionReady(
SubsessionReadyData::decode(content)?,
)),
MessageType::SubsessionAbort => {
content.ensure_empty()?;
Ok(LpMessage::SubsessionAbort)
}
}
}
}
/// Helper trait for improving readability to return error if bytes content is not empty
trait EnsureEmptyContent {
fn ensure_empty(&self) -> Result<(), LpError>;
}
impl EnsureEmptyContent for &[u8] {
fn ensure_empty(&self) -> Result<(), LpError> {
if !self.is_empty() {
return Err(LpError::InvalidPayloadSize {
expected: 0,
actual: self.len(),
});
}
Ok(())
}
}
#[cfg(test)]
@@ -609,7 +379,7 @@ mod tests {
let resp_header = LpHeader {
protocol_version: 1,
reserved: [0u8; 3],
reserved: 0,
receiver_idx: 0,
counter: 0,
};
@@ -641,13 +411,8 @@ mod tests {
.duration_since(UNIX_EPOCH)
.expect("System time before UNIX epoch")
.as_secs();
let mut rng = rand::thread_rng();
let ed25519 = ed25519::KeyPair::new(&mut rng);
let x25519 = ed25519.to_x25519();
let client_key = *x25519.public_key();
let client_ed25519_key = *ed25519.public_key();
let client_key = [1u8; 32];
let client_ed25519_key = [2u8; 32];
let hello1 =
ClientHelloData::new_with_fresh_salt(client_key, client_ed25519_key, timestamp);
let hello2 =
@@ -668,12 +433,8 @@ mod tests {
.duration_since(UNIX_EPOCH)
.expect("System time before UNIX epoch")
.as_secs();
let mut rng = rand::thread_rng();
let ed25519 = ed25519::KeyPair::new(&mut rng);
let x25519 = ed25519.to_x25519();
let client_key = *x25519.public_key();
let client_ed25519_key = *ed25519.public_key();
let client_key = [2u8; 32];
let client_ed25519_key = [3u8; 32];
let hello = ClientHelloData::new_with_fresh_salt(client_key, client_ed25519_key, timestamp);
let timestamp = hello.extract_timestamp();
@@ -692,12 +453,8 @@ mod tests {
.duration_since(UNIX_EPOCH)
.expect("System time before UNIX epoch")
.as_secs();
let mut rng = rand::thread_rng();
let ed25519 = ed25519::KeyPair::new(&mut rng);
let x25519 = ed25519.to_x25519();
let client_key = *x25519.public_key();
let client_ed25519_key = *ed25519.public_key();
let client_key = [3u8; 32];
let client_ed25519_key = [4u8; 32];
let hello = ClientHelloData::new_with_fresh_salt(client_key, client_ed25519_key, timestamp);
// First 8 bytes should be non-zero timestamp
+6 -33
View File
@@ -10,7 +10,6 @@ use parking_lot::Mutex;
use std::fmt::Write;
use std::fmt::{Debug, Formatter};
use std::sync::Arc;
use tracing::warn;
#[allow(dead_code)]
pub(crate) const UDP_HEADER_LEN: usize = 8;
@@ -26,11 +25,6 @@ pub const TRAILER_LEN: usize = 16;
#[allow(dead_code)]
pub(crate) const UDP_PAYLOAD_SIZE: usize = MTU - UDP_OVERHEAD - TRAILER_LEN;
pub mod version {
/// The current version of the Lewes Protocol that is put into each new constructed header.
pub const CURRENT: u8 = 1;
}
#[derive(Clone)]
pub struct LpPacket {
pub(crate) header: LpHeader,
@@ -193,7 +187,7 @@ impl OuterHeader {
#[derive(Debug, Clone)]
pub struct LpHeader {
pub protocol_version: u8,
pub reserved: [u8; 3],
pub reserved: u16,
pub receiver_idx: u32,
pub counter: u64,
}
@@ -205,8 +199,8 @@ impl LpHeader {
impl LpHeader {
pub fn new(receiver_idx: u32, counter: u64) -> Self {
Self {
protocol_version: version::CURRENT,
reserved: [0u8; 3],
protocol_version: 1,
reserved: 0,
receiver_idx,
counter,
}
@@ -217,7 +211,7 @@ impl LpHeader {
dst.put_u8(self.protocol_version);
// reserved
dst.put_slice(&self.reserved);
dst.put_slice(&[0, 0, 0]);
// sender index
dst.put_slice(&self.receiver_idx.to_le_bytes());
@@ -232,28 +226,7 @@ impl LpHeader {
}
let protocol_version = src[0];
// Ensure we are using compatible protocol
// right now only support a single version
if protocol_version > version::CURRENT {
return Err(LpError::IncompatibleFuturePacketVersion {
got: protocol_version,
highest_supported: version::CURRENT,
});
}
if protocol_version < version::CURRENT {
return Err(LpError::IncompatibleLegacyPacketVersion {
got: protocol_version,
lowest_supported: version::CURRENT,
});
}
// skip reserved bytes, but log if they're different from the expected zeroes
let reserved = [src[1], src[2], src[3]];
if reserved != [0u8; 3] {
warn!("received non-zero reserved bytes. got: {reserved:?}");
}
// Skip reserved bytes [1..4]
let mut receiver_idx_bytes = [0u8; 4];
receiver_idx_bytes.copy_from_slice(&src[4..8]);
@@ -265,7 +238,7 @@ impl LpHeader {
Ok(LpHeader {
protocol_version,
reserved: [0u8; 3],
reserved: 0,
receiver_idx,
counter,
})
+32 -35
View File
@@ -47,11 +47,12 @@
//! - **No cleanup needed**: No state was mutated
use crate::LpError;
use crate::keypair::{PrivateKey, PublicKey};
use libcrux_psq::v1::cred::{Authenticator, Ed25519};
use libcrux_psq::v1::impls::X25519 as PsqX25519;
use libcrux_psq::v1::psk_registration::{Initiator, InitiatorMsg, Responder};
use libcrux_psq::v1::traits::{Ciphertext as PsqCiphertext, PSQ};
use nym_crypto::asymmetric::{ed25519, x25519};
use nym_crypto::asymmetric::ed25519;
use nym_kkt::ciphersuite::{DecapsulationKey, EncapsulationKey};
use std::time::Duration;
use tls_codec::{Deserialize as TlsDeserializeTrait, Serialize as TlsSerializeTrait};
@@ -136,8 +137,8 @@ pub struct PsqResponderResult {
/// // Send ciphertext to gateway
/// ```
pub fn derive_psk_with_psq_initiator(
local_x25519_private: &x25519::PrivateKey,
remote_x25519_public: &x25519::PublicKey,
local_x25519_private: &PrivateKey,
remote_x25519_public: &PublicKey,
remote_kem_public: &EncapsulationKey,
salt: &[u8; 32],
) -> Result<([u8; 32], Vec<u8>), LpError> {
@@ -167,7 +168,7 @@ pub fn derive_psk_with_psq_initiator(
// Step 3: Combine ECDH + PSQ via Blake3 KDF
let mut combined = Vec::with_capacity(64 + psq_psk.len());
combined.extend_from_slice(&ecdh_secret);
combined.extend_from_slice(ecdh_secret.as_bytes());
combined.extend_from_slice(&psq_psk); // psq_psk is [u8; 32], need &
combined.extend_from_slice(salt);
@@ -219,8 +220,8 @@ pub fn derive_psk_with_psq_initiator(
/// )?;
/// ```
pub fn derive_psk_with_psq_responder(
local_x25519_private: &x25519::PrivateKey,
remote_x25519_public: &x25519::PublicKey,
local_x25519_private: &PrivateKey,
remote_x25519_public: &PublicKey,
local_kem_keypair: (&DecapsulationKey, &EncapsulationKey),
ciphertext: &[u8],
salt: &[u8; 32],
@@ -248,7 +249,7 @@ pub fn derive_psk_with_psq_responder(
// Step 5: Combine ECDH + PSQ via Blake3 KDF (same formula as initiator)
let mut combined = Vec::with_capacity(64 + psq_psk.len());
combined.extend_from_slice(&ecdh_secret);
combined.extend_from_slice(ecdh_secret.as_bytes());
combined.extend_from_slice(&psq_psk); // psq_psk is [u8; 32], need &
combined.extend_from_slice(salt);
@@ -279,8 +280,8 @@ pub fn derive_psk_with_psq_responder(
/// # Returns
/// `PsqInitiatorResult` containing PSK, payload, and raw PQ shared secret
pub fn psq_initiator_create_message(
local_x25519_private: &x25519::PrivateKey,
remote_x25519_public: &x25519::PublicKey,
local_x25519_private: &PrivateKey,
remote_x25519_public: &PublicKey,
remote_kem_public: &EncapsulationKey,
client_ed25519_sk: &ed25519::PrivateKey,
client_ed25519_pk: &ed25519::PublicKey,
@@ -334,7 +335,7 @@ pub fn psq_initiator_create_message(
// Step 3: Combine ECDH + PSQ via Blake3 KDF
let mut combined = Vec::with_capacity(64 + psq_psk.len());
combined.extend_from_slice(&ecdh_secret);
combined.extend_from_slice(ecdh_secret.as_bytes());
combined.extend_from_slice(psq_psk); // psq_psk is already a &[u8; 32]
combined.extend_from_slice(salt);
@@ -374,8 +375,8 @@ pub fn psq_initiator_create_message(
/// # Returns
/// `PsqResponderResult` containing PSK, PSK handle, and raw PQ shared secret
pub fn psq_responder_process_message(
local_x25519_private: &x25519::PrivateKey,
remote_x25519_public: &x25519::PublicKey,
local_x25519_private: &PrivateKey,
remote_x25519_public: &PublicKey,
local_kem_keypair: (&DecapsulationKey, &EncapsulationKey),
initiator_ed25519_pk: &ed25519::PublicKey,
psq_payload: &[u8],
@@ -443,7 +444,7 @@ pub fn psq_responder_process_message(
// Step 6: Combine ECDH + PSQ via Blake3 KDF (same formula as initiator)
let mut combined = Vec::with_capacity(64 + psq_psk.len());
combined.extend_from_slice(&ecdh_secret);
combined.extend_from_slice(ecdh_secret.as_bytes());
combined.extend_from_slice(&psq_psk); // psq_psk is [u8; 32], need &
combined.extend_from_slice(salt);
@@ -492,16 +493,12 @@ pub fn derive_subsession_psk(pq_shared_secret: &[u8; 32], subsession_index: u64)
#[cfg(test)]
mod tests {
use super::*;
use rand::thread_rng;
fn generate_x25519_keypair() -> x25519::KeyPair {
x25519::KeyPair::new(&mut thread_rng())
}
use crate::keypair::Keypair;
#[test]
fn test_psk_derivation_is_symmetric() {
let keypair_1 = generate_x25519_keypair();
let keypair_2 = generate_x25519_keypair();
let keypair_1 = Keypair::default();
let keypair_2 = Keypair::default();
let salt = [2u8; 32];
let mut rng = &mut rand09::rng();
@@ -536,8 +533,8 @@ mod tests {
#[test]
fn test_different_salts_produce_different_psks() {
let keypair_1 = generate_x25519_keypair();
let keypair_2 = generate_x25519_keypair();
let keypair_1 = Keypair::default();
let keypair_2 = Keypair::default();
let salt1 = [1u8; 32];
let salt2 = [2u8; 32];
@@ -565,9 +562,9 @@ mod tests {
#[test]
fn test_different_keys_produce_different_psks() {
let keypair_1 = generate_x25519_keypair();
let keypair_2 = generate_x25519_keypair();
let keypair_3 = generate_x25519_keypair();
let keypair_1 = Keypair::default();
let keypair_2 = Keypair::default();
let keypair_3 = Keypair::default();
let salt = [3u8; 32];
let mut rng = &mut rand09::rng();
@@ -604,8 +601,8 @@ mod tests {
let mut rng = rand09::rng();
// Generate X25519 keypairs for Noise
let client_keypair = generate_x25519_keypair();
let gateway_keypair = generate_x25519_keypair();
let client_keypair = Keypair::default();
let gateway_keypair = Keypair::default();
// Generate KEM keypair for PSQ
let (kem_sk, kem_pk) = generate_keypair_libcrux(&mut rng, KEM::X25519).unwrap();
@@ -662,8 +659,8 @@ mod tests {
let mut rng = rand09::rng();
// Generate X25519 keypairs for Noise
let client_keypair = generate_x25519_keypair();
let gateway_keypair = generate_x25519_keypair();
let client_keypair = Keypair::default();
let gateway_keypair = Keypair::default();
// Generate KEM keypair for PSQ
let (kem_sk, kem_pk) = generate_keypair_libcrux(&mut rng, KEM::X25519).unwrap();
@@ -701,8 +698,8 @@ mod tests {
fn test_different_kem_keys_different_psk() {
let mut rng = rand09::rng();
let client_keypair = generate_x25519_keypair();
let gateway_keypair = generate_x25519_keypair();
let client_keypair = Keypair::default();
let gateway_keypair = Keypair::default();
// Two different KEM keypairs
let (_, kem_pk1) = generate_keypair_libcrux(&mut rng, KEM::X25519).unwrap();
@@ -739,8 +736,8 @@ mod tests {
fn test_psq_psk_output_length() {
let mut rng = rand09::rng();
let client_keypair = generate_x25519_keypair();
let gateway_keypair = generate_x25519_keypair();
let client_keypair = Keypair::default();
let gateway_keypair = Keypair::default();
let (_, kem_pk) = generate_keypair_libcrux(&mut rng, KEM::X25519).unwrap();
let enc_key = EncapsulationKey::X25519(kem_pk);
@@ -762,8 +759,8 @@ mod tests {
fn test_psq_different_salts_different_psks() {
let mut rng = rand09::rng();
let client_keypair = generate_x25519_keypair();
let gateway_keypair = generate_x25519_keypair();
let client_keypair = Keypair::default();
let gateway_keypair = Keypair::default();
let (_, kem_pk) = generate_keypair_libcrux(&mut rng, KEM::X25519).unwrap();
let enc_key = EncapsulationKey::X25519(kem_pk);
+151 -128
View File
@@ -7,6 +7,7 @@
//! and Noise protocol state handling.
use crate::codec::OuterAeadKey;
use crate::keypair::{PrivateKey, PublicKey};
use crate::message::{EncryptedDataPayload, HandshakeData};
use crate::noise_protocol::{NoiseError, NoiseProtocol, ReadResult};
use crate::packet::LpHeader;
@@ -15,13 +16,10 @@ use crate::psk::{
};
use crate::replay::ReceivingKeyCounterValidator;
use crate::{LpError, LpMessage, LpPacket};
use nym_crypto::asymmetric::{ed25519, x25519};
use nym_crypto::asymmetric::ed25519;
use nym_kkt::ciphersuite::{DecapsulationKey, EncapsulationKey};
use nym_kkt::encryption::KKTSessionSecret;
use nym_kkt::kkt::decrypt_kkt_response_frame;
use parking_lot::Mutex;
use snow::Builder;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use zeroize::{Zeroize, ZeroizeOnDrop};
@@ -73,7 +71,6 @@ pub enum KKTState {
InitiatorWaiting {
/// KKT context for verifying the response
context: nym_kkt::context::KKTContext,
session_secret: KKTSessionSecret,
},
/// KKT exchange completed (initiator received and validated KEM key).
@@ -91,7 +88,7 @@ impl std::fmt::Debug for KKTState {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::NotStarted => write!(f, "KKTState::NotStarted"),
Self::InitiatorWaiting { context, .. } => f
Self::InitiatorWaiting { context } => f
.debug_struct("KKTState::InitiatorWaiting")
.field("context", context)
.finish(),
@@ -182,17 +179,20 @@ pub struct LpSession {
psk_injected: AtomicBool,
// PSQ-related keys stored for handshake
/// Local Ed25519 keys for PSQ authentication
local_ed25519: Arc<ed25519::KeyPair>,
/// Local Ed25519 private key for PSQ authentication
local_ed25519_private: ed25519::PrivateKey,
/// Local Ed25519 public key for PSQ authentication
local_ed25519_public: ed25519::PublicKey,
/// Remote Ed25519 public key for PSQ authentication
remote_ed25519_public: ed25519::PublicKey,
/// Local x25519 keys (Noise static key)
local_x25519: Arc<x25519::KeyPair>,
/// Local X25519 private key (Noise static key)
local_x25519_private: PrivateKey,
/// Remote X25519 public key (Noise static key)
remote_x25519_public: x25519::PublicKey,
remote_x25519_public: PublicKey,
/// Salt for PSK derivation
salt: [u8; 32],
@@ -273,7 +273,8 @@ impl LpSession {
/// Defaults to 1 (current LP version). Set during handshake via
/// `set_negotiated_version()` when ClientHello/ServerHello is processed.
pub fn negotiated_version(&self) -> u8 {
self.negotiated_version.load(Ordering::Acquire)
self.negotiated_version
.load(std::sync::atomic::Ordering::Acquire)
}
/// Sets the negotiated protocol version from handshake packet header.
@@ -281,22 +282,23 @@ impl LpSession {
/// Should be called during handshake when processing ClientHello (responder)
/// or ServerHello (initiator) to record the agreed protocol version.
pub fn set_negotiated_version(&self, version: u8) {
self.negotiated_version.store(version, Ordering::Release);
self.negotiated_version
.store(version, std::sync::atomic::Ordering::Release);
}
/// Returns the local X25519 public key.
/// Returns the local X25519 public key derived from the private key.
///
/// This is used for KKT protocol when the responder needs to send their
/// KEM public key in the KKT response.
pub fn local_x25519_public(&self) -> x25519::PublicKey {
*self.local_x25519.public_key()
pub fn local_x25519_public(&self) -> PublicKey {
self.local_x25519_private.public_key()
}
/// Returns the remote X25519 public key.
///
/// Used for tie-breaking in simultaneous subsession initiation.
/// Lower key loses and becomes responder.
pub fn remote_x25519_public(&self) -> &x25519::PublicKey {
pub fn remote_x25519_public(&self) -> &PublicKey {
&self.remote_x25519_public
}
@@ -351,17 +353,17 @@ impl LpSession {
/// * `id` - Session identifier
/// * `is_initiator` - True if this side initiates the Noise handshake.
/// * `local_ed25519_keypair` - This side's Ed25519 keypair for PSQ authentication
/// * `local_x25519_keypair` - This side's X25519 keypair for Noise protocol and DHKEM
/// * `local_x25519_key` - This side's X25519 private key for Noise protocol and DHKEM
/// * `remote_ed25519_key` - Peer's Ed25519 public key for PSQ authentication
/// * `remote_x25519_key` - Peer's X25519 public key for Noise protocol and DHKEM
/// * `salt` - Salt for PSK derivation
pub fn new(
id: u32,
is_initiator: bool,
local_ed25519_keypair: Arc<ed25519::KeyPair>,
local_x25519_keypair: Arc<x25519::KeyPair>,
local_ed25519_keypair: (&ed25519::PrivateKey, &ed25519::PublicKey),
local_x25519_key: &PrivateKey,
remote_ed25519_key: &ed25519::PublicKey,
remote_x25519_key: &x25519::PublicKey,
remote_x25519_key: &PublicKey,
salt: &[u8; 32],
) -> Result<Self, LpError> {
// XKpsk3 pattern requires remote static key known upfront (XK)
@@ -372,8 +374,8 @@ impl LpSession {
let params = pattern_name.parse()?;
let builder = Builder::new(params);
let local_key_bytes = local_x25519_keypair.private_key().as_bytes();
let builder = builder.local_private_key(local_key_bytes);
let local_key_bytes = local_x25519_key.to_bytes();
let builder = builder.local_private_key(&local_key_bytes);
let remote_key_bytes = remote_x25519_key.to_bytes();
let builder = builder.remote_public_key(&remote_key_bytes);
@@ -411,10 +413,19 @@ impl LpSession {
sending_counter: AtomicU64::new(0),
receiving_counter: Mutex::new(ReceivingKeyCounterValidator::default()),
psk_injected: AtomicBool::new(false),
local_ed25519: local_ed25519_keypair.clone(),
remote_ed25519_public: *remote_ed25519_key,
local_x25519: local_x25519_keypair,
remote_x25519_public: *remote_x25519_key,
// Ed25519 keys don't impl Clone, so convert to bytes and reconstruct
local_ed25519_private: ed25519::PrivateKey::from_bytes(
&local_ed25519_keypair.0.to_bytes(),
)
.expect("Valid ed25519 private key"),
local_ed25519_public: ed25519::PublicKey::from_bytes(
&local_ed25519_keypair.1.to_bytes(),
)
.expect("Valid ed25519 public key"),
remote_ed25519_public: ed25519::PublicKey::from_bytes(&remote_ed25519_key.to_bytes())
.expect("Valid ed25519 public key"),
local_x25519_private: local_x25519_key.clone(),
remote_x25519_public: remote_x25519_key.clone(),
salt: *salt,
outer_aead_key: Mutex::new(None),
pq_shared_secret: Mutex::new(None),
@@ -550,20 +561,13 @@ impl LpSession {
};
let mut rng = rand09::rng();
match request_kem_key(
&mut rng,
ciphersuite,
self.local_ed25519.private_key(),
&self.remote_x25519_public,
) {
Ok((session_secret, context, request_bytes)) => {
match request_kem_key(&mut rng, ciphersuite, &self.local_ed25519_private) {
Ok((context, request_frame)) => {
// Store context for response validation
*kkt_state = KKTState::InitiatorWaiting {
context,
session_secret,
};
*kkt_state = KKTState::InitiatorWaiting { context };
// Serialize KKT frame to bytes
let request_bytes = request_frame.to_bytes();
Some(Ok(LpMessage::KKTRequest(crate::message::KKTRequestData(
request_bytes,
))))
@@ -609,11 +613,8 @@ impl LpSession {
let mut kkt_state = self.kkt_state.lock();
// Extract context from waiting state
let (mut context, session_secret) = match &*kkt_state {
KKTState::InitiatorWaiting {
context,
session_secret,
} => (*context, *session_secret),
let mut context = match &*kkt_state {
KKTState::InitiatorWaiting { context } => *context,
_ => {
return Err(LpError::Internal(
"KKT response received in invalid state".to_string(),
@@ -628,10 +629,11 @@ impl LpSession {
None => {
// Signature-only mode: extract key from response and compute its hash
// This effectively bypasses hash validation while keeping signature validation
let (frame, _) = decrypt_kkt_response_frame(&session_secret, response_bytes)
.map_err(|e| {
LpError::Internal(format!("Failed to decrypt KKT response: {:?}", e))
})?;
use nym_kkt::frame::KKTFrame;
let (frame, _) = KKTFrame::from_bytes(response_bytes).map_err(|e| {
LpError::Internal(format!("Failed to parse KKT response: {:?}", e))
})?;
hash_for_validation = hash_encapsulation_key(
&context.ciphersuite().hash_function(),
@@ -645,7 +647,6 @@ impl LpSession {
// Validate response and extract KEM key
let kem_pk = validate_kem_response(
&mut context,
&session_secret,
&self.remote_ed25519_public,
hash_ref,
response_bytes,
@@ -679,19 +680,20 @@ impl LpSession {
request_bytes: &[u8],
responder_kem_pk: &EncapsulationKey,
) -> Result<LpMessage, LpError> {
use nym_kkt::kkt::handle_kem_request;
let mut rng = rand09::rng();
use nym_kkt::{frame::KKTFrame, kkt::handle_kem_request};
let mut kkt_state = self.kkt_state.lock();
// Deserialize request frame
let (request_frame, _) = KKTFrame::from_bytes(request_bytes).map_err(|e| {
LpError::Internal(format!("KKT request deserialization failed: {:?}", e))
})?;
// Handle request and create signed response
let response_bytes = handle_kem_request(
&mut rng,
request_bytes,
let response_frame = handle_kem_request(
&request_frame,
Some(&self.remote_ed25519_public), // Verify initiator signature
self.local_ed25519.private_key(), // Sign response
self.local_x25519.private_key(),
&self.local_ed25519_private, // Sign response
responder_kem_pk,
)
.map_err(|e| LpError::Internal(format!("KKT request handling failed: {:?}", e)))?;
@@ -700,6 +702,9 @@ impl LpSession {
// Responder doesn't store the kem_pk since they already have their own KEM keypair
*kkt_state = KKTState::ResponderProcessed;
// Serialize response frame
let response_bytes = response_frame.to_bytes();
Ok(LpMessage::KKTResponse(crate::message::KKTResponseData(
response_bytes,
)))
@@ -743,11 +748,11 @@ impl LpSession {
let session_context = self.id.to_le_bytes();
let psq_result = match psq_initiator_create_message(
self.local_x25519.private_key(),
&self.local_x25519_private,
&self.remote_x25519_public,
remote_kem,
self.local_ed25519.private_key(),
self.local_ed25519.public_key(),
&self.local_ed25519_private,
&self.local_ed25519_public,
&self.salt,
&session_context,
) {
@@ -881,7 +886,7 @@ impl LpSession {
let noise_payload = &payload[2 + psq_len..];
// Convert X25519 local keys to DecapsulationKey/EncapsulationKey (DHKEM)
let local_private_bytes = &self.local_x25519.private_key().to_bytes();
let local_private_bytes = &self.local_x25519_private.to_bytes();
let libcrux_private_key = libcrux_kem::PrivateKey::decode(
libcrux_kem::Algorithm::X25519,
local_private_bytes,
@@ -894,7 +899,7 @@ impl LpSession {
})?;
let dec_key = DecapsulationKey::X25519(libcrux_private_key);
let local_public_key = self.local_x25519_public();
let local_public_key = self.local_x25519_private.public_key();
let local_public_bytes = local_public_key.as_bytes();
let libcrux_public_key = libcrux_kem::PublicKey::decode(
libcrux_kem::Algorithm::X25519,
@@ -912,7 +917,7 @@ impl LpSession {
let session_context = self.id.to_le_bytes();
let psq_result = match psq_responder_process_message(
self.local_x25519.private_key(),
&self.local_x25519_private,
&self.remote_x25519_public,
(&dec_key, &enc_key),
&self.remote_ed25519_public,
@@ -1118,7 +1123,7 @@ impl LpSession {
/// Test-only method to set KKT state to Completed with a mock KEM key.
/// This allows tests to bypass KKT exchange and directly test PSQ handshake.
#[cfg(test)]
pub(crate) fn set_kkt_completed_for_test(&self, remote_x25519_pub: &x25519::PublicKey) {
pub(crate) fn set_kkt_completed_for_test(&self, remote_x25519_pub: &PublicKey) {
// Convert remote X25519 public key to EncapsulationKey for testing
let remote_kem_bytes = remote_x25519_pub.as_bytes();
let libcrux_public_key =
@@ -1171,7 +1176,7 @@ impl LpSession {
let pattern_name = "Noise_KKpsk0_25519_ChaChaPoly_SHA256";
let params = pattern_name.parse()?;
let local_key_bytes = self.local_x25519.private_key().to_bytes();
let local_key_bytes = self.local_x25519_private.to_bytes();
let remote_key_bytes = self.remote_x25519_public.to_bytes();
let builder = Builder::new(params)
@@ -1190,12 +1195,22 @@ impl LpSession {
noise_state: Mutex::new(NoiseProtocol::new(handshake_state)),
is_initiator,
// Copy key material from parent for into_session() conversion
local_ed25519: self.local_ed25519.clone(),
remote_ed25519_public: self.remote_ed25519_public,
remote_x25519_public: self.remote_x25519_public,
local_ed25519_private: ed25519::PrivateKey::from_bytes(
&self.local_ed25519_private.to_bytes(),
)
.expect("Valid Ed25519 private key from parent"),
local_ed25519_public: ed25519::PublicKey::from_bytes(
&self.local_ed25519_public.to_bytes(),
)
.expect("Valid Ed25519 public key from parent"),
remote_ed25519_public: ed25519::PublicKey::from_bytes(
&self.remote_ed25519_public.to_bytes(),
)
.expect("Valid Ed25519 public key from parent"),
local_x25519_private: self.local_x25519_private.clone(),
remote_x25519_public: self.remote_x25519_public.clone(),
pq_shared_secret: PqSharedSecret::new(pq_secret),
subsession_psk,
local_x25519: self.local_x25519.clone(),
})
}
}
@@ -1225,17 +1240,16 @@ pub struct SubsessionHandshake {
is_initiator: bool,
// Key material inherited from parent session for into_session() conversion
/// Local Ed25519 keys (for PSQ auth if needed)
local_ed25519: Arc<ed25519::KeyPair>,
/// Local x25519 keys (Noise static key)
local_x25519: Arc<x25519::KeyPair>,
/// Local Ed25519 private key (for PSQ auth if needed)
local_ed25519_private: ed25519::PrivateKey,
/// Local Ed25519 public key
local_ed25519_public: ed25519::PublicKey,
/// Remote Ed25519 public key
remote_ed25519_public: ed25519::PublicKey,
/// Local X25519 private key (Noise static key)
local_x25519_private: PrivateKey,
/// Remote X25519 public key (Noise static key)
remote_x25519_public: x25519::PublicKey,
remote_x25519_public: PublicKey,
/// PQ shared secret inherited from parent (for creating further subsessions)
pq_shared_secret: PqSharedSecret,
/// Subsession PSK (for deriving outer AEAD key)
@@ -1328,9 +1342,10 @@ impl SubsessionHandshake {
sending_counter: AtomicU64::new(0),
receiving_counter: Mutex::new(ReceivingKeyCounterValidator::new(0)),
psk_injected: AtomicBool::new(true), // PSK was in KKpsk0
local_ed25519: self.local_ed25519,
local_ed25519_private: self.local_ed25519_private,
local_ed25519_public: self.local_ed25519_public,
remote_ed25519_public: self.remote_ed25519_public,
local_x25519: self.local_x25519,
local_x25519_private: self.local_x25519_private,
remote_x25519_public: self.remote_x25519_public,
salt,
outer_aead_key: Mutex::new(Some(outer_key)),
@@ -1348,19 +1363,18 @@ impl SubsessionHandshake {
mod tests {
use super::*;
use crate::{replay::ReplayError, sessions_for_tests};
use rand::thread_rng;
// Helper function to generate keypairs for tests
fn generate_keypair() -> x25519::KeyPair {
x25519::KeyPair::new(&mut thread_rng())
fn generate_keypair() -> crate::keypair::Keypair {
crate::keypair::Keypair::default()
}
// Helper function to create a session with real keys for handshake tests
fn create_handshake_test_session(
receiver_index: u32,
is_initiator: bool,
local_keys: &x25519::KeyPair,
remote_pub_key: &x25519::PublicKey,
local_keys: &crate::keypair::Keypair,
remote_pub_key: &crate::keypair::PublicKey,
) -> LpSession {
use nym_crypto::asymmetric::ed25519;
@@ -1373,9 +1387,6 @@ mod tests {
};
let local_ed25519 = ed25519::KeyPair::from_secret(local_ed25519_seed, 0);
let local_x25519 = x25519::PrivateKey::from_bytes(local_keys.private_key().as_bytes())
.unwrap()
.into();
let remote_ed25519 = ed25519::KeyPair::from_secret(remote_ed25519_seed, 1);
let salt = [0u8; 32]; // Test salt
@@ -1384,8 +1395,8 @@ mod tests {
let session = LpSession::new(
receiver_index,
is_initiator,
Arc::new(local_ed25519),
Arc::new(local_x25519),
(local_ed25519.private_key(), local_ed25519.public_key()),
local_keys.private_key(),
remote_ed25519.public_key(),
remote_pub_key,
&salt,
@@ -1490,8 +1501,8 @@ mod tests {
#[test]
fn test_prepare_handshake_message_initial_state() {
let initiator_keys = Arc::new(generate_keypair());
let responder_keys = Arc::new(generate_keypair());
let initiator_keys = generate_keypair();
let responder_keys = generate_keypair();
let receiver_index = 12345u32;
let initiator_session = create_handshake_test_session(
@@ -1522,8 +1533,8 @@ mod tests {
#[test]
fn test_process_handshake_message_first_step() {
let initiator_keys = Arc::new(generate_keypair());
let responder_keys = Arc::new(generate_keypair());
let initiator_keys = generate_keypair();
let responder_keys = generate_keypair();
let receiver_index = 12345u32;
let initiator_session = create_handshake_test_session(
@@ -1568,8 +1579,8 @@ mod tests {
#[test]
fn test_handshake_driver_simulation() {
let initiator_keys = Arc::new(generate_keypair());
let responder_keys = Arc::new(generate_keypair());
let initiator_keys = generate_keypair();
let responder_keys = generate_keypair();
let initiator_session = create_handshake_test_session(
12345u32,
@@ -1663,8 +1674,8 @@ mod tests {
#[test]
fn test_encrypt_decrypt_after_handshake() {
// --- Setup Handshake ---
let initiator_keys = Arc::new(generate_keypair());
let responder_keys = Arc::new(generate_keypair());
let initiator_keys = generate_keypair();
let responder_keys = generate_keypair();
let initiator_session = create_handshake_test_session(
12345u32,
@@ -1732,8 +1743,8 @@ mod tests {
#[test]
fn test_encrypt_decrypt_before_handshake() {
let initiator_keys = Arc::new(generate_keypair());
let responder_keys = Arc::new(generate_keypair());
let initiator_keys = generate_keypair();
let responder_keys = generate_keypair();
let initiator_session = create_handshake_test_session(
12345u32,
@@ -1808,8 +1819,8 @@ mod tests {
/// Test that PSQ runs during handshake and derives a PSK
#[test]
fn test_psq_handshake_runs_with_psk_injection() {
let initiator_keys = Arc::new(generate_keypair());
let responder_keys = Arc::new(generate_keypair());
let initiator_keys = generate_keypair();
let responder_keys = generate_keypair();
let initiator_session = create_handshake_test_session(
12345u32,
@@ -1885,8 +1896,8 @@ mod tests {
fn test_x25519_to_kem_conversion() {
use nym_kkt::ciphersuite::EncapsulationKey;
let initiator_keys = Arc::new(generate_keypair());
let responder_keys = Arc::new(generate_keypair());
let initiator_keys = generate_keypair();
let responder_keys = generate_keypair();
// Verify we can convert X25519 public key to KEM format (as done in session.rs)
let x25519_public_bytes = responder_keys.public_key().as_bytes();
@@ -1909,8 +1920,8 @@ mod tests {
/// Test that PSQ actually derives a different PSK (not using dummy)
#[test]
fn test_psq_derived_psk_differs_from_dummy() {
let initiator_keys = Arc::new(generate_keypair());
let responder_keys = Arc::new(generate_keypair());
let initiator_keys = generate_keypair();
let responder_keys = generate_keypair();
// Create sessions - they start with dummy PSK [0u8; 32]
let initiator_session = create_handshake_test_session(
@@ -1983,8 +1994,8 @@ mod tests {
/// Test full end-to-end handshake with PSQ integration
#[test]
fn test_handshake_with_psq_end_to_end() {
let initiator_keys = Arc::new(generate_keypair());
let responder_keys = Arc::new(generate_keypair());
let initiator_keys = generate_keypair();
let responder_keys = generate_keypair();
let initiator_session = create_handshake_test_session(
12345u32,
@@ -2069,8 +2080,8 @@ mod tests {
/// Test that Ed25519 keys are used in PSQ authentication
#[test]
fn test_psq_handshake_uses_ed25519_authentication() {
let initiator_keys = Arc::new(generate_keypair());
let responder_keys = Arc::new(generate_keypair());
let initiator_keys = generate_keypair();
let responder_keys = generate_keypair();
// Create sessions with explicit Ed25519 keys
let initiator_session = create_handshake_test_session(
@@ -2152,8 +2163,8 @@ mod tests {
#[test]
fn test_handshake_abort_on_psq_failure() {
// Test that Ed25519 auth failure causes handshake abort
let initiator_keys = Arc::new(generate_keypair());
let responder_keys = Arc::new(generate_keypair());
let initiator_keys = generate_keypair();
let responder_keys = generate_keypair();
// Create sessions with MISMATCHED Ed25519 keys
// This simulates authentication failure
@@ -2166,8 +2177,11 @@ mod tests {
let initiator_session = LpSession::new(
receiver_index,
true,
Arc::new(initiator_ed25519),
initiator_keys.clone(),
(
initiator_ed25519.private_key(),
initiator_ed25519.public_key(),
),
initiator_keys.private_key(),
wrong_ed25519.public_key(), // Responder expects THIS key
responder_keys.public_key(),
&salt,
@@ -2181,8 +2195,11 @@ mod tests {
let responder_session = LpSession::new(
receiver_index,
false,
Arc::new(responder_ed25519),
responder_keys.clone(),
(
responder_ed25519.private_key(),
responder_ed25519.public_key(),
),
responder_keys.private_key(),
wrong_ed25519.public_key(), // Expects WRONG key (not initiator's)
initiator_keys.public_key(),
&salt,
@@ -2215,8 +2232,8 @@ mod tests {
fn test_psq_invalid_signature() {
// Test Ed25519 signature validation specifically
// Setup with matching X25519 keys but mismatched Ed25519 keys
let initiator_keys = Arc::new(generate_keypair());
let responder_keys = Arc::new(generate_keypair());
let initiator_keys = generate_keypair();
let responder_keys = generate_keypair();
// Initiator uses Ed25519 key [1u8]
let initiator_ed25519 = ed25519::KeyPair::from_secret([1u8; 32], 0);
@@ -2231,8 +2248,11 @@ mod tests {
let initiator_session = LpSession::new(
receiver_index,
true,
Arc::new(initiator_ed25519),
initiator_keys.clone(),
(
initiator_ed25519.private_key(),
initiator_ed25519.public_key(),
),
initiator_keys.private_key(),
wrong_ed25519_public, // This doesn't matter for initiator
responder_keys.public_key(),
&salt,
@@ -2246,8 +2266,11 @@ mod tests {
let responder_session = LpSession::new(
receiver_index,
false,
Arc::new(responder_ed25519),
responder_keys.clone(),
(
responder_ed25519.private_key(),
responder_ed25519.public_key(),
),
responder_keys.private_key(),
wrong_ed25519_public, // Responder expects WRONG key
initiator_keys.public_key(),
&salt,
@@ -2332,8 +2355,8 @@ mod tests {
// This test verifies the safety mechanism that prevents transport mode operations
// from running with the dummy PSK if PSQ injection fails or is skipped.
let initiator_keys = Arc::new(generate_keypair());
let responder_keys = Arc::new(generate_keypair());
let initiator_keys = generate_keypair();
let responder_keys = generate_keypair();
// Create session but don't complete handshake (no PSK injection will occur)
let session = create_handshake_test_session(
@@ -2381,8 +2404,8 @@ mod tests {
#[test]
fn test_demote_sets_read_only() {
let initiator_keys = Arc::new(generate_keypair());
let responder_keys = Arc::new(generate_keypair());
let initiator_keys = generate_keypair();
let responder_keys = generate_keypair();
let session = create_handshake_test_session(
12345u32,
@@ -2406,8 +2429,8 @@ mod tests {
#[test]
fn test_encrypt_fails_after_demotion() {
// --- Setup Handshake ---
let initiator_keys = Arc::new(generate_keypair());
let responder_keys = Arc::new(generate_keypair());
let initiator_keys = generate_keypair();
let responder_keys = generate_keypair();
let initiator_session = create_handshake_test_session(
12345u32,
@@ -2462,8 +2485,8 @@ mod tests {
#[test]
fn test_decrypt_works_after_demotion() {
// --- Setup Handshake ---
let initiator_keys = Arc::new(generate_keypair());
let responder_keys = Arc::new(generate_keypair());
let initiator_keys = generate_keypair();
let responder_keys = generate_keypair();
let initiator_session = create_handshake_test_session(
12345u32,
+42 -54
View File
@@ -1,6 +1,7 @@
#[cfg(test)]
mod tests {
use crate::codec::{parse_lp_packet, serialize_lp_packet};
use crate::keypair::PublicKey;
use crate::{
LpError,
message::LpMessage,
@@ -8,8 +9,7 @@ mod tests {
session_manager::SessionManager,
};
use bytes::BytesMut;
use nym_crypto::asymmetric::{ed25519, x25519};
use std::sync::Arc;
use nym_crypto::asymmetric::ed25519;
// Function to create a test packet - similar to how it's done in codec.rs tests
fn create_test_packet(
@@ -21,7 +21,7 @@ mod tests {
// Create the header
let header = LpHeader {
protocol_version,
reserved: [0u8; 3], // reserved
reserved: 0u16, // reserved
receiver_idx,
counter,
};
@@ -53,11 +53,6 @@ mod tests {
let ed25519_keypair_a = ed25519::KeyPair::from_secret([1u8; 32], 0);
let ed25519_keypair_b = ed25519::KeyPair::from_secret([2u8; 32], 1);
let x25519_keypair_a = ed25519_keypair_a.to_x25519();
let x25519_keypair_b = ed25519_keypair_b.to_x25519();
let ed25519_pubkey_a = *ed25519_keypair_a.public_key();
// Derive X25519 keys from Ed25519 (needed for KKT init test)
let x25519_pub_a = ed25519_keypair_a
.public_key()
@@ -69,9 +64,9 @@ mod tests {
.expect("Failed to derive X25519 from Ed25519");
// Convert to LP keypair types
let lp_pub_a = x25519::PublicKey::from_bytes(x25519_pub_a.as_bytes())
let lp_pub_a = PublicKey::from_bytes(x25519_pub_a.as_bytes())
.expect("Failed to create PublicKey from bytes");
let lp_pub_b = x25519::PublicKey::from_bytes(x25519_pub_b.as_bytes())
let lp_pub_b = PublicKey::from_bytes(x25519_pub_b.as_bytes())
.expect("Failed to create PublicKey from bytes");
// Use fixed receiver_index for deterministic test
@@ -84,9 +79,11 @@ mod tests {
let peer_a_sm = session_manager_1
.create_session_state_machine(
receiver_index,
Arc::new(ed25519_keypair_a),
(
ed25519_keypair_a.private_key(),
ed25519_keypair_a.public_key(),
),
ed25519_keypair_b.public_key(),
x25519_keypair_b.public_key(),
true,
&salt,
)
@@ -95,9 +92,11 @@ mod tests {
let peer_b_sm = session_manager_2
.create_session_state_machine(
receiver_index,
Arc::new(ed25519_keypair_b),
&ed25519_pubkey_a,
x25519_keypair_a.public_key(),
(
ed25519_keypair_b.private_key(),
ed25519_keypair_b.public_key(),
),
ed25519_keypair_a.public_key(),
false,
&salt,
)
@@ -513,11 +512,6 @@ mod tests {
let ed25519_keypair_a = ed25519::KeyPair::from_secret([3u8; 32], 0);
let ed25519_keypair_b = ed25519::KeyPair::from_secret([4u8; 32], 1);
let x25519_keypair_a = ed25519_keypair_a.to_x25519();
let x25519_keypair_b = ed25519_keypair_b.to_x25519();
let ed25519_pubkey_a = *ed25519_keypair_a.public_key();
// Derive X25519 keys from Ed25519 (same as state machine does internally)
let x25519_pub_a = ed25519_keypair_a
.public_key()
@@ -529,9 +523,9 @@ mod tests {
.expect("Failed to derive X25519 from Ed25519");
// Convert to LP keypair types
let lp_pub_a = x25519::PublicKey::from_bytes(x25519_pub_a.as_bytes())
let lp_pub_a = PublicKey::from_bytes(x25519_pub_a.as_bytes())
.expect("Failed to create PublicKey from bytes");
let lp_pub_b = x25519::PublicKey::from_bytes(x25519_pub_b.as_bytes())
let lp_pub_b = PublicKey::from_bytes(x25519_pub_b.as_bytes())
.expect("Failed to create PublicKey from bytes");
// Use fixed receiver_index for test
@@ -543,9 +537,11 @@ mod tests {
let peer_a_sm = session_manager_1
.create_session_state_machine(
receiver_index,
Arc::new(ed25519_keypair_a),
(
ed25519_keypair_a.private_key(),
ed25519_keypair_a.public_key(),
),
ed25519_keypair_b.public_key(),
x25519_keypair_b.public_key(),
true,
&salt,
)
@@ -553,9 +549,11 @@ mod tests {
let peer_b_sm = session_manager_2
.create_session_state_machine(
receiver_index,
Arc::new(ed25519_keypair_b),
&ed25519_pubkey_a,
x25519_keypair_a.public_key(),
(
ed25519_keypair_b.private_key(),
ed25519_keypair_b.public_key(),
),
ed25519_keypair_a.public_key(),
false,
&salt,
)
@@ -722,22 +720,16 @@ mod tests {
let session_manager = SessionManager::new();
// Generate Ed25519 keypair for PSQ authentication
let ed25519_keypair_a = ed25519::KeyPair::from_secret([5u8; 32], 0);
let ed25519_keypair_b = ed25519::KeyPair::from_secret([6u8; 32], 0);
let x25519_keypair_a = ed25519_keypair_a.to_x25519();
let x25519_keypair_b = ed25519_keypair_b.to_x25519();
let ed25519_keypair = ed25519::KeyPair::from_secret([5u8; 32], 0);
// Derive X25519 key from Ed25519 (same as state machine does internally)
let x25519_pub = ed25519_keypair_a
let x25519_pub = ed25519_keypair
.public_key()
.to_x25519()
.expect("Failed to derive X25519 from Ed25519");
let keypair_a = Arc::new(ed25519_keypair_a);
// Convert to LP keypair type (still needed for init_kkt_for_test below if used)
let _lp_pub = x25519::PublicKey::from_bytes(x25519_pub.as_bytes())
let _lp_pub = PublicKey::from_bytes(x25519_pub.as_bytes())
.expect("Failed to create PublicKey from bytes");
// Use fixed receiver_index for test
@@ -750,9 +742,8 @@ mod tests {
let _session = session_manager
.create_session_state_machine(
receiver_index,
keypair_a.clone(),
ed25519_keypair_b.public_key(),
x25519_keypair_b.public_key(),
(ed25519_keypair.private_key(), ed25519_keypair.public_key()),
ed25519_keypair.public_key(),
true,
&salt,
)
@@ -774,9 +765,8 @@ mod tests {
let _temp_session = session_manager
.create_session_state_machine(
receiver_index_temp,
keypair_a.clone(),
ed25519_keypair_b.public_key(),
x25519_keypair_a.public_key(),
(ed25519_keypair.private_key(), ed25519_keypair.public_key()),
ed25519_keypair.public_key(),
true,
&salt,
)
@@ -859,12 +849,6 @@ mod tests {
let ed25519_keypair_a = ed25519::KeyPair::from_secret([6u8; 32], 0);
let ed25519_keypair_b = ed25519::KeyPair::from_secret([7u8; 32], 1);
let x25519_keypair_a = ed25519_keypair_a.to_x25519();
let x25519_keypair_b = ed25519_keypair_b.to_x25519();
let pubkey_a = *ed25519_keypair_a.public_key();
let pubkey_b = *ed25519_keypair_b.public_key();
// Use fixed receiver_index for test
let receiver_index: u32 = 100005;
@@ -876,9 +860,11 @@ mod tests {
session_manager_1
.create_session_state_machine(
receiver_index,
Arc::new(ed25519_keypair_a),
&pubkey_b,
x25519_keypair_b.public_key(),
(
ed25519_keypair_a.private_key(),
ed25519_keypair_a.public_key()
),
ed25519_keypair_b.public_key(),
true,
&salt,
) // Initiator
@@ -888,9 +874,11 @@ mod tests {
session_manager_2
.create_session_state_machine(
receiver_index,
Arc::new(ed25519_keypair_b),
&pubkey_a,
x25519_keypair_a.public_key(),
(
ed25519_keypair_b.private_key(),
ed25519_keypair_b.public_key()
),
ed25519_keypair_a.public_key(),
false,
&salt,
) // Responder
+24 -43
View File
@@ -7,8 +7,7 @@
//! creation, retrieval, and storage of sessions.
use dashmap::DashMap;
use nym_crypto::asymmetric::{ed25519, x25519};
use std::sync::Arc;
use nym_crypto::asymmetric::ed25519;
use crate::noise_protocol::ReadResult;
use crate::state_machine::{LpAction, LpInput, LpState, LpStateBare};
@@ -170,9 +169,8 @@ impl SessionManager {
pub fn create_session_state_machine(
&self,
receiver_index: u32,
local_ed25519_keypair: Arc<ed25519::KeyPair>,
local_ed25519_keypair: (&ed25519::PrivateKey, &ed25519::PublicKey),
remote_ed25519_key: &ed25519::PublicKey,
remote_x25519_key: &x25519::PublicKey,
is_initiator: bool,
salt: &[u8; 32],
) -> Result<u32, LpError> {
@@ -181,7 +179,6 @@ impl SessionManager {
is_initiator,
local_ed25519_keypair,
remote_ed25519_key,
remote_x25519_key,
salt,
)?;
@@ -202,7 +199,7 @@ impl SessionManager {
pub fn init_kkt_for_test(
&self,
lp_id: u32,
remote_x25519_pub: &x25519::PublicKey,
remote_x25519_pub: &crate::keypair::PublicKey,
) -> Result<(), LpError> {
self.with_state_machine(lp_id, |sm| {
sm.session()?.set_kkt_completed_for_test(remote_x25519_pub);
@@ -220,19 +217,14 @@ mod tests {
fn test_session_manager_get() {
let manager = SessionManager::new();
let ed25519_keypair = ed25519::KeyPair::from_secret([10u8; 32], 0);
let ed25519_keypair2 = ed25519::KeyPair::from_secret([16u8; 32], 0);
let x25519_keypair2 = ed25519_keypair2.to_x25519();
let salt = [47u8; 32];
let receiver_index: u32 = 1001;
let sm_1_id = manager
.create_session_state_machine(
receiver_index,
Arc::new(ed25519_keypair),
ed25519_keypair2.public_key(),
x25519_keypair2.public_key(),
(ed25519_keypair.private_key(), ed25519_keypair.public_key()),
ed25519_keypair.public_key(),
true,
&salt,
)
@@ -249,19 +241,14 @@ mod tests {
fn test_session_manager_remove() {
let manager = SessionManager::new();
let ed25519_keypair = ed25519::KeyPair::from_secret([11u8; 32], 0);
let ed25519_keypair2 = ed25519::KeyPair::from_secret([16u8; 32], 0);
let x25519_keypair2 = ed25519_keypair2.to_x25519();
let salt = [48u8; 32];
let receiver_index: u32 = 2002;
let sm_1_id = manager
.create_session_state_machine(
receiver_index,
Arc::new(ed25519_keypair),
ed25519_keypair2.public_key(),
x25519_keypair2.public_key(),
(ed25519_keypair.private_key(), ed25519_keypair.public_key()),
ed25519_keypair.public_key(),
true,
&salt,
)
@@ -283,20 +270,14 @@ mod tests {
let ed25519_keypair_3 = ed25519::KeyPair::from_secret([14u8; 32], 2);
let salt = [49u8; 32];
let pubkey1 = *ed25519_keypair_1.public_key();
let pubkey2 = *ed25519_keypair_2.public_key();
let pubkey3 = *ed25519_keypair_3.public_key();
let xpubkey1 = *ed25519_keypair_1.to_x25519().public_key();
let xpubkey2 = *ed25519_keypair_2.to_x25519().public_key();
let xpubkey3 = *ed25519_keypair_3.to_x25519().public_key();
let sm_1 = manager
.create_session_state_machine(
3001,
Arc::new(ed25519_keypair_1),
&pubkey2,
&xpubkey2,
(
ed25519_keypair_1.private_key(),
ed25519_keypair_1.public_key(),
),
ed25519_keypair_1.public_key(),
true,
&salt,
)
@@ -305,9 +286,11 @@ mod tests {
let sm_2 = manager
.create_session_state_machine(
3002,
Arc::new(ed25519_keypair_2),
&pubkey3,
&xpubkey3,
(
ed25519_keypair_2.private_key(),
ed25519_keypair_2.public_key(),
),
ed25519_keypair_2.public_key(),
true,
&salt,
)
@@ -316,9 +299,11 @@ mod tests {
let sm_3 = manager
.create_session_state_machine(
3003,
Arc::new(ed25519_keypair_3),
&pubkey1,
&xpubkey1,
(
ed25519_keypair_3.private_key(),
ed25519_keypair_3.public_key(),
),
ed25519_keypair_3.public_key(),
true,
&salt,
)
@@ -339,17 +324,13 @@ mod tests {
fn test_session_manager_create_session() {
let manager = SessionManager::new();
let ed25519_keypair = ed25519::KeyPair::from_secret([15u8; 32], 0);
let ed25519_keypair2 = ed25519::KeyPair::from_secret([16u8; 32], 0);
let salt = [50u8; 32];
let receiver_index: u32 = 4004;
let x25519_keypair2 = ed25519_keypair2.to_x25519();
let sm = manager.create_session_state_machine(
receiver_index,
Arc::new(ed25519_keypair),
ed25519_keypair2.public_key(),
x25519_keypair2.public_key(),
(ed25519_keypair.private_key(), ed25519_keypair.public_key()),
ed25519_keypair.public_key(),
true,
&salt,
);
+281 -270
View File
@@ -15,15 +15,15 @@
use crate::{
LpError,
keypair::{Keypair, PrivateKey as LpPrivateKey, PublicKey as LpPublicKey},
message::{LpMessage, SubsessionKK1Data, SubsessionKK2Data, SubsessionReadyData},
noise_protocol::NoiseError,
packet::LpPacket,
session::{LpSession, SubsessionHandshake},
};
use bytes::BytesMut;
use nym_crypto::asymmetric::{ed25519, x25519};
use nym_crypto::asymmetric::ed25519;
use std::mem;
use std::sync::Arc;
use tracing::debug;
/// Represents the possible states of the Lewes Protocol connection.
@@ -90,7 +90,6 @@ impl From<&LpState> for LpStateBare {
}
/// Represents inputs that drive the state machine transitions.
#[allow(clippy::large_enum_variant)]
#[derive(Debug)]
pub enum LpInput {
/// Explicitly trigger the start of the handshake (optional, could be implicit on creation)
@@ -190,8 +189,7 @@ impl LpStateMachine {
/// * `is_initiator` - Whether this side initiates the handshake
/// * `local_ed25519_keypair` - Ed25519 keypair for PSQ authentication and X25519 derivation
/// (from client identity key or gateway signing key)
/// * `remote_ed25519_key` - Peer's Ed25519 public key for PSQ authentication
/// * `remote_x25519_key` - Peer's x25519 public key for Noise protocol and DHKEM
/// * `remote_ed25519_key` - Peer's Ed25519 public key for PSQ authentication and X25519 derivation
/// * `salt` - Fresh salt for PSK derivation (must be unique per session)
///
/// # Errors
@@ -201,9 +199,8 @@ impl LpStateMachine {
pub fn new(
receiver_index: u32,
is_initiator: bool,
local_ed25519_keypair: Arc<ed25519::KeyPair>,
local_ed25519_keypair: (&ed25519::PrivateKey, &ed25519::PublicKey),
remote_ed25519_key: &ed25519::PublicKey,
remote_x25519_key: &x25519::PublicKey,
salt: &[u8; 32],
) -> Result<Self, LpError> {
// We use standard RFC 7748 conversion to derive X25519 keys from Ed25519 identity keys.
@@ -216,7 +213,23 @@ impl LpStateMachine {
// - PSQ ECDH baseline security (pre-quantum)
// Convert Ed25519 keys to X25519 for Noise protocol
let local_x25519 = Arc::new(local_ed25519_keypair.to_x25519());
let local_x25519_private = local_ed25519_keypair.0.to_x25519();
let local_x25519_public = local_ed25519_keypair
.1
.to_x25519()
.map_err(LpError::Ed25519RecoveryError)?;
let remote_x25519_public = remote_ed25519_key
.to_x25519()
.map_err(LpError::Ed25519RecoveryError)?;
// Convert nym_crypto X25519 types to nym_lp keypair types
let lp_private = LpPrivateKey::from_bytes(local_x25519_private.as_bytes());
let lp_public = LpPublicKey::from_bytes(local_x25519_public.as_bytes())?;
let lp_remote_public = LpPublicKey::from_bytes(remote_x25519_public.as_bytes())?;
// Create X25519 keypair for Noise
let local_x25519_keypair = Keypair::from_keys(lp_private, lp_public);
// Create the session with both Ed25519 (for PSQ auth) and derived X25519 keys (for Noise)
// receiver_index is client-proposed, passed through directly
@@ -224,9 +237,9 @@ impl LpStateMachine {
receiver_index,
is_initiator,
local_ed25519_keypair,
local_x25519,
local_x25519_keypair.private_key(),
remote_ed25519_key,
remote_x25519_key,
&lp_remote_public,
salt,
)?;
@@ -426,75 +439,75 @@ impl LpStateMachine {
// --- Inline handle_handshake_packet logic ---
// 1. Check replay protection *before* processing
if let Err(e) = session.receiving_counter_quick_check(packet.header.counter) {
let _reason = e.to_string();
result_action = Some(Err(e));
LpState::Handshaking { session }
let _reason = e.to_string();
result_action = Some(Err(e));
LpState::Handshaking { session }
// LpState::Closed { reason }
} else {
// 2. Process the handshake message
match session.process_handshake_message(&packet.message) {
Ok(_) => {
// 3. Mark counter as received *after* successful processing
if let Err(e) = session.receiving_counter_mark(packet.header.counter) {
let _reason = e.to_string();
result_action = Some(Err(e));
// 2. Process the handshake message
match session.process_handshake_message(&packet.message) {
Ok(_) => {
// 3. Mark counter as received *after* successful processing
if let Err(e) = session.receiving_counter_mark(packet.header.counter) {
let _reason = e.to_string();
result_action = Some(Err(e));
// LpState::Closed { reason }
LpState::Handshaking { session }
} else {
// 4. First check if we need to send a handshake message (before checking completion)
match session.prepare_handshake_message() {
Some(Ok(message)) => {
match session.next_packet(message) {
Ok(response_packet) => {
result_action = Some(Ok(LpAction::SendPacket(response_packet)));
// Check if handshake became complete after preparing message
if session.is_handshake_complete() {
LpState::Transport { session } // Transition to Transport
} else {
LpState::Handshaking { session } // Remain Handshaking
}
}
Err(e) => {
let reason = e.to_string();
result_action = Some(Err(e));
LpState::Closed { reason }
}
}
}
Some(Err(e)) => {
let reason = e.to_string();
result_action = Some(Err(e));
LpState::Closed { reason }
}
None => {
// 5. No message to send - check if handshake is complete
if session.is_handshake_complete() {
result_action = Some(Ok(LpAction::HandshakeComplete));
LpState::Transport { session } // Transition to Transport
} else {
// Handshake stalled unexpectedly
let err = LpError::NoiseError(NoiseError::Other(
"Handshake stalled unexpectedly".to_string(),
));
let reason = err.to_string();
result_action = Some(Err(err));
LpState::Closed { reason }
}
}
}
}
}
Err(e) => { // Error from process_handshake_message
let reason = e.to_string();
result_action = Some(Err(e));
LpState::Closed { reason }
}
}
} else {
// 4. First check if we need to send a handshake message (before checking completion)
match session.prepare_handshake_message() {
Some(Ok(message)) => {
match session.next_packet(message) {
Ok(response_packet) => {
result_action = Some(Ok(LpAction::SendPacket(response_packet)));
// Check if handshake became complete after preparing message
if session.is_handshake_complete() {
LpState::Transport { session } // Transition to Transport
} else {
LpState::Handshaking { session } // Remain Handshaking
}
}
Err(e) => {
let reason = e.to_string();
result_action = Some(Err(e));
LpState::Closed { reason }
}
}
}
Some(Err(e)) => {
let reason = e.to_string();
result_action = Some(Err(e));
LpState::Closed { reason }
}
None => {
// 5. No message to send - check if handshake is complete
if session.is_handshake_complete() {
result_action = Some(Ok(LpAction::HandshakeComplete));
LpState::Transport { session } // Transition to Transport
} else {
// Handshake stalled unexpectedly
let err = LpError::NoiseError(NoiseError::Other(
"Handshake stalled unexpectedly".to_string(),
));
let reason = err.to_string();
result_action = Some(Err(err));
LpState::Closed { reason }
}
}
}
}
}
Err(e) => { // Error from process_handshake_message
let reason = e.to_string();
result_action = Some(Err(e));
LpState::Closed { reason }
}
}
}
// --- End inline handle_handshake_packet logic ---
}
}
// Reject SendData during handshake
// Reject SendData during handshake
(LpState::Handshaking { session }, LpInput::SendData(_)) => { // Keep session if returning to this state
result_action = Some(Err(LpError::InvalidStateTransition {
state: "Handshaking".to_string(),
@@ -509,114 +522,114 @@ impl LpStateMachine {
state: "Handshaking".to_string(),
input: "StartHandshake".to_string(),
}));
// Invalid input, remain in Handshaking state
LpState::Handshaking { session }
// Invalid input, remain in Handshaking state
LpState::Handshaking { session }
}
// --- Transport State ---
(LpState::Transport { session }, LpInput::ReceivePacket(packet)) => {
// Check if packet lp_id matches our session
if packet.header.receiver_idx() != session.id() {
// Check if packet lp_id matches our session
if packet.header.receiver_idx() != session.id() {
result_action = Some(Err(LpError::UnknownSessionId(packet.header.receiver_idx())));
LpState::Transport { session }
} else {
// Check message type - handle subsession initiation from peer
match &packet.message {
// Peer initiated subsession - we become responder
LpMessage::SubsessionKK1(kk1_data) => {
// Create subsession as responder
let subsession_index = session.next_subsession_index();
match session.create_subsession(subsession_index, false) {
Ok(subsession) => {
// Process KK1
match subsession.process_message(&kk1_data.payload) {
Ok(_) => {
// Prepare KK2 response
match subsession.prepare_message() {
Ok(kk2_payload) => {
let kk2_msg = LpMessage::SubsessionKK2(SubsessionKK2Data { payload: kk2_payload });
match session.next_packet(kk2_msg) {
Ok(response_packet) => {
result_action = Some(Ok(LpAction::SendPacket(response_packet)));
// Stay in SubsessionHandshaking, wait for SubsessionReady
LpState::SubsessionHandshaking { session, subsession: Box::new(subsession) }
}
Err(e) => {
let reason = e.to_string();
result_action = Some(Err(e));
LpState::Closed { reason }
}
}
}
Err(e) => {
let reason = e.to_string();
result_action = Some(Err(e));
LpState::Closed { reason }
}
}
}
Err(e) => {
let reason = e.to_string();
result_action = Some(Err(e));
LpState::Closed { reason }
}
}
}
Err(e) => {
let reason = e.to_string();
result_action = Some(Err(e));
LpState::Closed { reason }
}
}
}
// Normal encrypted data
LpMessage::EncryptedData(_) => {
// 1. Check replay protection
if let Err(e) = session.receiving_counter_quick_check(packet.header.counter) {
result_action = Some(Err(e));
LpState::Transport { session }
} else {
// 2. Decrypt data
match session.decrypt_data(&packet.message) {
Ok(plaintext) => {
// 3. Mark counter as received
if let Err(e) = session.receiving_counter_mark(packet.header.counter) {
result_action = Some(Err(e));
LpState::Transport { session }
} else {
// 4. Deliver data
result_action = Some(Ok(LpAction::DeliverData(BytesMut::from(plaintext.as_slice()))));
LpState::Transport { session }
}
}
Err(e) => {
let reason = e.to_string();
result_action = Some(Err(e.into()));
LpState::Closed { reason }
}
}
}
}
// Stale abort in Transport state - race already resolved.
// This can happen if abort arrives after loser already returned to Transport
// via KK1 processing (loser detected local < remote and became responder).
// The winner's abort message arrived late. Silently ignore.
LpMessage::SubsessionAbort => {
debug!("Ignoring stale SubsessionAbort in Transport state");
result_action = None;
LpState::Transport { session }
}
_ => {
// Unexpected message type in Transport state
let err = LpError::InvalidStateTransition {
state: "Transport".to_string(),
input: format!("Unexpected message type: {}", packet.message),
};
result_action = Some(Err(err));
LpState::Transport { session }
}
}
}
} else {
// Check message type - handle subsession initiation from peer
match &packet.message {
// Peer initiated subsession - we become responder
LpMessage::SubsessionKK1(kk1_data) => {
// Create subsession as responder
let subsession_index = session.next_subsession_index();
match session.create_subsession(subsession_index, false) {
Ok(subsession) => {
// Process KK1
match subsession.process_message(&kk1_data.payload) {
Ok(_) => {
// Prepare KK2 response
match subsession.prepare_message() {
Ok(kk2_payload) => {
let kk2_msg = LpMessage::SubsessionKK2(SubsessionKK2Data { payload: kk2_payload });
match session.next_packet(kk2_msg) {
Ok(response_packet) => {
result_action = Some(Ok(LpAction::SendPacket(response_packet)));
// Stay in SubsessionHandshaking, wait for SubsessionReady
LpState::SubsessionHandshaking { session, subsession: Box::new(subsession) }
}
Err(e) => {
let reason = e.to_string();
result_action = Some(Err(e));
LpState::Closed { reason }
}
}
}
Err(e) => {
let reason = e.to_string();
result_action = Some(Err(e));
LpState::Closed { reason }
}
}
}
Err(e) => {
let reason = e.to_string();
result_action = Some(Err(e));
LpState::Closed { reason }
}
}
}
Err(e) => {
let reason = e.to_string();
result_action = Some(Err(e));
LpState::Closed { reason }
}
}
}
// Normal encrypted data
LpMessage::EncryptedData(_) => {
// 1. Check replay protection
if let Err(e) = session.receiving_counter_quick_check(packet.header.counter) {
result_action = Some(Err(e));
LpState::Transport { session }
} else {
// 2. Decrypt data
match session.decrypt_data(&packet.message) {
Ok(plaintext) => {
// 3. Mark counter as received
if let Err(e) = session.receiving_counter_mark(packet.header.counter) {
result_action = Some(Err(e));
LpState::Transport { session }
} else {
// 4. Deliver data
result_action = Some(Ok(LpAction::DeliverData(BytesMut::from(plaintext.as_slice()))));
LpState::Transport { session }
}
}
Err(e) => {
let reason = e.to_string();
result_action = Some(Err(e.into()));
LpState::Closed { reason }
}
}
}
}
// Stale abort in Transport state - race already resolved.
// This can happen if abort arrives after loser already returned to Transport
// via KK1 processing (loser detected local < remote and became responder).
// The winner's abort message arrived late. Silently ignore.
LpMessage::SubsessionAbort => {
debug!("Ignoring stale SubsessionAbort in Transport state");
result_action = None;
LpState::Transport { session }
}
_ => {
// Unexpected message type in Transport state
let err = LpError::InvalidStateTransition {
state: "Transport".to_string(),
input: format!("Unexpected message type: {}", packet.message),
};
result_action = Some(Err(err));
LpState::Transport { session }
}
}
}
}
(LpState::Transport { session }, LpInput::SendData(data)) => {
// Encrypt and send application data
@@ -628,17 +641,17 @@ impl LpStateMachine {
result_action = Some(Err(e.into()));
}
}
// Remain in transport state
LpState::Transport { session }
// Remain in transport state
LpState::Transport { session }
}
// Reject StartHandshake if already in transport
// Reject StartHandshake if already in transport
(LpState::Transport { session }, LpInput::StartHandshake) => { // Keep session
result_action = Some(Err(LpError::InvalidStateTransition {
state: "Transport".to_string(),
input: "StartHandshake".to_string(),
}));
// Invalid input, remain in Transport state
LpState::Transport { session }
// Invalid input, remain in Transport state
LpState::Transport { session }
}
// --- Transport + InitiateSubsession → SubsessionHandshaking ---
@@ -957,25 +970,25 @@ impl LpStateMachine {
result_action = Some(Err(LpError::UnknownSessionId(packet.header.receiver_idx())));
LpState::ReadOnlyTransport { session }
} else if let Err(e) = session.receiving_counter_quick_check(packet.header.counter) {
result_action = Some(Err(e));
LpState::ReadOnlyTransport { session }
} else {
match session.decrypt_data(&packet.message) {
Ok(plaintext) => {
if let Err(e) = session.receiving_counter_mark(packet.header.counter) {
result_action = Some(Err(e));
LpState::ReadOnlyTransport { session }
} else {
result_action = Some(Ok(LpAction::DeliverData(BytesMut::from(plaintext.as_slice()))));
LpState::ReadOnlyTransport { session }
result_action = Some(Err(e));
LpState::ReadOnlyTransport { session }
} else {
match session.decrypt_data(&packet.message) {
Ok(plaintext) => {
if let Err(e) = session.receiving_counter_mark(packet.header.counter) {
result_action = Some(Err(e));
LpState::ReadOnlyTransport { session }
} else {
result_action = Some(Ok(LpAction::DeliverData(BytesMut::from(plaintext.as_slice()))));
LpState::ReadOnlyTransport { session }
}
}
Err(e) => {
let reason = e.to_string();
result_action = Some(Err(e.into()));
LpState::Closed { reason }
}
}
Err(e) => {
let reason = e.to_string();
result_action = Some(Err(e.into()));
LpState::Closed { reason }
}
}
}
}
@@ -1013,8 +1026,8 @@ impl LpStateMachine {
LpInput::Close,
) => {
result_action = Some(Ok(LpAction::ConnectionClosed));
// Transition to Closed state
LpState::Closed { reason: "Closed by user".to_string() }
// Transition to Closed state
LpState::Closed { reason: "Closed by user".to_string() }
}
// Ignore Close if already Closed
(closed_state @ LpState::Closed { .. }, LpInput::Close) => {
@@ -1027,36 +1040,36 @@ impl LpStateMachine {
// result_action = Some(Err(LpError::LpSessionClosed));
// closed_state
// }
// Ignore ReceivePacket if Closed
// Ignore ReceivePacket if Closed
(closed_state @ LpState::Closed { .. }, LpInput::ReceivePacket(_)) => {
result_action = Some(Err(LpError::LpSessionClosed));
closed_state
result_action = Some(Err(LpError::LpSessionClosed));
closed_state
}
// Ignore SendData if Closed
// Ignore SendData if Closed
(closed_state @ LpState::Closed { .. }, LpInput::SendData(_)) => {
result_action = Some(Err(LpError::LpSessionClosed));
closed_state
result_action = Some(Err(LpError::LpSessionClosed));
closed_state
}
// Processing state should not be matched directly if using replace
(LpState::Processing, _) => {
// This case should ideally be unreachable if placeholder logic is correct
let err = LpError::Internal("Reached Processing state unexpectedly".to_string());
let reason = err.to_string();
result_action = Some(Err(err));
LpState::Closed { reason }
// This case should ideally be unreachable if placeholder logic is correct
let err = LpError::Internal("Reached Processing state unexpectedly".to_string());
let reason = err.to_string();
result_action = Some(Err(err));
LpState::Closed { reason }
}
// --- Default: Invalid input for current state (if any combinations missed) ---
// Consider if this should transition to Closed state. For now, just report error
// and transition to Closed as a safety measure.
(invalid_state, input) => {
let err = LpError::InvalidStateTransition {
state: format!("{:?}", invalid_state), // Use owned state for debug info
input: format!("{:?}", input),
};
let reason = err.to_string();
result_action = Some(Err(err));
LpState::Closed { reason }
let err = LpError::InvalidStateTransition {
state: format!("{:?}", invalid_state), // Use owned state for debug info
input: format!("{:?}", input),
};
let reason = err.to_string();
result_action = Some(Err(err));
LpState::Closed { reason }
}
};
@@ -1092,10 +1105,6 @@ mod tests {
let ed25519_keypair_init = ed25519::KeyPair::from_secret([16u8; 32], 0);
let ed25519_keypair_resp = ed25519::KeyPair::from_secret([17u8; 32], 1);
let ed25519_pubkey_init = *ed25519_keypair_init.public_key();
let x25519_pubkey_init = ed25519_pubkey_init.to_x25519().unwrap();
let x25519_pubkey_resp = *ed25519_keypair_resp.to_x25519().public_key();
// Test salt
let salt = [51u8; 32];
@@ -1104,9 +1113,11 @@ mod tests {
let initiator_sm = LpStateMachine::new(
receiver_index,
true,
Arc::new(ed25519_keypair_init),
(
ed25519_keypair_init.private_key(),
ed25519_keypair_init.public_key(),
),
ed25519_keypair_resp.public_key(),
&x25519_pubkey_resp,
&salt,
);
assert!(initiator_sm.is_ok());
@@ -1121,9 +1132,11 @@ mod tests {
let responder_sm = LpStateMachine::new(
receiver_index,
false,
Arc::new(ed25519_keypair_resp),
&ed25519_pubkey_init,
&x25519_pubkey_init,
(
ed25519_keypair_resp.private_key(),
ed25519_keypair_resp.public_key(),
),
ed25519_keypair_init.public_key(),
&salt,
);
assert!(responder_sm.is_ok());
@@ -1145,10 +1158,6 @@ mod tests {
let ed25519_keypair_init = ed25519::KeyPair::from_secret([18u8; 32], 0);
let ed25519_keypair_resp = ed25519::KeyPair::from_secret([19u8; 32], 1);
let ed25519_pubkey_init = *ed25519_keypair_init.public_key();
let x25519_pubkey_init = ed25519_pubkey_init.to_x25519().unwrap();
let x25519_pubkey_resp = *ed25519_keypair_resp.to_x25519().public_key();
// Test salt
let salt = [52u8; 32];
let receiver_index: u32 = 88888;
@@ -1157,9 +1166,11 @@ mod tests {
let mut initiator = LpStateMachine::new(
receiver_index,
true, // is_initiator
Arc::new(ed25519_keypair_init),
(
ed25519_keypair_init.private_key(),
ed25519_keypair_init.public_key(),
),
ed25519_keypair_resp.public_key(),
&x25519_pubkey_resp,
&salt,
)
.unwrap();
@@ -1167,9 +1178,11 @@ mod tests {
let mut responder = LpStateMachine::new(
receiver_index,
false, // is_initiator
Arc::new(ed25519_keypair_resp),
&ed25519_pubkey_init,
&x25519_pubkey_init,
(
ed25519_keypair_resp.private_key(),
ed25519_keypair_resp.public_key(),
),
ed25519_keypair_init.public_key(),
&salt,
)
.unwrap();
@@ -1353,8 +1366,6 @@ mod tests {
let ed25519_keypair_init = ed25519::KeyPair::from_secret([20u8; 32], 0);
let ed25519_keypair_resp = ed25519::KeyPair::from_secret([21u8; 32], 1);
let x25519_pubkey_init = *ed25519_keypair_init.to_x25519().public_key();
let salt = [53u8; 32];
let receiver_index: u32 = 99901;
@@ -1362,9 +1373,11 @@ mod tests {
let mut initiator = LpStateMachine::new(
receiver_index,
true,
Arc::new(ed25519_keypair_init),
(
ed25519_keypair_init.private_key(),
ed25519_keypair_init.public_key(),
),
ed25519_keypair_resp.public_key(),
&x25519_pubkey_init,
&salt,
)
.unwrap();
@@ -1384,9 +1397,6 @@ mod tests {
let ed25519_keypair_init = ed25519::KeyPair::from_secret([22u8; 32], 0);
let ed25519_keypair_resp = ed25519::KeyPair::from_secret([23u8; 32], 1);
let ed25519_pubkey_init = *ed25519_keypair_init.public_key();
let x25519_pubkey_init = ed25519_pubkey_init.to_x25519().unwrap();
let salt = [54u8; 32];
let receiver_index: u32 = 99902;
@@ -1394,9 +1404,11 @@ mod tests {
let mut responder = LpStateMachine::new(
receiver_index,
false,
Arc::new(ed25519_keypair_resp),
&ed25519_pubkey_init,
&x25519_pubkey_init,
(
ed25519_keypair_resp.private_key(),
ed25519_keypair_resp.public_key(),
),
ed25519_keypair_init.public_key(),
&salt,
)
.unwrap();
@@ -1416,10 +1428,6 @@ mod tests {
let ed25519_keypair_init = ed25519::KeyPair::from_secret([24u8; 32], 0);
let ed25519_keypair_resp = ed25519::KeyPair::from_secret([25u8; 32], 1);
let ed25519_pubkey_init = *ed25519_keypair_init.public_key();
let x25519_pubkey_init = ed25519_pubkey_init.to_x25519().unwrap();
let x25519_pubkey_resp = *ed25519_keypair_resp.to_x25519().public_key();
let salt = [55u8; 32];
let receiver_index: u32 = 99903;
@@ -1427,9 +1435,11 @@ mod tests {
let mut initiator = LpStateMachine::new(
receiver_index,
true,
Arc::new(ed25519_keypair_init),
(
ed25519_keypair_init.private_key(),
ed25519_keypair_init.public_key(),
),
ed25519_keypair_resp.public_key(),
&x25519_pubkey_resp,
&salt,
)
.unwrap();
@@ -1437,9 +1447,11 @@ mod tests {
let mut responder = LpStateMachine::new(
receiver_index,
false,
Arc::new(ed25519_keypair_resp),
&ed25519_pubkey_init,
&x25519_pubkey_init,
(
ed25519_keypair_resp.private_key(),
ed25519_keypair_resp.public_key(),
),
ed25519_keypair_init.public_key(),
&salt,
)
.unwrap();
@@ -1481,8 +1493,6 @@ mod tests {
let ed25519_keypair_init = ed25519::KeyPair::from_secret([26u8; 32], 0);
let ed25519_keypair_resp = ed25519::KeyPair::from_secret([27u8; 32], 1);
let x25519_pubkey_resp = *ed25519_keypair_resp.to_x25519().public_key();
let salt = [56u8; 32];
let receiver_index: u32 = 99904;
@@ -1490,9 +1500,11 @@ mod tests {
let mut initiator = LpStateMachine::new(
receiver_index,
true,
Arc::new(ed25519_keypair_init),
(
ed25519_keypair_init.private_key(),
ed25519_keypair_init.public_key(),
),
ed25519_keypair_resp.public_key(),
&x25519_pubkey_resp,
&salt,
)
.unwrap();
@@ -1513,8 +1525,6 @@ mod tests {
let ed25519_keypair_init = ed25519::KeyPair::from_secret([28u8; 32], 0);
let ed25519_keypair_resp = ed25519::KeyPair::from_secret([29u8; 32], 1);
let x25519_pubkey_resp = *ed25519_keypair_resp.to_x25519().public_key();
let salt = [57u8; 32];
let receiver_index: u32 = 99905;
@@ -1522,9 +1532,11 @@ mod tests {
let mut initiator = LpStateMachine::new(
receiver_index,
true,
Arc::new(ed25519_keypair_init),
(
ed25519_keypair_init.private_key(),
ed25519_keypair_init.public_key(),
),
ed25519_keypair_resp.public_key(),
&x25519_pubkey_resp,
&salt,
)
.unwrap();
@@ -1558,11 +1570,6 @@ mod tests {
let ed25519_keypair_a = ed25519::KeyPair::from_secret([30u8; 32], 0);
let ed25519_keypair_b = ed25519::KeyPair::from_secret([31u8; 32], 1);
let ed25519_pubkey_a = *ed25519_keypair_a.public_key();
let x25519_pubkey_a = *ed25519_keypair_a.to_x25519().public_key();
let x25519_pubkey_b = *ed25519_keypair_b.to_x25519().public_key();
let salt = [60u8; 32];
let receiver_index: u32 = 111111;
@@ -1570,9 +1577,11 @@ mod tests {
let mut alice = LpStateMachine::new(
receiver_index,
true,
Arc::new(ed25519_keypair_a),
(
ed25519_keypair_a.private_key(),
ed25519_keypair_a.public_key(),
),
ed25519_keypair_b.public_key(),
&x25519_pubkey_b,
&salt,
)
.unwrap();
@@ -1580,9 +1589,11 @@ mod tests {
let mut bob = LpStateMachine::new(
receiver_index,
false,
Arc::new(ed25519_keypair_b),
&ed25519_pubkey_a,
&x25519_pubkey_a,
(
ed25519_keypair_b.private_key(),
ed25519_keypair_b.public_key(),
),
ed25519_keypair_a.public_key(),
&salt,
)
.unwrap();
+39 -23
View File
@@ -14,7 +14,7 @@ use nym_sphinx_types::{
};
use std::fmt::Display;
use thiserror::Error;
use tracing::{debug, trace};
use tracing::{debug, info, trace};
#[derive(Debug)]
pub enum MixProcessingResultData {
@@ -335,9 +335,36 @@ fn process_final_hop(
packet_type: PacketType,
key_rotation: SphinxKeyRotation,
) -> Result<MixProcessingResultData, PacketProcessingError> {
let payload_size = payload.len();
debug!(
"process_final_hop: payload_size={}, packet_size={:?}, packet_type={:?}, destination={}",
payload_size, packet_size, packet_type, destination
);
let (forward_ack, message) =
split_into_ack_and_message(payload, packet_size, packet_type, key_rotation)?;
let message_size = message.len();
let ack_present = forward_ack.is_some();
debug!(
"process_final_hop: after split - message_size={}, ack_present={}, payload_size={}",
message_size, ack_present, payload_size
);
// Verify expected message size
if let PacketSize::RegularPacket = packet_size {
use nym_sphinx_addressing::nodes::MAX_NODE_ADDRESS_UNPADDED_LEN;
let sphinx_ack_overhead = PacketSize::AckPacket.size() + MAX_NODE_ADDRESS_UNPADDED_LEN;
let expected_message_size = packet_size.plaintext_size() - sphinx_ack_overhead;
if message_size != expected_message_size {
use tracing::warn;
warn!(
"process_final_hop: MESSAGE SIZE MISMATCH! message_size={}, expected={}, payload_size={}, ack_present={}",
message_size, expected_message_size, payload_size, ack_present
);
}
}
Ok(MixProcessingResultData::FinalHop {
final_hop_data: ProcessedFinalHop {
destination,
@@ -364,28 +391,17 @@ fn split_into_ack_and_message(
| PacketSize::ExtendedPacket32
| PacketSize::OutfoxRegularPacket => {
trace!("received a normal packet!");
cfg_if::cfg_if! {
if #[cfg(feature = "no-mix-acks")] {
let _ = packet_type;
let _ = key_rotation;
// AIDEV-NOTE: When no-mix-acks is enabled, skip ack extraction entirely.
// The full payload (including ack portion) is returned as the message.
Ok((None, data))
} else {
let (ack_data, message) = split_hop_data_into_ack_and_message(data, packet_type)?;
let (ack_first_hop, ack_packet) =
match SurbAck::try_recover_first_hop_packet(&ack_data, packet_type) {
Ok((first_hop, packet)) => (first_hop, packet),
Err(err) => {
tracing::info!("Failed to recover first hop from ack data: {err}");
return Err(err.into());
}
};
let forward_ack = MixPacket::new(ack_first_hop, ack_packet, packet_type, key_rotation);
Ok((Some(forward_ack), message))
}
}
let (ack_data, message) = split_hop_data_into_ack_and_message(data, packet_type)?;
let (ack_first_hop, ack_packet) =
match SurbAck::try_recover_first_hop_packet(&ack_data, packet_type) {
Ok((first_hop, packet)) => (first_hop, packet),
Err(err) => {
info!("Failed to recover first hop from ack data: {err}");
return Err(err.into());
}
};
let forward_ack = MixPacket::new(ack_first_hop, ack_packet, packet_type, key_rotation);
Ok((Some(forward_ack), message))
}
}
}
-1
View File
@@ -12,7 +12,6 @@ license.workspace = true
workspace = true
[dependencies]
bincode = { workspace = true }
serde = { workspace = true, features = ["derive"] }
tokio-util.workspace = true
+6 -8
View File
@@ -1,6 +1,12 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
mod lp_messages;
pub use lp_messages::{
LpGatewayData, LpRegistrationRequest, LpRegistrationResponse, RegistrationMode,
};
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
use nym_authenticator_requests::AuthenticatorVersion;
@@ -9,14 +15,6 @@ use nym_ip_packet_requests::IpPair;
use nym_sphinx::addressing::{NodeIdentity, Recipient};
use serde::{Deserialize, Serialize};
mod lp_messages;
mod serialisation;
pub use lp_messages::{
LpGatewayData, LpRegistrationRequest, LpRegistrationResponse, RegistrationMode,
};
pub use serialisation::BincodeError;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct NymNode {
pub identity: NodeIdentity,
-21
View File
@@ -8,7 +8,6 @@ use serde::{Deserialize, Serialize};
use std::net::IpAddr;
use crate::GatewayData;
use crate::serialisation::{BincodeError, BincodeOptions, lp_bincode_serializer};
/// Registration request sent by client after LP handshake
/// Aligned with existing authenticator registration flow
@@ -129,16 +128,6 @@ impl LpRegistrationRequest {
(now as i64 - self.timestamp as i64).abs() <= max_skew_secs as i64
}
/// Attempt to serialise this `LpRegistrationRequest` into bytes.
pub fn serialise(&self) -> Result<Vec<u8>, BincodeError> {
lp_bincode_serializer().serialize(self)
}
/// Attempt to deserialise a `LpRegistrationRequest` from bytes.
pub fn try_deserialise(b: &[u8]) -> Result<Self, BincodeError> {
lp_bincode_serializer().deserialize(b)
}
}
impl LpRegistrationResponse {
@@ -174,16 +163,6 @@ impl LpRegistrationResponse {
allocated_bandwidth: 0,
}
}
/// Attempt to serialise this `LpRegistrationResponse` into bytes.
pub fn serialise(&self) -> Result<Vec<u8>, BincodeError> {
lp_bincode_serializer().serialize(self)
}
/// Attempt to deserialise a `LpRegistrationResponse` from bytes.
pub fn try_deserialise(b: &[u8]) -> Result<Self, BincodeError> {
lp_bincode_serializer().deserialize(b)
}
}
#[cfg(test)]
+39 -124
View File
@@ -6,18 +6,16 @@
// #![warn(clippy::expect_used)]
// #![warn(clippy::unwrap_used)]
use defguard_wireguard_rs::{
WGApi, WireguardInterfaceApi, error::WireguardInterfaceError, host::Peer, key::Key,
net::IpAddrMask,
};
use defguard_wireguard_rs::{WGApi, WireguardInterfaceApi, host::Peer, key::Key, net::IpAddrMask};
use nym_crypto::asymmetric::x25519::KeyPair;
use std::net::IpAddr;
use std::sync::Arc;
use tokio::sync::mpsc::{self, Receiver, Sender};
use tracing::error;
#[cfg(target_os = "linux")]
use nym_ip_packet_requests::IpPair;
#[cfg(target_os = "linux")]
use std::net::IpAddr;
#[cfg(target_os = "linux")]
use nym_network_defaults::constants::WG_TUN_BASE_NAME;
@@ -35,104 +33,13 @@ pub use peer_controller::{PeerControlRequest, PeerRegistrationData};
pub const CONTROL_CHANNEL_SIZE: usize = 256;
#[derive(Debug, thiserror::Error)]
pub enum WgApiWrapperError {
#[error("WireGuard kernel implementation is not available on this platform")]
KernelUnavailable,
#[error("WireGuard userspace implementation is not available on this platform")]
UserspaceUnavailable,
#[error("WireGuard interface error: {0}")]
Interface(#[from] WireguardInterfaceError),
}
pub struct WgApiWrapper {
inner: Box<dyn WireguardInterfaceApi + Sync + Send>,
}
impl WgApiWrapper {
/// Create new instance of `WgApiWrapper` choosing internal implementation based on `use_userspace` flag and platform availability.
///
/// Falls back to userspace implementation when kernel implementation is requested but not available.
pub fn new(ifname: &str, use_userspace: bool) -> Result<Self, WgApiWrapperError> {
if use_userspace {
Self::userspace(ifname)
} else {
Self::kernel(ifname).or_else(|err| {
if matches!(err, WgApiWrapperError::KernelUnavailable) {
Self::userspace(ifname)
} else {
Err(err)
}
})
}
}
/// Create userspace implementation
fn userspace(_ifname: &str) -> Result<Self, WgApiWrapperError> {
#[cfg(any(
target_os = "linux",
target_os = "macos",
target_os = "freebsd",
target_os = "netbsd"
))]
{
let api = WGApi::<defguard_wireguard_rs::Userspace>::new(_ifname)?;
Ok(Self {
inner: Box::new(api),
})
}
#[cfg(not(any(
target_os = "linux",
target_os = "macos",
target_os = "freebsd",
target_os = "netbsd"
)))]
{
Err(WgApiWrapperError::UserspaceUnavailable)
}
}
/// Create kernel implementation if available.
fn kernel(_ifname: &str) -> Result<Self, WgApiWrapperError> {
#[cfg(any(
target_os = "linux",
target_os = "windows",
target_os = "freebsd",
target_os = "netbsd"
))]
{
let api = WGApi::<defguard_wireguard_rs::Kernel>::new(_ifname)?;
Ok(Self {
inner: Box::new(api),
})
}
#[cfg(not(any(
target_os = "linux",
target_os = "windows",
target_os = "freebsd",
target_os = "netbsd"
)))]
{
Err(WgApiWrapperError::KernelUnavailable)
}
}
}
impl Drop for WgApiWrapper {
fn drop(&mut self) {
if let Err(e) = self.inner.remove_interface() {
error!("Could not remove the wireguard interface: {e:?}");
}
}
inner: WGApi,
}
impl WireguardInterfaceApi for WgApiWrapper {
fn create_interface(
&mut self,
&self,
) -> Result<(), defguard_wireguard_rs::error::WireguardInterfaceError> {
self.inner.create_interface()
}
@@ -151,6 +58,7 @@ impl WireguardInterfaceApi for WgApiWrapper {
self.inner.configure_peer_routing(peers)
}
#[cfg(not(target_os = "windows"))]
fn configure_interface(
&self,
config: &defguard_wireguard_rs::InterfaceConfiguration,
@@ -158,16 +66,17 @@ impl WireguardInterfaceApi for WgApiWrapper {
self.inner.configure_interface(config)
}
#[cfg(not(windows))]
fn remove_interface(
#[cfg(target_os = "windows")]
fn configure_interface(
&self,
config: &defguard_wireguard_rs::InterfaceConfiguration,
dns: &[std::net::IpAddr],
) -> Result<(), defguard_wireguard_rs::error::WireguardInterfaceError> {
self.inner.remove_interface()
self.inner.configure_interface(config, dns)
}
#[cfg(windows)]
fn remove_interface(
&mut self,
&self,
) -> Result<(), defguard_wireguard_rs::error::WireguardInterfaceError> {
self.inner.remove_interface()
}
@@ -197,10 +106,24 @@ impl WireguardInterfaceApi for WgApiWrapper {
fn configure_dns(
&self,
dns: &[IpAddr],
search_domains: &[&str],
dns: &[std::net::IpAddr],
) -> Result<(), defguard_wireguard_rs::error::WireguardInterfaceError> {
self.inner.configure_dns(dns, search_domains)
self.inner.configure_dns(dns)
}
}
impl WgApiWrapper {
pub fn new(wg_api: WGApi) -> Self {
WgApiWrapper { inner: wg_api }
}
}
impl Drop for WgApiWrapper {
fn drop(&mut self) {
if let Err(e) = defguard_wireguard_rs::WireguardInterfaceApi::remove_interface(&self.inner)
{
error!("Could not remove the wireguard interface: {e:?}");
}
}
}
@@ -258,7 +181,7 @@ pub async fn start_wireguard(
use_userspace: bool,
) -> Result<std::sync::Arc<WgApiWrapper>, Box<dyn std::error::Error + Send + Sync + 'static>> {
use base64::{Engine, prelude::BASE64_STANDARD};
use defguard_wireguard_rs::InterfaceConfiguration;
use defguard_wireguard_rs::{InterfaceConfiguration, WireguardInterfaceApi};
use ip_network::IpNetwork;
use peer_controller::PeerController;
use std::collections::HashMap;
@@ -270,7 +193,7 @@ pub async fn start_wireguard(
"Initializing WireGuard interface '{}' with use_userspace={}",
ifname, use_userspace
);
let mut wg_api = WgApiWrapper::new(&ifname, use_userspace)?;
let wg_api = defguard_wireguard_rs::WGApi::new(ifname.clone(), use_userspace)?;
let mut peer_bandwidth_managers = HashMap::with_capacity(peers.len());
for peer in peers.iter() {
@@ -291,22 +214,14 @@ pub async fn start_wireguard(
let interface_config = InterfaceConfiguration {
name: ifname.clone(),
prvkey: BASE64_STANDARD.encode(wireguard_data.inner.keypair().private_key().to_bytes()),
addresses: vec![IpAddrMask::host(IpAddr::from(
wireguard_data.inner.config().private_ipv4,
))],
port: wireguard_data.inner.config().announced_tunnel_port,
address: wireguard_data.inner.config().private_ipv4.to_string(),
port: wireguard_data.inner.config().announced_tunnel_port as u32,
peers: peers.clone(), // Clone since we need to use peers later to mark IPs as used
mtu: None,
};
info!(
"attempting to configure wireguard interface '{ifname}': addresses=[{}], port={}",
interface_config
.addresses
.iter()
.map(|s| s.to_string())
.collect::<Vec<_>>()
.join(", "),
interface_config.port
"attempting to configure wireguard interface '{ifname}': address={}, port={}",
interface_config.address, interface_config.port
);
info!("Configuring WireGuard interface...");
@@ -348,6 +263,7 @@ pub async fn start_wireguard(
wg_api.configure_peer_routing(&[catch_all_peer])?;
let host = wg_api.read_interface_data()?;
let wg_api = std::sync::Arc::new(WgApiWrapper::new(wg_api));
// Initialize IP pool from configuration
info!("Initializing IP pool for WireGuard peer allocation");
@@ -362,13 +278,13 @@ pub async fn start_wireguard(
for peer in &peers {
for allowed_ip in &peer.allowed_ips {
// Extract IPv4 and IPv6 from peer's allowed_ips
if let IpAddr::V4(ipv4) = allowed_ip.address {
if let IpAddr::V4(ipv4) = allowed_ip.ip {
// Find corresponding IPv6
if let Some(ipv6_mask) = peer
.allowed_ips
.iter()
.find(|ip| matches!(ip.address, IpAddr::V6(_)))
&& let IpAddr::V6(ipv6) = ipv6_mask.address
.find(|ip| matches!(ip.ip, IpAddr::V6(_)))
&& let IpAddr::V6(ipv6) = ipv6_mask.ip
{
ip_pool.mark_used(IpPair::new(ipv4, ipv6)).await;
}
@@ -376,7 +292,6 @@ pub async fn start_wireguard(
}
}
let wg_api = std::sync::Arc::new(wg_api);
let mut controller = PeerController::new(
ecash_manager,
metrics,
+8 -7
View File
@@ -321,7 +321,7 @@ impl PeerController {
bw_manager
.allowed_ips()
.iter()
.find(|ip_mask| ip_mask.address == ip)
.find(|ip_mask| ip_mask.ip == ip)
.and(Some(key.clone()))
}))
}
@@ -551,7 +551,7 @@ struct MockWgApi {
#[allow(clippy::todo)]
impl WireguardInterfaceApi for MockWgApi {
fn create_interface(
&mut self,
&self,
) -> std::result::Result<(), defguard_wireguard_rs::error::WireguardInterfaceError> {
todo!()
}
@@ -570,6 +570,7 @@ impl WireguardInterfaceApi for MockWgApi {
todo!()
}
#[cfg(not(target_os = "windows"))]
fn configure_interface(
&self,
_config: &defguard_wireguard_rs::InterfaceConfiguration,
@@ -577,16 +578,17 @@ impl WireguardInterfaceApi for MockWgApi {
todo!()
}
#[cfg(not(windows))]
fn remove_interface(
#[cfg(target_os = "windows")]
fn configure_interface(
&self,
_config: &defguard_wireguard_rs::InterfaceConfiguration,
_dns: &[std::net::IpAddr],
) -> std::result::Result<(), defguard_wireguard_rs::error::WireguardInterfaceError> {
todo!()
}
#[cfg(windows)]
fn remove_interface(
&mut self,
&self,
) -> std::result::Result<(), defguard_wireguard_rs::error::WireguardInterfaceError> {
todo!()
}
@@ -621,7 +623,6 @@ impl WireguardInterfaceApi for MockWgApi {
fn configure_dns(
&self,
_dns: &[std::net::IpAddr],
_search_domains: &[&str],
) -> std::result::Result<(), defguard_wireguard_rs::error::WireguardInterfaceError> {
todo!()
}
@@ -278,6 +278,31 @@ impl<R, S> FreshHandler<R, S> {
where
S: AsyncRead + AsyncWrite + Unpin,
{
// Log message sizes before sending to client
for (idx, packet) in packets.iter().enumerate() {
use nym_sphinx::addressing::nodes::MAX_NODE_ADDRESS_UNPADDED_LEN;
use nym_sphinx::params::PacketSize;
let sphinx_ack_overhead = PacketSize::AckPacket.size() + MAX_NODE_ADDRESS_UNPADDED_LEN;
let expected_size = PacketSize::RegularPacket.plaintext_size() - sphinx_ack_overhead;
debug!(
"push_packets_to_client: packet[{}] size={}, expected_regular_size={}, ack_overhead={}",
idx,
packet.len(),
expected_size,
sphinx_ack_overhead
);
if packet.len() == PacketSize::RegularPacket.plaintext_size() {
warn!(
"push_packets_to_client: WARNING - packet[{}] has full plaintext size ({}), expected {} (after ACK removal)",
idx,
packet.len(),
expected_size
);
}
}
// note: into_ws_message encrypts the requests and adds a MAC on it. Perhaps it should
// be more explicit in the naming?
let messages: Vec<Result<Message, WsError>> = packets
@@ -496,8 +521,9 @@ impl<R, S> FreshHandler<R, S> {
error!("{incompatible_err}");
Err(incompatible_err)
} else {
// Return the client's version for backwards compatibility.. temporary solution.
debug!("the client is using exactly the same (or older) protocol version as we are. We're good to continue!");
Ok(CURRENT_PROTOCOL_VERSION)
Ok(client_protocol_version)
}
}
@@ -717,9 +743,9 @@ impl<R, S> FreshHandler<R, S> {
}
pub(crate) fn handle_supported_protocol_request(&self) -> ServerResponse {
debug!("returning gateway protocol version");
use nym_gateway_requests::EMBEDDED_KEY_ROTATION_INFO_VERSION;
ServerResponse::SupportedProtocol {
version: CURRENT_PROTOCOL_VERSION,
version: EMBEDDED_KEY_ROTATION_INFO_VERSION,
}
}
@@ -297,7 +297,7 @@ impl MixnetListener {
let allowed_ipv4 = peer
.allowed_ips
.iter()
.find_map(|ip_mask| match ip_mask.address {
.find_map(|ip_mask| match ip_mask.ip {
IpAddr::V4(ipv4_addr) => Some(ipv4_addr),
_ => None,
})
@@ -307,7 +307,7 @@ impl MixnetListener {
let allowed_ipv6 = peer
.allowed_ips
.iter()
.find_map(|ip_mask| match ip_mask.address {
.find_map(|ip_mask| match ip_mask.ip {
IpAddr::V6(ipv6_addr) => Some(ipv6_addr),
_ => None,
})
@@ -846,11 +846,23 @@ impl MixnetListener {
| AuthenticatorVersion::V1
| AuthenticatorVersion::V2
| AuthenticatorVersion::V3
| AuthenticatorVersion::V4
| AuthenticatorVersion::V5 => {
// pre v6 this message hasn't existed
| AuthenticatorVersion::V4 => {
// pre v5 this message hasn't existed
return Err(AuthenticatorError::UnknownVersion);
}
AuthenticatorVersion::V5 => {
// V5 clients shouldn't send this message, but for backward compatibility
// with V5 clients connecting to V6 gateways, we allow it and return
// a V6-style response (V5 clients won't understand it, but at least we don't error)
// This enables V5 clients from mainnet (release/2026.1-kiwi) to work with
// V6 gateways (release/2026.2-oscypek) during the transition period
v6::response::AuthenticatorResponse::new_upgrade_mode_check(
request_id,
self.upgrade_mode_enabled(),
)
.to_bytes()
.map_err(AuthenticatorError::response_serialisation)?
}
AuthenticatorVersion::V6 => {
v6::response::AuthenticatorResponse::new_upgrade_mode_check(
request_id,
+109 -64
View File
@@ -5,10 +5,10 @@ use super::messages::LpRegistrationRequest;
use super::registration::process_registration;
use super::LpHandlerState;
use crate::error::GatewayError;
use nym_crypto::asymmetric::{ed25519, x25519};
use nym_lp::serialisation::{lp_bincode_serializer, BincodeOptions};
use nym_lp::{
codec::OuterAeadKey, message::ForwardPacketData, packet::LpHeader, LpMessage, LpPacket,
OuterHeader,
codec::OuterAeadKey, keypair::PublicKey, message::ForwardPacketData, packet::LpHeader,
LpMessage, LpPacket, OuterHeader,
};
use nym_lp_transport::traits::LpTransport;
use nym_metrics::{add_histogram_obs, inc};
@@ -290,11 +290,31 @@ where
async fn handle_client_hello(&mut self, packet: LpPacket) -> Result<(), GatewayError> {
use nym_lp::packet::LpHeader;
use nym_lp::state_machine::{LpInput, LpStateMachine};
let remote = self.remote_addr;
// Extract ClientHello data
let hello_data = match packet.message() {
LpMessage::ClientHello(hello_data) => hello_data,
let (receiver_index, client_ed25519_pubkey, salt) = match packet.message() {
LpMessage::ClientHello(hello_data) => {
// Validate timestamp
let timestamp = hello_data.extract_timestamp();
Self::validate_timestamp(
timestamp,
self.state.lp_config.debug.timestamp_tolerance,
)?;
// Extract client-proposed receiver_index
let receiver_index = hello_data.receiver_index;
let client_ed25519_pubkey = nym_crypto::asymmetric::ed25519::PublicKey::from_bytes(
&hello_data.client_ed25519_public_key,
)
.map_err(|e| {
GatewayError::LpProtocolError(
format!("Invalid client Ed25519 public key: {e}",),
)
})?;
(receiver_index, client_ed25519_pubkey, hello_data.salt)
}
other => {
inc!("lp_client_hello_failed");
return Err(GatewayError::LpProtocolError(format!(
@@ -303,23 +323,20 @@ where
}
};
// Validate timestamp
let timestamp = hello_data.extract_timestamp();
Self::validate_timestamp(timestamp, self.state.lp_config.debug.timestamp_tolerance)?;
// Extract client-proposed receiver_index
let receiver_index = hello_data.receiver_index;
// let client_ed25519_pubkey = hello_data.client_ed25519_public_key;
debug!("Processing ClientHello from {remote} (proposed receiver_index={receiver_index})",);
debug!(
"Processing ClientHello from {} (proposed receiver_index={})",
self.remote_addr, receiver_index
);
// Collision check for client-proposed receiver_index
// Check both handshake_states (in-progress) and session_states (established)
if self.state.handshake_states.contains_key(&receiver_index)
|| self.state.session_states.contains_key(&receiver_index)
{
warn!("Receiver index collision: {receiver_index} from {remote}",);
warn!(
"Receiver index collision: {} from {}",
receiver_index, self.remote_addr
);
inc!("lp_receiver_index_collision");
// Send Collision response to tell client to retry with new receiver_index
@@ -327,7 +344,7 @@ where
// Note: Do NOT set binding on collision - client may retry with new receiver_index
let collision_packet =
LpPacket::new(LpHeader::new(receiver_index, 0), LpMessage::Collision);
self.send_lp_packet(collision_packet, None).await?;
self.send_lp_packet(&collision_packet, None).await?;
return Ok(());
}
@@ -335,23 +352,32 @@ where
// Collision check passed - bind this connection to the receiver_index
// All subsequent packets on this connection must use this receiver_index
self.bound_receiver_idx = Some(receiver_index);
trace!("Bound connection from {remote} to receiver_idx={receiver_index} (via ClientHello)",);
trace!(
"Bound connection from {} to receiver_idx={} (via ClientHello)",
self.remote_addr,
receiver_index
);
// Create state machine for this handshake using client-proposed receiver_index
let mut state_machine = LpStateMachine::new(
receiver_index,
false, // responder
self.state.local_identity.clone(),
&hello_data.client_ed25519_public_key,
&hello_data.client_lp_public_key,
&hello_data.salt,
(
self.state.local_identity.private_key(),
self.state.local_identity.public_key(),
),
&client_ed25519_pubkey,
&salt,
)
.map_err(|e| {
inc!("lp_client_hello_failed");
GatewayError::LpHandshakeError(format!("Failed to create state machine: {}", e))
})?;
debug!("Created handshake state for {remote} (receiver_index={receiver_index})",);
debug!(
"Created handshake state for {} (receiver_index={})",
self.remote_addr, receiver_index
);
// Transition state machine to KKTExchange (responder waits for client's KKT request)
// For responder, StartHandshake returns None (just transitions state)
@@ -359,7 +385,8 @@ where
if let Some(Err(e)) = state_machine.process_input(LpInput::StartHandshake) {
inc!("lp_client_hello_failed");
return Err(GatewayError::LpHandshakeError(format!(
"StartHandshake failed: {e}",
"StartHandshake failed: {}",
e
)));
// Responder (gateway) gets Ok but no packet to send - we just wait for client's next packet
}
@@ -370,13 +397,14 @@ where
.insert(receiver_index, super::TimestampedState::new(state_machine));
debug!(
"Stored handshake state for {remote} (receiver_index={receiver_index}) - waiting for KKT request",
"Stored handshake state for {} (receiver_index={}) - waiting for KKT request",
self.remote_addr, receiver_index
);
// Send Ack to confirm ClientHello received
// No outer key - this is before PSK derivation
let ack_packet = LpPacket::new(LpHeader::new(receiver_index, 0), LpMessage::Ack);
self.send_lp_packet(ack_packet, None).await?;
self.send_lp_packet(&ack_packet, None).await?;
Ok(())
}
@@ -480,7 +508,7 @@ where
// Send response packet if needed
if let Some((packet, outer_key)) = should_send {
self.send_lp_packet(packet, outer_key.as_ref()).await?;
self.send_lp_packet(&packet, outer_key.as_ref()).await?;
trace!(
"Sent handshake response to {} (encrypted={})",
self.remote_addr,
@@ -558,7 +586,7 @@ where
self.remote_addr, receiver_idx
);
inc!("lp_subsession_kk2_sent");
self.send_lp_packet(response_packet, outer_key.as_ref())
self.send_lp_packet(&response_packet, outer_key.as_ref())
.await?;
Ok(())
}
@@ -604,7 +632,9 @@ where
let remote = self.remote_addr;
// Try to deserialize as LpRegistrationRequest first (most common case after handshake)
if let Ok(request) = LpRegistrationRequest::try_deserialise(&decrypted_bytes) {
if let Ok(request) =
lp_bincode_serializer().deserialize::<LpRegistrationRequest>(&decrypted_bytes)
{
debug!(
"LP registration request from {remote} (receiver_idx={receiver_idx}): mode={:?}",
request.mode
@@ -615,7 +645,9 @@ where
}
// Try to deserialize as ForwardPacketData (entry gateway forwarding to exit)
if let Ok(forward_data) = ForwardPacketData::decode(&decrypted_bytes) {
if let Ok(forward_data) =
lp_bincode_serializer().deserialize::<ForwardPacketData>(&decrypted_bytes)
{
debug!(
"LP forward request from {remote} (receiver_idx={receiver_idx}) to {}",
forward_data.target_lp_address
@@ -657,7 +689,7 @@ where
// Send SubsessionReady packet if present (for initiator - gateway is responder, so typically None)
if let Some(packet) = ready_packet {
self.send_lp_packet(packet, outer_key.as_ref()).await?;
self.send_lp_packet(&packet, outer_key.as_ref()).await?;
}
// Create new state machine from completed subsession
@@ -727,7 +759,7 @@ where
.map_err(|e| GatewayError::LpProtocolError(format!("Session error: {}", e)))?;
// Serialize and encrypt response
let response_bytes = response.serialise().map_err(|e| {
let response_bytes = lp_bincode_serializer().serialize(&response).map_err(|e| {
GatewayError::LpProtocolError(format!("Failed to serialize response: {}", e))
})?;
@@ -745,7 +777,7 @@ where
};
// Send response (encrypted with outer AEAD)
self.send_lp_packet(response_packet, outer_key.as_ref())
self.send_lp_packet(&response_packet, outer_key.as_ref())
.await?;
if response.success {
@@ -802,7 +834,7 @@ where
};
// Send encrypted response to client (encrypted with outer AEAD)
self.send_lp_packet(response_packet, outer_key.as_ref())
self.send_lp_packet(&response_packet, outer_key.as_ref())
.await?;
debug!(
@@ -870,7 +902,14 @@ where
#[allow(dead_code)]
async fn receive_client_hello(
&mut self,
) -> Result<(x25519::PublicKey, ed25519::PublicKey, [u8; 32]), GatewayError> {
) -> Result<
(
PublicKey,
nym_crypto::asymmetric::ed25519::PublicKey,
[u8; 32],
),
GatewayError,
> {
// Receive first packet which should be ClientHello (no outer encryption)
let (raw_bytes, _header) = self.receive_raw_packet().await?;
let packet = nym_lp::codec::parse_lp_packet(&raw_bytes, None)
@@ -897,11 +936,22 @@ where
self.state.lp_config.debug.timestamp_tolerance.as_secs()
);
// Retrieve X25519 PublicKey (for Noise protocol)
let client_pubkey = hello_data.client_lp_public_key;
// Convert bytes to X25519 PublicKey (for Noise protocol)
let client_pubkey = PublicKey::from_bytes(&hello_data.client_lp_public_key)
.map_err(|e| {
GatewayError::LpProtocolError(format!("Invalid client public key: {}", e))
})?;
// Retrieve Ed25519 PublicKey (for PSQ authentication)
let client_ed25519_pubkey = hello_data.client_ed25519_public_key;
// Convert bytes to Ed25519 PublicKey (for PSQ authentication)
let client_ed25519_pubkey = nym_crypto::asymmetric::ed25519::PublicKey::from_bytes(
&hello_data.client_ed25519_public_key,
)
.map_err(|e| {
GatewayError::LpProtocolError(format!(
"Invalid client Ed25519 public key: {}",
e
))
})?;
// Extract salt for PSK derivation
let salt = hello_data.salt;
@@ -1173,7 +1223,7 @@ where
/// * `outer_key` - Optional outer AEAD key for encryption (None for cleartext, Some for encrypted)
async fn send_lp_packet(
&mut self,
packet: LpPacket,
packet: &LpPacket,
outer_key: Option<&OuterAeadKey>,
) -> Result<(), GatewayError> {
use bytes::BytesMut;
@@ -1181,7 +1231,7 @@ where
// Serialize the packet (encrypted if outer_key provided)
let mut packet_buf = BytesMut::new();
serialize_lp_packet(&packet, &mut packet_buf, outer_key).map_err(|e| {
serialize_lp_packet(packet, &mut packet_buf, outer_key).map_err(|e| {
GatewayError::LpProtocolError(format!("Failed to serialize packet: {}", e))
})?;
@@ -1475,7 +1525,7 @@ mod tests {
let packet = LpPacket::new(
LpHeader {
protocol_version: 1,
reserved: [0u8; 3],
reserved: 0,
receiver_idx: 42,
counter: 0,
},
@@ -1541,13 +1591,13 @@ mod tests {
let packet = LpPacket::new(
LpHeader {
protocol_version: 1,
reserved: [0u8; 3],
reserved: 0,
receiver_idx: 99,
counter: 5,
},
LpMessage::Busy,
);
handler.send_lp_packet(packet, None).await
handler.send_lp_packet(&packet, None).await
});
let mut client_stream = TcpStream::connect(addr).await.unwrap();
@@ -1581,13 +1631,13 @@ mod tests {
let packet = LpPacket::new(
LpHeader {
protocol_version: 1,
reserved: [0u8; 3],
reserved: 0,
receiver_idx: 100,
counter: 10,
},
LpMessage::Handshake(HandshakeData(handshake_data)),
);
handler.send_lp_packet(packet, None).await
handler.send_lp_packet(&packet, None).await
});
let mut client_stream = TcpStream::connect(addr).await.unwrap();
@@ -1622,13 +1672,13 @@ mod tests {
let packet = LpPacket::new(
LpHeader {
protocol_version: 1,
reserved: [0u8; 3],
reserved: 0,
receiver_idx: 200,
counter: 20,
},
LpMessage::EncryptedData(EncryptedDataPayload(encrypted_payload)),
);
handler.send_lp_packet(packet, None).await
handler.send_lp_packet(&packet, None).await
});
let mut client_stream = TcpStream::connect(addr).await.unwrap();
@@ -1660,13 +1710,8 @@ mod tests {
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = listener.local_addr().unwrap();
let mut rng = rand::thread_rng();
let ed25519 = ed25519::KeyPair::new(&mut rng);
let x25519 = ed25519.to_x25519();
let client_key = *x25519.public_key();
let client_ed25519_key = *ed25519.public_key();
let client_key = [7u8; 32];
let client_ed25519_key = [8u8; 32];
let hello_data =
ClientHelloData::new_with_fresh_salt(client_key, client_ed25519_key, timestamp);
let expected_salt = hello_data.salt; // Clone salt before moving hello_data
@@ -1679,13 +1724,13 @@ mod tests {
let packet = LpPacket::new(
LpHeader {
protocol_version: 1,
reserved: [0u8; 3],
reserved: 0,
receiver_idx: 300,
counter: 30,
},
LpMessage::ClientHello(hello_data),
);
handler.send_lp_packet(packet, None).await
handler.send_lp_packet(&packet, None).await
});
let mut client_stream = TcpStream::connect(addr).await.unwrap();
@@ -1737,14 +1782,14 @@ mod tests {
let client_x25519_public = client_ed25519_keypair.public_key().to_x25519().unwrap();
let hello_data = ClientHelloData::new_with_fresh_salt(
client_x25519_public,
*client_ed25519_keypair.public_key(),
client_x25519_public.to_bytes(),
client_ed25519_keypair.public_key().to_bytes(),
timestamp,
);
let packet = LpPacket::new(
LpHeader {
protocol_version: 1,
reserved: [0u8; 3],
reserved: 0,
receiver_idx: 0,
counter: 0,
},
@@ -1798,8 +1843,8 @@ mod tests {
let client_x25519_public = client_ed25519_keypair.public_key().to_x25519().unwrap();
let mut hello_data = ClientHelloData::new_with_fresh_salt(
client_x25519_public,
*client_ed25519_keypair.public_key(),
client_x25519_public.to_bytes(),
client_ed25519_keypair.public_key().to_bytes(),
timestamp,
);
@@ -1814,7 +1859,7 @@ mod tests {
let packet = LpPacket::new(
LpHeader {
protocol_version: 1,
reserved: [0u8; 3],
reserved: 0,
receiver_idx: 0,
counter: 0,
},
+1 -1
View File
@@ -155,7 +155,7 @@ async fn check_existing_registration(
let mut ipv4 = None;
let mut ipv6 = None;
for ip_mask in &defguard_peer.allowed_ips {
match ip_mask.address {
match ip_mask.ip {
std::net::IpAddr::V4(v4) => ipv4 = Some(v4),
std::net::IpAddr::V6(v6) => ipv6 = Some(v6),
}
+1 -1
View File
@@ -447,7 +447,7 @@ impl GatewayTasksBuilder {
.await?;
continue;
};
used_private_network_ips.push(allowed_ip.address);
used_private_network_ips.push(allowed_ip.ip);
all_peers.push(peer);
}
+1 -1
View File
@@ -4,7 +4,7 @@
[package]
name = "nym-api"
license = "GPL-3.0"
version = "1.1.71"
version = "1.1.72"
authors.workspace = true
edition = "2021"
rust-version.workspace = true
+1 -1
View File
@@ -3,7 +3,7 @@
[package]
name = "nym-node"
version = "1.23.0"
version = "1.24.0"
authors.workspace = true
repository.workspace = true
homepage.workspace = true
+23
View File
@@ -151,6 +151,29 @@ impl ConnectionHandler {
let client = final_hop_data.destination;
let message = final_hop_data.message;
let message_size = message.len();
// Log message size for debugging
use nym_sphinx_addressing::nodes::MAX_NODE_ADDRESS_UNPADDED_LEN;
use nym_sphinx_params::PacketSize;
let sphinx_ack_overhead = PacketSize::AckPacket.size() + MAX_NODE_ADDRESS_UNPADDED_LEN;
let expected_size = PacketSize::RegularPacket.plaintext_size() - sphinx_ack_overhead;
debug!(
"handle_final_hop: client={}, message_size={}, expected_regular_size={}, ack_overhead={}, forward_ack_present={}",
client,
message_size,
expected_size,
sphinx_ack_overhead,
final_hop_data.forward_ack.is_some()
);
if message_size == PacketSize::RegularPacket.plaintext_size() {
warn!(
"handle_final_hop: WARNING - message has full plaintext size ({}), expected {} (after ACK removal)",
message_size, expected_size
);
}
// if possible attempt to push message directly to the client
match self.shared.try_push_message_to_client(client, message) {
+20 -15
View File
@@ -12,6 +12,7 @@ use nym_crypto::asymmetric::{ed25519, x25519};
use nym_lp::LpPacket;
use nym_lp::codec::{OuterAeadKey, parse_lp_packet, serialize_lp_packet};
use nym_lp::message::ForwardPacketData;
use nym_lp::serialisation::{BincodeOptions, lp_bincode_serializer};
use nym_lp::state_machine::{LpAction, LpInput, LpStateMachine};
use nym_lp_transport::traits::LpTransport;
use nym_registration_common::{GatewayData, LpRegistrationRequest, LpRegistrationResponse};
@@ -328,10 +329,6 @@ where
LpClientError::Crypto(format!("Failed to derive X25519 public key: {e}"))
})?;
let gateway_x25519_public = self.gateway_ed25519_public_key.to_x25519().map_err(|e| {
LpClientError::Crypto(format!("Failed to derive X25519 public key: {e}"))
})?;
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map_err(|_| LpClientError::Other("System time before UNIX epoch".into()))?
@@ -339,8 +336,8 @@ where
// Step 2: Generate ClientHelloData with fresh salt and both public keys
let client_hello_data = nym_lp::ClientHelloData::new_with_fresh_salt(
client_x25519_public,
*self.local_ed25519_keypair.public_key(),
client_x25519_public.to_bytes(),
self.local_ed25519_keypair.public_key().to_bytes(),
timestamp,
);
let salt = client_hello_data.salt;
@@ -385,9 +382,11 @@ where
let mut state_machine = LpStateMachine::new(
receiver_index,
true, // is_initiator
self.local_ed25519_keypair.clone(),
(
self.local_ed25519_keypair.private_key(),
self.local_ed25519_keypair.public_key(),
),
&self.gateway_ed25519_public_key,
&gateway_x25519_public,
&salt,
)?;
@@ -758,7 +757,7 @@ where
tracing::trace!("Built registration request: {:?}", request);
// 2. Serialize the request
let request_bytes = request.serialise().map_err(|e| {
let request_bytes = lp_bincode_serializer().serialize(&request).map_err(|e| {
LpClientError::SendRegistrationRequest(format!("Failed to serialize request: {e}"))
})?;
@@ -844,11 +843,13 @@ where
};
// 8. Deserialize the response
let response = LpRegistrationResponse::try_deserialise(&response_data).map_err(|e| {
LpClientError::ReceiveRegistrationResponse(format!(
"Failed to deserialize registration response: {e}",
))
})?;
let response: LpRegistrationResponse = lp_bincode_serializer()
.deserialize(&response_data)
.map_err(|e| {
LpClientError::ReceiveRegistrationResponse(format!(
"Failed to deserialize registration response: {e}",
))
})?;
tracing::debug!(
"Received registration response: success={}",
@@ -1036,7 +1037,11 @@ where
};
// 2. Serialize the ForwardPacketData
let forward_data_bytes = forward_data.to_bytes();
let forward_data_bytes = lp_bincode_serializer()
.serialize(&forward_data)
.map_err(|e| {
LpClientError::Transport(format!("Failed to serialize ForwardPacketData: {e}"))
})?;
tracing::trace!(
"Serialized ForwardPacketData ({} bytes)",
@@ -4,7 +4,7 @@
//! Error types for LP (Lewes Protocol) client operations.
use nym_lp::LpError;
use nym_registration_common::BincodeError;
use nym_lp::serialisation::BincodeError;
use std::io;
use thiserror::Error;
@@ -25,6 +25,7 @@ use nym_bandwidth_controller::BandwidthTicketProvider;
use nym_credentials_interface::TicketType;
use nym_crypto::asymmetric::{ed25519, x25519};
use nym_lp::codec::{OuterAeadKey, parse_lp_packet, serialize_lp_packet};
use nym_lp::serialisation::{BincodeOptions, lp_bincode_serializer};
use nym_lp::state_machine::{LpAction, LpInput, LpStateMachine};
use nym_lp::{LpMessage, LpPacket};
use nym_lp_transport::traits::LpTransport;
@@ -129,10 +130,6 @@ impl NestedLpSession {
LpClientError::Crypto(format!("Failed to derive X25519 public key: {}", e))
})?;
let gateway_x25519_public = self.exit_public_key.to_x25519().map_err(|e| {
LpClientError::Crypto(format!("Failed to derive X25519 public key: {e}"))
})?;
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map_err(|_| LpClientError::Other("System time before UNIX epoch".into()))?
@@ -140,8 +137,8 @@ impl NestedLpSession {
// Step 2: Generate ClientHello for exit gateway
let client_hello_data = nym_lp::ClientHelloData::new_with_fresh_salt(
client_x25519_public,
*self.client_keypair.public_key(),
client_x25519_public.to_bytes(),
self.client_keypair.public_key().to_bytes(),
timestamp,
);
let salt = client_hello_data.salt;
@@ -195,9 +192,11 @@ impl NestedLpSession {
let mut state_machine = LpStateMachine::new(
receiver_index,
true, // is_initiator
self.client_keypair.clone(),
(
self.client_keypair.private_key(),
self.client_keypair.public_key(),
),
&self.exit_public_key,
&gateway_x25519_public,
&salt,
)?;
@@ -330,7 +329,7 @@ impl NestedLpSession {
tracing::trace!("Built registration request: {:?}", request);
// Step 4: Serialize the request
let request_bytes = request.serialise().map_err(|e| {
let request_bytes = lp_bincode_serializer().serialize(&request).map_err(|e| {
LpClientError::Transport(format!("Failed to serialize registration request: {}", e))
})?;
@@ -398,12 +397,14 @@ impl NestedLpSession {
};
// Step 10: Deserialize the response
let response = LpRegistrationResponse::try_deserialise(&response_data).map_err(|e| {
LpClientError::Transport(format!(
"Failed to deserialize registration response: {}",
e
))
})?;
let response: LpRegistrationResponse = lp_bincode_serializer()
.deserialize(&response_data)
.map_err(|e| {
LpClientError::Transport(format!(
"Failed to deserialize registration response: {}",
e
))
})?;
tracing::debug!(
"Received registration response from exit: success={}",
@@ -504,7 +505,7 @@ impl NestedLpSession {
tracing::trace!("Built registration request: {:?}", request);
// Step 5: Serialize the request
let request_bytes = request.serialise().map_err(|e| {
let request_bytes = lp_bincode_serializer().serialize(&request).map_err(|e| {
LpClientError::Transport(format!("Failed to serialize registration request: {}", e))
})?;
@@ -572,12 +573,14 @@ impl NestedLpSession {
};
// Step 11: Deserialize the response
let response = LpRegistrationResponse::try_deserialise(&response_data).map_err(|e| {
LpClientError::Transport(format!(
"Failed to deserialize registration response: {}",
e
))
})?;
let response: LpRegistrationResponse = lp_bincode_serializer()
.deserialize(&response_data)
.map_err(|e| {
LpClientError::Transport(format!(
"Failed to deserialize registration response: {}",
e
))
})?;
tracing::debug!(
"Received registration response from exit: success={}",
@@ -4,7 +4,7 @@
[package]
name = "nym-network-requester"
license = "GPL-3.0"
version = "1.1.69"
version = "1.1.70"
authors.workspace = true
edition.workspace = true
rust-version = "1.85"
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-cli"
version = "1.1.68"
version = "1.1.69"
authors.workspace = true
edition = "2021"
license.workspace = true
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nymvisor"
version = "0.1.33"
version = "0.1.34"
authors.workspace = true
repository.workspace = true
homepage.workspace = true