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

24 KiB

Warzone Messenger (featherChat) — Architecture

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


High-Level Architecture

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

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

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

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

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

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)

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

Bot API (Telegram-Compatible)

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

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)

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