parent
336f5f6e04
commit
7193e284b2
@ -1,5 +1,6 @@
|
||||
## 5.1.3 - 2023-06-20
|
||||
|
||||
- New Feature: Batch Sign PSBTs. `Advanced/Tools -> File Management -> Batch Sign`
|
||||
- Enhancement: change Key Origin Information export format in multisig `addresses.csv` to match
|
||||
[BIP-0380](https://github.com/bitcoin/bips/blob/master/bip-0380.mediawiki#key-expressions)
|
||||
was `(m=0F056943)/m/48'/1'/0'/2'/0/0` now `[0F056943/48'/1'/0'/2'/0/0]`
|
||||
|
||||
@ -20,6 +20,7 @@ from public_constants import AF_CLASSIC, AF_P2WPKH, AF_P2WPKH_P2SH, AF_P2TR
|
||||
from glob import settings
|
||||
from pincodes import pa
|
||||
from menu import start_chooser
|
||||
from version import MAX_TXN_LEN
|
||||
|
||||
|
||||
CLEAR_PIN = '999999-999999'
|
||||
@ -1700,29 +1701,57 @@ async def bless_flash(*a):
|
||||
dis.show()
|
||||
|
||||
|
||||
def is_psbt(filename):
|
||||
if '-signed' in filename.lower(): # XXX problem: multi-signers?
|
||||
return False
|
||||
|
||||
with open(filename, 'rb') as fd:
|
||||
taste = fd.read(10)
|
||||
if taste[0:5] == b'psbt\xff':
|
||||
return True
|
||||
if taste[0:10] == b'70736274ff': # hex-encoded
|
||||
return True
|
||||
if taste[0:6] == b'cHNidP': # base64-encoded
|
||||
return True
|
||||
return False
|
||||
|
||||
async def batch_sign(*a):
|
||||
|
||||
force_vdisk = False
|
||||
prompt, escape = import_prompt_builder("PSBTs", no_nfc=True)
|
||||
if prompt:
|
||||
ch = await ux_show_story(prompt, escape=escape)
|
||||
if ch == "x": return
|
||||
if ch == "2":
|
||||
force_vdisk = True
|
||||
|
||||
choices = await file_picker(None, suffix='psbt', min_size=50,
|
||||
force_vdisk=force_vdisk,
|
||||
max_size=MAX_TXN_LEN, taster=is_psbt)
|
||||
if not choices:
|
||||
await ux_show_story("No PSBTs found. Need to have '.psbt' suffix.")
|
||||
|
||||
from auth import sign_psbt_file
|
||||
from ux import the_ux
|
||||
for label, path, fn in choices:
|
||||
ch = await ux_show_story("Sign %s ??\n\nPress OK to sign, (1) to skip this PSBT,"
|
||||
" X to quit and exit." % fn, escape="1")
|
||||
if ch == "x": break
|
||||
elif ch == "y":
|
||||
input_psbt = path + '/' + fn
|
||||
await sign_psbt_file(input_psbt)
|
||||
await sleep_ms(100)
|
||||
await the_ux.top_of_stack().interact()
|
||||
|
||||
|
||||
async def ready2sign(*a):
|
||||
# Top menu choice of top menu! Signing!
|
||||
# - check if any signable in SD card, if so do it
|
||||
# - if no card, check virtual disk for PSBT
|
||||
# - if still nothing, then talk about USB connection
|
||||
from version import MAX_TXN_LEN
|
||||
import stash
|
||||
from glob import NFC
|
||||
|
||||
def is_psbt(filename):
|
||||
if '-signed' in filename.lower(): # XXX problem: multi-signers?
|
||||
return False
|
||||
|
||||
with open(filename, 'rb') as fd:
|
||||
taste = fd.read(10)
|
||||
if taste[0:5] == b'psbt\xff':
|
||||
return True
|
||||
if taste[0:10] == b'70736274ff': # hex-encoded
|
||||
return True
|
||||
if taste[0:6] == b'cHNidP': # base64-encoded
|
||||
return True
|
||||
return False
|
||||
|
||||
# just check if we have candidates, no UI
|
||||
choices = await file_picker(None, suffix='psbt', min_size=50,
|
||||
max_size=MAX_TXN_LEN, taster=is_psbt)
|
||||
|
||||
@ -183,6 +183,7 @@ FileMgmtMenu = [
|
||||
MenuItem("Verify Backup", f=verify_backup),
|
||||
MenuItem("Backup System", predicate=has_secrets, f=backup_everything),
|
||||
MenuItem('Export Wallet', predicate=has_secrets, menu=WalletExportMenu), #dup elsewhere
|
||||
MenuItem('Batch Sign', predicate=has_secrets, f=batch_sign),
|
||||
MenuItem('Sign Text File', predicate=has_secrets, f=sign_message_on_sd),
|
||||
#MenuItem('Upgrade Firmware', f=microsd_upgrade),
|
||||
MenuItem('Clone Coldcard', predicate=has_secrets, f=clone_write_data),
|
||||
|
||||
@ -483,7 +483,7 @@ def parse_extended_key(ln, private=False):
|
||||
return node, chain, addr_fmt
|
||||
|
||||
|
||||
def import_prompt_builder(title):
|
||||
def import_prompt_builder(title, no_nfc=False):
|
||||
from glob import NFC, VD
|
||||
prompt, escape = None, None
|
||||
if NFC or VD:
|
||||
@ -492,7 +492,7 @@ def import_prompt_builder(title):
|
||||
if VD is not None:
|
||||
prompt += ", press (2) to import from Virtual Disk"
|
||||
escape += "2"
|
||||
if NFC is not None:
|
||||
if NFC is not None and not no_nfc:
|
||||
prompt += ", press (3) to import via NFC"
|
||||
escape += "3"
|
||||
prompt += "."
|
||||
|
||||
@ -2316,4 +2316,56 @@ def test_invalid_output_tapproot_psbt(fake_txn, start_sign, cap_story, dev):
|
||||
# error messages are disabled to save some space - problem file line is still included
|
||||
# assert "PSBT_IN_TAP_BIP32_DERIVATION xonly-pubkey length != 32" in story
|
||||
|
||||
|
||||
@pytest.mark.parametrize("num_tx", [1, 2, 10])
|
||||
@pytest.mark.parametrize("action", ["sign", "skip", "refuse"])
|
||||
def test_batch_sign(num_tx, action, fake_txn, need_keypress, pick_menu_item,
|
||||
cap_story, microsd_path, microsd_wipe, goto_home):
|
||||
|
||||
goto_home()
|
||||
microsd_wipe()
|
||||
|
||||
for i in range(num_tx):
|
||||
psbt = fake_txn(2, 2, segwit_in=random.getrandbits(1))
|
||||
with open(microsd_path(f"{i}.psbt"), "wb") as f:
|
||||
f.write(psbt)
|
||||
|
||||
pick_menu_item("Advanced/Tools")
|
||||
pick_menu_item("File Management")
|
||||
pick_menu_item("Batch Sign")
|
||||
time.sleep(.1)
|
||||
title, story = cap_story()
|
||||
if "Press (1)" in story:
|
||||
need_keypress("1")
|
||||
time.sleep(.1)
|
||||
title, story = cap_story()
|
||||
|
||||
for i in range(num_tx):
|
||||
assert "Sign" in story
|
||||
assert "(1) to skip" in story
|
||||
assert "X to quit and exit" in story
|
||||
if action == "skip":
|
||||
need_keypress("1") # skip this PSBT
|
||||
time.sleep(.5)
|
||||
title, story = cap_story()
|
||||
continue
|
||||
|
||||
need_keypress("y") # sign this PSBT
|
||||
time.sleep(.5)
|
||||
title, story = cap_story()
|
||||
assert title == "OK TO SEND?"
|
||||
if action == "refuse":
|
||||
need_keypress("x") # refuse
|
||||
time.sleep(.5)
|
||||
title, story = cap_story()
|
||||
continue
|
||||
|
||||
need_keypress("y") # confirm send
|
||||
time.sleep(.5)
|
||||
title, story = cap_story()
|
||||
assert "-signed.psbt" in story
|
||||
need_keypress("y")
|
||||
time.sleep(.5)
|
||||
title, story = cap_story()
|
||||
|
||||
# EOF
|
||||
|
||||
Loading…
Reference in New Issue
Block a user