diff --git a/res/lang/de.json b/res/lang/de.json index 514f970..5ed6bb9 100644 --- a/res/lang/de.json +++ b/res/lang/de.json @@ -1020,6 +1020,7 @@ "lite_hide_wipe": "Ausblenden & löschen", "lite_import": "Importieren", "lite_import_key_label": "Schlüssel importieren", + "lite_key_import_failed": "Schlüssel konnte nicht importiert werden — prüfen Sie ihn, entsperren Sie die Wallet (falls verschlüsselt) und versuchen Sie es erneut.", "lite_key_imported": "Schlüssel importiert — führen Sie eine Synchronisierung durch, um den Verlauf zu scannen", "lite_lock_failed": "Sperren fehlgeschlagen", "lite_lock_now": "Jetzt sperren", diff --git a/res/lang/es.json b/res/lang/es.json index cebaec7..9166506 100644 --- a/res/lang/es.json +++ b/res/lang/es.json @@ -1020,6 +1020,7 @@ "lite_hide_wipe": "Ocultar y borrar", "lite_import": "Importar", "lite_import_key_label": "Importar clave", + "lite_key_import_failed": "No se pudo importar la clave: compruébala, desbloquea el monedero (si está cifrado) e inténtalo de nuevo.", "lite_key_imported": "Clave importada: ejecuta una sincronización para escanear su historial", "lite_lock_failed": "Error al bloquear", "lite_lock_now": "Bloquear ahora", diff --git a/res/lang/fr.json b/res/lang/fr.json index fb6dc0c..61300c0 100644 --- a/res/lang/fr.json +++ b/res/lang/fr.json @@ -1020,6 +1020,7 @@ "lite_hide_wipe": "Masquer et effacer", "lite_import": "Importer", "lite_import_key_label": "Importer une clé", + "lite_key_import_failed": "Impossible d'importer la clé — vérifiez-la, déverrouillez le portefeuille (s'il est chiffré) et réessayez.", "lite_key_imported": "Clé importée — lancez une synchronisation pour analyser son historique", "lite_lock_failed": "Échec du verrouillage", "lite_lock_now": "Verrouiller maintenant", diff --git a/res/lang/ja.json b/res/lang/ja.json index 0601928..b715885 100644 --- a/res/lang/ja.json +++ b/res/lang/ja.json @@ -1020,6 +1020,7 @@ "lite_hide_wipe": "非表示にして消去", "lite_import": "インポート", "lite_import_key_label": "鍵をインポート", + "lite_key_import_failed": "鍵をインポートできませんでした。鍵を確認し、ウォレットのロックを解除して(暗号化されている場合)再試行してください。", "lite_key_imported": "鍵をインポートしました — 履歴をスキャンするには同期を実行してください", "lite_lock_failed": "ロックに失敗しました", "lite_lock_now": "今すぐロック", diff --git a/res/lang/ko.json b/res/lang/ko.json index ad4985b..a39aff0 100644 --- a/res/lang/ko.json +++ b/res/lang/ko.json @@ -1020,6 +1020,7 @@ "lite_hide_wipe": "숨기고 삭제", "lite_import": "가져오기", "lite_import_key_label": "키 가져오기", + "lite_key_import_failed": "키를 가져오지 못했습니다. 키를 확인하고 지갑 잠금을 해제한 후(암호화된 경우) 다시 시도하세요.", "lite_key_imported": "키를 가져왔습니다 — 동기화를 실행하여 기록을 스캔하세요", "lite_lock_failed": "잠금 실패", "lite_lock_now": "지금 잠그기", diff --git a/res/lang/pt.json b/res/lang/pt.json index c1ec56f..32190d5 100644 --- a/res/lang/pt.json +++ b/res/lang/pt.json @@ -1020,6 +1020,7 @@ "lite_hide_wipe": "Ocultar e apagar", "lite_import": "Importar", "lite_import_key_label": "Importar chave", + "lite_key_import_failed": "Não foi possível importar a chave — verifique-a, desbloqueie a carteira (se estiver criptografada) e tente novamente.", "lite_key_imported": "Chave importada — execute uma sincronização para escanear seu histórico", "lite_lock_failed": "Falha ao bloquear", "lite_lock_now": "Bloquear agora", diff --git a/res/lang/ru.json b/res/lang/ru.json index 836b07f..474ac91 100644 --- a/res/lang/ru.json +++ b/res/lang/ru.json @@ -1020,6 +1020,7 @@ "lite_hide_wipe": "Скрыть и стереть", "lite_import": "Импорт", "lite_import_key_label": "Импортировать ключ", + "lite_key_import_failed": "Не удалось импортировать ключ — проверьте его, разблокируйте кошелёк (если зашифрован) и повторите попытку.", "lite_key_imported": "Ключ импортирован — выполните синхронизацию, чтобы просканировать его историю", "lite_lock_failed": "Не удалось заблокировать", "lite_lock_now": "Заблокировать сейчас", diff --git a/res/lang/zh.json b/res/lang/zh.json index ada30ae..8e05a44 100644 --- a/res/lang/zh.json +++ b/res/lang/zh.json @@ -1020,6 +1020,7 @@ "lite_hide_wipe": "隐藏并清除", "lite_import": "导入", "lite_import_key_label": "导入密钥", + "lite_key_import_failed": "无法导入密钥——请检查密钥,解锁钱包(如已加密)后重试。", "lite_key_imported": "密钥已导入——运行同步以扫描其历史记录", "lite_lock_failed": "锁定失败", "lite_lock_now": "立即锁定", diff --git a/src/ui/pages/settings_page.cpp b/src/ui/pages/settings_page.cpp index 4afc3f2..c0b8a6c 100644 --- a/src/ui/pages/settings_page.cpp +++ b/src/ui/pages/settings_page.cpp @@ -1916,8 +1916,9 @@ void RenderSettingsPage(App* app) { } else { s_settingsState.lite_backup_status = TR("lite_working"); app->liteWallet()->importKeyAsync(liteKey, [](const wallet::LiteImportResult& r) { + // Generic text: the reason (non-secret) is in the lite log. s_settingsState.lite_backup_status = - r.ok ? std::string(TR("lite_key_imported")) : r.error; + r.ok ? TR("lite_key_imported") : TR("lite_key_import_failed"); }); sodium_memzero(s_settingsState.lite_import_key, sizeof(s_settingsState.lite_import_key)); } diff --git a/src/util/i18n.cpp b/src/util/i18n.cpp index 3aebf53..8472f0c 100644 --- a/src/util/i18n.cpp +++ b/src/util/i18n.cpp @@ -951,6 +951,7 @@ void I18n::loadBuiltinEnglish() strings_["lite_import_key_label"] = "Import key"; strings_["lite_import"] = "Import"; strings_["lite_key_imported"] = "Key imported — run a sync to scan its history"; + strings_["lite_key_import_failed"] = "Couldn't import the key \xE2\x80\x94 check it, unlock the wallet (if encrypted) and try again."; strings_["lite_security"] = "Security"; strings_["lite_encrypt_wallet"] = "Encrypt wallet"; strings_["lite_wallet_encrypted"] = "Wallet encrypted"; diff --git a/src/wallet/lite_wallet_controller.cpp b/src/wallet/lite_wallet_controller.cpp index d61a813..e352ca5 100644 --- a/src/wallet/lite_wallet_controller.cpp +++ b/src/wallet/lite_wallet_controller.cpp @@ -163,7 +163,19 @@ LiteNewAddressResult newAddressCore(LiteClientBridge& bridge, bool shielded) return out; } -// `key` must be non-empty; it is wiped before returning. +// The backend's import errors can repeat the key itself (do_import_key: "'' was not recognized as either a +// spending key or a viewing key ..."), so that text is never logged or shown: only a fixed, non-secret reason. +std::string liteImportFailureReason(const std::string& backendError) +{ + const auto has = [&backendError](const char* s) { return backendError.find(s) != std::string::npos; }; + if (has("was not recognized")) return "unrecognized key format"; + if (has("locked")) return "wallet is locked"; + if (has("already exists")) return "key already exists"; + if (has("decode")) return "key could not be decoded"; + return "key rejected by the backend"; +} + +// `key` must be non-empty; it is wiped before returning. A failure's error is a fixed reason, never backend text. LiteImportResult importKeyCore(LiteClientBridge& bridge, std::string& key, const std::atomic* retired = nullptr) { // Transparent WIFs begin with U/5/K/L (TImportCommand); shielded keys begin with @@ -180,13 +192,13 @@ LiteImportResult importKeyCore(LiteClientBridge& bridge, std::string& key, const LiteImportResult r; const auto result = bridge.execute(command, key); if (!result.ok) { // do_import_* failures come back "Error:"-prefixed (bridge -> ok=false) - r.error = result.error.empty() ? "key import failed" : result.error; + r.error = liteImportFailureReason(result.error); return r; } try { const auto j = nlohmann::json::parse(result.value); if (j.is_object() && j.contains("error")) { - r.error = extractJsonError(j); + r.error = liteImportFailureReason(extractJsonError(j)); return r; } } catch (...) { @@ -207,7 +219,7 @@ LiteImportResult importKeyCore(LiteClientBridge& bridge, std::string& key, const LiteImportResult alt = runImport(transparentFirst ? "import" : "timport"); if (alt.ok) out = alt; } - if (!out.ok) liteLog("Key import failed: " + out.error); // error text only — never the key + if (!out.ok) liteLog("Key import failed: " + out.error); // a fixed reason: the backend's text can repeat the key secureWipeLiteSecret(key); // wipe our copy after both attempts return out; } diff --git a/src/wallet/lite_wallet_controller.h b/src/wallet/lite_wallet_controller.h index 77abaa2..7196bbb 100644 --- a/src/wallet/lite_wallet_controller.h +++ b/src/wallet/lite_wallet_controller.h @@ -97,7 +97,8 @@ struct LiteBroadcastResult { std::string error; }; -// importKey outcome. `detail` is the backend's (non-secret) confirmation payload. +// importKey outcome. `detail` is the backend's (non-secret) confirmation payload. `error` is a fixed, non-secret reason, +// never the backend's text (which can repeat the key). struct LiteImportResult { bool ok = false; std::string detail; diff --git a/tests/test_phase4.cpp b/tests/test_phase4.cpp index d5f958b..9c90ada 100644 --- a/tests/test_phase4.cpp +++ b/tests/test_phase4.cpp @@ -8376,6 +8376,45 @@ void testLiteWalletControllerRetireStopsOpFollowUps() dragonx::test::g_liteFakeLocked = false; } +// The backend's import error for an unrecognized key repeats the key ("'' was not recognized ..."): the controller +// must never put that text in its result (shown in Settings) or in the lite log, only a fixed, non-secret reason. +void testLiteWalletControllerImportErrorNeverRepeatsKey() +{ + using namespace dragonx::wallet; + const auto caps = makeWalletCapabilities(WalletBuildKind::Lite, false, true); + const auto conn = defaultLiteConnectionSettings(); + dragonx::test::resetLiteFakeCounters(); + LiteWalletController c(caps, conn, LiteClientBridge::fromApi(dragonx::test::makeFakeLiteApi())); + EXPECT_TRUE(c.createWallet(LiteWalletCreateRequest{}).walletReady); + for (int i = 0; i < 400 && !c.syncComplete(); ++i) std::this_thread::sleep_for(std::chrono::milliseconds(5)); + dragonx::test::g_liteFakeImportFails = true; + const auto logMentions = [](const std::string& needle) { + for (const auto& line : LiteDiagnostics::instance().snapshot()) + if (line.find(needle) != std::string::npos) return true; + return false; + }; + + { // synchronous: both attempts fail; neither the result nor the log carries the key + const auto r = c.importKey("secret-extended-key-typoMARKSYNC"); + EXPECT_FALSE(r.ok); + EXPECT_EQ(r.error, std::string("unrecognized key format")); + EXPECT_TRUE(r.error.find("MARKSYNC") == std::string::npos); + EXPECT_FALSE(logMentions("MARKSYNC")); + EXPECT_TRUE(logMentions("Key import failed: unrecognized key format")); + } + { // async (the Settings path) + int n = 0; LiteImportResult got; got.ok = true; + EXPECT_TRUE(c.importKeyAsync("Kx1typoMARKASYNC", [&](const LiteImportResult& r) { ++n; got = r; })); + EXPECT_TRUE(c.waitWalletOpsIdle(std::chrono::milliseconds(3000))); + c.pumpWalletOps(); + EXPECT_EQ(n, 1); + EXPECT_FALSE(got.ok); + EXPECT_TRUE(got.error.find("MARKASYNC") == std::string::npos); + EXPECT_FALSE(logMentions("MARKASYNC")); + } + dragonx::test::g_liteFakeImportFails = false; +} + void testLiteWalletControllerEncryptsBeforeSyncStarts() { using namespace dragonx::wallet; @@ -13597,6 +13636,7 @@ int main() testLiteWalletControllerWalletOpsAsync(); testLiteWalletControllerRetire(); testLiteWalletControllerRetireStopsOpFollowUps(); + testLiteWalletControllerImportErrorNeverRepeatsKey(); testLiteWalletControllerEncryptsBeforeSyncStarts(); testLiteWalletControllerWorkerBacksOffOnSyncReadFailure(); testLiteWalletControllerDestroyWithBlockedOp();