Files
featherChat/warzone/docs/ARCHITECTURE.md
Siavash Sameni 3e0889e5dc v0.0.21: TUI overhaul, WZP call infrastructure, security hardening, federation
TUI:
- Split 1,756-line app.rs monolith into 7 modules (types, draw, commands, input, file_transfer, network, mod)
- Message timestamps [HH:MM], scrolling (PageUp/Down/arrows), connection status dot, unread badge
- /help command, terminal bell on incoming DM, /devices + /kick commands
- 44 unit tests (types, input, draw with TestBackend)

Server — WZP Call Infrastructure (FC-2/3/5/6/7/10):
- Call state management (CallState, CallStatus, active_calls, calls + missed_calls sled trees)
- WS call signal awareness (Offer/Answer/Hangup update state, missed call on offline)
- Group call endpoint (POST /groups/:name/call with SHA-256 room ID, fan-out)
- Presence API (GET /presence/:fp, POST /presence/batch)
- Missed call flush on WS reconnect
- WZP relay config + CORS

Server — Security (FC-P1):
- Auth enforcement middleware (AuthFingerprint extractor on 13 write handlers)
- Session auto-recovery (delete corrupted ratchet, show [session reset])
- WS connection cap (5/fingerprint) + global concurrency limit (200)
- Device management (GET /devices, POST /devices/:id/kick, POST /devices/revoke-all)

Server — Federation:
- Two-server federation via JSON config (--federation flag)
- Periodic presence sync (every 5s, full-state, self-healing)
- Message forwarding via HTTP POST with SHA-256(secret||body) auth
- Graceful degradation (peer down = queue locally)
- deliver_or_queue() replaces push-or-queue in ws.rs + messages.rs

Client — Group Messaging:
- SenderKeyDistribution storage + GroupSenderKey decryption in TUI
- sender_keys sled tree in LocalDb

WASM:
- All 8 WireMessage variants handled (no more "unsupported")
- decrypt_group_message() + create_sender_key_from_distribution() exports
- CallSignal parsing with signal_type mapping

Docs:
- ARCHITECTURE.md rewritten with Mermaid diagrams
- README.md created
- TASK_PLAN.md with FC-P{phase}-T{task} naming
- PROGRESS.md updated to v0.0.21

WZP submodule updated to 6f4e8eb (IAX2 trunking, adaptive quality, metrics, all S-tasks done)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 16:45:58 +04:00

22 KiB

Warzone Messenger (featherChat) — Architecture

Version: 0.0.21 Status: Phase 1 + Phase 2 + WZP Integration + Federation


High-Level Architecture

graph TB
    subgraph Clients
        CLI["CLI Client<br/>(warzone)"]
        TUI["TUI Client<br/>(ratatui)"]
        WEB["Web Client<br/>(WASM)"]
    end

    subgraph Protocol["warzone-protocol (shared library)"]
        ID["Identity<br/>Ed25519 + X25519"]
        X3DH["X3DH<br/>Key Agreement"]
        DR["Double Ratchet<br/>Forward Secrecy"]
        SK["Sender Keys<br/>Group Encryption"]
        WIRE["WireMessage<br/>8 variants"]
    end

    subgraph ServerA["warzone-server (Alpha)"]
        API_A["REST API<br/>(axum)"]
        WS_A["WebSocket<br/>Relay"]
        AUTH_A["Auth<br/>Middleware"]
        CALLS_A["Call State<br/>Manager"]
        FED_A["Federation<br/>Module"]
        DB_A["sled DB<br/>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<br/>(QUIC SFU)"]
        BRIDGE["Web Bridge<br/>(audio)"]
    end

    CLI --> Protocol
    TUI --> Protocol
    WEB --> Protocol
    Protocol -->|"HTTP / WS"| ServerA
    Protocol -->|"HTTP / WS"| ServerB

    FED_A <-->|"HTTP REST<br/>HMAC-SHA256"| FED_B

    ServerA -->|"Call Signaling<br/>Token Validation"| WZP
    ServerB -->|"Call Signaling"| WZP

Crate Structure

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

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

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

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

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

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

graph LR
    subgraph ServerAlpha["Server Alpha"]
        CA["Client A<br/>Client B"]
        FHA["Federation Handle"]
    end

    subgraph ServerBravo["Server Bravo"]
        CC["Client C<br/>Client D"]
        FHB["Federation Handle"]
    end

    FHA <-->|"Presence sync<br/>(every 5s)"| FHB
    FHA -->|"Forward message<br/>(HTTP POST)"| FHB
    FHB -->|"Forward message<br/>(HTTP POST)"| FHA

Configuration

Each server has a federation.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

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<br/>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)

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

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

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

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 (7)

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}

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 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)

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)

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

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