# CLAUDE.md — fog Codebase Guide This file provides context for AI assistants working on the fog repository. --- ## Project Overview **fog** is a privacy-preserving anonymous SMTP relay network. It implements Sphinx mixnet routing with post-quantum cryptography (Kyber-768) to provide sender anonymity, forward secrecy, and resistance to traffic analysis. Messages are routed through 3–6 encrypted hops over Tor before being delivered. - **Version**: 4.1.0 - **Language**: Go 1.21+ - **License**: MIT - **Architecture**: Single binary, monolithic source file --- ## Repository Structure ``` fog/ ├── fog.go # Entire application (~2,355 lines) — single source file ├── fog-client.sh # Interactive Bash SMTP client for end users ├── go.mod # Go module: "fog", requires go 1.21 ├── go.sum # Dependency checksums ├── README.md # User-facing documentation and quick start guide └── LICENSE # MIT License ``` There is **no Makefile, no CI/CD, no Docker configuration, and no test files** in this repository. --- ## Build & Run ### Build ```bash go mod tidy go build -tags="sqlite_omit_load_extension" -ldflags="-s -w" -trimpath -o fog fog.go ``` The `-tags="sqlite_omit_load_extension"` tag is required for the SQLite dependency. The `-ldflags="-s -w" -trimpath` flags strip debug info for smaller production binaries. ### Run (development) ```bash ./fog -name test.onion -short-name test -smtp 127.0.0.1:2525 -debug ``` ### Tests ```bash go test ./... ``` Note: There are currently no test files in the repository. ### Export node identity (first-time setup) ```bash ./fog -name your-onion-address.onion -short-name yourname -export-node-info ``` --- ## Command-Line Flags | Flag | Default | Description | |------|---------|-------------| | `-name` | `fog.onion` | Node hostname (.onion address) | | `-short-name` | — | Short label for logs | | `-smtp` | `127.0.0.1:2525` | SMTP listen address | | `-node` | `127.0.0.1:9999` | Sphinx node listen address | | `-sphinx` | `false` | Enable Sphinx mixnet routing | | `-pki` | — | Path to bootstrap PKI file (`nodes.json`) | | `-key` | — | Path to persistent node key file | | `-debug` | `false` | Enable verbose debug logging | | `-export-node-info` | — | Print node info JSON and exit | | `-version` | — | Print version and feature list, then exit | --- ## Dependencies Defined in `go.mod`: | Package | Version | Purpose | |---------|---------|---------| | `github.com/mattn/go-sqlite3` | v1.14.18 | SQLite driver for persistent message queue | | `golang.org/x/crypto` | v0.17.0 | HKDF key derivation | | `golang.org/x/net` | v0.19.0 | SOCKS5 proxy support (Tor) | | `github.com/symbolicsoft/kyber-k2so` | (indirect) | Kyber-768 post-quantum KEM | The `kyber-k2so` package is imported directly in `fog.go:54` as `kyberk2so` but is not listed in `go.mod` — run `go mod tidy` to resolve this if needed. --- ## Source Code Architecture (`fog.go`) The entire application lives in `fog.go`. It is organized into clearly delimited sections marked by banner comments: ``` // ============================================================================= // SECTION NAME // ============================================================================= ``` ### Key Sections and Approximate Line Ranges | Section | Approx. Lines | Description | |---------|--------------|-------------| | Constants & imports | 1–100 | Protocol constants, sizing, intervals | | Global state | ~100–160 | Package-level vars (`pki`, `pool`, `queue`, `stats`, etc.) | | PKI / Node types | ~160–400 | `Node`, `PKI`, gossip, health state | | Sphinx cryptography | ~400–900 | Kyber-768 KEM, packet creation/processing, HMAC | | Batch pool | ~900–1000 | Threshold mixing (`BatchPool`, `batchWorker`) | | Cover traffic | ~1000–1100 | Dummy message generation (`CoverTraffic`, `coverWorker`) | | SMTP server | ~1100–1350 | `startSMTP`, `handleSMTP`, ESMTP protocol | | Node server | ~1350–1500 | `startNodeServer`, `handleNode`, Sphinx forwarding | | Message relay | ~1500–1750 | `relayWorker`, `processMessage`, `deliverMessage` | | Header sanitization | ~1750–1850 | `sanitizeHeaders` removes identifying metadata | | DNS via Tor | ~1850–1950 | `lookupMXViaTor`, manual DNS MX query over SOCKS5 | | Replay cache | ~1950–2050 | `ReplayCache`, TTL-based deduplication | | Health checker | ~2050–2100 | `healthChecker`, `checkNode`, `checkAllNodes` | | Statistics | ~2100–2120 | `statsMonitor`, periodic log output | | Node init | ~2120–2165 | `initNode`, key generation/loading | | `main()` | ~2170–2355 | Entry point, flag parsing, goroutine orchestration | ### Core Types ```go // Node represents a fog network participant type Node struct { ID string // hex-encoded SHA-256 of public key PublicKey []byte // Kyber-768 public key (1184 bytes) Address string // host:port (.onion:9999) Name string // human-readable short name Version string LastSeen time.Time Healthy bool } // Message is the internal relay unit type Message struct { From string To []string Data []byte // raw SMTP DATA content IsRelay bool // true = Sphinx packet, false = original message Payload []byte // encoded Sphinx packet when IsRelay=true } ``` ### Critical Constants (`fog.go:59–99`) ```go KyberPKSize = 1184 // Kyber-768 public key bytes KyberCTSize = 1088 // Kyber-768 ciphertext bytes HeaderSize = 1232 // Sphinx header: CT(1088) + routing(128) + MAC(16) PayloadMax = 64*1024 // Fixed 64 KB packet size (prevents size correlation) MinHops = 3 MaxHops = 6 BatchThresholdMin = 5 BatchThresholdMax = 15 ``` ### Goroutine Map The `main()` function starts the following goroutines (when `-sphinx` is enabled): | Goroutine | Function | Purpose | |-----------|----------|---------| | Node server | `startNodeServer` | Accepts Sphinx packets from peers | | Health checker | `healthChecker` | Polls nodes every 3 minutes | | Batch worker | `batchWorker` | Flushes pool when threshold reached | | Gossip worker | `gossipWorker` | Exchanges PKI state every 5 minutes | | Cover worker | `coverWorker` | Sends dummy traffic at random intervals | | Relay workers (×3) | `relayWorker` | Processes message queue | | Stats monitor | `statsMonitor` | Logs metrics every 60 seconds | | Cache cleanup | `cacheCleanupWorker` | Evicts expired replay-cache entries | | PKI state saver | (inline) | Persists gossip state every 10 minutes | --- ## Runtime Files | File | Description | |------|-------------| | `nodes.json` | Bootstrap PKI — hand-crafted, **never overwritten** by fog | | `nodes_state.json` | Dynamic gossip state, auto-generated alongside `nodes.json` | | `/messages.db` | SQLite message queue, survives restarts | --- ## Protocol & Data Flow ``` Client │ SMTP (port 2525) ▼ Entry node │ Sphinx packet (Kyber-768, 3–6 hops, over Tor SOCKS5) ▼ Middle node(s) │ Each hop decrypts one layer, adds random delay, batches with others ▼ Exit node │ Final decryption → sanitize headers → MX lookup via Tor → deliver ▼ Destination SMTP server ``` **Key invariants:** - All inter-node traffic goes through Tor (SOCKS5 at `127.0.0.1:9050`) - All packets are padded to exactly 64 KB - DNS MX lookups are performed manually over Tor (no system resolver used) - The SMTP envelope (`MAIL FROM`, `RCPT TO`) is embedded in the Sphinx payload, not in headers --- ## Coding Conventions - **Single-file architecture**: All code lives in `fog.go`. Do not create new `.go` files unless strictly necessary. - **Package**: `package main` — this is a standalone binary, not a library. - **Error handling**: Errors are logged with structured prefixes like `[SMTP]`, `[PKI]`, `[SPHINX]`, `[HEALTH]`, `[POOL]`, `[STATS]`, `[FOG]`. Use the same prefix conventions when adding log lines. - **Concurrency**: Goroutines communicate via the `queue` channel (`chan *Message`, capacity 500). Shared state uses `sync.Mutex` or `sync/atomic`. Context cancellation (`ctx`) signals graceful shutdown. - **No external frameworks**: Standard library + the four listed dependencies only. No HTTP frameworks, CLI libraries, or ORMs. - **Security-sensitive code**: Cryptographic operations use constant-time comparisons (`hmac.Equal`) and `crypto/rand` exclusively. Never use `math/rand` for security purposes. --- ## Log Prefixes Reference | Prefix | Component | |--------|-----------| | `[FOG]` | Main / startup | | `[SMTP]` | SMTP server | | `[NODE]` | Sphinx node server | | `[PKI]` | PKI / gossip | | `[SPHINX]` | Packet creation/processing | | `[HEALTH]` | Node health checker | | `[POOL]` | Batch pool | | `[COVER]` | Cover traffic | | `[RELAY]` | Message relay worker | | `[DELIVER]` | Final delivery | | `[SANITIZE]` | Header sanitization | | `[DNS]` | Tor DNS lookup | | `[STATS]` | Statistics | | `[CACHE]` | Replay cache | | `[TOR]` | Tor SOCKS5 dialer | --- ## Security Constraints When modifying this codebase, preserve these invariants: 1. **No DNS leaks**: MX lookups must go through `lookupMXViaTor`, never `net.LookupMX`. 2. **No plaintext logs**: Never log message content, sender/recipient addresses in production paths. 3. **Fixed packet size**: Sphinx payloads must remain padded to `PayloadMax` (64 KB). 4. **Constant-time MAC verification**: Use `hmac.Equal`, not `==`, for HMAC comparisons. 5. **Ephemeral keys only**: Each Sphinx packet uses freshly generated Kyber keypairs — never reuse keys. 6. **Header sanitization**: The exit node must always call `sanitizeHeaders` before final delivery. --- ## Monitoring the Queue (SQLite) ```bash # Total queued messages sqlite3 /var/lib/fog/data/messages.db \ "SELECT COUNT(*) FROM message_queue;" # Messages ready to send sqlite3 /var/lib/fog/data/messages.db \ "SELECT COUNT(*) FROM message_queue WHERE send_after <= strftime('%s','now');" ``` --- ## Stats Log Format ``` [STATS] Up:2h30m R:45 S:42 F:3 | Sphinx:40 Direct:2 | Mix R:120 F:115 | Q:23 D:156 | Healthy:4 ``` | Field | Meaning | |-------|---------| | `R` | Messages received | | `S` | Messages sent successfully | | `F` | Failed deliveries | | `Sphinx` / `Direct` | Routing method used | | `Mix R` / `Mix F` | Sphinx packets received / forwarded | | `Q` | Currently queued in delay pool | | `D` | Total delayed messages delivered | | `Healthy` | Healthy nodes in PKI |