xprv master seed with tmp seeds and bip39 passphrase

(cherry picked from commit 5bfdc4f45a)
This commit is contained in:
scgbckbone 2023-12-18 18:58:09 +01:00 committed by doc-hex
parent 21cb0ec248
commit d3d176cafe
9 changed files with 152 additions and 44 deletions

View File

@ -1204,7 +1204,7 @@ class NewPassphrase(UserAuthorizedAction):
title = "Passphrase"
bypass_tmp = True
escape = "2"
escape = "x2"
while 1:
msg = ('BIP-39 passphrase (%d chars long) has been provided over '
'USB connection. Should we switch to that wallet now?\n\n')
@ -1212,9 +1212,15 @@ class NewPassphrase(UserAuthorizedAction):
escape += "1"
msg += "Press (1) to add passphrase to currently active temporary seed. "
if settings.master_get("words", True):
escape += "y"
msg += "Press OK to add passphrase to master seed. "
msg += ('Press (2) to view the provided passphrase.\n\n'
'OK to continue, X to cancel.')
ch = await ux_show_story(msg=msg % len(self._pw), title=title, escape=escape)
'X to cancel.')
ch = await ux_show_story(msg=msg % len(self._pw), title=title,
escape=escape, strict_escape=True)
if ch == '2':
await ux_show_story('Provided:\n\n%s\n\n' % self._pw, title=title)
continue

View File

