Answers 'how do you get keys to login elsewhere' — both mechanisms, Signal/WhatsApp style:
Server:
- migration 0002: key_backups (recovery blobs) + provisions (QR relay), ciphertext only
- routes/backup.js: POST/GET /api/backup, /backup/exists; provision new/get/complete
- routes/keys.js: POST /api/keys/reset — atomically wipe stale prekeys + install fresh
(fixes Bad MAC: restore regenerates prekey IDs that INSERT-OR-IGNORE kept stale)
Crypto (crypto.js):
- exportIdentityBlob/importIdentityBlob (identity travels only encrypted)
- generateRecoveryCode (120-bit Crockford base32) + encrypt/decryptWithCode (PBKDF2 250k)
- ephemeral ECDH (P-256) encrypt/decrypt for QR transfer
Client (app.js): account-menu 'Set up recovery code' + 'Link a device' (camera scan
via BarcodeDetector or paste); auth-screen 'Restore with recovery code' + 'Link from
another device' (shows QR + code, polls, restores). Vendored qrcode-generator.
All verified live end-to-end (3-process isolated-store integration tests):
restore-from-code and QR-link both let a fresh device receive + decrypt messages.
SW cache v3->v4.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Root cause of 'two users can't chat': all accounts on one browser shared a
single IndexedDB, so the second registration overwrote the first's Signal
identity keys, corrupting both. Verified via two-isolated-client integration
test (real crypto.js + store.js) that the protocol itself is correct.
- store.js: useAccount(username) namespaces IndexedDB as cipher:<username>;
keys generated at registration now persist under the right account
- accounts.js: localStorage-backed account registry + active pointer
- app.js: multi-account boot/activate, account-switch/add/logout menu,
error toasts on send/decrypt failures (no more silent failures)
- login guards against missing on-device keys (multi-device linking still TODO)
- SW cache v2->v3 + precache accounts.js
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- window.prompt/alert are suppressed in Brave and installed PWAs, so the
button appeared to do nothing. Replace with a proper modal (inline error,
Enter/Escape, click-outside to close).
- Mobile nav: sidebar was fully hidden (button unreachable); now sidebar shows
by default and a back button toggles between list and thread.
- Bump SW cache v1->v2 so the new shell actually ships past the cache.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>