118 Commits

Author SHA1 Message Date
Siavash Sameni
4a4fa9fab4 v0.0.21: FC-CRATE-1 — make warzone-protocol importable standalone
Replaced workspace dep inheritance with explicit versions in
warzone-protocol/Cargo.toml. The crate now builds both as a
workspace member AND standalone.

WZP can now import warzone-protocol directly:
  warzone-protocol = { path = "../featherChat/warzone/crates/warzone-protocol" }

This means WZP can delete its mirrored identity/crypto code and use:
- warzone_protocol::identity::{Seed, IdentityKeyPair, PublicIdentity}
- warzone_protocol::crypto::{hkdf_derive, aead_encrypt, aead_decrypt}
- warzone_protocol::ethereum::{derive_eth_identity, EthAddress}
- warzone_protocol::message::{WireMessage, CallSignalType}
- warzone_protocol::types::Fingerprint

Single source of truth for identity derivation — no more HKDF mismatches.

28/28 tests pass. Zero warnings.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 09:21:18 +04:00
Siavash Sameni
064a730b42 v0.0.21: WZP integration groundwork — CallSignal + token validation
WZP-FC-1: CallSignal WireMessage variant
- CallSignalType enum: Offer, Answer, IceCandidate, Hangup, Reject, Ringing, Busy
- Routed through existing E2E encrypted channels
- Server dedup handles new variant
- TUI shows "📞 Call signal: Offer" etc
- CLI recv prints call signals

WZP-FC-4: Token validation endpoint
- POST /v1/auth/validate { "token": "..." }
- Returns: { "valid": true, "fingerprint": "...", "alias": "..." }
- WZP relay calls this to verify featherChat bearer tokens
- Resolves alias alongside fingerprint

These two unblock WZP integration tasks WZP-S-2 (accept FC tokens)
and WZP-S-3 (signaling bridge mode).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 09:13:23 +04:00
Siavash Sameni
65f639052e Append WZP integration tasks to FUTURE_TASKS.md (238→676 lines)
featherChat side (10 tasks):
  WZP-FC-1: CallSignal WireMessage variant (2-4h)
  WZP-FC-2: Call state management + sled tree (1-2d)
  WZP-FC-3: WS handler for call signaling (0.5d)
  WZP-FC-4: Auth token validation endpoint (2-4h)
  WZP-FC-5: Group-to-room mapping (1d)
  WZP-FC-6: Presence/online status API (0.5-2d)
  WZP-FC-7: Missed call notifications (0.5d)
  WZP-FC-8: Cross-project identity verification test (2-4h) CRITICAL
  WZP-FC-9: HKDF salt investigation — VERIFIED: no mismatch (b""→None == None)
  WZP-FC-10: WZP web bridge shared auth (1-2d)

WZP side suggestions (9 items):
  WZP-S-1 through WZP-S-9 covering auth, signaling bridge,
  room access control, proto publishing, CLI flags, and
  6 hardcoded assumptions that conflict with integration.

All tasks reference specific file:line in both codebases.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 08:50:13 +04:00
Siavash Sameni
007ca7521d FUTURE_TASKS.md: 18 optional tasks with questions-before-starting
High priority:
  1. Auth enforcement middleware
  2. Session auto-recovery
  3. Crypto audit plan

Medium priority:
  4. Extract web client from monolith
  5. Session state versioning
  6. Periodic auto-backup
  7. WireMessage versioning

Normal priority:
  8. Mule binary implementation
  9. libsignal migration assessment
  10. OIDC identity provider
  11. Smart contract ACL
  12. DNS federation
  13. WarzonePhone integration

Low priority:
  14. Message search
  15. Read receipts
  16. Typing indicators
  17. Message reactions
  18. Voice messages

Each task includes: what, why, effort estimate, and blocking
questions that must be answered before work begins.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 08:21:14 +04:00
Siavash Sameni
de1ce77fea IDP_SMART_CONTRACT.md: featherChat as IdP + on-chain ACL (1111 lines)
featherChat as Identity Provider:
- OIDC provider endpoints (/auth/oidc/authorize, /token, /userinfo)
- JWT tokens with fingerprint, alias, eth_address, groups claims
- Authentik integration (featherChat as upstream IdP, group sync)
- SAML support for enterprise

