From c85aa93dd1c6e7bed15293892443752142feb86d Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Thu, 31 Jul 2025 13:58:03 +0700 Subject: [PATCH 1/7] fix: follow-up #27068 - apply changes for mnemonic too --- doc/release-notes-27068.md | 3 ++- src/wallet/hdchain.cpp | 2 +- src/wallet/rpc/backup.cpp | 8 ++++---- src/wallet/rpc/wallet.cpp | 34 ++++++++++++++++------------------ src/wallet/wallet.cpp | 20 ++++++++++++++------ 5 files changed, 37 insertions(+), 30 deletions(-) diff --git a/doc/release-notes-27068.md b/doc/release-notes-27068.md index 3f5c5dba37bd..119d84870da3 100644 --- a/doc/release-notes-27068.md +++ b/doc/release-notes-27068.md @@ -2,5 +2,6 @@ Wallet ------ - Wallet passphrases may now contain null characters. + Mnemonic passphrases may now contain null characters. Prior to this change, only characters up to the first - null character were recognized and accepted. (#27068) \ No newline at end of file + null character were recognized and accepted. (#6780) diff --git a/src/wallet/hdchain.cpp b/src/wallet/hdchain.cpp index 538d89692aab..cc7fc0f76924 100644 --- a/src/wallet/hdchain.cpp +++ b/src/wallet/hdchain.cpp @@ -67,7 +67,7 @@ bool CHDChain::SetMnemonic(const SecureString& ssMnemonic, const SecureString& s // printf("mnemonic: %s\n", ssMnemonicTmp.c_str()); if (!CMnemonic::Check(ssMnemonicTmp)) { - throw std::runtime_error(std::string(__func__) + ": invalid mnemonic: `" + std::string(ssMnemonicTmp.c_str()) + "`"); + throw std::runtime_error(std::string(__func__) + ": invalid mnemonic: `" + std::string(ssMnemonicTmp) + "`"); } CMnemonic::ToSeed(ssMnemonicTmp, ssMnemonicPassphrase, vchSeed); diff --git a/src/wallet/rpc/backup.cpp b/src/wallet/rpc/backup.cpp index 617d905170df..5f00452069df 100644 --- a/src/wallet/rpc/backup.cpp +++ b/src/wallet/rpc/backup.cpp @@ -908,8 +908,8 @@ RPCHelpMan dumphdinfo() UniValue obj(UniValue::VOBJ); obj.pushKV("hdseed", HexStr(hdChainCurrent.GetSeed())); - obj.pushKV("mnemonic", ssMnemonic.c_str()); - obj.pushKV("mnemonicpassphrase", ssMnemonicPassphrase.c_str()); + obj.pushKV("mnemonic", ssMnemonic); + obj.pushKV("mnemonicpassphrase", ssMnemonicPassphrase); return obj; }, @@ -2024,8 +2024,8 @@ RPCHelpMan listdescriptors() 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("mnemonic", mnemonic); + spk.pushKV("mnemonicpassphrase", mnemonic_passphrase); } } spk.pushKV("desc", descriptor); diff --git a/src/wallet/rpc/wallet.cpp b/src/wallet/rpc/wallet.cpp index 91c27a1a9940..0839ae703a1e 100644 --- a/src/wallet/rpc/wallet.cpp +++ b/src/wallet/rpc/wallet.cpp @@ -375,29 +375,27 @@ static RPCHelpMan upgradetohd() { LOCK(pwallet->cs_wallet); - SecureString secureWalletPassphrase; - secureWalletPassphrase.reserve(100); + SecureString wallet_passphrase; + wallet_passphrase.reserve(100); if (request.params[2].isNull()) { if (pwallet->IsCrypted()) { throw JSONRPCError(RPC_WALLET_UNLOCK_NEEDED, "Error: Wallet encrypted but passphrase not supplied to RPC."); } } else { - // TODO: get rid of this .c_str() by implementing SecureString::operator=(std::string) - // Alternately, find a way to make request.params[0] mlock()'d to begin with. - secureWalletPassphrase = request.params[2].get_str().c_str(); + wallet_passphrase = std::string_view{request.params[2].get_str()}; } - SecureString secureMnemonic; - secureMnemonic.reserve(256); + SecureString mnemonic; + mnemonic.reserve(256); if (!generate_mnemonic) { - secureMnemonic = request.params[0].get_str().c_str(); + mnemonic = std::string_view{request.params[0].get_str()}; } - SecureString secureMnemonicPassphrase; - secureMnemonicPassphrase.reserve(256); + SecureString mnemonic_passphrase; + mnemonic_passphrase.reserve(256); if (!request.params[1].isNull()) { - secureMnemonicPassphrase = request.params[1].get_str().c_str(); + mnemonic_passphrase = std::string_view{request.params[1].get_str()}; } // TODO: breaking changes kept for v21! @@ -421,7 +419,7 @@ static RPCHelpMan upgradetohd() pwallet->SetMinVersion(FEATURE_HD); if (pwallet->IsCrypted()) { - if (secureWalletPassphrase.empty()) { + if (wallet_passphrase.empty()) { throw JSONRPCError(RPC_WALLET_PASSPHRASE_INCORRECT, "Error: Wallet encrypted but supplied empty wallet passphrase"); } @@ -430,13 +428,13 @@ static RPCHelpMan upgradetohd() pwallet->Lock(); // Unlock the wallet - if (!pwallet->Unlock(secureWalletPassphrase)) { + if (!pwallet->Unlock(wallet_passphrase)) { throw JSONRPCError(RPC_WALLET_PASSPHRASE_INCORRECT, "Error: The wallet passphrase entered was incorrect"); } } if (pwallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) { - pwallet->SetupDescriptorScriptPubKeyMans(secureMnemonic, secureMnemonicPassphrase); + pwallet->SetupDescriptorScriptPubKeyMans(mnemonic, mnemonic_passphrase); } else { auto spk_man = pwallet->GetLegacyScriptPubKeyMan(); if (!spk_man) { @@ -445,20 +443,20 @@ static RPCHelpMan upgradetohd() if (pwallet->IsCrypted()) { pwallet->WithEncryptionKey([&](const CKeyingMaterial& encryption_key) { - spk_man->GenerateNewHDChain(secureMnemonic, secureMnemonicPassphrase, encryption_key); + spk_man->GenerateNewHDChain(mnemonic, mnemonic_passphrase, encryption_key); return true; }); } else { - spk_man->GenerateNewHDChain(secureMnemonic, secureMnemonicPassphrase); + spk_man->GenerateNewHDChain(mnemonic, mnemonic_passphrase); } } if (pwallet->IsCrypted()) { // Relock encrypted wallet pwallet->Lock(); - } else if (!secureWalletPassphrase.empty()) { + } else if (!wallet_passphrase.empty()) { // Encrypt non-encrypted wallet - if (!pwallet->EncryptWallet(secureWalletPassphrase)) { + if (!pwallet->EncryptWallet(wallet_passphrase)) { throw JSONRPCError(RPC_WALLET_ENCRYPTION_FAILED, "Failed to encrypt HD wallet"); } } diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index ac43926eaa8e..c385464042af 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -2872,11 +2872,16 @@ std::shared_ptr CWallet::Create(WalletContext& context, const std::stri error = strprintf(_("%s -- Incorrect seed, it should be a hex string"), __func__); return nullptr; } - SecureString secureMnemonic = args.GetArg("-mnemonic", "").c_str(); - SecureString secureMnemonicPassphrase = args.GetArg("-mnemonicpassphrase", "").c_str(); + + SecureString mnemonic, mnemonic_passphrase; + mnemonic.reserve(256); + mnemonic_passphrase.reserve(256); + + mnemonic = args.GetArg("-mnemonic", ""); + mnemonic_passphrase = args.GetArg("-mnemonicpassphrase", ""); LOCK(walletInstance->cs_wallet); if (auto spk_man = walletInstance->GetLegacyScriptPubKeyMan()) { - spk_man->GenerateNewHDChain(secureMnemonic, secureMnemonicPassphrase); + spk_man->GenerateNewHDChain(mnemonic, mnemonic_passphrase); } } @@ -2888,8 +2893,11 @@ std::shared_ptr CWallet::Create(WalletContext& context, const std::stri LOCK(walletInstance->cs_wallet); if (walletInstance->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) { - SecureString mnemonic = args.GetArg("-mnemonic", "").c_str(); - SecureString mnemonic_passphrase = args.GetArg("-mnemonicpassphrase", "").c_str(); + SecureString mnemonic, mnemonic_passphrase; + mnemonic.reserve(256); + mnemonic_passphrase.reserve(256); + mnemonic = args.GetArg("-mnemonic", ""); + mnemonic_passphrase = args.GetArg("-mnemonicpassphrase", ""); args.ForceRemoveArg("mnemonic"); args.ForceRemoveArg("mnemonicpassphrase"); walletInstance->SetupDescriptorScriptPubKeyMans(mnemonic, mnemonic_passphrase); @@ -3764,7 +3772,7 @@ void CWallet::SetupDescriptorScriptPubKeyMans(const SecureString& mnemonic_arg, // 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()) + "`"); + throw std::runtime_error(std::string(__func__) + ": invalid mnemonic: `" + std::string(mnemonic) + "`"); } SecureVector seed_key; CMnemonic::ToSeed(mnemonic, mnemonic_passphrase, seed_key); From 3ab8b5e18c74a9ca7a3850e520b2f60443df5f95 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Mon, 4 Aug 2025 21:54:33 +0700 Subject: [PATCH 2/7] doc: update release notes to add PR num --- doc/release-notes-27068.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/release-notes-27068.md b/doc/release-notes-27068.md index 119d84870da3..d393c235609c 100644 --- a/doc/release-notes-27068.md +++ b/doc/release-notes-27068.md @@ -4,4 +4,4 @@ Wallet - Wallet passphrases may now contain null characters. Mnemonic passphrases may now contain null characters. Prior to this change, only characters up to the first - null character were recognized and accepted. (#6780) + null character were recognized and accepted. (#6780 #6792) From 7f53b9fff92cd4e39811cacf9d035ba2f0e847dd Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Mon, 4 Aug 2025 22:22:40 +0700 Subject: [PATCH 3/7] fix: helper ToSeed for mnemonics doesn't loose end of string after 0-characters --- src/wallet/bip39.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/wallet/bip39.cpp b/src/wallet/bip39.cpp index 2dd636ec30e4..40d7fd0b70d9 100644 --- a/src/wallet/bip39.cpp +++ b/src/wallet/bip39.cpp @@ -143,7 +143,8 @@ void CMnemonic::ToSeed(const SecureString& mnemonic, const SecureString& passphr { SecureString ssSalt = SecureString("mnemonic") + passphrase; - SecureVector vchSalt(ssSalt.begin(), ssSalt.begin() + strnlen(ssSalt.data(), 256)); + SecureVector vchSalt(ssSalt.begin(), ssSalt.begin() + std::min(256, ssSalt.size())); seedRet.resize(64); + // NOTE: c_str() here is fine because mnemonic has only [a-z ] characters PKCS5_PBKDF2_HMAC_SHA512(mnemonic.c_str(), mnemonic.size(), vchSalt.data(), vchSalt.size(), 2048, 64, seedRet.data()); } From 6f841f50f652df49ddf1150d08e16ce37ef21f05 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Mon, 4 Aug 2025 21:55:15 +0700 Subject: [PATCH 4/7] test: add more comprehensive testing for mnemonic_passphrase including null character --- test/functional/test_framework/util.py | 9 +++++-- test/functional/wallet_mnemonicbits.py | 12 ++++----- test/functional/wallet_upgradetohd.py | 34 +++++++++++++++++--------- 3 files changed, 36 insertions(+), 19 deletions(-) diff --git a/test/functional/test_framework/util.py b/test/functional/test_framework/util.py index d3d7b3e6b09b..ebecfb5af380 100644 --- a/test/functional/test_framework/util.py +++ b/test/functional/test_framework/util.py @@ -543,21 +543,26 @@ def get_mnemonic(node): Raises exception if there is none. """ if not node.getwalletinfo()['descriptors']: - return node.dumphdinfo()["mnemonic"] + hd = node.dumphdinfo() + return (hd["mnemonic"], hd["mnemonicpassphrase"]) mnemonic = None + mnemonic_passphrase = None descriptors = node.listdescriptors(True)['descriptors'] for desc in descriptors: if desc['desc'][:4] == 'pkh(': if mnemonic is None: mnemonic = desc['mnemonic'] + mnemonic_passphrase = desc['mnemonicpassphrase'] else: assert_equal(mnemonic, desc['mnemonic']) + assert_equal(mnemonic_passphrase, desc['mnemonicpassphrase']) elif desc['desc'][:6] == 'combo(': assert 'mnemonic' not in desc + assert 'mnemonic_passphrase' not in desc else: raise AssertionError(f"Unknown descriptor type: {desc['desc']}") - return mnemonic + return (mnemonic, mnemonic_passphrase) # Transaction/Block functions ############################# diff --git a/test/functional/wallet_mnemonicbits.py b/test/functional/wallet_mnemonicbits.py index 4cc40c09ef77..6a9574f68327 100755 --- a/test/functional/wallet_mnemonicbits.py +++ b/test/functional/wallet_mnemonicbits.py @@ -26,7 +26,7 @@ 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 = get_mnemonic(self.nodes[0]) + mnemonic_pre = get_mnemonic(self.nodes[0])[0] self.nodes[0].encryptwallet('pass') @@ -74,11 +74,11 @@ def run_test(self): self.nodes[0].createwallet("wallet_256", blank=True, descriptors=self.options.descriptors) # blank wallet self.nodes[0].get_wallet_rpc("wallet_256").upgradetohd() - assert_equal(len(get_mnemonic(self.nodes[0].get_wallet_rpc(self.default_wallet_name)).split()), 12) # 12 words by default - assert_equal(len(get_mnemonic(self.nodes[0].get_wallet_rpc("wallet_160")).split()), 15) # 15 words - assert_equal(len(get_mnemonic(self.nodes[0].get_wallet_rpc("wallet_192")).split()), 18) # 18 words - assert_equal(len(get_mnemonic(self.nodes[0].get_wallet_rpc("wallet_224")).split()), 21) # 21 words - assert_equal(len(get_mnemonic(self.nodes[0].get_wallet_rpc("wallet_256")).split()), 24) # 24 words + assert_equal(len(get_mnemonic(self.nodes[0].get_wallet_rpc(self.default_wallet_name))[0].split()), 12) # 12 words by default + assert_equal(len(get_mnemonic(self.nodes[0].get_wallet_rpc("wallet_160"))[0].split()), 15) # 15 words + assert_equal(len(get_mnemonic(self.nodes[0].get_wallet_rpc("wallet_192"))[0].split()), 18) # 18 words + assert_equal(len(get_mnemonic(self.nodes[0].get_wallet_rpc("wallet_224"))[0].split()), 21) # 21 words + assert_equal(len(get_mnemonic(self.nodes[0].get_wallet_rpc("wallet_256"))[0].split()), 24) # 24 words if __name__ == '__main__': diff --git a/test/functional/wallet_upgradetohd.py b/test/functional/wallet_upgradetohd.py index 733eace8415e..de1958d31c8c 100755 --- a/test/functional/wallet_upgradetohd.py +++ b/test/functional/wallet_upgradetohd.py @@ -110,8 +110,9 @@ def run_test(self): self.log.info("Same mnemonic, another mnemonic passphrase, no wallet passphrase, should result in a different set of keys") new_mnemonic_passphrase = "somewords" - assert node.upgradetohd(mnemonic, new_mnemonic_passphrase) - assert_equal(mnemonic, get_mnemonic(node)) + assert node.upgradetohd(mnemonic[0], new_mnemonic_passphrase) + assert_equal(mnemonic[0], get_mnemonic(node)[0]) + assert mnemonic[1] != get_mnemonic(node)[1] if not self.options.descriptors: new_chainid = node.getwalletinfo()['hdchainid'] assert chainid != new_chainid @@ -125,8 +126,9 @@ def run_test(self): self.recover_non_hd() self.log.info("Same mnemonic, another mnemonic passphrase, no wallet passphrase, should result in a different set of keys (again)") - assert node.upgradetohd(mnemonic, new_mnemonic_passphrase) - assert_equal(mnemonic, get_mnemonic(node)) + assert node.upgradetohd(mnemonic[0], new_mnemonic_passphrase) + assert_equal(mnemonic[0], get_mnemonic(node)[0]) + assert mnemonic[1] != get_mnemonic(node)[1] if not self.options.descriptors: assert_equal(new_chainid, node.getwalletinfo()['hdchainid']) assert_equal(balance_non_HD, node.getbalance()) @@ -139,7 +141,7 @@ def run_test(self): self.recover_non_hd() self.log.info("Same mnemonic, no mnemonic passphrase, no wallet passphrase, should recover all coins after rescan") - assert node.upgradetohd(mnemonic) + assert node.upgradetohd(mnemonic[0], mnemonic[1]) assert_equal(mnemonic, get_mnemonic(node)) if not self.options.descriptors: assert_equal(chainid, node.getwalletinfo()['hdchainid']) @@ -152,7 +154,7 @@ def run_test(self): self.log.info("Same mnemonic, no mnemonic passphrase, no wallet passphrase, large enough keepool, should recover all coins with no extra rescan") self.restart_node(0, extra_args=['-keypool=10']) - assert node.upgradetohd(mnemonic) + assert node.upgradetohd(mnemonic[0], mnemonic[1]) assert_equal(mnemonic, get_mnemonic(node)) if not self.options.descriptors: assert_equal(chainid, node.getwalletinfo()['hdchainid']) @@ -163,7 +165,7 @@ def run_test(self): self.log.info("Same mnemonic, no mnemonic passphrase, no wallet passphrase, large enough keepool, rescan is skipped initially, should recover all coins after rescanblockchain") self.restart_node(0, extra_args=['-keypool=10']) - assert node.upgradetohd(mnemonic, "", "", False) + assert node.upgradetohd(mnemonic[0], mnemonic[1], "", False) assert_equal(mnemonic, get_mnemonic(node)) if not self.options.descriptors: assert_equal(chainid, node.getwalletinfo()['hdchainid']) @@ -176,7 +178,7 @@ def run_test(self): self.log.info("Same mnemonic, same mnemonic passphrase, encrypt wallet on upgrade, should recover all coins after rescan") walletpass = "111pass222" - assert node.upgradetohd(mnemonic, "", walletpass) + assert node.upgradetohd(mnemonic[0], "", walletpass) node.stop() node.wait_until_stopped() self.start_node(0, extra_args=['-rescan']) @@ -202,9 +204,9 @@ def run_test(self): node.stop() node.wait_until_stopped() self.start_node(0, extra_args=['-rescan']) - assert_raises_rpc_error(-13, "Error: Wallet encrypted but passphrase not supplied to RPC.", node.upgradetohd, mnemonic) - assert_raises_rpc_error(-14, "Error: The wallet passphrase entered was incorrect", node.upgradetohd, mnemonic, "", "wrongpass") - assert node.upgradetohd(mnemonic, "", walletpass) + assert_raises_rpc_error(-13, "Error: Wallet encrypted but passphrase not supplied to RPC.", node.upgradetohd, mnemonic[0]) + assert_raises_rpc_error(-14, "Error: The wallet passphrase entered was incorrect", node.upgradetohd, mnemonic[0], "", "wrongpass") + assert node.upgradetohd(mnemonic[0], "", walletpass) if not self.options.descriptors: assert_raises_rpc_error(-13, "Error: Please enter the wallet passphrase with walletpassphrase first.", node.dumphdinfo) else: @@ -241,9 +243,19 @@ def run_test(self): node.createwallet("wallet-12", blank=True) w12 = node.get_wallet_rpc("wallet-12") w12.upgradetohd(custom_mnemonic, "custom-passphrase") + assert_equal(get_mnemonic(w12)[0], custom_mnemonic) + assert_equal(get_mnemonic(w12)[1], "custom-passphrase") assert_equal(12, w12.getbalance()) w12.unloadwallet() + self.log.info("Check if null character at the end of mnemonic-passphrase matters") + node.createwallet("wallet-null", blank=True) + w_null = node.get_wallet_rpc("wallet-null") + w_null.upgradetohd(custom_mnemonic, "custom-passphrase\0") + assert_equal(0, w_null.getbalance()) + assert_equal(get_mnemonic(w_null)[1], "custom-passphrase\0") + w_null.unloadwallet() + if __name__ == '__main__': WalletUpgradeToHDTest().main () From 95e9e665aaadbc8ab570493c164f483012110817 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Mon, 4 Aug 2025 22:48:21 +0700 Subject: [PATCH 5/7] test: fix incorrect key name --- test/functional/test_framework/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/functional/test_framework/util.py b/test/functional/test_framework/util.py index ebecfb5af380..314dddb9d137 100644 --- a/test/functional/test_framework/util.py +++ b/test/functional/test_framework/util.py @@ -559,7 +559,7 @@ def get_mnemonic(node): assert_equal(mnemonic_passphrase, desc['mnemonicpassphrase']) elif desc['desc'][:6] == 'combo(': assert 'mnemonic' not in desc - assert 'mnemonic_passphrase' not in desc + assert 'mnemonicpassphrase' not in desc else: raise AssertionError(f"Unknown descriptor type: {desc['desc']}") return (mnemonic, mnemonic_passphrase) From bfacf0933ee989f725383fcb70871168e0c628d3 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Mon, 4 Aug 2025 23:24:45 +0700 Subject: [PATCH 6/7] test: enforce stricter validation of mnemonic passphrase in wallet_upgradetohd --- test/functional/wallet_upgradetohd.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/functional/wallet_upgradetohd.py b/test/functional/wallet_upgradetohd.py index de1958d31c8c..e6615220d6ff 100755 --- a/test/functional/wallet_upgradetohd.py +++ b/test/functional/wallet_upgradetohd.py @@ -112,7 +112,7 @@ def run_test(self): new_mnemonic_passphrase = "somewords" assert node.upgradetohd(mnemonic[0], new_mnemonic_passphrase) assert_equal(mnemonic[0], get_mnemonic(node)[0]) - assert mnemonic[1] != get_mnemonic(node)[1] + assert_equal(new_mnemonic_passphrase, get_mnemonic(node)[1]) if not self.options.descriptors: new_chainid = node.getwalletinfo()['hdchainid'] assert chainid != new_chainid @@ -128,7 +128,7 @@ def run_test(self): self.log.info("Same mnemonic, another mnemonic passphrase, no wallet passphrase, should result in a different set of keys (again)") assert node.upgradetohd(mnemonic[0], new_mnemonic_passphrase) assert_equal(mnemonic[0], get_mnemonic(node)[0]) - assert mnemonic[1] != get_mnemonic(node)[1] + assert_equal(new_mnemonic_passphrase, get_mnemonic(node)[1]) if not self.options.descriptors: assert_equal(new_chainid, node.getwalletinfo()['hdchainid']) assert_equal(balance_non_HD, node.getbalance()) From 28012eba37bccbf2bd23899360674261561ff214 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Mon, 4 Aug 2025 23:49:27 +0700 Subject: [PATCH 7/7] test: test \0 for walletpassphrase too for rpc upgradetohd --- test/functional/wallet_upgradetohd.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/functional/wallet_upgradetohd.py b/test/functional/wallet_upgradetohd.py index e6615220d6ff..2843f97f943b 100755 --- a/test/functional/wallet_upgradetohd.py +++ b/test/functional/wallet_upgradetohd.py @@ -199,13 +199,14 @@ def run_test(self): self.recover_non_hd() self.log.info("Same mnemonic, same mnemonic passphrase, encrypt wallet first, should recover all coins on upgrade after rescan") - walletpass = "111pass222" + # Null characters are allowed in wallet passphrases since v23 + walletpass = "111\0pass222" node.encryptwallet(walletpass) node.stop() node.wait_until_stopped() self.start_node(0, extra_args=['-rescan']) assert_raises_rpc_error(-13, "Error: Wallet encrypted but passphrase not supplied to RPC.", node.upgradetohd, mnemonic[0]) - assert_raises_rpc_error(-14, "Error: The wallet passphrase entered was incorrect", node.upgradetohd, mnemonic[0], "", "wrongpass") + assert_raises_rpc_error(-14, "Error: The wallet passphrase entered was incorrect", node.upgradetohd, mnemonic[0], "", "111") assert node.upgradetohd(mnemonic[0], "", walletpass) if not self.options.descriptors: assert_raises_rpc_error(-13, "Error: Please enter the wallet passphrase with walletpassphrase first.", node.dumphdinfo)