@ -71,9 +71,9 @@ def se2_and_real_secret():
return (not pa.is_secret_blank()) and (not pa.tmp_value)
def bip39_passphrase_active():
from stash import bip39_passphrase
from pincodes import pa
return settings.get('words', True) or (bip39_passphrase and pa.tmp_value)
import stash
return settings.get('words', True) \
or (settings.master_get('words', True) and stash.bip39_passphrase)
HWTogglesMenu = [

View File

@ -78,7 +78,7 @@ from version import is_devmode
KEEP_IF_BLANK_SETTINGS = ["bkpw", "wa", "sighshchk", "emu", "rz",
"axskip", "del", "pms", "idle_to", "batt_to", "b39skip", "brite"]
SEEDVAULT_FIELDS = [ 'seeds', 'seedvault', 'xfp' ]
SEEDVAULT_FIELDS = ['seeds', 'seedvault', 'xfp', 'words']
NUM_SLOTS = const(100)
SLOTS = range(NUM_SLOTS)

View File

@ -115,13 +115,24 @@ class PassphraseSaverMenu(MenuSystem):
bypass_tmp = True
pw, expect_xfp = item.arg
if pa.tmp_value and settings.get("words", None):
if pa.tmp_value and settings.get("words", True):
xfp = settings.get("xfp", 0)
title = "[%s]" % xfp2str(xfp)
ch = await ux_show_story("Temporary seed is active. Press (1)"
" to add passphrase to the current active"
" temporary seed instead of the main seed.",
title=title, escape='1')
msg = (
"Temporary seed is active. Press (1)"
" to add passphrase to the current active"
" temporary seed."
)
escape = "1x"
if settings.master_get("words", True):
escape += "y"
msg += " Press OK to add to master seed."
msg += "Press X to exit."
ch = await ux_show_story(msg, title=title, escape=escape,
strict_escape=True)
if ch == "x": return
if ch == '1':
bypass_tmp = False
@ -130,7 +141,7 @@ class PassphraseSaverMenu(MenuSystem):
if not applied:
return
xfp = settings.get('xfp')
xfp = settings.get('xfp', 0)
# verification step
if xfp == expect_xfp:

View File

@ -657,14 +657,14 @@ async def calc_bip39_passphrase(pw, bypass_tmp=False):
# get xfp of parent reliably - cannot go to settings for this if in ephemeral
if pa.tmp_value:
with stash.SensitiveValues(bypass_tmp=bypass_tmp) as sv:
assert sv.mode == 'words'
assert sv.mode == 'words', sv.mode
current_xfp = swab32(sv.node.my_fp())
else:
current_xfp = settings.get("xfp", 0)
with stash.SensitiveValues(bip39pw=pw, bypass_tmp=bypass_tmp) as sv:
# can't do it without original seed words (late, but caller has checked)
assert sv.mode == 'words'
assert sv.mode == 'words', sv.mode
nv = SecretStash.encode(xprv=sv.node)
xfp = swab32(sv.node.my_fp())
@ -1212,34 +1212,58 @@ class PassphraseMenu(MenuSystem):
# empty string here - noop
return
nv, xfp, parent_xfp = await calc_bip39_passphrase(pp_sofar,
bypass_tmp=True)
parent_xfp_str = xfp2str(parent_xfp)
xfp_str = xfp2str(xfp)
msg0 = "master seed [%s]" % parent_xfp_str
mdata = None
tdata = None
try:
m_nv, m_xfp, m_parent_xfp = await calc_bip39_passphrase(pp_sofar,
bypass_tmp=True)
m_parent_xfp_str = xfp2str(m_parent_xfp)
m_xfp_str = xfp2str(m_xfp)
mdata = (
m_nv, m_xfp, m_xfp_str, m_parent_xfp_str,
"master seed [%s]" % m_parent_xfp_str,
"(1) master+pass:\n%s%s\n\n" % (m_parent_xfp_str, m_xfp_str),
)
except AssertionError: pass
if pa.tmp_value and settings.get("words", True):
# we have ephemeral seed - can add passphrase to it as it is word based
t_nv, t_xfp, t_parent_xfp = await calc_bip39_passphrase(pp_sofar,
bypass_tmp=False)
t_parent_xfp_str = xfp2str(t_parent_xfp)
t_xfp_str = xfp2str(t_xfp)
choice_msg = "(1) master+pass:\n%s%s\n\n" % (parent_xfp_str, xfp_str)
choice_msg += "(2) tmp+pass:\n%s%s\n\n" % (t_parent_xfp_str, t_xfp_str)
ch = await ux_show_story(choice_msg, escape='12x', strict_escape=True,
scrollbar=False)
tdata = (
t_nv, t_xfp, t_xfp_str, t_parent_xfp_str,
"current active temporary seed [%s]" % t_parent_xfp_str,
"(2) tmp+pass:\n%s%s\n\n" % (t_parent_xfp_str, t_xfp_str),
)
if tdata is None and mdata is None:
# if master is not word based, temporary has to be, otherwise "Passphrase"
# not offered in menu
# should never be seen by user because flow.py::bip39_passphrase_active
await ux_show_story(title="FAILED", msg="Need word based secret")
return
tmp = False
if tdata and mdata:
ch = await ux_show_story(mdata[-1] + tdata[-1], escape='12x',
strict_escape=True, scrollbar=False)
if ch == "x": return # exit
if ch == "2":
parent_xfp_str = t_parent_xfp_str
xfp = t_xfp
xfp_str = t_xfp_str
msg0 = "current active temporary seed [%s]" % t_parent_xfp_str
nv = t_nv
tmp = True
elif tdata:
tmp = True
data = tdata if tmp else mdata
nv, xfp, xfp_str, parent_xfp_str, msg, _ = data
msg = ('Above is the master key fingerprint of the new wallet'
' created by adding passphrase to %s.'
' Press X to abort and keep editing passphrase,'
' OK to use the new wallet, (1) to use'
' and save to MicroSD') % msg0
' and save to MicroSD') % msg
ch = await ux_show_story(msg, title="[%s]" % xfp_str, escape='1')
if ch == 'x':

View File

@ -546,11 +546,11 @@ class USBHandler:
if cmd == 'pass':
# bip39 passphrase provided, maybe use it if authorized
assert self.encrypted_req, 'must encrypt'
import stash
from flow import bip39_passphrase_active
from auth import start_bip39_passphrase
from glob import settings
assert settings.get('words', True) or stash.bip39_passphrase, 'no seed'
assert bip39_passphrase_active(), 'no seed'
assert len(args) < 400, 'too long'
pw = str(args, 'utf8')
assert len(pw) < 100, 'too long'

View File

