Skip to content

Commit fbcc9e4

Browse files
committed
lp-reg gw flow working-ish
1 parent 55e891a commit fbcc9e4

File tree

20 files changed

+885
-155
lines changed

20 files changed

+885
-155
lines changed

Cargo.lock

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

common/nym-lp/src/keypair.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ impl PrivateKey {
3939
Ok(PrivateKey(SphinxPrivateKey::from(bytes)))
4040
}
4141

42+
pub fn from_bytes(bytes: &[u8; 32]) -> Self {
43+
PrivateKey(SphinxPrivateKey::from(*bytes))
44+
}
45+
4246
pub fn public_key(&self) -> PublicKey {
4347
let public_key = SphinxPublicKey::from(&self.0);
4448
PublicKey(public_key)
@@ -102,6 +106,21 @@ impl Keypair {
102106
}
103107
}
104108

109+
pub fn from_private_key(private_key: PrivateKey) -> Self {
110+
let public_key = private_key.public_key();
111+
Self {
112+
private_key,
113+
public_key,
114+
}
115+
}
116+
117+
pub fn from_keys(private_key: PrivateKey, public_key: PublicKey) -> Self {
118+
Self {
119+
private_key,
120+
public_key,
121+
}
122+
}
123+
105124
pub fn private_key(&self) -> &PrivateKey {
106125
&self.private_key
107126
}

common/nym-lp/src/state_machine.rs

