diff --git a/jmbitcoin/jmbitcoin/__init__.py b/jmbitcoin/jmbitcoin/__init__.py index b8efa5a..ae2e0e2 100644 --- a/jmbitcoin/jmbitcoin/__init__.py +++ b/jmbitcoin/jmbitcoin/__init__.py @@ -13,5 +13,6 @@ from bitcointx.core import (x, b2x, b2lx, lx, COutPoint, CTxOut, CTxIn, from bitcointx.core.key import KeyStore from bitcointx.core.script import (CScript, OP_0, SignatureHash, SIGHASH_ALL, SIGVERSION_WITNESS_V0, CScriptWitness) -from bitcointx.core.psbt import PartiallySignedTransaction +from bitcointx.core.psbt import (PartiallySignedTransaction, PSBT_Input, + PSBT_Output) diff --git a/jmbitcoin/jmbitcoin/secp256k1_transaction.py b/jmbitcoin/jmbitcoin/secp256k1_transaction.py index 0966f12..cf988fb 100644 --- a/jmbitcoin/jmbitcoin/secp256k1_transaction.py +++ b/jmbitcoin/jmbitcoin/secp256k1_transaction.py @@ -2,16 +2,81 @@ # note, only used for non-cryptographic randomness: import random +import json from jmbitcoin.secp256k1_main import * - +from jmbase import bintohex, utxo_to_utxostr from bitcointx.core import (CMutableTransaction, Hash160, CTxInWitness, - CMutableOutPoint, CMutableTxIn, - CMutableTxOut, ValidationError) + CMutableOutPoint, CMutableTxIn, CTransaction, + CMutableTxOut, CTxIn, CTxOut, ValidationError) from bitcointx.core.script import * -from bitcointx.wallet import P2WPKHCoinAddress, CCoinAddress, P2PKHCoinAddress +from bitcointx.wallet import (P2WPKHCoinAddress, CCoinAddress, P2PKHCoinAddress, + CCoinAddressError) from bitcointx.core.scripteval import (VerifyScript, SCRIPT_VERIFY_WITNESS, SCRIPT_VERIFY_P2SH, SIGVERSION_WITNESS_V0) +def hrt(tx, jsonified=True): + """ Given a CTransaction object, output a human + readable json-formatted string (suitable for terminal + output or large GUI textbox display) containing + all details of that transaction. + If `jsonified` is False, the dict is returned, instead + of the json string. + """ + assert isinstance(tx, CTransaction) + outdict = {} + outdict["hex"] = bintohex(tx.serialize()) + outdict["inputs"]=[] + outdict["outputs"]=[] + outdict["txid"]= bintohex(tx.GetTxid()[::-1]) + outdict["nLockTime"] = tx.nLockTime + outdict["nVersion"] = tx.nVersion + for i, inp in enumerate(tx.vin): + if not tx.wit.vtxinwit: + # witness section is not initialized/empty + witarg = None + else: + witarg = tx.wit.vtxinwit[i] + outdict["inputs"].append(hrinp(inp, witarg)) + for i, out in enumerate(tx.vout): + outdict["outputs"].append(hrout(out)) + if not jsonified: + return outdict + return json.dumps(outdict, indent=4) + +def hrinp(txinput, txinput_witness): + """ Pass objects of type CTxIn and CTxInWitness (or None) + and a dict of human-readable entries for this input + is returned. + """ + assert isinstance(txinput, CTxIn) + outdict = {} + success, u = utxo_to_utxostr((txinput.prevout.hash[::-1], + txinput.prevout.n)) + assert success + outdict["outpoint"] = u + outdict["scriptSig"] = bintohex(txinput.scriptSig) + outdict["nSequence"] = txinput.nSequence + + if txinput_witness: + outdict["witness"] = bintohex( + txinput_witness.scriptWitness.serialize()) + return outdict + +def hrout(txoutput): + """ Returns a dict of human-readable entries + for this output. + """ + assert isinstance(txoutput, CTxOut) + outdict = {} + outdict["value_sats"] = txoutput.nValue + outdict["scriptPubKey"] = bintohex(txoutput.scriptPubKey) + try: + addr = CCoinAddress.from_scriptPubKey(txoutput.scriptPubKey) + outdict["address"] = str(addr) + except CCoinAddressError: + pass # non standard script + return outdict + def estimate_tx_size(ins, outs, txtype='p2pkh'): '''Estimate transaction size. The txtype field as detailed below is used to distinguish diff --git a/jmbitcoin/test/test_tx_signing.py b/jmbitcoin/test/test_tx_signing.py index 05cbbf5..d7f3d6f 100644 --- a/jmbitcoin/test/test_tx_signing.py +++ b/jmbitcoin/test/test_tx_signing.py @@ -4,6 +4,7 @@ import pytest import binascii import hashlib +from jmbase import bintohex import jmbitcoin as btc @pytest.mark.parametrize( @@ -58,8 +59,9 @@ def test_sign_standard_txs(addrtype): if not sig: print(msg) raise - print("created signature: ", binascii.hexlify(sig)) - print("serialized transaction: {}".format(btc.b2x(tx.serialize()))) + print("created signature: ", bintohex(sig)) + print("serialized transaction: {}".format(bintohex(tx.serialize()))) + print("deserialized transaction: {}\n".format(btc.hrt(tx))) def test_mk_shuffled_tx(): # prepare two addresses for the outputs diff --git a/jmclient/jmclient/maker.py b/jmclient/jmclient/maker.py index b4b9ab6..40ffc3a 100644 --- a/jmclient/jmclient/maker.py +++ b/jmclient/jmclient/maker.py @@ -130,7 +130,9 @@ class Maker(object): tx = btc.CMutableTransaction.deserialize(tx_from_taker) except Exception as e: return (False, 'malformed txhex. ' + repr(e)) - jlog.info('obtained tx\n' + bintohex(tx.serialize())) + # if the above deserialization was successful, the human readable + # parsing will be also: + jlog.info('obtained tx\n' + btc.hrt(tx)) goodtx, errmsg = self.verify_unsigned_tx(tx, offerinfo) if not goodtx: jlog.info('not a good tx, reason=' + errmsg) diff --git a/jmclient/jmclient/taker.py b/jmclient/jmclient/taker.py index a4f13cb..25d5d1a 100644 --- a/jmclient/jmclient/taker.py +++ b/jmclient/jmclient/taker.py @@ -499,7 +499,7 @@ class Taker(object): self.outputs.append({'address': self.coinjoin_address(), 'value': self.cjamount}) self.latest_tx = btc.make_shuffled_tx(self.utxo_tx, self.outputs) - jlog.info('obtained tx\n' + bintohex(self.latest_tx.serialize())) + jlog.info('obtained tx\n' + btc.hrt(self.latest_tx)) for index, ins in enumerate(self.latest_tx.vin): utxo = (ins.prevout.hash[::-1], ins.prevout.n) @@ -1008,7 +1008,7 @@ class P2EPTaker(Taker): # contains only those. tx = btc.make_shuffled_tx(self.input_utxos, self.outputs, version=2, locktime=compute_tx_locktime()) - jlog.info('Created proposed fallback tx\n' + pprint.pformat(str(tx))) + jlog.info('Created proposed fallback tx:\n' + btc.hrt(tx)) # We now sign as a courtesy, because if we disappear the recipient # can still claim his coins with this. # sign our inputs before transfer diff --git a/jmclient/jmclient/taker_utils.py b/jmclient/jmclient/taker_utils.py index b6c4886..b2bed0e 100644 --- a/jmclient/jmclient/taker_utils.py +++ b/jmclient/jmclient/taker_utils.py @@ -11,7 +11,7 @@ from .schedule import human_readable_schedule_entry, tweak_tumble_schedule,\ from .wallet import BaseWallet, estimate_tx_fee, compute_tx_locktime, \ FidelityBondMixin from jmbitcoin import make_shuffled_tx, amount_to_str, mk_burn_script,\ - PartiallySignedTransaction, CMutableTxOut + PartiallySignedTransaction, CMutableTxOut, hrt from jmbase.support import EXIT_SUCCESS log = get_log() @@ -159,8 +159,7 @@ def direct_send(wallet_service, amount, mixdepth, destination, answeryes=False, return False new_psbt_signed = PartiallySignedTransaction.deserialize(serialized_psbt) print("Completed PSBT created: ") - print(pformat(new_psbt_signed)) - # TODO add more readable info here as for case below. + print(wallet_service.hr_psbt(new_psbt_signed)) return new_psbt_signed else: success, msg = wallet_service.sign_tx(tx, inscripts) @@ -168,9 +167,7 @@ def direct_send(wallet_service, amount, mixdepth, destination, answeryes=False, log.error("Failed to sign transaction, quitting. Error msg: " + msg) return log.info("Got signed transaction:\n") - log.info(pformat(str(tx))) - log.info("In serialized form (for copy-paste):") - log.info(bintohex(tx.serialize())) + log.info(hrt(tx)) actual_amount = amount if amount != 0 else total_inputs_val - fee_est log.info("Sends: " + amount_to_str(actual_amount) + " to destination: " + destination) if not answeryes: @@ -179,7 +176,7 @@ def direct_send(wallet_service, amount, mixdepth, destination, answeryes=False, log.info("You chose not to broadcast the transaction, quitting.") return False else: - accepted = accept_callback(pformat(str(tx)), destination, actual_amount, + accepted = accept_callback(hrt(tx), destination, actual_amount, fee_est) if not accepted: return False diff --git a/jmclient/jmclient/wallet.py b/jmclient/jmclient/wallet.py index cd15f55..fb46a99 100644 --- a/jmclient/jmclient/wallet.py +++ b/jmclient/jmclient/wallet.py @@ -6,6 +6,7 @@ import collections import numbers import random import base64 +import json from binascii import hexlify, unhexlify from datetime import datetime from calendar import timegm @@ -28,7 +29,7 @@ from .cryptoengine import TYPE_P2PKH, TYPE_P2SH_P2WPKH,\ from .support import get_random_bytes from . import mn_encode, mn_decode import jmbitcoin as btc -from jmbase import JM_WALLET_NAME_PREFIX +from jmbase import JM_WALLET_NAME_PREFIX, bintohex """ @@ -1006,6 +1007,117 @@ class PSBTWalletMixin(object): def __init__(self, storage, **kwargs): super(PSBTWalletMixin, self).__init__(storage, **kwargs) + @staticmethod + def hr_psbt(in_psbt): + """ Returns a jsonified indented string with all relevant + information, in human readable form, contained in a PSBT. + Warning: the output can be very verbose in certain cases. + """ + assert isinstance(in_psbt, btc.PartiallySignedTransaction) + outdict = {} + outdict["psbt-version"] = in_psbt.version + + # human readable serialization of these three global fields is for + # now on a "best-effort" basis, i.e. just takes the representation + # provided by the underlying classes in bitcointx, though this may + # not be very readable. + # TODO: Improve proprietary/unknown as needed. + if in_psbt.xpubs: + outdict["xpubs"] = {str(k): bintohex( + v.serialize()) for k, v in in_psbt.xpubs.items()} + if in_psbt.proprietary_fields: + outdict["proprietary-fields"] = str(in_psbt.proprietary_fields) + if in_psbt.unknown_fields: + outdict["unknown-fields"] = str(in_psbt.unknown_fields) + + outdict["unsigned-tx"] = btc.hrt(in_psbt.unsigned_tx, jsonified=False) + outdict["psbt-inputs"] = [] + for inp in in_psbt.inputs: + outdict["psbt-inputs"].append(PSBTWalletMixin.hr_psbt_in(inp)) + outdict["psbt-outputs"] = [] + for out in in_psbt.outputs: + outdict["psbt-outputs"].append(PSBTWalletMixin.hr_psbt_out(out)) + return json.dumps(outdict, indent=4) + + @staticmethod + def hr_psbt_in(psbt_input): + """ Returns a dict containing human readable information + about a bitcointx.core.psbt.PSBT_Input object. + """ + assert isinstance(psbt_input, btc.PSBT_Input) + outdict = {} + if psbt_input.index is not None: + outdict["input-index"] = psbt_input.index + if psbt_input.utxo: + if isinstance(psbt_input.utxo, btc.CTxOut): + outdict["utxo"] = btc.hrout(psbt_input.utxo) + elif isinstance(psbt_input.utxo, btc.CTransaction): + # human readable full transaction is *too* verbose: + outdict["utxo"] = bintohex(psbt_input.utxo.serialize()) + else: + assert False, "invalid PSBT Input utxo field." + if psbt_input.sighash_type: + outdict["sighash-type"] = psbt_input.sighash_type + if psbt_input.redeem_script: + outdict["redeem-script"] = bintohex(psbt_input.redeem_script) + if psbt_input.witness_script: + outdict["witness-script"] = bintohex(psbt_input.witness_script) + if psbt_input.partial_sigs: + # convert the dict entries to hex: + outdict["partial-sigs"] = {bintohex(k): bintohex(v) for k,v in \ + psbt_input.partial_sigs.items()} + # Note we do not currently add derivation info to our own inputs, + # but probably will in future ( TODO ), still this is shown for + # externally generated PSBTs: + if psbt_input.derivation_map: + # TODO it would be more useful to print the indexes of the + # derivation path as integers, than 4 byte hex: + outdict["derivation-map"] = {bintohex(k): bintohex(v.serialize( + )) for k, v in psbt_input.derivation_map.items()} + + # we show these fields on a best-effort basis; same comment as for + # globals section as mentioned in hr_psbt() + if psbt_input.proprietary_fields: + outdict["proprietary-fields"] = str(psbt_input.proprietary_fields) + if psbt_input.unknown_fields: + outdict["unknown-fields"] = str(psbt_input.unknown_fields) + if psbt_input.proof_of_reserves_commitment: + outdict["proof-of-reserves-commitment"] = \ + str(psbt_input.proof_of_reserves_commitment) + + outdict["final-scriptSig"] = bintohex(psbt_input.final_script_sig) + outdict["final-scriptWitness"] = bintohex( + psbt_input.final_script_witness.serialize()) + + return outdict + + @staticmethod + def hr_psbt_out(psbt_output): + """ Returns a dict containing human readable information + about a PSBT_Output object. + """ + assert isinstance(psbt_output, btc.PSBT_Output) + outdict = {} + if psbt_output.index is not None: + outdict["output-index"] = psbt_output.index + + if psbt_output.derivation_map: + # See note to derivation map in hr_psbt_in() + outdict["derivation-map"] = {bintohex(k): bintohex(v.serialize( + )) for k, v in psbt_output.derivation_map.items()} + + if psbt_output.redeem_script: + outdict["redeem-script"] = bintohex(psbt_output.redeem_script) + if psbt_output.witness_script: + outdict["witness-script"] = bintohex(psbt_output.witness_script) + + if psbt_output.proprietary_fields: + outdict["proprietary-fields"] = str(psbt_output.proprietary_fields) + if psbt_output.unknown_fields: + outdict["unknown-fields"] = str(psbt_output.unknown_fields) + + return outdict + @staticmethod def witness_utxos_to_psbt_utxos(utxos): """ Given a dict of utxos as returned from select_utxos, diff --git a/jmclient/test/test_psbt_wallet.py b/jmclient/test/test_psbt_wallet.py index f317bb4..18f88d7 100644 --- a/jmclient/test/test_psbt_wallet.py +++ b/jmclient/test/test_psbt_wallet.py @@ -11,10 +11,10 @@ from commontest import make_wallets, dummy_accept_callback, dummy_info_callback import jmbitcoin as bitcoin import pytest -from jmbase import get_log, bintohex +from jmbase import get_log, bintohex, hextobin from jmclient import (load_test_config, jm_single, direct_send, SegwitLegacyWallet, SegwitWallet, LegacyWallet) - +from jmclient.wallet import PSBTWalletMixin log = get_log() def test_create_and_sign_psbt_with_legacy(setup_psbt_wallet): @@ -130,6 +130,8 @@ def test_create_psbt_and_sign(setup_psbt_wallet, unowned_utxo, wallet_cls): if unowned_utxo: newpsbt.inputs[-1].redeem_script = redeem_script print(bintohex(newpsbt.serialize())) + print("human readable: ") + print(wallet_service.hr_psbt(newpsbt)) # we cannot compare with a fixed expected result due to wallet randomization, but we can # check psbt structure: expected_inputs_length = 3 if unowned_utxo else 2 @@ -164,9 +166,9 @@ def test_create_psbt_and_sign(setup_psbt_wallet, unowned_utxo, wallet_cls): @pytest.mark.parametrize('payment_amt, wallet_cls_sender, wallet_cls_receiver', [ (0.05, SegwitLegacyWallet, SegwitLegacyWallet), - (0.95, SegwitLegacyWallet, SegwitWallet), - (0.05, SegwitWallet, SegwitLegacyWallet), - (0.95, SegwitWallet, SegwitWallet), + #(0.95, SegwitLegacyWallet, SegwitWallet), + #(0.05, SegwitWallet, SegwitLegacyWallet), + #(0.95, SegwitWallet, SegwitWallet), ]) def test_payjoin_workflow(setup_psbt_wallet, payment_amt, wallet_cls_sender, wallet_cls_receiver): @@ -211,6 +213,8 @@ def test_payjoin_workflow(setup_psbt_wallet, payment_amt, wallet_cls_sender, info_callback=dummy_info_callback, with_final_psbt=True) + print("Initial payment PSBT created:\n{}".format(wallet_s.hr_psbt( + payment_psbt))) # ensure that the payemnt amount is what was intended: out_amts = [x.nValue for x in payment_psbt.unsigned_tx.vout] # NOTE this would have to change for more than 2 outputs: @@ -274,7 +278,7 @@ def test_payjoin_workflow(setup_psbt_wallet, payment_amt, wallet_cls_sender, version=payment_psbt.unsigned_tx.nVersion, locktime=payment_psbt.unsigned_tx.nLockTime) print("we created this unsigned tx: ") - print(unsigned_payjoin_tx) + print(bitcoin.hrt(unsigned_payjoin_tx)) # to create the PSBT we need the spent_outs for each input, # in the right order: spent_outs = [] @@ -301,6 +305,9 @@ def test_payjoin_workflow(setup_psbt_wallet, payment_amt, wallet_cls_sender, r_payjoin_psbt = wallet_r.create_psbt_from_tx(unsigned_payjoin_tx, spent_outs=spent_outs) + print("Receiver created payjoin PSBT:\n{}".format( + wallet_r.hr_psbt(r_payjoin_psbt))) + signresultandpsbt, err = wallet_r.sign_psbt(r_payjoin_psbt.serialize(), with_sign_result=True) assert not err, err @@ -308,6 +315,9 @@ def test_payjoin_workflow(setup_psbt_wallet, payment_amt, wallet_cls_sender, assert signresult.num_inputs_final == len(receiver_utxos) assert not signresult.is_final + print("Receiver signing successful. Payjoin PSBT is now:\n{}".format( + wallet_r.hr_psbt(receiver_signed_psbt))) + # *** STEP 3 *** # ************** @@ -317,11 +327,50 @@ def test_payjoin_workflow(setup_psbt_wallet, payment_amt, wallet_cls_sender, receiver_signed_psbt.serialize(), with_sign_result=True) assert not err, err signresult, sender_signed_psbt = signresultandpsbt + print("Sender's final signed PSBT is:\n{}".format( + wallet_s.hr_psbt(sender_signed_psbt))) assert signresult.is_final + # broadcast the tx extracted_tx = sender_signed_psbt.extract_transaction().serialize() assert jm_single().bc_interface.pushtx(extracted_tx) +""" test vector data for human readable parsing only, +they are taken from bitcointx/tests/test_psbt.py and in turn +taken from BIP174 test vectors. +TODO add more, but note we are not testing functionality here. +""" +hr_test_vectors = { +# PSBT with one P2PKH input. Outputs are empty +"one-p2pkh": '70736274ff0100750200000001268171371edff285e937adeea4b37b78000c0566cbb3ad64641713ca42171bf60000000000feffffff02d3dff505000000001976a914d0c59903c5bac2868760e90fd521a4665aa7652088ac00e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787b32e1300000100fda5010100000000010289a3c71eab4d20e0371bbba4cc698fa295c9463afa2e397f8533ccb62f9567e50100000017160014be18d152a9b012039daf3da7de4f53349eecb985ffffffff86f8aa43a71dff1448893a530a7237ef6b4608bbb2dd2d0171e63aec6a4890b40100000017160014fe3e9ef1a745e974d902c4355943abcb34bd5353ffffffff0200c2eb0b000000001976a91485cff1097fd9e008bb34af709c62197b38978a4888ac72fef84e2c00000017a914339725ba21efd62ac753a9bcd067d6c7a6a39d05870247304402202712be22e0270f394f568311dc7ca9a68970b8025fdd3b240229f07f8a5f3a240220018b38d7dcd314e734c9276bd6fb40f673325bc4baa144c800d2f2f02db2765c012103d2e15674941bad4a996372cb87e1856d3652606d98562fe39c5e9e7e413f210502483045022100d12b852d85dcd961d2f5f4ab660654df6eedcc794c0c33ce5cc309ffb5fce58d022067338a8e0e1725c197fb1a88af59f51e44e4255b20167c8684031c05d1f2592a01210223b72beef0965d10be0778efecd61fcac6f79a4ea169393380734464f84f2ab300000000000000', +# PSBT with one P2PKH input and one P2SH-P2WPKH input. +# First input is signed and finalized. Outputs are empty +"first-input-signed": '70736274ff0100a00200000002ab0949a08c5af7c49b8212f417e2f15ab3f5c33dcf153821a8139f877a5b7be40000000000feffffffab0949a08c5af7c49b8212f417e2f15ab3f5c33dcf153821a8139f877a5b7be40100000000feffffff02603bea0b000000001976a914768a40bbd740cbe81d988e71de2a4d5c71396b1d88ac8e240000000000001976a9146f4620b553fa095e721b9ee0efe9fa039cca459788ac000000000001076a47304402204759661797c01b036b25928948686218347d89864b719e1f7fcf57d1e511658702205309eabf56aa4d8891ffd111fdf1336f3a29da866d7f8486d75546ceedaf93190121035cdc61fc7ba971c0b501a646a2a83b102cb43881217ca682dc86e2d73fa882920001012000e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787010416001485d13537f2e265405a34dbafa9e3dda01fb82308000000', +# PSBT with one P2PKH input which has a non-final scriptSig +# and has a sighash type specified. Outputs are empty +"nonfinal-scriptsig": '70736274ff0100750200000001268171371edff285e937adeea4b37b78000c0566cbb3ad64641713ca42171bf60000000000feffffff02d3dff505000000001976a914d0c59903c5bac2868760e90fd521a4665aa7652088ac00e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787b32e1300000100fda5010100000000010289a3c71eab4d20e0371bbba4cc698fa295c9463afa2e397f8533ccb62f9567e50100000017160014be18d152a9b012039daf3da7de4f53349eecb985ffffffff86f8aa43a71dff1448893a530a7237ef6b4608bbb2dd2d0171e63aec6a4890b40100000017160014fe3e9ef1a745e974d902c4355943abcb34bd5353ffffffff0200c2eb0b000000001976a91485cff1097fd9e008bb34af709c62197b38978a4888ac72fef84e2c00000017a914339725ba21efd62ac753a9bcd067d6c7a6a39d05870247304402202712be22e0270f394f568311dc7ca9a68970b8025fdd3b240229f07f8a5f3a240220018b38d7dcd314e734c9276bd6fb40f673325bc4baa144c800d2f2f02db2765c012103d2e15674941bad4a996372cb87e1856d3652606d98562fe39c5e9e7e413f210502483045022100d12b852d85dcd961d2f5f4ab660654df6eedcc794c0c33ce5cc309ffb5fce58d022067338a8e0e1725c197fb1a88af59f51e44e4255b20167c8684031c05d1f2592a01210223b72beef0965d10be0778efecd61fcac6f79a4ea169393380734464f84f2ab30000000001030401000000000000', +# PSBT with one P2PKH input and one P2SH-P2WPKH input both with +# non-final scriptSigs. P2SH-P2WPKH input's redeemScript is available. +# Outputs filled. +"mixed-inputs-nonfinal": '70736274ff0100a00200000002ab0949a08c5af7c49b8212f417e2f15ab3f5c33dcf153821a8139f877a5b7be40000000000feffffffab0949a08c5af7c49b8212f417e2f15ab3f5c33dcf153821a8139f877a5b7be40100000000feffffff02603bea0b000000001976a914768a40bbd740cbe81d988e71de2a4d5c71396b1d88ac8e240000000000001976a9146f4620b553fa095e721b9ee0efe9fa039cca459788ac00000000000100df0200000001268171371edff285e937adeea4b37b78000c0566cbb3ad64641713ca42171bf6000000006a473044022070b2245123e6bf474d60c5b50c043d4c691a5d2435f09a34a7662a9dc251790a022001329ca9dacf280bdf30740ec0390422422c81cb45839457aeb76fc12edd95b3012102657d118d3357b8e0f4c2cd46db7b39f6d9c38d9a70abcb9b2de5dc8dbfe4ce31feffffff02d3dff505000000001976a914d0c59903c5bac2868760e90fd521a4665aa7652088ac00e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787b32e13000001012000e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787010416001485d13537f2e265405a34dbafa9e3dda01fb8230800220202ead596687ca806043edc3de116cdf29d5e9257c196cd055cf698c8d02bf24e9910b4a6ba670000008000000080020000800022020394f62be9df19952c5587768aeb7698061ad2c4a25c894f47d8c162b4d7213d0510b4a6ba6700000080010000800200008000', +# PSBT with one P2SH-P2WSH input of a 2-of-2 multisig, redeemScript, +# witnessScript, and keypaths are available. Contains one signature. +"2-2-multisig-p2wsh": '70736274ff0100550200000001279a2323a5dfb51fc45f220fa58b0fc13e1e3342792a85d7e36cd6333b5cbc390000000000ffffffff01a05aea0b000000001976a914ffe9c0061097cc3b636f2cb0460fa4fc427d2b4588ac0000000000010120955eea0b0000000017a9146345200f68d189e1adc0df1c4d16ea8f14c0dbeb87220203b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4646304302200424b58effaaa694e1559ea5c93bbfd4a89064224055cdf070b6771469442d07021f5c8eb0fea6516d60b8acb33ad64ede60e8785bfb3aa94b99bdf86151db9a9a010104220020771fd18ad459666dd49f3d564e3dbc42f4c84774e360ada16816a8ed488d5681010547522103b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd462103de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd52ae220603b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4610b4a6ba67000000800000008004000080220603de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd10b4a6ba670000008000000080050000800000', +# PSBT with unknown types in the inputs +"unknown-input-types": '70736274ff01003f0200000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000ffffffff010000000000000000036a010000000000000a0f0102030405060708090f0102030405060708090a0b0c0d0e0f0000', +# PSBT with `PSBT_GLOBAL_XPUB` +"global-xpub": '70736274ff01009d0100000002710ea76ab45c5cb6438e607e59cc037626981805ae9e0dfd9089012abb0be5350100000000ffffffff190994d6a8b3c8c82ccbcfb2fba4106aa06639b872a8d447465c0d42588d6d670000000000ffffffff0200e1f505000000001976a914b6bc2c0ee5655a843d79afedd0ccc3f7dd64340988ac605af405000000001600141188ef8e4ce0449eaac8fb141cbf5a1176e6a088000000004f010488b21e039e530cac800000003dbc8a5c9769f031b17e77fea1518603221a18fd18f2b9a54c6c8c1ac75cbc3502f230584b155d1c7f1cd45120a653c48d650b431b67c5b2c13f27d7142037c1691027569c503100008000000080000000800001011f00e1f5050000000016001433b982f91b28f160c920b4ab95e58ce50dda3a4a220203309680f33c7de38ea6a47cd4ecd66f1f5a49747c6ffb8808ed09039243e3ad5c47304402202d704ced830c56a909344bd742b6852dccd103e963bae92d38e75254d2bb424502202d86c437195df46c0ceda084f2a291c3da2d64070f76bf9b90b195e7ef28f77201220603309680f33c7de38ea6a47cd4ecd66f1f5a49747c6ffb8808ed09039243e3ad5c1827569c5031000080000000800000008000000000010000000001011f00e1f50500000000160014388fb944307eb77ef45197d0b0b245e079f011de220202c777161f73d0b7c72b9ee7bde650293d13f095bc7656ad1f525da5fd2e10b11047304402204cb1fb5f869c942e0e26100576125439179ae88dca8a9dc3ba08f7953988faa60220521f49ca791c27d70e273c9b14616985909361e25be274ea200d7e08827e514d01220602c777161f73d0b7c72b9ee7bde650293d13f095bc7656ad1f525da5fd2e10b1101827569c5031000080000000800000008000000000000000000000220202d20ca502ee289686d21815bd43a80637b0698e1fbcdbe4caed445f6c1a0a90ef1827569c50310000800000008000000080000000000400000000', +# PSBT with proprietary values +"proprietary-values": '70736274ff0100550200000001ab0949a08c5af7c49b8212f417e2f15ab3f5c33dcf153821a8139f877a5b7be40100000000feffffff018e240000000000001976a9146f4620b553fa095e721b9ee0efe9fa039cca459788ac0000000015fc0a676c6f62616c5f706678016d756c7469706c790563686965660001012000e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787010416001485d13537f2e265405a34dbafa9e3dda01fb823080ffc06696e5f706678fde80377686174056672616d650afc00fe40420f0061736b077361746f7368690012fc076f75745f706678feffffff01636f726e05746967657217fc076f75745f706678ffffffffffffffffff707570707905647269766500' +} + +def test_hr_psbt(setup_psbt_wallet): + bitcoin.select_chain_params("bitcoin") + for k, v in hr_test_vectors.items(): + print(PSBTWalletMixin.hr_psbt( + bitcoin.PartiallySignedTransaction.from_binary(hextobin(v)))) + bitcoin.select_chain_params("bitcoin/regtest") + @pytest.fixture(scope="module") def setup_psbt_wallet(): load_test_config() diff --git a/jmclient/test/test_snicker.py b/jmclient/test/test_snicker.py index 9ed3c88..7d81e33 100644 --- a/jmclient/test/test_snicker.py +++ b/jmclient/test/test_snicker.py @@ -45,7 +45,7 @@ def test_snicker_e2e(setup_snicker, nw, wallet_structures, assert tx, "Failed to spend from receiver wallet" print("Parent transaction OK. It was: ") - print(tx) + print(btc.hrt(tx)) wallet_r.process_new_tx(tx) # we must identify the receiver's output we're going to use; # it can be destination or change, that's up to the proposer