unified multisig import

This commit is contained in:
scgbckbone 2026-01-31 23:45:34 +01:00 committed by doc-hex
parent 366670b4d5
commit a3d5485fd2
3 changed files with 38 additions and 44 deletions

View File

@ -10,7 +10,7 @@ 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
from menu import MenuSystem, MenuItem, NonDefaultMenuItem, start_chooser, ToggleMenuItem
from menu import MenuSystem, MenuItem, NonDefaultMenuItem, start_chooser, ToggleMenuItem, ShortcutItem
from opcodes import OP_CHECKMULTISIG
from exceptions import FatalPSBTIssue
from glob import settings
@ -1380,7 +1380,7 @@ class MultisigMenu(MenuSystem):
@classmethod
def construct(cls):
# Dynamic menu with user-defined names of wallets shown
from glob import NFC
from flow import nfc_enabled
if not MultisigWallet.exists():
rv = [MenuItem('(none setup yet)', f=no_ms_yet)]
@ -1390,11 +1390,7 @@ class MultisigMenu(MenuSystem):
rv.append(MenuItem('%d/%d: %s' % (ms.M, ms.N, ms.name),
menu=make_ms_wallet_menu, arg=ms.storage_idx))
rv.append(MenuItem('Import from File', f=import_multisig))
rv.append(MenuItem('Import from QR', f=import_multisig_qr,
predicate=version.has_qwerty, shortcut=KEY_QR))
rv.append(MenuItem('Import via NFC', f=import_multisig_nfc,
predicate=bool(NFC), shortcut=KEY_NFC))
rv.append(MenuItem('Import', f=import_multisig))
rv.append(MenuItem('Export XPUB', f=export_multisig_xpubs))
rv.append(MenuItem('Create Airgapped', f=create_ms_step1))
rv.append(MenuItem('Trust PSBT?', f=trust_psbt_menu))
@ -1408,6 +1404,9 @@ class MultisigMenu(MenuSystem):
rv.append(NonDefaultMenuItem(
'Unsorted Multisig?' if version.has_qwerty else 'Unsorted Multi?',
'unsort_ms', f=unsorted_ms_menu))
rv.append(ShortcutItem(KEY_NFC, predicate=nfc_enabled, f=import_multisig_nfc))
rv.append(ShortcutItem(KEY_QR, predicate=version.has_qwerty, f=import_multisig_qr))
return rv
def update_contents(self):
@ -1913,26 +1912,19 @@ async def import_multisig_qr(*a):
except Exception as e:
await ux_show_story('Failed to import.\n\n%s\n%s' % (e, problem_file_line(e)))
async def import_multisig(*a):
# pick text file from SD card, import as multisig setup file
from actions import file_picker
from glob import VD
from ux import import_export_prompt
force_vdisk = False
if VD:
prompt = "Press (1) to import multisig wallet file from SD Card"
escape = "1"
if VD is not None:
prompt += ", press (2) to import from Virtual Disk"
escape += "2"
prompt += "."
ch = await ux_show_story(prompt, escape=escape)
if ch == "1":
force_vdisk=False
elif ch == "2":
force_vdisk = True
else:
return
ch = await import_export_prompt("multisig wallet file", is_import=True)
if isinstance(ch, str):
if ch == KEY_QR:
await import_multisig_qr()
elif ch == KEY_NFC:
await import_multisig_nfc()
return
def possible(filename):
with open(filename, 'rt') as fd:
@ -1944,11 +1936,11 @@ async def import_multisig(*a):
return True
fn = await file_picker(suffix=['.txt', '.json'], min_size=100, max_size=20*200,
taster=possible, force_vdisk=force_vdisk)
taster=possible, **ch)
if not fn: return
try:
with CardSlot(force_vdisk=force_vdisk) as card:
with CardSlot(**ch) as card:
with open(fn, 'rt') as fp:
data = fp.read()
except CardMissingError:
@ -1959,8 +1951,8 @@ async def import_multisig(*a):
try:
possible_name = (fn.split('/')[-1].split('.'))[0]
maybe_enroll_xpub(config=data, name=possible_name)
except Exception as e:
#import sys; sys.print_exception(e)
await ux_show_story('Failed to import.\n\n%s\n%s' % (e, problem_file_line(e)))
except BaseException as e:
# import sys; sys.print_exception(e)
await ux_show_story('Failed to import multisig.\n\n%s\n%s' % (e, problem_file_line(e)))
# EOF

View File

@ -1958,7 +1958,7 @@ def file_tx_signing_done(virtdisk_path, microsd_path):
txid = None
for l in _split:
if "TXID" in l:
if "TXID:" in l:
txid = l.split("\n")[-1].strip()
assert len(txid) == 64, "wrong txid"
break

