feat: friend list, bot API, ETH addressing, deep links, docs overhaul

Tier 1 — New features:
- E2E encrypted friend list: server stores opaque blob (POST/GET /v1/friends),
  protocol-level encrypt/decrypt with HKDF-derived key, 4 tests
- Telegram Bot API compatibility: /bot/register, /bot/:token/getUpdates,
  sendMessage, getMe — TG-style Update objects with proper message mapping
- ETH address resolution: GET /v1/resolve/:address (0x.../alias/@.../fp),
  bidirectional ETH↔fp mapping stored on key registration
- Seed recovery: /seed command in TUI + web client
- URL deep links: /message/@alias, /message/0xABC, /group/#ops
- Group members with online status in GET /groups/:name/members

Tier 2 — UX polish:
- TUI: /friend, /friend <addr>, /unfriend <addr> with presence checking
- Web: friend commands, showGroupMembers() on group join
- Web: ETH address in header, clickable addresses (click→peer or copy)
- Bot: full WireMessage→TG Update mapping (encrypted base64, CallSignal,
  FileHeader, bot_message JSON)

Documentation:
- USAGE.md rewritten: complete user guide with all commands
- SERVER.md rewritten: full admin guide with all 50+ endpoints
- CLIENT.md rewritten: architecture, commands, keyboard, storage
- LLM_HELP.md created: 1083-word token-optimized reference for helper LLM

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Siavash Sameni
2026-03-29 07:31:54 +04:00
parent dbf5d136cf
commit 7b72f7cba5
15 changed files with 2181 additions and 1023 deletions

View File

