def clean_psbt(b64psbt): psbt = PSBT() psbt.deserialize(b64psbt) for inp in psbt.inputs: if inp.witness_utxo is not None and inp.non_witness_utxo is not None: inp.non_witness_utxo = None return psbt.serialize()
def test_valid_psbt(self): for valid in self.data['valid']: with self.subTest(valid=valid): psbt = PSBT() psbt.deserialize(valid) serd = psbt.serialize() self.assertEqual(valid, serd)
def _generate_and_finalize(self, unknown_inputs, psbt): if not unknown_inputs: # Just do the normal signing process to test "all inputs" case sign_res = self.do_command(self.dev_args + ['signtx', psbt['psbt']]) finalize_res = self.wrpc.finalizepsbt(sign_res['psbt']) self.assertTrue(sign_res["signed"]) self.assertTrue(finalize_res["complete"]) else: # Sign only input one on first pass # then rest on second pass to test ability to successfully # ignore inputs that are not its own. Then combine both # signing passes to ensure they are actually properly being # partially signed at each step. first_psbt = PSBT() first_psbt.deserialize(psbt['psbt']) second_psbt = PSBT() second_psbt.deserialize(psbt['psbt']) # Blank master fingerprint to make hww fail to sign # Single input PSBTs will be fully signed by first signer for psbt_input in first_psbt.inputs[1:]: for pubkey, path in psbt_input.hd_keypaths.items(): psbt_input.hd_keypaths[pubkey] = KeyOriginInfo(b"\x00\x00\x00\x01", path.path) for pubkey, (leaves, origin) in psbt_input.tap_bip32_paths.items(): psbt_input.tap_bip32_paths[pubkey] = (leaves, KeyOriginInfo(b"\x00\x00\x00\x01", origin.path)) for pubkey, path in second_psbt.inputs[0].hd_keypaths.items(): second_psbt.inputs[0].hd_keypaths[pubkey] = KeyOriginInfo(b"\x00\x00\x00\x01", path.path) for pubkey, (leaves, origin) in second_psbt.inputs[0].tap_bip32_paths.items(): second_psbt.inputs[0].tap_bip32_paths[pubkey] = (leaves, KeyOriginInfo(b"\x00\x00\x00\x01", origin.path)) single_input = len(first_psbt.inputs) == 1 # Process the psbts first_psbt = first_psbt.serialize() second_psbt = second_psbt.serialize() # First will always have something to sign first_sign_res = self.do_command(self.dev_args + ['signtx', first_psbt]) self.assertTrue(first_sign_res["signed"]) self.assertTrue(single_input == self.wrpc.finalizepsbt(first_sign_res['psbt'])['complete']) # Second may have nothing to sign (1 input case) # and also may throw an error(e.g., ColdCard) second_sign_res = self.do_command(self.dev_args + ['signtx', second_psbt]) if 'psbt' in second_sign_res: if single_input: self.assertFalse(second_sign_res["signed"]) else: self.assertTrue(second_sign_res["signed"]) self.assertTrue(not self.wrpc.finalizepsbt(second_sign_res['psbt'])['complete']) combined_psbt = self.wrpc.combinepsbt([first_sign_res['psbt'], second_sign_res['psbt']]) else: self.assertTrue('error' in second_sign_res) combined_psbt = first_sign_res['psbt'] finalize_res = self.wrpc.finalizepsbt(combined_psbt) self.assertTrue(finalize_res['complete']) self.assertTrue(self.wrpc.testmempoolaccept([finalize_res['hex']])[0]["allowed"]) return finalize_res['hex']
def _test_signtx(self, input_types, multisig_types, external, op_return: bool): # Import some keys to the watch only wallet and send coins to them keypool_desc = self.do_command(self.dev_args + ['getkeypool', '--all', '30', '50']) import_result = self.wrpc.importdescriptors(keypool_desc) self.assertTrue(import_result[0]['success']) sh_wpkh_addr = self.wrpc.getnewaddress('', 'p2sh-segwit') wpkh_addr = self.wrpc.getnewaddress('', 'bech32') pkh_addr = self.wrpc.getnewaddress('', 'legacy') tr_addr = None if "tap" in input_types: tr_addr = self.wrpc.getnewaddress("", "bech32m") in_amt = 1 number_inputs = 0 # Single-sig if "segwit" in input_types: self.wpk_rpc.sendtoaddress(sh_wpkh_addr, in_amt) self.wpk_rpc.sendtoaddress(wpkh_addr, in_amt) number_inputs += 2 if "legacy" in input_types: self.wpk_rpc.sendtoaddress(pkh_addr, in_amt) number_inputs += 1 if "tap" in input_types: assert tr_addr is not None self.wpk_rpc.sendtoaddress(tr_addr, in_amt) number_inputs += 1 # Now do segwit/legacy multisig xpubs: Dict[bytes, KeyOriginInfo] = {} if "legacy" in multisig_types: sh_multi_desc, sh_multi_addr, sh_multi_xpubs = self._make_multisig( "legacy") xpubs.update(sh_multi_xpubs) sh_multi_import = { 'desc': sh_multi_desc, "timestamp": "now", "label": "shmulti" } multi_result = self.wrpc.importdescriptors([sh_multi_import]) self.assertTrue(multi_result[0]['success']) self.wpk_rpc.sendtoaddress(sh_multi_addr, in_amt) number_inputs += 1 if "segwit" in multisig_types: sh_wsh_multi_desc, sh_wsh_multi_addr, sh_wsh_xpubs = self._make_multisig( "p2sh-segwit") wsh_multi_desc, wsh_multi_addr, wsh_xpubs = self._make_multisig( "bech32") xpubs.update(sh_wsh_xpubs) xpubs.update(wsh_xpubs) sh_wsh_multi_import = { 'desc': sh_wsh_multi_desc, "timestamp": "now", "label": "shwshmulti" } wsh_multi_import = { 'desc': wsh_multi_desc, "timestamp": "now", "label": "wshmulti" } multi_result = self.wrpc.importdescriptors( [sh_wsh_multi_import, wsh_multi_import]) self.assertTrue(multi_result[0]['success']) self.assertTrue(multi_result[1]['success']) self.wpk_rpc.sendtoaddress(wsh_multi_addr, in_amt) self.wpk_rpc.sendtoaddress(sh_wsh_multi_addr, in_amt) number_inputs += 2 self.wpk_rpc.generatetoaddress(6, self.wpk_rpc.getnewaddress()) # Spend different amounts, with increasing number of inputs until the wallet is swept utxos = self.wrpc.listunspent() for i in range(1, number_inputs + 1): # Create a psbt spending the above change_addr = self.wpk_rpc.getrawchangeaddress() out_val = i / 4 outputs = [{ self.wpk_rpc.getnewaddress('', 'legacy'): out_val }, { self.wpk_rpc.getnewaddress('', 'p2sh-segwit'): out_val }, { self.wpk_rpc.getnewaddress('', 'bech32'): out_val }] if self.emulator.supports_taproot: outputs.append( {self.wpk_rpc.getnewaddress("", "bech32m"): out_val}) if op_return: outputs.append({ "data": "000102030405060708090a0b0c0d0e0f10111213141516171819101a1b1c1d1e1f" }) psbt = self.wrpc.walletcreatefundedpsbt( utxos[:i], outputs, 0, { "includeWatching": True, "changeAddress": change_addr, "subtractFeeFromOutputs": [0, 1, 2], }, True)["psbt"] # We need to modify the psbt to include our xpubs as Core does not include xpubs psbt_obj = PSBT() psbt_obj.deserialize(psbt) psbt_obj.xpub = xpubs psbt = psbt_obj.serialize() if external: # Sign with unknown inputs in two steps self._generate_and_finalize(True, psbt) # Sign all inputs all at once final_tx = self._generate_and_finalize(False, psbt) # Send off final tx to sweep the wallet self.wrpc.sendrawtransaction(final_tx)
def test_invalid_psbt(self): for invalid in self.data['invalid']: with self.subTest(invalid=invalid): with self.assertRaises(PSBTSerializationError): psbt = PSBT() psbt.deserialize(invalid)