Warzone Messenger (featherChat) — Architecture
Version: 0.0.46
Status: Phase 1 + Phase 2 + Phase 3 + WZP Integration + Federation + Bots + Admin
Features: E2E encrypted messaging (Double Ratchet), group messaging (Sender Keys), voice calls (DM E2E + group transport-encrypted), ring tones (Web Audio API), browser call notifications, group calls (/gcall, /gjoin, /gleave-call), read receipts (sent/delivered/read indicators), markdown rendering (TUI + Web), Telegram-compatible Bot API, admin commands, federation, device management, aliases, ETH address display, file transfer, friend lists, encrypted history backup
High-Level Architecture
Crate Structure
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
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
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
CallSignalType
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
Message Routing
WebSocket Lifecycle
Federation
Configuration
Each server has a federation.json:
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:
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
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)
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) |
POST /v1/groups/:name/signal |
Broadcast call signal to group members |
GET /v1/presence/:fp |
Check if peer is online |
GET /v1/wzp/relay-config |
Get relay address + service token |
Ring Tones
- Incoming call: Web Audio API oscillator playing a 440/480 Hz dual-tone pattern (classic North American ring cadence)
- Outgoing ringback: 2 seconds on / 4 seconds off pattern until callee answers or rejects
- Browser notifications: If the web client tab is in background, an incoming call triggers a system notification so the user does not miss it
Group Calls
/gcall <group> starts a group call room; /gjoin <group> joins an existing room; /gleave-call leaves
- Group call signals are broadcast via
POST /v1/groups/:name/signal (fan-out to all online members)
- Room naming convention: DM calls use a sorted fingerprint pair as room ID; group calls use
gc-<groupname>
- Encryption: Group calls are transport-encrypted only (QUIC with TLS). They are NOT end-to-end encrypted. MLS (RFC 9420) key agreement for group call media is on the roadmap.
Group Call Room ID
Deterministic, 32 hex chars. Prevents leaking group name to relay via QUIC SNI.
Device Management
- 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)
- BotFather creates bots and issues tokens; each bot gets an auto-registered alias
- Bot aliases must end with
Bot, bot, or _bot (enforced); non-bot users cannot register reserved aliases
- Per-bot numeric ID mapping: Each user is assigned a unique numeric ID per bot, preventing cross-bot user correlation (privacy)
- Telegram-compatible endpoints:
getUpdates (long-poll), sendMessage, editMessage, sendDocument, inline keyboards
sendMessage delivers plaintext (no E2E in v1 — bot messages are not encrypted)
- Messages from users arrive as encrypted blobs (base64) or plaintext bot messages
- System bots: Configured via
--bots-config <file> on server startup; auto-created on first run
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 |
| DM calls (voice) |
E2E encrypted |
ChaCha20-Poly1305 over QUIC via WZP relay |
| Group calls (voice) |
Transport-encrypted only |
QUIC/TLS — NOT E2E (MLS on roadmap) |
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
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 |
39 |
X3DH, Double Ratchet, Sender Keys, AEAD, HKDF, identity, ethereum, prekeys, mnemonic, friend list, x3dh web client, receipts |
| warzone-client (types) |
10 |
App init, scroll, connected, timestamps, normfp |
| warzone-client (input) |
25 |
Text editing, cursor movement, scroll keys, quit |
| warzone-client (draw) |
13 |
Rendering, timestamps, connection dot, scroll, unread badge, markdown |
| warzone-server (integration) |
10 |
Route handlers, auth middleware, group ops, call state |
| warzone-server (bin) |
10 |
CLI args, startup, federation init, bot config |
| Other (e2e, misc) |
48 |
Client-side E2E flows, file transfer, admin commands |
| Total |
155 |
All passing |
WZP side: 15 cross-project identity tests + 17 integration tests (separate repo).
Data Flow Diagrams
1:1 Direct Message (First Contact)
Group Message (Sender Keys)
Federated Message
Admin Commands
| Command |
Purpose |
/admin-calls |
List all currently active calls on the server |
/admin-unalias <alias> <pw> |
Force-remove an alias (requires admin password) |
/admin-help |
Show available admin commands |
Admin commands are available in the TUI client and are authenticated server-side.
Read Receipts
- TUI: Tracks which messages are visible in the viewport and sends
Receipt::Read back to the sender when a message scrolls into view
- Web: Sender sees delivery indicators: single check mark (sent) then double check mark (delivered) then blue double check mark (read)
- Deduplication: Each message is receipted only once; the client tracks which message IDs have already been acknowledged to avoid redundant receipt traffic
Markdown Rendering
- TUI: Custom
md_to_spans parser converts markdown to ratatui Span objects supporting bold, italic, inline code, headers, blockquotes, and lists
- Web:
renderMd() function in the embedded JS handles code blocks, inline code, bold, italic, headers, links, blockquotes, and ordered/unordered lists
- Both renderers are deliberately simple (no AST) to avoid pulling in heavy markdown dependencies
Known Issues and Limitations
| Issue |
Details |
| Group call signal delivery |
Depends on members being online; there is no offline queue for call signals |
| TUI voice calls |
Require the web client; no native audio (cpal) integration yet |
| Bot messages are plaintext |
v1 limitation; bots cannot participate in E2E encryption |
/gmembers ETH resolution |
Async resolution may briefly show the raw fingerprint before the ETH address loads |
| Service worker cache staleness |
Cache version in web.rs must be bumped on every change or browsers will serve stale WASM/JS content |
Extensibility
Adding New WireMessage Variants
- Add variant to
WireMessage in warzone-protocol/src/message.rs
- Update
extract_message_id() in routes/messages.rs and routes/ws.rs
- Handle in
tui/network.rs (process_wire_message)
- Handle in
warzone-wasm/src/lib.rs (decrypt_wire_message)
- bincode serialization is automatic
Adding New Server Routes
- Create module in
routes/
- Implement
pub fn routes() -> Router<AppState>
- Merge in
routes/mod.rs
- 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.