# Warzone Messenger (featherChat) — Architecture **Version:** 0.0.20 **Status:** Phase 1 complete, Phase 2 complete --- ## High-Level Architecture ``` ┌──────────────────────────────────────────────────────────────────┐ │ Clients │ │ │ │ ┌──────────────┐ ┌──────────────┐ ┌───────────────────────┐ │ │ │ CLI Client │ │ TUI Client │ │ Web Client (WASM) │ │ │ │ (warzone) │ │ (ratatui) │ │ (wasm-bindgen) │ │ │ └──────┬───────┘ └──────┬───────┘ └───────────┬───────────┘ │ │ │ │ │ │ │ ┌──────┴─────────────────┴───────────────────────┴──────────┐ │ │ │ warzone-protocol │ │ │ │ Identity · X3DH · Double Ratchet · Sender Keys · History │ │ │ └───────────────────────────┬───────────────────────────────┘ │ └──────────────────────────────┼──────────────────────────────────┘ │ HTTP / WebSocket ▼ ┌──────────────────────────────────────────────────────────────────┐ │ warzone-server │ │ │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌─────────────────┐ │ │ │ HTTP API │ │ WebSocket│ │ Auth │ │ Message Router │ │ │ │ (axum) │ │ Relay │ │Challenge │ │ + Dedup │ │ │ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────────┬────────┘ │ │ │ │ │ │ │ │ ┌────┴─────────────┴─────────────┴──────────────────┴────────┐ │ │ │ sled Database │ │ │ │ keys · messages · groups · aliases · tokens │ │ │ └─────────────────────────────────────────────────────────────┘ │ └──────────────────────────────────────────────────────────────────┘ ``` --- ## Crate Structure The project is a Cargo workspace with five crates: ``` warzone/ ├── Cargo.toml # Workspace root (v0.0.20) ├── crates/ │ ├── warzone-protocol/ # Core crypto & message types (library) │ ├── warzone-server/ # Server binary (axum + sled) │ ├── warzone-client/ # CLI/TUI client binary │ ├── warzone-wasm/ # WASM bridge for web client │ └── warzone-mule/ # Mule binary (future) └── docs/ ``` ### warzone-protocol The protocol crate is the heart of the system. It is a pure library with zero I/O dependencies, used by all other crates (including WASM). | Module | Purpose | |---------------|------------------------------------------------------| | `identity` | Seed, IdentityKeyPair, PublicIdentity, Fingerprint | | `mnemonic` | BIP39 mnemonic encode/decode | | `crypto` | HKDF-SHA256, ChaCha20-Poly1305 AEAD | | `prekey` | SignedPreKey, OneTimePreKey, PreKeyBundle | | `x3dh` | X3DH key agreement (initiate + respond) | | `ratchet` | Double Ratchet state machine | | `message` | WireMessage enum, MessageContent, ReceiptType | | `session` | Session management types | | `store` | Storage trait definitions | | `sender_keys` | Sender Key protocol for groups | | `history` | Encrypted backup/restore (HKDF → ChaCha20) | | `ethereum` | secp256k1 identity, Ethereum address derivation | | `types` | Fingerprint, DeviceId, SessionId, MessageId | | `errors` | ProtocolError enum | ### warzone-server An axum HTTP + WebSocket server with sled embedded database. | Module | Purpose | |------------------|--------------------------------------------| | `main` | CLI args, server startup (default :7700) | | `state` | AppState, Connections map, DedupTracker | | `db` | Database struct (5 sled trees) | | `routes/keys` | Pre-key bundle registration & retrieval | | `routes/messages`| Send, poll (fetch-and-delete), ack | | `routes/groups` | Create, join, leave, kick, members, list | | `routes/aliases` | Register, resolve, recover, renew, admin | | `routes/auth` | Challenge-response authentication | | `routes/ws` | WebSocket real-time message delivery | | `routes/web` | Static file serving for web client | | `routes/health` | Health check endpoint | ### warzone-client CLI and TUI client with local sled database for sessions, contacts, and history. | Module | Purpose | |-------------|------------------------------------------------| | `main` | CLI parser (clap): init, recover, info, etc. | | `keystore` | Seed encryption at rest (Argon2id + ChaCha20) | | `storage` | LocalDb: sessions, pre_keys, contacts, history | | `net` | HTTP + WebSocket client | | `tui/app` | TUI application (ratatui + crossterm) | | `cli/*` | CLI subcommand handlers | ### warzone-wasm WASM bridge exposing protocol functions to JavaScript via `wasm-bindgen`. | Export | Purpose | |-------------------------|--------------------------------------------| | `WasmIdentity` | Seed generation, fingerprint, bundle | | `WasmSession` | Encrypt/decrypt with Double Ratchet | | `create_receipt` | Build receipt WireMessages | | `decrypt_wire_message` | Full message decryption pipeline | | `self_test` | End-to-end crypto verification in WASM | | `debug_bundle_info` | Bundle introspection for debugging | ### warzone-mule (future) Placeholder for the mule binary — physical message relay for disconnected networks. --- ## 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 | | SHA-256 | `sha2` | Fingerprint computation | | Argon2id | `argon2` | Passphrase-based key derivation | | secp256k1 ECDSA | `k256` | Ethereum-compatible signing | | Keccak-256 | `tiny-keccak` | Ethereum address derivation | ### Protocol Stack ``` Application (plaintext) │ ▼ Double Ratchet (per-message keys, forward secrecy) │ ▼ X3DH (session establishment, 3-4 DH operations) │ ▼ ChaCha20-Poly1305 (authenticated encryption) │ ▼ Ed25519 (message signing + pre-key signing) │ ▼ WireMessage (bincode serialization) │ ▼ Transport (HTTP POST / WebSocket binary frame) ``` --- ## Dual-Curve Identity Model Warzone derives two separate cryptographic identities from a single BIP39 seed: ``` BIP39 Seed (32 bytes, 24 words) │ ├─── HKDF(info="warzone-ed25519") ──→ Ed25519 signing keypair │ │ │ └─→ SHA-256[:16] = Fingerprint │ ├─── HKDF(info="warzone-x25519") ──→ X25519 encryption keypair │ (used in X3DH + Double Ratchet) │ └─── HKDF(info="warzone-secp256k1") ──→ secp256k1 keypair │ └─→ Keccak-256[-20:] = Ethereum Address ``` **Messaging identity:** Ed25519 (signing) + X25519 (encryption). The fingerprint is `SHA-256(Ed25519_pubkey)[:16]`, displayed as `xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx`. **Ethereum identity:** secp256k1 keypair derived from the same seed with domain-separated HKDF. The Ethereum address is derived using standard `Keccak-256(uncompressed_pubkey[1:])[-20:]`. EIP-55 checksummed display is supported. This allows a single mnemonic to control both a Warzone messaging identity and an Ethereum wallet address. --- ## Wire Protocol All messages between clients use the `WireMessage` enum, serialized with bincode: ```rust enum WireMessage { // Session establishment (X3DH + first ratchet message) KeyExchange { id, sender_fingerprint, sender_identity_encryption_key, ephemeral_public, used_one_time_pre_key_id, ratchet_message, }, // Subsequent DM messages (Double Ratchet encrypted) Message { id, sender_fingerprint, ratchet_message }, // Delivery / read receipts (plaintext metadata) Receipt { sender_fingerprint, message_id, receipt_type }, // File transfer: header announces the file FileHeader { id, sender_fingerprint, filename, file_size, total_chunks, sha256 }, // File transfer: individual chunk (encrypted data) FileChunk { id, sender_fingerprint, filename, chunk_index, total_chunks, data }, // Group message encrypted with Sender Key (O(1) encryption) GroupSenderKey { id, sender_fingerprint, group_name, generation, counter, ciphertext }, // Sender Key distribution (sent via 1:1 encrypted channel) SenderKeyDistribution { sender_fingerprint, group_name, chain_key, generation }, } ``` ### Transport Encoding - **CLI ↔ Server (HTTP):** JSON envelope with bincode message as byte array - **CLI ↔ Server (WebSocket binary):** 64 hex chars (recipient fingerprint) + raw bincode bytes - **Web ↔ Server (WebSocket JSON):** `{"to": "fingerprint", "message": [byte_array]}` --- ## Server Architecture ### Framework - **axum 0.7** with tokio async runtime - **sled 0.34** embedded database (zero-config, no external DB) - **tower-http** for CORS and tracing middleware ### Route Structure All API endpoints live under `/v1`: ``` POST /v1/keys/register Register pre-key bundle GET /v1/keys/:fingerprint Fetch pre-key bundle POST /v1/keys/replenish Upload additional OTPKs GET /v1/keys/:fp/otpk-count Check remaining OTPKs GET /v1/keys/:fp/devices List registered devices GET /v1/keys/list List all registered fingerprints POST /v1/messages/send Send encrypted message GET /v1/messages/poll/:fp Fetch-and-delete queued messages DELETE /v1/messages/:id/ack Explicit message acknowledgment POST /v1/groups/create Create a group GET /v1/groups List all groups GET /v1/groups/:name Get group info POST /v1/groups/:name/join Join a group POST /v1/groups/:name/send Fan-out group message POST /v1/groups/:name/leave Leave a group POST /v1/groups/:name/kick Kick a member (creator only) GET /v1/groups/:name/members List members with aliases POST /v1/alias/register Register an alias POST /v1/alias/recover Recover alias with recovery key POST /v1/alias/renew Heartbeat / renew TTL GET /v1/alias/resolve/:name Resolve alias → fingerprint GET /v1/alias/whois/:fp Reverse lookup fingerprint → alias GET /v1/alias/list List all aliases POST /v1/alias/unregister Remove your own alias POST /v1/alias/admin-remove Admin: remove any alias POST /v1/auth/challenge Request a challenge POST /v1/auth/verify Verify signature, get bearer token GET /v1/ws/:fingerprint WebSocket upgrade for real-time push GET / Web client static files GET /health Health check ``` ### Message Routing ``` Incoming message │ ├─→ Dedup check (bounded FIFO, 10,000 IDs) │ │ │ ├─→ Duplicate? → silently drop │ └─→ New? → continue │ ├─→ Try WebSocket push to recipient │ │ │ ├─→ Connected? → instant delivery │ └─→ Offline? → queue in sled DB │ └─→ Renew sender's alias TTL ``` ### WebSocket Protocol 1. Client connects to `/v1/ws/:fingerprint` 2. Server flushes any queued messages from DB 3. Server registers a push channel for this connection 4. Incoming WS messages are routed to recipient's WS or queued 5. Multiple devices per fingerprint are supported (all receive a copy) 6. On disconnect, stale senders are cleaned up ### Dedup Tracker The `DedupTracker` prevents message duplication across HTTP and WebSocket delivery paths: - Bounded to 10,000 message IDs - FIFO eviction when capacity is exceeded - Uses `HashSet` for O(1) lookup + `VecDeque` for ordering - Thread-safe via `std::sync::Mutex` --- ## Web Client Architecture (WASM) The web client runs the **exact same crypto** as the CLI through the `warzone-wasm` crate: ``` ┌─────────────────────────────────────────────────────┐ │ Browser │ │ │ │ ┌──────────────────┐ ┌──────────────────────────┐ │ │ │ JavaScript UI │ │ warzone-wasm (WASM) │ │ │ │ (chat.html) │──│ WasmIdentity │ │ │ │ │ │ WasmSession │ │ │ │ WebSocket ◄─────┤ │ decrypt_wire_message() │ │ │ │ localStorage │ │ create_receipt() │ │ │ │ │ │ self_test() │ │ │ └──────────────────┘ └──────────────────────────┘ │ │ │ │ Storage: │ │ localStorage: seed_hex, spk_secret_hex │ │ localStorage: session: (base64 ratchet state) │ └─────────────────────────────────────────────────────┘ ``` Key design decisions: - **Web Crypto** provides cryptographic randomness (`OsRng` in WASM maps to `crypto.getRandomValues`) - **No OTPKs** in the web client — cannot reliably store secrets. X3DH works without DH4 (OTPKs are an anti-replay optimization, not a security requirement) - **SPK secret** stored in localStorage as hex, restored on page load - **Session state** serialized as base64 bincode, stored per-peer in localStorage - **bincode** wire format ensures CLI ↔ Web interoperability --- ## Data Flow Diagrams ### 1:1 Direct Message (First Message) ``` Alice Server Bob │ │ │ │ GET /v1/keys/:bob_fp │ │ │─────────────────────────────→│ │ │ ← PreKeyBundle (bincode) │ │ │ │ │ │ X3DH initiate(bundle) │ │ │ → shared_secret │ │ │ Double Ratchet init_alice() │ │ │ ratchet.encrypt("hello") │ │ │ │ │ │ WireMessage::KeyExchange │ │ │ POST /v1/messages/send │ │ │─────────────────────────────→│ push via WS (or queue) │ │ │─────────────────────────────→│ │ │ │ │ │ X3DH respond(spk_secret) │ │ │ → shared_secret │ │ │ init_bob() │ │ │ ratchet.decrypt() │ │ │ → "hello" │ ``` ### 1:1 Direct Message (Subsequent) ``` Alice Server Bob │ │ │ │ ratchet.encrypt("hi again") │ │ │ WireMessage::Message │ │ │ ─── WS binary ─────────────→│ ─── WS push ───────────────→│ │ │ │ │ │ ratchet.decrypt()│ │ │ → "hi again" │ │ │ │ │ │ WireMessage::Receipt │ │ │←──────────────────────────── │ │←─────────────────────────────│ │ │ ✓✓ delivered │ │ ``` ### Group Message (Sender Keys) ``` Alice Server Bob, Carol │ │ │ │ SenderKey::generate("ops") │ │ │ Distribute via 1:1 channels: │ │ │ encrypt(SK_dist, Bob) │ │ │ encrypt(SK_dist, Carol) │ │ │ ─────────────────────────────→│──── push to Bob,Carol ──→│ │ │ │ │ sender_key.encrypt("attack") │ │ │ WireMessage::GroupSenderKey │ │ │ POST /groups/ops/send │ │ │ ─────────────────────────────→│──── fan-out ────────────→│ │ │ │ │ │ bob_copy.decrypt() │ │ │ carol_copy.decrypt() │ │ │ → "attack" │ ``` ### File Transfer ``` Sender Server Recipient │ │ │ │ Read file, compute SHA-256 │ │ │ Split into 64KB chunks │ │ │ │ │ │ WireMessage::FileHeader │ │ │ ─────────────────────────────→│──── push ────────────────→│ │ │ │ │ WireMessage::FileChunk[0] │ │ │ ─────────────────────────────→│──── push ────────────────→│ │ WireMessage::FileChunk[1] │ │ │ ─────────────────────────────→│──── push ────────────────→│ │ ... │ │ │ WireMessage::FileChunk[N-1] │ │ │ ─────────────────────────────→│──── push ────────────────→│ │ │ │ │ │ Reassemble chunks │ │ │ Verify SHA-256 │ │ │ Save to downloads/ │ ``` File constraints: max 10 MB, 64 KB chunks. --- ## Storage Model ### Server sled Trees | Tree | Key Format | Value | Purpose | |------------|-------------------------------|--------------------------|------------------------------| | `keys` | `` | bincode PreKeyBundle | Identity + pre-key storage | | `keys` | `device::` | bincode PreKeyBundle | Per-device bundles | | `keys` | `otpk::` | hex pubkey | One-time pre-keys | | `messages` | `queue::` | raw bincode WireMessage | Offline message queue | | `groups` | `` | JSON GroupInfo | Group membership | | `aliases` | `a:` | fingerprint string | Forward lookup | | `aliases` | `fp:` | alias string | Reverse lookup | | `aliases` | `rec:` | JSON AliasRecord | Full record (TTL, recovery) | | `tokens` | `` | JSON {fp, expires_at} | Auth bearer tokens | ### Client sled Trees | Tree | Key Format | Value | Purpose | |-------------|-------------------------------|--------------------------|----------------------------| | `sessions` | `` | bincode RatchetState | Double Ratchet sessions | | `pre_keys` | `spk:` | 32-byte StaticSecret | Signed pre-key secrets | | `pre_keys` | `otpk:` | 32-byte StaticSecret | One-time pre-key secrets | | `contacts` | `` | JSON contact record | Contact list | | `history` | `hist:::` | JSON message record | Message history | ### Client Filesystem ``` ~/.warzone/ ├── identity.seed # WZS1 magic + salt(16) + nonce(12) + ciphertext(48) │ # or plain 32 bytes (legacy/testing) └── db/ # sled database directory ├── conf ├── db └── ... ``` The `WARZONE_HOME` environment variable overrides `~/.warzone`. --- ## Extensibility Points ### Adding New WireMessage Variants 1. Add a new variant to `WireMessage` in `warzone-protocol/src/message.rs` 2. Update `extract_message_id()` in both `routes/messages.rs` and `routes/ws.rs` 3. Handle in the TUI poll loop (`tui/app.rs`) 4. Handle in `decrypt_wire_message()` in `warzone-wasm/src/lib.rs` 5. bincode serialization is automatic (enum tag + fields) ### Adding New Transport Traits (future) The design document specifies a `Transport` trait: ```rust trait Transport { async fn send(&self, endpoint: &str, blob: &[u8]) -> Result<()>; async fn recv(&self) -> Result>; } ``` Current transports: HTTPS (reqwest) and WebSocket (axum/tungstenite). Planned: Bluetooth, LoRa, Wi-Fi Direct, USB/file. ### Adding New Storage Backends Client storage currently uses sled directly. The pattern for abstraction: - `LocalDb` in `storage.rs` already provides a clean API - Replace sled calls with trait methods for alternative backends (SQLite, IndexedDB, etc.) - Server's `Database` in `db.rs` follows the same pattern ### Adding New Server Routes 1. Create a module in `routes/` 2. Implement `pub fn routes() -> Router` 3. Merge into the router in `routes/mod.rs` 4. Access shared state via `State(state): State`