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