From 7b89e4b14c7ea21972877f59396ffeac4251a104 Mon Sep 17 00:00:00 2001 From: Peter Jensen Date: Mon, 29 Jan 2018 19:26:42 +0100 Subject: [PATCH] ui/passphrase: ask for source of passphrase --- src/apps/common/request_passphrase.py | 49 ++++++++++++++++--- src/trezor/ui/entry_select.py | 33 +++++++++++++ src/trezor/ui/keyboard.py | 68 +++++++++++++++++++++++++++ 3 files changed, 143 insertions(+), 7 deletions(-) create mode 100644 src/trezor/ui/entry_select.py diff --git a/src/apps/common/request_passphrase.py b/src/apps/common/request_passphrase.py index 888f89d06a..9fc8622c0a 100644 --- a/src/apps/common/request_passphrase.py +++ b/src/apps/common/request_passphrase.py @@ -1,4 +1,4 @@ -from trezor import ui, wire +from trezor import ui, wire, res async def request_passphrase(ctx): @@ -6,17 +6,52 @@ async def request_passphrase(ctx): from trezor.messages.PassphraseRequest import PassphraseRequest from trezor.messages.wire_types import PassphraseAck, Cancel from trezor.ui.text import Text + from trezor.ui.confirm import ConfirmDialog, CONFIRMED + from trezor.ui.entry_select import EntrySelector + from trezor.ui.keyboard import PassphraseKeyboard ui.display.clear() text = Text('Enter passphrase', ui.ICON_RESET, - 'Please enter passphrase', 'on your computer.') - text.render() + 'Where to enter your', 'passphrase?') + entry = EntrySelector(text) + entry_type = await entry - ack = await ctx.call(PassphraseRequest(), PassphraseAck, Cancel) - if ack.MESSAGE_WIRE_TYPE == Cancel: - raise wire.FailureError(ActionCancelled, 'Passphrase cancelled') + if entry_type == 1: + ui.display.clear() + text = Text('Passphrase entry', ui.ICON_RESET, + 'Please, type passphrase', 'on connected host.') + text.render() + ack = await ctx.call(PassphraseRequest(), PassphraseAck, Cancel) + if ack.MESSAGE_WIRE_TYPE == Cancel: + raise wire.FailureError(ActionCancelled, 'Passphrase cancelled') + return ack.passphrase + else: + def onchange(): + c = dialog.cancel + if keyboard.passphrase: + c.content = res.load(ui.ICON_CLEAR) + else: + c.content = res.load(ui.ICON_LOCK) + c.taint() + c.render() - return ack.passphrase + ui.display.clear() + keyboard = PassphraseKeyboard('Enter passphrase') + keyboard.onchange = onchange + dialog = ConfirmDialog(keyboard) + dialog.cancel.area = ui.grid(12) + dialog.confirm.area = ui.grid(14) + keyboard.onchange() + + while True: + result = await dialog + if result == CONFIRMED: + return keyboard.passphrase + elif result != CONFIRMED and keyboard.passphrase: + keyboard.change('') + continue + else: + raise wire.FailureError(ActionCancelled, 'Passphrase cancelled') async def protect_by_passphrase(ctx): diff --git a/src/trezor/ui/entry_select.py b/src/trezor/ui/entry_select.py new file mode 100644 index 0000000000..f5f7c26a0b --- /dev/null +++ b/src/trezor/ui/entry_select.py @@ -0,0 +1,33 @@ +from micropython import const +from trezor import loop +from trezor import ui +from trezor.ui import Widget +from trezor.ui.button import Button, BTN_CLICKED + +_DEVICE = const(0) +_HOST = const(1) + + +class EntrySelector(Widget): + + def __init__(self, content): + self.content = content + self.device = Button(ui.grid(8, n_y=4, n_x=4, cells_x=4), 'Device', + normal_style=ui.BTN_KEY, + active_style=ui.BTN_KEY_ACTIVE) + self.host = Button(ui.grid(12, n_y=4, n_x=4, cells_x=4), 'Host', + normal_style=ui.BTN_KEY, + active_style=ui.BTN_KEY_ACTIVE) + + def render(self): + self.device.render() + self.host.render() + + def touch(self, event, pos): + if self.device.touch(event, pos) == BTN_CLICKED: + return _DEVICE + if self.host.touch(event, pos) == BTN_CLICKED: + return _HOST + + async def __iter__(self): + return await loop.wait(super().__iter__(), self.content) diff --git a/src/trezor/ui/keyboard.py b/src/trezor/ui/keyboard.py index 0fb2d0b759..fe79ce27c5 100644 --- a/src/trezor/ui/keyboard.py +++ b/src/trezor/ui/keyboard.py @@ -2,7 +2,14 @@ from trezor import ui, res, loop, io from trezor.crypto import bip39 from trezor.ui import display from trezor.ui.button import Button, BTN_CLICKED, ICON +from .swipe import Swipe, SWIPE_LEFT, SWIPE_RIGHT, SWIPE_HORIZONTAL +KEYBOARD = { + '0': ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0'], + '1': ['_', 'abc', 'def', 'ghi', 'jkl', 'mno', 'pqrs', 'tuv', 'wxyz', '*#'], + '2': ['_', 'ABC', 'DEF', 'GHI', 'JKL', 'MNO', 'PQRS', 'TUV', 'WXYZ', '*#'], + '3': ['_', '.', '/', '!', '+', '-', '?', ',', ';', '$'] +} def key_buttons(): keys = ['abc', 'def', 'ghi', 'jkl', 'mno', 'pqr', 'stu', 'vwx', 'yz'] @@ -11,6 +18,24 @@ def key_buttons(): for i, k in enumerate(keys) ] +def render_scrollbar(page): + bbox = const(240) + size = const(8) + padding = 12 + page_count = len(KEYBOARD) + + if page_count * padding > bbox: + padding = bbox // page_count + + x = (bbox // 2) - (page_count // 2) * padding + y = 44 + + for i in range(0, page_count): + if i != page: + ui.display.bar_radius(x + i * padding, y, size, + size, ui.DARK_GREY, ui.BG, size // 2) + ui.display.bar_radius(x + page * padding, y, size, + size, ui.FG, ui.BG, size // 2) def compute_mask(text: str) -> int: mask = 0 @@ -21,6 +46,14 @@ def compute_mask(text: str) -> int: mask |= 1 << shift return mask +def digit_area(i): + if i == 9: # 0-position + i = 10 # display it in the middle + return ui.grid(i + 3) # skip the first line + +def generate_keyboard(index): + digits = list(range(0, 10)) # 0-9 + return digits class Input(Button): def __init__(self, area: tuple, content: str='', word: str=''): @@ -76,6 +109,41 @@ class Input(Button): iy = ty - ICON display.icon(ix, iy, res.load(i), fg_color, bg_color) +class PassphraseKeyboard(ui.Widget): + def __init__(self, label): + self.label = label + self.passphrase = '' + self.index = 1 + self.keyboard_type = 1 + self.keys = KEYBOARD[str(self.keyboard_type)] + + self.key_buttons = [Button(digit_area(i), d) + for i, d in enumerate(self.keys)] + self.onchange = None + + def render(self): + # clear canvas under input line + display.bar(0, 0, 240, 45, ui.BG) + + # input line with a header + header = self.passphrase if self.passphrase else self.label + display.text_center(120, 32, header, ui.BOLD, ui.GREY, ui.BG) + render_scrollbar(self.keyboard_type) + # pin matrix buttons + for btn in self.key_buttons: + btn.render() + + def touch(self, event, pos): + for btn in self.key_buttons: + if btn.touch(event, pos) == BTN_CLICKED: + self.change(self.passphrase + btn.content) + break + + def change(self, passphrase): + self.passphrase = passphrase + if self.onchange: + self.onchange() + class MnemonicKeyboard(ui.Widget): def __init__(self, prompt: str=''):