diff --git a/src/ui/pages/settings_page.cpp b/src/ui/pages/settings_page.cpp index 303f5dc..a01988a 100644 --- a/src/ui/pages/settings_page.cpp +++ b/src/ui/pages/settings_page.cpp @@ -128,6 +128,11 @@ struct SettingsPageState { std::string lite_export_label; char lite_import_key[512] = ""; 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; int mine_idle_delay = 120; bool idle_thread_scaling = false; @@ -1809,10 +1814,74 @@ void RenderSettingsPage(App* app) { Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(), 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()) { - // 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); 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 {