scriptpubkeyman.cpp raw

   1  // Copyright (c) 2023-present The Bitcoin Core developers
   2  // Distributed under the MIT software license, see the accompanying
   3  // file COPYING or http://www.opensource.org/licenses/mit-license.php.
   4  
   5  #include <addresstype.h>
   6  #include <chainparams.h>
   7  #include <coins.h>
   8  #include <key.h>
   9  #include <primitives/transaction.h>
  10  #include <psbt.h>
  11  #include <script/descriptor.h>
  12  #include <script/interpreter.h>
  13  #include <script/script.h>
  14  #include <script/signingprovider.h>
  15  #include <sync.h>
  16  #include <test/fuzz/FuzzedDataProvider.h>
  17  #include <test/fuzz/fuzz.h>
  18  #include <test/fuzz/util.h>
  19  #include <test/fuzz/util/descriptor.h>
  20  #include <test/util/setup_common.h>
  21  #include <test/util/time.h>
  22  #include <util/check.h>
  23  #include <util/time.h>
  24  #include <util/translation.h>
  25  #include <util/string.h>
  26  #include <validation.h>
  27  #include <wallet/context.h>
  28  #include <wallet/scriptpubkeyman.h>
  29  #include <wallet/test/util.h>
  30  #include <wallet/types.h>
  31  #include <wallet/wallet.h>
  32  #include <wallet/walletutil.h>
  33  
  34  #include <map>
  35  #include <memory>
  36  #include <optional>
  37  #include <string>
  38  #include <utility>
  39  #include <variant>
  40  
  41  namespace wallet {
  42  namespace {
  43  const TestingSetup* g_setup;
  44  
  45  //! The converter of mocked descriptors, needs to be initialized when the target is.
  46  MockedDescriptorConverter MOCKED_DESC_CONVERTER;
  47  
  48  void initialize_spkm()
  49  {
  50      static const auto testing_setup{MakeNoLogFileContext<const TestingSetup>()};
  51      g_setup = testing_setup.get();
  52      MOCKED_DESC_CONVERTER.Init();
  53  }
  54  
  55  void initialize_spkm_migration()
  56  {
  57      static const auto testing_setup{MakeNoLogFileContext<const TestingSetup>()};
  58      g_setup = testing_setup.get();
  59  }
  60  
  61  static std::optional<std::pair<WalletDescriptor, FlatSigningProvider>> CreateWalletDescriptor(FuzzedDataProvider& fuzzed_data_provider)
  62  {
  63      const std::string mocked_descriptor{fuzzed_data_provider.ConsumeRandomLengthString()};
  64      const auto desc_str{MOCKED_DESC_CONVERTER.GetDescriptor(mocked_descriptor)};
  65      if (!desc_str.has_value()) return std::nullopt;
  66      if (IsTooExpensive(MakeUCharSpan(*desc_str))) return {};
  67  
  68      FlatSigningProvider keys;
  69      std::string error;
  70      std::vector<std::unique_ptr<Descriptor>> parsed_descs = Parse(desc_str.value(), keys, error, false);
  71      if (parsed_descs.empty()) return std::nullopt;
  72  
  73      // Verify expand succeeds before making WalletDescriptor
  74      // Expansion results are not needed
  75      FlatSigningProvider out_keys;
  76      std::vector<CScript> scripts_temp;
  77      DescriptorCache temp_cache;
  78      if (!parsed_descs.at(0)->Expand(0, keys, scripts_temp, out_keys, &temp_cache)) return std::nullopt;
  79  
  80      WalletDescriptor w_desc{std::move(parsed_descs.at(0)), /*creation_time=*/0, /*range_start=*/0, /*range_end=*/1, /*next_index=*/1};
  81      return std::make_pair(w_desc, keys);
  82  }
  83  
  84  static DescriptorScriptPubKeyMan* CreateDescriptor(WalletDescriptor& wallet_desc, FlatSigningProvider& keys, CWallet& keystore)
  85  {
  86      LOCK(keystore.cs_wallet);
  87      auto spk_manager_res = keystore.AddWalletDescriptor(wallet_desc, keys, /*label=*/"", /*internal=*/false);
  88      if (!spk_manager_res) return nullptr;
  89      return &spk_manager_res.value().get();
  90  };
  91  
  92  FUZZ_TARGET(scriptpubkeyman, .init = initialize_spkm)
  93  {
  94      SeedRandomStateForTest(SeedRand::ZEROS);
  95      FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
  96      FakeNodeClock clock{ConsumeTime(fuzzed_data_provider)};
  97      const auto& node{g_setup->m_node};
  98      Chainstate& chainstate{node.chainman->ActiveChainstate()};
  99      std::unique_ptr<CWallet> wallet_ptr{std::make_unique<CWallet>(node.chain.get(), "", CreateMockableWalletDatabase())};
 100      CWallet& wallet{*wallet_ptr};
 101      {
 102          LOCK(wallet.cs_wallet);
 103          wallet.SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
 104          wallet.SetLastBlockProcessed(chainstate.m_chain.Height(), chainstate.m_chain.Tip()->GetBlockHash());
 105          wallet.m_keypool_size = 1;
 106      }
 107  
 108      auto wallet_desc{CreateWalletDescriptor(fuzzed_data_provider)};
 109      if (!wallet_desc.has_value()) return;
 110      auto spk_manager{CreateDescriptor(wallet_desc->first, wallet_desc->second, wallet)};
 111      if (spk_manager == nullptr) return;
 112  
 113      if (fuzzed_data_provider.ConsumeBool()) {
 114          auto wallet_desc{CreateWalletDescriptor(fuzzed_data_provider)};
 115          if (!wallet_desc.has_value()) {
 116              return;
 117          }
 118          std::string error;
 119          if (spk_manager->CanUpdateToWalletDescriptor(wallet_desc->first, error)) {
 120              auto new_spk_manager{CreateDescriptor(wallet_desc->first, wallet_desc->second, wallet)};
 121              if (new_spk_manager != nullptr) spk_manager = new_spk_manager;
 122          }
 123      }
 124  
 125      bool good_data{true};
 126      LIMITED_WHILE(good_data && fuzzed_data_provider.ConsumeBool(), 20) {
 127          CallOneOf(
 128              fuzzed_data_provider,
 129              [&] {
 130                  const CScript script{ConsumeScript(fuzzed_data_provider)};
 131                  if (spk_manager->IsMine(script)) {
 132                      assert(spk_manager->GetScriptPubKeys().contains(script));
 133                  }
 134              },
 135              [&] {
 136                  auto spks{spk_manager->GetScriptPubKeys()};
 137                  for (const CScript& spk : spks) {
 138                      assert(spk_manager->IsMine(spk));
 139                      CTxDestination dest;
 140                      bool extract_dest{ExtractDestination(spk, dest)};
 141                      if (extract_dest) {
 142                          const std::string msg{fuzzed_data_provider.ConsumeRandomLengthString()};
 143                          PKHash pk_hash{std::get_if<PKHash>(&dest) && fuzzed_data_provider.ConsumeBool() ?
 144                                             *std::get_if<PKHash>(&dest) :
 145                                             PKHash{ConsumeUInt160(fuzzed_data_provider)}};
 146                          std::string str_sig;
 147                          (void)spk_manager->SignMessage(msg, pk_hash, str_sig);
 148                          (void)spk_manager->GetMetadata(dest);
 149                      }
 150                  }
 151              },
 152              [&] {
 153                  auto spks{spk_manager->GetScriptPubKeys()};
 154                  if (!spks.empty()) {
 155                      auto& spk{PickValue(fuzzed_data_provider, spks)};
 156                      (void)spk_manager->MarkUnusedAddresses(spk);
 157                  }
 158              },
 159              [&] {
 160                  LOCK(spk_manager->cs_desc_man);
 161                  auto wallet_desc{spk_manager->GetWalletDescriptor()};
 162                  if (wallet_desc.descriptor->IsSingleType()) {
 163                      auto output_type{wallet_desc.descriptor->GetOutputType()};
 164                      if (output_type.has_value()) {
 165                          auto dest{spk_manager->GetNewDestination(*output_type)};
 166                          if (dest) {
 167                              assert(IsValidDestination(*dest));
 168                              assert(spk_manager->IsHDEnabled());
 169                          }
 170                      }
 171                  }
 172              },
 173              [&] {
 174                  CMutableTransaction tx_to;
 175                  const std::optional<CMutableTransaction> opt_tx_to{ConsumeDeserializable<CMutableTransaction>(fuzzed_data_provider, TX_WITH_WITNESS)};
 176                  if (!opt_tx_to) {
 177                      good_data = false;
 178                      return;
 179                  }
 180                  tx_to = *opt_tx_to;
 181  
 182                  std::map<COutPoint, Coin> coins{ConsumeCoins(fuzzed_data_provider)};
 183                  const int sighash{fuzzed_data_provider.ConsumeIntegral<int>()};
 184                  std::map<int, bilingual_str> input_errors;
 185                  (void)spk_manager->SignTransaction(tx_to, coins, sighash, input_errors);
 186              },
 187              [&] {
 188                  std::optional<PartiallySignedTransaction> opt_psbt{ConsumeDeserializableConstructor<PartiallySignedTransaction>(fuzzed_data_provider)};
 189                  if (!opt_psbt) {
 190                      good_data = false;
 191                      return;
 192                  }
 193                  auto psbt{*opt_psbt};
 194                  std::optional<PrecomputedTransactionData> txdata_res = PrecomputePSBTData(psbt);
 195                  if (!txdata_res) {
 196                      return;
 197                  }
 198                  const PrecomputedTransactionData& txdata = *txdata_res;
 199                  common::PSBTFillOptions options{
 200                      .sign = fuzzed_data_provider.ConsumeBool(),
 201                      .sighash_type = fuzzed_data_provider.ConsumeIntegralInRange<int>(0, 151),
 202                      .finalize = fuzzed_data_provider.ConsumeBool(),
 203                      .bip32_derivs = fuzzed_data_provider.ConsumeBool()
 204                  };
 205                  if (options.sighash_type == 151) options.sighash_type = std::nullopt;
 206                  (void)spk_manager->FillPSBT(psbt, txdata, options);
 207              }
 208          );
 209      }
 210  
 211      std::string descriptor;
 212      (void)spk_manager->GetDescriptorString(descriptor, /*priv=*/fuzzed_data_provider.ConsumeBool());
 213      (void)spk_manager->GetEndRange();
 214      (void)spk_manager->GetKeyPoolSize();
 215  }
 216  
 217  FUZZ_TARGET(spkm_migration, .init = initialize_spkm_migration)
 218  {
 219      SeedRandomStateForTest(SeedRand::ZEROS);
 220      FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
 221      FakeNodeClock clock{ConsumeTime(fuzzed_data_provider)};
 222      const auto& node{g_setup->m_node};
 223      Chainstate& chainstate{node.chainman->ActiveChainstate()};
 224  
 225      std::unique_ptr<CWallet> wallet_ptr{std::make_unique<CWallet>(node.chain.get(), "", CreateMockableWalletDatabase())};
 226      CWallet& wallet{*wallet_ptr};
 227      wallet.m_keypool_size = 1;
 228      {
 229          LOCK(wallet.cs_wallet);
 230          wallet.UnsetWalletFlag(WALLET_FLAG_DESCRIPTORS);
 231          wallet.SetLastBlockProcessed(chainstate.m_chain.Height(), chainstate.m_chain.Tip()->GetBlockHash());
 232      }
 233  
 234      auto& legacy_data{*wallet.GetOrCreateLegacyDataSPKM()};
 235  
 236      std::vector<CKey> keys;
 237      LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 30) {
 238          const auto key{ConsumePrivateKey(fuzzed_data_provider)};
 239          if (!key.IsValid()) return;
 240          auto pub_key{key.GetPubKey()};
 241          if (!pub_key.IsFullyValid()) return;
 242          if (legacy_data.LoadKey(key, pub_key) && std::find(keys.begin(), keys.end(), key) == keys.end()) keys.push_back(key);
 243      }
 244  
 245      size_t added_chains = 0;
 246      bool add_hd_chain{fuzzed_data_provider.ConsumeBool() && !keys.empty()};
 247      CHDChain hd_chain;
 248      auto version{fuzzed_data_provider.ConsumeBool() ? CHDChain::VERSION_HD_CHAIN_SPLIT : CHDChain::VERSION_HD_BASE};
 249      CKey hd_key;
 250      if (add_hd_chain) {
 251          hd_key = PickValue(fuzzed_data_provider, keys);
 252          hd_chain.nVersion = version;
 253          hd_chain.seed_id = hd_key.GetPubKey().GetID();
 254          legacy_data.LoadHDChain(hd_chain);
 255          added_chains++;
 256      }
 257  
 258      bool add_inactive_hd_chain{fuzzed_data_provider.ConsumeBool() && !keys.empty()};
 259      if (add_inactive_hd_chain) {
 260          CKey inactive_hd_key = PickValue(fuzzed_data_provider, keys);
 261          hd_chain.nVersion = fuzzed_data_provider.ConsumeBool() ? CHDChain::VERSION_HD_CHAIN_SPLIT : CHDChain::VERSION_HD_BASE;
 262          bool dup_chain = hd_key.IsValid() && std::equal(hd_key.begin(), hd_key.end(), inactive_hd_key.begin());
 263          hd_chain.seed_id = inactive_hd_key.GetPubKey().GetID();
 264          legacy_data.AddInactiveHDChain(hd_chain);
 265          if (!dup_chain) added_chains++;
 266      }
 267  
 268      bool watch_only = false;
 269      const auto pub_key = ConsumeDeserializable<CPubKey>(fuzzed_data_provider);
 270      if (!pub_key || !pub_key->IsFullyValid()) return;
 271      auto script_dest{GetScriptForDestination(WitnessV0KeyHash{*pub_key})};
 272      if (fuzzed_data_provider.ConsumeBool()) {
 273          script_dest = GetScriptForDestination(CTxDestination{PKHash(*pub_key)});
 274      }
 275      if (legacy_data.LoadWatchOnly(script_dest)) watch_only = true;
 276  
 277      size_t added_script{0};
 278      bool good_data{true};
 279      LIMITED_WHILE(good_data && fuzzed_data_provider.ConsumeBool(), 30) {
 280          CallOneOf(
 281              fuzzed_data_provider,
 282              [&] {
 283                  CKey key;
 284                  if (!keys.empty()) {
 285                      key = PickValue(fuzzed_data_provider, keys);
 286                  } else {
 287                      key = ConsumePrivateKey(fuzzed_data_provider, /*compressed=*/fuzzed_data_provider.ConsumeBool());
 288                  }
 289                  if (!key.IsValid()) return;
 290                  auto pub_key{key.GetPubKey()};
 291                  CScript script;
 292                  CallOneOf(
 293                      fuzzed_data_provider,
 294                      [&] {
 295                          script = GetScriptForDestination(CTxDestination{PKHash(pub_key)});
 296                      },
 297                      [&] {
 298                          script = GetScriptForDestination(WitnessV0KeyHash(pub_key));
 299                      },
 300                      [&] {
 301                          std::optional<CScript> script_opt{ConsumeDeserializable<CScript>(fuzzed_data_provider)};
 302                          if (!script_opt) {
 303                              good_data = false;
 304                              return;
 305                          }
 306                          script = script_opt.value();
 307                      }
 308                  );
 309                  if (fuzzed_data_provider.ConsumeBool()) script = GetScriptForDestination(ScriptHash(script));
 310                  if (!legacy_data.HaveCScript(CScriptID(script)) && legacy_data.AddCScript(script)) added_script++;
 311              },
 312              [&] {
 313                  CKey key;
 314                  if (!keys.empty()) {
 315                      key = PickValue(fuzzed_data_provider, keys);
 316                  } else {
 317                      key = ConsumePrivateKey(fuzzed_data_provider, /*compressed=*/fuzzed_data_provider.ConsumeBool());
 318                  }
 319                  if (!key.IsValid()) return;
 320                  const auto num_keys{fuzzed_data_provider.ConsumeIntegralInRange<size_t>(1, MAX_PUBKEYS_PER_MULTISIG)};
 321                  std::vector<CPubKey> pubkeys;
 322                  pubkeys.emplace_back(key.GetPubKey());
 323                  for (size_t i = 1; i < num_keys; i++) {
 324                      if (fuzzed_data_provider.ConsumeBool()) {
 325                          pubkeys.emplace_back(key.GetPubKey());
 326                      } else {
 327                          CKey private_key{ConsumePrivateKey(fuzzed_data_provider, /*compressed=*/fuzzed_data_provider.ConsumeBool())};
 328                          if (!private_key.IsValid()) return;
 329                          pubkeys.emplace_back(private_key.GetPubKey());
 330                      }
 331                  }
 332                  if (pubkeys.size() < num_keys) return;
 333                  CScript multisig_script{GetScriptForMultisig(num_keys, pubkeys)};
 334                  if (!legacy_data.HaveCScript(CScriptID(multisig_script)) && legacy_data.AddCScript(multisig_script)) {
 335                      added_script++;
 336                  }
 337              }
 338          );
 339      }
 340  
 341      auto result{legacy_data.MigrateToDescriptor()};
 342      assert(result);
 343      if ((add_hd_chain && version >= CHDChain::VERSION_HD_CHAIN_SPLIT) || (!add_hd_chain && add_inactive_hd_chain)) {
 344          added_chains *= 2;
 345      }
 346      size_t added_size{keys.size() + added_chains};
 347      if (added_script > 0) {
 348          assert(result->desc_spkms.size() >= added_size);
 349      } else {
 350          assert(result->desc_spkms.size() == added_size);
 351      }
 352      if (watch_only) assert(!result->watch_descs.empty());
 353      if (!result->solvable_descs.empty()) assert(added_script > 0);
 354  }
 355  
 356  } // namespace
 357  } // namespace wallet
 358