seed vault optimizations
This commit is contained in:
parent
3784e1e007
commit
05a08b6ff8
@ -469,7 +469,7 @@ async def pick_nickname(*a):
|
||||
|
||||
# Value is not stored with normal settings, it's part of "prelogin" settings
|
||||
# which are encrypted with zero-key.
|
||||
s = SettingsObject()
|
||||
s = SettingsObject.prelogin()
|
||||
nick = s.get('nick', '')
|
||||
|
||||
if not nick:
|
||||
@ -768,7 +768,7 @@ async def version_migration():
|
||||
if had_delay:
|
||||
from nvstore import SettingsObject
|
||||
settings.remove_key('lgto')
|
||||
s = SettingsObject()
|
||||
s = SettingsObject.prelogin()
|
||||
s.set('lgto', had_delay)
|
||||
s.save()
|
||||
del s
|
||||
@ -934,7 +934,7 @@ async def restore_main_secret(*a):
|
||||
|
||||
escape = None
|
||||
msg = "Restore main wallet and its settings?\n\n"
|
||||
if not await in_seed_vault():
|
||||
if not in_seed_vault():
|
||||
msg += (
|
||||
"Press OK to forget current temporary wallet "
|
||||
"settings, or press (1) to save & keep "
|
||||
@ -2238,10 +2238,11 @@ async def change_seed_vault(is_enabled):
|
||||
# user has changed seed vault enable/disable flag
|
||||
from glob import settings
|
||||
|
||||
if (not is_enabled) and settings.get('seeds'):
|
||||
# restore it
|
||||
settings.set('seedvault', 1)
|
||||
await ux_show_story("Please remove all seeds from the vault before disabling")
|
||||
if (not is_enabled) and settings.master_get('seeds'):
|
||||
# problem: they still have some seeds... also this path blocks
|
||||
# disable from within a tmp seed
|
||||
settings.set('seedvault', 1) # restore it
|
||||
await ux_show_story("Please remove all seeds from the vault before disabling.")
|
||||
|
||||
return
|
||||
|
||||
|
||||
@ -67,7 +67,7 @@ def value_resolution_chooser():
|
||||
def scramble_keypad_chooser():
|
||||
# rngk = randomize keypad for PIN entry
|
||||
|
||||
s = SettingsObject()
|
||||
s = SettingsObject.prelogin()
|
||||
which = s.get('rngk', 0)
|
||||
del s
|
||||
|
||||
@ -75,7 +75,7 @@ def scramble_keypad_chooser():
|
||||
|
||||
def set(idx, text):
|
||||
# save it, but "outside" of login PIN
|
||||
s = SettingsObject()
|
||||
s = SettingsObject.prelogin()
|
||||
s.set('rngk', idx)
|
||||
s.save()
|
||||
del s
|
||||
@ -85,7 +85,7 @@ def scramble_keypad_chooser():
|
||||
def kill_key_chooser():
|
||||
# kbtn = single keypress after anti-phishing words will wipe seed
|
||||
|
||||
s = SettingsObject()
|
||||
s = SettingsObject.prelogin()
|
||||
which = s.get('kbtn', -1)
|
||||
del s
|
||||
which = int(which) + 1
|
||||
@ -94,7 +94,7 @@ def kill_key_chooser():
|
||||
|
||||
def set(idx, text):
|
||||
# save it, but "outside" of login PIN
|
||||
s = SettingsObject()
|
||||
s = SettingsObject.prelogin()
|
||||
if idx == 0:
|
||||
s.remove_key('kbtn')
|
||||
else:
|
||||
|
||||
@ -33,7 +33,7 @@ def real_countdown_chooser(tag, offset, def_to):
|
||||
ch = lgto_ch[offset:]
|
||||
va = lgto_va[offset:]
|
||||
|
||||
s = SettingsObject()
|
||||
s = SettingsObject.prelogin()
|
||||
timeout = s.get(tag, def_to) # in minutes
|
||||
try:
|
||||
which = va.index(timeout)
|
||||
@ -42,7 +42,7 @@ def real_countdown_chooser(tag, offset, def_to):
|
||||
|
||||
def set_it(idx, text):
|
||||
# save on key0, not normal settings
|
||||
s = SettingsObject()
|
||||
s = SettingsObject.prelogin()
|
||||
s.set(tag, va[idx])
|
||||
s.save()
|
||||
del s
|
||||
|
||||
@ -327,7 +327,7 @@ NormalSystem = [
|
||||
MenuItem('Type Passwords', f=password_entry,
|
||||
predicate=lambda: settings.get("emu", False) and has_secrets()),
|
||||
MenuItem('Seed Vault', menu=make_seed_vault_menu,
|
||||
predicate=lambda: settings.get('seedvault') and has_secrets()),
|
||||
predicate=lambda: settings.master_get('seedvault') and has_secrets()),
|
||||
MenuItem('Secure Logout', f=logout_now),
|
||||
MenuItem('Advanced/Tools', menu=AdvancedNormalMenu),
|
||||
MenuItem('Settings', menu=SettingsMenu),
|
||||
|
||||
@ -125,8 +125,6 @@ class OutptValueCache:
|
||||
|
||||
# memory management: can't store very much, so trim as needed
|
||||
depth = HISTORY_SAVED
|
||||
if settings.capacity > 0.8:
|
||||
depth //= 2
|
||||
|
||||
# also limit in-memory use
|
||||
cls.load_cache()
|
||||
|
||||
@ -58,7 +58,8 @@ glob.numpad = numpad
|
||||
|
||||
# NV settings
|
||||
from nvstore import SettingsObject
|
||||
settings = SettingsObject(glob.dis)
|
||||
settings = SettingsObject()
|
||||
settings.load(glob.dis)
|
||||
glob.settings = settings
|
||||
|
||||
async def more_setup():
|
||||
|
||||
@ -77,6 +77,7 @@ from version import mk_num, is_devmode
|
||||
KEEP_IF_BLANK_SETTINGS = ["bkpw", "wa", "sighshchk", "emu", "rz",
|
||||
"axskip", "del", "pms", "idle_to", "b39skip"]
|
||||
|
||||
SEEDVAULT_FIELDS = [ 'seeds', 'seedvault', 'xfp' ]
|
||||
|
||||
NUM_SLOTS = const(100)
|
||||
SLOTS = range(NUM_SLOTS)
|
||||
@ -88,42 +89,25 @@ def MK4_FILENAME(slot):
|
||||
return MK4_WORKDIR + ('%03x.aes' % slot)
|
||||
|
||||
|
||||
# master current dict
|
||||
master_sv_data = None
|
||||
master_nvram_key = None
|
||||
|
||||
|
||||
def extract_master_sv_data(settings_dict):
|
||||
# allows us to specify what we want to keep
|
||||
# in global 'master_sv_data' variable
|
||||
# and save RAM
|
||||
if settings_dict is None:
|
||||
return
|
||||
seeds = settings_dict.get("seeds", [])
|
||||
seedvault = settings_dict.get("seedvault", 0)
|
||||
if (not seedvault) and (not seeds):
|
||||
return
|
||||
return {"seeds": seeds, "seedvault": seedvault,
|
||||
"xfp": settings_dict.get("xfp", 0)}
|
||||
|
||||
|
||||
def set_master_sv_data(data, key):
|
||||
global master_sv_data, master_nvram_key
|
||||
master_sv_data = extract_master_sv_data(data)
|
||||
master_nvram_key = key
|
||||
|
||||
|
||||
class SettingsObject:
|
||||
# class vars: track a few values from master seed settings
|
||||
master_sv_data = {}
|
||||
master_nvram_key = None
|
||||
|
||||
def __init__(self, dis=None):
|
||||
def __init__(self, nvram_key=None):
|
||||
# NOTE: constructor no longer loads the values by default (too slow).
|
||||
self.is_dirty = 0
|
||||
self.my_pos = None
|
||||
|
||||
self.nvram_key = b'\0'*32
|
||||
self.capacity = 0
|
||||
self.nvram_key = nvram_key or b'\0'*32
|
||||
self.current = self.default_values()
|
||||
|
||||
self.load(dis)
|
||||
@classmethod
|
||||
def prelogin(cls):
|
||||
# make an instance of the pre-login settings (ie. w/o key)
|
||||
rv = cls()
|
||||
rv.load()
|
||||
return rv
|
||||
|
||||
def get_aes(self, pos):
|
||||
# Build AES object for en/decrypt of specific block.
|
||||
@ -158,7 +142,6 @@ class SettingsObject:
|
||||
|
||||
for round in range(5):
|
||||
s.update('pad')
|
||||
|
||||
s = sha256(s.digest())
|
||||
|
||||
key = s.digest()
|
||||
@ -166,7 +149,7 @@ class SettingsObject:
|
||||
if mine:
|
||||
blank_object(new_secret)
|
||||
|
||||
# for restore from backup case, or when changing (created) the seed
|
||||
# save value for use in self.get_aes()
|
||||
self.nvram_key = key
|
||||
|
||||
def get_capacity(self):
|
||||
@ -271,6 +254,59 @@ class SettingsObject:
|
||||
|
||||
yield pos, taste
|
||||
|
||||
def leaving_master_seed(self):
|
||||
# going from master seed to a tmp seed, so capture a few values we need.
|
||||
|
||||
SettingsObject.master_nvram_key = self.nvram_key
|
||||
|
||||
for fn in SEEDVAULT_FIELDS:
|
||||
SettingsObject.master_sv_data[fn] = self.current.get(fn)
|
||||
|
||||
def return_to_master_seed(self):
|
||||
# switching from a tmp seed to the normal master seed
|
||||
# - we already kept the key needed, so just re-read
|
||||
assert SettingsObject.master_nvram_key
|
||||
self.nvram_key = SettingsObject.master_nvram_key
|
||||
self.load()
|
||||
|
||||
# these value no longer required, and might become stale
|
||||
SettingsObject.master_sv_data.clear()
|
||||
SettingsObject.master_nvram_key = None
|
||||
|
||||
def master_set(self, key, value):
|
||||
# Set a value, and it must be saved under the master seed's
|
||||
# Concern is we may be changing a setting from a tmp seed mode
|
||||
# - always does a save
|
||||
from glob import settings as self
|
||||
|
||||
if not SettingsObject.master_nvram_key:
|
||||
# simple, we are already on master seed
|
||||
self.set(key, value)
|
||||
self.save()
|
||||
else:
|
||||
# harder, slower: have to load, change and write
|
||||
tmp = SettingsObject(nvram_key=SettingsObject.master_nvram_key)
|
||||
tmp.load()
|
||||
tmp.set(key, value)
|
||||
tmp.save()
|
||||
del tmp
|
||||
|
||||
# track our copies
|
||||
if key in SEEDVAULT_FIELDS:
|
||||
SettingsObject.master_sv_data[key] = value
|
||||
|
||||
def master_get(self, kn, default=None):
|
||||
# Read a value from master seed's settings, perhaps from within context of tmp seed
|
||||
from glob import settings as self
|
||||
|
||||
if not SettingsObject.master_nvram_key:
|
||||
# simple, we are already on master seed
|
||||
return self.get(kn, default)
|
||||
|
||||
# LIMITATION: only supporting a few values we know we will need
|
||||
assert kn in SEEDVAULT_FIELDS
|
||||
return SettingsObject.master_sv_data.get(kn, default)
|
||||
|
||||
def load(self, dis=None):
|
||||
# Search all slots for any we can read, decrypt that,
|
||||
# and pick the newest one (in unlikely case of dups)
|
||||
@ -278,7 +314,6 @@ class SettingsObject:
|
||||
self.current.clear()
|
||||
self.my_pos = None
|
||||
self.is_dirty = 0
|
||||
self.capacity = 0
|
||||
nonempty = set()
|
||||
|
||||
for pos, taste in self._nonempty_slots(dis):
|
||||
@ -329,9 +364,6 @@ class SettingsObject:
|
||||
self.current['chain'] = 'XTN'
|
||||
|
||||
def get(self, kn, default=None):
|
||||
if master_sv_data and kn in ["seeds", "seedvault"]:
|
||||
# we are in temporary mode as global 'master_sv_data' is populated
|
||||
return master_sv_data.get(kn, default)
|
||||
return self.current.get(kn, default)
|
||||
|
||||
def changed(self):
|
||||
@ -458,7 +490,6 @@ class SettingsObject:
|
||||
# act blank too, just in case.
|
||||
self.current.clear()
|
||||
self.is_dirty = 0
|
||||
self.capacity = 0
|
||||
|
||||
@staticmethod
|
||||
def default_values():
|
||||
|
||||
@ -446,24 +446,22 @@ class PinAttempt:
|
||||
with stash.SensitiveValues(raw_secret, bypass_tmp=bypass_tmp) as sv:
|
||||
if chain is not None:
|
||||
sv.chain = chain
|
||||
|
||||
if raw_secret is None:
|
||||
# restore to main wallet
|
||||
settings.nvram_key = nvstore.master_nvram_key
|
||||
settings.load()
|
||||
# blank global as we return to main seed
|
||||
nvstore.set_master_sv_data(None, None)
|
||||
# restore to main wallet's settings
|
||||
settings.return_to_master_seed()
|
||||
else:
|
||||
sv.capture_xpub()
|
||||
|
||||
settings.merge_previous_active(old_values)
|
||||
|
||||
except stash.ZeroSecretException:
|
||||
# secret is zero - using ephemeral secrets in CC
|
||||
# with no se2 secret
|
||||
settings.nvram_key = b'\0'*32
|
||||
settings.load()
|
||||
# set PA_ZEROS to 1
|
||||
self.state_flags |= (1 << 4)
|
||||
return
|
||||
self.state_flags |= PA_ZERO_SECRET
|
||||
|
||||
settings.merge_previous_active(old_values)
|
||||
|
||||
def tmp_secret(self, encoded, chain=None, bip39pw=''):
|
||||
# Use indicated secret and stop using the SE; operate like this until reboot
|
||||
@ -473,11 +471,9 @@ class PinAttempt:
|
||||
return False
|
||||
|
||||
if not self.tmp_value:
|
||||
# going from master seed
|
||||
import nvstore
|
||||
# leaving from master seed, might capture some useful values
|
||||
from glob import settings
|
||||
nvstore.set_master_sv_data(settings.current,
|
||||
settings.nvram_key)
|
||||
settings.leaving_master_seed()
|
||||
|
||||
self.tmp_value = val
|
||||
|
||||
@ -487,6 +483,7 @@ class PinAttempt:
|
||||
# Copies system settings to new encrypted-key value, calculates
|
||||
# XFP, XPUB and saves into that, and starts using them.
|
||||
self.new_main_secret(self.tmp_value, chain=chain, bip39pw=bip39pw)
|
||||
|
||||
return True
|
||||
|
||||
def trick_request(self, method_num, data):
|
||||
|
||||
167
shared/seed.py
167
shared/seed.py
@ -23,6 +23,7 @@ from ubinascii import unhexlify as a2b_hex
|
||||
from pwsave import PassphraseSaver
|
||||
from glob import settings, dis
|
||||
from pincodes import pa
|
||||
from nvstore import SettingsObject
|
||||
|
||||
# seed words lengths we support: 24=>256 bits, and recommended
|
||||
VALID_LENGTHS = (24, 18, 12)
|
||||
@ -405,20 +406,22 @@ async def new_from_dice(nwords):
|
||||
# send them to home menu, now with a wallet enabled
|
||||
goto_top_menu(first_time=True)
|
||||
|
||||
async def in_seed_vault(xfp=None, seeds=None):
|
||||
def in_seed_vault(xfp=None):
|
||||
# Test if indicated xfp (or currently active XFP) is in the seed vault already.
|
||||
|
||||
if xfp is None:
|
||||
xfp = xfp2str(settings.get("xfp", 0))
|
||||
if seeds is None:
|
||||
seeds = settings.get("seeds", [])
|
||||
|
||||
if xfp in [s[0] for s in seeds]:
|
||||
seeds = settings.master_get("seeds", [])
|
||||
|
||||
if seeds and (xfp in [s[0] for s in seeds]):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
async def add_seed_to_vault(encoded, meta=None):
|
||||
import nvstore
|
||||
|
||||
if not settings.get("seedvault", False):
|
||||
if not settings.master_get("seedvault", False):
|
||||
# seed vault disabled
|
||||
return
|
||||
if pa.is_secret_blank():
|
||||
@ -430,20 +433,16 @@ async def add_seed_to_vault(encoded, meta=None):
|
||||
new_xfp_str = xfp2str(new_xfp)
|
||||
|
||||
# do not offer to store secrets that are already in vault
|
||||
if await in_seed_vault(new_xfp_str, settings.get("seeds", [])):
|
||||
if in_seed_vault(new_xfp_str):
|
||||
return
|
||||
|
||||
# do not offer to store main seed
|
||||
if nvstore.master_sv_data:
|
||||
main_xfp = nvstore.master_sv_data.get("xfp", 0)
|
||||
else:
|
||||
main_xfp = settings.get("xfp", 0)
|
||||
main_xfp = settings.master_get("xfp", 0)
|
||||
|
||||
if new_xfp == main_xfp:
|
||||
return
|
||||
|
||||
# if in ephemeral 'seeds' are grabbed from master_sv_data global var
|
||||
seeds = settings.get("seeds", [])
|
||||
seeds = settings.master_get("seeds", [])
|
||||
|
||||
xfp_ui = "[%s]" % new_xfp_str
|
||||
story = ("Press (1) to "
|
||||
@ -452,29 +451,21 @@ async def add_seed_to_vault(encoded, meta=None):
|
||||
"to continue without saving.")
|
||||
|
||||
ch = await ux_show_story(story, escape="1")
|
||||
if ch == "1":
|
||||
eph_nvram_key = None
|
||||
seeds.append((new_xfp_str,
|
||||
stash.SecretStash.storage_encode(encoded),
|
||||
xfp_ui,
|
||||
meta))
|
||||
if nvstore.master_sv_data:
|
||||
# in ephemeral
|
||||
nvstore.master_sv_data["seeds"] = seeds
|
||||
eph_nvram_key = settings.nvram_key[:]
|
||||
settings.nvram_key = nvstore.master_nvram_key
|
||||
settings.load()
|
||||
if ch != "1":
|
||||
# didn't want to save
|
||||
return
|
||||
|
||||
settings.set("seeds", seeds)
|
||||
settings.save()
|
||||
# Save it into master settings
|
||||
seeds.append((new_xfp_str,
|
||||
stash.SecretStash.storage_serialize(encoded),
|
||||
xfp_ui,
|
||||
meta))
|
||||
|
||||
if eph_nvram_key:
|
||||
# restore ephemerlal settings after save to main settings
|
||||
settings.nvram_key = eph_nvram_key
|
||||
settings.load()
|
||||
settings.master_set("seeds", seeds)
|
||||
|
||||
await ux_show_story(xfp_ui + "\nSaved to Seed Vault")
|
||||
return True
|
||||
await ux_show_story(xfp_ui + "\nSaved to Seed Vault")
|
||||
|
||||
return True
|
||||
|
||||
async def set_ephemeral_seed(encoded, chain=None, summarize_ux=True, bip39pw='',
|
||||
is_restore=False, meta=None):
|
||||
@ -812,8 +803,7 @@ class SeedVaultMenu(MenuSystem):
|
||||
goto_top_menu()
|
||||
|
||||
@staticmethod
|
||||
async def _clear(menu, label, item):
|
||||
import nvstore
|
||||
async def _remove(menu, label, item):
|
||||
from glob import dis, settings
|
||||
|
||||
idx, xfp_str, encoded = item.arg
|
||||
@ -823,10 +813,9 @@ class SeedVaultMenu(MenuSystem):
|
||||
"only remove from seed vault and keep "
|
||||
"encrypted settings for later use.\n\n"
|
||||
"WARNING: Funds will be lost if wallet is"
|
||||
" not backed up elsewhere.")
|
||||
" not backed-up elsewhere.")
|
||||
|
||||
ch = await ux_show_story(title="[" + xfp_str + "]",
|
||||
msg=msg, escape="1")
|
||||
ch = await ux_show_story(title="[" + xfp_str + "]", msg=msg, escape="1")
|
||||
if ch == "x": return
|
||||
|
||||
dis.fullscreen("Saving...")
|
||||
@ -843,46 +832,28 @@ class SeedVaultMenu(MenuSystem):
|
||||
# slot wiping
|
||||
if tmp_val:
|
||||
# wipe current settings
|
||||
master_nvram_key = nvstore.master_nvram_key
|
||||
settings.blank()
|
||||
pa.tmp_value = False
|
||||
settings.return_to_master_seed()
|
||||
else:
|
||||
# in main settings
|
||||
settings.save()
|
||||
master_nvram_key = settings.nvram_key[:]
|
||||
settings.set_key(pad_raw_secret(encoded))
|
||||
settings.load()
|
||||
settings.blank()
|
||||
xs = SettingsObject()
|
||||
xs.set_key(pad_raw_secret(encoded))
|
||||
xs.load()
|
||||
xs.blank()
|
||||
del xs
|
||||
|
||||
# back to master settings
|
||||
pa.tmp_value = False
|
||||
settings.nvram_key = master_nvram_key
|
||||
settings.load()
|
||||
nvstore.set_master_sv_data(None, None)
|
||||
|
||||
eph_nvram_key = None
|
||||
# will get global master_sv_data from nvstore if in ephemeral
|
||||
seeds = settings.get("seeds", [])
|
||||
# CAUTION: will get shadow copy if in tmp seed mode already
|
||||
seeds = settings.master_get("seeds", [])
|
||||
try:
|
||||
del seeds[idx]
|
||||
except IndexError:
|
||||
pass
|
||||
|
||||
if nvstore.master_sv_data:
|
||||
# in ephemeral
|
||||
nvstore.master_sv_data["seeds"] = seeds
|
||||
eph_nvram_key = settings.nvram_key[:]
|
||||
settings.nvram_key = nvstore.master_nvram_key
|
||||
settings.load()
|
||||
# need to load and work on master secrets, will be slow if on tmp seed
|
||||
settings.master_set("seeds", seeds)
|
||||
|
||||
settings.set("seeds", seeds)
|
||||
settings.save()
|
||||
|
||||
if tmp_val and (not wipe_slot):
|
||||
# we were in ephemeral mode before and have not
|
||||
# wiped seed - return back to ephemeral
|
||||
settings.nvram_key = eph_nvram_key
|
||||
settings.load()
|
||||
elif tmp_val and wipe_slot:
|
||||
if tmp_val and wipe_slot:
|
||||
goto_top_menu()
|
||||
|
||||
# pop menu stack
|
||||
@ -893,28 +864,27 @@ class SeedVaultMenu(MenuSystem):
|
||||
@staticmethod
|
||||
async def _detail(menu, label, item):
|
||||
xfp_str, encoded, name, meta = item.arg
|
||||
|
||||
# - first byte represents type of secret (internal encoding flag)
|
||||
txt = SecretStash.summary(a2b_hex(encoded[0:2])[0])
|
||||
|
||||
detail = "Name:\n%s\n\nMaster XFP:\n%s\n\nMetadata:\n%s\n\nSecret Type:\n%s" % (
|
||||
# (-2) one byte in hex that represents type of secret (internal)
|
||||
name, xfp_str, meta, txt
|
||||
)
|
||||
detail = "Name:\n%s\n\nMaster XFP:\n%s\n\nOrigin:\n%s\n\nSecret Type:\n%s" \
|
||||
% (name, xfp_str, meta, txt)
|
||||
|
||||
await ux_show_story(detail)
|
||||
|
||||
@staticmethod
|
||||
async def _rename(menu, label, item):
|
||||
# let them edit the name
|
||||
import nvstore
|
||||
from glob import dis
|
||||
from ux import ux_input_text
|
||||
|
||||
idx, xfp_str = item.arg
|
||||
|
||||
# will get global master_sv_data from nvstore if in ephemeral
|
||||
seeds = settings.get("seeds", [])
|
||||
seeds = settings.master_get("seeds", [])
|
||||
chk_xfp, encoded, old_name, meta = seeds[idx]
|
||||
assert chk_xfp == xfp_str
|
||||
|
||||
from ux import ux_input_text
|
||||
new_name = await ux_input_text(old_name, confirm_exit=False, max_len=40)
|
||||
|
||||
if not new_name:
|
||||
@ -923,26 +893,20 @@ class SeedVaultMenu(MenuSystem):
|
||||
dis.fullscreen("Saving...")
|
||||
|
||||
# save it
|
||||
eph_nvram_key = None
|
||||
seeds[idx] = (chk_xfp, encoded, new_name, meta)
|
||||
if nvstore.master_sv_data:
|
||||
# in ephemeral
|
||||
nvstore.master_sv_data["seeds"] = seeds
|
||||
eph_nvram_key = settings.nvram_key[:]
|
||||
settings.nvram_key = nvstore.master_nvram_key
|
||||
settings.load()
|
||||
|
||||
settings.set("seeds", seeds)
|
||||
settings.save()
|
||||
if eph_nvram_key:
|
||||
# restore ephemerlal settings after save to main settings
|
||||
settings.nvram_key = eph_nvram_key
|
||||
settings.load()
|
||||
# need to load and work on master secrets, will be slow if on tmp seed
|
||||
settings.master_set("seeds", seeds)
|
||||
|
||||
# update label in sub-menu
|
||||
menu.items[0].label = new_name
|
||||
menu.items[0].arg = menu.items[0].arg[0:2] + (new_name,) + menu.items[0].arg[3:]
|
||||
|
||||
# .. and name in parent menu too
|
||||
parent = the_ux.parent_of(menu)
|
||||
if parent:
|
||||
parent.update_contents()
|
||||
|
||||
@classmethod
|
||||
def construct(cls):
|
||||
# Dynamic menu with user-defined names of seeds shown
|
||||
@ -951,31 +915,32 @@ class SeedVaultMenu(MenuSystem):
|
||||
|
||||
rv = []
|
||||
|
||||
seeds = settings.get("seeds", [])
|
||||
seeds = settings.master_get("seeds", [])
|
||||
cur_xfp = xfp2str(settings.get("xfp", 0))
|
||||
|
||||
if not seeds:
|
||||
rv.append(MenuItem('(none saved yet)'))
|
||||
rv.append(MenuItem("Temporary Seed", menu=make_ephemeral_seed_menu))
|
||||
else:
|
||||
for i, (xfp_str, encoded, name, meta) in enumerate(seeds):
|
||||
current_active = cur_xfp == xfp_str
|
||||
is_active = (cur_xfp == xfp_str)
|
||||
submenu = [
|
||||
MenuItem(name, f=cls._detail, arg=(xfp_str, encoded, name, meta)),
|
||||
MenuItem('Use This Seed', f=cls._set, arg=(xfp_str, encoded)),
|
||||
MenuItem('Rename', f=cls._rename, arg=(i, xfp_str)),
|
||||
MenuItem('Delete', f=cls._clear, arg=(i, xfp_str, encoded)),
|
||||
MenuItem('Delete', f=cls._remove, arg=(i, xfp_str, encoded)),
|
||||
]
|
||||
if current_active:
|
||||
submenu[1] = MenuItem("In Use")
|
||||
if is_active:
|
||||
submenu[1] = MenuItem("Seed In Use")
|
||||
submenu[1].is_chosen = lambda: True
|
||||
|
||||
if pa.tmp_value and (not current_active):
|
||||
if pa.tmp_value and (not is_active):
|
||||
# if different ephemeral wallet active
|
||||
# DO NOT offer any modification api (rename/delete)
|
||||
submenu = submenu[:2]
|
||||
|
||||
item = MenuItem(('%-4s' % (str(i+1)+":")) + ('[%s]' % xfp_str),
|
||||
menu=MenuSystem(submenu))
|
||||
if current_active:
|
||||
item = MenuItem('%d: %s' % (i+1, name), menu=MenuSystem(submenu))
|
||||
if is_active:
|
||||
item.is_chosen = lambda: True
|
||||
|
||||
rv.append(item)
|
||||
@ -1008,7 +973,7 @@ class EphemeralSeedMenu(MenuSystem):
|
||||
|
||||
@classmethod
|
||||
def construct(cls):
|
||||
from glob import NFC, settings
|
||||
from glob import NFC
|
||||
from actions import nfc_recv_ephemeral, import_tapsigner_backup_file, import_xprv
|
||||
|
||||
import_ephemeral_menu = [
|
||||
@ -1028,14 +993,14 @@ class EphemeralSeedMenu(MenuSystem):
|
||||
MenuItem("Generate Words", menu=gen_ephemeral_menu),
|
||||
MenuItem("Import Words", menu=import_ephemeral_menu),
|
||||
MenuItem("Import XPRV", f=import_xprv, arg=True), # ephemeral=True
|
||||
MenuItem("Tapsigner Backup", f=import_tapsigner_backup_file, arg=True), # ephemeral=True
|
||||
MenuItem("Tapsigner Backup", f=import_tapsigner_backup_file, arg=True), # ephemeral=True
|
||||
]
|
||||
|
||||
return rv
|
||||
|
||||
|
||||
async def make_ephemeral_seed_menu(*a):
|
||||
if (not pa.tmp_value) and (not settings.get("seedvault", False)):
|
||||
if (not pa.tmp_value) and (not settings.master_get("seedvault", False)):
|
||||
# force a warning on them, unless they are already doing it.
|
||||
ch = await ux_show_story(
|
||||
"Temporary seed is a secret completely separate "
|
||||
|
||||
@ -132,7 +132,8 @@ class SecretStash:
|
||||
return 'master', ms, hd
|
||||
|
||||
@staticmethod
|
||||
def storage_encode(secret):
|
||||
def storage_serialize(secret):
|
||||
# make it a JSON-compatible field
|
||||
return B2A(bytes(secret).rstrip(b"\x00"))
|
||||
|
||||
@staticmethod
|
||||
|
||||
@ -848,6 +848,17 @@ def settings_get(sim_exec):
|
||||
|
||||
return doit
|
||||
|
||||
@pytest.fixture()
|
||||
def master_settings_get(sim_exec):
|
||||
|
||||
def doit(key):
|
||||
cmd = f"RV.write(repr(settings.master_get('{key}')))"
|
||||
resp = sim_exec(cmd)
|
||||
assert 'Traceback' not in resp, resp
|
||||
return eval(resp)
|
||||
|
||||
return doit
|
||||
|
||||
@pytest.fixture()
|
||||
def settings_remove(sim_exec):
|
||||
|
||||
|
||||
@ -9,7 +9,8 @@ import stash
|
||||
from seed import set_seed_value
|
||||
from utils import xfp2str
|
||||
from actions import goto_top_menu
|
||||
import nvstore
|
||||
from nvstore import SettingsObject
|
||||
|
||||
|
||||
tn = chains.BitcoinTestnet
|
||||
|
||||
@ -22,8 +23,8 @@ settings.set('idle_to', 0)
|
||||
|
||||
import main
|
||||
pa.tmp_value = None
|
||||
nvstore.master_sv_data = None
|
||||
nvstore.master_nvram_key = None
|
||||
SettingsObject.master_sv_data = {}
|
||||
SettingsObject.master_nvram_key = None
|
||||
set_seed_value(main.WORDS)
|
||||
|
||||
print("New key in effect: %s" % settings.get('xpub', 'MISSING'))
|
||||
|
||||
@ -218,8 +218,8 @@ def verify_ephemeral_secret_ui(cap_story, need_keypress, cap_menu, dev, fake_txn
|
||||
pick_menu_item(sc_menu[0])
|
||||
time.sleep(.1)
|
||||
else:
|
||||
assert "In Use" in m
|
||||
pick_menu_item("In Use") # noop
|
||||
assert "Seed In Use" in m
|
||||
pick_menu_item("Seed In Use") # noop
|
||||
|
||||
# delete it from records
|
||||
pick_menu_item("Delete")
|
||||
@ -748,7 +748,7 @@ def test_activate_current_tmp_secret(reset_seed_words, goto_eph_seed_menu,
|
||||
'017caa3142d48791f837b42fcd7a98662f9fb4101a15ae87cdbc1fecc96f33c11ffcefd8121daaba0625c918a335a0712b8c35c2da60e6fc6eef78b7028f4be02a',
|
||||
None), # BIP-85 -> BIP-32 -> #23
|
||||
])
|
||||
def test_seed_vault_menus(dev, data, settings_set, settings_get, pick_menu_item, need_keypress, cap_story,
|
||||
def test_seed_vault_menus(dev, data, settings_set, master_settings_get, pick_menu_item, need_keypress, cap_story,
|
||||
cap_menu, reset_seed_words, get_identity_story, get_seed_value_ux, fake_txn,
|
||||
try_sign, sim_exec, goto_home):
|
||||
# Verify "seed vault" feature works as intended
|
||||
@ -814,6 +814,7 @@ def test_seed_vault_menus(dev, data, settings_set, settings_get, pick_menu_item,
|
||||
need_keypress("y")
|
||||
m = cap_menu()
|
||||
assert m[0] == "AAAA"
|
||||
# UNTESTED: parent menu will also show our updated name
|
||||
pick_menu_item("Use This Seed")
|
||||
time.sleep(.1)
|
||||
title, story = cap_story()
|
||||
@ -849,7 +850,7 @@ def test_seed_vault_menus(dev, data, settings_set, settings_get, pick_menu_item,
|
||||
assert encoded[0:len(stored_secret)] == stored_secret
|
||||
|
||||
# check rename worked
|
||||
seeds = settings_get("seeds")
|
||||
seeds = master_settings_get("seeds")
|
||||
assert len(seeds) == 1
|
||||
entry = seeds[0]
|
||||
assert len(entry) == 4
|
||||
@ -864,7 +865,7 @@ def test_seed_vault_menus(dev, data, settings_set, settings_get, pick_menu_item,
|
||||
|
||||
def test_seed_vault_captures(request, dev, settings_set, settings_get, pick_menu_item,
|
||||
need_keypress, cap_story, reset_seed_words, fake_txn,
|
||||
generate_ephemeral_words, goto_home, get_secrets,
|
||||
generate_ephemeral_words, goto_home, get_secrets, master_settings_get,
|
||||
import_ephemeral_xprv, set_bip39_pw, restore_main_seed,
|
||||
restore_seed_xor, derive_bip85_secret, activate_bip85_ephemeral):
|
||||
# Capture seeds by all the different paths and verify correct values are captured.
|
||||
@ -928,7 +929,7 @@ def test_seed_vault_captures(request, dev, settings_set, settings_get, pick_menu
|
||||
restore_main_seed(seed_vault=True)
|
||||
|
||||
# check all saved okay
|
||||
seeds = settings_get('seeds')
|
||||
seeds = master_settings_get('seeds')
|
||||
n_seeds = len(seeds)
|
||||
assert n_seeds == expect_count
|
||||
|
||||
@ -1041,7 +1042,7 @@ def test_seed_vault_modifications(settings_set, reset_seed_words, pick_menu_item
|
||||
time.sleep(.1)
|
||||
m = cap_menu()
|
||||
assert "Rename" in m
|
||||
assert "In Use" in m
|
||||
assert "Seed In Use" in m
|
||||
assert "Delete" in m
|
||||
|
||||
pick_menu_item("Rename")
|
||||
@ -1084,7 +1085,7 @@ def test_seed_vault_modifications(settings_set, reset_seed_words, pick_menu_item
|
||||
time.sleep(.1)
|
||||
m = cap_menu()
|
||||
assert "Rename" in m
|
||||
assert "In Use" in m
|
||||
assert "Seed In Use" in m
|
||||
assert "Delete" in m
|
||||
|
||||
pick_menu_item("Delete")
|
||||
|
||||
Loading…
Reference in New Issue
Block a user