11 files updated to reflect current state (v0.0.22 → v0.0.46): ARCHITECTURE.md: - Ring tones, group calls, read receipts, markdown rendering sections - Bot API expanded (BotFather, numeric IDs, Telegram compat) - Admin commands, known issues, 155 tests TASK_PLAN.md: - All P1-P4 marked DONE with version numbers - Additional completed work section (bots, ETH, ring tones, group calls) - New FC-P7 (Voice & Transport): cpal, Sender Keys, WebTransport - FC-P6-T9/T10 added PROGRESS.md: - Full version history table v0.0.22 through v0.0.46 - Known issues section README.md: - Voice calls, ring tones, group calls, read receipts, markdown, 155 tests SECURITY.md: - Bot API security, voice call security, admin commands sections - Updated protection tables USAGE.md: - Group calls, read receipts, markdown formatting, admin commands CLIENT.md: - Call commands, read receipts, markdown rendering LLM_HELP.md + LLM_BOT_DEV.md: - Call/group call/admin commands, ring tones, per-bot numeric IDs TESTING_E2E.md: - Tests 16-18: ring tones, group calls, admin commands CLAUDE.md: - Ring tone notes, group signal endpoint, MLS roadmap Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
22 KiB
Warzone Client -- Operation Guide
Version: 0.0.46
1. Installation
Build from Source
Requires Rust 1.75+.
cd warzone/
cargo build -p warzone-client --release
The binary is at target/release/warzone. You can copy it anywhere or add
target/release to your PATH.
# Optional: install to ~/.cargo/bin
cargo install --path crates/warzone-client
Build the WASM Module (Web Client)
Requires wasm-pack.
cd crates/warzone-wasm
wasm-pack build --target web
# Output in pkg/ — copy to web client directory
2. TUI Architecture
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.
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).
$ 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.
What happens:
- Generates 32 random bytes (seed) from
OsRng. - Derives Ed25519 signing key and X25519 encryption key from the seed.
- Converts seed to a 24-word BIP39 mnemonic and displays it.
- 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. - Generates 1 signed pre-key (id=1) and 10 one-time pre-keys (ids 0-9).
- Stores pre-key secrets in the local sled database at
~/.warzone/db/. - Saves the public pre-key bundle to
~/.warzone/bundle.bin.
warzone recover <words...>
Recover an identity from a 24-word BIP39 mnemonic.
$ 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
Set passphrase (empty for no encryption): ****
Confirm passphrase: ****
Identity recovered. Fingerprint: a3f8:c912:44be:7d01:9e5a:3b2c:7f80:12d4
Recovery restores the seed and keypair. Pre-keys and sessions are NOT restored; contacts will need to re-establish sessions.
warzone info
Display your fingerprint and public keys.
$ warzone info
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 tui / warzone chat [peer]
Launch the interactive TUI client.
$ 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 |
warzone send <recipient> <message>
Send an encrypted message. Recipient can be a fingerprint or @alias.
$ warzone send a3f8:c912:44be:7d01:... "Hello!" --server http://wz.example.com:7700
$ warzone send @alice "Hello!" --server http://wz.example.com:7700
Behavior:
- Auto-registers your bundle with the server if needed.
- Checks for an existing Double Ratchet session with the recipient.
- 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::KeyExchangecontaining the X3DH parameters and the first encrypted message. - If a session exists: encrypts with the existing ratchet and sends a
WireMessage::Message. - Updates the local session state.
warzone recv
Poll for and decrypt incoming messages.
$ warzone recv --server http://wz.example.com:7700
Fetches messages from /v1/messages/poll/{fingerprint}, deserializes each
WireMessage, performs X3DH respond or ratchet decrypt as appropriate, and
prints plaintext to stdout.
warzone backup [output]
Export an encrypted backup of local data (sessions, pre-keys).
$ warzone backup my-backup.wzb
Backup saved to my-backup.wzb (4096 bytes encrypted)
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", ... }
}
warzone restore <input>
Restore from an encrypted backup. Requires the same seed (passphrase prompt).
$ warzone restore my-backup.wzb
Restored 12 entries from my-backup.wzb
Merges data without overwriting existing entries.
4. TUI Features
Message Timestamps
Every message is rendered with a [HH:MM] prefix in dark gray, derived from
chrono::Local::now() at receive/send time.
Message Scrolling
The message area supports scrolling with a "pinned to bottom" model:
scroll_offset = 0means 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:
- Deletes the corrupted session from the local database.
- 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.
Voice Calls
The TUI supports DM and group call commands:
| Command | Description |
|---|---|
/call [peer] |
Initiate a voice call with the current or specified peer |
/accept |
Accept an incoming call |
/reject |
Reject an incoming call |
/hangup |
End the current call |
Call state display: The TUI header bar shows call status with color coding:
- Yellow "CALLING..." — outgoing call ringing, waiting for peer to accept
- Green "IN CALL" + timer — active call with elapsed duration (MM:SS)
- No indicator when idle
Note: TUI audio requires the web client. When a call is active in the TUI, a hint is displayed directing the user to open the web client for actual audio. The TUI handles signaling (offer/answer/ICE) but does not capture or play audio.
Read Receipts
Read receipts track message delivery through three states: sent, delivered, and read.
- Sender fingerprint tracking: Each outgoing message records the sender's fingerprint so the system can match incoming receipts to the correct message.
- Dedup set: A per-conversation set prevents sending duplicate read receipts for the same message. Once a read receipt is sent for a message ID, it is not sent again.
- Viewport-based: Read receipts are triggered when a message scrolls into the visible area of the chat. Messages that are never scrolled into view do not generate read receipts.
Markdown Rendering
Messages support inline markdown formatting via the md_to_spans function, which converts markdown syntax into ratatui Span elements with appropriate styling:
| Syntax | TUI Rendering |
|---|---|
**bold** |
Bold attribute |
*italic* |
Italic attribute |
`code` |
Dark gray background, monospace feel |
# Header |
Bold + uppercase (line start only) |
> quote |
Italic + gray foreground (line start only) |
- list item |
Bullet prefix (line start only) |
Markdown is parsed per-message at render time. The web client renders the same syntax as HTML elements.
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+ encryptedFileChunkwire 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
FriendListcontaining address, alias, andadded_attimestamp 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
- On
/friend <address>: the client fetches the current encrypted blob from the server, decrypts it, adds the entry, re-encrypts, and uploads. - On
/unfriend <address>: same fetch-decrypt-modify-encrypt-upload cycle. - On
/friend(no argument): fetches and decrypts the blob, then checks/v1/presence/<fp>for each friend.
The server stores the blob at POST /v1/friends and returns it at
GET /v1/friends. It has no knowledge of the contents.
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-protocoldirectly, so web-to-CLI interoperability is fully supported. - URL deep links: paths like
/message/@alias,/message/0xABC, and/group/#opsauto-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 (
/installcommand). - 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
A "session" is a Double Ratchet state between you and one peer, identified by their fingerprint.
-
First message to a peer: X3DH key exchange establishes a shared secret. The ratchet is initialized. The session is saved in
~/.warzone/db/under thesessionstree, keyed by the peer's hex fingerprint. -
Subsequent messages: the ratchet state is loaded, used to encrypt or decrypt, then saved back.
-
Bidirectional: when Bob receives Alice's
KeyExchange, he initializes his side. From then on, both useWireMessage::Message.
Session Auto-Recovery
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.
Multi-Device
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.
11. Troubleshooting
"No identity found. Run warzone init first."
~/.warzone/identity.seed is missing. Run warzone init.
"No bundle found. Run warzone init first."
~/.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 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"
Signed pre-key secret missing from local database. Database may have been
deleted or corrupted. Re-initialize with warzone init.
"[session reset] Decryption failed"
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
# 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>