fix(security): don't penalise a correct PIN after a passphrase change

A passphrase change left the PIN vault sealing the old passphrase. The
next PIN unlock opened the vault, walletpassphrase answered RPC -14, and
the generic catch ran the lockout curve on a correct PIN, while Settings
kept showing the PIN as active.

- Passphrase change: when the active wallet's vault exists at completion
  (same vault path as at dispatch, passphrase actually changed), the
  vault is marked out of date for the session and kept on disk: PIN entry
  is no longer offered, a warning toast says so, and Settings shows
  "PIN out of date" with a tooltip on how to fix it. The Change
  Passphrase dialog notes beforehand that a PIN will go out of date.
- PIN unlock: RPC -14 after the vault opened is treated as "PIN out of
  date": passphrase entry, a message, no attempt counted, no lockout, and
  the vault is not deleted (-14 is only the daemon's answer; the vault may
  hold the only copy of a passphrase that opens an older backup). After a
  restart Settings shows the PIN as active until the first PIN attempt.
- A malformed vault (zero-filled by a removeVault() whose unlink failed,
  truncated, unknown version) is treated as missing: passphrase entry,
  "PIN file unreadable", no penalty. New SecureVault::isWellFormed().
- Race guards: Set/Change/Remove PIN are disabled while a passphrase
  change runs, and Change Passphrase while a PIN operation runs.
- Setting or removing the PIN, or switching wallets, clears the mark;
  setting a PIN offers PIN entry again. Change PIN keeps the mark (the
  vault still seals the same passphrase).
- Pure decisions in data/pin_vault_policy.h, unit-tested. New strings in
  all 8 languages.

No vault, wallet or settings format change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-12 13:22:00 -05:00
co-authored by Claude Opus 5.5
parent 62a5577491
commit f6e5038043
18 changed files with 362 additions and 24 deletions
+6
View File
@@ -237,6 +237,7 @@
"change_pass_confirm": "Confirmar nueva:",
"change_pass_current": "Frase de contraseña actual:",
"change_pass_new": "Nueva frase de contraseña:",
"change_pass_pin_note": "Esta cartera tiene un PIN de desbloqueo rápido. Tras el cambio quedará desactualizado: quita el PIN en Ajustes y vuelve a establecerlo.",
"change_pass_title": "Cambiar frase de contraseña",
"characters": "caracteres",
"chat": "Chat",
@@ -1688,10 +1689,13 @@
"sec_mode_passphrase": " Frase de contraseña",
"sec_not_connected_to_daemon": "Sin conexión con el daemon",
"sec_not_connected_to_daemon_pin": "Sin conexión con el daemon",
"sec_passphrase_changed_pin_stale": "Frase de contraseña cambiada; tu PIN está desactualizado",
"sec_passphrase_changed_successfully": "Frase de contraseña cambiada correctamente",
"sec_pin_changed_successfully": "PIN cambiado correctamente",
"sec_pin_removed": "PIN eliminado",
"sec_pin_set_successfully": "PIN configurado correctamente",
"sec_pin_stale_msg": "PIN desactualizado. Usa tu frase de contraseña.",
"sec_pin_unreadable": "Archivo del PIN ilegible. Usa tu frase de contraseña.",
"sec_restart_daemon_for_encryption": "Reinicia el daemon para que el cifrado surta efecto.",
"sec_too_many_attempts_wait": "Demasiados intentos. Espera %.0f segundos...",
"sec_total_elapsed_fmt": "Tiempo total: %dm %02ds",
@@ -1840,6 +1844,7 @@
"settings_open_log_folder": "Abrir carpeta de registros",
"settings_other": "Otros",
"settings_pin_active": "PIN",
"settings_pin_out_of_date": "PIN desactualizado",
"settings_privacy": "Privacidad",
"settings_quick_unlock_pin": "PIN de desbloqueo rápido",
"settings_reduce_transparency": "Reducir transparencia",
@@ -2097,6 +2102,7 @@
"tt_open_data_dir": "Abre en el gestor de archivos la carpeta con los datos de tu cartera y de la blockchain",
"tt_open_dir": "Clic para abrir en explorador de archivos",
"tt_open_log_folder": "Abre la carpeta que contiene los registros de depuración y de fallos",
"tt_pin_out_of_date": "La frase de contraseña de la cartera cambió después de establecer este PIN, así que el PIN ya no puede desbloquearla. Quita el PIN y vuelve a establecerlo.",
"tt_reduce_motion": "Desactivar transiciones animadas y lerp de saldo para accesibilidad",
"tt_remove_encrypt": "Quitar cifrado y almacenar la billetera sin protección",
"tt_remove_pin": "Quitar PIN y requerir contraseña para desbloquear",