finalize foreign single sig outputs from PSBT partial signatures
This commit is contained in:
parent
ef72dc00ae
commit
e021fc7317
@ -33,6 +33,7 @@ This lists the new changes that have not yet been published in a normal release.
|
||||
- Bugfix: Virtual Disk auto mode stuck on "Reading..." screen
|
||||
- Bugfix: Do not allow to change Main PIN to value already used as Trick PIN even if
|
||||
Trick PIN is hidden.
|
||||
- Bugfix: Finalization of foreign inputs from partial signatures. Thanks Christian Uebber
|
||||
- Change: `Destroy Seed` also removes all Trick PINs from SE2.
|
||||
|
||||
|
||||
|
||||
@ -2238,6 +2238,22 @@ class psbtObject(psbtProxy):
|
||||
sigs = sigs[:self.active_multisig.M]
|
||||
return sigs
|
||||
|
||||
def singlesig_signature(self, inp):
|
||||
# return signature that we added
|
||||
# or one signature from partial sigs if input is fully sign
|
||||
# (i.e. len(part_sigs)>=len(subpaths))
|
||||
ssig = None
|
||||
if inp.added_sigs:
|
||||
# we have added signature to this single sig input
|
||||
assert len(inp.added_sigs) == 1
|
||||
ssig = list(inp.added_sigs.items())[0]
|
||||
elif inp.part_sigs and inp.fully_signed:
|
||||
assert len(inp.part_sigs) == 1
|
||||
rv = list(inp.part_sigs.items())[0]
|
||||
ssig = rv[0], self.get(rv[1])
|
||||
|
||||
return ssig
|
||||
|
||||
def multisig_xfps_needed(self):
|
||||
# provide the set of xfp's that still need to sign PSBT
|
||||
# - used to find which multisig-signer needs to go next
|
||||
@ -2275,10 +2291,14 @@ class psbtObject(psbtProxy):
|
||||
fd.write(ser_compact_size(self.num_inputs))
|
||||
for in_idx, txi in self.input_iter():
|
||||
inp = self.inputs[in_idx]
|
||||
# only finalize if input with signatures added by us
|
||||
assert inp.added_sigs, 'No signature on input #%d' % in_idx
|
||||
|
||||
# first check - if no signature(s) - fail soon
|
||||
if inp.is_multisig:
|
||||
assert self.multi_input_complete(inp), 'Incomplete signature set on input #%d' % in_idx
|
||||
else:
|
||||
# single signature
|
||||
ssig = self.singlesig_signature(inp)
|
||||
assert ssig, 'No signature on input #%d' % in_idx
|
||||
|
||||
if inp.is_segwit:
|
||||
if inp.is_multisig:
|
||||
@ -2307,7 +2327,7 @@ class psbtObject(psbtProxy):
|
||||
txi.scriptSig = ss
|
||||
|
||||
else:
|
||||
pubkey, der_sig = list(inp.added_sigs.items())[0]
|
||||
pubkey, der_sig = ssig
|
||||
txi.scriptSig = ser_push_data(der_sig) + ser_push_data(pubkey)
|
||||
|
||||
fd.write(txi.serialize())
|
||||
@ -2329,14 +2349,14 @@ class psbtObject(psbtProxy):
|
||||
for in_idx, wit in self.input_witness_iter():
|
||||
inp = self.inputs[in_idx]
|
||||
|
||||
if inp.is_segwit and inp.added_sigs:
|
||||
if inp.is_segwit:
|
||||
# put in new sig: wit is a CTxInWitness
|
||||
assert not wit.scriptWitness.stack, 'replacing non-empty?'
|
||||
if inp.is_multisig:
|
||||
sigs = self.multisig_signatures(inp)
|
||||
wit.scriptWitness.stack = [b""] + sigs + [self.get(inp.witness_script)]
|
||||
else:
|
||||
pubkey, der_sig = list(inp.added_sigs.items())[0]
|
||||
pubkey, der_sig = self.singlesig_signature(inp)
|
||||
assert pubkey[0] in {0x02, 0x03} and len(pubkey) == 33, "bad v0 pubkey"
|
||||
wit.scriptWitness.stack = [der_sig, pubkey]
|
||||
|
||||
|
||||
@ -758,7 +758,7 @@ def test_big_txn(num_in, num_out, dev, quick_start_hsm, hsm_status, is_simulator
|
||||
attempt_psbt(psbt)
|
||||
|
||||
|
||||
@pytest.mark.veryslow
|
||||
@pytest.mark.manual
|
||||
def test_multiple_signings(dev, quick_start_hsm, is_simulator,
|
||||
attempt_psbt, fake_txn, load_hsm_users,
|
||||
auth_user):
|
||||
@ -774,7 +774,7 @@ def test_multiple_signings(dev, quick_start_hsm, is_simulator,
|
||||
attempt_psbt(psbt)
|
||||
|
||||
|
||||
@pytest.mark.veryslow
|
||||
@pytest.mark.manual
|
||||
@pytest.mark.parametrize("cc_first", [True, False])
|
||||
@pytest.mark.parametrize("M_N", [(2,3), (3,5), (15,15)])
|
||||
def test_multiple_signings_multisig(cc_first, M_N, dev, quick_start_hsm,
|
||||
|
||||
@ -1548,7 +1548,7 @@ def test_ms_sign_myself(M, use_regtest, make_myself_wallet, segwit, num_ins, dev
|
||||
assert is_complete
|
||||
|
||||
@pytest.mark.parametrize('addr_fmt', ['p2wsh', 'p2sh-p2wsh'])
|
||||
@pytest.mark.parametrize('acct_num', [ 0, None, 4321])
|
||||
@pytest.mark.parametrize('acct_num', [None, 4321])
|
||||
@pytest.mark.parametrize('M_N', [(2,3), (8,14)])
|
||||
@pytest.mark.parametrize('way', ["sd", "qr"])
|
||||
@pytest.mark.parametrize('incl_self', [True, False, None])
|
||||
|
||||
@ -16,7 +16,7 @@ from helpers import B2A, fake_dest_addr, parse_change_back, addr_from_display_fo
|
||||
from helpers import xfp2str, seconds2human_readable, hash160
|
||||
from msg import verify_message
|
||||
from bip32 import BIP32Node
|
||||
from constants import ADDR_STYLES, ADDR_STYLES_SINGLE, SIGHASH_MAP
|
||||
from constants import ADDR_STYLES, ADDR_STYLES_SINGLE, SIGHASH_MAP, simulator_fixed_xfp
|
||||
from txn import *
|
||||
from ctransaction import CTransaction, CTxOut, CTxIn, COutPoint
|
||||
from ckcc_protocol.constants import STXN_VISUALIZE, STXN_SIGNED
|
||||
@ -2070,8 +2070,8 @@ def _test_single_sig_sighash(cap_story, press_select, start_sign, end_sign, dev,
|
||||
|
||||
return doit
|
||||
|
||||
# TODO Locktime test MUST be run with --psbt2 flag on and off
|
||||
# pytest test_sign.py -k locktime {--psbt2,}
|
||||
# TODO Sighash test MUST be run with --psbt2 flag on and off
|
||||
# pytest test_sign.py -k sighash {--psbt2,}
|
||||
|
||||
@pytest.mark.bitcoind
|
||||
@pytest.mark.parametrize("addr_fmt", ["legacy", "p2sh-segwit", "bech32"])
|
||||
@ -3081,4 +3081,59 @@ def test_mk4_done_signing_infinite_loop(goto_home, try_sign, fake_txn, enable_hw
|
||||
if had_vdisk:
|
||||
enable_hw_ux("vdisk")
|
||||
|
||||
|
||||
@pytest.mark.bitcoind
|
||||
def test_finalize_with_foreign_inputs(bitcoind, bitcoind_d_sim_watch, start_sign, end_sign,
|
||||
cap_story, try_sign_microsd):
|
||||
# foreign inputs that have partial sigs filled
|
||||
# we still do not care about final_scriptsig & final_scriptwitness PSBT fields
|
||||
dest_address = bitcoind.supply_wallet.getnewaddress()
|
||||
alice = bitcoind.create_wallet(wallet_name="alice")
|
||||
bob = bitcoind.create_wallet(wallet_name="bob")
|
||||
cc = bitcoind_d_sim_watch
|
||||
alice_addr = alice.getnewaddress()
|
||||
bob_addr = bob.getnewaddress()
|
||||
cc_addr = cc.getnewaddress()
|
||||
# fund all addresses
|
||||
for addr in (alice_addr, bob_addr, cc_addr):
|
||||
bitcoind.supply_wallet.sendtoaddress(addr, 2.0)
|
||||
|
||||
# mine above sends
|
||||
bitcoind.supply_wallet.generatetoaddress(1, bitcoind.supply_wallet.getnewaddress())
|
||||
|
||||
psbt_list = []
|
||||
for w in (alice, bob, cc):
|
||||
assert w.listunspent()
|
||||
psbt = w.walletcreatefundedpsbt([], [{dest_address: 1.0}], 0, {"fee_rate": 20})["psbt"]
|
||||
psbt_list.append(psbt)
|
||||
|
||||
# join PSBTs to one
|
||||
the_psbt = bitcoind.supply_wallet.joinpsbts(psbt_list)
|
||||
|
||||
# bitcoin core would just fill finalscriptwitness, we need partial signatures
|
||||
# just add dummy signatures and remove
|
||||
pp = BasicPSBT().parse(base64.b64decode(the_psbt))
|
||||
for i in pp.inputs:
|
||||
assert len(i.bip32_paths) == 1 # single sigs
|
||||
der = list(i.bip32_paths.values())[0]
|
||||
if der[:4].hex().upper() == xfp2str(simulator_fixed_xfp):
|
||||
# our key
|
||||
continue
|
||||
pubkey = list(i.bip32_paths.keys())[0]
|
||||
assert not i.part_sigs # empty
|
||||
i.part_sigs[pubkey] = os.urandom(71) # dummy sig
|
||||
|
||||
# USB works and our signature is added (but only if we do not finalize)
|
||||
psbt = pp.as_bytes()
|
||||
start_sign(psbt)
|
||||
signed = end_sign(accept=True)
|
||||
assert signed != psbt
|
||||
for i in BasicPSBT().parse(signed).inputs:
|
||||
assert i.part_sigs
|
||||
|
||||
try_sign_microsd(psbt, finalize=True, accept=True)
|
||||
title, story = cap_story()
|
||||
assert title == "PSBT Signed"
|
||||
assert "Finalized transaction (ready for broadcast)" in story
|
||||
|
||||
# EOF
|
||||
|
||||
Loading…
Reference in New Issue
Block a user