Commit Graph

14 Commits

Author SHA1 Message Date
Siavash Sameni
0b58ddcee5 v0.0.35: WASM create_call_signal, selectable identity, web sections
FC-P3-T2: WASM create_call_signal() export
- Accepts signal_type string (offer/answer/hangup/etc), payload, target
- Returns bincode WireMessage::CallSignal bytes for WS send

FC-P3-T9: Selectable identity display in web
- ETH address shown in code-style block, click to copy
- addSys() gains rawHtml parameter for rich content

FC-P3-T5: Section navigation comments in web.rs
- 5 section markers: State, Crypto, Network, UI, Commands

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 16:00:43 +04:00
Siavash Sameni
3efce2ddf4 v0.0.22: version bump, ETH identity in web client, version bump rule
Version:
- Workspace + protocol: 0.0.21 → 0.0.22
- Web client VERSION: 0.0.17 → 0.0.22
- Service worker cache: wz-v2 → wz-v3

ETH identity:
- Added WasmIdentity::eth_address() export (derives from seed via secp256k1)
- Web client sends eth_address during key registration
- Identity display shows ETH address first, then fingerprint
- No more server-side resolve needed — computed client-side

CLAUDE.md:
- Added MANDATORY version bump rule (4 places to update)
- Must bump on every functional change, never skip SW cache

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 08:11:31 +04:00
Siavash Sameni
dbf5d136cf fix: WASM double-X3DH bug, federated aliases, deploy tooling
WASM fix (critical):
- encrypt_key_exchange_with_id was calling x3dh::initiate a second time,
  generating a new ephemeral key that didn't match the ratchet — receiver
  always failed to decrypt. Now stores X3DH result from initiate() and
  reuses it. Added 2 protocol tests confirming the fix + the bug.
- Bumped service worker cache to wz-v2 to force browsers to re-fetch.
- Disabled wasm-opt for Hetzner builds (libc compat issue).

Federation — alias support:
- resolve_alias falls back to federation peer if not found locally
- register_alias checks peer server before allowing — globally unique aliases
- Added resolve_remote_alias() and is_alias_taken_remote() to FederationHandle

Federation — key proxy fix:
- Remote bundles no longer cached locally (stale cache caused decrypt failures)
- Local vs remote determined by device: prefix in keys DB

Client fixes:
- Self-messaging blocked ("Cannot send messages to yourself")
- /peer <self> blocked
- last_dm_peer never set to self
- /r <message> sends reply inline (switches peer + sends in one command)

Deploy tooling:
- scripts/build-linux.sh with --ship (build + deploy + destroy)
- --update-all, --status, --logs commands
- WASM rebuilt on Hetzner VM before server binary
- deploy/ directory: systemd service, federation configs, setup script
- Journald log cap (50MB, 7-day retention)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 22:59:19 +04:00
Siavash Sameni
3e0889e5dc v0.0.21: TUI overhaul, WZP call infrastructure, security hardening, federation
TUI:
- Split 1,756-line app.rs monolith into 7 modules (types, draw, commands, input, file_transfer, network, mod)
- Message timestamps [HH:MM], scrolling (PageUp/Down/arrows), connection status dot, unread badge
- /help command, terminal bell on incoming DM, /devices + /kick commands
- 44 unit tests (types, input, draw with TestBackend)

Server — WZP Call Infrastructure (FC-2/3/5/6/7/10):
- Call state management (CallState, CallStatus, active_calls, calls + missed_calls sled trees)
- WS call signal awareness (Offer/Answer/Hangup update state, missed call on offline)
- Group call endpoint (POST /groups/:name/call with SHA-256 room ID, fan-out)
- Presence API (GET /presence/:fp, POST /presence/batch)
- Missed call flush on WS reconnect
- WZP relay config + CORS

Server — Security (FC-P1):
- Auth enforcement middleware (AuthFingerprint extractor on 13 write handlers)
- Session auto-recovery (delete corrupted ratchet, show [session reset])
- WS connection cap (5/fingerprint) + global concurrency limit (200)
- Device management (GET /devices, POST /devices/:id/kick, POST /devices/revoke-all)

Server — Federation:
- Two-server federation via JSON config (--federation flag)
- Periodic presence sync (every 5s, full-state, self-healing)
- Message forwarding via HTTP POST with SHA-256(secret||body) auth
- Graceful degradation (peer down = queue locally)
- deliver_or_queue() replaces push-or-queue in ws.rs + messages.rs

Client — Group Messaging:
- SenderKeyDistribution storage + GroupSenderKey decryption in TUI
- sender_keys sled tree in LocalDb

WASM:
- All 8 WireMessage variants handled (no more "unsupported")
- decrypt_group_message() + create_sender_key_from_distribution() exports
- CallSignal parsing with signal_type mapping

Docs:
- ARCHITECTURE.md rewritten with Mermaid diagrams
- README.md created
- TASK_PLAN.md with FC-P{phase}-T{task} naming
- PROGRESS.md updated to v0.0.21

WZP submodule updated to 6f4e8eb (IAX2 trunking, adaptive quality, metrics, all S-tasks done)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 16:45:58 +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
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
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
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
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