From da3942e9fb896b872bb62eaf925c4ffd26413d07 Mon Sep 17 00:00:00 2001 From: woltx <94266259+w0xlt@users.noreply.github.com> Date: Tue, 15 Jul 2025 00:45:18 -0700 Subject: [PATCH 1/2] wallet: `MigrateToDescriptor` no longer calls `CanSupportFeature` Github-Pull: bitcoin/bitcoin#32977 Rebased-From: ba0158522981287f2fde83f38392baac0216b0b4 --- src/wallet/scriptpubkeyman.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/wallet/scriptpubkeyman.cpp b/src/wallet/scriptpubkeyman.cpp index 04287581f5a0..ead0a739b809 100644 --- a/src/wallet/scriptpubkeyman.cpp +++ b/src/wallet/scriptpubkeyman.cpp @@ -1872,10 +1872,13 @@ std::optional LegacyDataSPKM::MigrateToDescriptor() for (const auto& chain_pair : m_inactive_hd_chains) { chains.push_back(chain_pair.second); } + + bool can_support_hd_split_feature = m_hd_chain.nVersion >= CHDChain::VERSION_HD_CHAIN_SPLIT; + for (const CHDChain& chain : chains) { for (int i = 0; i < 2; ++i) { // Skip if doing internal chain and split chain is not supported - if (chain.seed_id.IsNull() || (i == 1 && !m_storage.CanSupportFeature(FEATURE_HD_SPLIT))) { + if (chain.seed_id.IsNull() || (i == 1 && !can_support_hd_split_feature)) { continue; } // Get the master xprv From f9cba55ad70f56c11be33f829f83bcd9b9a174b4 Mon Sep 17 00:00:00 2001 From: brunoerg Date: Sat, 9 Mar 2024 06:22:50 -0300 Subject: [PATCH 2/2] fuzz: wallet: add target for `MigrateToDescriptor` Github-Pull: bitcoin/bitcoin#32624 Rebased-From: 779e7825dbfba6a1ebd6ad62a8b2f312bd2c6b5f --- src/wallet/test/fuzz/scriptpubkeyman.cpp | 145 ++++++++++++++++++++++- 1 file changed, 144 insertions(+), 1 deletion(-) diff --git a/src/wallet/test/fuzz/scriptpubkeyman.cpp b/src/wallet/test/fuzz/scriptpubkeyman.cpp index 0c17a6bf7a9f..49c2ef6e4e93 100644 --- a/src/wallet/test/fuzz/scriptpubkeyman.cpp +++ b/src/wallet/test/fuzz/scriptpubkeyman.cpp @@ -21,7 +21,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -46,10 +48,15 @@ void initialize_spkm() { static const auto testing_setup{MakeNoLogFileContext()}; g_setup = testing_setup.get(); - SelectParams(ChainType::MAIN); MOCKED_DESC_CONVERTER.Init(); } +void initialize_spkm_migration() +{ + static const auto testing_setup{MakeNoLogFileContext()}; + g_setup = testing_setup.get(); +} + /** * Key derivation is expensive. Deriving deep derivation paths take a lot of compute and we'd rather spend time * elsewhere in this target, like on actually fuzzing the DescriptorScriptPubKeyMan. So rule out strings which could @@ -203,5 +210,141 @@ FUZZ_TARGET(scriptpubkeyman, .init = initialize_spkm) (void)spk_manager->GetKeyPoolSize(); } +FUZZ_TARGET(spkm_migration, .init = initialize_spkm_migration) +{ + SeedRandomStateForTest(SeedRand::ZEROS); + FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()}; + SetMockTime(ConsumeTime(fuzzed_data_provider)); + const auto& node{g_setup->m_node}; + Chainstate& chainstate{node.chainman->ActiveChainstate()}; + + std::unique_ptr wallet_ptr{std::make_unique(node.chain.get(), "", CreateMockableWalletDatabase())}; + CWallet& wallet{*wallet_ptr}; + wallet.m_keypool_size = 1; + { + LOCK(wallet.cs_wallet); + wallet.UnsetWalletFlag(WALLET_FLAG_DESCRIPTORS); + wallet.SetLastBlockProcessed(chainstate.m_chain.Height(), chainstate.m_chain.Tip()->GetBlockHash()); + } + + auto& legacy_data{*wallet.GetOrCreateLegacyDataSPKM()}; + + std::vector keys; + LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 30) { + const auto key{ConsumePrivateKey(fuzzed_data_provider)}; + if (!key.IsValid()) return; + auto pub_key{key.GetPubKey()}; + if (!pub_key.IsFullyValid()) return; + if (legacy_data.LoadKey(key, pub_key) && std::find(keys.begin(), keys.end(), key) == keys.end()) keys.push_back(key); + } + + bool add_hd_chain{fuzzed_data_provider.ConsumeBool() && !keys.empty()}; + CHDChain hd_chain; + auto version{fuzzed_data_provider.ConsumeBool() ? CHDChain::VERSION_HD_CHAIN_SPLIT : CHDChain::VERSION_HD_BASE}; + CKey hd_key; + if (add_hd_chain) { + hd_key = PickValue(fuzzed_data_provider, keys); + hd_chain.nVersion = version; + hd_chain.seed_id = hd_key.GetPubKey().GetID(); + legacy_data.LoadHDChain(hd_chain); + } + + bool add_inactive_hd_chain{fuzzed_data_provider.ConsumeBool() && !keys.empty()}; + if (add_inactive_hd_chain) { + hd_key = PickValue(fuzzed_data_provider, keys); + hd_chain.nVersion = fuzzed_data_provider.ConsumeBool() ? CHDChain::VERSION_HD_CHAIN_SPLIT : CHDChain::VERSION_HD_BASE; + hd_chain.seed_id = hd_key.GetPubKey().GetID(); + legacy_data.AddInactiveHDChain(hd_chain); + } + + bool watch_only = false; + const auto pub_key = ConsumeDeserializable(fuzzed_data_provider); + if (!pub_key || !pub_key->IsFullyValid()) return; + auto script_dest{GetScriptForDestination(WitnessV0KeyHash{*pub_key})}; + if (fuzzed_data_provider.ConsumeBool()) { + script_dest = GetScriptForDestination(CTxDestination{PKHash(*pub_key)}); + } + if (legacy_data.LoadWatchOnly(script_dest)) watch_only = true; + + size_t added_script{0}; + bool good_data{true}; + LIMITED_WHILE(good_data && fuzzed_data_provider.ConsumeBool(), 30) { + CallOneOf( + fuzzed_data_provider, + [&] { + CKey key; + if (!keys.empty()) { + key = PickValue(fuzzed_data_provider, keys); + } else { + key = ConsumePrivateKey(fuzzed_data_provider, /*compressed=*/fuzzed_data_provider.ConsumeBool()); + } + if (!key.IsValid()) return; + auto pub_key{key.GetPubKey()}; + CScript script; + CallOneOf( + fuzzed_data_provider, + [&] { + script = GetScriptForDestination(CTxDestination{PKHash(pub_key)}); + }, + [&] { + script = GetScriptForDestination(WitnessV0KeyHash(pub_key)); + }, + [&] { + std::optional script_opt{ConsumeDeserializable(fuzzed_data_provider)}; + if (!script_opt) { + good_data = false; + return; + } + script = script_opt.value(); + } + ); + if (fuzzed_data_provider.ConsumeBool()) script = GetScriptForDestination(ScriptHash(script)); + if (!legacy_data.HaveCScript(CScriptID(script)) && legacy_data.AddCScript(script)) added_script++; + }, + [&] { + CKey key; + if (!keys.empty()) { + key = PickValue(fuzzed_data_provider, keys); + } else { + key = ConsumePrivateKey(fuzzed_data_provider, /*compressed=*/fuzzed_data_provider.ConsumeBool()); + } + if (!key.IsValid()) return; + const auto num_keys{fuzzed_data_provider.ConsumeIntegralInRange(1, MAX_PUBKEYS_PER_MULTISIG)}; + std::vector pubkeys; + pubkeys.emplace_back(key.GetPubKey()); + for (size_t i = 1; i < num_keys; i++) { + if (fuzzed_data_provider.ConsumeBool()) { + pubkeys.emplace_back(key.GetPubKey()); + } else { + CKey private_key{ConsumePrivateKey(fuzzed_data_provider, /*compressed=*/fuzzed_data_provider.ConsumeBool())}; + if (!private_key.IsValid()) return; + pubkeys.emplace_back(private_key.GetPubKey()); + } + } + if (pubkeys.size() < num_keys) return; + CScript multisig_script{GetScriptForMultisig(num_keys, pubkeys)}; + if (!legacy_data.HaveCScript(CScriptID(multisig_script)) && legacy_data.AddCScript(multisig_script)) { + added_script++; + } + } + ); + } + + auto result{legacy_data.MigrateToDescriptor()}; + assert(result); + size_t added_chains{static_cast(add_hd_chain) + static_cast(add_inactive_hd_chain)}; + if ((add_hd_chain && version >= CHDChain::VERSION_HD_CHAIN_SPLIT) || (!add_hd_chain && add_inactive_hd_chain)) { + added_chains *= 2; + } + size_t added_size{keys.size() + added_chains}; + if (added_script > 0) { + assert(result->desc_spkms.size() >= added_size); + } else { + assert(result->desc_spkms.size() == added_size); + } + if (watch_only) assert(!result->watch_descs.empty()); + if (!result->solvable_descs.empty()) assert(added_script > 0); +} + } // namespace } // namespace wallet