improve re-export UX; unify USB to done_signing; bugfixes
This commit is contained in:
parent
8a18e413e9
commit
3239dc6cd5
@ -1878,9 +1878,9 @@ async def ready2sign(*a):
|
||||
Put the proposed transaction onto MicroSD card \
|
||||
in PSBT format (Partially Signed Bitcoin Transaction) \
|
||||
or upload a transaction to be signed \
|
||||
from your desktop wallet software or command line tools.\n\n'''
|
||||
from your desktop wallet software or command line tools.'''
|
||||
|
||||
footnotes = ("\n\nYou will always be prompted to confirm the details "
|
||||
footnotes = ("You will always be prompted to confirm the details "
|
||||
"before any signature is performed.")
|
||||
|
||||
# if we have only one SD card inserted, at this point, we know no PSBTs on them
|
||||
|
||||
203
shared/auth.py
203
shared/auth.py
@ -8,17 +8,15 @@ from ubinascii import b2a_base64, a2b_base64
|
||||
from ubinascii import hexlify as b2a_hex
|
||||
from ubinascii import unhexlify as a2b_hex
|
||||
from uhashlib import sha256
|
||||
from public_constants import MSG_SIGNING_MAX_LENGTH, SUPPORTED_ADDR_FORMATS
|
||||
from public_constants import AFC_SCRIPT, AF_CLASSIC, AFC_BECH32, AF_P2WPKH, AF_P2WPKH_P2SH
|
||||
from public_constants import AFC_SCRIPT, AF_CLASSIC, AFC_BECH32, SUPPORTED_ADDR_FORMATS
|
||||
from public_constants import STXN_FINALIZE, STXN_VISUALIZE, STXN_SIGNED
|
||||
from sffile import SFFile
|
||||
from ux import ux_aborted, ux_show_story, abort_and_goto, ux_dramatic_pause, ux_clear_keys
|
||||
from ux import show_qr_code, OK, X, ux_input_text, ux_enter_bip32_index, abort_and_push
|
||||
from ux import ux_show_story, abort_and_goto, ux_dramatic_pause, ux_clear_keys
|
||||
from ux import show_qr_code, OK, X, abort_and_push, AbortInteraction
|
||||
from usb import CCBusyError
|
||||
from utils import HexWriter, xfp2str, problem_file_line, cleanup_deriv_path, chunk_checksum
|
||||
from utils import B2A, to_ascii_printable, show_single_address
|
||||
from utils import HexWriter, xfp2str, problem_file_line, cleanup_deriv_path, B2A, show_single_address
|
||||
from psbt import psbtObject, FatalPSBTIssue, FraudulentChangeOutput
|
||||
from files import CardSlot, CardMissingError, needs_microsd
|
||||
from files import CardSlot, CardMissingError
|
||||
from exceptions import HSMDenied
|
||||
from version import MAX_TXN_LEN
|
||||
from charcodes import KEY_QR, KEY_NFC, KEY_ENTER, KEY_CANCEL, KEY_LEFT, KEY_RIGHT
|
||||
@ -74,7 +72,7 @@ class UserAuthorizedAction:
|
||||
if allowed_cls and isinstance(cls.active_request, allowed_cls):
|
||||
return
|
||||
|
||||
# check if UX actally was cleared, and we're not really doing that anymore; recover
|
||||
# check if UX actually was cleared, and we're not really doing that anymore; recover
|
||||
# - happens if USB caller never comes back for their final results
|
||||
from ux import the_ux
|
||||
top_ux = the_ux.top_of_stack()
|
||||
@ -264,20 +262,25 @@ async def try_push_tx(data, txid, txn_sha=None):
|
||||
return False
|
||||
|
||||
class ApproveTransaction(UserAuthorizedAction):
|
||||
def __init__(self, psbt_len, flags=0x0, approved_cb=None, psbt_sha=None, cb_kws=None):
|
||||
from glob import settings
|
||||
def __init__(self, psbt_len, flags=None, psbt_sha=None, input_method=None,
|
||||
output_encoder=None, filename=None):
|
||||
super().__init__()
|
||||
self.psbt_len = psbt_len
|
||||
self.do_finalize = bool(flags & STXN_FINALIZE)
|
||||
self.do_visualize = bool(flags & STXN_VISUALIZE)
|
||||
|
||||
# do finalize is None if not USB, None = decide based on is_complete
|
||||
if flags is None:
|
||||
self.do_finalize = self.do_visualize = None
|
||||
else:
|
||||
self.do_finalize = bool(flags & STXN_FINALIZE)
|
||||
self.do_visualize = bool(flags & STXN_VISUALIZE)
|
||||
|
||||
self.stxn_flags = flags
|
||||
self.psbt = None
|
||||
self.psbt_sha = psbt_sha
|
||||
self.approved_cb = approved_cb
|
||||
self.input_method = input_method
|
||||
self.output_encoder = output_encoder
|
||||
self.filename = filename
|
||||
self.result = None # will be (len, sha256) of the resulting PSBT
|
||||
self.cb_kws = cb_kws or {}
|
||||
self.is_sd = (self.cb_kws.get("input_method", None) is None) \
|
||||
and (not self.cb_kws.get("force_vdisk", False))
|
||||
self.chain = chains.current_chain()
|
||||
|
||||
def render_output(self, o):
|
||||
@ -447,7 +450,7 @@ class ApproveTransaction(UserAuthorizedAction):
|
||||
if needs_txn_explorer:
|
||||
esc += "2"
|
||||
msg.write(" Press (2) to explore txn.")
|
||||
if self.is_sd and CardSlot.both_inserted():
|
||||
if (self.input_method == "sd") and CardSlot.both_inserted():
|
||||
esc += "b"
|
||||
msg.write(" (B) to write to lower SD slot.")
|
||||
msg.write(" %s to abort." % X)
|
||||
@ -516,70 +519,17 @@ class ApproveTransaction(UserAuthorizedAction):
|
||||
except BaseException as exc:
|
||||
return await self.failure("Signing failed late", exc)
|
||||
|
||||
if self.approved_cb:
|
||||
if self.is_sd and (ch == "b"):
|
||||
self.cb_kws["slot_b"] = True
|
||||
|
||||
await self.approved_cb(self.psbt, **self.cb_kws)
|
||||
|
||||
self.done()
|
||||
return
|
||||
|
||||
txid = None
|
||||
try:
|
||||
# re-serialize the PSBT back out
|
||||
with SFFile(TXN_OUTPUT_OFFSET, max_size=MAX_TXN_LEN, message="Saving...") as fd:
|
||||
if self.do_finalize:
|
||||
# user is forcing us to finalize over USB
|
||||
# try it & fail with detailed msg
|
||||
txid = self.psbt.finalize(fd)
|
||||
else:
|
||||
self.psbt.serialize(fd)
|
||||
|
||||
self.result = (fd.tell(), fd.checksum.digest())
|
||||
|
||||
self.done(redraw=(not txid))
|
||||
|
||||
await done_signing(self.psbt, self, self.input_method, self.filename, self.output_encoder,
|
||||
slot_b=True if ch == "b" else False, finalize=self.do_finalize)
|
||||
self.done()
|
||||
except AbortInteraction:
|
||||
# user might have sent new sign cmd, while we still at export prompt
|
||||
pass
|
||||
except BaseException as exc:
|
||||
# sys.print_exception(exc)
|
||||
return await self.failure("PSBT output failed", exc)
|
||||
|
||||
from glob import NFC
|
||||
|
||||
if self.do_finalize and txid and not hsm_active:
|
||||
# Unsigned PSBT came in via NFC or QR or Teleport
|
||||
|
||||
if await try_push_tx(self.result[0], txid, self.result[1]):
|
||||
return # success, exit
|
||||
|
||||
kq, kn = "(1)", "(3)"
|
||||
if version.has_qwerty:
|
||||
kq, kn = KEY_QR, KEY_NFC
|
||||
|
||||
while 1:
|
||||
# Show txid when we can; advisory
|
||||
# - maybe even as QR, hex-encoded in alnum mode
|
||||
tmsg = txid + '\n\nPress %s for QR Code of TXID. ' % kq
|
||||
|
||||
if NFC:
|
||||
tmsg += 'Press %s to share signed txn via NFC.' % kn
|
||||
|
||||
ch = await ux_show_story(tmsg, "Final TXID", escape='13'+KEY_NFC+KEY_QR)
|
||||
|
||||
if ch in '1'+KEY_QR:
|
||||
await show_qr_code(txid, True)
|
||||
continue
|
||||
|
||||
if ch in KEY_NFC+"3" and NFC:
|
||||
await NFC.share_signed_txn(txid, TXN_OUTPUT_OFFSET,
|
||||
self.result[0], self.result[1])
|
||||
continue
|
||||
break
|
||||
|
||||
elif version.has_qwerty and self.psbt.active_multisig:
|
||||
# Offer to teleport the result, which still needs signatures
|
||||
from teleport import kt_send_psbt
|
||||
|
||||
await kt_send_psbt(self.psbt, self.result[0], post_signing=True)
|
||||
|
||||
async def txn_explorer(self):
|
||||
# Page through unlimited-sized transaction details
|
||||
@ -761,7 +711,7 @@ def sign_transaction(psbt_len, flags=0x0, psbt_sha=None):
|
||||
# transaction (binary) loaded into PSRAM already, checksum checked
|
||||
UserAuthorizedAction.check_busy(ApproveTransaction)
|
||||
UserAuthorizedAction.active_request = ApproveTransaction(
|
||||
psbt_len, flags, psbt_sha=psbt_sha, cb_kws={"input_method": "usb"}
|
||||
psbt_len, flags, psbt_sha=psbt_sha, input_method="usb",
|
||||
)
|
||||
|
||||
# kill any menu stack, and put our thing at the top
|
||||
@ -789,19 +739,24 @@ def psbt_encoding_taster(taste, psbt_len):
|
||||
return decoder, output_encoder, psbt_len
|
||||
|
||||
|
||||
async def done_signing(psbt, input_method=None, filename=None, force_vdisk=False,
|
||||
async def done_signing(psbt, tx_req, input_method=None, filename=None,
|
||||
output_encoder=None, slot_b=False, finalize=None):
|
||||
# User authorized PSBT for signing, and we added signatures.
|
||||
# - allow PushTX if enabled (first thing)
|
||||
# - can save final TXN out to SD card/VirtDisk, share by NFC, QR.
|
||||
|
||||
from glob import dis, PSRAM
|
||||
from files import CardSlot, CardMissingError
|
||||
from glob import PSRAM, hsm_active
|
||||
from sffile import SFFile
|
||||
from ux import show_qr_code, import_export_prompt, ux_show_story
|
||||
from ux import show_qr_code, import_export_prompt
|
||||
|
||||
first_time = True
|
||||
msg = None
|
||||
title = None
|
||||
|
||||
txid = None
|
||||
is_complete = psbt.is_complete()
|
||||
if finalize is not None:
|
||||
# USB case - user can choose whether to attempt finalization
|
||||
is_complete = finalize
|
||||
|
||||
with SFFile(TXN_OUTPUT_OFFSET, max_size=MAX_TXN_LEN, message="Saving...") as psram:
|
||||
if is_complete:
|
||||
@ -815,9 +770,18 @@ async def done_signing(psbt, input_method=None, filename=None, force_vdisk=False
|
||||
data_len = psram.tell()
|
||||
data_sha2 = psram.checksum.digest()
|
||||
|
||||
UserAuthorizedAction.cleanup()
|
||||
if input_method == "usb":
|
||||
# return result over USB before going to all options
|
||||
tx_req.result = data_len, data_sha2
|
||||
if hsm_active:
|
||||
# it is enough to just return back via USB, other options
|
||||
# are pointless
|
||||
return
|
||||
|
||||
first_time = False
|
||||
msg = noun + " shared via USB."
|
||||
title = "PSBT Signed"
|
||||
|
||||
first_time = True
|
||||
if txid and await try_push_tx(data_len, txid, data_sha2):
|
||||
# go directly to reexport menu after pushTX
|
||||
first_time = False
|
||||
@ -830,32 +794,45 @@ async def done_signing(psbt, input_method=None, filename=None, force_vdisk=False
|
||||
while True:
|
||||
ch = None
|
||||
if first_time:
|
||||
# first time, assume they want to send out same way it came in -- dont prompt
|
||||
# first time, assume they want to send out same way it came in -- don't prompt
|
||||
if input_method == "qr":
|
||||
ch = KEY_QR
|
||||
elif input_method == "nfc":
|
||||
ch = KEY_NFC
|
||||
elif input_method == "kt":
|
||||
# not reached .. because we offer KT method before this is called
|
||||
ch = 't'
|
||||
else:
|
||||
# SD/VDisk
|
||||
ch = {"force_vdisk": force_vdisk, "slot_b": slot_b}
|
||||
ch = {"force_vdisk": input_method == "vdisk", "slot_b": slot_b}
|
||||
|
||||
if not ch:
|
||||
# show all possible export options (based on hardware enabled, features)
|
||||
ch = await import_export_prompt(noun, no_qr=not version.has_qr, offer_kt=offer_kt)
|
||||
intro = []
|
||||
if msg:
|
||||
intro.append(msg)
|
||||
if txid:
|
||||
intro.append('TXID:\n' + txid)
|
||||
|
||||
ch = await import_export_prompt(noun, intro="\n\n".join(intro), offer_kt=offer_kt,
|
||||
txid=txid, title=title)
|
||||
if ch == KEY_CANCEL:
|
||||
UserAuthorizedAction.cleanup()
|
||||
break
|
||||
|
||||
elif txid and (ch == '6'):
|
||||
await show_qr_code(txid, is_alnum=True, force_msg=True)
|
||||
continue
|
||||
|
||||
elif ch == KEY_QR:
|
||||
here = PSRAM.read_at(TXN_OUTPUT_OFFSET, data_len)
|
||||
msg = txid or 'Partly Signed PSBT'
|
||||
try:
|
||||
if len(here) > 920:
|
||||
# too big for simple QR - use BBQr instead
|
||||
raise ValueError
|
||||
hex_here = b2a_hex(here).upper().decode()
|
||||
await show_qr_code(hex_here, is_alnum=True, msg=msg)
|
||||
except (ValueError, RuntimeError):
|
||||
except (ValueError, RuntimeError, TypeError):
|
||||
from ux_q1 import show_bbqr_codes
|
||||
await show_bbqr_codes('T' if txid else 'P', here, msg)
|
||||
|
||||
@ -871,33 +848,26 @@ async def done_signing(psbt, input_method=None, filename=None, force_vdisk=False
|
||||
|
||||
msg = noun + " shared via NFC."
|
||||
|
||||
elif ch == 't':
|
||||
# after saving to card, they might want to teleport it
|
||||
elif (ch == 't') and not is_complete:
|
||||
# they might want to teleport it, but only if we have PSBT
|
||||
# there is no need to teleport PSBT if txn is already complete & ready to be broadcast
|
||||
from teleport import kt_send_psbt
|
||||
ok = await kt_send_psbt(psbt)
|
||||
msg = 'Failed to Teleport' if not ok else 'Sent by Teleport'
|
||||
ok, num_sigs_needed = await kt_send_psbt(psbt, data_len)
|
||||
title = 'Sent by Teleport' if ok else 'Failed to Teleport'
|
||||
if num_sigs_needed > 0:
|
||||
s, aux = ("", "is") if num_sigs_needed == 1 else ("s", "are")
|
||||
msg = "%d more signature%s %s still required." % (num_sigs_needed, s, aux)
|
||||
continue
|
||||
|
||||
else:
|
||||
# typical case: save to SD card, show filenames we used
|
||||
assert isinstance(ch, dict)
|
||||
msg = await _save_to_disk(psbt, txid, ch, is_complete, data_len, output_encoder, filename)
|
||||
if not msg:
|
||||
# it failed so start again with all possible options enabled.
|
||||
ch = None
|
||||
input_method = None
|
||||
first_time = False
|
||||
continue
|
||||
msg = await _save_to_disk(psbt, txid, ch, is_complete, data_len,
|
||||
output_encoder, filename)
|
||||
|
||||
msg += '\n\n'
|
||||
esc = '0'
|
||||
msg += 'Press (0) to save again by another method.'
|
||||
|
||||
ch2 = await ux_show_story(msg, title='PSBT Signed', escape=esc)
|
||||
if ch2 != '0':
|
||||
break
|
||||
else:
|
||||
input_method = None
|
||||
first_time = False
|
||||
input_method = None
|
||||
first_time = False
|
||||
title = "PSBT Signed"
|
||||
|
||||
async def _save_to_disk(psbt, txid, save_options, is_complete, data_len, output_encoder, filename=None):
|
||||
# Saving a PSBT from PSRAM to something disk-like.
|
||||
@ -995,7 +965,7 @@ async def _save_to_disk(psbt, txid, save_options, is_complete, data_len, output_
|
||||
|
||||
except OSError as exc:
|
||||
prob = 'Failed to write!\n\n%s\n\n' % exc
|
||||
sys.print_exception(exc)
|
||||
# sys.print_exception(exc)
|
||||
# fall through to try again
|
||||
|
||||
# If this point reached, some problem, we could not write.
|
||||
@ -1023,8 +993,6 @@ async def _save_to_disk(psbt, txid, save_options, is_complete, data_len, output_
|
||||
|
||||
if out2_fn:
|
||||
msg += 'Finalized transaction (ready for broadcast):\n\n%s' % out2_fn
|
||||
if txid and not del_after:
|
||||
msg += '\n\nFinal TXID:\n' + txid
|
||||
|
||||
return msg
|
||||
|
||||
@ -1085,11 +1053,8 @@ async def sign_psbt_file(filename, force_vdisk=False, slot_b=None, just_read=Fal
|
||||
|
||||
UserAuthorizedAction.cleanup()
|
||||
UserAuthorizedAction.active_request = ApproveTransaction(
|
||||
psbt_len,
|
||||
approved_cb=done_signing,
|
||||
cb_kws={"filename": filename,
|
||||
"force_vdisk": force_vdisk,
|
||||
"output_encoder": output_encoder}
|
||||
psbt_len, input_method="vdisk" if force_vdisk else "sd",
|
||||
filename=filename, output_encoder=output_encoder,
|
||||
)
|
||||
if ux_abort:
|
||||
# needed for auto vdisk mode
|
||||
|
||||
@ -18,9 +18,7 @@ async def export_by_qr(body, label, type_code, force_bbqr=False):
|
||||
from ux import show_qr_code
|
||||
|
||||
try:
|
||||
# ignore label/title - provides no useful info
|
||||
# makes qr smaller and harder to read
|
||||
if force_bbqr:
|
||||
if force_bbqr or len(body) > 2000:
|
||||
raise ValueError
|
||||
|
||||
await show_qr_code(body)
|
||||
@ -35,7 +33,7 @@ async def export_by_qr(body, label, type_code, force_bbqr=False):
|
||||
return
|
||||
|
||||
|
||||
async def export_contents(title, contents, fname_pattern, derive=None, addr_fmt=None, no_qr=None,
|
||||
async def export_contents(title, contents, fname_pattern, derive=None, addr_fmt=None,
|
||||
is_json=False, force_bbqr=False, force_prompt=False):
|
||||
# export text and json files while offering NFC, QR & Vdisk
|
||||
# produces signed export in case of SD/Vdisk (signed with key at deriv and addr_fmt)
|
||||
@ -49,8 +47,9 @@ async def export_contents(title, contents, fname_pattern, derive=None, addr_fmt=
|
||||
dis.fullscreen('Generating...')
|
||||
contents, derive, addr_fmt = contents()
|
||||
|
||||
if no_qr is not None:
|
||||
no_qr = not version.has_qwerty and (len(contents) >= MAX_V11_CHAR_LIMIT)
|
||||
# figure out if offering QR code export make sense given HW
|
||||
# len() is O(1)
|
||||
no_qr = not version.has_qwerty and (len(contents) >= MAX_V11_CHAR_LIMIT)
|
||||
|
||||
sig = not (derive is None and addr_fmt is None)
|
||||
|
||||
|
||||
@ -420,8 +420,7 @@ async def ux_sign_msg(txt, approved_cb=None, kill_menu=True):
|
||||
the_ux.push(MenuSystem(rv))
|
||||
|
||||
async def msg_signing_done(signature, address, text):
|
||||
ch = await import_export_prompt("Signed Msg", is_import=False,
|
||||
no_qr=not version.has_qwerty)
|
||||
ch = await import_export_prompt("Signed Msg")
|
||||
if ch == KEY_CANCEL:
|
||||
return
|
||||
|
||||
|
||||
@ -6,7 +6,7 @@ import stash, chains, ustruct, ure, uio, sys, ngu, uos, ujson, version
|
||||
from utils import xfp2str, str2xfp, swab32, cleanup_deriv_path, keypath_to_str, to_ascii_printable
|
||||
from utils import str_to_keypath, problem_file_line, parse_extended_key, get_filesize, extract_cosigner
|
||||
from ux import ux_show_story, ux_confirm, ux_dramatic_pause, ux_clear_keys
|
||||
from ux import import_export_prompt, ux_enter_bip32_index, ux_enter_number, OK, X
|
||||
from ux import ux_enter_bip32_index, ux_enter_number, OK, X
|
||||
from files import CardSlot, CardMissingError, needs_microsd
|
||||
from descriptor import MultisigDescriptor, multisig_descriptor_template
|
||||
from public_constants import AF_P2SH, AF_P2WSH_P2SH, AF_P2WSH, AFC_SCRIPT, MAX_SIGNERS, AF_CLASSIC
|
||||
|
||||
@ -523,7 +523,6 @@ class NFCHandler:
|
||||
from auth import psbt_encoding_taster, TXN_INPUT_OFFSET
|
||||
from auth import UserAuthorizedAction, ApproveTransaction
|
||||
from ux import the_ux
|
||||
from auth import done_signing
|
||||
from sffile import SFFile
|
||||
|
||||
data = await self.start_nfc_rx()
|
||||
@ -567,10 +566,9 @@ class NFCHandler:
|
||||
# start signing UX
|
||||
UserAuthorizedAction.cleanup()
|
||||
UserAuthorizedAction.active_request = ApproveTransaction(
|
||||
psbt_len, 0x0, psbt_sha=psbt_sha,
|
||||
approved_cb=done_signing,
|
||||
cb_kws={"input_method": "nfc",
|
||||
"output_encoder": output_encoder})
|
||||
psbt_len, psbt_sha=psbt_sha, input_method="nfc",
|
||||
output_encoder=output_encoder
|
||||
)
|
||||
# kill any menu stack, and put our thing at the top
|
||||
the_ux.push(UserAuthorizedAction.active_request)
|
||||
|
||||
|
||||
@ -543,9 +543,9 @@ async def start_export(notes):
|
||||
singular = (len(notes) == 1)
|
||||
|
||||
item = notes[0].type_label if singular else 'all notes & passwords'
|
||||
choice = await import_export_prompt(item, is_import=False, title="Data Export", no_nfc=True,
|
||||
footnotes="\n\nWARNING: No encryption happens here. "
|
||||
"Your secrets will be cleartext.")
|
||||
choice = await import_export_prompt(item, title="Data Export", no_nfc=True,
|
||||
footnotes="WARNING: No encryption happens here."
|
||||
" Your secrets will be cleartext.")
|
||||
if choice == KEY_CANCEL:
|
||||
return
|
||||
|
||||
|
||||
@ -81,7 +81,16 @@ class QRDisplaySingle(UserInteraction):
|
||||
|
||||
# draw display
|
||||
dis.busy_bar(False)
|
||||
dis.draw_qr_display(self.qr_data, self.msg or body, self.is_alnum,
|
||||
|
||||
if self.msg:
|
||||
msg = self.msg
|
||||
else:
|
||||
msg = None
|
||||
if isinstance(body, str) and not has_qwerty:
|
||||
# on Mk4 if no self.msg, we show part of the body
|
||||
msg = body
|
||||
|
||||
dis.draw_qr_display(self.qr_data, msg, self.is_alnum,
|
||||
self.sidebar, self.idx_hint(), self.invert,
|
||||
is_addr=self.is_addrs, force_msg=self.force_msg)
|
||||
|
||||
@ -103,7 +112,7 @@ class QRDisplaySingle(UserInteraction):
|
||||
break
|
||||
else:
|
||||
# Share any QR over NFC!
|
||||
await NFC.share_text(self.addrs[self.idx], secret=self.secret)
|
||||
await NFC.share_text(self.addrs[self.idx], is_secret=self.is_secret)
|
||||
self.redraw()
|
||||
continue
|
||||
elif ch in 'xy'+KEY_ENTER+KEY_CANCEL:
|
||||
|
||||
@ -4,11 +4,10 @@
|
||||
# secure environment of two Q's.
|
||||
#
|
||||
import ngu, aes256ctr, bip39, json, ndef, chains
|
||||
from io import BytesIO
|
||||
from utils import xfp2str, deserialize_secret
|
||||
from ubinascii import unhexlify as a2b_hex
|
||||
from ubinascii import hexlify as b2a_hex
|
||||
from glob import settings
|
||||
from glob import settings, dis
|
||||
from ux import ux_show_story, ux_confirm, the_ux, ux_dramatic_pause
|
||||
from ux_q1 import show_bbqr_codes, QRScannerInteraction, ux_input_text
|
||||
from charcodes import KEY_QR, KEY_NFC, KEY_CANCEL
|
||||
@ -192,8 +191,9 @@ WARNING: Receiver will have full access to all Bitcoin controlled by these keys!
|
||||
|
||||
async def kt_do_send(rx_pubkey, dtype, raw=None, obj=None, prefix=b'', rx_label='the receiver', kp=None):
|
||||
# We are rendering a QR and showing it to them for sending to another Q
|
||||
from glob import dis
|
||||
dis.fullscreen("Wait...")
|
||||
cleartext = dtype.encode() + (raw or json.dumps(obj).encode())
|
||||
dis.progress_bar_show(0.1)
|
||||
|
||||
# Pick and show noid key to sender
|
||||
noid_key, txt = pick_noid_key()
|
||||
@ -214,8 +214,8 @@ async def kt_do_send(rx_pubkey, dtype, raw=None, obj=None, prefix=b'', rx_label=
|
||||
"\n\n %s = %s\n\n" % (rx_label, txt, txt_grouper(txt))
|
||||
msg += "ENTER to view QR"
|
||||
|
||||
await tk_show_payload('S' if not prefix else 'E',
|
||||
payload, 'Teleport Password', msg, cta='Show to Receiver')
|
||||
await tk_show_payload('S' if not prefix else 'E', payload,
|
||||
'Teleport Password', msg, cta='Show to Receiver')
|
||||
|
||||
if not prefix:
|
||||
# not PSBT case ... reset menus, we are deep!
|
||||
@ -269,7 +269,6 @@ async def kt_decode_rx(is_psbt, payload):
|
||||
"Sender must start again.", title="Teleport Fail")
|
||||
return
|
||||
|
||||
from glob import dis
|
||||
while 1:
|
||||
# ask for noid key
|
||||
pw = await ux_input_text('', confirm_exit=False, hex_only=False, max_len=8,
|
||||
@ -312,6 +311,7 @@ async def kt_accept_values(dtype, raw):
|
||||
'''
|
||||
from flow import has_se_secrets, goto_top_menu
|
||||
|
||||
dis.fullscreen("Wait...")
|
||||
enc = None
|
||||
origin = 'Teleported'
|
||||
label = None
|
||||
@ -341,7 +341,8 @@ async def kt_accept_values(dtype, raw):
|
||||
out.write(raw)
|
||||
|
||||
# This will take over UX w/ the signing process
|
||||
sign_transaction(psbt_len, flags=STXN_FINALIZE)
|
||||
# flags=None --> whether to finalize is decided based on psbt.is_complete
|
||||
sign_transaction(psbt_len, flags=None)
|
||||
return
|
||||
|
||||
elif dtype == 'b':
|
||||
@ -579,7 +580,6 @@ class SecretPickerMenu(MenuSystem):
|
||||
if ch != 'y': return
|
||||
|
||||
from backups import render_backup_contents
|
||||
from glob import dis
|
||||
|
||||
dis.fullscreen("Buiding Backup...")
|
||||
|
||||
@ -620,7 +620,7 @@ class SecretPickerMenu(MenuSystem):
|
||||
await kt_do_send(self.rx_pubkey, 's', raw=raw)
|
||||
|
||||
|
||||
async def kt_send_psbt(psbt, psbt_len=None, post_signing=False):
|
||||
async def kt_send_psbt(psbt, psbt_len):
|
||||
# We just finished adding our signature to an incomplete PSBT.
|
||||
# User wants to send to one or more other senders for them to complete signing.
|
||||
|
||||
@ -632,44 +632,14 @@ async def kt_send_psbt(psbt, psbt_len=None, post_signing=False):
|
||||
# maybe it's not really a PSBT where we know the other signers? might be
|
||||
# a weird coinjoin we don't fully understand
|
||||
if not need:
|
||||
if not post_signing:
|
||||
await ux_show_story("No more signers?")
|
||||
await ux_show_story("No more signers?")
|
||||
return
|
||||
|
||||
num_to_complete = ms.M - (ms.N - len(need))
|
||||
# move out of PSRAM
|
||||
from auth import TXN_OUTPUT_OFFSET
|
||||
|
||||
if post_signing:
|
||||
# They just approved and signed a MS txn perhaps via USB or QR or any source
|
||||
# - offer to save?
|
||||
# - offer them to teleport it (we only come this far if possible)
|
||||
|
||||
if num_to_complete <= 0:
|
||||
# Sufficiently signed. We can probably finalize it too.
|
||||
# - if from USB, we'd be uploading back, SD would be saved, etc
|
||||
return
|
||||
|
||||
ch = await ux_show_story("%d more signatures are still required. Press (T) to pick another co-signer to sign next, using QR codes, or ENTER for other options." % num_to_complete, title="Teleport PSBT?", escape='t')
|
||||
if ch != 't':
|
||||
# ENTER/CANCEL both come here because we don't want to lose the PSBT
|
||||
# - they can also do a "T" and teleport again
|
||||
from auth import done_signing
|
||||
await done_signing(psbt)
|
||||
return
|
||||
|
||||
|
||||
if not psbt_len:
|
||||
# we need it serialized, might have only saved into Base64 or something
|
||||
with BytesIO() as fd:
|
||||
psbt.serialize(fd) # need prog bar?
|
||||
|
||||
psbt_len = fd.tell()
|
||||
bin_psbt = fd.getvalue()
|
||||
else:
|
||||
# move out of PSRAM
|
||||
from auth import TXN_OUTPUT_OFFSET
|
||||
|
||||
with SFFile(TXN_OUTPUT_OFFSET, psbt_len) as fd:
|
||||
bin_psbt = fd.read(psbt_len)
|
||||
with SFFile(TXN_OUTPUT_OFFSET, psbt_len) as fd:
|
||||
bin_psbt = fd.read(psbt_len)
|
||||
|
||||
my_xfp = settings.get('xfp')
|
||||
|
||||
@ -701,7 +671,8 @@ async def kt_send_psbt(psbt, psbt_len=None, post_signing=False):
|
||||
|
||||
async def sign_now(*a):
|
||||
# this will reset the UX stack:
|
||||
sign_transaction(psbt_len, flags=STXN_FINALIZE)
|
||||
# flags=None --> whether to finalize is decided based on psbt.is_complete
|
||||
sign_transaction(psbt_len, flags=None)
|
||||
|
||||
f = sign_now
|
||||
|
||||
@ -743,7 +714,6 @@ async def kt_send_file_psbt(*a):
|
||||
from version import MAX_TXN_LEN
|
||||
from ux import import_export_prompt
|
||||
from psbt import psbtObject
|
||||
from glob import dis
|
||||
|
||||
# choose any PSBT from SD
|
||||
picked = await import_export_prompt("PSBT", is_import=True, no_nfc=True, no_qr=True)
|
||||
|
||||
20
shared/ux.py
20
shared/ux.py
@ -393,7 +393,7 @@ def _import_prompt_builder(title, no_qr, no_nfc, slot_b_only=False):
|
||||
|
||||
|
||||
def export_prompt_builder(what_it_is, no_qr=False, no_nfc=False, key0=None, offer_kt=False,
|
||||
force_prompt=False):
|
||||
force_prompt=False, txid=None):
|
||||
# Build the prompt for export
|
||||
# - key0 can be for special stuff
|
||||
from glob import NFC, VD
|
||||
@ -430,6 +430,10 @@ def export_prompt_builder(what_it_is, no_qr=False, no_nfc=False, key0=None, offe
|
||||
prompt += ", (4) to show QR code"
|
||||
escape += '4'
|
||||
|
||||
if txid:
|
||||
prompt += ", (6) for QR Code of TXID"
|
||||
escape += "6"
|
||||
|
||||
if offer_kt:
|
||||
prompt += ", (T) to " + offer_kt
|
||||
escape += 't'
|
||||
@ -474,8 +478,9 @@ def import_export_prompt_decode(ch):
|
||||
return dict(force_vdisk=force_vdisk, slot_b=slot_b)
|
||||
|
||||
async def import_export_prompt(what_it_is, is_import=False, no_qr=False,
|
||||
no_nfc=False, title=None, intro='', footnotes='',
|
||||
offer_kt=False, slot_b_only=False, force_prompt=False):
|
||||
no_nfc=False, title=None, intro='', footnotes='',
|
||||
offer_kt=False, slot_b_only=False, force_prompt=False,
|
||||
txid=None):
|
||||
|
||||
# Show story allowing user to select source for importing/exporting
|
||||
# - return either str(mode) OR dict(file_args)
|
||||
@ -483,11 +488,12 @@ async def import_export_prompt(what_it_is, is_import=False, no_qr=False,
|
||||
# - KEY_CANCEL for abort by user
|
||||
# - dict() => do file system thing, using file_args to control vdisk vs. SD vs slot_b
|
||||
# - 't' => key teleport, but only offered with offer_kt is set (contetxt, and Q only)
|
||||
from glob import NFC
|
||||
|
||||
if is_import:
|
||||
prompt, escape = _import_prompt_builder(what_it_is, no_qr, no_nfc, slot_b_only)
|
||||
else:
|
||||
prompt, escape = export_prompt_builder(what_it_is, no_qr, no_nfc,
|
||||
prompt, escape = export_prompt_builder(what_it_is, no_qr, no_nfc, txid=txid,
|
||||
force_prompt=force_prompt, offer_kt=offer_kt)
|
||||
|
||||
# TODO: detect if we're only asking A or B, when just one card is inserted
|
||||
@ -498,8 +504,10 @@ async def import_export_prompt(what_it_is, is_import=False, no_qr=False,
|
||||
# they don't have NFC nor VD enabled, and no second slots... so will be file.
|
||||
return dict(force_vdisk=False, slot_b=None)
|
||||
else:
|
||||
ch = await ux_show_story(intro+prompt+footnotes, escape=escape, title=title,
|
||||
strict_escape=True)
|
||||
hints = ("" if no_qr else KEY_QR) + (KEY_NFC if not no_nfc and NFC else "")
|
||||
msg_lst = [i for i in (intro, prompt, footnotes) if i]
|
||||
ch = await ux_show_story("\n\n".join(msg_lst), escape=escape, title=title,
|
||||
strict_escape=True, hint_icons=hints)
|
||||
|
||||
return import_export_prompt_decode(ch)
|
||||
|
||||
|
||||
@ -993,7 +993,7 @@ class QRScannerInteraction:
|
||||
async def qr_psbt_sign(decoder, psbt_len, raw):
|
||||
# Got a PSBT coming in from QR scanner. Sign it.
|
||||
# - similar to auth.sign_psbt_file()
|
||||
from auth import UserAuthorizedAction, ApproveTransaction, done_signing
|
||||
from auth import UserAuthorizedAction, ApproveTransaction
|
||||
from ux import the_ux
|
||||
from sffile import SFFile
|
||||
from auth import TXN_INPUT_OFFSET, psbt_encoding_taster
|
||||
@ -1024,9 +1024,8 @@ async def qr_psbt_sign(decoder, psbt_len, raw):
|
||||
|
||||
UserAuthorizedAction.cleanup()
|
||||
UserAuthorizedAction.active_request = ApproveTransaction(
|
||||
psbt_len, approved_cb=done_signing,
|
||||
cb_kws={"input_method": "qr",
|
||||
"output_encoder": output_encoder}
|
||||
psbt_len, input_method="qr",
|
||||
output_encoder=output_encoder
|
||||
)
|
||||
the_ux.push(UserAuthorizedAction.active_request)
|
||||
|
||||
@ -1193,19 +1192,18 @@ async def show_bbqr_codes(type_code, data, msg, already_hex=False):
|
||||
# BBQr header
|
||||
hdr = 'B$' + encoding + type_code + int2base36(num_parts) + int2base36(pkt)
|
||||
|
||||
# encode the bytes
|
||||
assert pos < data_len, (pkt, pos, data_len)
|
||||
if already_hex:
|
||||
# not encoding, just chars->bytes
|
||||
# not encoding, just hex string
|
||||
hp = pos*2
|
||||
body = data[hp:hp+(part_size*2)].decode()
|
||||
body = data[hp:hp+(part_size*2)]
|
||||
else:
|
||||
# base32 encoding
|
||||
# encode bytes to base32 encoding
|
||||
body = b32encode(data[pos:pos+part_size])
|
||||
|
||||
pos += part_size
|
||||
|
||||
# first first packet, want to discover a working small value for QR version
|
||||
# first packet, want to discover a working small value for QR version
|
||||
if pkt == 0:
|
||||
mnv = 10 if num_parts > 1 else 1
|
||||
else:
|
||||
|
||||
@ -1287,14 +1287,15 @@ def try_sign_microsd(open_microsd, cap_story, pick_menu_item, goto_home,
|
||||
for r in range(10):
|
||||
time.sleep(0.1)
|
||||
title, story = cap_story()
|
||||
if title == 'PSBT Signed': break
|
||||
if 'Updated PSBT' in story: break
|
||||
if 'Finalized transaction' in story: break
|
||||
else:
|
||||
assert False, 'timed out'
|
||||
|
||||
lines = story.split('\n')
|
||||
txid = None
|
||||
if 'Final TXID:' in lines:
|
||||
txid = lines[lines.index('Final TXID:')+1]
|
||||
if 'TXID:' in lines:
|
||||
txid = lines[lines.index('TXID:')+1]
|
||||
|
||||
# This is fragile!
|
||||
# ignore "Press (T) to use Key Teleport to send PSBT to other co-signers" footer
|
||||
@ -1359,9 +1360,11 @@ def try_sign_microsd(open_microsd, cap_story, pick_menu_item, goto_home,
|
||||
@pytest.fixture
|
||||
def try_sign(start_sign, end_sign):
|
||||
|
||||
def doit(filename_or_data, accept=True, finalize=False, accept_ms_import=False):
|
||||
def doit(filename_or_data, accept=True, finalize=False, accept_ms_import=False,
|
||||
exit_export_loop=True):
|
||||
ip = start_sign(filename_or_data, finalize=finalize)
|
||||
return ip, end_sign(accept, finalize=finalize, accept_ms_import=accept_ms_import)
|
||||
return ip, end_sign(accept, finalize=finalize, accept_ms_import=accept_ms_import,
|
||||
exit_export_loop=exit_export_loop)
|
||||
|
||||
return doit
|
||||
|
||||
@ -1387,10 +1390,11 @@ def start_sign(dev):
|
||||
return doit
|
||||
|
||||
@pytest.fixture
|
||||
def end_sign(dev, need_keypress):
|
||||
def end_sign(dev, need_keypress, press_cancel):
|
||||
from ckcc_protocol.protocol import CCUserRefused
|
||||
|
||||
def doit(accept=True, in_psbt=None, finalize=False, accept_ms_import=False, expect_txn=True):
|
||||
def doit(accept=True, finalize=False, accept_ms_import=False, expect_txn=True,
|
||||
exit_export_loop=True):
|
||||
|
||||
if accept_ms_import:
|
||||
# XXX would be better to do cap_story here, but that would limit test to simulator
|
||||
@ -1445,6 +1449,9 @@ def end_sign(dev, need_keypress):
|
||||
for sig in sigs:
|
||||
assert len(sig) <= 71, "overly long signature observed"
|
||||
|
||||
if exit_export_loop:
|
||||
press_cancel() # landed back to export prompt - exit
|
||||
|
||||
return psbt_out
|
||||
|
||||
return doit
|
||||
@ -1871,10 +1878,48 @@ def load_export_and_verify_signature(microsd_path, virtdisk_path, verify_detache
|
||||
return contents, address, fname
|
||||
return doit
|
||||
|
||||
@pytest.fixture
|
||||
def file_tx_signing_done(virtdisk_path, microsd_path):
|
||||
def doit(story, encoding="base64", is_vdisk=False):
|
||||
path_f = virtdisk_path if is_vdisk else microsd_path
|
||||
enc = "rb" if encoding == "binary" else "r"
|
||||
_split = story.split("\n\n")
|
||||
export = None
|
||||
if 'Updated PSBT is:' == _split[0]:
|
||||
fname = _split[1]
|
||||
path = path_f(fname)
|
||||
with open(path, enc) as f:
|
||||
export = f.read().strip()
|
||||
|
||||
export_tx = None
|
||||
if "Finalized transaction (ready for broadcast)" in _split[2]:
|
||||
fname_tx = _split[3]
|
||||
path_tx = path_f(fname_tx)
|
||||
with open(path_tx, enc) as f:
|
||||
export_tx = f.read().strip()
|
||||
else:
|
||||
# just finalized tx
|
||||
assert "Finalized transaction (ready for broadcast):" == _split[0]
|
||||
fname_tx = _split[1]
|
||||
path_tx = path_f(fname_tx)
|
||||
with open(path_tx, enc) as f:
|
||||
export_tx = f.read()
|
||||
|
||||
txid = None
|
||||
for l in _split:
|
||||
if "TXID" in l:
|
||||
txid = l.split("\n")[-1].strip()
|
||||
assert len(txid) == 64, "wrong txid"
|
||||
break
|
||||
|
||||
return export, export_tx, txid
|
||||
|
||||
return doit
|
||||
|
||||
@pytest.fixture
|
||||
def load_export(need_keypress, cap_story, microsd_path, virtdisk_path, nfc_read_text, nfc_read_json,
|
||||
load_export_and_verify_signature, is_q1, press_cancel, press_select, readback_bbqr,
|
||||
cap_screen_qr, nfc_read_txn):
|
||||
cap_screen_qr, nfc_read_txn, file_tx_signing_done):
|
||||
def doit(way, label, is_json, sig_check=True, addr_fmt=AF_CLASSIC, ret_sig_addr=False,
|
||||
tail_check=None, sd_key=None, vdisk_key=None, nfc_key=None, ret_fname=False,
|
||||
fpattern=None, qr_key=None, is_tx=False, encoding="base64"):
|
||||
@ -1954,29 +1999,7 @@ def load_export(need_keypress, cap_story, microsd_path, virtdisk_path, nfc_read_
|
||||
label=label, tail_check=tail_check, fpattern=fpattern
|
||||
)
|
||||
elif is_tx:
|
||||
enc = "rb" if encoding == "binary" else "r"
|
||||
_split = story.split("\n\n")
|
||||
export = None
|
||||
if 'Updated PSBT is:' == _split[0]:
|
||||
fname = _split[1]
|
||||
path = path_f(fname)
|
||||
with open(path, enc) as f:
|
||||
export = f.read()
|
||||
|
||||
export_tx = None
|
||||
if "Finalized transaction (ready for broadcast)" in _split[2]:
|
||||
fname_tx = _split[3]
|
||||
path_tx = path_f(fname_tx)
|
||||
with open(path_tx, enc) as f:
|
||||
export_tx = f.read()
|
||||
else:
|
||||
# just finalized tx
|
||||
assert "Finalized transaction (ready for broadcast):" == _split[0]
|
||||
fname_tx = _split[1]
|
||||
path_tx = path_f(fname_tx)
|
||||
with open(path_tx, enc) as f:
|
||||
export_tx = f.read()
|
||||
|
||||
export, export_tx, _ = file_tx_signing_done(story, encoding, is_vdisk=(way == "vdisk"))
|
||||
return export, export_tx
|
||||
else:
|
||||
assert f"{label} file written" in story
|
||||
@ -2014,7 +2037,6 @@ def signing_artifacts_reexport(cap_story, need_keypress, load_export, press_canc
|
||||
def _check_story(the_way):
|
||||
time.sleep(.2)
|
||||
title, story = cap_story()
|
||||
assert title == "PSBT Signed"
|
||||
|
||||
if the_way in ["qr", "nfc"]:
|
||||
what = label + " shared via %s." % the_way.upper()
|
||||
@ -2027,9 +2049,6 @@ def signing_artifacts_reexport(cap_story, need_keypress, load_export, press_canc
|
||||
if txid:
|
||||
assert txid in story
|
||||
|
||||
assert "Press (0) to save again" in story
|
||||
need_keypress("0")
|
||||
|
||||
to_do = ["sd", "vdisk", "nfc", "qr"]
|
||||
if not is_usb:
|
||||
_check_story(way)
|
||||
@ -2544,6 +2563,7 @@ from test_notes import need_some_notes, need_some_passwords
|
||||
from test_nfc import try_sign_nfc, ndef_parse_txn_psbt
|
||||
from test_se2 import goto_trick_menu, clear_all_tricks, new_trick_pin, se2_gate, new_pin_confirmed
|
||||
from test_seed_xor import restore_seed_xor
|
||||
from test_sign import txid_from_export_prompt
|
||||
from test_ux import pass_word_quiz, word_menu_entry, enable_hw_ux
|
||||
from txn import fake_txn
|
||||
|
||||
|
||||
@ -2706,7 +2706,7 @@ def bitcoind_multisig(bitcoind, bitcoind_d_sim_watch, need_keypress, cap_story,
|
||||
@pytest.mark.parametrize("script", ["p2wsh", "p2sh-p2wsh", "p2sh"])
|
||||
@pytest.mark.parametrize('desc', ["multi", "sortedmulti"])
|
||||
def test_finalization(m_n, script, desc, use_regtest, clear_ms, bitcoind_multisig, bitcoind,
|
||||
try_sign, cap_story, settings_set):
|
||||
try_sign, cap_story, settings_set, txid_from_export_prompt, press_cancel):
|
||||
if desc == "multi":
|
||||
settings_set("unsort_ms", 1)
|
||||
|
||||
@ -2734,15 +2734,16 @@ def test_finalization(m_n, script, desc, use_regtest, clear_ms, bitcoind_multisi
|
||||
|
||||
psbt_bytes = base64.b64decode(psbt)
|
||||
# USB sign with COLDCARD & finalize
|
||||
_, txn = try_sign(psbt_bytes, finalize=True)
|
||||
_, txn = try_sign(psbt_bytes, finalize=True, exit_export_loop=False)
|
||||
tx_hex = txn.hex()
|
||||
res = wo.testmempoolaccept([tx_hex])
|
||||
assert res[0]["allowed"]
|
||||
res = wo.sendrawtransaction(tx_hex)
|
||||
assert len(res) == 64 # tx id
|
||||
time.sleep(0.1)
|
||||
_, story = cap_story()
|
||||
cc_tx_id = story.split("\n\n")[0]
|
||||
|
||||
cc_tx_id = txid_from_export_prompt()
|
||||
press_cancel() # exit QR display
|
||||
press_cancel() # exit export loop
|
||||
assert res == cc_tx_id
|
||||
|
||||
wo.generatetoaddress(1, bitcoind.supply_wallet.getnewaddress())
|
||||
@ -2760,15 +2761,16 @@ def test_finalization(m_n, script, desc, use_regtest, clear_ms, bitcoind_multisi
|
||||
|
||||
psbt_bytes = base64.b64decode(psbt)
|
||||
# USB sign with COLDCARD & finalize
|
||||
_, txn = try_sign(psbt_bytes, finalize=True)
|
||||
_, txn = try_sign(psbt_bytes, finalize=True, exit_export_loop=False)
|
||||
tx_hex = txn.hex()
|
||||
res = wo.testmempoolaccept([tx_hex])
|
||||
assert res[0]["allowed"]
|
||||
res = wo.sendrawtransaction(tx_hex)
|
||||
assert len(res) == 64 # tx id
|
||||
time.sleep(0.1)
|
||||
_, story = cap_story()
|
||||
cc_tx_id = story.split("\n\n")[0]
|
||||
|
||||
cc_tx_id = txid_from_export_prompt()
|
||||
press_cancel() # exit QR display
|
||||
press_cancel() # exit export loop
|
||||
assert res == cc_tx_id
|
||||
|
||||
wo.generatetoaddress(1, bitcoind.supply_wallet.getnewaddress())
|
||||
@ -2779,13 +2781,14 @@ def test_finalization(m_n, script, desc, use_regtest, clear_ms, bitcoind_multisi
|
||||
@pytest.mark.parametrize("m_n", [(2,3), (3,5), (15,15)])
|
||||
@pytest.mark.parametrize("script", ["p2wsh", "p2sh-p2wsh", "p2sh"])
|
||||
@pytest.mark.parametrize("sighash", list(SIGHASH_MAP.keys()))
|
||||
@pytest.mark.parametrize("psbt_v2", [True, False])
|
||||
@pytest.mark.parametrize('desc', ["multi", "sortedmulti"])
|
||||
def test_bitcoind_MofN_tutorial(m_n, script, clear_ms, goto_home, need_keypress, pick_menu_item,
|
||||
sighash, cap_menu, cap_story, microsd_path, use_regtest, bitcoind,
|
||||
microsd_wipe, settings_set, psbt_v2, is_q1, try_sign,
|
||||
finalize_v2_v0_convert, press_select, desc, bitcoind_multisig):
|
||||
microsd_wipe, settings_set, is_q1, try_sign, press_select,
|
||||
finalize_v2_v0_convert, desc, bitcoind_multisig, press_cancel,
|
||||
txid_from_export_prompt, pytestconfig, file_tx_signing_done):
|
||||
# 2of2 case here is described in docs with tutorial
|
||||
# TODO This test MUST be run with --psbt2 flag on and off
|
||||
if desc == "multi":
|
||||
settings_set("unsort_ms", 1)
|
||||
|
||||
@ -2823,7 +2826,7 @@ def test_bitcoind_MofN_tutorial(m_n, script, clear_ms, goto_home, need_keypress,
|
||||
half_signed_psbt = signer.walletprocesspsbt(psbt, True, sighash, True) # do not finalize
|
||||
psbt = half_signed_psbt["psbt"]
|
||||
|
||||
if psbt_v2:
|
||||
if pytestconfig.getoption('psbt2'):
|
||||
# below is noop if psbt is already v2
|
||||
po = BasicPSBT().parse(base64.b64decode(psbt))
|
||||
po.to_v2()
|
||||
@ -2856,20 +2859,11 @@ def test_bitcoind_MofN_tutorial(m_n, script, clear_ms, goto_home, need_keypress,
|
||||
press_select() # confirm signing
|
||||
time.sleep(0.1)
|
||||
title, story = cap_story()
|
||||
assert "PSBT Signed" == title
|
||||
assert "Updated PSBT is:" in story
|
||||
press_select()
|
||||
os.remove(microsd_path(name))
|
||||
|
||||
split_story = story.split("\n\n")
|
||||
fname = split_story[1]
|
||||
fname_tx = split_story[3]
|
||||
cc_tx_id = split_story[-2].split("\n")[-1]
|
||||
with open(microsd_path(fname), "r") as f:
|
||||
final_psbt = f.read().strip()
|
||||
|
||||
with open(microsd_path(fname_tx), "r") as f:
|
||||
final_tx = f.read().strip()
|
||||
final_psbt, final_tx, cc_tx_id = file_tx_signing_done(story)
|
||||
|
||||
po = BasicPSBT().parse(base64.b64decode(final_psbt))
|
||||
res = finalize_v2_v0_convert(po)
|
||||
@ -2897,7 +2891,7 @@ def test_bitcoind_MofN_tutorial(m_n, script, clear_ms, goto_home, need_keypress,
|
||||
half_signed_psbt = signer.walletprocesspsbt(psbt, True, sighash, True) # do not finalize
|
||||
psbt = half_signed_psbt["psbt"]
|
||||
|
||||
if psbt_v2:
|
||||
if pytestconfig.getoption('psbt2'):
|
||||
# below is noop if psbt is already v2
|
||||
po = BasicPSBT().parse(base64.b64decode(psbt))
|
||||
po.to_v2()
|
||||
@ -2905,15 +2899,15 @@ def test_bitcoind_MofN_tutorial(m_n, script, clear_ms, goto_home, need_keypress,
|
||||
|
||||
psbt_bytes = base64.b64decode(psbt)
|
||||
# USB sign with COLDCARD & finalize
|
||||
_, txn = try_sign(psbt_bytes, finalize=True)
|
||||
_, txn = try_sign(psbt_bytes, finalize=True, exit_export_loop=False)
|
||||
tx_hex = txn.hex()
|
||||
res = bitcoind_watch_only.testmempoolaccept([tx_hex])
|
||||
assert res[0]["allowed"]
|
||||
res = bitcoind_watch_only.sendrawtransaction(tx_hex)
|
||||
assert len(res) == 64 # tx id
|
||||
time.sleep(0.1)
|
||||
_, story = cap_story()
|
||||
cc_tx_id = story.split("\n\n")[0]
|
||||
cc_tx_id = txid_from_export_prompt()
|
||||
press_cancel() # exit QR display
|
||||
press_cancel() # exit export loop
|
||||
assert res == cc_tx_id
|
||||
|
||||
bitcoind_watch_only.generatetoaddress(1, bitcoind.supply_wallet.getnewaddress()) # need to mine above tx
|
||||
@ -2931,6 +2925,10 @@ def test_bitcoind_MofN_tutorial(m_n, script, clear_ms, goto_home, need_keypress,
|
||||
x = BasicPSBT().parse(base64.b64decode(psbt))
|
||||
for idx, i in enumerate(x.inputs):
|
||||
i.sighash = SIGHASH_MAP[sighash]
|
||||
|
||||
if pytestconfig.getoption('psbt2'):
|
||||
x.to_v2()
|
||||
|
||||
psbt = x.as_b64_str()
|
||||
|
||||
name = f"change_{M}of{N}_{script}.psbt"
|
||||
@ -2957,12 +2955,11 @@ def test_bitcoind_MofN_tutorial(m_n, script, clear_ms, goto_home, need_keypress,
|
||||
assert "missing" in story
|
||||
return
|
||||
|
||||
assert "PSBT Signed" == title
|
||||
assert "Updated PSBT is:" in story
|
||||
press_select()
|
||||
fname = story.split("\n\n")[-2]
|
||||
with open(microsd_path(fname), "r") as f:
|
||||
cc_signed_psbt = f.read().strip()
|
||||
cc_signed_psbt, _txn, _txid = file_tx_signing_done(story)
|
||||
assert _txn is None and _txid is None
|
||||
|
||||
press_cancel() # exit re-export loop
|
||||
|
||||
po = BasicPSBT().parse(base64.b64decode(cc_signed_psbt))
|
||||
cc_signed_psbt = finalize_v2_v0_convert(po)["psbt"]
|
||||
@ -3330,7 +3327,6 @@ def test_bare_cc_ms_qr_import(N, make_multisig, scan_a_qr, clear_ms, goto_home,
|
||||
press_cancel()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("psbtv2", [True, False])
|
||||
@pytest.mark.parametrize("desc", ["multi", "sortedmulti"])
|
||||
@pytest.mark.parametrize("data", [
|
||||
# (out_style, amount, is_change)
|
||||
@ -3339,8 +3335,9 @@ def test_bare_cc_ms_qr_import(N, make_multisig, scan_a_qr, clear_ms, goto_home,
|
||||
[("p2wsh-p2sh", 1000000, 1)] * 18 + [("p2wsh", 50000000, 0)] * 12,
|
||||
[("p2sh", 1000000, 1), ("p2wsh-p2sh", 50000000, 0), ("p2wsh", 800000, 1)] * 14,
|
||||
])
|
||||
def test_txout_explorer(psbtv2, data, clear_ms, import_ms_wallet, fake_ms_txn,
|
||||
start_sign, txout_explorer, desc):
|
||||
def test_txout_explorer(data, clear_ms, import_ms_wallet, fake_ms_txn,
|
||||
start_sign, txout_explorer, desc, pytestconfig):
|
||||
# TODO This test MUST be run with --psbt2 flag on and off
|
||||
clear_ms()
|
||||
M, N = 2, 3
|
||||
descriptor, bip67 = False, True
|
||||
@ -3362,7 +3359,8 @@ def test_txout_explorer(psbtv2, data, clear_ms, import_ms_wallet, fake_ms_txn,
|
||||
inp_amount = sum(outvals) + 100000 # 100k sat fee
|
||||
psbt = fake_ms_txn(1, len(data), M, keys, outstyles=outstyles,
|
||||
outvals=outvals, change_outputs=change_outputs,
|
||||
input_amount=inp_amount, psbt_v2=psbtv2, bip67=bip67)
|
||||
input_amount=inp_amount, psbt_v2=pytestconfig.getoption('psbt2'),
|
||||
bip67=bip67)
|
||||
start_sign(psbt)
|
||||
txout_explorer(data)
|
||||
|
||||
|
||||
@ -244,7 +244,7 @@ def try_sign_nfc(cap_story, pick_menu_item, goto_home, need_keypress,
|
||||
for r in range(10):
|
||||
time.sleep(0.1)
|
||||
title, story = cap_story()
|
||||
if title == 'PSBT Signed': break
|
||||
if "shared via NFC" in story: break
|
||||
else:
|
||||
assert False, 'timed out'
|
||||
|
||||
@ -347,7 +347,7 @@ def test_nfc_after(num_outs, fake_txn, try_sign, nfc_read, need_keypress,
|
||||
cap_story, is_q1, press_nfc, press_cancel):
|
||||
# Read signing result (transaction) over NFC, decode it.
|
||||
psbt = fake_txn(1, num_outs)
|
||||
orig, result = try_sign(psbt, accept=True, finalize=True)
|
||||
orig, result = try_sign(psbt, accept=True, finalize=True, exit_export_loop=False)
|
||||
|
||||
too_big = len(result) > 8000
|
||||
|
||||
@ -356,19 +356,21 @@ def test_nfc_after(num_outs, fake_txn, try_sign, nfc_read, need_keypress,
|
||||
|
||||
time.sleep(.1)
|
||||
title, story = cap_story()
|
||||
assert 'TXID' in title, story
|
||||
txid = a2b_hex(story.split()[0])
|
||||
assert f'Press {KEY_NFC if is_q1 else "(3)"}' in story
|
||||
assert 'TXID' in story, story
|
||||
txid = a2b_hex(story.split("\n")[3])
|
||||
assert f'press {KEY_NFC if is_q1 else "(3)"}' in story
|
||||
press_nfc()
|
||||
time.sleep(.2)
|
||||
|
||||
if too_big:
|
||||
title, story = cap_story()
|
||||
assert 'is too large' in story
|
||||
press_cancel()
|
||||
return
|
||||
|
||||
contents = nfc_read()
|
||||
press_cancel()
|
||||
press_cancel()
|
||||
|
||||
#print("contents = " + B2A(contents))
|
||||
for got in ndef.message_decoder(contents):
|
||||
@ -482,7 +484,7 @@ def test_nfc_pushtx(num_outs, chain, enable_nfc, settings_set, settings_remove,
|
||||
psbt = fake_txn(2, num_outs)
|
||||
|
||||
if way == "usb":
|
||||
_, result = try_sign(psbt, finalize=True)
|
||||
_, result = try_sign(psbt, finalize=True, exit_export_loop=False)
|
||||
elif way == "sd":
|
||||
ip, result, txid = try_sign_microsd(psbt, finalize=True, nfc_push_tx=True)
|
||||
elif way == "nfc":
|
||||
@ -501,10 +503,9 @@ def test_nfc_pushtx(num_outs, chain, enable_nfc, settings_set, settings_remove,
|
||||
time.sleep(.1)
|
||||
title, story = cap_story()
|
||||
if way == "usb":
|
||||
assert title == 'Final TXID'
|
||||
assert 'to share signed txn' in story
|
||||
assert 'TXID' in story
|
||||
elif way == "sd":
|
||||
assert title == "PSBT Signed"
|
||||
assert ('Updated PSBT' in story) or ('Finalized transaction' in story)
|
||||
else:
|
||||
assert False
|
||||
return
|
||||
|
||||
@ -134,7 +134,7 @@ def test_psbt_proxy_parsing(fn, sim_execfile, sim_exec):
|
||||
|
||||
@pytest.mark.unfinalized
|
||||
def test_speed_test(dev, fake_txn, is_mark3, is_mark4, start_sign, end_sign,
|
||||
press_select):
|
||||
press_select, press_cancel):
|
||||
# measure time to sign a larger txn
|
||||
if is_mark4:
|
||||
# Mk4: expect
|
||||
@ -169,6 +169,7 @@ def test_speed_test(dev, fake_txn, is_mark3, is_mark4, start_sign, end_sign,
|
||||
|
||||
print(" Tx time: %.1f" % tx_time)
|
||||
print("Sign time: %.1f" % ready_time)
|
||||
press_cancel()
|
||||
|
||||
if 0:
|
||||
# TODO: attempt to re-create the mega transaction: 5,569 inputs, one out
|
||||
@ -1211,10 +1212,26 @@ def hist_count(sim_exec):
|
||||
'import history; RV.write(str(len(history.OutptValueCache.runtime_cache)));'))
|
||||
return doit
|
||||
|
||||
@pytest.fixture
|
||||
def txid_from_export_prompt(cap_story, cap_screen_qr, cap_screen, need_keypress):
|
||||
def doit():
|
||||
time.sleep(.1)
|
||||
title, story = cap_story()
|
||||
assert "(6) for QR Code of TXID" in story
|
||||
need_keypress("6")
|
||||
time.sleep(.1)
|
||||
screen_txid = cap_screen().strip().replace("\n", "").replace("~", "")
|
||||
qr_txid = cap_screen_qr().decode().strip().lower()
|
||||
assert qr_txid == screen_txid
|
||||
return qr_txid
|
||||
|
||||
return doit
|
||||
|
||||
@pytest.mark.parametrize('num_utxo', [9, 100])
|
||||
@pytest.mark.parametrize('segwit_in', [False, True])
|
||||
def test_bip143_attack_data_capture(num_utxo, segwit_in, try_sign, fake_txn, settings_set,
|
||||
settings_get, cap_story, sim_exec, hist_count):
|
||||
settings_get, cap_story, sim_exec, hist_count,
|
||||
txid_from_export_prompt, press_cancel):
|
||||
|
||||
# cleanup prev runs, if very first time thru
|
||||
sim_exec('import history; history.OutptValueCache.clear()')
|
||||
@ -1224,16 +1241,15 @@ def test_bip143_attack_data_capture(num_utxo, segwit_in, try_sign, fake_txn, set
|
||||
# make a txn, capture the outputs of that as inputs for another txn
|
||||
psbt = fake_txn(1, num_utxo+3, segwit_in=segwit_in, change_outputs=range(num_utxo+2),
|
||||
outstyles=(['p2wpkh']*num_utxo) + ['p2wpkh-p2sh', 'p2pkh'])
|
||||
_, txn = try_sign(psbt, accept=True, finalize=True)
|
||||
_, txn = try_sign(psbt, accept=True, finalize=True, exit_export_loop=False)
|
||||
|
||||
open('debug/funding.psbt', 'wb').write(psbt)
|
||||
|
||||
num_inp_utxo = (1 if segwit_in else 0)
|
||||
|
||||
time.sleep(.1)
|
||||
title, story = cap_story()
|
||||
assert 'TXID' in title, story
|
||||
txid = story.strip().split()[0]
|
||||
txid = txid_from_export_prompt()
|
||||
press_cancel()
|
||||
press_cancel()
|
||||
|
||||
assert hist_count() in {128, hist_b4+num_utxo+num_inp_utxo}
|
||||
|
||||
@ -1273,21 +1289,17 @@ def test_bip143_attack_data_capture(num_utxo, segwit_in, try_sign, fake_txn, set
|
||||
|
||||
@pytest.mark.parametrize('segwit', [False, True])
|
||||
@pytest.mark.parametrize('num_ins', [1, 17])
|
||||
def test_txid_calc(num_ins, fake_txn, try_sign, dev, segwit, decode_with_bitcoind, cap_story):
|
||||
def test_txid_calc(num_ins, fake_txn, try_sign, dev, segwit, decode_with_bitcoind, cap_story,
|
||||
txid_from_export_prompt, press_cancel):
|
||||
# verify correct txid for transactions is being calculated
|
||||
xp = dev.master_xpub
|
||||
|
||||
psbt = fake_txn(num_ins, 1, xp, segwit_in=segwit)
|
||||
|
||||
_, txn = try_sign(psbt, accept=True, finalize=True)
|
||||
|
||||
#print('Signed; ' + B2A(txn))
|
||||
|
||||
time.sleep(.1)
|
||||
title, story = cap_story()
|
||||
assert '0' in story
|
||||
assert 'TXID' in title, story
|
||||
txid = story.strip().split()[0]
|
||||
_, txn = try_sign(psbt, accept=True, finalize=True, exit_export_loop=False)
|
||||
txid = txid_from_export_prompt()
|
||||
press_cancel() # exit QR
|
||||
press_cancel() # exit re-export loop
|
||||
|
||||
if 1:
|
||||
t = CTransaction()
|
||||
@ -1305,6 +1317,7 @@ def test_txid_calc(num_ins, fake_txn, try_sign, dev, segwit, decode_with_bitcoin
|
||||
|
||||
assert decoded['txid'] == txid
|
||||
|
||||
|
||||
@pytest.mark.unfinalized # iff partial=1
|
||||
@pytest.mark.reexport
|
||||
@pytest.mark.parametrize('encoding', ['binary', 'hex', 'base64'])
|
||||
@ -1527,36 +1540,36 @@ def test_value_render(dev, units, fake_txn, start_sign, cap_story, settings_set,
|
||||
@pytest.mark.qrcode
|
||||
@pytest.mark.parametrize('num_in', [1,2,3])
|
||||
@pytest.mark.parametrize('num_out', [1,2,3])
|
||||
def test_qr_txn(num_in, num_out, request, fake_txn, try_sign, dev, cap_screen_qr, qr_quality_check, cap_story, need_keypress):
|
||||
segwit=True
|
||||
@pytest.mark.parametrize('segwit', [True, False])
|
||||
def test_qr_txn(num_in, num_out, segwit, fake_txn, try_sign, dev, cap_screen_qr,
|
||||
qr_quality_check, cap_story, need_keypress, is_q1, press_cancel):
|
||||
|
||||
psbt = fake_txn(num_in, num_out, dev.master_xpub, segwit_in=False)
|
||||
psbt = fake_txn(num_in, num_out, dev.master_xpub, segwit_in=segwit)
|
||||
|
||||
|
||||
_, txn = try_sign(psbt, accept=True, finalize=True)
|
||||
open('debug/last.txn', 'wb').write(txn)
|
||||
|
||||
print("txn len = %d bytes" % len(txn))
|
||||
_, txn = try_sign(psbt, accept=True, finalize=True, exit_export_loop=False)
|
||||
with open('debug/last.txn', 'wb') as f:
|
||||
f.write(txn)
|
||||
|
||||
title, story = cap_story()
|
||||
|
||||
assert 'QR Code' in story
|
||||
assert '(6) for QR Code of TXID' in story
|
||||
|
||||
if 1:
|
||||
# check TXID qr code
|
||||
need_keypress('1')
|
||||
# check TXID qr code
|
||||
need_keypress('6')
|
||||
qr = cap_screen_qr().decode()
|
||||
press_cancel()
|
||||
|
||||
qr = cap_screen_qr().decode()
|
||||
t = CTransaction()
|
||||
t.deserialize(BytesIO(txn))
|
||||
assert t.txid().hex() == qr.lower()
|
||||
|
||||
t = CTransaction()
|
||||
t.deserialize(BytesIO(txn))
|
||||
assert t.txid().hex() == qr.lower()
|
||||
|
||||
else:
|
||||
# TODO: QR for txn itself yet
|
||||
need_keypress('2')
|
||||
if is_q1:
|
||||
need_keypress(KEY_QR)
|
||||
qr = cap_screen_qr().decode()
|
||||
assert qr.lower() == txn.hex()
|
||||
press_cancel()
|
||||
|
||||
press_cancel()
|
||||
|
||||
def test_missing_keypaths(dev, try_sign, fake_txn):
|
||||
|
||||
@ -1755,7 +1768,8 @@ def test_bitcoind_missing_foreign_utxo(bitcoind, bitcoind_d_sim_watch, microsd_p
|
||||
b"$$$$$$$$$$$$$$ Bitcoin",
|
||||
b"\xeb\x97\xf7\xb7\xf78\x9a';\x90F_\xfc\xe2b\xa4\x93)\xea\xac\xacR\xff\x9c\xbe\x1c\xf1\xad\xe9!\xee\xd9t1\x1f\x92\x83\x97\xb3\x98/\xff\xc8\xff\xc1\xc0\xdd\x1et\x00L\x13\xe0\xe3\x90\xe4\xd4\xf2x:\xf7Ab\x04\x91\x1e\xa8R\x92\xd3\x96OK\xc6I\x06\x9e\xce=\xb3",
|
||||
])
|
||||
def test_op_return_signing(op_return_data, dev, fake_txn, bitcoind_d_sim_watch, bitcoind, start_sign, end_sign, cap_story):
|
||||
def test_op_return_signing(op_return_data, dev, fake_txn, bitcoind_d_sim_watch, bitcoind,
|
||||
start_sign, end_sign, cap_story):
|
||||
cc = bitcoind_d_sim_watch
|
||||
dest_address = cc.getnewaddress()
|
||||
bitcoind.supply_wallet.generatetoaddress(101, dest_address)
|
||||
@ -1864,12 +1878,19 @@ def test_duplicate_unknow_values_in_psbt(dev, start_sign, end_sign, fake_txn):
|
||||
|
||||
@pytest.fixture
|
||||
def _test_single_sig_sighash(cap_story, press_select, start_sign, end_sign, dev,
|
||||
bitcoind, bitcoind_d_dev_watch, settings_set, finalize_v2_v0_convert):
|
||||
bitcoind, bitcoind_d_dev_watch, settings_set,
|
||||
finalize_v2_v0_convert, pytestconfig):
|
||||
def doit(addr_fmt, sighash, num_inputs=2, num_outputs=2, consolidation=False, sh_checks=False,
|
||||
psbt_v2=False, tx_check=True):
|
||||
psbt_v2=None, tx_check=True):
|
||||
|
||||
from decimal import Decimal, ROUND_DOWN
|
||||
|
||||
if psbt_v2 is None:
|
||||
# anything passed directly to this function overrides
|
||||
# pytest flag --psbt2 - only care about pytest flag
|
||||
# if psbt_v2 is not specified (None)
|
||||
psbt_v2 = pytestconfig.getoption('psbt2')
|
||||
|
||||
if dev.is_simulator:
|
||||
# if running against real HW you need to set CC to correct sighshchk mode
|
||||
# Below test need to run with sighshchk disabled:
|
||||
@ -2044,38 +2065,34 @@ 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,}
|
||||
|
||||
@pytest.mark.bitcoind
|
||||
@pytest.mark.parametrize("addr_fmt", ["legacy", "p2sh-segwit", "bech32"])
|
||||
@pytest.mark.parametrize("sighash", [sh for sh in SIGHASH_MAP if sh != 'ALL'])
|
||||
@pytest.mark.parametrize("num_outs", [1, 3, 5])
|
||||
@pytest.mark.parametrize("num_ins", [2, 5])
|
||||
@pytest.mark.parametrize("psbt_v2", [True, False])
|
||||
def test_sighash_same(addr_fmt, sighash, num_ins, num_outs, psbt_v2, _test_single_sig_sighash):
|
||||
def test_sighash_same(addr_fmt, sighash, num_ins, num_outs, _test_single_sig_sighash):
|
||||
# sighash is the same among all inputs
|
||||
_test_single_sig_sighash(addr_fmt, [sighash], num_inputs=num_ins, num_outputs=num_outs,
|
||||
psbt_v2=psbt_v2)
|
||||
_test_single_sig_sighash(addr_fmt, [sighash], num_inputs=num_ins, num_outputs=num_outs)
|
||||
|
||||
|
||||
@pytest.mark.bitcoind
|
||||
@pytest.mark.parametrize("addr_fmt", ["legacy", "p2sh-segwit", "bech32"])
|
||||
@pytest.mark.parametrize("sighash", list(itertools.combinations(SIGHASH_MAP.keys(), 2)))
|
||||
@pytest.mark.parametrize("num_outs", [2, 3, 5])
|
||||
@pytest.mark.parametrize("psbt_v2", [True, False])
|
||||
def test_sighash_different(addr_fmt, sighash, num_outs, psbt_v2, _test_single_sig_sighash):
|
||||
def test_sighash_different(addr_fmt, sighash, num_outs, _test_single_sig_sighash):
|
||||
# sighash differ among all inputs
|
||||
_test_single_sig_sighash(addr_fmt, sighash, num_inputs=2, num_outputs=num_outs,
|
||||
psbt_v2=psbt_v2)
|
||||
_test_single_sig_sighash(addr_fmt, sighash, num_inputs=2, num_outputs=num_outs)
|
||||
|
||||
|
||||
@pytest.mark.bitcoind
|
||||
@pytest.mark.parametrize("addr_fmt", ["legacy", "p2sh-segwit", "bech32"])
|
||||
@pytest.mark.parametrize("num_outs", [5, 8])
|
||||
@pytest.mark.parametrize("psbt_v2", [True, False])
|
||||
def test_sighash_fullmix(addr_fmt, num_outs, psbt_v2, _test_single_sig_sighash):
|
||||
def test_sighash_fullmix(addr_fmt, num_outs, _test_single_sig_sighash):
|
||||
# tx with 6 inputs representing all possible sighashes
|
||||
_test_single_sig_sighash(addr_fmt, tuple(SIGHASH_MAP.keys()), num_inputs=6,
|
||||
num_outputs=num_outs, psbt_v2=psbt_v2)
|
||||
_test_single_sig_sighash(addr_fmt, tuple(SIGHASH_MAP.keys()), num_inputs=6, num_outputs=num_outs)
|
||||
|
||||
|
||||
@pytest.mark.bitcoind
|
||||
@ -2207,7 +2224,7 @@ def test_batch_sign(num_tx, ui_path, action, fake_txn, need_keypress,
|
||||
time.sleep(.5)
|
||||
title, story = cap_story()
|
||||
assert "-signed.psbt" in story
|
||||
press_select()
|
||||
press_cancel()
|
||||
time.sleep(.5)
|
||||
title, story = cap_story()
|
||||
|
||||
@ -2337,7 +2354,7 @@ def test_psbt_v2_global_quantities(way, fake_txn, start_sign, end_sign, cap_stor
|
||||
])
|
||||
def test_locktime_ux(use_regtest, bitcoind_d_sim_watch, start_sign, end_sign,
|
||||
microsd_path, cap_story, goto_home, press_select,
|
||||
pick_menu_item, bitcoind, locktime):
|
||||
pick_menu_item, bitcoind, locktime, file_tx_signing_done):
|
||||
use_regtest()
|
||||
sim = bitcoind_d_sim_watch
|
||||
addr = sim.getnewaddress()
|
||||
@ -2366,12 +2383,15 @@ def test_locktime_ux(use_regtest, bitcoind_d_sim_watch, start_sign, end_sign,
|
||||
psbt_fname = "locktime.psbt"
|
||||
with open(microsd_path(psbt_fname), "w") as f:
|
||||
f.write(psbt)
|
||||
|
||||
goto_home()
|
||||
pick_menu_item('Ready To Sign')
|
||||
time.sleep(0.1)
|
||||
pick_menu_item(psbt_fname)
|
||||
time.sleep(0.1)
|
||||
title, story = cap_story()
|
||||
if 'OK TO SEND' not in title:
|
||||
pick_menu_item(psbt_fname)
|
||||
time.sleep(0.1)
|
||||
title, story = cap_story()
|
||||
|
||||
assert "WARNING" not in story
|
||||
if locktime != 0:
|
||||
@ -2390,18 +2410,10 @@ def test_locktime_ux(use_regtest, bitcoind_d_sim_watch, start_sign, end_sign,
|
||||
press_select() # confirm signing
|
||||
time.sleep(0.1)
|
||||
title, story = cap_story()
|
||||
assert title == 'PSBT Signed'
|
||||
assert "Updated PSBT is:" in story
|
||||
assert "Finalized transaction (ready for broadcast)" in story
|
||||
assert "TXID" in story
|
||||
split_story = story.split("\n\n")
|
||||
story_txid = split_story[-2].split("\n")[-1]
|
||||
signed_psbt_fname = split_story[1]
|
||||
with open(microsd_path(signed_psbt_fname), "r") as f:
|
||||
signed_psbt = f.read().strip()
|
||||
signed_txn_fname = split_story[3]
|
||||
with open(microsd_path(signed_txn_fname), "r") as f:
|
||||
signed_txn = f.read().strip()
|
||||
signed_psbt, signed_txn, story_txid = file_tx_signing_done(story)
|
||||
assert signed_psbt != psbt
|
||||
finalize_res = sim.finalizepsbt(signed_psbt)
|
||||
bitcoind_signed_txn = finalize_res["hex"]
|
||||
@ -2426,7 +2438,7 @@ def test_locktime_ux(use_regtest, bitcoind_d_sim_watch, start_sign, end_sign,
|
||||
def test_nsequence_blockheight_relative_locktime_ux(sequence, use_regtest, bitcoind_d_sim_watch,
|
||||
start_sign, end_sign, microsd_path, cap_story,
|
||||
goto_home, press_select, pick_menu_item,
|
||||
bitcoind, num_ins, differ):
|
||||
bitcoind, num_ins, differ, file_tx_signing_done):
|
||||
if differ and (sequence == 0):
|
||||
# this case makes no sense
|
||||
return
|
||||
@ -2475,12 +2487,15 @@ def test_nsequence_blockheight_relative_locktime_ux(sequence, use_regtest, bitco
|
||||
psbt_fname = "rtl-blockheight.psbt"
|
||||
with open(microsd_path(psbt_fname), "w") as f:
|
||||
f.write(psbt)
|
||||
|
||||
goto_home()
|
||||
pick_menu_item('Ready To Sign')
|
||||
time.sleep(0.1)
|
||||
pick_menu_item(psbt_fname)
|
||||
time.sleep(0.1)
|
||||
title, story = cap_story()
|
||||
if 'OK TO SEND' not in title:
|
||||
pick_menu_item(psbt_fname)
|
||||
time.sleep(0.1)
|
||||
title, story = cap_story()
|
||||
|
||||
assert "WARNING" not in story
|
||||
if sequence:
|
||||
@ -2502,18 +2517,11 @@ def test_nsequence_blockheight_relative_locktime_ux(sequence, use_regtest, bitco
|
||||
press_select() # confirm signing
|
||||
time.sleep(0.1)
|
||||
title, story = cap_story()
|
||||
assert title == 'PSBT Signed'
|
||||
assert "Updated PSBT is:" in story
|
||||
assert "Finalized transaction (ready for broadcast)" in story
|
||||
assert "TXID" in story
|
||||
split_story = story.split("\n\n")
|
||||
story_txid = split_story[-2].split("\n")[-1]
|
||||
signed_psbt_fname = split_story[1]
|
||||
with open(microsd_path(signed_psbt_fname), "r") as f:
|
||||
signed_psbt = f.read().strip()
|
||||
signed_txn_fname = split_story[3]
|
||||
with open(microsd_path(signed_txn_fname), "r") as f:
|
||||
signed_txn = f.read().strip()
|
||||
press_select() # exit saved story
|
||||
signed_psbt, signed_txn, story_txid = file_tx_signing_done(story)
|
||||
assert signed_psbt != psbt
|
||||
finalize_res = sim.finalizepsbt(signed_psbt)
|
||||
bitcoind_signed_txn = finalize_res["hex"]
|
||||
@ -2538,13 +2546,13 @@ def test_nsequence_blockheight_relative_locktime_ux(sequence, use_regtest, bitco
|
||||
|
||||
|
||||
@pytest.mark.bitcoind
|
||||
@pytest.mark.veryslow
|
||||
@pytest.mark.parametrize("num_ins", [1, 4, 11])
|
||||
@pytest.mark.parametrize("differ", [True, False])
|
||||
@pytest.mark.parametrize("seconds", [512, 10000, 1000000, 33554431])
|
||||
def test_nsequence_timebased_relative_locktime_ux(seconds, use_regtest, bitcoind_d_sim_watch, start_sign,
|
||||
microsd_path, cap_story, goto_home, press_select,
|
||||
pick_menu_item, bitcoind, end_sign, num_ins, differ):
|
||||
pick_menu_item, bitcoind, end_sign, num_ins, differ,
|
||||
file_tx_signing_done):
|
||||
sequence = SEQUENCE_LOCKTIME_TYPE_FLAG | (seconds >> 9)
|
||||
use_regtest()
|
||||
sim = bitcoind_d_sim_watch
|
||||
@ -2587,12 +2595,15 @@ def test_nsequence_timebased_relative_locktime_ux(seconds, use_regtest, bitcoind
|
||||
psbt_fname = "rtl-time.psbt"
|
||||
with open(microsd_path(psbt_fname), "w") as f:
|
||||
f.write(psbt)
|
||||
|
||||
goto_home()
|
||||
pick_menu_item('Ready To Sign')
|
||||
time.sleep(0.1)
|
||||
pick_menu_item(psbt_fname)
|
||||
pick_menu_item("Ready To Sign")
|
||||
time.sleep(0.1)
|
||||
title, story = cap_story()
|
||||
if 'OK TO SEND' not in title:
|
||||
pick_menu_item(psbt_fname)
|
||||
time.sleep(0.1)
|
||||
title, story = cap_story()
|
||||
|
||||
assert "WARNING" not in story
|
||||
assert "TX LOCKTIMES" in story
|
||||
@ -2613,18 +2624,10 @@ def test_nsequence_timebased_relative_locktime_ux(seconds, use_regtest, bitcoind
|
||||
press_select() # confirm signing
|
||||
time.sleep(0.1)
|
||||
title, story = cap_story()
|
||||
assert title == 'PSBT Signed'
|
||||
assert "Updated PSBT is:" in story
|
||||
assert "Finalized transaction (ready for broadcast)" in story
|
||||
assert "TXID" in story
|
||||
split_story = story.split("\n\n")
|
||||
story_txid = split_story[-2].split("\n")[-1]
|
||||
signed_psbt_fname = split_story[1]
|
||||
with open(microsd_path(signed_psbt_fname), "r") as f:
|
||||
signed_psbt = f.read().strip()
|
||||
signed_txn_fname = split_story[3]
|
||||
with open(microsd_path(signed_txn_fname), "r") as f:
|
||||
signed_txn = f.read().strip()
|
||||
signed_psbt, signed_txn, story_txid = file_tx_signing_done(story)
|
||||
assert signed_psbt != psbt
|
||||
finalize_res = sim.finalizepsbt(signed_psbt)
|
||||
bitcoind_signed_txn = finalize_res["hex"]
|
||||
@ -2650,12 +2653,11 @@ def test_nsequence_timebased_relative_locktime_ux(seconds, use_regtest, bitcoind
|
||||
|
||||
|
||||
@pytest.mark.bitcoind
|
||||
@pytest.mark.veryslow
|
||||
@pytest.mark.parametrize("abs_lock", [True, False])
|
||||
@pytest.mark.parametrize("num_rtl", [(2,3),(4,7),(8,3),(6,7)])
|
||||
def test_mixed_locktimes(num_rtl, use_regtest, bitcoind_d_sim_watch, start_sign,
|
||||
microsd_path, cap_story, goto_home, press_select,
|
||||
pick_menu_item, bitcoind, end_sign, abs_lock):
|
||||
def test_mixed_locktimes(num_rtl, use_regtest, bitcoind_d_sim_watch, start_sign, microsd_path,
|
||||
cap_story, goto_home, press_select, pick_menu_item, bitcoind, end_sign,
|
||||
abs_lock, file_tx_signing_done):
|
||||
tb, bb = num_rtl
|
||||
num_ins = tb + bb
|
||||
sequence = SEQUENCE_LOCKTIME_TYPE_FLAG | (512 >> 9)
|
||||
@ -2703,9 +2705,11 @@ def test_mixed_locktimes(num_rtl, use_regtest, bitcoind_d_sim_watch, start_sign,
|
||||
goto_home()
|
||||
pick_menu_item('Ready To Sign')
|
||||
time.sleep(0.1)
|
||||
pick_menu_item(psbt_fname)
|
||||
time.sleep(0.1)
|
||||
title, story = cap_story()
|
||||
if 'OK TO SEND' not in title:
|
||||
pick_menu_item(psbt_fname)
|
||||
time.sleep(0.1)
|
||||
title, story = cap_story()
|
||||
|
||||
assert "WARNING" not in story
|
||||
assert "TX LOCKTIMES" in story
|
||||
@ -2726,18 +2730,10 @@ def test_mixed_locktimes(num_rtl, use_regtest, bitcoind_d_sim_watch, start_sign,
|
||||
press_select() # confirm signing
|
||||
time.sleep(0.1)
|
||||
title, story = cap_story()
|
||||
assert title == 'PSBT Signed'
|
||||
assert "Updated PSBT is:" in story
|
||||
assert "Finalized transaction (ready for broadcast)" in story
|
||||
assert "TXID" in story
|
||||
split_story = story.split("\n\n")
|
||||
story_txid = split_story[-2].split("\n")[-1]
|
||||
signed_psbt_fname = split_story[1]
|
||||
with open(microsd_path(signed_psbt_fname), "r") as f:
|
||||
signed_psbt = f.read().strip()
|
||||
signed_txn_fname = split_story[3]
|
||||
with open(microsd_path(signed_txn_fname), "r") as f:
|
||||
signed_txn = f.read().strip()
|
||||
signed_psbt, signed_txn, story_txid = file_tx_signing_done(story)
|
||||
assert signed_psbt != psbt
|
||||
finalize_res = sim.finalizepsbt(signed_psbt)
|
||||
bitcoind_signed_txn = finalize_res["hex"]
|
||||
@ -2791,55 +2787,55 @@ def random_nLockTime_test_cases(num=10):
|
||||
])
|
||||
def test_timelocks_visualize(start_sign, end_sign, dev, bitcoind, use_regtest,
|
||||
bitcoind_d_sim_watch, nLockTime):
|
||||
# - works on simulator and connected USB real-device
|
||||
nLockTime, expect_ux = nLockTime
|
||||
num_ins = 10
|
||||
use_regtest()
|
||||
bitcoind_d_sim_watch.keypoolrefill(20)
|
||||
for i in range(num_ins):
|
||||
addr = bitcoind_d_sim_watch.getnewaddress()
|
||||
bitcoind.supply_wallet.sendtoaddress(addr, 1)
|
||||
# - works on simulator and connected USB real-device
|
||||
nLockTime, expect_ux = nLockTime
|
||||
num_ins = 10
|
||||
use_regtest()
|
||||
bitcoind_d_sim_watch.keypoolrefill(20)
|
||||
for i in range(num_ins):
|
||||
addr = bitcoind_d_sim_watch.getnewaddress()
|
||||
bitcoind.supply_wallet.sendtoaddress(addr, 1)
|
||||
|
||||
bitcoind.supply_wallet.generatetoaddress(1, bitcoind.supply_wallet.getnewaddress())
|
||||
dest_addr = bitcoind_d_sim_watch.getnewaddress() # self-spend
|
||||
utxos = bitcoind_d_sim_watch.listunspent()
|
||||
assert len(utxos) == num_ins
|
||||
bitcoind.supply_wallet.generatetoaddress(1, bitcoind.supply_wallet.getnewaddress())
|
||||
dest_addr = bitcoind_d_sim_watch.getnewaddress() # self-spend
|
||||
utxos = bitcoind_d_sim_watch.listunspent()
|
||||
assert len(utxos) == num_ins
|
||||
|
||||
ins = []
|
||||
for i, utxo in enumerate(utxos):
|
||||
if i % 2 == 0:
|
||||
nSeq = (SEQUENCE_LOCKTIME_TYPE_FLAG | i)
|
||||
else:
|
||||
confirmations = utxo["confirmations"]
|
||||
nSeq = confirmations + (20*i)
|
||||
ins = []
|
||||
for i, utxo in enumerate(utxos):
|
||||
if i % 2 == 0:
|
||||
nSeq = (SEQUENCE_LOCKTIME_TYPE_FLAG | i)
|
||||
else:
|
||||
confirmations = utxo["confirmations"]
|
||||
nSeq = confirmations + (20*i)
|
||||
|
||||
inp = {
|
||||
"txid": utxo["txid"],
|
||||
"vout": utxo["vout"],
|
||||
"sequence": nSeq,
|
||||
}
|
||||
ins.append(inp)
|
||||
inp = {
|
||||
"txid": utxo["txid"],
|
||||
"vout": utxo["vout"],
|
||||
"sequence": nSeq,
|
||||
}
|
||||
ins.append(inp)
|
||||
|
||||
psbt_resp = bitcoind_d_sim_watch.walletcreatefundedpsbt(
|
||||
ins, [{dest_addr: (num_ins - 0.1)}],
|
||||
nLockTime, {"fee_rate": 20}
|
||||
)
|
||||
psbt = base64.b64decode(psbt_resp.get("psbt"))
|
||||
psbt_resp = bitcoind_d_sim_watch.walletcreatefundedpsbt(
|
||||
ins, [{dest_addr: (num_ins - 0.1)}],
|
||||
nLockTime, {"fee_rate": 20}
|
||||
)
|
||||
psbt = base64.b64decode(psbt_resp.get("psbt"))
|
||||
|
||||
open('debug/locktimes.psbt', 'wb').write(psbt)
|
||||
open('debug/locktimes.psbt', 'wb').write(psbt)
|
||||
|
||||
# should be able to sign, but get warning
|
||||
# should be able to sign, but get warning
|
||||
|
||||
# use new feature to have Coldcard return the 'visualization' of transaction
|
||||
start_sign(psbt, False, stxn_flags=STXN_VISUALIZE)
|
||||
story = end_sign(accept=None, expect_txn=False)
|
||||
# use new feature to have Coldcard return the 'visualization' of transaction
|
||||
start_sign(psbt, False, stxn_flags=STXN_VISUALIZE)
|
||||
story = end_sign(accept=None, expect_txn=False)
|
||||
|
||||
story = story.decode('ascii')
|
||||
assert datetime.datetime.utcfromtimestamp(nLockTime).strftime("%Y-%m-%d %H:%M:%S") == expect_ux
|
||||
assert f"Abs Locktime: This tx can only be spent after {expect_ux} UTC (MTP)" in story
|
||||
assert "Block height RTL: 5 inputs have relative block height timelock" in story
|
||||
# when i=0 in loop time based RTL is zero
|
||||
assert "Time-based RTL: 4 inputs have relative time-based timelock" in story
|
||||
story = story.decode('ascii')
|
||||
assert datetime.datetime.utcfromtimestamp(nLockTime).strftime("%Y-%m-%d %H:%M:%S") == expect_ux
|
||||
assert f"Abs Locktime: This tx can only be spent after {expect_ux} UTC (MTP)" in story
|
||||
assert "Block height RTL: 5 inputs have relative block height timelock" in story
|
||||
# when i=0 in loop time based RTL is zero
|
||||
assert "Time-based RTL: 4 inputs have relative time-based timelock" in story
|
||||
|
||||
|
||||
@pytest.mark.parametrize('in_out', [(4,1),(2,2),(2,1)])
|
||||
@ -2947,7 +2943,6 @@ def test_sorting_outputs_by_size(fake_txn, start_sign, cap_story, use_testnet,
|
||||
press_cancel()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("psbtv2", [True, False])
|
||||
@pytest.mark.parametrize("chain", ["BTC", "XTN"])
|
||||
@pytest.mark.parametrize("data", [
|
||||
# (out_style, amount, is_change)
|
||||
@ -2956,8 +2951,9 @@ def test_sorting_outputs_by_size(fake_txn, start_sign, cap_story, use_testnet,
|
||||
[("p2pkh", 1000000, 1)] * 11 + [("p2wpkh", 50000000, 0)] * 16,
|
||||
[("p2pkh", 1000000, 1), ("p2wpkh", 50000000, 0), ("p2wpkh-p2sh", 800000, 1)] * 11,
|
||||
])
|
||||
def test_txout_explorer(psbtv2, chain, data, fake_txn, start_sign,
|
||||
settings_set, txout_explorer, cap_story):
|
||||
def test_txout_explorer(chain, data, fake_txn, start_sign, settings_set, txout_explorer,
|
||||
cap_story, pytestconfig):
|
||||
# TODO This test MUST be run with --psbt2 flag on and off
|
||||
settings_set("chain", chain)
|
||||
outstyles = []
|
||||
outvals = []
|
||||
@ -2972,7 +2968,7 @@ def test_txout_explorer(psbtv2, chain, data, fake_txn, start_sign,
|
||||
inp_amount = sum(outvals) + 100000 # 100k sat fee
|
||||
psbt = fake_txn(1, len(data), segwit_in=True, outstyles=outstyles,
|
||||
outvals=outvals, change_outputs=change_outputs,
|
||||
psbt_v2=psbtv2, input_amount=inp_amount)
|
||||
psbt_v2=pytestconfig.getoption('psbt2'), input_amount=inp_amount)
|
||||
|
||||
start_sign(psbt)
|
||||
txout_explorer(data, chain)
|
||||
|
||||
@ -82,7 +82,7 @@ def grab_payload(press_select, need_keypress, press_cancel, nfc_read_url, cap_s
|
||||
|
||||
url = nfc_read_url().replace('%24', '$')
|
||||
|
||||
assert url.startswith('https://keyteleport.com#')
|
||||
assert url.startswith('https://keyteleport.com/#')
|
||||
|
||||
nfc_data = url.rsplit('#')[1]
|
||||
assert nfc_data.startswith(f'B$2{tt_code}0100')
|
||||
@ -401,6 +401,7 @@ def test_tx_wrong_pub(rx_start, tx_start, cap_menu, enter_complex, pick_menu_ite
|
||||
# now, send that back
|
||||
rx_complete(data, pw, expect_fail=True)
|
||||
|
||||
time.sleep(.1)
|
||||
title, body = cap_story()
|
||||
|
||||
assert title == 'Teleport Fail'
|
||||
@ -416,11 +417,11 @@ def test_tx_wrong_pub(rx_start, tx_start, cap_menu, enter_complex, pick_menu_ite
|
||||
@pytest.mark.parametrize('incl_xpubs', [ False ])
|
||||
def test_teleport_ms_sign(M, use_regtest, make_myself_wallet, segwit, num_ins, dev, clear_ms,
|
||||
fake_ms_txn, try_sign, incl_xpubs, bitcoind, cap_story, need_keypress,
|
||||
cap_menu, pick_menu_item, grab_payload, rx_complete, press_select, ndef_parse_txn_psbt,
|
||||
press_nfc, nfc_read, settings_get, settings_set):
|
||||
cap_menu, pick_menu_item, grab_payload, rx_complete, press_select,
|
||||
ndef_parse_txn_psbt, press_nfc, nfc_read, settings_get, settings_set,
|
||||
txid_from_export_prompt):
|
||||
|
||||
# IMPORTANT: won't work if you start simulator with --ms flag. Use no args
|
||||
|
||||
all_out_styles = list(unmap_addr_fmt.keys())
|
||||
num_outs = len(all_out_styles)
|
||||
|
||||
@ -440,13 +441,15 @@ def test_teleport_ms_sign(M, use_regtest, make_myself_wallet, segwit, num_ins, d
|
||||
cur_wallet = 0
|
||||
my_xfp = select_wallet(cur_wallet)
|
||||
|
||||
_, updated = try_sign(psbt, accept_ms_import=incl_xpubs)
|
||||
_, updated = try_sign(psbt, accept_ms_import=incl_xpubs, exit_export_loop=False)
|
||||
open(f'debug/myself-after-1.psbt', 'wb').write(updated)
|
||||
assert updated != psbt
|
||||
|
||||
title, body = cap_story()
|
||||
assert title == 'Teleport PSBT?'
|
||||
assert 'Press (T)' in body
|
||||
assert title == "PSBT Signed"
|
||||
assert '(T) to use Key Teleport to send PSBT to other co-signers' in body
|
||||
|
||||
num_sigs_needed = M - 1 # we have already signed with first at this point
|
||||
|
||||
while 1:
|
||||
# expect: a menu of other signers to pick from
|
||||
@ -483,6 +486,13 @@ def test_teleport_ms_sign(M, use_regtest, make_myself_wallet, segwit, num_ins, d
|
||||
nn = xfp2str(next_xfp)
|
||||
open(f'debug/next_qr_{nn}.txt', 'wt').write(f'{nn}\n\n{pw}\n\n{data}')
|
||||
|
||||
time.sleep(.1)
|
||||
title, story = cap_story()
|
||||
assert title == 'Sent by Teleport'
|
||||
s, aux = ("", "is") if num_sigs_needed == 1 else ("s", "are")
|
||||
msg = "%d more signature%s %s still required." % (num_sigs_needed, s, aux)
|
||||
assert msg in story
|
||||
|
||||
# switch personalities, and try to read that QR
|
||||
new_xfp = select_wallet(idx)
|
||||
assert new_xfp == next_xfp
|
||||
@ -499,14 +509,14 @@ def test_teleport_ms_sign(M, use_regtest, make_myself_wallet, segwit, num_ins, d
|
||||
time.sleep(.25)
|
||||
|
||||
title, body = cap_story()
|
||||
if title != 'Teleport PSBT?':
|
||||
if 'Finalized TX' in body:
|
||||
break
|
||||
|
||||
assert title == 'Teleport PSBT?'
|
||||
assert 'more signatures' in body
|
||||
assert '(T) to use Key Teleport to send PSBT to other co-signers' in body
|
||||
num_sigs_needed -= 1
|
||||
|
||||
assert title == 'Final TXID'
|
||||
txid = body.split()[0]
|
||||
txid = txid_from_export_prompt()
|
||||
press_select() # exit QR
|
||||
|
||||
# share signed txn via low-level NFC
|
||||
press_nfc()
|
||||
@ -519,11 +529,11 @@ def test_teleport_ms_sign(M, use_regtest, make_myself_wallet, segwit, num_ins, d
|
||||
assert got_txn
|
||||
|
||||
|
||||
def test_teleport_big_ms(make_myself_wallet, clear_ms,
|
||||
fake_ms_txn, try_sign, cap_story, need_keypress,
|
||||
cap_menu, pick_menu_item, grab_payload, rx_complete, press_select, ndef_parse_txn_psbt,
|
||||
set_master_key,
|
||||
goto_home, press_nfc, nfc_read, settings_get, settings_set, open_microsd, import_ms_wallet):
|
||||
def test_teleport_big_ms(make_myself_wallet, clear_ms, fake_ms_txn, try_sign, cap_story,
|
||||
need_keypress, cap_menu, pick_menu_item, grab_payload, rx_complete,
|
||||
press_select, ndef_parse_txn_psbt, set_master_key, goto_home, press_nfc,
|
||||
nfc_read, settings_get, settings_set, open_microsd, import_ms_wallet,
|
||||
press_cancel):
|
||||
|
||||
# define lots of wallets and do teleport from SD disk
|
||||
|
||||
@ -567,8 +577,8 @@ def test_teleport_big_ms(make_myself_wallet, clear_ms,
|
||||
|
||||
# have 1 sigs now, need one more via teleport
|
||||
title, body = cap_story()
|
||||
assert title == 'Teleport PSBT?'
|
||||
need_keypress('t')
|
||||
assert '(T) to use Key Teleport to send PSBT to other co-signers' in body
|
||||
need_keypress('t')
|
||||
|
||||
# pick another one randomly
|
||||
m = cap_menu()
|
||||
@ -600,8 +610,8 @@ def test_teleport_big_ms(make_myself_wallet, clear_ms,
|
||||
time.sleep(.25)
|
||||
|
||||
title, body = cap_story()
|
||||
|
||||
assert title == 'Final TXID'
|
||||
assert 'Finalized TX' in body
|
||||
press_cancel()
|
||||
|
||||
|
||||
@pytest.mark.manual
|
||||
|
||||
@ -94,7 +94,6 @@ def try_sign_virtdisk(press_select, virtdisk_path, cap_story, virtdisk_wipe, pre
|
||||
# wait for it to finish signing
|
||||
time.sleep(.1)
|
||||
title, story = cap_story()
|
||||
assert "PSBT Signed" in title
|
||||
|
||||
split_story = story.split("\n\n")
|
||||
result_fn = split_story[1]
|
||||
@ -103,11 +102,7 @@ def try_sign_virtdisk(press_select, virtdisk_path, cap_story, virtdisk_wipe, pre
|
||||
if expect_finalize:
|
||||
result_txn = split_story[3]
|
||||
result_txid = split_story[4].split("\n")[-1]
|
||||
reexport_msg = split_story[5]
|
||||
else:
|
||||
reexport_msg = split_story[2]
|
||||
|
||||
assert "Press (0) to save again by another method." == reexport_msg
|
||||
got_psbt = None
|
||||
got_txn = None
|
||||
txid, got_txid = None, None
|
||||
|
||||
Loading…
Reference in New Issue
Block a user