# Warzone Messenger (featherChat) — Architecture **Version:** 0.0.21 **Status:** Phase 1 + Phase 2 + WZP Integration + Federation --- ## High-Level Architecture ```mermaid graph TB subgraph Clients CLI["CLI Client
(warzone)"] TUI["TUI Client
(ratatui)"] WEB["Web Client
(WASM)"] end subgraph Protocol["warzone-protocol (shared library)"] ID["Identity
Ed25519 + X25519"] X3DH["X3DH
Key Agreement"] DR["Double Ratchet
Forward Secrecy"] SK["Sender Keys
Group Encryption"] WIRE["WireMessage
8 variants"] end subgraph ServerA["warzone-server (Alpha)"] API_A["REST API
(axum)"] WS_A["WebSocket
Relay"] AUTH_A["Auth
Middleware"] CALLS_A["Call State
Manager"] FED_A["Federation
Module"] DB_A["sled DB
7 trees"] end subgraph ServerB["warzone-server (Bravo)"] API_B["REST API"] WS_B["WebSocket Relay"] FED_B["Federation Module"] DB_B["sled DB"] end subgraph WZP["WarzonePhone"] RELAY["WZP Relay
(QUIC SFU)"] BRIDGE["Web Bridge
(audio)"] end CLI --> Protocol TUI --> Protocol WEB --> Protocol Protocol -->|"HTTP / WS"| ServerA Protocol -->|"HTTP / WS"| ServerB FED_A <-->|"HTTP REST
HMAC-SHA256"| FED_B ServerA -->|"Call Signaling
Token Validation"| WZP ServerB -->|"Call Signaling"| WZP ``` --- ## Crate Structure ```mermaid graph LR subgraph Workspace PROTO["warzone-protocol
(library, no I/O)"] SERVER["warzone-server
(axum binary)"] CLIENT["warzone-client
(CLI/TUI binary)"] WASM["warzone-wasm
(wasm-bindgen)"] MULE["warzone-mule
(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 | | `types` | Fingerprint, DeviceId, SessionId, MessageId | ### warzone-server | Module | Purpose | |----------------------|---------------------------------------------------| | `main` | CLI args, startup, federation init | | `state` | AppState, Connections, CallState, DedupTracker | | `db` | 7 sled trees: keys, messages, groups, aliases, tokens, calls, missed_calls | | `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 | ### 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
(per-message keys)"] DR --> X3DH_INIT["X3DH Session Init
(3-4 DH operations)"] X3DH_INIT --> AEAD["ChaCha20-Poly1305
(AEAD encryption)"] AEAD --> SIGN["Ed25519 Signature
(pre-key signing)"] SIGN --> WIRE["WireMessage
(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
(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
xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx"] SECP -->|"Keccak-256[-20:]"| ETH["Ethereum Address
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
X3DH + first ratchet msg"] WM --> MSG["Message
Double Ratchet encrypted"] WM --> REC["Receipt
Sent/Delivered/Read"] WM --> FH["FileHeader
filename, size, SHA-256"] WM --> FC["FileChunk
64KB encrypted chunks"] WM --> GSK["GroupSenderKey
Sender Key encrypted"] WM --> SKD["SenderKeyDistribution
Share key via 1:1 channel"] WM --> CS["CallSignal
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 | HTTP POST | JSON with base64 message + HMAC auth header | --- ## 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 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
for fingerprint X"] --> DEDUP{"Dedup Check
(10K FIFO)"} DEDUP -->|Duplicate| DROP["Drop"] DEDUP -->|New| LOCAL{"push_to_client(X)
Local WS?"} LOCAL -->|Delivered| DONE["Done"] LOCAL -->|Not local| FED{"Federation
enabled?"} FED -->|No| QUEUE["Queue in
sled DB"] FED -->|Yes| REMOTE{"X in remote
presence?"} REMOTE -->|No| QUEUE REMOTE -->|Yes| FORWARD["HTTP POST to peer
/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 ServerAlpha["Server Alpha"] CA["Client A
Client B"] FHA["Federation Handle"] end subgraph ServerBravo["Server Bravo"] CC["Client C
Client D"] FHB["Federation Handle"] end FHA <-->|"Presence sync
(every 5s)"| FHB FHA -->|"Forward message
(HTTP POST)"| FHB FHB -->|"Forward message
(HTTP POST)"| FHA ``` ### 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" }, "presence_interval_secs": 5 } ``` Start with: `warzone-server --federation federation.json` ### Presence Sync Every 5 seconds, each server POSTs its connected fingerprint list to the peer: ``` POST /v1/federation/presence X-Federation-Token: SHA-256(secret || body) { "server_id": "alpha", "fingerprints": ["aabb...", "ccdd..."], "timestamp": ... } ``` The receiving server replaces its remote presence set entirely. If 3 intervals pass without a sync, the remote set is cleared (peer assumed down). ### Message Forwarding ```mermaid sequenceDiagram participant A as Client A (Alpha) participant SA as Server Alpha participant SB as Server Bravo participant C as Client C (Bravo) A->>SA: Send message to C SA->>SA: push_to_client(C) — not local SA->>SA: remote_presence.contains(C) — yes SA->>SB: POST /v1/federation/forward
X-Federation-Token: HMAC SB->>SB: Verify HMAC SB->>C: push_to_client(C) via WS SB->>SA: { "delivered": true } ``` ### Degradation | Scenario | Behavior | |----------|----------| | Peer unreachable | Message queued locally, retried on next connection | | Presence stale (>15s) | Remote fingerprints cleared, treated as offline | | Peer restarts | Presence repopulates within 5 seconds | | HMAC mismatch | Request rejected with 401 | --- ## 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
3 devices"] --> LIST["GET /v1/devices
(lists all sessions)"] USER --> KICK["POST /v1/devices/:id/kick
(force-close one)"] USER --> REVOKE["POST /v1/devices/revoke-all
(nuke all except current)"] KICK --> CLOSE["WS channel closed
+ token invalidated"] REVOKE --> NUKE["All WS closed
+ all tokens cleared"] ``` - Max 5 WS connections per fingerprint - Stale connections auto-cleaned on new registrations - `/devices` and `/kick ` available as TUI commands --- ## 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
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 (7) | Tree | Key Format | Value | |----------------|---------------------------|--------------------------| | `keys` | `` | bincode PreKeyBundle | | `messages` | `queue::` | raw bincode WireMessage | | `groups` | `` | JSON GroupInfo | | `aliases` | `a:`, `fp:`, `rec:` | Various | | `tokens` | `` | JSON {fp, expires_at} | | `calls` | `` | JSON CallState | | `missed_calls` | `missed::` | JSON {caller, timestamp} | ### Client sled Trees (5) | Tree | Key Format | Value | |----------------|---------------------------|--------------------------| | `sessions` | `` | bincode RatchetState | | `pre_keys` | `spk:`, `otpk:` | 32-byte StaticSecret | | `contacts` | `` | JSON contact record | | `history` | `hist:::` | JSON message record | | `sender_keys` | `sk::` | bincode SenderKey | --- ## Test Coverage | Crate | Tests | Coverage | |-------|------:|---------| | warzone-protocol | 28 | X3DH, Double Ratchet, Sender Keys, AEAD, HKDF, identity, ethereum, prekeys, mnemonic | | 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** | **72** | 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)
Double Ratchet init_alice()
ratchet.encrypt("hello") A->>S: WireMessage::KeyExchange S->>B: Push via WS (or queue) Note over B: X3DH respond(spk_secret)
init_bob()
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: Presence sync (every 5s) SA->>SB: POST /federation/presence [A, B] SB->>SA: POST /federation/presence [C, D] A->>SA: Message for C SA->>SA: Not local, C in remote presence SA->>SB: POST /federation/forward (HMAC auth) SB->>C: Push via local WS SB->>SA: { "delivered": true } ``` --- ## 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` 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.