scriptpubkeyman_tests.cpp raw

   1  // Copyright (c) 2020-2021 The Limenka 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 <key.h>
   6  #include <key_io.h>
   7  #include <test/util/setup_common.h>
   8  #include <script/solver.h>
   9  #include <wallet/scriptpubkeyman.h>
  10  #include <wallet/wallet.h>
  11  #include <wallet/test/util.h>
  12  
  13  #include <boost/test/unit_test.hpp>
  14  
  15  namespace wallet {
  16  BOOST_FIXTURE_TEST_SUITE(scriptpubkeyman_tests, BasicTestingSetup)
  17  
  18  // Test LegacyScriptPubKeyMan::CanProvide behavior, making sure it returns true
  19  // for recognized scripts even when keys may not be available for signing.
  20  BOOST_AUTO_TEST_CASE(CanProvide)
  21  {
  22      // Set up wallet and keyman variables.
  23      CWallet wallet(m_node.chain.get(), "", CreateMockableWalletDatabase());
  24      LegacyScriptPubKeyMan& keyman = *wallet.GetOrCreateLegacyScriptPubKeyMan();
  25  
  26      // Make a 1 of 2 multisig script
  27      std::vector<CKey> keys(2);
  28      std::vector<CPubKey> pubkeys;
  29      for (CKey& key : keys) {
  30          key.MakeNewKey(true);
  31          pubkeys.emplace_back(key.GetPubKey());
  32      }
  33      CScript multisig_script = GetScriptForMultisig(1, pubkeys);
  34      CScript p2sh_script = GetScriptForDestination(ScriptHash(multisig_script));
  35      SignatureData data;
  36  
  37      // Verify the p2sh(multisig) script is not recognized until the multisig
  38      // script is added to the keystore to make it solvable
  39      BOOST_CHECK(!keyman.CanProvide(p2sh_script, data));
  40      keyman.AddCScript(multisig_script);
  41      BOOST_CHECK(keyman.CanProvide(p2sh_script, data));
  42  }
  43  
  44  BOOST_AUTO_TEST_CASE(DescriptorScriptPubKeyManTests)
  45  {
  46      std::unique_ptr<interfaces::Chain>& chain = m_node.chain;
  47  
  48      CWallet keystore(chain.get(), "", CreateMockableWalletDatabase());
  49      auto key_scriptpath = GenerateRandomKey();
  50  
  51      // Verify that a SigningProvider for a pubkey is only returned if its corresponding private key is available
  52      auto key_internal = GenerateRandomKey();
  53      std::string desc_str = "tr(" + EncodeSecret(key_internal) + ",pk(" + HexStr(key_scriptpath.GetPubKey()) + "))";
  54      auto spk_man1 = dynamic_cast<DescriptorScriptPubKeyMan*>(CreateDescriptor(keystore, desc_str, true));
  55      BOOST_CHECK(spk_man1 != nullptr);
  56      auto signprov_keypath_spendable = spk_man1->GetSigningProvider(key_internal.GetPubKey());
  57      BOOST_CHECK(signprov_keypath_spendable != nullptr);
  58  
  59      desc_str = "tr(" + HexStr(XOnlyPubKey::NUMS_H) + ",pk(" + HexStr(key_scriptpath.GetPubKey()) + "))";
  60      auto spk_man2 = dynamic_cast<DescriptorScriptPubKeyMan*>(CreateDescriptor(keystore, desc_str, true));
  61      BOOST_CHECK(spk_man2 != nullptr);
  62      auto signprov_keypath_nums_h = spk_man2->GetSigningProvider(XOnlyPubKey::NUMS_H.GetEvenCorrespondingCPubKey());
  63      BOOST_CHECK(signprov_keypath_nums_h == nullptr);
  64  }
  65  
  66  static void legacy_IsKeyActive(const node::NodeContext& node, bool implicit_segwit)
  67  {
  68      const bool save_g_implicit_segwit{g_implicit_segwit};
  69      g_implicit_segwit = implicit_segwit;
  70      CWallet wallet(node.chain.get(), "", CreateMockableWalletDatabase());
  71      {
  72          LOCK(wallet.cs_wallet);
  73          wallet.SetMinVersion(FEATURE_LATEST);
  74          wallet.m_keypool_size = 10;
  75      }
  76      LegacyScriptPubKeyMan& spkm = *wallet.GetOrCreateLegacyScriptPubKeyMan();
  77  
  78      // Start off empty
  79      BOOST_CHECK(spkm.GetScriptPubKeys().empty());
  80  
  81      // Generate 20 keypool keys (10 internal, 10 external)
  82      {
  83          LOCK(wallet.cs_wallet);
  84          spkm.SetupGeneration();
  85      }
  86  
  87      // 4 scripts per keypool key (P2PK, P2PKH, P2WPKH, P2SH-P2WPKH)
  88      // Plus 4 scripts for the seed key
  89      // (If !implicit_segwit, P2WPKH and P2SH-P2WPKH are not generated.)
  90      auto scripts1 = spkm.GetScriptPubKeys();
  91      BOOST_CHECK_EQUAL(scripts1.size(), implicit_segwit ? 84 : 42);
  92  
  93      // All keys are active
  94      for (const CScript& script : scripts1) {
  95          BOOST_CHECK(spkm.IsKeyActive(script));
  96      }
  97  
  98      // Requesting single from spkm should not deactivate key
  99      CTxDestination dest1;
 100      {
 101          LOCK(wallet.cs_wallet);
 102          auto result = spkm.GetNewDestination(OutputType::BECH32);
 103          dest1 = result.value();
 104      }
 105      CScript script = GetScriptForDestination(dest1);
 106      BOOST_CHECK(spkm.IsKeyActive(script));
 107  
 108      // Key pool size did not change
 109      // (If !implicit_segwit, the two segwit addresses are added back.)
 110      auto scripts2 = spkm.GetScriptPubKeys();
 111      BOOST_CHECK_EQUAL(scripts2.size(), implicit_segwit ? 84 : 44);
 112  
 113      // Use key that is not the next key
 114      // (i.e. address gap in wallet recovery)
 115      {
 116          LOCK(wallet.cs_wallet);
 117          LOCK(spkm.cs_KeyStore);
 118          auto keys = spkm.MarkReserveKeysAsUsed(5);
 119          BOOST_CHECK_EQUAL(keys.size(), 4); // Because we already used one with GetNewDestination
 120      }
 121  
 122      // Key pool size did not change
 123      auto scripts3 = spkm.GetScriptPubKeys();
 124      BOOST_CHECK_EQUAL(scripts3.size(), implicit_segwit ? 84 : 44);
 125  
 126      // All keys are still active
 127      for (const CScript& script : scripts3) {
 128          BOOST_CHECK(spkm.IsKeyActive(script));
 129      }
 130  
 131      // When user encrypts wallet for the first time,
 132      // all existing keys are removed from active keypool
 133      {
 134          LOCK(wallet.cs_wallet);
 135          // called by EncryptWallet()
 136          spkm.SetupGeneration(true);
 137      }
 138  
 139      // 20 new keys were added
 140      auto scripts4 = spkm.GetScriptPubKeys();
 141      BOOST_CHECK_EQUAL(scripts4.size(), (implicit_segwit ? 84 : 43) * 2);
 142  
 143      // All 10 original keys are now inactive
 144      for (const CScript& script : scripts3) {
 145          BOOST_CHECK(!spkm.IsKeyActive(script));
 146      }
 147      g_implicit_segwit = save_g_implicit_segwit;
 148  }
 149  
 150  BOOST_AUTO_TEST_CASE(Legacy_IsKeyActive)
 151  {
 152      legacy_IsKeyActive(m_node, /*implicit_segwit=*/true);
 153  }
 154  
 155  BOOST_AUTO_TEST_CASE(Legacy_IsKeyActive_no_implicit_segwit)
 156  {
 157      legacy_IsKeyActive(m_node, /*implicit_segwit=*/false);
 158  }
 159  
 160  BOOST_AUTO_TEST_CASE(Descriptor_IsKeyActive)
 161  {
 162      CWallet wallet(m_node.chain.get(), "", CreateMockableWalletDatabase());
 163      {
 164          LOCK(wallet.cs_wallet);
 165          wallet.LoadMinVersion(FEATURE_LATEST);
 166          wallet.SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
 167          wallet.m_keypool_size = 10;
 168          wallet.SetupDescriptorScriptPubKeyMans();
 169      }
 170      DescriptorScriptPubKeyMan* spkm = dynamic_cast<DescriptorScriptPubKeyMan*>(wallet.GetScriptPubKeyMan(OutputType::BECH32, /*internal=*/false));
 171  
 172      // Start off with 10 pre-generated keys, 1 script each
 173      auto scripts1 = spkm->GetScriptPubKeys();
 174      BOOST_CHECK_EQUAL(scripts1.size(), 10);
 175  
 176      // All keys are active
 177      for (const CScript& script : scripts1) {
 178          BOOST_CHECK(spkm->IsKeyActive(script));
 179      }
 180  
 181      // Requesting single key from spkm should not deactivate key
 182      auto dest1 = spkm->GetNewDestination(OutputType::BECH32);
 183      CScript script = GetScriptForDestination(dest1.value());
 184      BOOST_CHECK(spkm->IsKeyActive(script));
 185  
 186      // Key pool size did not change
 187      auto scripts2 = spkm->GetScriptPubKeys();
 188      BOOST_CHECK_EQUAL(scripts2.size(), 10);
 189  
 190      // Use key that is not the next key
 191      // (i.e. address gap in wallet recovery)
 192      {
 193          LOCK(spkm->cs_desc_man);
 194          WalletDescriptor descriptor = spkm->GetWalletDescriptor();
 195          FlatSigningProvider provider;
 196          std::vector<CScript> scripts3;
 197          descriptor.descriptor->ExpandFromCache(/*pos=*/5, descriptor.cache, scripts3, provider);
 198  
 199          BOOST_CHECK_EQUAL(scripts3.size(), 1);
 200          spkm->MarkUnusedAddresses(scripts3.front());
 201      }
 202  
 203      // Key pool size increased to replace used keys
 204      auto scripts4 = spkm->GetScriptPubKeys();
 205      BOOST_CHECK_EQUAL(scripts4.size(), 16);
 206  
 207      // All keys are still active
 208      for (const CScript& script : scripts4) {
 209          BOOST_CHECK(spkm->IsKeyActive(script));
 210      }
 211  
 212      // When user encrypts wallet for the first time,
 213      // all existing keys are removed from active keypool
 214      {
 215          LOCK(wallet.cs_wallet);
 216          // called by EncryptWallet()
 217          wallet.SetupDescriptorScriptPubKeyMans();
 218      }
 219  
 220      // This SPKM is not affected
 221      for (const CScript& script : scripts4) {
 222          BOOST_CHECK(spkm->IsKeyActive(script));
 223      }
 224  
 225      // ...but at the wallet level all the keys from that SPKM are deactivated
 226      int num_script_keys_not_found = 0;
 227      for (const CScript& script : scripts4) {
 228          if (!wallet.IsDestinationActive(WitnessV0ScriptHash(script))) {
 229              ++num_script_keys_not_found;
 230          }
 231      }
 232      BOOST_CHECK_EQUAL(num_script_keys_not_found, 16);
 233  }
 234  
 235  BOOST_AUTO_TEST_SUITE_END()
 236  } // namespace wallet
 237