diff --git a/core/embed/rust/build.rs b/core/embed/rust/build.rs index 431ddfb9a3..6fec48da0a 100644 --- a/core/embed/rust/build.rs +++ b/core/embed/rust/build.rs @@ -168,6 +168,9 @@ fn generate_micropython_bindings() { .allowlist_var("MP_BUFFER_WRITE") .allowlist_var("MP_BUFFER_RW") .allowlist_var("mp_type_str") + .allowlist_var("mp_type_bytes") + .allowlist_var("mp_type_bytearray") + .allowlist_var("mp_type_memoryview") // dict .allowlist_type("mp_obj_dict_t") .allowlist_function("mp_obj_new_dict") diff --git a/core/embed/rust/src/micropython/buffer.rs b/core/embed/rust/src/micropython/buffer.rs index aba1288f79..45b202393b 100644 --- a/core/embed/rust/src/micropython/buffer.rs +++ b/core/embed/rust/src/micropython/buffer.rs @@ -66,7 +66,7 @@ impl TryFrom for StrBuffer { type Error = Error; fn try_from(obj: Obj) -> Result { - if obj.is_qstr() || unsafe { ffi::mp_type_str.is_type_of(obj) } { + if obj.is_str() { let bufinfo = get_buffer_info(obj, ffi::MP_BUFFER_READ)?; let new = Self { ptr: bufinfo.buf as _, @@ -189,3 +189,25 @@ impl crate::trace::Trace for StrBuffer { self.as_ref().trace(t) } } + +/// Version of `get_buffer` for strings that ties the string lifetime with +/// another object that is expected to be placed on stack or in micropython heap +/// and be the beginning of a chain of references that lead to the `obj`. +/// +/// SAFETY: +/// The caller must ensure that: +/// (a) the `_owner` is an object visible to the micropython GC, +/// (b) the `_owner` contains a reference that leads to `obj`, possibly through +/// other objects, +/// (c) that path is not broken as long as the returned reference lives. +pub unsafe fn get_str_owner(_owner: &T, obj: Obj) -> Result<&str, Error> { + if !obj.is_str() { + return Err(Error::TypeError); + } + // SAFETY: + // (a) only immutable references are taken. + // (b) micropython guarantees immutability and the buffer is not freed/moved as + // long as _owner satisfies precondition. + let buffer = unsafe { get_buffer(obj)? }; + str::from_utf8(buffer).map_err(|_| Error::TypeError) +} diff --git a/core/embed/rust/src/micropython/obj.rs b/core/embed/rust/src/micropython/obj.rs index 1b0a09d720..0cbd028947 100644 --- a/core/embed/rust/src/micropython/obj.rs +++ b/core/embed/rust/src/micropython/obj.rs @@ -410,3 +410,18 @@ impl Obj { } } } + +impl Obj { + pub fn is_bytes(self) -> bool { + unsafe { + ffi::mp_type_bytes.is_type_of(self) + || ffi::mp_type_bytearray.is_type_of(self) + || ffi::mp_type_memoryview.is_type_of(self) + } + } + + pub fn is_str(self) -> bool { + let is_type_str = unsafe { ffi::mp_type_str.is_type_of(self) }; + is_type_str || self.is_qstr() + } +} diff --git a/core/embed/rust/src/ui/component/text/paragraphs.rs b/core/embed/rust/src/ui/component/text/paragraphs.rs index 3f50d291d7..e5681bd02b 100644 --- a/core/embed/rust/src/ui/component/text/paragraphs.rs +++ b/core/embed/rust/src/ui/component/text/paragraphs.rs @@ -24,8 +24,23 @@ pub const PARAGRAPH_BOTTOM_SPACE: i16 = 5; pub type ParagraphVecLong = Vec, 32>; pub type ParagraphVecShort = Vec, 8>; +/// Maximum number of characters that can be displayed on screen at once. Used +/// for on-the-fly conversion of binary data to hexadecimal representation. +/// NOTE: can be fine-tuned for particular model screen to decrease memory +/// consumption and conversion time. +pub const SCRATCH_BUFFER_LEN: usize = 256; + pub trait ParagraphSource { - fn at(&self, i: usize) -> Paragraph<&str>; + /// Return text and associated style for given paragraph index and character + /// offset within the paragraph. + /// + /// Implementations can use the provided buffer to perform some kind of + /// conversion (currently used for displaying binary data in + /// hexadecimal) and return a reference to this buffer. Caller needs to + /// make sure the buffer is large enough to fit screenfull of characters. + fn at<'a>(&'a self, index: usize, offset: usize, buffer: &'a mut [u8]) -> Paragraph<&'a str>; + + /// Number of paragraphs. fn size(&self) -> usize; fn into_paragraphs(self) -> Paragraphs @@ -40,8 +55,8 @@ impl ParagraphSource for Vec, N> where T: AsRef, { - fn at(&self, i: usize) -> Paragraph<&str> { - self[i].to_ref() + fn at<'a>(&'a self, index: usize, offset: usize, _buffer: &'a mut [u8]) -> Paragraph<&'a str> { + self[index].offset_as_ref(offset) } fn size(&self) -> usize { @@ -53,8 +68,8 @@ impl ParagraphSource for [Paragraph; N] where T: AsRef, { - fn at(&self, i: usize) -> Paragraph<&str> { - self[i].to_ref() + fn at<'a>(&'a self, index: usize, offset: usize, _buffer: &'a mut [u8]) -> Paragraph<&'a str> { + self[index].offset_as_ref(offset) } fn size(&self) -> usize { @@ -66,9 +81,9 @@ impl ParagraphSource for Paragraph where T: AsRef, { - fn at(&self, i: usize) -> Paragraph<&str> { - assert_eq!(i, 0); - self.to_ref() + fn at<'a>(&'a self, index: usize, offset: usize, _buffer: &'a mut [u8]) -> Paragraph<&'a str> { + assert_eq!(index, 0); + self.offset_as_ref(offset) } fn size(&self) -> usize { @@ -157,26 +172,31 @@ where } } - /// Returns iterator over visible layouts (bounding box, style) together + /// Iterate over visible layouts (bounding box, style) together /// with corresponding string content. Should not get monomorphized. - fn visible_content<'a>( - content: &'a dyn ParagraphSource, + fn foreach_visible<'a, 'b>( + source: &'a dyn ParagraphSource, visible: &'a [TextLayout], offset: PageOffset, - ) -> impl Iterator { - visible.iter().zip( - (offset.par..content.size()) - .map(|i| content.at(i)) - .filter(|p| !p.content.is_empty()) - .enumerate() - .map(move |(i, p): (usize, Paragraph<&str>)| { - if i == 0 { - &p.content[offset.chr..] - } else { - p.content - } - }), - ) + func: &'b mut dyn FnMut(&TextLayout, &str), + ) { + let mut buffer = [0; SCRATCH_BUFFER_LEN]; + let mut vis_iter = visible.iter(); + let mut chr = offset.chr; + + for par in offset.par..source.size() { + let s = source.at(par, chr, &mut buffer).content; + if s.is_empty() { + chr = 0; + continue; + } + if let Some(layout) = vis_iter.next() { + func(layout, s); + } else { + break; + } + chr = 0; + } } } @@ -197,9 +217,14 @@ where } fn paint(&mut self) { - for (layout, content) in Self::visible_content(&self.source, &self.visible, self.offset) { - layout.render_text(content); - } + Self::foreach_visible( + &self.source, + &self.visible, + self.offset, + &mut |layout, content| { + layout.render_text(content); + }, + ) } fn bounds(&self, sink: &mut dyn FnMut(Rect)) { @@ -239,11 +264,15 @@ pub mod trace { impl crate::trace::Trace for Paragraphs { fn trace(&self, t: &mut dyn crate::trace::Tracer) { t.open("Paragraphs"); - for (layout, content) in Self::visible_content(&self.source, &self.visible, self.offset) - { - layout.layout_text(content, &mut layout.initial_cursor(), &mut TraceSink(t)); - t.string("\n"); - } + Self::foreach_visible( + &self.source, + &self.visible, + self.offset, + &mut |layout, content| { + layout.layout_text(content, &mut layout.initial_cursor(), &mut TraceSink(t)); + t.string("\n"); + }, + ); t.close(); } } @@ -290,16 +319,18 @@ impl Paragraph { self } + pub fn content(&self) -> &T { + &self.content + } + pub fn update(&mut self, content: T) { self.content = content } - fn to_ref(&self) -> Paragraph<&str> - where - T: AsRef, - { + /// Copy style and replace content. + pub fn with_content(&self, content: U) -> Paragraph { Paragraph { - content: self.content.as_ref(), + content, style: self.style, align: self.align, break_after: self.break_after, @@ -307,6 +338,13 @@ impl Paragraph { } } + pub fn offset_as_ref(&self, offset: usize) -> Paragraph<&str> + where + T: AsRef, + { + self.with_content(&self.content.as_ref()[offset..]) + } + fn layout(&self, area: Rect) -> TextLayout { TextLayout { padding_top: PARAGRAPH_TOP_SPACE, @@ -344,7 +382,8 @@ impl PageOffset { source: &dyn ParagraphSource, full_height: i16, ) -> (PageOffset, Option, Option) { - let paragraph = source.at(self.par); + let mut buffer = [0; SCRATCH_BUFFER_LEN]; + let paragraph = source.at(self.par, self.chr, &mut buffer); // Skip empty paragraphs. if paragraph.content.is_empty() { @@ -355,8 +394,9 @@ impl PageOffset { // Handle the `no_break` flag used to keep key-value pair on the same page. if paragraph.no_break && self.chr == 0 { + let mut next_buffer = [0; SCRATCH_BUFFER_LEN]; if let Some(next_paragraph) = - (self.par + 1 < source.size()).then(|| source.at(self.par + 1)) + (self.par + 1 < source.size()).then(|| source.at(self.par + 1, 0, &mut next_buffer)) { if Self::should_place_pair_on_next_page( ¶graph, @@ -371,7 +411,7 @@ impl PageOffset { // Find out the dimensions of the paragraph at given char offset. let mut layout = paragraph.layout(area); - let fit = layout.fit_text(¶graph.content[self.chr..]); + let fit = layout.fit_text(paragraph.content); let (used, remaining_area) = area.split_top(fit.height()); layout.bounds = used; diff --git a/core/embed/rust/src/ui/layout/util.rs b/core/embed/rust/src/ui/layout/util.rs index 94b623b8c2..a09a3aa758 100644 --- a/core/embed/rust/src/ui/layout/util.rs +++ b/core/embed/rust/src/ui/layout/util.rs @@ -1,10 +1,18 @@ use crate::{ error::Error, micropython::{ + buffer::{get_buffer, get_str_owner, StrBuffer}, + gc::Gc, iter::{Iter, IterBuf}, + list::List, obj::Obj, }, + ui::component::text::{ + paragraphs::{Paragraph, ParagraphSource}, + TextStyle, + }, }; +use core::str; use cstr_core::cstr; use heapless::Vec; @@ -32,3 +40,177 @@ where // Returns error if array.len() != N vec.into_array().map_err(|_| err) } + +fn hexlify<'a>(data: &[u8], buffer: &'a mut [u8]) -> &'a str { + const HEX_LOWER: [u8; 16] = *b"0123456789abcdef"; + let mut i: usize = 0; + for b in data.iter().take(buffer.len() / 2) { + let hi: usize = ((b & 0xf0) >> 4).into(); + let lo: usize = (b & 0x0f).into(); + buffer[i] = HEX_LOWER[hi]; + buffer[i + 1] = HEX_LOWER[lo]; + i += 2; + } + // SAFETY: only <0x7f bytes are used to construct the string + unsafe { str::from_utf8_unchecked(&buffer[0..i]) } +} + +pub enum StrOrBytes { + Str(StrBuffer), + Bytes(Obj), +} + +impl StrOrBytes { + pub fn as_str_offset<'a>(&'a self, offset: usize, buffer: &'a mut [u8]) -> &'a str { + match self { + StrOrBytes::Str(x) => &x.as_ref()[offset..], + StrOrBytes::Bytes(x) => Self::hexlify(*x, offset, buffer), + } + } + + fn hexlify(obj: Obj, offset: usize, buffer: &mut [u8]) -> &str { + if !obj.is_bytes() { + return "ERROR"; + } + + // Convert offset to byte representation, handle case where it points in the + // middle of a byte. + let bin_off = offset / 2; + let hex_off = offset % 2; + + // SAFETY: + // (a) only immutable references are taken + // (b) reference is discarded before returning to micropython + let bin_slice = if let Ok(buf) = unsafe { get_buffer(obj) } { + &buf[bin_off..] + } else { + return "ERROR"; + }; + let hexadecimal = hexlify(bin_slice, buffer); + + &hexadecimal[hex_off..] + } +} + +impl TryFrom for StrOrBytes { + type Error = Error; + + fn try_from(obj: Obj) -> Result { + if obj.is_str() { + Ok(StrOrBytes::Str(obj.try_into()?)) + } else if obj.is_bytes() { + Ok(StrOrBytes::Bytes(obj)) + } else { + Err(Error::TypeError) + } + } +} + +pub struct ConfirmBlob { + pub description: StrBuffer, + pub extra: StrBuffer, + pub data: StrOrBytes, + pub description_font: &'static TextStyle, + pub extra_font: &'static TextStyle, + pub data_font: &'static TextStyle, +} + +impl ParagraphSource for ConfirmBlob { + fn at<'a>(&'a self, index: usize, offset: usize, buffer: &'a mut [u8]) -> Paragraph<&'a str> { + match index { + 0 => Paragraph::new(self.description_font, &self.description.as_ref()[offset..]), + 1 => Paragraph::new(self.extra_font, &self.extra.as_ref()[offset..]), + 2 => Paragraph::new(self.data_font, self.data.as_str_offset(offset, buffer)), + _ => unreachable!(), + } + } + + fn size(&self) -> usize { + 3 + } +} + +pub struct PropsList { + items: Gc, + key_font: &'static TextStyle, + value_font: &'static TextStyle, + value_mono_font: &'static TextStyle, +} + +impl PropsList { + pub fn new( + obj: Obj, + key_font: &'static TextStyle, + value_font: &'static TextStyle, + value_mono_font: &'static TextStyle, + ) -> Result { + Ok(Self { + items: obj.try_into()?, + key_font, + value_font, + value_mono_font, + }) + } +} + +impl ParagraphSource for PropsList { + fn at<'a>(&'a self, index: usize, offset: usize, buffer: &'a mut [u8]) -> Paragraph<&'a str> { + let block = move |buffer| { + let entry = self.items.get(index / 2)?; + let [key, value, value_is_mono]: [Obj; 3] = iter_into_objs(entry)?; + let value_is_mono: bool = bool::try_from(value_is_mono)?; + let obj: Obj; + let style: &TextStyle; + + if index % 2 == 0 { + if !key.is_str() && key != Obj::const_none() { + return Err(Error::TypeError); + } + style = self.key_font; + obj = key; + } else { + if value_is_mono { + style = self.value_mono_font; + } else { + if value.is_bytes() { + return Err(Error::TypeError); + } + style = self.value_font; + } + obj = value; + }; + + if obj == Obj::const_none() { + return Ok(Paragraph::new(style, "")); + } + + let para = if obj.is_str() { + // SAFETY: + // As long as self is visible to GC, the string will be also. The paragraph + // rendering does not keep the returned references for long so we can reasonably + // expect for the chain of references from self to the buffer not to be broken. + let s = unsafe { get_str_owner(self, obj)? }; + Paragraph::new(style, &s[offset..]) + } else if obj.is_bytes() { + let s = StrOrBytes::hexlify(obj, offset, buffer); + Paragraph::new(style, s) + } else { + return Err(Error::TypeError); + }; + + if obj == key && value != Obj::const_none() { + Ok(para.no_break()) + } else { + Ok(para) + } + }; + match block(buffer) { + Ok(p) => p, + Err(_) => Paragraph::new(self.value_font, "ERROR"), + } + } + + fn size(&self) -> usize { + 2 * self.items.len() + } +} diff --git a/core/embed/rust/src/ui/model_tt/layout.rs b/core/embed/rust/src/ui/model_tt/layout.rs index 191e8e08be..956f9ad802 100644 --- a/core/embed/rust/src/ui/model_tt/layout.rs +++ b/core/embed/rust/src/ui/model_tt/layout.rs @@ -1,7 +1,5 @@ use core::{cmp::Ordering, convert::TryInto, ops::Deref}; -use heapless::Vec; - use crate::{ error::Error, micropython::{ @@ -31,7 +29,7 @@ use crate::{ layout::{ obj::{ComponentMsgObj, LayoutObj}, result::{CANCELLED, CONFIRMED, INFO}, - util::{iter_into_array, iter_into_objs}, + util::{iter_into_array, ConfirmBlob, PropsList}, }, }, }; @@ -331,26 +329,24 @@ extern "C" fn new_confirm_action(n_args: usize, args: *const Obj, kwargs: *mut M unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } } -fn _confirm_blob( +fn confirm_blob( title: StrBuffer, - data: Option, + data: Obj, description: Option, extra: Option, verb: Option, verb_cancel: Option, hold: bool, ) -> Result { - let mut par_source: Vec, 3> = Vec::new(); - if let Some(description) = description { - unwrap!(par_source.push(Paragraph::new(&theme::TEXT_NORMAL, description))); + let paragraphs = ConfirmBlob { + description: description.unwrap_or_else(StrBuffer::empty), + extra: extra.unwrap_or_else(StrBuffer::empty), + data: data.try_into()?, + description_font: &theme::TEXT_NORMAL, + extra_font: &theme::TEXT_BOLD, + data_font: &theme::TEXT_MONO, } - if let Some(extra) = extra { - unwrap!(par_source.push(Paragraph::new(&theme::TEXT_BOLD, extra))); - } - if let Some(data) = data { - unwrap!(par_source.push(Paragraph::new(&theme::TEXT_MONO, data))); - } - let paragraphs = Paragraphs::new(par_source); + .into_paragraphs(); let obj = if hold { LayoutObj::new(Frame::new(title, SwipeHoldPage::new(paragraphs, theme::BG)))? @@ -369,10 +365,10 @@ fn _confirm_blob( extern "C" fn new_confirm_blob(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { let block = move |_args: &[Obj], kwargs: &Map| { let title: StrBuffer = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?; - let data: StrBuffer = kwargs.get(Qstr::MP_QSTR_data)?.try_into()?; - let description: StrBuffer = - kwargs.get_or(Qstr::MP_QSTR_description, StrBuffer::empty())?; - let extra: StrBuffer = kwargs.get_or(Qstr::MP_QSTR_extra, StrBuffer::empty())?; + let data: Obj = kwargs.get(Qstr::MP_QSTR_data)?; + let description: Option = + kwargs.get(Qstr::MP_QSTR_description)?.try_into_option()?; + let extra: Option = kwargs.get(Qstr::MP_QSTR_extra)?.try_into_option()?; let verb_cancel: Option = kwargs .get(Qstr::MP_QSTR_verb_cancel) .unwrap_or_else(|_| Obj::const_none()) @@ -382,11 +378,11 @@ extern "C" fn new_confirm_blob(n_args: usize, args: *const Obj, kwargs: *mut Map let verb: StrBuffer = "CONFIRM".into(); - _confirm_blob( + confirm_blob( title, - Some(data), - Some(description), - Some(extra), + data, + description, + extra, Some(verb), verb_cancel, hold, @@ -401,31 +397,12 @@ extern "C" fn new_confirm_properties(n_args: usize, args: *const Obj, kwargs: *m let hold: bool = kwargs.get_or(Qstr::MP_QSTR_hold, false)?; let items: Obj = kwargs.get(Qstr::MP_QSTR_items)?; - let mut paragraphs = ParagraphVecLong::new(); - - let mut iter_buf = IterBuf::new(); - let iter = Iter::try_from_obj_with_buf(items, &mut iter_buf)?; - for para in iter { - let [key, value, is_mono]: [Obj; 3] = iter_into_objs(para)?; - let key = key.try_into_option::()?; - let value = value.try_into_option::()?; - - if let Some(key) = key { - if value.is_some() { - paragraphs.add(Paragraph::new(&theme::TEXT_BOLD, key).no_break()); - } else { - paragraphs.add(Paragraph::new(&theme::TEXT_BOLD, key)); - } - } - if let Some(value) = value { - if is_mono.try_into()? { - paragraphs.add(Paragraph::new(&theme::TEXT_MONO, value)); - } else { - paragraphs.add(Paragraph::new(&theme::TEXT_NORMAL, value)); - } - } - } - + let paragraphs = PropsList::new( + items, + &theme::TEXT_BOLD, + &theme::TEXT_NORMAL, + &theme::TEXT_MONO, + )?; let obj = if hold { LayoutObj::new(Frame::new( title, @@ -497,8 +474,9 @@ extern "C" fn new_show_qr(n_args: usize, args: *const Obj, kwargs: *mut Map) -> extern "C" fn new_confirm_value(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { let block = move |_args: &[Obj], kwargs: &Map| { let title: StrBuffer = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?; - let description: StrBuffer = kwargs.get(Qstr::MP_QSTR_description)?.try_into()?; - let value: StrBuffer = kwargs.get(Qstr::MP_QSTR_value)?.try_into()?; + let description: Option = + kwargs.get(Qstr::MP_QSTR_description)?.try_into_option()?; + let value: Obj = kwargs.get(Qstr::MP_QSTR_value)?; let verb: Option = kwargs .get(Qstr::MP_QSTR_verb) @@ -506,15 +484,7 @@ extern "C" fn new_confirm_value(n_args: usize, args: *const Obj, kwargs: *mut Ma .try_into_option()?; let hold: bool = kwargs.get_or(Qstr::MP_QSTR_hold, false)?; - _confirm_blob( - title, - Some(value), - Some(description), - None, - verb, - None, - hold, - ) + confirm_blob(title, value, description, None, verb, None, hold) }; unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } } @@ -1142,9 +1112,9 @@ pub static mp_module_trezorui2: Module = obj_module! { /// def confirm_blob( /// *, /// title: str, - /// data: str, - /// description: str = "", - /// extra: str = "", + /// data: str | bytes, + /// description: str | None, + /// extra: str | None, /// verb_cancel: str | None = None, /// ask_pagination: bool = False, /// hold: bool = False, @@ -1155,12 +1125,11 @@ pub static mp_module_trezorui2: Module = obj_module! { /// def confirm_properties( /// *, /// title: str, - /// items: Iterable[Tuple[str | None, str | None, bool]], + /// items: list[tuple[str | None, str | bytes | None, bool]], /// hold: bool = False, /// ) -> object: /// """Confirm list of key-value pairs. The third component in the tuple should be True if - /// the value is to be rendered as binary with monospace font, False otherwise. - /// This only concerns the text style, you need to decode the value to UTF-8 in python.""" + /// the value is to be rendered as binary with monospace font, False otherwise.""" Qstr::MP_QSTR_confirm_properties => obj_fn_kw!(0, new_confirm_properties).as_obj(), /// def confirm_reset_device( diff --git a/core/mocks/generated/trezorui2.pyi b/core/mocks/generated/trezorui2.pyi index a107947a71..e2de5ff49e 100644 --- a/core/mocks/generated/trezorui2.pyi +++ b/core/mocks/generated/trezorui2.pyi @@ -75,9 +75,9 @@ def confirm_action( def confirm_blob( *, title: str, - data: str, - description: str = "", - extra: str = "", + data: str | bytes, + description: str | None, + extra: str | None, verb_cancel: str | None = None, ask_pagination: bool = False, hold: bool = False, @@ -89,12 +89,11 @@ def confirm_blob( def confirm_properties( *, title: str, - items: Iterable[Tuple[str | None, str | None, bool]], + items: list[tuple[str | None, str | bytes | None, bool]], hold: bool = False, ) -> object: """Confirm list of key-value pairs. The third component in the tuple should be True if - the value is to be rendered as binary with monospace font, False otherwise. - This only concerns the text style, you need to decode the value to UTF-8 in python.""" + the value is to be rendered as binary with monospace font, False otherwise.""" # rust/src/ui/model_tt/layout.rs diff --git a/core/src/trezor/ui/layouts/tt_v2/__init__.py b/core/src/trezor/ui/layouts/tt_v2/__init__.py index 5870a81058..749254d4de 100644 --- a/core/src/trezor/ui/layouts/tt_v2/__init__.py +++ b/core/src/trezor/ui/layouts/tt_v2/__init__.py @@ -1,5 +1,4 @@ from typing import TYPE_CHECKING -from ubinascii import hexlify from trezor import io, log, loop, ui from trezor.enums import ButtonRequestType @@ -317,6 +316,8 @@ def _show_xpub(xpub: str, title: str, cancel: str) -> ui.Layout: title=title, data=xpub, verb_cancel=cancel, + extra=None, + description=None, ) ) return content @@ -596,9 +597,6 @@ async def confirm_blob( br_code: ButtonRequestType = BR_TYPE_OTHER, ask_pagination: bool = False, ) -> None: - if isinstance(data, bytes): - data = hexlify(data).decode() - await raise_if_not_confirmed( interact( ctx, @@ -607,6 +605,7 @@ async def confirm_blob( title=title.upper(), description=description or "", data=data, + extra=None, ask_pagination=ask_pagination, hold=hold, ) @@ -716,11 +715,8 @@ async def confirm_properties( hold: bool = False, br_code: ButtonRequestType = ButtonRequestType.ConfirmOutput, ) -> None: - def handle_bytes(prop: PropertyType) -> tuple[str | None, str | None, bool]: - if isinstance(prop[1], bytes): - return (prop[0], hexlify(prop[1]).decode(), True) - else: - return (prop[0], prop[1], False) + # Monospace flag for values that are bytes. + items = [(prop[0], prop[1], isinstance(prop[1], bytes)) for prop in props] await raise_if_not_confirmed( interact( @@ -728,7 +724,7 @@ async def confirm_properties( _RustLayout( trezorui2.confirm_properties( title=title.upper(), - items=map(handle_bytes, props), + items=items, hold=hold, ) ),