Files
featherChat/warzone/docs/ARCHITECTURE.md
Siavash Sameni 953b3bd13a docs: CLAUDE.md design principles, update ARCHITECTURE + SECURITY
- CLAUDE.md: design principles (E2E by default, semi-trusted server,
  federation transparency, TG bot compat), coding conventions for Rust/TUI/
  WASM/federation/bots, task naming, key files reference
- ARCHITECTURE.md: added bots to high-level diagram, friends/bot/resolve
  modules, 9 sled trees (was 7), bot API sequence diagram, addressing table,
  federated features table, test count 72→122
- SECURITY.md: v0.0.21, added friend list/API auth/device/bot alias to
  protected assets, auth & authorization section, rate limiting, session recovery

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 07:39:30 +04:00

690 lines
24 KiB
Markdown

# Warzone Messenger (featherChat) — Architecture
**Version:** 0.0.21
**Status:** Phase 1 + Phase 2 + WZP Integration + Federation
---
## High-Level Architecture
```mermaid
graph TB
CLI[CLI Client] --> PROTO[warzone-protocol]
TUI[TUI Client] --> PROTO
WEB[Web Client WASM] --> PROTO
BOT[Bots TG API] -->|HTTP| SRVA
PROTO -->|HTTP / WS| SRVA[Server Alpha]
PROTO -->|HTTP / WS| SRVB[Server Bravo]
SRVA <-->|Federation WS| SRVB
SRVA -->|Call Signaling| WZP[WarzonePhone Relay]
```
---
## Crate Structure
```mermaid
graph LR
subgraph Workspace
PROTO["warzone-protocol<br/>(library, no I/O)"]
SERVER["warzone-server<br/>(axum binary)"]
CLIENT["warzone-client<br/>(CLI/TUI binary)"]
WASM["warzone-wasm<br/>(wasm-bindgen)"]
MULE["warzone-mule<br/>(future)"]
end
SERVER --> PROTO
CLIENT --> PROTO
WASM --> PROTO
MULE --> PROTO
subgraph External["WarzonePhone (submodule)"]
WZP_PROTO["wzp-proto"]
WZP_CRYPTO["wzp-crypto"]
WZP_RELAY["wzp-relay"]
WZP_WEB["wzp-web"]
end
```
```
warzone/
├── Cargo.toml # Workspace root (v0.0.21)
├── federation.example.json # Federation config template
├── crates/
│ ├── warzone-protocol/ # Core crypto & message types
│ ├── warzone-server/ # Server binary (axum + sled)
│ ├── warzone-client/ # CLI/TUI client binary
│ ├── warzone-wasm/ # WASM bridge for web client
│ └── warzone-mule/ # Mule binary (future)
├── warzone-phone/ # WZP submodule (voice/video)
└── docs/
```
---
## Protocol Modules
### warzone-protocol
| Module | Purpose |
|---------------|------------------------------------------------------|
| `identity` | Seed, IdentityKeyPair, PublicIdentity, Fingerprint |
| `mnemonic` | BIP39 mnemonic encode/decode (24 words) |
| `crypto` | HKDF-SHA256, ChaCha20-Poly1305 AEAD |
| `prekey` | SignedPreKey, OneTimePreKey, PreKeyBundle |
| `x3dh` | X3DH key agreement (initiate + respond) |
| `ratchet` | Double Ratchet state machine (MAX_SKIP=1000) |
| `message` | WireMessage enum (8 variants), CallSignalType |
| `sender_keys` | Sender Key protocol for group encryption |
| `history` | Encrypted backup/restore |
| `ethereum` | secp256k1, Keccak-256, Ethereum address derivation |
| `friends` | E2E encrypted friend list (encrypt/decrypt with HKDF key) |
| `types` | Fingerprint, DeviceId, SessionId, MessageId |
### warzone-server
| Module | Purpose |
|----------------------|---------------------------------------------------|
| `main` | CLI args, startup, federation init |
| `state` | AppState, Connections, CallState, DedupTracker |
| `db` | 9 sled trees: keys, messages, groups, aliases, tokens, calls, missed_calls, friends, eth_addresses |
| `federation` | Peer config, presence sync, message forwarding |
| `auth_middleware` | Bearer token extractor (401 on protected routes) |
| `routes/auth` | Challenge-response authentication |
| `routes/ws` | WebSocket relay + call signaling awareness |
| `routes/messages` | Send, poll (fetch-and-delete), ack |
| `routes/groups` | Create, join, leave, kick, members, send |
| `routes/calls` | Call CRUD, group call initiation |
| `routes/devices` | Device listing, kick, revoke-all |
| `routes/presence` | Online status (single + batch) |
| `routes/federation` | Peer presence sync + message forwarding |
| `routes/wzp` | WZP relay config + service token |
| `routes/aliases` | Alias CRUD with TTL + recovery keys |
| `routes/keys` | Pre-key bundle registration & retrieval |
| `routes/friends` | Encrypted friend list blob storage (GET/POST) |
| `routes/bot` | Telegram Bot API compatibility layer |
| `routes/resolve` | Address resolution (ETH/alias/fingerprint → fp) |
### warzone-client (TUI)
| Module | Purpose |
|--------------------|-------------------------------------------------|
| `tui/mod` | Event loop, run_tui() entry point |
| `tui/types` | App, ChatLine, scroll/connection state |
| `tui/draw` | Rendering: timestamps, scroll, status dot, badge |
| `tui/input` | Keyboard: text editing, scroll keys |
| `tui/commands` | /help, /call, /devices, /kick, 20+ commands |
| `tui/file_transfer`| Chunked file send (DM + group) |
| `tui/network` | WS/HTTP polling, group decrypt, session recovery |
| `storage` | LocalDb: sessions, pre_keys, contacts, history, sender_keys |
### warzone-wasm
| Export | Purpose |
|-----------------------------------|--------------------------------------------|
| `WasmIdentity` | Seed generation, fingerprint, bundle |
| `WasmSession` | Encrypt/decrypt with Double Ratchet |
| `decrypt_wire_message` | Full message pipeline (all 8 variants) |
| `create_receipt` | Build receipt WireMessages |
| `decrypt_group_message` | Sender Key group decryption |
| `create_sender_key_from_distribution` | Build SenderKey from distribution |
| `self_test` | End-to-end crypto verification in WASM |
---
## Cryptographic Stack
```mermaid
graph TB
PLAIN["Plaintext Message"] --> DR["Double Ratchet<br/>(per-message keys)"]
DR --> X3DH_INIT["X3DH Session Init<br/>(3-4 DH operations)"]
X3DH_INIT --> AEAD["ChaCha20-Poly1305<br/>(AEAD encryption)"]
AEAD --> SIGN["Ed25519 Signature<br/>(pre-key signing)"]
SIGN --> WIRE["WireMessage<br/>(bincode serialization)"]
WIRE --> TRANSPORT["HTTP POST / WS Binary"]
style DR fill:#2d5016,color:#fff
style AEAD fill:#1a3a5c,color:#fff
style X3DH_INIT fill:#4a1a5c,color:#fff
```
### Primitives
| Primitive | Crate | Purpose |
|-----------------------|----------------------|------------------------------------------|
| Ed25519 | `ed25519-dalek` | Signing, identity verification |
| X25519 | `x25519-dalek` | Diffie-Hellman key exchange |
| ChaCha20-Poly1305 | `chacha20poly1305` | Authenticated encryption (AEAD) |
| HKDF-SHA256 | `hkdf` + `sha2` | Key derivation with domain separation |
| SHA-256 | `sha2` | Fingerprints, file integrity, room hashing |
| Argon2id | `argon2` | Passphrase-based seed encryption at rest |
| secp256k1 ECDSA | `k256` | Ethereum-compatible signing |
| Keccak-256 | `tiny-keccak` | Ethereum address derivation |
---
## Identity Derivation
```mermaid
graph LR
SEED["BIP39 Seed<br/>(32 bytes, 24 words)"]
SEED -->|"HKDF(info='warzone-ed25519')"| ED["Ed25519 Signing Key"]
SEED -->|"HKDF(info='warzone-x25519')"| X25519["X25519 Encryption Key"]
SEED -->|"HKDF(info='warzone-secp256k1')"| SECP["secp256k1 Key"]
SEED -->|"HKDF(info='warzone-history')"| HIST["History Encryption Key"]
ED -->|"SHA-256[:16]"| FP["Fingerprint<br/>xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx"]
SECP -->|"Keccak-256[-20:]"| ETH["Ethereum Address<br/>0x..."]
```
A single mnemonic controls: messaging identity (Ed25519 + X25519), Ethereum wallet (secp256k1), and backup encryption. WarzonePhone uses the same seed with identical HKDF parameters for shared identity (verified by 15 cross-project tests).
---
## Wire Protocol
### WireMessage Variants
```mermaid
graph TB
WM["WireMessage (bincode)"]
WM --> KE["KeyExchange<br/>X3DH + first ratchet msg"]
WM --> MSG["Message<br/>Double Ratchet encrypted"]
WM --> REC["Receipt<br/>Sent/Delivered/Read"]
WM --> FH["FileHeader<br/>filename, size, SHA-256"]
WM --> FC["FileChunk<br/>64KB encrypted chunks"]
WM --> GSK["GroupSenderKey<br/>Sender Key encrypted"]
WM --> SKD["SenderKeyDistribution<br/>Share key via 1:1 channel"]
WM --> CS["CallSignal<br/>Offer/Answer/Hangup/..."]
```
### CallSignalType
```
Offer | Answer | IceCandidate | Hangup | Reject | Ringing | Busy
```
### Transport Encoding
| Client | Path | Format |
|-----------|---------------|--------|
| CLI/TUI | WS binary | 64 hex chars (recipient fp) + raw bincode |
| CLI/TUI | HTTP POST | JSON envelope with bincode as byte array |
| Web | WS JSON | `{"to": "fingerprint", "message": [bytes]}` |
| Server↔Server | WS JSON | JSON frames over persistent federation WS |
---
## Server Architecture
### Route Map
```
Auth-Protected (bearer token required):
POST /v1/messages/send Send encrypted message
POST /v1/groups/create|join|send|leave|kick
POST /v1/alias/register|unregister|recover|renew|admin-remove
POST /v1/keys/register|replenish
POST /v1/calls/initiate|:id/end
POST /v1/groups/:name/call Group call initiation
POST /v1/devices/:id/kick Kick a device
POST /v1/devices/revoke-all Panic button
POST /v1/presence/batch Bulk online check
Public (no auth):
GET /v1/keys/:fp Fetch pre-key bundle
GET /v1/messages/poll/:fp Fetch queued messages
GET /v1/groups/:name|list|members
GET /v1/alias/resolve/:name|list|whois/:fp
GET /v1/calls/:id|active|missed
GET /v1/presence/:fp Online status
GET /v1/devices List own devices (auth)
GET /v1/wzp/relay-config WZP relay address + token
GET /v1/federation/status Federation health
GET /v1/ws/:fp WebSocket upgrade
GET /v1/friends Encrypted friend list (auth)
POST /v1/friends Save friend list (auth)
GET /v1/resolve/:address ETH/alias/fp resolution
POST /v1/bot/register Register a bot
GET /v1/bot/:token/getMe Bot identity
POST /v1/bot/:token/getUpdates Long-poll for messages
POST /v1/bot/:token/sendMessage Send message as bot
POST /v1/auth/challenge|verify|validate
Federation (HMAC-authenticated, server-to-server):
POST /v1/federation/presence Presence sync
POST /v1/federation/forward Message forwarding
```
### Message Routing
```mermaid
flowchart TD
MSG["Incoming Message<br/>for fingerprint X"] --> DEDUP{"Dedup Check<br/>(10K FIFO)"}
DEDUP -->|Duplicate| DROP["Drop"]
DEDUP -->|New| LOCAL{"push_to_client(X)<br/>Local WS?"}
LOCAL -->|Delivered| DONE["Done"]
LOCAL -->|Not local| FED{"Federation<br/>enabled?"}
FED -->|No| QUEUE["Queue in<br/>sled DB"]
FED -->|Yes| REMOTE{"X in remote<br/>presence?"}
REMOTE -->|No| QUEUE
REMOTE -->|Yes| FORWARD["HTTP POST to peer<br/>/v1/federation/forward"]
FORWARD -->|Success| DONE
FORWARD -->|Peer down| QUEUE
style DONE fill:#2d5016,color:#fff
style DROP fill:#5c1a1a,color:#fff
style QUEUE fill:#4a3a1a,color:#fff
```
### WebSocket Lifecycle
```mermaid
sequenceDiagram
participant C as Client
participant S as Server
C->>S: GET /v1/ws/:fingerprint
S->>S: Check connection cap (max 5)
S->>C: WS Upgrade
Note over S: Flush queued messages
S->>C: Binary(queued_msg_1)
S->>C: Binary(queued_msg_2)
Note over S: Flush missed calls
S->>C: Text({"type":"missed_call",...})
Note over S: Register push channel
loop Real-time
C->>S: Binary(64-hex-fp + bincode)
S->>S: Dedup + Call signal awareness
S->>S: deliver_or_queue(recipient)
end
C->>S: Close
S->>S: Cleanup stale senders
```
---
## Federation
```mermaid
graph LR
subgraph Alpha[Server Alpha]
CA[Client A + B]
end
subgraph Bravo[Server Bravo]
CC[Client C + D]
end
Alpha <-->|Persistent WS\nPresence + Forward| Bravo
```
### Configuration
Each server has a `federation.json`:
```json
{
"server_id": "alpha",
"shared_secret": "long-random-string-shared-between-both",
"peer": {
"id": "bravo",
"url": "http://10.0.0.2:7700"
}
}
```
Start with: `warzone-server --federation federation.json`
### Presence Sync
On startup each server opens a persistent WebSocket to its peer and authenticates with the shared secret. Presence updates and message forwards flow over this single connection:
```
WS /v1/federation/ws
Auth: {"type":"auth","secret":"HMAC(shared_secret)"}
Presence: {"type":"presence","fingerprints":["aabb...","ccdd..."]}
Forward: {"type":"forward","to":"<fp>","message":"<base64>"}
```
The receiving server replaces its remote presence set on each presence frame. If the WebSocket drops, the server auto-reconnects every 3 seconds and re-sends its full presence list.
### Message Forwarding
```mermaid
sequenceDiagram
participant SA as Server Alpha
participant SB as Server Bravo
Note over SA,SB: Persistent WS connection
SA->>SB: {"type":"auth","secret":"..."}
SA->>SB: {"type":"presence","fingerprints":["A","B"]}
SB->>SA: {"type":"presence","fingerprints":["C","D"]}
Note over SA: Client A sends message to C
SA->>SB: {"type":"forward","to":"C","message":"base64..."}
Note over SB: Deliver to Client C via local WS
```
### Degradation
| Scenario | Behavior |
|----------|----------|
| WS disconnected | Auto-reconnect every 3s, messages queue locally |
| Peer restarts | Presence repopulates on WS reconnect |
| HMAC mismatch | Request rejected with 401 |
### Federated Features
| Feature | How it works |
|---------|-------------|
| Message forwarding | deliver_or_queue() checks remote presence, forwards via WS |
| Key lookup | get_bundle() proxies to peer if fingerprint is not local |
| Alias resolution | resolve_alias() falls back to peer server |
| ETH resolution | resolve endpoint checks peer via HTTP |
| Presence | Bidirectional sync every 10s + on-connect |
---
## Call Infrastructure (WZP Integration)
```mermaid
sequenceDiagram
participant Caller as Caller (TUI)
participant FC as featherChat Server
participant WZP as WZP Relay
Caller->>FC: WireMessage::CallSignal(Offer)
FC->>FC: Create CallState(Ringing)
FC->>FC: push_to_client(callee)
alt Callee online
FC-->>Callee: CallSignal(Offer) via WS
Callee->>FC: CallSignal(Answer)
FC->>FC: Update CallState(Active)
Note over Caller,WZP: Both connect to WZP Relay with bearer token
Caller->>WZP: QUIC + AuthToken + Handshake
Callee->>WZP: QUIC + AuthToken + Handshake
Note over WZP: Encrypted media flows (ChaCha20-Poly1305)
else Callee offline
FC->>FC: Record missed call in sled
Note over FC: Flushed on callee's next WS connect
end
Caller->>FC: CallSignal(Hangup)
FC->>FC: Update CallState(Ended)
```
### Server Endpoints
| Endpoint | Purpose |
|----------|---------|
| `POST /v1/calls/initiate` | Create call (returns call_id) |
| `GET /v1/calls/:id` | Get call state |
| `POST /v1/calls/:id/end` | End a call |
| `GET /v1/calls/active` | List active calls |
| `POST /v1/calls/missed` | Get & clear missed calls |
| `POST /v1/groups/:name/call` | Group call (fan-out to members) |
| `GET /v1/presence/:fp` | Check if peer is online |
| `GET /v1/wzp/relay-config` | Get relay address + service token |
### Group Call Room ID
```
room_id = hex(SHA-256("featherchat-group:" + group_name)[:16])
```
Deterministic, 32 hex chars. Prevents leaking group name to relay via QUIC SNI.
---
## Device Management
```mermaid
flowchart LR
USER["User with<br/>3 devices"] --> LIST["GET /v1/devices<br/>(lists all sessions)"]
USER --> KICK["POST /v1/devices/:id/kick<br/>(force-close one)"]
USER --> REVOKE["POST /v1/devices/revoke-all<br/>(nuke all except current)"]
KICK --> CLOSE["WS channel closed<br/>+ token invalidated"]
REVOKE --> NUKE["All WS closed<br/>+ all tokens cleared"]
```
- Max 5 WS connections per fingerprint
- Stale connections auto-cleaned on new registrations
- `/devices` and `/kick <id>` available as TUI commands
---
## Bot API (Telegram-Compatible)
```mermaid
sequenceDiagram
participant Dev as Bot Developer
participant S as featherChat Server
participant U as User
Dev->>S: POST /v1/bot/register {name, fp}
S->>Dev: {token, alias: "@mybot_bot"}
loop Long-poll
Dev->>S: POST /bot/:token/getUpdates
S->>Dev: [updates...]
end
U->>S: Message to @mybot_bot
S->>S: Queue for bot fp
Dev->>S: getUpdates → receives message
Dev->>S: POST /bot/:token/sendMessage
S->>U: Deliver reply via WS
```
- Bots register with a fingerprint and get a token
- Bot aliases must end with `Bot`, `bot`, or `_bot` (enforced)
- Non-bot users cannot register reserved aliases
- `getUpdates` returns Telegram-compatible Update objects
- `sendMessage` delivers plaintext (no E2E in v1)
- Messages from users arrive as encrypted blobs (base64) or plaintext bot messages
### Addressing
Three address formats, all interchangeable:
| Format | Example | Usage |
|--------|---------|-------|
| Fingerprint | `522d:4d6e:a8ee:588a:...` | Internal routing, crypto |
| ETH address | `0x742d35Cc6634C0532...` | User-facing display |
| Alias | `@alice`, `@weatherbot` | Human-friendly |
Resolution: `GET /v1/resolve/:address` accepts any format, returns fingerprint.
ETH↔fingerprint mapping stored on key registration.
---
## Security Model
### What's Protected
| Layer | Protection | Mechanism |
|-------|-----------|-----------|
| Message content | E2E encrypted | ChaCha20-Poly1305 via Double Ratchet |
| Forward secrecy | Per-message keys | DH ratchet step on direction change |
| Session establishment | Authenticated | X3DH with signed pre-keys |
| Identity | Deterministic from seed | HKDF with domain separation |
| Seed at rest | Encrypted | Argon2id passphrase KDF |
| API writes | Auth-gated | Bearer token middleware (401) |
| Inter-server | Authenticated | SHA-256(secret \|\| body) token |
| WS connections | Rate-limited | 5 per fingerprint, 200 global |
| WZP relay | Token-gated | featherChat bearer token validation |
### What's NOT Protected (Phase 1 scope)
| Data | Exposure |
|------|----------|
| Sender/recipient metadata | Server sees routing info |
| Message timing | Server sees timestamps |
| Online/offline status | Server tracks WS connections |
| Group membership | Server stores plaintext member list |
| IP addresses | Server logs (standard for HTTP) |
Planned mitigations: sealed sender (Phase 6), onion routing, metadata encryption.
### Trust Boundaries
```mermaid
graph TB
subgraph TRUSTED["Trusted: Your Device"]
SEED["Seed in memory"]
LDB["Local sled DB"]
end
subgraph SEMI["Semi-Trusted: Server"]
SRVR["Sees metadata<br/>Can't read messages"]
end
subgraph UNTRUSTED["Untrusted: Network"]
NET["TLS protects transport"]
end
TRUSTED -->|"E2E encrypted + TLS"| SEMI
SEMI -->|"TLS"| UNTRUSTED
```
---
## Storage Model
### Server sled Trees (9)
| Tree | Key Format | Value |
|----------------|---------------------------|--------------------------|
| `keys` | `<fingerprint>` | bincode PreKeyBundle |
| `messages` | `queue:<fp>:<uuid>` | raw bincode WireMessage |
| `groups` | `<group_name>` | JSON GroupInfo |
| `aliases` | `a:<alias>`, `fp:<fp>`, `rec:<alias>` | Various |
| `tokens` | `<token_hex>` | JSON {fp, expires_at} |
| `calls` | `<call_id>` | JSON CallState |
| `missed_calls` | `missed:<fp>:<call_id>` | JSON {caller, timestamp} |
| `friends` | `<fingerprint>` | Encrypted blob (ChaCha20) |
| `eth_addresses` | `0x...` or `rev:<fp>` | ETH↔fingerprint mapping |
### Client sled Trees (5)
| Tree | Key Format | Value |
|----------------|---------------------------|--------------------------|
| `sessions` | `<peer_fp_hex>` | bincode RatchetState |
| `pre_keys` | `spk:<id>`, `otpk:<id>` | 32-byte StaticSecret |
| `contacts` | `<fingerprint>` | JSON contact record |
| `history` | `hist:<fp>:<ts>:<uuid>` | JSON message record |
| `sender_keys` | `sk:<fp>:<group>` | bincode SenderKey |
---
## Test Coverage
| Crate | Tests | Coverage |
|-------|------:|---------|
| warzone-protocol | 34 | X3DH, Double Ratchet, Sender Keys, AEAD, HKDF, identity, ethereum, prekeys, mnemonic, friend list, x3dh web client |
| warzone-client (types) | 10 | App init, scroll, connected, timestamps, normfp |
| warzone-client (input) | 25 | Text editing, cursor movement, scroll keys, quit |
| warzone-client (draw) | 9 | Rendering, timestamps, connection dot, scroll, unread badge |
| **Total** | **122** | All passing |
WZP side: 15 cross-project identity tests + 17 integration tests (separate repo).
---
## Data Flow Diagrams
### 1:1 Direct Message (First Contact)
```mermaid
sequenceDiagram
participant A as Alice
participant S as Server
participant B as Bob
A->>S: GET /v1/keys/:bob_fp
S->>A: PreKeyBundle (bincode)
Note over A: X3DH initiate(bundle)<br/>Double Ratchet init_alice()<br/>ratchet.encrypt("hello")
A->>S: WireMessage::KeyExchange
S->>B: Push via WS (or queue)
Note over B: X3DH respond(spk_secret)<br/>init_bob()<br/>ratchet.decrypt() = "hello"
B->>S: WireMessage::Receipt(Delivered)
S->>A: Push receipt
```
### Group Message (Sender Keys)
```mermaid
sequenceDiagram
participant A as Alice
participant S as Server
participant B as Bob
participant C as Carol
Note over A: SenderKey::generate("ops")
A->>S: SenderKeyDistribution (via 1:1 to Bob)
S->>B: Push distribution
A->>S: SenderKeyDistribution (via 1:1 to Carol)
S->>C: Push distribution
Note over A: sender_key.encrypt("attack")
A->>S: POST /groups/ops/send (GroupSenderKey)
S->>B: Fan-out
S->>C: Fan-out
Note over B,C: sender_key.decrypt() = "attack"
```
### Federated Message
```mermaid
sequenceDiagram
participant A as Client A (Alpha)
participant SA as Server Alpha
participant SB as Server Bravo
participant C as Client C (Bravo)
Note over SA,SB: Persistent WS between servers
SA->>SB: presence ["A","B"]
SB->>SA: presence ["C","D"]
A->>SA: Message for C
SA->>SA: Not local, C in remote presence
SA->>SB: forward to C via federation WS
SB->>C: Push via local WS
```
---
## Extensibility
### Adding New WireMessage Variants
1. Add variant to `WireMessage` in `warzone-protocol/src/message.rs`
2. Update `extract_message_id()` in `routes/messages.rs` and `routes/ws.rs`
3. Handle in `tui/network.rs` (process_wire_message)
4. Handle in `warzone-wasm/src/lib.rs` (decrypt_wire_message)
5. bincode serialization is automatic
### Adding New Server Routes
1. Create module in `routes/`
2. Implement `pub fn routes() -> Router<AppState>`
3. Merge in `routes/mod.rs`
4. Add `_auth: AuthFingerprint` for write endpoints
### Adding Federation Peers (Future)
Current: 1 peer via JSON config. Future: N peers via config array or DNS discovery. The `deliver_or_queue()` method would iterate over peers checking remote presence.