xprv master seed with tmp seeds and bip39 passphrase
(cherry picked from commit 5bfdc4f45a)
This commit is contained in:
parent
21cb0ec248
commit
d3d176cafe
@ -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
|
||||
|
||||
@ -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 = [
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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':
|
||||
|
||||
@ -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'
|
||||
|
||||
@ -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())
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user