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:
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user