feat(settings): redesign encrypt & change-passphrase modals onto reference design

Migrate the Encrypt Wallet (3-phase) and Change Passphrase dialogs in
renderEncryptWalletDialog onto the settings-modal reference design —
presentation-only, no change to the crypto/state-machine/secret-wipe logic:

- struct-form BeginOverlayDialog(OverlayDialogSpec{BlurFloat, cardWidth, idSuffix})
  replacing the legacy positional overload (same visual style, dp-scaled width)
- amber TextWrapped warning -> material::DialogWarningHeader(wiz_encrypt_warning)
- drop redundant Separator dividers
- every hardcoded English string -> TR() (13 new enc_*/change_pass_* keys added to
  the English source + all 8 res/lang/*.json additively; CJK subset rebuilt)

The strength-meter math, 3-phase transitions, "cannot close while encrypting"
guard, memset/SecureVault buffer wipes, and the encrypt/changePassphrase/vault
async paths are byte-for-byte unchanged. Verified: build + ctest + hygiene clean,
wiz_strength %s format-string safe across all languages, and a 3-lens adversarial
review (secret-hygiene / presentation-only / ui-i18n) returned zero findings.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-14 11:13:44 -05:00
parent 5dd68dc9bd
commit 625df8abe6
11 changed files with 157 additions and 41 deletions

View File

@@ -1125,35 +1125,33 @@ void App::renderEncryptWalletDialog() {
// Encrypt wallet dialog — multi-phase: passphrase → encrypting → PIN setup
if (show_encrypt_dialog_) {
const char* dlgTitle = (encrypt_dialog_phase_ == EncryptDialogPhase::PinSetup)
? "Quick-Unlock PIN" : "Encrypt Wallet";
? TR("wiz_pin_title") : TR("settings_encrypt_wallet");
// Prevent closing via X button while encrypting
bool canClose = (encrypt_dialog_phase_ != EncryptDialogPhase::Encrypting);
bool* pOpen = canClose ? &show_encrypt_dialog_ : nullptr;
if (BeginOverlayDialog(dlgTitle, pOpen, 460.0f, 0.94f)) {
OverlayDialogSpec ov;
ov.title = dlgTitle; ov.p_open = pOpen;
ov.style = OverlayStyle::BlurFloat;
ov.cardWidth = 480.0f; ov.idSuffix = "encrypt";
if (BeginOverlayDialog(ov)) {
// ---- Phase 1: Passphrase entry ----
if (encrypt_dialog_phase_ == EncryptDialogPhase::PassphraseEntry) {
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1, 0.7f, 0.3f, 1));
ImGui::TextWrapped(ICON_MD_WARNING
" If you lose your passphrase, you lose access to your funds.");
ImGui::PopStyleColor();
DialogWarningHeader(TR("wiz_encrypt_warning"));
ImGui::Spacing();
ImGui::TextWrapped("Encrypting your wallet protects your private keys "
"with a passphrase. After encryption, the daemon will restart.");
ImGui::Spacing();
ImGui::Separator();
ImGui::TextWrapped("%s", TR("enc_desc"));
ImGui::Spacing();
ImGui::Text("Passphrase:");
ImGui::TextUnformatted(TR("wiz_passphrase"));
ImGui::PushItemWidth(-1);
ImGui::InputText("##enc_pass", encrypt_pass_buf_, sizeof(encrypt_pass_buf_),
ImGuiInputTextFlags_Password);
ImGui::PopItemWidth();
ImGui::Text("Confirm:");
ImGui::TextUnformatted(TR("enc_confirm"));
ImGui::PushItemWidth(-1);
ImGui::InputText("##enc_confirm", encrypt_confirm_buf_, sizeof(encrypt_confirm_buf_),
ImGuiInputTextFlags_Password);
@@ -1174,7 +1172,7 @@ void App::renderEncryptWalletDialog() {
}
int classes = (int)hasDigit + (int)hasLower + (int)hasUpper + (int)hasSymbol;
const char* strengthLabel = "Weak";
const char* strengthLabel = TR("wiz_strength_weak");
ImVec4 strengthCol(0.9f, 0.2f, 0.2f, 1.0f);
float strengthPct = 0.25f;
int tier = 0; // 0=Weak, 1=Fair, 2=Good, 3=Strong
@@ -1183,9 +1181,9 @@ void App::renderEncryptWalletDialog() {
else if (len >= 8) tier = 1;
// Downgrade one tier when only a single character class is used.
if (classes <= 1 && tier > 0) tier -= 1;
if (tier == 3) { strengthLabel = "Strong"; strengthCol = ImVec4(0.3f,0.9f,0.5f,1); strengthPct = 1.0f; }
else if (tier == 2) { strengthLabel = "Good"; strengthCol = ImVec4(0.3f,0.9f,0.5f,1); strengthPct = 0.75f; }
else if (tier == 1) { strengthLabel = "Fair"; strengthCol = ImVec4(1,0.7f,0.3f,1); strengthPct = 0.5f; }
if (tier == 3) { strengthLabel = TR("wiz_strength_strong"); strengthCol = ImVec4(0.3f,0.9f,0.5f,1); strengthPct = 1.0f; }
else if (tier == 2) { strengthLabel = TR("wiz_strength_good"); strengthCol = ImVec4(0.3f,0.9f,0.5f,1); strengthPct = 0.75f; }
else if (tier == 1) { strengthLabel = TR("wiz_strength_fair"); strengthCol = ImVec4(1,0.7f,0.3f,1); strengthPct = 0.5f; }
float barW = ImGui::GetContentRegionAvail().x;
float barH = 4.0f;
@@ -1197,7 +1195,7 @@ void App::renderEncryptWalletDialog() {
dl->AddRectFilled(p, ImVec2(p.x + barW * strengthPct, p.y + barH),
ImGui::ColorConvertFloat4ToU32(strengthCol), 2.0f);
ImGui::Dummy(ImVec2(barW, barH));
ImGui::Text("Strength: %s", strengthLabel);
ImGui::Text(TR("wiz_strength"), strengthLabel);
}
if (!encrypt_status_.empty()) {
@@ -1210,7 +1208,7 @@ void App::renderEncryptWalletDialog() {
float btnW = (ImGui::GetContentRegionAvail().x - ImGui::GetStyle().ItemSpacing.x) * 0.5f;
ImGui::BeginDisabled(!valid || encrypt_in_progress_);
if (ui::material::TactileButton("Encrypt Wallet", ImVec2(btnW, 40))) {
if (ui::material::TactileButton(TR("settings_encrypt_wallet"), ImVec2(btnW, 40))) {
std::string pass(encrypt_pass_buf_);
enc_dlg_saved_passphrase_ = pass;
memset(encrypt_pass_buf_, 0, sizeof(encrypt_pass_buf_));
@@ -1222,7 +1220,7 @@ void App::renderEncryptWalletDialog() {
ImGui::EndDisabled();
ImGui::SameLine();
if (ui::material::TactileButton("Cancel", ImVec2(btnW, 40))) {
if (ui::material::TactileButton(TR("cancel"), ImVec2(btnW, 40))) {
memset(encrypt_pass_buf_, 0, sizeof(encrypt_pass_buf_));
memset(encrypt_confirm_buf_, 0, sizeof(encrypt_confirm_buf_));
show_encrypt_dialog_ = false;
@@ -1231,7 +1229,7 @@ void App::renderEncryptWalletDialog() {
// ---- Phase 2: Encrypting in progress ----
} else if (encrypt_dialog_phase_ == EncryptDialogPhase::Encrypting) {
const char* statusTitle = encrypt_in_progress_
? "Encrypting wallet..." : encrypt_status_.c_str();
? TR("enc_encrypting") : encrypt_status_.c_str();
ImGui::Text("%s", statusTitle);
ImGui::Spacing();
@@ -1258,7 +1256,7 @@ void App::renderEncryptWalletDialog() {
}
ImGui::Spacing();
ImGui::TextColored(ImVec4(1,1,1,0.4f), "Please wait, do not close the application.");
ImGui::TextColored(ImVec4(1,1,1,0.4f), "%s", TR("enc_wait"));
// Transition to PIN phase when encryption finishes successfully
if (!encrypt_in_progress_ && encrypt_dialog_phase_ == EncryptDialogPhase::Encrypting) {
@@ -1268,23 +1266,20 @@ void App::renderEncryptWalletDialog() {
// ---- Phase 3: PIN setup (after successful encryption) ----
} else if (encrypt_dialog_phase_ == EncryptDialogPhase::PinSetup) {
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.3f, 0.9f, 0.5f, 1));
ImGui::Text(ICON_MD_CHECK_CIRCLE " Wallet encrypted successfully!");
ImGui::Text(ICON_MD_CHECK_CIRCLE " %s", TR("enc_success"));
ImGui::PopStyleColor();
ImGui::Spacing();
ImGui::TextWrapped("A 4-8 digit PIN lets you unlock your wallet "
"without typing the full passphrase every time.");
ImGui::Spacing();
ImGui::Separator();
ImGui::TextWrapped("%s", TR("enc_pin_desc"));
ImGui::Spacing();
ImGui::Text("PIN (4-8 digits):");
ImGui::TextUnformatted(TR("wiz_pin_label"));
ImGui::PushItemWidth(-1);
ImGui::InputText("##enc_dlg_pin", enc_dlg_pin_buf_, sizeof(enc_dlg_pin_buf_),
ImGuiInputTextFlags_Password | ImGuiInputTextFlags_CharsDecimal);
ImGui::PopItemWidth();
ImGui::Text("Confirm PIN:");
ImGui::TextUnformatted(TR("wiz_pin_confirm"));
ImGui::PushItemWidth(-1);
ImGui::InputText("##enc_dlg_pin_confirm", enc_dlg_pin_confirm_buf_,
sizeof(enc_dlg_pin_confirm_buf_),
@@ -1304,7 +1299,7 @@ void App::renderEncryptWalletDialog() {
float btnW = (ImGui::GetContentRegionAvail().x - ImGui::GetStyle().ItemSpacing.x) * 0.5f;
ImGui::BeginDisabled(!pinValid || !hasPassphrase || pin_in_progress_);
if (ui::material::TactileButton("Set PIN", ImVec2(btnW, 40))) {
if (ui::material::TactileButton(TR("settings_set_pin"), ImVec2(btnW, 40))) {
pin_in_progress_ = true;
enc_dlg_pin_status_.clear();
std::string savedPass = enc_dlg_saved_passphrase_;
@@ -1317,7 +1312,7 @@ void App::renderEncryptWalletDialog() {
settings_->setPinEnabled(true);
settings_->save();
pin_in_progress_ = false;
ui::Notifications::instance().info("PIN set successfully");
ui::Notifications::instance().info(TR("enc_pin_set_ok"));
// Clean up
if (!enc_dlg_saved_passphrase_.empty()) {
util::SecureVault::secureZero(&enc_dlg_saved_passphrase_[0],
@@ -1328,20 +1323,20 @@ void App::renderEncryptWalletDialog() {
memset(enc_dlg_pin_confirm_buf_, 0, sizeof(enc_dlg_pin_confirm_buf_));
show_encrypt_dialog_ = false;
} else {
enc_dlg_pin_status_ = "Failed to create PIN vault";
enc_dlg_pin_status_ = TR("enc_pin_vault_fail");
pin_in_progress_ = false;
}
};
});
} else {
enc_dlg_pin_status_ = "Failed to create PIN vault";
enc_dlg_pin_status_ = TR("enc_pin_vault_fail");
pin_in_progress_ = false;
}
}
ImGui::EndDisabled();
ImGui::SameLine();
if (ui::material::TactileButton("Skip", ImVec2(btnW, 40))) {
if (ui::material::TactileButton(TR("wiz_skip"), ImVec2(btnW, 40))) {
if (!enc_dlg_saved_passphrase_.empty()) {
util::SecureVault::secureZero(&enc_dlg_saved_passphrase_[0],
enc_dlg_saved_passphrase_.size());
@@ -1350,8 +1345,7 @@ void App::renderEncryptWalletDialog() {
memset(enc_dlg_pin_buf_, 0, sizeof(enc_dlg_pin_buf_));
memset(enc_dlg_pin_confirm_buf_, 0, sizeof(enc_dlg_pin_confirm_buf_));
show_encrypt_dialog_ = false;
ui::Notifications::instance().info(
"PIN skipped. You can set one later in Settings.");
ui::Notifications::instance().info(TR("enc_pin_skipped"));
}
}
EndOverlayDialog();
@@ -1367,21 +1361,25 @@ void App::renderEncryptWalletDialog() {
// Change passphrase dialog
if (show_change_passphrase_) {
if (BeginOverlayDialog("Change Passphrase", &show_change_passphrase_, 440.0f, 0.94f)) {
ImGui::Text("Current Passphrase:");
OverlayDialogSpec ov;
ov.title = TR("change_pass_title"); ov.p_open = &show_change_passphrase_;
ov.style = OverlayStyle::BlurFloat;
ov.cardWidth = 460.0f; ov.idSuffix = "changepass";
if (BeginOverlayDialog(ov)) {
ImGui::TextUnformatted(TR("change_pass_current"));
ImGui::PushItemWidth(-1);
ImGui::InputText("##chg_old", change_old_pass_buf_, sizeof(change_old_pass_buf_),
ImGuiInputTextFlags_Password);
ImGui::PopItemWidth();
ImGui::Text("New Passphrase:");
ImGui::TextUnformatted(TR("change_pass_new"));
ImGui::PushItemWidth(-1);
ImGui::InputText("##chg_new", change_new_pass_buf_, sizeof(change_new_pass_buf_),
ImGuiInputTextFlags_Password);
ImGui::PopItemWidth();
ImGui::Text("Confirm New:");
ImGui::TextUnformatted(TR("change_pass_confirm"));
ImGui::PushItemWidth(-1);
ImGui::InputText("##chg_confirm", change_confirm_buf_, sizeof(change_confirm_buf_),
ImGuiInputTextFlags_Password);
@@ -1396,7 +1394,7 @@ void App::renderEncryptWalletDialog() {
strlen(change_new_pass_buf_) >= 8 &&
strcmp(change_new_pass_buf_, change_confirm_buf_) == 0;
ImGui::BeginDisabled(!valid || encrypt_in_progress_);
if (ui::material::TactileButton("Change Passphrase", ImVec2(-1, 40))) {
if (ui::material::TactileButton(TR("change_pass_title"), ImVec2(-1, 40))) {
changePassphrase(std::string(change_old_pass_buf_),
std::string(change_new_pass_buf_));
}

View File

@@ -508,6 +508,20 @@ void I18n::loadBuiltinEnglish()
strings_["wiz_encrypt_desc"] = "Encrypt your wallet to protect private keys with a passphrase.";
strings_["wiz_encrypt_warning"] = "If you lose your passphrase, you lose access to your funds.";
strings_["wiz_passphrase"] = "Passphrase:";
// --- Encrypt / change-passphrase dialogs (settings) ---
strings_["enc_desc"] = "Encrypting your wallet protects your private keys with a passphrase. After encryption, the daemon will restart.";
strings_["enc_confirm"] = "Confirm:";
strings_["enc_encrypting"] = "Encrypting wallet...";
strings_["enc_wait"] = "Please wait, do not close the application.";
strings_["enc_success"] = "Wallet encrypted successfully!";
strings_["enc_pin_desc"] = "A 4-8 digit PIN lets you unlock your wallet without typing the full passphrase every time.";
strings_["enc_pin_set_ok"] = "PIN set successfully";
strings_["enc_pin_vault_fail"] = "Failed to create PIN vault";
strings_["enc_pin_skipped"] = "PIN skipped. You can set one later in Settings.";
strings_["change_pass_title"] = "Change Passphrase";
strings_["change_pass_current"] = "Current Passphrase:";
strings_["change_pass_new"] = "New Passphrase:";
strings_["change_pass_confirm"] = "Confirm New:";
strings_["wiz_confirm"] = "Confirm:";
strings_["wiz_strength_weak"] = "Weak";
strings_["wiz_strength_fair"] = "Fair";