wallet_gethdkeys.py raw

   1  #!/usr/bin/env python3
   2  # Copyright (c) 2023 The Limenka developers
   3  # Distributed under the MIT software license, see the accompanying
   4  # file COPYING or http://www.opensource.org/licenses/mit-license.php.
   5  """Test wallet gethdkeys RPC."""
   6  
   7  from test_framework.descriptors import descsum_create
   8  from test_framework.test_framework import LimenkaTestFramework
   9  from test_framework.util import (
  10      assert_equal,
  11      assert_raises_rpc_error,
  12  )
  13  from test_framework.wallet_util import WalletUnlock
  14  
  15  
  16  class WalletGetHDKeyTest(LimenkaTestFramework):
  17      def add_options(self, parser):
  18          self.add_wallet_options(parser, descriptors=True, legacy=False)
  19  
  20      def set_test_params(self):
  21          self.setup_clean_chain = True
  22          self.num_nodes = 1
  23  
  24      def skip_test_if_missing_module(self):
  25          self.skip_if_no_wallet()
  26  
  27      def run_test(self):
  28          self.test_basic_gethdkeys()
  29          self.test_ranged_imports()
  30          self.test_lone_key_imports()
  31          self.test_ranged_multisig()
  32          self.test_mixed_multisig()
  33  
  34      def test_basic_gethdkeys(self):
  35          self.log.info("Test gethdkeys basics")
  36          self.nodes[0].createwallet("basic")
  37          wallet = self.nodes[0].get_wallet_rpc("basic")
  38          xpub_info = wallet.gethdkeys()
  39          assert_equal(len(xpub_info), 1)
  40          assert_equal(xpub_info[0]["has_private"], True)
  41  
  42          assert "xprv" not in xpub_info[0]
  43          xpub = xpub_info[0]["xpub"]
  44  
  45          xpub_info = wallet.gethdkeys(private=True)
  46          xprv = xpub_info[0]["xprv"]
  47          assert_equal(xpub_info[0]["xpub"], xpub)
  48          assert_equal(xpub_info[0]["has_private"], True)
  49  
  50          descs = wallet.listdescriptors(True)
  51          for desc in descs["descriptors"]:
  52              assert xprv in desc["desc"]
  53  
  54          self.log.info("HD pubkey can be retrieved from encrypted wallets")
  55          prev_xprv = xprv
  56          wallet.encryptwallet("pass")
  57          # HD key is rotated on encryption, there should now be 2 HD keys
  58          assert_equal(len(wallet.gethdkeys()), 2)
  59          # New key is active, should be able to get only that one and its descriptors
  60          xpub_info = wallet.gethdkeys(active_only=True)
  61          assert_equal(len(xpub_info), 1)
  62          assert xpub_info[0]["xpub"] != xpub
  63          assert "xprv" not in xpub_info[0]
  64          assert_equal(xpub_info[0]["has_private"], True)
  65  
  66          self.log.info("HD privkey can be retrieved from encrypted wallets")
  67          assert_raises_rpc_error(-13, "Error: Please enter the wallet passphrase with walletpassphrase first", wallet.gethdkeys, private=True)
  68          with WalletUnlock(wallet, "pass"):
  69              xpub_info = wallet.gethdkeys(active_only=True, private=True)[0]
  70              assert xpub_info["xprv"] != xprv
  71              for desc in wallet.listdescriptors(True)["descriptors"]:
  72                  if desc["active"]:
  73                      # After encrypting, HD key was rotated and should appear in all active descriptors
  74                      assert xpub_info["xprv"] in desc["desc"]
  75                  else:
  76                      # Inactive descriptors should have the previous HD key
  77                      assert prev_xprv in desc["desc"]
  78  
  79      def test_ranged_imports(self):
  80          self.log.info("Keys of imported ranged descriptors appear in gethdkeys")
  81          def_wallet = self.nodes[0].get_wallet_rpc(self.default_wallet_name)
  82          self.nodes[0].createwallet("imports")
  83          wallet = self.nodes[0].get_wallet_rpc("imports")
  84  
  85          xpub_info = wallet.gethdkeys()
  86          assert_equal(len(xpub_info), 1)
  87          active_xpub = xpub_info[0]["xpub"]
  88  
  89          import_xpub = def_wallet.gethdkeys(active_only=True)[0]["xpub"]
  90          desc_import = def_wallet.listdescriptors(True)["descriptors"]
  91          for desc in desc_import:
  92              desc["active"] = False
  93          wallet.importdescriptors(desc_import)
  94          assert_equal(wallet.gethdkeys(active_only=True), xpub_info)
  95  
  96          xpub_info = wallet.gethdkeys()
  97          assert_equal(len(xpub_info), 2)
  98          for x in xpub_info:
  99              if x["xpub"] == active_xpub:
 100                  for desc in x["descriptors"]:
 101                      assert_equal(desc["active"], True)
 102              elif x["xpub"] == import_xpub:
 103                  for desc in x["descriptors"]:
 104                      assert_equal(desc["active"], False)
 105              else:
 106                  assert False
 107  
 108  
 109      def test_lone_key_imports(self):
 110          self.log.info("Non-HD keys do not appear in gethdkeys")
 111          self.nodes[0].createwallet("lonekey", blank=True)
 112          wallet = self.nodes[0].get_wallet_rpc("lonekey")
 113  
 114          assert_equal(wallet.gethdkeys(), [])
 115          wallet.importdescriptors([{"desc": descsum_create("wpkh(cTe1f5rdT8A8DFgVWTjyPwACsDPJM9ff4QngFxUixCSvvbg1x6sh)"), "timestamp": "now"}])
 116          assert_equal(wallet.gethdkeys(), [])
 117  
 118          self.log.info("HD keys of non-ranged descriptors should appear in gethdkeys")
 119          def_wallet = self.nodes[0].get_wallet_rpc(self.default_wallet_name)
 120          xpub_info = def_wallet.gethdkeys(private=True)
 121          xpub = xpub_info[0]["xpub"]
 122          xprv = xpub_info[0]["xprv"]
 123          prv_desc = descsum_create(f"wpkh({xprv})")
 124          pub_desc = descsum_create(f"wpkh({xpub})")
 125          assert_equal(wallet.importdescriptors([{"desc": prv_desc, "timestamp": "now"}])[0]["success"], True)
 126          xpub_info = wallet.gethdkeys()
 127          assert_equal(len(xpub_info), 1)
 128          assert_equal(xpub_info[0]["xpub"], xpub)
 129          assert_equal(len(xpub_info[0]["descriptors"]), 1)
 130          assert_equal(xpub_info[0]["descriptors"][0]["desc"], pub_desc)
 131          assert_equal(xpub_info[0]["descriptors"][0]["active"], False)
 132  
 133      def test_ranged_multisig(self):
 134          self.log.info("HD keys of a multisig appear in gethdkeys")
 135          def_wallet = self.nodes[0].get_wallet_rpc(self.default_wallet_name)
 136          self.nodes[0].createwallet("ranged_multisig")
 137          wallet = self.nodes[0].get_wallet_rpc("ranged_multisig")
 138  
 139          xpub1 = wallet.gethdkeys()[0]["xpub"]
 140          xprv1 = wallet.gethdkeys(private=True)[0]["xprv"]
 141          xpub2 = def_wallet.gethdkeys()[0]["xpub"]
 142  
 143          prv_multi_desc = descsum_create(f"wsh(multi(2,{xprv1}/*,{xpub2}/*))")
 144          pub_multi_desc = descsum_create(f"wsh(multi(2,{xpub1}/*,{xpub2}/*))")
 145          assert_equal(wallet.importdescriptors([{"desc": prv_multi_desc, "timestamp": "now"}])[0]["success"], True)
 146  
 147          xpub_info = wallet.gethdkeys()
 148          assert_equal(len(xpub_info), 2)
 149          for x in xpub_info:
 150              if x["xpub"] == xpub1:
 151                  found_desc = next((d for d in xpub_info[0]["descriptors"] if d["desc"] == pub_multi_desc), None)
 152                  assert found_desc is not None
 153                  assert_equal(found_desc["active"], False)
 154              elif x["xpub"] == xpub2:
 155                  assert_equal(len(x["descriptors"]), 1)
 156                  assert_equal(x["descriptors"][0]["desc"], pub_multi_desc)
 157                  assert_equal(x["descriptors"][0]["active"], False)
 158              else:
 159                  assert False
 160  
 161      def test_mixed_multisig(self):
 162          self.log.info("Non-HD keys of a multisig do not appear in gethdkeys")
 163          def_wallet = self.nodes[0].get_wallet_rpc(self.default_wallet_name)
 164          self.nodes[0].createwallet("single_multisig")
 165          wallet = self.nodes[0].get_wallet_rpc("single_multisig")
 166  
 167          xpub = wallet.gethdkeys()[0]["xpub"]
 168          xprv = wallet.gethdkeys(private=True)[0]["xprv"]
 169          pub = def_wallet.getaddressinfo(def_wallet.getnewaddress())["pubkey"]
 170  
 171          prv_multi_desc = descsum_create(f"wsh(multi(2,{xprv},{pub}))")
 172          pub_multi_desc = descsum_create(f"wsh(multi(2,{xpub},{pub}))")
 173          import_res = wallet.importdescriptors([{"desc": prv_multi_desc, "timestamp": "now"}])
 174          assert_equal(import_res[0]["success"], True)
 175  
 176          xpub_info = wallet.gethdkeys()
 177          assert_equal(len(xpub_info), 1)
 178          assert_equal(xpub_info[0]["xpub"], xpub)
 179          found_desc = next((d for d in xpub_info[0]["descriptors"] if d["desc"] == pub_multi_desc), None)
 180          assert found_desc is not None
 181          assert_equal(found_desc["active"], False)
 182  
 183  
 184  if __name__ == '__main__':
 185      WalletGetHDKeyTest(__file__).main()
 186