add ability to switch between slip132 and bip32 representations of extended public keys in Export XPUB

(cherry picked from commit de0a679eef)
This commit is contained in:
scgbckbone 2024-09-23 11:20:01 +02:00
parent 718c0ca354
commit 53b7aae325
4 changed files with 95 additions and 43 deletions

View File

@ -6,6 +6,7 @@ This lists the new changes that have not yet been published in a normal release.
- Enhancement: Hide Secure Notes & Passwords in Deltamode. Wipe seed if notes menu accessed.
- Enhancement: Hide Seed Vault in Deltamode. Wipe seed if Seed Vault menu accessed.
- Enhancement: Ability to switch between BIP-32 XPUB and SLIP-132 garbage in `Export XPUB`
- Bugfix: Sometimes see a struck screen after _Verifying..._ in boot up sequence.
On Q, result is blank screen, on Mk4, result is three-dots screen.
- Bugfix: Do not allow to enable/disable Seed Vault feature when in temporary seed mode
@ -16,6 +17,7 @@ This lists the new changes that have not yet been published in a normal release.
- Change: Testnet3 -> Testnet4 (all parameters are the same)
# Mk4 Specific Changes
## 5.4.1 - 2024-??-??

View File

@ -1018,6 +1018,7 @@ async def export_xpub(label, _2, item):
chain = chains.current_chain()
acct = 0
slip132 = False # non-slip is default from Oct 2024
# decode menu code => standard derivation
mode = item.arg
@ -1033,24 +1034,44 @@ async def export_xpub(label, _2, item):
else:
remap = {44:0, 49:1, 84:2,86:3}[mode]
_, path, addr_fmt = chains.CommonDerivations[remap]
path = path.format(account='{acct}', coin_type=chain.b44_cointype, change=0, idx=0)[:-4]
# always show SLIP-132 style, because defacto
show_slip132 = (addr_fmt != AF_CLASSIC)
path = path.format(account=acct, coin_type=chain.b44_cointype,
change=0, idx=0)[:-4]
while 1:
msg = '''Show QR of the XPUB for path:\n\n%s\n\n''' % path
msg = 'Show QR of the XPUB for path:\n\n%s\n\n' % path
esc = ""
if path != "m":
esc += "1"
msg += "Press (1) to select account other than %s. " % (acct or "zero")
if addr_fmt != AF_CLASSIC:
esc += "2"
slp_af = addr_fmt
if slip132:
slp_af = AF_CLASSIC
if '{acct}' in path:
msg += "Press (1) to select account other than zero. "
slp = chain.slip132[slp_af].hint + "pub"
msg += " Press (2) to show %s %s." % (
slp, "(BIP-32)" if slip132 else "(SLIP-132)"
)
if glob.NFC:
msg += "Press %s to share via NFC. " % (KEY_NFC if version.has_qwerty else "(3)")
if version.has_qwerty:
esc += KEY_NFC
key_hint = KEY_NFC
else:
esc += "3"
key_hint = "(3)"
msg += " Press %s to share via NFC. " % key_hint
ch = await ux_show_story(msg, escape='13')
ch = await ux_show_story(msg, escape=esc)
if ch == 'x': return
if ch == "2":
slip132 = not slip132
continue
if ch == '1':
acct = await ux_enter_bip32_index('Account Number:') or 0
path = path.format(acct=acct)
pth_split = path.split("/")
pth_split[-1] = ("%dh" % acct)
path = "/".join(pth_split)
continue
# assume zero account if not picked
@ -1062,7 +1083,7 @@ async def export_xpub(label, _2, item):
# render xpub/ypub/zpub
with stash.SensitiveValues() as sv:
node = sv.derive_path(path) if path != 'm' else sv.node
xpub = chain.serialize_public(node, addr_fmt)
xpub = chain.serialize_public(node, addr_fmt if slip132 else AF_CLASSIC)
from ownership import OWNERSHIP
OWNERSHIP.note_wallet_used(addr_fmt, acct)
@ -1072,8 +1093,6 @@ async def export_xpub(label, _2, item):
else:
await show_qr_code(xpub, False)
break
def electrum_export_story(background=False):
# saves memory being in a function

View File

@ -180,7 +180,7 @@ XpubExportMenu = [
MenuItem("Segwit (BIP-84)", f=export_xpub, arg=84),
MenuItem("Classic (BIP-44)", f=export_xpub, arg=44),
MenuItem("Taproot/P2TR(86)", f=export_xpub, arg=86),
MenuItem("P2WPKH/P2SH (49)", f=export_xpub, arg=49),
MenuItem("P2WPKH/P2SH "+("(BIP-49)"if version.has_qwerty else "(49)"), f=export_xpub, arg=49),
MenuItem("Master XPUB", f=export_xpub, arg=0),
MenuItem("Current XFP", f=export_xpub, arg=-1),
]

View File

