Go Blind Relay Protocol & Server Architecture
Comprehensive code analysis of the Go Blind Relay: Ed25519 GATT Challenge-AUTH, dynamic RAM load fallbacks, token-gated S3 file streaming, token bucket flood guards, and proxy security.
Interactive Relay Storage Routing & RAM Pressure Engine
Adjust system RAM load and message payload size below to observe how the relay dynamically routes traffic between Redis Sorted Sets, Postgres Inline Storage, and S3 Object Storage.
1. Zero-Knowledge Architecture & Identity Derivation
The Wiltkey Blind Relay acts as a blind mailbox. It coordinates client routing and temporary message queuing without acquiring metadata about relationships, user identities, or plaintext contents. Authentication relies entirely on client Ed25519 identity keypairs exchanged face-to-face over BLE.
Opaque User ID Generation
The relay identifies users exclusively by the hex-encoded digest of SHA-256(Ed25519_pubkey). There are no registration forms, email requirements, phone numbers, or persistent user accounts on the relay:
2. GATT Ed25519 Challenge-Response Auth Sequence
When a client connects to the WebSocket endpoint (/ws), the server executes `ServeWS` (in websocket.go). Connections must prove ownership of their identity public key within 5 seconds before any message routing is permitted.
AUTH_OK Capability Advertising
Upon verification, the relay responds with AUTH_OK and advertises server capabilities (such as group_fanout). This allows client devices to enable single-upload group delivery when supported by the connected relay while retaining backwards compatibility with self-hosted instances.
3. Three-Tiered Storage & RAM Load Fallback Engine
When routing a message (via WebSocket SEND_MESSAGE or REST POST /api/v1/queue/post), the relay executes a three-tiered storage decision based on payload size and system memory pressure:
Tier 1: Redis Sorted Set Mailbox (<500 KB, RAM <80%)
Under normal conditions, offline messages under 500 KB are stored in Redis sorted sets keyed by recipient ID. To prevent memory exhaustion attacks, each recipient queue is bounded by maxQueueLen = 1000. When exceeded, the oldest entries are dropped. Offline hold TTL is keyed on recipient subscription tier (24 hours for free recipients, 72 hours for Plus subscribers).
Tier 2: Postgres Inline Fallback (<500 KB, RAM ≥80%)
If system memory load rises to 80% or above (monitored by checkMemoryPressure() via v.UsedPercent >= thresholdPercent in main.go:L845), the relay automatically bypasses Redis RAM and writes offline messages directly into Postgres inline table storage (pg.StoreMessageInline) to protect server RAM.
Tier 3: Token-Gated S3 Object Storage (≥500 KB up to 50 MB)
For file payloads ≥500 KB (up to the 50 MB Plus limit), the relay uploads the encrypted envelope to S3-compatible Object Storage (storage.Upload). It strips the bulky d ciphertext field to build a lightweight FILE_OFFER frame (metadata only):
- The recipient receives a
FILE_OFFERWebSocket frame containing message ID, sender ID, size, and metadata. - When the recipient taps to download, the client sends a
REQUEST_FILEframe. - The relay verifies authorization, issues a short-lived download token, and returns a
FILE_TOKENframe. - The client streams the file via HTTP
GET /api/v1/file?token=...with progress bars. - The S3 object and Postgres metadata row are deleted ONLY after the recipient's client issues a
FILE_RECEIVEDWebSocket ACK.
4. Abuse Protections, Token Bucket Flood Guards & Reverse Proxy Safety
Because clients authenticate using Ed25519 identity keypairs and stamps are crypto-verified, senders cannot forge their `sender_id`. To protect against availability/DoS attacks, the relay implements multi-layered guards:
Per-Socket Token Bucket Flood Guard
Each authenticated WebSocket connection runs a token bucket in `readPump` (in websocket.go:L27–L30):
Reverse Proxy Real IP Resolution (`clientIP()`)
HTTP rate limits and bans are keyed on client IP. When the relay sits behind a reverse proxy (e.g. Nginx, Caddy), every connection arrives from 127.0.0.1 unless headers are forwarded. In `main.go`, `clientIP()` verifies that the direct peer is loopback before trusting X-Real-IP or X-Forwarded-For:
If the reverse proxy fails to set
X-Real-IP or if the relay trusts untrusted headers from external peers, all users share a single rate-limit bucket. One abuser will throttle all users, and a single IP ban will lock out the entire userbase.
5. Function-to-Function Server Code Walkthrough
| Go Symbol | File Location | Description & Key Behavior |
|---|---|---|
ServeWS() |
websocket.go:L382 |
Upgrades HTTP connection to WS, issues 32B random challenge, verifies Ed25519 AUTH signature, registers client in Hub. |
handleSendMessage() |
handlers.go:L17 |
Enforces 50MB ceiling, 5MB premium gate, checks RAM load, uploads ≥500KB payloads to S3, or enqueues in Redis/Postgres. |
handleBroadcastGroupMessage() |
handlers.go:L142 |
Processes single-upload group messages, delivering the payload to each recipient socket/queue with zero-knowledge blindness. |
handleRequestFile() & handleFileReceived() |
handlers.go:L310 |
Mints short-lived download tokens for S3 streaming and processes `FILE_RECEIVED` ACKs to purge stored objects. |
AddMessageToQueue() |
redis.go:L150 |
Enqueues small offline envelopes in Redis Sorted Sets; enforces `maxQueueLen = 1000` cap to prevent memory exhaustion. |
clientIP() |
main.go:L790 |
Resolves real client IP by checking loopback peer status before evaluating `X-Real-IP` / `X-Forwarded-For` headers. |