Skip to content

Commit

Permalink
add [compact]seedQR and plaintext support to import mnemonics from QR…
Browse files Browse the repository at this point in the history
… and SD
  • Loading branch information
stepansnigirev committed Jul 10, 2022
1 parent ff196e4 commit 071208a
Show file tree
Hide file tree
Showing 4 changed files with 96 additions and 5 deletions.
2 changes: 1 addition & 1 deletion f469-disco
25 changes: 24 additions & 1 deletion src/gui/screens/mnemonic.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@
from ..decorators import *
from ..components import MnemonicTable, HintKeyboard
from .screen import Screen

from .prompt import Prompt

class MnemonicScreen(Screen):
QR = 1
SD = 2
def __init__(self, mnemonic="", title="Your recovery phrase:", note=None):
super().__init__()
self.title = add_label(title, scr=self, style="title")
Expand All @@ -22,6 +24,27 @@ def __init__(self, mnemonic="", title="Your recovery phrase:", note=None):
self.close_label = lv.label(self.close_button)
self.close_label.set_text("OK")

class MnemonicPrompt(Prompt):
def __init__(self, mnemonic="", title="Your recovery phrase:", note=None):
super().__init__(title, message="")
table = MnemonicTable(self)
table.set_mnemonic(mnemonic)
table.align(self.title, lv.ALIGN.OUT_BOTTOM_MID, 0, 30)


class ExportMnemonicScreen(MnemonicScreen):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.show_qr_btn = add_button(text="Show as QR code", scr=self, callback=on_release(self.select_qr))
self.show_qr_btn.align(self.table, lv.ALIGN.OUT_BOTTOM_MID, 0, 10)
self.save_sd_btn = add_button(text="Save to SD card (plaintext)", scr=self, callback=on_release(self.select_sd))
self.save_sd_btn.align(self.show_qr_btn, lv.ALIGN.OUT_BOTTOM_MID, 0, 10)

def select_sd(self):
self.set_value(self.SD)

def select_qr(self):
self.set_value(self.QR)

class NewMnemonicScreen(MnemonicScreen):
def __init__(
Expand Down
35 changes: 33 additions & 2 deletions src/keystore/ram.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
from embit.transaction import SIGHASH
from helpers import aead_encrypt, aead_decrypt, tagged_hash
import secp256k1
from gui.screens import Alert, PinScreen, MnemonicScreen, Prompt
from gui.screens import Alert, PinScreen, Prompt, Menu, QRAlert
from gui.screens.mnemonic import ExportMnemonicScreen
from binascii import hexlify

class RAMKeyStore(KeyStore):
Expand Down Expand Up @@ -371,4 +372,34 @@ async def show_mnemonic(self):
"Continue?")):
self.lock()
await self.unlock()
await self.show(MnemonicScreen(self.mnemonic))
v = await self.show(ExportMnemonicScreen(self.mnemonic))
if v == ExportMnemonicScreen.QR:
v = await self.show(
Menu([(1, "SeedQR (digits)"), (2, "Compact SeedQR (binary)"), (3, "Plaintext")],
last=(255, None),
title="Select encoding format",
note="Compact QR is smaller but not human-readable\n")
)
if v == 255:
return
elif v == 1:
nums = [bip39.WORDLIST.index(w) for w in self.mnemonic.split()]
qr_msg = "".join([("000"+str(n))[-4:] for n in nums])
msg = qr_msg
elif v == 2:
qr_msg = bip39.mnemonic_to_bytes(self.mnemonic)
msg = hexlify(qr_msg).decode()
elif v == 3:
qr_msg = self.mnemonic
msg = self.mnemonic
await self.show(QRAlert(title="Your mnemonic as QR code", message=msg, qr_message=qr_msg))
elif v == ExportMnemonicScreen.SD:
if not platform.is_sd_present:
raise KeyStoreError("SD card is not present")
if await self.show(Prompt("Are you sure?", message="Your mnemonic will be saved as a simple plaintext file.\n\nAnyone with access to it will be able to read your key.\n\nContinue?")):
platform.mount_sdcard()
fname = "/sd/%s.txt" % self.mnemonic.split()[0]
with open(platform.fpath(fname), "w") as f:
f.write(self.mnemonic)
platform.unmount_sdcard()
await self.show(Alert(title="Mnemonic is saved!", message="You mnemonic is saved in plaintext to\n\n%s\n\nPlease keep it safe." % fname))
39 changes: 38 additions & 1 deletion src/specter.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from embit import bip39
from embit.liquid.networks import NETWORKS
from gui.screens.settings import HostSettings
from gui.screens.mnemonic import MnemonicPrompt

# small helper functions
from helpers import gen_mnemonic, fix_mnemonic, load_apps, is_liquid
Expand Down Expand Up @@ -166,6 +167,10 @@ async def cross_app_communicate(self, stream, app:str=None, show_fn=None):
return await self.process_host_request(stream, popup=False, appname=app, show_fn=show_fn)

async def initmenu(self):
# only enable passive hosts
for host in self.hosts:
if host.button:
await host.enable()
# for every button we use an ID
# to avoid mistakes when editing strings
# If ID is None - it is a section title, not a button
Expand All @@ -174,6 +179,7 @@ async def initmenu(self):
(None, "Key management"),
(0, "Generate new key"),
(1, "Enter recovery phrase"),
(777, "Import recovery phrase"),
]
if self.keystore.is_key_saved and self.keystore.load_button:
buttons.append((2, self.keystore.load_button))
Expand Down Expand Up @@ -210,6 +216,35 @@ async def initmenu(self):
return self.mainmenu
elif menuitem == 3:
await self.update_devsettings()
elif menuitem == 777:
host = await self.gui.menu(title="What to use for import?", note="\n",
buttons=[(host, host.button) for host in self.hosts if host.button],
last=(255, None))
if host is None:
return
else:
stream = await host.get_data()
if not stream:
return
data = stream.read()
# digital mnemonic
if len(data) >= 4*12 and len(data) <= 4*24 and len(data) % 12 == 0 and (b" " not in data):
mnemonic = " ".join([bip39.WORDLIST[int(data[4*i:4*i+4])] for i in range(len(data)//4)])
# binary mnemonic
elif len(data) >= 16 and len(data) <= 32:
mnemonic = bip39.mnemonic_from_bytes(data)
else:
mnemonic = data.decode()
if not bip39.mnemonic_is_valid(mnemonic):
raise SpecterError("Invalid data")
scr = MnemonicPrompt(title="Imported mnemonic:", mnemonic=mnemonic)
res = await self.gui.show_screen()(scr)
if not res:
return
self.keystore.set_mnemonic(mnemonic, "")
self.init_apps()
self.current_menu = self.mainmenu
return self.mainmenu
# lock device
elif menuitem == 5:
await self.lock()
Expand All @@ -220,8 +255,10 @@ async def initmenu(self):
raise SpecterError("Not implemented")

async def mainmenu(self):
# interactive hosts are enabled later
for host in self.hosts:
await host.enable()
if not host.button:
await host.enable()
# buttons defined by host classes
# only added if there is a GUI-triggered communication
host_buttons = [
Expand Down

0 comments on commit 071208a

Please sign in to comment.