Lines changed: 28 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -225,37 +225,37 @@ impl LpStateMachine {
225225
// LpState::Closed { reason }
226226
LpState::Handshaking { session }
227227
} else {
228-
// 4. Check if handshake is now complete
229-
if session.is_handshake_complete() {
230-
result_action = Some(Ok(LpAction::HandshakeComplete));
231-
LpState::Transport { session } // Transition to Transport
232-
} else {
233-
// 5. Check if we need to send the next handshake message
234-
match session.prepare_handshake_message() {
235-
Some(Ok(message)) => {
236-
match session.next_packet(message) {
237-
Ok(response_packet) => {
238-
result_action = Some(Ok(LpAction::SendPacket(response_packet)));
239-
// Check AGAIN if handshake became complete *after preparing*
240-
if session.is_handshake_complete() {
241-
LpState::Transport { session } // Transition to Transport
242-
} else {
243-
LpState::Handshaking { session } // Remain Handshaking
244-
}
245-
}
246-
Err(e) => {
247-
let reason = e.to_string();
248-
result_action = Some(Err(e));
249-
LpState::Closed { reason }
228+
// 4. First check if we need to send a handshake message (before checking completion)
229+
match session.prepare_handshake_message() {
230+
Some(Ok(message)) => {
231+
match session.next_packet(message) {
232+
Ok(response_packet) => {
233+
result_action = Some(Ok(LpAction::SendPacket(response_packet)));
234+
// Check if handshake became complete after preparing message
235+
if session.is_handshake_complete() {
236+
LpState::Transport { session } // Transition to Transport
237+
} else {
238+
LpState::Handshaking { session } // Remain Handshaking
250239
}
251240
}
241+
Err(e) => {
242+
let reason = e.to_string();
243+
result_action = Some(Err(e));
244+
LpState::Closed { reason }
245+
}
252246
}
253-
Some(Err(e)) => {
254-
let reason = e.to_string();
255-
result_action = Some(Err(e));
256-
LpState::Closed { reason }
257-
}
258-
None => {
247+
}
248+
Some(Err(e)) => {
249+
let reason = e.to_string();
250+
result_action = Some(Err(e));
251+
LpState::Closed { reason }
252+
}
253+
None => {
254+
// 5. No message to send - check if handshake is complete
255+
if session.is_handshake_complete() {
256+
result_action = Some(Ok(LpAction::HandshakeComplete));
257+
LpState::Transport { session } // Transition to Transport
258+
} else {
259259
// Handshake stalled unexpectedly
260260
let err = LpError::NoiseError(NoiseError::Other(
261261
"Handshake stalled unexpectedly".to_string(),

common/wireguard/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ pub struct WireguardData {
164164
/// Start wireguard device
165165
#[cfg(target_os = "linux")]
166166
pub async fn start_wireguard(
167-
ecash_manager: Arc<EcashManager>,
167+
ecash_manager: Arc<dyn nym_credential_verification::ecash::traits::EcashManager + Send + Sync>,
168168
metrics: nym_node_metrics::NymNodeMetrics,
169169
peers: Vec<Peer>,
170170
upgrade_mode_status: nym_credential_verification::upgrade_mode::UpgradeModeStatus,

docker/localnet/README.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ Ports published to host:
8181
- 10001-10004 → Mixnet ports
8282
- 20001-20004 → Verloc ports
8383
- 30001-30004 → HTTP APIs
84+
- 41264 → LP control port (registration)
85+
- 51264 → LP data port
8486

8587
### Startup Flow
8688

@@ -246,6 +248,41 @@ container logs nym-gateway --follow &
246248
curl -L --socks5 localhost:1080 https://nymtech.com
247249
```
248250

251+
### LP (Lewes Protocol) Testing
252+
253+
The gateway is configured with LP listener enabled and **mock ecash verification** for testing:
254+
255+
```bash
256+
# LP listener ports (exposed on host):
257+
# - 41264: LP control port (TCP registration)
258+
# - 51264: LP data port
259+
260+
# Check LP ports are listening
261+
nc -zv localhost 41264
262+
nc -zv localhost 51264
263+
264+
# Test LP registration with nym-gateway-probe
265+
cargo run -p nym-gateway-probe run-local \
266+
--mnemonic "test mnemonic here" \
267+
--gateway-ip 'localhost:41264' \
268+
--only-lp-registration
269+
```
270+
271+
**Mock Ecash Mode**:
272+
- Gateway uses `--lp.use-mock-ecash true` flag
273+
- Accepts ANY bandwidth credential without blockchain verification
274+
- Perfect for testing LP protocol implementation
275+
- **WARNING**: Never use mock ecash in production!
276+
277+
**Testing without blockchain**:
278+
The mock ecash manager allows testing the complete LP registration flow without requiring:
279+
- Running nyxd blockchain
280+
- Deploying smart contracts
281+
- Acquiring real bandwidth credentials
282+
- Setting up coconut signers
283+
284+
This makes localnet perfect for rapid LP protocol development and testing.
285+
249286
## File Structure
250287

251288
```

docker/localnet/localnet.sh

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,8 @@ start_gateway() {
230230
-p 10004:10004 \
231231
-p 20004:20004 \
232232
-p 30004:30004 \
233+
-p 41264:41264 \
234+
-p 51264:51264 \
233235
-v "$VOLUME_PATH:/localnet" \
234236
-v "$NYM_VOLUME_PATH:/root/.nym" \
235237
-d \
@@ -250,14 +252,17 @@ start_gateway() {
250252
--http-bind-address=0.0.0.0:30004 \
251253
--http-access-token=lala \
252254
--public-ips $CONTAINER_IP \
255+
--enable-lp true \
256+
--lp-use-mock-ecash true \
253257
--output=json \
258+
--wireguard-enabled true \
254259
--bonding-information-output="/localnet/gateway.json";
255260
256261
echo "Waiting for network.json...";
257262
while [ ! -f /localnet/network.json ]; do
258263
sleep 2;
259264
done;
260-
echo "Starting gateway...";
265+
echo "Starting gateway with LP listener (mock ecash)...";
261266
exec nym-node run --id gateway-localnet --unsafe-disable-replay-protection --local
262267
'
263268

@@ -429,13 +434,36 @@ show_status() {
429434

430435
echo ""
431436
log_info "Port status:"
432-
for port in 9000 1080 10001 10002 10003 10004; do
437+
echo " Mixnet:"
438+
for port in 10001 10002 10003 10004; do
433439
if nc -z 127.0.0.1 $port 2>/dev/null; then
434-
echo -e " ${GREEN}${NC} Port $port - listening"
440+
echo -e " ${GREEN}${NC} Port $port - listening"
435441
else
436-
echo -e " ${RED}${NC} Port $port - not listening"
442+
echo -e " ${RED}${NC} Port $port - not listening"
437443
fi
438444
done
445+
echo " Gateway:"
446+
for port in 9000 30004; do
447+
if nc -z 127.0.0.1 $port 2>/dev/null; then
448+
echo -e " ${GREEN}${NC} Port $port - listening"
449+
else
450+
echo -e " ${RED}${NC} Port $port - not listening"
451+
fi
452+
done
453+
echo " LP (Lewes Protocol):"
454+
for port in 41264 51264; do
455+
if nc -z 127.0.0.1 $port 2>/dev/null; then
456+
echo -e " ${GREEN}${NC} Port $port - listening"
457+
else
458+
echo -e " ${RED}${NC} Port $port - not listening"
459+
fi
460+
done
461+
echo " SOCKS5:"
462+
if nc -z 127.0.0.1 1080 2>/dev/null; then
463+
echo -e " ${GREEN}${NC} Port 1080 - listening"
464+
else
465+
echo -e " ${RED}${NC} Port 1080 - not listening"
466+
fi
439467
}
440468

441469
# Build network topology with container IPs

gateway/src/node/lp_listener/handler.rs

Lines changed: 31 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use super::registration::process_registration;
77
use super::LpHandlerState;
88
use crate::error::GatewayError;
99
use nym_lp::{
10-
keypair::{Keypair, PublicKey},
10+
keypair::{Keypair, PrivateKey as LpPrivateKey, PublicKey},
1111
LpMessage, LpPacket, LpSession,
1212
};
1313
use nym_metrics::{add_histogram_obs, inc};
@@ -109,10 +109,21 @@ impl LpConnectionHandler {
109109
// 2. Client's public key (will be received during handshake)
110110
// 3. PSK (pre-shared key) - for now use a placeholder
111111

112-
// Generate fresh LP keypair (x25519) for this connection
113-
// Using Keypair::default() which generates a new random x25519 keypair
114-
// This is secure and simple - each connection gets its own keypair
115-
let gateway_keypair = Keypair::default();
112+
// Derive LP keypair from gateway's ed25519 identity using proper conversion
113+
// This creates a valid x25519 keypair for ECDH operations in Noise protocol
114+
let x25519_private = self.state.local_identity.private_key().to_x25519();
115+
let x25519_public = self.state.local_identity.public_key().to_x25519()
116+
.map_err(|e| GatewayError::LpHandshakeError(
117+
format!("Failed to convert ed25519 public key to x25519: {}", e)
118+
))?;
119+
120+
let lp_private = LpPrivateKey::from_bytes(x25519_private.as_bytes());
121+
let lp_public = PublicKey::from_bytes(x25519_public.as_bytes())
122+
.map_err(|e| GatewayError::LpHandshakeError(
123+
format!("Failed to create LP public key: {}", e)
124+
))?;
125+
126+
let gateway_keypair = Keypair::from_keys(lp_private, lp_public);
116127

117128
// Receive client's public key and salt via ClientHello message
118129
// The client initiates by sending ClientHello as first packet
@@ -289,11 +300,7 @@ impl LpConnectionHandler {
289300
.duration_since(UNIX_EPOCH)
290301
.expect("System time before UNIX epoch")
291302
.as_secs();
292-
if now >= timestamp {
293-
now - timestamp
294-
} else {
295-
timestamp - now
296-
}
303+
now.abs_diff(timestamp)
297304
},
298305
self.state.lp_config.timestamp_tolerance_secs
299306
);
@@ -333,22 +340,20 @@ impl LpConnectionHandler {
333340
)));
334341
}
335342

336-
// Extract registration request from LP message
337-
match packet.message() {
338-
LpMessage::EncryptedData(data) => {
339-
// Deserialize registration request
340-
bincode::deserialize(&data).map_err(|e| {
341-
GatewayError::LpProtocolError(format!(
342-
"Failed to deserialize registration request: {}",
343-
e
344-
))
345-
})
346-
}
347-
other => Err(GatewayError::LpProtocolError(format!(
348-
"Expected EncryptedData message, got {:?}",
349-
other
350-
))),
351-
}
343+
// Decrypt the packet payload using the established session
344+
let decrypted_bytes = session
345+
.decrypt_data(packet.message())
346+
.map_err(|e| {
347+
GatewayError::LpProtocolError(format!("Failed to decrypt registration request: {}", e))
348+
})?;
349+
350+
// Deserialize the decrypted bytes into LpRegistrationRequest
351+
bincode::deserialize(&decrypted_bytes).map_err(|e| {
352+
GatewayError::LpProtocolError(format!(
353+
"Failed to deserialize registration request: {}",
354+
e
355+
))
356+
})
352357
}
353358

354359
/// Send registration response after processing

gateway/src/node/lp_listener/mod.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,17 +108,29 @@ pub struct LpConfig {
108108
/// Recommended: 30-60 seconds
109109
#[serde(default = "default_timestamp_tolerance_secs")]
110110
pub timestamp_tolerance_secs: u64,
111+
112+
/// Use mock ecash manager for testing (default: false)
113+
///
114+
/// When enabled, the LP listener will use a mock ecash verifier that
115+
/// accepts any credential without blockchain verification. This is
116+
/// useful for testing the LP protocol implementation without requiring
117+
/// a full blockchain/contract setup.
118+
///
119+
/// WARNING: Only use this for local testing! Never enable in production.
120+
#[serde(default = "default_use_mock_ecash")]
121+
pub use_mock_ecash: bool,
111122
}
112123

113124
impl Default for LpConfig {
114125
fn default() -> Self {
115126
Self {
116-
enabled: false,
127+
enabled: true,
117128
bind_address: default_bind_address(),
118129
control_port: default_control_port(),
119130
data_port: default_data_port(),
120131
max_connections: default_max_connections(),
121132
timestamp_tolerance_secs: default_timestamp_tolerance_secs(),
133+
use_mock_ecash: default_use_mock_ecash(),
122134
}
123135
}
124136
}
@@ -143,6 +155,10 @@ fn default_timestamp_tolerance_secs() -> u64 {
143155
30 // 30 seconds - balances security vs clock skew tolerance
144156
}
145157

158+
fn default_use_mock_ecash() -> bool {
159+
false // Always default to real ecash for security
160+
}
161+
146162
/// Shared state for LP connection handlers
147163
#[derive(Clone)]
148164
pub struct LpHandlerState {

gateway/src/node/lp_listener/registration.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -315,8 +315,8 @@ async fn register_wg_peer(
315315
.unwrap_or_else(|_| SocketAddr::from_str("0.0.0.0:51820").unwrap()),
316316
);
317317
peer.allowed_ips = vec![
318-
format!("{}/32", client_ipv4).parse().unwrap(),
319-
format!("{}/128", client_ipv6).parse().unwrap(),
318+
format!("{client_ipv4}/32").parse().unwrap(),
319+
format!("{client_ipv6}/128").parse().unwrap(),
320320
];
321321
peer.persistent_keepalive_interval = Some(25);
322322

0 commit comments

Comments
 (0)