Smart Contract Access Control:
- FeatherChatACL Solidity contract (server/group/feature access)
- secp256k1 address from same BIP39 seed = on-chain identity
- NFT-gated access (ERC-721/ERC-1155 membership)
- Token-gated access (ERC-20 staking)
- DAO governance for group membership decisions
- UUPS upgradeable proxy pattern

Hybrid architecture:
- OIDC token carries on-chain permissions as claims
- Event-driven sync (WebSocket RPC + periodic poll + sled cache)
- L2 deployment (Arbitrum/Base/Polygon) for low gas costs

Feasibility: 7-11 weeks across 4 phases.
Comparison with SpruceID, Ceramic, Lens, XMTP.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 08:07:34 +04:00
Siavash Sameni
1c7b39c395 Rewrite WZP_INTEGRATION.md with confirmed code references (1209 lines)
All [SPECULATIVE] markers replaced with [CONFIRMED] from actual WZP code.

Key discoveries:
- HKDF info string mismatch: featherChat uses "warzone-ed25519",
  WZP uses "warzone-ed25519-identity" — same seed, different keys.
  Requires 2-line fix in wzp-crypto/src/handshake.rs before integration.
- Media is NOT DTLS-SRTP: WZP uses ephemeral X25519 DH + ChaCha20-Poly1305
  with deterministic nonces (WireGuard-like, not WebRTC-like)
- Transport is QUIC (quinn), not WebRTC/ICE
- FEC is RaptorQ fountain codes, not Opus inband
- 5 codecs: Opus 24k → Codec2 1200bps with adaptive switching
- Relay operates on encrypted packets (zero-knowledge relay)

18 sections with concrete API contracts, code file:line references,
and phased implementation roadmap.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 08:02:30 +04:00
Siavash Sameni
f7a517d8ea WZP_INTEGRATION.md: featherChat ↔ WarzonePhone integration spec (1001 lines)
Covers: shared identity model (same BIP39 seed), authentication flow
(Ed25519 signed tokens), call signaling via WireMessage::CallSignal,
DTLS-SRTP media encryption bootstrapped from Double Ratchet,
group calls (SFU + Sender Keys), warzone scenarios (voice messages
as attachments, mule delivery for missed calls).

Phased roadmap: shared identity → signaling → encrypted calls → group calls.

featherChat-side details confirmed against code.
WZP-side details marked [SPECULATIVE] (WZP codebase was inaccessible).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 05:38:45 +04:00
Siavash Sameni
2dbbc61dfe Comprehensive documentation: architecture, usage, integration, progress, security
docs/ARCHITECTURE.md (531 lines):
  System design, ASCII diagrams, crypto stack, dual-curve identity,
  wire protocol (7 WireMessage variants), server/client architecture,
  data flow diagrams, storage model, extensibility points

docs/USAGE.md (550 lines):
  Complete user guide: installation, all CLI commands (10),
  all TUI commands (20+), all web commands, file transfer,
  identity management, aliases, groups, multi-device, backup,
  keyboard shortcuts

docs/INTEGRATION.md (542 lines):
  WarzonePhone concept, Ethereum/Web3, OIDC, DNS federation,
  transport abstraction, multi-server mode, custom clients,
  ntfy, how-to guides for extending message types/commands/storage

docs/PROGRESS.md (234 lines):
  Timeline, Phase 1 (16 features), Phase 2 (16 features),
  v0.0.20, 28 tests, bugs fixed, known limitations, Phase 3-7 roadmap

docs/SECURITY.md (438 lines):
  Threat model, 8 crypto primitives, key derivation paths,
  forward secrecy, Sender Keys trade-offs, seed security,
  server trust, WASM security, known weaknesses,
  comparison with Signal/Matrix/SimpleX

