From 25356debcce4118cdfa86842029278fde1e64518 Mon Sep 17 00:00:00 2001 From: Gab <24553253+gabrix73@users.noreply.github.com> Date: Tue, 18 Aug 2026 21:43:48 +0200 Subject: Publish FOG design documentation and Merkle tree --- LICENSE | 21 - README.md | 737 +++---------- docs/FOG-ALPHA.md | 184 ++++ docs/FOG-ARCHITECTURE.md | 1122 +++++++++++++++++++ docs/FOG-COMPOSER.md | 1677 ++++++++++++++++++++++++++++ docs/FOG-CRYPTO-BENCHMARKS.md | 182 ++++ docs/FOG-CRYPTO-SUITES.md | 833 ++++++++++++++ docs/FOG-LOCAL-POC.md | 345 ++++++ docs/FOG-MESSAGING.md | 1013 +++++++++++++++++ docs/FOG-OBSERVABILITY.md | 718 ++++++++++++ docs/FOG-PKI.md | 1814 +++++++++++++++++++++++++++++++ docs/FOG-POC-PRESERVATION.md | 152 +++ docs/FOG-SECURITY-TEST-PLAN.md | 120 ++ docs/FOG-SIMULATION.md | 321 ++++++ docs/FOG-SPHINX-PROFILES.md | 1188 ++++++++++++++++++++ docs/FOG-STORAGE.md | 1507 +++++++++++++++++++++++++ docs/FOG-SX.md | 1032 ++++++++++++++++++ docs/FOG-THREAT-MODEL.md | 962 ++++++++++++++++ docs/FOG-WIRE.md | 1246 +++++++++++++++++++++ fog-client.sh | 304 ------ fog.go | 2355 ---------------------------------------- go.mod | 9 - go.sum | 6 - merkle-tree.txt | 49 + 24 files changed, 14641 insertions(+), 3256 deletions(-) delete mode 100644 LICENSE create mode 100644 docs/FOG-ALPHA.md create mode 100644 docs/FOG-ARCHITECTURE.md create mode 100644 docs/FOG-COMPOSER.md create mode 100644 docs/FOG-CRYPTO-BENCHMARKS.md create mode 100644 docs/FOG-CRYPTO-SUITES.md create mode 100644 docs/FOG-LOCAL-POC.md create mode 100644 docs/FOG-MESSAGING.md create mode 100644 docs/FOG-OBSERVABILITY.md create mode 100644 docs/FOG-PKI.md create mode 100644 docs/FOG-POC-PRESERVATION.md create mode 100644 docs/FOG-SECURITY-TEST-PLAN.md create mode 100644 docs/FOG-SIMULATION.md create mode 100644 docs/FOG-SPHINX-PROFILES.md create mode 100644 docs/FOG-STORAGE.md create mode 100644 docs/FOG-SX.md create mode 100644 docs/FOG-THREAT-MODEL.md create mode 100644 docs/FOG-WIRE.md delete mode 100644 fog-client.sh delete mode 100644 fog.go delete mode 100644 go.mod delete mode 100644 go.sum create mode 100644 merkle-tree.txt diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 3b12074..0000000 --- a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2025 Gab - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/README.md b/README.md index 0c53855..d0d4232 100644 --- a/README.md +++ b/README.md @@ -1,561 +1,176 @@ -# 🌫️ fog - Anonymous SMTP Relay Network - -**fog** is a privacy-preserving SMTP relay that uses Sphinx mixnet routing to provide sender anonymity, forward secrecy, and resistance to traffic analysis. Perfect for anonymous email delivery, Usenet posting, and secure communications. - -[![Version](https://img.shields.io/badge/version-3.0.8-blue.svg)](https://github.com/yourusername/fog) -[![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE) -[![Go](https://img.shields.io/badge/go-1.21+-00ADD8.svg)](https://go.dev/) - ---- - -## 🎯 Features - -### Core Privacy Features - -- **🔐 Sphinx Mixnet Routing**: 3-hop onion routing with per-hop encryption -- **⏱️ Timing Attack Resistance**: Configurable message delays (1-24h) with multiple strategies -- **🎭 Exit Node Anonymization**: Automatic header sanitization removes all identifying information -- **🔄 Forward Secrecy**: Each message uses ephemeral keys, past messages remain secure if node compromised -- **🚫 No Logs**: Zero persistent metadata retention -- **🔀 Batch Mixing**: Messages are batched and shuffled before forwarding -- **♻️ Replay Protection**: Message-ID cache prevents replay attacks - -### Technical Features - -- **📦 Persistent Queue**: SQLite-backed delay pool survives restarts -- **🎲 Multiple Delay Strategies**: Exponential (default), Constant, Poisson distributions -- **🏥 Health Checking**: Automatic node monitoring and path selection -- **🔍 Debug Mode**: Detailed logging for troubleshooting -- **🐧 Linux Optimized**: Systemd integration with security hardening - ---- - -## 🛡️ Security Guarantees - -| Threat | Protection | -|--------|------------| -| **Traffic Analysis** | Padding + cover traffic | -| **Timing Attacks** | Randomized delays (1-24h) + constant-time operations | -| **Replay Attacks** | Message-ID cache with TTL expiration | -| **Node Compromise** | Forward secrecy protects older messages | -| **Size Correlation** | Fixed 64KB packet size prevents size analysis | -| **Partial Network Observation** | Mixnet architecture breaks linkability | -| **Global Adversary** | Multi-hop routing + batch mixing breaks end-to-end correlation | -| **Metadata Analysis** | Exit node header sanitization + no persistent metadata | - ---- - -## 🚀 Quick Start (Debian/Ubuntu) - -### Prerequisites - -```bash -# Install dependencies -sudo apt update -sudo apt install -y golang-go tor git build-essential - -# Verify Go version (1.21+ required) -go version -``` - -### Installation - -```bash -# Clone repository -git clone https://github.com/yourusername/fog.git -cd fog - -# Build -go mod tidy -go build -tags="sqlite_omit_load_extension" -ldflags="-s -w" -trimpath -o fog fog.go - -# Install binary -sudo mkdir -p /var/lib/fog -sudo cp fog /var/lib/fog/ -sudo chmod +x /var/lib/fog/fog -``` - -### Create User & Directories - -```bash -# Create fog user -sudo useradd -r -s /bin/false fog - -# Create directories -sudo mkdir -p /var/lib/fog/data -sudo chown -R fog:fog /var/lib/fog -sudo chmod 700 /var/lib/fog/data -``` - -### Generate Node Identity - -```bash -# Generate your node keys -cd /var/lib/fog -sudo -u fog ./fog -name your-onion-address.onion -short-name yourname -export-node-info - -# This creates nodes.json with your public key -cat nodes.json -``` - -### Configure Tor Hidden Service - -Edit `/etc/tor/torrc`: - -``` -HiddenServiceDir /var/lib/tor/fog/ -HiddenServicePort 9999 127.0.0.1:9999 -HiddenServicePort 2525 127.0.0.1:2525 -``` - -Restart Tor and get your address: - -```bash -sudo systemctl restart tor -sudo cat /var/lib/tor/fog/hostname -# Example output: abc123xyz456.onion -``` - -### Join the Network - -**Join the existing fog network or create your own!** - -#### Option 1: Join Existing Network - -Contact the network operators to: -1. Share your `nodes.json` (contains your public key + onion address) -2. Receive the network `nodes.json` (contains all trusted nodes) -3. Deploy to `/var/lib/fog/nodes.json` - -#### Option 2: Create New Network - -Start with 1 node (you), then invite others: - -```bash -# Use your own nodes.json -sudo cp nodes.json /var/lib/fog/nodes.json -``` - -### Configure Systemd Service - -Create `/etc/systemd/system/fog.service`: - -```ini -[Unit] -Description=fog - Anonymous SMTP Relay -After=network.target tor.service -Wants=tor.service - -[Service] -Type=simple -User=fog -Group=fog -WorkingDirectory=/var/lib/fog - -ExecStart=/var/lib/fog/fog \ - -name YOUR_ONION.onion \ - -short-name yourname \ - -smtp 127.0.0.1:2525 \ - -node 127.0.0.1:9999 \ - -sphinx \ - -delay \ - -min-delay 2h \ - -max-delay 24h \ - -delay-strategy exponential \ - -pki-file /var/lib/fog/nodes.json \ - -data-dir /var/lib/fog/data \ - -debug - -Restart=always -RestartSec=10 - -# Security hardening -NoNewPrivileges=true -PrivateTmp=true -ProtectSystem=strict -ProtectHome=true -ReadWritePaths=/var/lib/fog/data -ProtectKernelTunables=true -ProtectKernelModules=true -ProtectControlGroups=true - -[Install] -WantedBy=multi-user.target -``` - -**Replace:** -- `YOUR_ONION.onion` with your Tor hidden service address -- `yourname` with your chosen node name (e.g., alice, bob, relay1) - -### Start Service - -```bash -sudo systemctl daemon-reload -sudo systemctl enable fog -sudo systemctl start fog - -# Check status -sudo systemctl status fog - -# Watch logs -sudo journalctl -u fog -f -``` - ---- - -## 📨 Usage - -### Send Anonymous Email - -```bash -{ - echo "EHLO client" - echo "MAIL FROM:" - echo "RCPT TO:" - echo "DATA" - echo "From: Anonymous User " - echo "To: recipient@destination.com" - echo "Subject: Anonymous message via fog" - echo "" - echo "This message was sent through the fog network." - echo "." - echo "QUIT" -} | nc 127.0.0.1 2525 -``` - -### Post to Usenet Anonymously - -```bash -{ - echo "EHLO client" - echo "MAIL FROM:" - echo "RCPT TO:" - echo "DATA" - echo "From: Anonymous Poster " - echo "Newsgroups: alt.test" - echo "Subject: Test post via fog" - echo "" - echo "This post was submitted anonymously through fog network." - echo "." - echo "QUIT" -} | nc 127.0.0.1 2525 -``` - -**At the exit node, headers are automatically sanitized:** -- `From:` → `Anonymous ` -- `Message-ID:` → `` -- All identifying headers removed (X-Mailer, Reply-To, etc.) - ---- - -## ⚙️ Configuration - -### Command Line Flags - -| Flag | Default | Description | -|------|---------|-------------| -| `-name` | required | Your .onion address | -| `-short-name` | required | Short node name (for logs) | -| `-smtp` | 127.0.0.1:2525 | SMTP listen address | -| `-node` | 127.0.0.1:9999 | Sphinx node listen address | -| `-pki-file` | required | Path to nodes.json | -| `-data-dir` | fog-data | Data directory for queue database | -| `-sphinx` | false | Enable Sphinx routing | -| `-delay` | false | Enable delay pool | -| `-min-delay` | 1h | Minimum message delay | -| `-max-delay` | 24h | Maximum message delay | -| `-delay-strategy` | exponential | Delay strategy: exponential, constant, poisson | -| `-debug` | false | Enable debug logging | - -### Delay Strategies - -**Exponential (Recommended):** -- More short delays, fewer long delays -- Natural traffic pattern -- Best for high-volume nodes - -**Constant:** -- Uniform random delays -- Predictable average latency -- Good for testing - -**Poisson:** -- Models natural arrival processes -- Best for research/analysis - -### Example Configurations - -**High Anonymity (24h delays):** -```bash --delay -min-delay 6h -max-delay 24h -delay-strategy exponential -``` - -**Medium Latency (6h delays):** -```bash --delay -min-delay 1h -max-delay 6h -delay-strategy constant -``` - -**Low Latency (no delays):** -```bash --sphinx -# (omit -delay flag) -``` - ---- - -## 🔍 Monitoring - -### Check Queue Status - -```bash -# View queue size -sqlite3 /var/lib/fog/data/messages.db \ - "SELECT COUNT(*) as total FROM message_queue;" - -# View ready messages -sqlite3 /var/lib/fog/data/messages.db \ - "SELECT COUNT(*) FROM message_queue WHERE send_after <= strftime('%s','now');" - -# View queue details -sqlite3 /var/lib/fog/data/messages.db \ - "SELECT id, from_addr, to_addr, - datetime(send_after, 'unixepoch') as send_time - FROM message_queue - ORDER BY send_after LIMIT 10;" -``` - -### Monitor Logs - -```bash -# All fog activity -sudo journalctl -u fog -f - -# Pool activity only -sudo journalctl -u fog -f | grep POOL - -# Statistics only -sudo journalctl -u fog -f | grep STATS - -# Header sanitization -sudo journalctl -u fog -f | grep SANITIZE -``` - -### Statistics Output - -``` -[STATS] Up:2h30m R:45 S:42 F:3 | Sphinx:40 Direct:2 | Mix R:120 F:115 | Q:23 D:156 | Healthy:4 -``` - -- **R**: Messages received -- **S**: Messages sent -- **F**: Failed deliveries -- **Sphinx**: Messages sent via Sphinx routing -- **Direct**: Messages sent directly (no Sphinx) -- **Mix R/F**: Mixed received/forwarded -- **Q**: Messages queued in delay pool -- **D**: Total delayed messages delivered -- **Healthy**: Number of healthy nodes in network - ---- - -## 🌐 Network Information - -### Current fog Network - -The fog network currently consists of 5 active nodes: - -| Node | Status | -|------|--------| -| kvara | ✅ Active | -| dries | ✅ Active | -| mct8 | ✅ Active | -| news | ✅ Active | -| pietro | ✅ Active | - -**Join us!** Run your own node and strengthen the network's resilience. - -### Minimum Network Requirements - -- **3 nodes minimum** for Sphinx routing (3-hop paths) -- **5+ nodes recommended** for proper anonymity set -- **Network diversity** improves security - ---- - -## 🔧 Troubleshooting - -### Service won't start - -**Error: "No such file or directory" for /var/lib/fog/data** - -```bash -sudo mkdir -p /var/lib/fog/data -sudo chown fog:fog /var/lib/fog/data -sudo chmod 700 /var/lib/fog/data -sudo systemctl restart fog -``` - -**Error: "PKI file not found"** - -```bash -# Make sure nodes.json exists -ls -l /var/lib/fog/nodes.json - -# If missing, generate or obtain from network -sudo -u fog /var/lib/fog/fog -name YOUR.onion -short-name name -export-node-info -sudo cp nodes.json /var/lib/fog/ -``` - -### Messages not being delivered - -**Check Tor connectivity:** -```bash -# Test Tor is running -curl --socks5-hostname 127.0.0.1:9050 https://check.torproject.org/api/ip - -# Check fog can reach other nodes -sudo journalctl -u fog | grep HEALTH -``` - -**Check Sphinx routing:** -```bash -# Verify enough healthy nodes -sudo journalctl -u fog | grep "nodes healthy" - -# Should show: "[HEALTH] Done. 4 nodes healthy" (or more) -``` - -### Queue not processing - -**Check scheduler:** -```bash -sudo journalctl -u fog | grep "Scheduler started" - -# Should show: "[POOL] Scheduler started (check every 1m0s)" -``` - -**Check for ready messages:** -```bash -sqlite3 /var/lib/fog/data/messages.db \ - "SELECT * FROM message_queue WHERE send_after <= strftime('%s','now');" -``` - ---- - -## 🤝 Contributing - -### Run a Node - -The best way to contribute is to run your own fog node! Requirements: - -- Debian/Ubuntu server with static IP or dynamic DNS -- Tor hidden service -- Reliable uptime (>95% recommended) -- Bandwidth: ~100GB/month for relay node - -**Get started:** Follow the Quick Start guide above and contact us to join the network. - -### Development - -```bash -# Clone repository -git clone https://github.com/yourusername/fog.git -cd fog - -# Run tests -go test ./... - -# Build -go build -tags="sqlite_omit_load_extension" -o fog fog.go - -# Run locally -./fog -name test.onion -short-name test -smtp 127.0.0.1:2525 -debug -``` - -### Submit Issues - -Found a bug? Have a feature request? [Open an issue](https://github.com/yourusername/fog/issues) - -### Security Vulnerabilities - -**Do not open public issues for security vulnerabilities.** - -Contact: security@fog.network (PGP key available) - ---- - -## 📚 Documentation - -- **[CHANGELOG.md](CHANGELOG.md)** - Version history and release notes -- **[ARCHITECTURE.md](ARCHITECTURE.md)** - Technical design and protocol specification -- **[SECURITY.md](SECURITY.md)** - Security model and threat analysis -- **[API.md](API.md)** - SMTP protocol and message format - ---- - -## 🎓 How It Works - -### Sphinx Mixnet Overview - -``` -Client → Entry Node → Middle Node → Exit Node → Destination - (Hop 1) (Hop 2) (Hop 3) -``` - -**Each hop:** -1. Decrypts one layer of encryption -2. Cannot see final destination (onion routing) -3. Adds random delay before forwarding -4. Batches with other messages for mixing - -**At exit node:** -1. Final decryption reveals cleartext message -2. Headers are sanitized (From, Message-ID, Date randomized) -3. All identifying metadata removed -4. Delivered to final destination - -**Security properties:** -- No single node knows both sender and recipient -- Forward secrecy: past messages safe if node compromised -- Timing attacks mitigated by random delays + batching -- Traffic analysis resisted by fixed packet sizes + mixing - ---- - -## 📖 License - -MIT License - see [LICENSE](LICENSE) file for details. - ---- - -## 🙏 Acknowledgments - -- **Sphinx Mix Network**: Based on the Sphinx protocol by George Danezis and Ian Goldberg -- **Tor Project**: For anonymous networking infrastructure -- **Go Community**: For excellent cryptography libraries - ---- - -## 📞 Contact - -- **Website**: https://yamn.virebent.art -- **Usenet**: alt.privacy.anon-server - ---- - -## ⚠️ Disclaimer - -fog is designed for legal, privacy-preserving communications. Users are responsible for compliance with applicable laws. The fog network operators do not endorse or condone illegal activity. - -**Exit node operators**: Be aware that running an exit node means your IP/server may be associated with traffic you did not originate. Consider legal implications in your jurisdiction. - ---- - -
- -**🌫️ Join the fog network today and reclaim your digital privacy! 🌫️** - -[![Download](https://img.shields.io/badge/Download-Latest-brightgreen.svg)](https://github.com/yourusername/fog/releases) -[![Donate](https://img.shields.io/badge/Donate-Bitcoin-orange.svg)](https://fog.network/donate) - -
+# FOG + +Status: public design and evidence documentation + +This repository intentionally publishes documentation only. It contains no +production daemon, private deployment material, operator inventory, runtime +secret, or claim that FOG currently provides anonymity. The integrity of every +published Markdown file is committed by `merkle-tree.txt`. + +FOG will be an autonomous, modular network for asynchronous private +communication. Its initial native services will be one-way drops, anonymous +mailboxes, direct messages, and asynchronous chat. SOCKS, VPN, web proxying, +and public Internet exits are excluded from the current design. + +FOG is one coherent product, but its security roles remain separable: + +```text +offline Composer + | + | QR or FOG-SX one-way transfer + v +online blind relay + | +random temporary entry selected from signed FOG-PKI consensus + | +entry gateway + | +mix layer 1 -> mix layer 2 -> mix layer 3 + | +courier / native service + | +four or more distributed storage replicas + | +recipient blind relay + | +recipient Composer decrypts and verifies +``` + +The Composer encrypts each message for the recipient before it leaves the +offline environment. The blind relay, entry, mixes, courier, and storage never +receive plaintext. KEMSphinx protects routing through the mixnet, while Noise +protects each adjacent online connection. These layers use different keys and +do not replace one another. + +Contacts use private pairwise identities and targeted single-use vouchers, +not global usernames. The Composer persists each ratchet transition together +with its exact fixed message envelope before export. Storage then persists an +immutable box and courier request generation. Retransmission reuses those +storage bytes for deduplication but receives fresh route, KEMSphinx, entry, +rendezvous, and reply material. Authenticated message acknowledgments report +durable receiving-Composer commit, never that a human read the message. + +Each conversation direction uses a separate evolving capability stream. Two +final replicas store each pseudorandom box, while two disjoint intermediate +replicas prevent the courier from learning the final pair. At least four +independent storage replicas are required. Courier acceptance is not storage +durability; ordinary durability requires authenticated receipts from both +final replicas. Empty reads do not advance a stream, and signed tombstones +prevent data resurrection until bounded expiry. + +FOG uses one source tree and coordinated release, but each online security role +is a separate executable with its own identity, keys, writable state, account, +configuration, and network policy. The initial `fog-drop`, `fog-mailbox`, and +`fog-im` features run as Composer-side modules over one common messaging and +storage protocol, not as distinguishable public services. + +The Composer can run as a networkless microVM on an online host or as a +portable bootable USB system on a physically offline computer. High-assurance +transfer avoids USB shuttling and uses QR or `FOG-SX`, an acknowledgment-free +simplex protocol. The preferred physical FOG-SX backend is a one-way TOSLINK +fiber called `FOG Lightpipe`. FOG-SX fixes bounded padded objects and frames, +but its RaptorQ and Reed-Solomon FEC options and its QR, Lightpipe, and MIDI +physical profiles remain non-active pending implementation, hardware, and +resource review. Visible light, audio modem, and paper tape remain possible +future backends. + +Composer software boots from an authenticated read-only image and keeps +mutable secrets in a volume-encrypted, object-authenticated transactional +vault. Imports are hostile fixed bundles; exports contain only committed +opaque work. A local commitment chain is not presented as complete rollback +protection. That claim requires an independent monotonic anchor outside the +vault and host rollback domain. Identity recovery does not resume stale live +ratchets, capabilities, prekeys, or outboxes. + +An entry is never globally predefined. The blind relay randomly selects a +small temporary set from the signed consensus and rotates it by session or +epoch. It chooses only among opaque entry variants prepared by the Composer and +does not learn the first internal mix hop. The entry remains separate from the +three mix hops. Paths avoid using the same operator more than once. + +Three independent mix nodes are enough for a functional local PoC, not for +production anonymity. Six mix nodes are the minimum meaningful alpha target, +and nine mix nodes are the preferred initial network target. At least four +storage replicas and three independent directory authorities are also needed. + +FOG will use signed epoch consensus, fixed packet sizes, randomized mixing +delays, replay protection, traffic padding, decoy traffic, capability-based +mailboxes, safe retries, and privacy-minimizing logs. The current non-active +cryptographic shortlist uses SHA3-256 and mandatory ML-DSA-65 plus Ed25519 for +PKI evaluation, the exact HPQC ML-KEM-768 plus X25519 split-PRF construction +for the calculated KEMSphinx candidate, and PQXDH plus Triple Ratchet and +ML-KEM Braid for messaging. X-Wing leads the adjacent-link KEM evaluation, but +FOG has not selected an exact post-quantum Noise profile. + +`FOG-WIRE-1` protects adjacent online links with TCP and one exact +consensus-authorized Noise profile. Relay-to-entry connections authenticate +the entry without assigning the relay a stable Noise identity; all node, +authority, courier, storage, and observer links use mutual role-specific +authentication. A fixed preface and authenticated prologue bind the network, +epoch, consensus, roles, peers, keys, and adjacency. Encrypted records have one +fixed size per profile, rekey after every record, and force a fresh handshake +at bounded record, byte, time, epoch, profile, or authorization boundaries. +There is no runtime profile negotiation, 0-RTT, resumption, TLS fallback, or +generic RPC bus. + +`FOG-PKI-1` uses complete deterministic consensus documents with independent +M-of-N authority signatures. Authority roots stay offline and certify rotating +online voting keys. Consumers never merge partial directory views. Offline +Composers retain monotonic state and verify newer consensus through a +threshold-signed append-only checkpoint, archive inclusion, and a Merkle +consistency proof. The initial claim-bearing quorum is 2-of-3. + +FOG will preserve Sphinx-family application compatibility through a stable +client SDK and explicit, consensus-authorized packet profiles. Core nodes will +not auto-detect foreign Sphinx variants or negotiate packet suites. Bridges to +specific external mix networks remain isolated edge adapters with separately +documented cross-network correlation risks. + +The normative baselines are `docs/FOG-THREAT-MODEL.md`, +`docs/FOG-ARCHITECTURE.md`, `docs/FOG-PKI.md`, `docs/FOG-WIRE.md`, +`docs/FOG-SPHINX-PROFILES.md`, `docs/FOG-MESSAGING.md`, +`docs/FOG-STORAGE.md`, `docs/FOG-COMPOSER.md`, `docs/FOG-SX.md`, and +`docs/FOG-OBSERVABILITY.md`. +`docs/FOG-CRYPTO-SUITES.md` is the current non-normative selection and evidence +record. Cryptographic properties are tied to exact protocols and +implementations. All named cryptographic, messaging, and narrow +BACAP/Pigeonhole storage candidates remain non-active pending exact integration +evidence. Anonymity, unlinkability, and unobservability remain conditional on +measured traffic, cover, topology, operator independence, endpoint integrity, +and the stated adversary. The global passive observer is a simulation and +validation target, not a present guarantee. + +`docs/FOG-SIMULATION.md` records the first deterministic traffic and topology +comparison matrix. It confirms that the functional PoC and sparse traffic are +not anonymity evidence, and it selects no numeric cover, delay, polling, +topology, or degraded-mode profile. The remaining formal observer, storage, +loop, queue, behavior, churn, and trace-driven models precede any such +selection. + +`FOG-OBSERVABILITY-1` now fixes the structural operations boundary. Production +roles emit no packet or request event streams. They collect only closed typed +metrics in coarse windows, export bucketed fixed-shape aggregates after a +delay, suppress traffic-sensitive values under a minimum activity threshold, +and keep bounded local summaries. The Composer and FOG-SX roles have no +automatic observer path. A public view requires multi-reporter aggregation, +fixed grouping, delayed non-overlapping windows, low-population suppression, +and anti-differencing review. No numeric observability profile or observer +service is active. + +Development proceeds from specifications and simulation through functional +fixtures, fault injection, an independent-operator alpha, and external review +before any real anonymity claim. + +`docs/FOG-LOCAL-POC.md`, `docs/FOG-SECURITY-TEST-PLAN.md`, and +`docs/FOG-ALPHA.md` describe the functional test boundaries and the next +transition without pretending that local containers are independent +operators. A reproducible local six-mix laboratory has completed its 13 fault +families, but it remains a non-cryptographic functional fixture. No FOG +protocol daemon or public network exists. The 19 distributed-alpha evidence +gates remain open pending active profiles, real daemons, governance, +independent operators, independent reproduction, and review. + +Supporting documents cover cryptographic evaluation, simulation, local PoC +constraints, security testing, alpha readiness, and PoC preservation. The +implementation and raw test workspace are deliberately outside this +documentation-only publication. diff --git a/docs/FOG-ALPHA.md b/docs/FOG-ALPHA.md new file mode 100644 index 0000000..03a712d --- /dev/null +++ b/docs/FOG-ALPHA.md @@ -0,0 +1,184 @@ +# FOG Operator Alpha + +Status: Readiness gate, not deployable + +Date: 2026-08-18 + +## 1. Purpose + +`FOG-OPERATOR-ALPHA-1` defines the transition from a single-host functional +fixture to an experimental network operated across independent administrative +domains. It consolidates the alpha requirements already present in +`FOG-THREAT-MODEL`, `FOG-ARCHITECTURE`, `FOG-PKI`, and `FOG-WIRE` without +activating a cryptographic, traffic, or deployment profile. + +The current FOG tree is not alpha-ready. No FOG protocol daemon or active +byte-exact protocol profile exists. The alpha readiness module records this +state explicitly and fails closed when evidence or operator separation is +missing. + +## 2. Local Alpha Laboratory + +A single host may reproduce the alpha role count with separate rootless +containers or virtual machines. That laboratory can test: + +- three-authority quorum and consensus failure; +- two mix instances in each of three layers; +- route selection across alternate nodes; +- replay-state loss, node loss, storage loss, and partitions; +- key rotation, revocation, epoch transition, and rollback behavior; +- bounded queues, malformed input, flooding, and restart behavior; +- deterministic deployment generation and role-specific containment. + +The laboratory must keep one executable, service account, secret scope, +writable state scope, and network policy per role. A single container running +all roles is not conforming because it erases the process, key, state, and +network boundaries the test is intended to exercise. + +All laboratory binaries are compiled once for the host platform. One +digest-pinned multi-binary fixture image may then be reused by every container, +with a different single-role entrypoint. Six mix containers do not cause six +`fog-mix` compilations. + +The laboratory does not demonstrate independent operation, provider or ASN +diversity, Internet timing, resistance to operator collusion, or anonymity. +Synthetic operator labels are test inputs only and must not be presented as +real independence. + +## 3. Minimum Distributed Topology + +The operator alpha requires: + +- three independently administered authority roots and online services with + a 2-of-3 quorum; +- independent consensus mirrors; +- at least two independent checkpoint monitors or witnesses; +- three mix layers with at least two independently operated mixes per layer; +- at least four independently operated storage replicas; +- separate entry, mix, courier, and storage identities; +- declared operator family, administration, provider, provider-account, ASN, + country, and location relationships; +- no route that repeats an operator or declared family. + +These are minimums, not evidence of sufficient anonymity. Traffic volume, +cover scheduling, topology diversity, observed network behavior, and +adversarial results remain part of every claim. + +Distributed operators consume verified release artifacts. The release +pipeline assembles minimal role-specific images from one compiled artifact set +and shared OCI layers. Operators do not need to compile locally unless they are +performing an independent reproducibility check. + +## 4. Readiness Evidence + +Every required readiness item has one of two states: `open` or `pass`. A pass +requires at least one immutable artifact reference, a lowercase SHA-256 digest, +an explicit scope, and a date. A note or successful command without retained +evidence does not close a gate. + +The required evidence covers: + +1. active byte-exact protocol profiles; +2. real single-role protocol daemons; +3. a validated operator inventory; +4. unit, integration, conformance, fuzz, race, and fault testing; +5. replay, tagging, n-1, flooding, load, side-channel, clock, consensus, + authority, node-loss, and storage-loss tests; +6. traffic simulation calibrated with observed or conservative distributions; +7. interoperability between independently built endpoints; +8. reproducible independent-host deployment and rollback; +9. rehearsed compromise, revocation, transition, quorum-loss, and restore + procedures; +10. public admission, family, common-control, revocation, and residual-risk + policy; +11. independent security review. + +The independent review gate is retained even though a broader production +review is also required. For alpha it means that someone outside the +implementation path has reviewed the exact experimental artifacts and that +unresolved findings are recorded. It is not a production certification. + +## 5. Inventory Boundary + +The machine-readable operator inventory contains only bounded public or opaque +identifiers needed to test separation and role counts. It must not contain: + +- private keys, recovery material, credentials, tokens, or passwords; +- host login instructions or management addresses; +- personal email addresses, phone numbers, or legal identity evidence; +- private admission evidence or incident details; +- packet, user, contact, mailbox, or capability identifiers. + +The validator rejects unknown fields, duplicate JSON keys, trailing values, +oversized files, invalid identifiers, duplicate node identities, shared role +state or secret scopes, insufficient role counts, and common administration, +provider accounts, or families inside independence-critical role sets. + +The validator reports aggregate provider, ASN, country, and location-group +counts. It does not invent a numeric diversity policy that the normative PKI +profile has not selected. + +## 6. Claim Boundary + +The only permitted baseline label is: + +```text +experimental-alpha-no-anonymity-claim +``` + +Until every gate passes, public material must say that FOG is a specification +and laboratory project. Even after the alpha gate passes, results must identify +the exact software, profiles, topology, traffic conditions, adversary, +measurement period, known failures, and operator assumptions. + +An alpha must not claim production anonymity, global-passive-observer +resistance, post-quantum security, reliable delivery, or protection against +collusion merely because the minimum node count exists. + +## 7. Implementation + +The gate implementation lives in `deploy/alpha/`. It is deliberately separate +from protocol daemons and uses only the Go standard library. It validates +readiness evidence and an externally supplied operator inventory. It does not +generate a deployment manifest because exact ports, transport profiles, +cryptographic suites, cover schedules, operational limits, and authority time +parameters remain non-active selections. + +## 8. Requirement Traceability + +| Alpha control | Requirements | +| --- | --- | +| Independent authority, mix, storage, mirror, and witness sets | `TM-PKI-01`, `TM-PKI-03`, `ARC-001`, `ARC-007`, `PKI-INV-01`, `PKI-INV-07` | +| Six mixes, two per layer | `TM-ROLE-02`, `ARC-003`, `FOG-ARCHITECTURE` section 16.2 | +| Evidence registry | `TM-SUPPLY-01`, `TM-CRYPTO-02`, `FOG-THREAT-MODEL` section 16.2 | +| Unique service, state, and secret scopes | `TM-CRYPTO-01`, `ARC-007`, `WIRE-INV-07` | +| Strict bounded inventory parser | `TM-AVAIL-01`, `ARC-008` | +| No anonymity claim | `TM-NET-01`, `TM-NET-02`, `TM-NET-03`, `ARC-009` | + +## 9. Next Implementation Block + +The next safe local block is a simulation-only alpha laboratory with three +authority fixtures, two mixes per layer, four storage fixtures, alternate-path +traversal, and an expanded fault matrix. It must reuse no production profile +identifier and must remain visibly distinct from the later protocol daemons. + +The completed laboratory is preserved as `FOG-ALPHA-LAB-POC-1` according to +`FOG-POC-PRESERVATION.md`. Preservation includes reproducible source and one +bounded demonstration workflow, not captured mutable containers or runtime +secrets. The frozen PoC remains functional evidence only. + +The checked-in definition gate fixes 17 roles, 23 pairwise permitted edges, +all eight complete mix routes, and 13 fault families in +`deploy/alpha/lab-topology.json` and `deploy/alpha/lab-faults.json`. Compatible +single-role fixtures and deterministic Compose generation now make the strict +validator report `runnable: true`. The bounded one-command rootless workflow +completed all eight routes, performed 102 containment and exact-network +checks and exercised all 13 fault families, including exact stale-state +rejection and fixture cover-scheduler supervision. It recorded sanitized +evidence and removed every disposable runtime resource. Dependency and license +inventory, frozen artifact hashes, and independent clean-room reproduction +remain required before accepting the preserved PoC. The canonical preservation +manifest now records zero third-party Go modules, exact external tool versions +and licenses, required source and evidence hashes, and all eight binary hashes. +The project license remains `NOASSERTION`; an independent host rebuild and +repository revision remain outstanding. diff --git a/docs/FOG-ARCHITECTURE.md b/docs/FOG-ARCHITECTURE.md new file mode 100644 index 0000000..9060c7c --- /dev/null +++ b/docs/FOG-ARCHITECTURE.md @@ -0,0 +1,1122 @@ +# FOG Architecture + +Status: Draft 0.1 + +Date: 2026-08-08 + +## 1. Purpose + +This document defines the normative component architecture, trust boundaries, +information flows, state ownership, deployment units, and dependency rules for +the FOG autonomous mix network. + +It refines `FOG-THREAT-MODEL.md`. It does not select final cryptographic +algorithms, packet geometry, delay distributions, or active numeric message, +storage, and Composer suites. Those decisions belong to narrower protocol +specifications and MUST satisfy the boundaries defined here. + +FOG is not implemented yet. Statements in this document are design +requirements, not claims about deployed software. + +The key words MUST, MUST NOT, REQUIRED, SHOULD, SHOULD NOT, and MAY describe +normative requirements in the sense of BCP 14 when they appear in uppercase. + +## 2. Architectural Scenario + +FOG is one product and one coordinated protocol suite with multiple +independently deployable security roles. It is not a single daemon, a shared +database application, or a collection of unrelated public microservices. + +The baseline implementation shape is: + +- one source repository and release process; +- one separately built executable for each online security role; +- a networkless Composer executable and image profile; +- small protocol libraries with strict dependency direction; +- a public client SDK with a deliberately narrow surface; +- a simulator and conformance corpus in the same repository; +- local Podman deployment only for the functional PoC; +- independent hosts and operators where a deployment makes anonymity claims. + +Separate processes are justified by independent deployment, key ownership, +network policy, compromise containment, and operator ownership. Application +features that do not need a server-side trust boundary MUST remain modules, +not daemons. + +## 3. Core Architectural Invariants + +The following invariants apply to every profile. + +### ARC-001: No universal trust domain + +No process, host, operator account, database, management system, or private key +MAY be required to observe or control the complete user-to-storage path. + +### ARC-002: The Composer is networkless + +`fog-compose` MUST have no network interface. All external input and output +cross explicit bounded transfer interfaces. A network-capable convenience +mode is not a conforming Composer profile. + +### ARC-003: No data-plane bypass + +Native user operations MUST traverse an entry, all configured mix layers, and +a terminal courier or service endpoint. Failure handling MUST NOT introduce a +direct client-to-courier, client-to-storage, entry-to-storage, or +mix-to-clearnet path. + +### ARC-004: Three distinct cryptographic layers + +Message end-to-end protection, KEMSphinx routing protection, and adjacent-node +Noise transport MUST use purpose-separated protocols and keys. Success or +failure of one layer MUST NOT be interpreted as validation of another. + +### ARC-005: Complete authenticated network view + +Route construction, entry eligibility, node roles, packet profiles, security +parameters, and key validity MUST derive from a complete threshold-authenticated +consensus accepted by the Composer. A relay-supplied partial view MUST NOT +control route construction. + +### ARC-006: Uniform external behavior + +Native applications MUST share consensus-authorized packet classes, routing +rules, retry classes, and cover processing. Core nodes MUST NOT vary packet +geometry or parser selection according to `fog-drop`, `fog-mailbox`, or +`fog-im`. + +### ARC-007: One private-key owner + +Each private key MUST have one owning role and one purpose. Separate roles MUST +NOT share private key files, secret volumes, writable state directories, or +backup credentials. + +### ARC-008: Bounded state and parsing + +Every queue, cache, request, response, parser, retry sequence, and storage +operation MUST have explicit size, time, and resource bounds. Unknown-critical, +non-canonical, expired, or unauthenticated input MUST fail closed. + +### ARC-009: Privacy-aware failure + +Availability recovery MUST preserve the declared layer count, packet class, +authenticated profile, entry constraints, cover policy, and consensus +freshness. If it cannot, the affected operation MUST stop and report a local +coarse failure. + +### ARC-010: No inherited external guarantee + +Tor, Nym, Katzenpost, YAMN, SMTP, NNTP, and foreign Sphinx networks are not +runtime dependencies of the FOG core. An edge bridge terminates the FOG trust +claim and MUST publish a separate threat model. + +## 4. System Planes + +FOG separates four planes: + +1. **Offline user plane**: plaintext, identities, contacts, message state, + storage capabilities, route construction, and validation inside the + Composer. +2. **Anonymous data plane**: entry, stratified mixes, courier, native service + endpoints, and storage replicas carrying fixed-class opaque traffic. +3. **Consensus control plane**: node descriptors, authority voting, + threshold-signed consensus, revocation, profile authorization, and + equivocation evidence. +4. **Operations and release plane**: local administration, signed builds, + configuration generation, coarse metrics, backups, incident response, and + updates. + +These planes MAY share public code and formats. They MUST NOT share universal +credentials or writable runtime state. + +```text + consensus control plane + node descriptors -> authorities -> signed consensus + | + v +offline user plane anonymous data plane + +Composer -> transfer -> blind relay -> entry -> L1 -> L2 -> L3 -> courier + ^ | | + | | v + +---- opaque input <-+ storage replicas + + operations and release plane + signed artifacts, local administration, coarse aggregates +``` + +## 5. Data Classification + +Architecture and protocol documents MUST label fields and state using these +classes or a stricter derived scheme. + +| Class | Meaning | Examples | +| --- | --- | --- | +| `PUBLIC-AUTH` | Public but authenticity or freshness matters | consensus, descriptors, release metadata, protocol profiles | +| `OPAQUE-META` | Encrypted content with sensitive timing or relationship metadata | KEMSphinx packets, transfer bundles, reply envelopes, relay queues | +| `CAPABILITY` | Possession grants a read, write, reply, or recovery operation | mailbox capabilities, reply blocks, rendezvous handles | +| `SECRET-ROLE` | Private material owned by one online role | Noise, KEMSphinx, authority, replica, queue-sealing keys | +| `SECRET-USER` | User secret or conversation state | identity keys, ratchet state, contact state, backup keys | +| `PLAINTEXT-USER` | Decrypted user content | drafts, messages, rendered attachment content | +| `AGGREGATE` | Delayed and privacy-reviewed operational data | thresholded loss or availability measurements | + +`CAPABILITY`, `SECRET-ROLE`, `SECRET-USER`, and `PLAINTEXT-USER` MUST NOT enter +logs, metrics, command lines, crash reports, descriptors, consensus, or support +bundles. + +## 6. Trust Domains and Deployment Units + +Each row is a distinct security domain even when the PoC places containers on +one host. + +| Domain | Baseline deployable | Primary state owner | Network position | +| --- | --- | --- | --- | +| Offline user | `fog-compose` | user identity, contacts, message and capability state | no network | +| Transfer receiver | restricted adapter owned by `fog-client-relay` | bounded incomplete transfer state | local physical input only | +| Online user relay | `fog-client-relay` | opaque queues, schedules, temporary entry and return state | client edge | +| Directory authority | `fog-authority` | descriptors, votes, consensus and revocation state | control plane only | +| Entry | `fog-entry` | bounded ingress and temporary return state | edge of data plane | +| Mix layer | `fog-mix` with fixed layer assignment | delay queues, replay state, epoch keys | one stratified layer | +| Courier | `fog-courier` | bounded request deduplication and replica dispatch state | terminal mix service | +| Storage | `fog-store` | opaque boxes, tombstones, replica metadata | behind courier | +| Optional native service | `fog-service-*` | minimum service-specific opaque state | terminal mix service | +| Observer | `fog-observer` | delayed coarse aggregates | operations plane | +| Release | offline release tooling | release signing and provenance state | outside runtime network | +| External bridge | `fog-bridge-*` | bridge-specific state | outside core guarantee | + +A conforming executable MUST activate exactly one online role. Multi-role +configuration flags are forbidden outside explicitly labeled local simulation +fixtures. + +## 7. Role Contracts + +### 7.1 `fog-compose` + +The Composer is the only core component authorized to handle user plaintext, +user identity private keys, contact secrets, message state, and mailbox +capabilities. + +It MUST: + +- verify release, consensus, contact, recovery, and message authenticity; +- persist the highest accepted consensus epoch and rollback state; +- build message-level ciphertext before export; +- select internal routes from the authenticated consensus; +- construct KEMSphinx packets, reply material, and storage operations; +- create fixed-class opaque work items for the blind relay; +- consume inbound bundles only after complete bounded parsing and + cryptographic verification; +- store mutable secrets in an authenticated encrypted state store; +- minimize plaintext lifetime and exclude plaintext from generic desktop + services, swap, previews, indexing, and crash dumps. + +It MUST NOT: + +- open a network socket or expose an HTTP, RPC, shell, or plugin server; +- delegate message encryption, route selection, capability derivation, or + contact authentication to the blind relay; +- import generic archives, office documents, HTML, scripts, executable files, + or unbounded media; +- accept a lower consensus epoch without an explicit authenticated recovery + procedure. + +`fog-drop`, `fog-mailbox`, and `fog-im` are initially Composer-side application +modules over shared messaging and storage protocol libraries. They are not +independent network daemons in the initial architecture. + +Threats addressed: `TM-ENDPOINT-01`, `TM-ENDPOINT-02`, `TM-ENDPOINT-03`, +`TM-APP-01`, `TM-CRYPTO-01`, `TM-PKI-02`. + +### 7.2 Transfer receiver + +The transfer receiver is a minimal adapter at the blind-relay boundary. It MAY +decode QR or one explicitly configured FOG-SX physical backend. It MUST output +only a bounded opaque FOG bundle to the relay queue. + +It MUST run without access to relay network credentials, queue decryption +keys, user identity keys, or general filesystems. FEC and transport checksums +MUST be completed before the inner bundle enters the relay, but they MUST NOT +be treated as authenticity. + +The high-assurance offline-to-online path MUST have no automatic reverse data +channel. Online-to-offline input uses a separately controlled visual or +receive-only import path and is not the reverse channel of FOG-SX. + +Threats addressed: `TM-ENDPOINT-02`, `TM-AVAIL-01`. + +### 7.3 `fog-client-relay` + +The blind relay is the online scheduling and transport agent for one local +Composer profile. It is allowed to know that its local user is using FOG and +to observe local submission and import times. It MUST remain blind to +plaintext, contacts, capabilities, application type, and the internal mix +route. + +It MUST: + +- maintain bounded encrypted-at-rest queues of opaque work items; +- fetch complete consensus documents and pass them unmodified to the Composer; +- verify enough public consensus metadata to reject obvious network misuse, + while treating the Composer as the final consensus authority for user work; +- choose an entry only from the Composer-authorized temporary candidate set; +- follow consensus-authorized transmission, retrieval, cover, retry, and + failure classes; +- generate client cover and loop traffic without using user identity or + contact secrets; +- maintain only short-lived, opaque return rendezvous state; +- transfer received opaque bundles to the controlled Composer import path; +- erase expired work, return state, and incomplete transfers according to + explicit limits. + +It MUST NOT: + +- parse message or service payloads; +- construct or rewrite the Composer's internal route; +- hold message identity keys, mailbox capabilities, contact vouchers, ratchet + state, or release signing keys; +- bypass an entry after failure; +- expose a general-purpose proxy, SOCKS, VPN, TUN, SMTP, NNTP, or web API; +- use a stable remote account identity as the default entry authentication + mechanism. + +The relay MAY select among opaque entry-specific submission variants prepared +by the Composer. The exact entry-capsule and fallback construction is defined +by `FOG-WIRE` and `FOG-SPHINX-PROFILES`; the relay MUST NOT learn the first +internal mix hop from that construction. + +Threats addressed: `TM-NET-01`, `TM-NET-02`, `TM-NET-03`, `TM-NET-06`, +`TM-ROLE-01`, `TM-ENDPOINT-01`, `TM-OPS-01`. + +### 7.4 `fog-authority` + +Authorities collectively define the authenticated network view. They do not +carry user data-plane packets. + +Each authority MUST: + +- authenticate and validate descriptors for admitted role identities; +- record declared operator families and deployment attributes; +- validate role, layer, key, profile, address, epoch, and revocation rules; +- exchange signed votes with peer authorities; +- produce canonical consensus only at the configured threshold; +- archive sufficient signed material to diagnose rollback and equivocation; +- publish current and next public keys with bounded overlap; +- separate long-term authority identity and recovery material from online + voting state; +- fail deterministically when a quorum or time condition is not met. + +An authority MUST NOT: + +- inject data-plane traffic through privileged paths; +- assign one process to multiple data-plane roles; +- unilaterally create a valid consensus; +- automatically trust a replacement authority or node identity; +- receive user capabilities, packet identifiers, or fine-grained flow data. + +Consensus distribution MAY use multiple mirrors, but mirrors are untrusted +transport. Signatures, epoch monotonicity, validity, and profile authorization +are verified at every consumer. + +Threats addressed: `TM-PKI-01`, `TM-PKI-02`, `TM-PKI-03`, `TM-NET-06`, +`TM-SUPPLY-01`. + +### 7.5 `fog-entry` + +The entry is an access gateway distinct from the three privacy-relevant mix +layers. It accepts relay sessions, normalizes ingress handling, and dispatches +opaque KEMSphinx packets to layer 1. + +It MUST: + +- authenticate itself through the fixed entry link profile; +- authorize submission using short-lived, consensus-bound material rather + than a stable user account by default; +- accept only fixed-class opaque submissions for the active epoch; +- decrypt or validate only the entry capsule needed to learn the authorized + first internal hop and packet binding; +- forward only to consensus-authorized layer-1 nodes; +- maintain bounded queues and non-amplifying failure behavior; +- process real and cover submissions through the same path; +- support bounded, opaque, short-lived return rendezvous where required by the + reply protocol; +- perform exactly one terminal KEMSphinx unwrap on an authorized reply route + when the return profile requires it, using a dedicated entry-return key and + never a mix-layer or entry-capsule key. + +It MUST NOT: + +- process an internal KEMSphinx mix layer or any forward KEMSphinx hop merely + because it is an entry; +- perform a reply-terminal unwrap outside an authenticated return-rendezvous + context or forward the result as if it were another mix hop; +- learn the complete route, final service, mailbox capability, contact, or + plaintext; +- select or rewrite the internal route; +- forward directly to layer 2, layer 3, courier, storage, bridge, or Internet; +- retain a durable per-user mailbox or account in the core profile; +- produce application-specific errors or timing classes. + +The exact entry capsule and return rendezvous are protocol decisions. They +MUST preserve Composer route authority and relay blindness. An implementation +convenience MUST NOT silently make the entry the first of only three total +hops. + +Threats addressed: `TM-NET-01`, `TM-NET-03`, `TM-NET-06`, `TM-ROLE-01`, +`TM-AVAIL-01`. + +### 7.6 `fog-mix` + +Every mix executable has one fixed layer assignment from the current +consensus. It is a cryptographic router, not an application server. + +It MUST: + +- authenticate adjacent eligible nodes through the fixed node link profile; +- accept packets only from roles permitted for its layer; +- perform exactly one KEMSphinx hop transformation; +- validate the packet profile, epoch, replay tag, routing command, and delay + bounds before enqueueing; +- persist replay state for the required maximum packet lifetime; +- delay and schedule packets according to the authenticated profile; +- process real, loop, decoy, forwarded, and reply traffic uniformly within + their authorized packet class; +- forward only to the next authorized layer or terminal role; +- enforce bounded queues, connection counts, cryptographic work, and retries; +- emit only coarse local health aggregates. + +It MUST NOT: + +- expose application plugins, storage APIs, user accounts, or general proxy + functions; +- select an arbitrary next layer or skip a layer; +- parse message, capability, courier, storage, or bridge payloads; +- share KEMSphinx private keys or replay databases with another role; +- log packets, replay tags, routes, per-packet delay, or fine-grained timing. + +Loss of valid replay state places the node outside the active privacy profile. +It MUST stop packet processing until safe state is restored or a new epoch +with fresh keys begins according to the replay specification. + +Threats addressed: `TM-NET-01`, `TM-NET-03`, `TM-NET-04`, `TM-NET-05`, +`TM-NET-06`, `TM-ROLE-02`, `TM-OPS-01`, `TM-AVAIL-01`. + +### 7.7 `fog-courier` + +The courier is the terminal data-plane mediator between anonymous requests and +storage or a separately declared native service. It terminates only the final +KEMSphinx service envelope and MUST not learn the client network origin. + +It MUST: + +- accept terminal packets only after all configured mix layers; +- parse one bounded, versioned courier command set; +- dispatch opaque encrypted operations to eligible replicas; +- keep replica selection knowledge separate from final capability-derived + record location where the storage construction requires it; +- keep only bounded, expiring request-deduplication and reply state; +- use single-use reply material or an equivalently reviewed construction; +- return replies through a consensus-authorized anonymous reply route; +- apply identical outer behavior to reads, writes, misses, retries, expected + outcomes, and cover operations as defined by `FOG-STORAGE`; +- enforce non-amplifying limits before expensive or fan-out work. + +A courier acceptance reply is not replica durability or message delivery. The +courier MUST preserve opaque final-replica receipt material without creating +or interpreting it. + +It MUST NOT: + +- receive user identity keys, message plaintext, contact state, or long-term + mailbox capabilities; +- connect directly to a blind relay or Composer; +- expose replicas directly to clients or mixes; +- become a durable mailbox database; +- load arbitrary third-party plugins in the core process; +- send data to the Internet or an external bridge under the core profile. + +Threats addressed: `TM-NET-01`, `TM-NET-02`, `TM-NET-05`, `TM-ROLE-03`, +`TM-APP-01`, `TM-AVAIL-01`. + +### 7.8 `fog-store` + +Storage replicas hold authenticated encrypted records addressed through +capability-derived, rotating, pseudorandom locations. They are not public +mailbox servers. + +Each replica MUST: + +- authenticate couriers and eligible replica peers through a fixed node link + profile; +- accept only bounded courier or replica protocol operations; +- validate record authentication before committing state; +- issue purpose-separated authenticated receipts only after durable local + commit under the exact active storage manifest; +- implement idempotent writes, explicit expiry, tombstones, quotas, bounded + retention, and garbage collection; +- keep replica and storage-envelope keys separate from node link keys; +- return fixed-class authenticated encrypted results; +- support the replica-independence and intermediate/final separation required + by the selected storage profile; +- encrypt storage media and backups as defense in depth without treating disk + encryption as end-to-end protection. + +It MUST NOT: + +- accept connections from Composers, blind relays, entries, or ordinary mixes; +- receive user plaintext or message identity private keys; +- expose record existence through a public unauthenticated lookup interface; +- share a writable database, database credentials, or backup key with another + replica operator; +- publish per-record or fine-grained access metrics. + +The intended privacy profile requires at least four independently operated +replicas. A smaller local fixture is functional testing only and MUST disable +the corresponding unlinkability claim. + +Threats addressed: `TM-NET-02`, `TM-ROLE-03`, `TM-ENDPOINT-03`, +`TM-CRYPTO-01`, `TM-AVAIL-01`. + +### 7.9 Native application modules and optional services + +The initial `fog-drop`, `fog-mailbox`, and `fog-im` state machines run inside +the Composer and produce a common bounded message/storage envelope. Their +network traffic MUST be indistinguishable within the declared packet class. + +A future feature MAY require a server-side `fog-service-*` executable. Such a +service MUST: + +- be a separately keyed terminal role behind all mix layers; +- declare its exact request fields and information exposure; +- accept one fixed bounded protocol, not arbitrary code or generic HTTP; +- use anonymous reply routes; +- receive no privilege to call unrelated services or public Internet targets; +- add a threat-model extension and independent conformance tests. + +No service is permitted to weaken the common packet profile or make the +courier parse application plaintext. + +Threats addressed: `TM-NET-06`, `TM-APP-01`, `TM-ROLE-03`. + +### 7.10 `fog-observer` + +The observer receives delayed, coarse, thresholded aggregates. It is not a +packet-flow collector. + +It MUST: + +- accept only a versioned aggregate schema; +- enforce release delay, minimum population, and suppression rules; +- separate operator health views from public views; +- expire raw signed aggregate submissions after bounded processing; +- publish the aggregation and differencing-risk policy. + +It MUST NOT: + +- receive packet IDs, replay tags, capabilities, user IDs, full routes, + connection logs, queue contents, or event-level timestamps; +- require universal read access to node logs or databases; +- instruct nodes to enable debug logging; +- be required for packet forwarding or consensus validity. + +Threats addressed: `TM-NET-03`, `TM-OPS-01`. + +### 7.11 Release system + +Release signing is outside every runtime role. Release keys MUST NOT exist on +mix, authority, entry, courier, storage, observer, relay, or general CI +workers. + +The release process MUST produce canonical signed metadata, artifact hashes, +version and compatibility information, rollback constraints, and provenance. +Composer and node update verification MUST fail closed. Emergency revocation +MUST use an authenticated path distinct from ordinary online administration. + +Threats addressed: `TM-SUPPLY-01`, `TM-ENDPOINT-01`, `TM-CRYPTO-01`, +`TM-CRYPTO-02`. + +### 7.12 `fog-bridge-*` + +An external bridge is never part of the core trust claim. It MUST be a +separate executable, identity, service descriptor, process, host policy, data +store, log policy, and threat-model appendix. + +It MUST NOT share a process with courier, storage, entry, or mix roles. Its +output is governed by the external protocol, and FOG cannot conceal metadata +that the external endpoint reveals. + +## 8. Trust-Boundary Interfaces + +Every interface MUST have one owning specification, exact framing, maximum +size, authentication rule, replay rule, timeout, failure class, and test +vectors. + +| ID | Interface | Producer -> consumer | Data class | Owning specification | +| --- | --- | --- | --- | --- | +| `IF-01` | Composer export | Composer -> transfer receiver -> relay | `OPAQUE-META` | `FOG-COMPOSER`, `FOG-SX` | +| `IF-02` | Composer import | relay or controlled medium -> Composer | `PUBLIC-AUTH`, `OPAQUE-META`, `CAPABILITY` | `FOG-COMPOSER` | +| `IF-03` | Descriptor upload | node -> authorities | `PUBLIC-AUTH` | `FOG-PKI`, `FOG-WIRE` | +| `IF-04` | Authority vote | authority -> authority | `PUBLIC-AUTH` | `FOG-PKI`, `FOG-WIRE` | +| `IF-05` | Consensus distribution | authorities or mirrors -> all roles | `PUBLIC-AUTH` | `FOG-PKI` | +| `IF-06` | Relay submission | relay -> entry | `OPAQUE-META` | `FOG-WIRE`, `FOG-SPHINX-PROFILES` | +| `IF-07` | Layer forwarding | entry/L1/L2/L3 -> next role | `OPAQUE-META` | `FOG-WIRE`, `FOG-SPHINX-PROFILES` | +| `IF-08` | Terminal request | L3 -> courier or service | `OPAQUE-META` | `FOG-WIRE`, `FOG-SPHINX-PROFILES`, service contract | +| `IF-09` | Replica operation | courier <-> replicas; replica <-> replica | `OPAQUE-META` | `FOG-STORAGE`, `FOG-WIRE` | +| `IF-10` | Anonymous reply | courier/service -> mix route -> entry/relay | `OPAQUE-META`, `CAPABILITY` | `FOG-SPHINX-PROFILES`, `FOG-WIRE` | +| `IF-11` | Aggregate submission | role -> observer | `AGGREGATE` | `FOG-OBSERVABILITY`, `FOG-WIRE` | +| `IF-12` | Signed update | release distribution -> role | `PUBLIC-AUTH` | `FOG-UPDATE` | +| `IF-13` | Local administration | operator -> one role | role-local | deployment profile | + +No generic RPC bus, shared event bus, shared SQL database, shared Redis, or +service mesh identity MAY span these trust boundaries in the core profile. + +## 9. Control-Plane Flows + +### 9.1 Node admission and descriptor publication + +1. An operator creates separate role and node identities using an offline or + controlled enrollment procedure. +2. The operator submits a signed admission request with role, family, layer, + address, public keys, supported profiles, and declared infrastructure. +3. Authorities validate policy and operator-family conflicts. +4. An admitted node creates an epoch descriptor and signs canonical bytes. +5. The node submits the descriptor independently to the authority set. +6. Authorities validate and include eligible descriptors in their votes. + +Admission does not imply health, honesty, independence, or permanent +eligibility. These are separately governed and measured. + +### 9.2 Consensus production + +1. Authorities exchange authenticated votes and commitments. +2. Each authority derives canonical topology and network parameters. +3. A consensus becomes valid only with the configured threshold of signatures + over identical canonical bytes. +4. Current and next key material overlap only for the specified window. +5. Authorities and mirrors publish signed votes, consensus, and revocations. + +FOG-PKI defines deterministic responses to split votes, missing quorum, clock +skew, stale epochs, rollback, freeze, and equivocation. Consumers MUST NOT +merge partial views locally. + +### 9.3 Consensus consumption by an offline Composer + +1. The relay obtains complete consensus from more than one retrieval path + where practical. +2. The relay passes the bytes and available consistency evidence through the + controlled import boundary. +3. The Composer performs canonical parsing, threshold signature validation, + epoch and rollback checks, profile checks, and trust-anchor checks. +4. Only the accepted consensus may drive routes, entry candidates, packet + profiles, and traffic parameters. +5. The Composer persists the highest accepted epoch before exporting work + based on it. + +An untrusted relay can withhold or replay data and cause denial of service, but +it MUST NOT be able to make a forged or rolled-back view valid. + +## 10. Outbound User Flow + +The logical outbound flow is: + +1. A Composer application module creates a bounded semantic message. +2. The Composer stages one authenticated message transition, encrypts the + message for the recipient, and atomically persists the advanced state with + the exact immutable envelope before that envelope becomes exportable. +3. The Composer atomically derives the required storage box and capability + transition, persists its immutable storage request generation, and only + then makes the operation exportable. +4. The Composer selects a consensus-valid terminal role and one node from each + mix layer, obeying operator-family constraints. +5. The Composer creates the KEMSphinx packet, reply material, and entry-bound + opaque submission variants. +6. The Composer exports a fixed-class work bundle over `IF-01`. +7. The relay validates only the outer work-bundle contract, queues it, and + selects one Composer-authorized entry variant under the current schedule. +8. The relay sends the opaque variant to the selected entry over `IF-06`. +9. The entry validates the entry capsule and forwards the bound packet to the + authorized layer-1 node. +10. Each mix performs exactly one transformation, delay, replay check, and + forwarding decision. +11. Layer 3 delivers the terminal packet to the selected courier or native + service. +12. The courier performs the bounded opaque operation and, where applicable, + contacts storage replicas through `IF-09`. +13. A result returns only through the supplied anonymous reply mechanism. + +At no point may the relay reconstruct the internal route, the entry change it, +or a mix interpret the user message. + +The exact choice between a complete packet per entry variant and a smaller +entry capsule bound to a shared packet remains an explicit protocol decision. +It MUST be resolved with packet-size, replay, fallback, and correlation +analysis before implementation. + +## 11. Retrieval and Reply Flow + +FOG does not require direct user-to-user sessions. Retrieval is a Composer- +constructed anonymous service operation. + +1. The relay obtains short-lived return-rendezvous material from eligible + entries or maintains an eligible live session under the active profile. +2. The relay transfers the opaque public or capability-bound rendezvous + material to the Composer through `IF-02`. +3. The Composer validates it against accepted consensus and binds a single-use + reply route to a retrieval or write operation. +4. The outbound operation traverses entry, all mix layers, and the courier. +5. The courier or service returns one fixed-class response using the supplied + single-use reply material. +6. The reply traverses the consensus-authorized mix route and terminates at a + bounded entry rendezvous or eligible live relay session. +7. The relay stores only the opaque response until expiry or controlled + Composer import. +8. The Composer stages authentication, decryption, deduplication, and message + state advancement, then atomically commits all effects before rendering + content or scheduling an acknowledgment. + +An entry return rendezvous MUST be random, short-lived, bounded, and unrelated +to a stable global username. It MUST NOT become a durable provider mailbox. +Exact queueing, polling, retransmission, acknowledgment, and SURB behavior is +defined by the packet, wire, messaging, and storage specifications. + +Empty reads, hits, misses, replies, retries, and acknowledgments MUST fit the +same declared external traffic classes. A relay MUST continue its configured +cover and retrieval schedule independently of whether the Composer has a real +operation pending. + +## 12. Cover and Loop Traffic Architecture + +Cover generation is a protocol subsystem, not an optional application feature. + +- The Composer prepares user-protocol decoys when they require message or + storage semantics unavailable to the relay. +- The blind relay generates network cover and loop traffic using only public + consensus and ephemeral local state. +- Entries process cover submissions identically to real submissions. +- Mixes generate and process consensus-authorized loop or decoy packets. +- Couriers, services, and replicas implement indistinguishable bounded cover + outcomes defined by their protocol. +- Nodes export only delayed aggregate loss and health measurements. + +The simulator determines cover rate, destination selection, delay, loop, +retry, polling, and shutdown parameters. Operators MUST NOT tune privacy- +critical distributions independently outside an authenticated profile. + +`FOG-SIMULATION.md` records the first deterministic comparison matrix. It +confirms that sparse use and the functional PoC do not support anonymity +claims, and that cover volume, local pool overlap, latency, and long-term +observation proxies trade different resources. It deliberately selects no +numeric profile. Loop, polling, retry, queue, degraded-mode, formal observer, +and trace-driven extensions remain required before parameters are frozen. + +If the minimum traffic or cover conditions attached to a claim disappear, +nodes follow the specified degraded-mode or shutdown policy. They MUST NOT +silently continue under the stronger claim. + +## 13. State Ownership + +| Role | Permitted durable state | Forbidden durable state | +| --- | --- | --- | +| Composer | encrypted identity, contacts, message state, capabilities, drafts, trust anchors, highest epoch | online session credentials for other roles, node private keys | +| Relay | encrypted opaque queues, schedule state, temporary entries, return handles, highest relay-checked epoch | plaintext, contact state, mailbox capabilities, internal routes | +| Authority | descriptors, votes, consensus history, admission and revocation records | user packets, capabilities, data-plane queues | +| Entry | node configuration, bounded replay-independent ingress state, short-lived opaque return state | durable user accounts, plaintext, internal routes, mailboxes | +| Mix | epoch keys, replay state, bounded delay queues, local aggregate counters | message state, application data, full routes, user identities | +| Courier | bounded deduplication, retry and reply state | durable mailboxes, user identity state, plaintext | +| Store | opaque records, tombstones, expiry and replica state | user identities, plaintext, courier dedup state | +| Service | explicitly specified minimum opaque service state | unrelated application state, client network identity | +| Observer | delayed aggregate submissions and published aggregates | event streams, packet identifiers, role secrets | + +State schemas MUST include version, ownership, integrity, maximum size, +retention, migration, backup, restore, corruption, and deletion behavior. +Copying a database between roles or replicas is not a recovery mechanism. + +## 14. Key Ownership + +Each narrower protocol specification MUST refine this table into exact key +lifecycle entries. + +| Key class | Sole owning role | Authorized use | Baseline persistence | +| --- | --- | --- | --- | +| Pairwise contact roots and handshake identities | Composer | private contact authentication and session establishment | long-term encrypted user state, with profile-specific recovery rules | +| Ratchet, outbox, deduplication, and conversation state | Composer | atomic message send, retry, receive, and acknowledgment transitions | encrypted mutable user state, non-resumable after stale restore | +| Directional stream capability and storage outbox state | Composer | rotating box derivation, immutable request generations, reads, writes, tombstones, and receipt validation | encrypted mutable user state, non-resumable after stale restore | +| Composer state-encryption key | Composer | local authenticated encryption | profile-specific protected storage | +| Backup or recovery key | user offline recovery domain | restore Composer state | separate from backup ciphertext | +| Relay queue-sealing key | blind relay | local opaque queue protection | local service-protected storage | +| Relay ephemeral cover state | blind relay | cover and loop construction | bounded or ephemeral | +| Node identity key | one online node | descriptor and role authentication | role-local protected storage | +| Noise link key | one online node | fixed adjacent-link profile | role-local, rotated by profile | +| Entry capsule key | one entry | open entry-bound submission capsule | epoch-bounded role-local state | +| Entry return KEMSphinx key | one entry | exactly one terminal reply unwrap for a short-lived rendezvous | epoch-bounded role-local state | +| Mix KEMSphinx key | one mix | exactly one layer transformation | epoch-bounded role-local state | +| Terminal KEMSphinx key | one courier or service | open terminal service envelope | epoch-bounded role-local state | +| Replica envelope key | one storage replica | replica request and response protection | epoch or storage-profile bounded | +| Replica receipt key | one storage replica | authenticate local durable storage results to the Composer | storage-manifest bounded, separate from envelope, identity, and Noise keys | +| Storage-at-rest key | one replica operator | defense-in-depth disk or database encryption | deployment-specific | +| Authority identity key | one authority | authority identity and authenticated recovery | preferably offline or hardware-protected | +| Authority online vote key | one authority | epoch vote and consensus participation | short-lived or tightly controlled online state | +| Authority wire key | one authority wire service | authenticated descriptor and authority-peer transport | root-certified bounded online state | +| Aggregate signing key | one reporting role | authenticate coarse metrics | role-local | +| Release signing key | offline release domain | sign canonical releases and metadata | offline, never on runtime hosts | + +Public consensus contains only public keys and authenticated parameters. +Private keys MUST NOT be copied through consensus, container images, shared +volumes, environment templates, logs, or support artifacts. + +No role may use a long-term user identity key as a network account, storage +capability, node identity, transport key, release key, or backup key. + +## 15. Network Reachability Policy + +Default-deny reachability is part of the architecture. + +| Source | Permitted destinations | Explicitly forbidden destinations | +| --- | --- | --- | +| Composer | none | every network target | +| Relay | consensus mirrors, eligible entries, explicitly configured local transfer interface | mixes, courier, storage, external bridges, Internet proxy targets | +| Authority | peer authorities, descriptor submitters, consensus publication endpoints, local administration | user data plane, storage records | +| Entry | eligible relays, layer-1 nodes, bounded reply-route peers required by profile | layer 2, layer 3, storage, public Internet | +| Layer 1 mix | entries and layer-2 nodes | relay, layer 3, storage, public Internet | +| Layer 2 mix | layer-1 and layer-3 nodes | relay, entry, storage, public Internet | +| Layer 3 mix | layer-2 and terminal courier/service nodes | relay, entry, storage, public Internet | +| Courier | layer-3 or authorized reply-route mix peers, eligible replicas | relay, Composer, public Internet | +| Store | eligible couriers and replica peers | Composer, relay, entry, ordinary mixes, public Internet | +| Observer | aggregate-reporting roles and publication endpoint | packet interfaces, role databases | +| Bridge | explicitly declared FOG terminal and external endpoint | all undeclared core interfaces | + +The reply protocol MAY require a terminal role to connect to a +consensus-authorized first reply hop that differs from the forward table. That +exception MUST be explicit in `FOG-SPHINX-PROFILES`, constrained by role and +epoch, and tested as part of the firewall policy. + +Management access is a separate operator-local boundary. No central +management credential may administer all authorities, all mix layers, the +courier, and the replica set in a claim-bearing deployment. + +## 16. Co-Location and Operator Separation + +### 16.1 Functional PoC + +The local PoC MAY place separate containers on one host and MAY use simulated +authority or storage fixtures. It MUST still use separate executables, service +users, state paths, key files, ports, and default-deny container networks. + +PoC co-location invalidates operator-independence, infrastructure-diversity, +and production anonymity claims. + +`FOG-LOCAL-POC.md` and `../deploy/podman/topology.json` refine this into the +first deployment contract. The Composer and authority fixtures are +networkless. Relay, entry, one mix in each fixed layer, courier, and four +storage fixtures use only internal pairwise networks for `IF-06` through +`IF-09`. The contract forbids host networking, published ports, shared +writable volumes, shared secret scopes, and missing containment controls. No +Compose manifest is generated until role fixtures exist. + +### 16.2 Alpha + +An alpha profile MUST use: + +- at least three authorities with a 2-of-3 quorum; +- three mix layers with at least two independently operated mixes per layer; +- at least four independently operated storage replicas; +- separate entry, mix, courier, and storage role identities; +- documented operator-family, provider, ASN, location, and administrative + relationships; +- no route containing the same operator family twice. + +One operator MAY run more than one role only when the relationship is declared, +route policy accounts for it, and the relevant security claim explicitly +allows it. One process or private key MUST NOT implement more than one role. + +### 16.3 Claim-bearing production profile + +The preferred initial topology is three mix nodes per layer. Authority, +entry, mix, courier, storage, observer, build, and release administration +SHOULD be organizationally separated where practical. + +No provider account, orchestration control plane, backup service, monitoring +credential, or CI system SHOULD control enough nominal operators to defeat the +claim. Concealed common control remains a documented residual risk. + +## 17. Failure and Degraded Modes + +| Condition | Required architectural response | +| --- | --- | +| Consensus signature or canonical parse failure | reject and retain last safely usable state only within its validity | +| Stale or rolled-back consensus | stop new route construction; require authenticated recovery | +| Authority quorum failure | no locally synthesized consensus; continue only under explicitly valid prior epoch rules | +| Entry failure | choose only another pre-authorized opaque variant under randomized bounded retry policy | +| Mix connection failure | bounded backoff; no layer skip or deterministic emergency route | +| Replay state unavailable | stop affected mix processing until safe recovery or specified fresh epoch | +| Cover process failure | leave the affected unobservability profile and follow explicit shutdown/degraded policy | +| Queue saturation | bounded shedding without detailed remote oracle or amplification | +| Courier or replica timeout | bounded randomized retry through the defined protocol; no direct client fallback | +| Replica loss | follow specified quorum or erasure behavior; never fabricate successful durability | +| Clock uncertainty outside bound | reject time-sensitive new state and surface coarse local fault | +| Update verification failure | retain last verified non-revoked version or stop if policy requires it | +| State corruption | quarantine state, avoid secret-bearing diagnostics, and use authenticated recovery | + +Detailed error codes may exist inside a trusted local process boundary for +testing. Remote peers receive only bounded protocol outcomes that do not expose +parsing, capability, record-existence, or route oracles. + +## 18. Configuration, Build, and Update Boundaries + +Configuration MUST be role-specific and schema-validated. A deployment tool +MAY generate multiple role configurations, but the generated runtime artifacts +MUST contain only the public information and secrets required by that role. + +Runtime services MUST use: + +- a dedicated unprivileged account; +- a role-specific read-only executable and configuration; +- one role-specific writable state directory; +- no shared writable source or configuration checkout; +- explicit network allowlists; +- bounded resource controls; +- disabled core dumps and privacy-unsafe debug modes; +- local secret injection that does not place secrets in command arguments or + container images. + +Build, release signing, consensus signing, node operation, and user identity +management are separate authorities. A successful CI build does not authorize +a release, a release does not authorize a network epoch, and an epoch does not +authorize user messages. + +### 18.1 Cost-minimal build and packaging profile + +One release build MUST compile all required role executables once per supported +operating-system and architecture target. Nodes and operators MUST reuse those +verified artifacts; they MUST NOT require per-node or per-operator compilation. +The build SHOULD share dependency, object, and module caches across role +executables. + +The functional fixture and local alpha laboratory MAY use one immutable +digest-pinned image containing multiple fixture executables, provided every +container activates exactly one role and preserves separate configuration, +service identity, state, secrets, and network policy. This packaging exception +does not permit a multi-role process. + +A distributed alpha or claim-bearing deployment SHOULD assemble one minimal +image per runtime role from the already compiled artifact set. Role images +SHOULD share identical OCI base layers so registries and hosts deduplicate +storage and transfer. Image assembly MUST NOT trigger a separate compilation +for every image or node. + +The supported build matrix MUST contain only platforms required by current +deployment or evidence gates. Independent reproducible rebuilds are release +verification evidence and MAY reuse the same source and pinned toolchain; they +are not required for routine startup of each node. + +## 19. Repository and Module Shape + +The future implementation repository SHOULD begin with this logical layout. +Exact language-specific names MAY vary without changing dependency direction. + +```text +fog/ + cmd/ + fog-compose/ + fog-sx-send/ + fog-sx-receive/ + fog-client-relay/ + fog-authority/ + fog-entry/ + fog-mix/ + fog-courier/ + fog-store/ + fog-observer/ + protocol/ + encoding/ + pki/ + wire/ + sphinx/ + messaging/ + storage/ + composer/ + sx/ + roles/ + authority/ + entry/ + mix/ + courier/ + store/ + observer/ + clientrelay/ + sdk/ + client/ + composer/ + sim/ + specs/ + testvectors/ + tests/ + conformance/ + integration/ + fault/ + topology/ + deploy/ + podman/ + ops/ +``` + +Dependency direction MUST be: + +```text +cmd -> one role -> protocol packages +sdk ------------> protocol packages +sim ------------> protocol models and independent simulation models +tests ----------> public contracts and built executables +``` + +Protocol packages MUST NOT import role implementations, network listeners, +databases, container tooling, or operator configuration. One role package MUST +NOT import another role's implementation. Cross-role behavior occurs only +through versioned protocol interfaces. + +The public SDK MUST expose message and packet construction contracts without +exposing node private APIs. Generic `utils`, shared mutable singletons, a +universal database package, and an in-process plugin bus are forbidden +substitutes for explicit boundaries. + +The following MUST NOT be split yet: + +- separate source repositories for every role; +- server daemons for `fog-drop`, `fog-mailbox`, and `fog-im`; +- a generic external bridge framework inside core nodes; +- operator-selectable cryptographic plugin systems; +- separate databases or queues where a role currently needs no durable state. + +## 20. Architectural Verification + +Before the local PoC, the repository MUST support or define tests for: + +- one executable activating only one role; +- dependency rules preventing role-to-role implementation imports; +- configuration schema rejection of multi-role or unknown-critical settings; +- default-deny reachability for every row in the network policy table; +- inability of Composer images to create or receive network traffic; +- absence of shared writable volumes and private key files across roles; +- fixed packet-class behavior across all native applications; +- complete traversal of entry and all mix layers; +- replay-state loss and fail-closed restart behavior; +- voucher consume-once behavior and atomic ratchet, outbox, deduplication, + inbox, and acknowledgment transactions; +- exact message-envelope and storage-request retry layering: immutable + courier-envelope bytes per request generation with fresh KEMSphinx, route, + entry, rendezvous, and reply material per transmission; +- deterministic storage shards, disjoint intermediates, receipt quorum, + empty-read non-advancement, tombstone precedence, and non-resurrection; +- exact Composer bundle headers, hostile import limits, commit-before-effect, + external-anchor reconciliation, non-resumable recovery, and update floors; +- exact FOG-SX object and frame headers, fixed profile tuple, size padding, + CRC domains, FEC and conflict budgets, no-ACK behavior, and physical + direction fixtures; +- consensus stale, rollback, split, and equivocation scenarios; +- queue, parser, retry, CPU, memory, connection, and storage bounds; +- log and metrics schemas that reject prohibited data classes; +- compromise fixtures showing the information available to each isolated role; +- update signature, version, rollback, and revocation behavior; +- deterministic conformance vectors for every trust-boundary interface. + +Tests MUST observe public contracts and externally visible behavior, not reach +through trust boundaries to share internal state. + +## 21. Threat Traceability + +| Threat | Primary architectural controls | +| --- | --- | +| `TM-NET-01` | uniform packet classes, relay scheduling, cover subsystem, stratified mixes | +| `TM-NET-02` | independent retrieval schedule, rotating capabilities, bounded rendezvous, storage separation | +| `TM-NET-03` | loop health, degraded-mode gate, bounded retries, no bypass | +| `TM-NET-04` | per-mix replay state, single-use reply material, idempotent terminal state | +| `TM-NET-05` | authenticated KEMSphinx processing, terminal validation, uniform failures | +| `TM-NET-06` | complete consensus, fixed profiles, application-independent core behavior | +| `TM-PKI-01` | permissioned admission, family declarations, route constraints | +| `TM-PKI-02` | threshold consensus, monotonic Composer state, consistency evidence | +| `TM-PKI-03` | distinct authorities, quorum rules, offline recovery, deterministic failure | +| `TM-ROLE-01` | blind relay, entry capsules, Composer route authority, no direct fallback | +| `TM-ROLE-02` | one fixed mix layer per process, family-separated routes, role-local keys | +| `TM-ROLE-03` | courier/replica separation, opaque commands, independent replicas | +| `TM-ENDPOINT-01` | networkless Composer, narrow devices, encrypted state, signed updates | +| `TM-ENDPOINT-02` | minimal transfer adapter, bounded formats, no reverse FOG-SX path | +| `TM-ENDPOINT-03` | role-owned state, separate backup keys, no database copying | +| `TM-APP-01` | Composer-side application state, bounded authenticated rendering, no active content | +| `TM-OPS-01` | separate observer, aggregate-only interface, prohibited data classes | +| `TM-SUPPLY-01` | offline release authority, provenance, signed updates, role-specific artifacts | +| `TM-CRYPTO-01` | sole key owners, purpose separation, lifecycle refinement requirement | +| `TM-CRYPTO-02` | consensus-authorized suites, no plugin negotiation or downgrade | +| `TM-AVAIL-01` | bounds at every interface, non-amplification, quotas, explicit degraded modes | + +Every later specification MUST reference the applicable threat IDs and +architecture invariants. If it changes a trust boundary or information flow, +this document and the threat model MUST be updated before implementation. + +## 22. Open Architectural Protocol Contracts + +This architecture intentionally leaves the following to narrower reviewed +specifications: + +- the exact entry submission capsule and packet binding; +- short-lived return rendezvous and offline Composer import mechanics; +- activation of an exact numeric message profile after the structural + `FOG-MESSAGING` contract and its non-active PQXDH, Triple Ratchet, and + ML-KEM Braid candidate pass byte-exact integration, vectors, implementation + review, and independent review; +- the exact reviewed entry and mutual Noise or PQNoise suites and numeric + wire-profile parameters within `FOG-WIRE-1`; X-Wing is the leading KEM to + evaluate, not an active profile; +- activation of a final `FOG-SPHINX-1` primitive suite after the fixed + structural profile and calculated candidate in `FOG-SPHINX-PROFILES.md` pass + benchmarks, vectors, simulation, and independent review; +- activation of an exact numeric storage profile after the structural + `FOG-STORAGE` contract and its non-active narrow BACAP/Pigeonhole candidate + pass primitive review, receipt analysis, geometry, vectors, simulation, and + independent review; +- activation of exact Composer MicroVM and Portable profiles after the + structural `FOG-COMPOSER` contract and its Linux vault, Qubes, Portable, and + TUF candidates pass platform, anchor, fault, recovery, and independent + review; +- cover, loop, delay, retry, retrieval, and shutdown distributions; +- activation of one exact numeric FOG-SX joint profile after the structural + `FOG-SX` contract and its non-active RaptorQ, Reed-Solomon, QR, Lightpipe, + and MIDI candidates pass implementation, license and IPR, resource, vector, + hardware-direction, and independent review; +- exact aggregate metrics and suppression thresholds; +- implementation languages, reviewed libraries, and activation evidence for + the non-active cryptographic candidates in `FOG-CRYPTO-SUITES.md`. + +These are explicit design dependencies. No daemon implementation may resolve +them through undocumented behavior. + +## 23. References + +- FOG threat model: `FOG-THREAT-MODEL.md` +- FOG public key infrastructure: `FOG-PKI.md` +- FOG wire protocol: `FOG-WIRE.md` +- FOG Sphinx profile framework: `FOG-SPHINX-PROFILES.md` +- FOG messaging protocol: `FOG-MESSAGING.md` +- FOG storage protocol: `FOG-STORAGE.md` +- FOG Composer protocol: `FOG-COMPOSER.md` +- FOG simplex transfer protocol: `FOG-SX.md` +- FOG observability protocol: `FOG-OBSERVABILITY.md` +- FOG cryptographic suite evaluation: `FOG-CRYPTO-SUITES.md` +- FOG traffic and topology simulation: `FOG-SIMULATION.md` +- FOG local Podman PoC: `FOG-LOCAL-POC.md` +- Katzenpost mix network specification: + +- Katzenpost public key infrastructure specification: + +- Katzenpost wire protocol specification: + +- Katzenpost Pigeonhole protocol specification: + +- Piotrowska et al., *The Loopix Anonymity System*: + +- Infeld et al., *Echomix: a Strong Anonymity System with Messaging*: + +- Noise Protocol Framework: + + +These references inform role separation, stratified mixing, consensus, +transport, reply, and storage boundaries. FOG requires its own protocol +profiles, conformance evidence, simulation, deployment evidence, and review. diff --git a/docs/FOG-COMPOSER.md b/docs/FOG-COMPOSER.md new file mode 100644 index 0000000..b517db5 --- /dev/null +++ b/docs/FOG-COMPOSER.md @@ -0,0 +1,1677 @@ +# FOG Composer + +Status: Draft 0.1 + +Date: 2026-08-08 + +## 1. Purpose + +This document defines `FOG-COMPOSER`, the networkless endpoint, local-state, +transfer, recovery, and update-verification contract for FOG native services. + +It fixes the common Composer security boundary, MicroVM and Portable +deployment profiles, process separation, authenticated encrypted vault, +transaction and rollback rules, hostile import handling, committed export +bundles, identity-safe recovery, update verification, local rendering, +resource limits, and conformance gates. + +It also records four non-active implementation candidates: + +- `FOG-COMPOSER-CANDIDATE-LINUX-VAULT-1` for a Linux read-only system image, + LUKS2 defense-in-depth volume encryption, a transactional embedded + database, and object-level authenticated encryption; +- `FOG-COMPOSER-CANDIDATE-MICROVM-QUBES-1` for a Qubes-style networkless VM + with narrowly allowlisted qrexec transfer services; +- `FOG-COMPOSER-CANDIDATE-PORTABLE-LINUX-1` for a signed read-only Linux image + on dedicated physically offline hardware; +- `FOG-COMPOSER-CANDIDATE-UPDATE-TUF-1` for offline update metadata derived + from The Update Framework. + +These candidates have no active numeric profile IDs, do not select final +libraries or cryptographic parameters, are not authorized for public release, +and do not establish deployed endpoint-security claims. + +The key words MUST, MUST NOT, REQUIRED, SHOULD, SHOULD NOT, and MAY describe +normative requirements in the sense of BCP 14 when they appear in uppercase. + +## 2. Scope + +`FOG-COMPOSER` owns: + +- the networkless Composer runtime and its local privilege boundaries; +- common, MicroVM, Portable, and lower-assurance transfer profiles; +- booted-image verification requirements visible to the Composer; +- encrypted mutable state, key wrapping, object protection, and state + migration; +- local atomic transactions spanning messaging, storage, PKI, imports, + exports, recovery, and application state; +- local state commitments and optional external monotonic anchors; +- fixed Composer bundle framing and direction-specific record allowlists; +- import quarantine, complete validation, deduplication, and state release; +- transactional export creation and duplicate-export behavior; +- identity recovery packages and non-resumable restored sessions; +- offline release and update verification on Composer systems; +- safe local rendering and native application module boundaries; +- local retention, deletion, diagnostic, and resource-limit behavior; +- Composer-specific conformance and fault-injection evidence. + +This document does not own: + +- message handshake, ratchet, envelope, acknowledgment, or fragmentation + cryptography; +- storage capabilities, replica envelopes, receipts, retention, or polling; +- KEMSphinx packet or SURB construction; +- adjacent online Noise links or blind-relay queue behavior; +- FOG-PKI consensus production, authority recovery, or transparency proofs; +- FOG-SX framing, FEC, physical signaling, or optical hardware; +- entry capsule and return-rendezvous constructions; +- release-repository production, signing ceremonies, or build provenance; +- a native multi-device protocol or automatic account recovery service. + +Those contracts belong to `FOG-MESSAGING`, `FOG-STORAGE`, +`FOG-SPHINX-PROFILES`, `FOG-WIRE`, `FOG-PKI`, `FOG-SX`, the entry and return +specifications, a future `FOG-UPDATE` release contract, and future +multi-device work. + +## 3. Security Boundary and Threats + +### 3.1 Assets + +The Composer holds the highest-value user assets in FOG: + +- message plaintext, drafts, permitted attachments, and rendered history; +- pairwise identity roots, handshake identities, prekeys, ratchets, and + skipped-message keys; +- storage read and write capabilities, recovery tombstones, and outboxes; +- contacts, private labels, verification decisions, and conversation state; +- PKI trust anchors, accepted epochs, checkpoints, manifests, and + equivocation evidence; +- KEMSphinx routes, ephemeral secrets, SURBs, reply tokens, and pending + network work; +- vault, object, transfer-pairing, backup, recovery, and local anchor keys; +- installed release state, trusted release roots, and rollback floors. + +Compromise of an unlocked Composer can expose or alter all local assets. No +storage, boot, VM, or transfer mechanism can preserve confidentiality against +an attacker that controls the code currently using the plaintext and keys. + +### 3.2 Adversaries + +The contract considers: + +- theft or forensic copying of powered-off storage; +- malicious, malformed, replayed, truncated, reordered, or oversized import; +- a compromised blind relay, transfer receiver, removable medium, QR reader, + FOG-SX decoder, or update distributor; +- partial database corruption, torn writes, power failure, disk-full faults, + and stale filesystem snapshots; +- rollback or cloning of a complete internally consistent Composer vault; +- a hostile MicroVM host, hypervisor, firmware, peripheral, DMA device, boot + chain, or system update; +- malicious contacts and authenticated but adversarial message content; +- physical observation, evil-maid access, side channels, and secret remnants; +- dependency, compiler, build, release-key, or update-metadata compromise. + +### 3.3 Trust distinctions + +Object-level authenticated encryption protects stored object confidentiality +and integrity under its exact key assumptions. It does not prove freshness. + +A hash-chained local journal detects missing, reordered, partially restored, +or corrupted state relative to the latest state still available locally. It +cannot detect replacement of the complete vault, journal, and keys by an older +coherent copy. + +Complete rollback detection requires a monotonic anchor outside the rollback +domain. A virtual TPM controlled by the same hostile VM host is not independent +of that host. A counter alone also does not automatically bind the intended +state commitment unless the selected anchor protocol proves that binding. + +Networklessness prevents direct network access by the guest or portable +runtime. It does not prevent a hostile host, firmware, peripheral, or human +from observing or modifying the endpoint. + +## 4. Protocol Invariants + +### COMPOSER-INV-01: No network interface + +A conforming Composer has no network adapter, route, network namespace access, +socket activation, proxy, update proxy, loopback service, HTTP server, RPC +server, or plugin listener. A convenience mode with networking is not a FOG +Composer profile. + +### COMPOSER-INV-02: One active mutable instance + +One Composer identity vault has exactly one active mutable instance. Copying a +vault, VM private volume, database, USB state partition, or live backup does +not create a second device. Any suspected clone freezes affected ratchets, +capabilities, prekeys, outboxes, and monotonic state. + +### COMPOSER-INV-03: Random data keys, human unlock + +Bulk state is encrypted under random vault and object keys. A passphrase or +recovery phrase is processed only by the profile's reviewed memory-hard KDF to +unlock or rewrap random key material. It is never used directly as an AEAD +key, identity key, ratchet seed, storage capability, or backup key. + +### COMPOSER-INV-04: Every sensitive object is authenticated + +Identity, contact, draft, message, ratchet, capability, outbox, inbox, +deduplication, PKI monotonic, recovery, and update state has object-level +authenticated encryption or an equally reviewed authenticated container. +Whole-volume encryption is defense in depth and is not accepted as the sole +object-integrity control. + +### COMPOSER-INV-05: Persist and anchor before effect + +A security-critical transition is not externally exportable and its plaintext +is not renderable until the complete local transaction is durable. Where the +profile claims full rollback detection, the corresponding external monotonic +anchor transition must also be durable before export or rendering. + +### COMPOSER-INV-06: Local chain is not full anti-rollback + +Documentation and UI MUST distinguish local consistency verification from +independent monotonic anchoring. A self-contained vault without an external +anchor MUST NOT claim detection of a complete coherent rollback. + +### COMPOSER-INV-07: Every import is hostile + +Filename, label, QR presentation, media filesystem, MIME type, transport +checksum, FEC result, relay origin, and operator statement confer no +authenticity. Complete bounded parsing and the owning inner cryptographic +verification occur before state transition or rendering. + +### COMPOSER-INV-08: Export contains committed opaque work only + +An export bundle contains only already committed public objects or opaque +protocol work. It never contains message plaintext, drafts, identity private +keys, contact labels, ratchet state, capability roots, database keys, backup +keys, crash diagnostics, or a long-term Composer signature visible to the +relay. + +### COMPOSER-INV-09: Transfer signatures do not create a public identity + +FOG does not sign relay-facing bundles with a long-term user or Composer key. +Each inner object supplies its owning authentication. A local transfer-pairing +authenticator MAY reject random injection but does not replace inner +verification and does not become a remote network identity. + +### COMPOSER-INV-10: Recovery never silently resumes live state + +An identity recovery package can preserve explicitly allowed long-term +identity and contact verification material. Restored live ratchets, +capability streams, prekeys, reply tokens, outboxes, and deduplication windows +remain frozen. They are replaced through authenticated recovery transitions, +not resumed from a stale snapshot. + +### COMPOSER-INV-11: Update verification is offline and monotonic + +An update installs only after threshold signature, metadata chain, target +hash, target length, platform, compatibility, expiry, version, and rollback +floor validation. No online fetch, local administrator override, unsigned +emergency image, or boot failure authorizes a downgrade. + +### COMPOSER-INV-12: Immutable runtime image + +The booted operating-system and Composer image are read-only and verified by a +root authenticated outside that mutable image. Writable application state, +temporary data, logs, and update staging cannot replace executable content. + +### COMPOSER-INV-13: No active imported content + +Initial native applications render bounded plain text and fixed local UI +objects only. HTML, scripts, macros, fonts, office files, PDFs, media codecs, +shell commands, desktop launchers, and automatic external resource loading are +not valid message content. + +### COMPOSER-INV-14: High-assurance paths are physically directional + +The high-assurance export and import paths use separate transmit-only and +receive-only mechanisms. A removable device alternated between online and +offline systems is a named lower-assurance profile, never an invisible +fallback. + +### COMPOSER-INV-15: No secret-bearing diagnostics + +Logs, metrics, crash dumps, support bundles, command arguments, environment +variables, shell history, swap, hibernation, thumbnails, previews, clipboard, +and generic desktop indexes contain no Composer secrets or plaintext. + +### COMPOSER-INV-16: No runtime extension mechanism + +The Composer loads no third-party plugin, interpreted script, external +renderer, generic parser, dynamic protocol module, or operator-selected crypto +provider. New functionality requires a reviewed release and an authenticated +profile transition. + +## 5. Deployment Profiles + +### 5.1 Common profile + +Every Composer profile MUST: + +- boot an authenticated read-only software image; +- omit or disable all network and radio devices and drivers; +- use one dedicated authenticated encrypted mutable vault; +- separate import quarantine and export spool from active vault state; +- disable swap, hibernation, core dumps, automatic crash reporting, previews, + indexing, and host clipboard integration; +- mount no internal disk or general removable filesystem automatically; +- expose only the minimum display, human input, import, export, state, and + optional monotonic-anchor devices; +- enforce the exact bundle profiles, limits, and direction allowlists; +- require explicit human unlock and explicit update or recovery ceremonies; +- show the current assurance profile and lost assumptions locally. + +### 5.2 MicroVM profile + +The MicroVM definition contains no virtual NIC. Network absence is enforced at +the hypervisor configuration, guest kernel configuration, process sandbox, +and conformance-test levels. + +Host integration is limited to: + +- a minimal display path; +- explicit keyboard and pointing input; +- one bounded import data channel; +- one separately authorized bounded export data channel; +- one dedicated mutable state block device; +- an optional external monotonic-anchor interface. + +Shared directories, host filesystem mounts, arbitrary qrexec, drag-and-drop, +clipboard, audio, camera, USB passthrough, generic guest agents, shell +services, and bidirectional device forwarding are forbidden. + +The host and hypervisor remain inside the endpoint trust assumption. A +MicroVM profile can reduce accidental network exposure and contain some +application failures, but cannot protect unlocked memory or execution from a +host that can inspect or replace the guest. + +The Qubes candidate uses a dedicated no-NetVM qube and two exact qrexec +services with fixed direction and byte bounds. General qrexec command +execution, file-copy services, URL opening, clipboard, and update proxy access +remain denied. Qubes is an integration candidate, not a runtime dependency or +an automatic security claim. + +### 5.3 Portable profile + +The Portable profile boots signed immutable media on a physically offline +computer. Its kernel and initramfs omit network, Bluetooth, cellular, NFC, +Thunderbolt networking, and unnecessary radio drivers. Firmware setup and +physical switches disable available radios where supported. + +It MUST: + +- verify the bootloader, kernel, initramfs, command line, root-image digest, + and Composer release identity before vault unlock; +- use a read-only verified root image and a separate encrypted mutable state + partition; +- refuse automatic internal-disk, network-share, and foreign-filesystem + mounting; +- use dedicated receive-only and transmit-only transfer devices in its + high-assurance form; +- keep Composer state media away from online machines; +- warn locally when Secure Boot, measured boot, immutable-root verification, + external anchoring, or physical directionality is absent. + +Portable means the signed system and encrypted state can be carried. It does +not mean the same active state may be cloned or used concurrently. A profile +that binds rollback protection to one machine TPM is machine-bound even if its +boot media is removable. + +### 5.4 Portable shuttle profile + +`PORTABLE_SHUTTLE` permits one explicitly labeled removable transfer medium to +move opaque bundles between online and offline systems. It is lower assurance +because the online system can attack the medium controller, filesystem, and +subsequent offline parser and because the medium provides a physical return +channel. + +This profile still requires the fixed Composer bundle parser, separate import +quarantine, no automatic execution, no general file browsing, and complete +inner authentication. It MUST NOT inherit the high-assurance simplex or +peripheral-compromise claim. + +### 5.5 Claim matrix + +| Property | MicroVM | Portable high assurance | Portable shuttle | +| --- | --- | --- | --- | +| Composer process has no network | required | required | required | +| Host compromise protection | not claimed | not applicable while dedicated offline hardware is honest | not claimed for online transfer host | +| Read-only verified system image | required | required | required | +| Separate directional transfer hardware | profile-dependent | required | absent by definition | +| Complete rollback detection | only with an anchor outside the hostile host | only with independent anchor | only with independent anchor | +| Physical peripheral isolation | host-dependent | required and measured | weakened | +| Endpoint compromise protection while unlocked | not claimed | not claimed | not claimed | + +## 6. Process and Module Architecture + +### 6.1 Security domains + +The Composer image contains these local domains: + +1. `fog-compose`: the only process that unlocks vault keys, performs protocol + state transitions, and renders authenticated plaintext; +2. import decoder: an unprivileged sandbox that reads one raw transfer stream, + validates only outer framing and limits, and writes one quarantine object; +3. export encoder: an unprivileged sandbox that reads one already sealed + opaque export and drives one transmit-only backend; +4. update verifier: a maintenance environment that has release roots and + inactive image access but no unlocked Composer vault; +5. optional anchor adapter: a minimal process or device interface that exposes + only the profile's monotonic prepare, advance, and read operations. + +The import decoder does not receive vault keys, identity state, contact state, +message plaintext, network configuration, a shell, or writable executable +paths. Its output remains untrusted when `fog-compose` opens it. + +The export encoder cannot query the vault or create new protocol work. It can +read only one immutable export spool item selected by `fog-compose` and cannot +write to import quarantine. + +### 6.2 Internal modules + +Inside the `fog-compose` trust domain, responsibilities remain explicit: + +```text +ui + -> native applications + -> messaging and storage coordinators + -> transaction service + -> encrypted vault + +import coordinator -> protocol verifiers -> transaction service +export coordinator -> committed protocol outbox -> export spool +PKI verifier -------^ | +anchor coordinator ---------------------------^ +``` + +The initial implementation SHOULD use modules named by responsibility: + +- `composer/vault`: key hierarchy, encrypted objects, schema, transactions; +- `composer/anchor`: state commitments and monotonic-anchor protocol; +- `composer/import`: bundle validation, quarantine, deduplication, dispatch; +- `composer/export`: committed selection, sealing, spool lifecycle; +- `composer/update`: trusted metadata and installed-version state; +- `composer/recovery`: recovery package creation and restore freeze; +- `composer/ui`: safe presentation and explicit user decisions; +- `apps/drop`, `apps/mailbox`, and `apps/im`: native state machines only. + +Protocol modules do not import UI, filesystem, database, qrexec, removable +media, or platform code. Platform adapters do not implement messaging, +storage, PKI, or cryptographic state transitions. + +### 6.3 What not to split + +The initial implementation does not create: + +- separate network services for native applications; +- one database per application; +- a generic plugin host or IPC bus; +- a background indexing or search service with plaintext access; +- a universal crypto, filesystem, archive, or document adapter; +- concurrent writable Composer processes. + +One transaction owner and one encrypted database simplify the required +cross-layer atomic commits. Process separation is used only where it removes +raw transfer or update parsing from the vault-bearing process. + +## 7. Boot and Runtime Hardening + +### 7.1 Verified immutable image + +The profile authenticates the complete boot path and one immutable root-image +digest. The Linux candidate uses a signed boot artifact and `dm-verity` for +read-only block verification. The authenticated root digest must be inside the +signed boot chain, not supplied by mutable kernel arguments or the state +volume. + +Verification failure stops before vault unlock. An integrity error after boot +locks the vault, produces no export, and enters recovery. Ignore-corruption and +continue-on-verification-failure modes are forbidden. + +### 7.2 Writable mounts + +The runtime permits only: + +- the dedicated encrypted state volume; +- a bounded encrypted or memory-backed import quarantine; +- a bounded export spool containing opaque committed bundles; +- bounded memory-backed temporary directories; +- explicit update staging only in maintenance mode. + +Executable, setuid, device, and interpreter behavior is disabled on mutable +mounts where the platform supports it. Imported filenames never become local +paths. The Composer does not traverse a foreign filesystem supplied by a +transfer medium. + +### 7.3 Runtime controls + +The active Composer profile requires: + +- no swap or hibernation; +- disabled core dumps and process-memory crash capture; +- locked-down debugging, tracing, performance counters, and ptrace; +- no shell or package manager in the user session; +- no automatic login or vault unlock; +- strict process, file-descriptor, memory, CPU, and disk quotas; +- default-deny device and syscall policy, including network socket creation; +- memory-backed plaintext staging with bounded lifetime; +- explicit lock on suspend, display loss, anchor loss, or integrity fault. + +Memory locking and explicit zeroization are best-effort implementation +controls. They do not prove that compilers, kernels, caches, firmware, DMA, +hibernation remnants, or physical memory retained no copy. + +## 8. Vault and Key Hierarchy + +### 8.1 Vault layers + +The Composer uses both: + +1. full-volume encryption to hide filesystem metadata, database pages, + journals, temporary files, and free space while powered off; +2. object-level authenticated encryption for every sensitive logical record. + +The Linux candidate evaluates LUKS2 for layer 1. It does not rely on ordinary +sector encryption to authenticate logical records. Any LUKS2 integrity mode +requires separate maturity, performance, recovery, and power-failure review. + +### 8.2 Key hierarchy + +The minimum hierarchy is: + +```text +human unlock secret + -> profile-fixed memory-hard KDF + -> unlock KEK + -> unwrap random vault key + -> profile-fixed KDF/exporter + -> identity-object key epoch + -> contact-object key epoch + -> messaging-state key epoch + -> storage-state key epoch + -> PKI-state key epoch + -> draft/content key epoch + -> outbox/import/export key epoch + -> local-state-authentication key epoch +``` + +The backup or recovery key hierarchy is generated independently. It never +derives from the live vault key, a contact root, message ratchet, storage +capability, transfer-pairing key, release key, or monotonic-anchor key. + +The unlock KDF stores its algorithm identifier, salt, memory cost, time cost, +parallelism, and output length in authenticated keyslot metadata. Parameters +are benchmarked per supported hardware class and may be raised through a +versioned rewrap without reencrypting all logical objects. + +The deployment profile states whether volume unlock and object-vault unlock +use one human secret or separate factors. If one human secret is used, each +layer has independent salts, context, KDF output, and wrapping key. Raw keys +are never reused between the LUKS2 and object-vault layers. + +### 8.3 Keyslot rules + +A keyslot wraps only random vault or recovery key material. Adding, removing, +or changing a passphrase is an authenticated transaction. The previous slot +remains in the live header only until the new slot and replacement header +backup are durably verified. + +A memory-hard KDF raises guessing cost but does not turn a weak passphrase into +a high-entropy secret. The UI requires a profile-appropriate secret and states +the offline-guessing risk. + +Removing a LUKS2 keyslot does not revoke an old passphrase against an attacker +who retained an earlier header backup containing that slot and the same volume +key. True revocation against copied old headers requires a reviewed full +volume-key and vault-key rotation, reencrypted data, retirement of old object +keys, and controlled destruction of obsolete headers and media. + +Unlock secrets are accepted only through the trusted local UI or a narrowly +specified hardware-token protocol. They never appear in command arguments, +environment variables, files in the export bundle, logs, clipboard, or shell +input history. + +Failed unlock attempts have bounded memory and CPU cost. The local UI may +apply a coarse delay, but denial-of-service resistance cannot depend on an +attacker-writable on-disk failure counter. + +### 8.4 Object envelope + +Every encrypted logical object has a canonical profile-fixed header containing +at least: + +```text +[ + vault_format_version, + vault_profile_id, + network_id, + composer_instance_id, + object_type, + object_id, + object_generation, + transaction_generation, + key_epoch, + plaintext_length, + padded_length, + nonce, + ciphertext +] +``` + +All fields preceding `ciphertext` are authenticated associated data. The +profile fixes lengths, encoding, nonce construction, padding classes, AEAD, +KDF, maximum plaintext, and key epoch. Unknown fields, alternate encodings, +nonce reuse, invalid padding, counter wrap, or authentication failure reject +the object. + +`composer_instance_id` is a random local domain separator. It is never placed +in relay-facing bundles, contact cards, messages, PKI, storage records, public +logs, or release metadata. + +### 8.5 Object classes + +The vault separates at least these object classes and key purposes: + +| Class | Examples | Restore rule | +| --- | --- | --- | +| Identity | contact roots, handshake identities | only through identity recovery policy | +| Contact | public roots, fingerprints, local labels, verification decisions | public and local metadata may be recovered | +| Messaging live state | ratchets, prekeys, skipped keys, ACK and dedup windows | stale copy never resumes | +| Storage live state | capabilities, indexes, tombstones, receipts, retry generations | stale copy never resumes | +| Content | drafts, inbox, sent plaintext, reassembly | optional local retention, not required for identity recovery | +| Protocol outbox | immutable envelopes, boxes, packets, reply material | exact live instance only | +| PKI state | genesis root, highest consensus, log checkpoint, manifests | monotonic verification required | +| Release state | trusted roots, metadata versions, installed target, rollback floor | monotonic verification required | +| Local control | transaction journal, state commitment, anchor receipt | never exported or identity-recovered as live state | + +### 8.6 Candidate primitives + +`FOG-COMPOSER-CANDIDATE-LINUX-VAULT-1` evaluates Argon2id for unlock key +derivation and XChaCha20-Poly1305 for object protection through maintained +reviewed libraries. The candidate uses random nonces from the OS CSPRNG and +purpose-separated KDF outputs. + +No primitive, parameter, library, ABI, database, or vault profile becomes +active merely because it appears here. Activation requires exact versions, +byte-level vectors, nonce analysis, crash tests, benchmarks, dependency +review, and independent security review. + +## 9. Transactional State Model + +### 9.1 One transaction owner + +Exactly one `fog-compose` process opens the mutable vault for writing. It uses +one transaction engine capable of atomic durable commit across every logical +object participating in a protocol transition. + +Messaging ratchet state, storage capability state, outbox objects, PKI +monotonic state, application queue state, import deduplication, and export +eligibility MUST NOT be committed through independent databases or eventually +consistent workers. + +### 9.2 State commitment + +Every security-critical transaction produces a canonical `StateCommitment`: + +```text +[ + commitment_format_version, + vault_profile_id, + network_id, + composer_instance_id, + transaction_generation, + previous_commitment, + encrypted_catalog_root, + highest_consensus_epoch, + consensus_hash, + transparency_tree_size, + transparency_root_hash, + release_root_version, + installed_release_version, + import_generation, + export_generation, + transaction_class +] +``` + +The commitment uses a profile-fixed authenticated hash or MAC construction. +It contains no plaintext, contact identifier, message identifier, capability, +box ID, or application type. It remains local except for a private external +anchor that is explicitly part of the same Composer trust domain. + +### 9.3 Unanchored commit + +An unanchored profile: + +1. stages all new encrypted objects and the next catalog separately; +2. validates cross-object invariants and resource bounds; +3. writes and syncs the transaction journal; +4. atomically selects the new catalog and state commitment; +5. syncs the database and containing filesystem metadata; +6. only then releases export eligibility or authenticated plaintext. + +This detects ordinary partial writes and local history discontinuity. A +complete older vault copy containing its matching keys and journal can still +pass. + +### 9.4 Externally anchored commit + +An anchored profile uses a prepared generation so the external effect never +precedes the anchor: + +1. retain generation `N` as the active catalog; +2. stage generation `N+1`, its encrypted objects, undo information, and exact + commitment in a durable `PREPARED` namespace; +3. sync the complete prepared namespace without exposing its work; +4. ask the independent anchor to advance from the exact accepted generation + and commitment to `N+1` and the new commitment; +5. receive and verify one anchor receipt bound to the instance, generations, + old commitment, and new commitment; +6. atomically select `N+1` as active and persist the receipt; +7. sync the active selector and journal; +8. only then export work or render newly accepted plaintext. + +The anchor operation is compare-and-advance, not an unchecked write. It must +reject a wrong old generation, wrong old commitment, repeated alternate next +commitment, counter wrap, unauthorized reset, or another instance. +An exact retry of an already completed compare-and-advance is idempotent and +returns the same authenticated successor state without another increment. + +### 9.5 Crash reconciliation + +At startup: + +- anchor equals latest finalized local commitment: open normally; +- anchor equals the one exact durable prepared successor: finalize it before + any other operation; +- anchor remains at the finalized predecessor and no external effect was + released: discard the prepared successor using its durable staging state; +- anchor is ahead without the exact prepared successor: enter + `RECOVERY_REQUIRED`; +- anchor has the same generation but another commitment: enter + `CLONE_OR_TAMPER_DETECTED`; +- local state is ahead of, behind, or unrelated to the anchor outside the + permitted one-step reconciliation: enter `RECOVERY_REQUIRED`. + +No user confirmation, clock change, file rename, or import bundle overrides a +mismatch. + +### 9.6 Anchor assurance levels + +The registry defines: + +- `LOCAL_CHAIN`: no external anchor and no complete-rollback claim; +- `HOST_BOUND_ANCHOR`: useful against accidental snapshot restore but not a + hostile MicroVM host controlling the anchor; +- `INDEPENDENT_ANCHOR`: a separate hardware or physically controlled state + that is outside the vault and host rollback domain. + +A TPM 2.0 NV counter is only a candidate building block. The active anchor +profile must prove reset authorization, endurance, atomic crash behavior, +binding between generation and commitment, device replacement, backup, +recovery, and denial-of-service behavior. A bare increment command is not by +itself the FOG anchor protocol. + +The anchor has no network interface and receives only its private local +instance handle, generation numbers, and opaque fixed-length commitments. It +does not receive the commitment body, object catalog, contact, message, +capability, application type, plaintext, or vault key. It necessarily observes +local anchor-operation count and timing, which remains an endpoint metadata +risk. + +### 9.7 Database candidate + +The Linux candidate evaluates one SQLite database with one writer and an exact +durability profile. The selected journal mode, synchronization level, +filesystem, block device, locking behavior, power-loss assumptions, and backup +API become immutable profile inputs. + +Copying an SQLite main file while a transaction or hot journal exists is not a +backup. The implementation uses the reviewed backup API or a fully quiescent +profile-specific snapshot and preserves every required journal and metadata +file. Database integrity checks do not replace object authentication or an +external monotonic anchor. + +## 10. Composer Transfer Bundle + +### 10.1 Fixed outer header + +Every Composer bundle starts with this exact 128-byte header: + +```text +offset length field +0 8 magic +8 2 bundle_format_version +10 1 bundle_kind +11 1 flags +12 4 bundle_profile_id +16 32 network_id +48 32 bundle_nonce +80 8 payload_length +88 4 record_count +92 4 record_table_length +96 32 payload_digest +``` + +`magic`, version, kind, flags, profile, counts, and lengths have one canonical +encoding. `bundle_nonce` contains 256 CSPRNG bits and is unique to the bundle; +it is not an identity or protocol replay token. `payload_digest` provides +bounded corruption detection and canonical deduplication only. It does not +authenticate the producer. + +### 10.2 Fixed record header + +Each record starts with this exact 48-byte header: + +```text +offset length field +0 2 record_type +2 2 record_version +4 4 flags +8 8 actual_length +16 8 padded_length +24 16 record_id +40 8 reserved +``` + +The body contains `actual_length` bytes followed by zero padding to +`padded_length`. The record table lists exact ordered offsets and types before +any body is dispatched. `record_id` is random and bundle-local. Reserved bits, +duplicate IDs, overlap, gaps outside canonical padding, non-zero padding, +integer overflow, inconsistent lengths, and trailing data reject the complete +bundle. + +Bundles contain no nested bundle, archive, directory, symlink, device node, +filesystem image, filename, URI, MIME type, compression stream, or executable +metadata. + +### 10.3 Bundle kinds + +The registry defines separate allowlists for: + +- `RELAY_EXPORT`: committed KEMSphinx submissions and bounded public relay + scheduling hints already authorized by the Composer; +- `RELAY_IMPORT`: complete signed PKI objects, opaque KEMSphinx replies, + conflict evidence, and bounded public relay state; +- `CONTACT_EXPORT` and `CONTACT_IMPORT`: one bounded contact card, voucher, or + authenticated contact transition; +- `RECOVERY_EXPORT` and `RECOVERY_IMPORT`: one encrypted recovery package and + its public format metadata; +- `UPDATE_IMPORT`: signed release metadata and exact target artifacts handled + only by the maintenance environment. + +Wrong-direction records reject the bundle. `UPDATE_IMPORT` is never parsed by +the unlocked ordinary Composer process, and ordinary relay or contact bundles +cannot contain an executable target. + +### 10.4 Absolute version-1 limits + +These are parser ceilings, not recommended operational batch sizes: + +| Item | Absolute limit | +| --- | --- | +| Outer header | exactly 128 bytes | +| Record header | exactly 48 bytes | +| Nesting | forbidden | +| Relay bundle | 64 MiB | +| Relay records | 2048 | +| One relay record | 256 KiB | +| Contact bundle | 1 MiB | +| Contact records | 64 | +| Recovery bundle | 64 MiB | +| Recovery records | 256 | +| Update metadata | 64 MiB | +| Complete update bundle | 16 GiB, streamed only | +| Update records | 4096 | +| Unknown record type or flag | reject complete bundle | + +FOG-SX and deployment profiles set lower transport and memory limits. An +update target is streamed to an inactive verified image and never allocated as +one memory buffer. + +### 10.5 Authentication ownership + +The outer bundle is a transport container. Authentication remains owned by +each embedded protocol: + +- PKI objects use authority signatures and monotonic consensus rules; +- KEMSphinx replies use their packet, SURB, token, storage, and message + authentication; +- contact objects use the exact contact-root or voucher signature rules; +- recovery objects use the recovery envelope and separately held key; +- updates use the release metadata threshold and target hashes. + +A Composer-relay pairing key MAY authenticate a local bundle envelope to +reduce random injection and accidental cross-user delivery. The relay is +still untrusted, a pairing MAC is never accepted as message or PKI +authenticity, and the pairing handle is not exported into the FOG network. + +## 11. Import Processing + +### 11.1 Quarantine + +Raw input first enters a new size-limited quarantine object created with an +unpredictable local name and exclusive creation. The decoder streams the +input, enforces the outer limit, calculates the digest, syncs the completed +object, and closes the input before `fog-compose` can open it. + +The decoder never extracts files or follows a path supplied by input. Partial, +oversized, timed-out, or multiply opened inputs are deleted without entering +the vault. + +### 11.2 Validation order + +The Composer: + +1. opens the quarantine object read-only without following links; +2. validates exact total size and the 128-byte header; +3. validates bundle direction, network, profile, kind, count, and limits; +4. validates the complete record table and non-overlap before allocation; +5. streams every record through its owning strict parser into staged state; +6. verifies every inner signature, AEAD, hash, token, generation, expiry, and + monotonic rule required by that record type; +7. compares conflicting complete PKI views and preserves evidence rather than + merging them; +8. computes all cross-record and cross-protocol state transitions; +9. commits import digest, deduplication, new protocol state, inbox, and any + resulting outbox through Section 9; +10. only after final commit and required anchor advance, releases plaintext or + marks resulting work exportable; +11. destroys staged plaintext and expires the quarantine object. + +One invalid critical record rejects the complete bundle. The parser does not +continue in order to collect attacker-selected diagnostic detail. + +### 11.3 Duplicate and replay handling + +The Composer stores a keyed local import identifier derived from the complete +bundle digest and profile. An exact duplicate is idempotent and does not repeat +rendering, ratchet advancement, capability advancement, voucher consumption, +update installation, or recovery. + +The bundle identifier is only an outer deduplication aid. Each embedded +protocol still performs its own replay and generation checks. Repacking the +same records into another bundle cannot bypass those checks. + +### 11.4 Failure privacy + +Detailed failure remains local and bounded. The Composer does not +automatically export a parse error, invalid-contact error, decryption error, +missing-message error, stale-state error, or update-verification oracle. + +The UI maps failures to coarse classes such as `INVALID_IMPORT`, +`AUTHENTICATION_FAILED`, `STALE_OR_ROLLED_BACK`, `RESOURCE_LIMIT`, +`RECOVERY_REQUIRED`, and `UNSUPPORTED_PROFILE`. Secret values and attacker +bytes are not copied into diagnostics. + +## 12. Export Processing + +### 12.1 Export transaction + +For every export generation, the Composer: + +1. selects only committed eligible outbox objects under the active schedule; +2. validates their protocol profile, lifetime, retry, geometry, and state; +3. generates any fresh KEMSphinx, SURB, route, entry, rendezvous, and reply + material required for this network transmission; +4. stages the exact immutable bundle, random bundle nonce, record table, + digest, export generation, and outbox transitions; +5. commits all state and advances the required external anchor; +6. creates a new export spool object with exclusive creation; +7. writes, syncs, seals read-only, and reopens the spool object to verify its + exact bytes and digest; +8. only then authorizes the export encoder to transmit that one object. + +A crash before step 5 creates no exportable bytes. A crash after step 5 +recovers the exact committed bundle. A crash during spool creation rebuilds +only those same committed bytes and does not advance a ratchet, capability, or +packet generation again. + +### 12.2 Duplicate physical export + +Copying or replaying one already sealed bundle can cause duplicate relay +submission. Bundle, packet, courier, storage, and message deduplication remain +required. The Composer never assumes physical transfer occurred merely +because it authorized the encoder. + +If the outcome is unknown, later retry creates the fresh outer packet material +required by the owning packet and storage profiles from already committed +inner state. It does not reconstruct an end-to-end message envelope or reuse a +single-use reply secret contrary to those profiles. + +### 12.3 Export contents + +Relay-facing export MUST NOT contain: + +- plaintext, local UI strings, application names, contact labels, drafts, or + message history; +- long-term user signatures over the bundle, stable Composer instance IDs, or + vault generations; +- message, session, capability, box, or receipt identifiers outside their + required opaque cryptographic layer; +- private PKI, release, recovery, backup, state, or transfer keys; +- filesystem paths, usernames, hostnames, locale, timezone, device model, or + software diagnostics. + +Export size class and timing remain observable to the physical transfer path +and blind relay. Cover and scheduling profiles, not the bundle container +alone, govern those metadata claims. + +### 12.4 Spool retention + +An opaque export spool item remains until one of: + +- an explicitly lower-assurance local transfer profile returns an + authenticated acceptance permitted by that profile; +- a bounded re-export window ends; +- the owning protocol produces authenticated terminal evidence; +- the operation is explicitly cancelled before a forbidden state transition; +- recovery freezes the entire instance. + +The high-assurance simplex profile has no automatic receiver acceptance path. +Its Composer therefore relies on bounded spool retention and later owning- +protocol evidence, not an FOG-SX acknowledgment. + +Deletion of a spool item never rewinds its protocol state. Sensitive reply +material and ephemeral packet keys follow their shorter owning lifetimes. + +## 13. Backup and Recovery + +### 13.1 Recovery goals + +Composer recovery is designed to preserve the minimum long-term authority +needed to reestablish an identity and verify known contacts. It is not a +transparent snapshot restore and does not promise recovery of undelivered +messages, forward-secret message keys, live storage positions, consumed +prekeys, pending acknowledgments, or current network work. + +The default `IdentityRecoveryPackage` MAY contain: + +- the FOG network ID and genesis trust-anchor material; +- the long-term pairwise or accountless identity roots explicitly selected + for recovery; +- contact public roots, verified fingerprints, and verification status; +- encrypted local contact labels when the user includes them; +- release trust roots and minimum accepted release version; +- the latest public PKI checkpoint and consistency metadata as a recovery + starting point, never as permission to roll backward; +- identity-generation and recovery-package sequence metadata; +- a declaration that every messaging session and storage stream must be + replaced before use. + +The default package MUST NOT contain live ratchets, chain keys, message keys, +skipped keys, one-time prekeys, active storage read or write capabilities, +outbox ciphertexts, SURBs, reply tokens, pending packets, deduplication +windows, transfer-pairing keys, local anchor credentials, or an active +Composer instance ID. + +### 13.2 Recovery envelope + +The recovery plaintext is one canonical bounded object with an explicit +format version, network, identity set, package sequence, creation release, +key profile, content allowlist, and restore policy. It is padded and +authenticated under a random recovery data key. + +That random key is wrapped by a distinct recovery key hierarchy. Recovery key +material is stored separately from the recovery ciphertext. A recovery +passphrase, if supported, uses its own profile-fixed memory-hard KDF, salt, and +parameters and does not reuse the live-vault keyslot or passphrase verifier. + +Recovery filenames, QR labels, media labels, and transport checksums are not +authenticated metadata. Every field that affects identity, version, content, +or restore behavior is inside the authenticated envelope. + +### 13.3 Recovery export + +Recovery creation requires explicit local user confirmation and a dedicated +ceremony outside normal message export. The Composer: + +1. validates that the selected identities and contact metadata are eligible; +2. generates a fresh recovery package ID and random data key; +3. constructs and encrypts the exact canonical package; +4. atomically records the package sequence and digest in live state; +5. advances the external anchor when required by the active profile; +6. exports ciphertext and recovery key material through distinct controlled + paths; +7. verifies one complete test decryption before reporting success; +8. erases transient recovery plaintext and wrapping material. + +Normal relay export, contact exchange, and FOG-SX network work MUST NOT carry a +recovery package or recovery key. + +### 13.4 Restore + +Restore occurs into a fresh verified Composer image and a newly initialized +vault with a new random `composer_instance_id`, vault key, object key epochs, +transfer-pairing keys, and local anchor state. + +After verifying and decrypting the package, the new Composer: + +- imports the allowed identity roots and contact verification history; +- refuses any recovered PKI, release, or rollback state lower than the trusted + state embedded in the verified recovery image or independent anchor; +- marks every historical messaging session and storage stream + `RECOVERY_REQUIRED` or `CLOSED`; +- creates no message, packet, prekey, voucher, read, write, or ACK from restored + mutable protocol bytes; +- obtains a current PKI view through the full long-offline consistency path; +- verifies the current release chain and rollback floor; +- uses the recovered identity authority to authenticate fresh contact-session + and storage-stream transitions; +- warns that contacts may need independent fingerprint confirmation when + compromise, identity change, or ambiguous recovery is suspected. + +The old instance is not automatically revoked merely because a new vault was +created. If the old device may still operate, identity compromise and clone +procedures apply and contacts require an authenticated identity transition. + +### 13.5 Full-state archives + +A routine full copy of a live Composer vault is forbidden as a resumable +backup. A profile MAY create a separately encrypted forensic archive for +disaster analysis, but it is marked `NON_RESUMABLE`, contains no unlock or +anchor key beside its ciphertext, and cannot be opened as an active vault. + +Copying a VM private volume, SQLite file, LUKS device, portable state +partition, or suspended memory image is not recovery. Such a copy is clone +evidence and freezes all live state if discovered. + +### 13.6 Deletion limits + +Deleting an object key or recovery key can make surviving ciphertext +inaccessible under the stated assumptions. It does not prove removal from +RAM, flash translation layers, snapshots, filesystem journals, controller +caches, old media, recipient devices, or adversarial copies. + +The UI describes deletion as local best effort with explicit retained-copy +limits. It never reports cryptographic erasure as physical proof. + +## 14. Offline Update Verification + +### 14.1 Separation of authority + +Release authority is separate from user identity, FOG-PKI authority, node, +relay, storage, backup, recovery, and Composer state authority. Runtime images +contain only public release roots and current trusted metadata, never a +release signing key. + +An update distributor, relay, mirror, removable medium, QR label, or package +manager is an untrusted transport. It cannot authorize code. + +### 14.2 Update candidate + +`FOG-COMPOSER-CANDIDATE-UPDATE-TUF-1` evaluates an exact future TUF revision +and maintained client implementation. The candidate uses distinct threshold +roles for root, targets, snapshot, and timestamp metadata, consistent target +hashes and lengths, version monotonicity, expiry, and sequential root +rotation. + +The selected release profile MUST pin: + +- exact TUF specification and implementation revisions; +- root, targets, snapshot, timestamp, and delegated-role thresholds; +- key algorithms, key IDs, role separation, and offline-key requirements; +- canonical metadata encoding and absolute size limits; +- trusted-time and maximum-clock-uncertainty behavior; +- target naming, architecture, deployment profile, and compatibility fields; +- installed-version, minimum-version, revocation, and rollback-floor rules; +- root rotation, repository recovery, and emergency response ceremonies. + +TUF metadata transport security is not release authenticity. A valid older +but unexpired view is still subject to the Composer's highest accepted +versions, rollback floor, and freeze policy. + +### 14.3 Update bundle validation + +The maintenance environment: + +1. verifies the fixed Composer `UPDATE_IMPORT` framing and streaming limits; +2. starts from the currently trusted release root stored in monotonic state; +3. applies every intermediate root version sequentially with the required old + and new thresholds; +4. verifies timestamp, snapshot, targets, delegations, versions, expiry, + hashes, lengths, and consistent-snapshot rules; +5. rejects metadata below any locally trusted version or rollback floor; +6. verifies target architecture, deployment profile, state-schema range, + boot profile, and hardware requirements; +7. streams each target to inactive storage while hashing and enforcing its + exact declared length; +8. verifies the complete inactive image and its signed boot and root digest; +9. records a signed-metadata-bound prepared boot transition without opening + the Composer vault; +10. activates the new boot slot for one bounded trial; +11. lets only the verified target image, after explicit user unlock, validate + the prepared transition, migrate state if required, commit the installed + release and rollback floor through Section 9, and mark boot success. + +No target is executed, mounted writable, or parsed by its own code before its +owning metadata, hash, and length have been verified. + +### 14.4 Trusted time and freeze + +Update expiry requires a trusted update-start time with a stated uncertainty. +File modification times, removable-media clocks, relay timestamps, target +timestamps, HTTP headers, and unauthenticated user input are not trusted time. + +If time uncertainty prevents expiry validation, the Composer stops update +installation and invokes a separately authenticated time-recovery ceremony. +It does not disable expiry. Highest accepted metadata versions reduce rollback +risk but do not independently prove that a distributor has supplied the +latest release. + +### 14.5 A/B image and boot failure + +The Portable candidate uses inactive-image installation and a bounded boot +trial. Automatic fallback is permitted only to an image still above the +authenticated rollback floor and not explicitly revoked. + +If the new image and the prior image are both unauthorized or incompatible, +the system enters signed recovery media rather than booting an older vulnerable +release. A boot-success marker is not trusted if it can be rolled back without +the profile's monotonic control. + +### 14.6 State migration + +A release declares the exact source and target vault schema range. Migration: + +- runs in a dedicated mode of the verified target image after explicit user + unlock; the maintenance update verifier never receives vault keys; +- runs offline with network and ordinary import/export disabled; +- opens the old state through the old reviewed reader and writes a separately + staged new catalog; +- authenticates and validates every source object before transformation; +- rejects unknown-critical object types and counter or size overflow; +- preserves no old live-state copy as a resumable second Composer; +- commits the new schema and release version through Section 9; +- cannot be reversed after external effect or anchored finalization. + +If migration fails before finalization, the old still-authorized image and +state remain active. If the anchor or state has advanced but reconciliation +cannot prove the exact prepared migration, recovery is required. + +### 14.7 Emergency response + +Emergency metadata may revoke a target or raise the minimum release version, +but uses the normal authenticated root and delegated authority rules. There is +no unsigned rescue build, universal operator password, hidden update URL, +network bypass, or local force-install flag. + +## 15. PKI, Time, and Monotonic Consumer State + +### 15.1 PKI import + +The Composer persists the complete trusted PKI consumer state required by +`FOG-PKI` in the same security-critical transaction as any route, profile, +message, or storage work that first depends on it. + +It accepts a newer view only after validating: + +- network and trust-anchor identity; +- canonical full consensus and independent authority quorum; +- sequential authority-set transitions; +- epoch, validity, freshness, and clock uncertainty; +- archive inclusion and append-only consistency from the stored checkpoint; +- active packet, wire, messaging, storage, entry, cover, and Composer profiles; +- complete current storage manifests and key windows; +- split-view, equivocation, and conflict evidence. + +A bundle containing multiple valid conflicting views freezes affected work and +preserves evidence. It does not select the numerically highest view or merge +descriptors. + +### 15.2 Offline time + +The Composer maintains separate notions of: + +- monotonic process time for one boot session; +- authenticated protocol epoch and version progression; +- profile-approved civil time with explicit uncertainty; +- release-metadata update-start time. + +An RTC can be a candidate input but is not trusted merely because it is +battery-backed. Clock rollback, implausible jump, uncertainty overflow, or +disagreement with authenticated epoch bounds stops new time-sensitive work. + +The UI permits a user to report the clock problem, not to declare an arbitrary +time valid. Exact offline time recovery remains a profile activation gate. + +## 16. Native Applications and Safe Rendering + +### 16.1 Common application boundary + +`fog-drop`, `fog-mailbox`, and `fog-im` are modules inside the Composer trust +domain. They receive authenticated bounded application frames only after +messaging, storage, import, and state commit. They do not parse raw transfer, +KEMSphinx, storage, or ratchet bytes. + +Applications cannot select packet geometry, route length, storage replica +count, retry timing, cover class, cryptographic primitive, update channel, or +external renderer. + +### 16.2 Initial content profile + +The initial content profile supports bounded UTF-8 plain text with canonical +normalization rules and a small fixed set of non-active local presentation +attributes. It rejects invalid UTF-8, control-character abuse, bidirectional +text policy violations, oversized grapheme sequences, unknown critical +fields, and active content. + +No content triggers: + +- network or filesystem access; +- contact creation or verification change; +- command execution, URL opening, media decoding, font installation, or + external process launch; +- automatic reply, read receipt, typing indicator, preview, or notification + containing plaintext outside the unlocked Composer; +- import, update, recovery, profile, or key transition. + +### 16.3 Local UI status + +The UI distinguishes at least: + +- locally queued but not exported; +- exported with unknown relay outcome; +- courier accepted, replica quorum committed, and degraded storage; +- authenticated recipient Composer commit acknowledgment; +- expired or uncertain delivery; +- contact fingerprint verified, unverified, changed, or recovery pending; +- PKI fresh, stale but valid, expired, split, or recovery required; +- vault locally consistent, externally anchored, unanchored, or mismatched; +- installed release verified, update available, revoked, or time-blocked. + +It never labels courier acceptance as delivery, replica commit as human read, +encryption as anonymity, networklessness as host integrity, or local hash-chain +verification as complete anti-rollback. + +### 16.4 Plaintext lifetime + +Plaintext is decrypted only for the active operation or visible bounded view. +Search indexes, caches, undo history, previews, clipboard, recent-file lists, +accessibility bridges, screenshots, and notifications are disabled unless a +later profile explicitly bounds and protects them. + +Lock, suspend, inactivity timeout, display detachment, update mode, integrity +failure, anchor failure, or fatal parser fault closes views and erases active +keys and staging memory on a best-effort basis. + +## 17. State and Key Lifecycle + +| Material | Owner | Persistence | Transition or destruction | +| --- | --- | --- | --- | +| Human unlock secret | user | never stored as plaintext | replace through authenticated keyslot rewrap | +| Unlock KDF output or KEK | Composer unlock transaction | memory only | erase after vault key unwrap or lock | +| Random vault key | one Composer instance | wrapped keyslot plus unlocked memory | rotate by profile; never export or identity-recover | +| Object key epoch | Composer vault | wrapped or derived encrypted state | rotate by object class; retain only for live objects | +| Local state-authentication key | Composer vault | one instance | erase on instance retirement; never use for backup | +| Composer instance ID | Composer vault | lifetime of one active instance | replace on restore or reinitialization; never export | +| State commitment chain | Composer vault | permanent for one instance | preserve until explicit retirement | +| External anchor key or handle | independent anchor domain | profile-specific monotonic lifetime | controlled replacement requires recovery ceremony | +| Import quarantine bytes | sandbox and Composer | one bounded import | delete after commit or rejection | +| Import dedup identifier | Composer vault | maximum bundle replay window | expire by authenticated profile, not input time | +| Export spool ciphertext | export encoder and Composer | bounded transfer or retry window | delete without rewinding protocol state | +| Transfer-pairing key | Composer and local blind relay boundary | local pairing generation | rotate on pairing compromise; never authenticate messages | +| Recovery data key | recovery transaction | package creation or restore only | erase after verified wrap or unwrap | +| Recovery wrapping key | user recovery domain | separate from package ciphertext | rotate by creating and testing a new package | +| Identity root in recovery | encrypted recovery package | explicit identity lifetime | revoke or replace through identity protocol after compromise | +| Live ratchet and capability state | current Composer instance only | encrypted mutable state | freeze on restore, clone, rollback, or compromise | +| Release trust roots | Composer maintenance state | sequential root lifetime | rotate only through authenticated old and new thresholds | +| Installed-release floor | Composer and external anchor where claimed | monotonic installation lifetime | only increase through authenticated metadata | +| Temporary plaintext and message keys | Composer process | one transaction or view | best-effort erase immediately after owning commit or close | + +Purpose-separated keys MUST NOT be converted, copied, or relabeled to satisfy a +different row. + +## 18. Failure and Recovery States + +### 18.1 Minimum vault states + +The Composer state machine includes: + +- `UNINITIALIZED`: no identity or mutable vault exists; +- `LOCKED`: image verified, vault keys absent from active memory; +- `UNLOCKING`: bounded keyslot and state verification in progress; +- `READY_UNANCHORED`: locally consistent, no complete-rollback claim; +- `READY_ANCHORED`: local and independent anchor state match; +- `IMPORT_STAGED`: one bounded import is parsed but has no effect; +- `EXPORT_PREPARED`: exact export state is durable but not yet released; +- `UPDATE_STAGED`: inactive release verified but not activated; +- `RECOVERY_REQUIRED`: ordinary protocol actions forbidden; +- `CLONE_OR_TAMPER_DETECTED`: local and anchor history conflict; +- `LOCKDOWN`: integrity, runtime, or secret-lifetime policy failed; +- `RETIRED`: no further use of instance keys or live protocol state. + +### 18.2 Failure table + +| Condition | Required response | +| --- | --- | +| Boot signature or root-image verification failure | stop before vault unlock | +| Vault keyslot authentication failure | generic local failure, no object parsing | +| Object AEAD or canonical encoding failure | quarantine object or vault, no partial use | +| Database integrity or durability uncertainty | lock and enter authenticated recovery | +| Local commitment-chain break | freeze all mutable protocol state | +| External anchor mismatch | `RECOVERY_REQUIRED` or `CLONE_OR_TAMPER_DETECTED` | +| Missing anchor in an anchor-required profile | no export, render, or state advancement | +| Malformed or oversized import | reject complete bundle and delete quarantine | +| One invalid critical bundle record | reject complete bundle | +| Duplicate valid import | idempotent success without repeated effect | +| Export spool partial write | rebuild exact committed bytes or discard partial file | +| PKI split or consistency failure | freeze new network work and preserve evidence | +| Clock uncertainty outside profile | stop time-sensitive PKI and update acceptance | +| Update signature, expiry, hash, length, or version failure | retain verified non-revoked version or stop | +| Migration failure before finalization | keep old still-authorized state and image | +| Migration or anchor ambiguity after advance | recovery required, no downgrade | +| Recovery package failure | no identity import and no detailed oracle | +| Lock, suspend, display loss, or runtime policy failure | erase active keys best effort and stop | + +User-visible diagnostics remain local, coarse, and free of attacker-controlled +secret bytes. A failure never opens networking, mounts an internal disk, +enables a general shell, skips verification, accepts an older profile, or +exports an automatic error. + +## 19. Resource Limits and Abuse Resistance + +Every active Composer profile defines lower limits within the absolute bundle +ceilings for: + +- vault objects, object bytes, transaction objects, and staged generations; +- identities, contacts, sessions, prekeys, skipped keys, capabilities, and + storage streams; +- drafts, messages, fragments, reassembly groups, history, and attachments; +- protocol outbox, inbox, deduplication, ACK, receipt, retry, and tombstone + state; +- quarantine items, bundle bytes, record count, parser depth, and parse time; +- export spool items, bytes, re-export attempts, and retention; +- PKI objects, consensus views, proof nodes, manifests, conflicts, and history; +- update metadata, targets, stream bytes, staging space, and migration work; +- recovery identities, contacts, labels, package bytes, and attempts; +- Argon2 memory, CPU, lanes, attempts, and concurrent KDF calls; +- UI text bytes, graphemes, lines, rendering time, and notification queue; +- anchor operations, prepared generations, reconciliation attempts, and + device timeouts; +- memory, file descriptors, processes, threads, temporary files, and disk + reserve. + +Limits are enforced before allocation or expensive cryptography where the +owning format permits. Authenticated contacts remain untrusted for resource +purposes. A valid signature or ciphertext does not authorize unbounded local +storage, rendering, KDF work, or notifications. + +Disk-full handling preserves the latest finalized catalog and anchor state. +The Composer does not evict security-critical replay, ratchet, capability, +commitment, or rollback state according to least-recently-used behavior. It +stops new work or applies an authenticated retention policy. + +## 20. Logging and Local Observability + +The default release logs only coarse boot, lock, integrity, capacity, and +failure-class counters needed to operate the local device. Logs are bounded, +stored inside the encrypted vault or volatile memory, and deleted by a fixed +policy. + +The Composer MUST NOT log or export: + +- plaintext, drafts, rendered content, contact labels, or fingerprints; +- private keys, unlock material, recovery keys, capabilities, or ratchets; +- message, session, box, receipt, packet, SURB, reply, voucher, or bundle + identifiers; +- routes, replica selection, entry sets, import timing histories, or per- + contact activity; +- object ciphertext samples, failed attacker input, decrypted fragments, or + detailed cryptographic errors; +- filesystem paths containing user identity, hostnames, locale, timezone, or + device serial numbers; +- state commitments or anchor receipts in a generic support bundle. + +There is no automatic telemetry, update check, crash upload, or remote +diagnostic channel. A manually exported diagnostic report uses an explicit +reviewed schema, contains only coarse redacted status selected by the user, +and never includes a raw log or vault object. + +## 21. Candidate Implementation Profiles + +### 21.1 Linux vault candidate + +`FOG-COMPOSER-CANDIDATE-LINUX-VAULT-1` combines: + +- an authenticated boot artifact and read-only `dm-verity` root image; +- a dedicated LUKS2 mutable volume with Argon2id keyslots; +- object-level XChaCha20-Poly1305 authenticated encryption; +- one transactional SQLite database with one writer; +- immutable import quarantine and export spool files; +- disabled network stack, swap, hibernation, core dumps, and active-content + desktop services. + +This combination is a review target, not a composed security proof. LUKS2, +dm-verity, AEAD, SQLite, boot firmware, filesystem, storage hardware, and the +Composer transaction protocol have different failure and trust assumptions. + +### 21.2 Qubes MicroVM candidate + +`FOG-COMPOSER-CANDIDATE-MICROVM-QUBES-1` evaluates: + +- a dedicated persistent Composer qube with no NetVM; +- an immutable reviewed template or standalone image measurement; +- one private encrypted state volume unlocked inside the guest; +- exact qrexec policies for bounded import and export services only; +- separate untrusted transfer qubes where physical device support requires + them; +- explicit denial of file copy, clipboard, URL opening, general command, + update proxy, audio, camera, block, USB, and arbitrary qrexec services. + +qrexec is a host-mediated data channel and the Qubes host remains trusted for +guest confidentiality and integrity. A host-controlled vTPM is not an +independent anchor against that host. + +### 21.3 Portable Linux candidate + +`FOG-COMPOSER-CANDIDATE-PORTABLE-LINUX-1` reuses the Linux vault profile but +boots a signed read-only image on dedicated offline hardware. It requires +driver removal, internal-disk automount denial, separate RX/TX devices, +physical inspection, and a profile-selected independent monotonic anchor for +any complete-rollback claim. + +Secure Boot without a narrowly controlled FOG release root, root-image +verification without an authenticated root digest, or a TPM without a reviewed +state-binding protocol does not satisfy the complete candidate. + +### 21.4 Activation gates + +Before any candidate receives an active numeric profile, FOG MUST freeze and +verify: + +1. exact operating system, kernel, bootloader, firmware assumptions, image + format, and immutable-root construction; +2. exact LUKS2, Argon2id, AEAD, KDF, hash, database, filesystem, and library + revisions and parameters; +3. byte-exact vault object, commitment, bundle, record, recovery, and update + serialization; +4. nonce uniqueness, key-purpose separation, wrapping, rotation, and deletion; +5. database atomicity, sync, power-loss, disk-full, corruption, and backup + behavior on supported hardware; +6. external-anchor state binding, crash protocol, endurance, reset, clone, + replacement, and recovery; +7. exact MicroVM device model and qrexec or equivalent policy; +8. exact Portable driver set, mounts, Secure Boot ownership, dm-verity chain, + and physical transfer direction; +9. exact TUF revision, client, metadata encoding, thresholds, trusted time, + root rotation, rollback floor, revocation, and offline repository workflow; +10. parser fuzzing, UI rendering, import/export duplication, hostile media, + migration, recovery, and update fault injection; +11. reproducible or independently verifiable builds, dependency provenance, + release ceremony, and rollback rehearsal; +12. independent endpoint, cryptographic integration, and implementation + review before public claims. + +## 22. Conformance and Adversarial Tests + +Before the local PoC, FOG-COMPOSER requires deterministic positive and +negative tests for: + +- image signature, boot argument, root digest, read-only root, and failure + before vault unlock; +- absence of NICs, network drivers, network syscalls, listeners, proxies, and + undeclared devices; +- MicroVM clipboard, shared directory, qrexec, device, and guest-agent denial; +- Portable radio, internal-disk mount, foreign-filesystem, and directional + device denial; +- keyslot creation, unlock, wrong secret, rewrap, rotation, and partial header + update; +- object AEAD, associated data, nonce, padding, generation, key epoch, wrong + instance, corruption, truncation, and unknown-critical fields; +- atomic messaging, storage, PKI, import, export, and application commits; +- every crash point before and after prepared state, anchor advance, + finalization, export release, and plaintext rendering; +- local-chain rollback, complete coherent rollback, clone, split generation, + anchor reset, missing anchor, same-generation conflict, and reconciliation; +- exact 128-byte bundle and 48-byte record headers; +- every bundle kind, record allowlist, direction, size, count, padding, + overlap, gap, duplicate ID, integer overflow, and trailing byte; +- malformed QR, FOG-SX, removable-medium, contact, PKI, reply, recovery, and + update inputs as data only; +- exact duplicate import and export, repacking, reordered records, replay, + interrupted transfer, and stale result; +- recovery creation, separate-key handling, test decrypt, restore into a new + instance, and refusal to resume live state; +- rejection of a copied VM volume, database, portable partition, or full-state + archive as an active restore; +- TUF thresholds, sequential root rotation, expiry, freeze, rollback, + fast-forward, mix-and-match, wrong target, hash, length, platform, + compatibility, and revocation; +- update stream interruption, inactive-image verification, A/B trial, + unauthorized fallback, migration failure, and recovery media; +- safe text rendering, Unicode edge cases, active content, external resource, + notification, clipboard, and parser resource abuse; +- disk full, fake-capacity media, I/O error, fsync failure, hot journal, + corrupted database, low memory, KDF exhaustion, and anchor timeout; +- lock, inactivity, suspend, display loss, update mode, and best-effort key + erasure; +- absence of prohibited data from logs, crash artifacts, swap, hibernation, + temporary files, export, and diagnostics. + +Testing MUST include parser fuzzing, property tests, transaction fault +injection, simulated power loss, race detection, hostile peripheral fixtures, +resource exhaustion, cross-version migrations, restore exercises, dependency +audits, and byte-identical vectors across independent implementations. + +Platform evidence MUST distinguish simulator, VM, dedicated offline hardware, +and production-profile results. + +## 23. Threat and Architecture Traceability + +| Requirement | Primary controls | +| --- | --- | +| `ARC-002` | no NIC, no socket service, bounded transfer only, conformance network tests | +| `ARC-004` | local vault, messaging, KEMSphinx, storage, and Noise keys remain separate | +| `ARC-005` | full PKI verification and monotonic consensus state before route construction | +| `ARC-006` | common opaque bundle and packet classes across native applications | +| `ARC-007` | explicit vault, object, transfer, backup, anchor, release, and protocol key ownership | +| `ARC-008` | exact headers, absolute parser ceilings, object and transaction bounds | +| `ARC-009` | fail closed on image, state, anchor, import, PKI, update, and recovery uncertainty | +| `IF-01` | committed opaque export only, sealed spool, no stable Composer signature | +| `IF-02` | hostile quarantine, complete parsing, owning inner authentication, atomic import | +| `IF-12` | threshold release metadata, target verification, inactive image, rollback floor | +| `TM-PKI-02` | persisted consensus and checkpoint monotonic state, split freeze, external anchor where claimed | +| `TM-ENDPOINT-01` | MicroVM and Portable profiles, immutable image, encrypted vault, no network or active content | +| `TM-ENDPOINT-02` | separate bounded binary directions, sandboxed decoder, no archive or filesystem import | +| `TM-ENDPOINT-03` | separate recovery hierarchy, identity-only default, no stale live-state resume or file-copy multi-device | +| `TM-APP-01` | authenticated commit before bounded plain-text rendering, no automatic actions | +| `TM-OPS-01` | no telemetry or secret-bearing log, crash, support, or export artifacts | +| `TM-SUPPLY-01` | separate release authority, TUF candidate, immutable verified images, provenance gates | +| `TM-CRYPTO-01` | random data keys, memory-hard unlock, purpose-separated object keys, lifecycle table | +| `TM-CRYPTO-02` | immutable profile registry, no runtime crypto selection or downgrade | +| `TM-AVAIL-01` | absolute limits, staged streaming, bounded KDF and parser work, disk reserve and safe stop | + +## 24. Claims Deliberately Withheld + +FOG-COMPOSER does not yet establish: + +- protection of plaintext or keys from a compromised unlocked Composer; +- protection of a MicroVM from its malicious host or hypervisor; +- detection of a complete coherent rollback without an independent anchor; +- safe binding or endurance of a concrete TPM, secure element, or token; +- secure deletion from every RAM, flash, snapshot, backup, or physical copy; +- a final operating system, database, AEAD, KDF, filesystem, or library; +- correctness of firmware, Secure Boot implementation, peripheral, DMA, or + hardware randomness; +- that network driver removal eliminates every physical or side channel; +- that TUF alone proves the distributor supplied the latest update while the + Composer is isolated; +- transparent recovery of forward-secret sessions, pending delivery, or live + capability streams; +- safe multi-device state, cloud recovery, or server-held recovery secrets; +- production security from a Qubes fixture, portable USB prototype, or local + fault test. + +## 25. Open Dependencies + +The structural Composer contract is fixed, but these dependencies remain open +before an active profile or implementation: + +- exact supported Linux distribution, kernel, boot, immutable-image, + filesystem, and hardware profiles; +- exact LUKS2, Argon2id, AEAD, KDF, database, and secure-memory selections; +- byte-exact vault, state commitment, bundle records, recovery envelope, and + update metadata integration; +- an independent monotonic-anchor construction with proven state binding, + crash reconciliation, endurance, replacement, and recovery; +- a trusted offline-time and clock-uncertainty recovery ceremony; +- exact Qubes version, qube type, template lifecycle, qrexec services, and + host policy for the MicroVM candidate; +- exact Secure Boot root ownership, firmware requirements, driver manifest, + RX/TX devices, and media policy for Portable; +- the full `FOG-UPDATE` repository, signing, provenance, reproducible-build, + revocation, and recovery contract; +- activation of one byte-exact numeric `FOG-SX` joint profile after its fixed + structural frame, object, padding, parser, no-ACK, and physical-direction + contract passes FEC, implementation, license and IPR, resource, vector, + hardware, and independent review; +- entry capsule and return-rendezvous bundles needed for complete relay + import/export; +- identity compromise, revocation, contact recovery UX, and fresh-session + transition vectors; +- future native attachment, full-text search, group, and multi-device + protocols. + +No implementation convenience may silently resolve these dependencies. + +## 26. Primary References + +- FOG threat model: `FOG-THREAT-MODEL.md` +- FOG architecture: `FOG-ARCHITECTURE.md` +- FOG public key infrastructure: `FOG-PKI.md` +- FOG wire protocol: `FOG-WIRE.md` +- FOG Sphinx profile framework: `FOG-SPHINX-PROFILES.md` +- FOG messaging protocol: `FOG-MESSAGING.md` +- FOG storage protocol: `FOG-STORAGE.md` +- FOG simplex transfer protocol: `FOG-SX.md` +- Qubes OS qrexec architecture: + +- Qubes OS device handling: + +- Linux kernel `dm-verity` documentation: + +- cryptsetup and LUKS2 specification resources: + +- RFC 9106, Argon2 Memory-Hard Function: + +- libsodium XChaCha20-Poly1305 documentation: + +- SQLite atomic commit documentation: + +- SQLite database-corruption and backup guidance: + +- Trusted Computing Group TPM 2.0 Library specification: + +- The Update Framework specification and security model: + + + +These references supply maintained formats, mechanisms, and failure lessons. +They do not make the combined FOG endpoint secure by inheritance. FOG still +requires exact profiles, integration analysis, hardware measurement, vectors, +fault testing, build provenance, update and recovery rehearsal, and +independent review. diff --git a/docs/FOG-CRYPTO-BENCHMARKS.md b/docs/FOG-CRYPTO-BENCHMARKS.md new file mode 100644 index 0000000..9f6ea21 --- /dev/null +++ b/docs/FOG-CRYPTO-BENCHMARKS.md @@ -0,0 +1,182 @@ +# FOG Cryptographic Benchmark Baseline + +## 1. Status + +This document records the first reproducible implementation benchmark for the +non-active candidates in `FOG-CRYPTO-SUITES.md`. + +It is an engineering baseline, not a profile selection or activation. It does +not establish anonymity, post-quantum security, constant-time behavior, +production capacity, or interoperability. + +The executable harness and complete three-sample data are in: + +- `../benchmarks/crypto/` +- `../benchmarks/crypto/results/2026-08-08-x86-64-i5-6300u.md` + +## 2. Snapshot + +The first host is an Intel Core i5-6300U with two physical cores and four +threads, running Linux amd64, Go 1.26.5, `GOAMD64=v1`, and the `powersave` +governor. It is useful as an older low-power x86-64 baseline, but it is not a +substitute for the required server, ARM64, and offline Composer classes. + +The Go module pins: + +- Katzenpost `v0.0.97`; +- HPQC `v0.0.85-0.20260715190213-e598e7ee2843`; +- the complete transitive graph in `go.mod` and `go.sum`. + +The harness is isolated from future daemons. It has no network listener, +persistent state, runtime suite registry, packet autodetection, algorithm +fallback, or profile activation path. + +## 3. Coverage + +The measured operations are: + +- SHA3-256 over 4 KiB, 128 KiB, 1 MiB, and 8 MiB objects; +- ML-DSA-65 plus Ed25519 key generation, signing, valid verification, and + verification with either component invalid; +- ML-KEM-768, X25519 hashed-ElGamal, the exact HPQC split-PRF KEM, and X-Wing + key generation, encapsulation, and decapsulation; +- four-logical-CPU parallel decapsulation for the two hybrid candidates; +- exact four-hop KEMSphinx forward construction, SURB construction, each hop + unwrap, all-hop unwrap, reply construction, and full request/reply crypto. + +The harness verifies both composite signature components over the same opaque +input before combining their results. It performs no component fallback. + +## 4. First-Host Medians + +All sequential values below use one logical CPU and the median of three +500 ms calibrated samples. The host was not frequency-locked or isolated, so +the raw ranges remain authoritative. + +### 4.1 PKI + +| Operation | 4 KiB | 128 KiB | 1 MiB | 8 MiB | +| --- | ---: | ---: | ---: | ---: | +| SHA3-256 | 19.2 us | 0.567 ms | 4.71 ms | 37.7 ms | +| composite sign | 1.16 ms | 2.33 ms | 11.0 ms | 78.5 ms | +| composite verify | 0.254 ms | 1.15 ms | 12.0 ms | 57.9 ms | + +Composite key generation measured 0.460 ms. The benchmark framing uses a +1,984-byte public key and 3,373-byte signature. These sizes do not define the +pending canonical FOG PKI encoding. + +At 128 KiB, an invalid ML-DSA-65 component measured 1.13 ms while an invalid +Ed25519 component measured 0.715 ms. Both components were evaluated, but the +paths were not timing-uniform. Focused statistical and implementation review +is required before accepting a remote failure surface. + +### 4.2 Hybrid KEMs + +| Construction | key generation | encapsulation | decapsulation | +| --- | ---: | ---: | ---: | +| HPQC ML-KEM-768 plus X25519 split-PRF | 0.348 ms | 0.515 ms | 0.460 ms | +| X-Wing | 0.180 ms | 0.275 ms | 0.365 ms | + +Both constructions use a 1,216-byte public key and 1,120-byte ciphertext in +this dependency snapshot. The split-PRF private serialization is 1,280 bytes; +X-Wing is 1,248 bytes. + +X-Wing is faster in this local snapshot. That result supports continued +FOG-WIRE evaluation only. It does not satisfy the distinct KEMSphinx combiner +proof obligation and cannot silently replace the retained split-PRF candidate. + +### 4.3 Complete KEMSphinx Geometry + +| Operation | Median | B/op | allocs/op | +| --- | ---: | ---: | ---: | +| build forward packet | 2.43 ms | 177,492 | 391 | +| create SURB | 2.33 ms | 123,668 | 348 | +| unwrap one hop | 0.515 to 0.519 ms | about 32,560 | 80 to 81 | +| unwrap all four hops | 2.04 ms | 130,192 | 323 | +| build reply from existing SURB | 11.6 us | 28,368 | 14 | +| full request/reply crypto | 8.89 ms | 653,450 | 1,452 | + +The complete operation covers two 16,150-byte packets, eight total unwraps, +one 6,058-byte SURB, and one 320-byte private reply-key block. It excludes +persistent replay insertion, wire I/O, queues, mixing delay, storage, and cover +traffic. + +The timing is acceptable for continued simulation and implementation +profiling on an old mobile CPU. The allocation volume is not yet acceptable as +capacity evidence and must be included in bounded-load and denial-of-service +work. + +## 5. Exact-Length Integration Finding + +The evaluated Katzenpost API is intentionally parameterized. Direct +`Unwrap` use did not impose FOG's exact external packet length and accepted a +packet shortened by one byte. + +FOG therefore needs an explicit protocol boundary before the cryptographic +library. The benchmark module implements a narrow adapter that requires: + +- exactly four path hops; +- exactly 10,156 forward or reply payload bytes; +- exactly 16,150 packet bytes; +- exactly 6,058 SURB bytes; +- exactly 10,188 encrypted reply bytes, including the payload tag; +- exactly 320 private reply-key bytes. + +Both shorter and longer inputs fail before cryptographic processing. The +adapter does not add a primitive or alternate wire format. Equivalent strict +checks are mandatory in any future FOG implementation and conformance corpus. + +## 6. Deliberate Exclusions + +No Noise benchmark is published because FOG has not selected exact reviewed +entry and mutual post-quantum handshake patterns. A primitive X-Wing result is +not a Noise handshake result. + +No PQXDH, Triple Ratchet, or ML-KEM Braid result is published because the +complete FOG message integration and supported implementation path remain +unselected. Rust was also absent on the first host. + +The first harness also does not supply: + +- peak live memory or stack-use evidence; +- persistent replay-database cost; +- p50, p95, or p99 latency under bounded adversarial load; +- malformed-input timing distributions or dudect-style testing; +- fuzzing, sanitizer, or cross-implementation vectors; +- current server, ARM64, or offline Composer measurements; +- queue, bandwidth, cover-traffic, or long-term disclosure results; +- a complete vulnerability, license, AEZ, or side-channel review. + +Direct HPQC and Katzenpost dependencies are AGPL. Their use here is evaluation +only and does not decide the future FOG distribution license. + +## 7. Selection Consequences + +This baseline makes the following limited decisions possible: + +1. Retain SHA3-256 and ML-DSA-65 plus Ed25519 for continued PKI encoding, + vector, separability, timing, and implementation work. +2. Retain the exact HPQC ML-KEM-768 plus X25519 split-PRF construction for the + calculated KEMSphinx geometry. +3. Retain X-Wing as the leading KEM to evaluate inside a future exact reviewed + FOG-WIRE Noise construction. +4. Require an exact-length FOG boundary around the maintained KEMSphinx API. +5. Move next to the privacy and capacity simulator while leaving every + cryptographic candidate non-active. + +No numeric suite or packet profile ID is assigned. + +## 8. Required Next Evidence + +Before activation, repeat and extend the matrix on: + +- a current x86-64 server; +- a lowest-supported ARM64 node or Composer; +- the intended offline Composer hardware; +- a controlled host with fixed governor, CPU isolation, and recorded thermal + behavior. + +Then add exact Noise handshakes, complete messaging operations, persistent +replay work, tail latency, memory profiles, fuzzing, deterministic positive and +negative vectors, dependency and license review, side-channel review, and an +independent implementation or integration. diff --git a/docs/FOG-CRYPTO-SUITES.md b/docs/FOG-CRYPTO-SUITES.md new file mode 100644 index 0000000..48402c1 --- /dev/null +++ b/docs/FOG-CRYPTO-SUITES.md @@ -0,0 +1,833 @@ +# FOG Cryptographic Suite Evaluation + +Status: Evaluation Draft 0.1 +Date: 2026-08-08 + +## 1. Purpose + +This document evaluates concrete cryptographic constructions for the distinct +security roles in FOG. It narrows the pre-implementation candidates without +activating a numeric protocol profile or treating algorithm names as security +evidence. + +The outcome is deliberately not one global cryptographic suite. FOG needs +different constructions for: + +1. PKI object hashing and authentication; +2. KEMSphinx packet construction; +3. adjacent-link Noise handshakes; +4. Composer-to-Composer end-to-end messaging. + +Each role has a different transcript, failure boundary, lifetime, packet-size +cost, implementation surface, and security requirement. A construction +appropriate for one role is not automatically appropriate for another. + +This evaluation is subordinate to `FOG-THREAT-MODEL.md` and the structural +protocol specifications. It does not change packet geometry, wire records, +messaging state machines, or PKI quorum rules except for the explicit PKI +signature-suite binding correction in Section 7.4. + +## 2. Status and Non-Goals + +This document: + +- records the standards and implementation evidence inspected on 2026-08-08; +- advances named, non-active candidates where the evidence is sufficient; +- records where an exact construction or implementation is still missing; +- defines common activation, benchmarking, dependency, and claim gates; +- prevents one candidate from being reused silently across security roles. + +This document does not: + +- assign a numeric hash, signature, KEM, wire, packet, or messaging profile; +- invent a KEM, KEM combiner, signature combiner, Noise pattern, ratchet, KDF, + AEAD, stream cipher, or wide-block construction; +- claim production post-quantum security, forward secrecy, post-compromise + security, deniability, anonymity, or side-channel resistance; +- make an Internet-Draft equivalent to a final standard; +- approve a library merely because it implements a standardized primitive; +- make a construction secure by inheritance from another protocol. + +Numeric activation remains a separate reviewed decision after the gates in +this document and the owning protocol specification pass. + +## 3. Evaluation Invariants + +### CRYPTO-INV-01: Purpose-specific suite families + +PKI, KEMSphinx, Noise, and messaging use separate suite records, identifiers, +keys, APIs, test vectors, and transition state. There is no universal runtime +crypto registry exposed to untrusted input. + +### CRYPTO-INV-02: Exact profile before attacker input + +The already authenticated consensus, connection context, packet geometry, or +local messaging state selects one exact suite before parsing or cryptographic +work. An implementation never trial-verifies alternate suites. + +### CRYPTO-INV-03: No component fallback + +A hybrid or composite construction succeeds only when its exact definition +succeeds. It never falls back to a surviving classical or post-quantum +component after a component, encoding, randomness, or verification failure. + +### CRYPTO-INV-04: Exact encoding and domain separation + +Every combiner input has a fixed, unambiguous encoding and an exact domain +string. Raw concatenation of variable-length values is forbidden. Algorithm, +profile, role, transcript, and public context fields required by the reviewed +construction are included exactly once in the defined order. + +### CRYPTO-INV-05: Independent keys + +Component keys are generated independently. Keys are additionally separated +by protocol, role, direction where applicable, owner, profile, and epoch or +session. A key component is not copied from another suite record. + +### CRYPTO-INV-06: Uniform remote failure + +Malformed lengths are rejected before expensive work. Component failures, +decapsulation failures, authentication failures, and padding failures collapse +to the owning protocol's coarse remote behavior. Diagnostics do not reveal +which component failed. + +### CRYPTO-INV-07: Evidence is construction-specific + +A claim names the exact construction, parameter set, encoding, implementation +revision, build, platform, key lifecycle, and test evidence. A primitive's +standardization does not validate its integration. + +### CRYPTO-INV-08: Transitions do not negotiate + +Old and new suites may overlap only through an authenticated, bounded PKI +transition. They use separate keys and state. Failure of the new suite does +not extend, reactivate, or select the old suite. + +## 4. Standards Snapshot + +The following snapshot is part of the evaluation record. A future activation +review MUST check for revisions, errata, withdrawals, and implementation +changes after this date. + +| Item | Status used by this evaluation | Relevant consequence | +| --- | --- | --- | +| FIPS 203, ML-KEM | final standard; NIST page carries a pending-update note | ML-KEM-768 is the leading standardized KEM parameter set, but errata must be frozen into the implementation review | +| FIPS 204, ML-DSA | final standard; NIST page carries a pending-update note | ML-DSA-65 is the leading standardized PQ signature component; hedged signing is the production candidate | +| FIPS 205, SLH-DSA | final standard | useful diversity candidate, but its signatures are too large for the first ordinary PKI suite | +| NIST SP 800-227 | final, September 2025 | provides KEM-use, key-confirmation, input-validation, ephemeral-key, and composite-KEM guidance | +| RFC 9794 | informational terminology | separates post-quantum and traditional components without proving a hybrid integration | +| RFC 9955 | informational hybrid-signature analysis | requires explicit analysis of separability, downgrade, binding, and artifacts | +| RFC 9980 | proposed-standard OpenPGP profile | provides a deployed standards precedent for ML-DSA-65 plus Ed25519 and ML-KEM-768 plus X25519, but its OpenPGP encoding is not a FOG wire format | +| X-Wing CFRG draft | Internet-Draft | a serious hybrid KEM candidate, not yet a final general-purpose RFC | +| PQNoise | reviewed paper and artifact lineage | supports KEM-based Noise analysis, but does not define FOG's required exact entry and mutual-authentication profiles | +| PQXDH revision 3 | maintained Signal specification | leading asynchronous handshake candidate, with authentication limitations that remain explicit | +| Double Ratchet revision 4 | maintained Signal specification | defines Triple Ratchet and the Sparse Post-Quantum Ratchet integration | +| ML-KEM Braid revision 1 | maintained Signal specification | leading continuous post-quantum ratchet component using ML-KEM-768 | + +The NIST notes are not license to select whichever behavior an implementation +happens to expose. Activation pins the standard edition, incorporated errata, +known-answer tests, and rejection behavior. + +## 5. Parameter and Artifact Sizes + +These are raw primitive sizes, not complete FOG objects or packets. + +| Primitive | Public or encapsulation key | Ciphertext or signature | Secret output or note | +| --- | ---: | ---: | --- | +| X25519 | 32 bytes | 32-byte public contribution | 32-byte shared value before protocol KDF | +| Ed25519 | 32 bytes | 64-byte signature | classical signature component | +| ML-KEM-768 | 1,184 bytes | 1,088-byte ciphertext | 32-byte shared secret | +| ML-DSA-65 | 1,952 bytes | 3,309-byte signature | NIST category 3 parameter set | +| ML-DSA-65 plus Ed25519 | 1,984 bytes | 3,373 bytes | raw component totals before FOG framing | +| SLH-DSA-SHA2-128s | 32 bytes | 7,856-byte signature | category 1, small public key but large signature | + +ML-KEM-768 is the common PQ KEM component advanced across current candidates. +That reduces implementation diversity, but it does not authorize key reuse or +a common KEM combiner. Every protocol still has independent keys, transcripts, +and failure handling. + +FOG does not describe the selected parameter sets as one exact symmetric-bit +security number. Classical and post-quantum categories, multi-user effects, +protocol composition, implementation leakage, and traffic analysis do not +collapse honestly to one marketing figure. + +## 6. Evaluation Outcome + +| Security role | Leading result | Status after this evaluation | +| --- | --- | --- | +| PKI object hash | SHA3-256 | first-host benchmark complete, still non-active | +| PKI signatures | ML-DSA-65 plus Ed25519, both mandatory over one FOG-bound input | first-host benchmark complete, still non-active | +| KEMSphinx KEM | HPQC ML-KEM-768 plus X25519 split-PRF construction in the already calculated order | first-host complete-packet benchmark complete, still non-active | +| KEMSphinx remaining primitives | evaluated Katzenpost KDF, MAC, stream, AEZ SPRP, and payload-tag integration | hold pending side-channel, AEZ, multi-host, and complete implementation review | +| Noise KEM | X-Wing is the leading construction to evaluate | component candidate only | +| Noise profile | exact PQ entry and mutual-authentication profiles | hold, no FOG profile selected | +| End-to-end handshake and ratchet | PQXDH revision 3 plus Triple Ratchet revision 4 plus ML-KEM Braid revision 1 | retained and advanced to integration and geometry review | +| Storage and Composer-local crypto | owned by their candidate specifications | not selected by this document | + +Advancing a candidate means that benchmark and integration work may target it. +It does not mean that a daemon, consensus, bundle, or public claim may use it. + +## 7. PKI Hash and Signature Candidate + +### 7.1 Candidate records + +```text +hash_candidate_name = FOG-PKI-HASH-CANDIDATE-SHA3-256-1 +hash_suite_id = UNASSIGNED +hash_output_length = 32 +hash_status = non-active + +signature_candidate_name = FOG-PKI-CANDIDATE-MLDSA65-ED25519-1 +signature_suite_id = UNASSIGNED +signature_status = non-active +component_order = ML-DSA-65, Ed25519 +verification_rule = ALL_COMPONENTS_REQUIRED +``` + +SHA3-256 preserves the existing 32-byte identifier widths and avoids a +length-extension interface. Its selection is still candidate-level because +FOG must publish byte-exact vectors for every PKI domain and measure its +implementation on every authority and consumer class. + +ML-DSA-65 plus Ed25519 is advanced because: + +- both components have stable standardized definitions; +- the parameter pair has a current standards precedent in RFC 9980; +- ML-DSA-65 and ML-KEM-768 target the same NIST category without selecting the + larger category-5 signature for every consensus object; +- Ed25519 is compact and widely implemented; +- requiring both components avoids a success path that silently becomes only + classical or only post-quantum. + +RFC 9980 is evidence for the parameter pairing, not the FOG encoding. FOG +does not copy OpenPGP packets, prehash rules, algorithm identifiers, or key +material formats. + +### 7.2 Key representation + +One FOG composite public-key record contains two independently generated +public keys in the fixed order ML-DSA-65, then Ed25519. One signature value +contains two exact-length signatures in that same order. + +The eventual PKI profile MUST define a fixed-length array rather than a raw +concatenated byte string. It MUST reject missing, duplicated, reordered, +trailing, unknown, or incorrectly sized components before verification. + +Key identifiers cover the complete candidate suite ID and complete composite +public-key encoding. A component key ID is not a substitute for the composite +key ID. Components from different records, subjects, authorities, purposes, +or validity intervals cannot be assembled into one valid key. + +### 7.3 Verification rule + +Both component signatures MUST validate over the identical exact FOG +signature input using the public keys in the one already trusted composite +key record. There is no threshold inside a signature and no partial success. + +Verification evaluates both components using an implementation strategy whose +remote timing does not disclose which component failed. The final result is +one success or one coarse authentication failure. Parser and key-record errors +are checked before signature work but do not produce component-specific remote +responses. + +### 7.4 Suite binding and separability + +The signature suite ID MUST be inside the signed input. The FOG-PKI signature +input is therefore: + +```text +[ + "FOG-PKI-SIGNATURE-1", + network_id, + object_type, + signature_suite_id, + signed_object_bytes +] +``` + +This corrects the earlier structural draft, in which `signature_suite_id` +appeared only in the outer `SignatureRecord`. Without this binding, one valid +component could be extracted and presented to a profile that accepted that +component alone over the same bytes. + +Binding the suite ID supplies the weak non-separability property described by +RFC 9955 when combined with exact key purpose, all-component verification, and +no fallback. It does not establish strong non-separability. Before activation, +FOG MUST publish an artifact analysis covering at least: + +- component stripping and rewrapping; +- cross-suite and cross-protocol reuse; +- key substitution and component recombination; +- duplicate or reordered components; +- mixed old and new profile records during transition; +- verification differences across implementations; +- exposure of a component signature outside FOG. + +FOG MUST NOT activate a classical-only signature suite that accepts an +extracted component under a compatible transcript during the candidate's +validity or drain interval. + +### 7.5 Signing randomness + +Production ML-DSA-65 signing uses the hedged variant with a fresh 32-byte +random value from the approved operating-system randomness source for every +signature operation. Deterministic signing is reserved for fixed conformance +vectors and explicit failure testing, not normal authority operation. + +Randomness failure stops signing. It does not select deterministic mode. +Ed25519 and ML-DSA component keys are independently generated and stored. + +### 7.6 Implementation candidates + +For a Go implementation, the standard `crypto/ed25519` package is the leading +Ed25519 component. Cloudflare CIRCL version 1.6.5 is a candidate source for +ML-DSA-65 and X-Wing evaluation because it is versioned, has known-answer +tests, and documents package-level review expectations. + +CIRCL's own project description still treats deployment conservatively and +does not make every package or integration constant-time by declaration. +Activation therefore pins the exact module version and package paths, audits +attacker-controlled panic paths and secret-dependent behavior, reproduces +FIPS vectors, and records the license and dependency graph. + +### 7.7 Alternatives not advanced + +- ML-DSA-44 is smaller but targets a lower category than the leading FOG + candidate. +- ML-DSA-87 increases every signature and public key without a demonstrated + FOG need for category 5 in the first profile. +- SLH-DSA provides valuable design diversity but a 7,856-byte 128s signature + is costly for frequently signed PKI objects and update bundles. It remains a + future root, release, or diversity study, not the first ordinary suite. +- NIST's additional-signature candidates and HQC are still undergoing + standardization work. They are tracked, not placed into an active profile. +- A classical-only signature is useful for isolated development tests only if + it has a separately named profile and no automatic relationship to the + claim-bearing network. + +## 8. Hybrid KEM Requirements + +FOG treats a hybrid KEM as a complete construction, not as two KEM names and a +concatenation operator. Its immutable definition includes: + +- component algorithms and parameter sets; +- component key and ciphertext encodings; +- generation independence; +- component order; +- combiner function; +- exact combiner input encoding and domain; +- public context bound by the combiner; +- malformed-key and malformed-ciphertext behavior; +- implicit-rejection and failure semantics; +- shared-secret length; +- key-confirmation responsibility; +- test vectors and implementation revision. + +NIST SP 800-227 requires the complete composite-KEM construction to receive +analysis. It also recommends explicit, unambiguous inputs and key confirmation +where the protocol requires assurance that both parties derived the same key. +FOG follows that guidance at the owning protocol boundary. + +No FOG code may expose an API equivalent to: + +```text +CombineAnyKEMs(list_of_names, raw_concatenated_values) +``` + +Each reviewed construction instead has a compile-time adapter with exact +types, lengths, domains, and failure behavior. + +## 9. KEMSphinx Candidate + +### 9.1 Retained construction + +The existing packet candidate remains: + +```text +FOG-SPHINX-CANDIDATE-MLKEM768-X25519-1 +``` + +It uses the evaluated HPQC security-preserving split-PRF hybrid construction +with X25519 hashed-ElGamal first and ML-KEM-768 second. The display name does +not define component order. The exact registry record and test vectors do. + +The hybrid ciphertext is 1,120 bytes: + +```text +X25519 hashed-ElGamal ciphertext 32 +ML-KEM-768 ciphertext 1088 +total 1120 +``` + +The complete already calculated four-hop FOG packet remains 16,150 bytes, +with a 4,096-byte user payload and a 6,058-byte public SURB. This evaluation +does not change a byte of that geometry. + +### 9.2 Why X-Wing does not silently replace it + +X-Wing is a strong general hybrid-KEM candidate, and RFC 9980 contains a +QSF/X-Wing-compatible ML-KEM-768 plus X25519 construction for OpenPGP. That +does not prove interchangeability inside KEMSphinx. + +KEMSphinx needs a hybrid KEM with the security properties required by its +packet proof and active-attacker model. The maintained Katzenpost KEMSphinx +specification explicitly warns that a hybrid construction designed for a +handshake protocol is not automatically suitable unless it provides the +required robust IND-CCA behavior. + +Replacing the split-PRF construction with X-Wing would create a newly named +packet candidate, new header bytes, new vectors, and a new proof and +implementation review. It cannot keep this candidate name or geometry record +by convenience. + +### 9.3 Evidence and implementation risks + +The HPQC repository supplies the exact split-PRF construction, X25519 adapter, +ML-KEM-768 implementation bindings, and KEMSphinx-oriented interfaces. It also +describes itself as experimental and states that it has not received an +external security review. No stable release line is assumed by FOG. + +The candidate therefore pins an exact source commit and transitive dependency +set. Its AGPL-3.0 licensing must be compatible with the future FOG repository +and distribution model before adoption. + +The rest of the evaluated Katzenpost packet suite, including KDF, header MAC, +header stream, AEZ-based payload SPRP, and payload-tag construction, remains +under review. AEZ's age and small implementation ecosystem make complete +vectors, misuse analysis, constant-time inspection, fuzzing, and an +independent review mandatory. Geometry compatibility is not primitive +approval. + +### 9.4 Benchmark admission + +The candidate is admitted to complete-packet benchmarking because its exact +ciphertext sizes and packet arithmetic are known. It remains non-active until +the gates in `FOG-SPHINX-PROFILES.md` and this document pass. + +## 10. Adjacent-Link Noise Evaluation + +### 10.1 Required FOG semantics + +FOG-WIRE needs at least two exact post-quantum link profiles: + +1. entry mode, where the blind relay authenticates the entry without exposing + a stable relay Noise identity; +2. mutual node mode, where both authorized adjacent roles authenticate each + other and the PKI-bound role and key context. + +Both profiles must preserve the existing fixed preface, authenticated +prologue, empty application payload during the handshake, fixed encrypted +records, standard Noise message bound, no 0-RTT, no resumption, and no runtime +negotiation. + +### 10.2 Evidence reviewed + +The PQNoise paper gives a reviewed method for replacing Noise DH operations +with KEM operations and analyzes the resulting protocol family. Katzenpost's +current wire specification gives concrete operational experience with +`Noise_pqXX_Xwing_ChaChaPoly_BLAKE2b`. + +That Katzenpost profile is not adopted directly because: + +- its `pqXX` authentication sequence is not the exact FOG entry or mutual + pattern already specified; +- FOG entry mode must avoid a stable initiator identity; +- FOG mutual mode is bound to exact PKI roles and adjacency; +- Katzenpost's nonstandard large Noise-message allowance is unnecessary for + FOG's empty-handshake-payload rule; +- FOG has its own preface, prologue, record, command, epoch, and transition + contract. + +### 10.3 Result + +X-Wing is the leading KEM construction to evaluate for FOG-WIRE because it +combines ML-KEM-768 and X25519 with a fixed analyzed combiner and has both a +current CFRG draft and maintained implementations. It is not yet a selected +FOG-WIRE profile. + +No exact hybrid post-quantum Noise profile is advanced in this evaluation. +FOG will not create `pqNK` or `pqKK` pattern names without a reviewed +specification, transcript definition, state-machine analysis, vectors, and a +maintained library that supports the required semantics. + +Classical Noise profiles may be used in an isolated functional PoC only under +separate explicit names and claims. They are not fallback profiles and cannot +produce a post-quantum or public-alpha security claim. + +### 10.4 Noise activation requirements + +Before selecting a numeric wire profile, FOG MUST have: + +- an exact reviewed entry handshake with responder authentication and no + stable initiator identity; +- an exact reviewed mutual handshake; +- exact protocol names, token sequences, transcript hashes, KEM operations, + combiner inputs, cipher, hash, prologue processing, and message limits; +- deterministic positive and negative vectors for both roles; +- identity-misbinding, unknown-key-share, downgrade, replay, reflection, + malformed-ciphertext, and component-failure analysis; +- key confirmation and channel-binding analysis; +- a maintained implementation or a separately reviewed implementation plan; +- byte, CPU, memory, handshake-flood, and side-channel benchmarks; +- independent review of the exact FOG integration. + +Until then, `FOG-WIRE.md` remains structurally complete but cryptographically +non-active. + +## 11. End-to-End Messaging Candidate + +### 11.1 Retained construction + +The leading messaging integration remains: + +```text +FOG-MSG-CANDIDATE-PQXDH-TR-MLKEM768-1 +``` + +It pins: + +- PQXDH revision 3 for asynchronous session initiation; +- Double Ratchet revision 4 Triple Ratchet; +- ML-KEM Braid revision 1 using ML-KEM-768; +- one-time classical and signed one-time ML-KEM prekeys; +- no reusable last-resort PQ prekey in a claim-bearing profile; +- the fixed FOG envelope, voucher, storage, retry, and transaction contracts. + +This is the strongest maintained end-to-end candidate found that matches +FOG's asynchronous, intermittently transferred Composer state without +inventing a ratchet. + +### 11.2 Claim boundaries + +The candidate does not by itself establish post-quantum authentication. +PQXDH revision 3 retains classical authentication assumptions. An outer FOG +PKI or pairwise-root signature does not silently rewrite PQXDH's proof or +turn its authentication into a post-quantum property. + +Triple Ratchet combines a classical Double Ratchet and the Sparse +Post-Quantum Ratchet. Post-quantum post-compromise recovery depends on fresh +ratchet progress. Dropped, delayed, reordered, or never-returning traffic can +delay or prevent the expected recovery boundary. + +FOG also withholds deniability, safe backup, multi-device convergence, group +security, and complete geometry claims until the exact integration is tested. + +### 11.3 Implementation evidence and risk + +Signal publishes the PQXDH, Double Ratchet, and ML-KEM Braid specifications. +The Sparse Post-Quantum Ratchet repository includes Rust code and formal +artifacts, including hax/F* and ProVerif work. This is useful evidence, not a +release or support promise for FOG. + +The broader `libsignal` repository is production software for Signal's own +clients, but its public documentation warns that external use is unsupported +and APIs may change. Its AGPL licensing, Rust integration boundary, release +pinning, unsupported external API status, and transitive dependency surface +must be resolved before selection. + +FOG will choose one of two explicit paths after a spike and license review: + +1. pin a reviewed Rust implementation behind a minimal memory-safe interface + with ownership, zeroization, panic, and serialization tests; or +2. implement the exact published construction using reviewed primitives and + reproduce upstream plus FOG integration vectors. + +A partial port, altered KDF, altered transcript, changed ratchet combination, +or locally simplified loss behavior is a new candidate, not this one. + +### 11.4 Integration work admitted + +The candidate advances to byte-exact envelope and packet-geometry work. The +next benchmark must include PQXDH initiation, ongoing Triple Ratchet headers, +ML-KEM Braid state and ciphertexts, skipped-key bounds, storage framing, and +fragmentation against the exact 4,096-byte KEMSphinx user payload. + +## 12. Storage and Composer Boundary + +This evaluation does not select the `FOG-STORAGE` or `FOG-COMPOSER` local +cryptographic profiles. Their candidates have different properties: + +- capability derivation and authenticated storage requests; +- object-level vault AEAD and key wrapping; +- passphrase KDF and platform memory limits; +- backup and recovery encryption; +- update and release authentication. + +Those profiles may reuse a standardized primitive only through independent +suite records and purpose-separated keys. They do not inherit the PKI, +KEMSphinx, Noise, or messaging suite identifier. + +## 13. Implementation Architecture + +The future source tree SHOULD isolate construction-specific adapters by +security role, for example: + +```text +internal/crypto/pki/ +internal/crypto/kemsphinx/ +internal/crypto/wire/ +internal/crypto/messaging/ +internal/crypto/testvectors/ +``` + +The shared layer may contain only inert metadata types, constant-time utility +wrappers whose behavior is independently reviewed, randomness interfaces, and +test-vector loading. It does not contain a generic negotiate-and-dispatch +registry or accept algorithm names from the network. + +Each executable imports only the operations required by its role. In +particular: + +- a mix does not import PKI signing or messaging secret operations; +- an authority does not import KEMSphinx decapsulation for mix processing; +- a blind relay does not import Composer message decryption; +- a Composer does not import online authority signing; +- a bridge does not receive core private-key handles. + +Secret-bearing types should be non-copyable where the implementation language +permits it. Serialization APIs are private to the owning persistence or +descriptor boundary. Ordinary application APIs do not return raw private +keys. + +## 14. Dependency and Supply-Chain Record + +Every candidate benchmark and later activation records: + +- upstream repository and canonical source location; +- exact tag and commit; +- standard edition and incorporated errata; +- module and transitive dependency lock; +- checksums and reproducible build procedure; +- implementation language and compiler version; +- license and redistribution analysis; +- known audit, review, issue, and vulnerability status; +- known-answer and cross-implementation vector results; +- target operating systems, architectures, and CPU feature paths; +- constant-time and attacker-controlled panic review; +- generated SBOM and release provenance. + +An untagged dependency may be benchmarked by commit. It cannot be activated +without an explicit maintenance and update policy. A repository being active +does not replace a cryptographic or side-channel review. + +## 15. Randomness, Secret Memory, and Failure + +### 15.1 Randomness + +All production key generation, ML-KEM encapsulation, ML-DSA hedged signing, +ephemeral handshake operations, and packet creation use the approved +operating-system randomness source through one narrow injectable interface. +Deterministic entropy is available only in test builds or explicit vector +tools. + +Short reads, unavailable randomness, health-test failure, fork duplication, +or impossible state stop the operation. No protocol silently repeats a seed, +switches to time-based randomness, or changes to deterministic mode. + +### 15.2 Secret lifetime + +The owning specifications define durable lifetime. At the implementation +boundary: + +- ephemeral KEM and DH private material is single-use where the construction + requires it; +- retired shared secrets and message keys are released promptly; +- error, metrics, tracing, panic, and crash paths never format secret bytes; +- core dumps and swap are addressed by the deployment profile; +- zeroization claims are limited to memory actually controlled by the + implementation and compiler; +- immutable language copies and allocator remnants remain documented limits. + +### 15.3 Remote behavior + +The implementation may keep coarse local counters such as malformed length, +authentication failure, resource limit, or internal failure. It never logs a +key, ciphertext content, transcript, message identifier, capability, route, +or component-specific decapsulation result. + +Externally, failures follow the owning protocol's uniform drop, close, or +fixed response. A second algorithm is never tried after failure. + +## 16. Benchmark Contract + +The benchmark phase measures complete protocol operations, not primitive +microbenchmarks alone. `FOG-CRYPTO-BENCHMARKS.md` records the first-host +implementation baseline. That partial completion does not relax the remaining +hardware, operation, review, or activation requirements below. + +### 16.1 Required hardware classes + +At minimum, publish results for: + +- the lowest supported x86-64 class without optional acceleration assumptions; +- a current x86-64 server class; +- the lowest supported ARM64 Composer or node class; +- the intended offline Composer hardware class. + +Every result names the CPU, microcode, OS, compiler, power mode, dependency +commit, feature flags, iteration count, warmup, and raw result artifact. + +### 16.2 Required measurements + +For every applicable role, measure: + +- key generation, encapsulation, decapsulation, signing, and verification; +- complete four-hop packet create and each hop unwrap; +- complete SURB creation, reply creation, and final reply decryption; +- entry and mutual Noise handshake candidates when available; +- PQXDH initiation and steady-state Triple Ratchet send and receive; +- allocation count, peak live memory, stack use, binary-size contribution, + and persistent key size; +- success, malformed, and cryptographic-failure paths; +- concurrent throughput and tail latency under bounded adversarial load; +- descriptor, consensus, update-bundle, envelope, record, SURB, and packet + byte costs; +- queue, cover-traffic, storage, and bandwidth consequences of the exact + complete geometry. + +Primitive results are retained for diagnosis, but profile selection uses the +complete-operation figures. + +### 16.3 Side-channel checks + +The benchmark harness also runs or records: + +- secret-dependent branch and memory-access review for exact package paths; +- dudect-style timing tests where applicable; +- malformed-input timing distributions; +- CPU-feature and fallback-path equivalence; +- race, sanitizer, fuzz, and attacker-controlled panic results; +- cross-process isolation assumptions for co-located PoC roles. + +Passing statistical tests is evidence, not proof of constant-time behavior. + +### 16.4 First-host baseline + +The pinned Go harness in `../benchmarks/crypto/` completed the first older +x86-64 baseline for SHA3-256, composite PKI operations, candidate KEMs, and the +exact four-hop 16,150-byte KEMSphinx request/reply path. + +The full request/reply cryptographic operation measured about 8.89 ms at the +median, with about 638 KiB and 1,452 allocations per operation. Direct use of +the parameterized maintained `Unwrap` API did not enforce FOG's exact external +packet length, so the harness adds a typed FOG boundary that rejects all +non-exact paths, packets, payloads, SURBs, encrypted replies, and reply-key +blocks before cryptographic processing. + +The result retains the evaluated candidates without activating them. Raw +three-sample data, environment details, exact sizes, exclusions, and observed +timing variability are in +`../benchmarks/crypto/results/2026-08-08-x86-64-i5-6300u.md`. + +## 17. Activation and Retirement + +A candidate can receive a numeric ID only after: + +1. its complete immutable record and encoding are published; +2. the owning protocol's geometry and state machine are byte-exact; +3. at least two independently integrated implementations reproduce positive + and negative vectors where the protocol gate requires them; +4. dependencies, licenses, builds, randomness, failures, side channels, key + lifecycle, and resource limits are reviewed; +5. complete-operation benchmarks fit the supported hardware and cover model; +6. transition, rollback, revocation, compromise, and retirement behavior is + tested; +7. protocol and implementation review findings are resolved or explicitly + accepted with bounded claims; +8. a decision record names the profile and its permitted deployment stage. + +Activation is monotonic. A newly discovered vulnerability can stop creation +or acceptance, but cannot trigger an unauthenticated downgrade. Emergency +retirement may cause message, packet, or availability loss. + +## 18. Claims Deliberately Withheld + +This evaluation does not support the following statements: + +- FOG is post-quantum secure. +- A hybrid is secure whenever either component remains secure. +- ML-KEM inclusion makes authentication post-quantum. +- ML-DSA plus Ed25519 is strongly non-separable. +- KEMSphinx geometry proves packet security or anonymity. +- PQNoise research already supplies FOG's exact Noise profiles. +- Signal's specifications validate FOG framing, persistence, loss, backup, or + offline transfer behavior. +- a memory-safe language eliminates side channels, secret copies, panics, or + protocol errors. +- NIST standardization or a maintained repository replaces independent + integration review. + +Public wording must name the exact active profile and evidence, or state that +the construction is a non-active research candidate. + +## 19. Open Work After This Evaluation + +The following items remain before any cryptographic profile can become active: + +- publish the exact FOG PKI composite-key and signature-component encoding; +- produce SHA3-256 domain vectors and the full PKI signature artifact analysis; +- decide the implementation and license path for ML-DSA-65; +- audit the pinned HPQC, Katzenpost, and AEZ dependency set; +- repeat complete 16,150-byte KEMSphinx packet, SURB, and persistent replay + benchmarks on current server, ARM64, and offline Composer hardware; +- select or produce a reviewed exact FOG entry and mutual PQNoise profile; +- complete the PQXDH, Triple Ratchet, ML-KEM Braid, FOG envelope, and storage + serialization and state integration; +- measure all candidates against complete packet, wire, bundle, and storage + geometry; +- publish deterministic and negative vectors and independent review results; +- revisit standards, drafts, errata, and implementation status at activation. + +## 20. Primary References + +- NIST FIPS 203, ML-KEM: + +- NIST FIPS 204, ML-DSA: + +- NIST FIPS 205, SLH-DSA: + +- NIST SP 800-227, Recommendations for Key-Encapsulation Mechanisms: + +- RFC 9794, Terminology for Post-Quantum Traditional Hybrid Schemes: + +- RFC 9955, Hybrid Signature Spectrums: + +- RFC 9980, Post-Quantum Cryptography in OpenPGP: + +- CFRG X-Wing KEM Internet-Draft: + +- PQNoise: + +- Katzenpost wire protocol: + +- Katzenpost KEMSphinx specification: + +- Katzenpost HPQC implementation: + +- Katzenpost AEZ implementation: + +- Signal PQXDH revision 3: + +- Signal Double Ratchet revision 4: + +- Signal ML-KEM Braid revision 1: + +- Signal Sparse Post-Quantum Ratchet implementation: + +- Signal libsignal implementation: + +- Cloudflare CIRCL: + +- Go ML-KEM package: + +- FOG threat model: `FOG-THREAT-MODEL.md` +- FOG architecture: `FOG-ARCHITECTURE.md` +- FOG PKI: `FOG-PKI.md` +- FOG wire protocol: `FOG-WIRE.md` +- FOG KEMSphinx profiles: `FOG-SPHINX-PROFILES.md` +- FOG messaging: `FOG-MESSAGING.md` +- FOG cryptographic benchmark baseline: `FOG-CRYPTO-BENCHMARKS.md` + +These sources support the shortlist and its constraints. The exact FOG +composition still requires its own vectors, tests, benchmarks, operational +analysis, and independent review. diff --git a/docs/FOG-LOCAL-POC.md b/docs/FOG-LOCAL-POC.md new file mode 100644 index 0000000..d156e10 --- /dev/null +++ b/docs/FOG-LOCAL-POC.md @@ -0,0 +1,345 @@ +# FOG Local Podman PoC + +Status: Runnable Fixture 0.2 + +Date: 2026-08-08 + +## 1. Purpose and Claim Boundary + +`FOG-LOCAL-POC-1` is the first runnable local process, isolation, baseline, +and failure-test boundary for FOG. It turns the architecture's role and +reachability rules into a validated rootless Podman fixture. + +The machine-readable artifacts and validator are in `../deploy/podman/`. + +This definition is functional evidence only. It cannot demonstrate operator +independence, infrastructure diversity, an anonymity set, traffic-analysis +resistance, availability, capacity, production key handling, or a safe +numeric protocol profile. All containers run on one host under one user and +therefore share a host kernel, storage stack, management account, clock, and +failure domain. + +The fixture does not activate FOG-WIRE, FOG-PKI, KEMSphinx, storage, +messaging, cover, delay, polling, retry, or degraded-mode parameters. It does +not implement or substitute for any protocol daemon. Its deliberately bounded +and non-cryptographic transport is named `FOG-POC-FIXTURE-1`. + +## 2. Requirements + +The PoC definition is governed by: + +- `ARC-001`, separate trust domains even when co-located; +- `ARC-002`, a networkless Composer; +- `ARC-003`, no data-plane bypass; +- `ARC-005`, complete authenticated network views; +- `ARC-007`, one private-key owner and no shared writable state; +- `ARC-008`, bounded configuration, work, and state; +- `ARC-009`, no privacy-weakening fallback; +- `IF-06` through `IF-09`, exact online data-plane adjacencies; +- `TM-NET-03`, `TM-NET-04`, `TM-PKI-02`, `TM-PKI-03`, + `TM-ROLE-01`, `TM-ROLE-02`, `TM-ROLE-03`, `TM-CRYPTO-01`, and + `TM-AVAIL-01`; +- `PKI-INV-01`, `PKI-INV-03`, `PKI-INV-04`, and `PKI-INV-08`; +- `WIRE-INV-07`, `WIRE-INV-08`, `WIRE-INV-09`, and `WIRE-INV-10`. + +Podman 5.8.3 and podman-compose 1.6.0 are present on the first local host. The +runtime contract targets rootless Podman and internal bridge networks. Podman +documents `network=none` as a network namespace without configured network +interfaces and an internal bridge as restricting external access. These +runtime properties remain acceptance-test subjects rather than assumptions. + +## 3. Deployment Stages + +### 3.1 Definition stage, completed + +The checked-in JSON plan and standard-library Go validator define roles, +network edges, private state ownership, container hardening, resource +containment, and the mandatory fault matrix. + +This stage prevented placeholder ports, fake cryptography, reusable example +keys, or an unreviewed generic fixture protocol from becoming accidental +implementation defaults. + +### 3.2 Fixture stage, current + +The executable harness provides explicitly named PoC-only binaries. They +exercise process lifecycle, exact opaque object geometry, network +reachability, bounded connections and work, restart behavior, role-local +state, secret isolation, and fault outcomes. They do not call their transport +FOG-WIRE or their public data FOG-PKI. + +The stage builds one scratch image without build-time networking, addresses it +by its local OCI manifest digest, and deterministically generates a Compose +manifest from validated `topology.json`. The internal TCP port `17001` belongs +only to the fixture harness and is not a selected FOG protocol port. + +### 3.3 Protocol-functional stage + +Actual role executables replace the fixtures only after the relevant exact +profiles, libraries, parsers, key formats, and conformance vectors are +selected. Each replacement must retain the same state and network ownership +contract and pass the same fault matrix. + +## 4. Role Inventory + +| Instance | Executable contract | Purpose | Network state | +| --- | --- | --- | --- | +| `composer-fixture` | `fog-poc-composer-fixture` | Networkless public-fixture acceptance state | `none` | +| `authority-fixture` | `fog-poc-authority-fixture` | Networkless authority-condition state | `none` | +| `relay` | `fog-client-relay` | Opaque client queue and entry submission | private pairwise network | +| `entry` | `fog-entry` | Entry boundary outside the mix layers | two private pairwise networks | +| `mix-l1` | `fog-mix`, fixed layer 1 | First mix position | two private pairwise networks | +| `mix-l2` | `fog-mix`, fixed layer 2 | Second mix position | two private pairwise networks | +| `mix-l3` | `fog-mix`, fixed layer 3 | Third mix position | two private pairwise networks | +| `courier` | `fog-courier` | Terminal request and replica mediation | five private pairwise networks | +| `store-a` through `store-d` | `fog-store` | Four separate storage fixtures | one private pairwise network each | + +The three-node description refers to the three fixed mix positions. Entry and +courier remain outside those three KEMSphinx delay layers. Four storage +containers exist so the PoC does not normalize a smaller shared storage +fixture into the intended architecture. + +The authority and Composer fixtures are networkless process and state +boundaries. Fault flags model only coarse acceptance states such as quorum +loss or conflicting public fixtures. They do not create signatures, +consensus objects, identities, contacts, capabilities, or message secrets and +supply no authority-independence claim. + +An observer is not in this first topology. `FOG-OBSERVABILITY-1` now defines +the separate structural logging and aggregate contract, but no observer, +numeric observability profile, or IF-11 codec is active in this fixture. No +container may substitute ordinary logs or packet traces for `fog-observer`. + +## 5. Network Segmentation + +Every declared network is internal and contains exactly two roles: + +```text +relay <-> entry <-> mix-l1 <-> mix-l2 <-> mix-l3 <-> courier + |-- store-a + |-- store-b + |-- store-c + `-- store-d +``` + +The nine networks are: + +| Network | Members | Interface | +| --- | --- | --- | +| `relay-entry` | relay, entry | `IF-06` | +| `entry-l1` | entry, mix-l1 | `IF-07` | +| `l1-l2` | mix-l1, mix-l2 | `IF-07` | +| `l2-l3` | mix-l2, mix-l3 | `IF-07` | +| `l3-courier` | mix-l3, courier | `IF-08` | +| `courier-store-a` | courier, store-a | `IF-09` | +| `courier-store-b` | courier, store-b | `IF-09` | +| `courier-store-c` | courier, store-c | `IF-09` | +| `courier-store-d` | courier, store-d | `IF-09` | + +There is no shared data-plane bridge. No role uses the host network. No +container port is published to the host. The Composer and authority fixtures +use `network=none`. Coarse public-data failure conditions are written only to +the targeted role's private fixture state and contain no consensus bytes or +cryptographic material. + +The runnable harness proves that containers cannot reach an +external address, host-local services, or undeclared roles. Name resolution +success is not sufficient evidence of an authorized protocol edge, and +failure of one edge must never create another. + +## 6. Filesystem, Keys, and Container Hardening + +Every role-container contract requires: + +- rootless execution as a dedicated non-root container user; +- one digest-pinned immutable image; +- a read-only root filesystem; +- `no-new-privileges`; +- all Linux capabilities dropped; +- no privileged mode, device passthrough, host PID, host IPC, host UTS, or + host network namespace; +- private `/run` and `/tmp` tmpfs mounts with bounded sizes in the generated + manifest; +- exactly one role-owned writable state volume; +- no source checkout, container socket, generic host directory, or other + role's state mounted; +- disabled core dumps and no secret-bearing diagnostic output; +- explicit CPU, memory, PID, file-descriptor, connection, and queue limits. + +The checked-in CPU, memory, and PID values are harness containment limits, not +capacity results or protocol profile parameters. Their only purpose is to +make local fault tests bounded. + +One 32-byte non-cryptographic fixture token per role is generated in memory +from the operating-system CSPRNG. It is streamed into a distinct Podman Secret +object. The installed `podman-compose` plus `runc` combination cannot mount +that file directly into a read-only role root filesystem, so a networkless, +bounded, one-shot stager copies it into a newly created role-specific volume +with mode `0400`. The Podman Secret source is removed immediately and the role +mounts only its own secret volume read-only. + +Tokens never enter source, JSON plans, images, host-supplied files, command +arguments, environment templates, logs, test reports, or public fixture objects. A +constant-time runtime challenge confirms that each role received its intended +token without reporting a value or digest. Teardown removes the disposable +secret and state volumes. Destruction of the `mix-l2` replay fixture requires +the separate `--allow-destructive` option and names the exact target first. + +## 7. Baseline Functional Flow + +The runnable fixture performs these steps from clean role state: + +1. Start the Composer and authority process fixtures without a configured + network interface and validate their private state and token scope. +2. Generate one opaque fixed-size packet fixture with the current 16,150-byte + candidate geometry, clearly labeled as non-cryptographic PoC data. +3. Admit the fixture only at relay, then traverse entry, mix-l1, mix-l2, + mix-l3, and courier in that order. +4. Dispatch one bounded opaque operation from courier to the storage fixture + set and collect only the modeled receipt result. +5. Return a bounded opaque result through the declared reverse adjacency, + without creating a direct courier-to-relay connection. +6. Verify that no role mounted another role's state or token and that no + container gained an undeclared network. +7. Report only the role names, scenario IDs, and coarse fixture outcomes. + +The successful first-host result is `complete-fixture`, four modeled fixture +receipts, and four fixture hops after entry. These counts describe only this +test transport and must not be read as storage durability or anonymity +evidence. + +The harness keeps no packet transcript. Container logging is disabled and no +result contains packet bytes, object identifiers, per-packet timing, keys, +route secrets, capabilities, or source addresses. + +## 8. Fault Matrix + +`fault-scenarios.json` defines fifteen mandatory scenarios: + +| Scenario | Expected invariant | +| --- | --- | +| `forbidden-role-edge` | Only declared pairwise reachability exists | +| `entry-stop` | No relay-to-layer-1 fallback | +| `mix-layer-two-stop` | No layer skip or courier delivery | +| `layer-two-three-partition` | Bounded failure on the same adjacency | +| `mix-replay-state-loss` | Same-epoch processing fails closed | +| `stale-consensus` | New work stops at hard expiry | +| `same-epoch-consensus-fork` | Consumers freeze instead of choosing a view | +| `authority-quorum-loss` | Partial views and insufficient signatures fail | +| `clock-uncertainty` | Time-sensitive acceptance stops | +| `single-store-loss` | No false durability result | +| `receipt-quorum-loss` | Required receipts cannot be fabricated | +| `courier-stop` | No layer-3 or client connection to storage | +| `entry-handshake-flood` | Work and responses remain bounded | +| `cover-process-stop` | A cover-dependent claim cannot continue silently | +| `role-secret-isolation` | No cross-role state or fixture-token access | + +Each scenario names target roles, the abstract action, expected behavior, +explicitly prohibited fallback, requirement IDs, automation status, and +whether it mutates role state. Abstract actions deliberately contain no shell +command. The fault runner resolves container and volume targets +from the validated plan, show destructive targets before execution, and start +state-destructive tests from disposable snapshots. + +No scenario may run concurrently with another. Each begins from a documented +clean baseline so an earlier partition, stopped role, or modified volume +cannot make a later test pass for the wrong reason. + +On the first host, all fourteen automatable scenarios pass. The +`cover-process-stop` row remains `not-automatable`: the fixture has no cover +scheduler and therefore cannot silently retain a cover or unobservability +claim. The effective runtime verifier completes 239 checks before the fault +matrix begins. + +## 9. Acceptance Gates + +### 9.1 Definition gate, satisfied + +- The strict parser accepts the checked-in topology and fault plan. +- Duplicate or unknown fields, trailing JSON, oversized or non-regular inputs + fail. +- Required roles, exact mix layers, four stores, pairwise internal networks, + private state, private secret scopes, hardening flags, and resource bounds + are enforced. +- Forbidden edges, host exposure, shared volumes, missing faults, and invalid + traceability references fail validation. +- The validator uses only the Go standard library and performs no network or + filesystem writes. + +### 9.2 Runnable fixture gate, satisfied + +- One local scratch image is built and referenced by its OCI manifest digest. +- PoC-only fixture roles and future online security roles have distinct + executable entrypoints. +- The generated Compose manifest derives from validated `topology.json`. +- Effective Podman state, not only source YAML, passes 239 runtime checks. +- Rootless operation, pairwise internal networks, failed host and external + probes, no published ports, private volumes, read-only roots, zero process + capability sets, and role-token isolation pass. +- The baseline and all fourteen automatable fault scenarios pass. +- Reports contain only coarse outcomes. The cover-only row is explicitly not + automatable and grants no cover claim. + +### 9.3 Protocol-functional gate, not yet satisfied + +- Replace fixtures with exact parsers, cryptographic profiles, key ownership, + replay persistence, command policy, consensus transitions, and storage + receipts from the normative specifications. +- Pass the PKI and FOG-WIRE functional PoC gates. +- Add parser conformance, fuzz, race, allocation, load, and mutation tests. +- Demonstrate current and next epoch transition, rollback refusal, revocation, + per-record rekey, fresh handshakes, exact packet geometry, and strict + failure behavior. + +None of these gates creates an anonymity or production claim. + +## 10. Validation Commands + +From `deploy/podman/`: + +```sh +gofmt -d cmd/fog-poc-plan/*.go internal/plan/*.go +go test ./... +go test -race ./... +go vet ./... +go run ./cmd/fog-poc-plan +go run -buildvcs=false ./cmd/fog-poc-lab -action all -allow-destructive +``` + +The expected summary is twelve roles, three mix layers, four stores, nine +internal pairwise networks, fifteen fault scenarios, and the claim +`functional-only`. + +## 11. Deferred Decisions + +This definition does not select: + +- a Noise or hybrid post-quantum construction, library, profile ID, port, + record size, timer, socket profile, or command shape; +- a PKI authentication suite, exact epoch schedule, topology shuffle, + authority wire-key profile, or recovery ceremony; +- an active KEMSphinx suite or entry capsule; +- storage request geometry, durability profile, or receipt primitive; +- cover, delay, loop, retry, polling, queue, or shutdown distributions; +- observability fields, aggregation windows, release delays, or thresholds; +- a production container base image, release channel, deployment user, host + firewall, backup, or update mechanism. + +These remain explicit dependencies. The fixture generator supplies none of +them. Any future protocol-functional manifest generator must fail when a +required value is absent rather than converting fixture values into an +undocumented protocol default. + +## 12. References + +- FOG threat model: `FOG-THREAT-MODEL.md` +- FOG architecture: `FOG-ARCHITECTURE.md` +- FOG PKI: `FOG-PKI.md` +- FOG wire protocol: `FOG-WIRE.md` +- FOG traffic simulator: `FOG-SIMULATION.md` +- Podman run and network modes: + +- Podman internal network option: + +- Podman secret management: + diff --git a/docs/FOG-MESSAGING.md b/docs/FOG-MESSAGING.md new file mode 100644 index 0000000..69b1ffd --- /dev/null +++ b/docs/FOG-MESSAGING.md @@ -0,0 +1,1013 @@ +# FOG Messaging + +Status: Draft 0.1 + +Date: 2026-08-08 + +## 1. Purpose + +This document defines `FOG-MESSAGING`, the Composer-to-Composer messaging +contract for FOG native asynchronous services. + +It fixes the contact model, private vouchers, message-envelope framing, +authentication boundary, state ownership, atomic send and receive behavior, +fragmentation, retries, Composer-local deduplication, authenticated commit +acknowledgments, session renewal, compromise response, and conformance gates. + +It also records a non-active cryptographic integration candidate named +`FOG-MSG-CANDIDATE-PQXDH-TR-MLKEM768-1`. The candidate combines the published +PQXDH asynchronous handshake with the published Triple Ratchet construction +and its ML-KEM Braid component. It is an evaluation target, not +`FOG-MESSAGING-1`, has no numeric profile identifier, is not authorized for a +public network, and does not by itself establish a deployed security claim. + +The key words MUST, MUST NOT, REQUIRED, SHOULD, SHOULD NOT, and MAY describe +normative requirements in the sense of BCP 14 when they appear in uppercase. + +## 2. Scope + +`FOG-MESSAGING` owns: + +- pairwise contact identities without global usernames; +- private contact identity cards and single-use receive vouchers; +- initiation roles and asynchronous session establishment; +- the fixed message envelope carried inside an encrypted storage record; +- authenticated application frames and their fixed plaintext layout; +- message-level state transitions and key lifecycle requirements; +- Composer-local retry, deduplication, fragment, and acknowledgment state; +- normal session renewal and identity-change behavior; +- recovery behavior after compromise or stale backup restore; +- the common messaging interface used by `fog-drop`, `fog-mailbox`, and + `fog-im`; +- parser limits, failure behavior, and conformance evidence. + +This document does not own: + +- KEMSphinx packet geometry, routes, SURBs, or packet replay state; +- mailbox capability derivation, storage record encryption, replication, + retention, tombstones, or empty-read behavior; +- adjacent-link Noise framing; +- entry capsules, return rendezvous, or transfer bundles; +- the concrete cover, retry, polling, or delay distributions; +- local Composer state encryption, update verification, or physical transfer; +- multi-device synchronization or group messaging. + +Those contracts belong to `FOG-SPHINX-PROFILES`, `FOG-STORAGE`, `FOG-WIRE`, +the entry and rendezvous specifications, the cover profile, `FOG-COMPOSER`, +`FOG-SX`, and future `FOG-GROUP` work. + +## 3. Security Boundary + +The sender Composer constructs a messaging envelope before any online role +receives the work. The intended recipient Composer is the only role allowed to +authenticate and decrypt that envelope. + +The full messaging envelope MUST be the protected plaintext of a fixed-size +`FOG-STORAGE` record. Its prefix and ratchet header are therefore not visible +to the blind relay, entry, mixes, courier, or storage replicas. KEMSphinx and +Noise add independent routing and adjacent-link protections but do not replace +message-level protection. + +FOG-MESSAGING does not hide endpoint compromise, user behavior, screenshots, +malicious plaintext chosen by a contact, or disclosure by an intended +recipient. Fixed envelope size also does not hide timing, polling, retries, or +conversation activity unless the external traffic profile supplies sufficient +cover and scheduling. + +## 4. Protocol Invariants + +### MSG-INV-01: No global contact identifier + +A contact is represented by a private pairwise identity and local user label. +The core protocol MUST NOT require a global username, phone number, email +address, public user directory, or stable network account. + +### MSG-INV-02: Contact authentication begins out of band + +A voucher cannot authenticate the human or organization that delivered it. +Before a contact is shown as verified, users MUST compare the complete +profile-bound fingerprint through an independently authenticated channel. + +### MSG-INV-03: One voucher, one initiator, one session + +A receive voucher authorizes exactly one designated initiator and exactly one +initial session. A valid first initialization consumes it. A second different +initialization using the same voucher is rejected. + +### MSG-INV-04: Persist before export or release + +The Composer MUST durably commit a send-side ratchet transition and its exact +immutable envelope before exporting it. It MUST durably commit a receive-side +ratchet transition and authenticated plaintext before rendering the message or +exporting an acknowledgment. + +### MSG-INV-05: Retry does not advance the ratchet + +A retry reuses the exact previously committed end-to-end envelope. It MUST NOT +derive a second message key or advance the ratchet again. FOG-STORAGE owns the +immutable box and courier-request generations used to place that envelope. +Every network retransmission uses new KEMSphinx packet material, route +randomness, entry material, SURB, rendezvous, and reply material. + +### MSG-INV-06: Online roles receive no social identifier + +Message IDs, session IDs, fragment IDs, application IDs, acknowledgment state, +ratchet headers, and contact identities remain inside the encrypted storage +record. Couriers MAY deduplicate opaque storage requests but MUST NOT receive a +FOG-MESSAGING identifier. + +### MSG-INV-07: Acknowledgment means durable commit + +An authenticated message acknowledgment means only that the receiving +Composer authenticated, decrypted, validated, and durably committed an +envelope. It MUST NOT mean that a human read, displayed, accepted, or acted on +the content. + +### MSG-INV-08: Fixed external behavior + +Drop, mailbox, private-message, chat, data, control, retry, and acknowledgment +operations use the same consensus-authorized storage and packet classes. +Application type MUST NOT select external geometry or an immediate response. + +### MSG-INV-09: No automatic downgrade + +A contact and session use one exact messaging profile. Unknown, retired, or +incompatible profiles stop processing. A failure MUST NOT activate an older +handshake, classical-only mode, smaller envelope, direct route, or plaintext. + +### MSG-INV-10: Recovery never clones a live ratchet + +Copying Composer files, restoring a stale backup, or adding a second device +MUST NOT create two active copies of one ratchet. Restored conversation state +enters recovery and requires a new authenticated session. + +## 5. Contact Model + +### 5.1 Pairwise identity + +Each relationship has a unique Composer-generated contact root. Reusing one +root across unrelated contacts is forbidden because it creates an avoidable +cross-contact correlation handle. + +A pairwise contact identity contains separate public keys for: + +- the contact root signature, which binds the contact card, exact profile, and + authorized handshake identity; +- the exact identity key required by the selected asynchronous handshake. + +The contact root does not sign ordinary message bodies. Message authenticity +after session establishment comes from the selected authenticated ratchet. +FOG does not claim deniability until the complete selected construction and +its FOG integration have been reviewed for that property. + +Human-readable contact names, notes, avatars, and address-book groups are +Composer-local plaintext. They MUST NOT appear in identity cards, vouchers, +envelopes, capabilities, logs, or external bundles. + +### 5.2 Contact identity card + +A `ContactIdentityCard` is a private out-of-band object with these ordered +semantic fields: + +```text +[ + object_type, + format_version, + messaging_profile_offer, + pairwise_contact_root_public_key, + handshake_identity_public_key, + identity_binding, + sequence, + not_before, + not_after, + contact_root_signature +] +``` + +`identity_binding` MUST cover the exact handshake identity, primitive suite, +profile offer, card sequence, validity interval, and a domain separator. The +card MUST NOT contain a mailbox read capability or global lookup name. + +The displayed verification fingerprint MUST commit to the canonical complete +card, the FOG network identifier, and the proposed messaging profile. The UI +MUST require a complete comparison or authenticated QR scan, not a short +user-selected substring. + +### 5.3 Receive voucher + +A `ReceiveVoucher` is a private single-use capability issued by the contact +that will answer the initial handshake. Its ordered semantic fields are: + +```text +[ + object_type, + format_version, + voucher_id, + issuer_identity_card, + intended_initiator_root_commitment, + messaging_profile_offer, + handshake_prekey_bundle, + initial_mailbox_write_grant, + storage_profile_requirement, + sequence, + not_before, + not_after, + flags, + issuer_contact_root_signature +] +``` + +The voucher ID MUST contain at least 256 bits from the Composer CSPRNG. The +root commitment MUST bind the complete intended initiator contact root and the +voucher domain. A claim-bearing pairwise voucher MUST NOT be bearer-capable. + +The prekey bundle MUST contain every one-time classical and post-quantum prekey +required by the active profile. A claim-bearing FOG profile MUST NOT use a +last-resort reusable post-quantum prekey. Exhaustion stops new session +creation until a fresh voucher is exchanged. + +`initial_mailbox_write_grant` authorizes deposits only into one dedicated +pairwise inbound stream. The selected storage construction MAY let its writer +derive the read capability and tombstone boxes in that same stream, in which +case the UI and security model MUST state that authority explicitly. The grant +MUST NOT expose any other inbound or outbound stream, capability registry, +identity key, recovery key, local database, or storage backup. FOG-STORAGE +defines its exact construction and rotation. + +The issuer MUST atomically persist the voucher record, all corresponding +private prekeys, the write-grant state, validity, sequence, and consumption +status before the voucher can leave the Composer. Export failure does not +permit a second voucher object with the same ID or private prekeys. + +### 5.4 Targeted contact ceremony + +The initial pairwise ceremony is: + +1. Both parties privately exchange `ContactIdentityCard` objects. +2. They verify the complete profile-bound fingerprints out of band. +3. They explicitly choose one session initiator. +4. The responder creates one `ReceiveVoucher` targeted to the chosen + initiator root. +5. The initiator imports and validates the voucher, then prepares one initial + session envelope. +6. That encrypted initial envelope carries a fresh return mailbox write grant + for the responder. +7. The responder consumes the voucher only while atomically committing the + valid initial session. + +The initiator MUST NOT create a second initial envelope from the same voucher. +Until the first authenticated response is committed, only the initial content +envelope may be outstanding. Later user messages remain in a local application +queue. This bound prevents multiple independent initial sessions and limits +replay ambiguity. + +### 5.5 Bearer and one-way drop vouchers + +An explicitly marked bearer voucher provides weaker authentication. The first +holder able to complete a valid initialization wins. Such a contact MUST be +displayed as `ACTIVE_UNVERIFIED`, and the UI MUST state that possession of the +voucher does not identify a person. + +`fog-drop` MAY use a separate one-time sealed drop voucher when the receiver +does not need an authenticated sender or continuing conversation. Each drop +requires an independent voucher and independent storage grant. A drop profile +MUST NOT claim ratchet forward secrecy, mutual contact authentication, or +post-compromise recovery that it does not implement. + +## 6. Encoding of Contact Objects + +Contact objects use `FOG-MSG-CBOR-1`, a restricted deterministic CBOR profile: + +- the top-level and nested structures are arrays with fixed field order; +- integers are unsigned and use their shortest encoding; +- byte strings have exact profile-defined lengths; +- text is forbidden except for a narrowly bounded protocol-owned ASCII label + if a later exact profile requires one; +- maps, floats, tags, indefinite lengths, compression, duplicate fields, + unknown fields, and trailing bytes are forbidden; +- one complete object is at most 65,536 bytes; +- parsers re-encode a valid object and require byte-for-byte equality. + +Signatures cover a domain separator and the canonical array with the signature +field omitted. The algorithm is selected by the exact messaging profile, not +by an unauthenticated field supplied to a generic verifier. + +Contact objects are confidential social-graph material even though they carry +public keys. They MUST NOT be published in consensus, a public directory, +telemetry, logs, crash reports, or ordinary support bundles. + +## 7. Messaging Profile Registry + +Every active messaging profile is an immutable mapping from one non-zero +unsigned 32-bit identifier to exact dependencies and behavior. An identifier +MUST NOT be reused after any byte layout, primitive, limit, state transition, +or failure rule changes. + +An exact profile record includes at least: + +```text +[ + messaging_profile_id, + contact_encoding_id, + contact_root_signature_suite_id, + handshake_specification_and_revision, + handshake_primitive_suite_id, + ratchet_specification_and_revision, + ratchet_primitive_suite_id, + exporter_kdf_id, + envelope_format_id, + envelope_header_area_length, + envelope_ciphertext_area_length, + ratchet_aead_id, + storage_profile_compatibility_id, + maximum_skip, + maximum_pending_messages, + maximum_fragment_count, + maximum_complete_message_length, + maximum_concurrent_fragment_groups, + retry_profile_id, + retention_profile_id, + parser_limits_profile_id, + conformance_vector_set_id +] +``` + +The accepted FOG consensus authorizes usable profile IDs. Contact cards and +vouchers narrow that authenticated set but cannot activate an absent profile. +No party negotiates by trial, and no message advertises alternatives. + +An incomplete candidate does not receive a numeric ID. An implementation MUST +NOT accept a candidate name where an active numeric profile ID is required. + +## 8. Fixed Message Envelope + +### 8.1 Storage relationship + +One `MessageEnvelope` is the fixed-size plaintext of one compatible encrypted +FOG-STORAGE record. FOG-STORAGE MUST authenticate the entire fixed record, +including all messaging padding, before exposing it to FOG-MESSAGING. + +For a profile with fixed ratchet header area `H` and fixed ratchet ciphertext +area `C`: + +```text +message_envelope_length = 64 + H + C +``` + +Activation requires an explicit compatibility record proving: + +```text +message_envelope_length + <= storage_record_plaintext_capacity + +storage_operation_length + <= compatible KEMSphinx user payload capacity +``` + +The current 4,096-byte KEMSphinx candidate user payload is not automatically +the messaging capacity. FOG-STORAGE still owns request framing, capability +material, encryption overhead, and replica operation geometry. + +### 8.2 Envelope prefix + +The exact 64-byte prefix is: + +```text +offset length field +0 2 envelope_format_version +2 1 envelope_kind +3 1 flags +4 4 messaging_profile_id +8 8 conversation_generation +16 32 lookup_id +48 2 actual_ratchet_header_length +50 14 reserved +``` + +All integers are unsigned network byte order. `flags` and `reserved` are zero +in the first format. Unknown kinds, non-zero reserved values, invalid profile +IDs, impossible lengths, and trailing bytes are rejected. + +`envelope_kind` is `INIT` or `RATCHET`. For `INIT`, `lookup_id` is the voucher +ID. For `RATCHET`, it is the session ID. Both remain hidden from online roles +by FOG-STORAGE encryption. + +The INIT handshake transcript MUST authenticate the proposed session ID, +voucher ID, conversation generation, envelope prefix, complete padded header +area, both contact identity bindings, and both mailbox write-grant contexts. +The RATCHET associated data MUST authenticate the prefix and complete padded +header area. + +### 8.3 Header and ciphertext areas + +The ratchet header is serialized exactly as required by the active profile and +right-padded with zero bytes to `H`. Its unpadded size must equal +`actual_ratchet_header_length`. A parser MUST verify every padding byte after +successful authentication. + +An INIT header MUST carry the exact upstream initialization data and one +proposed 256-bit session ID generated by the initiator CSPRNG. That session ID +is unique to the new conversation generation and becomes the `lookup_id` of +later RATCHET envelopes after the responder authenticates and commits INIT. + +The fixed ciphertext area `C` contains one AEAD-protected fixed plaintext +frame. The active ratchet derives the message key and nonce according to its +unmodified specification and profile binding. FOG MUST NOT reuse a nonce with +a key, improvise a second encryption layer with the same key, or expose a raw +message key to an application module. + +The fixed prefix and complete padded header area are associated data. A +message is not valid until the ratchet authentication, frame parsing, profile, +generation, sequence, and application-state checks all succeed. + +### 8.4 Encrypted plaintext frame + +The fixed frame header is exactly 144 bytes: + +```text +length field +2 frame_version +1 frame_kind +1 application_id +2 flags +2 reserved +8 conversation_generation +8 send_index +16 message_id +8 ack_base +32 ack_bitmap +16 fragment_group_id +2 fragment_index +2 fragment_count +4 complete_content_length +2 content_kind +2 content_length +32 complete_content_digest +2 control_code +2 reserved_2 +N content_and_zero_padding +``` + +`N` is fixed by the profile. `content_length` selects the initial content +bytes; every remaining byte is zero and is checked after AEAD authentication. +Compression is forbidden. + +`frame_kind` is `DATA`, `ACK_ONLY`, or `CONTROL`. `application_id` identifies +`DROP`, `MAILBOX`, or `IM` only inside the encrypted frame. Unknown values, +flag bits, controls, or content kinds are critical and cause rejection. + +`send_index` begins at 1 for each direction and conversation generation and +increases without wrap. `message_id` is an independent random 128-bit value +for one envelope. It is not used by an online role or as a cryptographic key. + +Version 1 content kinds are strict UTF-8 text, bounded opaque bytes, and +protocol control. The renderer MUST NOT interpret HTML, scripts, office +documents, archives, executable formats, active links, remote resources, or +embedded previews. Opaque bytes receive a neutral locally generated filename +and require explicit user action before export. + +Typing state, presence, last-seen state, delivery timestamps, read receipts, +remote avatars, automatic URL fetches, and external MIME resolution are not +part of the first profile. + +## 9. Fragmentation and Reassembly + +A semantic message larger than one frame is split by FOG-MESSAGING before +ratchet encryption. Every fragment is an independent ratchet message with its +own `send_index`, random `message_id`, message key, fixed envelope, storage +record, KEMSphinx packet, and outer reply material. + +Fragments of one semantic message share: + +- one random 128-bit `fragment_group_id`; +- the exact `fragment_count`; +- a zero-based `fragment_index`; +- `complete_content_length`; +- a profile-selected digest of the complete unfragmented content; +- the same application and content kind. + +The first profile MUST impose limits no larger than: + +- 256 fragments per semantic message; +- 1,048,576 complete content bytes; +- 16 incomplete fragment groups per contact; +- one profile-defined total incomplete-byte budget per Composer. + +Exact active limits MAY be lower. They MUST be identical for all native +applications using the profile. + +The Composer MUST durably commit all accepted fragments and verify count, +indexes, length, kind, and complete digest before releasing any part to the +application. Missing, conflicting, expired, oversized, or invalid groups are +discarded without partial rendering and without a distinguishable network +response. + +## 10. Session State Machine + +### 10.1 Contact and session states + +The minimum states are: + +- `INVITED`: a validated identity card or unconsumed voucher exists; +- `INIT_READY`: one initial envelope may be built by the chosen initiator; +- `INIT_SENT`: the immutable initial envelope is committed and may be retried; +- `ACTIVE_UNVERIFIED`: the session authenticates a key but no authentic human + fingerprint comparison is recorded; +- `ACTIVE_VERIFIED`: the pairwise identity and session are verified; +- `RENEWING`: an authenticated transition to a new profile or generation is + in progress; +- `FROZEN_IDENTITY_CHANGE`: an unexpected contact identity change blocks + sends and ordinary receives; +- `RECOVERY_REQUIRED`: restored, inconsistent, rolled-back, or suspected + compromised state cannot continue; +- `CLOSED`: the local relationship is intentionally terminated. + +State movement is monotonic except for a reviewed renewal that creates a new +conversation generation. Reopening `CLOSED`, `FROZEN_IDENTITY_CHANGE`, or +`RECOVERY_REQUIRED` requires an explicit new contact ceremony or recovery +protocol, not a file edit or network response. + +### 10.2 Initial session + +The initiator validates the voucher, active consensus profile, targeted root +commitment, validity, sequence, prekey signatures, one-time prekey presence, +and storage compatibility before creating an INIT envelope. + +The responder performs all parsing and cryptographic work against staged +state. A valid INIT must bind the selected voucher, both pairwise identities, +the proposed random session ID, conversation generation, return mailbox write +grant, and initial content. Only then may the responder atomically: + +1. mark the voucher consumed by the exact initial-envelope digest; +2. commit the resulting session and receive state; +3. commit the inbox content and acknowledgment state; +4. delete the consumed one-time private prekeys; +5. permit a scheduled authenticated response. + +An exact retry maps to the already committed result. A different INIT for a +consumed voucher is rejected. Failure before the atomic commit leaves the +voucher usable and does not delete its private prekeys. + +### 10.3 Established session + +An established session has one sending and one receiving direction per +conversation generation. The active profile owns the precise ratchet header, +message-key derivation, skipped-key handling, and post-compromise state. + +Application modules never mutate a ratchet directly. They submit bounded +semantic content to the messaging transaction and receive authenticated +committed content from it. + +## 11. Atomic State Transitions + +### 11.1 Send transaction + +For each new envelope, the Composer MUST: + +1. validate local contact, profile, queue, fragment, and generation bounds; +2. clone or transactionally stage the current sending ratchet; +3. derive exactly one message key and construct the exact fixed envelope; +4. atomically persist the new ratchet state, immutable envelope, send index, + application queue transition, retry metadata, and outbox status; +5. erase the message key and discarded staged state; +6. only after successful commit, make the envelope eligible for export. + +A crash before step 4 produces no exportable envelope. A crash after step 4 +recovers the exact envelope without deriving another message key. + +### 11.2 Receive transaction + +For each imported storage record, the Composer MUST: + +1. authenticate and decrypt the fixed FOG-STORAGE record; +2. parse the 64-byte prefix and select one local invitation or session; +3. check the keyed exact-envelope deduplication store; +4. stage the handshake or ratchet transition without modifying live state; +5. authenticate, decrypt, and strictly validate the complete frame; +6. validate generation, sequence, replay window, fragments, application + bounds, and acknowledgment summary; +7. atomically persist new ratchet state, dedup state, inbox or reassembly + state, receive window, and peer acknowledgment effects; +8. erase message, skipped, and discarded staged keys according to the profile; +9. only after commit, release complete content or schedule an acknowledgment. + +Authentication, parsing, policy, resource, or commit failure discards staged +state and releases no plaintext. The network-facing outcome stays within the +same coarse storage and cover class. + +### 11.3 Local durability requirement + +Ratchet state, outbox state, voucher consumption, one-time prekey deletion, +dedup insertion, and inbox commit form security-critical transactions. A +profile is not conforming if its storage engine can acknowledge one of these +effects while losing the others after power failure. + +FOG-COMPOSER defines structural encrypted local storage, commit-before-effect, +and honest rollback-assurance levels. This specification requires transaction +ordering but does not activate a database or external-anchor profile. + +## 12. Retry and Outbox Semantics + +The minimum outbox states are: + +- `PENDING`: committed and eligible for first export; +- `EXPORTED`: exported at least once and waiting for authenticated commit ACK; +- `ACKED`: covered by an authenticated peer acknowledgment; +- `EXPIRED`: retry age or attempt limit was reached; +- `CANCELLED`: locally cancelled before export where profile rules permit; +- `UNCERTAIN`: local import or durability evidence is insufficient and + automatic state advancement is unsafe. + +An immutable message envelope in `EXPORTED` is never reconstructed. +FOG-STORAGE atomically creates one immutable box record and one immutable +courier request generation. Retransmissions within that generation reuse its +exact storage bytes for courier deduplication, while every transmission uses a +fresh KEMSphinx packet, SURB, private reply token, route, rendezvous, and entry +capsule. A later storage request generation may reencrypt the same box only +under the bounded FOG-STORAGE transition and nonce rules. + +The retry profile defines maximum attempts, maximum age, backoff classes, +jitter source, queue budget, and expiry. It MUST be consensus-authenticated and +validated by simulation. A real retry MUST wait for its scheduled traffic slot +and MUST NOT create an immediate application-specific burst. + +Expiry means delivery is unknown, not that the peer certainly failed to +commit. The UI MUST distinguish `ACKED`, `UNACKNOWLEDGED`, and local processing +failure without claiming network certainty. + +## 13. Composer-Local Deduplication + +The exact envelope deduplication identifier is stored only inside the +Composer: + +```text +dedup_id = MAC(K_dedup, profile_domain || HASH(message_envelope)) +``` + +The active profile defines the exporter that derives `K_dedup`, the hash, MAC, +domains, and key rotation. The formula is a protocol interface, not permission +to select arbitrary primitives. + +For INIT, the voucher record also binds the first committed envelope digest. +For RATCHET, the session-local dedup store is checked before attempting a +ratchet transition. This permits an exact retry to be recognized after its +message key has been deleted. + +Dedup entries persist for at least the maximum retry age plus the profile's +recovery margin. Count and byte limits are mandatory. Eviction MUST NOT select +entries based on application type or message content. If safe deduplication +cannot be guaranteed, the session enters a coarse local error or recovery +state rather than accepting a conflicting replay. + +Message deduplication MUST NOT be delegated to a courier or replica. Their +separate bounded request replay rules operate on opaque storage operations and +must not receive `message_id`, `send_index`, or `session_id`. + +## 14. Authenticated Commit Acknowledgments + +### 14.1 Acknowledgment window + +Every encrypted plaintext frame contains one fixed acknowledgment summary for +the peer-to-local direction: + +- `ack_base` is the largest contiguous peer `send_index` durably committed; +- bitmap bit 0 represents `ack_base + 1`; +- bitmap bit 255 represents `ack_base + 256`; +- a set bit means that exact out-of-order index is durably committed. + +Indexes at or below `ack_base` are acknowledged. A sender updates its outbox +only after the summary arrives inside an authenticated, committed ratchet +message of the expected session and generation. + +### 14.2 ACK_ONLY + +If no data or control message is due, the Composer MAY create `ACK_ONLY` in a +normal scheduled message slot. It has the same envelope and external geometry +as all other frames and advances the ratchet exactly once. + +Receiving `ACK_ONLY` MUST NOT by itself trigger an acknowledgment. A later +ordinary frame may naturally summarize its committed index, but there is no +immediate ACK-of-ACK exchange. An ACK_ONLY envelope need not remain in an +ack-wait outbox after its bounded export policy completes. + +A duplicate data envelope may cause the next scheduled summary to repeat its +acknowledgment, but MUST NOT cause an immediate distinguishable reply. + +### 14.3 Meaning and fragment behavior + +`MESSAGE_COMMIT_ACK` is the only version 1 message delivery acknowledgment. It +means successful Composer commit. Network acceptance, courier success, replica +quorum, relay import, display, read state, and user action are different facts +and MUST NOT be presented as this acknowledgment. + +Acknowledging a fragment means that fragment was committed. It does not mean +the complete semantic message was reassembled or displayed. + +## 15. Limits and Malicious Contacts + +Every active profile defines exact equal limits for all native applications. +The first profile MUST NOT exceed: + +- `maximum_skip`: 1,024 ratchet message keys; +- `maximum_pending_messages`: a profile-fixed count and byte budget per + contact and Composer; +- `maximum_fragment_count`: 256; +- `maximum_complete_message_length`: 1,048,576 bytes; +- `maximum_concurrent_fragment_groups`: 16 per contact; +- one 65,536-byte contact object; +- unsigned 64-bit generations and sequence counters with no wrap. + +Skipped message keys are retained only within the exact ratchet bound and are +securely deleted after use or expiry. The parser validates sizes before +allocation and performs bounded cryptographic work per imported record. + +A malicious contact can send authenticated abusive content and attempt state, +CPU, storage, fragment, skip-window, or notification exhaustion. FOG therefore +requires per-contact and global quotas, explicit mute and close controls, +bounded notifications, no active rendering, and no response amplification. +Cryptographic authentication is not content safety. + +## 16. Session Renewal and Profile Transition + +A normal renewal begins only inside an authenticated active ratchet. The old +session carries a contact-root-signed offer that commits to the exact new +profile, new conversation generation, fresh one-time initialization material, +and storage transition context. + +Renewal is two-phase: + +1. both sides durably commit and authenticate the same transition offer; +2. both sides confirm the new generation before retiring the old sending + state. + +Old and new generations have distinct session IDs, ratchet state, dedup keys, +storage grants, and outboxes. Messages MUST NOT be decrypted under both. There +is no fallback from a failed new profile to an older one. + +An unexpected contact root or handshake identity change is not ordinary +renewal. The session enters `FROZEN_IDENTITY_CHANGE`, displays both old and new +profile-bound fingerprints locally, and requires fresh out-of-band +verification. + +## 17. Backup, Restore, and Compromise + +### 17.1 Backup boundary + +An independently encrypted backup MAY contain pairwise contact roots, verified +fingerprints, local labels, user-approved history, and metadata needed to +start recovery. Backup keys remain separate from Composer state-encryption and +messaging keys. + +An initial profile MUST treat active ratchet state, skipped message keys, +unconsumed one-time prekeys, live vouchers, retry material, and live mailbox +capabilities as non-resumable after a potentially stale restore. Restored +conversations enter `RECOVERY_REQUIRED` and cannot send or accept ordinary +ratchet traffic until a fresh authenticated session is established. + +This sacrifices seamless restore to prevent two restored copies from reusing +message keys, accepting conflicting histories, or impersonating one live +device state. + +### 17.2 Suspected compromise + +Post-compromise ratcheting does not defeat an attacker that remains active on +the Composer, controls randomness, or controls the long-term identity. On +suspected endpoint or identity compromise, the user MUST replace the affected +device or image, create new identity material, revoke or abandon old storage +capabilities where possible, and reauthenticate contacts out of band. + +No UI may promise that sending a few more messages automatically removes an +active attacker. + +### 17.3 Multi-device exclusion + +The first profile supports one active Composer instance per pairwise identity. +File copying, shared storage, or concurrent import on two devices is forbidden. +Multi-device use requires a separate protocol with explicit device identity, +session convergence, revocation, and metadata analysis. + +## 18. Group Messaging Exclusion + +Pairwise fan-out is not a group security protocol. It leaks different delivery +and membership patterns, lacks one coherent group epoch, and does not by +itself define removal, update, or post-compromise semantics. + +Group messaging is deferred to a future `FOG-GROUP` specification. That work +SHOULD evaluate Messaging Layer Security under RFC 9420 and its architecture +considerations in RFC 9750, while adapting identity and delivery only through +explicit FOG trust-boundary analysis. The initial `fog-im` profile is pairwise +only. + +## 19. Candidate Cryptographic Integration + +### 19.1 Candidate definition + +`FOG-MSG-CANDIDATE-PQXDH-TR-MLKEM768-1` evaluates: + +- PQXDH revision 3 for asynchronous initial shared-secret establishment; +- Double Ratchet specification revision 4 Triple Ratchet integration; +- ML-KEM Braid specification revision 1 using ML-KEM-768; +- the exact classical ratchet and authenticated-encryption dependencies + required by those maintained specifications; +- a separate FOG pairwise contact-root signature that binds, but does not + replace or weaken, the exact PQXDH identity and prekey checks; +- FOG fixed framing, atomic storage rules, voucher policy, and outer + FOG-STORAGE protection defined in this document. + +The candidate requires one-time classical and signed one-time ML-KEM prekeys. +FOG deliberately excludes the reusable last-resort PQ prekey from a +claim-bearing profile. Loss of fresh vouchers therefore affects availability. + +### 19.2 Claims deliberately withheld + +The candidate does not yet establish: + +- post-quantum authentication, because the reviewed PQXDH authentication claim + still depends on the specified classical authentication assumptions; +- complete FOG post-quantum security merely from ML-KEM inclusion; +- deniability; +- post-compromise recovery under message loss or continued active compromise; +- safe backup, multi-device, or group behavior; +- compatibility with the calculated 4,096-byte KEMSphinx payload; +- implementation or side-channel safety. + +### 19.3 Activation gates + +Before promotion to an active numeric profile, FOG MUST freeze and verify: + +1. exact upstream specification revisions and all primitive identifiers; +2. one maintained implementation strategy with constant-time and secret + deletion review; +3. byte-exact PQXDH, Triple Ratchet, ML-KEM Braid, FOG envelope, and contact + object serialization; +4. transcript and domain separation, including the outer pairwise root + binding; +5. exact header area, ciphertext area, content capacity, and storage geometry; +6. loss, reordering, skip-window, retry, crash, restore, and compromise state + transitions; +7. deterministic positive and negative vectors at every boundary; +8. fuzzing and resource tests for all untrusted inputs; +9. primitive, integration, side-channel, and lifecycle review; +10. independent security review before a public security claim. + +FOG MUST preserve the upstream constructions rather than silently changing +their KDF inputs, message-key combination, epoch behavior, or prekey checks. +Any required departure creates a separately analyzed candidate. + +## 20. Key and Secret Lifecycle + +| Material | Owner | Persistence | Required destruction or transition | +| --- | --- | --- | --- | +| Pairwise contact root private key | one Composer identity | long-term encrypted state; separately encrypted backup MAY be allowed | replace on compromise or explicit identity migration | +| Handshake identity private key | one Composer pairwise identity | long-term encrypted state | replace with contact root on compromise | +| Signed classical prekey | voucher issuer Composer | bounded by voucher and profile validity | delete after expiry and all bound voucher states retire | +| Classical one-time prekey | voucher issuer Composer | persisted before voucher export | delete atomically after valid INIT commit | +| ML-KEM one-time prekey | voucher issuer Composer | persisted before voucher export | delete atomically after valid INIT commit | +| Initiator ephemeral handshake secrets | initiator Composer | transaction only | erase after committed initial state and envelope | +| Handshake shared secrets | both Composers | transaction only | erase after ratchet and exporter state is derived | +| Classical ratchet private key | one session direction | encrypted mutable session state | erase when the reviewed ratchet transition permits | +| ML-KEM Braid state | one session | encrypted mutable session state | erase retired epochs and session on close or recovery | +| Chain key | one session direction | encrypted mutable session state | replace and erase on every chain advance | +| Message key | one envelope | transaction only | erase after durable send ciphertext or receive commit | +| Skipped message key | receiving Composer | bounded encrypted state | erase on use, expiry, bound overflow, or session retirement | +| Session dedup key | one session generation | encrypted mutable state | erase after retry and recovery retention ends | +| Pairwise inbound stream writer grant | authorized remote contact | deposits, reads where derivable, and tombstones only in that dedicated stream | abandon and replace through FOG-STORAGE rules after compromise or closure | +| Pairwise inbound stream reader state | receiving Composer; writer MAY derive under selected profile | read, verify, and advance that dedicated stream | never expose access to any other stream or capability registry | +| Local state-encryption key | Composer profile | protected local state | separate from message, storage, and backup keys | +| Backup key | user recovery domain | outside backup ciphertext | never derive from a contact or ratchet key | + +Secret values, plaintext, capabilities, contact objects, fingerprints, +session IDs, message IDs, ratchet headers, and envelope digests MUST NOT enter +logs, metrics, command arguments, crash reports, or support artifacts. + +## 21. Failure Behavior + +Externally observable failures are coarse and non-amplifying. Online roles do +not learn whether a storage record held an INIT, data fragment, duplicate, +ACK_ONLY, invalid ratchet message, or cover object. + +The Composer MAY show a local reason such as invalid voucher, verification +needed, incompatible profile, authentication failure, resource limit, stale +state, or recovery required. It MUST NOT automatically send an error message +to an unverified or unauthenticated source. + +Unknown-critical fields, malformed UTF-8, invalid padding, overflow, counter +wrap, conflicting fragment metadata, excessive skips, expired vouchers, +identity changes, state rollback, and storage commit uncertainty fail closed. +Failure MUST NOT alter packet size, route length, polling rate, or security +profile outside the authenticated cover policy. + +## 22. Conformance and Test Requirements + +Before the local PoC, FOG-MESSAGING requires tests or vectors for: + +- canonical contact card and voucher encoding and rejection; +- profile-bound fingerprint derivation and targeted voucher checks; +- issue-before-export and consume-once voucher transactions; +- exact 64-byte prefix and 144-byte frame header; +- every envelope kind, frame kind, application ID, and content kind; +- prefix, header-padding, frame-padding, transcript, and generation binding; +- send crash before commit and after commit; +- receive crash before commit and after commit; +- exact retry without a second ratchet advance; +- immutable storage request retransmission with fresh KEMSphinx, SURB, + rendezvous, route, and entry material; +- exact duplicate, conflicting INIT, replay, old generation, and excessive + skip handling; +- ACK base and bitmap boundaries, reordering, loss, duplicate data, and no + ACK-of-ACK trigger; +- fragment loss, duplication, conflict, timeout, digest failure, and quota + exhaustion; +- malicious contact CPU, memory, disk, notification, and parser bounds; +- normal renewal, profile transition, identity change, stale restore, and + suspected compromise; +- secure deletion instrumentation where the platform can provide evidence; +- candidate upstream and FOG integration vectors; +- absence of prohibited fields in logs, telemetry, storage requests, and + external bundles; +- identical external geometry and scheduling class across native apps, + retries, ACKs, and cover operations. + +Tests MUST include deterministic positive and negative vectors, property +tests, fuzzing, transaction fault injection, power-loss simulation, and +cross-implementation checks before profile activation. + +## 23. Threat and Architecture Traceability + +| Requirement | Primary controls | +| --- | --- | +| `ARC-002` | networkless Composer owns every contact, ratchet, plaintext, and envelope transition | +| `ARC-004` | message envelope remains independent from KEMSphinx and Noise | +| `ARC-006` | fixed storage and packet classes hide native application selection from core roles | +| `ARC-007` | pairwise, ratchet, storage, transport, node, and backup keys have distinct owners and purposes | +| `ARC-008` | strict object, envelope, queue, fragment, skip, retry, and parser bounds | +| `ARC-009` | authentication, state, and profile failures stop without bypass or downgrade | +| `IF-01` | only a committed immutable envelope may enter a Composer export bundle | +| `IF-02` | imported storage results are untrusted until storage and messaging validation commit | +| `IF-08` | courier receives only an opaque storage operation, never messaging fields | +| `IF-09` | storage carries a fixed encrypted record whose plaintext is the message envelope | +| `TM-NET-02` | directional pairwise stream grants, external schedule, encrypted messaging identifiers, no immediate ACK | +| `TM-NET-04` | exact-envelope dedup, consume-once voucher, fresh outer retry material | +| `TM-NET-05` | authenticated ratchet and transcript, strict padding and generation binding | +| `TM-NET-06` | consensus-authorized immutable profile and application-independent behavior | +| `TM-ENDPOINT-01` | networkless state owner, transaction ordering, recovery freeze | +| `TM-ENDPOINT-03` | no live-ratchet backup restore or file-copy multi-device state | +| `TM-APP-01` | authenticated complete reassembly, bounded content, no active rendering | +| `TM-OPS-01` | no contact, capability, identifier, digest, payload, or ratchet data in telemetry | +| `TM-CRYPTO-01` | exact lifecycle table, purpose separation, secure deletion requirements | +| `TM-CRYPTO-02` | fixed profiles, no trial negotiation, fallback, or candidate activation | +| `TM-AVAIL-01` | bounded retries, skip windows, fragments, queues, contact objects, and non-amplifying errors | + +## 24. Open Dependencies + +The structural messaging contract is fixed, but these dependencies remain +open before an active profile or daemon: + +- exact primitive suite, dependency revisions, and maintained implementation + for `FOG-MSG-CANDIDATE-PQXDH-TR-MLKEM768-1`; +- license, API-support, Rust integration, panic, secret-lifecycle, and formal + artifact review for the selected messaging implementation path; +- exact contact-root signature and fingerprint representation; +- byte-exact candidate serialization and integration vectors; +- exact fixed `H`, `C`, content capacity, and lower active limits; +- activation evidence and byte-exact geometry for the structural FOG-STORAGE + contract and its non-active BACAP/Pigeonhole candidate; +- authenticated retry, cover, polling, and expiration distributions; +- activation evidence for the structural FOG-COMPOSER vault, database, + external-anchor, recovery, and update-consumer profiles; +- identity revocation and fresh-session recovery ceremony; +- future multi-device and group protocols. + +No implementation convenience may silently resolve these dependencies. + +## 25. Primary References + +- Signal PQXDH specification, revision 3: + +- Signal Double Ratchet specification, revision 4: + +- Signal ML-KEM Braid specification: + +- Signal Sesame asynchronous session management specification: + +- NIST FIPS 203, Module-Lattice-Based Key-Encapsulation Mechanism Standard: + +- Katzenpost contact voucher specification: + +- Katzenpost contact voucher narration: + +- RFC 9420, The Messaging Layer Security Protocol: + +- RFC 9750, The Messaging Layer Security Architecture: + +- FOG threat model: `FOG-THREAT-MODEL.md` +- FOG architecture: `FOG-ARCHITECTURE.md` +- FOG Sphinx profile framework: `FOG-SPHINX-PROFILES.md` +- FOG storage protocol: `FOG-STORAGE.md` +- FOG Composer protocol: `FOG-COMPOSER.md` +- FOG cryptographic suite evaluation: `FOG-CRYPTO-SUITES.md` + +These references supply maintained constructions and design lessons. They do +not make the FOG integration secure by inheritance. FOG still requires exact +profiles, compatibility calculations, vectors, tests, lifecycle analysis, +simulation, implementation review, and independent security review. diff --git a/docs/FOG-OBSERVABILITY.md b/docs/FOG-OBSERVABILITY.md new file mode 100644 index 0000000..b72034f --- /dev/null +++ b/docs/FOG-OBSERVABILITY.md @@ -0,0 +1,718 @@ +# FOG Observability + +Status: Structural Baseline 0.1 + +Date: 2026-08-08 + +## 1. Purpose and Claim Boundary + +`FOG-OBSERVABILITY-1` defines privacy-safe local logging, operational metric +collection, aggregate submission, operator diagnosis, and public health +publication for FOG. + +The objective is to detect failed or unsafe role state without creating a +second traffic-analysis system. Observability is part of the threat model. A +log server, tracing system, dashboard, crash collector, or support workflow +can otherwise preserve exactly the timing and relationship metadata that the +data plane is intended to minimize. + +This baseline fixes: + +- a no-event-stream production default; +- a closed metric and local-summary vocabulary; +- coarse non-overlapping collection windows; +- bucketed values and low-activity suppression; +- delayed role-to-observer submissions; +- separate local, operator, and public views; +- public anti-differencing requirements; +- bounded local retention and raw-submission expiry; +- crash, support, debug, access, and conformance rules. + +It does not activate a numeric observability profile. Window duration, bucket +boundaries, minimum activity, minimum independent reporters, release delay, +merge policy, retention, serialization, signature suite, and public grouping +remain explicit evidence-gated selections. No implementation or deployment +may silently choose them as protocol defaults. + +The standard-library Go module in `../observability/` is an executable +conformance model for the role-local typed collector and bounded volatile +ring. Its test values are synthetic. It is not `fog-observer`, an IF-11 wire +codec, a signature implementation, or a public exporter. + +## 2. Governing Requirements + +This document refines: + +- `TM-OPS-01`, logging and metrics leakage; +- `TM-NET-01`, timing and volume correlation; +- `TM-NET-03`, active suppression and n-1 behavior; +- `TM-ROLE-01` through `TM-ROLE-03`, role compromise and collusion; +- `TM-AVAIL-01`, bounded work and overload handling; +- `ARC-001`, separate trust domains; +- `ARC-007`, one owner for private state and keys; +- `ARC-008`, bounded inputs, work, queues, and state; +- `ARC-009`, no privacy-weakening recovery; +- `IF-11`, role-to-observer aggregate submission; +- the logging sections of `FOG-WIRE`, `FOG-SPHINX-PROFILES`, + `FOG-STORAGE`, and `FOG-COMPOSER`. + +When this document is narrower than a generic logging or monitoring practice, +this document controls for claim-bearing FOG profiles. + +## 3. Threat Model + +The protected assets are: + +- whether one user, peer, mailbox, route, packet, or conversation was active; +- exact event time, direction, sequence, size, and duration; +- linkability across roles, hosts, epochs, restarts, or support cases; +- capabilities, keys, tokens, replay state, and opaque protocol objects; +- operator, host, deployment, and user metadata not already required in + authenticated public consensus. + +Relevant adversaries include a compromised role, observer, dashboard, +operator account, support system, backup, or log collector, as well as an +external party that later obtains retained operational data. A curious or +compromised observer is within scope. The design therefore does not assume +that centralizing raw logs is safe. + +No observability design can hide what a node operator sees directly on that +node or what a network observer sees on links. The goal is narrower: do not +create a new persistent, cross-role, queryable correlation dataset. + +## 4. Invariants + +### OBS-INV-01: No data-plane event stream + +Production roles do not emit one record for each packet, message, handshake, +storage request, read, write, retry, connection, or cryptographic operation. +They update bounded in-memory counters or health state inside one coarse +window. + +### OBS-INV-02: Closed typed vocabulary + +Every field, role, metric, state, failure class, value kind, and visibility +class is allowlisted. Production collection APIs accept no arbitrary label +map, free-form message, raw error, peer-provided string, path, address, or +identifier. + +### OBS-INV-03: Coarse windows only + +A role records one window identifier, not event timestamps. Windows are +profile-fixed, aligned, and non-overlapping. Operators and public consumers +cannot request arbitrary time ranges. + +### OBS-INV-04: No exact exported traffic values + +Raw counters exist only inside the open volatile collector. Sealing converts +them to profile-defined buckets. Exact counts, sizes, latencies, ratios, queue +depths, and durations do not enter local summaries, observer submissions, or +publications. + +### OBS-INV-05: Suppress small activity sets + +Below the profile's minimum activity, every traffic-sensitive metric carries +the fixed `suppressed` value. Suppression does not remove fields or change the +report shape. Non-traffic health state may remain available. + +### OBS-INV-06: Delayed fixed-schedule submission + +Online roles submit on a profile-fixed schedule independent of whether real +traffic occurred. Each submission declares the earliest release window. +Immediate event-triggered submission is forbidden. + +### OBS-INV-07: Public output requires multiple reporters + +No role-local report can become a public report. `fog-observer` first combines +the profile-required number of independently authenticated reporters, then +applies coarsening, delay, suppression, and differencing controls. + +### OBS-INV-08: No cumulative or subtractable public series + +Public output does not expose cumulative counters, overlapping query windows, +arbitrary filters, node-level series, or stable dimensions that allow two +answers to isolate a smaller group. + +### OBS-INV-09: Bounded retention + +Local summaries use a bounded volatile ring or a profile-authorized protected +store. Raw signed observer submissions expire after bounded processing. +Retention is fixed in the active profile and cannot be extended through a +dashboard query or support request. + +### OBS-INV-10: Observability is not required for forwarding + +Failure, absence, overload, or compromise of `fog-observer` does not alter +packet forwarding, consensus validity, storage behavior, cover scheduling, or +cryptographic validation. Roles queue at most a bounded aggregate submission +and otherwise discard it. + +### OBS-INV-11: Production tracing is unavailable + +Claim-bearing builds have no packet, record, request, span, or connection +tracing mode. `DEBUG` or `TRACE` cannot be enabled by runtime configuration. +Synthetic conformance builds are a separate artifact rejected by production +configuration. + +### OBS-INV-12: Fixed shape within role and profile + +Every local summary and observer report contains the same ordered metric +positions for its role and profile. Unset values are `unknown` or `zero`. +Sensitive low-activity values are `suppressed`. Presence or field order does +not reveal which code path ran. + +## 5. Information Planes + +FOG uses three observability planes: + +1. **Local safety summary**: one role retains bounded coarse windows needed + for local diagnosis. It never contains event records. +2. **Operator aggregate**: an online role sends a delayed fixed-shape + aggregate over mutually authenticated IF-11. The observer may use it for a + protected operator health view. +3. **Public aggregate**: the observer combines enough independent reporters + and publishes only a delayed, coarsened, non-subtractable view. + +```text +role-local raw counters, volatile within one window + | + | seal once, bucket, suppress + v + bounded local summary + delayed IF-11 aggregate + | + | authenticate, combine, expire raw + v + operator view public view + protected delayed and multi-reporter +``` + +The networkless Composer and `fog-sx-send`/`fog-sx-receive` do not submit +automatic observer reports. They may retain only the local safety summary +allowed by their deployment profile. + +## 6. Data Classes + +The following classes are forbidden in every FOG log, metric, report, +dashboard, crash artifact, and generic support bundle: + +- plaintext, drafts, rendered content, contacts, labels, fingerprints, and + application fields; +- packet or record bytes, ciphertext samples, entry capsules, KEMSphinx + packets, storage envelopes, and imported attacker input; +- capabilities, vouchers, SURBs, reply material, replay tags, receipt + material, ratchets, storage streams, and secret or private key material; +- message, session, packet, request, record, box, receipt, connection, trace, + span, route, or bundle identifiers; +- source or destination IP address, peer ID, next-hop ID, full route, replica + selection, entry set, or per-source history; +- exact event timestamps, per-operation duration, direction trace, retry + sequence, queue item, read frequency, miss streak, and tombstone timing; +- raw error strings, parser offsets, expected values, detailed cryptographic + stages, stack traces, memory dumps, or exception objects; +- hostname, username, filesystem path, locale, timezone, device serial, + billing data, private operator contact, or infrastructure detail absent from + the authenticated public consensus. + +Hashes, truncation, encryption under a central log key, or pseudonymization do +not make a forbidden identifier safe. A stable digest remains a correlation +handle. + +## 7. Production Defaults + +Before a numeric observability profile is activated, the safe defaults are: + +- no packet, request, connection, or storage-access log; +- no automatic remote logging or telemetry; +- no public metrics endpoint and no host-published Prometheus endpoint; +- no OpenTelemetry auto-instrumentation, trace ID, or span ID; +- no automatic crash upload or support bundle; +- core dumps, process-memory capture, and production debug mode disabled; +- bounded volatile local summary only; +- observer submission and public publication disabled; +- generic application errors reduced to local fixed failure classes; +- remote errors remain coarse and non-amplifying under the owning protocol. + +A deployment is not permitted to replace these defaults with a generic log +shipper, service-mesh tracer, application performance monitor, or container +log collector and still claim the same profile. + +## 8. Role-Local Window Model + +One role creates exactly one collector for each coarse window. It may update: + +- a fixed unsigned counter for an allowlisted count metric; +- a fixed level bucket for queue, age, latency, ratio, or capacity state; +- a fixed health value for consensus, clock, key, replay, or cover state; +- one internal activity counter used only for the suppression decision. + +All counters saturate on overflow. They never wrap. The collector holds no +identifiers, timestamps, strings, samples, exemplars, or per-peer maps. + +Sealing is irreversible and happens once. It produces: + +- one local report for every role; +- one delayed observer report only for an online reporting role; +- no observer report for Composer, observer, or FOG-SX roles. + +The role then discards its exact counters. A retry of IF-11 transmits the same +sealed aggregate object according to the future command-specific retry rule; +it does not reopen or recount the window. + +Discarding collector references is best-effort data minimization. The Go +runtime, allocator, swap, hibernation, crash capture, and host may retain +copies, so deployment controls remain necessary and complete erasure is not +claimed. + +## 9. Structural Report Schema + +The in-process structural report contains only: + +| Field | Meaning | Restriction | +| --- | --- | --- | +| `schema_version` | report schema version | fixed to version 1 | +| `profile_id` | authenticated observability profile | nonzero and consensus-authorized before activation | +| `scope` | `local` or `observer` | fixed enum | +| `role` | reporting security role | fixed enum, not node identity | +| `window_id` | coarse aligned window | no event time | +| `release_after_window` | earliest observer processing/release boundary | observer scope only and later than source window | +| `traffic_suppressed` | sensitive metrics are below threshold | fixed boolean, submission schedule remains constant | +| `measurements` | ordered fixed catalog for role and scope | metric and value enums only | + +The JSON emitted by the Go conformance module is a local test and diagnostic +representation, not the IF-11 wire encoding. Claim-bearing IF-11 requires a +separate byte-exact bounded encoding and signature specification. + +An aggregate-signing key identifier, signature suite, and signature belong to +the future authenticated IF-11 submission envelope. They are raw observer +input, never report measurements or public dimensions. Exact signing is not +defined until the PKI and observability suites select an encoding and +primitive. + +## 10. Value Vocabulary + +Count metrics use only: + +- `zero`; +- `low`; +- `medium`; +- `high`; +- `saturated`; +- `suppressed` when traffic-sensitive activity is below threshold. + +Level metrics use only: + +- `unknown`; +- `empty`; +- `low`; +- `medium`; +- `high`; +- `full`; +- `suppressed` when traffic-sensitive activity is below threshold. + +Health metrics use only: + +- `unknown`; +- `healthy`; +- `degraded`; +- `unavailable`. + +Bucket boundaries are part of one signed profile. Operators cannot customize +them per node. Public documentation states the boundaries and their privacy +rationale after activation. + +## 11. Metric Catalog + +The structural catalog is intentionally small. `Public eligible` means only +that a future observer may consider the metric after multi-reporter +aggregation. It never authorizes direct publication of one report. + +| Metric | Kind | Roles | Maximum visibility | Traffic-sensitive | +| --- | --- | --- | --- | --- | +| `process_start` | count | all | local | no | +| `process_stop` | count | all | local | no | +| `process_restart` | count | all | operator | no | +| `configuration_rejected` | count | all | operator | no | +| `consensus_health` | health | online reporters | public eligible | no | +| `clock_health` | health | online reporters | operator | no | +| `key_lifecycle_health` | health | online reporters | operator | no | +| `replay_database_health` | health | mix | operator | no | +| `queue_occupancy` | level | data plane | operator | yes | +| `handshake_completed` | count | online reporters | operator | yes | +| `handshake_failure` | count | online reporters | operator | yes | +| `connection_age` | level | online reporters | operator | yes | +| `profile_operations` | count | data plane | operator | yes | +| `padding_data_ratio` | level | data plane | public eligible | yes | +| `kemsphinx_failure` | count | mix, courier | operator | yes | +| `mix_latency` | level | mix | operator | yes | +| `cover_scheduler_health` | health | data plane | public eligible | no | +| `storage_success` | count | courier, store | public eligible | yes | +| `storage_overload` | count | courier, store | public eligible | yes | +| `storage_expiry` | count | store | public eligible | yes | +| `storage_corruption` | count | store | public eligible | no | +| `storage_repair` | count | store | public eligible | no | +| `capacity` | level | all | operator | no | +| `composer_boot` | count | Composer | local | no | +| `composer_lock` | count | Composer | local | no | +| `composer_integrity_failure` | count | Composer | local | no | +| `composer_failure` | count | Composer | local | no | +| `authority_validation_failure` | count | authority | operator | no | +| `observer_submission_rejected` | count | observer | local | no | +| `observer_publication_suppressed` | count | observer | local | no | + +Adding a metric, role, value, or visibility class is a protocol and threat- +model change. It requires schema versioning, conformance tests, and +differencing review. A runtime plugin cannot extend the catalog. + +## 12. Failure Classification + +Owning protocols map internal errors to the catalog before collection. Raw +errors never cross the boundary. A coarse count such as +`configuration_rejected`, `handshake_failure`, `kemsphinx_failure`, or +`authority_validation_failure` does not encode: + +- remote endpoint; +- exact parser or signature stage; +- expected or observed bytes; +- profile candidates; +- stack, file, line, or subsystem path; +- retry or connection identity. + +Where a protocol needs more than one failure class, it must add a small fixed +enum to this specification. It cannot place an exception message into a label +or diagnostic field. + +## 13. Window and Time Rules + +The active profile defines one origin and one duration. Roles derive: + +```text +window_id = floor((trusted_time - profile_origin) / window_duration) +``` + +This calculation is illustrative until the exact time profile is selected. +No event time is stored. Clock uncertainty beyond the profile bound changes +`clock_health` and follows the owning protocol's fail-closed time policy. +It does not cause a role to open shorter windows or emit immediate alerts. + +Window duration does not adapt to traffic volume. All reporting roles submit +at the same declared schedule with bounded profile-defined scheduling +behavior. Adaptive flush, inactivity flush, and event-triggered window close +are forbidden. + +## 14. Suppression and Bucketing + +The profile defines an activity unit for every reporting role, one minimum +activity threshold, and common bucket boundaries for compatible metric kinds. +Exact activity is never included in the sealed report. + +If activity is below the threshold: + +- every traffic-sensitive position is present with `suppressed`; +- non-traffic health and safety positions retain their bucketed value; +- the role still emits its scheduled observer submission; +- the public view emits no small-population inference about that role or + window. + +The suppression threshold is not an anonymity set size. It is one defense +against direct low-volume disclosure. Simulation and trace analysis must show +how it behaves under sparse use, outages, suppression attacks, and colluding +operators. + +## 15. IF-11 Aggregate Submission + +IF-11 carries one versioned aggregate submission from one authorized online +reporting role to `fog-observer`. It uses the mutually authenticated +role-specific FOG-WIRE context and an aggregate-signing key that is separate +from Noise, node identity, PKI vote, KEMSphinx, receipt, storage, and release +keys. + +The final IF-11 definition must bind at least: + +- network and schema version; +- observability profile; +- reporting role and authenticated reporter key; +- coarse window and earliest release window; +- exact ordered fixed-shape aggregate body; +- signature suite and purpose-separated signature. + +It must define exact size, padding, authentication, retry, replay, expiry, +parser limits, and failure behavior. FOG-WIRE fragmentation does not authorize +variable report geometry or arbitrary metric extensions. + +The observer rejects unknown fields, metrics, roles, profiles, values, +duplicate positions, reordered positions, stale windows, early release, +unauthorized signers, invalid signatures, and more than one accepted report +from the same reporter and window. Remote failure remains coarse and +non-amplifying. + +## 16. Observer Processing + +`fog-observer` maintains three separate stores: + +1. a bounded replay/deduplication index for accepted reporter windows; +2. short-lived raw signed submissions required for aggregation and audit; +3. derived operator and public aggregate windows. + +The stores use different access rights and retention. Raw submissions expire +after the bounded processing and dispute interval. They are not copied into a +general data lake, backup, search index, ticket, or dashboard cache. + +The observer never reads role log files, databases, queues, packet captures, +or container output. It cannot instruct a role to raise verbosity. It has no +credential accepted by data-plane, storage, authority-voting, or release +interfaces. + +## 17. Operator View + +The protected operator view may show only delayed fixed-window buckets from +metrics whose visibility is `operator` or `public eligible`. It may group by +role and declared public topology class when the active policy permits. + +It does not expose: + +- reporter or node drill-down for traffic-sensitive metrics; +- arbitrary time ranges or window overlap; +- raw submissions or signature identifiers as chart dimensions; +- correlation across roles, providers, links, or exact failure times; +- downloadable event records; +- queries parameterized by peer, route, connection, packet, or user input. + +Access is least privilege. Authentication and authorization failures are +recorded only as coarse local observer counters. Viewing a dashboard cannot +extend the underlying retention period. + +## 18. Public View and Differencing Defense + +A public aggregate requires all of the following: + +- the profile-defined minimum number of distinct authorized reporters; +- the required operator-family and role grouping; +- completion of the release delay; +- non-overlapping source and publication windows; +- bucketed output with no exact totals; +- suppression for low activity, reporter loss, or unsafe composition; +- one fixed set of published dimensions; +- no arbitrary filters, range queries, or node drill-down; +- review of adjacent releases for differencing and intersection leakage. + +Public series are not cumulative. If one group or window is suppressed, the +observer must not publish another overlapping total from which it can be +subtracted. A later merge may publish only when the merge rule was fixed in +advance and every released view remains non-subtractable. + +Reporter arrival, rejection, absence, and signature metadata are never public +dimensions. Public URLs, cache keys, ETags, and response timing must not vary +by hidden raw reporter state beyond the fixed publication schedule. + +## 19. Local Retention and Access + +The default implementation keeps sealed local summaries in a bounded volatile +ring. A role profile may instead authorize protected persistence only when it +defines: + +- exact maximum window count and age; +- owner and filesystem permissions; +- encryption and key ownership where required; +- atomic replacement and crash behavior; +- deletion and backup exclusion; +- manual export schema; +- consequences of a compromised operator account. + +No local report is retained indefinitely. Rotation removes the oldest whole +window. It does not compress old windows into cumulative history. + +The Composer stores any permitted summary inside its encrypted vault or +volatile memory. It has no automatic remote diagnostic path. A manually +exported Composer diagnostic contains only a newly created coarse report +selected and previewed by the user, never the raw ring or vault objects. + +## 20. Crash and Support Policy + +Production profiles disable: + +- core dumps and process memory capture; +- automatic panic, exception, or crash upload; +- operating-system crash collection that includes role memory; +- heap, goroutine, thread, profiler, packet, or syscall traces in support + artifacts; +- automatic attachment of logs, configuration, environment, or database + files to tickets. + +A crash increments only a coarse failure bucket after safe restart when the +owning state machine permits restart. Security-critical state such as replay, +ratchet, consensus, storage, or anchor state still follows its own fail-closed +recovery rules. Observability never authorizes bypass or state recreation. + +A manual support artifact uses a separate reviewed schema, shows the exact +fields to the operator or Composer user before export, excludes raw local +summaries by default, and has a fixed size and deletion policy. + +## 21. Debug and Test Builds + +Production artifacts contain no runtime switch that enables prohibited +logging. Environment variables, signals, command-line flags, configuration +files, remote administration, or observer requests cannot activate packet or +event tracing. + +Conformance builds may emit verbose traces only when all of the following +hold: + +- the build is distinctly labeled and rejected by production configuration; +- all keys, packets, contacts, routes, and inputs are public synthetic + fixtures generated for the test; +- the build cannot join a claim-bearing network; +- traces remain outside release artifacts and support bundles; +- tests prove that production builds omit the mode. + +## 22. Go Conformance Module + +`../observability/` follows a small library shape because future role code +will import the contract, while the observer daemon and wire codec remain +separate responsibilities. + +The module provides: + +- fixed `Role`, `Metric`, `Level`, `Health`, `Scope`, and value enums; +- role and value-kind validation; +- concurrency-safe saturating in-memory counters; +- an explicit policy with no default constructor; +- one-way collector sealing; +- fixed-shape local and observer reports; +- low-activity suppression; +- delayed observer windows; +- a concurrency-safe bounded volatile local ring; +- deterministic JSON only for conformance and local diagnostics. + +It deliberately does not provide: + +- arbitrary labels, messages, attributes, exemplars, or strings; +- exact timestamps, durations, sizes, counters, or samples in reports; +- disk persistence or remote log transport; +- OpenTelemetry, Prometheus, syslog, journald, or service-mesh adapters; +- IF-11 decoding, signing, networking, observer aggregation, or public output; +- a numeric production policy. + +The absence of those adapters is a security boundary, not an unfinished +convenience layer. + +## 23. Conformance and Adversarial Tests + +Before an observability profile becomes claim-bearing, tests must prove: + +1. every role rejects every metric outside its catalog; +2. every metric rejects the wrong value kind and unknown value; +3. exact counters saturate and never wrap; +4. sealing occurs once and exact counters are inaccessible afterward; +5. local and observer shapes are fixed for role and profile across zero, low, + threshold, and high activity; +6. low traffic produces `suppressed`, not a missing field or exact count; +7. Composer and FOG-SX roles cannot produce observer submissions; +8. observer reports cannot contain local-only fields; +9. observer release is later than the source window and overflow fails; +10. packet, message, trace, peer, route, capability, address, path, error, and + timestamp fields are absent from encoded reports; +11. local retention is bounded and chronological after rotation; +12. unknown, duplicate, reordered, stale, oversized, and trailing IF-11 input + fails before allocation or signature-dependent action; +13. duplicate reporter windows and replay fail without changing public state; +14. missing reporters, low populations, adjacent windows, and overlapping + queries cannot defeat suppression by differencing; +15. observer loss cannot alter data-plane or consensus behavior; +16. production binaries reject debug/test profiles and do not produce core + dumps or automatic support uploads; +17. malformed or attacker-controlled values never reach log formatting; +18. raw submissions expire and cannot be recovered from backups or dashboard + caches after the declared interval. + +The current Go module covers items 1 through 11 at the role-local contract +boundary. IF-11, observer, deployment, and public-release tests remain gated +on their exact implementations. + +## 24. Operational Alerts + +Alerts are delayed state conditions, not event notifications. Candidate alert +classes include: + +- consensus `degraded` or `unavailable`; +- clock `degraded` or `unavailable`; +- replay database `unavailable`; +- key lifecycle `degraded` or `unavailable`; +- cover scheduler `degraded` or `unavailable`; +- sustained capacity `high` or `full`; +- sustained storage corruption or repair failure; +- repeated configuration rejection or process restart buckets. + +The exact number of windows needed to alert is profile-defined. Alerts never +contain a packet, peer, connection, route, request, error string, or event +time. Immediate local safety shutdown remains the responsibility of the +owning role and does not wait for an alert. + +## 25. Deployment Requirements + +A claim-bearing deployment must demonstrate: + +- container or service-manager logging cannot capture stdout/stderr event + streams from the role; +- only fixed coarse startup failure output is possible before the collector; +- core dumps and automatic crash collection are disabled effectively, not + only in source configuration; +- observer credentials cannot access role state or another interface; +- no host port exposes role metrics directly; +- local summary storage and observer raw storage meet declared ownership, + permissions, capacity, backup, rotation, and deletion rules; +- dashboard and API caches do not outlive source retention; +- production configuration rejects unknown metrics, test profiles, and debug + modes; +- clock failure, observer outage, low activity, and reporter loss preserve the + declared fail-closed and suppression behavior. + +## 26. Requirements Traceability + +| Requirement | Observability response | +| --- | --- | +| `TM-OPS-01` | closed schema, forbidden data classes, coarse windows, suppression, delayed aggregation, bounded retention | +| `TM-NET-01` | no fine timing, event stream, route, peer, or cross-role trace identifiers | +| `TM-NET-03` | cover health, fixed reporting schedule, low-activity suppression, no weakened fallback | +| `TM-ROLE-01` | role-local collectors and keys, no universal log access | +| `TM-ROLE-02` | no packet, replay-tag, route, delay, or next-hop records | +| `TM-ROLE-03` | no request, selection, receipt, storage, or per-source histories | +| `TM-AVAIL-01` | saturating counters, bounded rings, fixed catalogs, bounded submissions | +| `ARC-001` | separate role collectors, observer, state, access, and reporting keys | +| `ARC-007` | one owner for local summaries and aggregate-signing material | +| `ARC-008` | fixed schema, no dynamic labels, bounded windows and retention | +| `ARC-009` | observability never authorizes a privacy-weakening recovery path | +| `IF-11` | delayed authenticated fixed-shape aggregate only | + +## 27. Open Activation Decisions + +Before enabling IF-11 or public output, FOG must select and validate: + +- window origin and duration; +- activity unit and minimum activity per reporting role; +- count, level, latency, ratio, age, queue, and capacity bucket boundaries; +- minimum independent reporters and operator-family diversity per public + group; +- role-to-observer delay, observer-to-public delay, and merge behavior; +- local, raw observer, derived operator, public, cache, and backup retention; +- the exact IF-11 body encoding, fixed size, padding, signature suite, key + certification, replay, retry, and expiry rules; +- public dimensions and anti-differencing composition analysis; +- clock-uncertainty behavior and fixed submission scheduling; +- deployment-specific storage, access, deletion, and crash controls; +- simulation and trace evidence for sparse traffic, outages, reporter loss, + malicious suppression, collusion, and long-term intersection; +- independent privacy and implementation review. + +Until those decisions are activated through signed profiles and evidence, the +safe deployment state remains local bounded summaries only, with observer and +public export disabled. + +## 28. References + +- FOG threat model: `FOG-THREAT-MODEL.md` +- FOG architecture: `FOG-ARCHITECTURE.md` +- FOG adjacent-link protocol: `FOG-WIRE.md` +- FOG KEMSphinx profiles: `FOG-SPHINX-PROFILES.md` +- FOG storage: `FOG-STORAGE.md` +- FOG Composer: `FOG-COMPOSER.md` +- FOG simulation: `FOG-SIMULATION.md` diff --git a/docs/FOG-PKI.md b/docs/FOG-PKI.md new file mode 100644 index 0000000..4bc0ebd --- /dev/null +++ b/docs/FOG-PKI.md @@ -0,0 +1,1814 @@ +# FOG Public Key Infrastructure + +Status: Draft 0.1 + +Date: 2026-08-08 + +## 1. Purpose + +This document defines `FOG-PKI-1`, the normative directory, admission, +authority, consensus, revocation, topology, and transparency protocol for the +FOG network. + +It refines the following baselines: + +- `FOG-THREAT-MODEL.md`, especially `TM-NET-06`, `TM-PKI-01`, + `TM-PKI-02`, `TM-PKI-03`, `TM-CRYPTO-01`, `TM-CRYPTO-02`, and + `TM-AVAIL-01`; +- `FOG-ARCHITECTURE.md`, especially `ARC-005`, `ARC-007`, `ARC-008`, + `ARC-009`, `IF-03`, `IF-04`, and `IF-05`. + +FOG is not implemented. Requirements in this document are protocol targets, +not statements about deployed security. + +The key words MUST, MUST NOT, REQUIRED, SHOULD, SHOULD NOT, and MAY describe +normative requirements in the sense of BCP 14 when they appear in uppercase. + +## 2. Security Goals + +FOG-PKI MUST provide: + +- one complete authenticated network view for a given epoch; +- M-of-N authority approval over identical canonical consensus bytes; +- public attribution of every authority signature; +- deterministic topology and parameter derivation from public inputs; +- permissioned admission and public role assignment; +- purpose-separated current and future node public keys; +- explicit validity, freshness, grace, and hard-expiry semantics; +- monotonic state that detects rollback and same-epoch forks; +- append-only consistency evidence for long-offline Composers; +- bounded key overlap, rotation, revocation, and authority replacement; +- public operator-family constraints for route selection; +- bounded deterministic parsing and offline verification; +- auditable evidence for authority, node, and checkpoint equivocation. + +FOG-PKI does not provide: + +- permissionless Sybil resistance; +- proof that nominally different operators are independent; +- availability after authority quorum loss; +- trusted time to an endpoint with no reliable clock; +- secrecy for public topology or node addresses; +- safety after compromise of the configured authority threshold; +- automatic recovery from loss of the pinned authority roots; +- anonymity by itself. + +## 3. Protocol Invariants + +### PKI-INV-01: Independent quorum signatures + +A valid consensus carries at least M valid signatures from distinct active +authorities over one identical canonical body. `FOG-PKI-1` does not use one +shared threshold private key. + +### PKI-INV-02: Sign once per epoch + +An authority MUST sign at most one consensus body hash for one network and +epoch. It MUST durably record that body hash before releasing its signature. + +### PKI-INV-03: Full view only + +Consumers MUST validate a full consensus. They MUST NOT merge descriptors, +signatures, topology, parameters, or revocations from different consensus +bodies. + +### PKI-INV-04: Monotonic acceptance + +A consumer MUST persist the highest accepted authority-set version, consensus +epoch, consensus hash, and transparency checkpoint. Older or conflicting state +MUST NOT replace it through an ordinary update. + +### PKI-INV-05: Offline roots, online voting keys + +Long-term authority root keys certify bounded-lifetime online voting keys. +Online voting keys sign routine protocol objects. Root private keys MUST NOT be +present on an online authority service. + +### PKI-INV-06: No self-authorized algorithms + +The signature and hash suite used to authenticate an object MUST be pinned by +already trusted state. An object MUST NOT select the algorithm by which its own +authenticity is decided. + +### PKI-INV-07: One node identity, one role + +One node identity has exactly one effective online role in an epoch. Layer +assignment is made by consensus, not self-declared by a node. + +### PKI-INV-08: Fail closed at hard expiry + +After consensus hard expiry, consumers MUST stop constructing new routes and +nodes MUST stop accepting new work under that consensus. Only explicitly +bounded drain and packet-lifetime behavior may continue. + +### PKI-INV-09: Public decisions, minimal personal data + +Admissions, assignments, suspensions, revocations, authority transitions, and +equivocation evidence are public. Personal names, private contact details, +credentials, and sensitive supporting evidence MUST NOT enter public PKI +objects. + +### PKI-INV-10: No trust on first use recovery + +Unknown authority, node, operator, mirror, witness, or replacement keys MUST +NOT become trusted because they are the only reachable keys. + +## 4. Roles and Trust + +### 4.1 Authority root operator + +The root operator controls one authority root key in an offline ceremony. The +root key certifies that authority's online vote keys and participates in +authority-set transitions. + +### 4.2 Online authority + +`fog-authority` accepts descriptors, exchanges protocol messages, constructs +deterministic proposals, signs one consensus body per epoch, and publishes +public artifacts. It has no privileged data-plane route. + +### 4.3 Operator + +An operator controls an operator identity and one or more separately keyed +nodes. The operator declares common control, infrastructure relationships, and +role requests. Admission does not prove honesty or independence. + +### 4.4 Node + +A node signs its descriptor and proves possession of its node identity. It +publishes only the role-specific public keys and endpoints needed by the +network. + +### 4.5 Mirror + +A mirror distributes immutable public objects. It is untrusted for +authenticity, freshness, completeness, and consistency. + +### 4.6 Witness or monitor + +A witness independently fetches, compares, archives, and republishes consensus +and transparency checkpoints. Witnesses improve detection but do not replace +the authority quorum. + +### 4.7 Consumer + +Consumers are Composers, blind relays, entries, mixes, couriers, storage +replicas, services, and observers. Each maintains role-appropriate monotonic +PKI state. The offline Composer is the final authority for user route +construction. + +## 5. Authority Set and Quorum + +An authority set contains: + +- an exact authority-set version; +- an odd number N of authority roots; +- a quorum M where `floor(N / 2) + 1 <= M <= N`; +- one stable identifier and root public key per authority; +- the permitted online signing suites; +- activation and retirement epochs; +- the transparency-log identifier; +- the hash of the previous authority-set manifest. + +The initial claim-bearing profile uses N = 3 and M = 2. A later preferred +profile uses N = 5 and M = 3. A local PoC MAY use a simulated smaller set, but +the resulting consensus is functional test data only. + +Consensus signatures are independent signatures. A signature record names its +authority and certified online key. A verifier counts at most one valid +signature per active authority. + +Signatures from unknown, duplicate, expired, revoked, not-yet-active, or +wrong-set keys do not count. Extra invalid signatures MUST make the envelope +invalid instead of being silently ignored, because inconsistent validation +would create implementation fingerprints. + +## 6. `FOG-PKI-CBOR-1` Encoding + +FOG-PKI objects use a restricted deterministic CBOR profile based on RFC 8949. +This profile is named `FOG-PKI-CBOR-1`. + +Every signed object has the form: + +```text +SignedObject = [ + magic, + encoding_version, + object_type, + body +] +``` + +where: + +- `magic` is the byte string `FOGPKI1`; +- `encoding_version` is unsigned integer `1`; +- `object_type` is a registered unsigned integer; +- `body` is an exact-length array defined by this specification. + +An authenticated envelope has the form: + +```text +SignedEnvelope = [ + signed_object_bytes, + signatures +] + +SignatureRecord = [ + authority_or_subject_id, + key_id, + signature_suite_id, + signature_bytes +] +``` + +`signed_object_bytes` is a CBOR byte string containing the exact canonical +encoding of `SignedObject`. A verifier MUST parse it, enforce this profile, +re-encode it, and require byte-for-byte equality before checking signatures. + +The profile permits only: + +- unsigned integers in their shortest encoding; +- definite-length byte strings; +- definite-length UTF-8 text strings where a field explicitly permits text; +- definite-length arrays with exact schema length. + +The profile forbids: + +- negative integers; +- maps; +- floating-point values; +- tags; +- simple values, including `null`, `true`, and `false`; +- indefinite-length items; +- duplicate set elements; +- unsorted set-like arrays; +- trailing bytes or concatenated objects. + +Boolean fields use unsigned integer `0` or `1`. Optional fields use an +explicit variant discriminator and exact variant array, never `null`. + +Set-like arrays MUST be sorted by the canonical byte encoding of the complete +element and MUST contain no duplicate element. Ordered protocol sequences MUST +retain their specified order and MUST NOT be sorted. + +Unknown object types, versions, fields, enum values, key purposes, signature +suites, hash suites, roles, parameters, or extensions are critical and MUST be +rejected in version 1. + +## 7. Object Types + +`FOG-PKI-1` reserves the following object types: + +| Value | Object | +| --- | --- | +| 1 | `TrustAnchorManifest` | +| 2 | `AuthorityOnlineKeyCertificate` | +| 3 | `OperatorRecord` | +| 4 | `AdmissionDecision` | +| 5 | `NodeDescriptor` | +| 6 | `AuthorityCommit` | +| 7 | `AuthorityReveal` | +| 8 | `AuthorityProposal` | +| 9 | `ConsensusBody` | +| 10 | `RevocationStatement` | +| 11 | `AuthoritySetTransition` | +| 12 | `EpochArchive` | +| 13 | `LogCheckpoint` | +| 14 | `EquivocationEvidence` | +| 15 | `RecoveryManifest` | +| 16 | `ProfileTransition` | +| 17 | `AuthorityWireKeyCertificate` | +| 18 | `StorageReplicaManifest` | + +Object type assignments never change meaning. Incompatible schemas require a +new encoding or object version. + +## 8. Domain Separation and Identifiers + +The trusted PKI suite defines one approved hash construction and its output +length. All hashes use a distinct ASCII domain string encoded as the first +element of a canonical CBOR array. + +Examples: + +```text +key_id = HASH(["FOG-PKI-KEY-ID-1", algorithm_id, public_key]) +authority_id = HASH(["FOG-PKI-AUTHORITY-ID-1", root_key_id]) +operator_id = HASH(["FOG-PKI-OPERATOR-ID-1", operator_key_id]) +node_id = HASH(["FOG-PKI-NODE-ID-1", node_identity_key_id]) +object_hash = HASH(["FOG-PKI-OBJECT-1", signed_object_bytes]) +consensus_hash = HASH(["FOG-PKI-CONSENSUS-1", consensus_body_bytes]) +descriptor_hash = HASH(["FOG-PKI-DESCRIPTOR-1", descriptor_bytes]) +storage_manifest_hash = HASH([ + "FOG-PKI-STORAGE-MANIFEST-1", + storage_manifest_bytes +]) +``` + +Signature input is the canonical encoding of: + +```text +[ + "FOG-PKI-SIGNATURE-1", + network_id, + object_type, + signature_suite_id, + signed_object_bytes +] +``` + +The exact strings above are part of version 1 and are case-sensitive. Binding +`signature_suite_id` prevents a valid component from being rewrapped under a +different signature suite over otherwise identical object bytes. + +`network_id` is a uniformly random 32-byte value created at genesis and pinned +in the initial trust-anchor manifest. It is not derived from a DNS name, +project title, mirror, or operator identity. + +Identifiers are public correlation handles inside PKI. They MUST NOT be reused +as message identities, user accounts, storage capabilities, transport secrets, +or release identities. + +## 9. Absolute Parser Limits + +All implementations MUST enforce these absolute version-1 limits before +allocation proportional to attacker input: + +| Item | Limit | +| --- | --- | +| Any `SignedEnvelope` | 8 MiB | +| One `PKIUpdateBundle` | 16 MiB | +| One `NodeDescriptor` envelope | 128 KiB | +| One `StorageReplicaManifest` envelope | 1 MiB | +| One proof bundle | 1 MiB | +| Nesting depth | 8 arrays | +| Authorities | 9 | +| Authority signatures | 9 | +| Operators | 512 | +| Nodes in one consensus | 1024 | +| Endpoints per node | 4 | +| Family or infrastructure groups per node | 16 | +| Epoch public keys per node | 24 | +| Active protocol profiles | 32 | +| Storage manifests per consensus | 3 | +| Replica records per storage manifest | 256 | +| Revocation entries per consensus | 4096 | +| Text field | 128 UTF-8 bytes | +| One public key or signature component | 128 KiB | + +The initial deployment profile MAY set lower limits. Increasing an absolute +limit requires a new PKI version and parser review. FOG-PKI uses no compression +in version 1. + +## 10. Trust Bootstrap + +### 10.1 Genesis material + +A new Composer or node begins with: + +- the 32-byte `network_id`; +- one exact genesis `TrustAnchorManifest` hash; +- the full genesis manifest; +- the release-verification trust anchor used for the software image; +- the initial transparency checkpoint; +- the accepted PKI encoding and cryptographic suite identifiers. + +The genesis manifest is a trust anchor. Self-signatures do not create its +trust. Its hash MUST be pinned in the verified software release and SHOULD be +available through at least one independent human-verifiable channel. + +DNS, TLS, a mirror, the blind relay, a QR label, or a reachable authority MUST +NOT replace the pinned genesis hash. + +### 10.2 `TrustAnchorManifest` + +The manifest body is: + +```text +[ + network_id, + manifest_version, + previous_manifest_hash, + activation_epoch, + retirement_epoch, + quorum_m, + authority_roots, + pki_authentication_suite_id, + allowed_object_suite_ids, + transparency_log_id, + epoch_origin, + epoch_duration, + schedule_profile_id, + hard_limits_profile_id +] +``` + +`authority_roots` is a sorted array of: + +```text +[ + authority_id, + root_key_id, + root_signature_algorithm_id, + root_public_key, + public_alias +] +``` + +`public_alias` is an informational pseudonymous label. It is not an identity +proof and MUST NOT contain private contact information. It is 1 to 64 bytes +from ASCII letters, digits, `.`, `_`, and `-`; it need not be globally unique +and MUST NOT be used in security decisions. + +Genesis uses an all-zero `previous_manifest_hash`. Later manifests are +accepted only through the transition procedure in Section 22. + +`manifest_version` is the `authority_set_version` used by certificates, +consensus, checkpoints, and transitions. The two terms describe the same +monotonic unsigned integer. + +## 11. Authority Key Hierarchy + +### 11.1 Authority root key + +Each authority has one active offline root identity at a time. Its root private +key: + +- certifies that authority's online voting keys; +- certifies that authority's dedicated online FOG-WIRE keys; +- signs authority-set transitions; +- signs root-key replacement or retirement; +- participates in authenticated disaster recovery. + +It MUST NOT sign routine consensus, descriptor, commit, reveal, proposal, +checkpoint, or admission objects. + +The root private key MUST be generated and used in an offline ceremony. Backup +material MUST be encrypted, authenticated, geographically separated where +appropriate, and stored separately from its decryption or recovery secret. + +### 11.2 Online vote key + +An online voting key signs routine authority protocol objects. It is certified +by its authority root in an `AuthorityOnlineKeyCertificate`: + +```text +[ + network_id, + authority_set_version, + authority_id, + online_key_generation, + online_key_id, + online_signature_suite_id, + online_public_key, + valid_from_epoch, + valid_until_epoch, + previous_certificate_hash +] +``` + +The certificate is signed by the matching authority root. An online key +certificate MUST cover no more than 32 epochs. One current and one next online +key certificate MAY overlap for at most two epochs. + +Consensus validation uses the online key valid for the consensus epoch. A key +outside its certified interval cannot sign that epoch even if its certificate +has not been explicitly revoked. + +### 11.3 Authority wire key + +An authority uses a dedicated online wire key for mutually authenticated +`FOG-WIRE` links with peer authorities and admitted descriptor submitters. It +MUST NOT reuse the authority root key or online vote key for transport. + +The wire key is certified by the matching authority root in an +`AuthorityWireKeyCertificate`: + +```text +[ + network_id, + authority_set_version, + authority_id, + wire_key_generation, + wire_key_id, + wire_algorithm_id, + wire_public_key, + supported_wire_profile_ids, + valid_from_epoch, + valid_until_epoch, + previous_certificate_hash +] +``` + +`supported_wire_profile_ids` is sorted and duplicate-free. Every listed +profile MUST already be trusted and MUST require the declared key algorithm. +The certificate does not authorize its own algorithm or wire profile. + +The certificate is signed by the matching offline authority root. It MUST +cover no more than 32 epochs. One current and one next authority wire-key +certificate MAY overlap for at most two epochs. There MUST be exactly one +valid wire key for one authority, profile, and epoch, unless one already +trusted profile defines a composite key as one key record. + +An authority wire key authenticates only `NODE_AUTHORITY` and +`AUTHORITY_AUTHORITY` contexts from `FOG-WIRE-1`. It MUST NOT sign votes, +consensus, checkpoints, admission decisions, descriptors, releases, or user +objects. + +### 11.4 Signature-suite composition + +A suite MAY require more than one signature component, including a classical +and post-quantum component. A composite signature is valid only when every +mandatory component in the already trusted suite validates over the same +signature input. + +Components from different signature records, objects, keys, or authorities +MUST NOT be combined to manufacture one valid composite signature. + +`FOG-PKI-HASH-CANDIDATE-SHA3-256-1` and +`FOG-PKI-CANDIDATE-MLDSA65-ED25519-1` are the leading non-active candidates. +The latter requires independently generated ML-DSA-65 and Ed25519 keys and +requires both components over the exact suite-bound signature input. Their +encoding, vectors, separability analysis, implementation, side-channel review, +and numeric activation remain pre-PoC gates. Algorithm names or post-quantum +labels alone do not satisfy review. + +## 12. Operator Identity and Family Records + +An operator uses one dedicated signing identity that is separate from node, +authority, release, user, and infrastructure login keys. + +An `OperatorRecord` contains: + +```text +[ + network_id, + operator_id, + operator_key_id, + operator_signature_algorithm_id, + operator_public_key, + public_alias, + family_ids, + infrastructure_group_ids, + declared_provider_codes, + declared_as_numbers, + declared_country_codes, + record_sequence, + valid_from_epoch, + valid_until_epoch +] +``` + +The operator signs the record, and admission authorities approve its hash. + +`family_ids` describe known common control or cooperation across operator +identities. `infrastructure_group_ids` describe shared provider accounts, +orchestration, management, backup, monitoring, corporate ownership, or other +common compromise domains. + +Authorities MAY conservatively merge family or infrastructure groups when +credible evidence indicates common control. They MUST NOT split an existing +group without an ordinary threshold decision and a public rationale code. + +Provider, ASN, and country declarations are public routing inputs, not proof +of operator independence. Route selection MUST reject reuse of one operator, +family, or prohibited infrastructure group where the active profile requires +diversity. + +Public records MUST NOT contain legal names, personal email addresses, phone +numbers, billing identifiers, street addresses, login names, or private abuse +reports. Authorities MAY maintain a separate access-controlled admission file, +but its content is outside consensus and MUST NOT be required by clients. + +## 13. Permissioned Admission + +### 13.1 Application + +The initial network is permissioned. An operator applies with: + +- its signed `OperatorRecord`; +- the node identity public key and proof of possession; +- requested role and capabilities; +- canonical endpoints; +- future role-specific public keys; +- declared family and infrastructure relationships; +- operational and policy evidence required by governance. + +Private supporting evidence is not placed in the public application object. + +### 13.2 Decision + +An `AdmissionDecision` contains: + +```text +[ + network_id, + decision_sequence, + subject_type, + subject_id, + operator_record_hash, + requested_role, + disposition, + public_reason_code, + constraints, + effective_epoch, + expiry_epoch +] +``` + +`disposition` is one of admit, deny, suspend, reinstate, or retire. It uses a +registered unsigned integer, not text. + +Ordinary admission, role change, reinstatement, and retirement require M +independent authority signatures. A single authority cannot admit a node. + +Admission binds one node identity to one operator and one eligible role. A +role change requires a new decision. Admission does not assign a mix layer; +the consensus topology does. + +### 13.3 Reapplication and replacement + +A revoked node identity MUST NOT be reused. A replacement generates a new node +identity and follows ordinary admission. A changed endpoint or role-specific +key does not require a new node identity if a valid monotonic descriptor and +the existing admission constraints permit the change. + +## 14. Node Identity and Descriptor + +### 14.1 Node identity + +Every online node has one role-local node identity signing key. It signs +descriptors and proves continuity across epoch-key rotation. It MUST NOT be +used for Noise transport, KEMSphinx, entry capsules, storage envelopes, +metrics, release signing, or operator administration. + +### 14.2 Descriptor body + +A `NodeDescriptor` contains: + +```text +[ + network_id, + descriptor_version, + descriptor_sequence, + node_id, + node_identity_key_id, + node_identity_algorithm_id, + node_identity_public_key, + operator_id, + operator_record_hash, + admitted_role, + capability_ids, + endpoints, + epoch_public_keys, + supported_profile_ids, + valid_from_epoch, + valid_until_epoch, + previous_descriptor_hash +] +``` + +It is signed independently by the node identity and operator identity. Both +signatures are required. + +`descriptor_sequence` starts at zero and increases by exactly one for each +accepted replacement descriptor. A new descriptor names the previous accepted +descriptor hash. A gap, rollback, duplicate sequence with a different hash, or +broken chain is invalid. + +One descriptor MUST cover no more than four consecutive epochs. It MUST carry +the public keys required for its current and next usable key periods without +extending private-key overlap beyond the owning protocol profile. + +### 14.3 Endpoints + +An endpoint is: + +```text +[ + transport_profile_id, + address_type, + address_bytes, + port, + priority_class +] +``` + +Only endpoint forms explicitly permitted by the active wire profile are +valid. An endpoint MUST NOT contain a URL path, username, password, API token, +query string, fragment, or unauthenticated redirect target. + +DNS-only identity is forbidden. If a profile permits DNS discovery, the +descriptor still binds the node identity and authenticated endpoint behavior; +DNS never authorizes a different node key. + +### 14.4 Epoch public keys + +Each public key entry is: + +```text +[ + purpose_id, + algorithm_id, + key_id, + public_key, + valid_from_epoch, + valid_until_epoch +] +``` + +Purpose IDs distinguish at least: + +- node Noise transport; +- entry submission capsule; +- entry return KEMSphinx terminal processing where required; +- mix KEMSphinx transformation; +- courier or service terminal KEMSphinx processing; +- replica envelope protection; +- replica durable-result receipt authentication; +- aggregate signing. + +A descriptor MUST contain exactly the key purposes required by its admitted +role and MUST NOT contain keys for another role. The current and next key +periods MUST be published before the descriptor deadline. More than one active +key for the same purpose and epoch is invalid unless the active suite defines +one composite key as a single key record. + +Private keys never enter a descriptor, vote, consensus, proof, log, example, +fixture, or support artifact. + +## 15. Epoch and Time Model + +The genesis manifest pins: + +- `epoch_origin`, an unsigned Unix-time second; +- `epoch_duration`, a whole number of seconds; +- one schedule profile; +- maximum clock uncertainty; +- consensus freshness and hard-expiry offsets; +- descriptor, commit, reveal, proposal, signature, and publication deadlines; +- packet and key grace constraints supplied by the owning protocol profiles. + +Epoch number at time `t` is: + +```text +floor((t - epoch_origin) / epoch_duration) +``` + +for `t >= epoch_origin`. + +The exact initial epoch duration and schedule offsets remain a pre-PoC +operational selection. They MUST be identical for all participants, encoded in +the trust manifest, and supported by fault-injection tests. Operators cannot +override them locally. + +Changing epoch origin, duration, schedule, or maximum clock uncertainty +requires an authority-set manifest transition with at least two epochs of +advance notice. It is not an ordinary consensus parameter change. + +Consumers MUST evaluate time with an explicit local uncertainty interval. A +relay-provided timestamp, mirror `Date` header, DNS response, or single time +server is not trusted time. If the uncertainty interval cannot establish +validity, the consumer fails closed for new work. + +## 16. Authority Protocol State Machine + +For target epoch E, each online authority executes these phases. + +### 16.1 Collect + +Authorities collect valid descriptors, operator records, admission decisions, +revocations, online key certificates, and announced transitions before the +descriptor deadline. + +Inputs arriving after the deadline are considered only for a later epoch. +Authorities MUST NOT create different inclusion behavior based on requester +latency after the public deadline. + +### 16.2 Commit + +Each authority generates a fresh uniformly random secret for epoch E and +publishes a signed `AuthorityCommit`: + +```text +[ + network_id, + authority_set_version, + epoch, + authority_id, + commitment, + previous_consensus_epoch, + previous_consensus_hash +] +``` + +The commitment is the suite hash of a domain-separated encoding containing +the network, epoch, authority, random secret, and previous consensus hash. + +Exactly: + +```text +commitment = HASH([ + "FOG-PKI-COMMIT-1", + network_id, + authority_set_version, + epoch, + authority_id, + previous_consensus_epoch, + previous_consensus_hash, + random_secret +]) +``` + +### 16.3 Reveal + +After the commit deadline, each authority publishes a signed +`AuthorityReveal` containing the secret corresponding to its prior commitment. +Reveals without one valid timely commit, invalid reveals, duplicates, or early +reveals are excluded and recorded. + +If fewer than M authorities provide valid commit/reveal pairs, no consensus is +produced. + +The shared seed is derived from the previous consensus hash and the sorted +valid authority reveals using the pinned hash suite: + +```text +shared_seed = HASH([ + "FOG-PKI-SHARED-SEED-1", + network_id, + authority_set_version, + epoch, + previous_consensus_epoch, + previous_consensus_hash, + sorted_reveals +]) + +sorted_reveals = [ + [authority_id, random_secret], + ... +] +``` + +The complete commit and reveal hashes are included in the consensus audit +section. `sorted_reveals` is sorted by `authority_id` and contains no duplicate +authority. + +Commit-reveal limits unilateral prediction but permits withholding and denial +of service by a last revealer. FOG-PKI does not claim bias-free randomness from +this construction. Topology stability and deterministic public auditing limit +where the seed is security-critical. + +### 16.4 Propose + +Each authority independently applies the exact deterministic rules in this +specification to the same eligible input set. It signs and exchanges an +`AuthorityProposal` containing the resulting consensus body hash and sorted +input object hashes. + +Authorities MUST NOT average, merge, or majority-vote individual fields from +different proposals. A body is signable only when at least M authorities have +published identical proposal body and input hashes. + +### 16.5 Sign + +Before signing, an authority: + +1. validates its active online key certificate; +2. recomputes the complete canonical consensus body; +3. verifies the previous consensus and authority-set chain; +4. persists `(network_id, epoch, consensus_hash)` in durable sign-once state; +5. refuses if any different hash is already stored for that epoch; +6. signs the canonical `ConsensusBody` object. + +The sign-once record MUST survive restart, restore, and failover. Cloning an +authority database into two active signers is forbidden. + +### 16.6 Publish + +Authorities exchange detached signatures. Any publisher can assemble a valid +`ConsensusEnvelope` only by attaching at least M distinct valid signatures to +the exact body bytes. + +Authorities publish the body, all signature records, relevant certificates, +input hashes, epoch archive, and transparency material. Mirrors may copy these +bytes but cannot modify them. + +Signature collection closes at the public signature deadline. The final +sorted signature-record set is committed by the epoch archive before +`valid_after`. A later signature over the same body remains evidence of that +authority's statement but MUST NOT be inserted into the finalized consensus +envelope for that epoch. + +## 17. Deterministic Eligibility and Topology + +### 17.1 Eligibility filtering + +A node is eligible for epoch E only if: + +- its admission is active for E; +- its operator record is active and threshold-approved; +- its descriptor chain and both descriptor signatures validate; +- the descriptor covers E and has the required future key material; +- it is not suspended or revoked; +- every endpoint, key, capability, and profile is permitted; +- its role does not conflict with another identity or active assignment; +- its operator and infrastructure declarations meet the active profile. + +All filtering operates on canonical public data and deterministic reason +codes. Private authority evidence may cause a signed public suspension or +revocation decision, but it MUST NOT silently change deterministic evaluation. + +### 17.2 Effective roles + +The consensus assigns each eligible node exactly one of: + +- entry; +- mix layer 1; +- mix layer 2; +- mix layer 3; +- courier; +- storage replica; +- declared native service; +- observer. + +The node requests an admitted role. Only mix layer number is assigned by the +consensus topology algorithm. A node cannot self-place into a layer. + +### 17.3 Topology generation + +Layer assignment remains stable within one `topology_generation`. A new +generation is created only for admission, removal, capacity change, diversity +repair, or deliberate rebalance. Routine epoch key rotation MUST NOT reshuffle +all mix layers. + +For a new generation: + +1. eligible nodes are grouped by role; +2. each group is sorted by `node_id`; +3. the shared seed drives a suite-defined deterministic pseudorandom shuffle + using unbiased sampling; +4. mix nodes are assigned to three balanced layers; +5. deterministic constraints maximize operator, family, provider, ASN, + country, and infrastructure-group diversity; +6. a validation pass proves that the profile has at least one allowed complete + route and meets its minimum node counts; +7. failure to satisfy hard constraints makes the proposal invalid. + +The exact deterministic shuffle and constraint solver require conformance +vectors before the PoC. Different implementations MUST produce byte-identical +topology from the same inputs and seed. + +### 17.4 Route constraints + +Consensus publishes the attributes needed by the Composer to reject routes +that repeat one operator, family, or prohibited infrastructure group. The +consensus MUST NOT precompute one user route or allow operators to choose user +paths. + +## 18. Consensus Body + +The canonical `ConsensusBody` is: + +```text +[ + network_id, + pki_version, + authority_set_version, + epoch, + topology_generation, + valid_after, + fresh_until, + valid_until, + previous_consensus_epoch, + previous_consensus_hash, + trust_anchor_manifest_hash, + commit_reveal_summary, + operator_records, + node_descriptors, + topology, + revocations, + storage_manifests, + active_profile_ids, + network_parameters, + announced_transition_hashes +] +``` + +`previous_consensus_epoch` and `previous_consensus_hash` identify the latest +prior epoch that produced a valid consensus. They may skip scheduled epochs +that ended without consensus. Each skipped epoch still receives a +no-consensus transparency archive under Section 24. + +### 18.1 Validity times + +`valid_after`, `fresh_until`, and `valid_until` are derived exactly from the +epoch schedule. Authorities do not choose them independently. + +- Before `valid_after`, the consensus may be staged but not used for new work. +- After `fresh_until`, a consumer reports a stale-update condition but MAY + continue only until `valid_until` under the same profile. +- At `valid_until`, new route construction and new work stop. + +No mirror, relay, node, operator, or single authority can extend these times. + +### 18.2 Full descriptors + +Version 1 consensus contains the complete accepted operator records and node +descriptors needed for route and link validation. It does not use +state-dependent descriptor deltas or fetch-on-demand partial views. + +Each embedded record or descriptor is a CBOR byte string containing its full +canonical signed envelope. Arrays are sorted by the corresponding stable +identifier, not by operator alias, endpoint, or arrival order. Consumers +independently validate the embedded operator and node signatures after the +authority quorum signature. + +`storage_manifests` contains the complete canonical previous, current, and +announced next `StorageReplicaManifest` objects required by the active +FOG-STORAGE retention and transition windows. The objects are sorted by +storage epoch and manifest hash, duplicate-free, and validated under +`FOG-PKI-CBOR-1` and the authority quorum. Consumers MUST NOT merge replica +records or storage keys from different manifests. The exact object schema, +sign-once state, shard inputs, and key windows are defined by +`FOG-STORAGE.md`. + +### 18.3 Profiles and parameters + +`active_profile_ids` authorizes exact wire, packet, storage, entry, cover, and +application protocol profiles. `network_parameters` contains only registered +integer parameter IDs and bounded unsigned integer values. + +For KEMSphinx packet processing, consensus and the applicable +`FOG-SPHINX-PROFILES` record MUST resolve exactly one packet profile for each +tuple `(epoch, link_context, packet_class)`. During a transition, old and new +packet profile IDs may both appear only with an unambiguous activation and +drain mapping. A consumer MUST reject a consensus that makes profile selection +ambiguous, requires packet-length detection, or maps one new-work tuple to +more than one packet profile. + +Every node descriptor selected for a KEMSphinx route MUST contain the exact +epoch KEM key purpose, role, layer, and packet profile required by that route. +The consensus is invalid if its topology cannot supply a complete four-hop +forward and reply shape required by the active packet profile. + +A parameter cannot select the suite used to authenticate its own consensus. +Security-critical profile transition requires a preannounced bounded overlap +and cannot silently downgrade an already accepted minimum. + +A `ProfileTransition` contains: + +```text +[ + network_id, + transition_sequence, + profile_class, + old_profile_ids, + new_profile_ids, + announcement_epoch, + activation_epoch, + retirement_epoch, + minimum_compatible_version +] +``` + +It requires M independent authority signatures, at least two epochs of advance +notice, a bounded old/new overlap, and inclusion of its hash in every +intervening consensus. Parameters classified as profile-bound change only +through this object. Parameters explicitly classified as dynamic MUST have +hard minimum, maximum, and per-epoch change bounds in the active profile. + +### 18.4 Signature envelope + +A `ConsensusEnvelope` is a `SignedEnvelope` whose signed object type is +`ConsensusBody`. Signature records are sorted by `authority_id` and contain no +duplicates. + +The consensus hash covers the body bytes only. The valid consensus identity is +the pair `(epoch, consensus_hash)`. Adding a valid signature does not create a +different consensus identity. + +## 19. Consumer Consensus Validation + +A consumer MUST perform these checks in order: + +1. enforce the outer byte limit before parsing; +2. decode one exact `SignedEnvelope` and reject trailing data; +3. validate `FOG-PKI-CBOR-1` and canonical byte equality; +4. require the pinned `network_id`, PKI version, and object type; +5. load the already trusted authority-set manifest for the declared version; +6. validate all required authority online vote-key and wire-key certificates + and revocation state; +7. verify every included signature and require at least M distinct valid + active authorities; +8. recompute the consensus hash; +9. require exact epoch schedule times and acceptable local clock uncertainty; +10. reject an epoch below the highest accepted epoch; +11. reject a different hash for an already accepted epoch; +12. verify the previous-consensus relationship or the offline consistency + bundle described in Section 25; +13. validate all records, descriptors, key purposes, profile IDs, parameters, + revocations, limits, topology assignments, and diversity constraints; +14. verify the epoch archive and transparency inclusion and consistency + evidence; +15. atomically persist the new manifest version, epoch, consensus hash, + checkpoint, and minimum accepted profile state; +16. only then expose the network view to route or link logic. + +Signature verification MUST occur before expensive validation proportional to +the complete topology where practical, but canonical parsing and strict bounds +always occur first. + +An implementation MUST NOT choose one of two valid-looking same-epoch bodies +by timestamp, signature count above M, mirror order, lexicographic hash, or +network reachability. It freezes and reports equivocation. + +## 20. Node Consumption and Key Acceptance + +Online nodes apply the same consensus authentication rules as Composers and +also MUST: + +- confirm their own effective role and layer before serving traffic; +- bind peer eligibility to the active consensus and link profile; +- accept role-specific epoch keys only for their declared purpose; +- validate root-certified current or staged authority wire keys before + `NODE_AUTHORITY` or `AUTHORITY_AUTHORITY` handshakes; +- stage next-epoch connections without forwarding next-epoch traffic early; +- close or reject peers removed by the next consensus at the specified + boundary; +- retain previous packet-processing keys only for the exact bounded packet + grace period; +- erase expired epoch private keys after all required replay and packet + windows close; +- stop if local configuration attempts to activate a second role. + +An operator configuration cannot add a peer, key, profile, or role absent from +consensus. + +## 21. Revocation and Suspension + +### 21.1 Scope + +A `RevocationStatement` may target: + +- one role-specific epoch key; +- one node identity and all its keys; +- one operator and all admitted nodes; +- one authority online voting key; +- one authority online wire key; +- one authority root through an authority-set transition; +- one protocol profile or suite through a preannounced transition. + +### 21.2 Body + +```text +[ + network_id, + revocation_sequence, + target_type, + target_id, + scope, + public_reason_code, + effective_epoch, + effective_time, + expiry_epoch, + replacement_policy, + previous_revocation_hash +] +``` + +Ordinary suspension, revocation, expiry change, and reinstatement require M +authority signatures. + +### 21.3 Self-revocation + +A node or operator MAY publish a self-revocation signed by the affected +identity. Consumers treat a valid self-revocation as an immediate denial-of- +service-safe removal of that identity. Self-revocation cannot authorize a +replacement key, identity, operator, role, or authority. + +### 21.4 Emergency revocation + +Between normal consensuses, M authorities MAY sign one emergency revocation +envelope. It MUST: + +- use a monotonic revocation sequence; +- identify the last valid consensus hash; +- have a short explicit expiry no later than the next normal consensus hard + expiry; +- be included in the next epoch archive; +- never add or reinstate an identity; +- never change quorum, authority roots, profiles, or topology except by + removing the compromised subject. + +A single authority warning is public evidence but is not an effective network +revocation. + +### 21.5 Compromise response + +After confirmed compromise: + +- stop new use of the affected private key; +- publish self-revocation where safe; +- issue threshold suspension or revocation; +- remove affected objects from new consensus; +- generate purpose-separated replacement keys; +- use ordinary admission or transition for replacement; +- preserve public evidence without publishing secret forensic material; +- document which past and future properties may have failed. + +Revocation does not erase previously recorded traffic, signatures, or +compromise effects. + +## 22. Authority-Set Transition + +Authority membership, quorum, root keys, PKI authentication suite, epoch +schedule, and transparency-log identity change only through an +`AuthoritySetTransition`. + +The transition body contains: + +```text +[ + network_id, + old_manifest_version, + old_manifest_hash, + new_manifest_version, + new_manifest_hash, + announcement_epoch, + activation_epoch, + transition_reason_code +] +``` + +Acceptance requires: + +1. versions increase by exactly one; +2. the old manifest is already trusted; +3. activation is announced at least two epochs in advance; +4. at least M old authority root signatures validate; +5. at least the new quorum of new authority root signatures validate; +6. the transition hash appears in every consensus during the overlap; +7. the new manifest chains to the old manifest hash; +8. the consumer persists the new manifest before its activation epoch; +9. old roots stop authorizing ordinary new state at activation; +10. a bounded verification overlap exists only for objects created before the + boundary. + +This dual-quorum procedure follows the principle that both the old trusted set +and the new set authorize a root transition. + +The old quorum signs the exact new root keys and suite identifiers. A new +authentication suite MUST already be supported and explicitly authorized as a +transition suite by the old manifest during the overlap. A client MUST NOT +jump directly from an old suite to an object that can be verified only by an +algorithm named inside that object. Software support for a suite does not make +the suite trusted without this chained authorization. + +If too many old roots are lost to reach the old quorum, the network has no +automatic in-protocol recovery. Recovery requires a separately authenticated +`RecoveryManifest`, explicit user or operator action, and a new trust anchor +obtained through independent channels. It MUST NOT resemble an ordinary +update or trust-on-first-use prompt. + +## 23. Topology and Profile Revocation Effects + +Removing one node MUST NOT cause consumers to bypass its layer. A new +consensus recomputes a valid complete topology or fails. + +If revocation makes the minimum topology or operator diversity impossible: + +- no stronger privacy profile is advertised; +- authorities may publish a lower claim profile only if it was already + supported, explicitly authorized, and not a silent downgrade; +- consumers whose minimum accepted profile is stronger stop new work; +- the local PoC profile may continue only as functional test mode. + +Profile removal is not a generic emergency revocation. It requires a +preannounced `ProfileTransition`, or an authority-set transition when the PKI +authentication suite itself changes, unless the profile already has an +authenticated hard retirement epoch. + +## 24. Append-Only Transparency Log + +### 24.1 Purpose and limitation + +FOG-PKI maintains an append-only Merkle log so a long-offline Composer can +verify that a new accepted view extends its stored checkpoint with a proof +whose size grows logarithmically rather than linearly with missed epochs. + +The log detects inconsistent signed history when views are compared. It does +not by itself guarantee that every client sees the same view. Independent +retrieval, witnesses, and checkpoint gossip remain necessary. + +### 24.2 Epoch archive + +For every scheduled epoch E, authorities create one canonical `EpochArchive` +once quorum can finalize either the successful or no-consensus outcome: + +```text +[ + network_id, + epoch, + archive_status, + failure_reason_code, + consensus_hash, + authority_manifest_hash, + online_key_certificate_hashes, + authority_wire_key_certificate_hashes, + authority_protocol_object_hashes, + consensus_signature_hashes, + operator_record_hashes, + admission_decision_hashes, + node_descriptor_hashes, + revocation_hashes, + transition_hashes, + equivocation_evidence_hashes +] +``` + +All set-like lists are sorted and duplicate-free. The archive commits to the +public protocol record without embedding private admission evidence. + +The archive is a canonical `SignedObject` byte string but needs no individual +signature envelope. Its authenticity derives from inclusion in the Merkle tree +identified by an M-of-N signed checkpoint. + +For a successful epoch, `archive_status` is unsigned integer `0`, +`failure_reason_code` is zero, and `consensus_hash` identifies the threshold- +valid consensus. For an epoch without consensus, `archive_status` is unsigned +integer `1`, `consensus_hash` is the all-zero hash, and the registered failure +code records the failed phase without private diagnostics. + +`authority_protocol_object_hashes` commits to authority commits, reveals, and +proposals associated with the epoch. +`consensus_signature_hashes` commits to every published authority signature +in the finalized consensus envelope. A consumer requires the envelope's exact +sorted signature-record hash set to match the archive. + +### 24.3 Merkle construction + +Each canonical `EpochArchive` byte string is one log leaf in increasing epoch +order. There is exactly one leaf per scheduled epoch. Missing consensus is +represented by the canonical failure archive; leaf indices never shift or get +reused. If quorum loss temporarily prevents checkpointing a failure archive, +the gap MUST be filled before a later checkpoint can advance past it. + +The Merkle tree, leaf hashing, node hashing, inclusion proofs, and consistency +proofs follow the RFC 9162 append-only construction with FOG-specific domain +separation and the hash suite pinned by the trust manifest. + +The version-1 hashes are: + +```text +empty_hash = HASH(["FOG-PKI-LOG-EMPTY-1", log_id]) + +leaf_hash = HASH([ + "FOG-PKI-LOG-LEAF-1", + log_id, + epoch_archive_bytes +]) + +node_hash = HASH([ + "FOG-PKI-LOG-NODE-1", + log_id, + left_child_hash, + right_child_hash +]) +``` + +Tree splitting, inclusion paths, and consistency paths follow RFC 9162. The +FOG domains above replace the RFC leaf and node prefixes; they do not change +tree ordering or proof traversal. + +The log MUST NOT use commutative child hashing. Left and right position are +part of verification. + +### 24.4 Checkpoint + +A `LogCheckpoint` contains: + +```text +[ + network_id, + log_id, + authority_set_version, + tree_size, + root_hash, + first_epoch, + last_epoch, + checkpoint_time +] +``` + +It requires M independent online authority signatures. For a successful +consensus, its archive and checkpoint MUST be published before that +consensus's `valid_after`. If quorum cannot checkpoint it by then, consumers do +not use it for new work. Failure archives are checkpointed before the log can +advance to any later epoch. + +`first_epoch`, `last_epoch`, and `tree_size` MUST describe one contiguous +epoch sequence with exactly one leaf per scheduled epoch. + +### 24.5 Proofs + +Mirrors provide: + +- an inclusion proof that the target `EpochArchive` is in the new checkpoint; +- a consistency proof that the new checkpoint extends the consumer's stored + checkpoint; +- the target archive and consensus envelope; +- any intervening authority-set transitions and online vote-key certificates; +- authority wire-key certificates required by an online consumer's selected + FOG-WIRE contexts. + +Proofs are not trusted objects and need no signature. Their validity derives +from the old stored root and new threshold-signed checkpoint. + +### 24.6 Checkpoint comparison + +The following are evidence of failure or equivocation: + +- two threshold-signed checkpoints with the same `tree_size` and different + roots; +- a newer threshold-signed checkpoint for which no valid consistency proof + from an accepted older checkpoint exists; +- one epoch archive committing to a consensus hash different from the accepted + threshold consensus; +- a checkpoint omitting a promised finalized epoch beyond the publication + deadline. + +Consumers freeze ordinary PKI updates on verified checkpoint inconsistency. +They do not reset their stored tree size to make a proof pass. + +## 25. Offline Composer Update Bundle + +The blind relay or controlled import medium supplies one bounded +`PKIUpdateBundle` containing: + +- the target full consensus envelope; +- authority online-key certificates needed to verify it; +- current revocations and emergency revocations; +- every sequential authority-set transition since the stored manifest; +- the target epoch archive; +- a threshold-signed current log checkpoint; +- an inclusion proof for the target archive; +- a consistency proof from the Composer's stored checkpoint; +- independently fetched distinct checkpoints or witness statements where + available; +- a coarse untrusted retrieval timestamp for diagnostics only. + +The bundle is a transfer container, not a signed PKI object. Every contained +object is independently bounded and verified. Extra, duplicate, conflicting, +or unrelated objects make the bundle invalid. + +The Composer MUST: + +1. verify transitions sequentially from its stored manifest; +2. verify the new checkpoint quorum under the correct active authority set; +3. verify checkpoint consistency from its stored tree root; +4. verify inclusion of the target epoch archive; +5. verify the archive's consensus hash; +6. validate the consensus through Section 19; +7. compare all supplied distinct checkpoints and evidence; +8. atomically persist the entire new trusted state; +9. retain sufficient prior checkpoint and manifest metadata for recovery and + equivocation evidence. + +If the relay withholds newer data, it can cause freeze or denial of service. +It cannot make stale data satisfy hard expiry or monotonic checks. + +If the Composer lacks its previous checkpoint or manifest state, it MUST NOT +silently bootstrap from the relay. It requires the explicit recovery process. + +## 26. Equivocation Evidence + +An `EquivocationEvidence` object contains two or more complete independently +verifiable signed objects demonstrating one of: + +- one authority signs two different consensus hashes for one epoch; +- two threshold-valid consensus bodies exist for one epoch; +- one node signs different descriptors with the same sequence and predecessor; +- one operator signs conflicting records with the same sequence; +- an authority signs two different storage manifest hashes for one storage + epoch; +- one authority signs incompatible commits, reveals, or proposals; +- threshold-signed checkpoints conflict or fail append-only consistency; +- an authority-set transition conflicts at one manifest version. + +Evidence objects MUST contain no secret randomness beyond a reveal already due +for publication and no private governance evidence. + +Node or operator equivocation causes deterministic exclusion pending a +threshold decision. Authority or checkpoint equivocation freezes affected +consumers until a valid authority-set transition or explicit recovery +manifest resolves the trust state. + +A consumer MUST NOT locally rewrite the authority set merely because it has +evidence against one authority. That would create a client-specific trust +view. + +## 27. Distribution and Mirrors + +Authorities and mirrors publish immutable objects addressed by object hash. +The latest pointer is an untrusted convenience and MUST return the complete +bytes needed for verification. + +Publishers SHOULD support retrieval by: + +- exact consensus epoch and hash; +- object hash; +- authority-set manifest version; +- transparency checkpoint tree size; +- inclusion and consistency proof parameters; +- revocation sequence. + +HTTP, HTTPS, removable media, QR, FOG-SX-adjacent transfer tooling, or another +transport MAY carry public PKI objects. Transport security can improve +availability and privacy but does not create PKI authenticity. + +Relays SHOULD fetch from at least two independently operated authorities or +mirrors and preserve distinct valid responses. They MUST NOT merge them. A +Composer import bundle includes conflicts as evidence rather than hiding them. + +Mirrors MUST NOT receive authority private keys, admission credentials, user +identities, message traffic, or special consensus signing privilege. + +## 28. Failure Behavior + +| Condition | Required behavior | +| --- | --- | +| Fewer than M valid consensus signatures | reject | +| Signature from wrong authority set | reject | +| Any included malformed or invalid signature | reject envelope | +| Same authority listed twice | reject envelope | +| Non-canonical encoding | reject before signature acceptance | +| Unknown-critical value | reject | +| Wrong network ID | reject | +| Same epoch, different hash | freeze and retain evidence | +| Lower epoch or manifest version | reject as rollback | +| Broken descriptor chain | exclude descriptor; proposal must agree | +| Missing required future key | exclude node | +| Invalid topology or insufficient diversity | no consensus for that profile | +| Commit/reveal quorum failure | no consensus | +| Proposal body mismatch | do not sign | +| Consensus past `fresh_until` | report stale; continue only to hard expiry | +| Consensus past `valid_until` | stop new work | +| Checkpoint consistency failure | freeze ordinary update | +| Authority transition lacks dual quorum | reject transition | +| Lost local monotonic state | require explicit recovery | +| Clock uncertainty exceeds bound | stop time-sensitive acceptance | + +Remote protocol errors MUST be coarse and non-amplifying. Local diagnostics +MAY identify deterministic validation stages but MUST NOT log private keys, +private admission material, unpublished random reveals, or credentials. + +## 29. Key Lifecycle Table + +| Key or secret | Generator | Authorized use | Lifetime and overlap | Compromise response | Backup | +| --- | --- | --- | --- | --- | --- | +| Network ID | genesis ceremony | domain and network separation | permanent | new network genesis | public, widely copied | +| Authority root private key | offline authority ceremony | online-key certificates and set transitions | long-term, one controlled transition overlap | root transition or explicit recovery | encrypted, separate recovery secret | +| Authority online vote private key | authority root ceremony or controlled online generation | commits, reveals, proposals, consensus, checkpoints | at most 32 epochs, at most 2-epoch overlap | threshold revoke certificate and rotate | SHOULD NOT be restored into concurrent signer | +| Authority wire private key | authority wire service OS CSPRNG or controlled ceremony | mutually authenticated `FOG-WIRE` authority and descriptor links | root-certified for at most 32 epochs, at most 2-epoch overlap | revoke certificate, stop new sessions, and rotate | SHOULD NOT be restored into concurrent service | +| Authority randomness secret | online authority CSPRNG | one epoch commit/reveal | one protocol run; erase after reveal and audit window | exclude invalid run; investigate RNG | no backup | +| Operator identity private key | operator ceremony | operator records and descriptor co-signing | long-term with admitted replacement | suspend operator, replace through admission | encrypted operator-controlled backup | +| Node identity private key | node enrollment | descriptor continuity | long-term for one node role | self-revoke and new admission | encrypted role-local backup if policy permits | +| Node epoch private key | owning node CSPRNG | one declared role purpose | current plus bounded grace; future key staged | revoke purpose or node; generate replacement | no routine backup | +| Replica receipt private key | one storage replica CSPRNG | authenticate one manifest-bounded local durable-result receipt | storage-manifest current plus bounded verification drain | stop receipts, revoke node or purpose, rotate through new manifest | no routine backup | +| Transparency checkpoint state | every authority | append-only root computation and sign-once tracking | permanent monotonic public history | freeze on inconsistency; recover from audited replicas | authenticated independent authority backups | +| Consumer monotonic state | each consumer | rollback and consistency detection | lifetime of installation or identity | explicit authenticated recovery | authenticated backup bound to consumer profile | + +The networkless Composer refines the final row through the local commitment, +external-anchor, import, recovery, and update-consumer rules in +`FOG-COMPOSER.md`. A self-contained consumer backup does not provide complete +rollback detection. + +No private key or randomness secret may appear in consensus, transparency +logs, test vectors, examples, command lines, environment templates, container +images, metrics, crash dumps, or support bundles. Tests generate ephemeral +fixtures at runtime. + +## 30. Privacy and Operational Data + +Public PKI necessarily reveals: + +- node and authority public identities; +- endpoints and supported transports; +- effective roles and mix layers; +- declared operator, family, provider, ASN, country, and infrastructure groups; +- public key schedules, profiles, validity, admission, suspension, and + revocation state; +- consensus timing and topology changes. + +This enables targeting and operational correlation. FOG accepts that exposure +because clients need a common auditable view and route-diversity inputs. + +FOG-PKI MUST NOT publish: + +- user or contact identities; +- mailbox capabilities or packet identifiers; +- node traffic counts or fine-grained health events; +- operator legal identity unless voluntarily required by a separate public + governance policy; +- private email, phone, billing, account, or management data; +- exact physical addresses; +- unpublished vulnerability or forensic details; +- authority or node private-key storage design beyond the public assurance + profile. + +Descriptor uploads, votes, and public objects SHOULD use fixed schedule +windows and bounded request behavior. Operational metrics belong to the +aggregate observer interface, not node descriptors. + +## 31. Denial-of-Service and Resource Controls + +Authorities MUST: + +- accept descriptor uploads only for admitted identities; +- authenticate before expensive signature or policy work where possible; +- accept at most one selected descriptor chain head per node and epoch; +- bound connections, body size, signatures, records, keys, endpoints, and + pending votes; +- rate-limit invalid uploads without creating different consensus views; +- persist protocol phase and sign-once state atomically; +- use bounded backoff for peer authority exchange; +- avoid error responses larger than the triggering request; +- keep publication serving separate from sensitive signing state; +- remain able to publish existing immutable objects during voting overload. + +A malicious admitted node can consume review and descriptor resources. The +permissioned registry, bounds, expiry, suspension, and operator-wide actions +limit but do not eliminate this risk. + +Authority quorum and transparency do not provide availability against a +sustained distributed attack or coordinated withholding. + +## 32. Conformance and Adversarial Tests + +Before the local PoC, FOG-PKI MUST have deterministic fixtures for: + +- every object type and exact canonical encoding; +- re-encoding equality and rejection of alternative CBOR encodings; +- every registered identifier and domain-separated hash; +- valid M-of-N consensus for 2-of-3 and 3-of-5 sets; +- duplicate, unknown, expired, revoked, and wrong-set signatures; +- composite signature all-component validation; +- authority online-key certification and overlap boundaries; +- authority wire-key certification, overlap, profile binding, and rejection of vote-key reuse; +- descriptor sequence, predecessor, node, and operator signatures; +- role-specific key-purpose acceptance and cross-purpose rejection; +- admission, denial, suspension, reinstatement, retirement, and self-revocation; +- commit, reveal, missing reveal, invalid reveal, and insufficient quorum; +- byte-identical topology generation from seed and inputs; +- operator, family, provider, ASN, country, and infrastructure constraints; +- same-epoch authority and node equivocation; +- storage-manifest canonical encoding, sign-once persistence, quorum, + chaining, previous/current/next windows, receipt keys, and no cross-manifest + replica merging; +- stale, expired, future, rolled-back, frozen, and split consensus; +- authority sign-once persistence across crash, restore, and failover; +- ordinary and emergency revocation; +- dual-quorum authority-set transition; +- loss of old-root quorum and explicit recovery refusal; +- RFC 9162 inclusion and consistency proofs, including edge tree sizes; +- conflicting checkpoints and missing archive leaves; +- long-offline Composer update from old checkpoint; +- interrupted atomic state update and recovery; +- maximum document, array, string, key, signature, and proof limits; +- parser mutation, fuzz, differential, and allocation tests; +- clock skew and uncertainty at every boundary; +- consensus publication and mirror corruption scenarios. + +Independent implementations MUST consume the same conformance corpus before +interoperability is claimed. + +## 33. Claim Gates + +### 33.1 Functional PoC + +The PoC may use simulated authorities but MUST exercise: + +- independent signature records; +- canonical consensus construction; +- descriptor validation; +- three-layer topology assignment; +- current and next node keys; +- rollback state; +- revocation; +- sign-once behavior; +- transparency inclusion and consistency proofs; +- expired-consensus fail-closed behavior. + +`FOG-LOCAL-POC.md` keeps the authority fixture networkless and explicitly +non-claim-bearing. Its fault plan requires insufficient-quorum, same-epoch +fork, stale-consensus, rollback-state, and clock-uncertainty cases before a +runnable fixture can pass. + +### 33.2 Operator alpha + +Alpha requires: + +- three independently administered authority roots and online services; +- a 2-of-3 quorum; +- independent consensus mirrors; +- at least two independent checkpoint monitors or witnesses; +- rehearsed online-key compromise, node revocation, authority replacement, + quorum loss, clock failure, and restore exercises; +- public admission, family, revocation, and residual-risk policy; +- confirmation that no shared provider account or management credential + controls the authority quorum. + +### 33.3 Production PKI claims + +Production claims additionally require: + +- independent protocol and implementation review; +- reviewed concrete classical and post-quantum policy; +- independently reproduced consensus and transparency roots; +- published conformance results and unresolved findings; +- demonstrated long-offline update and recovery ceremonies; +- ongoing operator-family and common-control auditing; +- explicit documentation that a compromised authority threshold can still + authorize a malicious network view. + +## 34. Requirements Traceability + +| Requirement | FOG-PKI control | +| --- | --- | +| `TM-NET-06` | full canonical consensus, fixed profile IDs, no partial-view merge | +| `TM-PKI-01` | permissioned admission, operator records, family and infrastructure constraints | +| `TM-PKI-02` | monotonic epoch and hash state, hard expiry, Merkle inclusion and consistency | +| `TM-PKI-03` | independent M-of-N authorities, offline roots, bounded online keys, deterministic quorum failure | +| `TM-OPS-01` | minimal public fields, no user data or private governance evidence | +| `TM-SUPPLY-01` | pinned genesis, certified online keys, canonical objects, authenticated transitions | +| `TM-CRYPTO-01` | purpose-specific key hierarchy, bounded overlap, explicit compromise response | +| `TM-CRYPTO-02` | already-trusted authentication suite, composite all-component validation, no downgrade | +| `TM-AVAIL-01` | permissioned uploads, strict limits, non-amplification, explicit no-consensus state | +| `ARC-005` | Composer-validated complete authenticated network view | +| `ARC-007` | one owner and purpose for root, vote, operator, node, and epoch keys | +| `ARC-008` | restricted CBOR, exact schema, absolute limits, unknown-critical rejection | +| `ARC-009` | stale and hard-expiry states, no locally synthesized fallback consensus | +| `IF-03` | authenticated bounded node descriptor upload | +| `IF-04` | signed commit, reveal, proposal, signature, and checkpoint exchange | +| `IF-05` | immutable hash-addressed full consensus and offline update bundle | + +## 35. Open Pre-Implementation Selections + +The protocol structure is fixed, but these selections block implementation: + +- exact encoding, vectors, implementation, artifact-separability analysis, + side-channel evidence, and activation record for the non-active SHA3-256 + and ML-DSA-65 plus Ed25519 candidates; +- exact epoch duration, schedule offsets, freshness, hard expiry, and clock + uncertainty; +- exact deterministic topology shuffle and diversity constraint algorithm; +- initial wire, KEMSphinx, entry, storage, cover, and application profile IDs; +- authority wire-key certificate issuance, storage, revocation, and rollover + profile; +- initial lower operational document and node-count limits; +- hardware and ceremony profile for authority roots; +- monitor and witness operators for alpha; +- explicit `RecoveryManifest` ceremony and user interface; +- public governance reason-code registry and private evidence retention policy. + +These values MUST be selected through reviewed profiles and conformance +vectors. Implementations MUST NOT invent local defaults. + +## 36. References + +- FOG threat model: `FOG-THREAT-MODEL.md` +- FOG architecture: `FOG-ARCHITECTURE.md` +- FOG wire protocol: `FOG-WIRE.md` +- FOG storage protocol: `FOG-STORAGE.md` +- FOG Composer protocol: `FOG-COMPOSER.md` +- FOG cryptographic suite evaluation: `FOG-CRYPTO-SUITES.md` +- FOG cryptographic benchmark baseline: `FOG-CRYPTO-BENCHMARKS.md` +- FOG local Podman PoC: `FOG-LOCAL-POC.md` +- NIST FIPS 204, Module-Lattice-Based Digital Signature Standard: + +- RFC 9955, Hybrid Signature Spectrums: + +- RFC 9980, Post-Quantum Cryptography in OpenPGP: + +- Katzenpost public key infrastructure specification: + +- Katzenpost mix network specification: + +- Tor directory authority and consensus specifications: + +- Tor shared-random protocol: + +- RFC 8949, Concise Binary Object Representation: + +- RFC 9162, Certificate Transparency Version 2.0: + +- The Update Framework specification: + + +These references inform quorum operation, offline and online key separation, +deterministic encoding, append-only consistency proofs, root transitions, +rollback handling, and freeze detection. They do not make FOG-PKI secure by +inheritance. FOG requires its own concrete suites, test vectors, +implementations, operations, and independent review. diff --git a/docs/FOG-POC-PRESERVATION.md b/docs/FOG-POC-PRESERVATION.md new file mode 100644 index 0000000..6c045ee --- /dev/null +++ b/docs/FOG-POC-PRESERVATION.md @@ -0,0 +1,152 @@ +# FOG Proof-of-Concept Preservation + +Status: Required preservation contract + +Date: 2026-08-18 + +## 1. Objective + +The completed local alpha laboratory will be preserved as a reproducible FOG +proof of concept under the Gab Virebent / Virebent identity. It must remain +possible to inspect the topology, rebuild the software, start the isolated +roles, run the demonstration and fault scenarios, verify the results, and +remove the runtime resources without relying on undocumented local state. + +The preserved PoC is functional and educational evidence. It is not an +anonymity, production, post-quantum, durability, or independent-operator claim. + +## 2. Canonical PoC Contents + +The source tree must preserve: + +- exact source code and pinned Go module dependencies; +- the pinned compiler and build-tool version requirements; +- Containerfile and container build context; +- strict machine-readable topology and fault-scenario definitions; +- deterministic manifest and configuration generators; +- test vectors and synthetic non-secret fixtures; +- commands to build, start, demonstrate, verify, stop, and clean up; +- tests for role isolation, permitted and forbidden network edges, alternate + mix paths, authority quorum, node loss, storage loss, and restoration; +- a concise demonstration script or command that emits only coarse, + non-sensitive outcomes; +- a sanitized result manifest containing source revision, tool versions, + configuration digests, image digest, test outcome, and artifact hashes; +- dependency and license inventory, plus an SBOM when the selected build tools + can produce it reproducibly. + +Generated manifests may be retained when they make review easier, but their +source definition and deterministic generator remain authoritative. + +## 3. What Must Not Be Preserved + +The PoC repository, release bundle, image, demonstration output, and supporting +documentation must not contain: + +- private keys, passwords, tokens, credentials, recovery material, or real + operator secrets; +- runtime secret volumes, queue state, replay databases, storage contents, or + Composer state; +- real user messages, contacts, capabilities, identifiers, or traffic traces; +- host login instructions, private addresses, account identifiers, or private + operator inventories; +- container caches, mutable volumes, core dumps, debug logs, or temporary + build directories; +- personal metadata that would link the Virebent identity to unrelated + identities or private accounts. + +Every demonstration run generates ephemeral role-local fixture secrets from +the operating-system CSPRNG and destroys the disposable runtime resources at +cleanup. The saved environment contains only public deterministic inputs and +sanitized aggregate results. + +## 4. Preservation Levels + +### 4.1 Source preservation + +Source, specifications, generators, lock files, tests, and reproduction +instructions are the primary long-term artifact. They belong in the future +FOG repository and a frozen version tag after the Virebent remote and human +Git author identity are confirmed. + +### 4.2 Reproducible build record + +The milestone records exact toolchain versions, dependency hashes, build +arguments, target platform, source revision, binary hashes, and OCI manifest +digest. A future rebuild must be compared against this record. Differences are +reported, never silently accepted. + +### 4.3 Optional executable archive + +At the frozen PoC milestone, one compressed OCI image archive may be attached +to the release or stored in a Virebent-controlled artifact location. It must +not be committed to the source repository. The archive is optional because it +costs storage; the reproducible source is mandatory. Its SHA-256, media type, +platform, size, and creation command are recorded next to the release. + +The archive is created once for the demonstrated platform, not once per +container or node. All local roles reuse the same digest-pinned multi-binary +image. + +## 5. Demonstration Contract + +The preserved PoC must expose one documented, non-interactive top-level +workflow with these phases: + +```text +preflight -> build once -> create disposable secrets -> start roles + -> verify isolation -> run baseline -> run selected faults + -> emit sanitized summary -> destroy disposable resources +``` + +The workflow must fail closed on missing tools, incompatible versions, +unexpected existing resources, invalid topology, image-digest mismatch, +failed containment, incomplete cleanup, or failed tests. It must not download +or execute an unpinned artifact implicitly. + +The default demonstration should finish on one ordinary development host with +bounded CPU, memory, disk, process, and time budgets. A short baseline mode is +required. Longer adversarial and benchmark modes remain explicit opt-ins. + +## 6. Evidence and Public Presentation + +The preserved result states: + +- what behavior was demonstrated; +- exact host and software assumptions; +- which checks passed, failed, or were skipped; +- why co-located containers are not independent operators; +- why the result is not anonymity or production evidence; +- which protocol components are fixtures rather than active FOG profiles. + +Screenshots or video may supplement the machine-readable result but are not +canonical evidence. Before publication they must be checked for usernames, +hostnames, paths, terminal history, notifications, embedded metadata, and +identity leakage. + +## 7. Acceptance Gate + +The PoC preservation task is complete only when a clean environment can follow +the documented workflow using the frozen source, reproduce the expected +sanitized result, and leave no runtime container, network, volume, secret, or +temporary file behind. The final preservation audit must also confirm that no +secret or private identity data entered source or release artifacts. + +The definition gate is preserved in `deploy/alpha/lab-topology.json`, +`deploy/alpha/lab-faults.json`, and the dependency-free `internal/lab` +validator. Its summary is now `runnable: true`: compatible fixtures, +deterministic Compose generation, all eight baseline routes, 102 containment +and exact-network checks, all authority single and quorum losses, all six +single-mix failures, sanitized evidence, and complete disposable-resource +cleanup were demonstrated by one bounded command on 2026-08-18. It is not yet +an accepted preserved PoC. A canonical manifest now records dependency, +external-tool license, source, binary, evidence, and toolchain data with a +domain-separated artifact-set digest. The project license remains +`NOASSERTION`, and repository revision metadata plus independent clean-host +reproduction remain open. + +The same-host clean-filesystem rehearsal now reproduces byte-identical fixture +binaries, complete result, fixed-timestamp OCI image, Compose digest, and +canonical artifact-set digest. It validates the local ceremony but does not +satisfy the independent rebuild requirement because the host and installed +toolchain are shared. diff --git a/docs/FOG-SECURITY-TEST-PLAN.md b/docs/FOG-SECURITY-TEST-PLAN.md new file mode 100644 index 0000000..59188b4 --- /dev/null +++ b/docs/FOG-SECURITY-TEST-PLAN.md @@ -0,0 +1,120 @@ +# FOG Security Test Plan + +Status: Structural test plan + +Date: 2026-08-18 + +## 1. Purpose and Claim Boundary + +This plan defines the security test families required before an operator +alpha. It supplies test contracts, not passing protocol evidence. Existing +non-cryptographic fixture results may validate the harness but cannot close a +protocol-functional gate. + +Every retained result must identify the software revision, active profile +digests, test configuration, host class, seed or corpus digest where relevant, +start and end dates, pass criteria, observed failures, and artifact SHA-256. +Private keys, capabilities, packet identifiers, user data, and fine-grained +production traffic do not belong in test reports. + +## 2. Test Levels + +| Level | Target | Minimum evidence | +| --- | --- | --- | +| Unit | pure codecs, state transitions, bounds, profile registry | deterministic tests and mutation cases | +| Conformance | canonical bytes and public protocol behavior | vectors consumed by two independent implementations | +| Integration | adjacent roles and complete route | fixed-profile success and fail-closed cases | +| Fuzz | every untrusted parser and stateful sequence | retained corpus, crash triage, allocation ceilings | +| Race | daemon concurrency and persistent state | race detector plus restart and contention cases | +| Fault | process, network, disk, clock, and key failures | deterministic scenario result and restoration check | +| Load | authenticated and unauthenticated saturation | CPU, memory, queue, latency, and amplification bounds | +| Side channel | valid and invalid cryptographic paths | reviewed measurement method and statistical result | +| Simulation | traffic, topology, compromise, suppression | scenario corpus, sensitivity analysis, confidence bounds | + +## 3. Mandatory Adversarial Matrix + +| Family | Required scenarios | Required invariant | +| --- | --- | --- | +| Replay | duplicate packet, restart, replay-state loss, epoch overlap, reply reuse | no repeated semantic effect; unsafe replay state stops processing | +| Tagging | mutate every authenticated field, packet truncation and extension, SURB mutation | uniform rejection before service effect | +| n-1 | suppress background traffic, inject hostile traffic, isolate a target, stop cover process | measured isolation signal and explicit degraded or stopped state | +| Flooding | pre-auth connections, malformed handshakes, fragments, queue pressure, storage fan-out | bounded CPU, memory, connections, disk, and response amplification | +| Clock | forward and backward jump, excessive uncertainty, cross-authority disagreement | no fresh state outside validity; coarse fault only | +| Consensus | stale, rollback, freeze, same-epoch fork, malformed mirror, hard expiry | no merge, downgrade, local synthesis, or trust-on-first-use recovery | +| Authority | one offline, quorum loss, sign-once restart, online-key compromise, replacement | deterministic liveness loss without invalid consensus | +| Node loss | entry, each mix layer, courier, reconnect storm, replay database corruption | no layer skip, direct fallback, or undeclared route | +| Storage loss | one replica, quorum loss, stale replica, lost receipt, tombstone conflict | no fabricated durability or resurrection | +| Network | fragmentation, reordering at application boundaries, partition, asymmetric loss | strict ordered wire state and bounded randomized recovery | +| State | crash before and after atomic commit, restore old state, disk full, partial write | commit-before-effect and fail-closed recovery | +| Supply chain | altered binary, dependency change, revoked release, rollback | verification failure or explicit stop, never silent acceptance | + +## 4. Parser and Fuzz Targets + +Every versioned object parser must have a dedicated target for empty, +truncated, oversized, non-canonical, duplicate, unknown-critical, trailing, +deeply nested, maximum-count, and cross-version input. Stateful fuzzers must +cover handshake, fragmentation, replay, consensus transition, ratchet, +capability stream, receipt, tombstone, Composer import, recovery, update, and +FOG-SX reconstruction sequences. + +Fuzz harnesses must enforce the protocol's allocation and work limits. A +result is incomplete when the fuzzer merely avoids panics but permits +unbounded memory, CPU, disk, file descriptors, goroutines, or cryptographic +operations. + +## 5. Local Alpha Laboratory Matrix + +The local laboratory uses three authority fixtures, two mixes per layer, one +entry, one courier, and four stores. It must exercise both mix choices in +every layer and cover at least: + +1. all eight combinations of one mix per layer; +2. loss of either mix in each layer while an allowed alternate remains; +3. loss of both mixes in a layer with no bypass; +4. one authority offline with quorum preserved; +5. two authorities offline with no new consensus; +6. loss of one store and loss of the required receipt quorum; +7. partitions on every permitted adjacency and probes on every forbidden edge; +8. restoration that proves stale replay, consensus, and storage state is not + silently reused. + +These scenarios validate orchestration and protocol behavior only. They do +not close operator-independence or anonymity gates. + +## 6. Pass and Failure Handling + +A test family passes only when all declared cases meet byte-exact or bounded +numeric criteria and every failure has been triaged. Flaky, skipped, timed-out, +or infrastructure-invalid cases are not passes. Expected failure tests must +assert the exact permitted coarse outcome and the absence of forbidden side +effects. + +Security regressions retain the smallest non-secret reproducer. Reports use +aggregate timing and resource data and must not introduce packet-level +production telemetry. High-severity unresolved findings keep the affected +readiness gate open. + +## 7. Required Commands by Go Module + +Each production or conformance Go module must pass: + +```sh +gofmt -d . +go test ./... +go test -race ./... +go vet ./... +``` + +Parser modules additionally run their registered fuzz targets under a +documented time and resource budget. Cryptographic modules run dependency +verification, complete-operation benchmarks, invalid-input timing tests, and +independent vector comparison. Container laboratories validate the effective +runtime state, not only source manifests. + +## 8. Gate Mapping + +The retained results map directly to `deploy/alpha/readiness.json`. Partial +fixture evidence remains described in the gate note while status stays +`open`. A gate changes to `pass` only after its complete scope has immutable +evidence. The readiness validator does not run tests or trust filenames; it +checks the declared status and evidence shape so review remains explicit. diff --git a/docs/FOG-SIMULATION.md b/docs/FOG-SIMULATION.md new file mode 100644 index 0000000..00fde5e --- /dev/null +++ b/docs/FOG-SIMULATION.md @@ -0,0 +1,321 @@ +# FOG Traffic and Topology Simulation + +Status: Engineering Baseline 0.1 + +Date: 2026-08-08 + +## 1. Purpose + +This document defines the first reproducible simulation boundary for FOG and +records its initial scenario matrix. It supplies evidence for `TM-NET-01`, +`TM-NET-02`, `TM-NET-03`, `TM-ROLE-02`, and `TM-AVAIL-01` without promoting a +delay, cover, topology, entry, or degraded-mode profile. + +The implementation is in `../sim/`. The first complete comparison artifact is +`../sim/results/2026-08-08-baseline.md`. + +The simulator is not a daemon, protocol implementation, packet generator, +deployment controller, capacity benchmark, or anonymity proof. + +## 2. Evidence Boundary + +The first model answers narrow comparative questions: + +- how mean mix delay changes modeled end-to-end latency and local pool overlap; +- how real and cover traffic density changes bandwidth and observation proxies; +- how one, two, or three nodes per layer change route diversity and traffic + concentration; +- how independently compromised entry and mix nodes affect repeated target + routes; +- how selective background suppression affects delivery and isolation; +- how repeated observations shrink a simplified candidate-recipient set. + +It does not answer: + +- whether FOG is anonymous against a global passive or active observer; +- which numeric cover or delay profile is safe; +- whether a local pool metric composes into end-to-end entropy; +- whether a timing window matches an optimal correlation attack; +- whether the storage, retrieval, reply, retry, and acknowledgment processes + are indistinguishable; +- whether loop statistics reliably detect n-1 or ordinary failure; +- how queue capacity, congestion, network jitter, churn, or operator behavior + affect deployment. + +Every output is tied to the exact model revision, configuration, seed, and +threat assumptions. A favorable proxy result is never promoted into a public +privacy claim. + +## 3. Model Contract + +### 3.1 Reproducibility + +`FOG-SIM-DISCRETE-EVENT-1` uses a pinned simulation-only SplitMix64 sequence. +The same valid configuration and seed produce the same JSON report. The PRNG +is isolated inside the simulation package and MUST NOT be imported by future +protocol, cryptographic, route-construction, or daemon code. + +Scenario parsing rejects unknown fields, non-regular files, files over 1 MiB, +non-finite values, invalid probability ranges, invalid topology, and expected +work above one million packets per replication or five million packets per +scenario invocation. Runtime generation also aborts if Poisson sampling +actually reaches either limit, so the validation estimate is not the only +resource bound. + +### 3.2 Traffic + +Each simulated user has independent Poisson real and cover injection +processes. User 0 sends every real target message to user 1 so repeated +relationship exposure can be measured. Other real and cover recipients are +uniform among users other than the sender. + +This uniform social graph is deliberately simple. It avoids hiding the model +behind inferred behavioral data, but it is not realistic enough for a claim. +Future trace-driven and non-uniform models must remain separately named. + +### 3.3 Topology and routes + +The model contains: + +- one configured entry set; +- exactly three stratified mix layers; +- one to three configured mixes per layer in the initial scenarios; +- four KEMSphinx positions, with the terminal outside the three delay pools; +- five core link transmissions for a fully delivered forward packet; +- one 16,150-byte packet on every link. + +Nodes receive deterministic operator-family assignments. The route generator +enumerates only entry, L1, L2, and L3 combinations with four distinct declared +operator families. A session uses a small entry set for a configured number of +packets before rotation. It does not rotate entry on every message. + +The model does not yet include terminal operator diversity, provider, ASN, +country, infrastructure-group, capacity, or topology-generation constraints. +It therefore validates only the narrow declared-family rule. + +### 3.4 Delay and fixed costs + +Each sender samples one delay for each of the three mix layers. The initial +matrix uses independent exponential samples, consistent with the evaluated +Poisson-mix literature and maintained Katzenpost design. A constant-delay mode +exists only for sensitivity tests and does not define a candidate profile. + +Modeled forward latency includes five configured link delays, four configured +KEMSphinx processing costs, and three sampled mix delays. It excludes entry +capsule processing, queues, wire handshakes, reassembly, storage, replies, +imports, retries, and user offline time. + +### 3.5 Compromise and active suppression + +Each entry and mix is independently marked compromised once per replication +using configured probabilities. Target route metrics record: + +- at least one compromised mix; +- all three mixes compromised; +- entry compromised; +- entry and all three mixes compromised; +- whether repeated messages eventually use a fully compromised mix route. + +The active sensitivity scenario allows a compromised mix to suppress each +non-target packet independently while always forwarding target packets. This +is a conservative isolation mechanism, not a complete strategic n-1 attacker. +It does not infer target packets from encrypted content or model loop-based +detection and response. + +## 4. Metrics + +### 4.1 Latency and bandwidth + +The report publishes nearest-rank p50, p95, p99, minimum, maximum, and mean +real-message latency. Core bandwidth counts the actual number of traversed +links, so suppression reduces downstream byte cost while increasing loss. + +### 4.2 Local pool size and entropy + +At each target departure from an honest mix, the simulator counts packets +resident in that mix delay pool. Under the memoryless Poisson-mix assumption, +the local equal-likelihood entropy proxy is: + +```text +H_local = log2(resident_packet_count) +``` + +It also records whether every honest mix on a target route had pool size one. +This is local opportunity for confusion, not the end-to-end posterior entropy +of a global observer. + +### 4.3 Timing candidate proxy + +For each target egress, the simulator counts all delivered packets inside a +configured centered timing window. A count of one is reported as a unique +timing candidate. + +The metric ignores route likelihood, delay likelihood, ingress history, +multiple links, user schedules, and machine-learned correlation. It is useful +only for comparing scenario density under identical rules. + +### 4.4 Long-term disclosure proxy + +For each target send, the simulator observes the recipients of all delivered +packets in the following configured interval. It intersects these recipient +sets across repeated sends and records whether the true fixed recipient is +still present or uniquely identified. + +This deliberately transparent construction demonstrates intersection risk. It +is not the formal statistical disclosure attack, a probabilistic posterior, +or the third-party unlinkability metric from the research literature. + +## 5. First Scenario Matrix + +The initial checked-in configurations compare: + +1. a 30-user, one-route functional PoC without cover; +2. a 100-user, two-mix-per-layer alpha with one cover packet per user per hour; +3. the same alpha with one cover packet every two minutes per user; +4. the same covered alpha with mean mix delay raised from 500 ms to 5 seconds; +5. a 1,000-user, three-mix-per-layer sparse topology; +6. a sparse alpha with 50 percent independent entry and mix compromise input; +7. a covered alpha with the same compromise input and 95 percent selective + background suppression. + +These values are intentionally separated sensitivity points. They are not +recommended operational defaults. + +## 6. Initial Results + +### 6.1 Sparse use remains exposed + +The no-cover PoC had pool size one at every target departure, a unique +ten-second timing candidate in 99.2 percent of target messages, and unique +recipient intersection by five observations in every replication. + +The sparse alpha used about 8.47 MiB of five-link core traffic per simulated +hour and a cover-to-real ratio near 9.9. Its local pool p50 remained one, +96.8 percent of target routes were isolated at every honest mix, and the +recipient intersection median fell from 63 candidates after one observation +to two after ten. + +These scenarios support only the existing conclusion that a small or idle +network cannot acquire anonymity through packet cryptography alone. + +### 6.2 Cover and delay protect different proxies + +One cover packet every two minutes per user raised five-link core traffic to +about 231.74 MiB/hour and the cover-to-real ratio to about 294. It removed +unique ten-second timing candidates and kept all 99 possible recipients in the +one-hour intersection proxy through ten observations. + +With mean mix delay still 500 ms, however, local pool p50 remained one and +64.9 percent of target routes were isolated at every honest mix. Raising mean +delay to five seconds at the same traffic level raised local pool p50 to three, +local entropy p50 to about 1.585 bits, and removed fully isolated target routes +in the 37 target samples. Modeled latency p50 increased from about 1.44 seconds +to 13.29 seconds. + +Cover volume, mixing overlap, observation windows, and latency are therefore +different dimensions. A strong result in one proxy does not substitute for +the others. + +### 6.3 More users do not automatically solve dilution + +The 1,000-user scenario improved the timing-candidate p50 to four and retained +a median 14 recipient candidates after ten observations. It also spread +traffic across three nodes per layer. Local pool p50 remained one and 84.2 +percent of target routes were isolated at every honest mix. + +Scaling topology and scaling traffic must be evaluated together. Adding nodes +without enough traffic per node can dilute mixing opportunity. + +### 6.4 Repeated compromise exposure is non-zero + +With independent 50 percent compromise inputs in the two-node-per-layer +alpha, 5.5 percent of sampled target routes used compromised nodes in all +three layers. Three of sixteen replications encountered at least one such +route. Entry and all three mixes were compromised together on 1.1 percent of +target routes. + +These sampled values are dependent on entry reuse, topology sampling, route +reuse, and small target counts. They are not analytical probabilities. They +do show why repeated routing and long-lived observation must be measured, and +why declared operator diversity does not remove concealed control risk. + +### 6.5 Selective suppression destroys the traffic condition + +The n-1 sensitivity scenario delivered only 7.9 percent of all emitted +packets. Its honest local pool p50 returned to one, 89.5 percent of target +routes with an honest mix were fully isolated, and half the replications saw a +fully compromised target route. + +This model does not yet determine a shutdown threshold. It confirms that cover +generation without loop health, anomaly evidence, bounded degraded behavior, +and active-attack analysis is insufficient. + +## 7. Decisions From This Baseline + +The baseline establishes only the following stable project decisions: + +- retain a deterministic, independent discrete-event simulator before daemon + implementation; +- keep packet geometry fixed and route-family validation enabled in every + scenario; +- reject the functional PoC as anonymity evidence; +- do not choose cover or delay parameters from intuition or a single metric; +- measure traffic density per mix, not user count or node count alone; +- keep active suppression and passive compromise as separate scenarios; +- withhold degraded-mode thresholds until loop, queue, loss, and operational + models exist. + +No numeric cover, delay, topology, entry-rotation, polling, or shutdown profile +is selected. + +## 8. Required Extensions + +Before the local PoC parameter set is frozen, add: + +- end-to-end posterior and Shannon-entropy propagation for a declared + observer; +- formal third-party unlinkability and statistical-disclosure experiments; +- user availability, non-uniform social graphs, bursts, retries, and traces; +- forward and reply paths, SURBs, acknowledgments, and rendezvous behavior; +- storage reads, empty polling, writes, receipts, and capability rotation; +- loop traffic, loss attribution, n-1 detection, false positives, and + degraded-mode state machines; +- queue service capacity, bandwidth ceilings, congestion, jitter, drops, and + flood distributions; +- topology churn, epochs, provider and ASN correlations, hidden operator + families, and compromised-position sweeps; +- confidence intervals and larger independent replication sets; +- calibration against PoC traces without collecting privacy-unsafe event logs. + +## 9. Reproduction and Checks + +From `sim/`: + +```sh +gofmt -d cmd/fog-sim/*.go internal/sim/*.go +go test ./... +go test -race ./... +go vet ./... +go run ./cmd/fog-sim -scenario-dir scenarios +``` + +The CLI emits JSON only to standard output. Checked-in Markdown results are a +review-oriented comparison; the deterministic JSON is the complete report. + +## 10. Primary References + +- Ania M. Piotrowska, *Studying the anonymity trilemma with a discrete-event + mix network simulator*: + +- Ania M. Piotrowska et al., *The Loopix Anonymity System*: + +- George Danezis, *Designing and attacking anonymous communication systems*: + +- Katzenpost mix network specification: + +- Katzenpost mix decoy loop specification: + +- FOG threat model: `FOG-THREAT-MODEL.md` +- FOG architecture: `FOG-ARCHITECTURE.md` +- FOG wire protocol: `FOG-WIRE.md` +- FOG KEMSphinx profiles: `FOG-SPHINX-PROFILES.md` diff --git a/docs/FOG-SPHINX-PROFILES.md b/docs/FOG-SPHINX-PROFILES.md new file mode 100644 index 0000000..f1d32de --- /dev/null +++ b/docs/FOG-SPHINX-PROFILES.md @@ -0,0 +1,1188 @@ +# FOG Sphinx Profiles + +Status: Draft 0.1 + +Date: 2026-08-08 + +## 1. Purpose + +This document defines `FOG-SPHINX-PROFILES`, the authenticated profile +framework for fixed-size KEMSphinx packets used by FOG. + +It fixes the structural packet contract, route shape, routing commands, SURB +rules, replay behavior, wire ownership, SDK boundary, transition behavior, +and conformance evidence required before a concrete packet suite can be +activated. + +It also records one calculated but non-active candidate geometry named +`FOG-SPHINX-CANDIDATE-MLKEM768-X25519-1`. The candidate is an engineering +input for benchmarks and review. It is not yet `FOG-SPHINX-1`, is not +authorized for a public network, and does not make a deployed security claim. + +The key words MUST, MUST NOT, REQUIRED, SHOULD, SHOULD NOT, and MAY describe +normative requirements in the sense of BCP 14 when they appear in uppercase. + +## 2. Scope + +`FOG-SPHINX-PROFILES` owns: + +- immutable packet-profile identifiers and their consensus authorization; +- fixed KEMSphinx packet geometry and exact route length; +- the mapping from PKI nodes to 32-byte packet node identifiers; +- per-hop routing command sequences and command padding; +- forward payload and SURB slots; +- single-use reply blocks and private reply tokens; +- replay-tag derivation, durable replay-state behavior, and retirement; +- packet lifetime and epoch binding; +- the logical bodies carried by FOG-WIRE packet commands; +- parser limits, uniform failures, and key lifecycle; +- conformance-vector and SDK requirements; +- isolation requirements for foreign Sphinx-family bridges. + +This document does not own: + +- message-level end-to-end encryption; +- contact, retry, acknowledgement, or application deduplication semantics; +- entry-capsule encryption and blind-relay entry selection; +- rendezvous capability allocation and offline return import; +- storage capabilities or replica protocols; +- adjacent-link Noise framing or its record size; +- the concrete delay distribution and cover rate; +- consensus production, signatures, or topology assignment. + +Those contracts belong to FOG-MESSAGING, FOG-STORAGE, the entry-capsule and +return-rendezvous specifications, FOG-WIRE, the cover profile, and FOG-PKI. + +## 3. Security Boundaries + +KEMSphinx protects per-hop routing information and transforms the packet at +every hop. It does not replace message-level encryption. The terminal receives +the fixed outer FOG payload and MUST still treat the native message or storage +operation as end-to-end encrypted unless the owning application protocol +explicitly defines a different public object. + +FOG-WIRE authenticates adjacent links but does not replace KEMSphinx. A valid +Noise connection is only a carrier for one packet whose KEMSphinx +authentication, route command, replay status, epoch, and geometry must still +be validated. + +Fixed packet size alone does not provide anonymity against timing, +intersection, n-1, volume, active tagging, or global observation. Delay, +traffic volume, cover traffic, topology, endpoint safety, and operator +independence remain necessary parts of any anonymity claim. + +## 4. Protocol Invariants + +### SPHINX-INV-01: One exact profile per epoch context + +A packet is processed under exactly one packet profile selected by the +accepted consensus and the authenticated FOG-WIRE epoch context. A node MUST +NOT guess a profile from packet length, try a list of suites, negotiate a +suite inside a packet, or fall back after failure. + +### SPHINX-INV-02: Four KEMSphinx hops + +Every core forward route and every core reply route has exactly four +KEMSphinx hops. A shorter or longer core route is invalid even if an +underlying Sphinx library can pad a variable path to the same header length. + +### SPHINX-INV-03: Entry is outside the forward mix route + +The forward entry is not a KEMSphinx hop. The blind relay submits an +entry-bound capsule; the entry releases the already constructed packet only +to its bound layer-1 node. + +### SPHINX-INV-04: One transform per hop + +An accepted KEMSphinx hop performs one and only one unwrap. No role unwraps +two layers, forwards a packet without the required unwrap, or changes a route +outside the authenticated command obtained from that unwrap. + +### SPHINX-INV-05: Fixed external geometry + +Every packet using one packet profile has one exact byte length. Forward, +reply, padding, loop, drop, mailbox, and asynchronous-message packets use the +same geometry. Source applications do not select packet sizes. + +### SPHINX-INV-06: Durable replay before action + +After successful KEM decapsulation and header authentication, a hop MUST +atomically check and durably insert its replay identifier before scheduling, +forwarding, terminal delivery, or returning success to an upstream worker. + +### SPHINX-INV-07: Single-use replies + +A SURB, its private decryption token, its `surb_id`, and the associated return +rendezvous are single-use. Uncertain delivery burns them. Retrying with the +same reply material is forbidden. + +### SPHINX-INV-08: No packet-layer fragmentation identity + +KEMSphinx packets contain no application message identifier, fragment index, +or fragment count. Application fragmentation occurs inside the fixed opaque +user payload under the owning end-to-end protocol. + +### SPHINX-INV-09: Purpose-separated keys and state + +Mix KEM keys, terminal KEM keys, Noise keys, entry-capsule keys, replay-state +protection keys, SURB private tokens, message keys, and storage keys are +distinct. Sharing one private key or replay database across roles is +forbidden. + +### SPHINX-INV-10: Failure never changes topology or profile + +Failure cannot remove a layer, substitute the entry as a mix, route directly +to storage, activate an old profile, send plaintext, or hand a foreign packet +to a core parser. + +## 5. Terminology and Encoding + +Terms used in this specification: + +- **packet profile**: permanent mapping from a numeric identifier to exact + KEMSphinx primitives, geometry, commands, epoch rules, limits, and SDK + behavior; +- **packet epoch context**: the epoch and accepted consensus already bound by + the FOG-WIRE connection carrying the packet; +- **packet node ID**: fixed 32-byte KEMSphinx routing identifier derived from + one consensus-authorized node and one packet profile; +- **forward route**: layer 1, layer 2, layer 3, then a courier or native + terminal; +- **reply route**: layer 3, layer 2, layer 1, then an entry return + rendezvous; +- **SURB**: a public Single Use Reply Block given to a terminal; +- **private reply token**: Composer-only payload decryption material paired + with one SURB; +- **raw replay tag**: profile-domain-separated digest of the current hop KEM + ciphertext; +- **stored replay ID**: keyed local representation committed to durable replay + state. + +All integers defined directly by FOG are unsigned and encoded in network byte +order. Fixed arrays have exactly the declared length. Reserved and padding +bytes are zero after decryption. Parsers reject non-zero reserved bytes, +truncation, trailing bytes, duplicate commands, reordered commands, unknown +commands, and lengths other than the active profile's exact values. + +## 6. Packet Profile Registry + +Every `packet_profile_id` is a non-zero unsigned 32-bit integer whose meaning +is permanent. An identifier MUST NOT be reused for changed bytes, algorithms, +command rules, route shape, limits, or behavior. + +An exact profile record contains at least: + +```text +[ + packet_profile_id, + kemsphinx_format_id, + kem_suite_id, + primitive_suite_id, + packet_node_id_hash_id, + replay_hash_id, + replay_store_mac_id, + additional_data, + hop_count, + route_shape_id, + command_registry_id, + kem_ciphertext_length, + header_mac_length, + payload_tag_length, + sprp_key_material_length, + per_hop_routing_info_length, + routing_info_length, + header_length, + surb_length, + sphinx_plaintext_header_length, + user_forward_payload_length, + forward_payload_length, + packet_length, + delay_profile_id, + maximum_packet_lifetime, + epoch_drain_limit, + parser_limits_profile_id, + sdk_contract_id, + conformance_vector_set_id +] +``` + +Registry metadata MAY label a reviewed profile `alpha`, `active`, `draining`, +or `retired`, but that mutable label is not part of the immutable profile +meaning. Only the accepted consensus authorizes actual use. An incomplete +candidate does not receive a numeric `packet_profile_id`. + +The profile record is published as an immutable release artifact and its +identifier is listed by FOG-PKI. Consensus `active_profile_ids` selects the +exact record. Numeric parameters are not operator-tunable. + +One packet profile MUST be compatible with the FOG-WIRE command-shape +registry active on every link that carries it. A consensus containing an +incompatible pair is invalid. + +## 7. Epoch and Profile Selection + +The packet bytes do not carry a negotiable profile identifier or epoch. The +authenticated FOG-WIRE connection context supplies: + +- network identity; +- epoch; +- accepted consensus hash; +- link context; +- wire profile; +- the one packet profile mapped to that epoch and command shape. + +For a given link context and packet class, one epoch maps to exactly one +packet profile. Old-epoch packets continue only on old-epoch connections +during the bounded drain interval. A node MUST NOT trial-decrypt an old +packet with several epoch keys. + +A Composer MUST NOT create a packet unless: + +```text +current_time_upper_bound + + maximum_route_delay + + relay_and_entry_queue_budget + + wire_delivery_budget + < packet_epoch_hard_expiry +``` + +The same check applies to a SURB's expected round trip. A packet or SURB that +cannot complete within its epoch budget is not created. + +## 8. Core Route Shape + +### 8.1 Forward route + +The only core forward shape is: + +```text +entry capsule -> L1 -> L2 -> L3 -> terminal +``` + +The entry capsule is outside this four-hop KEMSphinx packet. Its specification +must cryptographically bind the exact packet to the exact L1 packet node ID +without revealing that ID to the blind relay. + +The four KEMSphinx hops are: + +| Hop | Required role | Required routing result | +|---:|---|---| +| 0 | mix layer 1 | delay, then layer 2 | +| 1 | mix layer 2 | delay, then layer 3 | +| 2 | mix layer 3 | delay, then selected terminal | +| 3 | courier or native terminal | terminal recipient and payload | + +### 8.2 Reply route + +The only core reply shape is: + +```text +terminal uses SURB -> L3 -> L2 -> L1 -> entry rendezvous +``` + +The four KEMSphinx hops are: + +| Hop | Required role | Required routing result | +|---:|---|---| +| 0 | mix layer 3 | delay, then layer 2 | +| 1 | mix layer 2 | delay, then layer 1 | +| 2 | mix layer 1 | delay, then entry | +| 3 | entry | return rendezvous plus `surb_id` | + +The reply sender learns the public first-hop packet node ID contained in the +SURB. It does not learn later hops, the destination relay, or the Composer's +private reply token. + +### 8.3 Eligibility checks + +The Composer builds routes only from one accepted full consensus. Every hop +must have the required role, exact layer, active packet profile, current epoch +KEM key, endpoint reachability through the next role, and no effective +revocation. The route must also pass the consensus topology and diversity +rules. + +Each online hop independently verifies that the authenticated upstream role, +its own role and layer, the next packet node ID, and the next FOG-WIRE context +match the consensus. A valid header is not sufficient authorization for a +role-invalid edge. + +## 9. Packet Node IDs + +KEMSphinx routing uses a fixed 32-byte `packet_node_id`. It is not a private +key and is not a substitute for the PKI `node_id`. + +For profile `P`, it is derived as: + +```text +packet_node_id = HASH32( + "FOG-SPHINX-NODE-ID-1" || + u32be(length(network_id)) || + network_id || + u32be(P.packet_profile_id) || + u32be(length(node_id)) || + node_id +) +``` + +`HASH32` is fixed by `packet_node_id_hash_id`. Consensus validation derives +all active packet node IDs and rejects any duplicate. Nodes build an immutable +epoch-local lookup table from packet node ID to the exact authorized role, +layer, KEM key ID, and link endpoint. + +Packet node IDs may be logged only in coarse configuration validation. They +MUST NOT be logged per packet. + +## 10. Routing Commands + +The core profile uses the maintained Sphinx command model with these exact +tags: + +| Tag | Command | Body length | Meaning | +|---:|---|---:|---| +| `0x00` | `NULL` | 0 | terminates command parsing; remaining bytes are zero | +| `0x01` | `NEXT_NODE` | 64 | 32-byte next node ID and 32-byte next header MAC | +| `0x02` | `RECIPIENT` | 32 | opaque terminal recipient or return capability | +| `0x03` | `SURB_REPLY` | 16 | random single-use `surb_id` | +| `0x80` | `NODE_DELAY` | 4 | unsigned delay in profile-defined units | + +Unknown tags are invalid. No vendor command range is accepted by core nodes. + +### 10.1 Intermediate mix command sequence + +Every nonterminal mix hop contains exactly: + +```text +NODE_DELAY || NEXT_NODE || NULL || zero padding || next KEM ciphertext +``` + +The implementation may build commands in an internal representation, but the +encrypted routing bytes and parser result must preserve this semantic order. +There is exactly one delay and one next node. Zero delay is valid only if the +active cover profile explicitly includes it in the same public distribution +used for real and cover packets. + +The `NODE_DELAY` value is validated before queue insertion. Values outside the +active delay profile, arithmetic overflow, or a deadline past epoch expiry +cause a uniform drop. + +### 10.2 Forward terminal command sequence + +The terminal hop contains exactly: + +```text +RECIPIENT || NULL || zero padding || unused zero KEM slot +``` + +`RECIPIENT` is a random or derived 32-byte capability defined by the terminal +contract. It must not be a human address, username, public mailbox name, or +application-specific string. The terminal validates it before acting on the +payload. + +### 10.3 Reply terminal command sequence + +The entry terminal hop contains exactly: + +```text +RECIPIENT || SURB_REPLY || NULL || zero padding || unused zero KEM slot +``` + +Here `RECIPIENT` is an opaque short-lived return-rendezvous capability and +`SURB_REPLY` selects the Composer's private reply token. The entry atomically +consumes the rendezvous before queueing the returned opaque payload. + +### 10.4 Terminal padding + +The current maintained KEMSphinx geometry reserves one KEM ciphertext-sized +tail in every per-hop routing block, including the terminal block. The +terminal tail is all zero before routing encryption. FOG does not apply the +possible one-ciphertext optimization until a new separately identified +profile has complete vectors and interoperability review. + +## 11. Forward Plaintext Block + +After the terminal KEMSphinx unwrap and payload-tag validation, a forward +payload has this exact layout: + +```text +offset length field +0 1 surb_flag +1 1 reserved +2 S surb_slot +2+S U user_payload +``` + +`reserved` is zero. `S` is the profile's exact `surb_length`. `U` is the exact +`user_forward_payload_length`. + +`surb_flag` is: + +- `0x00`: no usable SURB; the entire `surb_slot` is zero after decryption; +- `0x01`: `surb_slot` contains one valid SURB for the same packet profile and + epoch. + +All other values are invalid. The slot always exists and always occupies the +same bytes. Application code receives exactly `U` opaque bytes and an optional +validated public SURB. It does not receive short lengths from the packet +layer. + +The 4,096-byte candidate user payload is owned internally by the next +protocol. FOG-MESSAGING or FOG-STORAGE defines authentication, actual body +length, padding, fragmentation, retries, deduplication, and acknowledgements +inside those bytes. + +## 12. SURB Contract + +### 12.1 Public SURB + +The public SURB encoding is exactly: + +```text +prebuilt_kemsphinx_header[header_length] || +first_hop_packet_node_id[32] || +reply_payload_key_material[sprp_key_material_length] +``` + +The packet profile and epoch are supplied by the containing authenticated +context and the local SURB object. They are not inserted into the opaque SURB +and do not change its size. + +The prebuilt reply path has exactly four hops and ends in the entry terminal +commands described in section 10.3. + +### 12.2 Private reply token + +The Composer stores, separately from the public SURB: + +```text +[ + token_version, + packet_profile_id, + epoch, + surb_id, + hard_expiry, + status, + reverse_order_payload_key_material +] +``` + +For four hops, `reverse_order_payload_key_material` contains five fixed key +and IV pairs: one per KEMSphinx hop plus the final reply payload pair. In the +candidate geometry this secret field is 320 bytes. + +The token is secret Composer state. It is encrypted and integrity-protected +at rest under the Composer state profile, excluded from logs and routine +backups unless the backup design explicitly protects it, and erased after +successful use, expiry, cancellation, or uncertain duplicate handling. + +### 12.3 Single-use state machine + +The only valid state transitions are: + +```text +AVAILABLE -> COMMITTED -> CONSUMED +AVAILABLE -> EXPIRED +COMMITTED -> BURNED +``` + +The terminal changes a public SURB from `AVAILABLE` to `COMMITTED` before +building or submitting a reply packet. A confirmed local construction error +before any packet or KEM operation may return it to `AVAILABLE`; after packet +construction or any send attempt, uncertainty results in `BURNED`. + +The Composer atomically changes the private token to `CONSUMED` before +releasing successfully authenticated plaintext to an application. A second +return with the same `surb_id` is discarded without another decryption +attempt. + +Applications needing retries provide multiple independently generated SURBs. +They never clone a SURB or its private token. + +### 12.4 Tagging and compulsion limits + +SURBs do not remove active-tagging or reply-compulsion risk. Implementations +MUST apply the same fixed packet schedule, reply size, and terminal queue +policy to successful replies, errors, and cover outcomes. A terminal cannot +send arbitrary immediate diagnostic replies outside the cover schedule. + +## 13. Replay Protection + +### 13.1 Raw replay tag + +For an authenticated hop, the raw tag is: + +```text +raw_replay_tag = HASH( + "FOG-SPHINX-REPLAY-TAG-1" || + network_id || + u32be(packet_profile_id) || + u64be(epoch) || + kem_key_id || + current_hop_kem_ciphertext +) +``` + +Lengths for variable fields are fixed by the active PKI and packet profiles. +`HASH` and its output length are fixed by `replay_hash_id`. + +The current hop KEM ciphertext is the public ciphertext at the front of the +current KEMSphinx header, not a future ciphertext hidden in routing data. + +### 13.2 Stored replay ID + +The durable database stores: + +```text +stored_replay_id = MAC( + replay_state_key_epoch, + "FOG-SPHINX-REPLAY-STORE-1" || raw_replay_tag +) +``` + +This local keyed representation reduces direct correlation between a stolen +database and previously captured packet headers. It is not a substitute for +disk, process, or host protection. + +`replay_state_key_epoch` is independently generated per node, packet profile, +and epoch. It is not derived from a KEMSphinx private key and is never shared +with another role or node. + +### 13.3 Processing order + +A hop processes an incoming packet in this order: + +1. verify authenticated FOG-WIRE context, exact command body, and packet + length; +2. enforce current epoch, profile, role, layer, upstream, and local key ID; +3. parse only fixed header offsets; +4. decapsulate the current KEM ciphertext; +5. derive hop keys and verify the current header MAC; +6. derive the raw replay tag and stored replay ID; +7. atomically check and durably insert the stored replay ID; +8. parse and validate the exact routing command sequence; +9. validate next-hop authorization and delay bounds; +10. transform the packet exactly once; +11. queue the transformed packet or terminal payload; +12. erase per-hop shared secrets and temporary keys. + +An unauthenticated random ciphertext is not inserted. A header that +successfully authenticates is inserted before later command validation, even +when the command, next hop, delay, or terminal capability is invalid. This +prevents repeated authenticated malformed work from bypassing replay state. + +### 13.4 Durable database + +Replay insertion is a crash-consistent transaction. The packet is not made +eligible for forwarding until the write-ahead record or equivalent durable +commit succeeds. Group commit is permitted only if scheduling and forwarding +wait for the corresponding durable barrier. + +A keyed in-memory filter MAY avoid many exact lookups, but it is only a front +cache. A positive filter result is confirmed against the exact durable set; +therefore filter false positives do not discard valid packets. + +Replay databases are separated by node, role, packet profile, epoch, and KEM +key ID. They are excluded from telemetry, snapshots shared across nodes, and +ordinary backups. Aggregate counts may be exported only under the observer +privacy profile. + +### 13.5 Restart and loss + +On restart, a node verifies database integrity, replay-state key availability, +epoch ownership, and committed sequence state before accepting packets. It +rebuilds any in-memory filter from the exact durable set. + +Missing, rolled-back, corrupt, or unverifiable replay state is fail-closed. +The node stops packet processing for that profile and epoch. It does not start +with an empty cache. Service can resume only from safely restored monotonic +state or a new epoch with fresh KEM and replay-state keys. + +### 13.6 Retirement + +An epoch replay database and its local key are retained until: + +```text +epoch_hard_expiry + + maximum_packet_lifetime + + maximum_clock_uncertainty + + crash_recovery_margin +``` + +has passed. Retirement erases the replay-state key and removes the database +through the deployment's recoverable secure-deletion policy. No node accepts +new work merely because an old database still exists. + +## 14. KEMSphinx Hop Processing + +Every mix worker has fixed-size buffers from its active geometry. It does not +allocate based on decrypted command values. + +For a valid intermediate hop it: + +- decrypts the current routing block; +- obtains the authenticated delay and next node; +- shifts the routing information according to KEMSphinx; +- copies the hidden next-hop KEM ciphertext into the public KEM field; +- applies one payload permutation; +- commits replay state; +- enters the bounded delay queue; +- emits the exact transformed packet on the authorized next link. + +For a valid terminal it: + +- verifies the terminal command sequence; +- verifies the final payload integrity tag; +- validates the fixed forward plaintext block for a forward packet, or + returns the still SURB-protected payload and `surb_id` for a reply packet; +- consumes any terminal or rendezvous capability atomically; +- hands only the bounded opaque object to the owning role contract. + +No mix exposes whether failure was KEM, MAC, replay, command, delay, route, +queue, payload tag, recipient, or epoch. Remote behavior is the same uniform +drop class and is subject to the cover schedule. + +## 15. Delay Contract + +`NODE_DELAY` contains a 32-bit count in the unit fixed by the active delay +profile. Implementations convert using checked arithmetic and a monotonic +clock. + +The delay profile fixes at least: + +```text +[ + delay_profile_id, + unit_nanoseconds, + maximum_encoded_delay, + maximum_per_hop_delay, + maximum_route_delay, + sampling_distribution_id, + quantization_rule, + queue_deadline_rule, + cover_schedule_profile_id +] +``` + +The Composer samples every mix delay from this authenticated distribution. +Operators do not alter it locally. Mixes validate the encoded value but do not +resample it. Queue pressure does not convert delayed traffic to immediate +traffic; overload follows the profile's uniform drop or shutdown behavior. + +The initial distribution and numeric limits remain simulation outputs. No +anonymity or latency claim follows from this structural specification. + +`FOG-SIMULATION.md` records an initial 500 ms versus 5 second exponential-delay +sensitivity comparison. It selects neither value. The longer value improved +local pool overlap under one high-cover scenario while increasing modeled +latency by roughly an order of magnitude; formal end-to-end and operational +evidence remains open. + +## 16. Padding and Fragmentation + +### 16.1 Packet padding + +Routing blocks, forward SURB slots, user payloads, reply payloads, and unused +fields always occupy their full profile lengths. Plain structural padding is +zero before the applicable cryptographic layer. Random bytes are used only +where the selected reviewed construction requires randomness. + +Application code MUST NOT create a shorter KEMSphinx payload. It supplies an +exact fixed-size inner envelope whose internal padding is authenticated by the +owning end-to-end protocol. + +### 16.2 Application fragmentation + +Messages larger than `user_forward_payload_length` are fragmented by +FOG-MESSAGING or FOG-STORAGE before KEMSphinx construction. Fragment metadata +is inside the end-to-end protected envelope. Each fragment becomes an +independent fixed-size KEMSphinx packet with independent route randomness, +KEM ciphertexts, replay tags, and optional SURB. + +KEMSphinx does not retransmit or deduplicate fragments. Reusing a packet for a +retry is forbidden. A retry constructs a fresh packet under the application +protocol's idempotency rules. + +### 16.3 FOG-WIRE fragmentation + +`PACKET_FORWARD` and the packet portion released from `PACKET_SUBMIT` are one +logical FOG-WIRE message with an exact KEMSphinx packet body. FOG-WIRE may +split that logical message across its fixed DATA records when the active wire +profile requires it. + +Wire fragments are link-local and are fully reassembled, bounded, and +authenticated before KEMSphinx parsing. They are never individually queued, +replayed, forwarded, or stored as KEMSphinx packets. + +## 17. FOG-WIRE Command Bodies + +This document owns these logical command shapes: + +```text +PACKET_FORWARD_BODY = kemsphinx_packet[packet_length] + +PACKET_RETURN_BODY = + surb_id[16] || + returned_payload[payload_tag_length + forward_payload_length] +``` + +`PACKET_FORWARD_BODY` is used for entry-to-L1, mix-to-mix, and L3-to-terminal +links. Link context and direction determine which role pair is legal. + +`PACKET_RETURN_BODY` is created only after the entry completes the final reply +hop and atomically consumes the return rendezvous. The returned payload remains +protected by the Composer's private reply token. The relay cannot decrypt or +modify it successfully. + +`PACKET_SUBMIT_BODY` remains owned by the entry-capsule specification because +it must hide and bind the first internal hop from the blind relay. + +A compatible FOG-WIRE command-shape registry has compile-time exact body +limits for these commands. Generic byte-string RPCs are not conforming. + +## 18. Candidate Geometry + +### 18.1 Status + +The following profile is a calculated candidate: + +```text +name = FOG-SPHINX-CANDIDATE-MLKEM768-X25519-1 +packet_profile_id = UNASSIGNED +candidate_status = geometry-only +``` + +The candidate name is permanently bound to this calculated geometry. It is +not a complete packet profile because final primitive IDs, dependency +revisions, delay limits, replay lifetime, and conformance vector IDs are still +unresolved. A numeric packet profile ID is assigned only after every field in +section 6 is frozen. It will not reuse an identifier from another candidate. + +This candidate MUST NOT be placed in a claim-bearing public consensus until +section 24's activation gates pass. + +`FOG-CRYPTO-SUITES.md` admits the exact calculated construction to +complete-packet benchmarking but does not activate it. The unresolved +non-KEM primitives and implementation evidence keep the complete profile +non-active. + +`FOG-CRYPTO-BENCHMARKS.md` records a first-host benchmark of this exact +geometry. Future implementations MUST preserve its strict integration +finding: the parameterized maintained API is wrapped by a FOG boundary that +rejects every path, packet, payload, SURB, encrypted reply, or reply-key length +that differs from this profile before cryptographic processing. The first-host +result does not change `geometry-only` status or assign a profile ID. + +### 18.2 Candidate primitive inputs + +The geometry calculation uses: + +- KEMSphinx with one KEM ciphertext per hop; +- exactly four hops; +- hybrid `MLKEM768-X25519` built by the maintained HPQC security-preserving + split-PRF combiner; +- component and ciphertext concatenation order of X25519 hashed-ElGamal KEM + first, then ML-KEM-768, matching the evaluated HPQC registry despite the + display name; the split-PRF is order-sensitive; +- ML-KEM-768 ciphertext length of 1,088 bytes; +- X25519 hashed-ElGamal KEM ciphertext length of 32 bytes; +- combined KEM ciphertext length of 1,120 bytes; +- 32-byte packet node IDs; +- 32-byte header MACs; +- 16-byte SURB IDs; +- 32-byte payload integrity tags; +- 48-byte SPRP keys and 16-byte SPRP IVs; +- two additional-data bytes fixed to `0x0000` for compatibility with the + evaluated maintained KEMSphinx format; +- a two-byte forward plaintext header; +- a 4,096-byte user forward payload; +- one fixed SURB slot in every forward plaintext block. + +The primitive suite currently evaluated with this geometry includes the +maintained Katzenpost KDF, header MAC, header stream, and AEZ-based payload +SPRP parameterization. Geometry compatibility does not constitute approval of +that primitive suite. In particular, the exact dependency revisions, +side-channel behavior, licensing, AEZ usage, deterministic vectors, and +independent review remain activation gates. + +### 18.3 Exact calculation + +```text +KEM_CIPHERTEXT_LENGTH = 1088 + 32 + = 1120 + +NEXT_NODE_LENGTH = 1 + 32 + 32 + = 65 + +SURB_REPLY_LENGTH = 1 + 16 + = 17 + +PER_HOP_ROUTING_INFO = 65 + 17 + 1120 + = 1202 + +ROUTING_INFO_LENGTH = 4 * 1202 + = 4808 + +HEADER_LENGTH = 2 + 1120 + 4808 + 32 + = 5962 + +SPRP_KEY_MATERIAL = 48 + 16 + = 64 + +SURB_LENGTH = 5962 + 32 + 64 + = 6058 + +FORWARD_PAYLOAD = 2 + 6058 + 4096 + = 10156 + +PACKET_LENGTH = 5962 + 32 + 10156 + = 16150 + +PRIVATE_REPLY_KEYS = (4 + 1) * 64 + = 320 +``` + +### 18.4 Candidate geometry table + +| Field | Bytes | +|---|---:| +| Hop count | 4 hops | +| Additional data | 2 | +| KEM ciphertext | 1,120 | +| Per-hop routing information | 1,202 | +| Routing information | 4,808 | +| Header MAC | 32 | +| Header | 5,962 | +| Payload tag | 32 | +| Public SURB | 6,058 | +| Forward plaintext header | 2 | +| User forward payload | 4,096 | +| Forward payload | 10,156 | +| Complete KEMSphinx packet | 16,150 | +| Private reply key material | 320 | + +Every arithmetic value is a protocol constant for this candidate. Runtime +configuration cannot change it. + +## 19. Key Lifecycle + +Every mix and terminal has an independently generated KEMSphinx key for each +authorized packet profile and epoch. Descriptors bind the public key, key ID, +purpose, owner node, role, layer, profile, and validity interval. + +Private keys: + +- are generated with an approved operating-system randomness source; +- are written only to role-local protected storage; +- are loaded only by the owning role process; +- are never copied to another layer, co-located role, authority, relay, or + observer; +- are never used for Noise, entry capsules, storage, signatures, or messages; +- remain available only for the exact old-epoch drain interval; +- are erased after packet and replay retirement conditions both hold. + +Key generation and persistence are crash-safe. A descriptor is not published +until the private key is durably available to its owner. A node never creates +a fresh private key under an already published key ID. + +KEM decapsulation failures, malformed public keys, and component failures in +a hybrid KEM are fatal to that packet. A hybrid implementation must reject +incorrect component lengths before component decapsulation and must not reveal +which component failed. + +## 20. Parser and Resource Limits + +Before cryptographic work, an implementation enforces: + +- exact logical message length; +- exact packet profile from connection context; +- one bounded packet buffer; +- no recursive, compressed, map-based, or self-describing packet data; +- no allocation based on routing commands; +- no profile or algorithm name supplied by the peer; +- per-connection, per-peer, per-key, and global cryptographic work budgets; +- bounded queues and deadlines. + +After decryption, it enforces: + +- exact command count, type, order, and zero padding; +- one legal next role and layer; +- delay bounds and epoch completion bounds; +- terminal capability length and one-time state; +- exact forward block flags and zero reserved bytes; +- exact payload and SURB lengths. + +Memory containing shared secrets, per-hop keys, private reply keys, and +decrypted routing blocks is cleared promptly using the reviewed library's +supported mechanism. Memory clearing is defense in depth and does not replace +process isolation. + +## 21. Logging and Observability + +Core roles MUST NOT log: + +- packet bytes or packet digests; +- raw replay tags or stored replay IDs; +- KEM ciphertexts or shared secrets; +- `surb_id`, SURBs, private reply tokens, or rendezvous capabilities; +- per-packet routes, next-hop IDs, delay values, or fine timing; +- terminal recipient capabilities or user payloads. + +Permitted local diagnostics are coarse reason counters, queue occupancy +buckets, bounded latency histograms, replay database health state, and +profile-level totals under FOG-OBSERVER privacy rules. Operators cannot enable +packet tracing on a claim-bearing profile. + +Test builds may use deterministic vectors and verbose traces only with public +test keys and synthetic packets. Such builds and keys are rejected by +production configuration. + +## 22. SDK Boundary + +The stable public SDK exposes typed operations, not raw cryptographic knobs. +It contains at least: + +```text +ValidatePacketProfile(profile, consensus_context) +DerivePacketNodeID(profile, node_id) +BuildForwardRoute(consensus, terminal, rng) +BuildReplyRoute(consensus, rendezvous, surb_id, rng) +CreateSURB(profile, epoch, reply_route, rng) +CreateForwardPacket(profile, route, fixed_payload, optional_surb, rng) +CreateReplyPacket(profile, public_surb, fixed_payload) +UnwrapOneHop(profile, epoch_context, private_key, exact_packet) +DecryptSURBReply(private_token, surb_id, exact_returned_payload) +``` + +The SDK does not expose: + +- arbitrary hop counts for core profiles; +- operator-selected algorithms or geometry; +- raw private-key serialization through ordinary application APIs; +- reuse or cloning of SURBs or reply tokens; +- packet parsing without an authenticated profile and epoch context; +- automatic foreign-profile detection; +- application-dependent packet sizes. + +SURB and private-token types are move-only or guarded by an atomic persistent +state abstraction. Copyable byte slices are not the primary API. + +The same reference codec and vector set are used by Composer, entry, mix, +terminal, relay import, simulator, and conformance tools. Each executable +still imports only the operations needed by its role. + +## 23. Conformance Evidence + +Each packet profile release includes machine-readable vectors generated with +fixed public test entropy. The vector manifest records dependency versions, +source revision, profile record hash, and generation command. + +The required vector set covers: + +1. packet node ID derivation; +2. hybrid KEM component key and ciphertext lengths; +3. complete candidate geometry arithmetic; +4. every routing command encoding and invalid tag; +5. zero padding and terminal unused KEM slot; +6. one complete four-hop forward packet at every unwrap; +7. one complete four-hop SURB reply at every unwrap and final decryption; +8. replay tags at each hop and durable duplicate rejection; +9. changed ciphertext, MAC, command, padding, payload tag, and SURB failures; +10. wrong profile, epoch, role, layer, route length, and upstream failures; +11. maximum and invalid delay values; +12. SURB state transitions, double use, expiry, and uncertain send; +13. replay database restart, rollback, corruption, and retirement; +14. FOG-WIRE fragmentation and exact logical body reassembly; +15. profile overlap with separate old and new epoch connections; +16. cross-implementation byte equality. + +Property and fuzz tests additionally cover all fixed-offset parsers, +truncations at every byte boundary, trailing bytes, unknown commands, command +reordering, integer boundaries, malformed KEM component lengths, and queue +resource limits. + +At least two independently integrated implementations must reproduce the +complete vectors before a claim-bearing profile becomes active. Calling the +same library through two thin wrappers is not independent evidence. + +## 24. Activation Gates + +`FOG-SPHINX-CANDIDATE-MLKEM768-X25519-1` remains non-active until all of the +following are complete: + +- reproducible create, unwrap, SURB, and replay benchmarks on every supported + hardware class using the exact 16,150-byte geometry; the first older x86-64 + create, unwrap, and SURB baseline is complete, while replay and the remaining + hardware classes are open; +- memory, queue, storage, and maximum-throughput budgets for every role; +- dependency revision pinning, license review, and reproducible builds; +- review of the ML-KEM-768 implementation and X25519 adapter; +- confirmation that the exact security-preserving combiner meets the + KEMSphinx hybrid-KEM requirement; +- review of KDF, MAC, stream, payload SPRP, fragile payload-tag construction, + and side-channel behavior; +- complete deterministic vectors and negative corpus; +- fuzzing and restart-safe replay tests; +- simulator evidence for packet size, delay, cover traffic, n-1, and long-term + disclosure behavior; +- compatibility verification with the selected maintained KEMSphinx target; +- independent protocol and implementation review; +- a separately documented decision promoting an exact immutable record to + `alpha` and later `active`. + +Failure of a gate produces a newly named geometry candidate or a newly +numbered complete packet profile, as applicable. It does not silently change +this candidate. + +## 25. Profile Transitions + +A packet-profile transition follows the FOG-PKI preannouncement and bounded +overlap rules. + +During overlap: + +- new work uses the profile mapped to the new epoch; +- old work drains only on old-epoch FOG-WIRE connections; +- each profile has separate KEM keys, replay-state keys, replay databases, + queues, command-shape registry, and SDK object types; +- a packet is never converted in place from one profile to another; +- a failure in the new profile does not reactivate the old profile; +- minimum accepted profile state is monotonic. + +After hard expiry, old connections close, old packets and SURBs are rejected, +private reply tokens expire, replay state completes its retention period, and +private KEM keys are erased. + +Emergency retirement can stop creation and acceptance immediately, but it +cannot downgrade. In-flight delivery may be lost. + +## 26. External Sphinx-Family Bridges + +Foreign Sphinx, KEMSphinx, Katzenpost, Nym, YAMN, SMTP, NNTP, and other network +formats never enter a core packet parser. A bridge has: + +- a separate `fog-bridge-*` executable and service identity; +- separate ports, FOG-WIRE link context, keys, writable state, queues, replay + domains, metrics, and deployment policy; +- one explicitly named foreign profile and version; +- strict parsing and resource bounds for that profile only; +- no access to Composer plaintext or core private keys; +- a fresh core packet constructed after policy validation, never a header + reinterpretation or in-place conversion; +- explicit disclosure as a correlation and availability point. + +A bridge terminates one network anonymity context and originates another. It +cannot claim end-to-end mix-path unlinkability across the boundary, even when +the application payload remains end-to-end encrypted. + +Automatic port sharing, packet sniffing, trial parsing, or silent downgrade +between core and foreign profiles is forbidden. + +## 27. Failure Matrix + +| Condition | Required behavior | +|---|---| +| Wrong packet length | reject before KEM work | +| Wrong epoch or profile context | reject, no alternate-key trial | +| KEM decapsulation failure | uniform drop | +| Header MAC failure | uniform drop, no durable replay insertion | +| Authenticated duplicate | uniform drop after exact replay lookup | +| Authenticated invalid command | durable replay insert, then uniform drop | +| Invalid next role or layer | durable replay insert, then uniform drop | +| Invalid delay or expired deadline | durable replay insert, then uniform drop | +| Replay commit failure | stop affected profile and epoch processing | +| Replay state missing or rolled back | fail closed until safe restore or new epoch | +| Delay queue overload | profile-defined uniform drop or role shutdown | +| Final payload tag failure | uniform drop, no terminal action | +| Invalid terminal capability | uniform drop after one-time-state rules | +| SURB reused or expired | discard without constructing another reply | +| Private token missing or consumed | discard returned payload | +| Old profile failure during transition | do not fall back or extend expiry | +| Foreign packet on core port | reject before foreign parsing | + +Local diagnostics use coarse stable reason classes. Remote peers receive no +fine-grained error response. + +## 28. Threat Traceability + +| Threat-model concern | Packet-profile response | +|---|---| +| `TM-NET-01` traffic correlation | one fixed geometry, four hops, authenticated delay and cover profile | +| `TM-NET-02` low anonymity set | no packet-format claim; simulator and deployment gates remain required | +| `TM-NET-03` compromised mixes | stratified route, one transform, role validation, purpose-separated keys | +| `TM-NET-04` replay and compulsion | durable per-hop replay, single-use SURBs and rendezvous state | +| `TM-NET-05` tagging | authenticated header and payload processing, uniform terminal failure | +| `TM-NET-06` profile fingerprinting | consensus-selected immutable profiles, no autodetection or negotiation | +| `TM-PKI-02` stale consensus | epoch-bound packet context and monotonic transition state | +| `TM-ROLE-01` role collapse | separate KEM keys, replay state, ports, commands, and bridge processes | +| `TM-ROLE-02` relay knowledge | entry capsule hides L1; relay handles only opaque fixed packets | +| `TM-ROLE-03` terminal exposure | terminal gets fixed opaque application payload, not source address or full route | +| `TM-CRYPTO-01` primitive misuse | reviewed parameterized library, immutable suite record, activation gates | +| `TM-CRYPTO-02` key reuse | role, purpose, profile, and epoch separation | +| `TM-AVAIL-01` resource exhaustion | fixed buffers, crypto budgets, durable replay, bounded queues and failures | + +## 29. Residual Risks + +Even a conforming implementation remains exposed to: + +- traffic analysis from timing, volume, routes, endpoints, and sparse use; +- n-1 and active-delay attacks by sufficiently placed malicious nodes; +- denial of service through connection, KEM, replay-store, queue, or terminal + exhaustion within residual budgets; +- endpoint or Composer compromise; +- malicious or colluding entry, mix, terminal, relay, storage, and authority + operators within the threat model's residual cases; +- reply tagging and compulsion not eliminated by SURBs; +- implementation, dependency, side-channel, randomness, and erasure defects; +- correlation introduced by external bridges; +- loss caused by fail-closed replay and single-use reply behavior; +- incorrect anonymity conclusions from a local PoC or low-traffic network. + +These risks must appear in deployment documentation and public claims. + +## 30. References + +- George Danezis and Ian Goldberg, *Sphinx: A Compact and Provably Secure Mix + Format*, 2009: +- Katzenpost, *The KEMSphinx Cryptographic Packet Format*: + +- Katzenpost, *The Sphinx Cryptographic Packet Format*: + +- Katzenpost, *The Katzenpost Mix Network Wire Protocol*: + +- Katzenpost, *Katzenpost Mix Network Replay Detection*: + +- Federico Giacon, Felix Heuer, and Bertram Poettering, *KEM Combiners*, + 2018: +- NIST, *FIPS 203: Module-Lattice-Based Key-Encapsulation Mechanism Standard*, + 2024: +- RFC 7748, *Elliptic Curves for Security*: + +- FOG Composer protocol: `FOG-COMPOSER.md` +- FOG cryptographic suite evaluation: `FOG-CRYPTO-SUITES.md` +- FOG cryptographic benchmark baseline: `FOG-CRYPTO-BENCHMARKS.md` +- FOG traffic and topology simulation: `FOG-SIMULATION.md` + +## 31. Completion Checklist + +Before implementation work for an active packet profile begins, reviewers +must be able to answer yes to all of these questions: + +- Is one immutable profile selected by authenticated consensus and epoch + context without packet guessing? +- Are both directions exactly four KEMSphinx hops with the entry outside the + forward route? +- Are packet, header, routing, payload, SURB, and reply-token sizes proven by + arithmetic and tests? +- Are command sequences exact, padded, role-valid, and parser-bounded? +- Is replay state durable before forwarding and fail-closed after rollback or + loss? +- Are SURBs, private tokens, IDs, and rendezvous capabilities atomic and + single-use? +- Is application fragmentation inside end-to-end protection and independent + of packet size? +- Are KEM, replay, Noise, entry, storage, and message keys purpose-separated? +- Do FOG-WIRE command shapes and packet geometry agree exactly? +- Are complete vectors, fuzzing, benchmarks, simulation, and independent + review available for the exact dependency set? +- Are external formats isolated in separate bridge processes and disclosed as + correlation points? +- Do failures stop safely without topology, profile, or plaintext fallback? diff --git a/docs/FOG-STORAGE.md b/docs/FOG-STORAGE.md new file mode 100644 index 0000000..00498ac --- /dev/null +++ b/docs/FOG-STORAGE.md @@ -0,0 +1,1507 @@ +# FOG Storage + +Status: Draft 0.1 + +Date: 2026-08-08 + +## 1. Purpose + +This document defines `FOG-STORAGE`, the capability-addressed scattered +storage contract used by FOG native asynchronous services. + +It fixes the storage trust boundaries, pairwise stream model, rotating box +capabilities, storage-epoch manifests, courier envelopes, replica selection, +single-box read and write operations, authenticated replica receipts, +idempotency, request retry and deduplication, empty-read behavior, retention, +tombstones, repair, resource bounds, and conformance gates. + +It also records a non-active integration candidate named +`FOG-STORAGE-CANDIDATE-BACAP-PIGEONHOLE-1`. The candidate evaluates the +published BACAP construction and the single-box portion of the published +Pigeonhole protocol while preserving FOG role separation and packet profiles. +It is not `FOG-STORAGE-1`, has no numeric profile identifier, is not directly +wire-compatible with Katzenpost, is not authorized for a public network, and +does not establish a deployed security claim. + +The key words MUST, MUST NOT, REQUIRED, SHOULD, SHOULD NOT, and MAY describe +normative requirements in the sense of BCP 14 when they appear in uppercase. + +## 2. Scope + +`FOG-STORAGE` owns: + +- storage stream, read-capability, write-capability, and per-box state; +- private per-contact directional mailbox streams; +- immutable public storage-replica manifests; +- storage epochs and replica envelope-key periods; +- fixed Composer-to-courier storage envelopes and replies; +- fixed courier-to-replica and replica-to-replica command bodies; +- deterministic final-shard selection and disjoint intermediate selection; +- single-box reads, data writes, tombstone writes, and authenticated results; +- courier request deduplication and bounded request state; +- idempotent final-replica behavior and replica repair; +- retention, garbage collection, backup deletion, and non-resurrection rules; +- fixed geometry compatibility with FOG-MESSAGING, KEMSphinx, and FOG-WIRE; +- storage overload, flooding, and non-amplification requirements; +- parser limits, failure behavior, lifecycle, and conformance evidence. + +This document does not own: + +- message identities, ratchets, application frames, or message ACK semantics; +- KEMSphinx packet construction, SURB cryptography, or packet replay state; +- adjacent-link Noise framing or connection scheduling; +- Composer local-database encryption and rollback detection; +- the entry capsule, return rendezvous, or offline transfer bundle; +- concrete cover, polling, retry-delay, or traffic-rate distributions; +- anonymous client admission or a general anti-Sybil solution; +- multi-box atomic copy, group delivery, multi-device synchronization, or + permanent archival storage. + +Those contracts belong to `FOG-MESSAGING`, `FOG-SPHINX-PROFILES`, `FOG-WIRE`, +`FOG-COMPOSER`, the entry and return specifications, `FOG-SX`, the cover +profile, future admission work, and future group or bulk-delivery protocols. + +## 3. Security Boundary and Information Exposure + +### 3.1 Composer + +The networkless Composer is the only role that holds stream root capabilities, +derives successive box identifiers and box keys, encrypts or decrypts box +payloads, verifies box authenticity, constructs storage envelopes, and makes +the final decision to advance a stream index. + +The Composer learns its contact relationship, stream direction, box sequence, +message envelope, retry state, replica selection, and detailed local result. +None of those data classes may be delegated to the blind relay. + +### 3.2 Courier + +The courier terminates the final KEMSphinx hop and sees: + +- one fixed storage profile and storage epoch; +- the exact storage-manifest hash; +- two intermediate replica identifiers and envelope-key identifiers; +- a fixed client ephemeral public key, two fixed encapsulations, and one + fixed opaque ciphertext; +- a short-lived hash used only for request deduplication and reply matching; +- coarse acceptance, timeout, and local overload state. + +The courier MUST NOT learn the box ID, stream capability, stream index, +operation type, final shard pair, message ID, contact, application type, +plaintext, data-versus-tombstone state, or empty-versus-hit result. + +### 3.3 Intermediate replica + +Each intermediate replica opens one addressed envelope and learns one fixed +inner storage request, including the box ID, operation type, storage epoch, +operation nonce, and deterministic final shard pair. It does not learn the +Composer network location, contact identity, stream root, stream index, +application type, or message plaintext. + +An intermediate replica MUST NOT persist a mailbox, capability root, or +unbounded request history. It keeps only bounded dispatch and reply state. + +### 3.4 Final replica + +Each final replica learns the box ID, operation type, storage epoch, signed +opaque box record, and the intermediate replica that forwarded the request. +It stores only the fixed authenticated record or tombstone in the selected +storage-epoch namespace. + +A final replica cannot identify the stream to which a box belongs unless the +capability construction, client behavior, collusion, or side information +breaks that unlinkability objective. Repeated reads of the same still-empty +box are necessarily linkable at a final replica and remain a documented +residual risk. + +### 3.5 Blind relay, entry, and mixes + +These roles see only their existing fixed transfer, entry, KEMSphinx, and +FOG-WIRE objects. They do not parse a FOG-STORAGE field. Storage protection +does not replace KEMSphinx or Noise, and those layers do not replace box and +envelope protection. + +## 4. Protocol Invariants + +### STORE-INV-01: No stable public mailbox + +A FOG mailbox is a private stream capability and an evolving sequence of +pseudorandom box IDs. The protocol has no public mailbox name, user lookup, +stable network account, or direct Composer-to-replica endpoint. + +### STORE-INV-02: One writer per stream + +One stream has exactly one active write-capability holder. Bidirectional +messaging uses two independent streams, one per direction. Concurrent copies +of one write state or multiple writers are invalid. + +### STORE-INV-03: Capability authority is explicit + +A read capability can derive future box locations, verify records, and decrypt +the corresponding payloads. A write capability can additionally derive +signing and encryption material, create data records, and create tombstones. +If the selected construction lets a writer derive the read capability, FOG +MUST state that fact and MUST NOT present write-only confidentiality as a +property. + +### STORE-INV-04: Box locations rotate + +Every stream index derives a new pseudorandom box ID and per-box key state. +Online roles MUST NOT receive a stream root or a stable capability from which +they can enumerate the sequence. + +### STORE-INV-05: Four independent replicas minimum + +A claim-bearing deployment has at least four eligible storage replicas. Two +final replicas are selected deterministically for a box, and the Composer +selects two distinct intermediate replicas outside that final pair. Fewer +than four disables the intermediate/final disjointness claim rather than +triggering silent fallback. + +### STORE-INV-06: Immutable data, authoritative tombstone + +The first valid data record at an empty box is immutable. An exact duplicate +is idempotent success. A different data record at the same box is conflict. +Only a valid tombstone from the box writer can replace data, and data can never +replace a committed tombstone within that storage-epoch namespace. + +### STORE-INV-07: Persist before export or advancement + +The Composer MUST atomically persist the next stream state, exact immutable +box record, recovery tombstone, request generation, and outbox state before +export. A reader advances only after the complete storage and owning upper +protocol transition commits. + +### STORE-INV-08: Retry has two distinct layers + +Within one courier request generation, every retransmission uses the exact +same fixed `CourierEnvelope` so the courier can deduplicate it. Every network +transmission uses a fresh KEMSphinx packet, route randomness, entry material, +SURB, private reply token, and rendezvous. A later request generation may +reencrypt the same immutable box operation only under explicit bounded rules. + +### STORE-INV-09: Storage ACK is not message delivery + +Courier acceptance means only that the courier accepted bounded work. +Replica commit means only that the stated replicas durably committed a box +record. Neither means that a recipient Composer fetched, authenticated, +committed, displayed, or read the FOG message. + +### STORE-INV-10: Empty read never advances state + +A signed empty result means only that selected replicas observed no record for +that box and epoch when they processed the request. It does not prove permanent +absence and MUST NOT advance the read capability. + +### STORE-INV-11: Fixed traffic classes + +Reads, data writes, tombstone writes, hits, misses, conflicts, retries, +expected errors, and cover operations use the same profile-fixed KEMSphinx and +FOG-WIRE geometry. Timing normalization remains owned by the cover profile. + +### STORE-INV-12: Bounded retention, not archival storage + +Every record, tombstone, key, dedup entry, retry, repair item, and backup copy +has a profile-defined upper lifetime. A peer-supplied timestamp cannot extend +it. FOG does not promise indefinite offline delivery. + +### STORE-INV-13: No capability at the courier + +The courier MUST NOT receive a stream read cap, stream write cap, per-contact +capability root, or a temporary stream capability from which it can enumerate +boxes. The initial profile therefore excludes Pigeonhole `CopyCommand` and +AllOrNothing processing. + +### STORE-INV-14: No downgrade or dynamic shard substitution + +Profiles, storage manifests, shard count, intermediate count, geometry, and +epoch windows are authenticated. Failure MUST NOT select arbitrary replicas, +reuse an old manifest, reduce the replica count, bypass the courier, or parse a +different storage construction. + +## 5. Terminology + +- **stream**: one single-writer sequence of independently addressed boxes; +- **write capability**: secret state that derives future box signing, + encryption, addressing, and tombstone authority; +- **read capability**: secret state that derives future box addressing, + verification, and decryption authority but cannot create valid records; +- **box index state**: evolving counter and KDF state for one stream direction; +- **box ID**: pseudorandom fixed identifier that also participates in record + verification under the selected capability construction; +- **box record**: one fixed authenticated encrypted payload or tombstone; +- **storage epoch**: storage-specific key and retention period, distinct from + the shorter FOG-PKI network epoch; +- **storage manifest**: immutable authority-authenticated replica set, key set, + profile, geometry, and lifetime for one storage epoch; +- **final replicas**: the two manifest replicas selected deterministically for + one box ID; +- **intermediate replicas**: two distinct non-final replicas selected by the + Composer to hide the final pair from the courier; +- **courier request generation**: one immutable envelope and short-lived + courier deduplication lifetime; +- **operation nonce**: random per-generation value visible only after replica + envelope decryption and bound into final-replica receipts; +- **replica receipt**: final-replica-authenticated statement about one box + operation at one epoch and operation nonce; +- **miss**: authenticated observation that no record was present, not proof of + permanent nonexistence; +- **tombstone**: writer-authenticated empty record that prevents later data + resurrection until the namespace expires. + +All integers defined directly by FOG are unsigned network byte order. Fixed +arrays have exactly the profile-defined length. Reserved and padding bytes are +zero after authenticated decryption. Parsers reject truncation, trailing +bytes, non-zero reserved fields, counter wrap, unknown-critical values, and +lengths other than the exact active profile geometry. + +## 6. Stream and Mailbox Model + +### 6.1 Directional pairwise streams + +One pairwise FOG conversation uses two independent streams: + +```text +Alice writer -> Bob reader +Bob writer -> Alice reader +``` + +Each stream root is unique to one relationship, direction, profile, and +generation. Reusing a stream across contacts, applications, groups, or both +directions is forbidden. + +When Bob gives Alice an inbound mailbox grant, Bob first creates the stream, +persists his reader state, and exports the corresponding writer state to +Alice. Alice can then write the stream and, under the BACAP candidate, derive +its read state and tombstone its boxes. This is acceptable only because that +stream contains Alice's own deposits to Bob and no other contact's data. + +The grant MUST NOT provide access to Bob's other inbound streams, outbound +streams, capability registry, message history, local database, storage backup, +or identity keys. + +### 6.2 Capability issuance + +Before exporting a stream grant, the issuing Composer MUST atomically persist: + +- the exact storage profile and stream generation; +- the initial box index and derivation state; +- the local read state and the exported write state; +- the contact and direction binding; +- the authorized storage-manifest range; +- whether the grant is a stream or single-box grant; +- issue, recovery, replacement, and closure status. + +A crash cannot produce two different grants for one persisted stream +generation. An exported write cap is secret bearer material and receives the +same transfer protection as a contact voucher. + +### 6.3 Capability evolution + +After deriving one box, a conforming implementation stages the next index and +evolving KDF state, then securely deletes the retired per-box secret after the +required data record and recovery tombstone are committed locally. + +The active profile sets a maximum box count far below unsigned 64-bit wrap and +a maximum stream lifetime. Stream renewal creates a new independent root and +is authenticated through the existing FOG-MESSAGING ratchet. + +FOG MUST NOT claim capability forward secrecy or backward unlinkability until +the exact construction, state serialization, deletion behavior, and backup +rules have been reviewed. + +### 6.4 Revocation and loss + +A copied read or write capability cannot be remotely revoked. Revocation +means abandoning the stream, distributing a fresh stream grant only to +remaining authorized parties, and allowing old records to expire or be +tombstoned where safe. + +Lost or rolled-back stream state cannot be reconstructed from online replicas. +A stale Composer restore places the affected stream in `RECOVERY_REQUIRED`. +It MUST NOT resume the old index or probe successive boxes in an attempt to +guess live state. + +### 6.5 One-time drops + +`fog-drop` requires a genuinely attenuated single-box writer grant. The grant +must expose only the signing and encryption authority for one exact box and +must not contain a future stream derivation state. + +The BACAP/Pigeonhole candidate is not activated for one-time drops until a +reviewed upstream or separately reviewed attenuation method supplies this +property. Exporting an ordinary unbounded BACAP write cap and merely asking a +sender to use it once is not a one-time capability. + +## 7. Storage Replica Manifest + +### 7.1 Purpose + +Final-shard selection must not change because ordinary PKI epochs add, remove, +or reorder descriptors while stored data remains live. FOG therefore uses one +immutable `StorageReplicaManifest` for each storage epoch. + +The current, previous, and announced next manifests are carried as complete +authority-authenticated objects by FOG-PKI consensus according to their +activation and retention windows. Consumers MUST NOT merge replica lists from +different manifests. + +### 7.2 Canonical object + +The public object uses `FOG-PKI-CBOR-1` and has this ordered body: + +```text +[ + network_id, + storage_manifest_version, + storage_epoch, + storage_profile_id, + geometry_compatibility_id, + valid_from, + fresh_until, + valid_until, + acceptance_window_id, + retention_profile_id, + cover_profile_id, + shard_function_id, + final_replica_count, + intermediate_replica_count, + minimum_replica_count, + ordered_replica_records, + previous_storage_manifest_hash +] +``` + +Each `ordered_replica_records` entry contains: + +```text +[ + replica_node_id, + node_descriptor_hash, + operator_id, + family_ids, + infrastructure_group_ids, + replica_envelope_algorithm_id, + replica_envelope_key_id, + replica_envelope_public_key, + replica_receipt_algorithm_id, + replica_receipt_key_id, + replica_receipt_public_key, + supported_storage_profile_ids +] +``` + +Replica records are sorted by complete `replica_node_id`, duplicate-free, and +byte-identical for every authority signer. One node, operator, prohibited +family, or prohibited infrastructure group MUST NOT occupy more than one of +the four positions used by one claim-bearing operation. + +The body is wrapped as one registered `FOG-PKI-CBOR-1` signed object and one +authenticated envelope containing independent authority signatures over +identical canonical bytes. It is valid only with the active FOG-PKI quorum. + +### 7.3 Sign-once and transition + +Authorities persist the manifest hash for a storage epoch before releasing a +signature. Signing two different manifests for one storage epoch is +equivocation evidence. + +The next manifest is announced early enough that every replica can generate +its purpose-separated next envelope and receipt keys and every offline +Composer can import it. The exact overlap is profile-defined. Failure to +produce a valid manifest stops new storage work for that epoch. + +Ordinary node removal does not rewrite an active storage manifest. Emergency +revocation can stop new operations to a compromised replica, but cannot +silently remap live boxes. Availability loss and a new manifest are handled as +an explicit storage transition. + +## 8. Storage Profile Registry and Epochs + +### 8.1 Immutable profile + +Every active storage profile maps one non-zero unsigned 32-bit identifier to +exact dependencies and behavior. An identifier MUST NOT be reused after any +primitive, field, size, shard rule, epoch rule, limit, response, or failure +behavior changes. + +An exact profile record includes at least: + +```text +[ + storage_profile_id, + capability_specification_and_revision, + capability_primitive_suite_id, + capability_encoding_id, + box_id_length, + box_signature_length, + box_payload_aead_id, + replica_envelope_specification_and_revision, + replica_envelope_suite_id, + replica_receipt_signature_suite_id, + envelope_hash_id, + shard_function_id, + courier_envelope_format_id, + courier_reply_format_id, + replica_request_format_id, + replica_response_format_id, + final_replica_count, + intermediate_replica_count, + minimum_replica_count, + storage_epoch_schedule_id, + key_acceptance_window_id, + retention_profile_id, + geometry_compatibility_id, + box_plaintext_capacity, + courier_dedup_retention, + request_generation_limit, + polling_profile_id, + cover_profile_id, + resource_limit_profile_id, + conformance_vector_set_id +] +``` + +The accepted consensus authorizes exact numeric profile IDs. Operators do not +select primitives, shard counts, geometry, retention, or parser behavior. +Incomplete candidates receive no numeric ID. + +### 8.2 Storage epoch + +The storage epoch is distinct from the FOG-PKI network epoch. Its duration, +origin, key-generation deadline, acceptance overlap, record namespaces, and +garbage-collection boundary are fixed by the authenticated storage profile. + +The candidate acceptance window is structurally equivalent to previous, +current, and next storage envelope keys around a boundary. Exact durations are +not selected in this draft. A request outside the accepted manifest and key +window is rejected without attempting every historical key. + +Replica clocks use the same explicit uncertainty model as FOG-PKI. A request, +courier, replica peer, HTTP header, or operator cannot supply authoritative +time or extend a record lifetime. + +### 8.3 Retained manifests and keys + +A role retains exactly the manifest, public verification material, and private +role keys needed for the profile's bounded previous, current, next, and drain +windows. It rejects older work and deletes expired private envelope and +receipt keys after all accepted request and response lifetimes end. + +The Composer retains public old-manifest material only while a live record may +still be read or a receipt may still be verified. A long-offline Composer must +import an authenticated manifest history, not accept a relay-provided current +replica set on trust. + +## 9. Fixed Formats and Geometry + +### 9.1 Encoding rules + +Online storage operations use fixed binary structures, not CBOR, maps, +compression, generic RPC, or packet autodetection. Every active profile gives +one exact length for each structure and one exact FOG-WIRE record count. + +All read, write, tombstone, miss, hit, conflict, accepted, result, and cover +bodies have equal outer geometry within the active traffic class. Fields that +are absent for one semantic operation are zero before authenticated +encryption. + +### 9.2 Courier envelope prefix + +The fixed `CourierEnvelope` begins with this exact 192-byte prefix: + +```text +offset length field +0 2 storage_format_version +2 1 envelope_kind +3 1 flags +4 4 storage_profile_id +8 8 storage_epoch +16 32 storage_manifest_hash +48 32 intermediate_replica_id_0 +80 32 intermediate_replica_id_1 +112 32 replica_envelope_key_id_0 +144 32 replica_envelope_key_id_1 +176 1 preferred_response_slot +177 1 response_slot_count +178 14 reserved +``` + +`envelope_kind` is `OPERATION`, `flags` is zero, `response_slot_count` is 2, +and `preferred_response_slot` is 0 or 1. The two intermediate IDs are distinct +and in canonical ascending order; preference is an independent CSPRNG choice +and does not reorder them. + +The prefix is followed by profile-fixed areas: + +```text +client_ephemeral_public_key[P] +intermediate_dek_0[D] +intermediate_dek_1[D] +encrypted_inner_request[Q] +zero_padding[Z] +``` + +The complete body, including `Z`, is exactly the compatible KEMSphinx +`user_forward_payload_length`. The multi-recipient envelope authenticates the +prefix and encrypts the complete fixed inner request to either intermediate +replica. The courier cannot decrypt it. + +The courier request hash is: + +```text +envelope_hash = HASH( + storage_profile_domain || complete_fixed_CourierEnvelope +) +``` + +The profile fixes the hash and domain. The hash is a short-lived courier-local +deduplication handle, not a message ID, box ID, account, capability, or +cross-generation identifier. + +### 9.3 Inner replica request + +After envelope decapsulation, an intermediate obtains one fixed +`ReplicaInnerRequest`: + +```text +[ + inner_format_version, + operation_kind, + flags, + storage_profile_id, + storage_epoch, + storage_manifest_hash, + operation_nonce[32], + box_id[B], + box_signature[S], + fixed_box_payload[X], + zero_padding +] +``` + +`operation_kind` is `READ`, `WRITE_DATA`, or `WRITE_TOMBSTONE`. For READ, the +signature and payload areas are zero. For WRITE_DATA, they contain the exact +capability-authenticated record. For WRITE_TOMBSTONE, the signature +authenticates the candidate construction's canonical empty payload and the +fixed payload area is zero. + +The operation nonce contains 256 CSPRNG bits and is unique per courier request +generation. It is not a capability and does not replace box-record +authentication. + +### 9.4 Courier reply + +Every anonymous reply begins with one exact 64-byte prefix: + +```text +offset length field +0 2 storage_reply_format_version +2 1 reply_kind +3 1 actual_response_slot +4 4 storage_profile_id +8 32 envelope_hash +40 1 outer_status +41 23 reserved +``` + +It is followed by one fixed opaque replica-response area `R` and zero padding +to the compatible KEMSphinx reply capacity. `reply_kind` is `ACCEPTED`, +`RESULT`, or `COARSE_REJECT`. ACCEPTED and COARSE_REJECT have a zero response +area. RESULT contains one replica-encrypted aggregate response from an +intermediate. + +The reply size, KEMSphinx route, and external schedule do not reveal the kind. +The courier necessarily knows which coarse reply it constructed but does not +learn the encrypted replica outcome. + +### 9.5 Replica commands + +FOG-WIRE command bodies are fixed by the same compatibility profile: + +- `REPLICA_REQUEST` carries one envelope hash and the fixed public-key, + encapsulation, and ciphertext fields required by one intermediate; +- `REPLICA_RESPONSE` carries the envelope hash, response slot, and one fixed + client-encrypted aggregate reply; +- `REPLICA_SYNC` carries one fixed `SHARD_OPERATION`, `SHARD_RESPONSE`, or + bounded repair body between storage replicas. + +Subtypes are authenticated inside their owning fixed body. FOG-WIRE context +and command selection do not authorize a different storage profile. + +### 9.6 Geometry equations + +For a KEMSphinx forward capacity `U`: + +```text +192 + P + (2 * D) + Q + Z = U +``` + +The encrypted inner capacity must satisfy: + +```text +message_envelope_length + + capability_payload_overhead + + inner_request_overhead + + replica_envelope_overhead + <= Q +``` + +The compatible reply must hold one fixed box record, two final-replica +receipts, aggregate framing, and response encryption. FOG-WIRE must hold the +same objects in its declared record counts. + +No document may treat the calculated 4,096-byte KEMSphinx candidate user +payload as the box or message capacity before a byte-exact geometry calculator +proves every equation in both directions. + +## 10. Replica and Courier Selection + +### 10.1 Eligible set + +The Composer uses only the ordered replica records in the accepted manifest. +Every selected replica must support the exact active storage, envelope, +receipt, and FOG-WIRE profiles and have an unrevoked current endpoint in the +accepted FOG-PKI consensus. + +A claim-bearing manifest has at least four storage replicas controlled by +distinct operators and prohibited-family domains. Co-location or shared +credentials invalidate the independence assumption even if node IDs differ. + +### 10.2 Final shard pair + +The profile's deterministic shard function scores every eligible replica from +the exact manifest using a domain-separated hash over at least the manifest +hash, replica storage identity, and box ID. The two lowest distinct scores are +the final shard pair, with complete node ID as the deterministic tie breaker. + +The candidate deliberately adds FOG network, profile, and manifest domain +binding to the Pigeonhole-derived two-shard selection. This is not byte- +compatible with Katzenpost `Shard2` and requires independent analysis, +byte-identical vectors, and a distinct immutable profile. + +Every Composer and replica computes the pair byte-identically. The courier is +not given the box ID or pair. + +### 10.3 Intermediate pair + +The Composer excludes the final pair, then chooses two distinct intermediate +replicas uniformly with its CSPRNG from the remaining eligible set. It rejects +a choice that repeats a prohibited operator, family, or infrastructure domain +across the courier and four storage positions when the active claim requires +that diversity. + +At exactly four replicas, the two non-final replicas are the only valid +intermediate set. At three or fewer, claim-bearing work stops. A local fixture +may use a named functional-test profile that makes no intermediate/final +unlinkability claim. + +### 10.4 Courier + +The Composer selects an eligible courier independently from the storage set +under the authenticated route and diversity policy. One courier request +generation remains pinned to that courier because its dedup cache holds the +operation state. + +A bounded later generation MAY use another courier after the previous +generation's lifetime and reply material expire. It keeps the same box record +and normally the same intermediate pair to avoid progressively exposing the +box to more storage roles. Exact failover and exposure limits belong to the +retry profile. + +## 11. Write Processing + +### 11.1 Composer write transaction + +FOG-MESSAGING first supplies one already committed immutable +`MessageEnvelope`. The storage writer then MUST: + +1. verify that no earlier box is still unresolved for the stream; +2. clone the current write-capability state; +3. derive one box ID, payload key, signing key, and next stream state; +4. encrypt and authenticate the fixed message envelope as one data box; +5. create the exact signed data record and a canonical signed recovery + tombstone for the same box; +6. select the manifest, final pair, intermediate pair, courier, and envelope + keys under the accepted profiles; +7. build one exact immutable `CourierEnvelope` request generation; +8. atomically persist the next stream state, data record, tombstone, immutable + courier envelope, selection, retry state, and storage outbox status; +9. erase retired per-box private material and discarded staged state; +10. only after commit, make the KEMSphinx operation exportable. + +A crash before step 8 exports nothing. A crash after step 8 recovers the exact +box and courier envelope without re-deriving the box or reusing a per-box key. + +Only one box write per stream is externally outstanding. Later application +messages may queue locally but cannot skip an unresolved storage index. + +### 11.2 Courier dispatch + +After terminal KEMSphinx validation, the courier: + +1. strictly parses the fixed envelope and accepted manifest window; +2. computes the envelope hash; +3. checks the bounded dedup cache; +4. on a cache miss, allocates one bounded state entry and dispatches exactly + one fixed request to each named intermediate replica; +5. uses the current request's SURB for one scheduled ACCEPTED or cached RESULT + reply and does not retain the SURB; +6. caches at most two fixed opaque replica responses until the dedup deadline. + +A cache hit never dispatches the request again. If a cached result is +available, the courier can return it using the fresh SURB carried by the +retransmission. Otherwise it returns ACCEPTED. All replies remain subject to +the external schedule and one-response amplification bound. + +### 11.3 Intermediate processing + +Each intermediate replica: + +1. authenticates the courier through FOG-WIRE; +2. validates exact profile, manifest, key ID, epoch, ciphertext, and limits; +3. decapsulates and authenticates the complete inner request; +4. recomputes and validates the two final replicas; +5. dispatches the exact operation to both final replicas through fixed + `REPLICA_SYNC` shard-operation bodies; +6. collects bounded final receipts without treating a link ACK as a commit; +7. builds one fixed aggregate response encrypted for the Composer ephemeral + key; +8. sends the opaque response to the courier and erases request secrets and + transient box state after the retry window. + +It performs no application parsing and never retains a stream capability. + +### 11.4 Final write transaction + +A final replica verifies the manifest, epoch, operation nonce, box ID, box +signature, record size, and storage quota before mutation. It then applies one +atomic transaction: + +- empty plus valid data: insert the complete record; +- same exact data: idempotent success; +- different data at the same box: conflict, no overwrite; +- any state plus valid tombstone: store the tombstone and delete live data; +- tombstone plus data: tombstone wins, reject resurrection; +- same valid tombstone: idempotent success. + +The replica durably commits database and required local journal state before +signing a commit receipt. A transport ACK, queued write, in-memory update, or +unflushed batch is not a commit receipt. + +### 11.5 Writer completion + +The Composer verifies receipts under the exact two final-replica receipt keys +from the manifest. `REPLICA_QUORUM_COMMITTED` requires matching durable +receipts from both final replicas for the same manifest, epoch, nonce, box ID, +record digest, and result. + +One receipt is degraded evidence, not quorum. A courier ACCEPTED reply is not +replica evidence. The writer retains and retries the immutable box until: + +- two matching commit receipts arrive; +- the receiving Composer's later message-level commit ACK proves successful + retrieval; +- a valid recovery tombstone is committed to both replicas; or +- the operation enters explicit `UNCERTAIN` or `RECOVERY_REQUIRED` state. + +The next storage box is not exported while the current box remains unresolved. + +## 12. Read, Empty, and Tombstone Processing + +### 12.1 Read query + +The reader clones but does not advance its current capability state, derives +the expected box ID, selects the manifest and roles, generates a fresh +operation nonce, and atomically persists one immutable read request generation +before export. + +Retransmission within that generation uses the same courier envelope and fresh +KEMSphinx and reply material. A later poll after a terminal result uses a new +operation nonce and new courier envelope but derives the same box ID until the +stream advances. + +### 12.2 Final read response + +Each final replica atomically reads one box and returns one fixed response: + +- `DATA`: complete record and its digest; +- `TOMBSTONE`: complete signed tombstone and its digest; +- `MISS`: no record observed for the box in that namespace; +- `CONFLICT` or coarse local failure where internal consistency is broken. + +The final replica signs a receipt over the network, storage profile, manifest, +epoch, operation nonce, box ID, result code, and record digest. MISS uses the +canonical empty digest. FOG-WIRE protects the receipt between the final and +intermediate replica. The intermediate then places it in the fixed aggregate +response encrypted toward the Composer, so the courier cannot inspect it. + +A receipt authenticates what one replica reported. It does not make a +malicious replica honest or make absence permanent. + +### 12.3 Aggregate result + +An intermediate aggregate contains two final-replica receipts and at most one +complete fixed box record. The record digest must match both receipts before +the Composer treats the final replicas as converged. + +The Composer handles results as follows: + +- matching DATA receipts: verify box signature, decrypt, validate fixed + payload and pass it to the owning upper protocol transaction; +- matching TOMBSTONE receipts: verify the writer signature, atomically advance + the read capability, and emit no message content; +- matching MISS receipts: report one empty poll locally and do not advance; +- one DATA and one MISS: treat as replication lag and retry under schedule; +- one TOMBSTONE and one older DATA: wait for tombstone convergence; +- different valid data digests: freeze the stream as `STORAGE_CONFLICT`; +- missing, invalid, wrong-manifest, wrong-nonce, or wrong-box receipt: discard + without advancement. + +### 12.4 Cross-layer receive commit + +For a DATA result, storage payload acceptance and FOG-MESSAGING acceptance are +one Composer transaction. The implementation stages the next read-capability +state, storage dedup state, message authentication, ratchet state, reassembly, +inbox, and message ACK state, then commits them together before rendering. + +If the box is authentically written but contains an invalid FOG-MESSAGING +object, the Composer atomically records a bounded poisoned-slot marker and MAY +advance the storage stream without advancing the message ratchet. It sends no +automatic error oracle. This behavior is permitted only for a box whose writer +authentication is valid, because otherwise an online attacker could skip +stream positions. + +The exact malicious-contact UI and quarantine retention belong to +FOG-COMPOSER, but their count and byte limits belong to the storage profile. + +### 12.5 Meaning of empty + +MISS is an expected asynchronous state. It can arise because the writer has +not written, replication is delayed, a replica is malicious, a request reached +the wrong retained epoch, or data expired. + +The reader polls again only under its authenticated retrieval schedule. It +does not immediately retry on MISS, advance to the next box, switch to a +direct replica, or send a message-level acknowledgment. + +## 13. Courier Deduplication, Retry, and Reply State + +### 13.1 Courier cache + +One courier cache entry contains only: + +```text +[ + envelope_hash, + storage_profile_id, + storage_epoch, + storage_manifest_hash, + created_monotonic_time, + intermediate_replica_ids[2], + dispatch_state[2], + opaque_response_slots[2], + terminal_deadline +] +``` + +It contains no SURB, capability, box ID, operation kind, final pair, message +identifier, user identifier, or application state. Cache memory and total +entries are bounded globally and per authenticated replica relationship. + +The cache MAY be volatile because final operations are idempotent. A courier +restart may redispatch an exact request, but cannot change the box record. +Persistent cache, if used, has a dedicated sealing key and the same strict +expiry. + +### 13.2 Request generation + +The Composer persists these minimum states: + +- `READY`: immutable generation committed but not exported; +- `IN_FLIGHT`: exported and eligible for exact retransmission; +- `COURIER_ACCEPTED`: courier accepted bounded work, durability unknown; +- `REPLICA_QUORUM_COMMITTED`: two matching final receipts verified; +- `DEGRADED`: only one matching final receipt or replica unavailable; +- `EMPTY`: one completed read poll returned matching MISS receipts; +- `TOMBSTONED`: matching valid tombstone receipts committed; +- `CONFLICT`: incompatible authenticated final states; +- `EXPIRED`: request-generation retry limit reached; +- `UNCERTAIN`: local durability or remote outcome cannot be resolved; +- `CANCELLED`: local cancellation before a security-critical transition. + +The message outbox remains separate. Storage quorum does not set a message to +ACKED, and message ACK may safely terminate storage retry after proving the +receiver committed the message. + +### 13.3 Retransmission + +Within one request generation, the Composer reuses exact `CourierEnvelope` +bytes and pins the courier and intermediate pair. Every transmission creates a +fresh packet, SURB, private reply token, route, entry material, and transfer +bundle identifier. + +The profile sets maximum attempts, maximum generation age, jitter, backoff, +cache retention, result polling, and maximum later generations. No retry is +immediate or seeded from a box, envelope, contact, or message identifier. + +A later generation builds a new inner request around the same immutable box +record and operation kind, using a fresh operation nonce and ephemeral +envelope key. It starts only after old reply +material and the previous courier cache window end, and it remains subject to +the fixed exposure budget. + +### 13.4 Reply material + +The courier never stores a SURB for later use. It consumes the fresh SURB from +the current KEMSphinx request for at most one fixed reply. If no result is ready +it returns ACCEPTED; the Composer later supplies a new single-use SURB by +retransmitting the same courier envelope. + +No result causes a direct second reply, and no storage ACK causes an ACK of its +own. This keeps amplification at one anonymous reply per accepted request. + +## 14. Replica Receipts and Consistency + +### 14.1 Receipt body + +A final-replica receipt is a profile-fixed canonical object whose signed body +contains: + +```text +[ + receipt_format_version, + network_id, + storage_profile_id, + storage_manifest_hash, + storage_epoch, + final_replica_id, + replica_receipt_key_id, + operation_nonce, + box_id, + operation_kind, + result_code, + record_digest +] +``` + +The signature suite is fixed by the storage profile and key record. A result +cannot name or negotiate its verifier. Receipt verification occurs only after +the enclosing replica response authenticates and parses. + +Receipts are sensitive metadata because they contain box IDs. They are visible +only to the processing storage replicas, then remain inside client-encrypted +replies and Composer state. They MUST NOT be published as generic monitoring +or transparency evidence. + +### 14.2 Durability model + +The initial structural profile has two final replicas and requires two +matching receipts for ordinary write quorum or read convergence. It does not +claim Byzantine consensus, linearizability, permanent durability, or global +read-after-write ordering. + +A malicious final replica can sign false state, withhold work, delete data, or +equivocate. Two independent receipts make the responsible identities locally +detectable but do not restore deleted data. A malicious intermediate can +withhold or reorder receipts but cannot forge a valid final receipt under the +selected signature assumption. + +### 14.3 Conflicts and tombstone precedence + +Two different valid data records for one box indicate writer-state cloning, +malicious writer behavior, or an implementation failure. Replicas preserve +first-write-wins locally, and the Composer freezes on cross-replica divergence. + +A valid tombstone has permanent precedence within its storage-epoch namespace. +Repair always propagates tombstone over data and never propagates data over a +tombstone. + +## 15. Retention, Tombstones, and Garbage Collection + +### 15.1 Record retention + +Each record belongs to one storage-epoch namespace and receives its expiry +only from the authenticated manifest and retention profile. A writer cannot +request longer retention, and a replica cannot selectively extend one user's +record. + +The active profile publishes the minimum and maximum retrieval window in +ordinary time units and storage epochs. Public documentation MUST explain that +an offline recipient who does not retrieve within that window can lose data. + +The candidate evaluates the Pigeonhole previous-and-current epoch retention +shape, but no duration is selected until offline-usage, capacity, abuse, and +cost simulations are complete. + +### 15.2 Tombstone creation + +The writer creates a canonical signed empty record. It is externally the same +size class as read and data operations. The courier cannot distinguish it, but +the replicas necessarily learn that a valid box is being tombstoned. + +A recovery tombstone is generated and stored locally with every data box before +the per-box signing key is erased. It may be exported only under explicit +abandon, recovery, user deletion, or profile policy. + +Routine successful delivery SHOULD normally rely on bounded natural expiry +rather than an immediate tombstone correlated with recipient activity. If +automatic tombstoning is enabled, its minimum hold, random delay, and cover +schedule are authenticated profile parameters. + +### 15.3 Replica tombstone state + +After durable tombstone commit, a replica deletes the live ciphertext and +retains the minimum authenticated tombstone record needed to reject +resurrection. That record remains until the complete namespace expires. + +Garbage collection MUST delete data and tombstones by deterministic epoch +policy, not access recency, contact activity, request frequency, or operator +preference. It runs under bounded IO and cannot block wire processing without +backpressure. + +### 15.4 Deletion limits + +A tombstone is not proof that every historical copy vanished. A recipient may +already hold plaintext; an attacker may have copied ciphertext; storage media, +journals, snapshots, and backups may retain blocks until their documented +destruction window. + +Replica backups use separate encryption keys and bounded retention. Restore +MUST NOT resurrect expired data or replace a tombstone with older live data. +The operator documents the maximum backup-deletion delay and tests restore +against tombstone and expiry state. + +### 15.5 Expired-gap recovery + +Because MISS never advances a reader, a box that expires before retrieval can +block every later box in that stream. If the writer still has an unacknowledged +immutable data record, it MAY reemit that exact record into a currently valid +storage-epoch namespace under a new bounded request generation. If the writer +has abandoned the data, it reemits the precomputed signed tombstone instead. + +The reader advances only after a converged authenticated DATA or TOMBSTONE +result. Cross-epoch reemission may reveal the same box to additional replicas +or link epochs and therefore has a profile exposure limit and simulation gate. +If neither the writer nor a fresh authenticated recovery session is available, +the reader cannot safely skip the gap. + +## 16. Replication, Repair, and Manifest Transition + +### 16.1 Normal replication + +Both intermediate replicas dispatch the authenticated operation to the same +two deterministic final replicas. Final writes are idempotent, so duplicate +paths cannot create duplicate boxes or change first-write-wins behavior. + +The profile fixes the maximum fan-out. One courier request creates at most two +courier-to-intermediate requests, four intermediate-to-final shard operations, +bounded responses, and one anonymous client reply. Implementations MUST NOT +increase fan-out after failure. + +### 16.2 Repair + +`REPLICA_SYNC` supports bounded repair only between the two final replicas for +one manifest and retained epoch. A repair body carries one complete +authenticated box record or tombstone and the evidence required by the +profile. The receiving replica independently verifies the box signature, +manifest, epoch, digest, and tombstone precedence before commit. + +Repair traffic uses fixed FOG-WIRE shapes and the authenticated store-to-store +schedule. It MUST NOT expose an unbounded database listing, stream sequence, +capability root, or arbitrary range query. + +### 16.3 Replica unavailability + +One unavailable final produces degraded durability and no ordinary quorum. +The operation retries under schedule but does not substitute an arbitrary +third final, because that would change deterministic location and reveal more +replicas. + +If a manifest cannot meet its deployment assumptions, new work stops or uses +an already authorized lower-claim functional profile. Existing data remains +bound to the old manifest until expiry. + +### 16.4 Manifest transition + +A new storage epoch creates a new immutable manifest and new replica envelope +and receipt keys. Old and new manifests coexist only for the explicit read, +retry, key, and drain windows. + +FOG does not silently migrate live boxes to a new final set. A writer that +needs a longer-lived logical message re-emits it through a new authenticated +storage operation or renewed stream according to the upper protocol. Cross- +epoch re-emission remains bounded and may expose linkability at replicas, so it +requires explicit simulation and profile rules. + +## 17. Cover, Empty Reads, and Failure Privacy + +### 17.1 Traffic scheduling + +Fixed bytes do not hide record counts or time. Any claim that reads, writes, +misses, or tombstones are externally indistinguishable requires: + +- Composer operations entering the same authenticated client schedule; +- KEMSphinx packets and replies using one geometry; +- courier-to-replica and replica-to-replica links using fixed-throughput or + independently scheduled fixed slots with valid cover; +- retry and result polling independent of application type; +- overload and shutdown behavior included in the simulation. + +Exact rates and distributions remain cover-profile selections. Operators MUST +NOT tune them independently in a claim-bearing deployment. + +### 17.2 Role-local distinguishability + +The courier can distinguish ACCEPTED from a cached opaque RESULT but cannot +decrypt the result. Intermediate and final replicas necessarily distinguish +operation types after envelope decryption. FOG claims only the information +exposure stated in Section 3, not perfect role-local indistinguishability. + +### 17.3 Repeated empty polling + +Repeated reads of one not-yet-written box reveal the same box ID to its final +replicas. Changing KEMSphinx routes, couriers, envelopes, or intermediates does +not remove that final-replica link. + +The retrieval profile therefore bounds polling frequency, uses cover traffic, +avoids immediate reaction to a write or MISS, and includes this leakage in +long-term intersection simulation. FOG makes no unconditional claim that an +honest-but-curious final replica cannot recognize repeat polls. + +### 17.4 Failure classes + +Online remote failures are coarse: + +- malformed or unauthorized envelope: uniform drop or fixed COARSE_REJECT; +- accepted bounded work: fixed ACCEPTED; +- available opaque result: fixed RESULT; +- overload or timeout: fixed coarse outcome under the cover schedule. + +Detailed DATA, TOMBSTONE, MISS, conflict, database, quota, receipt, and +replication results remain inside Composer-encrypted responses where possible. +No error is larger than the request, and one request produces at most one +anonymous reply. + +## 18. Resource Limits and Storage DoS + +Capability-based addressing is not a complete admission system. An attacker +can create unlimited self-owned capabilities, valid signatures, boxes, reads, +or tombstones. Random box IDs prevent guessing another stream but do not +prevent storage exhaustion. + +Every active profile MUST set lower limits within explicit version maxima for: + +- accepted envelope bytes and cryptographic work; +- courier cache entries, bytes, generations, dispatches, and lifetime; +- intermediate in-flight requests, final fan-out, retries, and response bytes; +- final records, bytes, writes, reads, tombstones, receipts, and repair work; +- per-peer FOG-WIRE queues and connections; +- per-manifest and per-storage-epoch total capacity; +- Composer streams, pending boxes, empty polls, poisoned slots, and retained + receipts; +- backup size, journal size, garbage-collection work, and restore input; +- cover backlog and degraded-mode thresholds. + +At minimum, the first structural profile has: + +- exactly two intermediates and two finals per request; +- no more than one anonymous reply per request; +- one unresolved external box per stream; +- no operator-controlled variable replication factor; +- no recursive operation, generic batch, arbitrary range, or server-side + capability traversal; +- no CopyCommand or AllOrNothing operation; +- no requester-selected expiry or replica list outside the manifest. + +Authentication and size validation occur before database mutation. Allocation +is bounded before public-key work where the format permits. Signature-valid +new writes are still subject to global epoch capacity and overload policy. + +When full, a replica preserves already committed records and tombstones until +their policy expiry and rejects new allocation with a coarse fixed response. +It MUST NOT evict according to access recency, user activity, application type, +or payment outside a separately reviewed admission protocol. + +Safe anonymous admission and fair rate control remain open. Until resolved, +FOG does not claim resistance to a determined distributed storage-flooding +adversary. + +## 19. Profile and State Transitions + +### 19.1 Stream states + +The minimum Composer stream states are: + +- `ISSUED`: reader state persisted and writer grant ready for export; +- `ACTIVE`: one writer and one reader state are valid; +- `WRITE_PENDING`: one immutable box and request generation are outstanding; +- `READ_POLLING`: the current box has a persisted read request generation; +- `RENEWING`: a new stream generation is authenticated through messaging; +- `STORAGE_CONFLICT`: replicas returned incompatible authenticated state; +- `FROZEN_CAPABILITY_CHANGE`: an unexpected grant or state change is present; +- `RECOVERY_REQUIRED`: rollback, cloning, loss, or compromise prevents reuse; +- `CLOSED`: no new boxes may be created. + +State files are not an API. A transition occurs only through the atomic +Composer operations defined here and in FOG-MESSAGING. + +### 19.2 Storage profile transition + +A contact switches storage profiles only through an authenticated +FOG-MESSAGING control transition that binds the exact new profile, stream +generation, capabilities, first index, and manifest activation. + +Old and new streams remain separate. There is no profile trial, mixed +capability derivation, dual decryption of one box, or fallback after failure. +The old stream drains only through its previously authorized profile and +retention window. + +### 19.3 Compromise + +Compromise of a write cap permits reading where derivable, arbitrary future +writes, conflicting writes, and tombstones for that stream. Compromise of a +read cap permits future location derivation and decryption but not valid writes +under the selected construction. + +Response requires stopping the stream, replacing the Composer or capability +state, distributing a new stream through an authenticated contact session, +and allowing old data to expire. Rotation to the next box does not remove an +attacker who copied the root capability. + +## 20. Candidate BACAP and Pigeonhole Integration + +### 20.1 Candidate definition + +`FOG-STORAGE-CANDIDATE-BACAP-PIGEONHOLE-1` evaluates: + +- the maintained BACAP implementation and its deterministic blinded Ed25519 + box sequence, evolving KDF state, signatures, and authenticated payload + encryption; +- the maintained Pigeonhole single-box courier and scattered-replica flow; +- two deterministic final replicas, two disjoint random intermediates, and at + least four eligible replicas; +- one fixed multi-recipient envelope for the two intermediates; +- identical courier-envelope retransmission with fresh Sphinx-family packet + and SURB material; +- FOG storage manifests, final-replica receipts, fixed binary formats, atomic + Composer state, and FOG-WIRE commands defined here. + +The candidate does not adopt the Katzenpost client daemon, PKI epochs, +transport wire bytes, service discovery, Sphinx geometry, or operator +configuration as FOG runtime dependencies. + +### 20.2 Deliberate exclusions and deviations + +The initial FOG candidate excludes Pigeonhole CopyCommand and AllOrNothing +streams because they give the courier a serialized write capability and make +it traverse capability-derived boxes. That behavior conflicts with the FOG +courier boundary. + +The candidate adds final-replica receipts so the offline Composer can +distinguish courier acceptance from evidence of final durable commit. This is +a FOG protocol extension requiring its own cryptographic and metadata review. + +FOG-MESSAGING fragmentation remains above storage. Multiple fragments are not +atomically visible at replicas; the receiving Composer releases content only +after authenticated complete reassembly. + +### 20.3 Claims deliberately withheld + +The candidate does not yet establish: + +- direct Katzenpost Pigeonhole interoperability; +- quantum-resistant box authenticity, capability security, or complete + storage confidentiality; +- unlinkability if the courier and relevant replicas collude; +- unlinkability of repeated reads to one empty box at a final replica; +- deletion of every backup or adversarial copy; +- Byzantine consistency, guaranteed delivery, or permanent durability; +- DoS resistance or anonymous fair admission; +- safe one-box attenuation for `fog-drop`; +- compatibility with the calculated 4,096-byte KEMSphinx payload; +- implementation, constant-time, side-channel, or secure-deletion safety. + +### 20.4 Activation gates + +Before promotion to an active numeric profile, FOG MUST freeze and verify: + +1. exact BACAP, Pigeonhole, KDF, signature, AEAD, hash, envelope, and receipt + revisions and source commits; +2. whether the reviewed capability semantics and security analysis cover the + exact exported writer and reader state used by FOG; +3. byte-exact capability, box, courier, replica, receipt, and manifest + serialization; +4. complete forward and reply geometry against one exact KEMSphinx and + FOG-WIRE profile; +5. transcript, network, profile, manifest, epoch, box, and operation-nonce + domain binding without changing upstream primitives silently; +6. deterministic shard and intermediate-selection vectors; +7. crash, rollback, duplicate, conflict, tombstone, repair, expiry, backup, + and manifest-transition behavior; +8. loss, delay, replay, empty polling, courier restart, and replica failure; +9. CPU, memory, disk, bandwidth, amplification, flood, and GC limits; +10. implementation maturity, dependency licensing, side-channel behavior, and + secret deletion; +11. independent cryptographic and implementation review before public claims. + +A required change to BACAP derivation, signing, or encryption creates a +separately identified candidate rather than an undocumented FOG variant. + +## 21. Key and Secret Lifecycle + +| Material | Owner | Persistence | Required destruction or transition | +| --- | --- | --- | --- | +| Stream write root and evolving state | one writer Composer | encrypted mutable state, never online | replace by new stream on compromise, clone, rollback, or profile transition | +| Stream read root and evolving state | one reader Composer; writer MAY derive under candidate | encrypted mutable state, never online | replace by new stream on compromise, clone, rollback, or profile transition | +| Per-box signing and payload keys | Composer transaction | transaction only | erase after data record, recovery tombstone, next state, and outbox commit | +| Immutable signed data record | writer Composer and final replicas | bounded outbox and storage-epoch state | remove after message ACK and recovery margin locally; GC by replica retention | +| Signed recovery tombstone | writer Composer, final replicas only after use | encrypted local outbox until resolution | erase locally after terminal retention; retain at replica to namespace expiry | +| Operation nonce | writer or reader Composer | one courier request generation | erase after generation and receipt retention | +| Composer envelope ephemeral private key | requesting Composer | one request generation | erase after all expected replies and retry retention end | +| Intermediate DEKs and shared secrets | Composer and addressed intermediate transaction | immutable public encapsulation plus transient secret | erase transient decapsulation and response secrets after bounded processing | +| Replica envelope private key | one storage replica | previous, current, next storage-key windows only | erase after accepted request and response drain ends | +| Replica receipt private key | one final replica | one storage-manifest key period | stop signing at retirement; erase after receipt verification drain | +| Courier envelope hash and opaque responses | one courier | bounded dedup cache only | expire at courier dedup deadline; never back up by default | +| Intermediate dispatch and aggregate state | one intermediate replica | bounded transient state | erase after response and retry window | +| Final box record or tombstone | two deterministic final replicas | bounded storage-epoch namespace | deterministic GC; tombstone blocks resurrection until namespace expiry | +| FOG-WIRE Noise key | one online node | role-local profile lifetime | never reuse as envelope, receipt, box, or at-rest key | +| Replica storage-at-rest key | one replica operator | deployment-specific protected storage | separate rotation and backup policy, no protocol authenticity claim | +| Replica backup key | one replica recovery domain | separate from backup ciphertext and live store | rotate and destroy under bounded backup-retention policy | + +Capability roots, per-box secrets, box IDs, operation nonces, courier hashes, +receipts, replica ciphertexts, stream indexes, message envelopes, contacts, and +selection details MUST NOT enter logs, metrics, command arguments, crash +reports, public evidence, or support bundles. + +## 22. Logging and Observability + +Couriers and replicas MAY export delayed, thresholded aggregate counts for +capacity, coarse success, overload, expiry, corruption, and repair only when +the observability profile proves that the aggregation cannot expose a small +activity set. + +They MUST NOT export: + +- box IDs, envelope hashes, operation nonces, receipt digests, or signatures; +- intermediate or final selection per request; +- per-record timestamps, read frequency, miss streaks, or tombstone timing; +- capability bytes, payloads, ciphertext samples, or decryption failures; +- per-contact, per-stream, per-courier-request, or per-source histories; +- fine-grained queue, connection, or storage-access event streams. + +Local debugging that enables any prohibited class places the node outside a +claim-bearing profile and must be disabled by default in release builds. + +## 23. Conformance and Adversarial Tests + +Before the local PoC, FOG-STORAGE requires deterministic positive and negative +tests for: + +- capability issue-before-export, one-writer enforcement, index advancement, + serialization, cloning detection, and stale restore; +- exact 192-byte courier prefix and 64-byte reply prefix; +- every fixed body, padding byte, reserved bit, operation, and result; +- manifest canonical encoding, authority signatures, sign-once state, + chaining, membership, key, diversity, and transition checks; +- shard selection and intermediate exclusion across complete vector sets; +- complete forward and reply geometry calculations; +- Composer crash before and after write-state, record, tombstone, envelope, + and outbox commit; +- reader crash before and after the combined storage and messaging commit; +- exact request retransmission with fresh KEMSphinx and SURB material; +- courier cache hit, miss, expiry, corruption, restart, and overload; +- courier behavior without storing a SURB; +- final empty insert, exact duplicate, conflicting data, tombstone overwrite, + tombstone duplicate, and data-after-tombstone rejection; +- two matching receipts, one missing receipt, invalid receipt, wrong nonce, + wrong epoch, wrong manifest, equivocation, and conflicting digests; +- DATA, TOMBSTONE, MISS, replication lag, poisoned message, and stream freeze; +- replica loss, repair, manifest change, key overlap, expiry, GC, backup + restore, and non-resurrection; +- fixed read, write, tombstone, hit, miss, retry, error, and cover geometry; +- request and response amplification bounds; +- malicious capability-holder CPU, memory, disk, queue, receipt, polling, and + notification exhaustion; +- absence of prohibited values from logs, metrics, crashes, and support data; +- candidate upstream vectors plus FOG-specific integration vectors. + +Testing MUST include parser fuzzing, property tests, transaction fault +injection, simulated power loss, disk-full and partial-write faults, clock +uncertainty, race detection, load and flood testing, cross-implementation +vectors, and restore exercises. + +## 24. Threat and Architecture Traceability + +| Requirement | Primary controls | +| --- | --- | +| `ARC-002` | the networkless Composer owns capabilities, box derivation, final verification, and index advancement | +| `ARC-003` | all client storage work traverses entry, every mix layer, courier, and replicas | +| `ARC-004` | box, envelope, KEMSphinx, and Noise protections use separate protocols and keys | +| `ARC-006` | fixed geometry and scheduling classes do not identify native applications | +| `ARC-007` | stream, box, envelope, receipt, wire, disk, and backup keys have distinct owners and purposes | +| `ARC-008` | exact parser, cache, queue, epoch, record, retry, fan-out, storage, and GC bounds | +| `ARC-009` | replica, manifest, profile, capacity, and receipt failures stop without bypass or downgrade | +| `IF-01` | only committed immutable storage request generations enter Composer export | +| `IF-02` | replies are untrusted until KEMSphinx, storage, receipt, capability, and upper-protocol commit | +| `IF-08` | the courier receives only the fixed opaque storage envelope after every mix layer | +| `IF-09` | exact fixed courier/replica and replica/replica commands, receipts, repair, and bounds | +| `IF-10` | one fresh single-use anonymous reply per transmitted request | +| `TM-NET-01` | fixed shapes, independent schedules, cover requirement, bounded retries, no immediate MISS reaction | +| `TM-NET-02` | rotating box IDs, directional streams, disjoint intermediates, fixed manifests, documented repeat-poll risk | +| `TM-NET-03` | no immediate fallback, bounded fan-out, degraded-state gate, traffic simulation | +| `TM-NET-04` | immutable request generations, courier dedup, idempotent final state, non-resurrection | +| `TM-NET-05` | authenticated envelopes, box records, receipts, strict result binding, uniform remote failure | +| `TM-NET-06` | consensus-authorized immutable profile, manifest, geometry, and application-independent behavior | +| `TM-ROLE-03` | courier blindness, disjoint intermediate/final roles, two final receipts, separate state and keys | +| `TM-ENDPOINT-01` | capability secrets stay in the Composer and commit before export or advancement | +| `TM-ENDPOINT-03` | stale restore freezes streams, separate backups, no live-state copying | +| `TM-APP-01` | one contact-specific writer stream, poisoned-slot handling, bounded payload before upper parsing | +| `TM-OPS-01` | no capability, box, request, receipt, selection, or per-record telemetry | +| `TM-CRYPTO-01` | complete lifecycle table, key purpose separation, bounded overlap and destruction | +| `TM-CRYPTO-02` | fixed profile and manifest, withheld PQ claims, no fallback or self-selected suite | +| `TM-AVAIL-01` | fixed fan-out, quotas, capacity limits, idempotency, GC, coarse overload, explicit admission gap | + +## 25. Open Dependencies + +The structural storage contract is fixed, but these dependencies remain open +before an active profile or daemon: + +- exact BACAP, envelope, receipt, hash, signature, AEAD, and KDF revisions and + reviewed implementations; +- resolution of upstream BACAP implementation maturity and audit gaps; +- exact storage epoch duration, manifest overlap, retention, courier-cache, + polling, retry, cover, and shutdown parameters; +- byte-exact forward, reply, FOG-WIRE, box, and messaging geometry; +- a reviewed receipt construction and metadata analysis; +- reviewed single-box capability attenuation for `fog-drop`; +- safe anonymous admission and fair storage-flood control; +- activation evidence for the structural FOG-COMPOSER vault, database, + external-anchor, and recovery profiles; +- future bulk, atomic multi-box, group, and multi-device protocols. + +No implementation convenience may silently resolve these dependencies. + +## 26. Primary References + +- Katzenpost Pigeonhole protocol specification: + +- Katzenpost, Understanding Pigeonhole: + +- Katzenpost HPQC BACAP implementation: + +- Infeld et al., *Echomix: a Strong Anonymity System with Messaging*: + +- FOG threat model: `FOG-THREAT-MODEL.md` +- FOG architecture: `FOG-ARCHITECTURE.md` +- FOG public key infrastructure: `FOG-PKI.md` +- FOG wire protocol: `FOG-WIRE.md` +- FOG Sphinx profile framework: `FOG-SPHINX-PROFILES.md` +- FOG messaging protocol: `FOG-MESSAGING.md` +- FOG Composer protocol: `FOG-COMPOSER.md` + +These references supply a published construction, implementation target, and +design lessons. They do not make the FOG integration secure by inheritance. +FOG still requires exact profiles, geometry, vectors, implementation review, +resource analysis, simulation, deployment evidence, and independent security +review. diff --git a/docs/FOG-SX.md b/docs/FOG-SX.md new file mode 100644 index 0000000..336acae --- /dev/null +++ b/docs/FOG-SX.md @@ -0,0 +1,1032 @@ +# FOG Simplex Transfer + +Status: Draft 0.1 + +Date: 2026-08-08 + +## 1. Purpose + +This document defines `FOG-SX`, the bounded, medium-independent simplex +transfer contract used to move one committed opaque Composer export from a +physically offline Composer toward an online blind relay. + +It fixes: + +- the transfer object and frame byte layouts; +- padded transfer-size classes and their leakage; +- profile selection without runtime negotiation; +- forward-error-correction ownership and candidate gates; +- acknowledgment-free sender and receiver state machines; +- parser, memory, CPU, time, and storage ceilings; +- common physical-backend requirements; +- candidate QR, TOSLINK Lightpipe, and MIDI DIN profiles; +- process isolation, failure behavior, and conformance evidence. + +This document records two non-active FEC candidates and three non-active +physical-backend candidates. No candidate has an active numeric profile ID. +No FOG-SX transfer is release-conformant until one complete joint profile has +passed the activation gates in Section 22. + +The key words MUST, MUST NOT, REQUIRED, SHOULD, SHOULD NOT, and MAY describe +normative requirements in the sense of BCP 14 when they appear in uppercase. + +## 2. Scope + +FOG-SX owns the path: + +```text +committed Composer RELAY_EXPORT bundle + -> sandboxed FOG-SX export encoder + -> physically transmit-only backend + -> one-way medium + -> physically receive-only backend + -> sandboxed FOG-SX transfer receiver + -> bounded reconstructed Composer bundle + -> blind-relay queue +``` + +FOG-SX version 1 carries exactly one complete Composer `RELAY_EXPORT` bundle +per transfer. It does not carry a filename, directory, stream name, MIME type, +URI, archive, compressed object, filesystem, document, script, executable, or +device command. + +FOG-SX does not own: + +- Composer bundle semantics, record allowlists, or spool transitions; +- KEMSphinx, messaging, storage, PKI, contact, update, or recovery + authentication; +- blind-relay acceptance or delivery evidence; +- online-to-offline Composer import; +- a removable-media filesystem format; +- generic optical, serial, audio, or camera device control; +- anonymity, cover traffic, or traffic-analysis resistance on the physical + link. + +Online-to-offline input uses a separately controlled receive-only Composer +path. It is not an FOG-SX acknowledgment channel and is not the reverse +direction of the same peripheral. + +## 3. Security Boundary + +The raw backend stream, every frame header, every symbol, every FEC parameter, +and every reconstructed transfer object are attacker-controlled input. + +The transfer receiver is outside the blind relay's trusted protocol state. It +MUST NOT hold: + +- relay network credentials or Noise private keys; +- Composer, user identity, messaging, storage, or PKI private keys; +- message plaintext, contacts, capabilities, routes, or application state; +- a shell, compiler, package manager, general browser, or general writable + filesystem; +- a bidirectional interface to the offline Composer. + +The receiver outputs only one bounded byte string that still has to pass the +complete `FOG-COMPOSER` bundle parser and every owning inner authentication +rule. FEC recovery, CRC success, SHA-256 equality, transfer-ID equality, +physical directionality, and operator observation confer no authenticity. + +Applicable requirements include `ARC-002`, `ARC-004`, `ARC-007`, `ARC-008`, +`ARC-009`, `IF-01`, `IF-02`, `TM-ENDPOINT-01`, `TM-ENDPOINT-02`, +`TM-OPS-01`, `TM-SUPPLY-01`, `TM-CRYPTO-01`, `TM-CRYPTO-02`, and +`TM-AVAIL-01`. + +## 4. Version-1 Invariants + +Every active FOG-SX version-1 profile MUST preserve all of these invariants: + +1. Data flows in one physical direction during a transfer. +2. The receiver sends no automatic acknowledgment, control, clock, retry, + negotiation, or completion signal to the sender. +3. One exact joint profile is configured before transfer. There is no + autodetection, negotiation, opportunistic upgrade, or downgrade. +4. Frames have one fixed size within a joint profile. +5. One transfer contains one padded transfer object and one Composer bundle. +6. Exact bundle length is hidden only within a declared size class. +7. All integers use unsigned network byte order. +8. Reserved bytes and unknown flags are zero. Any other value rejects the + frame or object. +9. CRC32C and SHA-256 are error-detection and reconstruction checks only. +10. The complete inner Composer bundle is validated before relay state + changes. +11. Resource limits are checked before allocation or FEC work. +12. The backend cannot introduce filenames, metadata records, control + messages, or an automatic return path. +13. Unknown profile, frame, size-class, and FEC identifiers fail closed. +14. Profile behavior is immutable for its numeric identifier. + +## 5. Terms + +- `ComposerBundle`: the byte-exact object defined by `FOG-COMPOSER`. +- `TransferObject`: the 128-byte FOG-SX object header, one ComposerBundle, + and zero padding to an exact size-class capacity. +- `source symbol`: one fixed-length slice used by the selected FEC. +- `repair symbol`: one FEC-generated symbol that is not an original source + symbol. +- `source block`: one independently recoverable group of source symbols. +- `frame`: one 96-byte FOG-SX header, one symbol payload, and one trailing + CRC32C. +- `joint profile`: the immutable tuple of format, backend, FEC, size, + scheduling, and resource parameters accepted by both endpoints. +- `receive window`: one explicit local operator action that permits the + receiver to admit a new transfer. +- `completion indication`: local human-visible receiver output. It is not a + protocol message. + +## 6. Profile Registry + +### 6.1 Joint profile + +An `sx_profile_id` identifies one complete immutable registry entry containing +at least: + +- FOG-SX format version; +- permitted transfer direction and Composer bundle kind; +- backend profile ID and hardware assurance level; +- FEC profile ID, exact algorithm revision, and implementation constraints; +- allowed size-class IDs; +- symbol payload length; +- object partitioning into source blocks and symbols; +- encoding-symbol-ID range and mapping; +- systematic, repair, interleaving, and repetition schedule; +- physical framing, modulation, bitrate, clock recovery, and timeout; +- CRC32C representation; +- sender cycle and duration ceilings; +- receiver frame, memory, storage, CPU, conflict, and decode-attempt budgets; +- release, dependency, and test-vector identity. + +The separately encoded backend, FEC, and size-class IDs let the receiver +reject inconsistent headers early. They do not permit the sender to assemble +a new tuple. Every field MUST equal the configured joint-profile entry. + +### 6.2 Distribution and activation + +The registry ships as immutable release data. It is not learned from the raw +stream. Numeric ID zero is invalid. Candidate names in this document are +symbolic and MUST NOT be placed on the wire as numeric IDs. + +Changing an algorithm, parameter, schedule, dependency, hardware assumption, +or parser limit requires a new joint profile ID. An implementation MUST NOT +reinterpret an old ID after an update. + +The operator selects one backend and one joint profile before opening a +receive window. A decoder MUST NOT scan multiple frame formats, CRC variants, +FEC schemes, or physical modulations to discover what the sender meant. + +## 7. Padded Size Classes + +### 7.1 Class definition + +A size class fixes `bundle_capacity`, the maximum number of ComposerBundle +bytes carried in its TransferObject. The exact FEC source length is: + +```text +transfer_object_length = 128 + bundle_capacity +``` + +The TransferObject is: + +```text +128-byte TransferObjectHeader +composer_bundle_length bytes of ComposerBundle +bundle_capacity - composer_bundle_length zero bytes +``` + +The sender selects the smallest class permitted by the joint profile whose +capacity is at least the exact committed bundle length. The sender MUST NOT +repack, reorder, compress, split, or semantically modify the committed bundle +to obtain another class. + +### 7.2 Version-1 structural class table + +The structural registry reserves these capacities. A joint profile activates +only an explicit subset. + +| Symbolic class | `bundle_capacity` | Intended use | +| --- | ---: | --- | +| `SX-C16K-1` | 16 KiB | small fixtures and constrained visual trials | +| `SX-C64K-1` | 64 KiB | small relay batches | +| `SX-C256K-1` | 256 KiB | ordinary relay batches | +| `SX-C1M-1` | 1 MiB | large relay batches | +| `SX-C4M-1` | 4 MiB | optical-only candidate | +| `SX-C16M-1` | 16 MiB | optical-only candidate | +| `SX-C64M-1` | 64 MiB | Composer relay parser ceiling | + +`KiB` and `MiB` mean powers of 1024. No version-1 FOG-SX class exceeds 64 MiB +of bundle capacity. Update, recovery, contact, and import bundles are outside +the version-1 FOG-SX direction even when their Composer limits would fit. + +### 7.3 Leakage + +Padding hides only the exact bundle length inside the selected class. A +physical observer and the receiver can observe at least the backend, timing, +class, frame count, losses, repetition duration, and operator behavior. + +The systematic source symbols of the same committed Composer bundle remain +correlatable across retransfers even when the transfer ID changes. FOG-SX +does not claim rerandomization or unlinkability. A future authenticated and +encrypted local Composer-relay envelope would be a separate Composer profile, +not an implicit property of FOG-SX. + +## 8. TransferObject Header + +### 8.1 Exact 128-byte layout + +```text +offset length field +0 8 magic +8 2 sx_format_version +10 1 object_kind +11 1 flags +12 4 sx_profile_id +16 4 backend_profile_id +20 4 fec_profile_id +24 4 size_class_id +28 32 transfer_id +60 8 composer_bundle_length +68 8 transfer_object_length +76 32 composer_bundle_digest +108 16 reserved +124 4 header_checksum +``` + +`magic` is the eight ASCII octets `FOGSXO1` followed by line feed, with hex +value `46 4f 47 53 58 4f 31 0a`. + +`sx_format_version` is 1. `object_kind` is 1 for `RELAY_EXPORT`. +`flags` is zero. `reserved` is sixteen zero octets. + +`transfer_id` is exactly 32 bytes sampled from the operating system CSPRNG for +one new transfer attempt. It is not a user identity, authenticity value, +nonce for encryption, replay token, or receipt. + +`composer_bundle_length` is the exact byte length of the committed bundle. +It MUST be at least the 128-byte Composer outer header and no greater than the +selected `bundle_capacity` or the Composer `RELAY_EXPORT` limit. + +`transfer_object_length` MUST equal `128 + bundle_capacity` for the selected +class. It is redundant by design and MUST match the registry before the +receiver allocates object storage. + +`composer_bundle_digest` is SHA-256 over the exact ComposerBundle bytes only, +not over TransferObject padding. It detects reconstruction and storage errors +and supports local duplicate handling. Because it is unkeyed and sent with +the object, an attacker can replace it. It provides no authenticity. + +### 8.2 Header checksum + +`header_checksum` is CRC32C using the algorithm and wire representation of +RFC 3309. For calculation, the four header-checksum octets are zero. The CRC +covers all 128 header octets and does not cover the bundle or padding. + +The decoded header is not trusted merely because its CRC is correct. The +receiver validates it against frame fields and the configured registry before +using lengths or counts. + +### 8.3 Canonical padding + +Every byte after the exact ComposerBundle and before the end of the selected +TransferObject MUST be zero. Non-zero padding rejects the complete object. +Trailing data beyond `transfer_object_length` is not part of the object and +cannot be delivered to the relay. + +## 9. SXFrame + +### 9.1 Exact 96-byte header + +```text +offset length field +0 8 magic +8 2 sx_format_version +10 1 frame_kind +11 1 flags +12 4 sx_profile_id +16 4 backend_profile_id +20 4 fec_profile_id +24 4 size_class_id +28 32 transfer_id +60 4 source_block_number +64 4 source_block_count +68 4 source_symbol_count +72 4 encoding_symbol_id +76 4 symbol_length +80 12 reserved +92 4 header_checksum +``` + +The fixed frame is: + +```text +96-byte SXFrameHeader +symbol_length bytes of symbol_payload +4-byte frame_checksum +``` + +`magic` is the eight ASCII octets `FOGSXF1` followed by line feed, with hex +value `46 4f 47 53 58 46 31 0a`. + +`sx_format_version` is 1. `frame_kind` is 1 for `SYMBOL`. No `END`, `ACK`, +`NAK`, negotiation, capability, metadata, filename, or control frame exists +in version 1. Backend idle and acquisition patterns remain outside SXFrame +and carry no transfer data. + +`flags` and `reserved` are zero. `symbol_length` equals the joint profile's +fixed symbol payload length. Every frame in the profile therefore has exactly +`100 + symbol_length` bytes. + +### 9.2 Block and symbol fields + +`source_block_count` and `source_symbol_count` are redundant values derived +from the size class and FEC profile. They MUST match the registry calculation. + +`source_block_number` is less than `source_block_count`. +`encoding_symbol_id` is interpreted only by the configured FEC profile and +MUST be inside that profile's declared range for the block. + +The tuple: + +```text +(sx_profile_id, transfer_id, source_block_number, encoding_symbol_id) +``` + +identifies one candidate symbol. It is not authenticated. Two byte-identical +copies are duplicates. Two different payloads for the same tuple are a +conflict and trigger the bounded conflict policy in Section 15. + +### 9.3 Checksums + +`header_checksum` is CRC32C over the complete 96-byte header with that field +zero. It permits early rejection before symbol allocation. + +`frame_checksum` is CRC32C over the transmitted 96-byte header, including its +filled header checksum, followed by the exact symbol payload. The trailing +frame-checksum field is excluded from its own calculation. + +Both CRC values use RFC 3309 CRC32C. A mismatch silently discards the frame. +A matching CRC does not authenticate the sender or symbol. + +## 10. FEC Contract + +### 10.1 Ownership + +The FEC profile, not the raw stream, fixes: + +- exact standard and revision; +- source-object partition algorithm; +- symbol alignment and length; +- minimum and maximum source symbols per block; +- source-block count calculation; +- source and repair encoding-symbol-ID mapping; +- maximum accepted ESI; +- repair-symbol generation; +- decoder input-selection and conflict rules; +- decode-attempt thresholds and maximum attempts; +- per-block memory, operation, and wall-time budgets; +- exact implementation and license evidence accepted for release; +- deterministic positive and negative vectors. + +FEC parameters MUST NOT be inferred from attacker-selected counts. The frame +counts are checked copies of values derived from the configured profile and +size class. + +### 10.2 Source partition + +The complete padded TransferObject is the FEC source object. The profile +partitions it into ordered source blocks and fixed-length source symbols. +Only the last source symbol of the last block may require FEC-internal zero +fill, and the profile must make this fill byte-exact and distinguish it from +TransferObject padding. + +Each source block decodes independently. Receiver code MUST NOT allocate the +entire candidate FEC matrix when one block is being decoded unless the active +profile's reviewed memory bound explicitly permits it. + +### 10.3 Corruption and injection + +FEC corrects erasures and some physical errors after failed frames are +discarded. It does not provide integrity against a malicious sender. One +forged but CRC-consistent encoding symbol may poison a decode. + +After all blocks decode, the receiver therefore validates, in order: + +1. exact TransferObject length; +2. exact 128-byte header and header CRC32C; +3. all profile, transfer, length, kind, reserved, and padding fields; +4. SHA-256 over the exact ComposerBundle; +5. exact Composer outer structure and direction; +6. owning inner authentication in the relay or Composer processing path. + +A failure at any step discards the reconstructed candidate and makes no relay +state change. + +## 11. Non-Active FEC Candidates + +### 11.1 RaptorQ candidate + +`FOG-SX-CANDIDATE-RAPTORQ-RFC6330-1` evaluates the fully specified RaptorQ +scheme in RFC 6330. + +Potential advantages: + +- systematic source symbols; +- repair symbols generated as needed without a fixed transmitted total; +- good fit for acknowledgment-free repeated emission; +- substantially larger source blocks than GF(256) Reed-Solomon. + +Required review items: + +- exact RFC parameter derivation and ESI mapping; +- decoder CPU and memory behavior on malformed or adversarial symbols; +- constant parser ceilings far below RFC maximums where appropriate; +- maintained implementation quality, unsafe-language containment, fuzzing, + and cross-implementation vectors; +- dependency license and the IETF RaptorQ IPR disclosures, including the + conditions and defensive-assertion language of disclosure 2554; +- full-object corruption and injection handling before relay delivery. + +No implementation may claim that the RFC's large theoretical object limit is +a FOG-SX permission to allocate or process that amount. + +### 11.2 Reed-Solomon candidate + +`FOG-SX-CANDIDATE-RS-GF256-RFC5510-1` evaluates systematic Reed-Solomon over +GF(2^8) using the fully specified construction in RFC 5510. + +Potential advantages: + +- mature, deterministic block-code behavior; +- recovery of `k` source symbols from any `k` valid encoding symbols for the + specified MDS construction; +- simpler finite redundancy and decode scheduling for small blocks. + +Constraints: + +- GF(2^8) provides at most 255 encoding symbols per source block; +- the profile must fix `k`, `n`, redundancy, block partitioning, and the + behavior after all `n` symbols have been emitted; +- additional repetition cannot create new repair symbols beyond the fixed + codeword and therefore handles burst losses less flexibly than a fountain + schedule; +- poisoned CRC-consistent symbols can still corrupt a decoded block. + +### 11.3 Selection rule + +Neither candidate is selected by this draft. Selection requires measured +results for all activated size and backend classes, complete license review, +dependency maintenance evidence, bounded hostile-input tests, independent +vectors, and reproducible resource measurements. + +An implementation MAY build isolated experimental adapters for both +candidates. It MUST NOT assign a production numeric ID or silently choose one +at runtime. + +## 12. Sender State Machine + +The sender uses these states: + +```text +IDLE + -> PREPARED + -> EMITTING + -> STOPPED + +PREPARED or EMITTING + -> FAILED +``` + +### 12.1 Prepare + +To enter `PREPARED`, the export encoder: + +1. receives read-only access to one immutable committed Composer spool item; +2. verifies exact bundle length and the selected size class; +3. samples a fresh 32-byte transfer ID from the OS CSPRNG; +4. creates the canonical TransferObject header; +5. streams the bundle and zero padding into the FEC encoder; +6. derives every block and schedule parameter from the configured registry; +7. confirms sender memory, time, frame, and output-device budgets; +8. closes every input not required for emission. + +It does not parse message contents or query Composer state. Failure creates no +partial success indication and does not mutate the committed Composer bundle. + +### 12.2 Emit + +The joint profile defines one deterministic cycle containing systematic and +repair symbols interleaved across source blocks. The schedule SHOULD disperse +adjacent source data and block repair across time so that one physical burst +does not erase a contiguous object region. + +The exact schedule, repair quota, block permutation, and ESI sequence are +profile inputs, not operator options. The sender repeats complete cycles until +one of: + +- the human explicitly stops after observing receiver completion; +- the configured maximum cycle count is reached; +- the configured monotonic-duration ceiling is reached; +- the backend or resource monitor fails closed. + +The sender never waits for receiver data and never changes its schedule based +on light, sound, serial input, network input, USB control input, or timing +purportedly supplied by the receiver. + +### 12.3 Stop and retry + +Stopping emission does not mean the relay accepted, queued, or delivered the +bundle. It records only a local transmitter event. + +A new manual transfer attempt uses a fresh transfer ID. It MAY reuse the exact +committed ComposerBundle while its spool-retention policy permits. It MUST NOT +rewind message, ratchet, storage, packet, or Composer state merely because the +physical transfer was stopped or failed. + +## 13. Receiver State Machine + +The receiver uses: + +```text +CLOSED + -> ARMED + -> COLLECTING + -> RECONSTRUCTING + -> COMPLETE + +ARMED, COLLECTING, or RECONSTRUCTING + -> FAILED + +COMPLETE or FAILED + -> CLOSED +``` + +### 13.1 Arm + +`CLOSED` admits no new transfer. An explicit local operator action chooses +the backend, joint profile, and allowed size classes and opens one bounded +receive window. + +The high-assurance receiver admits at most one active transfer. It creates no +state from idle patterns, CRC-failed data, unknown IDs, or a frame that does +not match the configured profile. + +### 13.2 Collect + +The first valid frame may establish the candidate transfer ID and size class +only after all fixed header, CRC, profile, and derived-count checks pass. +Later frames with another transfer ID are silently dropped while the slot is +occupied. + +For one symbol tuple, the receiver: + +- stores the first CRC-valid payload within budget; +- ignores an exact byte-identical duplicate; +- counts a different CRC-valid payload as a conflict; +- never lets a conflicting later payload silently replace an earlier one; +- aborts the transfer when the profile conflict ceiling is exceeded. + +Symbols are stored in bounded per-block structures. Raw physical frames and +failed frames are not retained after the minimal counters needed for the +local status display. + +### 13.3 Reconstruct + +The decoder attempts one block only at profile-defined unique-symbol +thresholds and intervals. Receipt of duplicates, conflicts, bad CRCs, or +arbitrary ESIs MUST NOT trigger unbounded repeated matrix work. + +Decoded blocks are staged in their exact object offsets. The receiver cannot +publish a partial object. After every block is present, it performs the full +validation sequence in Section 10.3 and streams the exact ComposerBundle into +one exclusively created bounded relay-ingress object. + +### 13.4 Complete + +`COMPLETE` means only that one byte-exact candidate passed FOG-SX structural +validation and was durably handed to the bounded relay-ingress queue. It does +not mean inner authentication, network submission, storage commit, or final +delivery succeeded. + +The receiver MAY show a local light, fixed icon, or coarse text indication to +the human. It MUST NOT send a frame, network callback, sound, optical flash, +USB control transfer, or other automatic completion signal to the sender. + +## 14. Validation Order Before Allocation + +For each backend-delivered candidate frame, the receiver performs: + +1. enforce the exact backend unit length; +2. locate the one profile-defined frame boundary without format scanning; +3. validate magic and version; +4. require `frame_kind = SYMBOL`, zero flags, and zero reserved bytes; +5. compare all profile IDs with the configured joint profile; +6. require the exact fixed symbol length; +7. validate the header CRC32C; +8. validate the complete frame CRC32C; +9. derive and compare size, block, source-symbol, and ESI bounds; +10. enforce active-transfer, duplicate, conflict, storage, and work budgets; +11. only then copy the symbol into bounded FEC storage. + +All additions and multiplications involving untrusted fields use checked +integer arithmetic. A decoder MUST NOT cast an untrusted 32-bit count to a +smaller type, allocate from it, or multiply it before checking the profile- +derived expected value. + +## 15. Version-1 Absolute Ceilings + +These are implementation ceilings. Active profiles MUST be equal or stricter. + +| Resource | Absolute ceiling | +| --- | ---: | +| Frame header | exactly 96 bytes | +| Symbol payload | 4096 bytes | +| Complete frame | 4196 bytes | +| TransferObject header | exactly 128 bytes | +| Composer bundle capacity | 64 MiB | +| TransferObject length | 64 MiB + 128 bytes | +| Active transfer IDs, high assurance | 1 | +| Active transfer IDs, lower assurance | 2 | +| Source blocks per transfer | 65536 | +| Source symbols per block | 4096 | +| Accepted unique symbols per block | 8192 | +| Conflicting tuples per block | 16 | +| FEC decode attempts per block | 32 | +| Simultaneous block decoders | 2 | +| Staged decoded object bytes | one selected TransferObject | +| Retained failed raw frames | 0 | +| Receive window | 24 monotonic hours | +| Sender cycles | 65535 | +| Parser nesting | forbidden | +| Decompression | forbidden | + +The active profile defines lower backend-appropriate duration, cycle, block, +symbol, memory, disk, and CPU budgets. The receiver stops accepting symbols +while a block decode consumes its quota. A timeout, disk-full condition, +memory-pressure event, excessive conflict rate, dependency panic, or budget +exhaustion aborts the candidate and returns to a safe closed state. + +Counters used only for resource enforcement saturate rather than wrap. They +are reset when the receive window closes and are not exported as telemetry. + +## 16. Duplicate, Replay, and Injection Behavior + +FOG-SX has no authenticated replay protection. A malicious source can copy or +recreate all of its public fields and checksums. + +The receiver MAY maintain a bounded local cache of recently completed tuples: + +```text +(sx_profile_id, transfer_id, composer_bundle_digest, + composer_bundle_length, size_class_id) +``` + +An exact completed duplicate can be discarded before a second relay-ingress +write. The cache is a local availability control, not proof that another +transfer with the same ID or digest is authentic. + +Composer and the owning inner protocols retain final deduplication and replay +authority. A new transfer ID does not authorize a duplicate message, receipt, +storage write, or state transition. + +## 17. Common Physical-Backend Contract + +Every backend profile fixes: + +- one direction and one physical transmitter/receiver role; +- exact connector, module class, and electrical or optical assumptions; +- raw carrier framing and acquisition pattern; +- modulation, line coding, symbol rate, bitrate, and clock tolerance; +- mapping from one complete SXFrame to carrier units; +- acquisition, frame, idle, loss, and end-of-window timeouts; +- maximum supported size classes and expected transfer duration; +- device-open flags, driver allowlist, and prohibited device capabilities; +- physical teardown, continuity, and direction tests; +- emitted-light, sound, electromagnetic, and human-visible safety constraints; +- deterministic vectors captured above and below the backend boundary. + +Backend code transports exact SXFrame bytes. It MUST NOT reinterpret a frame +as text, Base64, a file, MIDI music, audio content, a URL, or a document unless +the specific profile defines one fixed byte mapping solely as carrier coding. + +The high-assurance profile requires separate unidirectional components whose +physical construction enforces direction. Software configuration, a disabled +receive API, driver policy, or a supposedly unused bidirectional transceiver +is insufficient. + +The offline side exposes only a transmitter data input. The online side +exposes only a receiver data output. No common USB controller, shared debug +UART, management bus, network interface, storage controller, radio, or +bidirectional data cable may bridge the trust domains. + +## 18. QR Candidate + +`FOG-SX-CANDIDATE-QR-MODEL2-1` uses fixed-version QR Code Model 2 symbols as a +visual carrier from an offline display to an online camera. + +Before activation it must fix: + +- the ISO/IEC 18004 edition; +- QR version, byte mode, mask selection rule, and error-correction level; +- one exact binary mapping with one complete SXFrame per QR symbol and no QR + structured append or backend-level SXFrame fragmentation; +- quiet zone, module size, contrast, display refresh, dwell, and blanking; +- camera resolution, frame rate, exposure, focus, decoder, and timeout; +- maximum FOG-SX size classes and operator ergonomics; +- rejection of URLs, text actions, structured append outside the exact + profile, and general barcode dispatch. + +QR Code has its own Reed-Solomon error correction. That layer improves visual +recovery but does not authenticate FOG-SX or replace the frame CRC, object +digest, FEC profile, or inner Composer authentication. + +The camera is an attack surface. The online receiver uses a dedicated camera +or capture path with no microphone, speaker, storage automount, network, +vendor cloud service, or automatic barcode action. An online display used for +separate Composer import is not an acknowledgment and must not be driven by +FOG-SX receive state. + +## 19. FOG Lightpipe Candidate + +`FOG-SX-CANDIDATE-LIGHTPIPE-NRZ-1` is the preferred high-throughput candidate. +It uses a discrete optical transmitting module on the offline side, one fiber, +and a discrete optical receiving module on the online side. + +The high-assurance construction requires: + +- a transmitter-only module, such as the TOSLINK `TOTX` class; +- a receiver-only module, such as the TOSLINK `TORX` class; +- no `TODX` transceiver, duplex module, second fiber, or receiver on the + offline board; +- no transmitter, display-controlled return light, or software-controlled + emitter on the online board; +- galvanically separated power and no data-bearing ground or debug path + between boards; +- a simple allowlisted hardware interface whose offline driver can only emit + and whose online driver can only receive; +- schematics, bill of materials, board photographs, continuity tests, optical + direction tests, and teardown evidence for the exact hardware revision. + +TOSLINK defines optical transmitter and receiver device classes, not the +complete FOG-SX modulation. Before activation the candidate must fix the exact +NRZ or self-clocking line code, bitrate, transition density, preamble, clock +recovery, frame delimiter, escaping, idle pattern, light polarity, optical +module, fiber, distance, jitter, and error tests. + +Consumer S/PDIF or USB audio adapters are lower assurance. They may contain +bidirectional USB control, opaque firmware, audio clocks, mixers, microphones, +or unexpected device functions. They MUST NOT satisfy the high-assurance +Lightpipe profile merely because their payload leaves through an optical jack. + +A second online-to-offline optical system, if ever deployed for Composer +import, is a separately powered, separately controlled path with its own +receive-only offline hardware. It is not simultaneously active, is not +connected to FOG-SX state, and does not turn the export construction into a +duplex protocol. + +## 20. MIDI DIN Candidate + +`FOG-SX-CANDIDATE-MIDI-DIN-1` uses one MIDI DIN OUT circuit to one optically +isolated MIDI DIN IN circuit as a lower-throughput experimental carrier. + +The MIDI 1.0 electrical specification defines a 31.25 kbaud asynchronous +8-N-1 interface, a 5 mA current loop, and receiver opto-isolation. The active +candidate must use that electrical direction, not a bidirectional USB MIDI +device. + +Before activation it must fix: + +- exact byte transparency and framing above the MIDI serial byte; +- handling or prohibition of status-byte semantics and running status; +- escaping, preamble, frame delimiter, resynchronization, and idle behavior; +- maximum frame payload and size class consistent with transfer duration; +- compliant OUT, IN, cable, shield, grounding, and opto-isolator circuits; +- absence of MIDI THRU or any offline input path; +- hostile jitter, disconnect, stuck-bit, burst-loss, and opto-isolator tests. + +The carrier must not expose FOG-SX bytes to a general music application, +synthesizer plugin, device-discovery service, or cloud-connected MIDI stack. + +## 21. Other Backends + +Visible LED, screen-to-photodiode, audible modem, opto-isolated TX-only serial, +and punched paper tape remain architectural possibilities. None is specified +by this document and none may reuse a QR, Lightpipe, or MIDI backend ID. + +Each future backend needs its own threat analysis, exact physical and byte +mapping, hardware-direction proof, resource profile, conformance vectors, and +release gate. A generic runtime driver or modulation plugin system is not a +substitute for separately reviewed profiles. + +## 22. Candidate Activation Gates + +A joint FOG-SX profile receives a numeric ID only after all of these are +complete: + +1. byte-exact frame, object, size, FEC, schedule, and backend parameters; +2. two independent encoders and decoders or one implementation plus an + independent vector generator; +3. known-answer vectors for every size class and boundary value; +4. dependency, license, IPR, maintenance, and supply-chain review; +5. parser fuzzing with malformed headers, lengths, IDs, CRCs, padding, and + trailing data; +6. hostile FEC corpus covering duplicates, conflicts, poisoned symbols, + excessive ESIs, singular decode inputs, and resource exhaustion; +7. reproducible CPU, memory, disk, frame, time, and energy measurements; +8. crash, interruption, power-loss, disk-full, and restart testing; +9. physical directionality inspection and tests for the exact hardware; +10. confirmation that no automatic sender-visible completion path exists; +11. complete Composer and relay integration with inner authentication before + state change; +12. independent security review of parser, FEC, hardware, and claims. + +A local loopback, virtual QR camera, paired serial ports, audio adapter, or +single board can provide functional evidence only. It cannot establish a +physical data-diode or production endpoint-security claim. + +## 23. Implementation Shape + +The protocol implementation should preserve these logical modules: + +```text +protocol/sx/ + frame/ fixed header parsing, serialization, and CRC + object/ TransferObject construction, padding, and validation + profile/ immutable registry and derived parameter checks + fec/ narrow profile-specific encoder and decoder interfaces + schedule/ deterministic sender schedule + receiver/ bounded collection and reconstruction state machine + backend/ + qr/ exact visual carrier edge + lightpipe/ exact optical carrier edge + midi/ exact current-loop carrier edge +``` + +The process boundary is: + +```text +offline: fog-compose -> sealed spool -> fog-sx-send -> TX-only device +online: RX-only device -> fog-sx-receive -> bounded relay ingress +``` + +`frame`, `object`, and profile validation are pure protocol code and do not +open devices or files. FEC implementations receive already bounded slices and +profile constants. Backend modules do not parse Composer bundles or implement +FEC. The transfer receiver is a separate executable from `fog-client-relay`. + +There is no generic plugin loader, dynamic library selected by wire input, +universal device daemon, or in-process backend autodetection. Candidate FEC +libraries written in an unsafe language run behind the narrowest practical +sandbox and process boundary with fixed memory and CPU limits. + +## 24. State, Retention, and Logging + +The offline sender may read only the selected immutable spool item and its +public transfer profile. It stores no new long-term secret. Transfer IDs are +public ephemeral metadata and follow the spool attempt's bounded lifetime. + +The online receiver retains: + +- one bounded active symbol set during a receive window; +- one staged decoded TransferObject until complete validation; +- one exact ComposerBundle until durable relay-ingress handoff; +- a bounded recent-completion cache if enabled; +- coarse saturating error counters for the current local session. + +It deletes failed symbols, failed objects, padding, and raw frames after the +owning failure or completion path. Deletion is best effort on flash and other +journaled storage and is not claimed as forensic erasure. + +Logs and support output MUST NOT contain raw frames, symbols, transfer IDs, +bundle digests, Composer bytes, frame timing traces, device serial numbers, +camera images, audio captures, filesystem paths, or host identity. Local +status may show coarse progress, current class, and a generic failure category +without exporting telemetry. + +## 25. Conformance Tests + +Before the local PoC, deterministic tests must cover: + +- exact 128-byte object and 96-byte frame headers; +- both magic values, version, kind, zero flags, and reserved bytes; +- big-endian integers and checked arithmetic; +- every boundary around each size class and the 64 MiB ceiling; +- exact zero padding and rejection of non-zero or trailing bytes; +- SHA-256 and both CRC32C calculation domains and wire values; +- wrong profile tuple, symbol length, counts, block number, and ESI; +- exact duplicate and conflicting symbols; +- all FEC success, insufficient-symbol, poisoned-symbol, and abort paths; +- deterministic schedule and interleaving across cycles; +- receiver window, transfer-slot, decode-attempt, time, memory, disk, and CPU + limits; +- no partial output after any frame, block, object, or Composer failure; +- exact one-bundle relay-ingress handoff and duplicate suppression; +- sender stop and retry without state rewind; +- absence of ACK, reverse device access, network callback, and adaptive + sender behavior; +- each physical backend's acquisition, loss, jitter, disconnect, and + directionality fixtures; +- mutation, coverage-guided fuzz, property, race, crash, and fault-injection + testing at every parser and process boundary. + +Cross-implementation vectors include exact TransferObject bytes, every source +and repair symbol, frame bytes, CRCs, schedule order, reconstructed bytes, and +expected rejection reason class. Rejection classes are test outputs, not +remote protocol responses. + +## 26. Failure Behavior + +Malformed, unknown, inconsistent, oversized, stale, conflicting, timed-out, +or resource-exhausting input fails closed. The receiver sends nothing toward +the transmitter and publishes no partial Composer bundle. + +The local UI distinguishes only coarse states such as waiting, collecting, +checking, complete, timed out, incompatible profile, and failed. Detailed +parser positions, CRC values, FEC matrices, raw input, transfer IDs, and +digests are unavailable to the physical sender and excluded from ordinary +logs. + +If completion is uncertain, the human may stop and later restart a fresh +transfer attempt. Neither endpoint treats uncertainty as permission to bypass +Composer deduplication, inner authentication, or protocol state rules. + +## 27. Threat and Architecture Traceability + +| Requirement | Primary controls | +| --- | --- | +| `ARC-002` | networkless Composer, TX-only export, no automatic reverse path | +| `ARC-004` | transfer receiver has no relay, user, messaging, or storage keys | +| `ARC-007` | no FOG-SX secret; explicit ephemeral transfer-ID ownership | +| `ARC-008` | exact headers, fixed frames, derived counts, absolute ceilings | +| `ARC-009` | unknown profile, corruption, ambiguity, and exhaustion fail closed | +| `IF-01` | one committed opaque export, sandboxed encoder and receiver | +| `IF-02` | import remains a separate controlled direction, never an ACK path | +| `TM-ENDPOINT-01` | dedicated offline process and directional hardware assumptions | +| `TM-ENDPOINT-02` | bounded binary format, parser sandbox, no archive or filesystem | +| `TM-OPS-01` | no raw transfer logging, device identity, telemetry, or support dump | +| `TM-SUPPLY-01` | exact dependencies, hardware revision, license and release gates | +| `TM-CRYPTO-01` | CSPRNG transfer IDs, SHA-256 limited to error detection | +| `TM-CRYPTO-02` | immutable release registry, no negotiation or downgrade | +| `TM-AVAIL-01` | receive windows, one slot, conflict and FEC work budgets | + +## 28. Claims Deliberately Withheld + +FOG-SX does not yet establish: + +- an active FEC algorithm, implementation, or numeric profile; +- an active physical backend, bitrate, symbol size, or hardware design; +- physical one-way assurance from software configuration alone; +- authenticity, confidentiality, replay protection, or non-repudiation at the + FOG-SX layer; +- unlinkability of retransferred Composer bundles; +- concealment of transfer occurrence, timing, size class, frame count, or + physical location; +- resistance to a compromised offline Composer, online receiver, firmware, + camera, USB controller, optical module, or supply chain; +- elimination of optical, acoustic, electromagnetic, thermal, power, or + human-mediated covert channels; +- production security from a loopback, animated QR demo, consumer TOSLINK + adapter, USB MIDI adapter, or local PoC; +- successful network delivery merely because local reconstruction completed. + +## 29. Open Dependencies + +The structural FOG-SX contract is fixed, but these remain open: + +- selection between the RaptorQ and Reed-Solomon candidates or a separately + reviewed replacement; +- exact FEC parameters, implementation, dependency version, and license + decision; +- byte-exact joint profile IDs and size-class subsets; +- exact QR geometry, decoder, frame mapping, and transfer-duration limits; +- exact Lightpipe modules, board, line code, bitrate, clock recovery, and + hardware-direction evidence; +- exact MIDI byte mapping, circuit, frame size, and practical size classes; +- concrete per-profile CPU, memory, disk, cycle, time, and energy limits; +- Composer pairing-envelope decision if rerandomized local transport is later + required; +- conformance corpus, fault fixtures, benchmark results, and independent + review. + +No implementation convenience may silently resolve these dependencies. + +## 30. Primary References + +- FOG threat model: `FOG-THREAT-MODEL.md` +- FOG architecture: `FOG-ARCHITECTURE.md` +- FOG Composer: `FOG-COMPOSER.md` +- RFC 6330, RaptorQ Forward Error Correction Scheme for Object Delivery: + +- IETF IPR disclosure 2554 related to RFC 6330: + +- RFC 5510, Reed-Solomon Forward Error Correction Schemes: + +- RFC 3309, SCTP Checksum Change, including CRC32C definition: + +- FIPS PUB 180-4, Secure Hash Standard: + +- ISO/IEC 18004:2024, QR code symbology specification: + +- DENSO WAVE QR Code error-correction overview: + +- MIDI 1.0 Electrical Specification Update: + +- Toshiba TOSLINK transmitter, receiver, transceiver, and directivity naming: + diff --git a/docs/FOG-THREAT-MODEL.md b/docs/FOG-THREAT-MODEL.md new file mode 100644 index 0000000..f0dde91 --- /dev/null +++ b/docs/FOG-THREAT-MODEL.md @@ -0,0 +1,962 @@ +# FOG Threat Model + +Status: Draft 0.1 + +Date: 2026-08-08 + +## 1. Purpose + +This document defines the security and privacy model for the FOG autonomous +mix network and its native asynchronous services. It identifies protected +assets, trust boundaries, adversary capabilities, assumptions, required +properties, non-goals, residual risks, and the evidence required before FOG +makes public security claims. + +FOG is not implemented yet. Every property in this document is therefore a +design requirement or a validation target, not a statement about deployed +software. + +The key words MUST, MUST NOT, REQUIRED, SHOULD, SHOULD NOT, and MAY describe +normative requirements in the sense of BCP 14 when they appear in uppercase. + +## 2. Scope + +This threat model covers: + +- the offline `fog-compose` environment; +- transfer through QR or `FOG-SX` simplex media; +- the online `fog-client-relay`; +- entry gateways and three stratified mix layers; +- directory authorities and signed epoch consensus; +- KEMSphinx packet processing and replay handling; +- Noise-protected adjacent-node links; +- couriers, native services, and capability-addressed storage replicas; +- asynchronous one-way drops, mailboxes, private messaging, and later group + messaging; +- software distribution, configuration, updates, backups, logs, and + operational metrics. + +The FOG core does not include SOCKS, VPN, general Internet proxying, clearnet +exit nodes, web browsing, real-time audio or video, tokenomics, or arbitrary +user-selected routes. SMTP, NNTP, and foreign Sphinx networks may be reached +only through separately reviewed edge bridges whose weaker guarantees are +outside the core anonymity claim. + +## 3. System Boundary + +The intended high-level path is: + +```text +offline Composer + -> QR or FOG-SX simplex transfer +online blind relay + -> temporary entry selected from authenticated consensus +entry gateway + -> mix layer 1 -> mix layer 2 -> mix layer 3 +courier / native service + -> capability-addressed storage replicas +recipient blind relay + -> recipient offline Composer +``` + +The entry is not one of the three mix hops. The Composer constructs the +internal KEMSphinx route from authenticated consensus. The blind relay may +select an entry only from the Composer-approved consensus and temporary entry +set. The recipient Composer is the only component intended to recover message +plaintext. + +FOG uses three separate cryptographic layers: + +1. message-level end-to-end protection between Composers; +2. KEMSphinx protection for routing and per-hop transformation; +3. fixed Noise profiles for authenticated adjacent-node transport. + +No layer may be treated as a substitute for another. + +## 4. Protected Assets + +### 4.1 User and message assets + +- message plaintext, attachments permitted by the future format, and drafts; +- long-term identity keys, contact state, ratchet state, recovery material, + and backup keys; +- contact vouchers, reply material, mailbox capabilities, and storage + capabilities; +- the fact that a user is sending, receiving, polling, replying, or remaining + idle; +- sender identity, recipient identity, sender-recipient relationships, + conversation membership, and social graph; +- message length, fragment count, timing, frequency, retry behavior, and + conversation lifetime. + +### 4.2 Network and operator assets + +- authority identity keys, consensus signing keys, and voting state; +- node identity, Noise, KEMSphinx, replay, storage, and update keys; +- descriptors, operator-family declarations, topology assignments, epoch + state, revocations, and key-overlap state; +- unreleased topology changes and security-sensitive configuration; +- replay caches, delay queues, cover-traffic schedules, storage shards, and + tombstone state; +- release signing keys, build provenance, update metadata, and rollback state. + +### 4.3 Availability and integrity assets + +- consistent authenticated consensus; +- correct routing and packet transformation; +- bounded and non-amplifying protocol behavior; +- eventual delivery within the limits of an asynchronous best-effort system; +- integrity and freshness of Composer imports and software updates; +- the ability to revoke compromised identities, nodes, authorities, and + releases. + +## 5. Security Objectives + +### 5.1 Cryptographic objectives + +FOG MUST be designed so that: + +- only the intended recipient Composer can decrypt message content; +- recipients can authenticate the claimed sender or contact context according + to the selected messaging protocol; +- alteration, truncation, reordering, substitution, and unauthorized replay of + protected message state are detected; +- a node learns only the routing information needed for its immediate role; +- packet transformation prevents bitwise input-output linkage at an honest + mix; +- storage replicas cannot decrypt message content or derive stable contact + identities from capabilities; +- compromise of one key purpose does not directly expose keys for another + purpose; +- forward secrecy and post-compromise recovery are provided only if the final + reviewed messaging construction supports them and its state transitions are + implemented correctly. + +FOG MUST NOT claim forward secrecy, post-compromise security, deniability, or +post-quantum security merely because individual primitives have those labels. +Those properties apply only after the complete protocol and lifecycle have +been analyzed. + +### 5.2 Metadata privacy objectives + +FOG targets the following conditional properties: + +- sender anonymity against third parties; +- recipient anonymity against third parties; +- sender-recipient relationship unlinkability; +- route unlinkability across at least one honest mix transformation; +- resistance to short-term timing and volume correlation; +- reduced long-term intersection leakage; +- sender and recipient unobservability when traffic normalization, decoy + traffic, padded retrieval, and sufficient anonymity-set activity are all + present; +- unlinkability of successive mailbox locations and storage operations; +- indistinguishability, within defined traffic classes, of real messages, + retries, empty reads, acknowledgments, and cover operations. + +These are probabilistic properties, not absolute concealment. Their strength +depends on measured traffic volume, latency distributions, cover ratio, +topology, operator independence, user behavior, and adversary position. FOG +MUST publish the conditions and measured bounds attached to every metadata +privacy claim. + +### 5.3 Availability objectives + +FOG provides best-effort asynchronous delivery. It SHOULD tolerate isolated +node loss, replica loss, delayed packets, duplicate packets, temporary +partitions, and ordinary churn. It does not promise delivery against a global +active adversary, sustained distributed denial of service, a failed authority +quorum, or deliberate destruction of enough storage replicas. + +Privacy MUST fail closed where continuing would silently weaken a claimed +property. Availability failover MUST NOT introduce direct delivery, smaller +packet classes, bypassed layers, unauthenticated directories, fixed emergency +routes, or disabled cover traffic without placing the deployment outside the +affected privacy claim. + +## 6. Actors and Trust Boundaries + +| Role | May legitimately learn | Must not learn or control alone | +| --- | --- | --- | +| Composer | Local plaintext, identity and contact state, selected consensus, constructed route | Other users' plaintext or secrets; unauthenticated network state | +| FOG-SX receiver | Transfer size class, transfer timing, opaque bundle | Plaintext, contacts, private keys, final recipient | +| Blind relay | Client-side network context, opaque packets, chosen temporary entry | Plaintext, internal route, final service or recipient | +| Entry | Previous peer, next mix, packet timing, random short-lived return rendezvous and reply ID | Message plaintext, complete route, recipient identity, durable user account | +| Mix | Previous hop, next hop, local delay, replay tag | Complete route, endpoint identities, message plaintext | +| Authority | Node descriptors, operator declarations, votes, epoch state | User messages, mailbox capabilities, unilateral valid consensus | +| Courier | Requested native operation, replica set or shard information required by protocol | User plaintext, stable user identity, contact graph | +| Native service | Minimum service-specific opaque state | Network origin, unrelated conversations, Composer secrets | +| Storage replica | Opaque record, capability-derived location, retention state, access timing | Plaintext, stable mailbox identity, sender-recipient relationship | +| Observer | Delayed coarse health aggregates | Payloads, packet identifiers, detailed routes, capabilities, fine-grained timing | +| Edge bridge | Data required by the external protocol | Authority to extend core guarantees to the external network | + +No online role is universally trusted. Each role MUST be independently +deployable and separately keyed. Co-location is a deployment fact, not a +reason to collapse protocol boundaries or share secrets. + +## 7. Adversary Capabilities + +FOG analyzes capabilities independently rather than assuming a single linear +attacker tier. Real adversaries may combine any subset. + +### 7.1 Passive observation + +The adversary may: + +- observe one link, multiple links, the network perimeter, or nearly all + Internet links; +- record packet sizes, directions, timing, connection duration, retransmits, + outages, routing changes, and user online periods; +- retain encrypted traffic for future cryptanalysis; +- observe hosting providers, AS paths, DNS, time synchronization, software + downloads, bridge traffic, and public authority activity; +- compare observations across hours, epochs, months, and user behavior cycles. + +The global passive observer is a target adversary for metadata-resistance +research and simulation. FOG MUST NOT claim resistance to that observer until +the complete deployed traffic process, including clients, storage access, and +cover traffic, has quantitative evidence. + +### 7.2 Active network interference + +The adversary may drop, delay, reorder, duplicate, replay, corrupt, tag, +inject, selectively forward, partition, throttle, or burst packets. It may +manipulate clocks and availability to trigger retries or route changes. It may +create distinguishable failure patterns and observe the resulting behavior. + +The adversary may run malicious clients at scale and may attempt n-1, +confirmation, blending, flooding, resource-exhaustion, and amplification +attacks. + +### 7.3 Infrastructure compromise and collusion + +The adversary may compromise or operate: + +- one or more entries, mixes, couriers, services, replicas, observers, or + authorities; +- multiple roles on the same route; +- multiple nominal operators that secretly form one operator family; +- hosting providers, hypervisors, management networks, build systems, package + registries, or time sources; +- an authority threshold sufficient to sign malicious consensus; +- enough storage replicas to destroy or correlate a mailbox; +- an edge bridge and the corresponding external service. + +Compromise includes software exploitation, malicious updates, physical +access, legal compulsion, bribery, operator mistakes, secret copying, and +rollback to vulnerable state. + +### 7.4 Endpoint and contact compromise + +The adversary may compromise the online blind relay, the host running a +networkless microVM, a physically offline Composer, a transfer device, a +backup, or a recipient contact. It may use browser, firmware, peripheral, +DMA, side-channel, evil-maid, removable-media, or supply-chain attacks. + +A malicious or compromised contact may send malformed messages, exhaust reply +material, correlate replies, reveal conversation state, lie about identity, +or intentionally publish plaintext and relationship metadata. + +### 7.5 Cryptanalytic capability + +The adversary may exploit weak parameters, nonce reuse, RNG failure, side +channels, parser differentials, protocol composition errors, implementation +bugs, downgrade paths, key reuse, and future cryptanalytic advances. A future +adversary may have practical quantum capability. + +FOG assumes reviewed primitives remain secure only within their documented +parameters. Algorithm names alone are not security evidence. + +## 8. Core Assumptions + +All security claims depend on the following assumptions: + +1. The sender and recipient Composers are not compromised while handling the + protected plaintext or key state relevant to the claim. +2. Users obtain authentic Composer software, trust anchors, and updates. +3. Cryptographic random number generators provide sufficient entropy. +4. Selected primitives and libraries are correctly implemented and remain + secure for their configured parameters. +5. At least one mix on a route is honest and its secret state is not exposed + during the period relevant to per-hop route unlinkability. +6. The authority signing threshold is not compromised, and enough honest + authorities remain available to produce consensus. +7. Composer and relay reject stale, rolled-back, malformed, or + insufficiently signed consensus. +8. Declared operator families and infrastructure diversity reflect reality + closely enough for path constraints to matter. +9. Traffic classes, packet geometry, delay distributions, retry behavior, and + cover traffic are followed by all honest implementations. +10. The anonymity set has enough concurrent real and decoy activity for the + measured privacy target. A nearly idle network cannot hide activity by + declaration. +11. Users follow the operational profile attached to a claim. A networkless + Composer does not help if its secrets are copied to an online system. +12. External bridges, recipients, and publication systems may disclose the + content and metadata that their protocols inherently require. + +Violating an assumption MUST narrow or invalidate the corresponding claim. It +MUST NOT be hidden behind a generic statement that FOG is anonymous. + +## 9. Threat Catalogue and Required Responses + +### TM-NET-01: Timing and volume correlation + +An observer compares ingress and egress timing, size, direction, and burst +shape. + +Required responses: + +- fixed packet geometry within consensus-defined traffic classes; +- randomized independently sampled per-hop delays; +- normalized client transmission and retrieval schedules; +- decoy traffic and loops that use the same processing path as real traffic; +- padded storage operations and acknowledgments; +- no application-specific route, packet, or retry fingerprint; +- simulation and trace-based evaluation against realistic observers. + +Residual risk: timing defenses reduce correlation advantage but cannot make +repetitive behavior, low traffic, or indefinite observation harmless. + +### TM-NET-02: Long-term intersection and statistical disclosure + +An observer records which users are active around repeated sends or receives +and intersects candidate sets over time. + +Required responses: + +- cover activity independent of message activity; +- retrieval schedules that continue across empty and non-empty states; +- delayed and padded acknowledgments; +- rotating unlinkable capabilities and reply material; +- immutable storage-epoch manifests with disjoint intermediate and final + replicas under an explicit non-collusion assumption; +- client guidance that does not encourage deterministic schedules; +- long-horizon simulation and publication of residual disclosure risk. + +Residual risk: intersection leakage exists in all practical systems when user +behavior and availability are sufficiently distinctive. Repeated polling of +one empty capability-derived box remains linkable at its final replicas. + +### TM-NET-03: n-1 and active confirmation + +An active adversary suppresses honest traffic, injects its own packets, or +forces distinctive bursts so that a target packet becomes isolated. + +Required responses: + +- decoy-loop health signals and anomaly detection; +- conservative behavior during unexplained traffic collapse; +- no immediate deterministic retry after loss; +- rate and admission controls that limit hostile traffic without creating + stable user fingerprints; +- fault-injection tests that measure isolation and confirmation advantage; +- documented shutdown or degraded-mode policy when required anonymity + conditions disappear. + +Residual risk: a sufficiently strong active adversary can deny service and +may isolate traffic. FOG does not promise availability or anonymity under +arbitrary global active control. + +### TM-NET-04: Replay and duplicate confirmation + +An adversary replays packets or reply material and observes repeated effects. + +Required responses: + +- consensus-bound replay tags and bounded replay windows; +- replay state that survives ordinary process restarts for the required + maximum packet lifetime; +- single-use reply material with atomic consumption; +- message-level deduplication that does not emit distinguishable responses; +- duplicate storage writes and reads handled idempotently; +- fail-closed behavior if required replay state is corrupt or unavailable. + +### TM-NET-05: Tagging and malleability + +An adversary modifies a packet and observes whether a later component reacts +differently. + +Required responses: + +- a reviewed KEMSphinx profile with authenticated routing data and the + documented payload-integrity construction; +- constant-behavior rejection without detailed remote error oracles; +- no service action before complete packet and message validation; +- conformance and mutation tests covering every authenticated field; +- explicit analysis of SURB-specific tagging and confirmation risks. + +### TM-NET-06: Packet, route, and implementation fingerprinting + +An adversary distinguishes application, client version, route choice, or +fallback state. + +Required responses: + +- one authenticated profile and geometry per authorized traffic class and + epoch; +- identical core processing for all native source applications; +- no packet autodetection, silent downgrade, or per-packet suite negotiation; +- complete authenticated consensus view for route construction; +- bounded version overlap and a clear minimum accepted epoch; +- rollout tests that prevent minority clients from becoming a fingerprint. + +### TM-PKI-01: Sybil nodes and false operator diversity + +An adversary obtains many node positions or hides common control behind +nominally different operators. + +Required responses: + +- permissioned admission for the initial network; +- signed operator-family declarations and conflict rules; +- independent verification of provider, ASN, jurisdiction, ownership, and + administration where practical; +- route rejection when one declared family appears twice; +- transparent admission, suspension, and revocation records; +- no claim that permissionless Sybil resistance has been solved. + +Residual risk: social and corporate control can be concealed. Governance and +operator investigation remain part of the security boundary. + +### TM-PKI-02: Stale, rolled-back, frozen, split, or equivocated consensus + +An adversary gives different network views to clients or prevents updates. + +Required responses: + +- threshold signatures over canonical consensus bytes; +- monotonically increasing epochs and explicit validity intervals; +- current and next keys with bounded overlap; +- persisted highest-accepted epoch and rollback protection; +- independent retrieval paths or gossip for equivocation evidence; +- fail-closed behavior when quorum, time validity, or profile authorization + cannot be established; +- an offline-safe recovery procedure for long-disconnected Composers. + +Residual risk: a compromised signing threshold can authorize malicious +topology. A fully isolated client may not learn that another valid-looking +view exists without an external consistency mechanism. + +### TM-PKI-03: Authority outage or capture + +One or more authorities withhold votes, disappear, leak keys, or act under +compulsion. + +Required responses: + +- threshold operation with documented safety and liveness limits; +- offline root and recovery material separated from online epoch keys; +- rehearsed revocation and authority-replacement ceremonies; +- deterministic consensus failure behavior; +- no automatic trust-on-first-use replacement authority. + +### TM-ROLE-01: Malicious entry or blind relay + +A blind relay observes the local user context. An entry observes the relay +connection and first mix hop. On a reply, the entry also observes one random +short-lived rendezvous capability and reply ID while performing the terminal +KEMSphinx unwrap. Either role may delay, drop, batch, or fingerprint traffic. + +Required responses: + +- the Composer exports only opaque authenticated bundles; +- the blind relay cannot alter Composer-approved consensus or the internal + route; +- entry choice is constrained to a small authenticated temporary set; +- traffic normalization begins before application-dependent variation reaches + the entry; +- entry rotation is by session or epoch, not every message by default; +- entry return KEMSphinx keys are separate from forward capsule and mix keys; +- return rendezvous capabilities and reply IDs are random, bounded, + single-use, and do not create a durable entry account; +- clients detect sustained failure without switching to a privacy-bypassing + direct path. + +Residual risk: a compromised blind relay can associate the local endpoint with +FOG use and can deny service. Collusion with downstream observation increases +correlation power. + +### TM-ROLE-02: Malicious mix or route collusion + +A mix observes adjacent hops and local timing. Multiple malicious mixes share +their observations or secrets. + +Required responses: + +- independent operators and family-constrained route construction; +- per-epoch mix keys with overlap only where required; +- KEMSphinx transformation and replay detection at every mix; +- no detailed per-packet logs; +- cover traffic processed identically to real traffic; +- compromise simulations for every layer and route position. + +Residual risk: if all privacy-relevant hops on a route collude, route +unlinkability is not guaranteed. Later key disclosure may affect recorded +packets depending on the final KEMSphinx construction and key-erasure policy. + +### TM-ROLE-03: Malicious courier, service, or storage replica + +These roles correlate operations, return malformed data, selectively fail, +exhaust capabilities, or destroy records. + +Required responses: + +- message-level ciphertext remains opaque through all three roles; +- rotating capabilities and pseudorandom record locations; +- courier and replica knowledge separated by protocol design; +- at least four independently operated replicas for the intended profile; +- authenticated encrypted replica envelopes; +- final-replica receipts that distinguish courier acceptance from evidence of + durable local commit; +- uniform treatment of empty reads, hits, misses, retries, and acknowledgments; +- quorum or erasure behavior that survives defined replica loss; +- bounded retention, tombstones, quotas, and non-amplifying requests; +- client verification of every retrieved object before state transition. + +Residual risk: enough colluding replicas can correlate or destroy access. A +malicious service may learn application-level information that the final +service contract explicitly exposes. + +### TM-ENDPOINT-01: Composer compromise + +Malware, physical access, a hostile host, firmware, peripheral, or malicious +update captures plaintext or keys. + +Required responses: + +- a MicroVM profile with no virtual network interface and a minimal device + boundary; +- a Portable profile with signed immutable media, disabled network and radio + drivers, and no automatic internal-disk mounting; +- authenticated encryption for identities, contacts, drafts, and backups; +- purpose-separated keys and minimal plaintext lifetime; +- no secrets in logs, crash dumps, swap, thumbnails, previews, or generic + desktop indexing; +- signed updates with rollback protection and a documented offline ceremony; +- explicit warning that endpoint compromise defeats local confidentiality and + authenticity. + +Residual risk: software cannot preserve a secret while an attacker controls +the component using it. A networkless VM on a compromised host is weaker than +a physically isolated Composer. + +### TM-ENDPOINT-02: Malicious transfer input and bidirectional leakage + +A crafted QR, FOG-SX stream, removable device, or optical peripheral exploits +the parser or creates an unintended return channel. + +Required responses: + +- one minimal bounded binary format per transfer direction; +- no generic archives, filesystems, documents, HTML, scripts, or executable + content; +- fixed maximum sizes, allocation limits, canonical encodings, and complete + validation before interpretation; +- fuzzing and property tests for frame, FEC, and bundle parsers; +- transfer checksum treated only as error detection, never authenticity; +- inner bundle authentication before any semantic or Composer state change; +- no automatic acknowledgment or reverse data path in the high-assurance + FOG-SX profile; +- physical inspection that transmit-only and receive-only devices cannot + silently reverse direction. + +Residual risk: optical, acoustic, electromagnetic, thermal, and human-mediated +channels may still leak information outside the protocol model. + +### TM-ENDPOINT-03: Backup, recovery, and multi-device leakage + +Backups or device synchronization copy identity and conversation state into a +weaker environment. + +Required responses: + +- backups encrypted and authenticated independently from the live store; +- backup keys or recovery secrets stored separately from backup ciphertext; +- explicit key and format version in every encrypted store; +- tested restore, rotation, revocation, and lost-device procedures; +- no server-side recovery secret capable of silently impersonating a user; +- multi-device behavior treated as a separate protocol state machine, not file + copying. + +### TM-APP-01: Malicious contact and conversation insider + +A valid contact sends malformed or adversarial messages, correlates replies, +publishes shared state, or exploits group membership changes. + +Required responses: + +- strict authenticated message parsing before rendering; +- no active content or automatic external resource loading; +- per-contact rate, size, and state-transition bounds; +- single-use reply material and safe exhaustion behavior; +- transcript and membership rules defined by the messaging specification; +- explicit warning that a storage writer may read and tombstone its own + dedicated directional stream when the selected capability construction + grants that authority; +- compromised-contact analysis for direct and group messaging; +- no promise that encryption prevents a recipient from revealing plaintext. + +### TM-OPS-01: Logging and metrics leakage + +Operators, attackers, or support workflows recover metadata from logs, +traces, crash reports, metrics, or dashboards. + +Required responses: + +- logs MUST NOT contain payloads, contacts, capabilities, message or packet + identifiers, full routes, per-packet delay, or fine-grained event timing; +- production debug logging MUST be disabled; +- metrics MUST be coarse, delayed, aggregated, thresholded, and reviewed for + differencing attacks; +- node health identifiers MUST not become message-flow identifiers; +- crash dumps and support bundles MUST be disabled or privacy-scrubbed by + default; +- retention MUST be minimal and explicitly documented. + +Residual risk: even aggregate metrics can leak during low-volume periods. +Metrics may need suppression rather than publication. + +### TM-SUPPLY-01: Build, dependency, and update compromise + +An attacker inserts malicious code, changes protocol parameters, substitutes a +binary, compromises a dependency, or rolls a node back. + +Required responses: + +- pinned reviewed dependencies and recorded provenance; +- reproducible or independently verifiable release builds where practical; +- multiple-person release review for security-critical changes; +- signed canonical release metadata with version and rollback constraints; +- separate release, authority, node, and user identity keys; +- secure boot or measured boot where the deployment profile supports it; +- emergency revocation that does not create an unauthenticated update path. + +### TM-CRYPTO-01: Key compromise and lifecycle failure + +A key is copied, reused, retained too long, generated weakly, or not revoked. + +Required responses: + +- a key inventory defining owner, purpose, scope, storage, lifetime, overlap, + backup, revocation, and destruction; +- distinct keys for authority identity, epoch signing, release signing, Noise, + KEMSphinx, message identity, ratchet state, storage, capability derivation, + and backup encryption; +- cryptographic randomness from reviewed operating-system facilities; +- authenticated encrypted storage for long-lived private material; +- support for old verification keys only for explicit bounded migration; +- immediate stop of new use after compromise, followed by revocation and + replacement; +- best-effort zeroization without claiming that managed runtimes erase every + copy. + +### TM-CRYPTO-02: Store now, decrypt later and protocol downgrade + +An adversary records traffic for future classical or quantum cryptanalysis or +forces an older suite. + +Required responses: + +- suite and geometry authorization bound to signed epoch consensus; +- protocol identifiers authenticated in every relevant transcript; +- no silent fallback or operator-selectable downgrade; +- hybrid post-quantum constructions considered only after full packet, + performance, side-channel, and implementation review; +- bounded suite overlap and a published retirement procedure; +- claims separated into classical confidentiality, forward secrecy, and + post-quantum confidentiality. + +Residual risk: no deployed post-quantum construction can eliminate future +cryptanalytic uncertainty or endpoint compromise. + +### TM-AVAIL-01: Flooding, storage exhaustion, and distributed denial of service + +An adversary creates clients, packets, reads, writes, fragments, or authority +traffic at scale. + +Required responses: + +- strict size, rate, queue, memory, CPU, and concurrency bounds at every + boundary; +- admission or capability controls that avoid stable cross-service identity; +- non-amplifying error handling and bounded cryptographic work before + authentication where possible; +- storage quotas, expiry, tombstones, garbage collection, and overload policy; +- graceful shedding that preserves packet-class indistinguishability where + possible; +- simulation and load testing with malicious distributions; +- no availability claim against sustained global distributed attack. + +## 10. Trust Effects of Role Compromise + +The expected effect of isolated compromise is summarized below. Collusion can +combine observations and exceed these bounds. + +| Compromised role | Immediate exposure | Property that should remain | +| --- | --- | --- | +| Composer | That endpoint's plaintext, keys, contacts, and future local actions | Other users and unrelated conversations | +| Blind relay | Local endpoint context and FOG-use timing | Message plaintext and internal route secrecy | +| Entry | Relay address, first-hop traffic, next mix, and short-lived return metadata | Plaintext and final recipient | +| One mix | Adjacent hops, local timing and local secret state | End-to-end content; route unlinkability if another relevant mix is honest | +| One authority | Descriptors, votes and ability to withhold | Consensus authenticity without threshold compromise | +| Courier | Service request pattern and required replica routing | Message plaintext and direct client network identity | +| One replica | Local opaque records and access timing | Plaintext and complete mailbox stream | +| Native service | Explicit service-level fields | Network source and unrelated application state | +| Observer | Approved coarse aggregates | Per-message and per-user activity | +| Edge bridge | External-side protocol metadata | No extension of core anonymity through that bridge | + +The architecture specification MUST refine this table into exact information +flows and process privileges before implementation. + +## 11. Composer Security Profiles + +### 11.1 MicroVM profile + +The MicroVM MUST have no virtual network interface. Its host integration MUST +be limited to explicit display, human input, and the smallest feasible +transfer device. Clipboard sharing, host filesystem mounts, drag-and-drop, +guest agents, shared memory, audio input, camera input, and USB passthrough +MUST be disabled unless a later profile specifies and analyzes them. + +The MicroVM profile assumes the host can deny service and observe coarse user +activity. It MUST NOT claim protection from a host that can inspect guest +memory or replace the hypervisor. + +### 11.2 Portable profile + +The Portable profile MUST boot from signed immutable media, disable network +and radio support, avoid automatically mounting internal disks, and keep +mutable state in an authenticated encrypted store. Boot integrity and update +verification MUST occur without requiring an unauthenticated online channel. + +The high-assurance profile MUST NOT connect its Composer media to an online +computer. A removable device shuttled between online and offline systems is a +separate lower-assurance profile. + +Both profiles distinguish authenticated local state from freshness. A local +hash or MAC chain cannot detect replacement of the complete vault, journal, +and keys by an older coherent copy. A complete rollback-detection claim +requires a monotonic anchor outside the stated rollback domain. A host- +controlled virtual TPM is not independent of a hostile MicroVM host. + +### 11.3 Import and export boundary + +Consensus, contact vouchers, messages, updates, recovery packages, and FOG-SX +bundles are untrusted until cryptographically verified. Human-readable labels, +filenames, MIME types, QR presentation, or transport checksums do not confer +authenticity. + +## 12. Key Management Requirements + +Every protocol specification MUST provide a key-lifecycle table containing: + +- key name and cryptographic purpose; +- generating component and entropy source; +- authorized readers and operations; +- storage and hardware assumptions; +- epoch, session, message, or long-term lifetime; +- current and next-key overlap; +- compromise impact; +- revocation and recovery procedure; +- backup policy; +- destruction and residual-copy limitations. + +Private keys MUST NOT appear in source repositories, examples, test fixtures, +logs, command lines, crash reports, container images, or consensus documents. +Tests MUST use generated ephemeral fixtures. + +Authority, release, and offline recovery keys SHOULD have distinct offline +storage and ceremonies. Online services MUST receive only the minimum +short-lived material required for their role. + +## 13. Protocol Parsing and Failure Behavior + +Every parser MUST: + +- operate on a versioned format with an exact maximum size; +- reject non-canonical, truncated, overlong, duplicate, unknown-critical, and + trailing data where the format does not explicitly permit it; +- bound allocations, nesting, fragment count, decompression, FEC work, and + cryptographic work; +- authenticate before acting on semantic content whenever the protocol allows; +- avoid detailed remote errors that create parsing or validity oracles; +- produce deterministic local error classes suitable for testing without + logging sensitive input; +- be covered by conformance vectors, mutation tests, fuzzing, and differential + tests where an independent implementation exists. + +Core nodes MUST NOT parse generic archives, office documents, HTML, scripts, +or executable formats. + +## 14. Privacy-Preserving Operations + +Operational tooling is within the threat model. Deployments MUST define: + +- which metrics exist, their aggregation window, release delay, and minimum + population threshold; +- which logs exist, their fields, retention, access control, and deletion; +- how operators diagnose packet loss without packet-level identifiers; +- how key ceremonies, node admission, revocation, and emergency changes are + audited; +- how configuration drift and unsafe debug modes are detected; +- how time synchronization failure is detected without creating a single + trusted time source; +- how backup restore and disaster recovery avoid cloning live identities or + replay state. + +Public dashboards MUST suppress data when aggregation would reveal the +activity of a small number of users or nodes. + +## 15. Claims FOG Does Not Make + +FOG does not claim: + +- protection after sender or recipient Composer compromise; +- guaranteed delivery or resistance to arbitrary denial of service; +- absolute anonymity or zero metadata leakage; +- protection when all relevant route positions collude; +- that a three-node PoC has a meaningful production anonymity set; +- that encryption prevents recipients from revealing plaintext; +- permanent storage, guaranteed offline retrieval beyond the active retention + window, or deletion from every recipient, backup, journal, and adversarial + copy after a tombstone; +- deniability unless the selected messaging protocol explicitly provides and + validates it; +- post-quantum security based only on the presence of a post-quantum primitive; +- privacy across SMTP, NNTP, web, or foreign-network bridges equal to the FOG + core; +- protection from all physical, electromagnetic, acoustic, supply-chain, + coercion, or future cryptanalytic attacks; +- that permissioned admission completely prevents concealed Sybil control; +- that offline operation automatically makes a compromised device safe. + +## 16. Validation and Claim Gates + +### 16.1 Before the local PoC + +Required evidence: + +- normative architecture and trust-boundary specification; +- PKI, wire, packet, profile, messaging, storage, Composer, and FOG-SX + specifications; +- exact packet and message geometry; +- protocol state machines and key-lifecycle tables; +- conformance vectors and parser limits; +- simulator scenarios and measurable privacy metrics. + +`FOG-SIMULATION.md` supplies the first deterministic scenario and proxy-metric +baseline. This gate remains incomplete because formal end-to-end observer +metrics, replies, retries, storage polling, loop health, queues, congestion, +user behavior, churn, confidence intervals, and PoC trace calibration remain +open. + +`FOG-LOCAL-POC.md` supplies the first machine-readable local deployment and +fault-matrix definition. Its strict validator fixes the functional-only claim, +role separation, pairwise reachability, private state and secret scopes, +container containment, and required failure cases. The runnable fixture and +protocol-functional evidence remain open. + +### 16.2 Before an operator alpha + +Required evidence: + +- unit, integration, conformance, fuzz, race, and fault-injection testing; +- replay, tagging, n-1, flooding, clock, stale-consensus, split-authority, + node-loss, and storage-loss tests; +- simulation using observed or conservatively modeled traffic distributions; +- at least six mix nodes with two per layer and documented operator-family, + provider, ASN, and location diversity; +- at least three authorities with a 2-of-3 quorum; +- at least four storage replicas; +- reproducible deployment and rollback procedures; +- public documentation that alpha anonymity is experimental. + +### 16.3 Before production security claims + +Required evidence: + +- independent cryptographic and implementation review; +- resolved high-severity findings and published residual risks; +- realistic traffic volume and mandatory cover traffic; +- operational exercises for compromise, revocation, partition, restore, and + authority replacement; +- independently verified release provenance; +- quantitative claim statements tied to exact software, profile, topology, + traffic assumptions, and review version. + +Every public claim MUST identify its scope, assumptions, adversary capability, +software version, network profile, evidence, and known counterexamples. + +## 17. Open Security Decisions + +The following remain unresolved and block stronger claims: + +- activation of a byte-exact reviewed message profile after the structural + `FOG-MESSAGING` contract and its non-active PQXDH, Triple Ratchet, and + ML-KEM Braid candidate pass implementation and integration review; +- activation of the non-active SHA3-256 and ML-DSA-65 plus Ed25519 PKI + candidates after exact encoding, vectors, separability, implementation, and + side-channel review; +- the exact entry and mutual hybrid post-quantum Noise constructions and + reviewed library; X-Wing is only the leading KEM to evaluate; +- activation of the calculated HPQC split-PRF KEMSphinx candidate after exact + dependency, primitive, side-channel, complete-packet benchmark, vector, + simulation, and independent review; +- activation of a byte-exact reviewed storage profile after the structural + `FOG-STORAGE` contract and its non-active narrow BACAP/Pigeonhole candidate + pass receipt, geometry, implementation, DoS, and integration review; +- activation of exact Composer vault, platform, bundle, recovery, update, and + independent monotonic-anchor profiles after the structural + `FOG-COMPOSER` contract passes fault and implementation review; +- activation of one exact numeric FOG-SX joint profile after its fixed frame, + object, padding, parser, no-ACK, and physical-direction contract passes FEC, + implementation, license and IPR, resource, vector, hardware, and independent + review; +- cover, loop, delay, retry, retrieval, and acknowledgment distributions; +- identity compromise, revocation, contact recovery, and multi-device state + beyond the identity-only non-resumable recovery baseline; +- safe anonymous admission and rate control for clients; +- measurable degraded-mode thresholds and shutdown behavior. + +No implementation convenience may silently resolve these questions. + +## 18. Residual Risk Statement + +Even a correctly implemented FOG network will retain risk from endpoint +compromise, malicious contacts, long-term statistical disclosure, low traffic, +operator collusion, concealed common control, active denial of service, +physical and supply-chain compromise, bridge metadata, coercion, and future +cryptanalysis. Cover traffic and delay exchange bandwidth and latency for +reduced observer advantage; they do not create certainty. + +FOG's security documentation MUST remain a living record of observed attacks, +failed assumptions, simulation results, operational incidents, and changed +claims. + +## 19. Primary References + +- FOG messaging protocol: `FOG-MESSAGING.md` +- FOG storage protocol: `FOG-STORAGE.md` +- FOG Composer protocol: `FOG-COMPOSER.md` +- FOG simplex transfer protocol: `FOG-SX.md` +- FOG observability protocol: `FOG-OBSERVABILITY.md` +- FOG cryptographic suite evaluation: `FOG-CRYPTO-SUITES.md` +- FOG traffic and topology simulation: `FOG-SIMULATION.md` +- FOG local Podman PoC: `FOG-LOCAL-POC.md` +- Danezis and Goldberg, *Sphinx: A Compact and Provably Secure Mix Format*: + +- Piotrowska et al., *The Loopix Anonymity System*: + +- Infeld et al., *Echomix: a Strong Anonymity System with Messaging*: + +- Katzenpost mixnet threat model: + +- Katzenpost protocol specifications: + +- Noise Protocol Framework: + + +References inform FOG's attack coverage and terminology. They do not make FOG +secure by inheritance. FOG requires its own profiles, proofs or analyses, +tests, simulations, deployments, and reviews. diff --git a/docs/FOG-WIRE.md b/docs/FOG-WIRE.md new file mode 100644 index 0000000..1f0964a --- /dev/null +++ b/docs/FOG-WIRE.md @@ -0,0 +1,1246 @@ +# FOG Wire Protocol + +Status: Draft 0.1 + +Date: 2026-08-08 + +## 1. Purpose + +This document defines `FOG-WIRE-1`, the authenticated transport protocol used +between adjacent online FOG roles. + +It refines the following baselines: + +- `FOG-THREAT-MODEL.md`, especially `TM-NET-01`, `TM-NET-03`, `TM-NET-04`, + `TM-NET-05`, `TM-NET-06`, `TM-PKI-02`, `TM-ROLE-01`, `TM-ROLE-02`, + `TM-ROLE-03`, `TM-OPS-01`, `TM-CRYPTO-01`, `TM-CRYPTO-02`, and + `TM-AVAIL-01`; +- `FOG-ARCHITECTURE.md`, especially `ARC-003`, `ARC-004`, `ARC-005`, + `ARC-006`, `ARC-007`, `ARC-008`, `ARC-009`, `IF-03`, `IF-04`, `IF-06`, + `IF-07`, `IF-08`, `IF-09`, `IF-10`, and `IF-11`; +- `FOG-PKI.md`, especially consensus-authorized profiles, endpoints, + role-specific epoch public keys, monotonic profile state, and hard expiry. + +FOG is not implemented. Requirements in this document are protocol targets, +not statements about deployed security. + +The key words MUST, MUST NOT, REQUIRED, SHOULD, SHOULD NOT, and MAY describe +normative requirements in the sense of BCP 14 when they appear in uppercase. + +## 2. Scope + +`FOG-WIRE-1` owns: + +- the version-1 TCP carrier profile; +- selection of one exact consensus-authorized Noise profile; +- responder-only authentication for relay-to-entry links; +- mutual authentication for node, authority, storage, and observer links; +- binding of network, epoch, consensus, role, peer, key, and link context to + the Noise handshake; +- handshake and transport framing; +- fixed-size encrypted records and bounded logical-message fragmentation; +- command allowlists by link context and direction; +- per-record cipher-state rekey and bounded fresh handshakes; +- epoch and profile transition behavior; +- connection ownership, timeouts, backoff, overload, and failure behavior; +- key lifecycle, privacy-safe diagnostics, and conformance requirements. + +`FOG-WIRE-1` does not own: + +- message-level end-to-end encryption; +- KEMSphinx packet construction, routing, replay tags, or SURBs; +- the entry submission capsule or return rendezvous; +- storage capability semantics or replica durability; +- application acknowledgments, retries, or deduplication; +- mix delays, route selection, cover rates, or retrieval schedules; +- consensus construction or profile authorization; +- local administration, release updates, public mirror HTTP, or Composer + transfer formats. + +Noise protects one adjacent connection. It does not hide IP endpoints, TCP +connection existence, timing, direction, duration, or byte volume. It does +not replace KEMSphinx or end-to-end message encryption. + +## 3. Security Goals + +FOG-WIRE MUST provide, for one accepted connection: + +- authentication of every required online endpoint before application work; +- anonymity at the Noise identity layer for a blind relay connecting to an + entry; +- confidentiality and integrity of transport records after the handshake; +- forward secrecy to the extent supplied by the selected reviewed Noise or + PQNoise profile and correct erasure of ephemeral state; +- exact authorization of role adjacency and command direction; +- authenticated agreement on the network, epoch, consensus, profile, roles, + node identifiers, and transport-key identifiers; +- no silent suite, pattern, carrier, role, or version downgrade; +- fixed ciphertext length for records within one profile; +- bounded parsing, allocation, reassembly, cryptographic work, and queues; +- coarse non-amplifying remote failures; +- deterministic profile and epoch retirement. + +FOG-WIRE does not provide: + +- anonymity against an observer who can correlate connection timing or + volume; +- protocol camouflage or censorship resistance; +- authentication of a blind relay as a stable user or network identity; +- application-message authenticity or recipient authentication; +- KEMSphinx replay protection; +- delivery, liveness, or availability against sustained denial of service; +- safe operation after compromise of both endpoint processes; +- post-quantum security merely because one profile contains a post-quantum + primitive. + +## 4. Protocol Invariants + +### WIRE-INV-01: One authenticated profile + +Every connection uses exactly one immutable wire profile authorized by the +accepted consensus. Peers MUST NOT negotiate a list, guess a profile, or fall +back after failure. + +### WIRE-INV-02: One link context + +Every connection has exactly one link context that fixes the initiator role, +responder role, authentication mode, permitted direction, and command +allowlist. + +### WIRE-INV-03: No early application data + +Every Noise handshake payload is empty in version 1. No FOG command is sent, +accepted, buffered as trusted, or acted upon before the handshake completes +and peer authorization succeeds. Session resumption, PSK resumption, and +0-RTT application data are forbidden. + +### WIRE-INV-04: Consensus is the authority + +Operator configuration MAY restrict peers further, but MUST NOT add a peer, +role, endpoint, key, context, or profile absent from the accepted consensus or +trusted authority manifest. + +### WIRE-INV-05: Fixed encrypted records + +All post-handshake records on one connection have the exact ciphertext length +defined by its profile. Application type and semantic body length MUST NOT +change that record length. + +### WIRE-INV-06: Strict ordered state + +Version 1 runs over ordered TCP. Noise transport nonces and cipher-state +rekeys advance exactly once per successfully sent or received record. A +missing, duplicated, unauthentic, or out-of-order record terminates the +connection. + +### WIRE-INV-07: Purpose-separated keys + +Noise static keys, node identity keys, authority vote keys, KEMSphinx keys, +entry capsule keys, replica-envelope keys, message keys, queue-sealing keys, +and release keys are distinct. One key MUST NOT serve two of these purposes. + +### WIRE-INV-08: Bounded work before trust + +Preface, handshake, frame, record, fragment, connection, and queue work is +bounded before allocation proportional to attacker-controlled input. A peer +cannot request an arbitrary algorithm, record size, or reassembly budget. + +### WIRE-INV-09: Failure does not widen access + +Failure never causes plaintext transport, direct client fallback, layer +skipping, a new role edge, an older profile, a previous epoch, or a generic +RPC path. + +### WIRE-INV-10: Traffic scheduling is external but mandatory + +FOG-WIRE supplies fixed records and a padding record. The authenticated cover +profile decides when records are emitted. A local keepalive choice MUST NOT +silently replace or modify that traffic schedule. + +## 5. Terminology and Byte Order + +Terms used in this specification: + +- **wire profile**: immutable mapping from one numeric profile identifier to + an exact carrier, Noise protocol name, record geometry, limits, timers, + socket behavior, and compatible command-shape registry; +- **link context**: immutable role adjacency and directional command policy; +- **preface**: the fixed 32-byte cleartext connection header; +- **prologue**: the canonical byte string supplied to both Noise handshake + states and authenticated by the handshake transcript; +- **record**: one fixed-size plaintext structure encrypted as one Noise + transport message; +- **logical message**: one command body carried in one or more DATA records; +- **current consensus**: the accepted consensus valid for new work at the + local uncertainty interval; +- **staged consensus**: a valid future consensus that is not yet active for + new work. + +All integer fields defined directly by FOG-WIRE are unsigned and encoded in +network byte order, most significant byte first. Byte arrays have the exact +declared length. Reserved and padding bytes MUST be zero after decryption or +when transmitted in cleartext. + +TCP is a byte stream. An implementation MUST NOT assume that one write equals +one read, that one TCP segment equals one FOG frame, or that a partial read is +an error. + +## 6. Version-1 Carrier + +The only version-1 carrier is TCP as specified by RFC 9293. + +The version-1 carrier profile: + +- uses one direct TCP connection between descriptor-authorized endpoints; +- does not add TLS, DTLS, QUIC, HTTP, WebSocket, SOCKS, a service mesh, or a + proxy protocol header; +- treats IPv4 and IPv6 endpoint forms only as permitted by the active profile; +- binds the responder key through Noise rather than DNS or X.509; +- does not trust TCP source address as a cryptographic identity; +- uses no in-band carrier autodetection; +- uses no TCP urgent data; +- treats a half-close as connection termination after bounded output drain. + +A future carrier requires a new immutable wire profile and explicit profile +transition. One port MUST NOT auto-detect TCP FOG-WIRE, TLS, QUIC, or any +foreign protocol. + +FOG-WIRE has a visible fixed preface and does not claim to be +indistinguishable from unrelated network traffic. Noise ephemeral public keys +may also be fingerprintable. Deployment documentation MUST state this +residual risk. + +## 7. Wire Profile Registry + +Every `wire_profile_id` is a non-zero unsigned 32-bit integer whose meaning is +permanent. Reusing an identifier for changed bytes or behavior is forbidden. + +An exact wire profile definition contains at least: + +```text +[ + wire_profile_id, + wire_version, + carrier_id, + noise_protocol_name, + authentication_mode, + permitted_link_context_ids, + record_plaintext_size, + command_shape_registry_id, + maximum_session_records_per_direction, + maximum_session_ciphertext_bytes_per_direction, + maximum_session_age, + preface_timeout, + handshake_timeout, + fragment_timeout, + drain_timeout, + reconnect_backoff_profile_id, + socket_behavior_profile_id +] +``` + +The registry definition is part of the normative protocol release and is +identified by an immutable digest in release metadata. Consensus +`active_profile_ids` authorizes identifiers, not operator-provided profile +bodies. Implementations MUST reject an authorized identifier they do not +implement exactly. + +The profile fixes one complete Noise protocol name. Pattern, KEM or DH, +cipher, hash, hybrid combiner, key encoding, handshake message count, and +handshake message sizes are not independently negotiated. + +The initial concrete classical or hybrid post-quantum Noise names and reviewed +library remain a pre-PoC selection gate. FOG MUST use a published reviewed +construction and maintained implementation. It MUST NOT create a FOG-specific +KEM, cipher, combiner, Noise token, or handshake extension. + +## 8. Authentication Modes + +### 8.1 Entry mode + +`ENTRY-AUTH-1` has these semantics: + +- the initiator is a blind relay with no static Noise identity in the + handshake; +- the responder is an entry whose exact epoch Noise public key is known from + accepted consensus; +- the initiator authenticates the responder; +- the responder learns no stable relay identity from Noise; +- both sides use fresh handshake ephemeral state; +- all handshake payloads are empty; +- application work begins only after the complete handshake. + +A classical profile MAY realize these semantics with the Noise `NK` pattern +while mandating an empty first handshake payload. A post-quantum or hybrid +profile MUST name one reviewed equivalent construction explicitly. Support in +the abstract Noise framework is not authorization to improvise a conversion. + +The entry authenticates each later submission capsule under +`FOG-SPHINX-PROFILES`. A TCP address, client certificate, username, API token, +or reusable relay key MUST NOT become a durable FOG user identity. + +### 8.2 Mutual node mode + +`NODE-MUTUAL-1` has these semantics: + +- initiator and responder have distinct role-local static Noise keys; +- each endpoint knows the exact expected peer public key before connecting; +- each endpoint proves possession of its corresponding private key; +- both endpoints use fresh handshake ephemeral state; +- all handshake payloads are empty; +- application work begins only after complete mutual authentication. + +A classical profile MAY realize these semantics with Noise `KK`. A +post-quantum or hybrid profile MUST name a reviewed mutually authenticated +equivalent construction. The peer keys come from the same accepted consensus, +except authority wire keys, which come from root-certified authority wire-key +certificates referenced by that consensus. + +### 8.3 No optional authentication + +Version 1 has no anonymous-to-mutual upgrade, dummy identity, user password, +bearer token, client certificate, or authentication extension inside the +handshake. A link context determines one mode before parsing begins. + +## 9. Link Context Registry + +Version 1 reserves these link contexts: + +| ID | Name | Initiator | Responder | Mode | Connection use | +| --- | --- | --- | --- | --- | --- | +| 1 | `RELAY_ENTRY` | relay | entry | `ENTRY-AUTH-1` | submission and bounded return traffic | +| 2 | `ENTRY_LAYER1` | entry | layer-1 mix | `NODE-MUTUAL-1` | forward and reply KEMSphinx packets | +| 3 | `LAYER1_LAYER2` | layer-1 mix | layer-2 mix | `NODE-MUTUAL-1` | forward and reply KEMSphinx packets | +| 4 | `LAYER2_LAYER3` | layer-2 mix | layer-3 mix | `NODE-MUTUAL-1` | forward and reply KEMSphinx packets | +| 5 | `LAYER3_TERMINAL` | layer-3 mix | courier or authorized native service | `NODE-MUTUAL-1` | terminal request and anonymous reply | +| 6 | `COURIER_STORE` | courier | store | `NODE-MUTUAL-1` | bounded replica operations | +| 7 | `STORE_STORE` | lower `node_id` store | higher `node_id` store | `NODE-MUTUAL-1` | replica synchronization only | +| 8 | `NODE_AUTHORITY` | admitted node | authority | `NODE-MUTUAL-1` | descriptor submission only | +| 9 | `AUTHORITY_AUTHORITY` | lower `authority_id` | higher `authority_id` | `NODE-MUTUAL-1` | PKI protocol objects only | +| 10 | `ROLE_OBSERVER` | reporting role | observer | `NODE-MUTUAL-1` | approved aggregate reports only | + +The numeric ordering rule gives contexts 7 and 9 one canonical connector and +prevents persistent duplicate connections. Numeric comparison is over the +complete identifier bytes. + +The reply direction MAY use an already authenticated adjacency connection in +the reverse record direction. It does not authorize a new topology edge. The +packet profile determines which peer is the next authorized reply hop. + +Adding a context or changing its role pair, direction, mode, or command set +requires a new wire version or immutable profile transition. Operator +configuration cannot create a custom context. + +## 10. Peer Authorization + +Before starting a mutual handshake, each side MUST derive an expected peer +record from one accepted network view: + +```text +ExpectedPeer = ( + network_id, + epoch, + consensus_hash, + link_context_id, + local_role, + remote_role, + local_identifier, + remote_identifier, + local_wire_key_id, + remote_wire_key_id, + local_endpoint, + remote_endpoint +) +``` + +For node links, both node descriptors MUST be valid for the selected epoch, +their effective consensus roles and layers MUST match the link context, and +their Noise keys MUST have the `node Noise transport` purpose. + +For authority links, the authority identifier and dedicated wire public key +MUST validate through an `AuthorityWireKeyCertificate` signed by the matching +offline authority root. An authority vote key MUST NOT be used as a Noise +key. + +For `RELAY_ENTRY`, the relay identifier and key identifier are absent and are +encoded as all-zero values in the prologue. The entry descriptor, role, +endpoint, profile, and epoch key are still validated exactly. + +Authorization MUST be repeated before exposing a completed handshake to the +role implementation. A peer removed, suspended, revoked, reassigned, expired, +or no longer adjacent under consensus MUST NOT continue new work merely +because an old TCP connection remains open. + +## 11. Cleartext Preface + +Every connection begins with exactly one 32-byte `WirePreface`: + +```text +struct WirePreface { + byte magic[8]; // ASCII "FOGWIRE1" + uint16 wire_version; // 1 + uint32 wire_profile_id; + uint16 link_context_id; + uint64 epoch; + uint16 flags; // 0 + byte reserved[6]; // all zero +} +``` + +The preface is transmitted by the initiator and is not encrypted. The +responder MUST read all 32 bytes within the profile preface timeout and then: + +1. compare `magic` to the exact eight ASCII bytes `FOGWIRE1`; +2. require `wire_version` equal to 1; +3. require zero flags and reserved bytes; +4. require a locally implemented profile authorized for the declared epoch; +5. require the context to be permitted by that profile and listener; +6. require the declared epoch to be current or explicitly staged under + Section 23; +7. select exactly one handshake state without probing alternatives. + +The preface does not negotiate. The responder sends no selection, supported +list, retry profile, or downgrade hint. Any failure closes the TCP connection +without protocol bytes. + +## 12. Authenticated Noise Prologue + +Both peers construct the exact same byte string before initializing Noise: + +```text +WirePrologue = EncodeFixed( + "FOG-WIRE-PROLOGUE-1", + WirePreface, + network_id, + consensus_hash, + initiator_role_id, + responder_role_id, + initiator_identifier, + responder_identifier, + initiator_wire_key_id, + responder_wire_key_id +) +``` + +`EncodeFixed` is concatenation of the exact-width fields in the order shown. +The domain string is 19 ASCII bytes without a terminator. `network_id`, +`consensus_hash`, each identifier, and each key identifier use the hash output +length pinned by the trusted PKI suite. Role identifiers are unsigned 16-bit +integers. Absent relay identifier and key fields are all zero. + +No field is length-prefixed because every width is determined by the trusted +PKI suite and wire version. Implementations MUST publish byte-exact prologue +fixtures for every context. + +The complete `WirePrologue` is supplied as the Noise prologue input. It is +authenticated by the resulting handshake transcript but is not secret and is +not treated as extra key material. + +The implementation MUST retain the final Noise handshake hash as a +connection-local channel-binding value until connection teardown. It MUST NOT +log, publish, or reuse that value as a cross-session identifier. + +## 13. Handshake Framing and State Machine + +Each Noise handshake message is carried as: + +```text +struct HandshakeFrame { + uint16 noise_message_length; + byte noise_message[noise_message_length]; +} +``` + +Rules: + +- `noise_message_length` MUST be non-zero and at most 65,535; +- the profile MUST define the exact handshake message count and expected + length of every message; +- a received length different from the profile expectation terminates the + connection before proportional allocation; +- every Noise handshake payload is zero bytes; +- trailing, extra, duplicated, or out-of-order handshake frames terminate the + connection; +- the complete handshake MUST finish within the profile handshake timeout; +- a Noise parse, decapsulation, DH, AEAD, key, or authentication failure + terminates the connection; +- no application-specific error is sent during the handshake; +- ephemeral private state and incomplete cipher state are erased on success, + failure, or timeout to the extent supported by the runtime. + +The initiator state machine is: + +```text +TCP_CONNECTED + -> SEND_PREFACE + -> NOISE_HANDSHAKE + -> VERIFY_RESPONDER_OR_BOTH_PEERS + -> TRANSPORT + -> DRAIN_OR_FAIL + -> CLOSED +``` + +The responder state machine is: + +```text +TCP_ACCEPTED + -> READ_AND_VALIDATE_PREFACE + -> NOISE_HANDSHAKE + -> VERIFY_INITIATOR_IF_REQUIRED + -> TRANSPORT + -> DRAIN_OR_FAIL + -> CLOSED +``` + +There is no resumption state and no transition from a failed state back to +handshake on the same TCP connection. + +## 14. Transport Framing + +After a successful handshake, each record is carried as: + +```text +struct TransportFrame { + uint16 ciphertext_length; + byte ciphertext[ciphertext_length]; +} +``` + +For one connection, `ciphertext_length` is constant and equals: + +```text +record_plaintext_size + 16 +``` + +The 16 bytes are the Noise AEAD authentication overhead. The concrete profile +MUST use a Noise cipher function with this standard overhead. + +`record_plaintext_size` MUST be a multiple of 256 between 4,096 and 65,280 +bytes inclusive. Consequently every ciphertext fits the standard Noise +65,535-byte message limit. FOG-WIRE does not adopt a larger non-standard Noise +message limit. + +A receiver MUST validate the two-byte length against the one expected +constant before allocating or reading the ciphertext. Zero, short, overlong, +or profile-mismatched lengths terminate the connection. + +TCP split and coalescing are transparent to this framing. One implementation +MUST interoperate when every framing byte arrives in a separate TCP read and +when multiple frames arrive in one read. + +## 15. Record Plaintext + +Every decrypted record has this exact layout: + +```text +struct WireRecord { + uint8 record_version; // 1 + uint8 record_type; + uint16 flags; // 0 + byte message_id[16]; + uint16 command_id; + uint16 command_version; + uint32 fragment_index; + uint32 fragment_count; + uint32 total_length; + uint16 fragment_length; + uint16 reserved; // 0 + byte fragment_and_padding[record_plaintext_size - 40]; +} +``` + +The 40-byte header is fixed. Version 1 defines: + +| `record_type` | Name | Meaning | +| --- | --- | --- | +| 0 | `PADDING` | traffic-schedule record, discarded after validation | +| 1 | `DATA` | one fragment of one allowed logical command | +| 2 | `CLOSE` | authenticated planned close or fresh-handshake rotation | + +Unknown versions, types, flags, commands, command versions, or non-zero +reserved values are critical errors and terminate the connection. + +### 15.1 Padding record + +For `PADDING`, every field after `flags`, including all payload bytes, MUST be +zero. The receiver validates and discards it. It MUST pass through the same +Noise decrypt, length check, record rekey, accounting, and aggregate metrics +path as DATA records before semantic discard. + +### 15.2 Close record + +For `CLOSE`, `command_id` is a coarse close class: + +- 0: planned normal close; +- 1: authenticated fresh-handshake or epoch rotation. + +Every other header field after `flags`, except `command_id`, and all payload +bytes MUST be zero. +Failure, authentication, parsing, overload, or authorization errors do not +send a CLOSE reason. They close the socket without protocol bytes. + +### 15.3 Data record + +For `DATA`: + +- `message_id` MUST be a fresh non-zero 16-byte value generated from the + operating-system CSPRNG and unique within the connection; +- `command_id` and `command_version` MUST be allowed for the context, + direction, role pair, epoch, and command-shape registry; +- `fragment_count` MUST be from 1 through 4,096; +- `fragment_index` MUST be less than `fragment_count`; +- `total_length` MUST be no greater than 8 MiB and within the lower + context-specific limit; +- `fragment_length` MUST be no greater than + `record_plaintext_size - 40`; +- every non-final fragment MUST fill the complete fragment capacity; +- the final fragment length MUST equal the remaining declared body length; +- `fragment_count` MUST equal the unique count implied by total length and + fragment capacity; +- unused `fragment_and_padding` bytes MUST be zero. + +Zero-length command bodies use exactly one DATA record with zero fragment +length and are valid only when the command registry explicitly permits them. + +`message_id` is a connection-local reassembly handle, not a capability, +account, authentication token, delivery identifier, storage key, or +cross-session replay defense. A narrower command protocol owns idempotency and +deduplication across reconnects. + +## 16. Logical-Message Reassembly + +A receiver MAY interleave fragments from multiple logical messages only +within all profile bounds. It MUST: + +1. authenticate and validate each complete record before reading its fields; +2. reject duplicate fragment indices and inconsistent repeated metadata; +3. avoid allocation based only on `fragment_count` or `total_length`; +4. allocate or spool incrementally within a fixed connection budget; +5. accept at most eight incomplete logical messages per connection; +6. accept at most 16 MiB of incomplete reassembly state per connection; +7. expire incomplete messages at the profile fragment timeout; +8. erase partial bodies on timeout, connection loss, parse failure, or role + rejection; +9. invoke command handling only after exact complete reassembly and final + shape validation. + +A context profile MAY lower the 8 MiB message limit, eight-message count, or +16 MiB buffer budget. It MUST NOT raise them in wire version 1. + +Implementations SHOULD stream authenticated fragments into bounded +role-local temporary storage when a command legitimately exceeds the in-memory +budget. Temporary files MUST be private to the role account, unlinked or +randomly named without peer identifiers, size-bounded, and deleted on every +terminal path. + +## 17. Command Registry and Direction + +Version 1 reserves these command families: + +| ID | Command | Body owner | +| --- | --- | --- | +| 1 | `PACKET_SUBMIT` | future `FOG-ENTRY-CAPSULE` specification | +| 2 | `PACKET_FORWARD` | `FOG-SPHINX-PROFILES` fixed KEMSphinx packet | +| 3 | `PACKET_RETURN` | `FOG-SPHINX-PROFILES` bounded relay return object | +| 256 | `REPLICA_REQUEST` | `FOG-STORAGE` | +| 257 | `REPLICA_RESPONSE` | `FOG-STORAGE` | +| 258 | `REPLICA_SYNC` | `FOG-STORAGE` | +| 512 | `DESCRIPTOR_UPLOAD` | `FOG-PKI` | +| 513 | `DESCRIPTOR_RESULT` | `FOG-PKI` | +| 514 | `AUTHORITY_OBJECT` | `FOG-PKI` | +| 768 | `AGGREGATE_REPORT` | `FOG-OBSERVABILITY` | + +The directional allowlist is: + +| Context | Initiator to responder | Responder to initiator | +| --- | --- | --- | +| `RELAY_ENTRY` | `PACKET_SUBMIT` | `PACKET_RETURN` | +| `ENTRY_LAYER1` | `PACKET_FORWARD` | `PACKET_FORWARD` | +| `LAYER1_LAYER2` | `PACKET_FORWARD` | `PACKET_FORWARD` | +| `LAYER2_LAYER3` | `PACKET_FORWARD` | `PACKET_FORWARD` | +| `LAYER3_TERMINAL` | `PACKET_FORWARD` | `PACKET_FORWARD` | +| `COURIER_STORE` | `REPLICA_REQUEST` | `REPLICA_RESPONSE` | +| `STORE_STORE` | `REPLICA_SYNC` | `REPLICA_SYNC` | +| `NODE_AUTHORITY` | `DESCRIPTOR_UPLOAD` | `DESCRIPTOR_RESULT` | +| `AUTHORITY_AUTHORITY` | `AUTHORITY_OBJECT` | `AUTHORITY_OBJECT` | +| `ROLE_OBSERVER` | `AGGREGATE_REPORT` | none | + +`PACKET_FORWARD` in the reverse record direction is permitted only when the +packet profile validates the receiving peer as the next hop of an anonymous +reply. It does not allow arbitrary reverse RPC. + +Every narrower specification MUST define exact body bytes, version, maximum +size, expected padded size class, request-to-response relation, idempotency, +semantic timeout, and behavior after reconnect. Unknown commands never reach +role code. + +There is no vendor, experimental, operator-private, or generic-RPC command +range in version 1. + +## 18. Command Shapes and Length Privacy + +The active `command_shape_registry_id` maps each allowed command to: + +- one exact logical body length; or +- a small ordered set of public padded length classes; +- an exact record count for each class; +- a context-specific maximum in-flight count; +- whether zero-length bodies are valid. + +The sender pads the semantic object inside the owning protocol before passing +it to FOG-WIRE. FOG-WIRE zero-fills only unused space in the last fixed +record. It does not invent application padding classes. + +All native KEMSphinx applications use the same `PACKET_SUBMIT`, +`PACKET_FORWARD`, and `PACKET_RETURN` shapes for an active packet profile. +`fog-drop`, `fog-mailbox`, and `fog-im` MUST NOT select distinguishable wire +sizes. + +`FOG-SPHINX-PROFILES.md` defines the exact packet and return bodies. For the +non-active `FOG-SPHINX-CANDIDATE-MLKEM768-X25519-1`, `PACKET_FORWARD` is +16,150 bytes and `PACKET_RETURN` is 10,204 bytes. These calculated candidate +lengths do not authorize the suite. A wire profile paired with it must map each +body to one deterministic fixed record count and reject any other logical +length. + +The number and timing of records remain observable. A command shape is a +declared traffic class, not perfect length hiding. The simulator and owning +protocol MUST evaluate whether a class creates an unacceptable fingerprint. + +## 19. Cipher-State Rekey + +After encrypting and queueing each complete transport record, the sender MUST +call Noise `CipherState.Rekey()` on the outbound cipher state exactly once. +After successfully authenticating, decrypting, and validating the fixed +ciphertext length of each complete record, the receiver MUST call +`CipherState.Rekey()` on the inbound cipher state exactly once before +processing the record semantically. + +If authentication fails, the receiver does not advance or retry. It destroys +both transport cipher states and closes the connection. + +Noise rekey changes the current cipher key through a one-way function and does +not reset the Noise nonce. It does not add a fresh DH or KEM secret. Therefore +it MUST NOT be described as a replacement for a fresh authenticated +handshake. + +The separate inbound and outbound cipher states are never combined and +half-duplex Noise mode is forbidden. + +## 20. Fresh Handshake and Session Limits + +A connection MUST be replaced with a completely new TCP and Noise handshake +at the earliest of: + +- the profile maximum records sent in either direction; +- the profile maximum ciphertext bytes sent in either direction; +- the profile maximum session age; +- activation of a new required epoch key or wire profile; +- peer role, topology adjacency, endpoint, key, revocation, or authorization + change; +- any uncertainty about cipher-state synchronization; +- local secret-state restoration or process restart. + +Version-1 absolute ceilings are: + +| Item | Absolute ceiling | +| --- | --- | +| records per direction per session | 2^32 | +| ciphertext bytes per direction per session | 1 TiB | +| session age | 24 hours | + +Every concrete profile MUST select lower exact values based on cipher bounds, +cover rate, connection churn, epoch schedule, and measurements. + +The connector MAY establish one authenticated replacement in parallel. It +MUST NOT send new logical messages on the replacement until the handshake and +authorization complete. The old connection drains only already admitted +messages for the profile drain timeout, then closes. + +Logical-message retry across the replacement belongs to the command owner. A +transport reconnect MUST NOT silently report application success, replay an +unknown partial command, or create a second delivery without that command's +idempotency rule. + +## 21. Connection Ownership and Multiplicity + +There is at most one active data connection for one tuple: + +```text +( + network_id, + epoch, + wire_profile_id, + link_context_id, + initiator_identifier, + responder_identifier +) +``` + +`RELAY_ENTRY` uses one relay-local connection instance in place of an +initiator identifier. A relay MAY maintain the small temporary entry set +permitted by its authenticated cover profile, but the count MUST NOT vary +immediately with one user message. + +One additional authenticated connection MAY exist only as a staged +replacement. Any other duplicate is closed after authentication without +moving work to it. + +The canonical initiator in Section 9 owns reconnect attempts. A responder +does not open a reverse substitute connection. Nodes SHOULD maintain required +adjacency connections independently of application queue occupancy when the +cover profile requires it. + +## 22. Traffic Scheduling, Padding, and Liveness + +The active cover profile specifies connection maintenance and a record-emission +schedule for each privacy-relevant context. It may choose a measured constant, +Poisson, or other reviewed schedule, but all operators in that profile use the +same authenticated parameters. + +At each scheduled emission opportunity: + +- the role sends one queued DATA record allowed by the scheduling policy; or +- it sends one PADDING record when no eligible DATA record is selected. + +DATA arrival MUST NOT cause an undeclared immediate write that bypasses the +schedule. PADDING MUST NOT be disabled locally while a profile claims traffic +normalization. + +FOG-WIRE defines no `PING` or `PONG`. TCP itself has no sufficient application +liveness guarantee. Health is inferred from authenticated traffic, socket +failure, connection age, and separate coarse loop protocols. If a deployment +enables operating-system TCP keepalive only for dead-resource cleanup, its +exact settings MUST be profile-bound and it MUST NOT be counted as cover +traffic or anonymity evidence. + +Failure of required cover generation moves the role into the degraded or stop +state defined by the cover profile. It does not switch to activity-triggered +records. + +The first matrix in `FOG-SIMULATION.md` does not select this schedule. It shows +that high packet cover can improve simple timing and intersection proxies +without supplying adequate local mixing at short mean delay. Exact record +schedules still require queue, liveness, loop, polling, reply, and load models. + +## 23. Epoch and Profile Transitions + +The preface declares one epoch. The Noise prologue binds the exact consensus +hash for that epoch. Peers MUST NOT combine a descriptor, key, topology edge, +profile, or parameter from a different consensus body. + +During an authenticated transition: + +- a listener MAY accept the current epoch and one preannounced next epoch; +- a next-epoch connection MAY complete and remain staged before `valid_after`; +- no next-epoch application work is sent or accepted before `valid_after`; +- an old profile remains usable only through its signed overlap interval; +- minimum accepted profile state prevents rollback after promotion; +- a previous epoch connection stops accepting new work at its exact boundary; +- bounded draining MUST end before key or consensus hard expiry; +- expired private keys are erased after the owning protocol's last required + grace window; +- same-epoch consensus conflict freezes new connections and new work. + +A listener chooses the exact state directly from the preface and local +accepted PKI state. It does not try current and old parsers in sequence. + +At consensus `valid_until`, new handshakes and new logical messages under that +consensus stop. Existing connections MAY only perform an explicitly specified +bounded drain that does not outlive key authorization. + +## 24. Timeouts and Reconnect Backoff + +Every concrete wire profile defines exact values for: + +- TCP connect timeout; +- 32-byte preface timeout; +- complete Noise handshake timeout; +- fixed frame read and write timeout; +- incomplete logical-message fragment timeout; +- output drain timeout; +- maximum session age; +- reconnect initial delay, multiplier, ceiling, and jitter distribution. + +Version-1 absolute maxima are: + +| Timer | Maximum | +| --- | --- | +| preface | 30 seconds | +| complete handshake | 60 seconds | +| incomplete frame | 60 seconds | +| incomplete logical message | 5 minutes | +| drain | 30 seconds | +| reconnect ceiling | 15 minutes | + +Timeouts are monotonic-duration measurements, not peer-provided wall-clock +values. A timeout closes the connection without detailed remote error. + +Reconnect uses bounded exponential backoff with CSPRNG-derived full jitter. +The attempt schedule MUST NOT use node identifiers, message identifiers, +queue length, or application type as its random seed. Immediate deterministic +fallback to another route, layer, or older profile is forbidden. + +## 25. Backpressure and Resource Limits + +Before the local PoC, every role profile MUST set lower operational limits +within these version-1 absolute bounds: + +| Resource | Absolute bound | +| --- | --- | +| accepted unauthenticated connections per listener | 1,024 | +| concurrent handshakes per listener | 256 | +| active connections per authenticated peer/context | 1 plus 1 staged | +| encrypted record plaintext | 65,280 bytes | +| logical message | 8 MiB | +| fragments per logical message | 4,096 | +| incomplete messages per connection | 8 | +| incomplete reassembly bytes per connection | 16 MiB | +| queued complete logical messages per connection | 1,024 | +| command versions accepted per command | 2 during signed transition | + +Implementations MUST also bound total process connections, file descriptors, +handshake CPU, KEM decapsulations, per-source unauthenticated attempts, queue +bytes, write backlog, temporary files, and aggregate reassembly memory. + +When a queue is full, the role follows the owning command's shedding policy. +It MUST NOT allocate without bound, skip authentication, enlarge a packet, +send an error larger than the triggering record, or route around a required +layer. + +Entry-mode abuse control MAY use coarse short-lived network-source limits and +valid entry capsules, but MUST NOT create a stable cross-entry user account. +Safe anonymous admission and rate control remain an explicit open security +decision. + +## 26. Socket Behavior + +The immutable socket behavior profile fixes at least: + +- TCP no-delay behavior; +- keepalive enablement and exact cleanup timers if used; +- send and receive buffer ceilings; +- user-space write batching; +- address-family policy; +- dual-stack binding behavior; +- connection and accept backlog limits; +- maximum pending output bytes; +- graceful close and reset behavior. + +Operators MUST NOT tune these settings independently when they affect record +timing, batching, connection duration, or public traffic claims. The initial +values require trace measurement on supported operating systems because TCP +segmentation does not preserve FOG record boundaries. + +Proxy-protocol headers, transparent proxy source metadata, TLS termination, +and service-mesh sidecars are forbidden on core FOG-WIRE listeners. A +deployment requiring a network proxy is a separate analyzed profile and trust +boundary. + +## 27. Failure Behavior + +| Condition | Remote behavior | Local behavior | +| --- | --- | --- | +| invalid or unsupported preface | close without bytes | coarse counter | +| unauthorized epoch, profile, context, role, or endpoint | close without bytes | coarse authorization class | +| handshake length or timeout failure | close without bytes | coarse handshake class | +| Noise authentication or decapsulation failure | close without bytes | one aggregate crypto-failure class | +| transport length mismatch | close without bytes | coarse frame class | +| Noise record authentication failure | close without bytes | destroy both cipher states | +| unknown or malformed record | close without bytes | coarse record class | +| forbidden command or direction | close without bytes | coarse policy class | +| reassembly bound or timeout | close without bytes | erase partial state | +| queue saturation after authentication | owning protocol's uniform bounded outcome or close | aggregate overload class | +| planned rotation | one fixed CLOSE record if schedule permits | bounded drain and fresh handshake | +| TCP loss or half-close | no protocol error | erase partial state and back off | + +Peers never receive parser offsets, expected keys, supported profiles, +consensus hashes, role policy, capacity, queue depth, or cryptographic error +details. + +The implementation is not required to make all local computation paths +constant-time with respect to public invalid framing. Cryptographic libraries +MUST provide their required side-channel protections, and remotely observable +failure behavior MUST remain non-amplifying and free of detailed oracles. + +## 28. Logging and Metrics + +FOG-WIRE MUST NOT log or export: + +- plaintext or ciphertext record bodies; +- entry capsules or KEMSphinx packets; +- `message_id`, Noise handshake hash, traffic key, ephemeral key, or private + key material; +- per-record timestamps or direction traces; +- queue contents, logical-message hashes, capabilities, or reply material; +- source IP addresses for anonymous relay-to-entry sessions in persistent + application logs; +- detailed handshake failure stages associated with one remote endpoint. + +Role-local debugging MAY use coarse enumerated failure classes in a protected +short-retention log, without input bytes or secret values. Claim-bearing +profiles disable packet and record tracing. + +Metrics are delayed aggregates over declared windows and minimum populations. +Allowed examples include total completed handshakes by context, aggregate +failure class, coarse connection age bucket, aggregate queue occupancy bucket, +and total padding-to-data ratio. The observer interface receives aggregates, +not event streams. + +## 29. Key and Secret Lifecycle + +| Key or secret | Generator | Authorized use | Lifetime and overlap | Compromise response | Backup | +| --- | --- | --- | --- | --- | --- | +| Node Noise epoch private key | owning node OS CSPRNG | one node role and authorized adjacent links | profile-bounded current key, staged next key, exact grace only | revoke key or node, stop new handshakes, rotate | no routine backup | +| Entry Noise epoch private key | entry OS CSPRNG | authenticate `RELAY_ENTRY` and authorized node links | profile-bounded current and staged next | revoke entry key, stop sessions, rotate | no routine backup | +| Authority wire private key | authority wire service OS CSPRNG or controlled ceremony | authority mutual links only | root-certified bounded interval, staged next key | root-signed revocation and replacement | SHOULD NOT restore into a concurrent service | +| Handshake ephemeral private state | connection endpoint OS CSPRNG | one Noise handshake | until handshake completion or failure | close connection, erase state | none | +| Noise transport cipher states | Noise handshake `Split()` | one direction of one connection | until close or fresh handshake | destroy connection state and reauthenticate | none | +| Reassembly message identifier | sending role OS CSPRNG | one logical message within one connection | until completion or timeout | discard partial message | none | +| Queue-sealing key | owning role local secret facility | local queued ciphertext at rest | deployment-rotated, independent of Noise | quarantine queue, rotate, discard unverifiable state | profile-specific encrypted backup | + +Static private keys MUST be readable only by the one role service that owns +them. They MUST NOT appear in environment templates, command arguments, +container images, shared volumes, logs, crash dumps, examples, test fixtures, +or support bundles. + +Best-effort zeroization is required for ephemeral, cipher-state, and partial +plaintext buffers. Documentation MUST NOT claim complete erasure where the +language runtime, allocator, kernel, swap, hibernation, or hardware cannot +guarantee it. + +## 30. Implementation Requirements + +An implementation MUST: + +- use a maintained reviewed Noise or PQNoise library; +- use the operating-system CSPRNG for static keys, ephemeral state, message + identifiers, and jitter; +- keep protocol framing separate from role command parsing; +- enforce all limits before proportional allocation; +- use checked integer arithmetic for lengths, counts, and record totals; +- reject trailing bytes and unknown-critical values; +- authenticate complete records before semantic parsing; +- prevent one role package from importing another role implementation; +- expose one typed context-specific command interface, not raw generic RPC; +- disable core dumps and secret-bearing panic or exception output; +- hold static private keys in the minimum role-local protected facility; +- destroy connection state on every authentication or synchronization error; +- keep the Composer implementation free of FOG-WIRE listeners and sockets. + +The protocol implementation MUST NOT: + +- implement cryptographic primitives or hybrid combiners locally; +- let configuration specify an arbitrary Noise protocol string; +- use a shared transport key for multiple nodes or roles; +- retry decryption with old keys or profiles; +- accept a DNS result, TCP source, or TLS certificate instead of PKI key + validation; +- expose a generic byte tunnel to applications; +- compress records or logical messages at the wire layer; +- silently fragment an over-limit command; +- persist session cipher state for resumption. + +### 30.1 Required module boundaries + +The future implementation keeps these responsibilities independently +testable: + +```text +role executable and role package + -> TCP listener or connector adapter + -> FOG-WIRE session state machine + -> profile and link-context authorization + -> pure preface, prologue, frame, record, and fragment codecs + -> reviewed Noise or PQNoise library +``` + +The pure codecs MUST be testable without opening a socket or starting a role. +The session state machine owns no role database and invokes only a typed +context-specific command boundary after complete validation. The TCP adapter +owns socket IO but does not parse role command bodies. Role handlers own +semantic commands but never access Noise cipher state or unvalidated record +bytes. + +Profile registry, framing, session, and command-shape code MUST NOT be placed +in a generic `utils` package. Role packages MUST NOT call each other to bypass +the wire contract or share one transport singleton. Separate executables are +justified by the established security and state-isolation boundaries, not by +independent feature scaling. + +## 31. Conformance and Adversarial Tests + +Before the local PoC, FOG-WIRE MUST have tests for: + +- byte-exact preface encoding and every rejected field variation; +- byte-exact prologue construction for every context and PKI hash length; +- entry responder authentication with no relay static identity; +- mutual node and authority authentication; +- wrong network, consensus, epoch, context, role, node, endpoint, key, and + profile binding; +- empty handshake payload enforcement and attempted early application data; +- handshake messages at expected, short, long, zero, and 65,535-byte bounds; +- TCP reads split at every byte boundary and coalesced across multiple frames; +- fixed ciphertext length and standard Noise maximum enforcement; +- every record type and zero-reserved or zero-padding rule; +- unknown record, command, command version, close class, and direction; +- valid single- and multi-fragment reassembly; +- duplicate, missing, reordered, inconsistent, expired, excessive, and sparse + fragments; +- exact 8 MiB, 4,096-fragment, 8-message, and 16 MiB boundaries; +- per-record outbound and inbound rekey synchronization; +- authentication failure before and after a rekey boundary; +- fresh-handshake record, byte, age, epoch, topology, and profile boundaries; +- current and next epoch staging with no early forwarding; +- old-profile retirement and rollback refusal; +- duplicate connection collision and one staged replacement; +- queue saturation, slow reads, slow writes, half-close, reset, and timeout; +- reconnect backoff and jitter bounds under repeated failure; +- listener floods and expensive-handshake concurrency limits; +- log and metrics schema rejection of prohibited fields; +- parser mutation, coverage-guided fuzzing, differential framing tests, race + tests, and allocation tests; +- firewall validation for every link context and forbidden role edge. + +Tests generate ephemeral private fixtures at runtime. Private keys MUST NOT be +committed as example or conformance data. Public canonical framing fixtures +MAY be committed when they contain no secret material. + +Independent implementations MUST consume the same public framing and +transcript-binding corpus before interoperability is claimed. + +## 32. Claim Gates + +### 32.1 Functional PoC + +The local PoC MUST demonstrate: + +- one fixed reviewed Noise profile; +- responder-only relay-to-entry and mutual node authentication; +- complete traversal of entry and three mix layers; +- fixed records, padding records, fragmentation, and strict command policy; +- per-record rekey and bounded fresh handshakes; +- current and next epoch transition; +- malformed, unauthorized, flooded, and disconnected peers; +- no multi-role key, state, account, or writable-volume sharing. + +This demonstrates functional correctness only. It does not demonstrate a +production anonymity set or global-observer resistance. + +`FOG-LOCAL-POC.md` fixes the container and network boundary for this gate. It +uses one internal pairwise network per permitted data-plane adjacency, no host +ports, and no shared role state. Its current validated plan is not a FOG-WIRE +implementation and deliberately contains no invented wire profile or port. + +### 32.2 Operator alpha + +Alpha additionally requires: + +- fixed reviewed hybrid post-quantum or explicitly documented classical + profile selection; +- interoperability between independently built endpoints; +- fuzz, race, load, fault, and side-channel review results; +- measured TCP record segmentation and timing on supported systems; +- measured handshake cost and anonymous-entry flood behavior; +- six independently operated mixes and profile-conformant cover scheduling; +- rehearsed key compromise, revocation, epoch transition, and rollback tests; +- public residual-risk documentation for visible endpoints and traffic. + +### 32.3 Production transport claims + +Production claims additionally require: + +- independent protocol and implementation review; +- reviewed library and dependency provenance; +- published concrete suite, geometry, limits, and conformance results; +- verified erasure and crash behavior within stated platform limits; +- sustained adversarial load and resource-exhaustion testing; +- evidence that profile rollout does not create persistent minority + fingerprints; +- claim wording that distinguishes link confidentiality, forward secrecy, + post-quantum assumptions, and metadata privacy. + +## 33. Requirements Traceability + +| Requirement | FOG-WIRE control | +| --- | --- | +| `TM-NET-01` | fixed records, command shapes, profile-bound scheduling, padding records | +| `TM-NET-03` | bounded retries, no bypass, cover-profile failure gate | +| `TM-NET-04` | ordered Noise nonces, no resumption, command-owned cross-session deduplication | +| `TM-NET-05` | authenticated records, strict failure, no action before validation | +| `TM-NET-06` | immutable profiles, exact context, no negotiation or autodetection | +| `TM-PKI-02` | epoch and consensus binding, monotonic profile transition, hard expiry | +| `TM-ROLE-01` | anonymous relay mode, authenticated entry, no durable relay account | +| `TM-ROLE-02` | exact layer adjacency, mutual node authentication, no layer skip | +| `TM-ROLE-03` | courier/store contexts, command separation, fixed replica shapes | +| `TM-OPS-01` | prohibited log fields, delayed aggregate metrics | +| `TM-CRYPTO-01` | purpose-separated keys, lifecycle table, fresh handshakes | +| `TM-CRYPTO-02` | consensus-authorized exact Noise name, no fallback, bounded overlap | +| `TM-AVAIL-01` | strict frame, fragment, handshake, connection, queue, and CPU bounds | +| `ARC-003` | no data-plane bypass or new failure edge | +| `ARC-004` | Noise limited to adjacent transport protection | +| `ARC-005` | peers and profiles derived from one complete accepted consensus | +| `ARC-006` | fixed records and application-independent packet commands | +| `ARC-007` | one private-key owner and one purpose | +| `ARC-008` | exact versioned framing and absolute parser limits | +| `ARC-009` | coarse non-amplifying remote failure | +| `IF-03` | authenticated bounded descriptor command | +| `IF-04` | authenticated authority object command | +| `IF-06` | anonymous relay initiator, authenticated entry, fixed submission command | +| `IF-07` | mutually authenticated exact layer adjacency | +| `IF-08` | fixed authenticated layer-3 to terminal transport | +| `IF-09` | dedicated courier/store and store/store contexts | +| `IF-10` | reverse-direction packet only over authorized adjacency | +| `IF-11` | aggregate-only observer command | + +## 34. Open Pre-Implementation Selections + +The version-1 protocol structure is fixed, but these selections block daemon +implementation: + +- the exact reviewed classical or hybrid post-quantum Noise protocol names; +- the reviewed Noise or PQNoise library and supported implementation language; +- X-Wing is the leading KEM to evaluate, but no exact FOG entry or mutual + post-quantum handshake profile is selected; +- exact `wire_profile_id` assignments and immutable registry digests; +- record plaintext size and command-shape pairings; +- fresh-handshake record, byte, and age thresholds; +- concrete timeouts, reconnect distribution, and socket behavior profile; +- lower role-specific connection, queue, CPU, and memory limits; +- packet, PKI, and observer command body specifications, plus byte-exact + `FOG-STORAGE` command-to-record mappings; +- cover-record schedules and degraded-mode thresholds from simulation; +- safe anonymous entry admission and flood-control policy; +- authority wire-key certificate rollout and revocation ceremony. + +Implementations MUST NOT resolve these selections through undocumented local +defaults. Each selected value becomes an immutable reviewed profile with test +vectors and a signed transition path. + +## 35. References + +- FOG threat model: `FOG-THREAT-MODEL.md` +- FOG architecture: `FOG-ARCHITECTURE.md` +- FOG public key infrastructure: `FOG-PKI.md` +- FOG storage protocol: `FOG-STORAGE.md` +- FOG cryptographic suite evaluation: `FOG-CRYPTO-SUITES.md` +- FOG traffic and topology simulation: `FOG-SIMULATION.md` +- FOG local Podman PoC: `FOG-LOCAL-POC.md` +- Noise Protocol Framework: + +- Katzenpost mix network wire protocol: + +- Post Quantum Noise: + +- CFRG X-Wing KEM Internet-Draft: + +- RFC 9293, Transmission Control Protocol: + +- RFC 8174, Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words: + + +These references inform Noise handshake semantics, standard message bounds, +rekey behavior, TCP stream framing, post-quantum construction review, and +adjacent mix-link experience. They do not make a concrete FOG profile secure +without exact selection, conformance testing, operational measurement, and +independent review. diff --git a/fog-client.sh b/fog-client.sh deleted file mode 100644 index a141cf1..0000000 --- a/fog-client.sh +++ /dev/null @@ -1,304 +0,0 @@ -#!/bin/bash -# fog-client.sh - Interactive SMTP client for fog network -# Selects random entry node and sends message via Tor - -VERSION="2.0" - -# Colors -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -CYAN='\033[0;36m' -BLUE='\033[0;34m' -MAGENTA='\033[0;35m' -NC='\033[0m' - -# fog network nodes (onion SMTP addresses) -NODES=( - "ej5dj774rkmfxvo3jexcmyotkq6bwgmr45dmwrbmk366lcvalnrgolad.onion 2525" # kvara - "iycr4wfrdzieogdfeo7uxrj77w2vjlrhlrv3jg2ve62oe5aceqsqu7ad.onion 2525" # dries - "66ehoz4ir6beuovmgt4gbpdfpmy43iuouj36dylqvkwgyp2dwpcbvjqd.onion 2525" # mct8 - "y3lozzcvvxgorgfofupvfmn4j2fuu3sz2sw7ha3ifpcsxjkuafllzvyd.onion 2525" # news - "ejdrw3ka2mjhvsuz7uxjnzjircsdpoiu3a33g2xoywlafqetptjpqryd.onion 2525" # pietro -) - -NODE_NAMES=("kvara" "dries" "mct8" "news" "pietro") - -print_header() { - echo -e "${CYAN}" - echo "=========================================================" - echo " fog Network Client v${VERSION} - Anonymous SMTP Relay" - echo "=========================================================" - echo -e "${NC}" -} - -check_dependencies() { - local missing=0 - - if ! command -v nc &> /dev/null; then - echo -e "${RED}[x] netcat not found (apt install netcat-openbsd)${NC}" - missing=1 - fi - - if ! command -v torify &> /dev/null; then - echo -e "${RED}[x] torify not found (apt install tor)${NC}" - missing=1 - fi - - if ! pgrep -x tor > /dev/null 2>&1; then - echo -e "${RED}[x] Tor is not running (systemctl start tor)${NC}" - missing=1 - fi - - if [ $missing -eq 1 ]; then - exit 1 - fi - - echo -e "${GREEN}[ok] Dependencies ready${NC}" - echo "" -} - -# CSPRNG node selection (not bash $RANDOM) -select_random_node() { - local count=${#NODES[@]} - local random_index - random_index=$(od -An -tu4 -N4 /dev/urandom | tr -d ' ') - random_index=$((random_index % count)) - - SELECTED_NODE="${NODES[$random_index]}" - SELECTED_NAME="${NODE_NAMES[$random_index]}" - - echo -e "${CYAN}Entry node:${NC} ${MAGENTA}${SELECTED_NAME}${NC}" - echo "" -} - -read_multiline() { - local prompt="$1" - - echo -e "${YELLOW}${prompt}${NC}" - echo -e "${CYAN}(End with a single dot '.' on its own line)${NC}" - - MULTILINE_RESULT="" - local line - - while IFS= read -r line; do - if [ "$line" = "." ]; then - break - fi - MULTILINE_RESULT="${MULTILINE_RESULT}${line}"$'\r\n' - done -} - -send_message() { - local from="$1" - local to="$2" - local subject="$3" - local extra_headers="$4" - local body="$5" - - local node_host node_port - node_host=$(echo "$SELECTED_NODE" | awk '{print $1}') - node_port=$(echo "$SELECTED_NODE" | awk '{print $2}') - - echo -e "${CYAN}Connecting to ${SELECTED_NAME} via Tor...${NC}" - echo "" - - # SMTP conversation - # No Date/Message-ID/User-Agent: exit node generates sanitized ones - local response - response=$( - { - sleep 2 - echo "EHLO localhost" - sleep 1 - echo "MAIL FROM:<${from}>" - sleep 0.5 - echo "RCPT TO:<${to}>" - sleep 0.5 - echo "DATA" - sleep 0.5 - printf "From: %s\r\n" "$from" - printf "To: %s\r\n" "$to" - printf "Subject: %s\r\n" "$subject" - printf "MIME-Version: 1.0\r\n" - printf "Content-Type: text/plain; charset=utf-8\r\n" - printf "Content-Transfer-Encoding: 8bit\r\n" - # Extra headers (Newsgroups, References, etc.) - if [ -n "$extra_headers" ]; then - printf "%s" "$extra_headers" - fi - printf "\r\n" - # Body - use printf to avoid escape interpretation - printf "%s" "$body" - printf "\r\n.\r\n" - sleep 1 - echo "QUIT" - } | torify nc -w 30 "$node_host" "$node_port" 2>/dev/null - ) - - # Show server responses - while IFS= read -r line; do - if [ -n "$line" ]; then - echo -e "${BLUE} <- ${line}${NC}" - fi - done <<< "$response" - - echo "" - - if echo "$response" | grep -q "^250.*queued"; then - echo -e "${GREEN}[ok] Message accepted by ${SELECTED_NAME}${NC}" - echo -e "${CYAN} Routing through Sphinx mixnet (3-6 hops)${NC}" - return 0 - else - echo -e "${RED}[fail] Message not accepted${NC}" - return 1 - fi -} - -interactive_mode() { - echo -e "${CYAN}--- Compose Email ---${NC}" - echo "" - - echo -e -n "${YELLOW}From: ${NC}" - read -r from - [ -z "$from" ] && { echo -e "${RED}Error: From required${NC}"; exit 1; } - - echo -e -n "${YELLOW}To: ${NC}" - read -r to - [ -z "$to" ] && { echo -e "${RED}Error: To required${NC}"; exit 1; } - - echo -e -n "${YELLOW}Subject: ${NC}" - read -r subject - [ -z "$subject" ] && subject="(no subject)" - - echo "" - read_multiline "Body:" - local body="$MULTILINE_RESULT" - [ -z "$body" ] && { echo -e "${RED}Error: Body required${NC}"; exit 1; } - - echo "" - echo -e "${YELLOW}---${NC}" - echo -e " From: ${from}" - echo -e " To: ${to}" - echo -e " Subject: ${subject}" - echo -e " Via: ${SELECTED_NAME}" - echo -e "${YELLOW}---${NC}" - echo "" - - echo -e -n "${YELLOW}Send? [y/N]: ${NC}" - read -r confirm - [[ ! "$confirm" =~ ^[Yy]$ ]] && { echo "Cancelled"; exit 0; } - - echo "" - send_message "$from" "$to" "$subject" "" "$body" -} - -usenet_mode() { - echo -e "${CYAN}--- Compose Usenet Post ---${NC}" - echo "" - - echo -e -n "${YELLOW}From (name): ${NC}" - read -r from_name - [ -z "$from_name" ] && from_name="Anonymous" - - echo -e -n "${YELLOW}From (email) [noreply@fog.network]: ${NC}" - read -r from_email - [ -z "$from_email" ] && from_email="noreply@fog.network" - local from="${from_name} <${from_email}>" - - echo -e -n "${YELLOW}Newsgroups (e.g. alt.test): ${NC}" - read -r newsgroups - [ -z "$newsgroups" ] && { echo -e "${RED}Error: Newsgroups required${NC}"; exit 1; } - - echo -e -n "${YELLOW}Subject: ${NC}" - read -r subject - [ -z "$subject" ] && subject="(no subject)" - - echo -e -n "${YELLOW}References (Message-ID to reply to, empty for new post): ${NC}" - read -r references - - echo "" - read_multiline "Post body:" - local body="$MULTILINE_RESULT" - [ -z "$body" ] && { echo -e "${RED}Error: Body required${NC}"; exit 1; } - - # Mail2news gateway - destination - echo "" - echo -e "${CYAN}Mail2news gateways:${NC}" - echo -e " 1) mail2news@dizum.com (clearnet via Tor)" - echo -e " 2) mail2news@xilb7y4kj6u6qfo45o3yk2kilfv54ffukzei3puonuqlncy7cn2afwyd.onion" - echo -e " 3) Custom address" - echo -e -n "${YELLOW}Gateway [1]: ${NC}" - read -r gw_choice - - local to - case "$gw_choice" in - 2) to="mail2news@xilb7y4kj6u6qfo45o3yk2kilfv54ffukzei3puonuqlncy7cn2afwyd.onion" ;; - 3) - echo -e -n "${YELLOW}Custom gateway address: ${NC}" - read -r to - [ -z "$to" ] && { echo -e "${RED}Error: Address required${NC}"; exit 1; } - ;; - *) to="mail2news@dizum.com" ;; - esac - - # Build extra headers (Newsgroups goes in email headers, NOT body) - local extra_headers - extra_headers=$(printf "Newsgroups: %s\r\n" "$newsgroups") - if [ -n "$references" ]; then - extra_headers="${extra_headers}$(printf "References: %s\r\n" "$references")" - fi - - echo "" - echo -e "${YELLOW}---${NC}" - echo -e " From: ${from}" - echo -e " Newsgroups: ${newsgroups}" - echo -e " Subject: ${subject}" - [ -n "$references" ] && echo -e " References: ${references}" - echo -e " Gateway: ${to}" - echo -e " Via: ${SELECTED_NAME}" - echo -e "${YELLOW}---${NC}" - echo "" - - echo -e -n "${YELLOW}Post? [y/N]: ${NC}" - read -r confirm - [[ ! "$confirm" =~ ^[Yy]$ ]] && { echo "Cancelled"; exit 0; } - - echo "" - send_message "$from" "$to" "$subject" "$extra_headers" "$body" -} - -show_help() { - echo "Usage: $0 [OPTION]" - echo "" - echo " -e, --email Compose email (default)" - echo " -u, --usenet Compose Usenet post" - echo " -v, --version Show version" - echo " -h, --help Show this help" -} - -# Main -main() { - local mode="email" - - while [[ $# -gt 0 ]]; do - case $1 in - -e|--email) mode="email"; shift ;; - -u|--usenet) mode="usenet"; shift ;; - -v|--version) echo "fog-client v${VERSION}"; exit 0 ;; - -h|--help) show_help; exit 0 ;; - *) echo "Unknown: $1"; show_help; exit 1 ;; - esac - done - - print_header - check_dependencies - select_random_node - - case "$mode" in - email) interactive_mode ;; - usenet) usenet_mode ;; - esac -} - -main "$@" diff --git a/fog.go b/fog.go deleted file mode 100644 index 504cfb7..0000000 --- a/fog.go +++ /dev/null @@ -1,2355 +0,0 @@ -// fog v4.1.0 - Anonymous SMTP Relay with Post-Quantum Sphinx Mixnet -// v4.1.0: BUG FIXES AND SECURITY HARDENING -// - Fixed: SMTP envelope now embedded in Sphinx payload -// - Fixed: DNS MX lookup through Tor (no DNS leak) -// - Fixed: Multi-recipient delivery -// - Fixed: Exit node header sanitization restored -// - Fixed: ESMTP capabilities (8BITMIME, SMTPUTF8, SIZE) -// - Fixed: MIME-safe line handling (no TrimSpace corruption) -// - Fixed: Direct relay fallback when Sphinx unavailable -// - Fixed: Kyber key size validation in PKI -// v4.0.0: POST-QUANTUM CRYPTOGRAPHY -// - Kyber-768 key encapsulation (quantum-resistant) -// - Replaced Curve25519 with Kyber KEM -// - New packet format for larger PQ keys -// Previous versions used classical cryptography (Curve25519) -// Features: -// - PKI Gossip: fully decentralized node discovery -// - Threshold Batching: pool mixing with configurable threshold -// - Realistic Cover Traffic: low volume, irregular timing -// - Forward secrecy with ephemeral Kyber keys per hop -// Copyright 2025-2026 - fog Project - -package main - -import ( - "bufio" - "bytes" - "context" - "crypto/aes" - "crypto/cipher" - "crypto/hmac" - "crypto/rand" - "crypto/sha256" - "encoding/base64" - "encoding/binary" - "encoding/hex" - "encoding/json" - "errors" - "flag" - "fmt" - "io" - "log" - "math/big" - "net" - "net/smtp" - "os" - "os/signal" - "strings" - "sync" - "sync/atomic" - "syscall" - "time" - - kyberk2so "github.com/symbolicsoft/kyber-k2so" - "golang.org/x/crypto/hkdf" - "golang.org/x/net/proxy" -) - -const ( - Version = "4.1.0" - - TorSocks = "127.0.0.1:9050" - DefaultSMTP = "127.0.0.1:2525" - DefaultNode = "127.0.0.1:9999" - - // Timing - HealthInterval = 3 * time.Minute - GossipInterval = 5 * time.Minute - StatsInterval = 60 * time.Second - - // Threshold Batching - BatchThresholdMin = 5 - BatchThresholdMax = 15 - BatchTimeout = 5 * time.Minute - - // Cover Traffic - realistic small server pattern - CoverMinInterval = 30 * time.Minute - CoverMaxInterval = 4 * time.Hour - CoverMaxPerHour = 3 - CoverBurstChance = 0.1 - - // Kyber-768 sizes - KyberPKSize = 1184 - KyberSKSize = 2400 - KyberCTSize = 1088 - KyberSSSize = 32 - - // Sphinx with Kyber - MinHops = 3 - MaxHops = 6 - HeaderSize = 1232 // 1088 (Kyber CT) + 128 (routing) + 16 (MAC) - PayloadMax = 64 * 1024 - - // Limits - MaxMsgSize = 10 << 20 - QueueSize = 500 - Workers = 3 - CacheSize = 10000 - CacheTTL = 24 * time.Hour -) - -// ============================================================================= -// TYPES -// ============================================================================= - -type Node struct { - ID string `json:"id"` - PublicKey []byte `json:"public_key"` - Address string `json:"address"` - Name string `json:"name"` - Version string `json:"version"` - LastSeen time.Time `json:"last_seen"` - Healthy bool `json:"healthy"` -} - -type LocalNode struct { - ID string - Public []byte - Private []byte - Address string - Name string -} - -type Message struct { - ID string - From string - To []string - Data []byte - ReceivedAt time.Time -} - -// EnvelopeWrapper embeds SMTP envelope inside Sphinx payload -// so exit node can deliver using the original MAIL FROM/RCPT TO -type EnvelopeWrapper struct { - From string `json:"f"` - To []string `json:"t"` - Data []byte `json:"d"` -} - -type SphinxPacket struct { - Header []byte - Payload []byte -} - -type Stats struct { - Start time.Time - Received int64 - Delivered int64 - Failed int64 - SphinxRouted int64 - DirectRelay int64 - CoverSent int64 - GossipExch int64 - mu sync.Mutex -} - -// ============================================================================= -// GLOBALS -// ============================================================================= - -var ( - ctx context.Context - cancel context.CancelFunc - wg sync.WaitGroup - torDialer proxy.Dialer - - local LocalNode - hostname string - pkiFile string // Bootstrap PKI (read-only, never overwritten) - pkiStateFile string // Dynamic state (read-write, gossip discoveries) - keyFile string - - pki *PKI - pool *BatchPool - replay *ReplayCache - queue chan *Message - stats *Stats - cover *CoverTraffic - - useSphinx atomic.Bool - debugMode bool -) - -// ============================================================================= -// PKI WITH GOSSIP PROTOCOL -// ============================================================================= - -type PKI struct { - nodes map[string]*Node - mu sync.RWMutex -} - -func newPKI() *PKI { - return &PKI{nodes: make(map[string]*Node)} -} - -func (p *PKI) Add(n *Node) { - p.mu.Lock() - defer p.mu.Unlock() - - // v4.1.0: Enforce Kyber-768 key size (1184 bytes) - if len(n.PublicKey) != KyberPKSize { - if debugMode { - log.Printf("[PKI] Rejected node %s: invalid key size %d (need %d)", - n.Name, len(n.PublicKey), KyberPKSize) - } - return - } - - existing, ok := p.nodes[n.ID] - if !ok || n.LastSeen.After(existing.LastSeen) { - p.nodes[n.ID] = n - if debugMode { - idStr := n.ID - if len(idStr) > 16 { - idStr = idStr[:16] - } - log.Printf("[PKI] Added/updated node %s (%s)", n.Name, idStr) - } - } -} - -func (p *PKI) Remove(id string) { - p.mu.Lock() - defer p.mu.Unlock() - delete(p.nodes, id) -} - -func (p *PKI) Get(id string) *Node { - p.mu.RLock() - defer p.mu.RUnlock() - return p.nodes[id] -} - -func (p *PKI) GetAll() []*Node { - p.mu.RLock() - defer p.mu.RUnlock() - result := make([]*Node, 0, len(p.nodes)) - for _, n := range p.nodes { - result = append(result, n) - } - return result -} - -func (p *PKI) GetHealthy() []*Node { - p.mu.RLock() - defer p.mu.RUnlock() - result := make([]*Node, 0) - for _, n := range p.nodes { - if n.Healthy && n.ID != local.ID { - result = append(result, n) - } - } - return result -} - -func (p *PKI) GetOthers() []*Node { - p.mu.RLock() - defer p.mu.RUnlock() - result := make([]*Node, 0) - for _, n := range p.nodes { - if n.ID != local.ID { - result = append(result, n) - } - } - return result -} - -func (p *PKI) HealthyCount() int { - p.mu.RLock() - defer p.mu.RUnlock() - count := 0 - for _, n := range p.nodes { - if n.Healthy && n.ID != local.ID { - count++ - } - } - return count -} - -// CleanupDuplicates removes duplicate nodes with same address or name -func (p *PKI) CleanupDuplicates() int { - p.mu.Lock() - defer p.mu.Unlock() - - byAddress := make(map[string][]*Node) - for _, n := range p.nodes { - byAddress[n.Address] = append(byAddress[n.Address], n) - } - - removed := 0 - for addr, nodes := range byAddress { - if len(nodes) <= 1 { - continue - } - - var newest *Node - for _, n := range nodes { - if newest == nil || n.LastSeen.After(newest.LastSeen) { - newest = n - } - } - - for _, n := range nodes { - if n.ID != newest.ID { - delete(p.nodes, n.ID) - removed++ - if debugMode { - nID := n.ID - if len(nID) > 16 { - nID = nID[:16] - } - log.Printf("[PKI] Removed duplicate node %s (addr: %s)", nID, addr) - } - } - } - } - - byName := make(map[string][]*Node) - for _, n := range p.nodes { - if n.Name != "" { - byName[n.Name] = append(byName[n.Name], n) - } - } - - for name, nodes := range byName { - if len(nodes) <= 1 { - continue - } - - var newest *Node - for _, n := range nodes { - if newest == nil || n.LastSeen.After(newest.LastSeen) { - newest = n - } - } - - for _, n := range nodes { - if n.ID != newest.ID { - delete(p.nodes, n.ID) - removed++ - if debugMode { - nID := n.ID - if len(nID) > 16 { - nID = nID[:16] - } - log.Printf("[PKI] Removed duplicate node %s (name: %s)", nID, name) - } - } - } - } - - return removed -} - -func (p *PKI) SetHealth(id string, healthy bool) { - p.mu.Lock() - defer p.mu.Unlock() - if n, ok := p.nodes[id]; ok { - n.Healthy = healthy - n.LastSeen = time.Now() - } -} - -func (p *PKI) Load(path string) error { - data, err := os.ReadFile(path) - if err != nil { - return err - } - - var nodes map[string]*Node - if err := json.Unmarshal(data, &nodes); err != nil { - return err - } - - p.mu.Lock() - defer p.mu.Unlock() - - loaded := 0 - skipped := 0 - for id, n := range nodes { - n.ID = id - // v4.1.0: Validate Kyber key size on load - if len(n.PublicKey) != KyberPKSize { - log.Printf("[PKI] Skipping node %s: key size %d (need %d)", n.Name, len(n.PublicKey), KyberPKSize) - skipped++ - continue - } - p.nodes[id] = n - loaded++ - } - - log.Printf("[PKI] Loaded %d nodes from %s (skipped %d invalid)", loaded, path, skipped) - return nil -} - -// SaveState saves dynamic PKI state to a SEPARATE file (never overwrites bootstrap) -func (p *PKI) SaveState(path string) error { - p.mu.RLock() - defer p.mu.RUnlock() - - data, err := json.MarshalIndent(p.nodes, "", " ") - if err != nil { - return err - } - - return os.WriteFile(path, data, 0600) -} - -func (p *PKI) ExportForGossip() []byte { - p.mu.RLock() - defer p.mu.RUnlock() - data, _ := json.Marshal(p.nodes) - return data -} - -func (p *PKI) MergeFromGossip(data []byte) int { - var received map[string]*Node - if err := json.Unmarshal(data, &received); err != nil { - return 0 - } - - added := 0 - p.mu.Lock() - - for id, n := range received { - if id == local.ID { - continue - } - n.ID = id - - // v4.1.0: Validate Kyber key size from gossip - if len(n.PublicKey) != KyberPKSize { - if debugMode { - log.Printf("[GOSSIP] Rejected node %s: invalid key size %d", n.Name, len(n.PublicKey)) - } - continue - } - - existing, ok := p.nodes[id] - if !ok { - p.nodes[id] = n - added++ - idStr := id - if len(idStr) > 16 { - idStr = idStr[:16] - } - log.Printf("[GOSSIP] Discovered new node: %s (%s)", n.Name, idStr) - } else if n.LastSeen.After(existing.LastSeen) { - p.nodes[id] = n - } - } - - p.mu.Unlock() - p.CleanupDuplicates() - - return added -} - -// ============================================================================= -// GOSSIP PROTOCOL -// ============================================================================= - -func gossipWorker() { - defer wg.Done() - - select { - case <-ctx.Done(): - return - case <-time.After(30 * time.Second): - } - - ticker := time.NewTicker(GossipInterval) - defer ticker.Stop() - - for { - select { - case <-ctx.Done(): - return - case <-ticker.C: - doGossipRound() - } - } -} - -func doGossipRound() { - others := pki.GetOthers() - if len(others) == 0 { - return - } - - shuffleNodes(others) - count := 3 - if len(others) < count { - count = len(others) - } - - myData := pki.ExportForGossip() - - for i := 0; i < count; i++ { - node := others[i] - go gossipWith(node, myData) - } -} - -func gossipWith(node *Node, myData []byte) { - conn, err := dialTor(node.Address) - if err != nil { - if debugMode { - log.Printf("[GOSSIP] Failed to connect to %s: %v", node.Name, err) - } - return - } - defer conn.Close() - conn.SetDeadline(time.Now().Add(30 * time.Second)) - - fmt.Fprintf(conn, "GOSSIP %d\r\n", len(myData)) - conn.Write(myData) - conn.Write([]byte("\r\n")) - - reader := bufio.NewReader(conn) - line, err := reader.ReadString('\n') - if err != nil { - return - } - - if strings.HasPrefix(line, "GOSSIP ") { - var size int - fmt.Sscanf(line, "GOSSIP %d", &size) - if size > 0 && size < 1<<20 { - data := make([]byte, size) - io.ReadFull(reader, data) - added := pki.MergeFromGossip(data) - if added > 0 { - atomic.AddInt64(&stats.GossipExch, int64(added)) - } - } - } - - if debugMode { - log.Printf("[GOSSIP] Exchanged with %s", node.Name) - } -} - -// ============================================================================= -// THRESHOLD BATCH POOL -// ============================================================================= - -type BatchPool struct { - packets []*SphinxPacket - addedAt []time.Time - mu sync.Mutex - threshold int -} - -func newBatchPool() *BatchPool { - threshold := BatchThresholdMin + cryptoRandInt(BatchThresholdMax-BatchThresholdMin+1) - return &BatchPool{ - packets: make([]*SphinxPacket, 0), - addedAt: make([]time.Time, 0), - threshold: threshold, - } -} - -func (b *BatchPool) Add(p *SphinxPacket) { - b.mu.Lock() - defer b.mu.Unlock() - b.packets = append(b.packets, p) - b.addedAt = append(b.addedAt, time.Now()) -} - -func (b *BatchPool) Size() int { - b.mu.Lock() - defer b.mu.Unlock() - return len(b.packets) -} - -func (b *BatchPool) ShouldFlush() bool { - b.mu.Lock() - defer b.mu.Unlock() - - if len(b.packets) == 0 { - return false - } - - if len(b.packets) >= b.threshold { - return true - } - - if len(b.addedAt) > 0 && time.Since(b.addedAt[0]) > BatchTimeout { - return true - } - - return false -} - -func (b *BatchPool) Flush() []*SphinxPacket { - b.mu.Lock() - defer b.mu.Unlock() - - if len(b.packets) == 0 { - return nil - } - - result := b.packets - b.packets = make([]*SphinxPacket, 0) - b.addedAt = make([]time.Time, 0) - - shufflePackets(result) - - b.threshold = BatchThresholdMin + cryptoRandInt(BatchThresholdMax-BatchThresholdMin+1) - - log.Printf("[POOL] Flushing %d packets (next threshold: %d)", len(result), b.threshold) - return result -} - -func batchWorker() { - defer wg.Done() - - ticker := time.NewTicker(10 * time.Second) - defer ticker.Stop() - - for { - select { - case <-ctx.Done(): - return - case <-ticker.C: - if pool.ShouldFlush() { - packets := pool.Flush() - for _, p := range packets { - go processSphinxPacket(p) - } - } - } - } -} - -// ============================================================================= -// COVER TRAFFIC -// ============================================================================= - -type CoverTraffic struct { - lastSent time.Time - sentThisHour int - hourStart time.Time - mu sync.Mutex -} - -func newCoverTraffic() *CoverTraffic { - return &CoverTraffic{ - lastSent: time.Now(), - hourStart: time.Now().Truncate(time.Hour), - } -} - -func (c *CoverTraffic) shouldSend() bool { - c.mu.Lock() - defer c.mu.Unlock() - - now := time.Now() - - currentHour := now.Truncate(time.Hour) - if currentHour.After(c.hourStart) { - c.sentThisHour = 0 - c.hourStart = currentHour - } - - if c.sentThisHour >= CoverMaxPerHour { - return false - } - - if time.Since(c.lastSent) < CoverMinInterval { - return false - } - - elapsed := time.Since(c.lastSent) - maxWait := float64(CoverMaxInterval) - elapsedF := float64(elapsed) - probability := 0.05 + 0.45*(elapsedF/maxWait) - if probability > 0.5 { - probability = 0.5 - } - - if cryptoRandFloat() < probability { - c.lastSent = now - c.sentThisHour++ - return true - } - - return false -} - -func (c *CoverTraffic) shouldBurst() bool { - return cryptoRandFloat() < CoverBurstChance -} - -func coverWorker() { - defer wg.Done() - - initialDelay := time.Duration(60+cryptoRandInt(540)) * time.Second - select { - case <-ctx.Done(): - return - case <-time.After(initialDelay): - } - - for { - interval := time.Duration(5+cryptoRandInt(10)) * time.Minute - select { - case <-ctx.Done(): - return - case <-time.After(interval): - if cover.shouldSend() { - sendCoverMessage() - - if cover.shouldBurst() { - burstCount := 1 + cryptoRandInt(2) - for i := 0; i < burstCount; i++ { - burstDelay := time.Duration(10+cryptoRandInt(50)) * time.Second - select { - case <-ctx.Done(): - return - case <-time.After(burstDelay): - if cover.shouldSend() { - sendCoverMessage() - } - } - } - } - } - } - } -} - -func sendCoverMessage() { - healthy := pki.GetHealthy() - if len(healthy) < MinHops { - return - } - - size := 500 + cryptoRandInt(2000) - dummy := make([]byte, size) - rand.Read(dummy) - - hopCount := MinHops + cryptoRandInt(MaxHops-MinHops+1) - route := selectRoute(healthy, hopCount) - if route == nil { - return - } - - packet := createSphinxPacket(dummy, route, true) - if packet == nil { - return - } - - if err := sendToNode(route[0], packet); err != nil { - if debugMode { - log.Printf("[COVER] Failed to send: %v", err) - } - return - } - - atomic.AddInt64(&stats.CoverSent, 1) - if debugMode { - log.Printf("[COVER] Sent dummy message via %d hops", hopCount) - } -} - -// ============================================================================= -// REPLAY CACHE -// ============================================================================= - -type ReplayCache struct { - items map[string]time.Time - mu sync.RWMutex -} - -func newReplayCache() *ReplayCache { - return &ReplayCache{items: make(map[string]time.Time)} -} - -func (r *ReplayCache) Check(id string) bool { - r.mu.RLock() - _, exists := r.items[id] - r.mu.RUnlock() - return exists -} - -func (r *ReplayCache) Add(id string) { - r.mu.Lock() - r.items[id] = time.Now() - r.mu.Unlock() -} - -func (r *ReplayCache) Cleanup() { - r.mu.Lock() - defer r.mu.Unlock() - cutoff := time.Now().Add(-CacheTTL) - for id, t := range r.items { - if t.Before(cutoff) { - delete(r.items, id) - } - } -} - -func cacheCleanupWorker() { - defer wg.Done() - ticker := time.NewTicker(time.Hour) - defer ticker.Stop() - - for { - select { - case <-ctx.Done(): - return - case <-ticker.C: - replay.Cleanup() - } - } -} - -// ============================================================================= -// CRYPTO HELPERS -// ============================================================================= - -func cryptoRandInt(max int) int { - if max <= 0 { - return 0 - } - n, _ := rand.Int(rand.Reader, big.NewInt(int64(max))) - return int(n.Int64()) -} - -func cryptoRandFloat() float64 { - var b [8]byte - rand.Read(b[:]) - return float64(binary.BigEndian.Uint64(b[:])&0x1FFFFFFFFFFFFF) / float64(0x20000000000000) -} - -func cryptoRandBytes(n int) []byte { - b := make([]byte, n) - rand.Read(b) - return b -} - -func generateKeyPair() (pub, priv []byte) { - privKey, pubKey, err := kyberk2so.KemKeypair768() - if err != nil { - log.Printf("[CRYPTO] Failed to generate Kyber keypair: %v", err) - return nil, nil - } - return pubKey[:], privKey[:] -} - -func kyberEncapsulate(pubKey []byte) (ciphertext, sharedSecret []byte, err error) { - if len(pubKey) != KyberPKSize { - return nil, nil, fmt.Errorf("invalid public key size: %d (need %d)", len(pubKey), KyberPKSize) - } - var pk [1184]byte - copy(pk[:], pubKey) - ct, ss, err := kyberk2so.KemEncrypt768(pk) - if err != nil { - return nil, nil, err - } - return ct[:], ss[:], nil -} - -func kyberDecapsulate(ciphertext, privKey []byte) (sharedSecret []byte, err error) { - if len(ciphertext) != KyberCTSize { - return nil, fmt.Errorf("invalid ciphertext size: %d", len(ciphertext)) - } - if len(privKey) != KyberSKSize { - return nil, fmt.Errorf("invalid private key size: %d", len(privKey)) - } - var ct [1088]byte - var sk [2400]byte - copy(ct[:], ciphertext) - copy(sk[:], privKey) - ss, err := kyberk2so.KemDecrypt768(ct, sk) - if err != nil { - return nil, err - } - return ss[:], nil -} - -func deriveKeys(secret []byte) (encKey, macKey []byte) { - hkdfReader := hkdf.New(sha256.New, secret, nil, []byte("fog-sphinx")) - encKey = make([]byte, 32) - macKey = make([]byte, 32) - io.ReadFull(hkdfReader, encKey) - io.ReadFull(hkdfReader, macKey) - return -} - -func computeMAC(key, data []byte) []byte { - mac := hmac.New(sha256.New, key) - mac.Write(data) - return mac.Sum(nil) -} - -func verifyMAC(key, data, expected []byte) bool { - computed := computeMAC(key, data) - if len(expected) < len(computed) { - computed = computed[:len(expected)] - } - return hmac.Equal(computed, expected) -} - -func aesEncrypt(key, plaintext []byte) ([]byte, error) { - block, err := aes.NewCipher(key) - if err != nil { - return nil, err - } - gcm, err := cipher.NewGCM(block) - if err != nil { - return nil, err - } - nonce := cryptoRandBytes(gcm.NonceSize()) - return gcm.Seal(nonce, nonce, plaintext, nil), nil -} - -func aesDecrypt(key, ciphertext []byte) ([]byte, error) { - block, err := aes.NewCipher(key) - if err != nil { - return nil, err - } - gcm, err := cipher.NewGCM(block) - if err != nil { - return nil, err - } - if len(ciphertext) < gcm.NonceSize() { - return nil, errors.New("ciphertext too short") - } - nonce := ciphertext[:gcm.NonceSize()] - return gcm.Open(nil, nonce, ciphertext[gcm.NonceSize():], nil) -} - -// ============================================================================= -// SPHINX PACKET -// ============================================================================= - -func selectRoute(healthy []*Node, hopCount int) []*Node { - if len(healthy) < hopCount { - return nil - } - shuffleNodes(healthy) - return healthy[:hopCount] -} - -func createSphinxPacket(payload []byte, route []*Node, isDummy bool) *SphinxPacket { - if len(route) == 0 { - return nil - } - - padded := padPayload(payload) - - type hopInfo struct { - ciphertext []byte - encKey []byte - macKey []byte - } - - hops := make([]hopInfo, len(route)) - - for i := 0; i < len(route); i++ { - node := route[i] - - ciphertext, sharedSecret, err := kyberEncapsulate(node.PublicKey) - if err != nil { - log.Printf("[SPHINX-CREATE] Kyber encapsulation failed for hop %d: %v", i, err) - return nil - } - - encKey, macKey := deriveKeys(sharedSecret) - - hops[i] = hopInfo{ - ciphertext: ciphertext, - encKey: encKey, - macKey: macKey, - } - - if debugMode { - log.Printf("[SPHINX-CREATE] Hop %d (%s): ct=%s secret=%s", - i, node.Name, - base64.StdEncoding.EncodeToString(ciphertext)[:16], - base64.StdEncoding.EncodeToString(sharedSecret)[:16]) - } - } - - // Build layers from exit to entry (reverse order) - currentPayload := padded - - for i := len(route) - 1; i >= 0; i-- { - hop := hops[i] - - encrypted, err := aesEncrypt(hop.encKey, currentPayload) - if err != nil { - return nil - } - - var nextHop string - isExit := (i == len(route)-1) - if isExit { - if isDummy { - nextHop = "DUMMY" - } else { - nextHop = "EXIT" - } - } else { - nextHop = route[i+1].Address - } - - routingPadded := make([]byte, 128) - copy(routingPadded, []byte(nextHop)) - - mac := computeMAC(hop.macKey, routingPadded) - - header := make([]byte, 0, HeaderSize) - header = append(header, hop.ciphertext...) - header = append(header, routingPadded...) - header = append(header, mac[:16]...) - - currentPayload = append(header, encrypted...) - - if debugMode { - log.Printf("[SPHINX-CREATE] Layer %d: header=%d encrypted=%d total=%d", - i, len(header), len(encrypted), len(currentPayload)) - } - } - - return &SphinxPacket{ - Header: currentPayload[:HeaderSize], - Payload: currentPayload[HeaderSize:], - } -} - -func processSphinxPacket(packet *SphinxPacket) { - if len(packet.Header) < HeaderSize { - log.Printf("[SPHINX] Header too short: %d bytes (need %d)", len(packet.Header), HeaderSize) - return - } - - ciphertext := packet.Header[:KyberCTSize] - - secret, err := kyberDecapsulate(ciphertext, local.Private) - if err != nil { - log.Printf("[SPHINX] Kyber decapsulation failed: %v", err) - return - } - encKey, macKey := deriveKeys(secret) - - routingInfo := packet.Header[KyberCTSize : KyberCTSize+128] - receivedMAC := packet.Header[KyberCTSize+128 : HeaderSize] - - if debugMode { - log.Printf("[SPHINX-RECV] ct=%s secret=%s", - base64.StdEncoding.EncodeToString(ciphertext)[:16], - base64.StdEncoding.EncodeToString(secret)[:16]) - } - - if !verifyMAC(macKey, routingInfo, receivedMAC) { - log.Printf("[SPHINX] MAC verification failed") - return - } - - decrypted, err := aesDecrypt(encKey, packet.Payload) - if err != nil { - log.Printf("[SPHINX] Decryption failed: %v", err) - return - } - - nullIdx := bytes.IndexByte(routingInfo, 0) - var nextHopAddr string - if nullIdx == -1 { - nextHopAddr = string(routingInfo) - } else { - nextHopAddr = string(routingInfo[:nullIdx]) - } - nextHopAddr = strings.TrimSpace(nextHopAddr) - - if nextHopAddr == "DUMMY" { - if debugMode { - log.Printf("[SPHINX] Discarded dummy message") - } - return - } - - if nextHopAddr == "EXIT" { - deliverMessage(decrypted) - return - } - - // Forward to next hop - if len(decrypted) > HeaderSize { - nextPacket := &SphinxPacket{ - Header: decrypted[:HeaderSize], - Payload: decrypted[HeaderSize:], - } - - node := pki.Get(findNodeByAddress(nextHopAddr)) - if node != nil { - delay := time.Duration(500+cryptoRandInt(2000)) * time.Millisecond - time.Sleep(delay) - - maxRetries := 3 - var lastErr error - for attempt := 1; attempt <= maxRetries; attempt++ { - if err := sendToNode(node, nextPacket); err != nil { - lastErr = err - if attempt < maxRetries { - backoff := time.Duration(1< len(padded)-4 { - return nil, errors.New("invalid length") - } - return padded[4 : 4+length], nil -} - -// ============================================================================= -// EXIT NODE: DELIVERY WITH ENVELOPE AND HEADER SANITIZATION -// ============================================================================= - -func deliverMessage(padded []byte) { - data, err := unpadPayload(padded) - if err != nil { - log.Printf("[EXIT] Unpad failed: %v", err) - return - } - - // v4.1.0: Try to unwrap envelope first - var envelope EnvelopeWrapper - if err := json.Unmarshal(data, &envelope); err == nil && len(envelope.To) > 0 && len(envelope.Data) > 0 { - // Successfully unwrapped envelope - sanitized := sanitizeHeaders(envelope.Data) - - for _, rcpt := range envelope.To { - msg := &Message{ - From: envelope.From, - To: []string{rcpt}, - Data: sanitized, - } - if err := deliverToRecipient(msg); err != nil { - log.Printf("[EXIT] Delivery failed to %s: %v", rcpt, err) - atomic.AddInt64(&stats.Failed, 1) - } else { - atomic.AddInt64(&stats.Delivered, 1) - log.Printf("[EXIT] Delivered to %s", rcpt) - } - } - return - } - - // Fallback: parse raw message (backward compatibility) - msg := parseMessage(data) - if msg == nil || len(msg.To) == 0 { - log.Printf("[EXIT] Parse failed - no recipients found") - atomic.AddInt64(&stats.Failed, 1) - return - } - - msg.Data = sanitizeHeaders(msg.Data) - - for _, rcpt := range msg.To { - singleMsg := &Message{ - From: msg.From, - To: []string{rcpt}, - Data: msg.Data, - } - if err := deliverToRecipient(singleMsg); err != nil { - log.Printf("[EXIT] Delivery failed to %s: %v", rcpt, err) - atomic.AddInt64(&stats.Failed, 1) - } else { - atomic.AddInt64(&stats.Delivered, 1) - log.Printf("[EXIT] Delivered to %s", rcpt) - } - } -} - -// sanitizeHeaders removes identifying headers at exit node -func sanitizeHeaders(data []byte) []byte { - // Normalize line endings: support \r\n, \n, or mixed - normalized := strings.ReplaceAll(string(data), "\r\n", "\n") - lines := strings.Split(normalized, "\n") - - var headers []string - var body []string - inHeaders := true - fromFound := false - headerEndIdx := -1 - - for i, line := range lines { - if inHeaders && line == "" { - headerEndIdx = i - inHeaders = false - continue - } - - if inHeaders { - lower := strings.ToLower(line) - - // Strip identifying headers - if strings.HasPrefix(lower, "x-") || - strings.HasPrefix(lower, "received:") || - strings.HasPrefix(lower, "reply-to:") || - strings.HasPrefix(lower, "user-agent:") || - strings.HasPrefix(lower, "x-mailer:") { - continue - } - - // Replace From with anonymous - if strings.HasPrefix(lower, "from:") { - headers = append(headers, fmt.Sprintf("From: Anonymous ", local.Name)) - fromFound = true - continue - } - - // Replace Date with randomized - if strings.HasPrefix(lower, "date:") { - continue // Will inject our own below - } - - // Replace Message-ID with random - if strings.HasPrefix(lower, "message-id:") { - continue // Will inject our own below - } - - // Keep all other headers: Subject, To, Content-Type, MIME-Version, - // Newsgroups, References, In-Reply-To, Content-Transfer-Encoding - headers = append(headers, line) - } else { - body = append(body, line) - } - } - - // Inject required headers if missing or replaced - if !fromFound { - headers = append(headers, fmt.Sprintf("From: Anonymous ", local.Name)) - } - - // Always inject sanitized Date (randomized ±1-2 hours) - offset := time.Duration(cryptoRandInt(7200)-3600) * time.Second - headers = append(headers, fmt.Sprintf("Date: %s", - time.Now().Add(offset).UTC().Format("Mon, 02 Jan 2006 15:04:05 -0000"))) - - // Always inject random Message-ID - headers = append(headers, fmt.Sprintf("Message-ID: <%s@%s.fog>", - hex.EncodeToString(cryptoRandBytes(12)), local.Name)) - - // If no header/body separator was found, treat entire input as body - if headerEndIdx == -1 { - log.Printf("[SANITIZE] Warning: no header/body separator found, treating as headerless message") - body = lines - } - - // Rebuild message: headers + empty line + body - var result bytes.Buffer - for _, h := range headers { - result.WriteString(h) - result.WriteString("\r\n") - } - result.WriteString("\r\n") // Empty line separator - for i, b := range body { - result.WriteString(b) - if i < len(body)-1 { - result.WriteString("\r\n") - } - } - - return result.Bytes() -} - -// deliverToRecipient delivers a single message to a single recipient -func deliverToRecipient(msg *Message) error { - if len(msg.To) == 0 { - return errors.New("no recipient") - } - - rcpt := msg.To[0] - parts := strings.Split(rcpt, "@") - if len(parts) != 2 { - return fmt.Errorf("invalid recipient: %s", rcpt) - } - domain := parts[1] - - var smtpAddr string - if strings.HasSuffix(domain, ".onion") { - smtpAddr = domain + ":25" - } else { - // v4.1.0: Resolve MX through Tor (no DNS leak) - mxHost, err := lookupMXViaTor(domain) - if err != nil { - if debugMode { - log.Printf("[EXIT] MX lookup via Tor failed for %s: %v, using domain directly", domain, err) - } - smtpAddr = domain + ":25" - } else { - smtpAddr = mxHost + ":25" - } - } - - conn, err := dialTor(smtpAddr) - if err != nil { - return fmt.Errorf("connect to %s: %v", smtpAddr, err) - } - defer conn.Close() - - client, err := smtp.NewClient(conn, domain) - if err != nil { - return fmt.Errorf("smtp client: %v", err) - } - defer client.Close() - - fromAddr := extractAddress(msg.From) - if fromAddr == "" { - fromAddr = fmt.Sprintf("anonymous@%s.fog", local.Name) - } - - if err := client.Mail(fromAddr); err != nil { - return fmt.Errorf("MAIL FROM: %v", err) - } - if err := client.Rcpt(rcpt); err != nil { - return fmt.Errorf("RCPT TO: %v", err) - } - - wc, err := client.Data() - if err != nil { - return fmt.Errorf("DATA: %v", err) - } - - if _, err := wc.Write(msg.Data); err != nil { - wc.Close() - return fmt.Errorf("write data: %v", err) - } - - if err := wc.Close(); err != nil { - return fmt.Errorf("end data: %v", err) - } - - return nil -} - -// lookupMXViaTor resolves MX records through Tor SOCKS5 -// Falls back to direct domain if resolution fails -func lookupMXViaTor(domain string) (string, error) { - // Tor exit nodes handle DNS resolution internally - // We connect to a public DNS-over-TCP service through Tor - conn, err := torDialer.Dial("tcp", "1.1.1.1:53") - if err != nil { - // Fallback: let Tor exit node resolve by connecting directly - return domain, nil - } - defer conn.Close() - conn.SetDeadline(time.Now().Add(10 * time.Second)) - - // Build minimal DNS MX query - txID := cryptoRandBytes(2) - query := buildDNSMXQuery(txID, domain) - - // DNS over TCP: 2-byte length prefix - lenBuf := make([]byte, 2) - binary.BigEndian.PutUint16(lenBuf, uint16(len(query))) - conn.Write(lenBuf) - conn.Write(query) - - // Read response length - if _, err := io.ReadFull(conn, lenBuf); err != nil { - return domain, err - } - respLen := binary.BigEndian.Uint16(lenBuf) - if respLen > 4096 { - return domain, errors.New("DNS response too large") - } - - resp := make([]byte, respLen) - if _, err := io.ReadFull(conn, resp); err != nil { - return domain, err - } - - // Parse MX from response - mx := parseDNSMXResponse(resp) - if mx != "" { - return mx, nil - } - - return domain, nil -} - -// buildDNSMXQuery creates a raw DNS query for MX records -func buildDNSMXQuery(txID []byte, domain string) []byte { - var buf bytes.Buffer - - // Transaction ID - buf.Write(txID) - // Flags: standard query, recursion desired - buf.Write([]byte{0x01, 0x00}) - // Questions: 1 - buf.Write([]byte{0x00, 0x01}) - // Answer, Authority, Additional: 0 - buf.Write([]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00}) - - // Encode domain name - parts := strings.Split(domain, ".") - for _, part := range parts { - buf.WriteByte(byte(len(part))) - buf.WriteString(part) - } - buf.WriteByte(0x00) // Root label - - // Type: MX (15) - buf.Write([]byte{0x00, 0x0f}) - // Class: IN (1) - buf.Write([]byte{0x00, 0x01}) - - return buf.Bytes() -} - -// parseDNSMXResponse extracts the first MX hostname from a DNS response -func parseDNSMXResponse(resp []byte) string { - if len(resp) < 12 { - return "" - } - - // Skip header (12 bytes) - offset := 12 - - // Skip question section - qdCount := int(binary.BigEndian.Uint16(resp[4:6])) - for i := 0; i < qdCount && offset < len(resp); i++ { - // Skip name - for offset < len(resp) { - if resp[offset] == 0 { - offset++ - break - } - if resp[offset]&0xC0 == 0xC0 { - offset += 2 - break - } - offset += int(resp[offset]) + 1 - } - offset += 4 // Skip type and class - } - - // Parse answer section - anCount := int(binary.BigEndian.Uint16(resp[6:8])) - for i := 0; i < anCount && offset < len(resp); i++ { - // Skip name (possibly compressed) - if offset < len(resp) && resp[offset]&0xC0 == 0xC0 { - offset += 2 - } else { - for offset < len(resp) { - if resp[offset] == 0 { - offset++ - break - } - offset += int(resp[offset]) + 1 - } - } - - if offset+10 > len(resp) { - break - } - - rtype := binary.BigEndian.Uint16(resp[offset : offset+2]) - offset += 2 // Type - offset += 2 // Class - offset += 4 // TTL - rdLen := int(binary.BigEndian.Uint16(resp[offset : offset+2])) - offset += 2 // RDLENGTH - - if rtype == 15 && rdLen > 2 { // MX record - offset += 2 // Skip preference - // Read exchange name - name := readDNSName(resp, offset) - if name != "" { - return name - } - } - - offset += rdLen - } - - return "" -} - -// readDNSName reads a DNS name from a response, handling compression -func readDNSName(resp []byte, offset int) string { - var parts []string - visited := make(map[int]bool) // Prevent infinite loops from malicious packets - - for offset < len(resp) { - if visited[offset] { - break - } - visited[offset] = true - - length := int(resp[offset]) - if length == 0 { - break - } - - if length&0xC0 == 0xC0 { - if offset+1 >= len(resp) { - break - } - newOffset := int(binary.BigEndian.Uint16(resp[offset:offset+2]) & 0x3FFF) - offset = newOffset - continue - } - - offset++ - if offset+length > len(resp) { - break - } - parts = append(parts, string(resp[offset:offset+length])) - offset += length - } - - return strings.Join(parts, ".") -} - -// ============================================================================= -// SMTP SERVER -// ============================================================================= - -func startSMTP(addr string) error { - listener, err := net.Listen("tcp", addr) - if err != nil { - return err - } - - log.Printf("[SMTP] Listening on %s", addr) - - go func() { - <-ctx.Done() - listener.Close() - }() - - for { - conn, err := listener.Accept() - if err != nil { - if ctx.Err() != nil { - return nil - } - continue - } - go handleSMTP(conn) - } -} - -func handleSMTP(conn net.Conn) { - defer conn.Close() - conn.SetDeadline(time.Now().Add(5 * time.Minute)) - - reader := bufio.NewReader(conn) - writer := bufio.NewWriter(conn) - - write := func(s string) { - writer.WriteString(s + "\r\n") - writer.Flush() - } - - write(fmt.Sprintf("220 fog/%s ESMTP", Version)) - - var from string - var to []string - var data bytes.Buffer - inData := false - - for { - line, err := reader.ReadString('\n') - if err != nil { - return - } - - if inData { - // v4.1.0: Only trim \r\n, preserve internal whitespace for MIME/PGP integrity - stripped := strings.TrimRight(line, "\r\n") - - if stripped == "." { - inData = false - write("250 OK queued") - - msg := &Message{ - ID: hex.EncodeToString(cryptoRandBytes(8)), - From: from, - To: to, - Data: data.Bytes(), - ReceivedAt: time.Now(), - } - - select { - case queue <- msg: - atomic.AddInt64(&stats.Received, 1) - log.Printf("[SMTP] Queued %s from %s to %v (%d bytes)", msg.ID, from, to, len(msg.Data)) - default: - log.Printf("[SMTP] Queue full, dropping message") - } - - from = "" - to = nil - data.Reset() - } else { - // Dot-stuffing (RFC 5321 4.5.2) - if strings.HasPrefix(stripped, ".") { - stripped = stripped[1:] - } - data.WriteString(stripped + "\r\n") - } - continue - } - - line = strings.TrimRight(line, "\r\n") - upper := strings.ToUpper(line) - - switch { - case strings.HasPrefix(upper, "EHLO"): - // v4.1.0: Proper ESMTP capability advertisement - write(fmt.Sprintf("250-%s", hostname)) - write("250-8BITMIME") - write("250-SMTPUTF8") - write(fmt.Sprintf("250-SIZE %d", MaxMsgSize)) - write("250 PIPELINING") - - case strings.HasPrefix(upper, "HELO"): - write(fmt.Sprintf("250 %s", hostname)) - - case strings.HasPrefix(upper, "MAIL FROM:"): - from = extractAddress(line[10:]) - write("250 OK") - - case strings.HasPrefix(upper, "RCPT TO:"): - to = append(to, extractAddress(line[8:])) - write("250 OK") - - case upper == "DATA": - if from == "" || len(to) == 0 { - write("503 Bad sequence") - continue - } - write("354 Start mail input") - inData = true - - case upper == "QUIT": - write("221 Bye") - return - - case upper == "RSET": - from = "" - to = nil - data.Reset() - write("250 OK") - - case upper == "NOOP": - write("250 OK") - - default: - write("500 Unknown command") - } - } -} - -func extractAddress(s string) string { - s = strings.TrimSpace(s) - // Use LAST '<' to handle nested brackets like > - if lastStart := strings.LastIndex(s, "<"); lastStart != -1 { - if end := strings.Index(s[lastStart:], ">"); end != -1 { - return s[lastStart+1 : lastStart+end] - } - } - if strings.HasPrefix(s, "<") && strings.HasSuffix(s, ">") { - return s[1 : len(s)-1] - } - return s -} - -// ============================================================================= -// NODE SERVER -// ============================================================================= - -func startNodeServer(addr string) error { - listener, err := net.Listen("tcp", addr) - if err != nil { - return err - } - - log.Printf("[NODE] Listening on %s", addr) - - go func() { - <-ctx.Done() - listener.Close() - }() - - go func() { - defer wg.Done() - for { - conn, err := listener.Accept() - if err != nil { - if ctx.Err() != nil { - return - } - continue - } - go handleNode(conn) - } - }() - - return nil -} - -func handleNode(conn net.Conn) { - defer conn.Close() - conn.SetDeadline(time.Now().Add(60 * time.Second)) - - reader := bufio.NewReader(conn) - line, err := reader.ReadString('\n') - if err != nil { - return - } - line = strings.TrimSpace(line) - - switch { - case strings.HasPrefix(line, "SPHINX "): - var size int - fmt.Sscanf(line, "SPHINX %d", &size) - if size > 0 && size < 1<<20 { - data := make([]byte, size) - io.ReadFull(reader, data) - - if len(data) > HeaderSize { - packet := &SphinxPacket{ - Header: data[:HeaderSize], - Payload: data[HeaderSize:], - } - pool.Add(packet) - conn.Write([]byte("OK\r\n")) - } - } - - case strings.HasPrefix(line, "GOSSIP "): - var size int - fmt.Sscanf(line, "GOSSIP %d", &size) - if size > 0 && size < 1<<20 { - data := make([]byte, size) - io.ReadFull(reader, data) - pki.MergeFromGossip(data) - - myData := pki.ExportForGossip() - fmt.Fprintf(conn, "GOSSIP %d\r\n", len(myData)) - conn.Write(myData) - conn.Write([]byte("\r\n")) - } - - case line == "PING": - conn.Write([]byte("PONG\r\n")) - - case line == "INFO": - info := fmt.Sprintf("fog/%s %s %d nodes\r\n", - Version, local.Name, pki.HealthyCount()) - conn.Write([]byte(info)) - } -} - -// ============================================================================= -// RELAY WORKER -// ============================================================================= - -func relayWorker(id int) { - defer wg.Done() - - for { - select { - case <-ctx.Done(): - return - case msg := <-queue: - processMessage(msg, id) - } - } -} - -func processMessage(msg *Message, workerID int) { - // Check replay - msgHash := hex.EncodeToString(computeMAC([]byte("replay"), msg.Data)[:16]) - if replay.Check(msgHash) { - log.Printf("[WORKER %d] Replay detected: %s", workerID, msg.ID) - return - } - replay.Add(msgHash) - - // Random delay - delay := time.Duration(100+cryptoRandInt(2000)) * time.Millisecond - time.Sleep(delay) - - // v4.1.0: Wrap SMTP envelope into payload for Sphinx routing - envelopePayload, err := json.Marshal(&EnvelopeWrapper{ - From: msg.From, - To: msg.To, - Data: msg.Data, - }) - if err != nil { - log.Printf("[WORKER %d] Failed to marshal envelope for %s: %v", workerID, msg.ID, err) - atomic.AddInt64(&stats.Failed, 1) - return - } - - // Check payload size limit - if len(envelopePayload) > PayloadMax-4 { - log.Printf("[WORKER %d] Message %s too large for Sphinx (%d bytes), using direct relay", - workerID, msg.ID, len(envelopePayload)) - if err := directRelay(msg); err != nil { - log.Printf("[WORKER %d] Direct relay failed for %s: %v", workerID, msg.ID, err) - atomic.AddInt64(&stats.Failed, 1) - } else { - atomic.AddInt64(&stats.DirectRelay, 1) - log.Printf("[WORKER %d] Direct relayed %s (oversized)", workerID, msg.ID) - } - return - } - - // v4.1.0: Fallback to direct relay if Sphinx unavailable - if !useSphinx.Load() { - log.Printf("[WORKER %d] Sphinx disabled, using direct relay for %s", workerID, msg.ID) - if err := directRelay(msg); err != nil { - log.Printf("[WORKER %d] Direct relay failed for %s: %v", workerID, msg.ID, err) - atomic.AddInt64(&stats.Failed, 1) - } else { - atomic.AddInt64(&stats.DirectRelay, 1) - log.Printf("[WORKER %d] Direct relayed %s", workerID, msg.ID) - } - return - } - - healthy := pki.GetHealthy() - if len(healthy) < MinHops { - log.Printf("[WORKER %d] Not enough healthy nodes (%d < %d), using direct relay for %s", - workerID, len(healthy), MinHops, msg.ID) - if err := directRelay(msg); err != nil { - log.Printf("[WORKER %d] Direct relay failed for %s: %v", workerID, msg.ID, err) - atomic.AddInt64(&stats.Failed, 1) - } else { - atomic.AddInt64(&stats.DirectRelay, 1) - log.Printf("[WORKER %d] Direct relayed %s (insufficient nodes)", workerID, msg.ID) - } - return - } - - hopCount := MinHops + cryptoRandInt(MaxHops-MinHops+1) - if hopCount > len(healthy) { - hopCount = len(healthy) - } - route := selectRoute(healthy, hopCount) - if route == nil { - log.Printf("[WORKER %d] Failed to select route for %s", workerID, msg.ID) - atomic.AddInt64(&stats.Failed, 1) - return - } - - // v4.1.0: Use envelope payload instead of raw msg.Data - packet := createSphinxPacket(envelopePayload, route, false) - if packet == nil { - log.Printf("[WORKER %d] Failed to create Sphinx packet for %s", workerID, msg.ID) - atomic.AddInt64(&stats.Failed, 1) - return - } - - if err := sendToNode(route[0], packet); err != nil { - log.Printf("[WORKER %d] Failed to send to first hop for %s: %v", workerID, msg.ID, err) - atomic.AddInt64(&stats.Failed, 1) - return - } - - atomic.AddInt64(&stats.SphinxRouted, 1) - log.Printf("[WORKER %d] Sphinx routed %s via %d hops", workerID, msg.ID, hopCount) -} - -// v4.1.0: directRelay delivers message directly through Tor (no Sphinx) -func directRelay(msg *Message) error { - sanitized := sanitizeHeaders(msg.Data) - - for _, rcpt := range msg.To { - singleMsg := &Message{ - From: msg.From, - To: []string{rcpt}, - Data: sanitized, - } - if err := deliverToRecipient(singleMsg); err != nil { - return fmt.Errorf("relay to %s: %v", rcpt, err) - } - } - return nil -} - -func parseMessage(data []byte) *Message { - lines := strings.Split(string(data), "\n") - msg := &Message{Data: data} - - for _, line := range lines { - line = strings.TrimRight(line, "\r") - if line == "" { - break - } - lower := strings.ToLower(line) - if strings.HasPrefix(lower, "from:") { - msg.From = extractAddress(line[5:]) - } else if strings.HasPrefix(lower, "to:") { - msg.To = append(msg.To, extractAddress(line[3:])) - } - } - - if msg.From == "" { - msg.From = fmt.Sprintf("anonymous@%s.fog", local.Name) - } - - return msg -} - -// ============================================================================= -// HEALTH CHECKER -// ============================================================================= - -func healthChecker() { - defer wg.Done() - - ticker := time.NewTicker(HealthInterval) - defer ticker.Stop() - - for { - select { - case <-ctx.Done(): - return - case <-ticker.C: - checkAllNodes() - } - } -} - -func checkAllNodes() { - others := pki.GetOthers() - for _, node := range others { - go checkNode(node) - } -} - -func checkNode(node *Node) { - conn, err := dialTor(node.Address) - if err != nil { - pki.SetHealth(node.ID, false) - return - } - defer conn.Close() - conn.SetDeadline(time.Now().Add(15 * time.Second)) - - fmt.Fprintf(conn, "PING\r\n") - reader := bufio.NewReader(conn) - line, err := reader.ReadString('\n') - if err != nil || !strings.HasPrefix(line, "PONG") { - pki.SetHealth(node.ID, false) - return - } - - pki.SetHealth(node.ID, true) -} - -// ============================================================================= -// STATS -// ============================================================================= - -func statsMonitor() { - defer wg.Done() - - ticker := time.NewTicker(StatsInterval) - defer ticker.Stop() - - for { - select { - case <-ctx.Done(): - return - case <-ticker.C: - uptime := time.Since(stats.Start).Truncate(time.Second) - log.Printf("[STATS] Up:%v | R:%d D:%d F:%d | Sphinx:%d Direct:%d | Cover:%d Gossip:%d | Pool:%d Nodes:%d", - uptime, - atomic.LoadInt64(&stats.Received), - atomic.LoadInt64(&stats.Delivered), - atomic.LoadInt64(&stats.Failed), - atomic.LoadInt64(&stats.SphinxRouted), - atomic.LoadInt64(&stats.DirectRelay), - atomic.LoadInt64(&stats.CoverSent), - atomic.LoadInt64(&stats.GossipExch), - pool.Size(), - pki.HealthyCount()) - } - } -} - -// ============================================================================= -// HELPERS -// ============================================================================= - -func dialTor(addr string) (net.Conn, error) { - return torDialer.Dial("tcp", addr) -} - -func shuffleNodes(nodes []*Node) { - for i := len(nodes) - 1; i > 0; i-- { - j := cryptoRandInt(i + 1) - nodes[i], nodes[j] = nodes[j], nodes[i] - } -} - -func shufflePackets(packets []*SphinxPacket) { - for i := len(packets) - 1; i > 0; i-- { - j := cryptoRandInt(i + 1) - packets[i], packets[j] = packets[j], packets[i] - } -} - -func initNode(addr string) { - var pub, priv []byte - var id string - - if keyFile != "" { - if data, err := os.ReadFile(keyFile); err == nil { - var saved struct { - ID string `json:"id"` - Public string `json:"public_key"` - Private string `json:"private_key"` - } - if err := json.Unmarshal(data, &saved); err == nil { - pub, _ = base64.StdEncoding.DecodeString(saved.Public) - priv, _ = base64.StdEncoding.DecodeString(saved.Private) - id = saved.ID - if len(pub) == KyberPKSize && len(priv) == KyberSKSize && id != "" { - log.Printf("[NODE] Loaded existing Kyber keypair from %s", keyFile) - } else if len(pub) == 32 && len(priv) == 32 { - log.Printf("[NODE] Found old Curve25519 keys, regenerating Kyber keypair") - pub, priv, id = nil, nil, "" - } else { - log.Printf("[NODE] Invalid key sizes (pub=%d priv=%d), regenerating", len(pub), len(priv)) - pub, priv, id = nil, nil, "" - } - } - } - } - - if pub == nil || priv == nil { - pub, priv = generateKeyPair() - id = hex.EncodeToString(computeMAC(pub, []byte("node-id"))[:16]) - log.Printf("[NODE] Generated new Kyber-768 keypair") - - if keyFile != "" { - saved := struct { - ID string `json:"id"` - Public string `json:"public_key"` - Private string `json:"private_key"` - }{ - ID: id, - Public: base64.StdEncoding.EncodeToString(pub), - Private: base64.StdEncoding.EncodeToString(priv), - } - if data, err := json.MarshalIndent(saved, "", " "); err == nil { - if err := os.WriteFile(keyFile, data, 0600); err == nil { - log.Printf("[NODE] Saved keypair to %s", keyFile) - } else { - log.Printf("[NODE] Warning: failed to save keypair: %v", err) - } - } - } - } - - local = LocalNode{ - ID: id, - Public: pub, - Private: priv, - Address: addr, - Name: hostname, - } - - publicAddr := addr - if hostname != "" && hostname != "fog.onion" { - port := "9999" - if _, p, err := net.SplitHostPort(addr); err == nil { - port = p - } - publicAddr = hostname + ":" + port - } - - pki.Add(&Node{ - ID: local.ID, - PublicKey: local.Public, - Address: publicAddr, - Name: local.Name, - Version: Version, - LastSeen: time.Now(), - Healthy: true, - }) -} - -// ============================================================================= -// MAIN -// ============================================================================= - -func main() { - smtpAddr := flag.String("smtp", DefaultSMTP, "SMTP listen address") - nodeAddr := flag.String("node", DefaultNode, "Node listen address") - name := flag.String("name", "fog.onion", "Server hostname") - sphinx := flag.Bool("sphinx", false, "Enable Sphinx routing") - pkiFlag := flag.String("pki", "", "PKI file path") - keyFlag := flag.String("key", "", "Node key file path (for persistent identity)") - debug := flag.Bool("debug", false, "Enable debug logging") - exportInfo := flag.Bool("export-node-info", false, "Export node info and exit") - version := flag.Bool("version", false, "Show version") - - flag.Parse() - - if *version { - fmt.Printf("fog v%s\n\n", Version) - fmt.Println("Features:") - fmt.Println(" - Sphinx multi-hop routing (3-6 hops)") - fmt.Println(" - PKI Gossip protocol (fully decentralized)") - fmt.Println(" - Threshold batch mixing") - fmt.Println(" - Realistic cover traffic") - fmt.Println(" - AES-256-GCM encryption") - fmt.Println(" - Forward secrecy (Kyber-768 KEM)") - fmt.Println(" - SMTP envelope preservation through mixnet") - fmt.Println(" - Exit node header sanitization") - fmt.Println(" - DNS MX resolution through Tor") - fmt.Println(" - ESMTP: 8BITMIME, SMTPUTF8, PIPELINING") - os.Exit(0) - } - - debugMode = *debug - - pki = newPKI() - pool = newBatchPool() - replay = newReplayCache() - queue = make(chan *Message, QueueSize) - stats = &Stats{Start: time.Now()} - cover = newCoverTraffic() - - hostname = *name - pkiFile = *pkiFlag - keyFile = *keyFlag - - if pkiFile != "" { - // Derive state file path: nodes.json -> nodes_state.json - pkiStateFile = strings.TrimSuffix(pkiFile, ".json") + "_state.json" - - // Load bootstrap PKI (hand-crafted, never overwritten by fog) - if err := pki.Load(pkiFile); err != nil { - log.Printf("[PKI] Bootstrap load failed: %v", err) - } - - // Merge dynamic state (gossip discoveries from previous runs) - if data, err := os.ReadFile(pkiStateFile); err == nil { - added := pki.MergeFromGossip(data) - if added > 0 { - log.Printf("[PKI] Merged %d nodes from dynamic state", added) - } - } - - removed := pki.CleanupDuplicates() - if removed > 0 { - log.Printf("[PKI] Cleaned up %d duplicate nodes", removed) - } - } - - initNode(*nodeAddr) - - if *exportInfo { - hostname = *name - keyFile = *keyFlag - pki = newPKI() - initNode(*nodeAddr) - - info := map[string]interface{}{ - "id": local.ID, - "public_key": base64.StdEncoding.EncodeToString(local.Public), - "address": fmt.Sprintf("%s:9999", *name), - "name": *name, - "version": Version, - } - data, _ := json.MarshalIndent(info, "", " ") - fmt.Println(string(data)) - os.Exit(0) - } - - dialer, err := proxy.SOCKS5("tcp", TorSocks, nil, proxy.Direct) - if err != nil { - log.Fatalf("[TOR] Connection failed: %v", err) - } - torDialer = dialer - - ctx, cancel = context.WithCancel(context.Background()) - defer cancel() - - log.Printf("[FOG] Starting v%s", Version) - log.Printf("[FOG] Hostname: %s", hostname) - log.Printf("[FOG] PKI: %d total nodes, %d healthy", len(pki.GetAll()), pki.HealthyCount()) - - useSphinx.Store(*sphinx) - - if *sphinx { - log.Printf("[FOG] Sphinx mode ENABLED") - log.Printf("[FOG] Batch threshold: %d-%d, Cover: %d-%.0fh interval", - BatchThresholdMin, BatchThresholdMax, - int(CoverMinInterval.Minutes()), CoverMaxInterval.Hours()) - - // Start node server FIRST (must be ready before health checks) - wg.Add(1) - if err := startNodeServer(*nodeAddr); err != nil { - log.Fatalf("[NODE] Failed: %v", err) - } - - // Initial health check at startup (don't wait 3 minutes) - log.Printf("[FOG] Running initial health check...") - checkAllNodes() - // Wait for Tor hidden service connections (can take 15-30s each) - time.Sleep(45 * time.Second) - healthy := pki.HealthyCount() - log.Printf("[FOG] Initial health: %d healthy nodes", healthy) - if healthy < MinHops { - log.Printf("[FOG] WARNING: only %d healthy nodes (need %d for Sphinx), will use direct relay until more nodes come online", healthy, MinHops) - } - - wg.Add(1) - go healthChecker() - - wg.Add(1) - go batchWorker() - - wg.Add(1) - go gossipWorker() - - wg.Add(1) - go coverWorker() - } else { - log.Printf("[FOG] Direct relay mode (Sphinx disabled)") - } - - for i := 0; i < Workers; i++ { - wg.Add(1) - go relayWorker(i) - } - - wg.Add(1) - go statsMonitor() - - wg.Add(1) - go cacheCleanupWorker() - - sig := make(chan os.Signal, 1) - signal.Notify(sig, os.Interrupt, syscall.SIGTERM) - - go func() { - <-sig - log.Printf("[FOG] Shutdown signal received") - cancel() - }() - - if pkiStateFile != "" { - go func() { - ticker := time.NewTicker(10 * time.Minute) - defer ticker.Stop() - for { - select { - case <-ctx.Done(): - pki.SaveState(pkiStateFile) - return - case <-ticker.C: - pki.SaveState(pkiStateFile) - } - } - }() - } - - if err := startSMTP(*smtpAddr); err != nil { - log.Fatalf("[SMTP] Failed: %v", err) - } - - wg.Wait() - - if pkiStateFile != "" { - pki.SaveState(pkiStateFile) - } - - log.Printf("[FOG] Shutdown complete") -} diff --git a/go.mod b/go.mod deleted file mode 100644 index 869d133..0000000 --- a/go.mod +++ /dev/null @@ -1,9 +0,0 @@ -module fog - -go 1.21 - -require ( - github.com/mattn/go-sqlite3 v1.14.18 - golang.org/x/crypto v0.17.0 - golang.org/x/net v0.19.0 -) diff --git a/go.sum b/go.sum deleted file mode 100644 index 6abaca5..0000000 --- a/go.sum +++ /dev/null @@ -1,6 +0,0 @@ -github.com/mattn/go-sqlite3 v1.14.18 h1:JL0eqdCOq6DJVNPSvArO/bIV9/P7fbGrV00LZHc+5aI= -github.com/mattn/go-sqlite3 v1.14.18/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= -golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= -golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= -golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= -golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= diff --git a/merkle-tree.txt b/merkle-tree.txt new file mode 100644 index 0000000..fd588d9 --- /dev/null +++ b/merkle-tree.txt @@ -0,0 +1,49 @@ +FOG-DOCS-MERKLE-1 +hash: SHA-256 +path-order: bytewise ascending UTF-8 repository-relative paths +leaf: SHA256(0x00 || uint64be(path_length) || path || uint64be(content_length) || content) +node: SHA256(0x01 || left_hash || right_hash) +shape: RFC 6962 recursive split at the largest power of two smaller than the leaf count +scope: every repository file whose name ends in .md; merkle-tree.txt is intentionally excluded +leaf-count: 18 + +leaves: +L00 9b9241e53e75e86897d8c0e67f4fd960041c7d8b0df0b7be70a109f08e8fd2ca 9576 README.md +L01 415213d7b09f51e5c08f04d992dfe2f4635632924e4a4951723a7d66b793d985 8799 docs/FOG-ALPHA.md +L02 50eeae3e905a5342bb9f8f45a004949c5c8d10968ce2fd994c817cbee645565a 55832 docs/FOG-ARCHITECTURE.md +L03 adb3fcd7ff4946398c4868c0f062cbcffaa8a11797aba2375c6c3bdff1a61c41 78489 docs/FOG-COMPOSER.md +L04 a1572dc0e269e2d8b6213344a37b4e96b2bb134c0b7f436b0dbe9ac79f96040a 7565 docs/FOG-CRYPTO-BENCHMARKS.md +L05 5d3dede7d4cf14e18666758eee7c4fb2b19232e2102c586fe2e732f7fa74af84 37535 docs/FOG-CRYPTO-SUITES.md +L06 012d873502c9bb3dae72ab98b5b72bab37cffe54b7d3213ac93099e0aded3102 16204 docs/FOG-LOCAL-POC.md +L07 a1cb8c3915ce87a95215a22c4d77055ab9aeb055458d239f1910499c09218ad4 46508 docs/FOG-MESSAGING.md +L08 682b534264fecb4ed31210529d9aeb85f9bbea88d29cd36c081662171d21f4a8 31602 docs/FOG-OBSERVABILITY.md +L09 449455df488c4cf4bc0121e8d174e1164e7a1f0f404f36ed4716a5377b024ce1 67912 docs/FOG-PKI.md +L10 ffcdc77522d694bcbc11be0ce28ab58f9928d097f56e20a46ff08950566ef211 6950 docs/FOG-POC-PRESERVATION.md +L11 e36d9acf73343eb088a5ccac6c5528ab7334a40f5d88cae973674532279b0c22 6716 docs/FOG-SECURITY-TEST-PLAN.md +L12 94050ece3310571d3758f80eb2b1867c1bb2ae7e2fe933814732fcf3ff1b32db 13908 docs/FOG-SIMULATION.md +L13 6b6b795434bb471f0bac368bd0a18cc824fa956712f0c96f6b17a5b873ce3891 46401 docs/FOG-SPHINX-PROFILES.md +L14 9c9017d04486a377ffd1d189a99c5cda1efe33040e9a22f28633d214d1aa6e97 67516 docs/FOG-STORAGE.md +L15 9ec2dbb5b0d0c439aa3424e9da64fb26b4798e7fea7acaa3eb2c2290f98d6a36 43156 docs/FOG-SX.md +L16 0595a5fe5da9fb8dbee2fac17b059f78028bc75d5ebc8863f1ac9a913c23bdde 43740 docs/FOG-THREAT-MODEL.md +L17 370bba48b1424040bd9d74eace070b87afac1cc912f175d371f5838c2235c052 54012 docs/FOG-WIRE.md + +internal-nodes-postorder: +N[0,2) 8a571e447f8f9cba4269060096b71395868b2a972a1935d03f8c3eca98d3e336 +N[2,4) f0fd551eef1f4107631e87226a77a98656c15a7c688f703b36937345fbe6e700 +N[0,4) f3bcc0d59b2815a588836a334143fa01327292cb70de56e0a3165568c270713e +N[4,6) f939f6550e1b5171e0b1e8cb89475b6a82a17139976b4fc69b8ac1f4b7d2365a +N[6,8) 11fd5d6e48fe3294ba015335adaa0d32ee30da80ee8dc50623d3d97108073db8 +N[4,8) 0fcc1239c92a4cf9ad0a545ccddb208ba995571377cf6bfcc1fc4b0e98b95191 +N[0,8) 33caade8ce2b90a8ef5aea5c59862dfc5a4d7567f7b5f7ddaa4d28eb52480ed3 +N[8,10) db00e1154a62aa86b01480ec5d299f1063b3c159ece49a55686860f1873d9257 +N[10,12) 50d1363126cb6fe2d5fcfc1902cd7a401cbbbc1e4fa054e6bfd31a72dcfe9ffb +N[8,12) 8e5e56fe18531f87d7fae335f636b4eef591456e0700baeb7b35087f2ed3b1c4 +N[12,14) ae242a00d262e7eb56e40777acd68d093ac544703f019ccf0a2cee1b25c91933 +N[14,16) 2051bc8fd348330c541879147cee36984648eb77e810101872607d9fb929b7d2 +N[12,16) ef892a49dbd5ea2a3da3c3b64a590fd1b0ffe7c3f6e980178d0722357032588f +N[8,16) d7e5c0b333d8b4d2f909465c9393e91ae9f505a4352704248ff5468d5000b02f +N[0,16) b4caa77b1d05d979bfb02effafbdc63411d0e6bb55227822f002b39a1627fb97 +N[16,18) 36a18347a69583877658b211e3a6ad55c4d8f538b8abcbd16cfb783cf7272d14 +N[0,18) c5dbc6087b5f4a5ed77cb6811880962df1182ca9c4c7f218524d83985d3e9407 + +root: c5dbc6087b5f4a5ed77cb6811880962df1182ca9c4c7f218524d83985d3e9407 -- cgit v1.2.3