Files
wikideck/docs/wallet-security.md
T
2026-10-28 18:33:52 -04:00

6.6 KiB
Executable File
Raw Blame History

Encrypted local wallet — stage 2

What changes

The browser no longer creates or returns a plaintext wallet at startup. Open Wallet backup & recovery and choose:

  • Create encrypted wallet: explicitly generate a new P-256 identity and choose a local password of 12–1024 characters.
  • Encrypt existing wallet: migrate the previous same-origin plaintext record, preserving its exact public key/address. This is required before the old key can sign in the new UI.
  • Unlock wallet: decrypt an existing local vault in this tab with its local password.
  • Recover and save encrypted wallet: decrypt a portable backup with its backup password and re-encrypt it under a new local password. Existing storage, even when locked, requires explicit replacement confirmation. Back up that identity first; replacement does not transfer its assets.

The local password and backup password may differ. They have no reset endpoint, are not sent to a server, and are not stored in local/session storage. Forgotten local passwords require a working portable backup and its password. Without either a usable local vault/password or backup/password, access is lost.

Storage and migration guarantees

Vault records use the existing reviewed backup encryption primitive: PBKDF2-SHA256 (600,000 iterations), random 16-byte salt, AES-256-GCM with random 12-byte IV and authenticated format/network/public-key headers. The local wrapper stores a format/version and random revision plus the encrypted backup. It stores no plaintext private CryptoKey or password for newly created/migrated vaults. Public-key metadata remains visible; encryption does not hide wallet identity or on-chain holdings.

The existing testnet/mainnet IndexedDB names remain separate and unchanged to find legacy records safely. Migration reads the legacy key only after the user opts in, encrypts and decrypts to verify identity, then replaces the record in a committed IndexedDB readwrite transaction. No automatic deletion, new identity or network migration occurs. Storage failure leaves the original record intact. The old CryptoKey is never removed before encrypted replacement commits.

Cross-tab readwrite compare-and-swap checks the expected stored revision/record in the same transaction; optional Web Locks additionally serialize storage. A competing create/migration/restore must fail rather than overwrite a newly stored identity. This is application consistency, not protection against a malicious script deliberately editing IndexedDB. Old application versions/tabs must be closed before migration; cached old bundles are not guaranteed to understand a vault record.

Replacing a record does not securely erase previous browser storage pages, OS backups, disk snapshots, old downloaded files or keys previously copied by other scripts. If the old identity may have been exposed, encryption is not revocation: a separately planned transfer to a fresh identity is required. This work never transfers assets or silently rotates keys.

Session and locking

UI components receive a public Wallet handle only; private keys remain inside the wallet module. All NFT transfer and trade signatures go through its session signer. A stale handle from before locking/re-unlocking is rejected even for the same address.

  • Explicit Lock wallet now clears the module's session references immediately.
  • Five minutes without trusted pointer/keyboard input locks the session. RPC polling and automated signing do not reset idle time.
  • Four hours since unlocking locks it even if active.
  • Background/hidden-tab, pagehide and reload end the session. Hiding a tab during encryption/export/unlock may cancel its result; encrypted storage may already have committed, in which case it remains locked and can be unlocked again.
  • BroadcastChannel messages contain only a lock signal, never keys, passwords or decrypted backups. Manual/background locks and replacement invalidate other tabs where supported. If unavailable, storage revision is checked before returning signatures/backups; do not assume immediate cross-tab notification on every browser.
  • Lock epochs are checked across asynchronous crypto/storage steps. A signature finishing after a detected lock/replacement is discarded. Signing/export also recheck vault revision. An already returned/submitted signature cannot be revoked by locking; signed transactions/offers can remain valid on-chain.

Timers can be delayed by the browser or event-loop load. Expiry is checked again on signer/export/get-wallet access so delayed timer callbacks do not authorize an expired session. JS does not provide guaranteed zeroization of strings or CryptoKey/browser memory; clearing references is not a memory-forensics guarantee.

Form password values clear after operations, lock notifications, hiding the page and unmounting. Browser/password-manager behavior is outside this application's memory guarantees. Backups remain the existing portable format for native miner compatibility. A downloaded backup password does not implicitly change the local password.

Validation and limitations

Node/WebCrypto storage-boundary tests cover encrypted record-only persistence, explicit no-wallet startup, signature verification, locked/stale handles, idle/absolute expiry, lock during unlock/export, wrong passwords, replacement consent, storage retry/quota failure, conflicting storage revisions, same-identity legacy migration and preservation after failed migration. Existing cryptographic tamper, wrong-network and native-miner backup tests remain.

Real browser smoke checks on a disposable loopback origin passed create → lock → unlock and reload → locked. IndexedDB inspection confirmed the vault wrapper/ciphertext and absence of a top-level private key/password without retrieving private material. Tests use ephemeral test credentials only. Final Node 24 TypeScript/Vite build passed; the complete suite passed 86 tests, 0 failed, 0 skipped, including real RandomX HTTP integration and expanded wallet storage/lifecycle assertions.

Production wallet review remains open. At-rest encryption reduces profile-theft exposure when locked but cannot protect an unlocked page or password entry from XSS, a malicious release, privileged extensions or a compromised browser/OS. The in-memory key intentionally remains extractable to support existing backup export. This is not hardware-isolated signing, a browser-extension wallet, or an independent audit. Multi-browser/background/cross-tab races and destructive crash/restore drills need broader coverage; no independent assurance or formal crypto verification is claimed. Do not use valuable assets merely because local encryption exists.