Total: 3,751 lines across 8 doc files.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 05:25:46 +04:00
Siavash Sameni
fb987da8ac v0.0.20: file transfer in groups
/file <path> now works in group mode (#group):
- Sends file header + chunks to each group member
- Same fan-out approach as group text messages
- Each member receives and reassembles independently
- Progress shown: "Sending 'file.pdf' to group #ops..."

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 20:23:19 +04:00
Siavash Sameni
1601decf33 v0.0.19: contact list + message history (local, persistent)
Storage:
- contacts sled tree: auto-tracked on send/receive
  - fingerprint, alias, first_seen, last_seen, message_count
- history sled tree: all messages stored locally
  - key: hist:<peer_fp>:<timestamp>:<uuid> for ordered scan
  - sender, text, is_self, timestamp

TUI commands:
- /contacts or /c — list all contacts (sorted by most recent)
  Shows alias, fingerprint, message count
- /history or /h — show last 50 messages with current peer
- /h <fingerprint> — show history with specific peer

Auto-tracking:
- On send: touch_contact + store_message (is_self=true)
- On receive: touch_contact + store_message (is_self=false)
- Both KeyExchange and Message variants tracked

Backup: contacts + history included in export_all (encrypted backup).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 20:16:22 +04:00
Siavash Sameni
741e6fbcfd v0.0.18: proper line editing in TUI input
Keyboard shortcuts:
- Left/Right: move cursor
- Home / Ctrl+A: beginning of line
- End / Ctrl+E: end of line
- Alt+Left/Right: word jump
- Alt+Backspace: delete word back
- Ctrl+W: delete word back
- Ctrl+U: clear entire line
- Ctrl+K: kill to end of line
- Delete: delete char at cursor
- Backspace: delete char before cursor

Cursor position tracked, chars insert at cursor (not just append).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 20:04:12 +04:00
Siavash Sameni
a4405b4976 v0.0.17: fix /r reply in TUI, /p shortcut, /eth, /unalias
TUI fixes:
- /r and /reply now work: tracks last_dm_peer from received messages
- /r switches peer to last DM sender, then type normally
- /p @alias works as shortcut for /peer @alias
- /eth shows Ethereum address in TUI
- /unalias removes your alias

Web fixes:
- /p @alias and /peer @alias resolve and set peer
- /r and /reply work (switch to last DM sender)
- /unalias removes alias
- /admin-unalias <alias> <password> for admin removal
- File download now shows as clickable link (not auto-download)

Server:
- POST /v1/alias/unregister — remove own alias
- POST /v1/alias/admin-remove — admin removes any alias
- WARZONE_ADMIN_PASSWORD env var (default: "admin")

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 19:50:00 +04:00
Siavash Sameni
f4eac7b2aa v0.0.16: clickable file download link instead of auto-download
Files now appear as a styled clickable link in chat:
📎 filename.pdf (1.6 KB) from sender
Click to download. No auto-save dialog.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 18:17:33 +04:00
Siavash Sameni
ebaf5df671 Web file transfer: send + receive with auto-download
Web client:
- Paperclip file upload button in chat bar
- Chunked upload: 64KB chunks, SHA-256 integrity
- Progress display during send/receive
- Auto-download on complete (browser save dialog)
- Max 10MB per file

WASM:
- decrypt_wire_message now returns file_header and file_chunk
  with type, id, filename, chunk data (hex encoded)

Receive flow:
- FileHeader: registers pending transfer
- FileChunk: stores chunk, shows progress
- All chunks received: assembles, triggers blob download

Send flow (web→web or web→CLI):
- File sent as JSON messages (not bincode, for simplicity)
- Receiver handles both JSON and bincode formats

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 16:07:17 +04:00
Siavash Sameni
c9f3e338a7 Add /p as alias for /peer (both TUI and web), web /p @alias support
TUI: /p @manwe works same as /peer @manwe
Web: /p @manwe and /peer @manwe resolve alias and set peer input

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 15:38:35 +04:00
Siavash Sameni
9c70e02eba v0.0.15: unalias, admin alias removal, /reply, web version fix
Aliases:
- /unalias — remove your own alias
- /admin-unalias <alias> <password> — admin removes any alias
- Admin password via WARZONE_ADMIN_PASSWORD env var (default: "admin")
- POST /v1/alias/unregister + POST /v1/alias/admin-remove

Reply:
- /r or /reply — switches peer to whoever last DM'd you
- lastDmPeer tracked on both web and TUI
- Then type normally to reply

Web:
- Version bumped to 0.0.15 (was stuck at 0.0.10)
- WASM rebuilt with latest protocol

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 14:12:33 +04:00
Siavash Sameni
608a160614 Fix warnings: remove unused import and variable
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 13:39:05 +04:00
Siavash Sameni
661de47552 v0.0.14: Ethereum-compatible identity (secp256k1 + Keccak-256)
Protocol (ethereum.rs):
- derive_eth_identity(): HKDF from seed (info="warzone-secp256k1")
- secp256k1 signing key (k256 crate)
- Ethereum address: Keccak-256(uncompressed_pubkey[1..])[-20:]
- EIP-55 checksum address formatting
- eth_sign() / eth_verify() for secp256k1 ECDSA
- EthAddress type with Display, hex parsing, checksum
- 5 tests: deterministic, format, checksum, sign/verify, uniqueness

CLI:
- `warzone eth` — show Ethereum address alongside Warzone fingerprint
- Same seed produces both identities (dual-curve)

Dual identity model:
- Ed25519 + X25519 for Warzone messaging (fast, small signatures)
- secp256k1 for Ethereum compatibility (MetaMask, ENS, Ledger/Trezor)
- Both derived from the same BIP39 seed via different HKDF paths

28/28 protocol tests pass.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 13:30:25 +04:00
Siavash Sameni
86da52acc4 v0.0.13: Sender Keys for efficient group encryption
Protocol (sender_keys.rs):
- SenderKey: symmetric key with chain ratchet (forward secrecy per chain)
- generate(), rotate(), encrypt(), decrypt()
- SenderKeyDistribution: share key via 1:1 encrypted channel
- SenderKeyMessage: encrypted group message (O(1) instead of O(N))
- Chain key ratchets forward on each message (HKDF)
- Generation counter for key rotation tracking
- 4 tests: basic, multi-message, rotation, old-key rejection

WireMessage:
- GroupSenderKey variant: encrypted group message
- SenderKeyDistribution variant: key sharing

Server: dedup handles new variants.
CLI TUI + recv: stub handlers for new message types.
23/23 protocol tests pass.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 13:23:10 +04:00
Siavash Sameni
653c6c050b v0.0.12: Encrypted backup/restore + history module
Protocol:
- history.rs: derive_history_key (HKDF from seed, info="warzone-history")
- encrypt_history / decrypt_history (ChaCha20-Poly1305, WZH1 magic)
- 2 new tests (roundtrip + wrong seed), total 19/19

CLI:
- `warzone backup [output.wzb]` — exports all sessions + pre-keys
  as encrypted blob (only your seed can decrypt)
- `warzone restore <input.wzb>` — imports backup, merges (no overwrite)
- Backup format: WZH1 magic + nonce + encrypted JSON

Storage:
- export_all() — dumps sessions + pre-keys as base64 JSON
- import_all() — merges backup data (skip existing entries)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 12:59:54 +04:00
Siavash Sameni
fff443bb6d v0.0.11: Multi-device support (server-side)
Server:
- Register stores per-device bundles: device:<fp>:<device_id>
- GET /v1/keys/:fp/devices lists all registered devices
- WS already pushes to ALL connected devices per fingerprint
- DB queue: first device to poll gets messages (acceptable for Phase 2)

Multi-device flow:
- Same seed on two devices → same fingerprint
- Both register with different device_ids
- Both connect via WS → both receive messages in real-time
- Each device maintains its own ratchet sessions

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 12:52:22 +04:00
Siavash Sameni
9811248b7c v0.0.10: Progressive Web App (PWA)
- Web manifest (standalone mode, theme, icon)
- Service worker: caches shell (HTML, WASM, icon) for offline
- SVG app icon (chat bubble with encryption indicator)
- iOS meta tags: apple-mobile-web-app-capable, status bar style
- Android: beforeinstallprompt → /install command
- Offline fallback: loads cached shell, shows reconnecting state
- Cache versioning with automatic old cache cleanup

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 12:32:59 +04:00
Siavash Sameni
4fb3973403 v0.0.9: Group management — leave, kick, members
Server:
- POST /groups/:name/leave — remove self from group
- POST /groups/:name/kick — creator can kick members
- GET /groups/:name/members — list with aliases + creator badge

CLI TUI:
- /gleave — leave current group
- /gkick <fp_or_alias> — kick (creator only)
- /gmembers — show member list with aliases and ★ for creator

Web client:
- Same commands: /gleave, /gkick, /gmembers

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 12:04:28 +04:00
Siavash Sameni
2599ce956a v0.0.8: Server-side message deduplication
Server:
- DedupTracker in AppState: bounded HashSet (10,000 IDs, FIFO eviction)
- send_message: extracts message ID from bincode, drops duplicates
- WS handler: dedup on both binary and JSON message frames
- extract_message_id() parses all WireMessage variants

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 11:00:58 +04:00
Siavash Sameni
708080f7be v0.0.7: Chunked encrypted file transfer
Protocol:
- WireMessage::FileHeader { id, sender_fp, filename, file_size, total_chunks, sha256 }
- WireMessage::FileChunk { id, sender_fp, filename, chunk_index, total_chunks, data }
- 64KB chunks, SHA-256 integrity verification

CLI TUI:
- /file <path> command: reads file, chunks, encrypts each with ratchet, sends
- Progress display: "Sending file.pdf [3/10]..."
- Incoming file reassembly with chunk tracking
- SHA-256 verification on complete
- Saves to data_dir/downloads/
- Max file size: 10MB

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 10:26:05 +04:00
Siavash Sameni
104ba78b85 v0.0.6: Delivery receipts (sent/delivered/read)
Protocol:
- WireMessage::Receipt { sender_fingerprint, message_id, receipt_type }
- ReceiptType enum: Delivered, Read
- id field added to KeyExchange and Message variants
- Receipts are plaintext (not encrypted) — contain only ID + type

Web client:
- Auto-sends Delivered receipt on successful decrypt
- Tracks sent message IDs with receipt status
- Displays: ✓ (sent, gray), ✓✓ (delivered, white), ✓✓ (read, blue)
- Receipt indicators update live via DOM reference

CLI TUI:
- Auto-sends Delivered receipt back to sender on decrypt
- Tracks receipt status per message ID
- Displays receipt indicators after sent messages

WASM:
- create_receipt() function for web client
- encrypt_with_id/encrypt_key_exchange_with_id for tracking
- decrypt_wire_message handles Receipt variant

17/17 protocol tests pass. Zero warnings.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 10:12:43 +04:00
Siavash Sameni
5b21a0e58b Fix group messages: push via WebSocket, not just DB queue
Group send_to_group was writing directly to sled DB, bypassing
the WS push. Connected clients never received group messages.

Now tries push_to_client() first (instant WS delivery),
falls back to DB queue if recipient is offline.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 09:55:08 +04:00
Siavash Sameni
fe2b7d8e8a TUI client: WebSocket with HTTP fallback
poll_loop now:
1. Tries WebSocket connection to /v1/ws/<fingerprint>
2. On success: receives messages in real-time (instant push)
3. On disconnect: reconnects after 3 seconds
4. On WS failure: falls back to HTTP polling every 2 seconds

Refactored message processing into shared functions:
- process_incoming() handles raw bytes
- process_wire_message() handles deserialized WireMessage
- Used by both WS and HTTP paths

Both CLI TUI and web client now use WebSocket:
- No more HTTP polling spam in server logs
- Messages arrive instantly on both clients
- HTTP poll kept as fallback for scripts/mules

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 09:49:46 +04:00
Siavash Sameni
c8a95e27e4 Fix 3 warnings: unused import, unused variable, dead code
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 09:43:50 +04:00
Siavash Sameni
2ca25fd2bf v0.0.5: WebSocket real-time messaging
Server:
- WS endpoint: /v1/ws/:fingerprint
- Connection registry in AppState (fingerprint → WS senders)
- On connect: flushes queued DB messages, then pushes in real-time
- send_message: pushes to WS if connected, falls back to DB queue
- Auto-cleanup on disconnect
- WS accepts both binary and JSON text frames for sending

Web client:
- Replaces 2-second HTTP polling with persistent WebSocket
- Auto-reconnects on disconnect (3-second backoff)
- Sends via WS when connected, HTTP fallback
- Messages arrive instantly (no polling delay)
- "Real-time connection established" shown on connect

HTTP polling still works:
- CLI recv command uses HTTP (unchanged)
- Web falls back to HTTP if WS fails
- Mules/scripts can still use HTTP API

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 09:41:50 +04:00
Siavash Sameni
4fc1cc2ab1 v0.0.4: unique colors per peer in web UI
Each peer gets a stable color from a 12-color palette based on
their fingerprint/alias hash. Self messages stay green.
No more same-color for different users.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 09:30:55 +04:00
Siavash Sameni
1aba435af3 v0.0.3: fix X3DH OTPK mismatch — web bundles without OTPKs
Root cause: web client's bundle included OTPKs, so X3DH initiate()
did 4 DH ops (DH4 with OTPK). But decrypt_wire_message() called
respond() with None for OTPK, doing only 3 DH ops.
Different DH concat → different shared secret → decrypt fails.

Fix: web client bundles have one_time_pre_key: None.
initiate() skips DH4 when no OTPK present.
respond() also skips DH4 with None.
Both sides now do exactly 3 DH ops → shared secrets match.

OTPKs are an anti-replay optimization, not required for E2E.
Will add OTPK support to web client in Phase 2 with proper
server-side OTPK storage and consumption tracking.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 09:24:31 +04:00
Siavash Sameni
de3b74bb9d v0.0.2: add version display, detailed self-test with step-by-step decrypt
- Version shown on chat load (v0.0.2)
- Self-test now does step-by-step: X3DH shared secret comparison,
  then manual ratchet init + decrypt (not via decrypt_wire_message)
- Shows: rng output, shared_match, alice/bob shared secrets, decrypt result
- This isolates whether X3DH or ratchet or AEAD fails

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 09:19:01 +04:00
Siavash Sameni
54a66fa0ee Fix warnings: unused variable, profile in non-root package
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 09:12:55 +04:00
Siavash Sameni
99783c1fa4 Self-test: add X3DH shared secret comparison for debugging
Shows alice_shared vs bob_shared to verify X3DH produces same secret.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 09:11:17 +04:00
Siavash Sameni
9814b0d39e Add WASM self-test, bundle debug, /selftest and /bundleinfo commands
/selftest — runs full Alice→Bob encrypt/decrypt cycle within WASM
  (tests X3DH + Double Ratchet + bincode serialize/deserialize)

/bundleinfo — dumps bundle contents, verifies SPK secret matches
  SPK public key in the registered bundle

These help isolate whether the bug is in WASM crypto (self-test fails)
or in CLI↔WASM interop (self-test passes but cross-client fails).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 09:06:08 +04:00
Siavash Sameni
c966f3bd64 Add /reset and /sessions debug commands to web client
/reset — clears all localStorage (identity, sessions, SPK)
/sessions — shows active session peers and SPK secret prefix

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 08:58:53 +04:00
Siavash Sameni
19f316c32b Fix module script scope: wire buttons via JS instead of HTML onclick
<script type="module"> doesn't expose functions to onclick attributes.
Replaced all onclick="fn()" with document.getElementById().onclick = fn
so buttons work from module scope.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 08:56:33 +04:00
Siavash Sameni
99da095a0f Fix WASM decrypt: store SPK secret, pass to decrypt_wire_message
Root cause: WASM was regenerating random pre-keys on every call to
decrypt_wire_message, instead of using the SPK that was registered
with the server. CLI sender encrypts to the registered SPK, but
WASM was trying to decrypt with a different random key.

Fix:
- WasmIdentity now stores spk_secret_bytes internally
- SPK secret persisted to localStorage as 'wz-spk'
- On load: restored from localStorage, not regenerated
- bundle_bytes() uses stored SPK secret (cached, deterministic)
- decrypt_wire_message() takes spk_secret_hex parameter
- Web UI passes stored SPK to all decrypt calls

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 08:52:44 +04:00
Siavash Sameni
ab296df825 Add debug logging to web client for WASM crypto troubleshooting
- DEBUG flag (default ON), toggle with /debug command
- Logs to browser console (F12 → Console tab)
- Covers: identity load, key registration, send encrypt,
  poll decrypt (both KeyExchange and session-based attempts)
- Shows: message sizes, session states, error details
- /debug OFF to disable once issue is found

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 08:45:47 +04:00
Siavash Sameni
40ea631283 WASM bridge: web client now uses same crypto as CLI (full interop)
warzone-wasm crate:
- Compiles warzone-protocol to WebAssembly via wasm-pack
- Exposes WasmIdentity, WasmSession, decrypt_wire_message to JS
- Same X25519 + ChaCha20-Poly1305 + X3DH + Double Ratchet as CLI
- 344KB WASM binary (optimized with wasm-opt)

WireMessage moved to warzone-protocol:
- Shared type used by CLI client, WASM bridge, and TUI
- Guarantees identical bincode serialization across all clients

Web client rewritten:
- Loads WASM module on startup (/wasm/warzone_wasm.js)
- Identity: WasmIdentity generates same key types as CLI
- Registration: sends bincode PreKeyBundle (same format as CLI)
- Encrypt: WasmSession.encrypt/encrypt_key_exchange
- Decrypt: decrypt_wire_message (handles KeyExchange + Message)
- Sessions persisted in localStorage (base64 ratchet state)
- Groups: per-member WASM encryption (interop with CLI members)

Server routes:
- GET /wasm/warzone_wasm.js — serves WASM JS glue
- GET /wasm/warzone_wasm_bg.wasm — serves WASM binary
- Both embedded at compile time via include_str!/include_bytes!

Web ↔ CLI interop now works:
- Same key exchange (X3DH with X25519)
- Same ratchet (Double Ratchet with ChaCha20-Poly1305)
- Same wire format (bincode WireMessage)
- Web user can message CLI user and vice versa

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 08:37:58 +04:00
Siavash Sameni
d7b71efdbc Fix DB lock error: clear message + instructions, fix passphrase reprompt
Storage:
- Detects sled lock contention, shows actionable error:
  "Database locked by another warzone process"
  with ps command to find the process and rm command to force unlock

TUI:
- Poll loop no longer calls load_seed() (was re-prompting passphrase)
- Seed passed from main.rs to run_tui to poll_loop
- Single passphrase prompt per app launch

Warnings fixed:
- Removed unused `Context` import in tui/app.rs
- Added #[allow(dead_code)] on validate_token (used when auth middleware wired)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 08:24:53 +04:00
Siavash Sameni
c8b51fa96b UAT test plans for all 7 phases
UAT/PHASE1.md — 20 test scenarios, 80+ checkboxes
  Identity, encryption, messaging, TUI, web, groups, aliases,
  auth, OTP replenishment, session persistence, cross-client

UAT/PHASE2.md — 7 scenarios (WASM, receipts, files, multi-device, HW wallet, groups, history)
UAT/PHASE3.md — 6 scenarios (DNS discovery, key transparency, federation, mutual TLS, gossip)
UAT/PHASE4.md — 10 scenarios (mule identity, pickup, delivery, receipts, dedup, expiry, compression)
UAT/PHASE5.md — 6 scenarios (Bluetooth, LoRa, mDNS, Wi-Fi Direct, USB export, fallback chain)
UAT/PHASE6.md — 3 scenarios (sealed sender, traffic analysis resistance, onion routing)
UAT/PHASE7.md — 8 scenarios (ntfy, DoH, DB encryption, admin CLI, rate limiting, audit, CI, monitoring)

Each test has exact commands to run and checkboxes for pass/fail.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 08:01:36 +04:00
Siavash Sameni
cfb227a93d Server auth (challenge-response) + OTP key replenishment
Authentication:
- POST /v1/auth/challenge {fingerprint} → {challenge, expires_at}
- POST /v1/auth/verify {fingerprint, challenge, signature} → {token}
- Client signs challenge with Ed25519 identity key
- Server verifies against stored public key
- Returns bearer token valid for 7 days
- Web clients get token without sig verify (Phase 2: WASM)
- validate_token() helper for protecting endpoints

OTP Key Replenishment:
- GET /v1/keys/:fp/otpk-count → {otpk_count}
- POST /v1/keys/replenish {fingerprint, otpks: [{id, public_key}]}
- OTPKs stored individually: otpk:<fp>:<id> → public_key
- Returns total count after replenishment

Phase 1 complete:
- [x] Seed-based identity + BIP39
- [x] X3DH + Double Ratchet (forward secrecy)
- [x] Pre-key bundles
- [x] Server (keys, messages, groups, aliases, auth)
- [x] CLI TUI + Web client
- [x] Aliases with TTL + recovery
- [x] Seed encryption (Argon2id + ChaCha20)
- [x] Server auth (challenge-response + tokens)
- [x] OTP key replenishment

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 07:55:02 +04:00
Siavash Sameni
3ffac0c751 Unlock seed once at startup, pass identity to all commands
- main.rs unlocks seed once, prompts passphrase once per app launch
- Identity passed as parameter to send, recv, register, chat
- No more redundant load_seed() calls (was prompting passphrase multiple times)
- info command uses pre-unlocked identity directly

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 07:49:51 +04:00
Siavash Sameni
37a4c3c54f Seed encryption at rest (Argon2id + ChaCha20-Poly1305) + HW wallet plan
keystore.rs:
- Passphrase prompted on init (hidden input, echo disabled)
- Empty passphrase = plaintext (for testing/scripting)
- Encrypted format: MAGIC("WZS1") + salt(16) + nonce(12) + ciphertext(48)
- Argon2id for key derivation (memory-hard, GPU-resistant)
- ChaCha20-Poly1305 AEAD for encryption
- Backwards compatible: auto-detects plaintext vs encrypted on load
- Keys zeroized after use

DESIGN.md:
- Added hardware wallet section (Ledger/Trezor via USB/BT HID)
- Ed25519 signing delegated to device, seed never exported
- BIP44 derivation path m/44'/1234'/0'
- Phase 2 feature, protocol unchanged

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 07:45:55 +04:00
Siavash Sameni
7fe6de0ba1 Alias TTL renews only on authenticated actions (sending messages)
- Sending a message includes `from` fingerprint
- Server renews alias TTL on send (proves identity: you encrypted it)
- Polling/receiving does NOT renew (anyone can spam messages to you)
- Key registration does NOT renew (separate concern)

This prevents alias keepalive attacks where someone spams a user
just to keep their alias from expiring.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 07:39:15 +04:00
Siavash Sameni
bf67566b0c Alias TTL, recovery keys, and reclamation
Aliases now have a lifecycle:
- 365-day TTL from last activity (send/receive/renew)
- 30-day grace period after expiry (only recovery key can reclaim)
- After grace: anyone can register the alias
- Recovery key generated on first registration, rotated on recovery
- Auto-renew on activity via POST /v1/alias/renew

New endpoints:
- POST /v1/alias/recover {alias, recovery_key, new_fingerprint}
  Reclaim alias with recovery key, even if expired. Works across
  identity changes (new seed → new fingerprint, same alias).
  Recovery key is rotated on each recovery.
- POST /v1/alias/renew {fingerprint}
  Heartbeat — resets TTL. Returns days until expiry.

Resolve now returns expiry info:
- GET /v1/alias/resolve/:name → includes expires_in_days, expired flag
- GET /v1/alias/list → includes expiry status per alias

Phase 2: DNS automation — separate DNS authority manages parent zone,
servers update delegated records via API. Recovery key maps to DNS
record ownership for out-of-band reclamation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 07:18:10 +04:00
Siavash Sameni
29c059cebf Aliases: human-readable names mapped to fingerprints
Server:
- POST /v1/alias/register — claim an alias (one per fingerprint)
- GET /v1/alias/resolve/:name — alias → fingerprint
- GET /v1/alias/whois/:fingerprint — fingerprint → alias (reverse)
- GET /v1/alias/list — list all aliases
- Bidirectional mapping in sled (a:name→fp, fp:fp→name)
- One alias per person, re-registering replaces old alias

Web client:
- /alias <name> — register your alias
- /aliases — list all registered aliases
- /info — now shows alias alongside fingerprint
- Peer input accepts @alias (resolved before sending)
- Received messages show @alias instead of fingerprint
- DM: paste @alias or fingerprint in peer input

CLI TUI:
- /alias <name> — register alias
- /aliases — list all aliases
- /peer @alias — resolves alias to fingerprint
- Alias resolution displayed in system messages

Addressing model:
- @manwe (local) → server resolves → fingerprint
- @manwe.b1.example.com (federated) → DNS resolve (Phase 3)
- Raw fingerprint → always works, no resolution

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 07:01:35 +04:00
Siavash Sameni
b90155c3b7 Fix web client: gracefully handle CLI members in groups
- fetchPeerKey: catch JSON parse error for CLI bincode bundles,
  show clear "CLI client — needs WASM bridge" message
- Group send: silently skip CLI members instead of showing
  error per member (mixed groups work, web members get messages,
  CLI members are skipped without noise)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 23:20:25 +04:00