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>
This commit is contained in:
531
warzone/docs/ARCHITECTURE.md
Normal file
531
warzone/docs/ARCHITECTURE.md
Normal file
@@ -0,0 +1,531 @@
|
||||
# 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>`
|
||||
542
warzone/docs/INTEGRATION.md
Normal file
542
warzone/docs/INTEGRATION.md
Normal file
@@ -0,0 +1,542 @@
|
||||
# Warzone Messenger (featherChat) — Integration & Extensibility Guide
|
||||
|
||||
**Version:** 0.0.20
|
||||
|
||||
Items marked with **(future)** are designed but not yet implemented.
|
||||
|
||||
---
|
||||
|
||||
## WarzonePhone Integration (future)
|
||||
|
||||
WarzonePhone is envisioned as a separate project for encrypted voice/video calls, sharing infrastructure with the messenger.
|
||||
|
||||
### Shared Components
|
||||
|
||||
- **Identity:** Same BIP39 seed and fingerprint. One identity for messaging + calls.
|
||||
- **Server infrastructure:** Same server hosts both message relay and SRTP/VoIP signaling.
|
||||
- **Pre-key bundles:** Reuse X3DH bundles for call setup (SRTP key exchange).
|
||||
- **Contact list:** Shared aliases and contact metadata.
|
||||
|
||||
### Voice Messages
|
||||
|
||||
Before VoIP is built, voice messages can be sent as file attachments:
|
||||
|
||||
```
|
||||
/file voice-message.opus
|
||||
```
|
||||
|
||||
The `/file` command already supports arbitrary file transfer up to 10 MB. An Opus audio file at 32 kbps allows ~40 minutes per message.
|
||||
|
||||
### Integration Pattern
|
||||
|
||||
```
|
||||
warzone-protocol (shared)
|
||||
│
|
||||
┌─────┴──────┐
|
||||
│ │
|
||||
warzone-client warzone-phone
|
||||
(messaging) (VoIP, future)
|
||||
```
|
||||
|
||||
Both binaries link against `warzone-protocol` for identity, key exchange, and encryption.
|
||||
|
||||
---
|
||||
|
||||
## Ethereum / Web3 Integration
|
||||
|
||||
### Current Implementation (v0.0.20)
|
||||
|
||||
The `ethereum` module in `warzone-protocol` provides:
|
||||
|
||||
- **secp256k1 keypair** derived from the BIP39 seed via `HKDF(seed, info="warzone-secp256k1")`
|
||||
- **Ethereum address** computation: `Keccak-256(uncompressed_pubkey[1:])[-20:]`
|
||||
- **EIP-55 checksummed addresses**
|
||||
- **ECDSA signing and verification** (secp256k1)
|
||||
- CLI command: `warzone eth`
|
||||
- TUI command: `/eth`
|
||||
|
||||
### MetaMask / Wallet Connect (future)
|
||||
|
||||
Planned integration flow:
|
||||
|
||||
```
|
||||
1. User clicks "Connect Wallet" in web client
|
||||
2. Web client requests eth_sign(challenge) from MetaMask
|
||||
3. Server verifies secp256k1 signature
|
||||
4. Server maps Ethereum address → Warzone fingerprint
|
||||
5. Session established
|
||||
|
||||
Challenge: MetaMask signs with secp256k1, but Warzone messaging
|
||||
uses Ed25519/X25519. The wallet connect only proves ownership of
|
||||
the Ethereum address — a separate X3DH session is still needed
|
||||
for E2E encryption.
|
||||
```
|
||||
|
||||
### ENS Resolution (future)
|
||||
|
||||
Planned: resolve ENS names to Warzone fingerprints.
|
||||
|
||||
```
|
||||
@vitalik.eth → resolve ENS → 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045
|
||||
→ server lookup → Warzone fingerprint
|
||||
→ /peer @vitalik.eth
|
||||
```
|
||||
|
||||
Implementation would use `alloy` or `ethers-rs` for ENS resolution.
|
||||
|
||||
### Hardware Wallet Support (future)
|
||||
|
||||
Ledger and Trezor natively support secp256k1. Integration plan:
|
||||
|
||||
- Seed lives on the hardware wallet, never exported
|
||||
- Ed25519 signing delegated to device (BIP44 path `m/44'/1234'/0'`)
|
||||
- X25519 derived from Ed25519 or separate derivation path
|
||||
- Session key delegation: sign once per 30 days, client uses delegated key for daily operations
|
||||
|
||||
### Session Delegation (future)
|
||||
|
||||
For hardware wallets that cannot be used for every message:
|
||||
|
||||
```
|
||||
Hardware wallet signs: "I delegate signing to ephemeral key X for 30 days"
|
||||
Client stores ephemeral key in memory
|
||||
All messages signed with ephemeral key
|
||||
Contacts verify delegation chain: HW_pubkey → delegation_cert → ephemeral_sig
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## OIDC Integration (future)
|
||||
|
||||
For organizational deployments, an OIDC provider can gate registration and associate corporate identities.
|
||||
|
||||
### Concept
|
||||
|
||||
```
|
||||
1. User authenticates with corporate IdP (Okta, Azure AD, etc.)
|
||||
2. IdP issues OIDC token containing email/groups
|
||||
3. User presents OIDC token to Warzone server during registration
|
||||
4. Server verifies token, associates fingerprint with corporate identity
|
||||
5. Optional: server restricts messaging to verified users only
|
||||
|
||||
Benefits:
|
||||
- Gated registration (only org members can register)
|
||||
- Corporate directory integration (resolve by email)
|
||||
- Audit trail (fingerprint ↔ corporate identity mapping)
|
||||
- Seed recovery via corporate identity (re-register)
|
||||
```
|
||||
|
||||
### Implementation Pattern
|
||||
|
||||
```rust
|
||||
// Future: auth middleware
|
||||
async fn register_with_oidc(
|
||||
State(state): State<AppState>,
|
||||
bearer: TypedHeader<Authorization<Bearer>>,
|
||||
Json(req): Json<RegisterRequest>,
|
||||
) -> AppResult<Json<Value>> {
|
||||
let claims = verify_oidc_token(&bearer.token())?;
|
||||
// Associate claims.email with req.fingerprint
|
||||
// Only allow registration if claims are valid
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## DNS Federation (future)
|
||||
|
||||
### Server Discovery
|
||||
|
||||
Each Warzone server publishes a DNS TXT record:
|
||||
|
||||
```
|
||||
_warzone._tcp.example.com TXT "v=wz1; endpoint=https://wz.example.com; pubkey=base64..."
|
||||
```
|
||||
|
||||
Other servers discover peers by querying DNS:
|
||||
|
||||
```
|
||||
1. User sends message to user@example.com
|
||||
2. Local server: DNS TXT lookup → _warzone._tcp.example.com
|
||||
3. Parse endpoint URL and server pubkey
|
||||
4. TLS connection, mutual authentication
|
||||
5. Deliver encrypted message blob
|
||||
```
|
||||
|
||||
### Key Transparency
|
||||
|
||||
Users publish their public keys in DNS to prevent server MITM:
|
||||
|
||||
```
|
||||
_wz._id.<SHA256(fingerprint)[:16]>.example.com TXT "v=wz1; fp=a3f8...; pubkey=base64...; sig=base64..."
|
||||
```
|
||||
|
||||
The `sig` field is a self-signature — even the DNS admin cannot forge it without the user's private key.
|
||||
|
||||
### Alias Resolution via DNS (future)
|
||||
|
||||
```
|
||||
_wz._alias.alice.example.com TXT "fp=a3f8c912..."
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Transport Abstraction
|
||||
|
||||
The protocol is transport-agnostic. The `WireMessage` format is identical regardless of how it travels.
|
||||
|
||||
### Current Transports (v0.0.20)
|
||||
|
||||
| Transport | Client→Server | Server→Client | Status |
|
||||
|-----------|---------------|---------------|--------|
|
||||
| HTTPS | POST JSON | GET poll | Implemented |
|
||||
| WebSocket | Binary/JSON | Binary push | Implemented |
|
||||
|
||||
### Planned Transports (future)
|
||||
|
||||
| Transport | Range | Bandwidth | Use Case |
|
||||
|-------------|------------|------------|-----------------------------|
|
||||
| Bluetooth | 10-100m | ~2 Mbps | Mule sync, nearby devices |
|
||||
| LoRa | 2-15 km | 0.3-50 kbps| Emergency text, receipts |
|
||||
| Wi-Fi Direct| ~200m | ~250 Mbps | Local group mesh |
|
||||
| USB/File | Physical | Unlimited | Sneakernet, mule export |
|
||||
|
||||
### LoRa Compact Format (future)
|
||||
|
||||
For LoRa's ~250 byte payload limit:
|
||||
|
||||
```
|
||||
[1] version
|
||||
[1] type (text=0x01, receipt=0x02, beacon=0x03)
|
||||
[8] sender fingerprint (truncated)
|
||||
[8] recipient fingerprint (truncated)
|
||||
[4] timestamp (unix 32-bit)
|
||||
[12] nonce
|
||||
[~216] ciphertext (~200 chars of text)
|
||||
```
|
||||
|
||||
### USB / Sneakernet (future)
|
||||
|
||||
```bash
|
||||
warzone export --since 24h --to /mnt/usb/messages.wz
|
||||
# Carry USB drive to destination
|
||||
warzone import /mnt/usb/messages.wz
|
||||
```
|
||||
|
||||
### Implementing a New Transport
|
||||
|
||||
Define a type that implements the transport interface (conceptual — trait not yet formalized):
|
||||
|
||||
```rust
|
||||
// Future trait
|
||||
trait Transport: Send + Sync {
|
||||
async fn send(&self, endpoint: &str, blob: &[u8]) -> Result<()>;
|
||||
async fn recv(&self) -> Result<Vec<u8>>;
|
||||
fn name(&self) -> &str;
|
||||
}
|
||||
```
|
||||
|
||||
The message blob is always a bincode-serialized `WireMessage`. The transport only needs to deliver bytes.
|
||||
|
||||
---
|
||||
|
||||
## Multi-Server Mode (future)
|
||||
|
||||
### Federation
|
||||
|
||||
Servers communicate using mutual TLS and server-to-server protocol:
|
||||
|
||||
```
|
||||
Server A Server B
|
||||
│ │
|
||||
│ DNS lookup: _warzone._tcp.B │
|
||||
│ TLS connect + mutual auth │
|
||||
│ ─── deliver encrypted blob ────────→│
|
||||
│ ←── delivery receipt ───────────────│
|
||||
```
|
||||
|
||||
### Server-to-Server Relay
|
||||
|
||||
When direct connectivity is not available:
|
||||
|
||||
```
|
||||
Server A → Server C (relay) → Server B
|
||||
|
||||
Server C is configured as a relay for B.
|
||||
C queues messages for B until B reconnects.
|
||||
```
|
||||
|
||||
### Gossip Discovery (future)
|
||||
|
||||
Servers share their known peer lists:
|
||||
|
||||
```json
|
||||
{
|
||||
"peers": [
|
||||
{"domain": "wz.example.com", "pubkey": "base64...", "last_seen": 1711443600},
|
||||
{"domain": "chat.org", "pubkey": "base64...", "last_seen": 1711440000}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Mule Protocol (future)
|
||||
|
||||
Physical message relay between disconnected networks:
|
||||
|
||||
1. Mule authenticates with source server
|
||||
2. Mule picks up queued outbound messages (encrypted blobs)
|
||||
3. Mule physically travels to destination
|
||||
4. Mule delivers blobs to destination server
|
||||
5. Mule carries back delivery receipts
|
||||
6. Receipt enforcement: no receipts = no new pickup
|
||||
|
||||
---
|
||||
|
||||
## Custom Client Development
|
||||
|
||||
### Using warzone-protocol as a Library
|
||||
|
||||
Add to your `Cargo.toml`:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
warzone-protocol = { path = "../warzone/crates/warzone-protocol" }
|
||||
```
|
||||
|
||||
Core operations:
|
||||
|
||||
```rust
|
||||
use warzone_protocol::identity::Seed;
|
||||
use warzone_protocol::prekey::{generate_signed_pre_key, generate_one_time_pre_keys};
|
||||
use warzone_protocol::x3dh;
|
||||
use warzone_protocol::ratchet::RatchetState;
|
||||
use warzone_protocol::message::WireMessage;
|
||||
|
||||
// Generate identity
|
||||
let seed = Seed::generate();
|
||||
let identity = seed.derive_identity();
|
||||
let pub_id = identity.public_identity();
|
||||
println!("Fingerprint: {}", pub_id.fingerprint);
|
||||
|
||||
// Generate pre-key bundle
|
||||
let (spk_secret, spk) = generate_signed_pre_key(&identity, 1);
|
||||
let otpks = generate_one_time_pre_keys(1, 10);
|
||||
|
||||
// Initiate session (Alice side)
|
||||
let x3dh_result = x3dh::initiate(&identity, &their_bundle)?;
|
||||
let mut ratchet = RatchetState::init_alice(
|
||||
x3dh_result.shared_secret,
|
||||
x25519_dalek::PublicKey::from(their_bundle.signed_pre_key.public_key),
|
||||
);
|
||||
|
||||
// Encrypt
|
||||
let encrypted = ratchet.encrypt(b"hello")?;
|
||||
|
||||
// Build wire message
|
||||
let wire = WireMessage::Message {
|
||||
id: uuid::Uuid::new_v4().to_string(),
|
||||
sender_fingerprint: pub_id.fingerprint.to_string(),
|
||||
ratchet_message: encrypted,
|
||||
};
|
||||
let bytes = bincode::serialize(&wire)?;
|
||||
```
|
||||
|
||||
### WASM for Browsers
|
||||
|
||||
The `warzone-wasm` crate exposes the protocol to JavaScript:
|
||||
|
||||
```javascript
|
||||
import init, { WasmIdentity, WasmSession, decrypt_wire_message } from './warzone_wasm.js';
|
||||
|
||||
await init();
|
||||
|
||||
// Create identity
|
||||
const identity = new WasmIdentity();
|
||||
console.log("Fingerprint:", identity.fingerprint());
|
||||
console.log("Seed:", identity.seed_hex());
|
||||
|
||||
// Register bundle with server
|
||||
const bundleBytes = identity.bundle_bytes();
|
||||
await fetch('/v1/keys/register', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
fingerprint: identity.fingerprint_hex(),
|
||||
bundle: Array.from(bundleBytes),
|
||||
}),
|
||||
});
|
||||
|
||||
// Create session and encrypt
|
||||
const session = WasmSession.initiate(identity, theirBundleBytes);
|
||||
const encrypted = session.encrypt_key_exchange(identity, theirBundleBytes, "hello");
|
||||
|
||||
// Decrypt incoming
|
||||
const result = decrypt_wire_message(
|
||||
identity.seed_hex(),
|
||||
identity.spk_secret_hex(),
|
||||
messageBytes,
|
||||
existingSessionBase64, // null for first message
|
||||
);
|
||||
const parsed = JSON.parse(result);
|
||||
// parsed.sender, parsed.text, parsed.session_data, parsed.message_id
|
||||
```
|
||||
|
||||
### Native Mobile (future)
|
||||
|
||||
The `warzone-protocol` crate compiles to any Rust target:
|
||||
|
||||
- **iOS:** via `cargo-lipo` or Swift package with C FFI
|
||||
- **Android:** via `cargo-ndk` with JNI bindings
|
||||
- Same crypto, same wire format, full interop
|
||||
|
||||
---
|
||||
|
||||
## Notification Integration (future)
|
||||
|
||||
### ntfy Concept
|
||||
|
||||
[ntfy](https://ntfy.sh) provides push notifications without Google Play Services:
|
||||
|
||||
```
|
||||
User registers topic: wz_<fingerprint_prefix>
|
||||
Server pushes on new message:
|
||||
POST https://ntfy.example.com/wz_a3f8c912
|
||||
Body: "New message" (NO content — E2E encrypted)
|
||||
User receives push → opens Warzone to read
|
||||
```
|
||||
|
||||
Self-hostable alongside the Warzone server. ntfy handles Android/iOS/desktop notifications.
|
||||
|
||||
### Metadata Consideration
|
||||
|
||||
ntfy sees that *someone* messaged a topic (user). Mitigation: self-host ntfy on the same infrastructure as the Warzone server.
|
||||
|
||||
---
|
||||
|
||||
## How to Add New Message Types
|
||||
|
||||
### Step 1: Extend WireMessage
|
||||
|
||||
In `warzone-protocol/src/message.rs`:
|
||||
|
||||
```rust
|
||||
pub enum WireMessage {
|
||||
// ... existing variants ...
|
||||
|
||||
/// Your new message type
|
||||
MyNewType {
|
||||
id: String,
|
||||
sender_fingerprint: String,
|
||||
// your fields here
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
bincode serialization is automatic — the variant gets a new enum tag.
|
||||
|
||||
### Step 2: Update Server Dedup
|
||||
|
||||
In `warzone-server/src/routes/messages.rs` and `routes/ws.rs`, update `extract_message_id()`:
|
||||
|
||||
```rust
|
||||
WireMessage::MyNewType { id, .. } => Some(id),
|
||||
```
|
||||
|
||||
### Step 3: Handle in Clients
|
||||
|
||||
**TUI client** (`warzone-client/src/tui/app.rs`): Handle the new variant in the message receive/poll loop.
|
||||
|
||||
**Web client** (`warzone-wasm/src/lib.rs`): Add a match arm in `decrypt_wire_message()`:
|
||||
|
||||
```rust
|
||||
WireMessage::MyNewType { id, sender_fingerprint, .. } => {
|
||||
Ok(serde_json::json!({
|
||||
"type": "my_new_type",
|
||||
"id": id,
|
||||
"sender": sender_fingerprint,
|
||||
}).to_string())
|
||||
}
|
||||
```
|
||||
|
||||
### Step 4: Add Tests
|
||||
|
||||
In the protocol crate, add serialization and round-trip tests.
|
||||
|
||||
---
|
||||
|
||||
## How to Add New Commands
|
||||
|
||||
### TUI Commands
|
||||
|
||||
In `warzone-client/src/tui/app.rs`, inside `handle_send()`:
|
||||
|
||||
```rust
|
||||
if text.starts_with("/mycommand ") {
|
||||
let arg = text[11..].trim();
|
||||
self.add_message(ChatLine {
|
||||
sender: "system".into(),
|
||||
text: format!("My command: {}", arg),
|
||||
is_system: true,
|
||||
is_self: false,
|
||||
message_id: None,
|
||||
});
|
||||
return;
|
||||
}
|
||||
```
|
||||
|
||||
Pattern: parse the command text, perform the action, add a system message for feedback.
|
||||
|
||||
### Web Commands
|
||||
|
||||
In the web client JavaScript, add to the command dispatcher:
|
||||
|
||||
```javascript
|
||||
if (text.startsWith('/mycommand ')) {
|
||||
const arg = text.slice(11).trim();
|
||||
addSystemMessage(`My command: ${arg}`);
|
||||
return;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## How to Add New Storage Backends
|
||||
|
||||
### Current Pattern
|
||||
|
||||
Both server (`db.rs`) and client (`storage.rs`) use sled directly with method wrappers:
|
||||
|
||||
```rust
|
||||
pub struct LocalDb {
|
||||
sessions: sled::Tree,
|
||||
// ...
|
||||
}
|
||||
|
||||
impl LocalDb {
|
||||
pub fn save_session(&self, peer: &Fingerprint, state: &RatchetState) -> Result<()> {
|
||||
let data = bincode::serialize(state)?;
|
||||
self.sessions.insert(key, data)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Abstracting to Traits (future)
|
||||
|
||||
```rust
|
||||
trait SessionStore {
|
||||
fn save_session(&self, peer: &Fingerprint, state: &RatchetState) -> Result<()>;
|
||||
fn load_session(&self, peer: &Fingerprint) -> Result<Option<RatchetState>>;
|
||||
}
|
||||
|
||||
trait MessageStore {
|
||||
fn queue_message(&self, to: &str, message: &[u8]) -> Result<()>;
|
||||
fn poll_messages(&self, fingerprint: &str) -> Result<Vec<Vec<u8>>>;
|
||||
}
|
||||
|
||||
// Implementations:
|
||||
struct SledStore { /* ... */ }
|
||||
struct SqliteStore { /* ... */ }
|
||||
struct IndexedDbStore { /* ... */ } // for WASM
|
||||
```
|
||||
|
||||
The key insight: all storage is key-value with prefix scanning. Any ordered KV store (sled, RocksDB, SQLite, IndexedDB, LevelDB) can serve as a backend.
|
||||
234
warzone/docs/PROGRESS.md
Normal file
234
warzone/docs/PROGRESS.md
Normal file
@@ -0,0 +1,234 @@
|
||||
# Warzone Messenger (featherChat) — Progress Report
|
||||
|
||||
**Current Version:** 0.0.20
|
||||
**Last Updated:** 2026-03-28
|
||||
|
||||
---
|
||||
|
||||
## Project Timeline
|
||||
|
||||
### Phase 0 — Python Prototype (pre-Rust)
|
||||
|
||||
The project began as `chat.py`, a Python WebSocket chat with basic features:
|
||||
|
||||
- Basic chat server + web UI
|
||||
- WebSocket SSH tunnel
|
||||
- Nginx reverse proxy + ArvanCloud deployment
|
||||
- ECDH + AES-GCM DMs (no forward secrecy)
|
||||
- Group chat with passwords
|
||||
- PWA support
|
||||
- File upload
|
||||
|
||||
### Phase 1 — Identity & Crypto Foundation (Rust Rewrite)
|
||||
|
||||
The Rust rewrite established the cryptographic foundation:
|
||||
|
||||
| Feature | Version | Status |
|
||||
|------------------------------------------|---------|--------|
|
||||
| Cargo workspace scaffold (5 crates) | 0.0.1 | Done |
|
||||
| Seed-based identity (Ed25519 + X25519) | 0.0.2 | Done |
|
||||
| BIP39 mnemonic generation and recovery | 0.0.2 | Done |
|
||||
| Seed encryption at rest (Argon2id + ChaCha20-Poly1305) | 0.0.3 | Done |
|
||||
| Pre-key bundle generation and storage | 0.0.4 | Done |
|
||||
| X3DH key exchange implementation | 0.0.5 | Done |
|
||||
| Double Ratchet for 1:1 messaging | 0.0.6 | Done |
|
||||
| Basic server: axum, sled DB, store-and-forward | 0.0.4 | Done |
|
||||
| CLI client with subcommands | 0.0.5 | Done |
|
||||
| WASM bridge (warzone-wasm crate) | 0.0.8 | Done |
|
||||
| Server auth (challenge-response, bearer tokens) | 0.0.9 | Done |
|
||||
| OTP key replenishment | 0.0.9 | Done |
|
||||
| Fetch-and-delete delivery | 0.0.7 | Done |
|
||||
| Aliases with TTL, recovery keys | 0.0.10 | Done |
|
||||
| 17 protocol tests | 0.0.10 | Done |
|
||||
| CLI ↔ Web interop verified | 0.0.10 | Done |
|
||||
|
||||
### Phase 2 — Core Messaging
|
||||
|
||||
Built on the Phase 1 foundation to deliver a complete messaging experience:
|
||||
|
||||
| Feature | Version | Status |
|
||||
|------------------------------------------|---------|--------|
|
||||
| TUI client (ratatui + crossterm) | 0.0.7 | Done |
|
||||
| Web client (WASM) | 0.0.8 | Done |
|
||||
| WebSocket real-time push | 0.0.11 | Done |
|
||||
| Delivery receipts (sent/delivered/read) | 0.0.12 | Done |
|
||||
| File transfer (chunked, SHA-256 verified)| 0.0.13 | Done |
|
||||
| Group chat (server fan-out) | 0.0.10 | Done |
|
||||
| Group management (create/join/leave/kick)| 0.0.14 | Done |
|
||||
| Sender Keys for group encryption | 0.0.15 | Done |
|
||||
| Message deduplication (bounded FIFO) | 0.0.16 | Done |
|
||||
| Ethereum-compatible identity (secp256k1) | 0.0.14 | Done |
|
||||
| Encrypted backup/restore | 0.0.17 | Done |
|
||||
| Local message history (sled) | 0.0.17 | Done |
|
||||
| Contact list with message counts | 0.0.17 | Done |
|
||||
| Alias auto-renewal on activity | 0.0.18 | Done |
|
||||
| Multi-device key registration | 0.0.18 | Done |
|
||||
| DB lock handling with user-friendly errors | 0.0.19 | Done |
|
||||
| Readline-style TUI editing (Ctrl-A/E/U/W)| 0.0.19 | Done |
|
||||
| Reply shortcut (/r, /reply) | 0.0.19 | Done |
|
||||
| 28 protocol tests | 0.0.20 | Done |
|
||||
|
||||
---
|
||||
|
||||
## Current Version: v0.0.20
|
||||
|
||||
### Codebase Statistics
|
||||
|
||||
| Metric | Value |
|
||||
|-------------------|--------------------------------|
|
||||
| Crates | 5 (protocol, server, client, wasm, mule) |
|
||||
| Protocol tests | 28 |
|
||||
| Rust edition | 2021 |
|
||||
| Min Rust version | 1.75 |
|
||||
| License | MIT |
|
||||
|
||||
### Protocol Crate Modules
|
||||
|
||||
| Module | Approximate Scope |
|
||||
|---------------|---------------------------------------|
|
||||
| identity | Seed, keypair derivation, fingerprints|
|
||||
| crypto | HKDF, ChaCha20-Poly1305 AEAD |
|
||||
| prekey | Signed + one-time pre-keys |
|
||||
| x3dh | Extended Triple Diffie-Hellman |
|
||||
| ratchet | Double Ratchet state machine |
|
||||
| message | WireMessage (7 variants), content types|
|
||||
| sender_keys | Sender Key encrypt/decrypt/rotate |
|
||||
| history | Encrypted backup format |
|
||||
| ethereum | secp256k1, Keccak-256, EIP-55 |
|
||||
| types | Fingerprint, DeviceId, SessionId |
|
||||
| mnemonic | BIP39 encode/decode |
|
||||
| store | Storage trait definitions |
|
||||
| errors | Error types |
|
||||
|
||||
### Feature Summary
|
||||
|
||||
**Working end-to-end:**
|
||||
- 1:1 encrypted DMs with forward secrecy (X3DH + Double Ratchet)
|
||||
- Group messaging with Sender Keys
|
||||
- WebSocket real-time delivery + offline queue
|
||||
- File transfer (up to 10 MB, chunked, SHA-256 verified)
|
||||
- Delivery and read receipts
|
||||
- TUI client with full command set
|
||||
- Web client (WASM) with identical crypto
|
||||
- Alias system with TTL, recovery, admin
|
||||
- Challenge-response authentication
|
||||
- Ethereum address derivation from same seed
|
||||
- Encrypted backup and restore
|
||||
- Contact list and message history
|
||||
- Multi-device support (basic)
|
||||
|
||||
---
|
||||
|
||||
## Test Suite
|
||||
|
||||
28 tests across the protocol crate:
|
||||
|
||||
| Module | Tests | Coverage |
|
||||
|---------------|-------|---------------------------------------------|
|
||||
| identity | 3 | Deterministic derivation, mnemonic roundtrip, fingerprint format |
|
||||
| crypto | 4 | AEAD roundtrip, wrong key, wrong AAD, HKDF determinism |
|
||||
| x3dh | ~4 | Initiate/respond, shared secret match, with/without OTPK |
|
||||
| ratchet | ~6 | Encrypt/decrypt, out-of-order, multiple messages, ping-pong |
|
||||
| sender_keys | 4 | Basic encrypt/decrypt, multiple messages, rotation, old key rejection |
|
||||
| ethereum | 5 | Deterministic derivation, address format, checksum, sign/verify, different seeds |
|
||||
| history | 2 | Roundtrip encryption, wrong seed rejection |
|
||||
| prekey | ~2 | Bundle generation, signature verification |
|
||||
|
||||
---
|
||||
|
||||
## Bugs Fixed
|
||||
|
||||
| Bug | Version Fixed | Description |
|
||||
|-----|---------------|-------------|
|
||||
| X3DH OTPK mismatch | 0.0.8 | Web client regenerated SPK on each page load, causing X3DH failures. Fixed by persisting SPK secret in localStorage and restoring on load. |
|
||||
| Axum route syntax | 0.0.11 | Route path parameters used wrong syntax for axum 0.7. Updated to `/:param` format. |
|
||||
| WASM SPK regeneration | 0.0.12 | WasmIdentity regenerated pre-keys on every `bundle_bytes()` call. Fixed by caching the bundle and storing SPK secret bytes. |
|
||||
| DB lock handling | 0.0.19 | sled database lock caused cryptic panic when another warzone process was running. Added user-friendly error message with recovery instructions. |
|
||||
| Dedup overflow | 0.0.16 | Dedup tracker grew unbounded. Fixed with FIFO eviction at 10,000 entries. |
|
||||
| Alias normalization | 0.0.18 | Fingerprints with colons caused lookup failures. Added `normalize_fp()` to strip non-hex characters. |
|
||||
| Receipt routing | 0.0.12 | Receipts sent to wrong fingerprint when switching peers in TUI. Fixed by including correct sender_fingerprint in Receipt wire messages. |
|
||||
|
||||
---
|
||||
|
||||
## Known Issues and Limitations
|
||||
|
||||
### Current Limitations
|
||||
|
||||
1. **No perfect forward secrecy in groups:** Sender Keys provide forward secrecy within a chain but not per-message PFS like Double Ratchet. Acceptable for groups under 50 members.
|
||||
|
||||
2. **No sealed sender:** The server sees sender and recipient fingerprints in message routing metadata. Planned for Phase 6.
|
||||
|
||||
3. **No server-at-rest encryption:** The sled database on the server is unencrypted. Message content is E2E encrypted, but metadata (fingerprints, timestamps, group membership) is visible to the server operator.
|
||||
|
||||
4. **Auth tokens in memory:** Challenge-response tokens are partially stored in memory (challenges are in a static HashMap). Production deployment should use the DB for all auth state.
|
||||
|
||||
5. **No rate limiting:** No protection against message flooding or registration spam. Planned for Phase 7.
|
||||
|
||||
6. **Single server only:** No federation between servers yet. Planned for Phase 3.
|
||||
|
||||
7. **No push notifications:** Users must keep a WebSocket connection open or poll. ntfy integration planned for Phase 7.
|
||||
|
||||
8. **Web client: no OTPKs:** The web client does not generate one-time pre-keys (cannot reliably store secrets). X3DH works without DH4, but replay protection is slightly weaker.
|
||||
|
||||
9. **Web client: localStorage only:** Seed and session data stored in browser localStorage. Clearing browser data = lost identity.
|
||||
|
||||
10. **No message ordering guarantees:** Messages may arrive out of order. The Double Ratchet handles this for decryption, but the UI does not reorder displayed messages.
|
||||
|
||||
---
|
||||
|
||||
## Roadmap: What's Next
|
||||
|
||||
### Phase 3 — Federation & Key Transparency (next priority)
|
||||
|
||||
- DNS TXT record format for server discovery
|
||||
- User self-signed key publication to DNS
|
||||
- Key verification: server vs DNS cross-check
|
||||
- Server-to-server mutual TLS
|
||||
- Federated message delivery
|
||||
- Server key pinning (TOFU)
|
||||
- Gossip-based peer discovery
|
||||
|
||||
### Phase 4 — Warzone Delivery
|
||||
|
||||
- Mule protocol specification and implementation
|
||||
- Mule authentication and authorization
|
||||
- Message pickup with capacity declaration
|
||||
- Delivery receipt enforcement
|
||||
- Outer encryption layer (hide metadata from mule)
|
||||
- Bundle compression (zstd)
|
||||
- Mule CLI binary
|
||||
|
||||
### Phase 5 — Transport Fallbacks
|
||||
|
||||
- Bluetooth mule transfer (phone-to-phone)
|
||||
- LoRa transport layer (compact binary format)
|
||||
- mDNS / LAN discovery for local mesh
|
||||
- Wi-Fi Direct for nearby device sync
|
||||
|
||||
### Phase 6 — Metadata Protection
|
||||
|
||||
- Sealed sender (server doesn't know the sender)
|
||||
- Onion routing between federated servers (opt-in)
|
||||
- Padding and traffic shaping
|
||||
- Traffic analysis resistance
|
||||
|
||||
### Phase 7 — Polish & Operations
|
||||
|
||||
- ntfy push notification integration
|
||||
- DNS-over-HTTPS for censored networks
|
||||
- Admin CLI for server management
|
||||
- Rate limiting and abuse prevention
|
||||
- Monitoring and health checks
|
||||
- Audit logging
|
||||
- Server-at-rest encryption (optional `--encrypt-db` flag)
|
||||
- Cross-compilation CI (Linux x86/ARM, macOS, Windows, WASM)
|
||||
- PWA: service worker, offline shell, install prompt
|
||||
|
||||
### Priority Order
|
||||
|
||||
1. Federation (Phase 3) — enables multi-server deployment
|
||||
2. Mule protocol (Phase 4) — core differentiator for warzone use
|
||||
3. Sealed sender (Phase 6) — strongest metadata privacy
|
||||
4. Push notifications (Phase 7) — usability for mobile/desktop
|
||||
5. Transport fallbacks (Phase 5) — Bluetooth, LoRa
|
||||
6. Polish (Phase 7) — rate limiting, admin tools, CI
|
||||
438
warzone/docs/SECURITY.md
Normal file
438
warzone/docs/SECURITY.md
Normal file
@@ -0,0 +1,438 @@
|
||||
# Warzone Messenger (featherChat) — Security Model & Threat Analysis
|
||||
|
||||
**Version:** 0.0.20
|
||||
**Last Updated:** 2026-03-28
|
||||
|
||||
---
|
||||
|
||||
## Threat Model
|
||||
|
||||
### What Is Protected
|
||||
|
||||
| Asset | Protection |
|
||||
|--------------------------|-----------------------------------------------|
|
||||
| Message content | E2E encrypted (ChaCha20-Poly1305 via Double Ratchet) |
|
||||
| Message integrity | AEAD authentication + Ed25519 signatures |
|
||||
| Past messages | Forward secrecy (Double Ratchet DH ratchet) |
|
||||
| Future messages | Future secrecy (recovery after DH ratchet step)|
|
||||
| Identity seed at rest | Argon2id + ChaCha20-Poly1305 passphrase encryption |
|
||||
| Identity portability | BIP39 mnemonic (24 words) |
|
||||
| Session state | Encrypted backup (HKDF + ChaCha20-Poly1305) |
|
||||
| Pre-key authenticity | Ed25519 signature on signed pre-keys |
|
||||
| Key exchange integrity | X3DH with 3-4 DH operations |
|
||||
|
||||
### What Is NOT Protected (Current)
|
||||
|
||||
| Asset | Exposure |
|
||||
|--------------------------|------------------------------------------------|
|
||||
| Sender/recipient metadata| Server sees who talks to whom |
|
||||
| Message timing | Server sees when messages are sent/received |
|
||||
| Group membership | Server stores group member lists in plaintext |
|
||||
| Alias ↔ fingerprint mapping | Server stores this mapping |
|
||||
| Message sizes | Server sees encrypted message sizes |
|
||||
| Online/offline status | Server knows when clients connect via WebSocket|
|
||||
| IP addresses | Server sees client IP addresses |
|
||||
|
||||
### Trust Boundaries
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────┐
|
||||
│ TRUSTED: Client device │
|
||||
│ - Seed in memory (after unlock) │
|
||||
│ - Ratchet state │
|
||||
│ - Plaintext messages (in memory and local DB) │
|
||||
├─────────────────────────────────────────────────────┤
|
||||
│ SEMI-TRUSTED: Server │
|
||||
│ - Sees metadata (who, when, how much) │
|
||||
│ - Cannot read message content │
|
||||
│ - Cannot forge messages (no signing keys) │
|
||||
│ - Could drop, delay, or reorder messages │
|
||||
│ - Could serve wrong pre-key bundles (MITM) │
|
||||
│ → Mitigated by fingerprint verification │
|
||||
│ → Future: DNS key transparency │
|
||||
├─────────────────────────────────────────────────────┤
|
||||
│ UNTRUSTED: Network │
|
||||
│ - All traffic encrypted (TLS + E2E) │
|
||||
│ - Passive observer sees TLS-encrypted WebSocket │
|
||||
│ - Active attacker cannot read or forge messages │
|
||||
├─────────────────────────────────────────────────────┤
|
||||
│ UNTRUSTED: Other users │
|
||||
│ - Cannot read messages not addressed to them │
|
||||
│ - Cannot impersonate others (no access to seed) │
|
||||
│ - Trust established via TOFU or fingerprint verify │
|
||||
└─────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Cryptographic Primitives
|
||||
|
||||
### Why Each Primitive Was Chosen
|
||||
|
||||
| Primitive | Used For | Why This One |
|
||||
|---------------------|-------------------------|-------------------------------------------------|
|
||||
| Ed25519 | Signing, identity | Fast, compact (32-byte keys), no side-channel concerns, widely audited. Deterministic signatures (no nonce reuse risk). |
|
||||
| X25519 | Key exchange (DH) | Paired with Ed25519, same curve family. Constant-time implementations. Used by Signal, WireGuard, Noise. |
|
||||
| ChaCha20-Poly1305 | AEAD encryption | Stream cipher — no padding oracle attacks. Faster than AES on platforms without AES-NI (ARM, WASM). Used by TLS 1.3, WireGuard, Signal. |
|
||||
| HKDF-SHA256 | Key derivation | Standard (RFC 5869). Clean domain separation via info strings. Used throughout Signal protocol. |
|
||||
| SHA-256 | Fingerprints | Ubiquitous, well-understood. 128-bit truncation for fingerprints provides sufficient collision resistance for this use case. |
|
||||
| Argon2id | Passphrase KDF | Winner of the Password Hashing Competition. Memory-hard (resists GPU/ASIC attacks). `id` variant provides resistance to both side-channel and GPU attacks. |
|
||||
| secp256k1 ECDSA | Ethereum compatibility | Required for Ethereum address derivation and wallet interop. Not used for messaging crypto. |
|
||||
| Keccak-256 | Ethereum addresses | Required by Ethereum spec. Only used for address derivation, not for messaging. |
|
||||
|
||||
### Crate Security
|
||||
|
||||
All crypto crates are widely audited Rust implementations:
|
||||
|
||||
| Crate | Notes |
|
||||
|-----------------------|------------------------------------------------|
|
||||
| `ed25519-dalek` 2.x | Maintained by dalek-cryptography team |
|
||||
| `x25519-dalek` 2.x | Same team, constant-time DH |
|
||||
| `chacha20poly1305` 0.10| RustCrypto project, audited |
|
||||
| `hkdf` 0.12 | RustCrypto project |
|
||||
| `sha2` 0.10 | RustCrypto project |
|
||||
| `argon2` 0.5 | RustCrypto project, Argon2id support |
|
||||
| `k256` 0.13 | RustCrypto secp256k1 implementation |
|
||||
| `rand` 0.8 | OS-provided randomness (OsRng) |
|
||||
| `zeroize` 1.x | Secure memory zeroing (Seed, keys) |
|
||||
|
||||
---
|
||||
|
||||
## Key Derivation Paths
|
||||
|
||||
All keys are derived from a single 32-byte seed using HKDF-SHA256 with distinct info strings for domain separation:
|
||||
|
||||
```
|
||||
Seed (32 bytes)
|
||||
│
|
||||
├─ HKDF(ikm=seed, salt="", info="warzone-ed25519") → Ed25519 signing key
|
||||
│
|
||||
├─ HKDF(ikm=seed, salt="", info="warzone-x25519") → X25519 encryption key
|
||||
│
|
||||
├─ HKDF(ikm=seed, salt="", info="warzone-secp256k1") → secp256k1 key (Ethereum)
|
||||
│
|
||||
└─ HKDF(ikm=seed, salt="", info="warzone-history") → History encryption key
|
||||
```
|
||||
|
||||
### X3DH Key Derivation
|
||||
|
||||
```
|
||||
DH1 = our_identity_x25519 * their_signed_pre_key
|
||||
DH2 = our_ephemeral * their_identity_x25519
|
||||
DH3 = our_ephemeral * their_signed_pre_key
|
||||
DH4 = our_ephemeral * their_one_time_pre_key (optional)
|
||||
|
||||
shared_secret = HKDF(ikm=DH1||DH2||DH3[||DH4], salt="", info="warzone-x3dh")
|
||||
```
|
||||
|
||||
### Double Ratchet Key Derivation
|
||||
|
||||
```
|
||||
Root KDF: HKDF(ikm=DH_output, salt=root_key, info="warzone-rk")
|
||||
→ (new_root_key, chain_key)
|
||||
|
||||
Chain KDF: HKDF(ikm=chain_key, salt="", info="warzone-ck")
|
||||
→ (new_chain_key, message_key)
|
||||
```
|
||||
|
||||
### Sender Key Derivation
|
||||
|
||||
```
|
||||
Message key: HKDF(ikm=chain_key, salt="", info="wz-sk-msg-{generation}-{counter}")
|
||||
Chain step: HKDF(ikm=chain_key, salt="", info="wz-sk-chain")
|
||||
```
|
||||
|
||||
### Seed Encryption
|
||||
|
||||
```
|
||||
salt = random(16 bytes)
|
||||
key = Argon2id(passphrase, salt) → 32 bytes
|
||||
nonce = random(12 bytes)
|
||||
ciphertext = ChaCha20-Poly1305(key, nonce, seed)
|
||||
|
||||
File: WZS1(4) || salt(16) || nonce(12) || ciphertext(48)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Forward Secrecy
|
||||
|
||||
### What Forward Secrecy Means
|
||||
|
||||
If an attacker compromises your current keys, they **cannot decrypt past messages**. Each message uses a unique key derived from the ratchet state, and old keys are deleted after use.
|
||||
|
||||
### How the Double Ratchet Provides It
|
||||
|
||||
```
|
||||
Message 1: chain_key_0 → message_key_0 (used, then deleted)
|
||||
Message 2: chain_key_1 → message_key_1 (used, then deleted)
|
||||
...
|
||||
DH Ratchet step: new DH exchange → new root_key → new chain
|
||||
Message N: chain_key_N → message_key_N (used, then deleted)
|
||||
```
|
||||
|
||||
**Symmetric ratchet:** Each message key is derived from the previous chain key, then the old chain key is overwritten. Compromise of the current chain key reveals future messages in this chain, but not past ones.
|
||||
|
||||
**DH ratchet:** Periodically, a new Diffie-Hellman exchange occurs (new ephemeral keypair). This "heals" from compromise — even if the current chain key is stolen, after the next DH ratchet step, the attacker is locked out again.
|
||||
|
||||
### Forward Secrecy Properties
|
||||
|
||||
| Scenario | Past Messages | Future Messages |
|
||||
|---------------------------------------|---------------|-----------------|
|
||||
| Current message key compromised | Safe | Safe |
|
||||
| Current chain key compromised | Safe | At risk until next DH ratchet |
|
||||
| DH private key compromised | Safe | At risk until next DH ratchet |
|
||||
| Seed compromised | At risk (all) | At risk (all) |
|
||||
|
||||
**Key insight:** Compromising the seed compromises everything because all keys derive from it. Protect the seed above all else.
|
||||
|
||||
### Skipped Message Keys
|
||||
|
||||
The Double Ratchet caches up to 1,000 skipped message keys to handle out-of-order delivery. These cached keys are a temporary window — they allow decryption of delayed messages but represent stored key material.
|
||||
|
||||
---
|
||||
|
||||
## Sender Keys: Trade-offs for Groups
|
||||
|
||||
### Efficiency vs Forward Secrecy
|
||||
|
||||
**Double Ratchet (1:1):** O(1) encrypt, O(1) decrypt. Perfect forward secrecy per message.
|
||||
|
||||
**Sender Keys (groups):** O(1) encrypt (one ciphertext for all members), O(1) decrypt per member. Forward secrecy per sender key chain, but NOT per-message.
|
||||
|
||||
### What Sender Keys Provide
|
||||
|
||||
- Forward ratcheting: each message advances the chain. Once a message key is derived and used, the chain key moves forward irreversibly.
|
||||
- Compromise of the current chain key reveals future messages in this generation, but not past messages.
|
||||
- Key rotation on member join/leave: all members generate new sender keys.
|
||||
|
||||
### What Sender Keys Do NOT Provide
|
||||
|
||||
- **No per-message forward secrecy:** Unlike Double Ratchet, there is no DH ratchet step per message. The symmetric chain ratchets forward, but a compromised chain key reveals all future messages until rotation.
|
||||
- **No future secrecy from member removal:** When a member is kicked, all remaining members must rotate their sender keys. If the kicked member cached the old chain key before rotation, they can read messages encrypted before the rotation completes.
|
||||
|
||||
### When This Matters
|
||||
|
||||
For groups under 50 members (the target), Sender Keys are a reasonable trade-off. For larger or higher-security groups, MLS (Message Layer Security, RFC 9420) would be more appropriate. MLS is not implemented and is not planned for the near term.
|
||||
|
||||
---
|
||||
|
||||
## Seed Security
|
||||
|
||||
### Encryption at Rest
|
||||
|
||||
The seed file uses defense-in-depth:
|
||||
|
||||
1. **Argon2id** with default parameters for memory-hard key derivation
|
||||
2. **ChaCha20-Poly1305** for authenticated encryption
|
||||
3. **Random salt** (16 bytes) prevents rainbow tables
|
||||
4. **Random nonce** (12 bytes) prevents nonce reuse
|
||||
5. **File permissions** set to 0600 on Unix
|
||||
|
||||
### Passphrase Strength
|
||||
|
||||
Argon2id makes brute-force expensive, but the passphrase is still the weak link. Users should choose strong passphrases. An empty passphrase stores the seed in plaintext (for testing only).
|
||||
|
||||
### Memory Safety
|
||||
|
||||
The `Seed` struct implements `Zeroize` and `ZeroizeOnDrop` from the `zeroize` crate. When the seed goes out of scope, its memory is securely overwritten with zeros.
|
||||
|
||||
### Hardware Wallet Concept (future)
|
||||
|
||||
The ideal: seed never leaves a hardware device. Ed25519/X25519 operations delegated to Ledger/Trezor. Session key delegation allows daily use without touching the hardware wallet for every message.
|
||||
|
||||
---
|
||||
|
||||
## Server Trust Model
|
||||
|
||||
### What the Server Can Do
|
||||
|
||||
- **See metadata:** sender fingerprint, recipient fingerprint, timestamp, message size
|
||||
- **Drop messages:** silently discard messages (denial of service)
|
||||
- **Delay messages:** hold messages before delivery
|
||||
- **Reorder messages:** deliver messages in a different order
|
||||
- **Serve wrong pre-key bundles:** MITM attack on key exchange (mitigated by fingerprint verification)
|
||||
- **See group membership:** group member lists are stored in plaintext
|
||||
- **See online status:** WebSocket connections reveal when users are online
|
||||
- **See alias mappings:** alias ↔ fingerprint relationships
|
||||
|
||||
### What the Server CANNOT Do
|
||||
|
||||
- **Read message content:** all messages are E2E encrypted
|
||||
- **Forge messages:** the server does not have users' signing keys
|
||||
- **Derive keys:** the server never has seeds, private keys, or session keys
|
||||
- **Decrypt past messages:** even if the server is later compromised, stored ciphertext remains unreadable
|
||||
- **Modify messages undetected:** AEAD authentication detects tampering
|
||||
|
||||
### MITM via Pre-Key Substitution
|
||||
|
||||
The most serious server attack: serve a malicious pre-key bundle (the server's own) instead of the recipient's real bundle. The sender would unknowingly encrypt to the server.
|
||||
|
||||
**Current mitigation:** Users verify fingerprints out-of-band. If the fingerprint matches, the pre-key bundle is authentic (because the signed pre-key is signed by the identity key corresponding to the fingerprint).
|
||||
|
||||
**Future mitigation:** DNS key transparency — users publish their public keys in DNS TXT records with self-signatures. The server cannot forge these records without the user's private key.
|
||||
|
||||
---
|
||||
|
||||
## Alias System Security
|
||||
|
||||
### TTL and Reclamation
|
||||
|
||||
- Aliases expire after **365 days** of inactivity
|
||||
- **30-day grace period** after expiry: only the recovery key holder can reclaim
|
||||
- After grace period: anyone can register the alias
|
||||
- Activity auto-renews: sending messages, WebSocket activity
|
||||
|
||||
This prevents:
|
||||
- **Squatting:** unused aliases are reclaimed
|
||||
- **Immediate takeover:** grace period gives the original owner time to recover
|
||||
|
||||
### Recovery Keys
|
||||
|
||||
- Generated server-side (16 random bytes, displayed as 32 hex chars)
|
||||
- Rotated on each recovery operation
|
||||
- Stored alongside the alias record
|
||||
- The only way to reclaim an alias after losing access to the associated fingerprint
|
||||
|
||||
### Admin Capabilities
|
||||
|
||||
The server admin (via `WARZONE_ADMIN_PASSWORD`) can:
|
||||
- Remove any alias (`/alias/admin-remove`)
|
||||
- Access is password-protected but not cryptographically authenticated
|
||||
|
||||
The admin **cannot**:
|
||||
- Read messages
|
||||
- Impersonate users
|
||||
- Modify pre-key bundles without detection
|
||||
|
||||
---
|
||||
|
||||
## WASM Security
|
||||
|
||||
### Same Crypto as Native
|
||||
|
||||
The web client uses the exact same Rust code compiled to WebAssembly:
|
||||
- `warzone-protocol` compiled via `wasm-pack`
|
||||
- X3DH, Double Ratchet, ChaCha20-Poly1305 — identical implementations
|
||||
- CLI-to-web and web-to-CLI messages are fully interoperable
|
||||
|
||||
### Randomness
|
||||
|
||||
WASM uses `OsRng` from the `rand` crate, which maps to `crypto.getRandomValues()` in the browser. This is a cryptographically secure random number generator provided by the Web Crypto API.
|
||||
|
||||
### Key Storage Limitations
|
||||
|
||||
| Concern | Native CLI | Web Client |
|
||||
|----------------------|-------------------------------|------------------------------|
|
||||
| Seed storage | Encrypted file (Argon2id) | localStorage (plaintext hex) |
|
||||
| Session persistence | sled DB on disk | localStorage (base64) |
|
||||
| Memory protection | Zeroize on drop | WASM linear memory (no zeroize guarantee) |
|
||||
| Process isolation | OS process isolation | Browser sandbox |
|
||||
| Side channels | Constant-time crypto | Same (WASM), but JS interop may leak timing |
|
||||
|
||||
**Key risk:** The web client stores the seed as plaintext hex in `localStorage`. Any XSS vulnerability could steal the seed. The native client encrypts the seed with a passphrase.
|
||||
|
||||
### No OTPKs in Web Client
|
||||
|
||||
The web client does not generate one-time pre-keys because `localStorage` cannot reliably store secrets that must be used exactly once. X3DH works without DH4 (the one-time pre-key DH). This means:
|
||||
- The key exchange still produces a shared secret from 3 DH operations
|
||||
- Anti-replay protection is slightly weaker (an attacker who captures the initial key exchange message could replay it if the server does not enforce OTP key deletion)
|
||||
- The server-side dedup tracker provides partial replay protection
|
||||
|
||||
---
|
||||
|
||||
## Known Weaknesses and Mitigations Planned
|
||||
|
||||
### 1. No Sealed Sender
|
||||
|
||||
**Weakness:** The server sees sender and recipient fingerprints.
|
||||
|
||||
**Mitigation (Phase 6):** Sealed sender — the server only sees the recipient, not the sender. The sender's identity is encrypted inside the E2E envelope.
|
||||
|
||||
### 2. No Traffic Analysis Protection
|
||||
|
||||
**Weakness:** Message timing and sizes reveal communication patterns.
|
||||
|
||||
**Mitigation (Phase 6):** Traffic padding and shaping. Optional onion routing between federated servers.
|
||||
|
||||
### 3. Web Client Seed Exposure
|
||||
|
||||
**Weakness:** Seed stored as plaintext hex in `localStorage`.
|
||||
|
||||
**Mitigation (planned):** Use Web Crypto API's `CryptoKey` with `non-extractable` flag. Derive encryption keys inside a Service Worker. Prompt for passphrase on load.
|
||||
|
||||
### 4. No Key Transparency
|
||||
|
||||
**Weakness:** Server could serve malicious pre-key bundles (MITM).
|
||||
|
||||
**Mitigation (Phase 3):** DNS TXT records with self-signed public keys. Cross-check server response against DNS.
|
||||
|
||||
### 5. Server Metadata Exposure
|
||||
|
||||
**Weakness:** Group membership, alias mappings, online status visible to server.
|
||||
|
||||
**Mitigation (partial, Phase 6):** Sealed sender hides sender identity. Group membership could be encrypted to group members only.
|
||||
|
||||
### 6. No Post-Quantum Crypto
|
||||
|
||||
**Weakness:** All key exchanges use classical DH (X25519). A future quantum computer could break stored ciphertext.
|
||||
|
||||
**Mitigation (future):** Hybrid key exchange (X25519 + ML-KEM/Kyber). The HKDF construction supports this naturally — concatenate classical and post-quantum shared secrets as HKDF input.
|
||||
|
||||
### 7. Auth Token Storage
|
||||
|
||||
**Weakness:** Challenge nonces stored in memory (not persisted). Server restart clears pending challenges.
|
||||
|
||||
**Mitigation (planned):** Store challenges in sled DB with TTL.
|
||||
|
||||
---
|
||||
|
||||
## Comparison with Other Messengers
|
||||
|
||||
### vs Signal
|
||||
|
||||
| Aspect | Warzone | Signal |
|
||||
|-----------------------|----------------------------------|----------------------------------|
|
||||
| Protocol | X3DH + Double Ratchet (same) | X3DH + Double Ratchet |
|
||||
| Groups | Sender Keys | Sender Keys (+ considering MLS) |
|
||||
| Identity | Seed-based (BIP39 mnemonic) | Phone number based |
|
||||
| Server | Self-hosted, open source | Centralized |
|
||||
| Federation | Planned (DNS-based) | No federation |
|
||||
| Offline delivery | Mule protocol (planned) | Push notification only |
|
||||
| Metadata protection | Not yet (planned) | Sealed sender, SGX enclaves |
|
||||
| Audit | Not audited | Extensively audited |
|
||||
| Mobile | Not yet (planned) | Native iOS/Android apps |
|
||||
| Phone requirement | No phone needed | Requires phone number |
|
||||
|
||||
### vs Matrix (Element)
|
||||
|
||||
| Aspect | Warzone | Matrix/Element |
|
||||
|-----------------------|----------------------------------|----------------------------------|
|
||||
| Protocol | Signal protocol (X3DH + DR) | Olm/Megolm (Double Ratchet variant) |
|
||||
| Groups | Sender Keys | Megolm (similar concept) |
|
||||
| Federation | Planned (DNS TXT) | Built-in (homeserver federation) |
|
||||
| Complexity | Minimal (5 crates) | Complex (homeserver, synapse) |
|
||||
| Encryption by default | Always E2E | Optional (not all rooms) |
|
||||
| Binary size | Single static binary | Python/Node server + Electron client |
|
||||
| Offline/mule | Designed for it | Not designed for offline |
|
||||
|
||||
### vs SimpleX
|
||||
|
||||
| Aspect | Warzone | SimpleX |
|
||||
|-----------------------|----------------------------------|----------------------------------|
|
||||
| Identity | Seed-based fingerprint | No user identity (contact-level) |
|
||||
| Metadata | Server sees sender/recipient | Server sees neither (relays) |
|
||||
| Groups | Sender Keys | Per-member encryption |
|
||||
| Federation | Planned (DNS) | Relay-based |
|
||||
| Offline delivery | Mule protocol (planned) | Queue-based |
|
||||
| Simplicity | Single binary, sled DB | Haskell server, complex setup |
|
||||
| Ethereum integration | Built-in | None |
|
||||
|
||||
### Key Differentiators
|
||||
|
||||
1. **Seed-based identity** with BIP39 mnemonic — no phone numbers, no accounts, portable across devices
|
||||
2. **Dual-curve identity** — same seed produces both messaging keys and Ethereum address
|
||||
3. **Designed for warzone conditions** — mule protocol, transport abstraction, offline-first
|
||||
4. **Single static binary** — no runtime dependencies, easy deployment
|
||||
5. **WASM web client** with identical crypto — no JS crypto, same Rust code
|
||||
6. **Self-hosted by design** — no dependency on centralized infrastructure
|
||||
550
warzone/docs/USAGE.md
Normal file
550
warzone/docs/USAGE.md
Normal file
@@ -0,0 +1,550 @@
|
||||
# Warzone Messenger (featherChat) — Usage Guide
|
||||
|
||||
**Version:** 0.0.20
|
||||
|
||||
---
|
||||
|
||||
## Installation
|
||||
|
||||
### Build from Source
|
||||
|
||||
Requirements: Rust 1.75+, cargo
|
||||
|
||||
```bash
|
||||
# Clone the repository
|
||||
git clone <repo-url>
|
||||
cd warzone
|
||||
|
||||
# Build all binaries
|
||||
cargo build --release
|
||||
|
||||
# Binaries are in target/release/
|
||||
# warzone-server — server
|
||||
# warzone-client — CLI/TUI client
|
||||
```
|
||||
|
||||
### Build the WASM Module (Web Client)
|
||||
|
||||
Requirements: wasm-pack
|
||||
|
||||
```bash
|
||||
cd crates/warzone-wasm
|
||||
wasm-pack build --target web
|
||||
# Output in pkg/ — copy to web client directory
|
||||
```
|
||||
|
||||
### Server Configuration
|
||||
|
||||
The server accepts two flags:
|
||||
|
||||
```bash
|
||||
warzone-server --bind 0.0.0.0:7700 --data-dir ./warzone-data
|
||||
```
|
||||
|
||||
| Flag | Default | Description |
|
||||
|--------------|-------------------|-----------------------|
|
||||
| `--bind` | `0.0.0.0:7700` | Listen address |
|
||||
| `--data-dir` | `./warzone-data` | sled database path |
|
||||
|
||||
Environment variables:
|
||||
|
||||
| Variable | Default | Description |
|
||||
|-------------------------|----------|----------------------------|
|
||||
| `WARZONE_ADMIN_PASSWORD`| `admin` | Password for admin alias ops|
|
||||
| `RUST_LOG` | `info` | Log level filter |
|
||||
|
||||
---
|
||||
|
||||
## Quick Start
|
||||
|
||||
### CLI Quick Start
|
||||
|
||||
```bash
|
||||
# 1. Generate a new identity
|
||||
warzone init
|
||||
# → Prompts for passphrase
|
||||
# → Displays fingerprint and 24-word mnemonic
|
||||
# → SAVE THE MNEMONIC — it is your identity
|
||||
|
||||
# 2. Register with a server
|
||||
warzone register --server http://your-server:7700
|
||||
|
||||
# 3. Show your identity
|
||||
warzone info
|
||||
# Fingerprint: a3f8:c912:44be:7d01:...
|
||||
# Signing key: ...
|
||||
# Encryption key: ...
|
||||
|
||||
# 4. Send a message
|
||||
warzone send a3f8:c912:44be:7d01:... "Hello!" --server http://your-server:7700
|
||||
|
||||
# 5. Receive messages
|
||||
warzone recv --server http://your-server:7700
|
||||
|
||||
# 6. Launch interactive TUI
|
||||
warzone chat --server http://your-server:7700
|
||||
warzone chat a3f8:c912:44be:7d01:... --server http://your-server:7700
|
||||
warzone chat @alice --server http://your-server:7700
|
||||
```
|
||||
|
||||
### Web Client Quick Start
|
||||
|
||||
1. Navigate to the server URL in a browser (e.g., `http://your-server:7700`)
|
||||
2. The web client generates a new identity automatically on first visit
|
||||
3. Your seed is stored in `localStorage` — back it up via the displayed hex seed
|
||||
4. Use `/peer <fingerprint>` or `/peer @alias` to select a chat partner
|
||||
5. Type messages and press Enter to send
|
||||
|
||||
---
|
||||
|
||||
## CLI Commands
|
||||
|
||||
### `warzone init`
|
||||
|
||||
Generate a new identity (seed + keypair + pre-keys).
|
||||
|
||||
```bash
|
||||
$ warzone init
|
||||
Set passphrase (empty for no encryption): ****
|
||||
Confirm passphrase: ****
|
||||
|
||||
Your identity:
|
||||
Fingerprint: a3f8:c912:44be:7d01:9e5a:3b2c:7f80:12d4
|
||||
Mnemonic: abandon ability able about above absent absorb abstract ...
|
||||
|
||||
SAVE YOUR MNEMONIC — it is the ONLY way to recover your identity.
|
||||
```
|
||||
|
||||
The seed is stored at `~/.warzone/identity.seed`, encrypted with Argon2id + ChaCha20-Poly1305.
|
||||
|
||||
### `warzone recover <words...>`
|
||||
|
||||
Recover an identity from a BIP39 mnemonic.
|
||||
|
||||
```bash
|
||||
$ warzone recover abandon ability able about above absent absorb abstract ...
|
||||
Set passphrase (empty for no encryption): ****
|
||||
Confirm passphrase: ****
|
||||
Identity recovered. Fingerprint: a3f8:c912:44be:7d01:9e5a:3b2c:7f80:12d4
|
||||
```
|
||||
|
||||
### `warzone info`
|
||||
|
||||
Display your fingerprint and public keys.
|
||||
|
||||
```bash
|
||||
$ warzone info
|
||||
Fingerprint: a3f8:c912:44be:7d01:9e5a:3b2c:7f80:12d4
|
||||
Signing key: 3a7b...
|
||||
Encryption key: 9f2c...
|
||||
```
|
||||
|
||||
### `warzone eth`
|
||||
|
||||
Display your Ethereum-compatible address derived from the same seed.
|
||||
|
||||
```bash
|
||||
$ warzone eth
|
||||
Warzone fingerprint: a3f8:c912:44be:7d01:9e5a:3b2c:7f80:12d4
|
||||
Ethereum address: 0x71C7656EC7ab88b098defB751B7401B5f6d8976F
|
||||
Same seed, dual identity.
|
||||
```
|
||||
|
||||
### `warzone register`
|
||||
|
||||
Register your pre-key bundle with a server.
|
||||
|
||||
```bash
|
||||
$ warzone register --server http://localhost:7700
|
||||
```
|
||||
|
||||
### `warzone send <recipient> <message>`
|
||||
|
||||
Send an encrypted message. Recipient can be a fingerprint or `@alias`.
|
||||
|
||||
```bash
|
||||
$ warzone send a3f8:c912:44be:7d01:... "Hello!" --server http://localhost:7700
|
||||
$ warzone send @alice "Hello!" --server http://localhost:7700
|
||||
```
|
||||
|
||||
### `warzone recv`
|
||||
|
||||
Poll the server for messages and decrypt them.
|
||||
|
||||
```bash
|
||||
$ warzone recv --server http://localhost:7700
|
||||
```
|
||||
|
||||
### `warzone chat [peer]`
|
||||
|
||||
Launch the interactive TUI client.
|
||||
|
||||
```bash
|
||||
$ warzone chat --server http://localhost:7700
|
||||
$ warzone chat @alice --server http://localhost:7700
|
||||
$ warzone chat a3f8:c912:... --server http://localhost:7700
|
||||
```
|
||||
|
||||
### `warzone backup [output]`
|
||||
|
||||
Export an encrypted backup of local data (sessions, pre-keys).
|
||||
|
||||
```bash
|
||||
$ warzone backup my-backup.wzb
|
||||
Backup saved to my-backup.wzb (4096 bytes encrypted)
|
||||
```
|
||||
|
||||
The backup is encrypted with your seed via HKDF(info="warzone-history") + ChaCha20-Poly1305.
|
||||
|
||||
### `warzone restore <input>`
|
||||
|
||||
Restore from an encrypted backup. Requires the same seed.
|
||||
|
||||
```bash
|
||||
$ warzone restore my-backup.wzb
|
||||
Restored 12 entries from my-backup.wzb
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## TUI Commands
|
||||
|
||||
The TUI is launched with `warzone chat`. All commands start with `/`.
|
||||
|
||||
### Peer & Navigation
|
||||
|
||||
| Command | Short | Description |
|
||||
|------------------------|-------|----------------------------------------------|
|
||||
| `/peer <fp_or_alias>` | `/p` | Set the active peer (fingerprint or @alias) |
|
||||
| `/dm` | | Switch to DM mode (clear group context) |
|
||||
| `/r` or `/reply` | | Switch to last person who DM'd you |
|
||||
| `/info` | | Display your fingerprint |
|
||||
| `/eth` | | Display your Ethereum address |
|
||||
| `/quit` | `/q` | Exit the TUI |
|
||||
|
||||
```
|
||||
/peer a3f8:c912:44be:7d01:9e5a:3b2c:7f80:12d4
|
||||
/p @alice
|
||||
/r
|
||||
/dm
|
||||
```
|
||||
|
||||
### Alias Management
|
||||
|
||||
| Command | Description |
|
||||
|-----------------------|--------------------------------------------|
|
||||
| `/alias <name>` | Register an alias for your fingerprint |
|
||||
| `/unalias` | Remove your alias |
|
||||
| `/aliases` | List all registered aliases |
|
||||
|
||||
```
|
||||
/alias alice
|
||||
/unalias
|
||||
/aliases
|
||||
```
|
||||
|
||||
When you register an alias, the server returns a **recovery key** (32 hex chars). Save it — it is the only way to reclaim the alias if you lose access to your identity.
|
||||
|
||||
### Contacts & History
|
||||
|
||||
| Command | Short | Description |
|
||||
|------------------------|-------|------------------------------------------|
|
||||
| `/contacts` | `/c` | List all contacts with message counts |
|
||||
| `/history [peer]` | `/h` | Show message history (last 50 messages) |
|
||||
|
||||
```
|
||||
/contacts
|
||||
/c
|
||||
/history a3f8c91244be7d01
|
||||
/h
|
||||
```
|
||||
|
||||
If a peer is already set, `/h` without arguments shows that peer's history.
|
||||
|
||||
### Group Commands
|
||||
|
||||
| Command | Description |
|
||||
|------------------------|------------------------------------------|
|
||||
| `/g <name>` | Switch to group (auto-join) |
|
||||
| `/gcreate <name>` | Create a new group |
|
||||
| `/gjoin <name>` | Join an existing group |
|
||||
| `/gleave` | Leave the current group |
|
||||
| `/gkick <fp_or_alias>` | Kick a member (creator only) |
|
||||
| `/gmembers` | List members of the current group |
|
||||
| `/glist` | List all groups on the server |
|
||||
|
||||
```
|
||||
/gcreate ops-team
|
||||
/g ops-team
|
||||
/gjoin ops-team
|
||||
/gmembers
|
||||
/gkick a3f8c91244be7d01
|
||||
/gleave
|
||||
/glist
|
||||
```
|
||||
|
||||
Group messages are prefixed with `#groupname` in the UI. The current target shows in the header bar.
|
||||
|
||||
### File Transfer
|
||||
|
||||
| Command | Description |
|
||||
|-------------------|----------------------------------------------|
|
||||
| `/file <path>` | Send a file to the current peer or group |
|
||||
|
||||
```
|
||||
/file /path/to/document.pdf
|
||||
/file ./photo.jpg
|
||||
```
|
||||
|
||||
Constraints:
|
||||
- Maximum file size: 10 MB
|
||||
- Chunk size: 64 KB
|
||||
- Files are sent as `FileHeader` + `FileChunk` wire messages
|
||||
- SHA-256 verification on receipt
|
||||
- Received files are saved to the current directory
|
||||
|
||||
### Input Editing
|
||||
|
||||
The TUI supports full readline-style editing:
|
||||
|
||||
| Key | Action |
|
||||
|-----------------|------------------------------|
|
||||
| Left/Right | Move cursor |
|
||||
| Home / Ctrl-A | Move to beginning of line |
|
||||
| End / Ctrl-E | Move to end of line |
|
||||
| Backspace | Delete character before cursor|
|
||||
| Delete | Delete character at cursor |
|
||||
| Ctrl-U | Clear entire input |
|
||||
| Ctrl-W | Delete word before cursor |
|
||||
| Enter | Send message / execute command|
|
||||
| Ctrl-C | Quit |
|
||||
|
||||
### Receipt Indicators
|
||||
|
||||
Sent messages display receipt status:
|
||||
|
||||
| Indicator | Meaning |
|
||||
|-----------|----------------------------|
|
||||
| (tick) | Sent (no confirmation yet) |
|
||||
| (double tick, gray) | Delivered (decrypted by recipient) |
|
||||
| (double tick, blue) | Read (viewed by recipient) |
|
||||
|
||||
---
|
||||
|
||||
## Web Client Commands
|
||||
|
||||
The web client supports the same commands as the TUI, plus additional web-specific commands:
|
||||
|
||||
### Standard Commands (same as TUI)
|
||||
|
||||
`/peer`, `/p`, `/alias`, `/unalias`, `/r`, `/reply`, `/contacts`, `/c`,
|
||||
`/history`, `/h`, `/g`, `/gcreate`, `/gjoin`, `/gleave`, `/gkick`, `/gmembers`,
|
||||
`/glist`, `/file`, `/eth`, `/info`, `/quit`, `/dm`
|
||||
|
||||
### Alias Resolution
|
||||
|
||||
Both TUI and web support `@alias` syntax:
|
||||
|
||||
```
|
||||
/peer @alice # Resolves alias to fingerprint
|
||||
/p @bob # Short form
|
||||
```
|
||||
|
||||
### Web-Only Commands
|
||||
|
||||
| Command | Description |
|
||||
|-------------------|----------------------------------------------------|
|
||||
| `/selftest` | Run WASM crypto self-test (X3DH + ratchet cycle) |
|
||||
| `/bundleinfo` | Debug: show bundle details (keys, sizes) |
|
||||
| `/debug` | Toggle debug mode (verbose output) |
|
||||
| `/reset` | Clear identity and all local data |
|
||||
| `/install` | Show PWA installation instructions |
|
||||
| `/sessions` | List active ratchet sessions |
|
||||
| `/admin-unalias` | Admin: remove any alias (requires admin password) |
|
||||
|
||||
### Web Client Storage
|
||||
|
||||
The web client stores data in `localStorage`:
|
||||
|
||||
| Key | Value | Purpose |
|
||||
|----------------------|--------------------------------|----------------------------|
|
||||
| `wz_seed` | hex seed (64 chars) | Identity seed |
|
||||
| `wz_spk_secret` | hex SPK secret (64 chars) | Signed pre-key secret |
|
||||
| `wz_session:<fp>` | base64 ratchet state | Per-peer session |
|
||||
| `wz_contacts` | JSON contact list | Contact metadata |
|
||||
|
||||
---
|
||||
|
||||
## Identity Management
|
||||
|
||||
### Seed
|
||||
|
||||
Your identity is a 32-byte seed. All keys are deterministically derived from it. **Lose the seed = lose the identity forever.**
|
||||
|
||||
### Mnemonic Backup
|
||||
|
||||
The seed is displayed as a 24-word BIP39 mnemonic during `warzone init`. Write it down on paper and store securely. You can recover your full identity from the mnemonic using `warzone recover`.
|
||||
|
||||
### Passphrase Encryption
|
||||
|
||||
The seed file (`~/.warzone/identity.seed`) is encrypted at rest:
|
||||
|
||||
```
|
||||
File format: WZS1(4 bytes) + salt(16) + nonce(12) + ciphertext(48)
|
||||
|
||||
Encryption: Argon2id(passphrase, salt) → 32-byte key
|
||||
ChaCha20-Poly1305(key, nonce, seed) → ciphertext
|
||||
```
|
||||
|
||||
An empty passphrase stores the seed in plaintext (for testing only).
|
||||
|
||||
### Ethereum Address
|
||||
|
||||
Your Ethereum address is derived from the same seed with domain-separated HKDF. Use `warzone eth` or `/eth` in the TUI to display it.
|
||||
|
||||
### Fingerprint Format
|
||||
|
||||
Fingerprints are `SHA-256(Ed25519_pubkey)[:16]` displayed as 8 groups of 4 hex digits:
|
||||
|
||||
```
|
||||
a3f8:c912:44be:7d01:9e5a:3b2c:7f80:12d4
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Alias System
|
||||
|
||||
Aliases provide human-readable names for fingerprints.
|
||||
|
||||
### Registration
|
||||
|
||||
```
|
||||
/alias alice
|
||||
```
|
||||
|
||||
Returns a **recovery key** — save it securely. One alias per fingerprint. One fingerprint per alias.
|
||||
|
||||
### Rules
|
||||
|
||||
- Aliases are 1-32 alphanumeric characters (plus `_` and `-`)
|
||||
- Case-insensitive, normalized to lowercase
|
||||
- TTL: 365 days of inactivity (auto-renewed on any message activity)
|
||||
- Grace period: 30 days after expiry before reclamation
|
||||
- Recovery key: allows reclaiming an expired alias
|
||||
|
||||
### Recovery
|
||||
|
||||
If you lose access to your identity but have the recovery key, the server provides an alias recovery endpoint. This is an HTTP API operation:
|
||||
|
||||
```
|
||||
POST /v1/alias/recover
|
||||
{
|
||||
"alias": "alice",
|
||||
"recovery_key": "a1b2c3...",
|
||||
"new_fingerprint": "new_fp_hex"
|
||||
}
|
||||
```
|
||||
|
||||
The recovery key is rotated on each recovery.
|
||||
|
||||
### Admin Operations
|
||||
|
||||
An admin (with `WARZONE_ADMIN_PASSWORD`) can remove any alias:
|
||||
|
||||
```
|
||||
POST /v1/alias/admin-remove
|
||||
{
|
||||
"alias": "alice",
|
||||
"admin_password": "admin"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Group Management
|
||||
|
||||
### Creating and Joining
|
||||
|
||||
```
|
||||
/gcreate ops-team # Create a group (you become creator)
|
||||
/g ops-team # Switch to group (auto-joins if not a member)
|
||||
/gjoin ops-team # Explicitly join
|
||||
```
|
||||
|
||||
Groups auto-create on first join if they do not exist.
|
||||
|
||||
### Messaging
|
||||
|
||||
When the peer is set to a group (shows as `#groupname` in the header), all messages go to that group. The server fans out to all members.
|
||||
|
||||
### Membership
|
||||
|
||||
- Creator can kick members with `/gkick <fingerprint>`
|
||||
- Any member can leave with `/gleave`
|
||||
- `/gmembers` shows all members with their aliases (if registered)
|
||||
|
||||
### Sender Keys (Implemented in Protocol)
|
||||
|
||||
The protocol implements Sender Keys for efficient group encryption:
|
||||
|
||||
1. Each member generates a `SenderKey` (random 32-byte chain key)
|
||||
2. The key is distributed to all members via 1:1 encrypted channels (`SenderKeyDistribution`)
|
||||
3. Group messages are encrypted once with the sender's key (`GroupSenderKey`)
|
||||
4. On member join/leave, all members rotate their sender keys
|
||||
|
||||
This provides O(1) encryption per message instead of O(N) per-member encryption.
|
||||
|
||||
---
|
||||
|
||||
## Multi-Device Setup
|
||||
|
||||
### Current Support
|
||||
|
||||
The server stores per-device bundles (`device:<fp>:<device_id>`). Multiple WebSocket connections per fingerprint are supported — all connected devices receive messages.
|
||||
|
||||
### Setting Up a Second Device
|
||||
|
||||
1. On the new device, recover from mnemonic: `warzone recover <24 words>`
|
||||
2. Register with the server: `warzone register --server http://...`
|
||||
3. Both devices now share the same fingerprint and receive messages
|
||||
|
||||
### Limitations
|
||||
|
||||
- Ratchet sessions are per-device (not synchronized between devices)
|
||||
- Starting a new session on one device does not invalidate the other's session
|
||||
- Encrypted backup/restore can transfer session state between devices
|
||||
|
||||
---
|
||||
|
||||
## Encrypted Backup & Restore
|
||||
|
||||
### Creating a Backup
|
||||
|
||||
```bash
|
||||
warzone backup my-backup.wzb
|
||||
```
|
||||
|
||||
This exports:
|
||||
- All ratchet sessions (Double Ratchet state)
|
||||
- All pre-key secrets (signed + one-time)
|
||||
- Encrypted with HKDF(seed, info="warzone-history") + ChaCha20-Poly1305
|
||||
|
||||
### Restoring a Backup
|
||||
|
||||
```bash
|
||||
warzone restore my-backup.wzb
|
||||
```
|
||||
|
||||
Requires the same seed (passphrase prompt). Merges data without overwriting existing entries.
|
||||
|
||||
### Backup File Format
|
||||
|
||||
```
|
||||
WZH1(4 bytes) + nonce(12) + ciphertext
|
||||
|
||||
Plaintext: JSON {
|
||||
"version": 1,
|
||||
"sessions": { "<fp>": "base64_bincode", ... },
|
||||
"pre_keys": { "spk:1": "base64_bytes", "otpk:1": "base64_bytes", ... }
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user