View File

@ -161,7 +161,7 @@ def offer_ms_import(cap_story, dev, sim_root_dir):
return doit
@pytest.fixture
def import_multisig(request, is_q1, need_keypress, offer_ms_import):
def import_multisig(request, is_q1, need_keypress, offer_ms_import, press_nfc):
def doit(fname=None, way="sd", data=None, name=None):
assert fname or data
if fname:
@ -192,13 +192,15 @@ def import_multisig(request, is_q1, need_keypress, offer_ms_import):
pick_menu_item("Multisig Wallets")
time.sleep(.1)
ms_menu = cap_menu()
pick_menu_item("Import")
time.sleep(.1)
title, story = cap_story()
if way == "qr":
if "Import from QR" not in ms_menu and not is_q1:
if "to scan QR code" not in story and not is_q1:
pytest.skip("No QR support")
scan_a_qr = request.getfixturevalue('scan_a_qr')
pick_menu_item("Import from QR")
need_keypress(KEY_QR)
actual_vers, parts = split_qrs(config, 'U', max_version=20)
random.shuffle(parts)
@ -208,11 +210,11 @@ def import_multisig(request, is_q1, need_keypress, offer_ms_import):
time.sleep(2.0 / len(parts))
elif way == "nfc":
if "Import via NFC" not in ms_menu:
if "import via NFC" not in story:
pytest.skip("NFC disabled")
nfc_write_text = request.getfixturevalue('nfc_write_text')
pick_menu_item("Import via NFC")
press_nfc()
nfc_write_text(config)
time.sleep(0.5)
@ -228,9 +230,6 @@ def import_multisig(request, is_q1, need_keypress, offer_ms_import):
with open(path_f(fname), "w") as f:
f.write(config)
pick_menu_item("Import from File")
time.sleep(.1)
_, story = cap_story()
if way == "vdisk":
if "(2) to import from Virtual Disk" not in story:
pytest.skip("VDisk disabled")
@ -862,7 +861,7 @@ def test_import_ux(N, vdisk, goto_home, cap_story, pick_menu_item,
goto_home()
pick_menu_item('Settings')
pick_menu_item('Multisig Wallets')
pick_menu_item('Import from File')
pick_menu_item('Import')
time.sleep(0.5)
_, story = cap_story()
if vdisk:
@ -1735,7 +1734,7 @@ def test_make_airgapped(addr_fmt, acct_num, M_N, goto_home, cap_story, pick_menu
goto_home()
pick_menu_item('Settings')
pick_menu_item('Multisig Wallets')
pick_menu_item('Import from File')
pick_menu_item('Import')
time.sleep(0.5)
_, story = cap_story()
if "Press (1) to import multisig wallet file from SD Card" in story:
@ -2573,7 +2572,7 @@ def test_legacy_multisig_witness_utxo_in_psbt(bitcoind, use_regtest, clear_ms, m
goto_home()
pick_menu_item('Settings')
pick_menu_item('Multisig Wallets')
pick_menu_item('Import from File')
pick_menu_item('Import')
time.sleep(0.3)
_, story = cap_story()
if "Press (1) to import multisig wallet file from SD Card" in story:
@ -3065,7 +3064,7 @@ def test_exotic_descriptors(desc, clear_ms, goto_home, need_keypress, pick_menu_
goto_home()
pick_menu_item('Settings')
pick_menu_item('Multisig Wallets')
pick_menu_item('Import from File')
pick_menu_item('Import')
time.sleep(0.1)
_, story = cap_story()
if "Press (1) to import multisig wallet file from SD Card" in story:
@ -3336,7 +3335,8 @@ def test_scan_any_qr(fpath, is_q1, scan_a_qr, clear_ms, goto_home,
@pytest.mark.parametrize("N", [3, 15])
def test_bare_cc_ms_qr_import(N, make_multisig, scan_a_qr, clear_ms, goto_home,
pick_menu_item, cap_story, press_cancel, is_q1):
pick_menu_item, cap_story, press_cancel, is_q1,
need_keypress):
# bare:
# - no fingerprints
# - no xfps
@ -3368,7 +3368,9 @@ def test_bare_cc_ms_qr_import(N, make_multisig, scan_a_qr, clear_ms, goto_home,
# multisig import path needs to be used
pick_menu_item("Settings")
pick_menu_item("Multisig Wallets")
pick_menu_item("Import from QR")
pick_menu_item("Import")
need_keypress(KEY_QR)
for p in parts:
scan_a_qr(p)
time.sleep(2.0 / len(parts))