1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
|
# 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` |
| `<data-dir>/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 |
|