Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pagination for sign/verify message #1384

Merged
merged 10 commits into from
Jan 11, 2021
6 changes: 5 additions & 1 deletion core/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## 2.3.6 [unreleased]

### Added
- Support for Output Descriptors export [#1363]
- Support for Output Descriptors export. [#1363]
- Paginated display for signing/verifying long messages. [#1271]

### Changed
- Bump nanopb dependency to 0.4.4. [#1402]
- Automatic breaking text on whitespace. [#1384]

### Deprecated

Expand Down Expand Up @@ -348,9 +350,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
[#1193]: https://github.com/trezor/trezor-firmware/issues/1193
[#1206]: https://github.com/trezor/trezor-firmware/issues/1206
[#1246]: https://github.com/trezor/trezor-firmware/issues/1246
[#1271]: https://github.com/trezor/trezor-firmware/issues/1271
[#1292]: https://github.com/trezor/trezor-firmware/issues/1292
[#1322]: https://github.com/trezor/trezor-firmware/issues/1322
[#1335]: https://github.com/trezor/trezor-firmware/issues/1335
[#1351]: https://github.com/trezor/trezor-firmware/issues/1351
[#1363]: https://github.com/trezor/trezor-firmware/pull/1363
[#1384]: https://github.com/trezor/trezor-firmware/issues/1384
[#1402]: https://github.com/trezor/trezor-firmware/pull/1402
37 changes: 36 additions & 1 deletion core/embed/extmod/modtrezorui/display.c
Original file line number Diff line number Diff line change
Expand Up @@ -594,7 +594,7 @@ static uint8_t convert_char(const uint8_t c) {
return c;
}

// UTF-8 handling: https://en.wikipedia.org/wiki/UTF-8#Description
// UTF-8 handling: https://en.wikipedia.org/wiki/UTF-8#Encoding

// bytes 11xxxxxx are first bytes of UTF-8 characters
if (c >= 0xC0) {
Expand Down Expand Up @@ -867,3 +867,38 @@ void display_fade(int start, int end, int delay) {
}
display_backlight(end);
}

#define UTF8_IS_CONT(ch) (((ch)&0xC0) == 0x80)

void display_utf8_substr(const char *buf_start, size_t buf_len, int char_off,
int char_len, const char **out_start, int *out_len) {
size_t i = 0;

for (; i < buf_len; i++) {
if (char_off == 0) {
break;
}
if (!UTF8_IS_CONT(buf_start[i])) {
char_off--;
}
}
size_t i_start = i;

for (; i < buf_len; i++) {
if (char_len == 0) {
break;
}
if (!UTF8_IS_CONT(buf_start[i])) {
char_len--;
}
}

for (; i < buf_len; i++) {
if (!UTF8_IS_CONT(buf_start[i])) {
break;
}
}

*out_start = buf_start + i_start;
*out_len = i - i_start;
}
4 changes: 4 additions & 0 deletions core/embed/extmod/modtrezorui/display.h
Original file line number Diff line number Diff line change
Expand Up @@ -113,4 +113,8 @@ int display_orientation(int degrees);
int display_backlight(int val);
void display_fade(int start, int end, int delay);

// helper for locating a substring in buffer with utf-8 string
void display_utf8_substr(const char *buf_start, size_t buf_len, int char_off,
int char_len, const char **out_start, int *out_len);

#endif
65 changes: 55 additions & 10 deletions core/embed/extmod/modtrezorui/modtrezorui-display.h
Original file line number Diff line number Diff line change
Expand Up @@ -317,11 +317,16 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_trezorui_Display_print_obj,
/// font: int,
/// fgcolor: int,
/// bgcolor: int,
/// text_offset: int = None,
/// text_len: int = None,
/// ) -> None:
/// """
/// Renders left-aligned text at position (x,y) where x is left position and
/// y is baseline. Font font is used for rendering, fgcolor is used as
/// foreground color, bgcolor as background.
///
/// Arguments text_offset and text_len can be used to render a substring of
/// the text.
/// """
STATIC mp_obj_t mod_trezorui_Display_text(size_t n_args, const mp_obj_t *args) {
mp_int_t x = mp_obj_get_int(args[1]);
Expand All @@ -331,10 +336,25 @@ STATIC mp_obj_t mod_trezorui_Display_text(size_t n_args, const mp_obj_t *args) {
mp_int_t font = mp_obj_get_int(args[4]);
mp_int_t fgcolor = mp_obj_get_int(args[5]);
mp_int_t bgcolor = mp_obj_get_int(args[6]);
display_text(x, y, text.buf, text.len, font, fgcolor, bgcolor);

const char *buf_start = text.buf;
int buf_len = text.len;
if (n_args > 7) {
mp_int_t off = mp_obj_get_int(args[7]);
mp_int_t len = n_args > 8 ? mp_obj_get_int(args[8]) : text.len - off;
if (off < 0 || off > text.len) {
mp_raise_ValueError("Invalid text_offset");
}
if (len < 0 || len + off > text.len) {
mp_raise_ValueError("Invalid text_len");
}
display_utf8_substr(text.buf, text.len, off, len, &buf_start, &buf_len);
}

display_text(x, y, buf_start, buf_len, font, fgcolor, bgcolor);
return mp_const_none;
}
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorui_Display_text_obj, 7, 7,
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorui_Display_text_obj, 7, 9,
mod_trezorui_Display_text);

/// def text_center(
Expand Down Expand Up @@ -397,20 +417,45 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorui_Display_text_right_obj,
7, 7,
mod_trezorui_Display_text_right);

/// def text_width(self, text: str, font: int) -> int:
/// def text_width(
/// self,
/// text: str,
/// font: int,
/// text_offset: int = None,
/// text_len: int = None,
/// ) -> int:
/// """
/// Returns a width of text in pixels. Font font is used for rendering.
///
/// Arguments text_offset and text_len can be used to render a substring of
/// the text.
/// """
STATIC mp_obj_t mod_trezorui_Display_text_width(mp_obj_t self, mp_obj_t text,
mp_obj_t font) {
STATIC mp_obj_t mod_trezorui_Display_text_width(size_t n_args,
const mp_obj_t *args) {
mp_buffer_info_t txt = {0};
mp_get_buffer_raise(text, &txt, MP_BUFFER_READ);
mp_int_t f = mp_obj_get_int(font);
int w = display_text_width(txt.buf, txt.len, f);
mp_get_buffer_raise(args[1], &txt, MP_BUFFER_READ);
mp_int_t f = mp_obj_get_int(args[2]);

const char *buf_start = txt.buf;
int buf_len = txt.len;
if (n_args > 3) {
mp_int_t off = mp_obj_get_int(args[3]);
mp_int_t len = n_args > 4 ? mp_obj_get_int(args[4]) : txt.len - off;
if (off < 0 || off > txt.len) {
mp_raise_ValueError("Invalid text_offset");
}
if (len < 0 || len + off > txt.len) {
mp_raise_ValueError("Invalid text_len");
}
display_utf8_substr(txt.buf, txt.len, off, len, &buf_start, &buf_len);
}

int w = display_text_width(buf_start, buf_len, f);
return mp_obj_new_int(w);
}
STATIC MP_DEFINE_CONST_FUN_OBJ_3(mod_trezorui_Display_text_width_obj,
mod_trezorui_Display_text_width);
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorui_Display_text_width_obj,
3, 5,
mod_trezorui_Display_text_width);

/// def text_split(self, text: str, font: int, requested_width: int) -> int:
/// """
Expand Down
14 changes: 13 additions & 1 deletion core/mocks/generated/trezorui.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -114,11 +114,15 @@ class Display:
font: int,
fgcolor: int,
bgcolor: int,
text_offset: int = None,
text_len: int = None,
) -> None:
"""
Renders left-aligned text at position (x,y) where x is left position and
y is baseline. Font font is used for rendering, fgcolor is used as
foreground color, bgcolor as background.
Arguments text_offset and text_len can be used to render a substring of
the text.
"""

def text_center(
Expand Down Expand Up @@ -151,9 +155,17 @@ class Display:
foreground color, bgcolor as background.
"""

def text_width(self, text: str, font: int) -> int:
def text_width(
self,
text: str,
font: int,
text_offset: int = None,
text_len: int = None,
) -> int:
"""
Returns a width of text in pixels. Font font is used for rendering.
Arguments text_offset and text_len can be used to render a substring of
the text.
"""

def text_split(self, text: str, font: int, requested_width: int) -> int:
Expand Down
2 changes: 1 addition & 1 deletion core/src/apps/cardano/layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ async def show_warning_tx_staking_key_hash(

page2 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN)
page2.normal("Staking key hash:")
page2.mono(*chunks(hexlify(staking_key_hash), 17))
page2.mono(*chunks(hexlify(staking_key_hash).decode(), 17))

page3 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN)
page3.normal("Change amount:")
Expand Down
49 changes: 47 additions & 2 deletions core/src/apps/common/layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@
from trezor.ui.container import Container
from trezor.ui.qr import Qr
from trezor.ui.scroll import Paginated
from trezor.ui.text import Text
from trezor.ui.text import TEXT_MAX_LINES, Span, Text
from trezor.utils import chunks

from apps.common import HARDENED
from apps.common.confirm import confirm, require_confirm

if False:
from typing import Iterable, Iterator, List
from typing import Iterable, Iterator, List, Union
from trezor import wire


Expand Down Expand Up @@ -136,3 +136,48 @@ async def show_success(
await require_confirm(
ctx, text, ButtonRequestType.Success, confirm=button, cancel=None
)


def paginate_text(
text: str,
mmilata marked this conversation as resolved.
Show resolved Hide resolved
header: str,
font: int = ui.NORMAL,
header_icon: str = ui.ICON_DEFAULT,
icon_color: int = ui.ORANGE_ICON,
break_words: bool = False,
) -> Union[Text, Paginated]:
span = Span(text, 0, font, break_words=break_words)
if span.count_lines() <= TEXT_MAX_LINES:
result = Text(
header,
header_icon=header_icon,
icon_color=icon_color,
new_lines=False,
)
result.content = [font, text]
return result

else:
pages: List[ui.Component] = []
span.reset(text, 0, font, break_words=break_words, line_width=204)
while span.has_more_content():
# advance to first line of the page
span.next_line()
page = Text(
header,
header_icon=header_icon,
icon_color=icon_color,
new_lines=False,
content_offset=0,
char_offset=span.start,
line_width=204,
render_page_overflow=False,
)
page.content = [font, text]
pages.append(page)

# roll over the remaining lines on the page
for _ in range(TEXT_MAX_LINES - 1):
span.next_line()

return Paginated(pages)
2 changes: 1 addition & 1 deletion core/src/apps/common/passphrase.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ async def _request_on_host(ctx: wire.Context) -> str:
text.normal("the passphrase!")
await require_confirm(ctx, text, ButtonRequestType.Other)

text = Text("Hidden wallet", ICON_CONFIG)
text = Text("Hidden wallet", ICON_CONFIG, break_words=True)
text.normal("Use this passphrase?")
text.br()
text.mono(ack.passphrase)
Expand Down
26 changes: 10 additions & 16 deletions core/src/apps/common/signverify.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
from ubinascii import hexlify

from trezor import utils, wire
from trezor import ui, utils, wire
from trezor.crypto.hashlib import blake256, sha256
from trezor.ui.text import Text

from apps.common.confirm import require_confirm
from apps.common.layout import split_address
from apps.common.layout import paginate_text, split_address
from apps.common.writers import write_bitcoin_varint

if False:
from typing import List
from apps.common.coininfo import CoinInfo


Expand All @@ -30,24 +29,18 @@ def message_digest(coin: CoinInfo, message: bytes) -> bytes:
return ret


def split_message(message: bytes) -> List[str]:
def decode_message(message: bytes) -> str:
try:
m = bytes(message).decode()
words = m.split(" ")
return bytes(message).decode()
except UnicodeError:
m = "hex(%s)" % hexlify(message).decode()
words = [m]
return words
return "hex(%s)" % hexlify(message).decode()


async def require_confirm_sign_message(
ctx: wire.Context, coin: str, message: bytes
) -> None:
header = "Sign {} message".format(coin)
message_lines = split_message(message)
text = Text(header, new_lines=False)
text.normal(*message_lines)
await require_confirm(ctx, text)
await require_confirm(ctx, paginate_text(decode_message(message), header))


async def require_confirm_verify_message(
Expand All @@ -60,6 +53,7 @@ async def require_confirm_verify_message(
text.mono(*split_address(address))
await require_confirm(ctx, text)

text = Text(header, new_lines=False)
text.mono(*split_message(message))
await require_confirm(ctx, text)
await require_confirm(
ctx,
paginate_text(decode_message(message), header, font=ui.MONO),
)
2 changes: 1 addition & 1 deletion core/src/apps/monero/layout/confirms.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ async def _require_confirm_output(
async def _require_confirm_payment_id(ctx, payment_id: bytes):
if not await common.naive_pagination(
ctx,
[ui.MONO] + list(chunks(hexlify(payment_id), 16)),
[ui.MONO] + list(chunks(hexlify(payment_id).decode(), 16)),
"Payment ID",
ui.ICON_SEND,
ui.GREEN,
Expand Down
4 changes: 2 additions & 2 deletions core/src/trezor/ui/info.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,13 @@ def on_render(self) -> None:
ui.display.bar_radius(x, y, w, h, bg_color, ui.BG, ui.RADIUS)

# render the info text
render_text( # type: ignore
render_text(
self.text,
new_lines=False,
max_lines=6,
offset_y=y + TEXT_LINE_HEIGHT,
offset_x=x + TEXT_MARGIN_LEFT - ui.VIEWX,
offset_x_max=x + w - ui.VIEWX,
line_width=w,
fg=fg_color,
bg=bg_color,
)
Expand Down