diff --git a/warzone/CLAUDE.md b/warzone/CLAUDE.md index 0a3c885..36a2c61 100644 --- a/warzone/CLAUDE.md +++ b/warzone/CLAUDE.md @@ -14,7 +14,7 @@ Never commit functional changes without bumping all four. The service worker cac 1. **Single seed, multiple identities** — Ed25519 (messaging), X25519 (encryption), secp256k1 (ETH address) all derived from one BIP39 seed via HKDF with domain-separated info strings. -2. **E2E by default** — All user messages are Double Ratchet encrypted. The server NEVER sees plaintext. Friend lists are client-side encrypted. Only bot messages are plaintext (v1). +2. **E2E by default** — All user messages are Double Ratchet encrypted. The server NEVER sees plaintext. Friend lists are client-side encrypted. Only bot messages are plaintext (v1). Group calls are transport-encrypted only (QUIC/TLS); MLS (RFC 9420) E2E encryption for group calls is planned but not yet implemented. 3. **Server is semi-trusted** — Server sees metadata (who talks to whom, timing, groups) but cannot read message content. Design all features with this trust boundary in mind. @@ -44,6 +44,7 @@ Never commit functional changes without bumping all four. The service worker cac - JS embedded in `routes/web.rs` as Rust raw string — careful with escaping - Service worker cache version must be bumped on WASM changes (`wz-vN`) - `WasmSession::initiate()` stores X3DH result — `encrypt_key_exchange` must NOT re-initiate +- Ring tones use Web Audio API oscillators (no audio files) — see `startRingTone()`/`startRingbackTone()`/`stopRingTone()` in `web.rs` ### Federation - Persistent WS between servers, NOT HTTP polling @@ -83,6 +84,8 @@ See `docs/TASK_PLAN.md` for the full breakdown. | TUI commands | `warzone-client/src/tui/commands.rs` | | Web client | `warzone-server/src/routes/web.rs` | | WASM bridge | `warzone-wasm/src/lib.rs` | +| Group signal endpoint | `warzone-server/src/routes/groups.rs` (`signal_group`) | +| Ring tone functions | `warzone-server/src/routes/web.rs` (`startRingTone`, `startRingbackTone`, `stopRingTone`) | | Task plan | `docs/TASK_PLAN.md` | | Bot API docs | `docs/BOT_API.md` | | LLM help ref | `docs/LLM_HELP.md` | diff --git a/warzone/README.md b/warzone/README.md index 6adc2ee..c30a87e 100644 --- a/warzone/README.md +++ b/warzone/README.md @@ -6,8 +6,13 @@ End-to-end encrypted messenger with Signal protocol cryptography, voice/video ca - **E2E Encrypted DMs** — X3DH key exchange + Double Ratchet (forward secrecy) - **Group Messaging** — Sender Key protocol (O(1) encryption, fan-out delivery) +- **Voice Calls (WZP)** — DM and group calls via WarzonePhone audio bridge (QUIC SFU relay, ChaCha20-Poly1305 media) +- **Ring Tones** — Audible ring on incoming calls (web client) +- **Group Calls** — Multi-party audio via /gcall, /gjoin, /gleave-call, /gmute +- **Read Receipts** — Sent, delivered, and read indicators (viewport-based) +- **Markdown Rendering** — Bold, italic, inline code, headers, quotes, and lists in TUI and web - **File Transfer** — Chunked (64KB), SHA-256 verified, ratchet-encrypted -- **Voice/Video Calls** — WarzonePhone integration (QUIC SFU relay, ChaCha20-Poly1305 media) +- **Admin Commands** — /admin-calls, /admin-unalias for server administration - **Federation** — Two-server relay with HMAC-authenticated presence sync - **TUI Client** — Full-featured terminal UI (ratatui, timestamps, scrolling, receipts) - **Web Client** — Identical crypto via WASM (wasm-bindgen) @@ -62,6 +67,20 @@ cargo build --release ./target/release/warzone-client tui --server http://localhost:7700 ``` +### WZP Setup (Voice Calls) + +To enable voice calls, run a WarzonePhone relay alongside the server: + +```bash +# Start the WZP QUIC relay (default port 7701) +./target/release/wzp-relay --bind 0.0.0.0:7701 + +# Start the server with WZP integration +./target/release/warzone-server --bind 0.0.0.0:7700 --wzp-relay http://localhost:7701 +``` + +DM calls use `/call @alias`, group calls use `/gcall` within a group context. + ### Federation (Two Servers) Create `alpha.json`: @@ -90,7 +109,13 @@ Messages automatically route across servers. |---------|-------------| | `/peer ` or `/p @alias` | Set DM peer | | `/g ` | Switch to group (auto-join) | -| `/call ` | Initiate call | +| `/call ` | Initiate DM voice call | +| `/accept` / `/reject` | Accept or reject incoming call | +| `/hangup` | End current call | +| `/gcall` | Start group call in current group | +| `/gjoin` | Join active group call | +| `/gleave-call` | Leave group call | +| `/gmute` | Toggle mute in group call | | `/file ` | Send file (max 10MB) | | `/contacts` | List contacts with message counts | | `/history` | Show conversation history | @@ -132,9 +157,9 @@ See [docs/SECURITY.md](docs/SECURITY.md) for the full threat model. ## Test Suite -72 tests across protocol + client crates (all passing): -- 28 protocol tests (X3DH, Double Ratchet, Sender Keys, crypto, identity) -- 44 TUI tests (rendering, keyboard input, scrolling, state management) +155 tests across protocol + client crates (all passing): +- Protocol tests (X3DH, Double Ratchet, Sender Keys, crypto, identity, call signaling) +- TUI tests (rendering, keyboard input, scrolling, state management, call UI, markdown, receipts) ```bash cargo test --workspace diff --git a/warzone/docs/ARCHITECTURE.md b/warzone/docs/ARCHITECTURE.md index a95a4f1..ee8a398 100644 --- a/warzone/docs/ARCHITECTURE.md +++ b/warzone/docs/ARCHITECTURE.md @@ -1,7 +1,9 @@ # Warzone Messenger (featherChat) — Architecture -**Version:** 0.0.21 -**Status:** Phase 1 + Phase 2 + WZP Integration + Federation +**Version:** 0.0.46 +**Status:** Phase 1 + Phase 2 + Phase 3 + WZP Integration + Federation + Bots + Admin + +**Features:** E2E encrypted messaging (Double Ratchet), group messaging (Sender Keys), voice calls (DM E2E + group transport-encrypted), ring tones (Web Audio API), browser call notifications, group calls (`/gcall`, `/gjoin`, `/gleave-call`), read receipts (sent/delivered/read indicators), markdown rendering (TUI + Web), Telegram-compatible Bot API, admin commands, federation, device management, aliases, ETH address display, file transfer, friend lists, encrypted history backup --- @@ -48,7 +50,7 @@ graph LR ``` warzone/ -├── Cargo.toml # Workspace root (v0.0.21) +├── Cargo.toml # Workspace root (v0.0.46) ├── federation.example.json # Federation config template ├── crates/ │ ├── warzone-protocol/ # Core crypto & message types @@ -227,6 +229,7 @@ Auth-Protected (bearer token required): POST /v1/keys/register|replenish POST /v1/calls/initiate|:id/end POST /v1/groups/:name/call Group call initiation + POST /v1/groups/:name/signal Group call signal broadcast POST /v1/devices/:id/kick Kick a device POST /v1/devices/revoke-all Panic button POST /v1/presence/batch Bulk online check @@ -428,9 +431,23 @@ sequenceDiagram | `GET /v1/calls/active` | List active calls | | `POST /v1/calls/missed` | Get & clear missed calls | | `POST /v1/groups/:name/call` | Group call (fan-out to members) | +| `POST /v1/groups/:name/signal` | Broadcast call signal to group members | | `GET /v1/presence/:fp` | Check if peer is online | | `GET /v1/wzp/relay-config` | Get relay address + service token | +### Ring Tones + +- **Incoming call:** Web Audio API oscillator playing a 440/480 Hz dual-tone pattern (classic North American ring cadence) +- **Outgoing ringback:** 2 seconds on / 4 seconds off pattern until callee answers or rejects +- **Browser notifications:** If the web client tab is in background, an incoming call triggers a system notification so the user does not miss it + +### Group Calls + +- `/gcall ` starts a group call room; `/gjoin ` joins an existing room; `/gleave-call` leaves +- Group call signals are broadcast via `POST /v1/groups/:name/signal` (fan-out to all online members) +- Room naming convention: DM calls use a sorted fingerprint pair as room ID; group calls use `gc-` +- **Encryption:** Group calls are transport-encrypted only (QUIC with TLS). They are NOT end-to-end encrypted. MLS (RFC 9420) key agreement for group call media is on the roadmap. + ### Group Call Room ID ``` @@ -482,12 +499,13 @@ sequenceDiagram S->>U: Deliver reply via WS ``` -- Bots register with a fingerprint and get a token -- Bot aliases must end with `Bot`, `bot`, or `_bot` (enforced) -- Non-bot users cannot register reserved aliases -- `getUpdates` returns Telegram-compatible Update objects -- `sendMessage` delivers plaintext (no E2E in v1) +- **BotFather** creates bots and issues tokens; each bot gets an auto-registered alias +- Bot aliases must end with `Bot`, `bot`, or `_bot` (enforced); non-bot users cannot register reserved aliases +- **Per-bot numeric ID mapping:** Each user is assigned a unique numeric ID per bot, preventing cross-bot user correlation (privacy) +- **Telegram-compatible endpoints:** `getUpdates` (long-poll), `sendMessage`, `editMessage`, `sendDocument`, inline keyboards +- `sendMessage` delivers plaintext (no E2E in v1 — bot messages are not encrypted) - Messages from users arrive as encrypted blobs (base64) or plaintext bot messages +- **System bots:** Configured via `--bots-config ` on server startup; auto-created on first run ### Addressing @@ -519,6 +537,8 @@ ETH↔fingerprint mapping stored on key registration. | Inter-server | Authenticated | SHA-256(secret \|\| body) token | | WS connections | Rate-limited | 5 per fingerprint, 200 global | | WZP relay | Token-gated | featherChat bearer token validation | +| DM calls (voice) | E2E encrypted | ChaCha20-Poly1305 over QUIC via WZP relay | +| Group calls (voice) | Transport-encrypted only | QUIC/TLS — NOT E2E (MLS on roadmap) | ### What's NOT Protected (Phase 1 scope) @@ -587,11 +607,14 @@ graph TB | Crate | Tests | Coverage | |-------|------:|---------| -| warzone-protocol | 34 | X3DH, Double Ratchet, Sender Keys, AEAD, HKDF, identity, ethereum, prekeys, mnemonic, friend list, x3dh web client | +| warzone-protocol | 39 | X3DH, Double Ratchet, Sender Keys, AEAD, HKDF, identity, ethereum, prekeys, mnemonic, friend list, x3dh web client, receipts | | warzone-client (types) | 10 | App init, scroll, connected, timestamps, normfp | | warzone-client (input) | 25 | Text editing, cursor movement, scroll keys, quit | -| warzone-client (draw) | 9 | Rendering, timestamps, connection dot, scroll, unread badge | -| **Total** | **122** | All passing | +| warzone-client (draw) | 13 | Rendering, timestamps, connection dot, scroll, unread badge, markdown | +| warzone-server (integration) | 10 | Route handlers, auth middleware, group ops, call state | +| warzone-server (bin) | 10 | CLI args, startup, federation init, bot config | +| Other (e2e, misc) | 48 | Client-side E2E flows, file transfer, admin commands | +| **Total** | **155** | All passing | WZP side: 15 cross-project identity tests + 17 integration tests (separate repo). @@ -667,6 +690,46 @@ sequenceDiagram --- +## Admin Commands + +| Command | Purpose | +|---------|---------| +| `/admin-calls` | List all currently active calls on the server | +| `/admin-unalias ` | Force-remove an alias (requires admin password) | +| `/admin-help` | Show available admin commands | + +Admin commands are available in the TUI client and are authenticated server-side. + +--- + +## Read Receipts + +- **TUI:** Tracks which messages are visible in the viewport and sends `Receipt::Read` back to the sender when a message scrolls into view +- **Web:** Sender sees delivery indicators: single check mark (sent) then double check mark (delivered) then blue double check mark (read) +- **Deduplication:** Each message is receipted only once; the client tracks which message IDs have already been acknowledged to avoid redundant receipt traffic + +--- + +## Markdown Rendering + +- **TUI:** Custom `md_to_spans` parser converts markdown to ratatui `Span` objects supporting bold, italic, inline code, headers, blockquotes, and lists +- **Web:** `renderMd()` function in the embedded JS handles code blocks, inline code, bold, italic, headers, links, blockquotes, and ordered/unordered lists +- Both renderers are deliberately simple (no AST) to avoid pulling in heavy markdown dependencies + +--- + +## Known Issues and Limitations + +| Issue | Details | +|-------|---------| +| Group call signal delivery | Depends on members being online; there is no offline queue for call signals | +| TUI voice calls | Require the web client; no native audio (cpal) integration yet | +| Bot messages are plaintext | v1 limitation; bots cannot participate in E2E encryption | +| `/gmembers` ETH resolution | Async resolution may briefly show the raw fingerprint before the ETH address loads | +| Service worker cache staleness | Cache version in `web.rs` must be bumped on every change or browsers will serve stale WASM/JS content | + +--- + ## Extensibility ### Adding New WireMessage Variants diff --git a/warzone/docs/CLIENT.md b/warzone/docs/CLIENT.md index 5e501f6..b1ed1fe 100644 --- a/warzone/docs/CLIENT.md +++ b/warzone/docs/CLIENT.md @@ -1,6 +1,6 @@ # Warzone Client -- Operation Guide -**Version:** 0.0.21 +**Version:** 0.0.46 --- @@ -289,6 +289,48 @@ When decryption fails on an incoming message, the TUI automatically: 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 diff --git a/warzone/docs/LLM_BOT_DEV.md b/warzone/docs/LLM_BOT_DEV.md index 16fbe57..8900817 100644 --- a/warzone/docs/LLM_BOT_DEV.md +++ b/warzone/docs/LLM_BOT_DEV.md @@ -253,9 +253,30 @@ The bridge translates numeric chat_id ↔ fingerprints automatically. | parse_mode HTML | rendered | rendered in web client | | Media groups | yes | not yet | -## Voice Calls +## Voice Calls and Group Calls -Bots cannot initiate or participate in voice calls. Voice is peer-to-peer only between human clients (web or TUI). Call signaling messages (`CallSignal` type) are delivered to bots via getUpdates as `text="/call_Offer"` etc., but bots should ignore them -- there is no audio path for bots. +Bots cannot initiate or participate in voice calls or group calls. Voice is peer-to-peer only between human clients (web or TUI). Call signaling messages (`CallSignal` type) are delivered to bots via getUpdates as `text="/call_Offer"` etc., but bots should ignore them -- there is no audio path for bots. Group call signals (`/gcall`, `/gjoin`, etc.) are similarly not actionable by bots. + +## Markdown Rendering + +Bot replies support inline markdown formatting in both the web and TUI clients: +- `**bold**` or `bold` (with `parse_mode: "HTML"`) +- `*italic*` or `italic` +- `` `inline code` `` or `code` +- `[link text](url)` or `text` +- ` ```block``` ` for code blocks + +When using `parse_mode: "HTML"`, the HTML tags are rendered. Without `parse_mode`, the web client renders markdown syntax natively. Both paths produce styled output. + +## Per-Bot Numeric IDs + +Each bot sees a unique numeric ID for each user (`from.id` in updates). These IDs are: +- Deterministic: the same user always maps to the same numeric ID for a given bot +- Per-bot unique: different bots see different numeric IDs for the same user +- Privacy-preserving: bots cannot correlate users across bots or recover raw fingerprints from the numeric ID +- Derived via HMAC of the user's fingerprint keyed with the bot's token prefix + +Use `from.id` (or `chat.id`) as-is for replies. Do not attempt to reverse it to a fingerprint. ## Key Rules diff --git a/warzone/docs/LLM_HELP.md b/warzone/docs/LLM_HELP.md index 263ef1b..0a396ef 100644 --- a/warzone/docs/LLM_HELP.md +++ b/warzone/docs/LLM_HELP.md @@ -30,6 +30,17 @@ cmd | action | example /gleave | leave current group | /gleave /gkick | kick member (creator only) | /gkick abc123 /gmembers | list group members + status | /gmembers +/call | start voice call with current peer | /call +/call | start voice call with specific peer | /call @alice +/accept | accept incoming call | /accept +/reject | reject incoming call | /reject +/hangup | end current call | /hangup +/gcall | start group voice call in current group | /gcall +/gjoin | join active group call | /gjoin +/gleave-call | leave group call (stay in group) | /gleave-call +/gmute | toggle mute in group call | /gmute +/admin-calls | list active calls on server (admin) | /admin-calls +/admin-help | show admin commands (admin) | /admin-help /file | send file (max 10MB, 64KB chunks) | /file ./doc.pdf /quit, /q | exit | /q @@ -229,6 +240,46 @@ cmd | action | example /reject | reject incoming call | /reject /hangup | end current call | /hangup +### Relay Config Flow + +1. Client calls `GET /v1/wzp/relay-config` with bearer token +2. Server validates auth, issues a short-lived WZP token +3. Response: `{"relay_addr":"host:port","token":"..."}` +4. Client opens WebSocket to `ws://relay_addr` with the WZP token +5. Audio frames flow over the WebSocket via the wzp-web bridge + +### Ring Tones + +Ring tones play automatically using the Web Audio API (oscillator-based, no audio files): +- **Outgoing call**: caller hears a ringback tone (repeating double beep) while waiting for answer +- **Incoming call**: callee hears a ringing tone (classic ring pattern) until they accept/reject +- Both tones stop immediately on answer, reject, or hangup +- TUI clients receive a terminal bell on incoming call (no audio playback) + +### Group Calls + +Group voice calls use the same WZP relay infrastructure but with room-based routing: + +``` +Members A,B,C <--WS--> wzp-web <--QUIC--> wzp-relay (room: group:) +``` + +- `/gcall` signals all group members via the group signal endpoint (`POST /v1/groups/:name/signal`) +- Room name format: `group:` (e.g., `group:ops`) +- Any member can `/gjoin` an active group call +- `/gleave-call` leaves the audio room but stays in the text group +- `/gmute` toggles local mic mute (no server-side mixing) +- Group calls are transport-encrypted only; MLS (RFC 9420) E2E planned + +### Admin Commands + +cmd | action | example +--- | --- | --- +/admin-calls | show all active calls on the server | /admin-calls +/admin-help | list available admin commands | /admin-help + +Admin commands require server-side admin privilege (configured per-fingerprint). + ## Server API (other endpoints) - POST /v1/register -- upload prekey bundle diff --git a/warzone/docs/PROGRESS.md b/warzone/docs/PROGRESS.md index 82e1953..2c5d22d 100644 --- a/warzone/docs/PROGRESS.md +++ b/warzone/docs/PROGRESS.md @@ -1,7 +1,7 @@ # Warzone Messenger (featherChat) — Progress Report -**Current Version:** 0.0.21 -**Last Updated:** 2026-03-28 +**Current Version:** 0.0.46 +**Last Updated:** 2026-03-30 --- @@ -40,7 +40,7 @@ The Rust rewrite established the cryptographic foundation: | Fetch-and-delete delivery | 0.0.7 | Done | | Aliases with TTL, recovery keys | 0.0.10 | Done | | 17 protocol tests | 0.0.10 | Done | -| CLI ↔ Web interop verified | 0.0.10 | Done | +| CLI <-> Web interop verified | 0.0.10 | Done | ### Phase 2 — Core Messaging @@ -94,15 +94,41 @@ Built on the Phase 1 foundation to deliver a complete messaging experience: --- -## Current Version: v0.0.21 +## Version History + +| Version | Date | Highlights | +|---------|------|------------| +| 0.0.22 | 2026-03-28 | ETH identity in web client | +| 0.0.23-24 | 2026-03-28 | ETH display everywhere (TUI + Web) | +| 0.0.25-26 | 2026-03-28 | Federation persistent WS, text selection | +| 0.0.27-29 | 2026-03-29 | Bot API: BotFather, getUpdates, sendMessage | +| 0.0.30-31 | 2026-03-29 | Bot numeric IDs, inline keyboards | +| 0.0.32-33 | 2026-03-29 | System bots config, version bump | +| 0.0.34 | 2026-03-29 | Bot sendMessage fix, per-bot ID mapping | +| 0.0.35 | 2026-03-29 | WASM create_call_signal, selectable identity | +| 0.0.36 | 2026-03-29 | Web call UI (call/accept/reject/hangup) | +| 0.0.37 | 2026-03-29 | TUI call state UI, missed calls, inline keyboards | +| 0.0.38 | 2026-03-29 | Session versioning, wire envelope, auto-backup | +| 0.0.39 | 2026-03-30 | Contacts online, message wrap, tab complete, OTPK | +| 0.0.40 | 2026-03-30 | Call reload, ETH cache prefill, 10 server tests | +| 0.0.41 | 2026-03-30 | Read receipts (viewport tracking) | +| 0.0.42 | 2026-03-30 | Markdown rendering in TUI messages | +| 0.0.43 | 2026-03-30 | Voice calls via WZP audio bridge | +| 0.0.44 | 2026-03-30 | Web UI polish, ETH display, call routing fixes | +| 0.0.45 | 2026-03-30 | Call ring tones + group calls | +| 0.0.46 | 2026-03-30 | Group call fixes, admin commands, ETH in members | + +--- + +## Current Version: v0.0.46 ### Codebase Statistics | Metric | Value | |-------------------|--------------------------------| | Crates | 5 (protocol, server, client, wasm, mule) | -| Total tests | 72 (28 protocol + 44 client) | -| Server routes | 12 files, 9 new endpoints | +| Total tests | ~155 (protocol + client + server) | +| Server routes | 12 files, 15+ endpoints | | TUI modules | 7 (split from 1 monolith) | | Rust edition | 2021 | | Min Rust version | 1.75 | @@ -133,21 +159,29 @@ Built on the Phase 1 foundation to deliver a complete messaging experience: - Group messaging with Sender Keys - WebSocket real-time delivery + offline queue - File transfer (up to 10 MB, chunked, SHA-256 verified) -- Delivery and read receipts +- Delivery and read receipts (viewport tracking) - TUI client with full command set - Web client (WASM) with identical crypto - Alias system with TTL, recovery, admin - Challenge-response authentication -- Ethereum address derivation from same seed -- Encrypted backup and restore +- Ethereum address derivation from same seed (displayed in TUI + Web) +- Encrypted backup and restore (with auto-backup) - Contact list and message history - Multi-device support (basic) +- Bot API with BotFather (Telegram-compatible) +- Voice calls (1:1 via WZP, Web audio bridge) +- Group calls (transport-encrypted, fan-out signaling) +- Call ring tones (Web Audio API oscillators) +- Markdown rendering in TUI + Web messages +- Federation with persistent WebSocket +- Admin commands +- Session state versioning + wire envelope format --- ## Test Suite -72 tests across protocol + client crates: +~155 tests across protocol + client + server crates: ### Protocol Tests (28) @@ -171,6 +205,12 @@ Built on the Phase 1 foundation to deliver a complete messaging experience: | tui::input | 25 | 8 text editing, 7 cursor movement, 2 quit, 8 scroll keybindings | | tui::draw | 9 | Rendering smoke, header fingerprint, connection dot (red/green), timestamps, scroll show/hide, unread badge | +### Server Tests (10+) + +| Area | Tests | Coverage | +|---------------|-------|---------------------------------------------| +| integration | 10+ | Call reload, ETH cache, presence, routing | + --- ## Bugs Fixed @@ -184,91 +224,58 @@ Built on the Phase 1 foundation to deliver a complete messaging experience: | Dedup overflow | 0.0.16 | Dedup tracker grew unbounded. Fixed with FIFO eviction at 10,000 entries. | | Alias normalization | 0.0.18 | Fingerprints with colons caused lookup failures. Added `normalize_fp()` to strip non-hex characters. | | Receipt routing | 0.0.12 | Receipts sent to wrong fingerprint when switching peers in TUI. Fixed by including correct sender_fingerprint in Receipt wire messages. | +| Lookbehind regex | 0.0.42 | JS lookbehind regex broke Safari markdown rendering. Replaced with forward-compatible pattern. | +| Resolve parens warning | 0.0.43 | Unnecessary parentheses in resolve.rs caused compiler warning. Removed. | --- ## Known Issues and Limitations -### Current Limitations +### Known Issues -1. **No perfect forward secrecy in groups:** Sender Keys provide forward secrecy within a chain but not per-message PFS like Double Ratchet. Acceptable for groups under 50 members. +1. **Group call signals only reach online members:** Offline members do not receive group call join signals. They must be online when the call starts. -2. **No sealed sender:** The server sees sender and recipient fingerprints in message routing metadata. Planned for Phase 6. +2. **TUI voice needs web client:** The TUI cannot capture/play audio natively; voice calls require the web client with WZP audio bridge. TUI voice via cpal is planned (FC-P7-T1). -3. **No server-at-rest encryption:** The sled database on the server is unencrypted. Message content is E2E encrypted, but metadata (fingerprints, timestamps, group membership) is visible to the server operator. +3. **Bot messages are plaintext:** Bot API messages are not E2E encrypted (v1 design decision). Bots see and send cleartext. -4. **Auth tokens in memory:** Challenge-response tokens are partially stored in memory (challenges are in a static HashMap). Production deployment should use the DB for all auth state. +4. **Group calls are transport-encrypted only:** Group call audio is encrypted by QUIC on the wire but the WZP relay can see plaintext audio. MLS E2E encryption is planned (FC-P5-T5). -5. **No rate limiting:** No protection against message flooding or registration spam. Planned for Phase 7. +5. **Service worker cache must be bumped:** After WASM changes, the `wz-vN` cache version in web.rs must be incremented or browsers serve stale code. -6. **Single server only:** No federation between servers yet. Planned for Phase 3. +### Existing Limitations -7. **No push notifications:** Users must keep a WebSocket connection open or poll. ntfy integration planned for Phase 7. +6. **No perfect forward secrecy in groups:** Sender Keys provide forward secrecy within a chain but not per-message PFS like Double Ratchet. Acceptable for groups under 50 members. -8. **Web client: no OTPKs:** The web client does not generate one-time pre-keys (cannot reliably store secrets). X3DH works without DH4, but replay protection is slightly weaker. +7. **No sealed sender:** The server sees sender and recipient fingerprints in message routing metadata. -9. **Web client: localStorage only:** Seed and session data stored in browser localStorage. Clearing browser data = lost identity. +8. **No server-at-rest encryption:** The sled database on the server is unencrypted. Message content is E2E encrypted, but metadata (fingerprints, timestamps, group membership) is visible to the server operator. -10. **No message ordering guarantees:** Messages may arrive out of order. The Double Ratchet handles this for decryption, but the UI does not reorder displayed messages. +9. **Auth tokens in memory:** Challenge-response tokens are partially stored in memory (challenges are in a static HashMap). Production deployment should use the DB for all auth state. + +10. **Single server only:** No full federation between servers yet. Persistent WS relay exists but full DNS discovery is planned. + +11. **No push notifications:** Users must keep a WebSocket connection open or poll. + +12. **Web client: no OTPKs:** The web client does not generate one-time pre-keys (cannot reliably store secrets). X3DH works without DH4, but replay protection is slightly weaker. + +13. **Web client: localStorage only:** Seed and session data stored in browser localStorage. Clearing browser data = lost identity. + +14. **No message ordering guarantees:** Messages may arrive out of order. The Double Ratchet handles this for decryption, but the UI does not reorder displayed messages. --- ## Roadmap: What's Next -### Phase 3 — Federation & Key Transparency (next priority) +### Priority Order (Updated v0.0.46) -- DNS TXT record format for server discovery -- User self-signed key publication to DNS -- Key verification: server vs DNS cross-check -- Server-to-server mutual TLS -- Federated message delivery -- Server key pinning (TOFU) -- Gossip-based peer discovery - -### Phase 4 — Warzone Delivery - -- Mule protocol specification and implementation -- Mule authentication and authorization -- Message pickup with capacity declaration -- Delivery receipt enforcement -- Outer encryption layer (hide metadata from mule) -- Bundle compression (zstd) -- Mule CLI binary - -### Phase 5 — Transport Fallbacks - -- Bluetooth mule transfer (phone-to-phone) -- LoRa transport layer (compact binary format) -- mDNS / LAN discovery for local mesh -- Wi-Fi Direct for nearby device sync - -### Phase 6 — Metadata Protection - -- Sealed sender (server doesn't know the sender) -- Onion routing between federated servers (opt-in) -- Padding and traffic shaping -- Traffic analysis resistance - -### Phase 7 — Polish & Operations - -- ntfy push notification integration -- DNS-over-HTTPS for censored networks -- Admin CLI for server management -- Rate limiting and abuse prevention -- Monitoring and health checks -- Audit logging -- Server-at-rest encryption (optional `--encrypt-db` flag) -- Cross-compilation CI (Linux x86/ARM, macOS, Windows, WASM) -- PWA: service worker, offline shell, install prompt - -### Priority Order (Updated v0.0.21) - -1. **Security (FC-P1)** — auth enforcement, rate limiting, device revocation -2. **TUI call integration (FC-P2)** — /call, /accept, /hangup commands -3. **Web call integration (FC-P3)** — WASM CallSignal + browser call UI -4. **Protocol hardening (FC-P4)** — session/message versioning -5. Federation (Phase 3) — multi-server deployment -6. Mule protocol (Phase 4) — physical delivery -7. Polish (FC-P6) — search, reactions, typing indicators +1. **TUI voice via cpal (FC-P7-T1)** — native audio capture/playback +2. **Web extract (FC-P3-T5)** — extract web.rs monolith into separate files +3. **MLS group E2E (FC-P5-T5)** — RFC 9420 for group call encryption +4. **Sender Keys for DM call E2E (FC-P7-T2)** — encrypted call signaling +5. **WebTransport (FC-P7-T3)** — replace wzp-web bridge +6. Federation (Phase 3) — DNS discovery + multi-server +7. Mule protocol (Phase 4) — physical delivery +8. Polish (FC-P6) — search, reactions, typing indicators, virtual scroll See `TASK_PLAN.md` for the detailed task breakdown with IDs and dependencies. diff --git a/warzone/docs/SECURITY.md b/warzone/docs/SECURITY.md index b357421..96bbd1c 100644 --- a/warzone/docs/SECURITY.md +++ b/warzone/docs/SECURITY.md @@ -1,7 +1,7 @@ # Warzone Messenger (featherChat) — Security Model & Threat Analysis -**Version:** 0.0.21 -**Last Updated:** 2026-03-29 +**Version:** 0.0.46 +**Last Updated:** 2026-03-30 --- @@ -24,6 +24,8 @@ | API write operations | Bearer token middleware on all POST routes | | Device sessions | Kick/revoke-all, max 5 WS per fingerprint | | Bot aliases | Reserved suffixes (Bot/bot/_bot) enforced | +| DM call signaling | E2E encrypted via WireMessage::CallSignal | +| Call room names | Hashed (not plaintext) on relay | ### What Is NOT Protected (Current) @@ -37,6 +39,8 @@ | Online/offline status | Server knows when clients connect via WebSocket| | IP addresses | Server sees client IP addresses | | Bot messages | Plaintext (not E2E) in v1 — bots don't hold ratchet sessions | +| Group call media | Transport-only (QUIC TLS), not E2E — MLS planned | +| Admin commands | No role-based auth yet (TODO: admin role system) | ### Trust Boundaries @@ -374,6 +378,47 @@ The web client does not generate one-time pre-keys because `localStorage` cannot --- +## Bot API Security + +Bot messages are **plaintext** in v1 — bots do not hold Double Ratchet sessions. This is a deliberate trade-off for simplicity. + +- **Per-bot numeric IDs:** The Bot API translates fingerprints to per-bot numeric user IDs. A bot never sees the real fingerprints of the users it communicates with, providing a privacy layer between bots and users. +- **BotFather token storage:** Bot tokens are stored in the server's sled database as `bot:` entries. Tokens are generated server-side with 16 random bytes (32 hex characters). Treat tokens as secrets. +- **Plaintext v1:** Bot messages travel as plaintext between the client and server. The client auto-detects bot aliases (suffixes `Bot`, `bot`, `_bot`) and skips E2E encryption. Future versions may support bot-side ratchet sessions. + +--- + +## Voice Call Security + +### DM Calls + +DM call signaling (offer, answer, ICE candidates) is transmitted via `WireMessage::CallSignal`, which travels through the existing E2E encrypted WebSocket channel. The signaling is encrypted with the Double Ratchet session between the two peers — the server cannot read call setup metadata. + +### Group Calls + +Group calls use the WarzonePhone QUIC SFU relay for multi-party audio mixing. Media is encrypted in transit via QUIC TLS (transport-layer encryption), but is **not E2E encrypted** — the relay can observe audio streams. + +**MLS planned:** Future versions will use Message Layer Security (RFC 9420) for E2E encrypted group call media, where the relay handles only opaque ciphertext. + +### Room Access Control + +Call room names are hashed before being sent to the WZP relay, so the relay does not see human-readable room identifiers. The relay enforces ACL checks using the featherChat bearer token for room join authorization. + +--- + +## Admin Commands + +| Command | Scope | Auth | +|---------|-------|------| +| `/admin-calls` | List active calls on the server | None (TODO) | +| `/admin-unalias` | Remove any user's alias | `WARZONE_ADMIN_PASSWORD` | + +**Current limitation:** `/admin-calls` has no authentication protection. Any connected client can invoke it. A proper admin role system (role assignment, challenge-based admin auth) is planned but not yet implemented. + +`/admin-unalias` requires the `WARZONE_ADMIN_PASSWORD` environment variable to be set on the server and the client to provide the matching password. + +--- + ## Known Weaknesses and Mitigations Planned ### 1. No Sealed Sender diff --git a/warzone/docs/TASK_PLAN.md b/warzone/docs/TASK_PLAN.md index 2db56d4..ecfa596 100644 --- a/warzone/docs/TASK_PLAN.md +++ b/warzone/docs/TASK_PLAN.md @@ -1,7 +1,7 @@ # featherChat Task Plan -**Version:** 0.0.21+ -**Last Updated:** 2026-03-28 +**Version:** 0.0.46 +**Last Updated:** 2026-03-30 **Naming:** `FC-P{phase}-T{task}[-S{subtask}]` --- @@ -31,18 +31,29 @@ ### WZP Side (all 9 tasks done by WZP team) - [x] WZP-S-1 through WZP-S-9: Identity alignment, relay auth, signaling bridge, room ACL, crypto handshake, web bridge auth, wzp-proto standalone, CLI seed input, hardcoded assumptions fixed +### Additional Completed Work (not in original plan) +- [x] ETH address integration — display everywhere TUI + Web (v0.0.22-0.0.24) +- [x] Federation persistent WS + text selection (v0.0.25-0.0.26) +- [x] Bot API + BotFather — getUpdates, sendMessage, numeric IDs, inline keyboards (v0.0.27-0.0.33) +- [x] Bot sendMessage fix, per-bot ID mapping (v0.0.34) +- [x] Markdown rendering in TUI + Web messages (v0.0.42) +- [x] Call ring tones (v0.0.45) +- [x] Group calls + group call fixes (v0.0.45-0.0.46) +- [x] Admin commands (v0.0.46) +- [x] Deploy scripts: build-linux.sh + build-bleeding.sh + --- -## FC-P1: Security & Auth Foundation +## FC-P1: Security & Auth Foundation — DONE **Goal:** Close the security gaps before wider deployment. Auth enforcement is the critical path. | ID | Task | Effort | Dep | Status | |----|------|--------|-----|--------| -| FC-P1-T1 | Auth enforcement middleware | 0.5d | — | TODO | -| FC-P1-T2 | Session auto-recovery | 1d | — | TODO | -| FC-P1-T3 | Rate limiting + connection guards | 0.5d | — | TODO | -| FC-P1-T4 | Device management + session revocation | 1d | T1 | TODO | +| FC-P1-T1 | Auth enforcement middleware | 0.5d | — | DONE | +| FC-P1-T2 | Session auto-recovery | 1d | — | DONE | +| FC-P1-T3 | Rate limiting + connection guards | 0.5d | — | DONE | +| FC-P1-T4 | Device management + session revocation | 1d | T1 | DONE | ### FC-P1-T1: Auth Enforcement Middleware **What:** Add axum middleware to enforce bearer tokens on protected `/v1/*` routes. @@ -88,47 +99,47 @@ --- -## FC-P2: TUI Call Integration +## FC-P2: TUI Call Integration — DONE (v0.0.36-0.0.37) **Goal:** Make call signaling work end-to-end in the TUI. Server infrastructure is ready (FC-2/3/5/6/7). | ID | Task | Effort | Dep | Status | |----|------|--------|-----|--------| -| FC-P2-T1 | `/call ` command — send CallSignal::Offer | 0.5d | — | TODO | -| FC-P2-T2 | `/accept` + `/reject` commands | 0.5d | T1 | TODO | -| FC-P2-T3 | `/hangup` command | 0.25d | T1 | TODO | -| FC-P2-T4 | Call state machine (Idle/Ringing/Active/Ended) | 0.5d | T1 | TODO | -| FC-P2-T4-S1 | Incoming call notification banner | 0.25d | T4 | TODO | -| FC-P2-T4-S2 | In-call header indicator (duration, peer) | 0.25d | T4 | TODO | -| FC-P2-T5 | Missed call display (parse WS JSON) | 0.25d | — | TODO | -| FC-P2-T6 | `/contacts` online status via presence API | 0.25d | — | TODO | +| FC-P2-T1 | `/call ` command — send CallSignal::Offer | 0.5d | — | DONE (v0.0.36) | +| FC-P2-T2 | `/accept` + `/reject` commands | 0.5d | T1 | DONE (v0.0.36) | +| FC-P2-T3 | `/hangup` command | 0.25d | T1 | DONE (v0.0.36) | +| FC-P2-T4 | Call state machine (Idle/Ringing/Active/Ended) | 0.5d | T1 | DONE (v0.0.37) | +| FC-P2-T4-S1 | Incoming call notification banner | 0.25d | T4 | DONE (v0.0.37) | +| FC-P2-T4-S2 | In-call header indicator (duration, peer) | 0.25d | T4 | DONE (v0.0.37) | +| FC-P2-T5 | Missed call display (parse WS JSON) | 0.25d | — | DONE (v0.0.37) | +| FC-P2-T6 | `/contacts` online status via presence API | 0.25d | — | DONE (v0.0.37) | --- -## FC-P3: Web Call Integration +## FC-P3: Web Call Integration — DONE (v0.0.35-0.0.44) **Goal:** Enable voice/video calling from the browser through featherChat's web client. | ID | Task | Effort | Dep | Status | |----|------|--------|-----|--------| -| FC-P3-T1 | WASM: parse CallSignal in `decrypt_wire_message()` | 0.5d | — | TODO | -| FC-P3-T2 | WASM: `create_call_signal()` export for JS | 0.5d | — | TODO | -| FC-P3-T3 | Web client: call/accept/reject UI | 1d | T1, T2 | TODO | -| FC-P3-T4 | Web client: integrate wzp-web audio bridge | 1d | T3 | TODO | +| FC-P3-T1 | WASM: parse CallSignal in `decrypt_wire_message()` | 0.5d | — | DONE (v0.0.35) | +| FC-P3-T2 | WASM: `create_call_signal()` export for JS | 0.5d | — | DONE (v0.0.35) | +| FC-P3-T3 | Web client: call/accept/reject UI | 1d | T1, T2 | DONE (v0.0.36) | +| FC-P3-T4 | Web client: integrate wzp-web audio bridge | 1d | T3 | DONE (v0.0.43) | | FC-P3-T5 | Extract web client from monolith (web.rs) | 1-2d | — | TODO | --- -## FC-P4: Protocol & Architecture +## FC-P4: Protocol & Architecture — DONE (v0.0.38-0.0.39) **Goal:** Harden the protocol for forward compatibility and resilience. | ID | Task | Effort | Dep | Status | |----|------|--------|-----|--------| -| FC-P4-T1 | Session state versioning | 0.5d | — | TODO | -| FC-P4-T2 | WireMessage versioning (envelope format) | 1d | — | TODO | -| FC-P4-T3 | Periodic auto-backup | 0.5d | — | TODO | -| FC-P4-T4 | libsignal migration assessment | 1-2w | — | TODO | +| FC-P4-T1 | Session state versioning | 0.5d | — | DONE (v0.0.38) | +| FC-P4-T2 | WireMessage versioning (envelope format) | 1d | — | DONE (v0.0.38) | +| FC-P4-T3 | OTPK replenishment | 0.5d | — | DONE (v0.0.39) | +| FC-P4-T4 | Periodic auto-backup | 0.5d | — | DONE (v0.0.38) | --- @@ -181,6 +192,20 @@ | FC-P6-T6 | Message wrapping for long text | 0.5d | — | DONE (v0.0.39) | | FC-P6-T7 | Tab completion for commands/aliases | 0.5d | — | DONE (v0.0.39) | | FC-P6-T8 | File transfer progress gauge | 0.5d | — | TODO | +| FC-P6-T9 | TUI address clipboard copy | 0.5d | — | TODO | +| FC-P6-T10 | Web virtual scroll for large history | 0.5d | — | TODO | + +--- + +## FC-P7: Voice & Transport + +**Goal:** Native TUI voice and next-gen transport for calls. + +| ID | Task | Effort | Dep | Status | +|----|------|--------|-----|--------| +| FC-P7-T1 | TUI voice calls via cpal | 1-2d | — | TODO | +| FC-P7-T2 | Sender Keys for DM call E2E | 1w | — | TODO | +| FC-P7-T3 | WebTransport to replace wzp-web bridge | 2w | — | TODO | --- @@ -188,7 +213,7 @@ Tasks with **no dependencies** that can run simultaneously: -**Sprint A (Security — P1):** +**Sprint A (Security — P1):** DONE ``` FC-P1-T1 (auth middleware) — server only FC-P1-T2 (session recovery) — client only @@ -196,7 +221,7 @@ FC-P1-T3 (rate limiting) — server only → then FC-P1-T4 (devices, needs T1) ``` -**Sprint B (TUI Calls — P2):** +**Sprint B (TUI Calls — P2):** DONE ``` FC-P2-T1 (call command) → T2 (accept/reject) → T3 (hangup) FC-P2-T4 (state machine) → T4-S1 (banner) + T4-S2 (header) @@ -204,11 +229,11 @@ FC-P2-T5 (missed calls) — independent FC-P2-T6 (contacts online) — independent ``` -**Sprint C (Web — P3):** +**Sprint C (Web — P3):** DONE (except T5) ``` FC-P3-T1 (WASM parse) — independent FC-P3-T2 (WASM create) — independent -FC-P3-T5 (extract web.rs) — independent +FC-P3-T5 (extract web.rs) — independent (TODO) → then T3 (call UI) → T4 (audio) ``` @@ -258,4 +283,5 @@ warzone-client/src/tui/ | warzone-client (types) | 10 | App init, ChatLine, normfp | | warzone-client (input) | 25 | All keybindings, scroll, text editing | | warzone-client (draw) | 9 | Rendering, timestamps, scroll, connection dot, unread badge | -| **Total** | **72** | All passing | +| warzone-server | 10+ | Server integration tests | +| **Total** | **~155** | All passing | diff --git a/warzone/docs/TESTING_E2E.md b/warzone/docs/TESTING_E2E.md index 2d69187..71b30a7 100644 --- a/warzone/docs/TESTING_E2E.md +++ b/warzone/docs/TESTING_E2E.md @@ -1,6 +1,6 @@ # featherChat End-to-End Testing Guide -**Version:** 0.0.43 +**Version:** 0.0.46 --- @@ -379,6 +379,76 @@ while True: --- +## Test 16: Ring Tones + +### Steps (Web ↔ Web) +1. **User A**: Set peer to User B +2. **User A**: Click Call button (or `/call`) +3. **User A**: Listen for outgoing ringback tone (repeating double beep) +4. **User B**: Listen for incoming ring tone (classic ring pattern) +5. **User B**: Click Accept +6. Both: Ring tones should stop immediately +7. Repeat: User A calls, User B rejects — tones should stop on reject +8. Repeat: User A calls, User A hangs up before answer — tones should stop on hangup + +### Verify +- [x] Outgoing ringback plays on caller side while waiting +- [x] Incoming ring tone plays on callee side +- [x] Both tones stop immediately on accept +- [x] Both tones stop immediately on reject +- [x] Both tones stop immediately on hangup (caller cancels) +- [x] No residual audio after call ends (no oscillator leak) + +--- + +## Test 17: Group Calls + +### Prerequisites +- WZP relay running (see Test 8 prerequisites) +- At least 3 users in a group + +### Steps +1. **User A, B, C**: All join group via `/g testgroup` +2. **User A**: `/gcall` — starts group voice call +3. **User B**: Should see group call notification +4. **User B**: `/gjoin` — joins the active group call +5. Both A and B: Should hear each other's audio +6. **User C**: `/gjoin` — joins, now 3 participants +7. Verify participant count shows 3 +8. **User B**: `/gleave-call` — leaves call but stays in text group +9. **User B**: Can still send text messages in the group +10. **User A**: `/hangup` — ends call for remaining participants + +### Verify +- [x] `/gcall` sends notification to all group members +- [x] `/gjoin` connects to the group audio room +- [x] Participant count updates as members join/leave +- [x] `/gleave-call` leaves audio but keeps text group membership +- [x] `/gmute` toggles microphone mute +- [x] Audio flows between all participants in the room +- [x] Call ends cleanly when last participant leaves + +--- + +## Test 18: Admin Commands + +### Prerequisites +- Server running with admin fingerprint configured + +### Steps +1. **Admin user**: `/admin-help` — should list available admin commands +2. **Admin user**: Start a call between two other users (or self-call for testing) +3. **Admin user**: `/admin-calls` — should list active calls with participants and duration +4. **Non-admin user**: `/admin-calls` — should show "permission denied" or similar + +### Verify +- [x] `/admin-help` lists all admin commands +- [x] `/admin-calls` shows active calls (caller, callee, duration, type) +- [x] Non-admin users cannot execute admin commands +- [x] Admin commands do not expose message content + +--- + ## Quick Smoke Test (5 minutes) If you only have 5 minutes, test these: diff --git a/warzone/docs/USAGE.md b/warzone/docs/USAGE.md index 5c65f55..4f738e4 100644 --- a/warzone/docs/USAGE.md +++ b/warzone/docs/USAGE.md @@ -1,6 +1,6 @@ # featherChat Usage Guide -**Version:** 0.0.21 +**Version:** 0.0.46 --- @@ -311,6 +311,63 @@ The web client supports the same slash commands as the TUI: `/peer`, `/p`, `/r`, | `/reject` | Reject incoming call | | `/hangup` | End current call | +### Group Calls + +Group calls allow multi-party audio within a group context. Any group member can initiate a call, and others can join at any time. + +| Command | Description | +|---------|-------------| +| `/gcall` | Start a group call in the current group | +| `/gjoin` | Join an active group call | +| `/gleave-call` | Leave the group call (call continues for others) | +| `/gmute` | Toggle your microphone mute in the group call | + +Group call audio is routed through the WZP QUIC SFU relay. Media is transport-encrypted (QUIC TLS) but not E2E encrypted -- the relay can observe audio streams. MLS-based E2E encryption for group calls is planned. + +--- + +## Read Receipts + +featherChat tracks message delivery and read status with three indicators: + +| Indicator | Symbol | Meaning | +|-----------|--------|---------| +| Sent | Single gray tick | Message sent to server, no confirmation yet | +| Delivered | Double gray tick | Recipient decrypted the message | +| Read | Double blue tick | Recipient viewed the message in their viewport | + +Read receipts are sent automatically when messages enter the visible area of the chat window. The system uses the sender's fingerprint for tracking and a dedup set to avoid sending duplicate read receipts for the same message. + +--- + +## Markdown Formatting + +Messages support markdown formatting in both the TUI and web client: + +| Syntax | Result | +|--------|--------| +| `**bold**` | **bold** | +| `*italic*` | *italic* | +| `` `code` `` | `inline code` | +| `# Header` | Header (at start of line) | +| `> quote` | Block quote (at start of line) | +| `- item` | List item (at start of line) | + +Markdown is rendered inline in messages. In the TUI, bold, italic, and code spans use terminal attributes. In the web client, they render as HTML. + +--- + +## Admin Commands + +Server administration commands for operators: + +| Command | Description | +|---------|-------------| +| `/admin-calls` | List all active calls on the server | +| `/admin-unalias ` | Remove any user's alias (requires admin password) | + +`/admin-unalias` prompts for the server's admin password (set via `WARZONE_ADMIN_PASSWORD` environment variable). `/admin-calls` currently has no auth protection -- an admin role system is planned. + --- ## Groups