/ src / wallet / test / fuzz / scriptpubkeyman.cpp
scriptpubkeyman.cpp
  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 <util/check.h>
 22  #include <util/translation.h>
 23  #include <validation.h>
 24  #include <wallet/scriptpubkeyman.h>
 25  #include <wallet/test/util.h>
 26  #include <wallet/types.h>
 27  #include <wallet/wallet.h>
 28  #include <wallet/walletutil.h>
 29  
 30  #include <map>
 31  #include <memory>
 32  #include <optional>
 33  #include <string>
 34  #include <utility>
 35  #include <variant>
 36  
 37  namespace wallet {
 38  namespace {
 39  const TestingSetup* g_setup;
 40  
 41  //! The converter of mocked descriptors, needs to be initialized when the target is.
 42  MockedDescriptorConverter MOCKED_DESC_CONVERTER;
 43  
 44  void initialize_spkm()
 45  {
 46      static const auto testing_setup{MakeNoLogFileContext<const TestingSetup>()};
 47      g_setup = testing_setup.get();
 48      SelectParams(ChainType::MAIN);
 49      MOCKED_DESC_CONVERTER.Init();
 50  }
 51  
 52  /**
 53   * Key derivation is expensive. Deriving deep derivation paths take a lot of compute and we'd rather spend time
 54   * elsewhere in this target, like on actually fuzzing the DescriptorScriptPubKeyMan. So rule out strings which could
 55   * correspond to a descriptor containing a too large derivation path.
 56   */
 57  static bool TooDeepDerivPath(std::string_view desc)
 58  {
 59      const FuzzBufferType desc_buf{reinterpret_cast<const unsigned char *>(desc.data()), desc.size()};
 60      return HasDeepDerivPath(desc_buf);
 61  }
 62  
 63  static std::optional<std::pair<WalletDescriptor, FlatSigningProvider>> CreateWalletDescriptor(FuzzedDataProvider& fuzzed_data_provider)
 64  {
 65      const std::string mocked_descriptor{fuzzed_data_provider.ConsumeRandomLengthString()};
 66      if (TooDeepDerivPath(mocked_descriptor)) return {};
 67      const auto desc_str{MOCKED_DESC_CONVERTER.GetDescriptor(mocked_descriptor)};
 68      if (!desc_str.has_value()) return std::nullopt;
 69  
 70      FlatSigningProvider keys;
 71      std::string error;
 72      std::unique_ptr<Descriptor> parsed_desc{Parse(desc_str.value(), keys, error, false)};
 73      if (!parsed_desc) return std::nullopt;
 74  
 75      WalletDescriptor w_desc{std::move(parsed_desc), /*creation_time=*/0, /*range_start=*/0, /*range_end=*/1, /*next_index=*/1};
 76      return std::make_pair(w_desc, keys);
 77  }
 78  
 79  static DescriptorScriptPubKeyMan* CreateDescriptor(WalletDescriptor& wallet_desc, FlatSigningProvider& keys, CWallet& keystore)
 80  {
 81      LOCK(keystore.cs_wallet);
 82      keystore.AddWalletDescriptor(wallet_desc, keys, /*label=*/"", /*internal=*/false);
 83      return keystore.GetDescriptorScriptPubKeyMan(wallet_desc);
 84  };
 85  
 86  FUZZ_TARGET(scriptpubkeyman, .init = initialize_spkm)
 87  {
 88      FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
 89      const auto& node{g_setup->m_node};
 90      Chainstate& chainstate{node.chainman->ActiveChainstate()};
 91      std::unique_ptr<CWallet> wallet_ptr{std::make_unique<CWallet>(node.chain.get(), "", CreateMockableWalletDatabase())};
 92      CWallet& wallet{*wallet_ptr};
 93      {
 94          LOCK(wallet.cs_wallet);
 95          wallet.SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
 96          wallet.SetLastBlockProcessed(chainstate.m_chain.Height(), chainstate.m_chain.Tip()->GetBlockHash());
 97      }
 98  
 99      auto wallet_desc{CreateWalletDescriptor(fuzzed_data_provider)};
100      if (!wallet_desc.has_value()) return;
101      auto spk_manager{CreateDescriptor(wallet_desc->first, wallet_desc->second, wallet)};
102      if (spk_manager == nullptr) return;
103  
104      bool good_data{true};
105      LIMITED_WHILE(good_data && fuzzed_data_provider.ConsumeBool(), 300) {
106          CallOneOf(
107              fuzzed_data_provider,
108              [&] {
109                  auto wallet_desc{CreateWalletDescriptor(fuzzed_data_provider)};
110                  if (!wallet_desc.has_value()) {
111                      good_data = false;
112                      return;
113                  }
114                  std::string error;
115                  if (spk_manager->CanUpdateToWalletDescriptor(wallet_desc->first, error)) {
116                      auto new_spk_manager{CreateDescriptor(wallet_desc->first, wallet_desc->second, wallet)};
117                      if (new_spk_manager != nullptr) spk_manager = new_spk_manager;
118                  }
119              },
120              [&] {
121                  const CScript script{ConsumeScript(fuzzed_data_provider)};
122                  auto is_mine{spk_manager->IsMine(script)};
123                  if (is_mine == isminetype::ISMINE_SPENDABLE) {
124                      assert(spk_manager->GetScriptPubKeys().count(script));
125                  }
126              },
127              [&] {
128                  auto spks{spk_manager->GetScriptPubKeys()};
129                  for (const CScript& spk : spks) {
130                      assert(spk_manager->IsMine(spk) == ISMINE_SPENDABLE);
131                      CTxDestination dest;
132                      bool extract_dest{ExtractDestination(spk, dest)};
133                      if (extract_dest) {
134                          const std::string msg{fuzzed_data_provider.ConsumeRandomLengthString()};
135                          PKHash pk_hash{std::get_if<PKHash>(&dest) && fuzzed_data_provider.ConsumeBool() ?
136                                             *std::get_if<PKHash>(&dest) :
137                                             PKHash{ConsumeUInt160(fuzzed_data_provider)}};
138                          std::string str_sig;
139                          (void)spk_manager->SignMessage(msg, pk_hash, str_sig);
140                      }
141                  }
142              },
143              [&] {
144                  CKey key{ConsumePrivateKey(fuzzed_data_provider, /*compressed=*/fuzzed_data_provider.ConsumeBool())};
145                  if (!key.IsValid()) {
146                      good_data = false;
147                      return;
148                  }
149                  spk_manager->AddDescriptorKey(key, key.GetPubKey());
150                  spk_manager->TopUp();
151              },
152              [&] {
153                  std::string descriptor;
154                  (void)spk_manager->GetDescriptorString(descriptor, /*priv=*/fuzzed_data_provider.ConsumeBool());
155              },
156              [&] {
157                  LOCK(spk_manager->cs_desc_man);
158                  auto wallet_desc{spk_manager->GetWalletDescriptor()};
159                  if (wallet_desc.descriptor->IsSingleType()) {
160                      auto output_type{wallet_desc.descriptor->GetOutputType()};
161                      if (output_type.has_value()) {
162                          auto dest{spk_manager->GetNewDestination(*output_type)};
163                          if (dest) {
164                              assert(IsValidDestination(*dest));
165                              assert(spk_manager->IsHDEnabled());
166                          }
167                      }
168                  }
169              },
170              [&] {
171                  CMutableTransaction tx_to;
172                  const std::optional<CMutableTransaction> opt_tx_to{ConsumeDeserializable<CMutableTransaction>(fuzzed_data_provider, TX_WITH_WITNESS)};
173                  if (!opt_tx_to) {
174                      good_data = false;
175                      return;
176                  }
177                  tx_to = *opt_tx_to;
178  
179                  std::map<COutPoint, Coin> coins{ConsumeCoins(fuzzed_data_provider)};
180                  const int sighash{fuzzed_data_provider.ConsumeIntegral<int>()};
181                  std::map<int, bilingual_str> input_errors;
182                  (void)spk_manager->SignTransaction(tx_to, coins, sighash, input_errors);
183              },
184              [&] {
185                  std::optional<PartiallySignedTransaction> opt_psbt{ConsumeDeserializable<PartiallySignedTransaction>(fuzzed_data_provider)};
186                  if (!opt_psbt) {
187                      good_data = false;
188                      return;
189                  }
190                  auto psbt{*opt_psbt};
191                  const PrecomputedTransactionData txdata{PrecomputePSBTData(psbt)};
192                  const int sighash_type{fuzzed_data_provider.ConsumeIntegralInRange<int>(0, 150)};
193                  (void)spk_manager->FillPSBT(psbt, txdata, sighash_type, fuzzed_data_provider.ConsumeBool(), fuzzed_data_provider.ConsumeBool(), nullptr, fuzzed_data_provider.ConsumeBool());
194              }
195          );
196      }
197  }
198  
199  } // namespace
200  } // namespace wallet