feat(lite): wire the create-time passphrase into the lite encrypt/unlock flow (W5-3)

The lite create/open/restore requests carried a passphrase field that the UI collected
(a labeled, masked "passphrase" input) but the backend initialize* calls never used —
so a user could believe their lite wallet was passphrase-protected at creation when it
did nothing. It now has a real meaning, wired in LiteWalletController:

- create / restore -> encryptWallet(passphrase): the backend encrypts + locks + saves
  the brand-new wallet.
- open -> unlockWallet(passphrase), but only when encryptionStatus() reports the existing
  wallet is actually encrypted + locked (no spurious unlock on an unencrypted wallet).

encryptWallet/unlockWallet take their own copy of the passphrase and wipe it; the
request copy is still wiped as before. A post-create encrypt failure is liteLog'd (the
wallet still exists, so the create is not failed).

Six existing lite-controller tests carried an incidental "hunter2" create passphrase from
when the field was dead; removed (they exercise non-encryption flows and want an
unencrypted wallet), and added testLiteWalletControllerCreateEncryptsWithPassphrase to
prove the new behavior. Completes the wallet-hardening P0-A cluster (7/7). ctest 1/1.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-08-02 14:39:02 -05:00
parent 7e4822c021
commit c7d163f44a
3 changed files with 55 additions and 11 deletions

View File

@@ -1172,25 +1172,47 @@ void LiteWalletController::workerLoop()
LiteWalletLifecycleResult LiteWalletController::createWallet(LiteWalletCreateRequest request)
{
auto result = lifecycle_.createWallet(request);
secureWipeLiteSecret(request.passphrase);
onLifecycleResult(result);
// If the user supplied a passphrase, encrypt the brand-new wallet with it now that it's open
// (the backend encrypts + locks + saves). Previously this passphrase was collected but never
// used (W5-3) — a passphrase field that silently did nothing. encryptWallet() takes its own
// copy and wipes it.
if (walletOpen_.load() && !request.passphrase.empty()) {
const auto enc = encryptWallet(request.passphrase);
if (!enc.ok) liteLog("wallet created but encryption failed: " + enc.error);
}
secureWipeLiteSecret(request.passphrase);
return result;
}
LiteWalletLifecycleResult LiteWalletController::openWallet(LiteWalletOpenRequest request)
{
auto result = lifecycle_.openWallet(request);
secureWipeLiteSecret(request.passphrase);
onLifecycleResult(result);
// An existing wallet may be encrypted + locked — use the supplied passphrase to unlock it so it
// opens ready to use. Only meaningful when the wallet is actually locked (W5-3).
if (walletOpen_.load() && !request.passphrase.empty()) {
const auto encStatus = encryptionStatus();
if (encStatus.ok && encStatus.encrypted && encStatus.locked) {
if (!unlockWallet(request.passphrase))
liteLog("wallet opened but unlock failed (wrong passphrase?)");
}
}
secureWipeLiteSecret(request.passphrase);
return result;
}
LiteWalletLifecycleResult LiteWalletController::restoreWallet(LiteWalletRestoreRequest request)
{
auto result = lifecycle_.restoreWallet(request);
onLifecycleResult(result);
// If the user supplied a passphrase, encrypt the restored wallet with it now that it's open (W5-3).
if (walletOpen_.load() && !request.passphrase.empty()) {
const auto enc = encryptWallet(request.passphrase);
if (!enc.ok) liteLog("wallet restored but encryption failed: " + enc.error);
}
secureWipeLiteSecret(request.seedPhrase);
secureWipeLiteSecret(request.passphrase);
onLifecycleResult(result);
return result;
}