wallet_api: set m_password in the recovery creation paths
What changed, and why it matters
This commit fixes a bug in Monero's wallet programming interface (wallet_api). When a wallet was restored from a recovery seed or from private keys, the internal copy of the wallet password was not being saved. As a result, operations that later needed the password—such as saving the wallet to disk—could fail or behave incorrectly. The patch simply records the password in those three recovery code paths and adds tests to confirm that a recovered wallet can be saved and reopened with the same password.
Treat as a functional/robustness fix worth including in release notes. Review whether any other WalletImpl methods that accept a password also fail to update m_password, and ensure the test suite exercises store() after all recovery variants. No immediate emergency response is indicated, but downstream wallet applications using wallet_api should update.
Security signals we found
Missing internal state update after successful authentication/creation
Password needed for subsequent wallet store/re-encryption operations
Recovery code paths differed from normal createWallet path which already set m_password
Test additions confirm functional/security regression coverage
Evidence from the diff
WalletImpl provides three recovery methods: recover() (from mnemonic seed), recoverFromKeysWithPassword() (from address/view/spend keys), and recoverFromDevice() (from hardware device). Each calls the underlying wallet2 generate function with the supplied password, but previously none assigned m_password afterwards. The patch adds m_password = password after successful generation in all three paths. The new unit tests verify that a wallet recovered from seed or from keys can be stored to a new file and reopened with WALLET_PASS, which would fail if m_password remained empty because store() uses it to encrypt/rewrite the wallet cache.
Changed components
src/wallet/api/wallet.cppWalletImpl::recover()WalletImpl::recoverFromKeysWithPassword()WalletImpl::recoverFromDevice()tests/libwallet_api_tests/main.cppInspect captured patch +40 / −0
diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp
index bad6f5a..fa98635 100644
--- a/src/wallet/api/wallet.cpp
+++ b/src/wallet/api/wallet.cpp
@@ -681,6 +681,7 @@ bool WalletImpl::recoverFromKeysWithPassword(const std::string &path,
setStatusError(string(tr("failed to generate new wallet: ")) + e.what());
return false;
}
+ m_password = password;
return true;
}
@@ -698,6 +699,7 @@ bool WalletImpl::recoverFromDevice(const std::string &path, const std::string &p
setStatusError(string(tr("failed to generate new wallet: ")) + e.what());
return false;
}
+ m_password = password;
return true;
}
@@ -766,6 +768,7 @@ bool WalletImpl::recover(const std::string &path, const std::string &password, c
try {
m_wallet->set_seed_language(old_language);
m_wallet->generate(path, password, recovery_key, true, false);
+ m_password = password;
} catch (const std::exception &e) {
setStatusCritical(e.what());
diff --git a/tests/libwallet_api_tests/main.cpp b/tests/libwallet_api_tests/main.cpp
index 2867343..e803e91 100644
--- a/tests/libwallet_api_tests/main.cpp
+++ b/tests/libwallet_api_tests/main.cpp
@@ -382,6 +382,43 @@ TEST_F(WalletManagerTest, WalletManagerRecoversWallet)
ASSERT_TRUE(wmgr->closeWallet(wallet2));
}
+TEST_F(WalletManagerTest, WalletManagerStoresPasswordOfWalletRecoveredFromSeed)
+{
+ Monero::Wallet * wallet1 = wmgr->createWallet(WALLET_NAME, WALLET_PASS, WALLET_LANG, Monero::NetworkType::MAINNET);
+ std::string seed1 = wallet1->seed();
+ std::string address1 = wallet1->mainAddress();
+ ASSERT_TRUE(wmgr->closeWallet(wallet1));
+ Utils::deleteWallet(WALLET_NAME);
+ Monero::Wallet * wallet2 = wmgr->recoveryWallet(WALLET_NAME, WALLET_PASS, seed1, Monero::NetworkType::MAINNET, 0);
+ ASSERT_TRUE(wallet2->status() == Monero::Wallet::Status_Ok);
+ ASSERT_TRUE(wallet2->mainAddress() == address1);
+ ASSERT_TRUE(wallet2->store(WALLET_NAME_COPY));
+ ASSERT_TRUE(wmgr->closeWallet(wallet2));
+ Monero::Wallet * wallet3 = wmgr->openWallet(WALLET_NAME_COPY, WALLET_PASS, Monero::NetworkType::MAINNET);
+ ASSERT_TRUE(wallet3->status() == Monero::Wallet::Status_Ok);
+ ASSERT_TRUE(wallet3->mainAddress() == address1);
+ ASSERT_TRUE(wmgr->closeWallet(wallet3));
+}
+
+TEST_F(WalletManagerTest, WalletManagerStoresPasswordOfWalletRecoveredFromKeys)
+{
+ Monero::Wallet * wallet1 = wmgr->createWallet(WALLET_NAME, WALLET_PASS, WALLET_LANG, Monero::NetworkType::MAINNET);
+ std::string address1 = wallet1->mainAddress();
+ std::string viewkey1 = wallet1->secretViewKey();
+ std::string spendkey1 = wallet1->secretSpendKey();
+ ASSERT_TRUE(wmgr->closeWallet(wallet1));
+ Utils::deleteWallet(WALLET_NAME);
+ Monero::Wallet * wallet2 = wmgr->createWalletFromKeys(WALLET_NAME, WALLET_PASS, WALLET_LANG, Monero::NetworkType::MAINNET, 0, address1, viewkey1, spendkey1);
+ ASSERT_TRUE(wallet2->status() == Monero::Wallet::Status_Ok);
+ ASSERT_TRUE(wallet2->mainAddress() == address1);
+ ASSERT_TRUE(wallet2->store(WALLET_NAME_COPY));
+ ASSERT_TRUE(wmgr->closeWallet(wallet2));
+ Monero::Wallet * wallet3 = wmgr->openWallet(WALLET_NAME_COPY, WALLET_PASS, Monero::NetworkType::MAINNET);
+ ASSERT_TRUE(wallet3->status() == Monero::Wallet::Status_Ok);
+ ASSERT_TRUE(wallet3->mainAddress() == address1);
+ ASSERT_TRUE(wmgr->closeWallet(wallet3));
+}
+
TEST_F(WalletManagerTest, WalletManagerStoresWallet1)
{
Why this scored 44/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.