Batch Sign

(cherry picked from commit 39a125a753)
This commit is contained in:
scgbckbone 2023-06-18 09:04:16 +02:00 committed by doc-hex
parent 336f5f6e04
commit 7193e284b2
5 changed files with 100 additions and 17 deletions

View File

@ -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]`

View File

@ -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)

View File

@ -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),

View File

@ -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 += "."

View File

@ -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