@@ -1,5 +1,7 @@
# Warzone Client -- Operation Guide
**Version:** 0.0.21
---
## 1. Installation
@@ -21,313 +23,509 @@ The binary is at `target/release/warzone`. You can copy it anywhere or add
cargo install --path crates/warzone-client
```
---
### Build the WASM Module (Web Client)
## 2. Quick Start
Requires wasm-pack.
```bash
# 1. Generate a new identity
warzone init
# 2. Register your key bundle with a server
warzone register -s http://wz.example.com:7700
# 3. Send an encrypted message
warzone send a3f8:c912:44be:7d01 "Hello from Warzone" -s http://wz.example.com:7700
# 4. Poll for incoming messages
warzone recv -s http://wz.example.com:7700
cd crates/warzone-wasm
wasm-pack build --target web
# Output in pkg/ — copy to web client directory
```
---
## 3. CLI Commands
## 2. TUI Architecture
### warzone init
The interactive client is built on **ratatui** (rendering) and **crossterm**
(terminal I/O). The event loop polls at **100 ms** intervals, giving a
responsive feel without busy-waiting.
Generate a new identity (seed, keypair, and pre-keys).
### Module Layout
The TUI lives in `crates/warzone-client/src/tui/` and is split into seven
modules:
| Module | Responsibility |
|-----------------|---------------------------------------------------------|
| `types` | Core data structures: `App`, `ChatLine`, `ReceiptStatus`, `PendingFileTransfer`, constants (`MAX_FILE_SIZE`, `CHUNK_SIZE`) |
| `draw` | Rendering: header bar, message list with timestamps and receipt indicators, input box with unread badge, scroll windowing |
| `commands` | All `/`-prefixed command handlers (peer, alias, group, file, history, friends, devices, etc.) and message send logic |
| `input` | Key event dispatch: text editing, cursor movement, scroll, quit |
| `file_transfer` | Chunked file send: reads file, SHA-256 hash, splits into 64 KB encrypted chunks |
| `network` | WebSocket receive loop (with HTTP polling fallback), incoming message decryption, receipt handling, session auto-recovery |
| `mod` | Public entry point `run_tui()`: sets up terminal, spawns network task, runs the 100 ms event loop |
### Event Loop
```
loop {
terminal.draw(app) // ratatui render pass
if event::poll(100ms) { // crossterm poll
handle key event // Enter → send; everything else → input.rs
}
if app.should_quit { break }
}
```
Messages arrive asynchronously on a background tokio task (`network::poll_loop`)
and are pushed into a shared `Arc<Mutex<Vec<ChatLine>>>`.
---
## 3. CLI Subcommands
### `warzone init`
Generate a new identity (seed, keypair, pre-keys).
```bash
$ warzone init
Identity generated!
Set passphrase (empty for no encryption): ****
Confirm passphrase: ****
Fingerprint: b7d1:e845:0022:9f3a
Your identity:
Fingerprint: a3f8:c912:44be:7d01:9e5a:3b2c:7f80:12d4
Mnemonic: abandon ability able about above absent absorb abstract ...
Recovery mnemonic (WRITE THIS DOWN):
1. abandon 2. ability 3. able 4. about
5. above 6. absent 7. absorb 8. abstract
9. absurd 10. abuse 11. access 12. accident
13. account 14. accuse 15. achieve 16. acid
17. acoustic 18. acquire 19. across 20. act
21. action 22. actor 23. actress 24. actual
Seed saved to ~/.warzone/identity.seed
Generated 1 signed pre-key + 10 one-time pre-keys
To register with a server, run:
warzone send <recipient-fingerprint> <message> -s http://server:7700
Or register your key bundle manually:
(bundle auto-registered on first send)
SAVE YOUR MNEMONIC — it is the ONLY way to recover your identity.
```
**What happens:**
1. Generates 32 random bytes (seed) from `OsRng`.
2. Derives Ed25519 signing key and X25519 encryption key from the seed.
3. Converts seed to a 24-word BIP39 mnemonic and displays it.
4. Saves the raw seed to `~/.warzone/identity.seed` (mode 0600 on Unix).
4. Prompts for a passphrase. Encrypts the seed with Argon2id + ChaCha20-Poly1305
and saves to `~/.warzone/identity.seed` (mode 0600 on Unix). An empty
passphrase stores the seed in plaintext.
5. Generates 1 signed pre-key (id=1) and 10 one-time pre-keys (ids 0-9).
6. Stores pre-key secrets in the local sled database at `~/.warzone/db/`.
7. Saves the public pre-key bundle to `~/.warzone/bundle.bin`.
---
### warzone recover \<words...\>
### `warzone recover <words...>`
Recover an identity from a BIP39 mnemonic.
Recover an identity from a 24-word BIP39 mnemonic.
```bash
$ warzone recover abandon ability able about above absent absorb abstract \
absurd abuse access accident account accuse achieve acid \
acoustic acquire across act action actor actress actual
Identity recovered!
Fingerprint: b7d1:e845:0022:9f3a
Seed saved to ~/.warzone/identity.seed
Set passphrase (empty for no encryption): ****
Confirm passphrase: ****
Identity recovered. Fingerprint: a3f8:c912:44be:7d01:9e5a:3b2c:7f80:12d4
```
**Note:** recovery restores the seed and keypair but does NOT restore
pre-keys or sessions. You will need to run `warzone init`-style pre-key
generation separately or your contacts will need to re-establish sessions.
Recovery restores the seed and keypair. Pre-keys and sessions are NOT restored;
contacts will need to re-establish sessions.
---
### warzone info
### `warzone info`
Display your fingerprint and public keys.
```bash
$ warzone info
Fingerprint: b7d1:e845:0022:9f3a
Signing key: 3a7c... (64 hex chars)
Encryption key: 9d2f... (64 hex chars)
Fingerprint: a3f8:c912:44be:7d01:9e5a:3b2c:7f80:12d4
Signing key: 3a7b... (64 hex chars)
Encryption key: 9f2c... (64 hex chars)
```
Requires a saved identity (`~/.warzone/identity.seed`).
---
### warzone register
### `warzone tui` / `warzone chat [peer]`
Register your pre-key bundle with a server.
Launch the interactive TUI client.
```bash
$ warzone register -s http://wz.example.com:7700
Bundle registered with http://wz.example.com:7700
$ warzone chat --server http://wz.example.com:7700
$ warzone chat a3f8:c912:44be:7d01:... --server http://wz.example.com:7700
$ warzone chat @alice --server http://wz.example.com:7700
```
An optional `peer` argument (fingerprint or `@alias`) pre-sets the active
DM target.
**Flags:**
| Flag | Short | Default | Description |
|------|-------|---------|-------------|
| `--server` | `-s` | `http://localhost:7700` | Server URL |
This uploads `~/.warzone/bundle.bin` to the server. Registration is also
performed automatically on the first `send`.
| Flag | Short | Default | Description |
|------------|-------|-----------------------|--------------|
| `--server` | `-s` | `http://localhost:7700` | Server URL |
---
### warzone send
### `warzone send <recipient> <message>`
Send an encrypted message to a recipient.
Send an encrypted message. Recipient can be a fingerprint or `@alias`.
```bash
$ warzone send a3f8:c912:44be:7d01 "Hello, are you safe?" -s http://wz.example.com:7700
No existing session. Fetching key bundle for a3f8:c912:44be:7d01...
Bundle registered with http://wz.example.com:7700
Message sent to a3f8:c912:44be:7d01
$ warzone send a3f8:c912:44be:7d01:... "Hello!" --server http://wz.example.com:7700
$ warzone send @alice "Hello!" --server http://wz.example.com:7700
```
**Arguments:**
| Argument | Description |
|----------|-------------|
| `recipient` | Recipient fingerprint (e.g. `a3f8:c912:44be:7d01`) |
| `message` | Message text (quote if it contains spaces) |
**Flags:**
| Flag | Short | Default | Description |
|------|-------|---------|-------------|
| `--server` | `-s` | `http://localhost:7700` | Server URL |
**Behavior:**
1. Auto-registers your bundle with the server (if not already done).
1. Auto-registers your bundle with the server if needed.
2. Checks for an existing Double Ratchet session with the recipient.
3. If no session exists:
- Fetches recipient's pre-key bundle from the server.
- Verifies the signed pre-key signature.
- Performs X3DH key exchange.
- Initializes the Double Ratchet as Alice (initiator).
- Sends a `WireMessage::KeyExchange` containing the X3DH parameters
and the first encrypted message.
4. If a session exists:
- Encrypts using the existing ratchet.
- Sends a `WireMessage::Message`.
3. If no session: fetches the recipient's pre-key bundle, verifies the signed
pre-key signature, performs X3DH, initializes the ratchet as Alice, and
sends a `WireMessage::KeyExchange` containing the X3DH parameters and the
first encrypted message.
4. If a session exists: encrypts with the existing ratchet and sends a
`WireMessage::Message`.
5. Updates the local session state.
---
### warzone recv
### `warzone recv`
Poll for and decrypt incoming messages.
```bash
$ warzone recv -s http://wz.example.com:7700
Polling for messages as b7d1:e845:0022:9f3a...
Received 2 message(s):
[new session] a3f8:c912:44be:7d01: Hello, are you safe?
a3f8:c912:44be:7d01: I'm sending supplies tomorrow.
$ warzone recv --server http://wz.example.com:7700
```
**Flags:**
| Flag | Short | Default | Description |
|------|-------|---------|-------------|
| `--server` | `-s` | `http://localhost:7700` | Server URL |
**Behavior:**
1. Polls `/v1/messages/poll/{our_fingerprint}`.
2. For each message:
- Deserializes the `WireMessage` from bincode.
- **KeyExchange:** loads signed pre-key secret and (if applicable)
one-time pre-key secret from local storage, performs X3DH respond,
initializes ratchet as Bob, decrypts the message, and saves the session.
- **Message:** loads existing session, decrypts with the ratchet, saves
updated session state.
3. Prints decrypted messages to stdout.
**Note:** messages are currently NOT acknowledged after polling. They will
be returned again on the next poll. Acknowledgment is TODO.
Fetches messages from `/v1/messages/poll/{fingerprint}`, deserializes each
`WireMessage`, performs X3DH respond or ratchet decrypt as appropriate, and
prints plaintext to stdout.
---
### warzone chat
### `warzone backup [output]`
Launch the interactive TUI.
Export an encrypted backup of local data (sessions, pre-keys).
```bash
$ warzone chat -s http://wz.example.com:7700
TODO: launch TUI connected to http://wz.example.com:7700
$ warzone backup my-backup.wzb
Backup saved to my-backup.wzb (4096 bytes encrypted)
```
**Status:** not yet implemented. The TUI will use `ratatui` and `crossterm`
(dependencies are already in `Cargo.toml`). Planned for Phase 2.
The backup is encrypted with `HKDF(seed, info="warzone-history")` +
ChaCha20-Poly1305.
**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", ... }
}
```
---
## 4. Identity Management
### `warzone restore <input>`
### Storage Layout
```
~/.warzone/
identity.seed # 32-byte raw seed (plaintext -- encryption is TODO)
bundle.bin # bincode-serialized PreKeyBundle (public data)
db/ # sled database directory
sessions/ # Double Ratchet state per peer
pre_keys/ # signed and one-time pre-key secrets
```
### File Permissions
On Unix, `identity.seed` is created with mode `0600` (owner read/write only).
The sled database directory inherits default permissions.
### Seed Security
**Current state:** the seed is stored as **plaintext** 32 bytes. This is a
known Phase 1 limitation.
**Planned (Phase 2):** encrypt the seed at rest using:
- Passphrase input at startup
- Argon2id key derivation from passphrase
- ChaCha20-Poly1305 encryption of the seed bytes
### Mnemonic Backup
The 24-word BIP39 mnemonic shown during `init` is the ONLY way to recover
your identity if you lose `~/.warzone/`. Write it down on paper and store it
securely.
The mnemonic is displayed once at generation time and can be recovered from
the seed using the protocol library, but the CLI does not currently expose a
"show mnemonic" command.
### Recovery
Restore from an encrypted backup. Requires the same seed (passphrase prompt).
```bash
warzone recover word1 word2 word3 ... word24
$ warzone restore my-backup.wzb
Restored 12 entries from my-backup.wzb
```
This recreates `~/.warzone/identity.seed` with the same seed. The same
fingerprint and keypairs are derived deterministically. However:
- Pre-keys are NOT regenerated. Run `warzone init` on a fresh directory to
generate new pre-keys (this will also generate a new seed, so you would need
to coordinate).
- Sessions are NOT recovered. All contacts will need to establish new sessions.
**TODO:** a `recover` flow that also regenerates pre-keys without creating a
new seed.
Merges data without overwriting existing entries.
---
## 5. Web Client
## 4. TUI Features
The web client is served by the server at `/`. Open it in a browser:
### Message Timestamps
```
http://localhost:7700/
```
Every message is rendered with a `[HH:MM]` prefix in dark gray, derived from
`chrono::Local::now()` at receive/send time.
### Features
### Message Scrolling
- **Generate New Identity:** creates a random 32-byte seed in the browser.
- **Recover from Mnemonic:** paste a hex-encoded seed (not BIP39 words;
hex encoding is used as a placeholder).
- **Chat interface:** dark-themed monospace UI with message display.
- **Commands:**
- `/help` -- show available commands
- `/info` -- show your fingerprint
- `/seed` -- display your seed (hex-encoded)
The message area supports scrolling with a "pinned to bottom" model:
- `scroll_offset = 0` means the newest messages are visible.
- Scrolling up increases the offset; scrolling down decreases it.
- The visible window is computed as `items[total - offset - height .. total - offset]`.
### Connection Status Indicator
The header bar displays a colored dot after the server URL:
- Green dot: WebSocket connection active.
- Red dot: disconnected (HTTP polling fallback or reconnecting).
### Unread Badge
When `scroll_offset > 0`, the input box title changes from `" message "` to
`" [N new] "` showing how many messages are below the current scroll position.
This makes it obvious that new content has arrived while reading history.
### Terminal Bell
A terminal bell (`\x07`) is emitted on every incoming DM (both `KeyExchange`
and `Message` wire types). This triggers a system notification in most terminal
emulators.
### Receipt Indicators
Sent messages display delivery status after the message text:
| Indicator | Meaning |
|-----------------|----------------------------------------|
| Single tick | Sent (no confirmation yet) |
| Double tick | Delivered (decrypted by recipient) |
| Double tick blue| Read (viewed by recipient) |
### Session Auto-Recovery
When decryption fails on an incoming message, the TUI automatically:
1. Deletes the corrupted session from the local database.
2. Displays a system message: `[session reset] Decryption failed for <fp>. Session cleared -- next message will re-establish.`
The next incoming `KeyExchange` from that peer will create a fresh session
without manual intervention.
---
## 5. Full Command Reference
All commands start with `/` and are entered in the TUI input box.
### Peer and Navigation
| Command | Short | Description |
|------------------------|---------|----------------------------------------------|
| `/peer <fp_or_alias>` | `/p` | Set the active DM peer (fingerprint or @alias) |
| `/dm` | | Switch to DM mode (clear group context) |
| `/reply` | `/r` | Switch to the last person who DM'd you |
| `/info` | | Display your fingerprint |
| `/eth` | | Display your Ethereum address (derived from seed) |
| `/seed` | | Display your 24-word recovery mnemonic |
| `/quit` | `/q` | Exit the TUI |
| `/help` | `/?` | Show the built-in help text |
### Alias Management
| Command | Description |
|-----------------------|--------------------------------------------|
| `/alias <name>` | Register an alias for your fingerprint. Returns a recovery key -- save it. |
| `/unalias` | Remove your alias from the server |
| `/aliases` | List all registered aliases on the server |
Alias rules: 1-32 alphanumeric characters (plus `_` and `-`), case-insensitive,
normalized to lowercase. TTL is 365 days of inactivity with a 30-day grace
period before reclamation.
### Contacts and History
| Command | Short | Description |
|------------------------|---------|------------------------------------------|
| `/contacts` | `/c` | List all contacts with message counts |
| `/history [peer]` | `/h` | Show message history (last 50 messages). Uses current peer if set. |
### Group Commands
| Command | Description |
|-------------------------|------------------------------------------|
| `/g <name>` | Switch to group (auto-join if needed) |
| `/gcreate <name>` | Create a new group (you become creator) |
| `/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 |
Group messages use Sender Keys for O(1) encryption per message. Each member
generates a `SenderKey` distributed via 1:1 encrypted channels. Keys rotate on
member join/leave.
### File Transfer
| Command | Description |
|-------------------|----------------------------------------------|
| `/file <path>` | Send a file to the current peer or group |
Constraints:
- Maximum file size: 10 MB
- Chunk size: 64 KB
- Files are sent as `FileHeader` + encrypted `FileChunk` wire messages
- SHA-256 verification on receipt
- Received files are saved to `~/.warzone/downloads/`
### Device Management
| Command | Description |
|-----------------------|------------------------------------------|
| `/devices` | List your active device sessions |
| `/kick <device_id>` | Kick a specific device session |
---
## 6. Keyboard Shortcuts
### Text Editing
| Key | Action |
|------------------|---------------------------------|
| Left / Right | Move cursor one character |
| 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 line |
| Ctrl+K | Kill from cursor to end of line |
| Ctrl+W | Delete word before cursor |
| Alt+Backspace | Delete word before cursor |
| Alt+Left | Jump one word left |
| Alt+Right | Jump one word right |
### Scrolling
| Key | Action |
|------------------|------------------------------------------|
| PageUp | Scroll up 10 messages |
| PageDown | Scroll down 10 messages |
| Up | Scroll up 1 message (when input is empty)|
| Down | Scroll down 1 message (when input is empty)|
| End | Snap to bottom (when input is empty) |
| Ctrl+End | Snap to bottom (always) |
### Quit
| Key | Action |
|------------------|---------|
| Ctrl+C | Quit |
| Esc | Quit |
---
## 7. Friend List
The friend list is an E2E encrypted contact list stored on the server as an
opaque blob. The server never sees the plaintext.
### Encryption
- Key derivation: `HKDF(seed, info="warzone-friends")` produces a 32-byte key.
- Encryption: ChaCha20-Poly1305 with AAD `"warzone-friends-aad"`.
- Plaintext format: JSON-serialized `FriendList` containing address, alias,
and `added_at` timestamp per friend.
### Commands
| Command | Description |
|------------------------|------------------------------------------------|
| `/friend` | List all friends with online/offline presence |
| `/friend <address>` | Add a friend (fingerprint or ETH address) |
| `/unfriend <address>` | Remove a friend |
When listing friends, the TUI queries the server's presence endpoint for each
friend to show real-time online/offline status.
### How It Works
1. Seed is generated with `crypto.getRandomValues(32)`.
2. ECDH P-256 keypair is derived (not X25519 -- Web Crypto limitation).
3. Fingerprint is `SHA-256(ECDH_public_key)[0..16]` formatted as 4 hex
groups.
4. Seed is saved in `localStorage` under key `wz-seed`.
5. On page load, the client tries to auto-load a saved seed.
6. Public key is registered with the server via `POST /v1/keys/register`.
7. Messages are polled every 5 seconds from `/v1/messages/poll/{fingerprint}`.
1. On `/friend <address>`: the client fetches the current encrypted blob from
the server, decrypts it, adds the entry, re-encrypts, and uploads.
2. On `/unfriend <address>`: same fetch-decrypt-modify-encrypt-upload cycle.
3. On `/friend` (no argument): fetches and decrypts the blob, then checks
`/v1/presence/<fp>` for each friend.
### Limitations
- **No cross-client compatibility:** the web client uses P-256 while the CLI
uses X25519/Ed25519. Messages between the two cannot be decrypted. This
will be resolved in Phase 2 (WASM port of the protocol library).
- **No Double Ratchet:** message decryption is not implemented in JS.
Received messages display as `[encrypted message]`.
- **No BIP39:** seed is shown as hex bytes, not mnemonic words.
- **Unencrypted seed storage:** `localStorage` is accessible to any JS on
the same origin.
The server stores the blob at `POST /v1/friends` and returns it at
`GET /v1/friends`. It has no knowledge of the contents.
---
## 6. Session Management
## 8. Local Storage
### Directory Layout
```
~/.warzone/
identity.seed # Encrypted seed (Argon2id + ChaCha20-Poly1305)
bundle.bin # bincode-serialized PreKeyBundle (public data)
db/ # sled database directory
sessions/ # Double Ratchet state per peer (keyed by hex fingerprint)
pre_keys/ # Signed and one-time pre-key secrets
contacts/ # Contact metadata and message counts
history/ # Message history per peer
sender_keys/ # Sender Key state for group encryption
downloads/ # Received files from /file transfers
```
### Seed Encryption
The seed file uses a fixed format:
```
WZS1 (4 bytes magic) + salt (16) + nonce (12) + ciphertext (48)
Encryption: Argon2id(passphrase, salt) -> 32-byte key
ChaCha20-Poly1305(key, nonce, seed) -> ciphertext
```
An empty passphrase at `init` time stores the seed in plaintext (for testing
only). The seed file is created with mode `0600` (owner read/write) on Unix.
### Mnemonic Backup
The 24-word BIP39 mnemonic shown during `init` is the only way to recover
your identity if you lose `~/.warzone/`. Write it down on paper. You can also
view it later with `/seed` in the TUI.
---
## 9. Web Client
The web client is served by the server at `/` and uses a **WASM bridge**
(`warzone-wasm`) that exposes the exact same cryptographic primitives as the
CLI: X25519, ChaCha20-Poly1305, X3DH, Double Ratchet.
### Features
- **Same crypto as TUI:** the WASM module wraps `warzone-protocol` directly,
so web-to-CLI interoperability is fully supported.
- **URL deep links:** paths like `/message/@alias`, `/message/0xABC`, and
`/group/#ops` auto-navigate to the corresponding conversation.
- **Clickable addresses:** fingerprints and aliases in the chat are rendered
as interactive links.
- **Service worker cache:** all shell assets (`/`, WASM JS, WASM binary,
manifest, icon) are cached by a versioned service worker (`wz-v2`). The
cache name is bumped on updates to force refresh.
- **PWA support:** includes a manifest and install prompt (`/install` command).
- **BIP39 mnemonic:** seed is displayed as 24 words via the WASM bridge
(not hex).
### 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
Data is stored 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 |
---
## 10. Session Management
### How Sessions Work
@@ -336,172 +534,63 @@ by their fingerprint.
1. **First message to a peer:** X3DH key exchange establishes a shared secret.
The ratchet is initialized. The session is saved in `~/.warzone/db/`
under the `sessions` tree, keyed by the peer's fingerprint (hex-encoded).
under the `sessions` tree, keyed by the peer's hex fingerprint.
2. **Subsequent messages:** the ratchet state is loaded, used to encrypt or
decrypt, then saved back.
3. **Bidirectional:** both parties maintain the same session. When Bob
receives Alice's KeyExchange, he initializes his side of the ratchet. From
then on, both use `WireMessage::Message`.
3. **Bidirectional:** when Bob receives Alice's `KeyExchange`, he initializes
his side. From then on, both use `WireMessage::Message`.
### Session Storage
### Session Auto-Recovery
Sessions are serialized with `bincode` and stored in the `sessions` sled
tree. The key is the peer's 32-character hex fingerprint.
On decrypt failure, the TUI deletes the corrupted session and displays a
warning. The next incoming `KeyExchange` from that peer re-establishes the
session automatically. No manual intervention required.
### Session Reset
### Multi-Device
There is currently no command to reset a session. If a session becomes
corrupted or out of sync:
1. Delete the local database: `rm -rf ~/.warzone/db/`
2. Re-run `warzone init` to generate new pre-keys.
3. Re-register with the server.
4. Your contact must also reset their session with you.
**TODO (Phase 2):** a `warzone reset-session <fingerprint>` command.
The server stores per-device bundles (`device:<fp>:<device_id>`). Multiple
WebSocket connections per fingerprint are supported -- all connected devices
receive messages. Ratchet sessions are per-device and not synchronized; use
`warzone backup` / `warzone restore` to transfer session state.
---
## 7. Pre-Key Management
### What Are Pre-Keys
Pre-keys enable asynchronous session establishment. When Alice wants to
message Bob for the first time:
1. Alice fetches Bob's **pre-key bundle** from the server.
2. The bundle contains Bob's public identity key, a signed pre-key, and
optionally a one-time pre-key.
3. Alice uses these to perform X3DH without Bob being online.
### Pre-Key Types
| Type | Quantity | Lifetime | Purpose |
|------|----------|----------|---------|
| Signed pre-key | 1 (id=1) | Long-term (no rotation yet) | Medium-term DH key, signed by identity |
| One-time pre-keys | 10 (ids 0-9) | Single use | Consumed during X3DH, then deleted |
### When to Replenish
One-time pre-keys are consumed when someone initiates a session with you.
After all 10 are used, X3DH falls back to using only the signed pre-key
(DH4 is skipped), which provides slightly weaker security properties.
**Current state:** there is no automatic replenishment. You must manually
re-initialize if you expect many incoming new sessions.
**TODO (Phase 2):** the server will notify the client when one-time pre-key
supply is low, and the client will upload fresh ones automatically.
---
## 8. Security Model
### What Is Encrypted
- **Message body:** encrypted with ChaCha20-Poly1305 using per-message keys
from the Double Ratchet. Even the server cannot read it.
### What Is NOT Encrypted
- **Sender fingerprint:** visible to the server and anyone intercepting
traffic.
- **Recipient fingerprint:** visible to the server (needed for routing).
- **Message size:** visible to the server.
- **Timing:** when messages are sent and received.
- **IP addresses:** visible to the server and network observers.
- **Seed on disk:** stored as plaintext (encryption TODO).
### Threat Model
| Threat | Protected? | Notes |
|--------|-----------|-------|
| Server reads messages | Yes | E2E encryption; server sees only ciphertext |
| Network eavesdropper reads messages | Yes | E2E encryption |
| Server impersonates a user | Partially | Pre-key signatures prevent forgery of signed pre-keys, but the server could substitute a fake bundle (no key transparency yet) |
| Compromised past session key | Yes | Forward secrecy via chain ratchet; break-in recovery via DH ratchet |
| Stolen device (seed file) | No | Seed is plaintext on disk (encryption TODO) |
| Metadata analysis (who talks to whom) | No | Fingerprints visible to server |
| Active MITM on first contact | Partially | TOFU model; no out-of-band verification mechanism in the client yet |
| One-time pre-keys exhausted | Graceful degradation | X3DH works without OT pre-keys but with reduced replay protection |
### Trust Model
**Trust on first use (TOFU):** the first time you message someone, you trust
that the server returns their genuine pre-key bundle. There is no
verification step yet.
**Planned (Phase 3):** DNS-based key transparency where users publish
self-signed public keys in DNS TXT records, allowing cross-verification
independent of the server.
---
## 9. Troubleshooting
## 11. Troubleshooting
### "No identity found. Run `warzone init` first."
You haven't generated an identity, or `~/.warzone/identity.seed` is missing.
```bash
warzone init
```
`~/.warzone/identity.seed` is missing. Run `warzone init`.
### "No bundle found. Run `warzone init` first."
The pre-key bundle file `~/.warzone/bundle.bin` is missing. This happens if
you ran `recover` without a full `init`.
Re-run `warzone init` (this will generate a NEW identity). To keep your
recovered identity, you would need to manually regenerate pre-keys (not yet
supported as a standalone command).
`~/.warzone/bundle.bin` is missing. This happens if you ran `recover` without
regenerating pre-keys. Re-run `warzone init` (generates a new identity).
### "failed to fetch recipient's bundle. Are they registered?"
The recipient has not registered their pre-key bundle with the server, or
you are using the wrong server URL, or the fingerprint is incorrect.
- Verify the fingerprint (ask the recipient for theirs via `warzone info`).
- Verify the server URL.
- Ask the recipient to run `warzone register -s <server>`.
The recipient has not registered with the server, or the fingerprint / alias
is wrong, or the server URL is incorrect. Verify with `warzone info` and
`warzone register`.
### "X3DH respond failed" / "missing signed pre-key"
Your signed pre-key secret is missing from the local database. This can
happen if:
- The database was deleted or corrupted.
- You recovered an identity but did not regenerate pre-keys.
Signed pre-key secret missing from local database. Database may have been
deleted or corrupted. Re-initialize with `warzone init`.
Fix: re-initialize with `warzone init` (generates a new identity) or restore
from backup.
### "[session reset] Decryption failed"
### "decrypt failed" / "no session"
- **"no session"**: you received a `WireMessage::Message` from someone you
have no session with. This means you missed their initial `KeyExchange`
message, or your session database was lost. Ask them to re-send their first
message.
- **"decrypt failed"**: the ratchet state is out of sync. This can happen if
one side's state was lost or if messages were duplicated. Reset the session
on both sides.
### Messages Keep Reappearing on recv
Messages are not auto-acknowledged after polling. This is a known Phase 1
limitation. The same messages will be returned on every `recv` call.
**Workaround:** none currently. Acknowledgment will be added in Phase 2.
The TUI auto-recovery has cleared the corrupted session. Ask the other party
to send a new message -- a fresh `KeyExchange` will re-establish the session.
### Corrupted Database
If `~/.warzone/db/` is corrupted:
```bash
# Back up your seed first
cp ~/.warzone/identity.seed ~/identity.seed.bak
rm -rf ~/.warzone/db/
warzone init # regenerate pre-keys (NOTE: generates a new identity)
# To keep your old identity, recover from mnemonic after:
warzone recover <24 words>
```
To keep your existing identity, manually copy `identity.seed` before
deleting, then use `warzone recover` after re-init.