Commit Graph

1 Commits

Author SHA1 Message Date
clawbot
c951028837 feat: password-gated recovery phrase display for HD wallets (closes #161)
Some checks failed
check / check (push) Has been cancelled
A user who created a wallet in AutistMask and did not write the phrase
down had no way to retrieve it. Adds a "Show recovery phrase" action on
the wallet row in Settings, next to the per-wallet actions that already
live there, mirroring the per-address private key export in structure,
password gate and warning treatment.

The screen displays the secret that owns every address in the wallet, so:

  - Only HD wallets are offered it. walletHasRecoveryPhrase() is an
    allowlist on type "hd", so the key and xprv types — which have no
    phrase at all — are excluded, as is any type added later.
  - Nothing is decrypted and nothing enters the page until
    decryptWithPassword accepts the password. A wrong password produces a
    full-sentence error and leaves the value node empty.
  - Leaving the screen wipes it by any route, not just "Back": views that
    hold a secret register a cleanup with showView() via onViewLeave(),
    which also covers the settings gear.
  - A decrypt still in flight when the screen is left is discarded rather
    than written. reveal() captures a generation counter that every
    clear() bumps and refuses to touch the DOM if it has moved: without
    that check the write lands after the wipe, and nothing is scheduled to
    wipe again, so the phrase stays in the hidden screen for the life of
    the popup. crypto_pwhash is synchronous, so the reachable window is a
    still-pending sodium.ready on the first vault use of a page load.
  - The phrase is never assigned to state, so it cannot be persisted, and
    the view is not in RESTORABLE_VIEWS — reopening the popup lands on
    Home. That set moves to src/popup/restorableViews.js so the exclusion
    can be asserted directly; the popup entry point cannot be required
    outside a browser.
  - The phrase cannot reach the logger: the view does not import
    src/shared/log.js, and the failed-decrypt path reports a fixed
    sentence rather than the caught error.

show() also pushes the navigation stack itself, because it can return
without navigating when the wallet has no phrase; pushing in the Settings
click handler left an entry no screen transition matched.

Tests: unit coverage for the type gate, the RESTORABLE_VIEWS exclusion
and the absence of any logger path; the DOM behaviour is driven against
the real popup in the e2e suite, which is where this repo tests views —
including a probe that leaves the screen while the decrypt is in flight
by dispatching both clicks in one page task, since a human cannot
interleave them once libsodium's wasm is warm.
2026-08-11 13:03:33 +00:00