Reject witness-only UTXO for legacy inputs; Suppress fee for unverified witness UTXOs;normalize legacy inputs to proper utxo
This commit is contained in:
parent
d5aba396a6
commit
59eb529a20
@ -9,6 +9,9 @@ This lists the new changes that have not yet been published in a normal release.
|
||||
- Enhancement: WIF Store export watch-only descriptor
|
||||
- Enhancement: WIF Store address detection without the need for PSBT_IN_BIP32_DERIVATION (Electrum support)
|
||||
- Enhancement: Improve USB length validation
|
||||
- Bugfix: Fixes legacy input amount spoofing by rejecting witness-utxo-only PSBT inputs when Coldcard is expected to sign a non-segwit input.
|
||||
When both UTXO fields are present the full non_witness_utxo is now preferred for amount/script lookup. Thanks, @Damir
|
||||
- Bugfix: Emit warning and do not calculate fee for legacy UTXOs with only witness utxo
|
||||
- Bugfix: Disable Virtual Disk and NFC before activating HSM
|
||||
- Bugfix: Custom address default menu position wrong
|
||||
- Bugfix: Delta Mode Trick PIN was never restored from backup
|
||||
|
||||
@ -720,49 +720,58 @@ class psbtInputProxy(psbtProxy):
|
||||
fd = self.fd
|
||||
old_pos = fd.tell()
|
||||
|
||||
if self.witness_utxo:
|
||||
# Going forward? Just what we will witness; no other junk
|
||||
# - prefer this format, altho does that imply segwit txn must be generated?
|
||||
# - I don't know why we wouldn't always use this
|
||||
# - once we use this partial utxo data, we must create witness data out
|
||||
if self.utxo:
|
||||
# skip over all the parts of the txn we don't care about, without
|
||||
# fully parsing it... pull out a single TXO
|
||||
fd.seek(self.utxo[0])
|
||||
|
||||
_, marker, flags = unpack("<iBB", fd.read(6))
|
||||
wit_format = (marker == 0 and flags != 0x0)
|
||||
if not wit_format:
|
||||
# rewind back over marker+flags
|
||||
fd.seek(-2, 1)
|
||||
|
||||
# How many ins? We accept zero here because utxo's inputs might have been
|
||||
# trimmed to save space, and we have test cases like that.
|
||||
num_in = deser_compact_size(fd)
|
||||
_skip_n_objs(fd, num_in, 'CTxIn')
|
||||
|
||||
num_out = deser_compact_size(fd)
|
||||
assert idx < num_out, "not enuf outs"
|
||||
_skip_n_objs(fd, idx, 'CTxOut')
|
||||
|
||||
fd.seek(self.witness_utxo[0])
|
||||
utxo = CTxOut()
|
||||
utxo.deserialize(fd)
|
||||
|
||||
# ... followed by more outs, and maybe witness data, but we don't care ...
|
||||
|
||||
fd.seek(old_pos)
|
||||
|
||||
return utxo
|
||||
|
||||
assert self.utxo, 'no utxo'
|
||||
|
||||
# skip over all the parts of the txn we don't care about, without
|
||||
# fully parsing it... pull out a single TXO
|
||||
fd.seek(self.utxo[0])
|
||||
|
||||
_, marker, flags = unpack("<iBB", fd.read(6))
|
||||
wit_format = (marker == 0 and flags != 0x0)
|
||||
if not wit_format:
|
||||
# rewind back over marker+flags
|
||||
fd.seek(-2, 1)
|
||||
|
||||
# How many ins? We accept zero here because utxo's inputs might have been
|
||||
# trimmed to save space, and we have test cases like that.
|
||||
num_in = deser_compact_size(fd)
|
||||
_skip_n_objs(fd, num_in, 'CTxIn')
|
||||
|
||||
num_out = deser_compact_size(fd)
|
||||
assert idx < num_out, "not enuf outs"
|
||||
_skip_n_objs(fd, idx, 'CTxOut')
|
||||
assert self.witness_utxo, 'no utxo'
|
||||
|
||||
fd.seek(self.witness_utxo[0])
|
||||
utxo = CTxOut()
|
||||
utxo.deserialize(fd)
|
||||
|
||||
# ... followed by more outs, and maybe witness data, but we don't care ...
|
||||
|
||||
fd.seek(old_pos)
|
||||
|
||||
return utxo
|
||||
|
||||
def witness_utxo_is_provably_segwit(self, utxo):
|
||||
af, addr_or_pubkey, addr_is_segwit = utxo.get_address()
|
||||
if addr_is_segwit:
|
||||
return True
|
||||
|
||||
if af != AF_P2SH or not self.redeem_script:
|
||||
return False
|
||||
|
||||
redeem_script = self.get(self.redeem_script)
|
||||
return redeem_script[0] == 0 and \
|
||||
((len(redeem_script) == 22 and redeem_script[1] == 20) or
|
||||
(len(redeem_script) == 34 and redeem_script[1] == 32)) and \
|
||||
hash160(redeem_script) == addr_or_pubkey
|
||||
|
||||
def determine_my_signing_key(self, my_idx, utxo, my_xfp, psbt, cosign_xfp=None):
|
||||
# See what it takes to sign this particular input
|
||||
# - type of script
|
||||
@ -1831,6 +1840,7 @@ class psbtObject(psbtProxy):
|
||||
# hashes match, and what values are we getting?
|
||||
# Important: parse incoming UTXO to build total input value
|
||||
foreign = []
|
||||
unverified_witness_utxo = []
|
||||
total_in = 0
|
||||
from_wif_store = []
|
||||
prevouts = set()
|
||||
@ -1860,12 +1870,18 @@ class psbtObject(psbtProxy):
|
||||
|
||||
assert utxo.nValue >= 0, "negative input value: i%d" % i
|
||||
total_in += utxo.nValue
|
||||
if not inp.utxo and not inp.witness_utxo_is_provably_segwit(utxo):
|
||||
unverified_witness_utxo.append(i)
|
||||
|
||||
# Look at what kind of input this will be, and therefore what
|
||||
# type of signing will be required, and which key we need.
|
||||
# - also validates redeem_script when present
|
||||
# - also finds appropriate multisig wallet to be used
|
||||
inp.determine_my_signing_key(i, utxo, self.my_xfp, self, cosign_xfp)
|
||||
|
||||
if inp.required_key and not inp.is_segwit and not inp.utxo:
|
||||
raise FatalPSBTIssue('Legacy input #%d requires non-witness UTXO' % i)
|
||||
|
||||
# determine_my_signing_key is updating fully_signed for multisig inputs
|
||||
# based on redeem/witness script
|
||||
if inp.fully_signed:
|
||||
@ -1898,7 +1914,7 @@ class psbtObject(psbtProxy):
|
||||
|
||||
# XXX scan witness data provided, and consider those ins signed if not multisig?
|
||||
|
||||
if not foreign:
|
||||
if not foreign and not unverified_witness_utxo:
|
||||
# no foreign inputs, we can calculate the total input value
|
||||
self.total_value_in = total_in
|
||||
assert total_in > 0 or self.por322, "zero value txn"
|
||||
@ -1907,9 +1923,15 @@ class psbtObject(psbtProxy):
|
||||
# OK for multi-party transactions (coinjoin etc.)
|
||||
assert not self.por322 # cannot have foreign inputs in POR txn
|
||||
self.total_value_in = None
|
||||
self.warnings.append(
|
||||
("Unable to calculate fee", "Some input(s) haven't provided UTXO(s): " + seq_to_str(foreign))
|
||||
)
|
||||
if foreign:
|
||||
self.warnings.append(
|
||||
("Unable to calculate fee", "Some input(s) haven't provided UTXO(s): " + seq_to_str(foreign))
|
||||
)
|
||||
if unverified_witness_utxo:
|
||||
self.warnings.append(
|
||||
("Unable to calculate fee", "Some input(s) provided unverified witness UTXO(s): " +
|
||||
seq_to_str(unverified_witness_utxo))
|
||||
)
|
||||
|
||||
if len(self.presigned_inputs) == self.num_inputs:
|
||||
# Maybe wrong for multisig cases? Maybe they want to add their
|
||||
|
||||
@ -517,6 +517,42 @@ class BasicPSBT:
|
||||
def as_b64_str(self):
|
||||
return b64encode(self.as_bytes()).decode()
|
||||
|
||||
def convert_witness_utxo_to_utxo(self, idx):
|
||||
# Test helper: the original prev txn cannot be reconstructed from a
|
||||
# witness_utxo, so retarget this PSBT input to a synthetic funding txn.
|
||||
inp = self.inputs[idx]
|
||||
assert inp.witness_utxo
|
||||
assert inp.utxo is None
|
||||
|
||||
prev_txo = CTxOut()
|
||||
prev_txo.deserialize(io.BytesIO(inp.witness_utxo))
|
||||
|
||||
if self.is_v2():
|
||||
assert inp.prevout_idx is not None
|
||||
prevout_idx = inp.prevout_idx
|
||||
else:
|
||||
assert self.parsed_txn
|
||||
txin = self.parsed_txn.vin[idx]
|
||||
prevout_idx = txin.prevout.n
|
||||
|
||||
funding = CTransaction()
|
||||
funding.nVersion = 2
|
||||
funding.vin = [CTxIn(COutPoint(0, 0xffffffff), nSequence=0xffffffff)]
|
||||
funding.vout = [CTxOut(0, b'') for _ in range(prevout_idx)]
|
||||
funding.vout.append(prev_txo)
|
||||
funding.calc_sha256()
|
||||
|
||||
inp.utxo = funding.serialize_with_witness()
|
||||
inp.witness_utxo = None
|
||||
|
||||
if self.is_v2():
|
||||
inp.previous_txid = ser_uint256(funding.sha256)
|
||||
else:
|
||||
txin.prevout.hash = funding.sha256
|
||||
self.txn = self.parsed_txn.serialize_with_witness()
|
||||
|
||||
return funding
|
||||
|
||||
def to_v2(self):
|
||||
if self.version is None or self.version == 0:
|
||||
self.version = 2
|
||||
|
||||
@ -1279,7 +1279,7 @@ def fake_ms_txn(pytestconfig):
|
||||
# - but has UTXO's to match needs
|
||||
from struct import pack
|
||||
|
||||
def doit(num_ins, num_outs, M, keys, fee=10000, outvals=None, segwit_in=False,
|
||||
def doit(num_ins, num_outs, M, keys, fee=10000, outvals=None,
|
||||
outstyles=['p2pkh'], change_outputs=[], incl_xpubs=False, hack_psbt=None,
|
||||
hack_change_out=False, input_amount=1E8, psbt_v2=None, bip67=True,
|
||||
violate_script_key_order=False, path_mapper=None, inp_af=AF_P2WSH,
|
||||
@ -1329,21 +1329,14 @@ def fake_ms_txn(pytestconfig):
|
||||
)
|
||||
|
||||
# lots of supporting details needed for p2sh inputs
|
||||
if inp_af:
|
||||
if inp_af == AF_P2WSH:
|
||||
psbt.inputs[i].witness_script = script
|
||||
elif inp_af == AF_P2SH:
|
||||
psbt.inputs[i].redeem_script = script
|
||||
else:
|
||||
assert inp_af == AF_P2WSH_P2SH
|
||||
psbt.inputs[i].witness_script = script
|
||||
psbt.inputs[i].redeem_script = b'\0\x20' + sha256(script).digest()
|
||||
|
||||
if inp_af == AF_P2WSH:
|
||||
psbt.inputs[i].witness_script = script
|
||||
elif inp_af == AF_P2SH:
|
||||
psbt.inputs[i].redeem_script = script
|
||||
else:
|
||||
if segwit_in:
|
||||
psbt.inputs[i].witness_script = script
|
||||
else:
|
||||
psbt.inputs[i].redeem_script = script
|
||||
assert inp_af == AF_P2WSH_P2SH
|
||||
psbt.inputs[i].witness_script = script
|
||||
psbt.inputs[i].redeem_script = b'\0\x20' + sha256(script).digest()
|
||||
|
||||
for pubkey, xfp_path in details:
|
||||
psbt.inputs[i].bip32_paths[pubkey] = b''.join(pack('<I', j) for j in xfp_path)
|
||||
@ -1359,10 +1352,10 @@ def fake_ms_txn(pytestconfig):
|
||||
|
||||
supply.vout.append(CTxOut(int(input_amount), scriptPubKey))
|
||||
|
||||
if not segwit_in:
|
||||
psbt.inputs[i].utxo = supply.serialize_with_witness()
|
||||
else:
|
||||
if inp_af in [AF_P2WSH, AF_P2WSH_P2SH]:
|
||||
psbt.inputs[i].witness_utxo = supply.vout[-1].serialize()
|
||||
else:
|
||||
psbt.inputs[i].utxo = supply.serialize_with_witness()
|
||||
|
||||
if lock_time and not i:
|
||||
seq = 0xfffffffd
|
||||
@ -1532,9 +1525,9 @@ def test_1of1_multisig_sign(finalize, clear_ms, import_ms_wallet, fake_ms_txn, s
|
||||
@pytest.mark.bitcoind
|
||||
@pytest.mark.parametrize('num_ins', [ 15 ])
|
||||
@pytest.mark.parametrize('M', [ 2, 4, 1])
|
||||
@pytest.mark.parametrize('segwit', [True, False])
|
||||
@pytest.mark.parametrize('addr_fmt', [AF_P2SH, AF_P2WSH])
|
||||
@pytest.mark.parametrize('incl_xpubs', [ True, False ])
|
||||
def test_ms_sign_myself(M, use_regtest, make_myself_wallet, segwit, num_ins, dev, clear_ms,
|
||||
def test_ms_sign_myself(M, use_regtest, make_myself_wallet, addr_fmt, num_ins, dev, clear_ms,
|
||||
fake_ms_txn, try_sign, incl_xpubs, bitcoind, sim_root_dir):
|
||||
|
||||
# IMPORTANT: wont work if you start simulator with --ms flag. Use no args
|
||||
@ -1550,9 +1543,9 @@ def test_ms_sign_myself(M, use_regtest, make_myself_wallet, segwit, num_ins, dev
|
||||
N = len(keys)
|
||||
assert M<=N
|
||||
|
||||
psbt = fake_ms_txn(num_ins, num_outs, M, keys, segwit_in=segwit, incl_xpubs=incl_xpubs,
|
||||
psbt = fake_ms_txn(num_ins, num_outs, M, keys, incl_xpubs=incl_xpubs,
|
||||
outstyles=all_out_styles, change_outputs=list(range(1,num_outs)),
|
||||
inp_af=AF_P2SH)
|
||||
inp_af=addr_fmt)
|
||||
|
||||
with open(f'{sim_root_dir}/debug/myself-before.psbt', 'w') as f:
|
||||
f.write(b64encode(psbt).decode())
|
||||
@ -3114,7 +3107,7 @@ def test_ms_wallet_ordering(clear_ms, import_ms_wallet, try_sign_microsd, fake_m
|
||||
name = f'ms2'
|
||||
keys3 = import_ms_wallet(3, 5, name=name, accept=1, do_import=True, addr_fmt="p2wsh")
|
||||
|
||||
psbt = fake_ms_txn(5, 5, 3, keys3, outstyles=all_out_styles, segwit_in=True, incl_xpubs=True)
|
||||
psbt = fake_ms_txn(5, 5, 3, keys3, outstyles=all_out_styles, incl_xpubs=True)
|
||||
|
||||
try_sign_microsd(psbt, encoding='base64')
|
||||
|
||||
@ -3135,12 +3128,12 @@ def test_ms_xpub_ordering(descriptor, m_n, clear_ms, make_multisig, import_ms_wa
|
||||
import_ms_wallet(M, N, keys=opt, name=name, accept=1, do_import=True,
|
||||
addr_fmt="p2wsh", descriptor=descriptor)
|
||||
psbt = fake_ms_txn(5, 5, M, opt, outstyles=all_out_styles,
|
||||
segwit_in=True, incl_xpubs=True)
|
||||
incl_xpubs=True)
|
||||
try_sign_microsd(psbt, encoding='base64')
|
||||
for opt_1 in all_options:
|
||||
# create PSBT with original keys order
|
||||
psbt = fake_ms_txn(5, 5, M, opt_1, outstyles=all_out_styles,
|
||||
segwit_in=True, incl_xpubs=True)
|
||||
incl_xpubs=True)
|
||||
try_sign_microsd(psbt, encoding='base64')
|
||||
|
||||
|
||||
@ -4102,7 +4095,7 @@ def test_change_output_script_type(clear_ms, import_ms_wallet, start_sign, end_s
|
||||
sign_check(psbt)
|
||||
|
||||
psbt = fake_ms_txn(2, 2, M, keys, force_outstyle="p2sh-p2wsh",
|
||||
change_outputs=[0,1], inp_af=AF_P2SH, segwit_in=True)
|
||||
change_outputs=[0,1], inp_af=AF_P2SH)
|
||||
|
||||
sign_check(psbt)
|
||||
|
||||
|
||||
@ -559,6 +559,9 @@ def test_change_case(start_sign, use_regtest, end_sign, check_against_bitcoind,
|
||||
chg_addr = 'mvBGHpVtTyjmcfSsy6f715nbTGvwgbgbwo'
|
||||
|
||||
psbt = open('data/example-change.psbt', 'rb').read()
|
||||
b4 = BasicPSBT().parse(psbt)
|
||||
b4.convert_witness_utxo_to_utxo(0)
|
||||
psbt = b4.as_bytes()
|
||||
|
||||
start_sign(psbt)
|
||||
|
||||
@ -568,7 +571,6 @@ def test_change_case(start_sign, use_regtest, end_sign, check_against_bitcoind,
|
||||
assert split_sory[0] == "Change back:"
|
||||
assert chg_addr == addr_from_display_format(split_sory[-1])
|
||||
|
||||
b4 = BasicPSBT().parse(psbt)
|
||||
check_against_bitcoind(B2A(b4.txn), Decimal('0.00000294'), change_outs=[1,])
|
||||
|
||||
signed = end_sign(True)
|
||||
@ -610,6 +612,7 @@ def test_change_fraud_path(start_sign, use_regtest, end_sign, case, check_agains
|
||||
|
||||
psbt = open('data/example-change.psbt', 'rb').read()
|
||||
b4 = BasicPSBT().parse(psbt)
|
||||
b4.convert_witness_utxo_to_utxo(0)
|
||||
|
||||
(pubkey, path), = b4.outputs[1].bip32_paths.items()
|
||||
skp = bytearray(b4.outputs[1].bip32_paths[pubkey])
|
||||
@ -654,6 +657,7 @@ def test_change_fraud_addr(start_sign, end_sign, use_regtest, check_against_bitc
|
||||
|
||||
psbt = open('data/example-change.psbt', 'rb').read()
|
||||
b4 = BasicPSBT().parse(psbt)
|
||||
b4.convert_witness_utxo_to_utxo(0)
|
||||
|
||||
# tweak output addr to garbage
|
||||
t = CTransaction()
|
||||
@ -691,6 +695,7 @@ def test_change_p2sh_p2wpkh(start_sign, end_sign, check_against_bitcoind, use_re
|
||||
psbt = f.read()
|
||||
|
||||
b4 = BasicPSBT().parse(psbt)
|
||||
b4.convert_witness_utxo_to_utxo(0)
|
||||
|
||||
t = CTransaction()
|
||||
t.deserialize(BytesIO(b4.txn))
|
||||
@ -840,15 +845,16 @@ def test_sign_multisig_partial_fail(start_sign, end_sign):
|
||||
def test_sign_wutxo(start_sign, set_seed_words, end_sign, cap_story, sim_exec, sim_execfile,
|
||||
sim_root_dir):
|
||||
|
||||
# Example from SomberNight: we can sign it, but signature won't be accepted by
|
||||
# network because the PSBT lies about the UTXO amount and tries to give away to miners,
|
||||
# as overly-large fee.
|
||||
# Example from SomberNight, normalized to use a full UTXO so this test still
|
||||
# reaches the fee display path after legacy witness-only UTXOs are rejected.
|
||||
|
||||
set_seed_words('fault lava rice chest uncle exclude power tornado catalog stool'
|
||||
' swear rival sun aspect oyster deer pepper exchange scrap toward'
|
||||
' mix second world shaft')
|
||||
|
||||
in_psbt = a2b_hex(open('data/snight-example.psbt', 'rb').read()[:-1])
|
||||
snight = BasicPSBT().parse(a2b_hex(open('data/snight-example.psbt', 'rb').read()[:-1]))
|
||||
snight.convert_witness_utxo_to_utxo(0)
|
||||
in_psbt = snight.as_bytes()
|
||||
|
||||
for fin in (False, True):
|
||||
start_sign(in_psbt, finalize=fin)
|
||||
@ -1136,6 +1142,7 @@ def test_change_troublesome(dev, start_sign, cap_story, try_path, expect, sim_ro
|
||||
|
||||
psbt = open('data/example-change.psbt', 'rb').read()
|
||||
b4 = BasicPSBT().parse(psbt)
|
||||
b4.convert_witness_utxo_to_utxo(0)
|
||||
|
||||
pubkey = a2b_hex('03c80814536f8e801859fc7c2e5129895b261153f519d4f3418ffb322884a7d7e1')
|
||||
path = [int(p) if ("'" not in p) else 0x80000000+int(p[:-1])
|
||||
@ -1769,6 +1776,104 @@ def test_own_utxo_missing(segwit_in, num_missing, dev, fake_txn, start_sign, cap
|
||||
assert "Missing own UTXO(s)" in story
|
||||
press_cancel()
|
||||
|
||||
def _replace_input_utxo_with_witness_utxo(psbt, idx):
|
||||
inp = psbt.inputs[idx]
|
||||
txn = CTransaction()
|
||||
txn.deserialize(BytesIO(inp.utxo))
|
||||
assert len(txn.vout) == 1
|
||||
inp.witness_utxo = txn.vout[0].serialize()
|
||||
inp.utxo = None
|
||||
|
||||
def test_nested_segwit_witness_utxo_only_fee_shown(dev, fake_txn, start_sign,
|
||||
cap_story, end_sign):
|
||||
psbt = fake_txn(1, 2, dev.master_xpub, segwit_in=True, wrapped=True)
|
||||
start_sign(psbt)
|
||||
time.sleep(.1)
|
||||
title, story = cap_story()
|
||||
assert title == "OK TO SEND?"
|
||||
assert "Network fee" in story
|
||||
assert "unverified witness UTXO" not in story
|
||||
end_sign(accept=True)
|
||||
|
||||
def test_own_legacy_witness_utxo_only_fails(dev, fake_txn, start_sign, cap_story, press_cancel):
|
||||
def hack(psbt):
|
||||
_replace_input_utxo_with_witness_utxo(psbt, 0)
|
||||
|
||||
psbt = fake_txn(1, 2, dev.master_xpub, segwit_in=False, psbt_hacker=hack)
|
||||
start_sign(psbt)
|
||||
time.sleep(.1)
|
||||
title, story = cap_story()
|
||||
assert title == "Failure"
|
||||
assert "Legacy input #0 requires non-witness UTXO" in story
|
||||
press_cancel()
|
||||
|
||||
def test_foreign_legacy_witness_utxo_only_ok(dev, fake_txn, start_sign, cap_story, end_sign):
|
||||
def hack(psbt):
|
||||
pk = list(psbt.inputs[1].bip32_paths.keys())[0]
|
||||
pp = psbt.inputs[1].bip32_paths[pk]
|
||||
psbt.inputs[1].bip32_paths[pk] = b'what' + pp[4:]
|
||||
_replace_input_utxo_with_witness_utxo(psbt, 1)
|
||||
|
||||
psbt = fake_txn(2, 2, dev.master_xpub, segwit_in=False, psbt_hacker=hack)
|
||||
start_sign(psbt)
|
||||
time.sleep(.1)
|
||||
title, story = cap_story()
|
||||
assert title == "OK TO SEND?"
|
||||
assert "Limited Signing" in story
|
||||
assert "Unable to calculate fee" in story
|
||||
assert "Some input(s) provided unverified witness UTXO(s): 1" in story
|
||||
assert "Legacy input #1 requires non-witness UTXO" not in story
|
||||
signed = end_sign(accept=True)
|
||||
assert signed != psbt
|
||||
|
||||
def test_mismatched_p2sh_witness_program_unverified(dev, fake_txn, start_sign,
|
||||
cap_story, end_sign):
|
||||
def hack(psbt):
|
||||
pk = list(psbt.inputs[1].bip32_paths.keys())[0]
|
||||
pp = psbt.inputs[1].bip32_paths[pk]
|
||||
psbt.inputs[1].bip32_paths[pk] = b'what' + pp[4:]
|
||||
|
||||
inp = psbt.inputs[1]
|
||||
txn = CTransaction()
|
||||
txn.deserialize(BytesIO(inp.utxo))
|
||||
assert len(txn.vout) == 1
|
||||
|
||||
redeem_script = bytes([0, 20]) + os.urandom(32)
|
||||
inp.redeem_script = redeem_script
|
||||
inp.witness_utxo = CTxOut(txn.vout[0].nValue,
|
||||
bytes([0xa9, 0x14]) + hash160(redeem_script) + bytes([0x87])).serialize()
|
||||
inp.utxo = None
|
||||
|
||||
psbt = fake_txn(2, 2, dev.master_xpub, segwit_in=False, psbt_hacker=hack)
|
||||
start_sign(psbt)
|
||||
time.sleep(.1)
|
||||
title, story = cap_story()
|
||||
assert title == "OK TO SEND?"
|
||||
assert "Limited Signing" in story
|
||||
assert "Unable to calculate fee" in story
|
||||
assert "Some input(s) provided unverified witness UTXO(s): 1" in story
|
||||
assert "Network fee" not in story
|
||||
signed = end_sign(accept=True)
|
||||
assert signed != psbt
|
||||
|
||||
def test_presigned_own_legacy_witness_utxo_only_ok(dev, fake_txn, start_sign, cap_story, end_sign):
|
||||
def hack(psbt):
|
||||
pubkey = list(psbt.inputs[1].bip32_paths.keys())[0]
|
||||
psbt.inputs[1].part_sigs[pubkey] = os.urandom(71)
|
||||
_replace_input_utxo_with_witness_utxo(psbt, 1)
|
||||
|
||||
psbt = fake_txn(2, 2, dev.master_xpub, segwit_in=False, psbt_hacker=hack)
|
||||
start_sign(psbt)
|
||||
time.sleep(.1)
|
||||
title, story = cap_story()
|
||||
assert title == "OK TO SEND?"
|
||||
assert "Partly Signed Already" in story
|
||||
assert "Unable to calculate fee" in story
|
||||
assert "Some input(s) provided unverified witness UTXO(s): 1" in story
|
||||
assert "Legacy input #1 requires non-witness UTXO" not in story
|
||||
signed = end_sign(accept=True)
|
||||
assert signed != psbt
|
||||
|
||||
@pytest.mark.bitcoind
|
||||
def test_bitcoind_missing_foreign_utxo(bitcoind, bitcoind_d_sim_watch, microsd_path, try_sign):
|
||||
# batch tx created from three different psbts (using joinpsbts)
|
||||
|
||||
@ -734,7 +734,7 @@ def test_teleport_real_ms(dev, fake_ms_txn):
|
||||
# match the default paths created by CC in airgapped MS wallet creation.
|
||||
return str_to_path(deriv)
|
||||
|
||||
psbt = fake_ms_txn(3, 2, M, keys, fee=10000, outvals=None, segwit_in=False,
|
||||
psbt = fake_ms_txn(3, 2, M, keys, fee=10000, outvals=None, inp_af=AF_P2WSH,
|
||||
outstyles=['p2pkh'], change_outputs=[], incl_xpubs=False,
|
||||
hack_change_out=False, input_amount=1E8, path_mapper=p2wsh_mapper)
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user