From d287f0996bdc47756c2afda651bf00bdef7f3bc7 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Sun, 5 Jan 2025 15:45:30 +0700 Subject: [PATCH 01/14] refactor: drop -usehd=1 from some functional tests so far as it is default option --- test/functional/wallet_dump.py | 2 +- test/functional/wallet_importmulti.py | 1 - test/functional/wallet_keypool_hd.py | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/test/functional/wallet_dump.py b/test/functional/wallet_dump.py index 947b8f1cf83d..816a812df820 100755 --- a/test/functional/wallet_dump.py +++ b/test/functional/wallet_dump.py @@ -87,7 +87,7 @@ class WalletDumpTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 self.disable_mocktime = True - self.extra_args = [["-keypool=90", "-usehd=1"]] + self.extra_args = [["-keypool=90"]] self.rpc_timeout = 120 def skip_test_if_missing_module(self): diff --git a/test/functional/wallet_importmulti.py b/test/functional/wallet_importmulti.py index 606ccb1ffc6b..8883303305d4 100755 --- a/test/functional/wallet_importmulti.py +++ b/test/functional/wallet_importmulti.py @@ -37,7 +37,6 @@ class ImportMultiTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 2 self.setup_clean_chain = True - self.extra_args = [['-usehd=1']] * self.num_nodes def skip_test_if_missing_module(self): self.skip_if_no_wallet() diff --git a/test/functional/wallet_keypool_hd.py b/test/functional/wallet_keypool_hd.py index b15857c31519..80cc5a02f219 100755 --- a/test/functional/wallet_keypool_hd.py +++ b/test/functional/wallet_keypool_hd.py @@ -18,7 +18,6 @@ class KeyPoolTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 - self.extra_args = [['-usehd=1']] def skip_test_if_missing_module(self): self.skip_if_no_wallet() From 267693ffe4a8fb489c01bbad17b7c6baac554a21 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Fri, 21 Feb 2025 22:16:45 +0700 Subject: [PATCH 02/14] fix: expand text commentary for RPC encryptwallet --- src/wallet/rpc/encrypt.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wallet/rpc/encrypt.cpp b/src/wallet/rpc/encrypt.cpp index f81334a425c4..b9f72b17d1e7 100644 --- a/src/wallet/rpc/encrypt.cpp +++ b/src/wallet/rpc/encrypt.cpp @@ -252,7 +252,7 @@ RPCHelpMan encryptwallet() throw JSONRPCError(RPC_WALLET_ENCRYPTION_FAILED, "Error: Failed to encrypt the wallet."); } - return "wallet encrypted; The keypool has been flushed and a new HD seed was generated (if you are using HD). You need to make a new backup."; + return "wallet encrypted; The keypool has been flushed and a new HD seed was generated (if you are using HD). You need to make a new backup or write down the new seed (mnemonic)."; }, }; } From 979ed95b6ee70763c57dc4af899f8b46dafc0e98 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Mon, 6 Jan 2025 18:14:34 +0700 Subject: [PATCH 03/14] feat: implement mnemonic for descriptor wallets --- src/bench/wallet_balance.cpp | 2 +- src/qt/test/addressbooktests.cpp | 2 +- src/qt/test/wallettests.cpp | 2 +- src/serialize.h | 20 ++-- src/wallet/rpc/backup.cpp | 10 ++ src/wallet/scriptpubkeyman.cpp | 139 +++++++++++++++++++++++-- src/wallet/scriptpubkeyman.h | 17 ++- src/wallet/test/coinselector_tests.cpp | 10 +- src/wallet/test/util.cpp | 2 +- src/wallet/test/wallet_tests.cpp | 2 +- src/wallet/wallet.cpp | 44 +++++--- src/wallet/wallet.h | 2 +- src/wallet/walletdb.cpp | 58 +++++++++-- src/wallet/walletdb.h | 4 +- src/wallet/wallettool.cpp | 2 +- test/functional/test_runner.py | 2 +- 16 files changed, 258 insertions(+), 60 deletions(-) diff --git a/src/bench/wallet_balance.cpp b/src/bench/wallet_balance.cpp index 004a4c5aa665..0ea66fbe4d6e 100644 --- a/src/bench/wallet_balance.cpp +++ b/src/bench/wallet_balance.cpp @@ -28,7 +28,7 @@ static void WalletBalance(benchmark::Bench& bench, const bool set_dirty, const b { LOCK(wallet.cs_wallet); wallet.SetWalletFlag(WALLET_FLAG_DESCRIPTORS); - wallet.SetupDescriptorScriptPubKeyMans(); + wallet.SetupDescriptorScriptPubKeyMans("", ""); if (wallet.LoadWallet() != DBErrors::LOAD_OK) assert(false); } auto handler = test_setup->m_node.chain->handleNotifications({&wallet, [](CWallet*) {}}); diff --git a/src/qt/test/addressbooktests.cpp b/src/qt/test/addressbooktests.cpp index 49d81f3d65e9..14eaa0c2a05a 100644 --- a/src/qt/test/addressbooktests.cpp +++ b/src/qt/test/addressbooktests.cpp @@ -76,7 +76,7 @@ void TestAddAddressesToSendBook(interfaces::Node& node) wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS); { LOCK(wallet->cs_wallet); - wallet->SetupDescriptorScriptPubKeyMans(); + wallet->SetupDescriptorScriptPubKeyMans("", ""); } auto build_address = [wallet]() { diff --git a/src/qt/test/wallettests.cpp b/src/qt/test/wallettests.cpp index e486f6d5260d..02f3b88317e1 100644 --- a/src/qt/test/wallettests.cpp +++ b/src/qt/test/wallettests.cpp @@ -126,7 +126,7 @@ void TestGUI(interfaces::Node& node) wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS); { LOCK(wallet->cs_wallet); - wallet->SetupDescriptorScriptPubKeyMans(); + wallet->SetupDescriptorScriptPubKeyMans("", ""); // Add the coinbase key FlatSigningProvider provider; diff --git a/src/serialize.h b/src/serialize.h index 3c25e8c1753c..b47c3b0d562a 100644 --- a/src/serialize.h +++ b/src/serialize.h @@ -26,6 +26,7 @@ #include #include +#include #include #include @@ -820,9 +821,16 @@ struct VectorFormatter /** * string */ -template void Serialize(Stream& os, const std::basic_string& str); -template void Unserialize(Stream& is, std::basic_string& str); +template void Serialize(Stream& os, const std::basic_string& str); +template void Unserialize(Stream& is, std::basic_string& str); +/** + * SecureString + */ +/* +template void Serialize(Stream& os, const SecureString& str); +template void Unserialize(Stream& is, SecureString& str); +*/ /** * prevector * prevectors of unsigned char are a special case and are intended to be serialized as a single opaque blob. @@ -951,16 +959,16 @@ struct DefaultFormatter /** * string */ -template -void Serialize(Stream& os, const std::basic_string& str) +template +void Serialize(Stream& os, const std::basic_string& str) { WriteCompactSize(os, str.size()); if (!str.empty()) os.write(MakeByteSpan(str)); } -template -void Unserialize(Stream& is, std::basic_string& str) +template +void Unserialize(Stream& is, std::basic_string& str) { unsigned int nSize = ReadCompactSize(is); str.resize(nSize); diff --git a/src/wallet/rpc/backup.cpp b/src/wallet/rpc/backup.cpp index 6d7d99c3927e..fb06818cc859 100644 --- a/src/wallet/rpc/backup.cpp +++ b/src/wallet/rpc/backup.cpp @@ -1969,6 +1969,8 @@ RPCHelpMan listdescriptors() { {RPCResult::Type::OBJ, "", "", { {RPCResult::Type::STR, "desc", "Descriptor string representation"}, + {RPCResult::Type::STR, "mnemonic", "The mnemonic for this Descriptor wallet (bip39, english words). Presented only if private=true and created with mnemonic"}, + {RPCResult::Type::STR, "mnemonicpassphrase", "The mnemonic passphrase for this Descriptor wallet (bip39). Presented only if private=true and created with mnemonic"}, {RPCResult::Type::NUM, "timestamp", "The creation time of the descriptor"}, {RPCResult::Type::BOOL, "active", "Whether this descriptor is currently used to generate new addresses"}, {RPCResult::Type::BOOL, "internal", /*optional=*/true, "True if this descriptor is used to generate change addresses. False if this descriptor is used to generate receiving addresses; defined only for active descriptors"}, @@ -2015,6 +2017,14 @@ RPCHelpMan listdescriptors() if (!desc_spk_man->GetDescriptorString(descriptor, priv)) { throw JSONRPCError(RPC_WALLET_ERROR, "Can't get descriptor string."); } + if (priv) { + SecureString mnemonic; + SecureString mnemonic_passphrase; + if (desc_spk_man->GetMnemonicString(mnemonic, mnemonic_passphrase) && !mnemonic.empty()) { + spk.pushKV("mnemonic", mnemonic.c_str()); + spk.pushKV("mnemonicpassphrase", mnemonic_passphrase.c_str()); + } + } spk.pushKV("desc", descriptor); spk.pushKV("timestamp", wallet_descriptor.creation_time); const bool active = active_spk_mans.count(desc_spk_man) != 0; diff --git a/src/wallet/scriptpubkeyman.cpp b/src/wallet/scriptpubkeyman.cpp index cb254ec9f7d4..2f3498c2e0a0 100644 --- a/src/wallet/scriptpubkeyman.cpp +++ b/src/wallet/scriptpubkeyman.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include namespace wallet { @@ -1849,6 +1850,7 @@ bool DescriptorScriptPubKeyMan::CheckDecryptionKey(const CKeyingMaterial& master keyFail = true; break; } + // TODO: test for mnemonics keyPass = true; if (m_decryption_thoroughly_checked) break; @@ -1875,15 +1877,34 @@ bool DescriptorScriptPubKeyMan::Encrypt(const CKeyingMaterial& master_key, Walle { const CKey &key = key_in.second; CPubKey pubkey = key.GetPubKey(); + assert(pubkey.GetID() == key_in.first); + const auto mnemonic_in = m_mnemonics.find(key_in.first); CKeyingMaterial secret(key.begin(), key.end()); std::vector crypted_secret; if (!EncryptSecret(master_key, secret, pubkey.GetHash(), crypted_secret)) { return false; } + std::vector crypted_mnemonic; + std::vector crypted_mnemonic_passphrase; + if (mnemonic_in != m_mnemonics.end()) { + const Mnemonic mnemonic = mnemonic_in->second; + + CKeyingMaterial mnemonic_secret(mnemonic.first.begin(), mnemonic.first.end()); + CKeyingMaterial mnemonic_passphrase_secret(mnemonic.second.begin(), mnemonic.second.end()); + if (!EncryptSecret(master_key, mnemonic_secret, pubkey.GetHash(), crypted_mnemonic)) { + return false; + } + if (!EncryptSecret(master_key, mnemonic_passphrase_secret, pubkey.GetHash(), crypted_mnemonic_passphrase)) { + return false; + } + } + m_map_crypted_keys[pubkey.GetID()] = make_pair(pubkey, crypted_secret); - batch->WriteCryptedDescriptorKey(GetID(), pubkey, crypted_secret); + m_crypted_mnemonics[pubkey.GetID()] = make_pair(crypted_mnemonic, crypted_mnemonic_passphrase); + batch->WriteCryptedDescriptorKey(GetID(), pubkey, crypted_secret, crypted_mnemonic, crypted_mnemonic_passphrase); } m_map_keys.clear(); + m_mnemonics.clear(); return true; } @@ -2008,12 +2029,12 @@ void DescriptorScriptPubKeyMan::AddDescriptorKey(const CKey& key, const CPubKey { LOCK(cs_desc_man); WalletBatch batch(m_storage.GetDatabase()); - if (!AddDescriptorKeyWithDB(batch, key, pubkey)) { + if (!AddDescriptorKeyWithDB(batch, key, pubkey, "", "")) { throw std::runtime_error(std::string(__func__) + ": writing descriptor private key failed"); } } -bool DescriptorScriptPubKeyMan::AddDescriptorKeyWithDB(WalletBatch& batch, const CKey& key, const CPubKey &pubkey) +bool DescriptorScriptPubKeyMan::AddDescriptorKeyWithDB(WalletBatch& batch, const CKey& key, const CPubKey &pubkey, const SecureString& mnemonic, const SecureString& mnemonic_passphrase) { AssertLockHeld(cs_desc_man); assert(!m_storage.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)); @@ -2030,22 +2051,37 @@ bool DescriptorScriptPubKeyMan::AddDescriptorKeyWithDB(WalletBatch& batch, const } std::vector crypted_secret; + std::vector crypted_mnemonic; + std::vector crypted_mnemonic_passphrase; CKeyingMaterial secret(key.begin(), key.end()); + CKeyingMaterial mnemonic_secret(mnemonic.begin(), mnemonic.end()); + CKeyingMaterial mnemonic_passphrase_secret(mnemonic_passphrase.begin(), mnemonic_passphrase.end()); if (!m_storage.WithEncryptionKey([&](const CKeyingMaterial& encryption_key) { - return EncryptSecret(encryption_key, secret, pubkey.GetHash(), crypted_secret); + if (!EncryptSecret(encryption_key, secret, pubkey.GetHash(), crypted_secret)) return false; + if (!mnemonic.empty()) { + if (!EncryptSecret(encryption_key, mnemonic_secret, pubkey.GetHash(), crypted_mnemonic)) { + return false; + } + if (!EncryptSecret(encryption_key, mnemonic_passphrase_secret, pubkey.GetHash(), crypted_mnemonic_passphrase)) { + return false; + } + } + return true; })) { return false; } m_map_crypted_keys[pubkey.GetID()] = make_pair(pubkey, crypted_secret); - return batch.WriteCryptedDescriptorKey(GetID(), pubkey, crypted_secret); + m_crypted_mnemonics[pubkey.GetID()] = make_pair(crypted_mnemonic, crypted_mnemonic_passphrase); + return batch.WriteCryptedDescriptorKey(GetID(), pubkey, crypted_secret, crypted_mnemonic, crypted_mnemonic_passphrase); } else { m_map_keys[pubkey.GetID()] = key; - return batch.WriteDescriptorKey(GetID(), pubkey, key.GetPrivKey()); + m_mnemonics[pubkey.GetID()] = make_pair(mnemonic, mnemonic_passphrase); + return batch.WriteDescriptorKey(GetID(), pubkey, key.GetPrivKey(), mnemonic, mnemonic_passphrase); } } -bool DescriptorScriptPubKeyMan::SetupDescriptorGeneration(const CExtKey& master_key, bool internal) +bool DescriptorScriptPubKeyMan::SetupDescriptorGeneration(const CExtKey& master_key, const SecureString& secure_mnemonic, const SecureString& secure_mnemonic_passphrase, bool internal) { LOCK(cs_desc_man); assert(m_storage.IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)); @@ -2055,6 +2091,16 @@ bool DescriptorScriptPubKeyMan::SetupDescriptorGeneration(const CExtKey& master_ return false; } + if (!secure_mnemonic.empty()) { + // TODO: remove duplicated code with AddKey() + SecureVector seed_key_tmp; + CMnemonic::ToSeed(secure_mnemonic, secure_mnemonic_passphrase, seed_key_tmp); + + CExtKey master_key_tmp; + master_key_tmp.SetSeed(MakeByteSpan(seed_key_tmp)); + assert(master_key == master_key_tmp); + } + int64_t creation_time = GetTime(); std::string xpub = EncodeExtPubKey(master_key.Neuter()); @@ -2075,7 +2121,7 @@ bool DescriptorScriptPubKeyMan::SetupDescriptorGeneration(const CExtKey& master_ // Store the master private key, and descriptor WalletBatch batch(m_storage.GetDatabase()); - if (!AddDescriptorKeyWithDB(batch, master_key.key, master_key.key.GetPubKey())) { + if (!AddDescriptorKeyWithDB(batch, master_key.key, master_key.key.GetPubKey(), secure_mnemonic, secure_mnemonic_passphrase)) { throw std::runtime_error(std::string(__func__) + ": writing descriptor master private key failed"); } if (!batch.WriteDescriptor(GetID(), m_wallet_descriptor)) { @@ -2356,14 +2402,26 @@ void DescriptorScriptPubKeyMan::SetCache(const DescriptorCache& cache) } } -bool DescriptorScriptPubKeyMan::AddKey(const CKeyID& key_id, const CKey& key) +bool DescriptorScriptPubKeyMan::AddKey(const CKeyID& key_id, const CKey& key, const SecureString& mnemonic, const SecureString& mnemonic_passphrase) { LOCK(cs_desc_man); + if (!mnemonic.empty()) { + // TODO: remove duplicated code with AddKey() + SecureVector seed_key_tmp; + CMnemonic::ToSeed(mnemonic, mnemonic_passphrase, seed_key_tmp); + + CExtKey master_key_tmp; + master_key_tmp.SetSeed(MakeByteSpan(seed_key_tmp)); + assert(key == master_key_tmp.key); + } + m_map_keys[key_id] = key; + m_mnemonics[key_id] = make_pair(mnemonic, mnemonic_passphrase); + return true; } -bool DescriptorScriptPubKeyMan::AddCryptedKey(const CKeyID& key_id, const CPubKey& pubkey, const std::vector& crypted_key) +bool DescriptorScriptPubKeyMan::AddCryptedKey(const CKeyID& key_id, const CPubKey& pubkey, const std::vector& crypted_key, const std::vector& mnemonic,const std::vector& mnemonic_passphrase) { LOCK(cs_desc_man); if (!m_map_keys.empty()) { @@ -2371,6 +2429,7 @@ bool DescriptorScriptPubKeyMan::AddCryptedKey(const CKeyID& key_id, const CPubKe } m_map_crypted_keys[key_id] = make_pair(pubkey, crypted_key); + m_crypted_mnemonics[key_id] = make_pair(mnemonic, mnemonic_passphrase); return true; } @@ -2412,7 +2471,6 @@ bool DescriptorScriptPubKeyMan::GetDescriptorString(std::string& out, const bool FlatSigningProvider provider; provider.keys = GetKeys(); - if (priv) { // For the private version, always return the master key to avoid // exposing child private keys. The risk implications of exposing child @@ -2423,6 +2481,65 @@ bool DescriptorScriptPubKeyMan::GetDescriptorString(std::string& out, const bool return m_wallet_descriptor.descriptor->ToNormalizedString(provider, out, &m_wallet_descriptor.cache); } +bool DescriptorScriptPubKeyMan::GetMnemonicString(SecureString& mnemonic_out, SecureString& mnemonic_passphrase_out) const +{ + LOCK(cs_desc_man); + + mnemonic_out.clear(); + mnemonic_passphrase_out.clear(); + + if (m_mnemonics.empty() && m_crypted_mnemonics.empty()) { + WalletLogPrintf("%s: Descriptor wallet has no mnemonic defined\n", __func__); + return false; + } + if (m_storage.IsLocked(false)) return false; + + if (m_mnemonics.size() + m_crypted_mnemonics.size() > 1) { + WalletLogPrintf("%s: ERROR: One descriptor has multiple mnemonics. Can't match it\n", __func__); + return false; + } + if (m_storage.HasEncryptionKeys() && !m_storage.IsLocked(true)) { + if (!m_crypted_mnemonics.empty() && m_map_crypted_keys.size() != 1) { + WalletLogPrintf("%s: ERROR: can't choose encryption key for mnemonic out of %lld\n", __func__, m_map_crypted_keys.size()); + return false; + } + const CPubKey& pubkey = m_map_crypted_keys.begin()->second.first; + const auto mnemonic = m_crypted_mnemonics.begin()->second; + const std::vector& crypted_mnemonic = mnemonic.first; + const std::vector& crypted_mnemonic_passphrase = mnemonic.second; + + SecureVector mnemonic_v; + SecureVector mnemonic_passphrase_v; + if (!m_storage.WithEncryptionKey([&](const CKeyingMaterial& encryption_key) { + return DecryptSecret(encryption_key, crypted_mnemonic, pubkey.GetHash(), mnemonic_v); + })) { + LogPrintf("can't decrypt mnemonic pubkey %s crypted: %s\n", pubkey.GetHash().ToString(), HexStr(crypted_mnemonic)); + return false; + } + if (!crypted_mnemonic_passphrase.empty()) { + if (!m_storage.WithEncryptionKey([&](const CKeyingMaterial& encryption_key) { + return DecryptSecret(encryption_key, crypted_mnemonic_passphrase, pubkey.GetHash(), mnemonic_passphrase_v); + })) { + LogPrintf("can't decrypt mnemonic-passphrase\n"); + return false; + } + } + + std::copy(mnemonic_v.begin(), mnemonic_v.end(), std::back_inserter(mnemonic_out)); + std::copy(mnemonic_passphrase_v.begin(), mnemonic_passphrase_v.end(), std::back_inserter(mnemonic_passphrase_out)); + + return true; + } + if (m_mnemonics.empty()) return false; + + const auto mnemonic_it = m_mnemonics.begin(); + + mnemonic_out = mnemonic_it->second.first; + mnemonic_passphrase_out = mnemonic_it->second.second; + + return true; +} + void DescriptorScriptPubKeyMan::UpgradeDescriptorCache() { LOCK(cs_desc_man); diff --git a/src/wallet/scriptpubkeyman.h b/src/wallet/scriptpubkeyman.h index 1cb998b7c5c8..976b761fab0f 100644 --- a/src/wallet/scriptpubkeyman.h +++ b/src/wallet/scriptpubkeyman.h @@ -511,6 +511,11 @@ class DescriptorScriptPubKeyMan : public ScriptPubKeyMan using CryptedKeyMap = std::map>>; using KeyMap = std::map; + using Mnemonic = std::pair; + using MnemonicMap = std::map; + using CryptedMnemonic = std::pair, std::vector>; + using CryptedMnemonicMap = std::map; + ScriptPubKeyMap m_map_script_pub_keys GUARDED_BY(cs_desc_man); PubKeyMap m_map_pubkeys GUARDED_BY(cs_desc_man); int32_t m_max_cached_index = -1; @@ -518,10 +523,13 @@ class DescriptorScriptPubKeyMan : public ScriptPubKeyMan KeyMap m_map_keys GUARDED_BY(cs_desc_man); CryptedKeyMap m_map_crypted_keys GUARDED_BY(cs_desc_man); + MnemonicMap m_mnemonics GUARDED_BY(cs_desc_man); + CryptedMnemonicMap m_crypted_mnemonics GUARDED_BY(cs_desc_man); + //! keeps track of whether Unlock has run a thorough check before bool m_decryption_thoroughly_checked = false; - bool AddDescriptorKeyWithDB(WalletBatch& batch, const CKey& key, const CPubKey &pubkey) EXCLUSIVE_LOCKS_REQUIRED(cs_desc_man); + bool AddDescriptorKeyWithDB(WalletBatch& batch, const CKey& key, const CPubKey &pubkey, const SecureString& mnemonic, const SecureString& mnemonic_passphrase) EXCLUSIVE_LOCKS_REQUIRED(cs_desc_man); KeyMap GetKeys() const EXCLUSIVE_LOCKS_REQUIRED(cs_desc_man); @@ -564,7 +572,7 @@ class DescriptorScriptPubKeyMan : public ScriptPubKeyMan bool IsHDEnabled() const override; //! Setup descriptors based on the given CExtkey - bool SetupDescriptorGeneration(const CExtKey& master_key, bool internal); + bool SetupDescriptorGeneration(const CExtKey& master_key, const SecureString& secure_mnemonic, const SecureString& secure_mnemonic_passphrase, bool internal); bool HavePrivateKeys() const override; @@ -590,8 +598,8 @@ class DescriptorScriptPubKeyMan : public ScriptPubKeyMan void SetCache(const DescriptorCache& cache); - bool AddKey(const CKeyID& key_id, const CKey& key); - bool AddCryptedKey(const CKeyID& key_id, const CPubKey& pubkey, const std::vector& crypted_key); + bool AddKey(const CKeyID& key_id, const CKey& key, const SecureString& mnemonic, const SecureString& mnemonic_passphrase); + bool AddCryptedKey(const CKeyID& key_id, const CPubKey& pubkey, const std::vector& crypted_key, const std::vector& mnemonic,const std::vector& mnemonic_passphrase); bool HasWalletDescriptor(const WalletDescriptor& desc) const; void UpdateWalletDescriptor(WalletDescriptor& descriptor); @@ -603,6 +611,7 @@ class DescriptorScriptPubKeyMan : public ScriptPubKeyMan const std::vector GetScriptPubKeys() const; bool GetDescriptorString(std::string& out, const bool priv) const; + bool GetMnemonicString(SecureString& mnemonic_out, SecureString& mnemonic_passphrase_out) const; void UpgradeDescriptorCache(); }; diff --git a/src/wallet/test/coinselector_tests.cpp b/src/wallet/test/coinselector_tests.cpp index 1044303a4a12..f090ae9e7bfd 100644 --- a/src/wallet/test/coinselector_tests.cpp +++ b/src/wallet/test/coinselector_tests.cpp @@ -302,7 +302,7 @@ BOOST_AUTO_TEST_CASE(bnb_search_test) wallet->LoadWallet(); LOCK(wallet->cs_wallet); wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS); - wallet->SetupDescriptorScriptPubKeyMans(); + wallet->SetupDescriptorScriptPubKeyMans("", ""); std::vector coins; @@ -325,7 +325,7 @@ BOOST_AUTO_TEST_CASE(bnb_search_test) wallet->LoadWallet(); LOCK(wallet->cs_wallet); wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS); - wallet->SetupDescriptorScriptPubKeyMans(); + wallet->SetupDescriptorScriptPubKeyMans("", ""); std::vector coins; @@ -406,7 +406,7 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test) wallet->LoadWallet(); LOCK(wallet->cs_wallet); wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS); - wallet->SetupDescriptorScriptPubKeyMans(); + wallet->SetupDescriptorScriptPubKeyMans("", ""); std::vector coins; @@ -716,7 +716,7 @@ BOOST_AUTO_TEST_CASE(ApproximateBestSubset) wallet->LoadWallet(); LOCK(wallet->cs_wallet); wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS); - wallet->SetupDescriptorScriptPubKeyMans(); + wallet->SetupDescriptorScriptPubKeyMans("", ""); std::vector coins; @@ -738,7 +738,7 @@ BOOST_AUTO_TEST_CASE(SelectCoins_test) wallet->LoadWallet(); LOCK(wallet->cs_wallet); wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS); - wallet->SetupDescriptorScriptPubKeyMans(); + wallet->SetupDescriptorScriptPubKeyMans("", ""); // Random generator stuff std::default_random_engine generator; diff --git a/src/wallet/test/util.cpp b/src/wallet/test/util.cpp index f9b798223d82..3c9f6b58be74 100644 --- a/src/wallet/test/util.cpp +++ b/src/wallet/test/util.cpp @@ -27,7 +27,7 @@ std::unique_ptr CreateSyncedWallet(interfaces::Chain& chain, interfaces { LOCK(wallet->cs_wallet); wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS); - wallet->SetupDescriptorScriptPubKeyMans(); + wallet->SetupDescriptorScriptPubKeyMans("", ""); FlatSigningProvider provider; std::string error; diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp index 472257d8239f..59bc2865b9ed 100644 --- a/src/wallet/test/wallet_tests.cpp +++ b/src/wallet/test/wallet_tests.cpp @@ -375,7 +375,7 @@ BOOST_FIXTURE_TEST_CASE(coin_mark_dirty_immature_credit, TestChain100Setup) LOCK(wallet.cs_wallet); wallet.SetWalletFlag(WALLET_FLAG_DESCRIPTORS); - wallet.SetupDescriptorScriptPubKeyMans(); + wallet.SetupDescriptorScriptPubKeyMans("", ""); wallet.SetLastBlockProcessed(m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash()); diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index c28e23fabe07..977f58749194 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -38,6 +38,7 @@ #ifdef USE_BDB #include #endif +#include // TODO(refactor): move dependency it to scriptpubkeyman.cpp #include #include #include @@ -350,7 +351,7 @@ std::shared_ptr CreateWallet(WalletContext& context, const std::string& // Set a seed for the wallet if (wallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) { LOCK(wallet->cs_wallet); - wallet->SetupDescriptorScriptPubKeyMans(); + wallet->SetupDescriptorScriptPubKeyMans("", ""); } else { // TODO: drop this condition after removing option to create non-HD wallets // related backport bitcoin#11250 @@ -746,7 +747,7 @@ bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase) // If we are using descriptors, make new descriptors with a new seed if (IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS) && !IsWalletFlagSet(WALLET_FLAG_BLANK_WALLET)) { - SetupDescriptorScriptPubKeyMans(); + SetupDescriptorScriptPubKeyMans("", ""); } else if (auto spk_man = GetLegacyScriptPubKeyMan()) { // if we are not using HD, generate new keypool if (spk_man->IsHDEnabled()) { @@ -2869,7 +2870,11 @@ std::shared_ptr CWallet::Create(WalletContext& context, const std::stri LOCK(walletInstance->cs_wallet); if (walletInstance->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) { - walletInstance->SetupDescriptorScriptPubKeyMans(); + SecureString mnemonic = gArgs.GetArg("-mnemonic", "").c_str(); + SecureString mnemonic_passphrase = gArgs.GetArg("-mnemonicpassphrase", "").c_str(); + gArgs.ForceRemoveArg("mnemonic"); + gArgs.ForceRemoveArg("mnemonicpassphrase"); + walletInstance->SetupDescriptorScriptPubKeyMans(mnemonic, mnemonic_passphrase); // SetupDescriptorScriptPubKeyMans already calls SetupGeneration for us so we don't need to call SetupGeneration separately } else { // Top up the keypool // Legacy wallets need SetupGeneration here. @@ -3207,17 +3212,16 @@ bool CWallet::UpgradeToHD(const SecureString& secureMnemonic, const SecureString return false; } - if (IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) { - error = Untranslated("Use RPC 'importdescriptors' to add new descriptors to Descriptor Wallets"); - return false; - } - WalletLogPrintf("Upgrading wallet to HD\n"); SetMinVersion(FEATURE_HD); - if (!GenerateNewHDChain(secureMnemonic, secureMnemonicPassphrase, secureWalletPassphrase)) { - error = Untranslated("Failed to generate HD wallet"); - return false; + if (IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) { + SetupDescriptorScriptPubKeyMans(secureMnemonic, secureMnemonicPassphrase); + } else { + if (!GenerateNewHDChain(secureMnemonic, secureMnemonicPassphrase, secureWalletPassphrase)) { + error = Untranslated("Failed to generate HD wallet"); + return false; + } } return true; } @@ -3821,15 +3825,18 @@ void CWallet::LoadDescriptorScriptPubKeyMan(uint256 id, WalletDescriptor& desc) m_spk_managers[id] = std::move(spk_manager); } -void CWallet::SetupDescriptorScriptPubKeyMans() +void CWallet::SetupDescriptorScriptPubKeyMans(const SecureString& mnemonic_arg, const SecureString mnemonic_passphrase) { AssertLockHeld(cs_wallet); // Make a seed - CKey seed_key; - seed_key.MakeNewKey(true); - CPubKey seed = seed_key.GetPubKey(); - assert(seed_key.VerifyPubKey(seed)); + // TODO: remove duplicated code with CHDChain::SetMnemonic + const SecureString mnemonic = mnemonic_arg.empty() ? CMnemonic::Generate(gArgs.GetIntArg("-mnemonicbits", CHDChain::DEFAULT_MNEMONIC_BITS)) : mnemonic_arg; + if (!CMnemonic::Check(mnemonic)) { + throw std::runtime_error(std::string(__func__) + ": invalid mnemonic: `" + std::string(mnemonic.c_str()) + "`"); + } + SecureVector seed_key; + CMnemonic::ToSeed(mnemonic, mnemonic_passphrase, seed_key); // Get the extended key CExtKey master_key; @@ -3846,7 +3853,7 @@ void CWallet::SetupDescriptorScriptPubKeyMans() throw std::runtime_error(std::string(__func__) + ": Could not encrypt new descriptors"); } } - spk_manager->SetupDescriptorGeneration(master_key, internal); + spk_manager->SetupDescriptorGeneration(master_key, mnemonic, mnemonic_passphrase, internal); uint256 id = spk_manager->GetID(); m_spk_managers[id] = std::move(spk_manager); AddActiveScriptPubKeyMan(id, internal); @@ -3929,6 +3936,9 @@ ScriptPubKeyMan* CWallet::AddWalletDescriptor(WalletDescriptor& desc, const Flat return nullptr; } + SecureString mnemonic; + SecureString mnemonic_passphrase; + auto spk_man = GetDescriptorScriptPubKeyMan(desc); if (spk_man) { WalletLogPrintf("Update existing descriptor: %s\n", desc.descriptor->ToString()); diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index f59cc4de6da6..14f0ea7e56e2 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -1051,7 +1051,7 @@ class CWallet final : public WalletStorage, public interfaces::Chain::Notificati void DeactivateScriptPubKeyMan(uint256 id, bool internal); //! Create new DescriptorScriptPubKeyMans and add them to the wallet - void SetupDescriptorScriptPubKeyMans() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + void SetupDescriptorScriptPubKeyMans(const SecureString& mnemonic, const SecureString mnemonic_passphrase) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); //! Return the DescriptorScriptPubKeyMan for a WalletDescriptor if it is already in the wallet DescriptorScriptPubKeyMan* GetDescriptorScriptPubKeyMan(const WalletDescriptor& desc) const; diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index b8814bf801ca..da74b4bef972 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -242,7 +242,7 @@ bool WalletBatch::EraseActiveScriptPubKeyMan(bool internal) return EraseIC(key); } -bool WalletBatch::WriteDescriptorKey(const uint256& desc_id, const CPubKey& pubkey, const CPrivKey& privkey) +bool WalletBatch::WriteDescriptorKey(const uint256& desc_id, const CPubKey& pubkey, const CPrivKey& privkey, const SecureString& mnemonic, const SecureString& mnemonic_passphrase) { // hash pubkey/privkey to accelerate wallet load std::vector key; @@ -250,19 +250,19 @@ bool WalletBatch::WriteDescriptorKey(const uint256& desc_id, const CPubKey& pubk key.insert(key.end(), pubkey.begin(), pubkey.end()); key.insert(key.end(), privkey.begin(), privkey.end()); - return WriteIC(std::make_pair(DBKeys::WALLETDESCRIPTORKEY, std::make_pair(desc_id, pubkey)), std::make_pair(privkey, Hash(key)), false); + return WriteIC(std::make_pair(DBKeys::WALLETDESCRIPTORKEY, std::make_pair(desc_id, pubkey)), std::make_pair(std::make_pair(privkey, Hash(key)), std::make_pair(mnemonic, mnemonic_passphrase)), false); } -bool WalletBatch::WriteCryptedDescriptorKey(const uint256& desc_id, const CPubKey& pubkey, const std::vector& secret) +bool WalletBatch::WriteCryptedDescriptorKey(const uint256& desc_id, const CPubKey& pubkey, const std::vector& secret, const std::vector& mnemonic, const std::vector& mnemonic_passphrase) { - if (!WriteIC(std::make_pair(DBKeys::WALLETDESCRIPTORCKEY, std::make_pair(desc_id, pubkey)), secret, false)) { + if (!WriteIC(std::make_pair(DBKeys::WALLETDESCRIPTORCKEY, std::make_pair(desc_id, pubkey)), std::make_pair(secret, std::make_pair(mnemonic, mnemonic_passphrase)), false)) { return false; } EraseIC(std::make_pair(DBKeys::WALLETDESCRIPTORKEY, std::make_pair(desc_id, pubkey))); return true; } -bool WalletBatch::WriteDescriptor(const uint256& desc_id, const WalletDescriptor& descriptor) +bool WalletBatch::WriteDescriptor(const uint256& desc_id, const WalletDescriptor& descriptor/*, const SecureString& mnemonic, const SecureString& mnemonic_passphrase*/) { return WriteIC(make_pair(DBKeys::WALLETDESCRIPTOR, desc_id), descriptor); } @@ -336,6 +336,8 @@ class CWalletScanState { std::map m_descriptor_caches; std::map, CKey> m_descriptor_keys; std::map, std::pair>> m_descriptor_crypt_keys; + std::map, std::pair> mnemonics; + std::map, std::pair, std::vector>> crypted_mnemonics; bool tx_corrupt{false}; CWalletScanState() { @@ -705,6 +707,21 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, return false; } wss.m_descriptor_keys.insert(std::make_pair(std::make_pair(desc_id, pubkey.GetID()), key)); + + SecureString mnemonic; + SecureString mnemonic_passphrase; + // it's okay if wallet doesn't have mnemonic. + // The wallet may be created in an older version of Dash Core or by importing descriptor + try + { + ssValue >> mnemonic; + ssValue >> mnemonic_passphrase; + } + catch (const std::ios_base::failure&) {} + + if (!mnemonic.empty()) { + wss.mnemonics.insert(std::make_pair(std::make_pair(desc_id, pubkey.GetID()), std::make_pair(mnemonic, mnemonic_passphrase))); + } } else if (strType == DBKeys::WALLETDESCRIPTORCKEY) { uint256 desc_id; CPubKey pubkey; @@ -721,6 +738,22 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, wss.m_descriptor_crypt_keys.insert(std::make_pair(std::make_pair(desc_id, pubkey.GetID()), std::make_pair(pubkey, privkey))); wss.fIsEncrypted = true; + + // TODO : remove copy-paste with plain-text key + std::vector mnemonic; + std::vector mnemonic_passphrase; + // it's okay if wallet doesn't have mnemonic. + // The wallet may be created in an older version of Dash Core or by importing descriptor + try + { + ssValue >> mnemonic; + ssValue >> mnemonic_passphrase; + } + catch (const std::ios_base::failure&) {} + if (!mnemonic.empty()) { + wss.crypted_mnemonics.insert(std::make_pair(std::make_pair(desc_id, pubkey.GetID()), std::make_pair(mnemonic, mnemonic_passphrase))); + } + } else if (strType == DBKeys::LOCKED_UTXO) { uint256 hash; uint32_t n; @@ -865,11 +898,22 @@ DBErrors WalletBatch::LoadWallet(CWallet* pwallet) // Set the descriptor keys for (auto desc_key_pair : wss.m_descriptor_keys) { auto spk_man = pwallet->GetScriptPubKeyMan(desc_key_pair.first.first); - ((DescriptorScriptPubKeyMan*)spk_man)->AddKey(desc_key_pair.first.second, desc_key_pair.second); + auto it = wss.mnemonics.find(desc_key_pair.first); + if (it == wss.mnemonics.end()) { + ((DescriptorScriptPubKeyMan*)spk_man)->AddKey(desc_key_pair.first.second, desc_key_pair.second, "", ""); + } else { + ((DescriptorScriptPubKeyMan*)spk_man)->AddKey(desc_key_pair.first.second, desc_key_pair.second, it->second.first, it->second.second); + } } + for (auto desc_key_pair : wss.m_descriptor_crypt_keys) { auto spk_man = pwallet->GetScriptPubKeyMan(desc_key_pair.first.first); - ((DescriptorScriptPubKeyMan*)spk_man)->AddCryptedKey(desc_key_pair.first.second, desc_key_pair.second.first, desc_key_pair.second.second); + auto it = wss.crypted_mnemonics.find(desc_key_pair.first); + if (it == wss.crypted_mnemonics.end()) { + ((DescriptorScriptPubKeyMan*)spk_man)->AddCryptedKey(desc_key_pair.first.second, desc_key_pair.second.first, desc_key_pair.second.second, {}, {}); + } else { + ((DescriptorScriptPubKeyMan*)spk_man)->AddCryptedKey(desc_key_pair.first.second, desc_key_pair.second.first, desc_key_pair.second.second, it->second.first, it->second.second); + } } if (fNoncriticalErrors && result == DBErrors::LOAD_OK) diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h index f4c3d94de672..68bda27c680f 100644 --- a/src/wallet/walletdb.h +++ b/src/wallet/walletdb.h @@ -213,8 +213,8 @@ class WalletBatch /** Write a CGovernanceObject to the database */ bool WriteGovernanceObject(const Governance::Object& obj); - bool WriteDescriptorKey(const uint256& desc_id, const CPubKey& pubkey, const CPrivKey& privkey); - bool WriteCryptedDescriptorKey(const uint256& desc_id, const CPubKey& pubkey, const std::vector& secret); + bool WriteDescriptorKey(const uint256& desc_id, const CPubKey& pubkey, const CPrivKey& privkey, const SecureString& mnemonic, const SecureString& mnemonic_passphrase); + bool WriteCryptedDescriptorKey(const uint256& desc_id, const CPubKey& pubkey, const std::vector& secret, const std::vector& mnemonic, const std::vector& mnemonic_passphrase); bool WriteDescriptor(const uint256& desc_id, const WalletDescriptor& descriptor); bool WriteDescriptorDerivedCache(const CExtPubKey& xpub, const uint256& desc_id, uint32_t key_exp_index, uint32_t der_index); bool WriteDescriptorParentCache(const CExtPubKey& xpub, const uint256& desc_id, uint32_t key_exp_index); diff --git a/src/wallet/wallettool.cpp b/src/wallet/wallettool.cpp index f214744db6a1..184837a69730 100644 --- a/src/wallet/wallettool.cpp +++ b/src/wallet/wallettool.cpp @@ -50,7 +50,7 @@ static void WalletCreate(CWallet* wallet_instance, uint64_t wallet_creation_flag spk_man->GenerateNewHDChain(/*secureMnemonic=*/"", /*secureMnemonicPassphrase=*/""); } } else { - wallet_instance->SetupDescriptorScriptPubKeyMans(); + wallet_instance->SetupDescriptorScriptPubKeyMans("", ""); } tfm::format(std::cout, "Topping up keypool...\n"); diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index ead8103e0ebf..66b5bb231c7d 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -299,7 +299,7 @@ 'wallet_upgradewallet.py --legacy-wallet', 'wallet_importdescriptors.py --descriptors', 'wallet_mnemonicbits.py --legacy-wallet', - # 'wallet_mnemonicbits.py --descriptors', # TODO : implement mnemonics for descriptor wallets + 'wallet_mnemonicbits.py --descriptors', 'rpc_bind.py --ipv4', 'rpc_bind.py --ipv6', 'rpc_bind.py --nonloopback', From 4f6be3decad149de9c186c9652bf75ef1ed97bdd Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Fri, 21 Feb 2025 23:44:21 +0700 Subject: [PATCH 04/14] test: make functional test wallet_mnemonicbits.py works for descriptor wallets --- test/functional/wallet_mnemonicbits.py | 62 ++++++++++++++++++++++---- 1 file changed, 54 insertions(+), 8 deletions(-) diff --git a/test/functional/wallet_mnemonicbits.py b/test/functional/wallet_mnemonicbits.py index 5fa6aabf4cf4..d3bd41b66674 100755 --- a/test/functional/wallet_mnemonicbits.py +++ b/test/functional/wallet_mnemonicbits.py @@ -24,7 +24,42 @@ def run_test(self): self.stop_node(0) self.nodes[0].assert_start_raises_init_error(['-mnemonicbits=123'], "Error: Invalid '-mnemonicbits'. Allowed values: 128, 160, 192, 224, 256.") self.start_node(0) - assert_equal(len(self.nodes[0].dumphdinfo()["mnemonic"].split()), 12) # 12 words by default + + mnemonic_pre = self.nodes[0].listdescriptors(True)['descriptors'][1]["mnemonic"] if self.options.descriptors else self.nodes[0].dumphdinfo()["mnemonic"] + + self.nodes[0].encryptwallet('pass') + self.nodes[0].walletpassphrase('pass', 100) + if self.options.descriptors: + assert "mnemonic" not in self.nodes[0].listdescriptors()['descriptors'][0] + assert "mnemonic" in self.nodes[0].listdescriptors(True)['descriptors'][0] + + descriptors = self.nodes[0].listdescriptors(True)['descriptors'] + assert_equal(descriptors[0]['mnemonic'], descriptors[1]['mnemonic']) + + mnemonic_count = 0 + found_in_encrypted = 0 + for desc in descriptors: + if 'mnemonic' not in desc: + # skip imported coinbase private key + continue + assert_equal(len(desc['mnemonic'].split()), 12) + mnemonic_count += 1 + if desc['mnemonic'] == mnemonic_pre: + found_in_encrypted += 1 + assert not desc['active'] + else: + assert desc['active'] + # there should 5 descriptors in total + # one of them imported private key for coinbase without mnemonic + # encryption of descriptor wallet creates new private keys, + # it should be 2 active and 2 inactive mnemonics + assert_equal(found_in_encrypted, 2) + assert_equal(mnemonic_count, 4) + assert_equal(len(descriptors), 5) + else: + assert_equal(len(self.nodes[0].dumphdinfo()["mnemonic"].split()), 12) # 12 words by default + # legacy HD wallets could have only one chain + assert_equal(mnemonic_pre, self.nodes[0].dumphdinfo()["mnemonic"]) self.log.info("Can have multiple wallets with different mnemonic length loaded at the same time") self.restart_node(0, extra_args=["-mnemonicbits=160"]) @@ -34,16 +69,27 @@ def run_test(self): self.restart_node(0, extra_args=["-mnemonicbits=224"]) self.nodes[0].createwallet("wallet_224") self.restart_node(0, extra_args=["-mnemonicbits=256"]) + self.nodes[0].get_wallet_rpc(self.default_wallet_name).walletpassphrase('pass', 100) self.nodes[0].loadwallet("wallet_160") self.nodes[0].loadwallet("wallet_192") self.nodes[0].loadwallet("wallet_224") - self.nodes[0].createwallet("wallet_256", False, True) # blank - self.nodes[0].get_wallet_rpc("wallet_256").upgradetohd() - assert_equal(len(self.nodes[0].get_wallet_rpc(self.default_wallet_name).dumphdinfo()["mnemonic"].split()), 12) # 12 words by default - assert_equal(len(self.nodes[0].get_wallet_rpc("wallet_160").dumphdinfo()["mnemonic"].split()), 15) # 15 words - assert_equal(len(self.nodes[0].get_wallet_rpc("wallet_192").dumphdinfo()["mnemonic"].split()), 18) # 18 words - assert_equal(len(self.nodes[0].get_wallet_rpc("wallet_224").dumphdinfo()["mnemonic"].split()), 21) # 21 words - assert_equal(len(self.nodes[0].get_wallet_rpc("wallet_256").dumphdinfo()["mnemonic"].split()), 24) # 24 words + if self.options.descriptors: + self.nodes[0].createwallet("wallet_256", False, True, "", False, True) # blank Descriptors + self.nodes[0].get_wallet_rpc("wallet_256").upgradetohd() + # first descriptor is private key with no mnemonic for CbTx (see node.importprivkey), we use number#1 here instead + assert_equal(len(self.nodes[0].get_wallet_rpc(self.default_wallet_name).listdescriptors(True)["descriptors"][1]["mnemonic"].split()), 12) # 12 words by default + assert_equal(len(self.nodes[0].get_wallet_rpc("wallet_160").listdescriptors(True)["descriptors"][0]["mnemonic"].split()), 15) # 15 words + assert_equal(len(self.nodes[0].get_wallet_rpc("wallet_192").listdescriptors(True)["descriptors"][0]["mnemonic"].split()), 18) # 18 words + assert_equal(len(self.nodes[0].get_wallet_rpc("wallet_224").listdescriptors(True)["descriptors"][0]["mnemonic"].split()), 21) # 21 words + assert_equal(len(self.nodes[0].get_wallet_rpc("wallet_256").listdescriptors(True)["descriptors"][0]["mnemonic"].split()), 24) # 24 words + else: + self.nodes[0].createwallet("wallet_256", False, True) # blank HD legacy + self.nodes[0].get_wallet_rpc("wallet_256").upgradetohd() + assert_equal(len(self.nodes[0].get_wallet_rpc(self.default_wallet_name).dumphdinfo()["mnemonic"].split()), 12) # 12 words by default + assert_equal(len(self.nodes[0].get_wallet_rpc("wallet_160").dumphdinfo()["mnemonic"].split()), 15) # 15 words + assert_equal(len(self.nodes[0].get_wallet_rpc("wallet_192").dumphdinfo()["mnemonic"].split()), 18) # 18 words + assert_equal(len(self.nodes[0].get_wallet_rpc("wallet_224").dumphdinfo()["mnemonic"].split()), 21) # 21 words + assert_equal(len(self.nodes[0].get_wallet_rpc("wallet_256").dumphdinfo()["mnemonic"].split()), 24) # 24 words if __name__ == '__main__': From e27f95c46cb6359e94af98021e467ece26d61306 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Tue, 29 Apr 2025 14:38:27 +0700 Subject: [PATCH 05/14] fix: text message for HD wallets (both descriptors and non-descriptors) --- src/wallet/rpc/encrypt.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/wallet/rpc/encrypt.cpp b/src/wallet/rpc/encrypt.cpp index b9f72b17d1e7..6efe31c074cb 100644 --- a/src/wallet/rpc/encrypt.cpp +++ b/src/wallet/rpc/encrypt.cpp @@ -252,7 +252,10 @@ RPCHelpMan encryptwallet() throw JSONRPCError(RPC_WALLET_ENCRYPTION_FAILED, "Error: Failed to encrypt the wallet."); } - return "wallet encrypted; The keypool has been flushed and a new HD seed was generated (if you are using HD). You need to make a new backup or write down the new seed (mnemonic)."; + if (pwallet->IsHDEnabled()) { + return "wallet encrypted; If you will forget passphrase you will lose access to your funds. Be sure that you have backup of your seed or mnemonic."; + } + return "wallet encrypted; The keypool has been flushed. You need to make a new backup."; }, }; } From 8eb5d4a0fd2a81a1115fb9f3b85236088b3a520d Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Tue, 29 Apr 2025 14:44:47 +0700 Subject: [PATCH 06/14] fix: do not reset seed / mnemonic for descriptor wallets in case of encryption --- src/wallet/wallet.cpp | 2 +- test/functional/wallet_descriptor.py | 2 +- test/functional/wallet_mnemonicbits.py | 6 ++---- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 977f58749194..a7f558b9cdce 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -747,7 +747,7 @@ bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase) // If we are using descriptors, make new descriptors with a new seed if (IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS) && !IsWalletFlagSet(WALLET_FLAG_BLANK_WALLET)) { - SetupDescriptorScriptPubKeyMans("", ""); + // Do nothing for descriptor wallets (keep old seed / mnemonic) } else if (auto spk_man = GetLegacyScriptPubKeyMan()) { // if we are not using HD, generate new keypool if (spk_man->IsHDEnabled()) { diff --git a/test/functional/wallet_descriptor.py b/test/functional/wallet_descriptor.py index ca7d8875c1dd..9d919fb3d529 100755 --- a/test/functional/wallet_descriptor.py +++ b/test/functional/wallet_descriptor.py @@ -91,7 +91,7 @@ def run_test(self): send_wrpc.walletpassphrase('pass', 10) addr = send_wrpc.getnewaddress() info2 = send_wrpc.getaddressinfo(addr) - assert info1['hdmasterfingerprint'] != info2['hdmasterfingerprint'] + assert info1['hdmasterfingerprint'] == info2['hdmasterfingerprint'] send_wrpc.walletlock() assert 'hdmasterfingerprint' in send_wrpc.getaddressinfo(send_wrpc.getnewaddress()) info3 = send_wrpc.getaddressinfo(addr) diff --git a/test/functional/wallet_mnemonicbits.py b/test/functional/wallet_mnemonicbits.py index d3bd41b66674..195bfe28a1e0 100755 --- a/test/functional/wallet_mnemonicbits.py +++ b/test/functional/wallet_mnemonicbits.py @@ -46,16 +46,14 @@ def run_test(self): mnemonic_count += 1 if desc['mnemonic'] == mnemonic_pre: found_in_encrypted += 1 - assert not desc['active'] - else: assert desc['active'] # there should 5 descriptors in total # one of them imported private key for coinbase without mnemonic # encryption of descriptor wallet creates new private keys, # it should be 2 active and 2 inactive mnemonics assert_equal(found_in_encrypted, 2) - assert_equal(mnemonic_count, 4) - assert_equal(len(descriptors), 5) + assert_equal(mnemonic_count, 2) + assert_equal(len(descriptors), 3) else: assert_equal(len(self.nodes[0].dumphdinfo()["mnemonic"].split()), 12) # 12 words by default # legacy HD wallets could have only one chain From 4bd214e7f62ced990d3f8ce59151b3b4c16e5988 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Tue, 13 May 2025 15:26:15 +0700 Subject: [PATCH 07/14] test: adjust commentary, ensure cb private key is "inactive" descriptor --- test/functional/wallet_mnemonicbits.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/functional/wallet_mnemonicbits.py b/test/functional/wallet_mnemonicbits.py index 195bfe28a1e0..eee32254080e 100755 --- a/test/functional/wallet_mnemonicbits.py +++ b/test/functional/wallet_mnemonicbits.py @@ -40,6 +40,7 @@ def run_test(self): found_in_encrypted = 0 for desc in descriptors: if 'mnemonic' not in desc: + assert not desc['active'] # skip imported coinbase private key continue assert_equal(len(desc['mnemonic'].split()), 12) @@ -47,10 +48,9 @@ def run_test(self): if desc['mnemonic'] == mnemonic_pre: found_in_encrypted += 1 assert desc['active'] - # there should 5 descriptors in total - # one of them imported private key for coinbase without mnemonic - # encryption of descriptor wallet creates new private keys, - # it should be 2 active and 2 inactive mnemonics + # there should 3 descriptors in total + # one of them is inactive imported private key for coinbase. It has no mnemonic without mnemonic + # two other should be active and have mnemonic assert_equal(found_in_encrypted, 2) assert_equal(mnemonic_count, 2) assert_equal(len(descriptors), 3) From 3a8ab8039c3c04d7cd6cda3b68be2ab21c703f5a Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Tue, 13 May 2025 15:22:38 +0700 Subject: [PATCH 08/14] fix: intermittent error in wallet_mnemonic.py due to different order of descriptors Descriptor with Coinbase private key can be in any position --- test/functional/wallet_mnemonicbits.py | 43 ++++++++++++++++++-------- 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/test/functional/wallet_mnemonicbits.py b/test/functional/wallet_mnemonicbits.py index eee32254080e..7a72e3d6b219 100755 --- a/test/functional/wallet_mnemonicbits.py +++ b/test/functional/wallet_mnemonicbits.py @@ -17,6 +17,25 @@ def set_test_params(self): def skip_test_if_missing_module(self): self.skip_if_no_wallet() + def get_mnemonic(self, node): + if not self.options.descriptors: + return node.dumphdinfo()["mnemonic"] + + mnemonic = None + descriptors = node.listdescriptors(True)['descriptors'] + for desc in descriptors: + if desc['desc'][:4] == 'pkh(': + pass + if mnemonic is None: + mnemonic = desc['mnemonic'] + else: + assert_equal(mnemonic, desc['mnemonic']) + elif desc['desc'][:6] == 'combo(': + assert 'mnemonic' not in desc + else: + raise AssertionError(f"Unknown descriptor type: {desc['desc']}") + return mnemonic + def run_test(self): self.log.info("Test -mnemonicbits") @@ -25,33 +44,32 @@ def run_test(self): self.nodes[0].assert_start_raises_init_error(['-mnemonicbits=123'], "Error: Invalid '-mnemonicbits'. Allowed values: 128, 160, 192, 224, 256.") self.start_node(0) - mnemonic_pre = self.nodes[0].listdescriptors(True)['descriptors'][1]["mnemonic"] if self.options.descriptors else self.nodes[0].dumphdinfo()["mnemonic"] + mnemonic_pre = self.get_mnemonic(self.nodes[0]) + self.nodes[0].encryptwallet('pass') self.nodes[0].walletpassphrase('pass', 100) if self.options.descriptors: - assert "mnemonic" not in self.nodes[0].listdescriptors()['descriptors'][0] - assert "mnemonic" in self.nodes[0].listdescriptors(True)['descriptors'][0] - - descriptors = self.nodes[0].listdescriptors(True)['descriptors'] - assert_equal(descriptors[0]['mnemonic'], descriptors[1]['mnemonic']) + for desc in self.nodes[0].listdescriptors()['descriptors']: + assert "mnemonic" not in desc mnemonic_count = 0 - found_in_encrypted = 0 + cb_count = 0 + descriptors = self.nodes[0].listdescriptors(True)['descriptors'] for desc in descriptors: if 'mnemonic' not in desc: assert not desc['active'] # skip imported coinbase private key + cb_count += 1 continue assert_equal(len(desc['mnemonic'].split()), 12) mnemonic_count += 1 - if desc['mnemonic'] == mnemonic_pre: - found_in_encrypted += 1 - assert desc['active'] + assert desc['mnemonic'] == mnemonic_pre + assert desc['active'] # there should 3 descriptors in total # one of them is inactive imported private key for coinbase. It has no mnemonic without mnemonic # two other should be active and have mnemonic - assert_equal(found_in_encrypted, 2) + assert_equal(mnemonic_count, 2) assert_equal(mnemonic_count, 2) assert_equal(len(descriptors), 3) else: @@ -74,8 +92,7 @@ def run_test(self): if self.options.descriptors: self.nodes[0].createwallet("wallet_256", False, True, "", False, True) # blank Descriptors self.nodes[0].get_wallet_rpc("wallet_256").upgradetohd() - # first descriptor is private key with no mnemonic for CbTx (see node.importprivkey), we use number#1 here instead - assert_equal(len(self.nodes[0].get_wallet_rpc(self.default_wallet_name).listdescriptors(True)["descriptors"][1]["mnemonic"].split()), 12) # 12 words by default + assert_equal(len(self.get_mnemonic(self.nodes[0].get_wallet_rpc(self.default_wallet_name)).split()), 12) # 12 words by default assert_equal(len(self.nodes[0].get_wallet_rpc("wallet_160").listdescriptors(True)["descriptors"][0]["mnemonic"].split()), 15) # 15 words assert_equal(len(self.nodes[0].get_wallet_rpc("wallet_192").listdescriptors(True)["descriptors"][0]["mnemonic"].split()), 18) # 18 words assert_equal(len(self.nodes[0].get_wallet_rpc("wallet_224").listdescriptors(True)["descriptors"][0]["mnemonic"].split()), 21) # 21 words From e636d193d1e992822468dd50ec8c59e6cfa77b12 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Thu, 15 May 2025 14:42:21 +0700 Subject: [PATCH 09/14] test: multiple fixes in wallet_mnemonics to fix review comments --- test/functional/wallet_mnemonicbits.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/test/functional/wallet_mnemonicbits.py b/test/functional/wallet_mnemonicbits.py index 7a72e3d6b219..02c70a72551e 100755 --- a/test/functional/wallet_mnemonicbits.py +++ b/test/functional/wallet_mnemonicbits.py @@ -25,7 +25,6 @@ def get_mnemonic(self, node): descriptors = node.listdescriptors(True)['descriptors'] for desc in descriptors: if desc['desc'][:4] == 'pkh(': - pass if mnemonic is None: mnemonic = desc['mnemonic'] else: @@ -67,10 +66,10 @@ def run_test(self): assert desc['mnemonic'] == mnemonic_pre assert desc['active'] # there should 3 descriptors in total - # one of them is inactive imported private key for coinbase. It has no mnemonic without mnemonic - # two other should be active and have mnemonic - assert_equal(mnemonic_count, 2) + # One of them is inactive imported private key for coinbase. It has no mnemonic + # Two other should be active and have mnemonic assert_equal(mnemonic_count, 2) + assert_equal(cb_count, 1) assert_equal(len(descriptors), 3) else: assert_equal(len(self.nodes[0].dumphdinfo()["mnemonic"].split()), 12) # 12 words by default From d093506b983fafb7c140e42d2a78c9f22753bb38 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Tue, 1 Jul 2025 15:22:59 +0700 Subject: [PATCH 10/14] fix: compilation errors due to advancing develop branch --- src/wallet/test/coinselector_tests.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wallet/test/coinselector_tests.cpp b/src/wallet/test/coinselector_tests.cpp index f090ae9e7bfd..f124f611cc49 100644 --- a/src/wallet/test/coinselector_tests.cpp +++ b/src/wallet/test/coinselector_tests.cpp @@ -344,7 +344,7 @@ BOOST_AUTO_TEST_CASE(bnb_search_test) wallet->LoadWallet(); LOCK(wallet->cs_wallet); wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS); - wallet->SetupDescriptorScriptPubKeyMans(); + wallet->SetupDescriptorScriptPubKeyMans("", ""); std::vector coins; From c8a6f69c09cccd238953b3bb913d38e6a1d351d1 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Wed, 16 Jul 2025 14:46:48 +0700 Subject: [PATCH 11/14] refactor: use args instead gArgs in new mnemonic code Co-authored-by: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> --- src/wallet/wallet.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index a7f558b9cdce..e27f11657d94 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -2870,10 +2870,10 @@ std::shared_ptr CWallet::Create(WalletContext& context, const std::stri LOCK(walletInstance->cs_wallet); if (walletInstance->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) { - SecureString mnemonic = gArgs.GetArg("-mnemonic", "").c_str(); - SecureString mnemonic_passphrase = gArgs.GetArg("-mnemonicpassphrase", "").c_str(); - gArgs.ForceRemoveArg("mnemonic"); - gArgs.ForceRemoveArg("mnemonicpassphrase"); + SecureString mnemonic = args.GetArg("-mnemonic", "").c_str(); + SecureString mnemonic_passphrase = args.GetArg("-mnemonicpassphrase", "").c_str(); + args.ForceRemoveArg("mnemonic"); + args.ForceRemoveArg("mnemonicpassphrase"); walletInstance->SetupDescriptorScriptPubKeyMans(mnemonic, mnemonic_passphrase); // SetupDescriptorScriptPubKeyMans already calls SetupGeneration for us so we don't need to call SetupGeneration separately } else { // Top up the keypool @@ -3831,7 +3831,7 @@ void CWallet::SetupDescriptorScriptPubKeyMans(const SecureString& mnemonic_arg, // Make a seed // TODO: remove duplicated code with CHDChain::SetMnemonic - const SecureString mnemonic = mnemonic_arg.empty() ? CMnemonic::Generate(gArgs.GetIntArg("-mnemonicbits", CHDChain::DEFAULT_MNEMONIC_BITS)) : mnemonic_arg; + const SecureString mnemonic = mnemonic_arg.empty() ? CMnemonic::Generate(m_args.GetIntArg("-mnemonicbits", CHDChain::DEFAULT_MNEMONIC_BITS)) : mnemonic_arg; if (!CMnemonic::Check(mnemonic)) { throw std::runtime_error(std::string(__func__) + ": invalid mnemonic: `" + std::string(mnemonic.c_str()) + "`"); } From b3a7475509a133eda95adc68f100a5595f552907 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Wed, 16 Jul 2025 14:49:38 +0700 Subject: [PATCH 12/14] refactor: removed dead commented code from serialize.h Co-authored-by: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> --- src/serialize.h | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/serialize.h b/src/serialize.h index b47c3b0d562a..d0ac4e8f8917 100644 --- a/src/serialize.h +++ b/src/serialize.h @@ -824,13 +824,6 @@ struct VectorFormatter template void Serialize(Stream& os, const std::basic_string& str); template void Unserialize(Stream& is, std::basic_string& str); -/** - * SecureString - */ -/* -template void Serialize(Stream& os, const SecureString& str); -template void Unserialize(Stream& is, SecureString& str); -*/ /** * prevector * prevectors of unsigned char are a special case and are intended to be serialized as a single opaque blob. From c89e1815015f34a218a4a94ab95bcd871bdc6320 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Wed, 16 Jul 2025 14:55:47 +0700 Subject: [PATCH 13/14] feat: improve wording for errors, RPC and logs Co-authored-by: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> --- src/wallet/rpc/backup.cpp | 10 +++++----- src/wallet/rpc/encrypt.cpp | 2 +- src/wallet/scriptpubkeyman.cpp | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/wallet/rpc/backup.cpp b/src/wallet/rpc/backup.cpp index fb06818cc859..7d6464fa1961 100644 --- a/src/wallet/rpc/backup.cpp +++ b/src/wallet/rpc/backup.cpp @@ -874,9 +874,9 @@ RPCHelpMan dumphdinfo() RPCResult{ RPCResult::Type::OBJ, "", "", { - {RPCResult::Type::STR_HEX, "hdseed", "The HD seed (bip32, in hex)"}, - {RPCResult::Type::STR, "mnemonic", "The mnemonic for this HD wallet (bip39, english words)"}, - {RPCResult::Type::STR, "mnemonicpassphrase", "The mnemonic passphrase for this HD wallet (bip39)"}, + {RPCResult::Type::STR_HEX, "hdseed", "The HD seed (BIP32, in hex)"}, + {RPCResult::Type::STR, "mnemonic", "The mnemonic for this HD wallet (BIP39, english words)"}, + {RPCResult::Type::STR, "mnemonicpassphrase", "The mnemonic passphrase for this HD wallet (BIP39)"}, } }, RPCExamples{ @@ -1969,8 +1969,8 @@ RPCHelpMan listdescriptors() { {RPCResult::Type::OBJ, "", "", { {RPCResult::Type::STR, "desc", "Descriptor string representation"}, - {RPCResult::Type::STR, "mnemonic", "The mnemonic for this Descriptor wallet (bip39, english words). Presented only if private=true and created with mnemonic"}, - {RPCResult::Type::STR, "mnemonicpassphrase", "The mnemonic passphrase for this Descriptor wallet (bip39). Presented only if private=true and created with mnemonic"}, + {RPCResult::Type::STR, "mnemonic", "The mnemonic for this descriptor wallet (BIP39, english words). Presented only if private=true and created with a mnemonic"}, + {RPCResult::Type::STR, "mnemonicpassphrase", "The mnemonic passphrase for this descriptor wallet (BIP39). Presented only if private=true and created with a mnemonic"}, {RPCResult::Type::NUM, "timestamp", "The creation time of the descriptor"}, {RPCResult::Type::BOOL, "active", "Whether this descriptor is currently used to generate new addresses"}, {RPCResult::Type::BOOL, "internal", /*optional=*/true, "True if this descriptor is used to generate change addresses. False if this descriptor is used to generate receiving addresses; defined only for active descriptors"}, diff --git a/src/wallet/rpc/encrypt.cpp b/src/wallet/rpc/encrypt.cpp index 6efe31c074cb..56969202aeff 100644 --- a/src/wallet/rpc/encrypt.cpp +++ b/src/wallet/rpc/encrypt.cpp @@ -253,7 +253,7 @@ RPCHelpMan encryptwallet() } if (pwallet->IsHDEnabled()) { - return "wallet encrypted; If you will forget passphrase you will lose access to your funds. Be sure that you have backup of your seed or mnemonic."; + return "wallet encrypted; If you forget the passphrase, you will lose access to your funds. Make sure that you have backup of your seed or mnemonic."; } return "wallet encrypted; The keypool has been flushed. You need to make a new backup."; }, diff --git a/src/wallet/scriptpubkeyman.cpp b/src/wallet/scriptpubkeyman.cpp index 2f3498c2e0a0..3ea51a99e45c 100644 --- a/src/wallet/scriptpubkeyman.cpp +++ b/src/wallet/scriptpubkeyman.cpp @@ -2513,14 +2513,14 @@ bool DescriptorScriptPubKeyMan::GetMnemonicString(SecureString& mnemonic_out, Se if (!m_storage.WithEncryptionKey([&](const CKeyingMaterial& encryption_key) { return DecryptSecret(encryption_key, crypted_mnemonic, pubkey.GetHash(), mnemonic_v); })) { - LogPrintf("can't decrypt mnemonic pubkey %s crypted: %s\n", pubkey.GetHash().ToString(), HexStr(crypted_mnemonic)); + WalletLogPrintf("%s: ERROR: can't decrypt mnemonic pubkey %s crypted: %s\n", __func__, pubkey.GetHash().ToString(), HexStr(crypted_mnemonic)); return false; } if (!crypted_mnemonic_passphrase.empty()) { if (!m_storage.WithEncryptionKey([&](const CKeyingMaterial& encryption_key) { return DecryptSecret(encryption_key, crypted_mnemonic_passphrase, pubkey.GetHash(), mnemonic_passphrase_v); })) { - LogPrintf("can't decrypt mnemonic-passphrase\n"); + WalletLogPrintf("%s: ERROR: can't decrypt mnemonic passphrase\n", __func__); return false; } } From 42e80f1f750827b914f41bf862d1daee4a6fd495 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Wed, 16 Jul 2025 14:58:55 +0700 Subject: [PATCH 14/14] refactor: add prefix crypted_ for arguments related to crypted mnemonic Co-authored-by: UdjinM6 --- src/wallet/scriptpubkeyman.cpp | 4 ++-- src/wallet/scriptpubkeyman.h | 2 +- src/wallet/walletdb.cpp | 4 ++-- src/wallet/walletdb.h | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/wallet/scriptpubkeyman.cpp b/src/wallet/scriptpubkeyman.cpp index 3ea51a99e45c..8c82ff2c7add 100644 --- a/src/wallet/scriptpubkeyman.cpp +++ b/src/wallet/scriptpubkeyman.cpp @@ -2421,7 +2421,7 @@ bool DescriptorScriptPubKeyMan::AddKey(const CKeyID& key_id, const CKey& key, co return true; } -bool DescriptorScriptPubKeyMan::AddCryptedKey(const CKeyID& key_id, const CPubKey& pubkey, const std::vector& crypted_key, const std::vector& mnemonic,const std::vector& mnemonic_passphrase) +bool DescriptorScriptPubKeyMan::AddCryptedKey(const CKeyID& key_id, const CPubKey& pubkey, const std::vector& crypted_key, const std::vector& crypted_mnemonic,const std::vector& crypted_mnemonic_passphrase) { LOCK(cs_desc_man); if (!m_map_keys.empty()) { @@ -2429,7 +2429,7 @@ bool DescriptorScriptPubKeyMan::AddCryptedKey(const CKeyID& key_id, const CPubKe } m_map_crypted_keys[key_id] = make_pair(pubkey, crypted_key); - m_crypted_mnemonics[key_id] = make_pair(mnemonic, mnemonic_passphrase); + m_crypted_mnemonics[key_id] = make_pair(crypted_mnemonic, crypted_mnemonic_passphrase); return true; } diff --git a/src/wallet/scriptpubkeyman.h b/src/wallet/scriptpubkeyman.h index 976b761fab0f..6dc1b91b5867 100644 --- a/src/wallet/scriptpubkeyman.h +++ b/src/wallet/scriptpubkeyman.h @@ -599,7 +599,7 @@ class DescriptorScriptPubKeyMan : public ScriptPubKeyMan void SetCache(const DescriptorCache& cache); bool AddKey(const CKeyID& key_id, const CKey& key, const SecureString& mnemonic, const SecureString& mnemonic_passphrase); - bool AddCryptedKey(const CKeyID& key_id, const CPubKey& pubkey, const std::vector& crypted_key, const std::vector& mnemonic,const std::vector& mnemonic_passphrase); + bool AddCryptedKey(const CKeyID& key_id, const CPubKey& pubkey, const std::vector& crypted_key, const std::vector& crypted_mnemonic,const std::vector& crypted_mnemonic_passphrase); bool HasWalletDescriptor(const WalletDescriptor& desc) const; void UpdateWalletDescriptor(WalletDescriptor& descriptor); diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index da74b4bef972..d7e4b13c5447 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -253,9 +253,9 @@ bool WalletBatch::WriteDescriptorKey(const uint256& desc_id, const CPubKey& pubk return WriteIC(std::make_pair(DBKeys::WALLETDESCRIPTORKEY, std::make_pair(desc_id, pubkey)), std::make_pair(std::make_pair(privkey, Hash(key)), std::make_pair(mnemonic, mnemonic_passphrase)), false); } -bool WalletBatch::WriteCryptedDescriptorKey(const uint256& desc_id, const CPubKey& pubkey, const std::vector& secret, const std::vector& mnemonic, const std::vector& mnemonic_passphrase) +bool WalletBatch::WriteCryptedDescriptorKey(const uint256& desc_id, const CPubKey& pubkey, const std::vector& secret, const std::vector& crypted_mnemonic, const std::vector& crypted_mnemonic_passphrase) { - if (!WriteIC(std::make_pair(DBKeys::WALLETDESCRIPTORCKEY, std::make_pair(desc_id, pubkey)), std::make_pair(secret, std::make_pair(mnemonic, mnemonic_passphrase)), false)) { + if (!WriteIC(std::make_pair(DBKeys::WALLETDESCRIPTORCKEY, std::make_pair(desc_id, pubkey)), std::make_pair(secret, std::make_pair(crypted_mnemonic, crypted_mnemonic_passphrase)), false)) { return false; } EraseIC(std::make_pair(DBKeys::WALLETDESCRIPTORKEY, std::make_pair(desc_id, pubkey))); diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h index 68bda27c680f..43cd4885acc5 100644 --- a/src/wallet/walletdb.h +++ b/src/wallet/walletdb.h @@ -214,7 +214,7 @@ class WalletBatch bool WriteGovernanceObject(const Governance::Object& obj); bool WriteDescriptorKey(const uint256& desc_id, const CPubKey& pubkey, const CPrivKey& privkey, const SecureString& mnemonic, const SecureString& mnemonic_passphrase); - bool WriteCryptedDescriptorKey(const uint256& desc_id, const CPubKey& pubkey, const std::vector& secret, const std::vector& mnemonic, const std::vector& mnemonic_passphrase); + bool WriteCryptedDescriptorKey(const uint256& desc_id, const CPubKey& pubkey, const std::vector& secret, const std::vector& crypted_mnemonic, const std::vector& crypted_mnemonic_passphrase); bool WriteDescriptor(const uint256& desc_id, const WalletDescriptor& descriptor); bool WriteDescriptorDerivedCache(const CExtPubKey& xpub, const uint256& desc_id, uint32_t key_exp_index, uint32_t der_index); bool WriteDescriptorParentCache(const CExtPubKey& xpub, const uint256& desc_id, uint32_t key_exp_index);