feat(lite): encryption UI — encrypt/unlock/lock/decrypt in Settings
Add a "Security" subsection to Settings → Backup & keys (open wallet only) that wires the encryption controller methods to the UI: - Unencrypted wallet: passphrase field + "Encrypt wallet". - Encrypted + locked: "Unlock" (passphrase) ; Encrypted + unlocked: "Lock now". - Encrypted: passphrase + "Remove encryption" (decrypt). - Status line reflects the result; state shown from WalletState.isEncrypted()/ isLocked() (kept current by the controller's encryptionstatus refresh poll). Secret hygiene: the passphrase inputs (lite_enc_pass / lite_dec_pass) are sodium-zeroed immediately after each action and when the wallet closes while the section was open. Runtime-checked: app auto-opens a wallet and the new encryptionstatus worker poll runs clean (no errors); tests pass; hygiene clean. Follow-ups (not yet): a send-time unlock prompt and a startup lock-screen overlay for an encrypted+locked wallet (today: unlock via Settings; balances remain viewable while locked). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -128,6 +128,11 @@ struct SettingsPageState {
|
|||||||
std::string lite_export_label;
|
std::string lite_export_label;
|
||||||
char lite_import_key[512] = "";
|
char lite_import_key[512] = "";
|
||||||
std::string lite_backup_status;
|
std::string lite_backup_status;
|
||||||
|
// Encryption passphrase inputs (SECRET: zeroed right after each action). lite_enc_pass is
|
||||||
|
// reused for Encrypt (unencrypted wallet) and Unlock (locked wallet); lite_dec_pass for Decrypt.
|
||||||
|
char lite_enc_pass[128] = "";
|
||||||
|
char lite_dec_pass[128] = "";
|
||||||
|
std::string lite_encryption_status;
|
||||||
bool mine_when_idle = false;
|
bool mine_when_idle = false;
|
||||||
int mine_idle_delay = 120;
|
int mine_idle_delay = 120;
|
||||||
bool idle_thread_scaling = false;
|
bool idle_thread_scaling = false;
|
||||||
@@ -1809,10 +1814,74 @@ void RenderSettingsPage(App* app) {
|
|||||||
Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(),
|
Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(),
|
||||||
s_settingsState.lite_backup_status.c_str());
|
s_settingsState.lite_backup_status.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- Security: passphrase encryption (encrypt / unlock / lock / decrypt) ----
|
||||||
|
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
||||||
|
ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
|
||||||
|
Type().text(TypeStyle::Body2, "Security");
|
||||||
|
const auto& wstate = app->getWalletState();
|
||||||
|
const float encLabelX = leftX - sectionOrigin.x + liteLabelW;
|
||||||
|
if (!wstate.isEncrypted()) {
|
||||||
|
ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
|
||||||
|
ImGui::AlignTextToFramePadding();
|
||||||
|
ImGui::TextUnformatted("Passphrase");
|
||||||
|
ImGui::SameLine(encLabelX);
|
||||||
|
ImGui::SetNextItemWidth(liteInputW);
|
||||||
|
ImGui::InputText("##LiteEncryptPass", s_settingsState.lite_enc_pass,
|
||||||
|
sizeof(s_settingsState.lite_enc_pass), ImGuiInputTextFlags_Password);
|
||||||
|
ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
|
||||||
|
if (TactileButton("Encrypt wallet##LiteEncrypt", ImVec2(0, 0), S.resolveFont("button"))) {
|
||||||
|
const auto r = app->liteWallet()->encryptWallet(s_settingsState.lite_enc_pass);
|
||||||
|
sodium_memzero(s_settingsState.lite_enc_pass, sizeof(s_settingsState.lite_enc_pass));
|
||||||
|
s_settingsState.lite_encryption_status = r.ok ? "Wallet encrypted" : r.error;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (wstate.isLocked()) {
|
||||||
|
ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
|
||||||
|
ImGui::AlignTextToFramePadding();
|
||||||
|
ImGui::TextUnformatted("Unlock");
|
||||||
|
ImGui::SameLine(encLabelX);
|
||||||
|
ImGui::SetNextItemWidth(liteInputW);
|
||||||
|
ImGui::InputText("##LiteUnlockPass", s_settingsState.lite_enc_pass,
|
||||||
|
sizeof(s_settingsState.lite_enc_pass), ImGuiInputTextFlags_Password);
|
||||||
|
ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
|
||||||
|
if (TactileButton("Unlock##LiteUnlock", ImVec2(0, 0), S.resolveFont("button"))) {
|
||||||
|
const bool ok = app->liteWallet()->unlockWallet(s_settingsState.lite_enc_pass);
|
||||||
|
sodium_memzero(s_settingsState.lite_enc_pass, sizeof(s_settingsState.lite_enc_pass));
|
||||||
|
s_settingsState.lite_encryption_status = ok ? "Wallet unlocked" : "Unlock failed";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
|
||||||
|
if (TactileButton("Lock now##LiteLock", ImVec2(0, 0), S.resolveFont("button"))) {
|
||||||
|
s_settingsState.lite_encryption_status =
|
||||||
|
app->liteWallet()->lockWallet() ? "Wallet locked" : "Lock failed";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
|
||||||
|
ImGui::AlignTextToFramePadding();
|
||||||
|
ImGui::TextUnformatted("Passphrase");
|
||||||
|
ImGui::SameLine(encLabelX);
|
||||||
|
ImGui::SetNextItemWidth(liteInputW);
|
||||||
|
ImGui::InputText("##LiteDecryptPass", s_settingsState.lite_dec_pass,
|
||||||
|
sizeof(s_settingsState.lite_dec_pass), ImGuiInputTextFlags_Password);
|
||||||
|
ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
|
||||||
|
if (TactileButton("Remove encryption##LiteDecrypt", ImVec2(0, 0), S.resolveFont("button"))) {
|
||||||
|
const auto r = app->liteWallet()->decryptWallet(s_settingsState.lite_dec_pass);
|
||||||
|
sodium_memzero(s_settingsState.lite_dec_pass, sizeof(s_settingsState.lite_dec_pass));
|
||||||
|
s_settingsState.lite_encryption_status = r.ok ? "Encryption removed" : r.error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!s_settingsState.lite_encryption_status.empty()) {
|
||||||
|
ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
|
||||||
|
Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(),
|
||||||
|
s_settingsState.lite_encryption_status.c_str());
|
||||||
|
}
|
||||||
} else if (!s_settingsState.lite_export_secret.empty()) {
|
} else if (!s_settingsState.lite_export_secret.empty()) {
|
||||||
// Wallet closed while a backup was revealed — don't leave it in memory.
|
// Wallet closed while a backup/secret was revealed — don't leave it in memory.
|
||||||
wallet::secureWipeLiteSecret(s_settingsState.lite_export_secret);
|
wallet::secureWipeLiteSecret(s_settingsState.lite_export_secret);
|
||||||
s_settingsState.lite_export_label.clear();
|
s_settingsState.lite_export_label.clear();
|
||||||
|
sodium_memzero(s_settingsState.lite_enc_pass, sizeof(s_settingsState.lite_enc_pass));
|
||||||
|
sodium_memzero(s_settingsState.lite_dec_pass, sizeof(s_settingsState.lite_dec_pass));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user