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

532 lines
26 KiB
Markdown

# 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:<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:
```rust
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>`