- 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>
690 lines
24 KiB
Markdown
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.
|