Relay traffic over the mixnet
Summary. Goblin gives the Nostr relay pool a custom websocket transport that dials every relay through the local Nym SOCKS5 proxy. The proxy resolves the relay host inside the mixnet (
socks5h-style, no clearnet DNS), then the TLS + websocket handshake runs over that tunnel.
Motivation
The nostr-sdk relay pool normally opens websockets directly. To put relay traffic on the mixnet without forking the SDK, Goblin implements the SDK’s WebSocketTransport trait with its own connector. This is the clean seam: the entire rest of the Nostr layer is unchanged; only how a socket is opened differs.
How it works
For each relay URL the pool wants to connect to, NymWebSocketTransport::connect:
- Parses the host and port (defaulting 80 for
ws://, 443 forwss://). - Opens a SOCKS5 connection to
127.0.0.1:1080and asks the proxy to reach(host, port). Because the proxy does the DNS resolution inside the mixnet, the destination host is never resolved on the clear. - Runs the TLS (for
wss) and websocket handshake over that mixnet stream. - Splits the socket into a sink (writes) and a stream (reads), adapting tungstenite messages to the pool’s message type.
All of this is wrapped in the pool’s connect timeout. The result is an ordinary websocket from the SDK’s point of view: it just happens to traverse five mixnet hops.
Reference
In goblin/src/nym/transport.rs:
NymWebSocketTransportimplementsnostr_relay_pool::transport::websocket::WebSocketTransport;support_ping()istrue.connect(): host/port parse,tokio_socks::tcp::Socks5Stream::connect(socks5_addr, (host, port)), thentokio_tungstenite::client_async_tls(url, stream), split intoWebSocketSink/WebSocketStream.tg_to_message(): maps tungsteniteText/Binary/Ping/Pong/Closeto the pool’sMessage.NymSink: sink adapter converting pool messages back to tungstenite messages.- The SOCKS5 address comes from
crate::nym::socks5_addr()(127.0.0.1:1080).
References
- The proxy that serves
:1080: The in-process mixnet client. - The pool that uses this transport: The NostrService.
socks5h(DNS-in-proxy) rationale: HTTP over the mixnet.