diff --git a/src/app.cpp b/src/app.cpp index 7a675f1..a071832 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -439,6 +439,15 @@ void App::update() lite_send_callback_ = nullptr; } } + + // Startup lock screen: once the first refresh reveals the (auto-opened) wallet is + // encrypted, prompt to unlock if it's locked. Soft by design — balances stay viewable via + // viewing keys while locked; only spending needs the passphrase, so the user may dismiss + // and browse read-only. Fires once per session. + if (!lite_startup_lock_checked_ && state_.encrypted) { + lite_startup_lock_checked_ = true; + if (state_.locked) requestLiteUnlock(); + } } async_tasks_.reapCompleted(); diff --git a/src/app.h b/src/app.h index 2459838..77f3226 100644 --- a/src/app.h +++ b/src/app.h @@ -431,6 +431,8 @@ private: bool lite_firstrun_dismissed_ = false; // Lite send-time unlock: set to show the unlock modal when a spend is attempted while locked. bool lite_unlock_prompt_ = false; + // One-shot: prompt to unlock on startup once we learn the auto-opened wallet is encrypted+locked. + bool lite_startup_lock_checked_ = false; std::unique_ptr daemon_controller_; std::unique_ptr xmrig_manager_; util::AsyncTaskManager async_tasks_; diff --git a/tests/fake_lite_backend.h b/tests/fake_lite_backend.h index 56bc58b..3c492e7 100644 --- a/tests/fake_lite_backend.h +++ b/tests/fake_lite_backend.h @@ -128,10 +128,11 @@ inline char* liteFakeExecute(const char* command, const char* args) ++g_liteFakeSaveCount; return liteFakeDup("{\"result\":\"success\"}"); } - // Encryption state machine. encrypt -> encrypted (keys stay in memory this session); - // lock/unlock toggle locked; decrypt clears it; encryptionstatus reports {encrypted,locked}. + // Encryption state machine. encrypt -> encrypted AND locked (matches the real backend, + // which removes keys from memory right after encrypting — verified via lite_smoke --encrypt); + // unlock/lock toggle locked; decrypt clears it; encryptionstatus reports {encrypted,locked}. if (std::strcmp(c, "encrypt") == 0) { - g_liteFakeEncrypted = true; g_liteFakeLocked = false; + g_liteFakeEncrypted = true; g_liteFakeLocked = true; return liteFakeDup("{\"result\":\"success\"}"); } if (std::strcmp(c, "unlock") == 0) { diff --git a/tests/test_phase4.cpp b/tests/test_phase4.cpp index 35e9c04..a34e838 100644 --- a/tests/test_phase4.cpp +++ b/tests/test_phase4.cpp @@ -4777,7 +4777,8 @@ void testLiteWalletControllerEncryption() return c; }; - // encrypt -> lock -> unlock -> decrypt, observed via encryptionStatus() + // encrypt -> unlock -> lock -> decrypt, observed via encryptionStatus(). NOTE: encrypt LOCKS + // immediately (the real backend removes keys after encrypting — verified via lite_smoke). { auto c = open(); const auto s0 = c->encryptionStatus(); @@ -4788,17 +4789,17 @@ void testLiteWalletControllerEncryption() EXPECT_TRUE(c->encryptWallet("walletpass").ok); const auto s1 = c->encryptionStatus(); EXPECT_TRUE(s1.encrypted); - EXPECT_FALSE(s1.locked); - - EXPECT_TRUE(c->lockWallet()); - const auto s2 = c->encryptionStatus(); - EXPECT_TRUE(s2.encrypted); - EXPECT_TRUE(s2.locked); + EXPECT_TRUE(s1.locked); // encrypt locks immediately EXPECT_TRUE(c->unlockWallet("walletpass")); + const auto s2 = c->encryptionStatus(); + EXPECT_TRUE(s2.encrypted); + EXPECT_FALSE(s2.locked); + + EXPECT_TRUE(c->lockWallet()); const auto s3 = c->encryptionStatus(); EXPECT_TRUE(s3.encrypted); - EXPECT_FALSE(s3.locked); + EXPECT_TRUE(s3.locked); EXPECT_TRUE(c->decryptWallet("walletpass").ok); const auto s4 = c->encryptionStatus(); diff --git a/tools/lite_smoke.cpp b/tools/lite_smoke.cpp index ff0e8b4..f4c45f7 100644 --- a/tools/lite_smoke.cpp +++ b/tools/lite_smoke.cpp @@ -7,7 +7,7 @@ // against a real lightwalletd server. The "real backend smoke test" the plan gates behind // passing fake-backend tests. // -// lite_smoke [server-url] [--create] [--refresh] [--full] [--restore-recent] [--keys] +// lite_smoke [server-url] [--create] [--refresh] [--full] [--restore-recent] [--keys] [--encrypt] // // Read-only by default. --create initializes a new wallet. --refresh runs the refresh // commands through the parsers (shape check). --full also does a (slow) full sync first. @@ -126,12 +126,36 @@ static void runLiteKeysShapeChecks(LiteClientBridge& bridge) } } +// Exercise the encryption commands (encrypt/lock/unlock + encryptionstatus) against the real +// backend. SECRET-SAFE: the throwaway passphrase is never printed; only encrypted/locked flags. +static void runLiteEncryptionChecks(LiteClientBridge& bridge) +{ + const char* kPass = "smoke-encryption-passphrase"; // throwaway; isolated HOME only + auto status = [&](const char* label) { + auto r = bridge.execute("encryptionstatus", ""); + auto j = nlohmann::json::parse(r.value, nullptr, false); + const bool enc = j.is_object() && j.value("encrypted", false); + const bool lck = j.is_object() && j.value("locked", false); + std::printf("[lite-smoke] encstatus %-13s bridge_ok=%d encrypted=%d locked=%d\n", + label, r.ok, enc, lck); + }; + status("(initial)"); + std::printf("[lite-smoke] encrypt bridge_ok=%d\n", bridge.execute("encrypt", kPass).ok); + status("(after encrypt)"); + std::printf("[lite-smoke] lock bridge_ok=%d\n", bridge.execute("lock", "").ok); + status("(after lock)"); + std::printf("[lite-smoke] unlock bridge_ok=%d\n", bridge.execute("unlock", kPass).ok); + status("(after unlock)"); + bridge.execute("save", ""); +} + int main(int argc, char** argv) { std::setvbuf(stdout, nullptr, _IONBF, 0); // unbuffered so output survives a timeout kill std::string server = "https://lite.dragonx.is"; - bool doCreate = false, doRefresh = false, doFull = false, doRestoreRecent = false, doKeys = false; + bool doCreate = false, doRefresh = false, doFull = false, doRestoreRecent = false, doKeys = false, + doEncrypt = false; for (int i = 1; i < argc; ++i) { const std::string arg = argv[i]; if (arg == "--create") doCreate = true; @@ -139,6 +163,7 @@ int main(int argc, char** argv) else if (arg == "--full") doFull = true; else if (arg == "--restore-recent") doRestoreRecent = true; else if (arg == "--keys") doKeys = true; + else if (arg == "--encrypt") doEncrypt = true; else server = arg; } @@ -201,6 +226,23 @@ int main(int argc, char** argv) return 0; } + if (doEncrypt) { + // Encryption command check against the real backend (fast, no sync). Isolated HOME. + std::printf("[lite-smoke] initializeNew() for encryption check...\n"); + auto cr = bridge.initializeNew(false, server); + std::printf("[lite-smoke] initializeNew ok=%d\n", cr.ok); + if (!cr.ok) { + std::printf("[lite-smoke] error = %s\n", cr.error.c_str()); + bridge.shutdown(); + return 1; + } + std::printf("[lite-smoke] --- encryption check (encrypt/lock/unlock + status) ---\n"); + runLiteEncryptionChecks(bridge); + bridge.shutdown(); + std::printf("[lite-smoke] done\n"); + return 0; + } + if (doCreate) { std::printf("[lite-smoke] initializeNew() ... (real network + writes wallet state)\n"); auto result = bridge.initializeNew(false, server);