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..d0ac4e8f8917 100644 --- a/src/serialize.h +++ b/src/serialize.h @@ -26,6 +26,7 @@ #include #include +#include #include #include @@ -820,8 +821,8 @@ 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); /** * prevector @@ -951,16 +952,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..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,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 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"}, @@ -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/rpc/encrypt.cpp b/src/wallet/rpc/encrypt.cpp index f81334a425c4..56969202aeff 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."; + if (pwallet->IsHDEnabled()) { + 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 cb254ec9f7d4..8c82ff2c7add 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& crypted_mnemonic,const std::vector& crypted_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(crypted_mnemonic, crypted_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); + })) { + 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); + })) { + WalletLogPrintf("%s: ERROR: can't decrypt mnemonic passphrase\n", __func__); + 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..6dc1b91b5867 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& crypted_mnemonic,const std::vector& crypted_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..f124f611cc49 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; @@ -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; @@ -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..e27f11657d94 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(); + // 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()) { @@ -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 = 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 // 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(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()) + "`"); + } + 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..d7e4b13c5447 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& crypted_mnemonic, const std::vector& crypted_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(crypted_mnemonic, crypted_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..43cd4885acc5 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& 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); 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', 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_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() diff --git a/test/functional/wallet_mnemonicbits.py b/test/functional/wallet_mnemonicbits.py index 5fa6aabf4cf4..02c70a72551e 100755 --- a/test/functional/wallet_mnemonicbits.py +++ b/test/functional/wallet_mnemonicbits.py @@ -17,6 +17,24 @@ 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(': + 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") @@ -24,7 +42,39 @@ 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.get_mnemonic(self.nodes[0]) + + + self.nodes[0].encryptwallet('pass') + self.nodes[0].walletpassphrase('pass', 100) + if self.options.descriptors: + for desc in self.nodes[0].listdescriptors()['descriptors']: + assert "mnemonic" not in desc + + mnemonic_count = 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 + 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 + # 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 + # 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 +84,26 @@ 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() + 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 + 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__':