show descriptor & key expression in story; signed key expression export

This commit is contained in:
scgbckbone 2026-03-03 04:10:11 +01:00 committed by doc-hex
parent 45542d1d4f
commit cfc46b565e
3 changed files with 57 additions and 24 deletions

View File

@ -1198,8 +1198,8 @@ async def ss_descriptor_skeleton(_0, _1, item):
async def key_expression_skeleton_step2(_1, _2, item):
# pick a semi-random file name, render and save it.
orig_path = item.arg
await make_key_expression_export(orig_path)
orig_path, addr_fmt = item.arg
await make_key_expression_export(orig_path, addr_fmt)
async def key_expression_skeleton(_0, _1, item):
# Export key expression -> [xfp/d/e/r]xpub
@ -1212,12 +1212,14 @@ async def key_expression_skeleton(_0, _1, item):
elif ch != 'y':
return
# element on 2nd index is address format for signed exports
# if multisig key use p2pkh
todo = [
("Segwit P2WPKH", "m/84h/%dh/%dh"),
("Classic P2PKH", "m/44h/%dh/%dh"),
("P2SH-Segwit", "m/49h/%dh/%dh"),
("Multi P2WSH", "m/48h/%dh/%dh/2h"),
("Multi P2SH-P2WSH", "m/48h/%dh/%dh/1h"),
("Segwit P2WPKH", "m/84h/%dh/%dh", AF_P2WPKH),
("Classic P2PKH", "m/44h/%dh/%dh", AF_CLASSIC),
("P2SH-Segwit", "m/49h/%dh/%dh", AF_P2WPKH_P2SH),
("Multi P2WSH", "m/48h/%dh/%dh/2h", AF_CLASSIC),
("Multi P2SH-P2WSH", "m/48h/%dh/%dh/1h", AF_CLASSIC),
]
from address_explorer import KeypathMenu
@ -1228,8 +1230,8 @@ async def key_expression_skeleton(_0, _1, item):
ct = chains.current_chain().b44_cointype
rv = [
MenuItem(label, f=key_expression_skeleton_step2, arg=orig_der % (ct, acct_num))
for label, orig_der in todo
MenuItem(label, f=key_expression_skeleton_step2, arg=(orig_der % (ct, acct_num), af))
for label, orig_der, af in todo
]
rv += [MenuItem("Custom Path", menu=doit)]

View File

