wallet_importseed.py raw

   1  #!/usr/bin/env python3
   2  # Copyright (c) 2013 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 the 'seeds' argument to the importdescriptors RPC
   6  
   7  Test importingi seeds by using the BIP 93 test vectors to verify that imported
   8  seeds are compatible with descriptors containing the corresponding xpubs, that
   9  the wallet is able to recognize and send funds, and that the wallet can derive
  10  addresses, when given only seeds as private data."""
  11  
  12  import time
  13  
  14  from test_framework.test_framework import LimenkaTestFramework
  15  from test_framework.util import (
  16      assert_raises_rpc_error,
  17  )
  18  
  19  
  20  class ImportDescriptorsTest(LimenkaTestFramework):
  21      def add_options(self, parser):
  22          self.add_wallet_options(parser, legacy=False)
  23  
  24      def set_test_params(self):
  25          self.num_nodes = 1
  26          self.setup_clean_chain = True
  27          self.wallet_names = []
  28  
  29      def skip_test_if_missing_module(self):
  30          self.skip_if_no_wallet()
  31  
  32      def run_test(self):
  33          test_start = int(time.time())
  34  
  35          # Spend/receive tests
  36          self.nodes[0].createwallet(wallet_name='w0', descriptors=True)
  37          self.nodes[0].createwallet(wallet_name='w1', descriptors=True, blank=True)
  38          w0 = self.nodes[0].get_wallet_rpc('w0')
  39          w1 = self.nodes[0].get_wallet_rpc('w1')
  40  
  41          self.generatetoaddress(self.nodes[0], 2, w0.getnewaddress())
  42          self.generate(self.nodes[0], 100)
  43  
  44          # Test 1: send coins to wallet, check they are not received, then import
  45          #         the descriptor and make sure they are recognized. Send them
  46          #         back and repeat. Uses single codex32 seed.
  47          #
  48          # xpub converted from BIP 93 test vector 1 xpriv using rust-limenka
  49          xpub = "tpubD6NzVbkrYhZ4YAqhvsGTCD5axU32P9MH7ySPr38icriLyJc4KcCvwVzE3rsi" \
  50                 "XaAHBC8QtYWhiBGdc6aZRmroQShGcWygQfErbvLULfJSi8j"
  51          descriptors = [
  52              f"wsh(pk({xpub}/55/*))",
  53              f"tr({xpub}/1/2/3/4/5/*)",
  54              f"pkh({xpub}/*)",
  55              f"wpkh({xpub}/*)",
  56              f"rawtr({xpub}/1/2/3/*)",
  57          ]
  58          assert_raises_rpc_error(-4, "This wallet has no available keys", w1.getnewaddress)
  59          for descriptor in descriptors:
  60              descriptor_chk = w0.getdescriptorinfo(descriptor)["descriptor"]
  61              addr = w0.deriveaddresses(descriptor_chk, range=[0, 20])[0]
  62  
  63              assert w0.getbalance() > 99  # sloppy balance checks, to account for fees
  64              w0.sendtoaddress(addr, 95)
  65              self.generate(self.nodes[0], 1)
  66              assert w0.getbalance() < 5
  67  
  68              w1.importdescriptors(
  69                  [{"desc": descriptor_chk, "timestamp": test_start, "range": 0, "active": True}],
  70                  [["ms10testsxxxxxxxxxxxxxxxxxxxxxxxxxx4nzvca9cmczlw"]],
  71              )
  72  
  73              assert w1.getbalance() > 94
  74              w1.sendtoaddress(w0.getnewaddress(), 95, "", "", True)
  75              self.generate(self.nodes[0], 1)
  76              assert w0.getbalance() > 99
  77          w1.getnewaddress()  # no failure now
  78  
  79          # Test 2: deriveaddresses on hardened keys fails before import, succeeds after.
  80          #         Uses single codex32 seed in 2 shares.
  81          #
  82          # xpub converted from BIP 93 test vector 2 xpriv using rust-limenka
  83          self.nodes[0].createwallet(wallet_name='w2', descriptors=True, blank=True)
  84          w2 = self.nodes[0].get_wallet_rpc('w2')
  85  
  86          xpub = "tpubD6NzVbkrYhZ4Wf289qp46iFM6zACTdXTqqrA3pKUV8bF8SNBcYS8xvVPZg43" \
  87                 "6YhSuCqTKLfnDkmwi9TE6fa5cvxm3NHRCBbgJoC6YgsQBFY"
  88          descriptor = f"tr([fab6868a/1h/2]{xpub}/1h/2/*h)"
  89          descriptor_chk = w2.getdescriptorinfo(descriptor)["descriptor"]
  90          assert_raises_rpc_error(
  91              -4,
  92              "This wallet has no available keys",
  93              w2.getnewaddress,
  94              address_type="bech32m",
  95          )
  96  
  97          # Try importing descriptor with wrong seed
  98          err = w2.importdescriptors(
  99              [{"desc": descriptor_chk, "timestamp": test_start, "active": True, "range": [0, 20]}],
 100              [["ms10testsxxxxxxxxxxxxxxxxxxxxxxxxxx4nzvca9cmczlw"]],
 101          )
 102          assert "Cannot expand descriptor." in err[0]["error"]["message"]
 103          assert_raises_rpc_error(
 104              -4,
 105              "This wallet has no available keys",
 106              w2.getnewaddress,
 107              address_type="bech32m",
 108          )
 109  
 110          # Try various failure cases
 111          assert_raises_rpc_error(
 112              -5,
 113              "single share must be the S share",
 114              w2.importdescriptors,
 115              [{"desc": descriptor_chk, "timestamp": test_start, "active": True, "range": [0, 20]}],
 116              [["MS12NAMEA320ZYXWVUTSRQPNMLKJHGFEDCAXRPP870HKKQRM"]],
 117          )
 118  
 119          assert_raises_rpc_error(
 120              -5,
 121              "two input shares had the same index",
 122              w2.importdescriptors,
 123              [{"desc": descriptor_chk, "timestamp": test_start, "active": True, "range": [0, 20]}],
 124              [[
 125                  "MS12NAMEA320ZYXWVUTSRQPNMLKJHGFEDCAXRPP870HKKQRM",
 126                  "MS12NAMEA320ZYXWVUTSRQPNMLKJHGFEDCAXRPP870HKKQRM",
 127              ]],
 128          )
 129  
 130          assert_raises_rpc_error(
 131              -5,
 132              "input shares had inconsistent seed IDs",
 133              w2.importdescriptors,
 134              [{"desc": descriptor_chk, "timestamp": test_start, "active": True, "range": [0, 20]}],
 135              [[
 136                  "MS12NAMEA320ZYXWVUTSRQPNMLKJHGFEDCAXRPP870HKKQRM",
 137                  "ms13cashcacdefghjklmnpqrstuvwxyz023949xq35my48dr",
 138              ]],
 139          )
 140  
 141          # Do it correctly
 142          w2.importdescriptors(
 143              [{"desc": descriptor_chk, "timestamp": test_start, "active": True, "range": [0, 20]}],
 144              [[
 145                  "MS12NAMEA320ZYXWVUTSRQPNMLKJHGFEDCAXRPP870HKKQRM",
 146                  "MS12NAMECACDEFGHJKLMNPQRSTUVWXYZ023FTR2GDZMPY6PN",
 147              ]],
 148          )
 149          # getnewaddress no longer fails. Annoyingl, deriveaddresses will
 150          w2.getnewaddress(address_type="bech32m")
 151          assert_raises_rpc_error(
 152              -5,
 153              "Cannot derive script without private keys",
 154              w2.deriveaddresses,
 155              descriptor_chk,
 156              0,
 157          )
 158          # Do it again, to see if nothing breaks
 159          w2.importdescriptors(
 160              [{"desc": descriptor_chk, "timestamp": test_start, "active": True, "range": [0, 20]}],
 161              [[
 162                  "MS12NAMEA320ZYXWVUTSRQPNMLKJHGFEDCAXRPP870HKKQRM",
 163                  "MS12NAMECACDEFGHJKLMNPQRSTUVWXYZ023FTR2GDZMPY6PN",
 164              ]],
 165          )
 166  
 167          # Test 3: multiple seeds, multiple descriptors
 168          #
 169          # xpubs converted from BIP 93 test vector 3, 4 and 5 xprivs using rust-limenka
 170  
 171          self.nodes[0].createwallet(wallet_name='w3', descriptors=True, blank=True)
 172          w3 = self.nodes[0].get_wallet_rpc('w3')
 173          xpub1 = "tpubD6NzVbkrYhZ4WNNA2qNKYbaxKR3TYtP2n5bNSj6JKzYsVUPxahe2vWJKwiX2" \
 174                  "wfoTJyERQNJ8YnmJvprMHygyaXziTdyFVsSGNmfQtDCCSJ3"  # vector 3
 175          xpub2 = "tpubD6NzVbkrYhZ4Y9KL2R346X9ZwcN16c37vjXuZEhDV2LaMt84zqVbKVbVAw1z" \
 176                  "nMksNtdKnSRZQXyBL9qJaNnq9BkjtRBdsQbxkTbSGZGrcG6"  # vector 4
 177          xpub3 = "tpubD6NzVbkrYhZ4Ykomd4u92cmRCkhZtctLkKU3vCVi7DKBAopRDWVpq6wEGoq7" \
 178                  "xYbCQQjEGM8KkqxvQDoLa3sdfpzTBv1yodq4FKwrCdxweHE"  # vector 5
 179  
 180          descriptor1 = f"rawtr({xpub1}/1/2h/*)"
 181          descriptor1_chk = w3.getdescriptorinfo(descriptor1)["descriptor"]
 182          descriptor2 = f"wpkh({xpub2}/1h/2/*)"
 183          descriptor2_chk = w3.getdescriptorinfo(descriptor2)["descriptor"]
 184          descriptor3 = f"pkh({xpub3}/1h/2/3/4/5/6/7/8/9/10/*)"
 185          descriptor3_chk = w3.getdescriptorinfo(descriptor3)["descriptor"]
 186  
 187          assert_raises_rpc_error(
 188              -4,
 189              "This wallet has no available keys",
 190              w3.getnewaddress,
 191              address_type="bech32m",
 192          )
 193          assert_raises_rpc_error(
 194              -4,
 195              "This wallet has no available keys",
 196              w3.getnewaddress,
 197              address_type="bech32",
 198          )
 199          assert_raises_rpc_error(
 200              -4,
 201              "This wallet has no available keys",
 202              w3.getnewaddress,
 203              address_type="legacy",
 204          )
 205  
 206          # First try without enough input shares.
 207          assert_raises_rpc_error(
 208              -5,
 209              "did not have enough input shares",
 210              w3.importdescriptors,
 211              [
 212                  {"desc": descriptor1_chk, "timestamp": test_start, "active": True, "range": 10},
 213                  {"desc": descriptor2_chk, "timestamp": test_start, "active": True, "range": 15},
 214              ],
 215              [[
 216                  "ms13casheekgpemxzshcrmqhaydlp6yhms3ws7320xyxsar9",
 217                  "ms13cashf8jh6sdrkpyrsp5ut94pj8ktehhw2hfvyrj48704",
 218              ], [
 219                  "ms10leetsllhdmn9m42vcsamx24zrxgs3qrl7ahwvhw4fnzrhve25gvezzyqqtum9pgv99ycma",
 220              ]],
 221          )
 222          # Wallet still doesn't work, even the descriptor whose seed was correctly specified
 223          assert_raises_rpc_error(
 224              -4,
 225              "This wallet has no available keys",
 226              w3.getnewaddress,
 227              address_type="bech32",
 228          )
 229  
 230          # Do it properly
 231          w3.importdescriptors(
 232              [
 233                  {"desc": descriptor1_chk, "timestamp": test_start, "active": True, "range": 10},
 234                  {"desc": descriptor2_chk, "timestamp": test_start, "active": True, "range": 15},
 235                  {"desc": descriptor3_chk, "timestamp": test_start, "active": True, "range": 15},
 236              ],
 237              [[
 238                  "ms13cashd0wsedstcdcts64cd7wvy4m90lm28w4ffupqs7rm",
 239                  "ms13casheekgpemxzshcrmqhaydlp6yhms3ws7320xyxsar9",
 240                  "ms13cashf8jh6sdrkpyrsp5ut94pj8ktehhw2hfvyrj48704",
 241              ], [
 242                  "ms10leetsllhdmn9m42vcsamx24zrxgs3qrl7ahwvhw4fnzrhve25gvezzyqqtum9pgv99ycma",
 243              ]],
 244          )
 245          # All good now for the two descriptors that had seeds
 246          w3.getnewaddress(address_type="bech32")
 247          w3.getnewaddress(address_type="bech32m")
 248          # but the one without a seed still doesn't work
 249          assert_raises_rpc_error(
 250              -12,
 251              "No legacy addresses available",
 252              w3.getnewaddress,
 253              address_type="legacy",
 254          )
 255  
 256          # Ok, try to import the legacy one separately.
 257          w3.importdescriptors(
 258              [{"desc": descriptor3_chk, "timestamp": test_start, "active": True, "range": 15}],
 259              [["MS100C8VSM32ZXFGUHPCHTLUPZRY9X8GF2TVDW0S3JN54KHCE6MUA7LQPZYGSFJD"  # concat string
 260                "6AN074RXVCEMLH8WU3TK925ACDEFGHJKLMNPQRSTUVWXY06FHPV80UNDVARHRAK"]],
 261          )
 262          # And all is well!
 263          w3.getnewaddress(address_type="bech32")
 264          w3.getnewaddress(address_type="bech32m")
 265          w3.getnewaddress(address_type="legacy")
 266  
 267  
 268  if __name__ == '__main__':
 269      ImportDescriptorsTest(__file__).main()
 270