@ -7,6 +7,7 @@ import stash, chains
from h import b2a_hex
from pincodes import pa
from glob import settings
from nvstore import SettingsObject
from stash import SecretStash, SensitiveValues
from utils import xfp2str, swab32
@ -20,19 +21,21 @@ v = node.deserialize(main.TPRV)
assert v == b32_version_priv
assert node
if settings.get('xfp') == swab32(node.my_fp()):
print("right xfp already")
settings.current = sim_defaults
settings.set('chain', 'XTN')
settings.set('words', False)
else:
settings.current = sim_defaults
settings.set('chain', 'XTN')
pa.tmp_value = None
SettingsObject.master_sv_data = {}
SettingsObject.master_nvram_key = None
raw = SecretStash.encode(xprv=node)
pa.change(new_secret=raw)
pa.new_main_secret(raw)
raw = SecretStash.encode(xprv=node)
pa.change(new_secret=raw)
pa.new_main_secret(raw)
settings.set('words', False)
print("New key in effect: %s" % settings.get('xpub', 'MISSING'))
print("Fingerprint: %s" % xfp2str(settings.get('xfp', 0)))
print("New key in effect: %s" % settings.get('xpub', 'MISSING'))
print("Fingerprint: %s" % xfp2str(settings.get('xfp', 0)))
assert settings.get('xfp', 0) == swab32(node.my_fp())
assert settings.get('xfp', 0) == swab32(node.my_fp())

View File

@ -11,7 +11,7 @@ from ckcc_protocol.protocol import CCProtocolPacker, CCProtoError, CCUserRefused
from ckcc_protocol.constants import *
import json
from mnemonic import Mnemonic
from constants import simulator_fixed_xfp, simulator_fixed_words
from constants import simulator_fixed_xfp, simulator_fixed_words, simulator_fixed_tprv
from helpers import xfp2str
# add the BIP39 test vectors
@ -371,4 +371,63 @@ def test_bip39pass_on_ephemeral_seed_usb(generate_ephemeral_words, import_epheme
assert xpub in ident_story
@pytest.mark.parametrize("usb", [True, False])
def test_tmp_on_xprv_master(generate_ephemeral_words, goto_home, cap_menu,
pick_menu_item, need_keypress, enter_complex,
cap_story, unit_test, microsd_path, expect_ftux,
set_bip39_pw, usb):
passphrase = "jfkdsfdks"
fname = "ek.txt"
fpath = microsd_path("ek.txt")
with open(fpath, "w") as f:
f.write(simulator_fixed_tprv)
unit_test('devtest/clear_seed.py')
time.sleep(.2)
pick_menu_item('Import Existing')
pick_menu_item("Import XPRV")
time.sleep(.2)
title, story = cap_story()
if "Press (1)" in story:
need_keypress("1")
need_keypress("y") # Select file containing...
pick_menu_item(fname)
time.sleep(.2)
expect_ftux()
m = cap_menu()
assert "Passphrase" not in m
sec = generate_ephemeral_words(24, from_main=True, seed_vault=False)
parent = Mnemonic.to_seed(" ".join(sec), passphrase=passphrase)
parent_node = BIP32Node.from_master_secret(parent, netcode="XTN")
parent_fp = parent_node.fingerprint().hex().upper()
m = cap_menu()
# temporary seed is word-based - offer passphrase
assert "Passphrase" in m
if usb:
res_fp = set_bip39_pw(passphrase, reset=False, seed_vault=False, on_tmp=True)
assert xfp2str(res_fp) == parent_fp
with pytest.raises(Exception):
set_bip39_pw(passphrase, reset=False, seed_vault=False, on_tmp=True)
return
pick_menu_item("Passphrase")
need_keypress("y")
enter_complex(passphrase)
pick_menu_item("APPLY")
time.sleep(.1)
title, story = cap_story()
assert parent_fp in title # no choice story
assert "current active temporary seed" in story
need_keypress("y")
time.sleep(.2)
title, story = cap_story()
if "Press (1)" in story:
need_keypress("y")
m = cap_menu()
assert "Passphrase" not in m
# EOF

View File

@ -738,6 +738,10 @@ def test_menu_wrapping(goto_home, pick_menu_item, cap_story, need_keypress, cap_
# home
for i in range(10): # settings on 5th in home (10 is way past that)
need_keypress(DOWN)
# sitting at Logout
# one up to get to settings
need_keypress(UP)
need_keypress("y")
menu = cap_menu()
# assert we are in settings, meaning we found bottom of home menu
@ -745,6 +749,7 @@ def test_menu_wrapping(goto_home, pick_menu_item, cap_story, need_keypress, cap_
for i in range(10):
need_keypress(UP)
need_keypress("y")
menu = cap_menu()
# assert we are in Login settings, meaning we found top of settings menu