poll_messages now collects all queued messages, returns them,
then deletes them from sled. No more duplicate delivery.
This is correct for store-and-forward: once the client receives
the messages, the server's job is done. If the client crashes
before processing, the messages are lost — acceptable for Phase 1.
Phase 2 can add explicit ack-based delivery if needed.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Axum 0.7 uses :param for path parameters. {param} is axum 0.8+ syntax.
Routes were silently not matching, causing 404 on all key lookups.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Client: strip colons before putting fingerprints in URL paths
(colons in URLs confuse axum path matching).
Server: normalize fingerprints in message routes too.
All fingerprint storage and lookup is now hex-only, case-insensitive.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Server: normalize fingerprints by stripping colons and lowercasing
before storing/looking up in sled. Adds tracing for register/lookup.
Client: check HTTP status before parsing JSON response body.
Shows clear error when user is not registered instead of parse error.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Was showing xxxx:xxxx:xxxx:xxxx (8 bytes) but from_hex expected
16 bytes, causing parse failure. Now displays all 16 bytes:
xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx
Users need to re-init to see the full fingerprint.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
All data paths now use keystore::data_dir() which checks
WARZONE_HOME first, falls back to ~/.warzone.
This avoids the HOME override hack that breaks rustup/cargo.
Usage: WARZONE_HOME=/tmp/bob warzone init
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
X3DH fix:
- Added identity_encryption_key (X25519) to PreKeyBundle
- initiate() and respond() now use correct DH operations per Signal spec:
DH1=IK_a*SPK_b, DH2=EK_a*IK_b, DH3=EK_a*SPK_b, DH4=EK_a*OPK_b
- All 17 tests pass including x3dh_shared_secret_matches
Web client (served at /):
- Identity generation with seed (stored in localStorage)
- Recovery from hex-encoded seed
- Auto-load saved identity on page load
- Fingerprint display (same format as CLI: xxxx:xxxx:xxxx:xxxx)
- Key registration with server via /v1/keys/register
- Chat UI with message polling (5s interval)
- Commands: /help, /info, /seed
- Dark theme matching warzone aesthetic
Both clients (CLI + Web) now exist:
- CLI: warzone init, warzone info, warzone recover
- Web: http://localhost:7700/ (served by warzone-server)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Key transparency via DNS TXT records with self-signatures
(server can't MITM because it can't forge user's signature)
- Per-device ratchet sessions (Signal model), cross-device sync via seed
- LoRa deferred to later phases, not Phase 1
- Sealed sender before onion routing
- Phase 3 updated to include key transparency alongside federation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Decisions: Sender Keys for groups, optional onion routing, deniability
by default, Bluetooth + LoRa transports, no tokenization.
New sections: transport abstraction (HTTPS/WS/BT/LoRa/Wi-Fi Direct/USB),
LoRa compact binary format, sealed sender vs onion routing discussion.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- /reply <msg> or /r <msg> sends encrypted DM to last person
- lastDmPeer set when sending a DM or receiving one
- Shows error if no prior DM conversation exists
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Browser:
- ECDH key pair saved to localStorage (chat-key-priv, chat-key-pub)
- Loaded on reconnect, only generated once
- Re-registers public key with server on every connect
- Corrupted keys auto-regenerate
Server:
- Keys saved to keys.json on disk after each registration
- Loaded on startup, survives restarts
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Server:
- /keys POST: register ECDH public key (JWK) for a username
- /keys GET: list users with registered keys
- /keys/<user> GET: get user's public key
- /dm POST: relay encrypted DM blob to recipient
- SSE streams now register for DM delivery via name param
- Server never sees plaintext - only ciphertext passes through
Web UI:
- Auto-generates ECDH P-256 key pair on load (no setup needed)
- /dm @username message - sends E2E encrypted DM
- /users - list users with registered keys
- DMs shown with lock icon, pink color, direction arrows
- Decryption happens entirely in browser
- Key re-registered on name change
- Derived AES keys cached per peer
Protocol:
- ECDH key exchange: each client exports JWK public key
- Shared secret derived via ECDH P-256
- Messages encrypted with AES-256-GCM + random 12-byte nonce
- Ciphertext + nonce sent as base64 through server
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- /group/<name> URL creates/joins a group (auto-created on first visit)
- / and /chat redirect to /group/lobby (default group)
- Each group has isolated history, clients, and SSE streams
- /setpass <password> sets a password for the current group
- /clearpass removes the password
- Password prompt modal in web UI, stored in sessionStorage
- SSE sends auth-fail event if wrong password, triggers re-prompt
- Group name shown as tag in header
- TCP clients use lobby group by default
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Web manifest with standalone display mode
- SVG chat bubble icon (no external assets needed)
- Service worker for install + offline page
- iOS meta tags: apple-mobile-web-app-capable, status bar style
- Mobile-optimized layout: safe-area insets, dvh units, rounded inputs
- Name input moved to header, file button + send in bottom bar
- 16px font on input (prevents iOS zoom)
- Name persisted to localStorage on mobile
- Keyboard-aware scroll (visualViewport resize listener)
- Install banner with prompt for Android Chrome
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Server:
- /tunnel/<dest> routes: parspack (185.208.174.152:22),
mequ (188.213.68.133:2022), alipi (10.66.66.2:22)
- /tunnel without dest defaults to parspack
Client (tunnel.py):
- --destination / -d flag to pick target
- Lists available destinations in --help
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Web UI:
- Requests browser notification permission on load
- Shows desktop notification for messages from others when tab unfocused
- Tab title shows unread count: "(3) Chat"
- Resets on focus
Terminal client:
- Bell (\a) on messages from others
- Terminal title updates to show sender and preview
- Title resets when user types
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Web UI:
- Textarea replaces input: Shift+Enter for newline, Enter to send
- Pasted text preserves newlines, tabs, whitespace
- Markdown: ```code blocks```, `inline code`, **bold**, *italic*, auto-links
- File upload button (paperclip icon), files stored in memory with download links
Python CLI client:
- Colored usernames: green for self, cyan for system, unique color per other user
- /file <path> command to upload files
- Multiline messages displayed with continuation indent
- JSON protocol for multiline + file support (backwards compatible)
Server:
- POST /chat/upload for multipart file uploads
- GET /files/<id>/<name> for file downloads
- TCP protocol accepts JSON packets for multiline text and file transfers
- Falls back to plain text for old clients
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- chat.py: multi-user chat server (stdlib only, single port)
- Web UI at /chat with SSE real-time messaging
- Per-user colors (green for self, palette for others)
- Curses TUI client with scroll support
- WebSocket SSH tunnel at /tunnel -> 185.208.174.152:22
- /version endpoint for deployment verification
- /tunnel.py download endpoint
- tunnel.py: SSH-over-WebSocket client with custom DNS support
- nginx: Kubernetes manifests (Deployment + Service + Ingress)
- Reverse proxy to chat.py at 188.213.68.133:9997
- SSE buffering disabled, WebSocket upgrade for /tunnel
- nginx.txt: alternate nginx deployment with different ingress host
- apache: Bitnami Apache Helm values (initial attempt, replaced by nginx)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>