* moving lp packets in lp-data crate * one more bit * fmt * crate description
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: Mapssession_id → LpStateMachine(during handshake)session_states: Mapssession_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
-
Client establishes outer LP session with entry gateway
-
Client creates
ClientHellopacket for exit gateway -
Client wraps inner packet in
ForwardPacketData:- Sets
target_gateway_identityto exit's Ed25519 key - Sets
target_lp_addressto exit's LP listener address - Serializes complete LP packet as
inner_packet_bytes
- Sets
-
Client encrypts
ForwardPacketDatausing outer session -
Client sends as
EncryptedDatato entry gateway -
Entry Gateway decrypts, sees
ForwardPacketData -
Entry Gateway connects to exit gateway (new TCP)
-
Entry Gateway sends
inner_packet_bytesdirectly -
Entry Gateway receives exit's response
-
Entry Gateway encrypts response using outer session
-
Entry Gateway sends encrypted response to client
-
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:
- Client encapsulates using authenticated KEM key from KKT
- Produces 32-byte PSK + ciphertext
- Gateway decapsulates to derive same PSK
- 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