Files
Simon Wicky 71d4b5b3ea moving lp packets in lp-data crate (#6810)
* moving lp packets in lp-data crate

* one more bit

* fmt

* crate description
2026-05-20 14:32:01 +02:00
..

Nym Lewes Protocol

The Lewes Protocol (LP) is a secure network communication protocol implemented in Rust. It provides authenticated, encrypted sessions with replay protection and supports nested session forwarding for privacy-preserving multi-hop connections.

Architecture Overview

┌─────────────────┐     ┌────────────────┐     ┌───────────────┐
│ Transport Layer │◄───►│ LP Session     │◄───►│ LP Codec      │
│ (TCP)           │     │ - State machine│     │ - Serialize   │
└─────────────────┘     │ - Noise crypto │     │ - Deserialize │
                        │ - Replay prot. │     └───────────────┘
                        └────────────────┘

Packet Structure

The protocol uses a length-prefixed packet format over TCP:

Wire Format:
┌────────────────────┬─────────────────────────────────────────┐
│ Length (4B BE u32) │ LpPacket                                │
└────────────────────┴─────────────────────────────────────────┘

LpPacket:
┌──────────────────┬───────────────────┬──────────────────┐
│ Header (16B)     │ Message           │ Trailer (16B)    │
├──────────────────┼───────────────────┼──────────────────┤
│ Version (1B)     │ Type (2B LE u16)  │ Reserved         │
│ Reserved (3B)    │ Content (var)     │ (16 bytes)       │
│ SessionID (4B LE)│                   │                  │
│ Counter (8B LE)  │                   │                  │
└──────────────────┴───────────────────┴──────────────────┘
  • Header: Protocol version (1), session identifier, monotonic counter
  • Message: Type discriminant + variable-length content
  • Trailer: Reserved for future use (16 bytes)

Message Types

Type Value Purpose
Busy 0x0000 Server congestion signal
Handshake 0x0001 Noise protocol handshake messages
EncryptedData 0x0002 Encrypted application data
ClientHello 0x0003 Initial session negotiation
KKTRequest 0x0004 KEM Key Transfer request
KKTResponse 0x0005 KEM Key Transfer response
ForwardPacket 0x0006 Nested session forwarding

Session Establishment

Session ID

Sessions are identified by a deterministic 32-bit ID computed from both parties' X25519 public keys:

session_id = make_lp_id(client_x25519_pub, gateway_x25519_pub)

The computation is order-independent, allowing both sides to derive the same ID independently.

BOOTSTRAP_SESSION_ID (0): A special session ID used only for the initial ClientHello packet, since neither side can compute the final ID until both X25519 keys are known.

Handshake Flow

┌────────┐                              ┌─────────┐
│ Client │                              │ Gateway │
└───┬────┘                              └────┬────┘
    │                                        │
    │ 1. ClientHello (session_id=0)          │
    │   [client_x25519, client_ed25519, salt]│
    │───────────────────────────────────────►│
    │                                        │ (computes session_id)
    │                                        │ (stores state machine)
    │                                        │
    │ 2. KKTRequest (session_id=N)           │
    │   [signed request for KEM key]         │
    │───────────────────────────────────────►│
    │                                        │
    │ 3. KKTResponse                         │
    │   [gateway KEM key + signature]        │
    │◄───────────────────────────────────────│
    │                                        │
    │ 4. Noise Handshake Msg 1               │
    │   [PSQ payload + noise message]        │
    │───────────────────────────────────────►│
    │                                        │ (derives PSK from PSQ)
    │ 5. Noise Handshake Msg 2               │
    │   [PSK handle + noise message]         │
    │◄───────────────────────────────────────│
    │                                        │
    │ 6. Noise Handshake Msg 3               │
    │───────────────────────────────────────►│
    │                                        │
    │ ═══════ Session Established ═══════    │
    │                                        │
    │ 7. EncryptedData                       │
    │   [encrypted application data]         │
    │◄──────────────────────────────────────►│
    │                                        │

ClientHello Data

struct ClientHelloData {
    client_lp_public_key: [u8; 32],      // X25519 (derived from Ed25519)
    client_ed25519_public_key: [u8; 32], // For authentication
    salt: [u8; 32],                       // timestamp (8B) + nonce (24B)
}

Packet-Per-Connection Model

The gateway processes exactly one packet per TCP connection, then closes. State persists between connections via in-memory maps:

TCP Connect → Receive Packet → Process → Send Response → TCP Close

State Storage:

  • handshake_states: Maps session_id → LpStateMachine (during handshake)
  • session_states: Maps session_id → LpSession (after handshake complete)

Both maps use TTL-based cleanup to remove stale entries (default: 5 min handshake, 1 hour session).

Gateway Packet Routing

Packet Received
    │
    ├─► session_id == 0 (BOOTSTRAP)
    │   └─► handle_client_hello()
    │       └─► Create state machine, store in handshake_states
    │
    ├─► session_id in handshake_states
    │   └─► handle_handshake_packet()
    │       └─► Process KKT/Noise, move to session_states when complete
    │
    └─► session_id in session_states
        └─► handle_transport_packet()
            └─► Decrypt, process registration or forwarding

Session Forwarding

Forwarding enables a client to establish an independent session with an exit gateway through an entry gateway, providing network-level privacy.

Architecture

┌──────────┐
│  Client  │
└────┬─────┘
     │ Outer LP Session (established, encrypted)
     │
     ▼
┌────────────────┐
│ Entry Gateway  │  Sees: Client IP
│                │  Doesn't see: Exit destination
└────────┬───────┘
         │ Forwards inner packets (TCP)
         │
         ▼
┌────────────────┐
│ Exit Gateway   │  Sees: Entry Gateway IP
│                │  Doesn't see: Client IP
└────────────────┘

ForwardPacket Message

struct ForwardPacketData {
    target_gateway_identity: [u8; 32], // Exit gateway's Ed25519 key
    target_lp_address: String,          // e.g., "2.2.2.2:41264"
    inner_packet_bytes: Vec<u8>,        // Complete LP packet for exit
}

Forwarding Flow

  1. Client establishes outer LP session with entry gateway

  2. Client creates ClientHello packet for exit gateway

  3. Client wraps inner packet in ForwardPacketData:

    • Sets target_gateway_identity to exit's Ed25519 key
    • Sets target_lp_address to exit's LP listener address
    • Serializes complete LP packet as inner_packet_bytes
  4. Client encrypts ForwardPacketData using outer session

  5. Client sends as EncryptedData to entry gateway

  6. Entry Gateway decrypts, sees ForwardPacketData

  7. Entry Gateway connects to exit gateway (new TCP)

  8. Entry Gateway sends inner_packet_bytes directly

  9. Entry Gateway receives exit's response

  10. Entry Gateway encrypts response using outer session

  11. Entry Gateway sends encrypted response to client

  12. Client decrypts response, processes in inner session state

NestedLpSession

The NestedLpSession struct manages the inner session from the client's perspective:

struct NestedLpSession {
    exit_identity: [u8; 32],           // Exit gateway Ed25519
    exit_address: String,               // Exit LP address
    client_keypair: Arc<ed25519::KeyPair>,
    exit_public_key: ed25519::PublicKey,
    state_machine: Option<LpStateMachine>,
}

Usage:

// Create nested session targeting exit gateway
let nested = NestedLpSession::new(exit_identity, exit_address, keypair, exit_pubkey);

// Perform handshake through outer session
nested.handshake_and_register(&mut outer_client).await?;

// Inner session now established with exit gateway

State Machine States

ReadyToHandshake
       │
       ▼
   KKTExchange ◄─── KKTRequest/KKTResponse
       │
       ▼
   Handshaking ◄─── Noise messages + PSQ
       │
       ▼
    Transport ◄─── EncryptedData
       │
       ▼
     Closed

Cryptography

Key Types

  • Ed25519: Identity keys, signing
  • X25519: Key exchange (derived from Ed25519 via RFC 7748)

Noise Protocol

  • Pattern: Noise_XKpsk3_25519_ChaChaPoly_SHA256
  • Provides: Forward secrecy, mutual authentication, PSK binding

PSK Derivation (PSQ)

The Pre-Shared Key is derived via Post-Quantum Secure Key Exchange:

  1. Client encapsulates using authenticated KEM key from KKT
  2. Produces 32-byte PSK + ciphertext
  3. Gateway decapsulates to derive same PSK
  4. PSK injected into Noise at position 3

Replay Protection

  • Monotonic counter: Each packet has incrementing 64-bit counter
  • Sliding window: Bitmap tracks received counters (1024 packet window)
  • SIMD optimized: Branchless validation for constant-time operation
// Validation flow
validator.will_accept_branchless(counter) // Check before decrypt
validator.mark_did_receive_branchless(counter) // Mark after decrypt

Sessions

LpSession Fields

struct LpSession {
    id: u32,                    // Session identifier
    is_initiator: bool,         // Client or gateway role
    noise_state: NoiseState,    // Noise transport state
    kkt_state: KktState,        // KKT exchange progress
    psq_state: PsqState,        // PSQ handshake progress
    psk_handle: Option<Vec<u8>>,// PSK handle from responder
    sending_counter: AtomicU64, // Outgoing packet counter
    receiving_counter: Validator, // Replay protection
    psk_injected: AtomicBool,   // Safety: real PSK injected?
}

PSK Safety

Sessions initialize with a dummy PSK. The psk_injected flag must be true before encrypt_data() or decrypt_data() will operate, preventing accidental use of the insecure dummy.

File Structure

common/nym-lp/src/
├── lib.rs           # Module exports
├── message.rs       # LpMessage enum, ClientHelloData, ForwardPacketData
├── packet.rs        # LpPacket, LpHeader, BOOTSTRAP_SESSION_ID
├── codec.rs         # Serialization/deserialization
├── session.rs       # LpSession, cryptographic operations
├── state_machine.rs # LpStateMachine, state transitions
├── psk.rs           # PSK derivation utilities
└── error.rs         # Error types