diff --git a/Cargo.lock b/Cargo.lock index c76ac98c4e..3674983671 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6700,7 +6700,6 @@ dependencies = [ "libcrux-sha3", "num_enum", "nym-crypto", - "nym-sphinx", "rand 0.9.2", "thiserror 2.0.17", "rand_chacha 0.9.0", @@ -6722,7 +6721,6 @@ dependencies = [ name = "nym-lp" version = "0.1.0" dependencies = [ - "ansi_term", "bs58", "bytes", "chacha20poly1305", @@ -6735,7 +6733,6 @@ dependencies = [ "nym-crypto", "nym-kkt", "nym-lp-common", - "nym-sphinx", "parking_lot", "rand 0.8.5", "rand 0.9.2", @@ -6746,7 +6743,6 @@ dependencies = [ "thiserror 2.0.17", "tls_codec", "tracing", - "utoipa", "zeroize", ] diff --git a/common/crypto/src/asymmetric/ed25519/mod.rs b/common/crypto/src/asymmetric/ed25519/mod.rs index 313fb7e5d1..9706697b67 100644 --- a/common/crypto/src/asymmetric/ed25519/mod.rs +++ b/common/crypto/src/asymmetric/ed25519/mod.rs @@ -20,6 +20,7 @@ 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")] @@ -110,6 +111,18 @@ 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. @@ -136,6 +149,16 @@ impl From 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; @@ -185,14 +208,25 @@ 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 { - Ok(PublicKey(ed25519_dalek::VerifyingKey::from_bytes( - b.try_into()?, - )?)) + Self::from_byte_array(b.try_into()?) + } + + #[inline] + pub fn from_byte_array(b: &[u8; PUBLIC_KEY_LENGTH]) -> Result { + Ok(PublicKey(ed25519_dalek::VerifyingKey::from_bytes(b)?)) } pub fn to_base58_string(self) -> String { diff --git a/common/crypto/src/asymmetric/x25519/mod.rs b/common/crypto/src/asymmetric/x25519/mod.rs index c01e437ef4..e717df3d92 100644 --- a/common/crypto/src/asymmetric/x25519/mod.rs +++ b/common/crypto/src/asymmetric/x25519/mod.rs @@ -4,6 +4,7 @@ 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}; @@ -56,6 +57,15 @@ 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", &"") + .field("public_key", &self.public_key.to_base58_string()) + .finish() + } +} + impl KeyPair { #[cfg(feature = "rand")] pub fn new(rng: &mut R) -> Self { @@ -93,6 +103,15 @@ impl From 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; @@ -116,6 +135,13 @@ 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) @@ -129,14 +155,17 @@ 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 { if b.len() != PUBLIC_KEY_SIZE { return Err(KeyRecoveryError::InvalidSizePublicKey { @@ -146,7 +175,12 @@ impl PublicKey { } let mut bytes = [0; PUBLIC_KEY_SIZE]; bytes.copy_from_slice(&b[..PUBLIC_KEY_SIZE]); - Ok(Self(x25519_dalek::PublicKey::from(bytes))) + Ok(Self::from_byte_array(&bytes)) + } + + #[inline] + pub fn from_byte_array(b: &[u8; PUBLIC_KEY_SIZE]) -> Self { + Self(x25519_dalek::PublicKey::from(*b)) } pub fn to_base58_string(self) -> String { @@ -174,6 +208,12 @@ 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; @@ -296,6 +336,10 @@ 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() } diff --git a/common/nym-kkt/Cargo.toml b/common/nym-kkt/Cargo.toml index e01fd953ae..1cabf14fb2 100644 --- a/common/nym-kkt/Cargo.toml +++ b/common/nym-kkt/Cargo.toml @@ -13,7 +13,6 @@ num_enum = { workspace = true } # internal nym-crypto = { path = "../crypto", features = ["asymmetric", "serde"] } -nym-sphinx = { path = "../nymsphinx" } libcrux-kem = { git = "https://github.com/cryspen/libcrux" } libcrux-sha3 = { git = "https://github.com/cryspen/libcrux" } diff --git a/common/nym-kkt/src/encryption.rs b/common/nym-kkt/src/encryption.rs index df6c9cd4e6..7bb7610622 100644 --- a/common/nym-kkt/src/encryption.rs +++ b/common/nym-kkt/src/encryption.rs @@ -1,25 +1,29 @@ -use blake3::Hasher; - -use libcrux_chacha20poly1305::{NONCE_LEN, TAG_LEN}; - -use nym_sphinx::{PrivateKey, PublicKey}; - -use rand::{CryptoRng, RngCore}; -use zeroize::Zeroize; +// Copyright 2025-2026 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 use crate::kkt::KKT_INITIAL_FRAME_AAD; use crate::{ ciphersuite::CURVE25519_KEY_LEN, context::KKTContext, error::KKTError, frame::KKTFrame, }; +use blake3::Hasher; +use libcrux_chacha20poly1305::{NONCE_LEN, TAG_LEN}; +use nym_crypto::asymmetric::x25519; +use rand::{CryptoRng, RngCore}; +use zeroize::Zeroize; #[derive(Clone, Copy, Zeroize)] pub struct KKTSessionSecret([u8; 32]); impl KKTSessionSecret { - pub fn new(remote_public_key: &PublicKey) -> (Self, PublicKey) { - // this doesn't use the newer rand crate - let ephemeral_private_key = PrivateKey::random(); - let ephemeral_public_key = PublicKey::from(&ephemeral_private_key); + pub fn new(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); + + 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), @@ -29,21 +33,22 @@ impl KKTSessionSecret { pub fn from_bytes(secret: [u8; 32]) -> Self { Self(secret) } - pub fn try_derive(private_key: &PrivateKey, public_key: &[u8]) -> Result { + + fn try_derive(private_key: &x25519::PrivateKey, public_key: &[u8]) -> Result { 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 = PublicKey::from(pub_key); + let pk = x25519::PublicKey::from(pub_key); Ok(Self::derive(private_key, &pk)) } - pub fn derive(private_key: &PrivateKey, public_key: &PublicKey) -> Self { + 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.as_bytes()); + hasher.update(&shared_secret); shared_secret.zeroize(); Self(hasher.finalize().as_bytes().to_owned()) @@ -55,13 +60,13 @@ impl KKTSessionSecret { pub fn encrypt_initial_kkt_frame( rng: &mut R, - remote_public_key: &PublicKey, + remote_public_key: &x25519::PublicKey, kkt_frame: &KKTFrame, ) -> Result<(KKTSessionSecret, Vec), KKTError> where R: CryptoRng + RngCore, { - let (session_secret_key, ephemeral_public_key) = KKTSessionSecret::new(remote_public_key); + let (session_secret_key, ephemeral_public_key) = KKTSessionSecret::new(rng, remote_public_key); let mut encrypted_frame = encrypt_kkt_frame(rng, &session_secret_key, kkt_frame, KKT_INITIAL_FRAME_AAD)?; @@ -76,7 +81,7 @@ where } pub fn decrypt_initial_kkt_frame( - responder_private_key: &PrivateKey, + 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 { @@ -185,13 +190,14 @@ mod test { #[test] fn test_keygen() { - let responder_x25519_keypair = generate_keypair_x25519(); + let mut rng = rng(); + let responder_x25519_keypair = generate_keypair_x25519(&mut rng); let (session_secret_key, ephemeral_public_key) = - KKTSessionSecret::new(&responder_x25519_keypair.1); + KKTSessionSecret::new(&mut rng, responder_x25519_keypair.public_key()); let shared_secret = KKTSessionSecret::try_derive( - &responder_x25519_keypair.0, + responder_x25519_keypair.private_key(), ephemeral_public_key.as_bytes().as_slice(), ) .unwrap(); diff --git a/common/nym-kkt/src/key_utils.rs b/common/nym-kkt/src/key_utils.rs index 302f7a125f..2845e7a907 100644 --- a/common/nym-kkt/src/key_utils.rs +++ b/common/nym-kkt/src/key_utils.rs @@ -1,42 +1,53 @@ -use crate::{ - ciphersuite::{HashFunction, KEM}, - error::KKTError, -}; +use crate::ciphersuite::HashFunction; use classic_mceliece_rust::keypair_boxed; -use libcrux_kem::{Algorithm, key_gen}; use libcrux_sha3; -use nym_crypto::asymmetric::ed25519; use rand::{CryptoRng, RngCore}; -pub fn generate_keypair_ed25519(rng: &mut R, index: Option) -> ed25519::KeyPair + +pub fn generate_keypair_ed25519( + rng: &mut R, + index: Option, +) -> nym_crypto::asymmetric::ed25519::KeyPair where R: RngCore + CryptoRng, { let mut secret_initiator: [u8; 32] = [0u8; 32]; rng.fill_bytes(&mut secret_initiator); - ed25519::KeyPair::from_secret(secret_initiator, index.unwrap_or(0)) + nym_crypto::asymmetric::ed25519::KeyPair::from_secret(secret_initiator, index.unwrap_or(0)) } -pub fn generate_keypair_x25519() -> (nym_sphinx::PrivateKey, nym_sphinx::PublicKey) { - let private_key = nym_sphinx::PrivateKey::random(); - let public_key = nym_sphinx::PublicKey::from(&private_key); - (private_key, public_key) +pub fn generate_keypair_x25519(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( rng: &mut R, - kem: KEM, -) -> Result<(libcrux_kem::PrivateKey, libcrux_kem::PublicKey), KKTError> + kem: crate::ciphersuite::KEM, +) -> Result<(libcrux_kem::PrivateKey, libcrux_kem::PublicKey), crate::error::KKTError> where R: RngCore + CryptoRng, { match kem { - 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 { + 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 { info: "Key Generation Error: Unsupported Libcrux Algorithm", }), } diff --git a/common/nym-kkt/src/kkt.rs b/common/nym-kkt/src/kkt.rs index f2fba1ace5..c83454a52a 100644 --- a/common/nym-kkt/src/kkt.rs +++ b/common/nym-kkt/src/kkt.rs @@ -8,7 +8,7 @@ //! //! The underlying KKT protocol is implemented in the `session` module. -use nym_crypto::asymmetric::ed25519; +use nym_crypto::asymmetric::{ed25519, x25519}; use rand::{CryptoRng, RngCore}; use crate::{ @@ -60,7 +60,7 @@ pub fn request_kem_key( rng: &mut R, ciphersuite: Ciphersuite, signing_key: &ed25519::PrivateKey, - responder_dh_public_key: &nym_sphinx::PublicKey, + responder_dh_public_key: &x25519::PublicKey, ) -> Result<(KKTSessionSecret, KKTContext, Vec), KKTError> { // OneWay mode: client only wants responder's KEM key // None: client doesn't send their own KEM key @@ -161,7 +161,7 @@ pub fn handle_kem_request<'a, R>( encrypted_request_bytes: &[u8], initiator_vk: Option<&ed25519::PublicKey>, responder_signing_key: &ed25519::PrivateKey, - responder_dh_private_key: &nym_sphinx::PrivateKey, + responder_dh_private_key: &x25519::PrivateKey, responder_kem_key: &EncapsulationKey<'a>, ) -> Result, KKTError> where @@ -200,6 +200,13 @@ 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(); @@ -213,8 +220,8 @@ mod tests { rng.fill_bytes(&mut responder_secret); let ed25519_resp = ed25519::KeyPair::from_secret(responder_secret, 1); - let x25519_resp_priv = nym_sphinx::PrivateKey::random(); - let x25519_resp_pub = nym_sphinx::PublicKey::from(&x25519_resp_priv); + let x25519_resp_priv = random_x25519_key(); + let x25519_resp_pub = x25519::PublicKey::from(&x25519_resp_priv); // Generate responder's KEM keypair (X25519 for testing) let (_, responder_kem_pk) = generate_keypair_libcrux(&mut rng, KEM::X25519).unwrap(); @@ -282,8 +289,8 @@ 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 = nym_sphinx::PrivateKey::random(); - let x25519_resp_pub = nym_sphinx::PublicKey::from(&x25519_resp_priv); + let x25519_resp_priv = random_x25519_key(); + let x25519_resp_pub = x25519::PublicKey::from(&x25519_resp_priv); let ciphersuite = Ciphersuite::resolve_ciphersuite( KEM::X25519, @@ -343,8 +350,8 @@ mod tests { rng.fill_bytes(&mut responder_secret); let responder_keypair = ed25519::KeyPair::from_secret(responder_secret, 1); - let x25519_resp_priv = nym_sphinx::PrivateKey::random(); - let x25519_resp_pub = nym_sphinx::PublicKey::from(&x25519_resp_priv); + 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]; @@ -396,8 +403,8 @@ mod tests { rng.fill_bytes(&mut responder_secret); let responder_keypair = ed25519::KeyPair::from_secret(responder_secret, 1); - let x25519_resp_priv = nym_sphinx::PrivateKey::random(); - let x25519_resp_pub = nym_sphinx::PublicKey::from(&x25519_resp_priv); + 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); diff --git a/common/nym-kkt/src/lib.rs b/common/nym-kkt/src/lib.rs index ee8e336c60..bdfd643cb7 100644 --- a/common/nym-kkt/src/lib.rs +++ b/common/nym-kkt/src/lib.rs @@ -246,7 +246,7 @@ mod test { let responder_ed25519_keypair = generate_keypair_ed25519(&mut rng, Some(1)); // generate responder x25519 keys - let responder_x25519_keypair = generate_keypair_x25519(); + let responder_x25519_keypair = generate_keypair_x25519(&mut rng); for kem in [KEM::MlKem768, KEM::XWing, KEM::X25519, KEM::McEliece] { for hash_function in [ @@ -315,14 +315,18 @@ mod test { // encryption - initiator frame - let (i_session_secret, i_bytes) = - encrypt_initial_kkt_frame(&mut rng, &responder_x25519_keypair.1, &i_frame) - .unwrap(); + 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.0, &i_bytes).unwrap(); + 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(); @@ -369,14 +373,18 @@ mod test { // encryption - initiator frame - let (i_session_secret, i_bytes) = - encrypt_initial_kkt_frame(&mut rng, &responder_x25519_keypair.1, &i_frame) - .unwrap(); + 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.0, &i_bytes).unwrap(); + decrypt_initial_kkt_frame(responder_x25519_keypair.private_key(), &i_bytes) + .unwrap(); let (mut r_context, r_obtained_key) = responder_ingest_message( &r_context, @@ -431,14 +439,18 @@ mod test { // encryption - initiator frame - let (i_session_secret, i_bytes) = - encrypt_initial_kkt_frame(&mut rng, &responder_x25519_keypair.1, &i_frame) - .unwrap(); + 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.0, &i_bytes).unwrap(); + 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, diff --git a/common/nym-lp/Cargo.toml b/common/nym-lp/Cargo.toml index c7ea8fc19a..08bdef92f2 100644 --- a/common/nym-lp/Cargo.toml +++ b/common/nym-lp/Cargo.toml @@ -13,9 +13,7 @@ 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" } @@ -23,7 +21,6 @@ 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 = [ @@ -39,6 +36,7 @@ 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]] diff --git a/common/nym-lp/src/codec.rs b/common/nym-lp/src/codec.rs index 8d6e0ccdd9..2067bb37a5 100644 --- a/common/nym-lp/src/codec.rs +++ b/common/nym-lp/src/codec.rs @@ -321,6 +321,8 @@ 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 @@ -603,11 +605,13 @@ 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 = [42u8; 32]; - let client_ed25519_key = [43u8; 32]; + let client_key = x25519::PublicKey::from_byte_array(&[42u8; 32]); + let client_ed25519_key = *valid_ed25519.public_key(); let salt = [99u8; 32]; let hello_data = ClientHelloData { receiver_index: 12345, @@ -661,8 +665,11 @@ mod tests { .as_secs(); // Create ClientHelloData with fresh salt - let client_key = [7u8; 32]; - let client_ed25519_key = [8u8; 32]; + 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 hello_data = ClientHelloData::new_with_fresh_salt(client_key, client_ed25519_key, timestamp); @@ -749,43 +756,6 @@ 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: [0u8; 3], - 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(); diff --git a/common/nym-lp/src/keypair.rs b/common/nym-lp/src/keypair.rs deleted file mode 100644 index 6f9546ba52..0000000000 --- a/common/nym-lp/src/keypair.rs +++ /dev/null @@ -1,200 +0,0 @@ -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 { - 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 { - 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 { - 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 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 { - 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::from_base58_string(&self.private) - } - - pub fn public_key(&self) -> Result { - PublicKey::from_base58_string(&self.public) - } -} diff --git a/common/nym-lp/src/kkt_orchestrator.rs b/common/nym-lp/src/kkt_orchestrator.rs index efb9e2e9d4..f07168a933 100644 --- a/common/nym-lp/src/kkt_orchestrator.rs +++ b/common/nym-lp/src/kkt_orchestrator.rs @@ -64,7 +64,7 @@ use crate::LpError; use crate::message::{KKTRequestData, KKTResponseData}; -use nym_crypto::asymmetric::ed25519; +use nym_crypto::asymmetric::{ed25519, x25519}; use nym_kkt::ciphersuite::{Ciphersuite, EncapsulationKey}; use nym_kkt::context::KKTContext; use nym_kkt::encryption::KKTSessionSecret; @@ -90,7 +90,7 @@ 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: &nym_sphinx::PublicKey, + responder_dh_public_key: &x25519::PublicKey, ) -> Result<(KKTSessionSecret, KKTContext, KKTRequestData), LpError> { // Note: Uses rand 0.9's thread_rng() to match nym-kkt's rand version let mut rng = rand09::rng(); @@ -163,7 +163,7 @@ pub fn handle_request<'a>( request_data: &KKTRequestData, initiator_vk: Option<&ed25519::PublicKey>, responder_signing_key: &ed25519::PrivateKey, - responder_dh_private_key: &nym_sphinx::PrivateKey, + responder_dh_private_key: &x25519::PrivateKey, responder_kem_key: &EncapsulationKey<'a>, ) -> Result { let mut rng = rand09::rng(); @@ -199,7 +199,7 @@ mod tests { let initiator_ed25519_keypair = generate_keypair_ed25519(&mut rng, Some(0)); let responder_ed25519_keypair = generate_keypair_ed25519(&mut rng, Some(1)); - let (responder_x25519_sk, responder_x25519_pk) = generate_keypair_x25519(); + let responder_x25519 = generate_keypair_x25519(&mut rng); // Generate responder's KEM keypair (X25519 for testing) let (_, responder_kem_pk) = generate_keypair_libcrux(&mut rng, KEM::X25519).unwrap(); @@ -225,7 +225,7 @@ mod tests { let (session_secret, context, request_data) = create_request( ciphersuite, initiator_ed25519_keypair.private_key(), - &responder_x25519_pk, + responder_x25519.public_key(), ) .unwrap(); @@ -234,7 +234,7 @@ mod tests { &request_data, Some(initiator_ed25519_keypair.public_key()), responder_ed25519_keypair.private_key(), - &responder_x25519_sk, + responder_x25519.private_key(), &responder_kem_key, ) .unwrap(); @@ -262,7 +262,7 @@ mod tests { // let responder_ed25519_keypair = generate_keypair_ed25519(&mut rng, Some(1)); - // let (responder_x25519_sk, responder_x25519_pk) = generate_keypair_x25519(); + // let responder_x25519 = generate_keypair_x25519(&mut rng); // let (_, responder_kem_pk) = generate_keypair_libcrux(&mut rng, KEM::X25519).unwrap(); // let responder_kem_key = EncapsulationKey::X25519(responder_kem_pk); @@ -317,7 +317,7 @@ mod tests { let initiator_ed25519_keypair = generate_keypair_ed25519(&mut rng, Some(0)); let responder_ed25519_keypair = generate_keypair_ed25519(&mut rng, Some(1)); - let (responder_x25519_sk, responder_x25519_pk) = generate_keypair_x25519(); + let responder_x25519 = generate_keypair_x25519(&mut rng); // Different keypair for wrong signature let mut wrong_secret = [0u8; 32]; @@ -338,7 +338,7 @@ mod tests { let (_session_secret, _context, request_data) = create_request( ciphersuite, initiator_ed25519_keypair.private_key(), - &responder_x25519_pk, + responder_x25519.public_key(), ) .unwrap(); @@ -347,7 +347,7 @@ mod tests { &request_data, Some(wrong_keypair.public_key()), // Wrong key! responder_ed25519_keypair.private_key(), - &responder_x25519_sk, + responder_x25519.private_key(), &responder_kem_key, ); @@ -368,7 +368,7 @@ mod tests { let initiator_ed25519_keypair = generate_keypair_ed25519(&mut rng, Some(0)); let responder_ed25519_keypair = generate_keypair_ed25519(&mut rng, Some(1)); - let (responder_x25519_sk, responder_x25519_pk) = generate_keypair_x25519(); + let responder_x25519 = generate_keypair_x25519(&mut rng); let (_, responder_kem_pk) = generate_keypair_libcrux(&mut rng, KEM::X25519).unwrap(); let responder_kem_key = EncapsulationKey::X25519(responder_kem_pk); @@ -387,7 +387,7 @@ mod tests { let (session_secret, context, request_data) = create_request( ciphersuite, initiator_ed25519_keypair.private_key(), - &responder_x25519_pk, + responder_x25519.public_key(), ) .unwrap(); @@ -395,7 +395,7 @@ mod tests { &request_data, Some(initiator_ed25519_keypair.public_key()), responder_ed25519_keypair.private_key(), - &responder_x25519_sk, + responder_x25519.private_key(), &responder_kem_key, ) .unwrap(); @@ -426,7 +426,7 @@ mod tests { rng.fill_bytes(&mut responder_secret); let responder_ed25519_keypair = generate_keypair_ed25519(&mut rng, Some(1)); - let (responder_x25519_sk, _responder_x25519_pk) = generate_keypair_x25519(); + let responder_x25519 = generate_keypair_x25519(&mut rng); let (_, responder_kem_pk) = generate_keypair_libcrux(&mut rng, KEM::X25519).unwrap(); let responder_kem_key = EncapsulationKey::X25519(responder_kem_pk); @@ -438,7 +438,7 @@ mod tests { &malformed_request, None, responder_ed25519_keypair.private_key(), - &responder_x25519_sk, + responder_x25519.private_key(), &responder_kem_key, ); @@ -459,7 +459,7 @@ mod tests { let initiator_ed25519_keypair = generate_keypair_ed25519(&mut rng, Some(0)); let responder_ed25519_keypair = generate_keypair_ed25519(&mut rng, Some(1)); - let (_responder_x25519_sk, responder_x25519_pk) = generate_keypair_x25519(); + let responder_x25519 = generate_keypair_x25519(&mut rng); let ciphersuite = Ciphersuite::resolve_ciphersuite( KEM::X25519, @@ -472,7 +472,7 @@ mod tests { let (session_secret, context, _request_data) = create_request( ciphersuite, initiator_ed25519_keypair.private_key(), - &responder_x25519_pk, + responder_x25519.public_key(), ) .unwrap(); diff --git a/common/nym-lp/src/lib.rs b/common/nym-lp/src/lib.rs index f85570ae78..7e8eae8bd1 100644 --- a/common/nym-lp/src/lib.rs +++ b/common/nym-lp/src/lib.rs @@ -4,7 +4,6 @@ pub mod codec; pub mod config; pub mod error; -pub mod keypair; pub mod kkt_orchestrator; pub mod message; pub mod noise_protocol; @@ -30,12 +29,14 @@ pub const NOISE_PSK_INDEX: u8 = 3; #[cfg(test)] pub fn sessions_for_tests() -> (LpSession, LpSession) { - use crate::keypair::Keypair; - use nym_crypto::asymmetric::ed25519; + use nym_crypto::asymmetric::{ed25519, x25519}; + use std::sync::Arc; + + let mut rng = rand::thread_rng(); // X25519 keypairs for Noise protocol - let keypair_1 = Keypair::default(); - let keypair_2 = Keypair::default(); + let keypair_1 = Arc::new(x25519::KeyPair::new(&mut rng)); + let keypair_2 = Arc::new(x25519::KeyPair::new(&mut rng)); // Use a fixed receiver_index for deterministic tests let receiver_index: u32 = 12345; @@ -44,6 +45,8 @@ 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]; @@ -52,11 +55,8 @@ pub fn sessions_for_tests() -> (LpSession, LpSession) { let initiator_session = LpSession::new( receiver_index, true, - ( - ed25519_keypair_1.private_key(), - ed25519_keypair_1.public_key(), - ), - keypair_1.private_key(), + Arc::new(ed25519_keypair_1), + keypair_1.clone(), ed25519_keypair_2.public_key(), keypair_2.public_key(), &salt, @@ -66,12 +66,9 @@ pub fn sessions_for_tests() -> (LpSession, LpSession) { let responder_session = LpSession::new( receiver_index, false, - ( - ed25519_keypair_2.private_key(), - ed25519_keypair_2.public_key(), - ), - keypair_2.private_key(), - ed25519_keypair_1.public_key(), + Arc::new(ed25519_keypair_2), + keypair_2.clone(), + &ed25519_keypair1_pubkey, keypair_1.public_key(), &salt, ) @@ -87,6 +84,7 @@ 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}; @@ -202,6 +200,10 @@ 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; @@ -212,11 +214,9 @@ mod tests { let _ = local_manager .create_session_state_machine( receiver_index, - ( - ed25519_keypair_local.private_key(), - ed25519_keypair_local.public_key(), - ), + Arc::new(ed25519_keypair_local), ed25519_keypair_remote.public_key(), + &x25519_keypair_remote_pubkey, true, &salt, ) @@ -225,11 +225,9 @@ mod tests { let _ = remote_manager .create_session_state_machine( receiver_index, - ( - ed25519_keypair_remote.private_key(), - ed25519_keypair_remote.public_key(), - ), - ed25519_keypair_local.public_key(), + Arc::new(ed25519_keypair_remote), + &ed25519_keypair_local_pubkey, + &x25519_keypair_local_pubkey, false, &salt, ) diff --git a/common/nym-lp/src/message.rs b/common/nym-lp/src/message.rs index 60e46d4580..578395a957 100644 --- a/common/nym-lp/src/message.rs +++ b/common/nym-lp/src/message.rs @@ -4,6 +4,7 @@ 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}; @@ -14,9 +15,9 @@ 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: [u8; 32], + pub client_lp_public_key: x25519::PublicKey, /// Client's Ed25519 public key (32 bytes) - for PSQ authentication - pub client_ed25519_public_key: [u8; 32], + pub client_ed25519_public_key: ed25519::PublicKey, /// Salt for PSK derivation (32 bytes: 8-byte timestamp + 24-byte nonce) pub salt: [u8; 32], } @@ -46,8 +47,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: [u8; 32], - client_ed25519_public_key: [u8; 32], + client_lp_public_key: x25519::PublicKey, + client_ed25519_public_key: ed25519::PublicKey, timestamp: u64, ) -> Self { // Generate salt: timestamp + nonce @@ -80,8 +81,8 @@ impl ClientHelloData { pub fn encode(&self, dst: &mut BytesMut) { dst.put_u32_le(self.receiver_index); - dst.put_slice(&self.client_lp_public_key); - dst.put_slice(&self.client_ed25519_public_key); + 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); } @@ -96,10 +97,15 @@ 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: b[4..36].try_into().unwrap(), - client_ed25519_public_key: b[36..68].try_into().unwrap(), + 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, + )?, salt: b[68..].try_into().unwrap(), }) } @@ -635,8 +641,13 @@ mod tests { .duration_since(UNIX_EPOCH) .expect("System time before UNIX epoch") .as_secs(); - let client_key = [1u8; 32]; - let client_ed25519_key = [2u8; 32]; + + 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 hello1 = ClientHelloData::new_with_fresh_salt(client_key, client_ed25519_key, timestamp); let hello2 = @@ -657,8 +668,12 @@ mod tests { .duration_since(UNIX_EPOCH) .expect("System time before UNIX epoch") .as_secs(); - let client_key = [2u8; 32]; - let client_ed25519_key = [3u8; 32]; + 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 hello = ClientHelloData::new_with_fresh_salt(client_key, client_ed25519_key, timestamp); let timestamp = hello.extract_timestamp(); @@ -677,8 +692,12 @@ mod tests { .duration_since(UNIX_EPOCH) .expect("System time before UNIX epoch") .as_secs(); - let client_key = [3u8; 32]; - let client_ed25519_key = [4u8; 32]; + 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 hello = ClientHelloData::new_with_fresh_salt(client_key, client_ed25519_key, timestamp); // First 8 bytes should be non-zero timestamp diff --git a/common/nym-lp/src/psk.rs b/common/nym-lp/src/psk.rs index ea48db9415..45c352f490 100644 --- a/common/nym-lp/src/psk.rs +++ b/common/nym-lp/src/psk.rs @@ -47,12 +47,11 @@ //! - **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; +use nym_crypto::asymmetric::{ed25519, x25519}; use nym_kkt::ciphersuite::{DecapsulationKey, EncapsulationKey}; use std::time::Duration; use tls_codec::{Deserialize as TlsDeserializeTrait, Serialize as TlsSerializeTrait}; @@ -137,8 +136,8 @@ pub struct PsqResponderResult { /// // Send ciphertext to gateway /// ``` pub fn derive_psk_with_psq_initiator( - local_x25519_private: &PrivateKey, - remote_x25519_public: &PublicKey, + local_x25519_private: &x25519::PrivateKey, + remote_x25519_public: &x25519::PublicKey, remote_kem_public: &EncapsulationKey, salt: &[u8; 32], ) -> Result<([u8; 32], Vec), LpError> { @@ -168,7 +167,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.as_bytes()); + combined.extend_from_slice(&ecdh_secret); combined.extend_from_slice(&psq_psk); // psq_psk is [u8; 32], need & combined.extend_from_slice(salt); @@ -220,8 +219,8 @@ pub fn derive_psk_with_psq_initiator( /// )?; /// ``` pub fn derive_psk_with_psq_responder( - local_x25519_private: &PrivateKey, - remote_x25519_public: &PublicKey, + local_x25519_private: &x25519::PrivateKey, + remote_x25519_public: &x25519::PublicKey, local_kem_keypair: (&DecapsulationKey, &EncapsulationKey), ciphertext: &[u8], salt: &[u8; 32], @@ -249,7 +248,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.as_bytes()); + combined.extend_from_slice(&ecdh_secret); combined.extend_from_slice(&psq_psk); // psq_psk is [u8; 32], need & combined.extend_from_slice(salt); @@ -280,8 +279,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: &PrivateKey, - remote_x25519_public: &PublicKey, + local_x25519_private: &x25519::PrivateKey, + remote_x25519_public: &x25519::PublicKey, remote_kem_public: &EncapsulationKey, client_ed25519_sk: &ed25519::PrivateKey, client_ed25519_pk: &ed25519::PublicKey, @@ -335,7 +334,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.as_bytes()); + combined.extend_from_slice(&ecdh_secret); combined.extend_from_slice(psq_psk); // psq_psk is already a &[u8; 32] combined.extend_from_slice(salt); @@ -375,8 +374,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: &PrivateKey, - remote_x25519_public: &PublicKey, + local_x25519_private: &x25519::PrivateKey, + remote_x25519_public: &x25519::PublicKey, local_kem_keypair: (&DecapsulationKey, &EncapsulationKey), initiator_ed25519_pk: &ed25519::PublicKey, psq_payload: &[u8], @@ -444,7 +443,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.as_bytes()); + combined.extend_from_slice(&ecdh_secret); combined.extend_from_slice(&psq_psk); // psq_psk is [u8; 32], need & combined.extend_from_slice(salt); @@ -493,12 +492,16 @@ pub fn derive_subsession_psk(pq_shared_secret: &[u8; 32], subsession_index: u64) #[cfg(test)] mod tests { use super::*; - use crate::keypair::Keypair; + use rand::thread_rng; + + fn generate_x25519_keypair() -> x25519::KeyPair { + x25519::KeyPair::new(&mut thread_rng()) + } #[test] fn test_psk_derivation_is_symmetric() { - let keypair_1 = Keypair::default(); - let keypair_2 = Keypair::default(); + let keypair_1 = generate_x25519_keypair(); + let keypair_2 = generate_x25519_keypair(); let salt = [2u8; 32]; let mut rng = &mut rand09::rng(); @@ -533,8 +536,8 @@ mod tests { #[test] fn test_different_salts_produce_different_psks() { - let keypair_1 = Keypair::default(); - let keypair_2 = Keypair::default(); + let keypair_1 = generate_x25519_keypair(); + let keypair_2 = generate_x25519_keypair(); let salt1 = [1u8; 32]; let salt2 = [2u8; 32]; @@ -562,9 +565,9 @@ mod tests { #[test] fn test_different_keys_produce_different_psks() { - let keypair_1 = Keypair::default(); - let keypair_2 = Keypair::default(); - let keypair_3 = Keypair::default(); + let keypair_1 = generate_x25519_keypair(); + let keypair_2 = generate_x25519_keypair(); + let keypair_3 = generate_x25519_keypair(); let salt = [3u8; 32]; let mut rng = &mut rand09::rng(); @@ -601,8 +604,8 @@ mod tests { let mut rng = rand09::rng(); // Generate X25519 keypairs for Noise - let client_keypair = Keypair::default(); - let gateway_keypair = Keypair::default(); + let client_keypair = generate_x25519_keypair(); + let gateway_keypair = generate_x25519_keypair(); // Generate KEM keypair for PSQ let (kem_sk, kem_pk) = generate_keypair_libcrux(&mut rng, KEM::X25519).unwrap(); @@ -659,8 +662,8 @@ mod tests { let mut rng = rand09::rng(); // Generate X25519 keypairs for Noise - let client_keypair = Keypair::default(); - let gateway_keypair = Keypair::default(); + let client_keypair = generate_x25519_keypair(); + let gateway_keypair = generate_x25519_keypair(); // Generate KEM keypair for PSQ let (kem_sk, kem_pk) = generate_keypair_libcrux(&mut rng, KEM::X25519).unwrap(); @@ -698,8 +701,8 @@ mod tests { fn test_different_kem_keys_different_psk() { let mut rng = rand09::rng(); - let client_keypair = Keypair::default(); - let gateway_keypair = Keypair::default(); + let client_keypair = generate_x25519_keypair(); + let gateway_keypair = generate_x25519_keypair(); // Two different KEM keypairs let (_, kem_pk1) = generate_keypair_libcrux(&mut rng, KEM::X25519).unwrap(); @@ -736,8 +739,8 @@ mod tests { fn test_psq_psk_output_length() { let mut rng = rand09::rng(); - let client_keypair = Keypair::default(); - let gateway_keypair = Keypair::default(); + let client_keypair = generate_x25519_keypair(); + let gateway_keypair = generate_x25519_keypair(); let (_, kem_pk) = generate_keypair_libcrux(&mut rng, KEM::X25519).unwrap(); let enc_key = EncapsulationKey::X25519(kem_pk); @@ -759,8 +762,8 @@ mod tests { fn test_psq_different_salts_different_psks() { let mut rng = rand09::rng(); - let client_keypair = Keypair::default(); - let gateway_keypair = Keypair::default(); + let client_keypair = generate_x25519_keypair(); + let gateway_keypair = generate_x25519_keypair(); let (_, kem_pk) = generate_keypair_libcrux(&mut rng, KEM::X25519).unwrap(); let enc_key = EncapsulationKey::X25519(kem_pk); diff --git a/common/nym-lp/src/session.rs b/common/nym-lp/src/session.rs index 52e62a706a..3a26b6b3e9 100644 --- a/common/nym-lp/src/session.rs +++ b/common/nym-lp/src/session.rs @@ -7,7 +7,6 @@ //! 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; @@ -16,12 +15,13 @@ use crate::psk::{ }; use crate::replay::ReceivingKeyCounterValidator; use crate::{LpError, LpMessage, LpPacket}; -use nym_crypto::asymmetric::ed25519; +use nym_crypto::asymmetric::{ed25519, x25519}; 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}; @@ -182,20 +182,17 @@ pub struct LpSession { psk_injected: AtomicBool, // PSQ-related keys stored for handshake - /// Local Ed25519 private key for PSQ authentication - local_ed25519_private: ed25519::PrivateKey, - - /// Local Ed25519 public key for PSQ authentication - local_ed25519_public: ed25519::PublicKey, + /// Local Ed25519 keys for PSQ authentication + local_ed25519: Arc, /// Remote Ed25519 public key for PSQ authentication remote_ed25519_public: ed25519::PublicKey, - /// Local X25519 private key (Noise static key) - local_x25519_private: PrivateKey, + /// Local x25519 keys (Noise static key) + local_x25519: Arc, /// Remote X25519 public key (Noise static key) - remote_x25519_public: PublicKey, + remote_x25519_public: x25519::PublicKey, /// Salt for PSK derivation salt: [u8; 32], @@ -276,8 +273,7 @@ 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(std::sync::atomic::Ordering::Acquire) + self.negotiated_version.load(Ordering::Acquire) } /// Sets the negotiated protocol version from handshake packet header. @@ -285,23 +281,22 @@ 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, std::sync::atomic::Ordering::Release); + self.negotiated_version.store(version, Ordering::Release); } - /// Returns the local X25519 public key derived from the private key. + /// Returns the local X25519 public 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) -> PublicKey { - self.local_x25519_private.public_key() + pub fn local_x25519_public(&self) -> x25519::PublicKey { + *self.local_x25519.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) -> &PublicKey { + pub fn remote_x25519_public(&self) -> &x25519::PublicKey { &self.remote_x25519_public } @@ -356,17 +351,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_key` - This side's X25519 private key for Noise protocol and DHKEM + /// * `local_x25519_keypair` - This side's X25519 keypair 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: (&ed25519::PrivateKey, &ed25519::PublicKey), - local_x25519_key: &PrivateKey, + local_ed25519_keypair: Arc, + local_x25519_keypair: Arc, remote_ed25519_key: &ed25519::PublicKey, - remote_x25519_key: &PublicKey, + remote_x25519_key: &x25519::PublicKey, salt: &[u8; 32], ) -> Result { // XKpsk3 pattern requires remote static key known upfront (XK) @@ -377,8 +372,8 @@ impl LpSession { let params = pattern_name.parse()?; let builder = Builder::new(params); - let local_key_bytes = local_x25519_key.to_bytes(); - let builder = builder.local_private_key(&local_key_bytes); + let local_key_bytes = local_x25519_keypair.private_key().as_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); @@ -416,19 +411,10 @@ impl LpSession { sending_counter: AtomicU64::new(0), receiving_counter: Mutex::new(ReceivingKeyCounterValidator::default()), psk_injected: AtomicBool::new(false), - // 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(), + local_ed25519: local_ed25519_keypair.clone(), + remote_ed25519_public: *remote_ed25519_key, + local_x25519: local_x25519_keypair, + remote_x25519_public: *remote_x25519_key, salt: *salt, outer_aead_key: Mutex::new(None), pq_shared_secret: Mutex::new(None), @@ -567,7 +553,7 @@ impl LpSession { match request_kem_key( &mut rng, ciphersuite, - &self.local_ed25519_private, + self.local_ed25519.private_key(), &self.remote_x25519_public, ) { Ok((session_secret, context, request_bytes)) => { @@ -704,8 +690,8 @@ impl LpSession { &mut rng, request_bytes, Some(&self.remote_ed25519_public), // Verify initiator signature - &self.local_ed25519_private, // Sign response - &self.local_x25519_private, + self.local_ed25519.private_key(), // Sign response + self.local_x25519.private_key(), responder_kem_pk, ) .map_err(|e| LpError::Internal(format!("KKT request handling failed: {:?}", e)))?; @@ -757,11 +743,11 @@ impl LpSession { let session_context = self.id.to_le_bytes(); let psq_result = match psq_initiator_create_message( - &self.local_x25519_private, + self.local_x25519.private_key(), &self.remote_x25519_public, remote_kem, - &self.local_ed25519_private, - &self.local_ed25519_public, + self.local_ed25519.private_key(), + self.local_ed25519.public_key(), &self.salt, &session_context, ) { @@ -895,7 +881,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.to_bytes(); + let local_private_bytes = &self.local_x25519.private_key().to_bytes(); let libcrux_private_key = libcrux_kem::PrivateKey::decode( libcrux_kem::Algorithm::X25519, local_private_bytes, @@ -908,7 +894,7 @@ impl LpSession { })?; let dec_key = DecapsulationKey::X25519(libcrux_private_key); - let local_public_key = self.local_x25519_private.public_key(); + let local_public_key = self.local_x25519_public(); let local_public_bytes = local_public_key.as_bytes(); let libcrux_public_key = libcrux_kem::PublicKey::decode( libcrux_kem::Algorithm::X25519, @@ -926,7 +912,7 @@ impl LpSession { let session_context = self.id.to_le_bytes(); let psq_result = match psq_responder_process_message( - &self.local_x25519_private, + self.local_x25519.private_key(), &self.remote_x25519_public, (&dec_key, &enc_key), &self.remote_ed25519_public, @@ -1132,7 +1118,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: &PublicKey) { + pub(crate) fn set_kkt_completed_for_test(&self, remote_x25519_pub: &x25519::PublicKey) { // Convert remote X25519 public key to EncapsulationKey for testing let remote_kem_bytes = remote_x25519_pub.as_bytes(); let libcrux_public_key = @@ -1185,7 +1171,7 @@ impl LpSession { let pattern_name = "Noise_KKpsk0_25519_ChaChaPoly_SHA256"; let params = pattern_name.parse()?; - let local_key_bytes = self.local_x25519_private.to_bytes(); + let local_key_bytes = self.local_x25519.private_key().to_bytes(); let remote_key_bytes = self.remote_x25519_public.to_bytes(); let builder = Builder::new(params) @@ -1204,22 +1190,12 @@ impl LpSession { noise_state: Mutex::new(NoiseProtocol::new(handshake_state)), is_initiator, // Copy key material from parent for into_session() conversion - 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(), + local_ed25519: self.local_ed25519.clone(), + remote_ed25519_public: self.remote_ed25519_public, + remote_x25519_public: self.remote_x25519_public, pq_shared_secret: PqSharedSecret::new(pq_secret), subsession_psk, + local_x25519: self.local_x25519.clone(), }) } } @@ -1249,16 +1225,17 @@ pub struct SubsessionHandshake { is_initiator: bool, // Key material inherited from parent session for into_session() conversion - /// Local Ed25519 private key (for PSQ auth if needed) - local_ed25519_private: ed25519::PrivateKey, - /// Local Ed25519 public key - local_ed25519_public: ed25519::PublicKey, + /// Local Ed25519 keys (for PSQ auth if needed) + local_ed25519: Arc, + + /// Local x25519 keys (Noise static key) + local_x25519: Arc, + /// 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: PublicKey, + remote_x25519_public: x25519::PublicKey, /// PQ shared secret inherited from parent (for creating further subsessions) pq_shared_secret: PqSharedSecret, /// Subsession PSK (for deriving outer AEAD key) @@ -1351,10 +1328,9 @@ 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_private: self.local_ed25519_private, - local_ed25519_public: self.local_ed25519_public, + local_ed25519: self.local_ed25519, remote_ed25519_public: self.remote_ed25519_public, - local_x25519_private: self.local_x25519_private, + local_x25519: self.local_x25519, remote_x25519_public: self.remote_x25519_public, salt, outer_aead_key: Mutex::new(Some(outer_key)), @@ -1372,18 +1348,19 @@ 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() -> crate::keypair::Keypair { - crate::keypair::Keypair::default() + fn generate_keypair() -> x25519::KeyPair { + x25519::KeyPair::new(&mut thread_rng()) } // 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: &crate::keypair::Keypair, - remote_pub_key: &crate::keypair::PublicKey, + local_keys: &x25519::KeyPair, + remote_pub_key: &x25519::PublicKey, ) -> LpSession { use nym_crypto::asymmetric::ed25519; @@ -1396,6 +1373,9 @@ 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 @@ -1404,8 +1384,8 @@ mod tests { let session = LpSession::new( receiver_index, is_initiator, - (local_ed25519.private_key(), local_ed25519.public_key()), - local_keys.private_key(), + Arc::new(local_ed25519), + Arc::new(local_x25519), remote_ed25519.public_key(), remote_pub_key, &salt, @@ -1510,8 +1490,8 @@ mod tests { #[test] fn test_prepare_handshake_message_initial_state() { - let initiator_keys = generate_keypair(); - let responder_keys = generate_keypair(); + let initiator_keys = Arc::new(generate_keypair()); + let responder_keys = Arc::new(generate_keypair()); let receiver_index = 12345u32; let initiator_session = create_handshake_test_session( @@ -1542,8 +1522,8 @@ mod tests { #[test] fn test_process_handshake_message_first_step() { - let initiator_keys = generate_keypair(); - let responder_keys = generate_keypair(); + let initiator_keys = Arc::new(generate_keypair()); + let responder_keys = Arc::new(generate_keypair()); let receiver_index = 12345u32; let initiator_session = create_handshake_test_session( @@ -1588,8 +1568,8 @@ mod tests { #[test] fn test_handshake_driver_simulation() { - let initiator_keys = generate_keypair(); - let responder_keys = generate_keypair(); + let initiator_keys = Arc::new(generate_keypair()); + let responder_keys = Arc::new(generate_keypair()); let initiator_session = create_handshake_test_session( 12345u32, @@ -1683,8 +1663,8 @@ mod tests { #[test] fn test_encrypt_decrypt_after_handshake() { // --- Setup Handshake --- - let initiator_keys = generate_keypair(); - let responder_keys = generate_keypair(); + let initiator_keys = Arc::new(generate_keypair()); + let responder_keys = Arc::new(generate_keypair()); let initiator_session = create_handshake_test_session( 12345u32, @@ -1752,8 +1732,8 @@ mod tests { #[test] fn test_encrypt_decrypt_before_handshake() { - let initiator_keys = generate_keypair(); - let responder_keys = generate_keypair(); + let initiator_keys = Arc::new(generate_keypair()); + let responder_keys = Arc::new(generate_keypair()); let initiator_session = create_handshake_test_session( 12345u32, @@ -1828,8 +1808,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 = generate_keypair(); - let responder_keys = generate_keypair(); + let initiator_keys = Arc::new(generate_keypair()); + let responder_keys = Arc::new(generate_keypair()); let initiator_session = create_handshake_test_session( 12345u32, @@ -1905,8 +1885,8 @@ mod tests { fn test_x25519_to_kem_conversion() { use nym_kkt::ciphersuite::EncapsulationKey; - let initiator_keys = generate_keypair(); - let responder_keys = generate_keypair(); + let initiator_keys = Arc::new(generate_keypair()); + let responder_keys = Arc::new(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(); @@ -1929,8 +1909,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 = generate_keypair(); - let responder_keys = generate_keypair(); + let initiator_keys = Arc::new(generate_keypair()); + let responder_keys = Arc::new(generate_keypair()); // Create sessions - they start with dummy PSK [0u8; 32] let initiator_session = create_handshake_test_session( @@ -2003,8 +1983,8 @@ mod tests { /// Test full end-to-end handshake with PSQ integration #[test] fn test_handshake_with_psq_end_to_end() { - let initiator_keys = generate_keypair(); - let responder_keys = generate_keypair(); + let initiator_keys = Arc::new(generate_keypair()); + let responder_keys = Arc::new(generate_keypair()); let initiator_session = create_handshake_test_session( 12345u32, @@ -2089,8 +2069,8 @@ mod tests { /// Test that Ed25519 keys are used in PSQ authentication #[test] fn test_psq_handshake_uses_ed25519_authentication() { - let initiator_keys = generate_keypair(); - let responder_keys = generate_keypair(); + let initiator_keys = Arc::new(generate_keypair()); + let responder_keys = Arc::new(generate_keypair()); // Create sessions with explicit Ed25519 keys let initiator_session = create_handshake_test_session( @@ -2172,8 +2152,8 @@ mod tests { #[test] fn test_handshake_abort_on_psq_failure() { // Test that Ed25519 auth failure causes handshake abort - let initiator_keys = generate_keypair(); - let responder_keys = generate_keypair(); + let initiator_keys = Arc::new(generate_keypair()); + let responder_keys = Arc::new(generate_keypair()); // Create sessions with MISMATCHED Ed25519 keys // This simulates authentication failure @@ -2186,11 +2166,8 @@ mod tests { let initiator_session = LpSession::new( receiver_index, true, - ( - initiator_ed25519.private_key(), - initiator_ed25519.public_key(), - ), - initiator_keys.private_key(), + Arc::new(initiator_ed25519), + initiator_keys.clone(), wrong_ed25519.public_key(), // Responder expects THIS key responder_keys.public_key(), &salt, @@ -2204,11 +2181,8 @@ mod tests { let responder_session = LpSession::new( receiver_index, false, - ( - responder_ed25519.private_key(), - responder_ed25519.public_key(), - ), - responder_keys.private_key(), + Arc::new(responder_ed25519), + responder_keys.clone(), wrong_ed25519.public_key(), // Expects WRONG key (not initiator's) initiator_keys.public_key(), &salt, @@ -2241,8 +2215,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 = generate_keypair(); - let responder_keys = generate_keypair(); + let initiator_keys = Arc::new(generate_keypair()); + let responder_keys = Arc::new(generate_keypair()); // Initiator uses Ed25519 key [1u8] let initiator_ed25519 = ed25519::KeyPair::from_secret([1u8; 32], 0); @@ -2257,11 +2231,8 @@ mod tests { let initiator_session = LpSession::new( receiver_index, true, - ( - initiator_ed25519.private_key(), - initiator_ed25519.public_key(), - ), - initiator_keys.private_key(), + Arc::new(initiator_ed25519), + initiator_keys.clone(), wrong_ed25519_public, // This doesn't matter for initiator responder_keys.public_key(), &salt, @@ -2275,11 +2246,8 @@ mod tests { let responder_session = LpSession::new( receiver_index, false, - ( - responder_ed25519.private_key(), - responder_ed25519.public_key(), - ), - responder_keys.private_key(), + Arc::new(responder_ed25519), + responder_keys.clone(), wrong_ed25519_public, // Responder expects WRONG key initiator_keys.public_key(), &salt, @@ -2364,8 +2332,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 = generate_keypair(); - let responder_keys = generate_keypair(); + let initiator_keys = Arc::new(generate_keypair()); + let responder_keys = Arc::new(generate_keypair()); // Create session but don't complete handshake (no PSK injection will occur) let session = create_handshake_test_session( @@ -2413,8 +2381,8 @@ mod tests { #[test] fn test_demote_sets_read_only() { - let initiator_keys = generate_keypair(); - let responder_keys = generate_keypair(); + let initiator_keys = Arc::new(generate_keypair()); + let responder_keys = Arc::new(generate_keypair()); let session = create_handshake_test_session( 12345u32, @@ -2438,8 +2406,8 @@ mod tests { #[test] fn test_encrypt_fails_after_demotion() { // --- Setup Handshake --- - let initiator_keys = generate_keypair(); - let responder_keys = generate_keypair(); + let initiator_keys = Arc::new(generate_keypair()); + let responder_keys = Arc::new(generate_keypair()); let initiator_session = create_handshake_test_session( 12345u32, @@ -2494,8 +2462,8 @@ mod tests { #[test] fn test_decrypt_works_after_demotion() { // --- Setup Handshake --- - let initiator_keys = generate_keypair(); - let responder_keys = generate_keypair(); + let initiator_keys = Arc::new(generate_keypair()); + let responder_keys = Arc::new(generate_keypair()); let initiator_session = create_handshake_test_session( 12345u32, diff --git a/common/nym-lp/src/session_integration/mod.rs b/common/nym-lp/src/session_integration/mod.rs index a3b58f2575..74f4dfdfb4 100644 --- a/common/nym-lp/src/session_integration/mod.rs +++ b/common/nym-lp/src/session_integration/mod.rs @@ -1,7 +1,6 @@ #[cfg(test)] mod tests { use crate::codec::{parse_lp_packet, serialize_lp_packet}; - use crate::keypair::PublicKey; use crate::{ LpError, message::LpMessage, @@ -9,7 +8,8 @@ mod tests { session_manager::SessionManager, }; use bytes::BytesMut; - use nym_crypto::asymmetric::ed25519; + use nym_crypto::asymmetric::{ed25519, x25519}; + use std::sync::Arc; // Function to create a test packet - similar to how it's done in codec.rs tests fn create_test_packet( @@ -53,6 +53,11 @@ 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() @@ -64,9 +69,9 @@ mod tests { .expect("Failed to derive X25519 from Ed25519"); // Convert to LP keypair types - let lp_pub_a = PublicKey::from_bytes(x25519_pub_a.as_bytes()) + let lp_pub_a = x25519::PublicKey::from_bytes(x25519_pub_a.as_bytes()) .expect("Failed to create PublicKey from bytes"); - let lp_pub_b = PublicKey::from_bytes(x25519_pub_b.as_bytes()) + let lp_pub_b = x25519::PublicKey::from_bytes(x25519_pub_b.as_bytes()) .expect("Failed to create PublicKey from bytes"); // Use fixed receiver_index for deterministic test @@ -79,11 +84,9 @@ mod tests { let peer_a_sm = session_manager_1 .create_session_state_machine( receiver_index, - ( - ed25519_keypair_a.private_key(), - ed25519_keypair_a.public_key(), - ), + Arc::new(ed25519_keypair_a), ed25519_keypair_b.public_key(), + x25519_keypair_b.public_key(), true, &salt, ) @@ -92,11 +95,9 @@ mod tests { let peer_b_sm = session_manager_2 .create_session_state_machine( receiver_index, - ( - ed25519_keypair_b.private_key(), - ed25519_keypair_b.public_key(), - ), - ed25519_keypair_a.public_key(), + Arc::new(ed25519_keypair_b), + &ed25519_pubkey_a, + x25519_keypair_a.public_key(), false, &salt, ) @@ -512,6 +513,11 @@ 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() @@ -523,9 +529,9 @@ mod tests { .expect("Failed to derive X25519 from Ed25519"); // Convert to LP keypair types - let lp_pub_a = PublicKey::from_bytes(x25519_pub_a.as_bytes()) + let lp_pub_a = x25519::PublicKey::from_bytes(x25519_pub_a.as_bytes()) .expect("Failed to create PublicKey from bytes"); - let lp_pub_b = PublicKey::from_bytes(x25519_pub_b.as_bytes()) + let lp_pub_b = x25519::PublicKey::from_bytes(x25519_pub_b.as_bytes()) .expect("Failed to create PublicKey from bytes"); // Use fixed receiver_index for test @@ -537,11 +543,9 @@ mod tests { let peer_a_sm = session_manager_1 .create_session_state_machine( receiver_index, - ( - ed25519_keypair_a.private_key(), - ed25519_keypair_a.public_key(), - ), + Arc::new(ed25519_keypair_a), ed25519_keypair_b.public_key(), + x25519_keypair_b.public_key(), true, &salt, ) @@ -549,11 +553,9 @@ mod tests { let peer_b_sm = session_manager_2 .create_session_state_machine( receiver_index, - ( - ed25519_keypair_b.private_key(), - ed25519_keypair_b.public_key(), - ), - ed25519_keypair_a.public_key(), + Arc::new(ed25519_keypair_b), + &ed25519_pubkey_a, + x25519_keypair_a.public_key(), false, &salt, ) @@ -720,16 +722,22 @@ mod tests { let session_manager = SessionManager::new(); // Generate Ed25519 keypair for PSQ authentication - let ed25519_keypair = ed25519::KeyPair::from_secret([5u8; 32], 0); + 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(); // Derive X25519 key from Ed25519 (same as state machine does internally) - let x25519_pub = ed25519_keypair + let x25519_pub = ed25519_keypair_a .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 = PublicKey::from_bytes(x25519_pub.as_bytes()) + let _lp_pub = x25519::PublicKey::from_bytes(x25519_pub.as_bytes()) .expect("Failed to create PublicKey from bytes"); // Use fixed receiver_index for test @@ -742,8 +750,9 @@ mod tests { let _session = session_manager .create_session_state_machine( receiver_index, - (ed25519_keypair.private_key(), ed25519_keypair.public_key()), - ed25519_keypair.public_key(), + keypair_a.clone(), + ed25519_keypair_b.public_key(), + x25519_keypair_b.public_key(), true, &salt, ) @@ -765,8 +774,9 @@ mod tests { let _temp_session = session_manager .create_session_state_machine( receiver_index_temp, - (ed25519_keypair.private_key(), ed25519_keypair.public_key()), - ed25519_keypair.public_key(), + keypair_a.clone(), + ed25519_keypair_b.public_key(), + x25519_keypair_a.public_key(), true, &salt, ) @@ -849,6 +859,12 @@ 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; @@ -860,11 +876,9 @@ mod tests { session_manager_1 .create_session_state_machine( receiver_index, - ( - ed25519_keypair_a.private_key(), - ed25519_keypair_a.public_key() - ), - ed25519_keypair_b.public_key(), + Arc::new(ed25519_keypair_a), + &pubkey_b, + x25519_keypair_b.public_key(), true, &salt, ) // Initiator @@ -874,11 +888,9 @@ mod tests { session_manager_2 .create_session_state_machine( receiver_index, - ( - ed25519_keypair_b.private_key(), - ed25519_keypair_b.public_key() - ), - ed25519_keypair_a.public_key(), + Arc::new(ed25519_keypair_b), + &pubkey_a, + x25519_keypair_a.public_key(), false, &salt, ) // Responder diff --git a/common/nym-lp/src/session_manager.rs b/common/nym-lp/src/session_manager.rs index 8eb1bbcb3d..c669a9df41 100644 --- a/common/nym-lp/src/session_manager.rs +++ b/common/nym-lp/src/session_manager.rs @@ -7,7 +7,8 @@ //! creation, retrieval, and storage of sessions. use dashmap::DashMap; -use nym_crypto::asymmetric::ed25519; +use nym_crypto::asymmetric::{ed25519, x25519}; +use std::sync::Arc; use crate::noise_protocol::ReadResult; use crate::state_machine::{LpAction, LpInput, LpState, LpStateBare}; @@ -169,8 +170,9 @@ impl SessionManager { pub fn create_session_state_machine( &self, receiver_index: u32, - local_ed25519_keypair: (&ed25519::PrivateKey, &ed25519::PublicKey), + local_ed25519_keypair: Arc, remote_ed25519_key: &ed25519::PublicKey, + remote_x25519_key: &x25519::PublicKey, is_initiator: bool, salt: &[u8; 32], ) -> Result { @@ -179,6 +181,7 @@ impl SessionManager { is_initiator, local_ed25519_keypair, remote_ed25519_key, + remote_x25519_key, salt, )?; @@ -199,7 +202,7 @@ impl SessionManager { pub fn init_kkt_for_test( &self, lp_id: u32, - remote_x25519_pub: &crate::keypair::PublicKey, + remote_x25519_pub: &x25519::PublicKey, ) -> Result<(), LpError> { self.with_state_machine(lp_id, |sm| { sm.session()?.set_kkt_completed_for_test(remote_x25519_pub); @@ -217,14 +220,19 @@ 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, - (ed25519_keypair.private_key(), ed25519_keypair.public_key()), - ed25519_keypair.public_key(), + Arc::new(ed25519_keypair), + ed25519_keypair2.public_key(), + x25519_keypair2.public_key(), true, &salt, ) @@ -241,14 +249,19 @@ 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, - (ed25519_keypair.private_key(), ed25519_keypair.public_key()), - ed25519_keypair.public_key(), + Arc::new(ed25519_keypair), + ed25519_keypair2.public_key(), + x25519_keypair2.public_key(), true, &salt, ) @@ -270,14 +283,20 @@ 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, - ( - ed25519_keypair_1.private_key(), - ed25519_keypair_1.public_key(), - ), - ed25519_keypair_1.public_key(), + Arc::new(ed25519_keypair_1), + &pubkey2, + &xpubkey2, true, &salt, ) @@ -286,11 +305,9 @@ mod tests { let sm_2 = manager .create_session_state_machine( 3002, - ( - ed25519_keypair_2.private_key(), - ed25519_keypair_2.public_key(), - ), - ed25519_keypair_2.public_key(), + Arc::new(ed25519_keypair_2), + &pubkey3, + &xpubkey3, true, &salt, ) @@ -299,11 +316,9 @@ mod tests { let sm_3 = manager .create_session_state_machine( 3003, - ( - ed25519_keypair_3.private_key(), - ed25519_keypair_3.public_key(), - ), - ed25519_keypair_3.public_key(), + Arc::new(ed25519_keypair_3), + &pubkey1, + &xpubkey1, true, &salt, ) @@ -324,13 +339,17 @@ 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, - (ed25519_keypair.private_key(), ed25519_keypair.public_key()), - ed25519_keypair.public_key(), + Arc::new(ed25519_keypair), + ed25519_keypair2.public_key(), + x25519_keypair2.public_key(), true, &salt, ); diff --git a/common/nym-lp/src/state_machine.rs b/common/nym-lp/src/state_machine.rs index 37a4ee72f8..fb467d213e 100644 --- a/common/nym-lp/src/state_machine.rs +++ b/common/nym-lp/src/state_machine.rs @@ -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; +use nym_crypto::asymmetric::{ed25519, x25519}; use std::mem; +use std::sync::Arc; use tracing::debug; /// Represents the possible states of the Lewes Protocol connection. @@ -90,6 +90,7 @@ 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) @@ -189,7 +190,8 @@ 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 and X25519 derivation + /// * `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` - Fresh salt for PSK derivation (must be unique per session) /// /// # Errors @@ -199,8 +201,9 @@ impl LpStateMachine { pub fn new( receiver_index: u32, is_initiator: bool, - local_ed25519_keypair: (&ed25519::PrivateKey, &ed25519::PublicKey), + local_ed25519_keypair: Arc, remote_ed25519_key: &ed25519::PublicKey, + remote_x25519_key: &x25519::PublicKey, salt: &[u8; 32], ) -> Result { // We use standard RFC 7748 conversion to derive X25519 keys from Ed25519 identity keys. @@ -213,23 +216,7 @@ impl LpStateMachine { // - PSQ ECDH baseline security (pre-quantum) // Convert Ed25519 keys to X25519 for Noise protocol - 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); + let local_x25519 = Arc::new(local_ed25519_keypair.to_x25519()); // Create the session with both Ed25519 (for PSQ auth) and derived X25519 keys (for Noise) // receiver_index is client-proposed, passed through directly @@ -237,9 +224,9 @@ impl LpStateMachine { receiver_index, is_initiator, local_ed25519_keypair, - local_x25519_keypair.private_key(), + local_x25519, remote_ed25519_key, - &lp_remote_public, + remote_x25519_key, salt, )?; @@ -1105,6 +1092,10 @@ 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]; @@ -1113,11 +1104,9 @@ mod tests { let initiator_sm = LpStateMachine::new( receiver_index, true, - ( - ed25519_keypair_init.private_key(), - ed25519_keypair_init.public_key(), - ), + Arc::new(ed25519_keypair_init), ed25519_keypair_resp.public_key(), + &x25519_pubkey_resp, &salt, ); assert!(initiator_sm.is_ok()); @@ -1132,11 +1121,9 @@ mod tests { let responder_sm = LpStateMachine::new( receiver_index, false, - ( - ed25519_keypair_resp.private_key(), - ed25519_keypair_resp.public_key(), - ), - ed25519_keypair_init.public_key(), + Arc::new(ed25519_keypair_resp), + &ed25519_pubkey_init, + &x25519_pubkey_init, &salt, ); assert!(responder_sm.is_ok()); @@ -1158,6 +1145,10 @@ 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; @@ -1166,11 +1157,9 @@ mod tests { let mut initiator = LpStateMachine::new( receiver_index, true, // is_initiator - ( - ed25519_keypair_init.private_key(), - ed25519_keypair_init.public_key(), - ), + Arc::new(ed25519_keypair_init), ed25519_keypair_resp.public_key(), + &x25519_pubkey_resp, &salt, ) .unwrap(); @@ -1178,11 +1167,9 @@ mod tests { let mut responder = LpStateMachine::new( receiver_index, false, // is_initiator - ( - ed25519_keypair_resp.private_key(), - ed25519_keypair_resp.public_key(), - ), - ed25519_keypair_init.public_key(), + Arc::new(ed25519_keypair_resp), + &ed25519_pubkey_init, + &x25519_pubkey_init, &salt, ) .unwrap(); @@ -1366,6 +1353,8 @@ 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; @@ -1373,11 +1362,9 @@ mod tests { let mut initiator = LpStateMachine::new( receiver_index, true, - ( - ed25519_keypair_init.private_key(), - ed25519_keypair_init.public_key(), - ), + Arc::new(ed25519_keypair_init), ed25519_keypair_resp.public_key(), + &x25519_pubkey_init, &salt, ) .unwrap(); @@ -1397,6 +1384,9 @@ 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; @@ -1404,11 +1394,9 @@ mod tests { let mut responder = LpStateMachine::new( receiver_index, false, - ( - ed25519_keypair_resp.private_key(), - ed25519_keypair_resp.public_key(), - ), - ed25519_keypair_init.public_key(), + Arc::new(ed25519_keypair_resp), + &ed25519_pubkey_init, + &x25519_pubkey_init, &salt, ) .unwrap(); @@ -1428,6 +1416,10 @@ 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; @@ -1435,11 +1427,9 @@ mod tests { let mut initiator = LpStateMachine::new( receiver_index, true, - ( - ed25519_keypair_init.private_key(), - ed25519_keypair_init.public_key(), - ), + Arc::new(ed25519_keypair_init), ed25519_keypair_resp.public_key(), + &x25519_pubkey_resp, &salt, ) .unwrap(); @@ -1447,11 +1437,9 @@ mod tests { let mut responder = LpStateMachine::new( receiver_index, false, - ( - ed25519_keypair_resp.private_key(), - ed25519_keypair_resp.public_key(), - ), - ed25519_keypair_init.public_key(), + Arc::new(ed25519_keypair_resp), + &ed25519_pubkey_init, + &x25519_pubkey_init, &salt, ) .unwrap(); @@ -1493,6 +1481,8 @@ 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; @@ -1500,11 +1490,9 @@ mod tests { let mut initiator = LpStateMachine::new( receiver_index, true, - ( - ed25519_keypair_init.private_key(), - ed25519_keypair_init.public_key(), - ), + Arc::new(ed25519_keypair_init), ed25519_keypair_resp.public_key(), + &x25519_pubkey_resp, &salt, ) .unwrap(); @@ -1525,6 +1513,8 @@ 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; @@ -1532,11 +1522,9 @@ mod tests { let mut initiator = LpStateMachine::new( receiver_index, true, - ( - ed25519_keypair_init.private_key(), - ed25519_keypair_init.public_key(), - ), + Arc::new(ed25519_keypair_init), ed25519_keypair_resp.public_key(), + &x25519_pubkey_resp, &salt, ) .unwrap(); @@ -1570,6 +1558,11 @@ 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; @@ -1577,11 +1570,9 @@ mod tests { let mut alice = LpStateMachine::new( receiver_index, true, - ( - ed25519_keypair_a.private_key(), - ed25519_keypair_a.public_key(), - ), + Arc::new(ed25519_keypair_a), ed25519_keypair_b.public_key(), + &x25519_pubkey_b, &salt, ) .unwrap(); @@ -1589,11 +1580,9 @@ mod tests { let mut bob = LpStateMachine::new( receiver_index, false, - ( - ed25519_keypair_b.private_key(), - ed25519_keypair_b.public_key(), - ), - ed25519_keypair_a.public_key(), + Arc::new(ed25519_keypair_b), + &ed25519_pubkey_a, + &x25519_pubkey_a, &salt, ) .unwrap(); diff --git a/gateway/src/node/lp_listener/handler.rs b/gateway/src/node/lp_listener/handler.rs index 7960d4f026..5f70199637 100644 --- a/gateway/src/node/lp_listener/handler.rs +++ b/gateway/src/node/lp_listener/handler.rs @@ -5,9 +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::{ - codec::OuterAeadKey, keypair::PublicKey, message::ForwardPacketData, packet::LpHeader, - LpMessage, LpPacket, OuterHeader, + codec::OuterAeadKey, message::ForwardPacketData, packet::LpHeader, LpMessage, LpPacket, + OuterHeader, }; use nym_lp_transport::traits::LpTransport; use nym_metrics::{add_histogram_obs, inc}; @@ -289,31 +290,11 @@ 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 (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) - } + let hello_data = match packet.message() { + LpMessage::ClientHello(hello_data) => hello_data, other => { inc!("lp_client_hello_failed"); return Err(GatewayError::LpProtocolError(format!( @@ -322,20 +303,23 @@ where } }; - debug!( - "Processing ClientHello from {} (proposed receiver_index={})", - self.remote_addr, receiver_index - ); + // 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})",); // 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: {} from {}", - receiver_index, self.remote_addr - ); + warn!("Receiver index collision: {receiver_index} from {remote}",); inc!("lp_receiver_index_collision"); // Send Collision response to tell client to retry with new receiver_index @@ -351,32 +335,23 @@ 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 {} to receiver_idx={} (via ClientHello)", - self.remote_addr, - receiver_index - ); + trace!("Bound connection from {remote} to receiver_idx={receiver_index} (via ClientHello)",); // 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.private_key(), - self.state.local_identity.public_key(), - ), - &client_ed25519_pubkey, - &salt, + self.state.local_identity.clone(), + &hello_data.client_ed25519_public_key, + &hello_data.client_lp_public_key, + &hello_data.salt, ) .map_err(|e| { inc!("lp_client_hello_failed"); GatewayError::LpHandshakeError(format!("Failed to create state machine: {}", e)) })?; - debug!( - "Created handshake state for {} (receiver_index={})", - self.remote_addr, receiver_index - ); + debug!("Created handshake state for {remote} (receiver_index={receiver_index})",); // Transition state machine to KKTExchange (responder waits for client's KKT request) // For responder, StartHandshake returns None (just transitions state) @@ -384,8 +359,7 @@ 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 } @@ -396,8 +370,7 @@ where .insert(receiver_index, super::TimestampedState::new(state_machine)); debug!( - "Stored handshake state for {} (receiver_index={}) - waiting for KKT request", - self.remote_addr, receiver_index + "Stored handshake state for {remote} (receiver_index={receiver_index}) - waiting for KKT request", ); // Send Ack to confirm ClientHello received @@ -897,14 +870,7 @@ where #[allow(dead_code)] async fn receive_client_hello( &mut self, - ) -> Result< - ( - PublicKey, - nym_crypto::asymmetric::ed25519::PublicKey, - [u8; 32], - ), - GatewayError, - > { + ) -> Result<(x25519::PublicKey, 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) @@ -931,22 +897,11 @@ where self.state.lp_config.debug.timestamp_tolerance.as_secs() ); - // 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 X25519 PublicKey (for Noise protocol) + let client_pubkey = hello_data.client_lp_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 - )) - })?; + // Retrieve Ed25519 PublicKey (for PSQ authentication) + let client_ed25519_pubkey = hello_data.client_ed25519_public_key; // Extract salt for PSK derivation let salt = hello_data.salt; @@ -1705,8 +1660,13 @@ mod tests { let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); let addr = listener.local_addr().unwrap(); - let client_key = [7u8; 32]; - let client_ed25519_key = [8u8; 32]; + 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 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 @@ -1777,8 +1737,8 @@ 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.to_bytes(), - client_ed25519_keypair.public_key().to_bytes(), + client_x25519_public, + *client_ed25519_keypair.public_key(), timestamp, ); let packet = LpPacket::new( @@ -1838,8 +1798,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.to_bytes(), - client_ed25519_keypair.public_key().to_bytes(), + client_x25519_public, + *client_ed25519_keypair.public_key(), timestamp, ); diff --git a/nym-registration-client/src/lp_client/client.rs b/nym-registration-client/src/lp_client/client.rs index 2cb6a3ed0f..6e350eeb50 100644 --- a/nym-registration-client/src/lp_client/client.rs +++ b/nym-registration-client/src/lp_client/client.rs @@ -328,6 +328,10 @@ 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()))? @@ -335,8 +339,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.to_bytes(), - self.local_ed25519_keypair.public_key().to_bytes(), + client_x25519_public, + *self.local_ed25519_keypair.public_key(), timestamp, ); let salt = client_hello_data.salt; @@ -381,11 +385,9 @@ where let mut state_machine = LpStateMachine::new( receiver_index, true, // is_initiator - ( - self.local_ed25519_keypair.private_key(), - self.local_ed25519_keypair.public_key(), - ), + self.local_ed25519_keypair.clone(), &self.gateway_ed25519_public_key, + &gateway_x25519_public, &salt, )?; diff --git a/nym-registration-client/src/lp_client/nested_session.rs b/nym-registration-client/src/lp_client/nested_session.rs index 2f011e9b15..7fbdbbc11e 100644 --- a/nym-registration-client/src/lp_client/nested_session.rs +++ b/nym-registration-client/src/lp_client/nested_session.rs @@ -129,6 +129,10 @@ 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()))? @@ -136,8 +140,8 @@ impl NestedLpSession { // Step 2: Generate ClientHello for exit gateway let client_hello_data = nym_lp::ClientHelloData::new_with_fresh_salt( - client_x25519_public.to_bytes(), - self.client_keypair.public_key().to_bytes(), + client_x25519_public, + *self.client_keypair.public_key(), timestamp, ); let salt = client_hello_data.salt; @@ -191,11 +195,9 @@ impl NestedLpSession { let mut state_machine = LpStateMachine::new( receiver_index, true, // is_initiator - ( - self.client_keypair.private_key(), - self.client_keypair.public_key(), - ), + self.client_keypair.clone(), &self.exit_public_key, + &gateway_x25519_public, &salt, )?;