Files
featherChat/warzone/docs/ARCHITECTURE.md
Siavash Sameni 2dbbc61dfe Comprehensive documentation: architecture, usage, integration, progress, security
docs/ARCHITECTURE.md (531 lines):
  System design, ASCII diagrams, crypto stack, dual-curve identity,
  wire protocol (7 WireMessage variants), server/client architecture,
  data flow diagrams, storage model, extensibility points

docs/USAGE.md (550 lines):
  Complete user guide: installation, all CLI commands (10),
  all TUI commands (20+), all web commands, file transfer,
  identity management, aliases, groups, multi-device, backup,
  keyboard shortcuts

docs/INTEGRATION.md (542 lines):
  WarzonePhone concept, Ethereum/Web3, OIDC, DNS federation,
  transport abstraction, multi-server mode, custom clients,
  ntfy, how-to guides for extending message types/commands/storage

docs/PROGRESS.md (234 lines):
  Timeline, Phase 1 (16 features), Phase 2 (16 features),
  v0.0.20, 28 tests, bugs fixed, known limitations, Phase 3-7 roadmap

docs/SECURITY.md (438 lines):
  Threat model, 8 crypto primitives, key derivation paths,
  forward secrecy, Sender Keys trade-offs, seed security,
  server trust, WASM security, known weaknesses,
  comparison with Signal/Matrix/SimpleX

Total: 3,751 lines across 8 doc files.

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

26 KiB

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:

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:<fp> (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 <fingerprint> bincode PreKeyBundle Identity + pre-key storage
keys device:<fp>:<device_id> bincode PreKeyBundle Per-device bundles
keys otpk:<fp>:<id> hex pubkey One-time pre-keys
messages queue:<fp>:<uuid> raw bincode WireMessage Offline message queue
groups <group_name> JSON GroupInfo Group membership
aliases a:<alias> fingerprint string Forward lookup
aliases fp:<fingerprint> alias string Reverse lookup
aliases rec:<alias> JSON AliasRecord Full record (TTL, recovery)
tokens <token_hex> JSON {fp, expires_at} Auth bearer tokens

Client sled Trees

Tree Key Format Value Purpose
sessions <peer_fp_hex> bincode RatchetState Double Ratchet sessions
pre_keys spk:<id> 32-byte StaticSecret Signed pre-key secrets
pre_keys otpk:<id> 32-byte StaticSecret One-time pre-key secrets
contacts <fingerprint> JSON contact record Contact list
history hist:<fp>:<ts>:<uuid> 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:

trait Transport {
    async fn send(&self, endpoint: &str, blob: &[u8]) -> Result<()>;
    async fn recv(&self) -> Result<Vec<u8>>;
}

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<AppState>
  3. Merge into the router in routes/mod.rs
  4. Access shared state via State(state): State<AppState>