diff --git a/assets/dontcopy.png b/assets/dontcopy.png new file mode 100644 index 0000000000..4a1480f22b Binary files /dev/null and b/assets/dontcopy.png differ diff --git a/assets/swipedown.png b/assets/swipedown.png new file mode 100644 index 0000000000..00af05f40c Binary files /dev/null and b/assets/swipedown.png differ diff --git a/src/apps/management/__init__.py b/src/apps/management/__init__.py index df1d776fba..88ff912d81 100644 --- a/src/apps/management/__init__.py +++ b/src/apps/management/__init__.py @@ -12,8 +12,8 @@ def dispatch_LoadDevice(*args, **kwargs): @unimport def dispatch_ResetDevice(*args, **kwargs): - from .reset_device import layout_reset_device - return layout_reset_device(*args, **kwargs) + from .reset_device import reset_device + return reset_device(*args, **kwargs) @unimport diff --git a/src/apps/management/reset_device.py b/src/apps/management/reset_device.py index 689b76dfcf..77fdb7d210 100644 --- a/src/apps/management/reset_device.py +++ b/src/apps/management/reset_device.py @@ -1,6 +1,5 @@ from micropython import const -from trezor import wire, ui -from trezor.ui.container import Container +from trezor import config, ui, wire from trezor.utils import unimport, chunks from ubinascii import hexlify @@ -10,16 +9,15 @@ if __debug__: @unimport -async def layout_reset_device(ctx, msg): - from trezor import config +async def reset_device(ctx, msg): from trezor.ui.text import Text from trezor.crypto import hashlib, random, bip39 + from trezor.ui.keyboard import MnemonicKeyboard from trezor.messages.EntropyRequest import EntropyRequest from trezor.messages.Success import Success from trezor.messages import FailureType from trezor.messages import ButtonRequestType from trezor.messages.wire_types import EntropyAck - from apps.management.change_pin import request_pin_confirm from apps.common.confirm import require_confirm from apps.common import storage @@ -37,11 +35,13 @@ async def layout_reset_device(ctx, msg): internal_entropy = random.bytes(32) + # display internal entropy if msg.display_random: entropy_lines = chunks(hexlify(internal_entropy).decode(), 16) entropy_content = Text('Internal entropy', ui.ICON_RESET, ui.MONO, *entropy_lines) await require_confirm(ctx, entropy_content, ButtonRequestType.ResetDevice) + # request new PIN if msg.pin_protection: curpin = '' newpin = await request_pin_confirm(ctx) @@ -49,6 +49,7 @@ async def layout_reset_device(ctx, msg): curpin = '' newpin = '' + # request external entropy and compute mnemonic external_entropy_ack = await ctx.call(EntropyRequest(), EntropyAck) ehash = hashlib.sha256() ehash.update(internal_entropy) @@ -56,46 +57,62 @@ async def layout_reset_device(ctx, msg): entropy = ehash.digest() mnemonic = bip39.from_data(entropy[:msg.strength // 8]) - await show_mnemonic_by_word(ctx, mnemonic) + # mnemonic safety warning + warning_content = Text( + 'Backup your seed', ui.ICON_NOCOPY, ui.NORMAL, + 'Never make a digital', + 'copy of your recovery', + 'seed and never upload', + 'it online!') + await require_confirm( + ctx, + warning_content, + ButtonRequestType.ResetDevice, + confirm='I understand', + cancel=None) + # ask to write down mnemonic + await show_mnemonic(mnemonic) + + # ask for random word to check correctness + words = mnemonic.split() + index = random.uniform(len(words)) + res = await MnemonicKeyboard('Type %s. word' % (index + 1)) + if res != words[index]: + content = Text( + 'Wrong entry!', ui.ICON_CLEAR, + 'You have entered', + 'wrong seed word.', + 'Please, reconnect', + 'the device and try again.', icon_color=ui.RED) + ui.display.clear() + await content + raise wire.FailureError(FailureType.DataError, 'Wrong entry') + + # write into storage if curpin != newpin: config.change_pin(curpin, newpin) - storage.load_settings(label=msg.label, - use_passphrase=msg.passphrase_protection) + storage.load_settings( + label=msg.label, use_passphrase=msg.passphrase_protection) storage.load_mnemonic(mnemonic) + # show success message + content = Text( + 'Backup is done!', ui.ICON_CONFIRM, + 'Never make a digital', + 'copy of your recovery', + 'seed and never upload', + 'it online!', icon_color=ui.GREEN) + await require_confirm( + ctx, + content, + ButtonRequestType.ResetDevice, + confirm='Finish setup', + cancel=None) + return Success(message='Initialized') -async def show_mnemonic_by_word(ctx, mnemonic): - from trezor.ui.text import Text - from trezor.messages.ButtonRequestType import ConfirmWord - from apps.common.confirm import confirm - - words = mnemonic.split() - - if __debug__: - global current_word - - for index, word in enumerate(words): - if __debug__: - current_word = word - await confirm(ctx, - Text('Recovery seed setup', ui.ICON_RESET, - ui.NORMAL, 'Write down seed word', '', - ui.BOLD, '%d. %s' % (index + 1, word)), - ConfirmWord, confirm='Next', cancel=None) - - for index, word in enumerate(words): - if __debug__: - current_word = word - await confirm(ctx, - Text('Recovery seed setup', ui.ICON_RESET, - ui.NORMAL, 'Confirm seed word', '', - ui.BOLD, '%d. %s' % (index + 1, word)), - ConfirmWord, confirm='Next', cancel=None) - - async def show_mnemonic(mnemonic): from trezor.ui.scroll import paginate @@ -108,26 +125,23 @@ async def show_mnemonic(mnemonic): async def show_mnemonic_page(page, page_count, mnemonic): from trezor.ui.button import Button - from trezor.ui.scroll import render_scrollbar, animate_swipe + from trezor.ui.text import Text + from trezor.ui.scroll import Scrollpage, animate_swipe + lines = ['%d. %s' % (wi + 1, word) for wi, word in mnemonic[page]] + scroll_page = Scrollpage( + Text('Recovery seed setup', ui.ICON_RESET, ui.MONO, lines), + page, + page_count) ui.display.clear() - ui.header('Write down your seed', ui.ICON_RESET, ui.BG, ui.LIGHT_GREEN) - render_scrollbar(page, page_count) - - for pi, (wi, word) in enumerate(mnemonic[page]): - top = pi * 35 + 68 - pos = wi + 1 - offset = 0 - if pos > 9: - offset += 12 - ui.display.text(10, top, '%d.' % pos, ui.BOLD, ui.LIGHT_GREEN, ui.BG) - ui.display.text(30 + offset, top, '%s' % word, ui.BOLD, ui.FG, ui.BG) + scroll_page.render() if page + 1 == page_count: await Button( - (0, 240 - 48, 240, 48), - 'Finish', + ui.grid(4, n_x=1), + "I'm done", normal_style=ui.BTN_CONFIRM, active_style=ui.BTN_CONFIRM_ACTIVE) + ui.display.clear() else: await animate_swipe() diff --git a/src/trezor/res/nocopy.toig b/src/trezor/res/nocopy.toig new file mode 100644 index 0000000000..eeb3029ebf Binary files /dev/null and b/src/trezor/res/nocopy.toig differ diff --git a/src/trezor/res/swipedown.toig b/src/trezor/res/swipedown.toig new file mode 100644 index 0000000000..0b5707ddf2 Binary files /dev/null and b/src/trezor/res/swipedown.toig differ diff --git a/src/trezor/ui/confirm.py b/src/trezor/ui/confirm.py index 5d9c278f7c..2eb381d9e4 100644 --- a/src/trezor/ui/confirm.py +++ b/src/trezor/ui/confirm.py @@ -16,10 +16,10 @@ class ConfirmDialog(Widget): def __init__(self, content, confirm=DEFAULT_CONFIRM, cancel=DEFAULT_CANCEL): self.content = content if cancel is not None: - self.confirm = Button(ui.grid(8, n_x=2), confirm, + self.confirm = Button(ui.grid(9, n_x=2), confirm, normal_style=ui.BTN_CONFIRM, active_style=ui.BTN_CONFIRM_ACTIVE) - self.cancel = Button(ui.grid(9, n_x=2), cancel, + self.cancel = Button(ui.grid(8, n_x=2), cancel, normal_style=ui.BTN_CANCEL, active_style=ui.BTN_CANCEL_ACTIVE) else: diff --git a/src/trezor/ui/scroll.py b/src/trezor/ui/scroll.py index e2c0597b95..2701a1d585 100644 --- a/src/trezor/ui/scroll.py +++ b/src/trezor/ui/scroll.py @@ -1,5 +1,5 @@ from micropython import const -from trezor import loop, ui +from trezor import loop, ui, res from .swipe import Swipe, SWIPE_UP, SWIPE_DOWN, SWIPE_VERTICAL @@ -37,17 +37,15 @@ async def animate_swipe(): sleep = loop.sleep(time_delay) for t in ui.pulse(draw_delay): fg = ui.blend(ui.GREY, ui.DARK_GREY, t) - ui.display.bar_radius(102, 214, 36, 4, fg, ui.BG, 2) - ui.display.bar_radius(106, 222, 28, 4, fg, ui.BG, 2) - ui.display.bar_radius(110, 230, 20, 4, fg, ui.BG, 2) + ui.display.icon(110, 210, res.load(ui.ICON_SWIPE), fg, ui.BG) await sleep def render_scrollbar(page, page_count): bbox = const(220) - size = const(10) + size = const(8) - padding = 18 + padding = 14 if page_count * padding > bbox: padding = bbox // page_count @@ -62,11 +60,16 @@ def render_scrollbar(page, page_count): size, ui.FG, ui.BG, 4) -class Scrollbar(ui.Widget): +class Scrollpage(ui.Widget): - def __init__(self, page, page_count): + def __init__(self, content, page, page_count): + self.content = content self.page = page self.page_count = page_count def render(self): + self.content.render() render_scrollbar(self.page, self.page_count) + + async def __iter__(self): + return await loop.wait(super().__iter__(), self.content) diff --git a/src/trezor/ui/style.py b/src/trezor/ui/style.py index 59ae73aae4..7e21370dfe 100644 --- a/src/trezor/ui/style.py +++ b/src/trezor/ui/style.py @@ -55,6 +55,8 @@ ICON_LOCK = 'trezor/res/lock.toig' ICON_SEND = 'trezor/res/send.toig' ICON_CLICK = 'trezor/res/click.toig' ICON_BACK = 'trezor/res/left.toig' +ICON_NOCOPY = 'trezor/res/nocopy.toig' +ICON_SWIPE = 'trezor/res/swipedown.toig' # buttons BTN_DEFAULT = { @@ -153,5 +155,5 @@ LDR_DEFAULT_ACTIVE = { 'bg-color': BG, 'fg-color': GREEN, 'icon': ICON_SEND, - 'icon-fg-color': GREEN, + 'icon-fg-color': WHITE, } diff --git a/src/trezor/ui/text.py b/src/trezor/ui/text.py index 3d8338312d..688aca8648 100644 --- a/src/trezor/ui/text.py +++ b/src/trezor/ui/text.py @@ -22,11 +22,20 @@ class Text(ui.Widget): bg = ui.BG ui.header(self.header_text, self.header_icon, ui.TITLE_GREY, ui.BG, self.icon_color) - for item in self.content: - if isinstance(item, str): - ui.display.text(offset_x, offset_y, item, style, fg, bg) + def process(eitem): + nonlocal offset_y + nonlocal style + nonlocal fg + if isinstance(eitem, str): + ui.display.text(offset_x, offset_y, eitem, style, fg, bg) offset_y += TEXT_LINE_HEIGHT - elif item == ui.MONO or item == ui.NORMAL or item == ui.BOLD: - style = item + elif isinstance(eitem, (tuple, list, dict, set)): + for i in eitem: + process(i) + elif eitem == ui.MONO or eitem == ui.NORMAL or eitem == ui.BOLD: + style = eitem else: - fg = item + fg = eitem + + for item in self.content: + process(item)