diff --git a/common/nym-lp/src/config.rs b/common/nym-lp/src/config.rs new file mode 100644 index 0000000000..824d56e7ac --- /dev/null +++ b/common/nym-lp/src/config.rs @@ -0,0 +1,79 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +//! Configuration for LP protocol. +//! +//! AIDEV-NOTE: LP security stack = KKT (key fetch) → PSQ (PQ PSK) → Noise (transport). +//! KEM algorithm selection affects only PSQ layer. Noise always uses X25519 DH. +//! Migration to PQ KEMs (MlKem768, XWing) requires only config change. + +use nym_kkt::ciphersuite::KEM; +use serde::{Deserialize, Serialize}; +use std::time::Duration; + +/// Default PSK time-to-live (1 hour, matches psk.rs implementation). +pub const DEFAULT_PSK_TTL_SECS: u64 = 3600; + +/// Configuration for LP protocol. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct LpConfig { + /// KEM algorithm for PSQ key encapsulation. + /// X25519 = classical (testing), MlKem768 = PQ, XWing = hybrid. + #[serde(with = "kem_serde")] + pub kem_algorithm: KEM, + + /// PSK time-to-live in seconds. + pub psk_ttl_secs: u64, + + /// Enable KKT for authenticated key distribution. + pub enable_kkt: bool, +} + +impl Default for LpConfig { + fn default() -> Self { + Self { + kem_algorithm: KEM::X25519, + psk_ttl_secs: DEFAULT_PSK_TTL_SECS, + enable_kkt: true, + } + } +} + +impl LpConfig { + /// Returns PSK TTL as Duration. + pub fn psk_ttl(&self) -> Duration { + Duration::from_secs(self.psk_ttl_secs) + } +} + +mod kem_serde { + use nym_kkt::ciphersuite::KEM; + use serde::{Deserialize, Deserializer, Serialize, Serializer}; + + pub fn serialize(kem: &KEM, serializer: S) -> Result + where + S: Serializer, + { + match kem { + KEM::X25519 => "X25519", + KEM::MlKem768 => "MlKem768", + KEM::XWing => "XWing", + KEM::McEliece => "McEliece", + } + .serialize(serializer) + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + match s.as_str() { + "X25519" => Ok(KEM::X25519), + "MlKem768" => Ok(KEM::MlKem768), + "XWing" => Ok(KEM::XWing), + "McEliece" => Ok(KEM::McEliece), + _ => Err(serde::de::Error::custom(format!("Unknown KEM: {}", s))), + } + } +} diff --git a/common/nym-lp/src/lib.rs b/common/nym-lp/src/lib.rs index c761892080..727ad43846 100644 --- a/common/nym-lp/src/lib.rs +++ b/common/nym-lp/src/lib.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 pub mod codec; +pub mod config; pub mod error; pub mod keypair; pub mod kkt_orchestrator; @@ -14,6 +15,7 @@ pub mod session; mod session_integration; pub mod session_manager; +pub use config::LpConfig; pub use error::LpError; pub use message::{ClientHelloData, LpMessage}; pub use packet::{BOOTSTRAP_RECEIVER_IDX, LpPacket, OuterHeader}; diff --git a/common/nym-lp/src/psk.rs b/common/nym-lp/src/psk.rs index bf529e859b..502701a936 100644 --- a/common/nym-lp/src/psk.rs +++ b/common/nym-lp/src/psk.rs @@ -6,6 +6,12 @@ //! This module implements identity-bound PSK derivation where both client and gateway //! derive the same PSK from their LP keypairs. //! +//! AIDEV-NOTE: PSQ is embedded in Noise (not separate protocol) because: +//! 1. Single round-trip: PSQ ciphertext piggybacks on Noise handshake messages +//! 2. PSK binding: Noise XKpsk3 pattern authenticates both ECDH and PSQ-derived PSK +//! 3. Simpler state machine: No separate PSQ negotiation phase needed +//! 4. Atomic security: Session establishment either succeeds fully or fails completely +//! //! Two approaches are supported: //! - **Legacy ECDH-only** (`derive_psk`) - Simple but no post-quantum security //! - **PSQ-enhanced** (`derive_psk_with_psq_*`) - Combines ECDH with post-quantum KEM @@ -140,7 +146,12 @@ pub fn derive_psk_with_psq_initiator( let ecdh_secret = local_x25519_private.diffie_hellman(remote_x25519_public); // Step 2: PSQ encapsulation for post-quantum security - // Extract X25519 public key from EncapsulationKey + // AIDEV-NOTE: KEM algorithm migration path: + // - X25519: Current default for testing/compatibility (no HNDL resistance) + // - MlKem768: Future production default (NIST PQ Level 3, HNDL resistant) + // - XWing: Maximum security option (hybrid X25519 + ML-KEM) + // Migration: Update LpConfig.kem_algorithm, no protocol changes needed. + // KKT protocol adapts automatically to different KEM key sizes. let kem_pk = match remote_kem_public { EncapsulationKey::X25519(pk) => pk, _ => { diff --git a/common/nym-lp/src/state_machine.rs b/common/nym-lp/src/state_machine.rs index 984cc3011a..db46168369 100644 --- a/common/nym-lp/src/state_machine.rs +++ b/common/nym-lp/src/state_machine.rs @@ -2,6 +2,16 @@ // SPDX-License-Identifier: Apache-2.0 //! Lewes Protocol State Machine for managing connection lifecycle. +//! +//! AIDEV-NOTE: LP protocol flow (KKT → PSQ → Noise): +//! 1. KKTExchange: Client requests gateway's KEM public key (signed for MITM protection) +//! 2. Handshaking: Noise XKpsk3 with PSQ-derived PSK embedded in handshake messages +//! - PSQ ciphertext piggybacked on ClientHello (no extra round-trip) +//! - PSK = Blake3(ECDH || PSQ_secret || salt) provides hybrid classical+PQ security +//! 3. Transport: ChaCha20-Poly1305 authenticated encryption with derived keys +//! +//! State machine ensures protocol steps execute in correct order. Invalid transitions +//! return LpError, preventing protocol violations. use crate::{ LpError,