@ -35,7 +35,8 @@ async def export_by_qr(body, label, type_code, force_bbqr=False):
async def export_contents(title, contents, fname_pattern, derive=None, addr_fmt=None,
is_json=False, force_bbqr=False, force_prompt=False, direct_way=None):
is_json=False, force_bbqr=False, force_prompt=False, direct_way=None,
intro="", footer="", ux_title=None):
# 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)
# checks if suitable to offer QR export on Mk4
@ -59,8 +60,8 @@ async def export_contents(title, contents, fname_pattern, derive=None, addr_fmt=
ch = direct_way # set it to direct way only once, outside the loop
while True:
if direct_way is None:
ch = await import_export_prompt("%s file" % title,
force_prompt=force_prompt, no_qr=no_qr)
ch = await import_export_prompt("%s file" % title, intro=intro, footnotes=footer,
force_prompt=force_prompt, no_qr=no_qr, title=ux_title)
if ch == KEY_CANCEL:
break
elif ch == KEY_QR:
@ -500,11 +501,15 @@ async def make_descriptor_wallet_export(addr_type, account_num=0, mode=None, int
)
dis.progress_bar_show(1)
await export_contents("Descriptor", body, fname_pattern, derive + "/0/0",
addr_type, force_prompt=True, direct_way=direct_way)
intro, footer = (body, "") if version.has_qwerty else ("", body)
title = "Descriptor"
await export_contents(title, body, fname_pattern, derive + "/0/0", addr_type,
force_prompt=True, direct_way=direct_way, intro=intro, footer=footer,
ux_title=title if version.has_qwerty else None)
async def make_key_expression_export(orig_der, fname_pattern="key_expr.txt"):
async def make_key_expression_export(orig_der, addr_fmt=AF_CLASSIC, fname_pattern="key_expr.txt"):
from glob import dis
dis.fullscreen('Generating...')
@ -516,8 +521,11 @@ async def make_key_expression_export(orig_der, fname_pattern="key_expr.txt"):
body = "[%s/%s]%s" % (xfp, orig_der.replace("m/", ""), ek)
await export_contents("Key Expression", body, fname_pattern,
None, None, force_prompt=True)
intro, footer = (body, "") if version.has_qwerty else ("", body)
title = "Key Expression"
await export_contents(title, body, fname_pattern, orig_der + "/0/0", addr_fmt,
force_prompt=True, intro=intro, footer=footer,
ux_title=title if version.has_qwerty else None)
# EOF

View File

@ -643,7 +643,7 @@ def test_export_xpub(chain, acct_num, dev, cap_menu, pick_menu_item, goto_home,
def test_generic_descriptor_export(chain, addr_fmt, acct_num, goto_home,
settings_set, need_keypress, expect_acctnum_captured, OK,
pick_menu_item, way, cap_story, cap_menu, int_ext, settings_get,
virtdisk_path, load_export, press_select, skip_if_useless_way):
virtdisk_path, load_export, press_select, skip_if_useless_way, is_q1):
skip_if_useless_way(way)
@ -697,9 +697,16 @@ def test_generic_descriptor_export(chain, addr_fmt, acct_num, goto_home,
expect_acctnum_captured(acct_num)
time.sleep(.1)
title, story = cap_story()
idx = 0 if is_q1 else 1
story_desc = story.split("\n\n")[idx]
contents = load_export(way, label="Descriptor", is_json=False, addr_fmt=addr_fmt)
descriptor = contents.strip()
assert descriptor == story_desc.strip()
if int_ext is False:
descriptor = descriptor.split("\n")[0] # external
assert descriptor.startswith(desc_prefix)
@ -771,6 +778,8 @@ def test_zeus_descriptor_export(addr_fmt, acct_num, goto_home, need_keypress, pi
time.sleep(.1)
title, story = cap_story()
idx = 0 if is_q1 else 1
story_desc = story.split("\n\n")[idx]
expect_acctnum_captured(acct_num)
@ -789,6 +798,8 @@ def test_zeus_descriptor_export(addr_fmt, acct_num, goto_home, need_keypress, pi
descriptor = contents.strip()
assert descriptor == story_desc.strip()
assert descriptor.startswith(desc_prefix)
desc_obj = Descriptor.parse(descriptor)
assert desc_obj.serialize(int_ext=True) == descriptor
@ -895,7 +906,7 @@ def test_samourai_vs_generic(chain, account, settings_set, pick_menu_item, goto_
@pytest.mark.parametrize("acct_num", [None, (2 ** 31) - 1])
def test_key_expression_export(chain, addr_fmt, acct_num, goto_home, settings_set, need_keypress,
pick_menu_item, way, cap_story, cap_menu, virtdisk_path, dev,
load_export, press_select, skip_if_useless_way):
load_export, press_select, skip_if_useless_way, is_q1):
skip_if_useless_way(way, allow_mk4_qr=True)
@ -934,15 +945,22 @@ def test_key_expression_export(chain, addr_fmt, acct_num, goto_home, settings_se
elif addr_fmt == AF_P2WSH:
menu_item = "Multi P2WSH"
derive = f"m/48h/{chain_num}h/{acct_num}h/2h"
addr_fmt = AF_CLASSIC
else:
assert addr_fmt == AF_P2WSH_P2SH
menu_item = "Multi P2SH-P2WSH"
derive = f"m/48h/{chain_num}h/{acct_num}h/1h"
addr_fmt = AF_CLASSIC
assert menu_item in menu
pick_menu_item(menu_item)
contents = load_export(way, label="Key Expression", is_json=False, sig_check=False)
time.sleep(.1)
title, story = cap_story()
idx = 0 if is_q1 else 1
story_key_exp = story.split("\n\n")[idx]
contents = load_export(way, label="Key Expression", is_json=False,addr_fmt=addr_fmt)
key_exp = contents.strip()
xfp = dev.master_fingerprint
@ -954,7 +972,7 @@ def test_key_expression_export(chain, addr_fmt, acct_num, goto_home, settings_se
).subkey_for_path(derive)
target = f"[{xfp}/{derive.replace('m/', '')}]{node.hwif()}"
assert key_exp == target
assert key_exp == target == story_key_exp
@pytest.mark.parametrize('path', [
@ -965,7 +983,7 @@ def test_key_expression_export(chain, addr_fmt, acct_num, goto_home, settings_se
"m/45h",
])
def test_custom_key_expression_export(path, goto_home, pick_menu_item, cap_menu, need_keypress,
press_select, load_export, use_testnet, dev):
press_select, load_export, use_testnet, dev, cap_story, is_q1):
use_testnet()
goto_home()
pick_menu_item("Advanced/Tools")
@ -1003,7 +1021,12 @@ def test_custom_key_expression_export(path, goto_home, pick_menu_item, cap_menu,
m = cap_menu()
pick_menu_item(m[2 if part[-1] == "h" else 3])
contents = load_export("sd", label="Key Expression", is_json=False, sig_check=False)
time.sleep(.25)
title, story = cap_story()
idx = 0 if is_q1 else 1
story_key_exp = story.split("\n\n")[idx]
contents = load_export("sd", label="Key Expression", is_json=False)
key_exp = contents.strip()
xfp = dev.master_fingerprint
@ -1013,6 +1036,6 @@ def test_custom_key_expression_export(path, goto_home, pick_menu_item, cap_menu,
node = BIP32Node.from_master_secret(seed, netcode="XTN").subkey_for_path(path)
target = f"[{xfp}/{path.replace('m/', '')}]{node.hwif()}"
assert key_exp == target
assert key_exp == target == story_key_exp
# EOF