@ -547,15 +547,15 @@ def test_export_public_txt(way, dev, pick_menu_item, goto_home, press_select, mi
@pytest.mark.qrcode
@pytest.mark.parametrize('chain', ["BTC", "XTN"])
@pytest.mark.parametrize('acct_num', [ None, 0, 99, 8989])
@pytest.mark.parametrize('use_nfc', [False, True])
def test_export_xpub(use_nfc, acct_num, dev, cap_menu, pick_menu_item, goto_home,
def test_export_xpub(chain, acct_num, dev, cap_menu, pick_menu_item, goto_home,
cap_story, need_keypress, enter_number, cap_screen_qr,
use_mainnet, nfc_read_text, is_q1, press_select, press_cancel,
settings_set, nfc_read_text, is_q1, press_select, press_cancel,
press_nfc, expect_acctnum_captured):
# XPUB's via QR
use_mainnet()
settings_set("chain", chain)
chain_num = 0 if chain == "BTC" else 1
goto_home()
pick_menu_item('Advanced/Tools')
pick_menu_item('Export Wallet')
@ -565,13 +565,13 @@ def test_export_xpub(use_nfc, acct_num, dev, cap_menu, pick_menu_item, goto_home
for m in top_items:
is_xfp = False
if '-84' in m:
expect = "m/84h/0h/{acct}h"
elif '86' in m and 'P2TR' in m:
expect = "m/86h/0h/{acct}h"
expect = f"m/84h/{chain_num}h/{{acct}}h"
elif '-86' in m:
expect = f"m/86h/{chain_num}h/{{acct}}h"
elif '-44' in m:
expect = "m/44h/0h/{acct}h"
expect = f"m/44h/{chain_num}h/{{acct}}h"
elif '49' in m:
expect = "m/49h/0h/{acct}h"
expect = f"m/49h/{chain_num}h/{{acct}}h"
elif 'Master' in m:
expect = "m"
elif 'XFP' in m:
@ -581,17 +581,21 @@ def test_export_xpub(use_nfc, acct_num, dev, cap_menu, pick_menu_item, goto_home
time.sleep(0.3)
if is_xfp:
got = cap_screen_qr().decode('ascii')
if use_nfc:
press_nfc()
assert got == xfp2str(simulator_fixed_xfp).upper()
press_cancel()
time.sleep(.1)
press_nfc()
time.sleep(.2)
nfc_got = nfc_read_text()
time.sleep(.2)
assert nfc_got == got == xfp2str(simulator_fixed_xfp).upper()
press_cancel() # cancel animation
press_cancel() # cancel QR
continue
time.sleep(0.3)
title, story = cap_story()
assert expect in story
assert expect.format(acct=0) in story
if 'acct' in expect:
if expect != "m":
assert "Press (1) to select account" in story
if acct_num is not None:
need_keypress('1')
@ -601,24 +605,52 @@ def test_export_xpub(use_nfc, acct_num, dev, cap_menu, pick_menu_item, goto_home
expect = expect.format(acct=acct_num)
title, story = cap_story()
assert expect in story
assert "Press (1) to select account" not in story
assert "Press (1) to select account" in story
expect = expect.format(acct=0)
if not use_nfc:
press_select()
got_pub = cap_screen_qr().decode('ascii')
else:
if f'Press {KEY_NFC if is_q1 else "(3)"}' not in story:
raise pytest.skip("NFC disabled")
expect = expect.format(acct=0)
press_select()
got_pub = cap_screen_qr().decode('ascii')
if f'Press {KEY_NFC if is_q1 else "(3)"}' in story:
assert 'NFC' in story
press_nfc()
time.sleep(0.2)
got_pub = nfc_read_text()
got_nfc_pub = nfc_read_text()
time.sleep(0.1)
#press_select()
press_cancel() # cancel animation
press_cancel() # cancel QR
assert got_nfc_pub == got_pub
if got_pub[0] not in 'xt':
got_pub,*_ = slip132undo(got_pub)
time.sleep(.1)
_, story = cap_story()
assert got_pub[0] in 'xt'
if "Press (2)" in story:
if chain == "BTC":
assert f"{'z' if expect[:5] == 'm/84h' else 'y'}pub (SLIP-132)" in story
else:
assert f"{'v' if expect[:5] == 'm/84h' else 'u'}pub (SLIP-132)" in story
need_keypress("2")
time.sleep(.1)
_, story = cap_story()
assert ("%spub (BIP-32)" % ("x" if chain == "BTC" else "t")) in story
assert "Press (2)" in story
press_select()
got_slip_pub = cap_screen_qr().decode('ascii')
got_unslip, *_ = slip132undo(got_slip_pub)
assert got_unslip == got_pub
if f'Press {KEY_NFC if is_q1 else "(3)"}' in story:
assert 'NFC' in story
press_nfc()
time.sleep(0.2)
got_nfc_slip_pub = nfc_read_text()
time.sleep(0.1)
press_cancel() # cancel animation
assert got_slip_pub == got_nfc_slip_pub
press_cancel() # cancel QR
expect_acctnum_captured(acct_num)
@ -628,7 +660,6 @@ def test_export_xpub(use_nfc, acct_num, dev, cap_menu, pick_menu_item, goto_home
if expect != 'm':
wallet = wallet.subkey_for_path(expect[2:].replace('h', "'"))
assert got.sec() == wallet.sec()
press_cancel()
@pytest.mark.parametrize("chain", ["BTC", "XTN", "XRT"])