feat(security): persist unlock lockout across restarts; keep unlock errors visible

- The failed-unlock error message now stays on screen until the next attempt /
  mode switch / success, instead of fading after ~3s (the reason was often gone
  before it could be read).
- The escalating failed-attempt lockout counter + expiry are now persisted
  (keyed to the active wallet file) and restored once per lock-screen session,
  so killing and relaunching the app can no longer reset the lockout curve — a
  brute-force window. A forward clock change is capped; the PIN/passphrase is
  never stored. The verification (walletpassphrase/decrypt) path is unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-10-18 23:20:32 -05:00
co-authored by Claude Opus 4.8
parent 89d3975eb3
commit a30a083df0
4 changed files with 66 additions and 4 deletions
+2
View File
@@ -1673,6 +1673,7 @@ void App::switchToWallet(const std::string& walletFile, bool stopDaemonConfirmed
pin_vault_stale_ = false; // the out-of-date mark was the previous wallet's vault
lock_attempts_ = 0;
lock_lockout_timer_ = 0.0f;
lock_state_restored_ = false; // re-restore persisted lockout for the newly-active wallet
lock_error_msg_.clear();
lock_error_timer_ = 0.0f;
lock_unlock_in_progress_ = false;
@@ -7031,6 +7032,7 @@ void App::pumpSeedMigration()
seed_migration_dest_ = std::move(dest);
seed_migration_temp_dir_ = std::move(tmp);
seed_migration_backed_up_ = false;
seed_migration_understood_ = false;
seed_migration_step_ = SeedMigrationStep::ShowSeed;
// Persist the pending migration so a later sweep/adopt step can find the new wallet.
if (settings_) {