Running a BlindHop Exit Service
Overview
BlindHop's exit service (blindhop-exit) runs as a Nym Service Provider, receiving anonymous traffic from the mixnet and forwarding JSON-RPC requests to Substrate full nodes.
Requirements
- Server with stable internet connection
- Rust toolchain (stable, edition 2024)
- Access to a Substrate full node (direct or via public RPC endpoint)
Quick Start
cargo build -p blindhop-exit --release
./target/release/blindhop-exit \
--target-rpc wss://sys.turboflakes.io/asset-hub-paseo \
--data-dir .blindhop-exit
The exit service will:
- Connect to the Nym mixnet as a Service Provider
- Store cryptographic identity keys in
.blindhop-exit/(keeping its address stable across restarts) - Print its Nym address to stdout and write it to
.exit_nym_address - Begin processing incoming requests according to its security policy
Production Deployment (systemd)
For running in production on a server (e.g. Ubuntu 24.04 on Oracle Cloud / Hetzner), automated scripts are provided in the deploy/ directory:
# 1. Build exit binary with --locked against audited Cargo.lock
./deploy/build-exit.sh
# 2. Install systemd service, blindhop user, and start service
sudo ./deploy/install-exit.sh --target-rpc wss://sys.turboflakes.io/asset-hub-paseo
# 3. Back up cryptographic keys (archive saved to a secure location)
sudo ./deploy/backup-exit-keys.sh
Hardening & Resource Limits
The provided systemd unit (deploy/blindhop-exit.service) applies defense-in-depth isolation:
- System Isolation: Runs as an unprivileged
blindhopsystem user withProtectSystem=strict, read-only filesystem (only/var/lib/blindhop-exitis writable), private/tmp, and no ambient capabilities. - Resource Limits: Capped at
MemoryMax=320Mand 1 CPU core (CPUQuota=100%,CPUWeight=50). - Network & Syscalls: Restricted to
@system-servicesyscall filter and IPv4/IPv6/Unix sockets only.
Exit Policy & Concurrency
To protect both the exit operator and the upstream Substrate full node:
- Method Allowlist: Forwards only single requests (with a numeric or string
id) for an explicit list of read-only methods — chain info, blocks, read-only state (e.g.state_getStorage,state_getMetadata,state_call), fee queries,system_accountNextIndex— plusauthor_submitExtrinsic. Batches, notifications, subscriptions, node-administration/identifying methods (author_insertKey,author_rotateKeys,system_peers,system_localPeerId) and unbounded queries (state_getKeys,state_getPairs) are rejected. The full list isALLOWED_METHODSinexit/src/policy.rs. - Size Limits: Accepts requests up to 1 MiB and responses up to 4 MiB.
- Concurrency Limiting: Max 16 concurrent requests globally (max 8 per anonymous client tag). Requests wait up to 5s in a 32-capacity queue before being answered with
BUSY -32005. - Connection Pooling: Upstream WebSocket connections are pooled (
UpstreamPool), matching responses by JSON-RPC ID. - Self-Healing Watchdog: The exit sends itself a liveness probe through the mixnet every 60s; if no probe returns within 180s (e.g. gateway disconnect), the process exits with an error so systemd restarts it.
Future Plans
- Community-operated exits — registry of available exit services
- Multi-chain support — route to different chains based on message metadata
- Load balancing — distribute across multiple full nodes