parent
5de8c9672b
commit
05bd7ab39c
@ -1,6 +1,7 @@
|
||||
## 5.2.1 - 2023-11-XX
|
||||
|
||||
- New Feature: Temporary Seed from COLDCARD encrypted backup.
|
||||
- New Feature: Export seed as SeedQR
|
||||
- Enhancement: Add current temporary seed to Seed Vault from within Seed Vault menu.
|
||||
If current seed is temporary and not saved yet, `Add current tmp` menu item is
|
||||
shown in Seed Vault menu.
|
||||
|
||||
@ -719,6 +719,42 @@ async def view_seed_words(*a):
|
||||
stash.blank_object(msg)
|
||||
stash.blank_object(raw)
|
||||
|
||||
async def export_seedqr(*a):
|
||||
# see standard: <https://github.com/SeedSigner/seedsigner/blob/dev/docs/seed_qr/README.md>
|
||||
import bip39, stash
|
||||
|
||||
if not await ux_confirm('The next screen will show the seed words in a QR code.'
|
||||
'\n\nAnyone with knowledge of those words '
|
||||
'can control all funds in this wallet.'):
|
||||
return
|
||||
|
||||
from glob import dis
|
||||
dis.fullscreen("Wait...")
|
||||
dis.busy_bar(True)
|
||||
|
||||
# Note: cannot reach this menu item if no words. If they are tmp, that's cool.
|
||||
|
||||
with stash.SensitiveValues(bypass_tmp=False) as sv:
|
||||
if sv.deltamode:
|
||||
# give up and wipe self rather than show true seed values.
|
||||
import callgate
|
||||
callgate.fast_wipe()
|
||||
|
||||
if sv.mode != 'words':
|
||||
raise ValueError(sv.mode)
|
||||
|
||||
words = bip39.b2a_words(sv.raw).split(' ')
|
||||
|
||||
dis.busy_bar(False)
|
||||
qr = ''.join('%04d'% bip39.get_word_index(w) for w in words)
|
||||
|
||||
del words
|
||||
|
||||
from ux import show_qr_code
|
||||
await show_qr_code(qr, True)
|
||||
|
||||
stash.blank_object(qr)
|
||||
|
||||
async def damage_myself():
|
||||
# called when it's time to disable ourselves due to various
|
||||
# features related to duress and so on
|
||||
|
||||
@ -212,6 +212,8 @@ SeedFunctionsMenu = [
|
||||
MenuItem('Seed XOR', menu=SeedXORMenu),
|
||||
MenuItem("Destroy Seed", f=clear_seed),
|
||||
MenuItem('Lock Down Seed', f=convert_ephemeral_to_master),
|
||||
MenuItem('Export SeedQR', f=export_seedqr,
|
||||
predicate=lambda: settings.get('words', True)),
|
||||
]
|
||||
|
||||
DangerZoneMenu = [
|
||||
|
||||
@ -29,14 +29,14 @@ class QRDisplaySingle(UserInteraction):
|
||||
# - inverted QR (black/white swap) still readable by scanners, altho wrong
|
||||
if self.is_alnum:
|
||||
# targeting 'alpha numeric' mode, nice and dense; caps only tho
|
||||
enc = uqr.Mode_ALPHANUMERIC
|
||||
enc = uqr.Mode_ALPHANUMERIC if not msg.isdigit() else uqr.Mode_NUMERIC
|
||||
msg = msg.upper()
|
||||
else:
|
||||
# has to be 'binary' mode, altho shorter msg, typical 34-36
|
||||
enc = uqr.Mode_BYTE
|
||||
|
||||
# can fail if not enough space in QR
|
||||
self.qr_data = uqr.make(msg, min_version=3, max_version=11, encoding=enc)
|
||||
self.qr_data = uqr.make(msg, min_version=2, max_version=11, encoding=enc)
|
||||
|
||||
def redraw(self):
|
||||
# Redraw screen.
|
||||
|
||||
@ -364,9 +364,9 @@ async def show_qr_codes(addrs, is_alnum, start_n):
|
||||
o = QRDisplaySingle(addrs, is_alnum, start_n, sidebar=None)
|
||||
await o.interact_bare()
|
||||
|
||||
async def show_qr_code(data, is_alnum):
|
||||
async def show_qr_code(data, is_alnum, msg=None):
|
||||
from qrs import QRDisplaySingle
|
||||
o = QRDisplaySingle([data], is_alnum)
|
||||
o = QRDisplaySingle([data], is_alnum, sidebar=msg)
|
||||
await o.interact_bare()
|
||||
|
||||
async def ux_enter_bip32_index(prompt, can_cancel=False, unlimited=False):
|
||||
|
||||
@ -669,6 +669,46 @@ def test_show_seed(mode, b39_word, goto_home, pick_menu_item, cap_story, need_ke
|
||||
|
||||
need_keypress('y') # clear screen
|
||||
|
||||
@pytest.mark.qrcode
|
||||
@pytest.mark.parametrize("data", [
|
||||
(simulator_fixed_words, [2007, 1585, 123, 131, 745, 43, 1506, 1930, 664, 749, 1200, 113, 1321, 330, 1764, 698, 1160, 656, 647, 1424, 135, 767, 987, 335]),
|
||||
("task tube actor end cannon potato sign card occur donkey soup baby tooth bless barely pull gap priority", [1776, 1872, 21, 588, 267, 1350, 1602, 276, 1222, 521, 1663, 136, 1830, 189, 148, 1386, 762, 1367]),
|
||||
("vacuum bridge buddy supreme exclude milk consider tail expand wasp pattern nuclear", [1924,222,235,1743,631,1124,378,1770,641,1980,1290,1210]),
|
||||
("approve fruit lens brass ring actual stool coin doll boss strong rate", "008607501025021714880023171503630517020917211425"),
|
||||
("good battle boil exact add seed angle hurry success glad carbon whisper", "080301540200062600251559007008931730078802752004"),
|
||||
("forum undo fragile fade shy sign arrest garment culture tube off merit", "073318950739065415961602009907670428187212261116"),
|
||||
("sound federal bonus bleak light raise false engage round stock update render quote truck quality fringe palace foot recipe labor glow tortoise potato still", "166206750203018810361417065805941507171219081456140818651401074412730727143709940798183613501710"),
|
||||
("atom solve joy ugly ankle message setup typical bean era cactus various odor refuse element afraid meadow quick medal plate wisdom swap noble shallow", "011416550964188800731119157218870156061002561932122514430573003611011405110613292018175411971576"),
|
||||
("attack pizza motion avocado network gather crop fresh patrol unusual wild holiday candy pony ranch winter theme error hybrid van cereal salon goddess expire", "011513251154012711900771041507421289190620080870026613431420201617920614089619290300152408010643"),
|
||||
])
|
||||
def test_show_seed_qr(data, goto_home, pick_menu_item, cap_story, need_keypress,
|
||||
sim_exec, cap_menu, get_pp_sofar, get_secrets, cap_screen_qr,
|
||||
set_encoded_secret, qr_quality_check, set_seed_words):
|
||||
n = 4 # SeedQr 4 str chars for each index
|
||||
words, qr_expect = data
|
||||
if isinstance(qr_expect, str):
|
||||
qr_expect = [int(qr_expect[i:i+n]) for i in range(0, len(qr_expect), n)]
|
||||
set_seed_words(words)
|
||||
|
||||
goto_home()
|
||||
pick_menu_item('Advanced/Tools')
|
||||
pick_menu_item('Danger Zone')
|
||||
pick_menu_item('Seed Functions')
|
||||
pick_menu_item('Export SeedQR')
|
||||
|
||||
time.sleep(.01)
|
||||
title, body = cap_story()
|
||||
assert 'Are you SURE' in body
|
||||
assert 'can control all funds' in body
|
||||
need_keypress('y') # skip warning
|
||||
time.sleep(0.01)
|
||||
|
||||
qr = cap_screen_qr().decode('ascii')
|
||||
qr = [int(qr[i:i+n]) for i in range(0, len(qr), n)]
|
||||
assert qr == qr_expect
|
||||
|
||||
need_keypress('y') # clear screen
|
||||
|
||||
def test_destroy_seed(goto_home, pick_menu_item, cap_story, need_keypress, sim_exec,
|
||||
cap_menu, get_secrets):
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user