From 7fd125b27d48e410509f3009e2eb9fa5cd6729dd Mon Sep 17 00:00:00 2001 From: Andrew Chow Date: Mon, 28 Nov 2022 17:10:44 -0500 Subject: [PATCH] wallet: Be able to unlock the wallet for migration Since migration reloads the wallet, the wallet will always be locked unless the passphrase is given. migratewallet can now take the passphrase in order to unlock the wallet for migration. --- src/wallet/rpc/wallet.cpp | 14 +++++++------- src/wallet/wallet.cpp | 17 +++++++++++++++-- src/wallet/wallet.h | 2 +- test/functional/wallet_migration.py | 1 - 4 files changed, 23 insertions(+), 11 deletions(-) diff --git a/src/wallet/rpc/wallet.cpp b/src/wallet/rpc/wallet.cpp index 06dfd1106be..f59b532cef4 100644 --- a/src/wallet/rpc/wallet.cpp +++ b/src/wallet/rpc/wallet.cpp @@ -724,6 +724,7 @@ static RPCHelpMan migratewallet() HELP_REQUIRING_PASSPHRASE, { {"wallet_name", RPCArg::Type::STR, RPCArg::DefaultHint{"the wallet name from the RPC endpoint"}, "The name of the wallet to migrate. If provided both here and in the RPC endpoint, the two must be identical."}, + {"passphrase", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "The wallet passphrase"}, }, RPCResult{ RPCResult::Type::OBJ, "", "", @@ -752,15 +753,14 @@ static RPCHelpMan migratewallet() wallet_name = request.params[0].get_str(); } - WalletContext& context = EnsureWalletContext(request.context); - { - std::shared_ptr wallet = GetWallet(context, wallet_name); - if (wallet && wallet->IsCrypted()) { - throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE, "Error: migratewallet on encrypted wallets is currently unsupported."); - } + SecureString wallet_pass; + wallet_pass.reserve(100); + if (!request.params[1].isNull()) { + wallet_pass = std::string_view{request.params[1].get_str()}; } - util::Result res = MigrateLegacyToDescriptor(wallet_name, context); + WalletContext& context = EnsureWalletContext(request.context); + util::Result res = MigrateLegacyToDescriptor(wallet_name, wallet_pass, context); if (!res) { throw JSONRPCError(RPC_WALLET_ERROR, util::ErrorString(res).original); } diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 52666eac66a..c999adf8caf 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -3875,7 +3875,7 @@ std::optional CWallet::GetDescriptorsForLegacy(bilingual_str& err std::optional res = legacy_spkm->MigrateToDescriptor(); if (res == std::nullopt) { - error = _("Error: Unable to produce descriptors for this legacy wallet. Make sure the wallet is unlocked first"); + error = _("Error: Unable to produce descriptors for this legacy wallet. Make sure to provide the wallet's passphrase if it is encrypted."); return std::nullopt; } return res; @@ -4165,7 +4165,7 @@ bool DoMigration(CWallet& wallet, WalletContext& context, bilingual_str& error, return true; } -util::Result MigrateLegacyToDescriptor(const std::string& wallet_name, WalletContext& context) +util::Result MigrateLegacyToDescriptor(const std::string& wallet_name, const SecureString& passphrase, WalletContext& context) { MigrationResult res; bilingual_str error; @@ -4215,6 +4215,19 @@ util::Result MigrateLegacyToDescriptor(const std::string& walle { LOCK(local_wallet->cs_wallet); + // Unlock the wallet if needed + if (local_wallet->IsLocked() && !local_wallet->Unlock(passphrase)) { + if (passphrase.find('\0') == std::string::npos) { + return util::Error{Untranslated("Error: Wallet decryption failed, the wallet passphrase was not provided or was incorrect.")}; + } else { + return util::Error{Untranslated("Error: Wallet decryption failed, the wallet passphrase entered was incorrect. " + "The passphrase contains a null character (ie - a zero byte). " + "If this passphrase was set with a version of this software prior to 25.0, " + "please try again with only the characters up to — but not including — " + "the first null character.")}; + } + } + // First change to using SQLite if (!local_wallet->MigrateToSQLite(error)) return util::Error{error}; diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 40720acc8e5..cab516f382e 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -1007,7 +1007,7 @@ struct MigrationResult { }; //! Do all steps to migrate a legacy wallet to a descriptor wallet -util::Result MigrateLegacyToDescriptor(const std::string& wallet_name, WalletContext& context); +util::Result MigrateLegacyToDescriptor(const std::string& wallet_name, const SecureString& passphrase, WalletContext& context); } // namespace wallet #endif // BITCOIN_WALLET_WALLET_H diff --git a/test/functional/wallet_migration.py b/test/functional/wallet_migration.py index 72c5fe7b840..24373f81131 100755 --- a/test/functional/wallet_migration.py +++ b/test/functional/wallet_migration.py @@ -403,7 +403,6 @@ class WalletMigrationTest(BitcoinTestFramework): wallet.encryptwallet("pass") - assert_raises_rpc_error(-15, "Error: migratewallet on encrypted wallets is currently unsupported.", wallet.migratewallet) # TODO: Fix migratewallet so that we can actually migrate encrypted wallets def run_test(self):