0a6f78a921
Initial implementation of the Lewes Protocol (LP) for gateway registration: - Add nym-lp crate with Noise protocol handshake - Add LP listener to gateway for handling registrations - Add LP client for registration flow - Integrate KKT for post-quantum KEM key exchange - Integrate PSQ for post-quantum PSK derivation - Add Ed25519 authentication throughout - Add docker/localnet support for testing Co-authored-by: Jędrzej Stuczyński <jedrzej.stuczynski@gmail.com>
89 KiB
89 KiB
LP Registration - Component Architecture
Technical architecture deep-dive
Table of Contents
- System Overview
- Gateway Architecture
- Client Architecture
- Shared Protocol Library
- Data Flow Diagrams
- State Machines
- Database Schema
- Integration Points
1. System Overview
High-Level System Diagram
┌────────────────────────────────────────────────────────────────────────────┐
│ EXTERNAL SYSTEMS │
├────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────┐ ┌──────────────────────┐ │
│ │ Nym Blockchain │ │ WireGuard Daemon │ │
│ │ (Nyx) │ │ (wg0 interface) │ │
│ │ │ │ │ │
│ │ • E-cash contract │ │ • Kernel module │ │
│ │ • Verification │ │ • Peer management │ │
│ │ keys │ │ • Tunnel routing │ │
│ └──────────┬──────────┘ └─────────┬────────────┘ │
│ │ │ │
└─────────────┼──────────────────────────────┼───────────────────────────────┘
│ │
│ RPC calls │ Netlink/ioctl
│ (credential queries) │ (peer add/remove)
│ │
┌─────────────▼──────────────────────────────▼───────────────────────────────┐
│ GATEWAY COMPONENTS │
├────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────────────────────────────────────────────────────────┐ │
│ │ nym-node (Gateway Mode) │ │
│ │ gateway/src/node/ │ │
│ └──────────────────────────────────────────────────────────────────────┘ │
│ │ │ │
│ ┌────────▼──────────┐ ┌─────────▼──────────┐ │
│ │ LpListener │ │ Mixnet Listener │ │
│ │ (LP Protocol) │ │ (Traditional) │ │
│ │ :41264 │ │ :1789, :9000 │ │
│ └────────┬──────────┘ └────────────────────┘ │
│ │ │
│ ┌────────▼────────────────────────────────────────┐ │
│ │ Shared Gateway Services │ │
│ │ ┌────────────┐ ┌──────────────┐ ┌─────────┐ │ │
│ │ │ EcashMgr │ │ WG Controller│ │ Storage │ │ │
│ │ │ (verify) │ │ (peer mgmt) │ │ (SQLite)│ │ │
│ │ └────────────┘ └──────────────┘ └─────────┘ │ │
│ └─────────────────────────────────────────────────┘ │
│ │
└────────────────────────────────────────────────────────────────────────────┘
▲
│ TCP :41264
│ (LP Protocol)
│
┌─────────────┴───────────────────────────────────────────────────────────────┐
│ CLIENT COMPONENTS │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ Application (nym-gateway-probe, nym-vpn-client) │ │
│ │ │ │
│ │ Uses: │ │
│ │ • nym-registration-client (LP registration) │ │
│ │ • nym-bandwidth-controller (e-cash credential acquisition) │ │
│ │ • wireguard-rs (WireGuard tunnel setup) │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │ │ │
│ ┌────────▼──────────────┐ ┌─────────▼────────────┐ │
│ │ LpRegistrationClient │ │ BandwidthController │ │
│ │ (LP protocol client) │ │ (e-cash client) │ │
│ └────────┬──────────────┘ └──────────────────────┘ │
│ │ │
│ ┌────────▼────────────────────────────────────┐ │
│ │ common/nym-lp (Protocol Library) │ │
│ │ • State machine │ │
│ │ • Noise protocol │ │
│ │ • Cryptographic primitives │ │
│ └─────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
Code Locations:
- Gateway:
gateway/src/node/lp_listener/ - Client:
nym-registration-client/src/lp_client/ - Protocol:
common/nym-lp/src/
2. Gateway Architecture
2.1. Gateway Module Structure
gateway/src/node/
│
├─ lp_listener/
│ │
│ ├─ mod.rs [Main module, config, listener]
│ │ ├─ LpConfig (Configuration struct)
│ │ ├─ LpHandlerState (Shared state across connections)
│ │ └─ LpListener (TCP accept loop)
│ │ └─ run() ───────────────────┐
│ │ │
│ ├─ handler.rs [Per-connection handler]
│ │ └─ LpConnectionHandler <──────┘ spawned per connection
│ │ ├─ handle() (Main connection lifecycle)
│ │ ├─ receive_client_hello()
│ │ ├─ validate_timestamp()
│ │ └─ [emit metrics]
│ │
│ ├─ registration.rs [Business logic]
│ │ ├─ process_registration() (Mode router: dVPN/Mixnet)
│ │ ├─ register_wg_peer() (WireGuard peer setup)
│ │ ├─ credential_verification() (E-cash verification)
│ │ └─ credential_storage_preparation()
│ │
│ └─ handshake.rs (if exists) [Noise handshake helpers]
│
├─ wireguard/ [WireGuard integration]
│ ├─ peer_controller.rs (PeerControlRequest handler)
│ └─ ...
│
└─ storage/ [Database layer]
├─ gateway_storage.rs
└─ models/
2.2. Gateway Connection Flow
[TCP Accept Loop - LpListener::run()]
↓
┌────────────────────────────────────────────────────────────────┐
│ loop { │
│ stream = listener.accept().await? │
│ ↓ │
│ if active_connections >= max_connections { │
│ send(LpMessage::Busy) │
│ continue │
│ } │
│ ↓ │
│ spawn(async move { │
│ LpConnectionHandler::new(stream, state).handle().await │
│ }) │
│ } │
└────────────────────────────────────────────────────────────────┘
↓ spawned task
┌────────────────────────────────────────────────────────────────┐
│ [LpConnectionHandler::handle()] │
│ gateway/src/node/lp_listener/handler.rs:101-216 │
├────────────────────────────────────────────────────────────────┤
│ │
│ [1] Setup │
│ ├─ Convert gateway ed25519 → x25519 │
│ ├─ Start metrics timer │
│ └─ inc!(active_lp_connections) │
│ │
│ [2] Receive ClientHello │
│ ├─ receive_client_hello(stream).await? │
│ │ ├─ Read length-prefixed packet │
│ │ ├─ Deserialize ClientHelloData │
│ │ ├─ Extract: client_pub, salt, timestamp │
│ │ └─ validate_timestamp(timestamp, tolerance)? │
│ │ → if invalid: inc!(lp_client_hello_failed) │
│ │ return Err(...) │
│ └─ ✓ ClientHello valid │
│ │
│ [3] Derive PSK │
│ └─ psk = nym_lp::derive_psk( │
│ gw_lp_keypair.secret, │
│ client_pub, │
│ salt │
│ ) │
│ │
│ [4] Noise Handshake │
│ ├─ state_machine = LpStateMachine::new( │
│ │ is_initiator: false, // responder │
│ │ local_keypair: gw_lp_keypair, │
│ │ remote_pubkey: client_pub, │
│ │ psk: psk │
│ │ ) │
│ │ │
│ ├─ loop { │
│ │ packet = receive_packet(stream).await? │
│ │ action = state_machine.process_input( │
│ │ ReceivePacket(packet) │
│ │ )? │
│ │ match action { │
│ │ SendPacket(p) => send_packet(stream, p).await? │
│ │ HandshakeComplete => break │
│ │ _ => continue │
│ │ } │
│ │ } │
│ │ │
│ ├─ observe!(lp_handshake_duration_seconds, duration) │
│ └─ inc!(lp_handshakes_success) │
│ │
│ [5] Receive Registration Request │
│ ├─ packet = receive_packet(stream).await? │
│ ├─ action = state_machine.process_input(ReceivePacket(p)) │
│ ├─ plaintext = match action { │
│ │ DeliverData(data) => data, │
│ │ _ => return Err(...) │
│ │ } │
│ └─ request = bincode::deserialize::< │
│ LpRegistrationRequest │
│ >(&plaintext)? │
│ │
│ [6] Process Registration ───────────────┐ │
│ │ │
└──────────────────────────────────────────┼─────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────┐
│ [process_registration()] │
│ gateway/src/node/lp_listener/registration.rs:136-288 │
├──────────────────────────────────────────────────────────────────┤
│ │
│ [1] Validate timestamp (second check) │
│ └─ if !request.validate_timestamp(30): return ERROR │
│ │
│ [2] Match on request.mode │
│ ├─ RegistrationMode::Dvpn ───────────┐ │
│ │ │ │
│ └─ RegistrationMode::Mixnet{..} ─────┼────────────┐ │
│ │ │ │
└──────────────────────────────────────────┼───────────┼───────────┘
│ │
┌───────────────────────────────┘ │
│ │
▼ ▼
┌───────────────────────────────┐ ┌──────────────────────────┐
│ [dVPN Mode] │ │ [Mixnet Mode] │
├───────────────────────────────┤ ├──────────────────────────┤
│ │ │ │
│ [A] register_wg_peer() │ │ [A] Generate client_id │
│ ├─ Allocate IPs │ │ from request │
│ ├─ Create Peer config │ │ │
│ ├─ DB: insert_wg_peer() │ │ [B] Skip WireGuard │
│ │ → get client_id │ │ │
│ ├─ DB: create_bandwidth() │ │ [C] credential_verify() │
│ ├─ WG: add_peer() │ │ (same as dVPN) │
│ └─ Prepare GatewayData │ │ │
│ │ │ [D] Return response │
│ [B] credential_verification()│ │ (no gateway_data) │
│ (see below) │ │ │
│ │ └──────────────────────────┘
│ [C] Return response with │
│ gateway_data │
│ │
└───────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ [register_wg_peer()] │
│ gateway/src/node/lp_listener/registration.rs:291-404 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ [1] Allocate Private IPs │
│ ├─ random_octet = rng.gen_range(1..255) │
│ ├─ ipv4 = Ipv4Addr::new(10, 1, 0, random_octet) │
│ └─ ipv6 = Ipv6Addr::new(0xfd00, 0, ..., random_octet) │
│ │
│ [2] Create Peer Config │
│ └─ peer = Peer { │
│ public_key: request.wg_public_key, │
│ allowed_ips: [ipv4/32, ipv6/128], │
│ persistent_keepalive: Some(25), │
│ endpoint: None │
│ } │
│ │
│ [3] CRITICAL ORDER - Database Operations │
│ ├─ client_id = storage.insert_wireguard_peer( │
│ │ &peer, │
│ │ ticket_type │
│ │ ).await? │
│ │ ↓ │
│ │ SQL: INSERT INTO wireguard_peers │
│ │ (public_key, ticket_type, created_at) │
│ │ VALUES (?, ?, NOW()) │
│ │ RETURNING id │
│ │ → client_id: i64 │
│ │ │
│ └─ credential_storage_preparation( │
│ ecash_verifier, │
│ client_id │
│ ).await? │
│ ↓ │
│ SQL: INSERT INTO bandwidth │
│ (client_id, available) │
│ VALUES (?, 0) │
│ │
│ [4] Send to WireGuard Controller │
│ ├─ (tx, rx) = oneshot::channel() │
│ ├─ wg_controller.send( │
│ │ PeerControlRequest::AddPeer { │
│ │ peer: peer.clone(), │
│ │ response_tx: tx │
│ │ } │
│ │ ).await? │
│ │ │
│ ├─ result = rx.await? // Wait for controller response │
│ │ │
│ └─ if result.is_err() { │
│ // ROLLBACK: │
│ storage.delete_bandwidth(client_id).await? │
│ storage.delete_wireguard_peer(client_id).await? │
│ return Err(WireGuardPeerAddFailed) │
│ } │
│ │
│ [5] Prepare Gateway Data │
│ └─ gateway_data = GatewayData { │
│ public_key: wireguard_data.public_key, │
│ endpoint: format!("{}:{}", announced_ip, port), │
│ private_ipv4: ipv4, │
│ private_ipv6: ipv6 │
│ } │
│ │
│ [6] Return │
│ └─ Ok((gateway_data, client_id)) │
│ │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ [credential_verification()] │
│ gateway/src/node/lp_listener/registration.rs:87-133 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ [1] Check Mock Mode │
│ └─ if ecash_verifier.is_mock() { │
│ inc!(lp_bandwidth_allocated_bytes_total, MOCK_BW) │
│ return Ok(1073741824) // 1 GB │
│ } │
│ │
│ [2] Create Verifier │
│ └─ verifier = CredentialVerifier::new( │
│ CredentialSpendingRequest(request.credential), │
│ ecash_verifier.clone(), │
│ BandwidthStorageManager::new(storage, client_id) │
│ ) │
│ │
│ [3] Verify Credential (multi-step) │
│ └─ allocated_bandwidth = verifier.verify().await? │
│ ↓ │
│ [Internal Steps]: │
│ ├─ Check nullifier not spent: │
│ │ SQL: SELECT COUNT(*) FROM spent_credentials │
│ │ WHERE nullifier = ? │
│ │ if count > 0: return Err(AlreadySpent) │
│ │ │
│ ├─ Verify BLS signature: │
│ │ if !bls12_381_verify( │
│ │ public_key: ecash_verifier.public_key(), │
│ │ message: hash(gateway_id + bw + expiry), │
│ │ signature: credential.signature │
│ │ ): return Err(InvalidSignature) │
│ │ │
│ ├─ Mark nullifier spent: │
│ │ SQL: INSERT INTO spent_credentials │
│ │ (nullifier, expiry, spent_at) │
│ │ VALUES (?, ?, NOW()) │
│ │ │
│ └─ Allocate bandwidth: │
│ SQL: UPDATE bandwidth │
│ SET available = available + ? │
│ WHERE client_id = ? │
│ → allocated_bandwidth = credential.bandwidth_amount │
│ │
│ [4] Update Metrics │
│ ├─ inc_by!(lp_bandwidth_allocated_bytes_total, allocated) │
│ └─ inc!(lp_credential_verification_success) │
│ │
│ [5] Return │
│ └─ Ok(allocated_bandwidth) │
│ │
└─────────────────────────────────────────────────────────────────┘
│
│ (Back to process_registration)
▼
┌─────────────────────────────────────────────────────────────────┐
│ [Build Success Response] │
├─────────────────────────────────────────────────────────────────┤
│ │
│ response = LpRegistrationResponse { │
│ success: true, │
│ error: None, │
│ gateway_data: Some(gateway_data), // dVPN only │
│ allocated_bandwidth, │
│ session_id │
│ } │
│ │
│ inc!(lp_registration_success_total) │
│ inc!(lp_registration_dvpn_success) // or mixnet │
│ observe!(lp_registration_duration_seconds, duration) │
│ │
└─────────────────────────────────────────────────────────────────┘
│
│ (Back to handler)
▼
┌─────────────────────────────────────────────────────────────────┐
│ [Send Response] │
│ gateway/src/node/lp_listener/handler.rs:177-211 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ [1] Serialize │
│ └─ response_bytes = bincode::serialize(&response)? │
│ │
│ [2] Encrypt │
│ ├─ action = state_machine.process_input( │
│ │ SendData(response_bytes) │
│ │ ) │
│ └─ packet = match action { │
│ SendPacket(p) => p, │
│ _ => unreachable!() │
│ } │
│ │
│ [3] Send │
│ └─ send_packet(stream, &packet).await? │
│ │
│ [4] Cleanup │
│ ├─ dec!(active_lp_connections) │
│ ├─ inc!(lp_connections_completed_gracefully) │
│ └─ observe!(lp_connection_duration_seconds, total_duration) │
│ │
└─────────────────────────────────────────────────────────────────┘
Code References:
- Listener:
gateway/src/node/lp_listener/mod.rs:226-289 - Handler:
gateway/src/node/lp_listener/handler.rs:101-478 - Registration:
gateway/src/node/lp_listener/registration.rs:58-404
3. Client Architecture
3.1. Client Module Structure
nym-registration-client/src/
│
└─ lp_client/
├─ mod.rs [Module exports]
├─ client.rs [Main client implementation]
│ ├─ LpRegistrationClient
│ │ ├─ new()
│ │ ├─ connect()
│ │ ├─ perform_handshake()
│ │ ├─ send_registration_request()
│ │ ├─ receive_registration_response()
│ │ └─ [private helpers]
│ │
│ ├─ send_packet() [Packet I/O]
│ └─ receive_packet()
│
└─ error.rs [Error types]
└─ LpClientError
3.2. Client Workflow
┌───────────────────────────────────────────────────────────────┐
│ Application (e.g., nym-gateway-probe, nym-vpn-client) │
└───────────────────────────────────┬───────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ [Create LP Client] │
│ nym-registration-client/src/lp_client/client.rs:64-132 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ let mut client = LpRegistrationClient::new_with_default_psk( │
│ client_lp_keypair, // X25519 keypair │
│ gateway_lp_public_key, // X25519 public (from ed25519) │
│ gateway_lp_address, // SocketAddr (IP:41264) │
│ client_ip, // Client's IP address │
│ LpConfig::default() // Timeouts, TCP_NODELAY, etc. │
│ ); │
│ │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ [1] Connect to Gateway │
│ client.rs:133-169 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ client.connect().await? │
│ ↓ │
│ stream = tokio::time::timeout( │
│ self.config.connect_timeout, // e.g., 5 seconds │
│ TcpStream::connect(self.gateway_lp_address) │
│ ).await? │
│ ↓ │
│ stream.set_nodelay(self.config.tcp_nodelay)? // true │
│ ↓ │
│ self.tcp_stream = Some(stream) │
│ │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ [2] Perform Noise Handshake │
│ client.rs:212-325 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ client.perform_handshake().await? │
│ ↓ │
│ [A] Generate ClientHello: │
│ ├─ salt = random_bytes(32) │
│ ├─ client_hello_data = ClientHelloData { │
│ │ client_public_key: self.local_keypair.public, │
│ │ salt, │
│ │ timestamp: unix_timestamp(), │
│ │ protocol_version: 1 │
│ │ } │
│ └─ packet = LpPacket { │
│ header: LpHeader { session_id: 0, seq: 0 }, │
│ message: ClientHello(client_hello_data) │
│ } │
│ │
│ [B] Send ClientHello: │
│ └─ Self::send_packet(stream, &packet).await? │
│ │
│ [C] Derive PSK: │
│ └─ psk = nym_lp::derive_psk( │
│ self.local_keypair.private, │
│ &self.gateway_public_key, │
│ &salt │
│ ) │
│ │
│ [D] Create State Machine: │
│ └─ state_machine = LpStateMachine::new( │
│ is_initiator: true, │
│ local_keypair: &self.local_keypair, │
│ remote_pubkey: &self.gateway_public_key, │
│ psk: &psk │
│ )? │
│ │
│ [E] Exchange Handshake Messages: │
│ └─ loop { │
│ match state_machine.current_state() { │
│ WaitingForHandshake => │
│ // Send initial handshake packet │
│ action = state_machine.process_input( │
│ StartHandshake │
│ )? │
│ packet = match action { │
│ SendPacket(p) => p, │
│ _ => unreachable!() │
│ } │
│ Self::send_packet(stream, &packet).await? │
│ │
│ HandshakeInProgress => │
│ // Receive gateway response │
│ packet = Self::receive_packet(stream).await? │
│ action = state_machine.process_input( │
│ ReceivePacket(packet) │
│ )? │
│ if let SendPacket(p) = action { │
│ Self::send_packet(stream, &p).await? │
│ } │
│ │
│ HandshakeComplete => │
│ break // Done! │
│ │
│ _ => return Err(...) │
│ } │
│ } │
│ │
│ [F] Store State Machine: │
│ └─ self.state_machine = Some(state_machine) │
│ │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ [3] Send Registration Request │
│ client.rs:433-507 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ client.send_registration_request( │
│ wg_public_key, │
│ bandwidth_controller, │
│ ticket_type │
│ ).await? │
│ ↓ │
│ [A] Acquire Bandwidth Credential: │
│ └─ credential = bandwidth_controller │
│ .get_ecash_ticket( │
│ ticket_type, │
│ gateway_identity, │
│ DEFAULT_TICKETS_TO_SPEND // e.g., 1 │
│ ).await? │
│ .data // CredentialSpendingData │
│ │
│ [B] Build Request: │
│ └─ request = LpRegistrationRequest::new_dvpn( │
│ wg_public_key, │
│ credential, │
│ ticket_type, │
│ self.client_ip │
│ ) │
│ │
│ [C] Serialize: │
│ └─ request_bytes = bincode::serialize(&request)? │
│ │
│ [D] Encrypt via State Machine: │
│ ├─ state_machine = self.state_machine.as_mut()? │
│ ├─ action = state_machine.process_input( │
│ │ LpInput::SendData(request_bytes) │
│ │ )? │
│ └─ packet = match action { │
│ LpAction::SendPacket(p) => p, │
│ _ => return Err(...) │
│ } │
│ │
│ [E] Send: │
│ └─ Self::send_packet(stream, &packet).await? │
│ │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ [4] Receive Registration Response │
│ client.rs:615-715 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ gateway_data = client.receive_registration_response().await? │
│ ↓ │
│ [A] Receive Packet: │
│ └─ packet = Self::receive_packet(stream).await? │
│ │
│ [B] Decrypt via State Machine: │
│ ├─ state_machine = self.state_machine.as_mut()? │
│ ├─ action = state_machine.process_input( │
│ │ LpInput::ReceivePacket(packet) │
│ │ )? │
│ └─ response_data = match action { │
│ LpAction::DeliverData(data) => data, │
│ _ => return Err(UnexpectedAction) │
│ } │
│ │
│ [C] Deserialize: │
│ └─ response = bincode::deserialize::< │
│ LpRegistrationResponse │
│ >(&response_data)? │
│ │
│ [D] Validate: │
│ ├─ if !response.success { │
│ │ return Err(RegistrationRejected { │
│ │ reason: response.error.unwrap_or_default() │
│ │ }) │
│ │ } │
│ └─ gateway_data = response.gateway_data │
│ .ok_or(MissingGatewayData)? │
│ │
│ [E] Return: │
│ └─ Ok(gateway_data) │
│ │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ [Application: Setup WireGuard Tunnel] │
├─────────────────────────────────────────────────────────────────┤
│ │
│ // Client now has: │
│ // • gateway_data.public_key (WireGuard public key) │
│ // • gateway_data.endpoint (IP:port) │
│ // • gateway_data.private_ipv4 (10.1.0.x) │
│ // • gateway_data.private_ipv6 (fd00::x) │
│ // • wg_private_key (from wg_keypair generated earlier) │
│ │
│ wg_config = format!(r#" │
│ [Interface] │
│ PrivateKey = {} │
│ Address = {}/32, {}/128 │
│ │
│ [Peer] │
│ PublicKey = {} │
│ Endpoint = {} │
│ AllowedIPs = 0.0.0.0/0, ::/0 │
│ PersistentKeepalive = 25 │
│ "#, │
│ wg_private_key, │
│ gateway_data.private_ipv4, │
│ gateway_data.private_ipv6, │
│ gateway_data.public_key, │
│ gateway_data.endpoint │
│ ) │
│ │
│ // Apply config via wg-quick or wireguard-rs │
│ wireguard_tunnel.set_config(wg_config).await? │
│ │
│ ✅ VPN tunnel established! │
│ │
└─────────────────────────────────────────────────────────────────┘
Code References:
- Client main:
nym-registration-client/src/lp_client/client.rs:39-780 - Packet I/O:
nym-registration-client/src/lp_client/client.rs:333-431
4. Shared Protocol Library
4.1. nym-lp Module Structure
common/nym-lp/src/
│
├─ lib.rs [Public API exports]
│ ├─ pub use session::*
│ ├─ pub use state_machine::*
│ ├─ pub use psk::*
│ └─ ...
│
├─ session.rs [LP session management]
│ └─ LpSession
│ ├─ new_initiator()
│ ├─ new_responder()
│ ├─ encrypt()
│ ├─ decrypt()
│ └─ [replay validation]
│
├─ state_machine.rs [Noise protocol state machine]
│ ├─ LpStateMachine
│ │ ├─ new()
│ │ ├─ process_input()
│ │ └─ current_state()
│ │
│ ├─ LpState (enum)
│ │ ├─ WaitingForHandshake
│ │ ├─ HandshakeInProgress
│ │ ├─ HandshakeComplete
│ │ └─ Failed
│ │
│ ├─ LpInput (enum)
│ │ ├─ StartHandshake
│ │ ├─ ReceivePacket(LpPacket)
│ │ └─ SendData(Vec<u8>)
│ │
│ └─ LpAction (enum)
│ ├─ SendPacket(LpPacket)
│ ├─ DeliverData(Vec<u8>)
│ └─ HandshakeComplete
│
├─ noise_protocol.rs [Noise XKpsk3 implementation]
│ └─ LpNoiseProtocol
│ ├─ new()
│ ├─ build_initiator()
│ ├─ build_responder()
│ └─ into_transport_mode()
│
├─ psk.rs [PSK derivation]
│ └─ derive_psk(secret_key, public_key, salt) -> [u8; 32]
│
├─ keypair.rs [X25519 keypair management]
│ └─ Keypair
│ ├─ generate()
│ ├─ from_bytes()
│ └─ ed25519_to_x25519()
│
├─ packet.rs [Packet structure]
│ ├─ LpPacket { header, message }
│ └─ LpHeader { session_id, seq, flags }
│
├─ message.rs [Message types]
│ └─ LpMessage (enum)
│ ├─ ClientHello(ClientHelloData)
│ ├─ Handshake(Vec<u8>)
│ ├─ EncryptedData(Vec<u8>)
│ └─ Busy
│
├─ codec.rs [Serialization]
│ ├─ serialize_lp_packet()
│ └─ parse_lp_packet()
│
└─ replay/ [Replay protection]
├─ validator.rs [Main validator]
│ └─ ReplayValidator
│ ├─ new()
│ └─ validate(nonce: u64) -> bool
│
└─ simd/ [SIMD optimizations]
├─ mod.rs
├─ avx2.rs [AVX2 bitmap ops]
├─ sse2.rs [SSE2 bitmap ops]
├─ neon.rs [ARM NEON ops]
└─ scalar.rs [Fallback scalar ops]
4.2. State Machine State Transitions
┌────────────────────────────────────────────────────────────────┐
│ LP State Machine (Initiator) │
├────────────────────────────────────────────────────────────────┤
│ │
│ [Initial State] │
│ WaitingForHandshake │
│ │ │
│ │ Input: StartHandshake │
│ │ Action: SendPacket(Handshake msg 1) │
│ ▼ │
│ HandshakeInProgress │
│ │ │
│ │ Input: ReceivePacket(Handshake msg 2) │
│ │ Action: SendPacket(Handshake msg 3) │
│ │ HandshakeComplete │
│ ▼ │
│ HandshakeComplete ──────────────────┐ │
│ │ │ │
│ │ Input: SendData(plaintext) │ Input: ReceivePacket │
│ │ Action: SendPacket(encrypted) │ Action: DeliverData │
│ └─────────────┬────────────────────┘ │
│ │ │
│ │ (stays in HandshakeComplete) │
│ │ │
│ ┌─────────────▼────────────────────────┐ │
│ │ Any state + error input: │ │
│ │ → Failed │ │
│ └──────────────────────────────────────┘ │
│ │
└────────────────────────────────────────────────────────────────┘
┌────────────────────────────────────────────────────────────────┐
│ LP State Machine (Responder) │
├────────────────────────────────────────────────────────────────┤
│ │
│ [Initial State] │
│ WaitingForHandshake │
│ │ │
│ │ Input: ReceivePacket(Handshake msg 1) │
│ │ Action: SendPacket(Handshake msg 2) │
│ ▼ │
│ HandshakeInProgress │
│ │ │
│ │ Input: ReceivePacket(Handshake msg 3) │
│ │ Action: HandshakeComplete │
│ ▼ │
│ HandshakeComplete ──────────────────┐ │
│ │ │ │
│ │ Input: SendData(plaintext) │ Input: ReceivePacket │
│ │ Action: SendPacket(encrypted) │ Action: DeliverData │
│ └─────────────┬────────────────────┘ │
│ │ │
│ │ (stays in HandshakeComplete) │
│ │ │
└────────────────────────────────────────────────────────────────┘
Code References:
- State machine:
common/nym-lp/src/state_machine.rs:96-420 - Session:
common/nym-lp/src/session.rs:45-180
5. Data Flow Diagrams
5.1. Successful dVPN Registration Data Flow
Client Gateway DB WG Controller Blockchain
│ │ │ │ │
│ [TCP Connect] │ │ │ │
├─────────────────────>│ │ │ │
│ │ │ │ │
│ [ClientHello] │ │ │ │
├─────────────────────>│ │ │ │
│ │ [validate time] │ │ │
│ │ │ │ │
│ [Noise Handshake] │ │ │ │
│<────────────────────>│ │ │ │
│ (3 messages) │ │ │ │
│ │ │ │ │
│ [Encrypted Request] │ │ │ │
│ • wg_pub_key │ │ │ │
│ • credential │ │ │ │
│ • mode: Dvpn │ │ │ │
├─────────────────────>│ │ │ │
│ │ [decrypt] │ │ │
│ │ │ │ │
│ │ [register_wg_peer] │ │
│ │ │ │ │
│ │ INSERT peer │ │ │
│ ├─────────────────>│ │ │
│ │ ← client_id: 123 │ │ │
│ │ │ │ │
│ │ INSERT bandwidth │ │ │
│ ├─────────────────>│ │ │
│ │ ← OK │ │ │
│ │ │ │ │
│ │ AddPeer request │ │ │
│ ├────────────────────────────────────────> │
│ │ │ wg set wg0 peer... │ │
│ │ │ ← OK │ │
│ │ ← AddPeer OK ────────────────────────┤ │
│ │ │ │ │
│ │ [credential_verification] │ │
│ │ │ │ │
│ │ SELECT nullifier │ │ │
│ ├─────────────────>│ │ │
│ │ ← count: 0 │ │ │
│ │ │ │ │
│ │ [verify BLS sig] │ │ │
│ │ │ │ [query │
│ │ │ │ public key]│
│ │ │ │<─────────────┤
│ │ │ │ ← pub_key ───┤
│ │ │ │ │
│ │ ✓ signature OK │ │ │
│ │ │ │ │
│ │ INSERT nullifier │ │ │
│ ├─────────────────>│ │ │
│ │ ← OK │ │ │
│ │ │ │ │
│ │ UPDATE bandwidth │ │ │
│ ├─────────────────>│ │ │
│ │ ← OK │ │ │
│ │ │ │ │
│ │ [build response] │ │ │
│ │ [encrypt] │ │ │
│ │ │ │ │
│ [Encrypted Response] │ │ │ │
│ • success: true │ │ │ │
│ • gateway_data │ │ │ │
│ • allocated_bw │ │ │ │
│<─────────────────────┤ │ │ │
│ │ │ │ │
│ [decrypt] │ │ │ │
│ ✓ Registration OK │ │ │ │
│ │ │ │ │
[Client sets up WireGuard tunnel with gateway_data]
5.2. Error Flow: Credential Already Spent
Client Gateway DB
│ │ │
│ ... (handshake)... │ │
│ │ │
│ [Encrypted Request] │ │
│ • credential │ │
│ (nullifier reused)│ │
├─────────────────────>│ │
│ │ [decrypt] │
│ │ │
│ │ [credential_verification]
│ │ │
│ │ SELECT nullifier │
│ ├─────────────────>│
│ │ ← count: 1 ✗ │
│ │ │
│ │ ✗ AlreadySpent │
│ │ │
│ │ [build error] │
│ │ [encrypt] │
│ │ │
│ [Encrypted Response] │ │
│ • success: false │ │
│ • error: "Credential│ │
│ already spent" │ │
│<─────────────────────┤ │
│ │ │
│ ✗ Registration Failed│ │
│ │ │
[Client must acquire new credential and retry]
Code References:
- Overall flow: See sequence diagrams in
LP_REGISTRATION_SEQUENCES.md - Data structures:
common/registration/src/lp_messages.rs
6. State Machines
6.1. Replay Protection State
ReplayValidator maintains sliding window for nonce validation:
┌─────────────────────────────────────────────────────────────────┐
│ ReplayValidator State │
├─────────────────────────────────────────────────────────────────┤
│ │
│ struct ReplayValidator { │
│ nonce_high: u64, // Highest seen nonce │
│ nonce_low: u64, // Lowest in window │
│ seen_bitmap: [u64; 16] // Bitmap: 1024 bits total │
│ } │
│ │
│ Window size: 1024 packets │
│ Memory: 144 bytes per session │
│ │
├─────────────────────────────────────────────────────────────────┤
│ │
│ [Validation Algorithm] │
│ │
│ validate(nonce: u64) -> Result<bool> { │
│ // Case 1: nonce too old (outside window) │
│ if nonce < nonce_low: │
│ return Ok(false) // Reject: too old │
│ │
│ // Case 2: nonce within current window │
│ if nonce <= nonce_high: │
│ offset = (nonce - nonce_low) as usize │
│ bucket_idx = offset / 64 │
│ bit_idx = offset % 64 │
│ bit_mask = 1u64 << bit_idx │
│ ↓ │
│ if seen_bitmap[bucket_idx] & bit_mask != 0: │
│ return Ok(false) // Reject: duplicate │
│ ↓ │
│ // Mark as seen (SIMD-optimized if available) │
│ seen_bitmap[bucket_idx] |= bit_mask │
│ return Ok(true) // Accept │
│ │
│ // Case 3: nonce advances window │
│ if nonce > nonce_high: │
│ advance = nonce - nonce_high │
│ ↓ │
│ if advance >= 1024: │
│ // Reset entire window │
│ seen_bitmap.fill(0) │
│ nonce_low = nonce │
│ nonce_high = nonce │
│ else: │
│ // Shift window by 'advance' bits │
│ shift_bitmap_left(&mut seen_bitmap, advance) │
│ nonce_low += advance │
│ nonce_high = nonce │
│ ↓ │
│ // Mark new nonce as seen │
│ offset = (nonce - nonce_low) as usize │
│ bucket_idx = offset / 64 │
│ bit_idx = offset % 64 │
│ seen_bitmap[bucket_idx] |= 1u64 << bit_idx │
│ return Ok(true) // Accept │
│ } │
│ │
└─────────────────────────────────────────────────────────────────┘
[Visualization of Sliding Window]
Time ──────────────────────────────────────────────────────────>
Packet nonces: 100 101 102 ... 1123 [1124 arrives]
│ │
nonce_low nonce_high
Bitmap (1024 bits):
[111111111111...111111111110000000000000000000000]
↑ bit 0 ↑ bit 1023 (most recent)
(nonce 100) (nonce 1123)
When nonce 1124 arrives:
1. Shift bitmap left by 1 bit
2. nonce_low = 101
3. nonce_high = 1124
4. Set bit 1023 (for nonce 1124)
Bitmap becomes:
[11111111111...1111111111100000000000000000000]
↑ bit 0 ↑ bit 1023
(nonce 101) (nonce 1124)
Code References:
- Replay validator:
common/nym-lp/src/replay/validator.rs:25-125 - SIMD ops:
common/nym-lp/src/replay/simd/
7. Database Schema
7.1. Gateway Database Tables
-- WireGuard peers table
CREATE TABLE wireguard_peers (
id INTEGER PRIMARY KEY AUTOINCREMENT, -- client_id
public_key BLOB NOT NULL UNIQUE, -- WireGuard public key [32 bytes]
ticket_type TEXT NOT NULL, -- "V1MixnetEntry", etc.
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
last_seen TIMESTAMP,
INDEX idx_public_key (public_key)
);
-- Bandwidth tracking table
CREATE TABLE bandwidth (
client_id INTEGER PRIMARY KEY,
available INTEGER NOT NULL DEFAULT 0, -- Bytes remaining
used INTEGER NOT NULL DEFAULT 0, -- Bytes consumed
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (client_id) REFERENCES wireguard_peers(id)
ON DELETE CASCADE
);
-- Spent credentials (nullifier tracking)
CREATE TABLE spent_credentials (
nullifier BLOB PRIMARY KEY, -- Credential nullifier [32 bytes]
expiry TIMESTAMP NOT NULL, -- Credential expiration
spent_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
client_id INTEGER, -- Optional link to client
FOREIGN KEY (client_id) REFERENCES wireguard_peers(id)
ON DELETE SET NULL,
INDEX idx_nullifier (nullifier), -- Critical for performance!
INDEX idx_expiry (expiry) -- For cleanup queries
);
-- LP session tracking (optional, for metrics/debugging)
CREATE TABLE lp_sessions (
session_id INTEGER PRIMARY KEY,
client_ip TEXT NOT NULL,
started_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
completed_at TIMESTAMP,
status TEXT, -- "success", "handshake_failed", "credential_rejected", etc.
client_id INTEGER,
FOREIGN KEY (client_id) REFERENCES wireguard_peers(id)
ON DELETE SET NULL
);
7.2. Database Operations by Component
┌─────────────────────────────────────────────────────────────┐
│ Registration Flow DB Ops │
├─────────────────────────────────────────────────────────────┤
│ │
│ [1] register_wg_peer() │
│ ├─ INSERT INTO wireguard_peers │
│ │ (public_key, ticket_type) │
│ │ VALUES (?, ?) │
│ │ RETURNING id │
│ │ → client_id │
│ │ │
│ └─ INSERT INTO bandwidth │
│ (client_id, available) │
│ VALUES (?, 0) │
│ │
│ [2] credential_verification() │
│ ├─ SELECT COUNT(*) FROM spent_credentials │
│ │ WHERE nullifier = ? │
│ │ → count (should be 0) │
│ │ │
│ ├─ INSERT INTO spent_credentials │
│ │ (nullifier, expiry, client_id) │
│ │ VALUES (?, ?, ?) │
│ │ │
│ └─ UPDATE bandwidth │
│ SET available = available + ?, │
│ updated_at = NOW() │
│ WHERE client_id = ? │
│ │
│ [3] Connection lifecycle (optional) │
│ ├─ INSERT INTO lp_sessions │
│ │ (session_id, client_ip, status) │
│ │ VALUES (?, ?, 'in_progress') │
│ │ │
│ └─ UPDATE lp_sessions │
│ SET completed_at = NOW(), │
│ status = 'success', │
│ client_id = ? │
│ WHERE session_id = ? │
│ │
└─────────────────────────────────────────────────────────────┘
[Cleanup/Maintenance Queries]
-- Remove expired nullifiers (run daily)
DELETE FROM spent_credentials
WHERE expiry < datetime('now', '-30 days');
-- Find stale WireGuard peers (not seen in 7 days)
SELECT p.id, p.public_key, p.last_seen
FROM wireguard_peers p
WHERE p.last_seen < datetime('now', '-7 days');
-- Bandwidth usage report
SELECT
p.public_key,
b.available,
b.used,
b.updated_at
FROM wireguard_peers p
JOIN bandwidth b ON b.client_id = p.id
ORDER BY b.used DESC
LIMIT 100;
Code References:
- Database models: Gateway storage module
- Queries:
gateway/src/node/lp_listener/registration.rs
8. Integration Points
8.1. External System Integration
┌──────────────────────────────────────────────────────────────┐
│ LP Registration Integrations │
├──────────────────────────────────────────────────────────────┤
│ │
│ [1] Blockchain (Nym Chain / Nyx) │
│ ├─ E-cash Contract │
│ │ ├─ Query: Get public verification keys │
│ │ ├─ Used by: EcashManager in gateway │
│ │ └─ Frequency: Cached, refreshed periodically │
│ │ │
│ └─ Mixnet Contract (optional, future) │
│ ├─ Query: Gateway info, capabilities │
│ └─ Used by: Client gateway selection │
│ │
│ [2] WireGuard Daemon │
│ ├─ Interface: Netlink / wg(8) command │
│ │ ├─ AddPeer: wg set wg0 peer <key> allowed-ips ... │
│ │ ├─ RemovePeer: wg set wg0 peer <key> remove │
│ │ └─ ListPeers: wg show wg0 dump │
│ │ │
│ ├─ Used by: WireGuard Controller (gateway) │
│ ├─ Communication: mpsc channel (async) │
│ └─ Frequency: Per registration/deregistration │
│ │
│ [3] Gateway Storage (SQLite/PostgreSQL) │
│ ├─ Tables: wireguard_peers, bandwidth, spent_credentials │
│ ├─ Used by: LP registration, credential verification │
│ ├─ Access: SQLx (async, type-safe) │
│ └─ Transactions: Required for peer registration │
│ │
│ [4] Metrics System (Prometheus) │
│ ├─ Exporter: Built into nym-node │
│ ├─ Endpoint: http://<gateway>:8080/metrics │
│ ├─ Metrics: lp_* namespace (see main doc) │
│ └─ Scrape interval: Typically 15-60s │
│ │
│ [5] BandwidthController (Client-side) │
│ ├─ Purpose: Acquire e-cash credentials │
│ ├─ Methods: │
│ │ └─ get_ecash_ticket(type, gateway, count) │
│ │ → CredentialSpendingData │
│ │ │
│ ├─ Blockchain interaction: Queries + blind signing │
│ └─ Used by: LP client before registration │
│ │
└──────────────────────────────────────────────────────────────┘
8.2. Module Dependencies
[Gateway Dependencies]
nym-node (gateway mode)
├─ gateway/src/node/lp_listener/
│ ├─ Depends on:
│ │ ├─ common/nym-lp (protocol library)
│ │ ├─ common/registration (message types)
│ │ ├─ gateway/storage (database)
│ │ ├─ gateway/wireguard (WG controller)
│ │ └─ common/bandwidth-controller (e-cash verification)
│ │
│ └─ Provides:
│ └─ LP registration service (:41264)
│
├─ gateway/src/node/wireguard/
│ ├─ Depends on:
│ │ ├─ wireguard-rs (WG tunnel)
│ │ └─ gateway/storage (peer tracking)
│ │
│ └─ Provides:
│ ├─ PeerController (mpsc handler)
│ └─ WireGuard daemon interface
│
└─ gateway/src/node/storage/
├─ Depends on:
│ └─ sqlx (database access)
│
└─ Provides:
├─ GatewayStorage trait
└─ Database operations
[Client Dependencies]
nym-vpn-client (or other app)
├─ nym-registration-client/
│ ├─ Depends on:
│ │ ├─ common/nym-lp (protocol library)
│ │ ├─ common/registration (message types)
│ │ └─ common/bandwidth-controller (credentials)
│ │
│ └─ Provides:
│ └─ LpRegistrationClient
│
├─ common/bandwidth-controller/
│ ├─ Depends on:
│ │ ├─ Blockchain RPC client
│ │ └─ E-cash cryptography
│ │
│ └─ Provides:
│ ├─ BandwidthController
│ └─ Credential acquisition
│
└─ wireguard-rs/
├─ Depends on:
│ └─ System WireGuard
│
└─ Provides:
└─ Tunnel management
[Shared Dependencies]
common/nym-lp/
├─ Depends on:
│ ├─ snow (Noise protocol)
│ ├─ x25519-dalek (ECDH)
│ ├─ chacha20poly1305 (AEAD)
│ ├─ blake3 (KDF, hashing)
│ ├─ bincode (serialization)
│ └─ tokio (async runtime)
│
└─ Provides:
├─ LpStateMachine
├─ LpSession
├─ Noise protocol
├─ PSK derivation
├─ Replay protection
└─ Message types
common/registration/
├─ Depends on:
│ ├─ serde (serialization)
│ └─ common/crypto (credential types)
│
└─ Provides:
├─ LpRegistrationRequest
├─ LpRegistrationResponse
└─ GatewayData
Code References:
- Gateway dependencies:
gateway/Cargo.toml - Client dependencies:
nym-registration-client/Cargo.toml - Protocol dependencies:
common/nym-lp/Cargo.toml
Summary
This document provides complete architectural details for:
- System Overview: High-level component interaction
- Gateway Architecture: Module structure, connection flow, data processing
- Client Architecture: Workflow from connection to WireGuard setup
- Shared Protocol Library: nym-lp module organization and state machines
- Data Flow: Successful and error case flows with database operations
- State Machines: Handshake states and replay protection
- Database Schema: Tables, indexes, and operations
- Integration Points: External systems and module dependencies
All diagrams include:
- Component boundaries
- Data flow arrows
- Code references (file:line)
- Database operations
- External system calls
Document Version: 1.0 Last Updated: 2025-11-11 Maintainer: @drazen