diff --git a/src/apps/management/recovery_device.py b/src/apps/management/recovery_device.py index 15c3f225..8fab3d8d 100644 --- a/src/apps/management/recovery_device.py +++ b/src/apps/management/recovery_device.py @@ -2,22 +2,41 @@ from trezor import ui, wire from trezor.utils import unimport -def nth(n): - if 4 <= n % 100 <= 20: - sfx = 'th' - else: - sfx = {1: 'st', 2: 'nd', 3: 'rd'}.get(n % 10, 'th') - return str(n) + sfx - - @unimport async def layout_recovery_device(ctx, msg): + from trezor.crypto import bip39 + from trezor.messages.FailureType import UnexpectedMessage, ProcessError + from trezor.messages.Success import Success + from trezor.ui.keyboard import KeyboardMultiTap + from trezor.ui.text import Text + from apps.common import storage + from apps.common.confirm import require_confirm - msg = 'Please enter ' + nth(msg.word_count) + ' word' + if storage.is_initialized(): + raise wire.FailureError(UnexpectedMessage, 'Already initialized') - ui.display.clear() - ui.header('Recovery device', ui.ICON_RECOVERY, ui.BG, ui.LIGHT_GREEN) - ui.display.text(10, 74, msg, ui.BOLD, ui.FG, ui.BG) - ui.display.text(10, 104, 'of your mnemonic.', ui.BOLD, ui.FG, ui.BG) + words = [] - # TODO + kbd = KeyboardMultiTap() + for i in range(0, msg.word_count): + kbd.prompt = '%s. ' % (i + 1) + word = await kbd + words.append(word) + + # TODO: confirm words, start again? + await require_confirm(ctx, Text( + 'Recovering seed', ui.ICON_RESET)) + + mnemonic = ' '.join(words) + + if not msg.enforce_wordlist and not bip39.check(mnemonic): + raise wire.FailureError(ProcessError, 'Mnemonic is not valid') + + # TODO: request pin + pin = '' + + storage.load_mnemonic(mnemonic) + storage.load_settings(pin=pin, + passphrase_protection=msg.passphrase_protection, + language=msg.language, + label=msg.label) diff --git a/src/trezor/ui/keyboard.py b/src/trezor/ui/keyboard.py index 1f83a156..4a01cd95 100644 --- a/src/trezor/ui/keyboard.py +++ b/src/trezor/ui/keyboard.py @@ -4,7 +4,7 @@ from trezor.ui import display from trezor.ui.button import Button, BTN_CLICKED -def cell_area(i, n_x=3, n_y=3, start_x=0, start_y=40, end_x=240, end_y=240 - 48, spacing=0): +def cell_area(i, n_x=3, n_y=3, start_x=0, start_y=40, end_x=240, end_y=240, spacing=0): w = (end_x - start_x) // n_x h = (end_y - start_y) // n_y x = (i % n_x) * w @@ -32,8 +32,9 @@ def compute_mask(text): class KeyboardMultiTap(ui.Widget): - def __init__(self, content=''): + def __init__(self, content='', prompt=''): self.content = content + self.prompt = prompt self.sugg_mask = 0xffffffff self.sugg_word = None self.pending_button = None @@ -52,8 +53,8 @@ class KeyboardMultiTap(ui.Widget): display.bar(0, 0, 205, 40, ui.BG) # input line - content_width = display.text_width(self.content, ui.BOLD) - display.text(20, 30, self.content, ui.BOLD, ui.FG, ui.BG) + content_width = display.text_width(self.prompt + self.content, ui.BOLD) + display.text(20, 30, self.prompt + self.content, ui.BOLD, ui.FG, ui.BG) # pending marker if self.pending_button is not None: @@ -85,13 +86,18 @@ class KeyboardMultiTap(ui.Widget): self._update_suggestion() self._update_buttons() return - if self.sugg_button.touch(event, pos) == BTN_CLICKED and self.sugg_word is not None: - self.content = self.sugg_word + if self.sugg_button.touch(event, pos) == BTN_CLICKED: + if self.content == bip39.find_word(self.content): + result = self.content + self.content = '' + elif self.sugg_word is not None: + result = None + self.content = self.sugg_word self.pending_button = None self.pending_index = 0 self._update_suggestion() self._update_buttons() - return + return result for btn in self.key_buttons: if btn.touch(event, pos) == BTN_CLICKED: if self.pending_button is btn: @@ -117,7 +123,7 @@ class KeyboardMultiTap(ui.Widget): def _update_buttons(self): for btn in self.key_buttons: - if compute_mask(btn.content) & self.sugg_mask: + if btn is self.pending_button or compute_mask(btn.content) & self.sugg_mask: btn.enable() else: btn.disable() @@ -125,18 +131,27 @@ class KeyboardMultiTap(ui.Widget): def __iter__(self): timeout = loop.sleep(1000 * 1000 * 1) touch = loop.select(io.TOUCH) - wait = loop.wait(touch, timeout) - while True: + wait_timeout = loop.wait(touch, timeout) + wait_touch = loop.wait(touch) + content = None + while content is None: self.render() + if self.pending_button is not None: + wait = wait_timeout + else: + wait = wait_touch result = yield wait if touch in wait.finished: event, *pos = result - self.touch(event, pos) + content = self.touch(event, pos) else: self.pending_button = None self.pending_index = 0 - self._update_suggestion() - self._update_buttons() + if self.sugg_word is None: + self.content = self.content[:-1] + self._update_suggestion() + self._update_buttons() + return content def zoom_buttons(keys, upper=False): diff --git a/src/trezor/ui/style.py b/src/trezor/ui/style.py index 1a50fc74..34da5f51 100644 --- a/src/trezor/ui/style.py +++ b/src/trezor/ui/style.py @@ -122,12 +122,14 @@ BTN_KEY = { 'fg-color': FG, 'text-style': MONO, 'border-color': BG, + 'radius': RADIUS, } BTN_KEY_ACTIVE = { - 'bg-color': GREY, - 'fg-color': BG, + 'bg-color': FG, + 'fg-color': BLACKISH, 'text-style': MONO, - 'border-color': GREY, + 'border-color': FG, + 'radius': RADIUS, } # loader