You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
440 lines
28 KiB
440 lines
28 KiB
#! /usr/bin/env python |
|
'''Test of psbt creation, update, signing and finalizing |
|
using the functionality of the PSBT Wallet Mixin. |
|
Note that Joinmarket's PSBT code is a wrapper around |
|
bitcointx.core.psbt, and the basic test vectors for |
|
BIP174 are tested there, not here. |
|
''' |
|
|
|
import copy |
|
import base64 |
|
from commontest import make_wallets, dummy_accept_callback, dummy_info_callback |
|
|
|
import jmbitcoin as bitcoin |
|
import pytest |
|
from jmbase import get_log, bintohex, hextobin, utxostr_to_utxo |
|
from jmclient import (load_test_config, jm_single, direct_send, |
|
SegwitLegacyWallet, SegwitWallet, LegacyWallet, |
|
VolatileStorage, get_network) |
|
from jmclient.wallet import PSBTWalletMixin |
|
|
|
pytestmark = pytest.mark.usefixtures("setup_regtest_bitcoind") |
|
|
|
log = get_log() |
|
|
|
def create_volatile_wallet(seedphrase, wallet_cls=SegwitWallet): |
|
storage = VolatileStorage() |
|
wallet_cls.initialize(storage, get_network(), max_mixdepth=4, |
|
entropy=wallet_cls.entropy_from_mnemonic(seedphrase)) |
|
storage.save() |
|
return wallet_cls(storage) |
|
|
|
@pytest.mark.parametrize('walletseed, xpub, spktype_wallet, spktype_destn, partial, psbt', [ |
|
("prosper diamond marriage spy across start shift elevator job lunar edge gallery", |
|
"tpubDChjiEhsafnW2LcmK1C77XiEAgZddi6xZyxjMujBzUqZPTMRwsv3e5vSBYsdiPtCyc6TtoHTCjkxBjtF22tf8Z5ABRdeBUNwHCsqEyzR5wT", |
|
"p2wpkh", "p2sh-p2wpkh", False, |
|
"cHNidP8BAMQCAAAAA7uEliZeXLPfjeUiRBw6e5oZV1DtBrDmLthfDC4oaHQLAAAAAAD/////+X1Exketc4o5b9BPxsj70O+VlGvgiZz0KP1OMRtVLUQAAAAAAP////9r5ylMhQyxbJvCbU8aNE3NOPoXJwUaUZm4H3iT4RnaSwAAAAAA/////wKMz/AIAAAAABepFJwmRAefvZS7VQStD4k52Rn0k71Gh4zP8AgAAAAAFgAUA2shnTVftDXq+ssPwzml2UKdu1QAAAAAAAEBHwDh9QUAAAAAFgAUqw1Ifto4LztwcsxV6q+sQThIdloiBgMDZ5u3RN6Xum+OLkgAzwLFXGWFLwBUraMi7Oin4fYfrwzvYoLxAAAAAAAAAAAAAQEfAOH1BQAAAAAWABQpSCwoeMSghUoVflvtTPiqBPi+5yIGA/tAH4kVpqd3wzidaTNFxtwdpHTydkmB825us2w/3cAVDO9igvEAAAAAAQAAAAABAR8A4fUFAAAAABYAFEqM0KJ5FJ7ak2NL8PDqOPI0I1PaIgYD84aDwOqXKfGvEbre+bpNpuT0uZv6syESzz5PMu4RyLkM72KC8QAAAAACAAAAAAEAF6kUnCZEB5+9lLtVBK0PiTnZGfSTvUaHACICAw8k2gGGcF5sR8yKO5JeAkrkH15rmtCq8sCoDYbywTNzEO9igvEMAAAAIgAAAJQCAAAA"), |
|
("prosper diamond marriage spy across start shift elevator job lunar edge gallery", |
|
"tpubDChjiEhsafnW2LcmK1C77XiEAgZddi6xZyxjMujBzUqZPTMRwsv3e5vSBYsdiPtCyc6TtoHTCjkxBjtF22tf8Z5ABRdeBUNwHCsqEyzR5wT", |
|
"p2wpkh", "p2wpkh", False, |
|
"cHNidP8BAMMCAAAAA7uEliZeXLPfjeUiRBw6e5oZV1DtBrDmLthfDC4oaHQLAAAAAAD/////+X1Exketc4o5b9BPxsj70O+VlGvgiZz0KP1OMRtVLUQAAAAAAP////9r5ylMhQyxbJvCbU8aNE3NOPoXJwUaUZm4H3iT4RnaSwAAAAAA/////wKMz/AIAAAAABYAFBaOTObQIdtCaryiPxaDV5rsGYWUjM/wCAAAAAAWABR9TJm5rcSoIMW7bE1bnj7REL/eygAAAAAAAQEfAOH1BQAAAAAWABSrDUh+2jgvO3ByzFXqr6xBOEh2WiIGAwNnm7dE3pe6b44uSADPAsVcZYUvAFStoyLs6Kfh9h+vDO9igvEAAAAAAAAAAAABAR8A4fUFAAAAABYAFClILCh4xKCFShV+W+1M+KoE+L7nIgYD+0AfiRWmp3fDOJ1pM0XG3B2kdPJ2SYHzbm6zbD/dwBUM72KC8QAAAAABAAAAAAEBHwDh9QUAAAAAFgAUSozQonkUntqTY0vw8Oo48jQjU9oiBgPzhoPA6pcp8a8Rut75uk2m5PS5m/qzIRLPPk8y7hHIuQzvYoLxAAAAAAIAAAAAACICAuqCicVUfcM5IiVSiB/0ZemodybG5Im9Fu8MLorQSE4UEO9igvEMAAAAIgAAAOkAAAAA"), |
|
("prosper diamond marriage spy across start shift elevator job lunar edge gallery", |
|
"tpubDChjiEhsafnW2LcmK1C77XiEAgZddi6xZyxjMujBzUqZPTMRwsv3e5vSBYsdiPtCyc6TtoHTCjkxBjtF22tf8Z5ABRdeBUNwHCsqEyzR5wT", |
|
"p2wpkh", "p2wpkh", True, |
|
"cHNidP8BAMMCAAAAA7uEliZeXLPfjeUiRBw6e5oZV1DtBrDmLthfDC4oaHQLAAAAAAD/////+X1Exketc4o5b9BPxsj70O+VlGvgiZz0KP1OMRtVLUQAAAAAAP////9r5ylMhQyxbJvCbU8aNE3NOPoXJwUaUZm4H3iT4RnaSwAAAAAA/////wKMz/AIAAAAABYAFLH/IL11rTJ3wX1NcmUIsJ/T4j4jjM/wCAAAAAAWABR8GPNb1HUpCz8PKOc8aQXLD1wjcAAAAAAAAQEfAOH1BQAAAAAWABSrDUh+2jgvO3ByzFXqr6xBOEh2WiIGAwNnm7dE3pe6b44uSADPAsVcZYUvAFStoyLs6Kfh9h+vDE5vcGUAAAAAAAAAAAABAR8A4fUFAAAAABYAFClILCh4xKCFShV+W+1M+KoE+L7nIgYD+0AfiRWmp3fDOJ1pM0XG3B2kdPJ2SYHzbm6zbD/dwBUM72KC8QAAAAABAAAAAAEBHwDh9QUAAAAAFgAUSozQonkUntqTY0vw8Oo48jQjU9oiBgPzhoPA6pcp8a8Rut75uk2m5PS5m/qzIRLPPk8y7hHIuQzvYoLxAAAAAAIAAAAAACICAsQ7ZvU9tsbBoSje5rIJQBStlUkQaRCssKylEixre3AYEO9igvEMAAAAIgAAABcCAAAA"), |
|
]) |
|
def test_sign_external_psbt(setup_psbt_wallet, walletseed, xpub, |
|
spktype_wallet, spktype_destn, partial, psbt): |
|
bitcoin.select_chain_params("bitcoin") |
|
wallet_cls = SegwitWallet if spktype_wallet == "p2wpkh" else SegwitLegacyWallet |
|
wallet = create_volatile_wallet(walletseed, wallet_cls=wallet_cls) |
|
# if we want to actually sign, our wallet has to recognize the fake utxos |
|
# as being in the wallet, so we inject them: |
|
class DummyUtxoManager(object): |
|
_utxo = {0:{}} |
|
def add_utxo(self, utxo, path, value, height): |
|
self._utxo[0][utxo] = (path, value, height) |
|
wallet._index_cache[0][0] = 1000 |
|
wallet._utxos = DummyUtxoManager() |
|
p0, p1, p2 = (wallet.get_path(0, 0, i) for i in range(3)) |
|
if not partial: |
|
wallet._utxos.add_utxo(utxostr_to_utxo( |
|
"0b7468282e0c5fd82ee6b006ed5057199a7b3a1c4422e58ddfb35c5e269684bb:0"), |
|
p0, 10000, 1) |
|
wallet._utxos.add_utxo(utxostr_to_utxo( |
|
"442d551b314efd28f49c89e06b9495efd0fbc8c64fd06f398a73ad47c6447df9:0"), |
|
p1, 10000, 1) |
|
wallet._utxos.add_utxo(utxostr_to_utxo( |
|
"4bda19e193781fb899511a052717fa38cd4d341a4f6dc29b6cb10c854c29e76b:0"), |
|
p2, 10000, 1) |
|
signresult_and_signedpsbt, err = wallet.sign_psbt(base64.b64decode( |
|
psbt.encode("ascii")),with_sign_result=True) |
|
assert not err |
|
signresult, signedpsbt = signresult_and_signedpsbt |
|
if partial: |
|
assert not signresult.is_final |
|
assert signresult.num_inputs_signed == 2 |
|
assert signresult.num_inputs_final == 2 |
|
else: |
|
assert signresult.is_final |
|
assert signresult.num_inputs_signed == 3 |
|
assert signresult.num_inputs_final == 3 |
|
print(PSBTWalletMixin.human_readable_psbt(signedpsbt)) |
|
bitcoin.select_chain_params("bitcoin/regtest") |
|
|
|
def test_create_and_sign_psbt_with_legacy(setup_psbt_wallet): |
|
""" The purpose of this test is to check that we can create and |
|
then partially sign a PSBT where we own one input and the other input |
|
is of legacy p2pkh type. |
|
""" |
|
wallet_service = make_wallets(1, [[1,0,0,0,0]], 1)[0]['wallet'] |
|
wallet_service.sync_wallet(fast=True) |
|
utxos = wallet_service.select_utxos(0, bitcoin.coins_to_satoshi(0.5)) |
|
assert len(utxos) == 1 |
|
# create a legacy address and make a payment into it |
|
legacy_addr = bitcoin.CCoinAddress.from_scriptPubKey( |
|
bitcoin.pubkey_to_p2pkh_script( |
|
bitcoin.privkey_to_pubkey(b"\x01"*33))) |
|
tx = direct_send(wallet_service, bitcoin.coins_to_satoshi(0.3), 0, |
|
str(legacy_addr), accept_callback=dummy_accept_callback, |
|
info_callback=dummy_info_callback, |
|
return_transaction=True) |
|
assert tx |
|
# this time we will have one utxo worth <~ 0.7 |
|
my_utxos = wallet_service.select_utxos(0, bitcoin.coins_to_satoshi(0.5)) |
|
assert len(my_utxos) == 1 |
|
# find the outpoint for the legacy address we're spending |
|
n = -1 |
|
for i, t in enumerate(tx.vout): |
|
if bitcoin.CCoinAddress.from_scriptPubKey(t.scriptPubKey) == legacy_addr: |
|
n = i |
|
assert n > -1 |
|
utxos = copy.deepcopy(my_utxos) |
|
utxos[(tx.GetTxid()[::-1], n)] ={"script": legacy_addr.to_scriptPubKey(), |
|
"value": bitcoin.coins_to_satoshi(0.3)} |
|
outs = [{"value": bitcoin.coins_to_satoshi(0.998), |
|
"address": wallet_service.get_addr(0,0,0)}] |
|
tx2 = bitcoin.mktx(list(utxos.keys()), outs) |
|
spent_outs = wallet_service.witness_utxos_to_psbt_utxos(my_utxos) |
|
spent_outs.append(tx) |
|
new_psbt = wallet_service.create_psbt_from_tx(tx2, spent_outs, |
|
force_witness_utxo=False) |
|
signed_psbt_and_signresult, err = wallet_service.sign_psbt( |
|
new_psbt.serialize(), with_sign_result=True) |
|
assert err is None |
|
signresult, signed_psbt = signed_psbt_and_signresult |
|
assert signresult.num_inputs_signed == 1 |
|
assert signresult.num_inputs_final == 1 |
|
assert not signresult.is_final |
|
|
|
@pytest.mark.parametrize('unowned_utxo, wallet_cls', [ |
|
(True, SegwitLegacyWallet), |
|
(False, SegwitLegacyWallet), |
|
(True, SegwitWallet), |
|
(False, SegwitWallet), |
|
(True, LegacyWallet), |
|
(False, LegacyWallet), |
|
]) |
|
def test_create_psbt_and_sign(setup_psbt_wallet, unowned_utxo, wallet_cls): |
|
""" Plan of test: |
|
1. Create a wallet and source 3 destination addresses. |
|
2. Make, and confirm, transactions that fund the 3 addrs. |
|
3. Create a new tx spending 2 of those 3 utxos and spending |
|
another utxo we don't own (extra is optional per `unowned_utxo`). |
|
4. Create a psbt using the above transaction and corresponding |
|
`spent_outs` field to fill in the redeem script. |
|
5. Compare resulting PSBT with expected structure. |
|
6. Use the wallet's sign_psbt method to sign the whole psbt, which |
|
means signing each input we own. |
|
7. Check that each input is finalized as per expected. Check that the whole |
|
PSBT is or is not finalized as per whether there is an unowned utxo. |
|
8. In case where whole psbt is finalized, attempt to broadcast the tx. |
|
""" |
|
# steps 1 and 2: |
|
wallet_service = make_wallets(1, [[3,0,0,0,0]], 1, |
|
wallet_cls=wallet_cls)[0]['wallet'] |
|
wallet_service.sync_wallet(fast=True) |
|
utxos = wallet_service.select_utxos(0, bitcoin.coins_to_satoshi(1.5)) |
|
# for legacy wallets, psbt creation requires querying for the spending |
|
# transaction: |
|
if wallet_cls == LegacyWallet: |
|
fulltxs = [] |
|
for utxo, v in utxos.items(): |
|
fulltxs.append(jm_single().bc_interface.get_deser_from_gettransaction( |
|
jm_single().bc_interface.get_transaction(utxo[0]))) |
|
|
|
assert len(utxos) == 2 |
|
u_utxos = {} |
|
if unowned_utxo: |
|
# note: tx creation uses the key only; psbt creation uses the value, |
|
# which can be fake here; we do not intend to attempt to fully |
|
# finalize a psbt with an unowned input. See |
|
# https://github.com/Simplexum/python-bitcointx/issues/30 |
|
# the redeem script creation (which is artificial) will be |
|
# avoided in future. |
|
priv = b"\xaa"*32 + b"\x01" |
|
pub = bitcoin.privkey_to_pubkey(priv) |
|
script = bitcoin.pubkey_to_p2sh_p2wpkh_script(pub) |
|
redeem_script = bitcoin.pubkey_to_p2wpkh_script(pub) |
|
u_utxos[(b"\xaa"*32, 12)] = {"value": 1000, "script": script} |
|
utxos.update(u_utxos) |
|
# outputs aren't interesting for this test (we selected 1.5 but will get 2): |
|
outs = [{"value": bitcoin.coins_to_satoshi(1.999), |
|
"address": wallet_service.get_addr(0,0,0)}] |
|
tx = bitcoin.mktx(list(utxos.keys()), outs) |
|
|
|
if wallet_cls != LegacyWallet: |
|
spent_outs = wallet_service.witness_utxos_to_psbt_utxos(utxos) |
|
force_witness_utxo=True |
|
else: |
|
spent_outs = fulltxs |
|
# the extra input is segwit: |
|
if unowned_utxo: |
|
spent_outs.extend( |
|
wallet_service.witness_utxos_to_psbt_utxos(u_utxos)) |
|
force_witness_utxo=False |
|
newpsbt = wallet_service.create_psbt_from_tx(tx, spent_outs, |
|
force_witness_utxo=force_witness_utxo) |
|
# see note above |
|
if unowned_utxo: |
|
newpsbt.inputs[-1].redeem_script = redeem_script |
|
print(bintohex(newpsbt.serialize())) |
|
print("human readable: ") |
|
print(wallet_service.human_readable_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 |
|
assert len(newpsbt.inputs) == expected_inputs_length |
|
assert len(newpsbt.outputs) == 1 |
|
# note: redeem_script field is a CScript which is a bytes instance, |
|
# so checking length is best way to check for existence (comparison |
|
# with None does not work): |
|
if wallet_cls == SegwitLegacyWallet: |
|
assert len(newpsbt.inputs[0].redeem_script) != 0 |
|
assert len(newpsbt.inputs[1].redeem_script) != 0 |
|
if unowned_utxo: |
|
assert newpsbt.inputs[2].redeem_script == redeem_script |
|
|
|
signed_psbt_and_signresult, err = wallet_service.sign_psbt( |
|
newpsbt.serialize(), with_sign_result=True) |
|
assert err is None |
|
signresult, signed_psbt = signed_psbt_and_signresult |
|
expected_signed_inputs = len(utxos) if not unowned_utxo else len(utxos)-1 |
|
assert signresult.num_inputs_signed == expected_signed_inputs |
|
assert signresult.num_inputs_final == expected_signed_inputs |
|
|
|
if not unowned_utxo: |
|
assert signresult.is_final |
|
# only in case all signed do we try to broadcast: |
|
extracted_tx = signed_psbt.extract_transaction().serialize() |
|
assert jm_single().bc_interface.pushtx(extracted_tx) |
|
else: |
|
# transaction extraction must fail for not-fully-signed psbts: |
|
with pytest.raises(ValueError) as e: |
|
extracted_tx = signed_psbt.extract_transaction() |
|
|
|
@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), |
|
]) |
|
def test_payjoin_workflow(setup_psbt_wallet, payment_amt, wallet_cls_sender, |
|
wallet_cls_receiver): |
|
""" Workflow step 1: |
|
Create a payment from a wallet, and create a finalized PSBT. |
|
This step is fairly trivial as the functionality is built-in to |
|
PSBTWalletMixin. |
|
Note that only Segwit* wallets are supported for PayJoin. |
|
|
|
Workflow step 2: |
|
Receiver creates a new partially signed PSBT with the same amount |
|
and at least one more utxo. |
|
|
|
Workflow step 3: |
|
Given a partially signed PSBT created by a receiver, here the sender |
|
completes (co-signs) the PSBT they are given. Note this code is a PSBT |
|
functionality check, and does NOT include the detailed checks that |
|
the sender should perform before agreeing to sign (see: |
|
https://github.com/btcpayserver/btcpayserver-doc/blob/eaac676866a4d871eda5fd7752b91b88fdf849ff/Payjoin-spec.md#receiver-side |
|
). |
|
""" |
|
|
|
wallet_r = make_wallets(1, [[3,0,0,0,0]], 1, |
|
wallet_cls=wallet_cls_receiver)[0]["wallet"] |
|
wallet_s = make_wallets(1, [[3,0,0,0,0]], 1, |
|
wallet_cls=wallet_cls_sender)[0]["wallet"] |
|
for w in [wallet_r, wallet_s]: |
|
w.sync_wallet(fast=True) |
|
|
|
# destination address for payment: |
|
destaddr = str(bitcoin.CCoinAddress.from_scriptPubKey( |
|
bitcoin.pubkey_to_p2wpkh_script(bitcoin.privkey_to_pubkey(b"\x01"*33)))) |
|
|
|
payment_amt = bitcoin.coins_to_satoshi(payment_amt) |
|
|
|
# *** STEP 1 *** |
|
# ************** |
|
|
|
# create a normal tx from the sender wallet: |
|
payment_psbt = direct_send(wallet_s, payment_amt, 0, destaddr, |
|
accept_callback=dummy_accept_callback, |
|
info_callback=dummy_info_callback, |
|
with_final_psbt=True) |
|
|
|
print("Initial payment PSBT created:\n{}".format( |
|
wallet_s.human_readable_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: |
|
assert any([out_amts[i] == payment_amt for i in [0, 1]]) |
|
|
|
# ensure that we can actually broadcast the created tx: |
|
# (note that 'extract_transaction' represents an implicit |
|
# PSBT finality check). |
|
extracted_tx = payment_psbt.extract_transaction().serialize() |
|
# don't want to push the tx right now, because of test structure |
|
# (in production code this isn't really needed, we will not |
|
# produce invalid payment transactions). |
|
res = jm_single().bc_interface.testmempoolaccept(bintohex(extracted_tx)) |
|
assert res[0]["allowed"], "Payment transaction was rejected from mempool." |
|
|
|
# *** STEP 2 *** |
|
# ************** |
|
|
|
# Simple receiver utxo choice heuristic. |
|
# For more generality we test with two receiver-utxos, not one. |
|
all_receiver_utxos = wallet_r.get_all_utxos() |
|
# TODO is there a less verbose way to get any 2 utxos from the dict? |
|
receiver_utxos_keys = list(all_receiver_utxos.keys())[:2] |
|
receiver_utxos = {k: v for k, v in all_receiver_utxos.items( |
|
) if k in receiver_utxos_keys} |
|
|
|
# receiver will do other checks as discussed above, including payment |
|
# amount; as discussed above, this is out of the scope of this PSBT test. |
|
|
|
# construct unsigned tx for payjoin-psbt: |
|
payjoin_tx_inputs = [(x.prevout.hash[::-1], |
|
x.prevout.n) for x in payment_psbt.unsigned_tx.vin] |
|
payjoin_tx_inputs.extend(receiver_utxos.keys()) |
|
# find payment output and change output |
|
pay_out = None |
|
change_out = None |
|
for o in payment_psbt.unsigned_tx.vout: |
|
jm_out_fmt = {"value": o.nValue, |
|
"address": str(bitcoin.CCoinAddress.from_scriptPubKey( |
|
o.scriptPubKey))} |
|
if o.nValue == payment_amt: |
|
assert pay_out is None |
|
pay_out = jm_out_fmt |
|
else: |
|
assert change_out is None |
|
change_out = jm_out_fmt |
|
|
|
# we now know there were two outputs and know which is payment. |
|
# bump payment output with our input: |
|
outs = [pay_out, change_out] |
|
our_inputs_val = sum([v["value"] for _, v in receiver_utxos.items()]) |
|
pay_out["value"] += our_inputs_val |
|
print("we bumped the payment output value by: ", our_inputs_val) |
|
print("It is now: ", pay_out["value"]) |
|
unsigned_payjoin_tx = bitcoin.make_shuffled_tx(payjoin_tx_inputs, outs, |
|
version=payment_psbt.unsigned_tx.nVersion, |
|
locktime=payment_psbt.unsigned_tx.nLockTime) |
|
print("we created this unsigned tx: ") |
|
print(bitcoin.human_readable_transaction(unsigned_payjoin_tx)) |
|
# to create the PSBT we need the spent_outs for each input, |
|
# in the right order: |
|
spent_outs = [] |
|
for i, inp in enumerate(unsigned_payjoin_tx.vin): |
|
input_found = False |
|
for j, inp2 in enumerate(payment_psbt.unsigned_tx.vin): |
|
if inp.prevout == inp2.prevout: |
|
spent_outs.append(payment_psbt.inputs[j].utxo) |
|
input_found = True |
|
break |
|
if input_found: |
|
continue |
|
# if we got here this input is ours, we must find |
|
# it from our original utxo choice list: |
|
for ru in receiver_utxos.keys(): |
|
if (inp.prevout.hash[::-1], inp.prevout.n) == ru: |
|
spent_outs.append( |
|
wallet_r.witness_utxos_to_psbt_utxos( |
|
{ru: receiver_utxos[ru]})[0]) |
|
input_found = True |
|
break |
|
# there should be no other inputs: |
|
assert input_found |
|
|
|
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.human_readable_psbt(r_payjoin_psbt))) |
|
|
|
signresultandpsbt, err = wallet_r.sign_psbt(r_payjoin_psbt.serialize(), |
|
with_sign_result=True) |
|
assert not err, err |
|
signresult, receiver_signed_psbt = signresultandpsbt |
|
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.human_readable_psbt(receiver_signed_psbt))) |
|
|
|
# *** STEP 3 *** |
|
# ************** |
|
|
|
# take the half-signed PSBT, validate and co-sign: |
|
|
|
signresultandpsbt, err = wallet_s.sign_psbt( |
|
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.human_readable_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.human_readable_psbt( |
|
bitcoin.PartiallySignedTransaction.from_binary(hextobin(v)))) |
|
bitcoin.select_chain_params("bitcoin/regtest") |
|
|
|
@pytest.fixture(scope="module") |
|
def setup_psbt_wallet(): |
|
load_test_config()
|
|
|