wallet_taproot.py
1 #!/usr/bin/env python3 2 # Copyright (c) 2021-2022 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 generation and spending of P2TR addresses.""" 6 7 import random 8 import uuid 9 10 from decimal import Decimal 11 from test_framework.address import output_key_to_p2tr 12 from test_framework.key import H_POINT 13 from test_framework.test_framework import BitcoinTestFramework 14 from test_framework.util import assert_equal 15 from test_framework.descriptors import descsum_create 16 from test_framework.script import ( 17 CScript, 18 MAX_PUBKEYS_PER_MULTI_A, 19 OP_CHECKSIG, 20 OP_CHECKSIGADD, 21 OP_NUMEQUAL, 22 taproot_construct, 23 ) 24 from test_framework.segwit_addr import encode_segwit_address 25 26 # xprvs/xpubs, and m/* derived x-only pubkeys (created using independent implementation) 27 KEYS = [ 28 { 29 "xprv": "tprv8ZgxMBicQKsPeNLUGrbv3b7qhUk1LQJZAGMuk9gVuKh9sd4BWGp1eMsehUni6qGb8bjkdwBxCbgNGdh2bYGACK5C5dRTaif9KBKGVnSezxV", 30 "xpub": "tpubD6NzVbkrYhZ4XqNGAWGWSzmxGWFwVjVTjZxh2fioKbVYi7Jx8fdbprVWsdW7mHwqjchBVas8TLZG4Xwuz4RKU4iaCqiCvoSkFCzQptqk5Y1", 31 "pubs": [ 32 "83d8ee77a0f3a32a5cea96fd1624d623b836c1e5d1ac2dcde46814b619320c18", 33 "a30253b018ea6fca966135bf7dd8026915427f24ccf10d4e03f7870f4128569b", 34 "a61e5749f2f3db9dc871d7b187e30bfd3297eea2557e9be99897ea8ff7a29a21", 35 "8110cf482f66dc37125e619d73075af932521724ffc7108309e88f361efe8c8a", 36 ] 37 }, 38 { 39 "xprv": "tprv8ZgxMBicQKsPe98QUPieXy5KFPVjuZNpcC9JY7K7buJEm8nWvJogK4kTda7eLjK9U4PnMNbSjEkpjDJazeBZ4rhYNYD7N6GEdaysj1AYSb5", 40 "xpub": "tpubD6NzVbkrYhZ4XcACN3PEwNjRpR1g4tZjBVk5pdMR2B6dbd3HYhdGVZNKofAiFZd9okBserZvv58A6tBX4pE64UpXGNTSesfUW7PpW36HuKz", 41 "pubs": [ 42 "f95886b02a84928c5c15bdca32784993105f73de27fa6ad8c1a60389b999267c", 43 "71522134160685eb779857033bfc84c7626f13556154653a51dd42619064e679", 44 "48957b4158b2c5c3f4c000f51fd2cf0fd5ff8868ebfb194256f5e9131fc74bd8", 45 "086dda8139b3a84944010648d2b674b70447be3ae59322c09a4907bc80be62c1", 46 ] 47 }, 48 { 49 "xprv": "tprv8ZgxMBicQKsPe3ZJmcj9aJ2EPZJYYCh6Lp3v82p75wspgaXmtDZ2RBtkAtWcGnW2VQDzMHQPBkCKMoYTqh1RfJKjv4PcmWVR7KqTpjsdboN", 50 "xpub": "tpubD6NzVbkrYhZ4XWb6fGPjyhgLxapUhXszv7ehQYrQWDgDX4nYWcNcbgWcM2RhYo9s2mbZcfZJ8t5LzYcr24FK79zVybsw5Qj3Rtqug8jpJMy", 51 "pubs": [ 52 "9fa5ffb68821cf559001caa0577eeea4978b29416def328a707b15e91701a2f7", 53 "8a104c54cd34acba60c97dd8f1f7abc89ba9587afd88dc928e91aca7b1c50d20", 54 "13ba6b252a4eb5ef31d39cb521724cdab19a698323f5c17093f28fb1821d052f", 55 "f6c2b4863fd5ba1ba09e3a890caed8b75ffbe013ebab31a06ab87cd6f72506af", 56 ] 57 }, 58 { 59 "xprv": "tprv8ZgxMBicQKsPdKziibn63Rm6aNzp7dSjDnufZMStXr71Huz7iihCRpbZZZ6Voy5HyuHCWx6foHMipzMzUq4tZrtkZ24DJwz5EeNWdsuwX5h", 60 "xpub": "tpubD6NzVbkrYhZ4Wo2WcFSgSqRD9QWkGxddo6WSqsVBx7uQ8QEtM7WncKDRjhFEexK119NigyCsFygA4b7sAPQxqebyFGAZ9XVV1BtcgNzbCRR", 61 "pubs": [ 62 "03a669ea926f381582ec4a000b9472ba8a17347f5fb159eddd4a07036a6718eb", 63 "bbf56b14b119bccafb686adec2e3d2a6b51b1626213590c3afa815d1fd36f85d", 64 "2994519e31bbc238a07d82f85c9832b831705d2ee4a2dbb477ecec8a3f570fe5", 65 "68991b5c139a4c479f8c89d6254d288c533aefc0c5b91fac6c89019c4de64988", 66 ] 67 }, 68 { 69 "xprv": "tprv8ZgxMBicQKsPen4PGtDwURYnCtVMDejyE8vVwMGhQWfVqB2FBPdekhTacDW4vmsKTsgC1wsncVqXiZdX2YFGAnKoLXYf42M78fQJFzuDYFN", 70 "xpub": "tpubD6NzVbkrYhZ4YF6BAXtXsqCtmv1HNyvsoSXHDsJzpnTtffH1onTEwC5SnLzCHPKPebh2i7Gxvi9kJNADcpuSmH8oM3rCYcHVtdXHjpYoKnX", 71 "pubs": [ 72 "aba457d16a8d59151c387f24d1eb887efbe24644c1ee64b261282e7baebdb247", 73 "c8558b7caf198e892032d91f1a48ee9bdc25462b83b4d0ac62bb7fb2a0df630e", 74 "8a4bcaba0e970685858d133a4d0079c8b55bbc755599e212285691eb779ce3dc", 75 "b0d68ada13e0d954b3921b88160d4453e9c151131c2b7c724e08f538a666ceb3", 76 ] 77 }, 78 { 79 "xprv": "tprv8ZgxMBicQKsPd91vCgRmbzA13wyip2RimYeVEkAyZvsEN5pUSB3T43SEBxPsytkxb42d64W2EiRE9CewpJQkzR8HKHLV8Uhk4dMF5yRPaTv", 80 "xpub": "tpubD6NzVbkrYhZ4Wc3i6L6N1Pp7cyVeyMcdLrFGXGDGzCfdCa5F4Zs3EY46N72Ws8QDEUYBVwXfDfda2UKSseSdU1fsBegJBhGCZyxkf28bkQ6", 81 "pubs": [ 82 "9b4d495b74887815a1ff623c055c6eac6b6b2e07d2a016d6526ebac71dd99744", 83 "8e971b781b7ce7ab742d80278f2dfe7dd330f3efd6d00047f4a2071f2e7553cb", 84 "b811d66739b9f07435ccda907ec5cd225355321c35e0a7c7791232f24cf10632", 85 "4cd27a5552c272bc80ba544e9cc6340bb906969f5e7a1510b6cef9592683fbc9", 86 ] 87 }, 88 { 89 "xprv": "tprv8ZgxMBicQKsPdEhLRxxwzTv2t18j7ruoffPeqAwVA2qXJ2P66RaMZLUWQ85SjoA7xPxdSgCB9UZ72m65qbnaLPtFTfHVP3MEmkpZk1Bv8RT", 90 "xpub": "tpubD6NzVbkrYhZ4Whj8KcdYPsa9T2efHC6iExzS7gynaJdv8WdripPwjq6NaH5gQJGrLmvUwHY1smhiakUosXNDTEa6qfKUQdLKV6DJBre6XvQ", 91 "pubs": [ 92 "d0c19def28bb1b39451c1a814737615983967780d223b79969ba692182c6006b", 93 "cb1d1b1dc62fec1894d4c3d9a1b6738e5ff9c273a64f74e9ab363095f45e9c47", 94 "245be588f41acfaeb9481aa132717db56ee1e23eb289729fe2b8bde8f9a00830", 95 "5bc4ad6d6187fa82728c85a073b428483295288f8aef5722e47305b5872f7169", 96 ] 97 }, 98 { 99 "xprv": "tprv8ZgxMBicQKsPcxbqxzcMAwQpiCD8x6qaZEJTxdKxw4w9GuMzDACTD9yhEsHGfqQcfYX4LivosLDDngTykYEp9JnTdcqY7cHqU8PpeFFKyV3", 100 "xpub": "tpubD6NzVbkrYhZ4WRddreGwaM4wHDj57S2V8XuFF9NGMLjY7PckqZ23PebZR1wGA4w84uX2vZphdZVsnREjij1ibYjEBTaTVQCEZCLs4xUDapx", 101 "pubs": [ 102 "065cc1b92bd99e5a3e626e8296a366b2d132688eb43aea19bc14fd8f43bf07fb", 103 "5b95633a7dda34578b6985e6bfd85d83ec38b7ded892a9b74a3d899c85890562", 104 "dc86d434b9a34495c8e845b969d51f80d19a8df03b400353ffe8036a0c22eb60", 105 "06c8ffde238745b29ae8a97ae533e1f3edf214bba6ec58b5e7b9451d1d61ec19", 106 ] 107 }, 108 { 109 "xprv": "tprv8ZgxMBicQKsPe6zLoU8MTTXgsdJVNBErrYGpoGwHf5VGvwUzdNc7NHeCSzkJkniCxBhZWujXjmD4HZmBBrnr3URgJjM6GxRgMmEhLdqNTWG", 110 "xpub": "tpubD6NzVbkrYhZ4Xa28h7nwrsBoSepRXWRmRqsc5nyb5MHfmRjmFmRhYnG4d9dC7uxixN5AfsEv1Lz3mCAuWvERyvPgKozHUVjfo8EG6foJGy7", 111 "pubs": [ 112 "d826a0a53abb6ffc60df25b9c152870578faef4b2eb5a09bdd672bbe32cdd79b", 113 "939365e0359ff6bc6f6404ee220714c5d4a0d1e36838b9e2081ede217674e2ba", 114 "4e8767edcf7d3d90258cfbbea01b784f4d2de813c4277b51279cf808bac410a2", 115 "d42a2c280940bfc6ede971ae72cde2e1df96c6da7dab06a132900c6751ade208", 116 ] 117 }, 118 { 119 "xprv": "tprv8ZgxMBicQKsPeB5o5oCsN2dVxM2mtJiYERQEBRc4JNwC1DFGYaEdNkmh8jJYVPU76YhkFoRoWTdh1p3yQGykG8TfDW34dKgrgSx28gswUyL", 120 "xpub": "tpubD6NzVbkrYhZ4Xe7aySsTmSHcXNYi3duSoj11TweMiejaqhW3Ay4DZFPZJses4sfpk4b9VHRhn8v4cKTMjugMM3hqXcqSSmRdiW8QvASXjfY", 121 "pubs": [ 122 "e360564b2e0e8d06681b6336a29d0750210e8f34afd9afb5e6fd5fe6dba26c81", 123 "76b4900f00a1dcce463b6d8e02b768518fce4f9ecd6679a13ad78ea1e4815ad3", 124 "5575556e263c8ed52e99ab02147cc05a738869afe0039911b5a60a780f4e43d2", 125 "593b00e2c8d4bd6dda0fd9e238888acf427bb4e128887fd5a40e0e9da78cbc01", 126 ] 127 }, 128 { 129 "xprv": "tprv8ZgxMBicQKsPfEH6jHemkGDjZRnAaKFJVGH8pQU638E6SdbX9hxit1tK2sfFPfL6KS7v8FfUKxstbfEpzSymbdfBM9Y5UkrxErF9fJaKLK3", 130 "xpub": "tpubD6NzVbkrYhZ4YhJtcwKN9fsr8TJ6jeSD4Zsv6vWPTQ2VH7rHn6nK4WWBCzKK7FkdVVwm3iztCU1UmStY4hX6gRbBmp9UzK9C59dQEzeXS12", 131 "pubs": [ 132 "7631cacec3343052d87ef4d0065f61dde82d7d2db0c1cc02ef61ef3c982ea763", 133 "c05e44a9e735d1b1bef62e2c0d886e6fb4923b2649b67828290f5cacc51c71b7", 134 "b33198b20701afe933226c92fd0e3d51d3f266f1113d864dbd026ae3166ef7f2", 135 "f99643ac3f4072ee4a949301e86963a9ca0ad57f2ef29f6b84fda037d7cac85b", 136 ] 137 }, 138 { 139 "xprv": "tprv8ZgxMBicQKsPdNWU38dT6aGxtqJR4oYS5kPpLVBcuKiiu7gqTYqMMqhUG6DP7pPahzPQu36sWSmeLCP1C4AwqcR5FX2RyRoZfd4B8pAnSdX", 140 "xpub": "tpubD6NzVbkrYhZ4WqYFvnJ3Vyw5TrpME8jLf3zbd1DvKbX7jbwc5wewYLKLSFRzZWV6hZj7XhsXAy7fhE5jB25DiWyNM3ztXbsXHRVCrp5BiPY", 141 "pubs": [ 142 "2258b1c3160be0864a541854eec9164a572f094f7562628281a8073bb89173a7", 143 "83df59d0a5c951cdd62b7ab225a62079f48d2a333a86e66c35420d101446e92e", 144 "2a654bf234d819055312f9ca03fad5836f9163b09cdd24d29678f694842b874a", 145 "aa0334ab910047387c912a21ec0dab806a47ffa38365060dbc5d47c18c6e66e7", 146 ] 147 }, 148 { 149 "xprv": "tprv8mGPkMVz5mZuJDnC2NjjAv7E9Zqa5LCgX4zawbZu5nzTtLb5kGhPwycX4H1gtW1f5ZdTKTNtQJ61hk71F2TdcQ93EFDTpUcPBr98QRji615", 150 "xpub": "tpubDHxRtmYEE9FaBgoyv2QKaKmLibMWEfPb6NbNE7cCW4nripqrNfWz8UEPEPbHCrakwLvwFfsqoaf4pjX4gWStp4nECRf1QwBKPkLqnY8pHbj", 151 "pubs": [ 152 "00a9da96087a72258f83b338ef7f0ea8cbbe05da5f18f091eb397d1ecbf7c3d3", 153 "b2749b74d51a78f5fe3ebb3a7c0ff266a468cade143dfa265c57e325177edf00", 154 "6b8747a6bbe4440d7386658476da51f6e49a220508a7ec77fe7bccc3e7baa916", 155 "4674bf4d9ebbe01bf0aceaca2472f63198655ecf2df810f8d69b38421972318e", 156 ] 157 } 158 ] 159 160 161 def key(hex_key): 162 """Construct an x-only pubkey from its hex representation.""" 163 return bytes.fromhex(hex_key) 164 165 def pk(hex_key): 166 """Construct a script expression for taproot_construct for pk(hex_key).""" 167 return (None, CScript([bytes.fromhex(hex_key), OP_CHECKSIG])) 168 169 def multi_a(k, hex_keys, sort=False): 170 """Construct a script expression for taproot_construct for a multi_a script.""" 171 xkeys = [bytes.fromhex(hex_key) for hex_key in hex_keys] 172 if sort: 173 xkeys.sort() 174 ops = [xkeys[0], OP_CHECKSIG] 175 for i in range(1, len(hex_keys)): 176 ops += [xkeys[i], OP_CHECKSIGADD] 177 ops += [k, OP_NUMEQUAL] 178 return (None, CScript(ops)) 179 180 def compute_taproot_address(pubkey, scripts): 181 """Compute the address for a taproot output with given inner key and scripts.""" 182 return output_key_to_p2tr(taproot_construct(pubkey, scripts).output_pubkey) 183 184 def compute_raw_taproot_address(pubkey): 185 return encode_segwit_address("bcrt", 1, pubkey) 186 187 class WalletTaprootTest(BitcoinTestFramework): 188 """Test generation and spending of P2TR address outputs.""" 189 190 def set_test_params(self): 191 self.num_nodes = 2 192 self.setup_clean_chain = True 193 self.extra_args = [['-keypool=100'], ['-keypool=100']] 194 195 def skip_test_if_missing_module(self): 196 self.skip_if_no_wallet() 197 198 def setup_network(self): 199 self.setup_nodes() 200 201 def init_wallet(self, *, node): 202 pass 203 204 @staticmethod 205 def make_desc(pattern, privmap, keys, pub_only = False): 206 pat = pattern.replace("$H", H_POINT) 207 for i in range(len(privmap)): 208 if privmap[i] and not pub_only: 209 pat = pat.replace("$%i" % (i + 1), keys[i]['xprv']) 210 else: 211 pat = pat.replace("$%i" % (i + 1), keys[i]['xpub']) 212 return descsum_create(pat) 213 214 @staticmethod 215 def make_addr(treefn, keys, i): 216 args = [] 217 for j in range(len(keys)): 218 args.append(keys[j]['pubs'][i]) 219 tree = treefn(*args) 220 if isinstance(tree, tuple): 221 return compute_taproot_address(*tree) 222 if isinstance(tree, bytes): 223 return compute_raw_taproot_address(tree) 224 assert False 225 226 def do_test_addr(self, comment, pattern, privmap, treefn, keys): 227 self.log.info("Testing %s address derivation" % comment) 228 229 # Create wallets 230 wallet_uuid = uuid.uuid4().hex 231 self.nodes[0].createwallet(wallet_name=f"privs_tr_enabled_{wallet_uuid}", blank=True) 232 self.nodes[0].createwallet(wallet_name=f"pubs_tr_enabled_{wallet_uuid}", blank=True, disable_private_keys=True) 233 self.nodes[0].createwallet(wallet_name=f"addr_gen_{wallet_uuid}", disable_private_keys=True, blank=True) 234 privs_tr_enabled = self.nodes[0].get_wallet_rpc(f"privs_tr_enabled_{wallet_uuid}") 235 pubs_tr_enabled = self.nodes[0].get_wallet_rpc(f"pubs_tr_enabled_{wallet_uuid}") 236 addr_gen = self.nodes[0].get_wallet_rpc(f"addr_gen_{wallet_uuid}") 237 238 desc = self.make_desc(pattern, privmap, keys, False) 239 desc_pub = self.make_desc(pattern, privmap, keys, True) 240 assert_equal(self.nodes[0].getdescriptorinfo(desc)['descriptor'], desc_pub) 241 result = addr_gen.importdescriptors([{"desc": desc_pub, "active": True, "timestamp": "now"}]) 242 assert result[0]['success'] 243 address_type = "bech32m" if "tr" in pattern else "bech32" 244 for i in range(4): 245 addr_g = addr_gen.getnewaddress(address_type=address_type) 246 if treefn is not None: 247 addr_r = self.make_addr(treefn, keys, i) 248 assert_equal(addr_g, addr_r) 249 desc_a = addr_gen.getaddressinfo(addr_g)['desc'] 250 if desc.startswith("tr("): 251 assert desc_a.startswith("tr(") 252 rederive = self.nodes[1].deriveaddresses(desc_a) 253 assert_equal(len(rederive), 1) 254 assert_equal(rederive[0], addr_g) 255 256 # tr descriptors can be imported 257 result = privs_tr_enabled.importdescriptors([{"desc": desc, "timestamp": "now"}]) 258 assert result[0]['success'] 259 result = pubs_tr_enabled.importdescriptors([{"desc": desc_pub, "timestamp": "now"}]) 260 assert result[0]["success"] 261 262 # Cleanup 263 privs_tr_enabled.unloadwallet() 264 pubs_tr_enabled.unloadwallet() 265 addr_gen.unloadwallet() 266 267 def do_test_sendtoaddress(self, comment, pattern, privmap, treefn, keys_pay, keys_change): 268 self.log.info("Testing %s through sendtoaddress" % comment) 269 270 # Create wallets 271 wallet_uuid = uuid.uuid4().hex 272 self.nodes[0].createwallet(wallet_name=f"rpc_online_{wallet_uuid}", blank=True) 273 rpc_online = self.nodes[0].get_wallet_rpc(f"rpc_online_{wallet_uuid}") 274 275 desc_pay = self.make_desc(pattern, privmap, keys_pay) 276 desc_change = self.make_desc(pattern, privmap, keys_change) 277 desc_pay_pub = self.make_desc(pattern, privmap, keys_pay, True) 278 desc_change_pub = self.make_desc(pattern, privmap, keys_change, True) 279 assert_equal(self.nodes[0].getdescriptorinfo(desc_pay)['descriptor'], desc_pay_pub) 280 assert_equal(self.nodes[0].getdescriptorinfo(desc_change)['descriptor'], desc_change_pub) 281 result = rpc_online.importdescriptors([{"desc": desc_pay, "active": True, "timestamp": "now"}]) 282 assert result[0]['success'] 283 result = rpc_online.importdescriptors([{"desc": desc_change, "active": True, "timestamp": "now", "internal": True}]) 284 assert result[0]['success'] 285 address_type = "bech32m" if "tr" in pattern else "bech32" 286 for i in range(4): 287 addr_g = rpc_online.getnewaddress(address_type=address_type) 288 if treefn is not None: 289 addr_r = self.make_addr(treefn, keys_pay, i) 290 assert_equal(addr_g, addr_r) 291 boring_balance = int(self.boring.getbalance() * 100000000) 292 to_amnt = random.randrange(1000000, boring_balance) 293 self.boring.sendtoaddress(address=addr_g, amount=Decimal(to_amnt) / 100000000, subtractfeefromamount=True) 294 self.generatetoaddress(self.nodes[0], 1, self.boring.getnewaddress(), sync_fun=self.no_op) 295 test_balance = int(rpc_online.getbalance() * 100000000) 296 ret_amnt = random.randrange(100000, test_balance) 297 # Increase fee_rate to compensate for the wallet's inability to estimate fees for script path spends. 298 res = rpc_online.sendtoaddress(address=self.boring.getnewaddress(), amount=Decimal(ret_amnt) / 100000000, subtractfeefromamount=True, fee_rate=200) 299 self.generatetoaddress(self.nodes[0], 1, self.boring.getnewaddress(), sync_fun=self.no_op) 300 assert rpc_online.gettransaction(res)["confirmations"] > 0 301 302 # Cleanup 303 txid = rpc_online.sendall(recipients=[self.boring.getnewaddress()])["txid"] 304 self.generatetoaddress(self.nodes[0], 1, self.boring.getnewaddress(), sync_fun=self.no_op) 305 assert rpc_online.gettransaction(txid)["confirmations"] > 0 306 rpc_online.unloadwallet() 307 308 def do_test_psbt(self, comment, pattern, privmap, treefn, keys_pay, keys_change): 309 self.log.info("Testing %s through PSBT" % comment) 310 311 # Create wallets 312 wallet_uuid = uuid.uuid4().hex 313 self.nodes[0].createwallet(wallet_name=f"psbt_online_{wallet_uuid}", disable_private_keys=True, blank=True) 314 self.nodes[1].createwallet(wallet_name=f"psbt_offline_{wallet_uuid}", blank=True) 315 self.nodes[1].createwallet(f"key_only_wallet_{wallet_uuid}", blank=True) 316 psbt_online = self.nodes[0].get_wallet_rpc(f"psbt_online_{wallet_uuid}") 317 psbt_offline = self.nodes[1].get_wallet_rpc(f"psbt_offline_{wallet_uuid}") 318 key_only_wallet = self.nodes[1].get_wallet_rpc(f"key_only_wallet_{wallet_uuid}") 319 320 desc_pay = self.make_desc(pattern, privmap, keys_pay, False) 321 desc_change = self.make_desc(pattern, privmap, keys_change, False) 322 desc_pay_pub = self.make_desc(pattern, privmap, keys_pay, True) 323 desc_change_pub = self.make_desc(pattern, privmap, keys_change, True) 324 assert_equal(self.nodes[0].getdescriptorinfo(desc_pay)['descriptor'], desc_pay_pub) 325 assert_equal(self.nodes[0].getdescriptorinfo(desc_change)['descriptor'], desc_change_pub) 326 result = psbt_online.importdescriptors([{"desc": desc_pay_pub, "active": True, "timestamp": "now"}]) 327 assert result[0]['success'] 328 result = psbt_online.importdescriptors([{"desc": desc_change_pub, "active": True, "timestamp": "now", "internal": True}]) 329 assert result[0]['success'] 330 result = psbt_offline.importdescriptors([{"desc": desc_pay, "active": True, "timestamp": "now"}]) 331 assert result[0]['success'] 332 result = psbt_offline.importdescriptors([{"desc": desc_change, "active": True, "timestamp": "now", "internal": True}]) 333 assert result[0]['success'] 334 for key in keys_pay + keys_change: 335 result = key_only_wallet.importdescriptors([{"desc": descsum_create(f"wpkh({key['xprv']}/*)"), "timestamp":"now"}]) 336 assert result[0]["success"] 337 address_type = "bech32m" if "tr" in pattern else "bech32" 338 for i in range(4): 339 addr_g = psbt_online.getnewaddress(address_type=address_type) 340 if treefn is not None: 341 addr_r = self.make_addr(treefn, keys_pay, i) 342 assert_equal(addr_g, addr_r) 343 boring_balance = int(self.boring.getbalance() * 100000000) 344 to_amnt = random.randrange(1000000, boring_balance) 345 self.boring.sendtoaddress(address=addr_g, amount=Decimal(to_amnt) / 100000000, subtractfeefromamount=True) 346 self.generatetoaddress(self.nodes[0], 1, self.boring.getnewaddress(), sync_fun=self.no_op) 347 test_balance = int(psbt_online.getbalance() * 100000000) 348 ret_amnt = random.randrange(100000, test_balance) 349 # Increase fee_rate to compensate for the wallet's inability to estimate fees for script path spends. 350 psbt = psbt_online.walletcreatefundedpsbt([], [{self.boring.getnewaddress(): Decimal(ret_amnt) / 100000000}], None, {"subtractFeeFromOutputs":[0], "fee_rate": 200, "change_type": address_type})['psbt'] 351 res = psbt_offline.walletprocesspsbt(psbt=psbt, finalize=False) 352 for wallet in [psbt_offline, key_only_wallet]: 353 res = wallet.walletprocesspsbt(psbt=psbt, finalize=False) 354 355 decoded = wallet.decodepsbt(res["psbt"]) 356 if pattern.startswith("tr("): 357 for psbtin in decoded["inputs"]: 358 assert "non_witness_utxo" not in psbtin 359 assert "witness_utxo" in psbtin 360 assert "taproot_internal_key" in psbtin 361 assert "taproot_bip32_derivs" in psbtin 362 assert "taproot_key_path_sig" in psbtin or "taproot_script_path_sigs" in psbtin 363 if "taproot_script_path_sigs" in psbtin: 364 assert "taproot_merkle_root" in psbtin 365 assert "taproot_scripts" in psbtin 366 367 rawtx = self.nodes[0].finalizepsbt(res['psbt'])['hex'] 368 res = self.nodes[0].testmempoolaccept([rawtx]) 369 assert res[0]["allowed"] 370 371 txid = self.nodes[0].sendrawtransaction(rawtx) 372 self.generatetoaddress(self.nodes[0], 1, self.boring.getnewaddress(), sync_fun=self.no_op) 373 assert psbt_online.gettransaction(txid)['confirmations'] > 0 374 375 # Cleanup 376 psbt = psbt_online.sendall(recipients=[self.boring.getnewaddress()], psbt=True)["psbt"] 377 res = psbt_offline.walletprocesspsbt(psbt=psbt, finalize=False) 378 rawtx = self.nodes[0].finalizepsbt(res['psbt'])['hex'] 379 txid = self.nodes[0].sendrawtransaction(rawtx) 380 self.generatetoaddress(self.nodes[0], 1, self.boring.getnewaddress(), sync_fun=self.no_op) 381 assert psbt_online.gettransaction(txid)['confirmations'] > 0 382 psbt_online.unloadwallet() 383 psbt_offline.unloadwallet() 384 385 def do_test(self, comment, pattern, privmap, treefn): 386 nkeys = len(privmap) 387 keys = random.sample(KEYS, nkeys * 4) 388 self.do_test_addr(comment, pattern, privmap, treefn, keys[0:nkeys]) 389 self.do_test_sendtoaddress(comment, pattern, privmap, treefn, keys[0:nkeys], keys[nkeys:2*nkeys]) 390 self.do_test_psbt(comment, pattern, privmap, treefn, keys[2*nkeys:3*nkeys], keys[3*nkeys:4*nkeys]) 391 392 def run_test(self): 393 self.nodes[0].createwallet(wallet_name="boring") 394 self.boring = self.nodes[0].get_wallet_rpc("boring") 395 396 self.log.info("Mining blocks...") 397 gen_addr = self.boring.getnewaddress() 398 self.generatetoaddress(self.nodes[0], 101, gen_addr, sync_fun=self.no_op) 399 400 self.do_test( 401 "tr(XPRV)", 402 "tr($1/*)", 403 [True], 404 lambda k1: (key(k1), []) 405 ) 406 self.do_test( 407 "tr(H,XPRV)", 408 "tr($H,pk($1/*))", 409 [True], 410 lambda k1: (key(H_POINT), [pk(k1)]) 411 ) 412 self.do_test( 413 "wpkh(XPRV)", 414 "wpkh($1/*)", 415 [True], 416 None 417 ) 418 self.do_test( 419 "tr(XPRV,{H,{H,XPUB}})", 420 "tr($1/*,{pk($H),{pk($H),pk($2/*)}})", 421 [True, False], 422 lambda k1, k2: (key(k1), [pk(H_POINT), [pk(H_POINT), pk(k2)]]) 423 ) 424 self.do_test( 425 "wsh(multi(1,XPRV,XPUB))", 426 "wsh(multi(1,$1/*,$2/*))", 427 [True, False], 428 None 429 ) 430 self.do_test( 431 "tr(XPRV,{XPUB,XPUB})", 432 "tr($1/*,{pk($2/*),pk($2/*)})", 433 [True, False], 434 lambda k1, k2: (key(k1), [pk(k2), pk(k2)]) 435 ) 436 self.do_test( 437 "tr(XPRV,{{XPUB,H},{H,XPUB}})", 438 "tr($1/*,{{pk($2/*),pk($H)},{pk($H),pk($2/*)}})", 439 [True, False], 440 lambda k1, k2: (key(k1), [[pk(k2), pk(H_POINT)], [pk(H_POINT), pk(k2)]]) 441 ) 442 self.do_test( 443 "tr(XPUB,{{H,{H,XPUB}},{H,{H,{H,XPRV}}}})", 444 "tr($1/*,{{pk($H),{pk($H),pk($2/*)}},{pk($H),{pk($H),{pk($H),pk($3/*)}}}})", 445 [False, False, True], 446 lambda k1, k2, k3: (key(k1), [[pk(H_POINT), [pk(H_POINT), pk(k2)]], [pk(H_POINT), [pk(H_POINT), [pk(H_POINT), pk(k3)]]]]) 447 ) 448 self.do_test( 449 "tr(XPRV,{XPUB,{{XPUB,{H,H}},{{H,H},XPUB}}})", 450 "tr($1/*,{pk($2/*),{{pk($2/*),{pk($H),pk($H)}},{{pk($H),pk($H)},pk($2/*)}}})", 451 [True, False], 452 lambda k1, k2: (key(k1), [pk(k2), [[pk(k2), [pk(H_POINT), pk(H_POINT)]], [[pk(H_POINT), pk(H_POINT)], pk(k2)]]]) 453 ) 454 self.do_test( 455 "tr(H,multi_a(1,XPRV))", 456 "tr($H,multi_a(1,$1/*))", 457 [True], 458 lambda k1: (key(H_POINT), [multi_a(1, [k1])]) 459 ) 460 self.do_test( 461 "tr(H,sortedmulti_a(1,XPRV,XPUB))", 462 "tr($H,sortedmulti_a(1,$1/*,$2/*))", 463 [True, False], 464 lambda k1, k2: (key(H_POINT), [multi_a(1, [k1, k2], True)]) 465 ) 466 self.do_test( 467 "tr(H,{H,multi_a(1,XPUB,XPRV)})", 468 "tr($H,{pk($H),multi_a(1,$1/*,$2/*)})", 469 [False, True], 470 lambda k1, k2: (key(H_POINT), [pk(H_POINT), [multi_a(1, [k1, k2])]]) 471 ) 472 self.do_test( 473 "tr(H,sortedmulti_a(1,XPUB,XPRV,XPRV))", 474 "tr($H,sortedmulti_a(1,$1/*,$2/*,$3/*))", 475 [False, True, True], 476 lambda k1, k2, k3: (key(H_POINT), [multi_a(1, [k1, k2, k3], True)]) 477 ) 478 self.do_test( 479 "tr(H,multi_a(2,XPRV,XPUB,XPRV))", 480 "tr($H,multi_a(2,$1/*,$2/*,$3/*))", 481 [True, False, True], 482 lambda k1, k2, k3: (key(H_POINT), [multi_a(2, [k1, k2, k3])]) 483 ) 484 self.do_test( 485 "tr(XPUB,{{XPUB,{XPUB,sortedmulti_a(2,XPRV,XPUB,XPRV)}})", 486 "tr($2/*,{pk($2/*),{pk($2/*),sortedmulti_a(2,$1/*,$2/*,$3/*)}})", 487 [True, False, True], 488 lambda k1, k2, k3: (key(k2), [pk(k2), [pk(k2), multi_a(2, [k1, k2, k3], True)]]) 489 ) 490 rnd_pos = random.randrange(MAX_PUBKEYS_PER_MULTI_A) 491 self.do_test( 492 "tr(XPUB,multi_a(1,H...,XPRV,H...))", 493 "tr($2/*,multi_a(1" + (",$H" * rnd_pos) + ",$1/*" + (",$H" * (MAX_PUBKEYS_PER_MULTI_A - 1 - rnd_pos)) + "))", 494 [True, False], 495 lambda k1, k2: (key(k2), [multi_a(1, ([H_POINT] * rnd_pos) + [k1] + ([H_POINT] * (MAX_PUBKEYS_PER_MULTI_A - 1 - rnd_pos)))]) 496 ) 497 self.do_test( 498 "rawtr(XPRV)", 499 "rawtr($1/*)", 500 [True], 501 lambda k1: key(k1) 502 ) 503 504 if __name__ == '__main__': 505 WalletTaprootTest(__file__).main()