# 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.