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>
21 lines
880 B
SQL
21 lines
880 B
SQL
-- Multi-device key sync: encrypted recovery backup + QR provisioning relay.
|
|
-- Everything stored here is CIPHERTEXT the server cannot read.
|
|
|
|
-- One encrypted backup blob per user, decryptable only with the user's recovery
|
|
-- code (client-side PBKDF2 + AES-GCM). Server sees {salt, iv, ct} — opaque.
|
|
CREATE TABLE key_backups (
|
|
user_id TEXT PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE,
|
|
payload TEXT NOT NULL,
|
|
updated_at INTEGER NOT NULL
|
|
);
|
|
|
|
-- Short-lived QR-linking channel. The new device publishes its ephemeral ECDH
|
|
-- public key; the authorizing device drops back a blob encrypted to that key.
|
|
-- The server only relays ciphertext. `id` is an unguessable capability token.
|
|
CREATE TABLE provisions (
|
|
id TEXT PRIMARY KEY,
|
|
ephemeral_pub TEXT NOT NULL,
|
|
payload TEXT,
|
|
created_at INTEGER NOT NULL,
|
|
claimed_at INTEGER
|
|
);
|