From 08cad2f909882175ee300d82dc42ea11d6ed2a11 Mon Sep 17 00:00:00 2001 From: Martin Milata Date: Fri, 24 Mar 2023 16:54:47 +0100 Subject: [PATCH] fix(core/ui): coinjoin layouts style update [no changelog] --- core/assets/coinjoin16.png | Bin 209 -> 378 bytes core/embed/rust/librust_qstr.h | 2 +- core/embed/rust/src/ui/component/label.rs | 17 +- core/embed/rust/src/ui/component/painter.rs | 5 + core/embed/rust/src/ui/component/placed.rs | 12 +- .../rust/src/ui/display/loader/circular.rs | 160 ++++++++++++------ core/embed/rust/src/ui/display/loader/mod.rs | 3 + core/embed/rust/src/ui/model_tr/constant.rs | 4 +- .../rust/src/ui/model_tt/component/button.rs | 10 +- .../model_tt/component/coinjoin_progress.rs | 131 ++++++++++++++ .../ui/model_tt/component/homescreen/mod.rs | 1 + .../rust/src/ui/model_tt/component/mod.rs | 2 + core/embed/rust/src/ui/model_tt/constant.rs | 4 +- core/embed/rust/src/ui/model_tt/layout.rs | 109 +++++++----- core/embed/rust/src/ui/model_tt/theme.rs | 4 + core/mocks/generated/trezorui2.pyi | 23 +-- core/src/apps/base.py | 4 +- core/src/apps/bitcoin/sign_tx/bitcoin.py | 5 +- core/src/apps/bitcoin/sign_tx/progress.py | 13 +- core/src/apps/homescreen/__init__.py | 14 +- core/src/trezor/ui/layouts/tt_v2/__init__.py | 40 +++-- .../src/trezor/ui/layouts/tt_v2/homescreen.py | 10 +- tests/ui_tests/fixtures.json | 28 +-- 23 files changed, 439 insertions(+), 162 deletions(-) create mode 100644 core/embed/rust/src/ui/model_tt/component/coinjoin_progress.rs diff --git a/core/assets/coinjoin16.png b/core/assets/coinjoin16.png index ac4470474ff763f84856213bed2854de152ad5f7..8a6e40d542abaa48e7292352a9bc2d2376e2d24d 100644 GIT binary patch delta 187 zcmcb}_={`sfb7pZN5w1B(cP%IC&#YH1ag8WRNi0dVN-jzTQVd20M#j1Z z#=1tvA%+%KMg~>}#@Yrz(%|U1ADIjc3Cs6hj6LrG?CYH>+o fZUI7%0?Zae1BfkhN2)o2nixD?{an^LB{Ts5S}QdM delta 19 acmeyxbdhm_(L~p2oD80>elF{r5}E)>S_b$4 diff --git a/core/embed/rust/librust_qstr.h b/core/embed/rust/librust_qstr.h index 297a05e9d8..eb9d4c9232 100644 --- a/core/embed/rust/librust_qstr.h +++ b/core/embed/rust/librust_qstr.h @@ -50,7 +50,6 @@ static void _librust_qstrs(void) { MP_QSTR_request_slip39; MP_QSTR_select_word; MP_QSTR_select_word_count; - MP_QSTR_show_busyscreen; MP_QSTR_show_group_share_success; MP_QSTR_show_homescreen; MP_QSTR_show_lockscreen; @@ -58,6 +57,7 @@ static void _librust_qstrs(void) { MP_QSTR_show_remaining_shares; MP_QSTR_show_share_words; MP_QSTR_show_progress; + MP_QSTR_show_progress_coinjoin; MP_QSTR_show_address_details; MP_QSTR_attach_timer_fn; diff --git a/core/embed/rust/src/ui/component/label.rs b/core/embed/rust/src/ui/component/label.rs index ff36d3b678..120a99402e 100644 --- a/core/embed/rust/src/ui/component/label.rs +++ b/core/embed/rust/src/ui/component/label.rs @@ -1,7 +1,7 @@ use crate::ui::{ component::{Component, Event, EventCtx, Never}, display::Font, - geometry::{Alignment, Offset, Rect}, + geometry::{Alignment, Insets, Offset, Rect}, }; use super::{text::TextStyle, TextLayout}; @@ -9,6 +9,7 @@ use super::{text::TextStyle, TextLayout}; pub struct Label { text: T, layout: TextLayout, + vertical: Alignment, } impl Label @@ -19,6 +20,7 @@ where Self { text, layout: TextLayout::new(style).with_align(align), + vertical: Alignment::Start, } } @@ -34,6 +36,11 @@ where Self::new(text, Alignment::Center, style) } + pub fn vertically_aligned(mut self, align: Alignment) -> Self { + self.vertical = align; + self + } + pub fn text(&self) -> &T { &self.text } @@ -68,7 +75,13 @@ where .with_bounds(bounds) .fit_text(self.text.as_ref()) .height(); - self.layout = self.layout.with_bounds(bounds.with_height(height)); + let diff = bounds.height() - height; + let insets = match self.vertical { + Alignment::Start => Insets::bottom(diff), + Alignment::Center => Insets::new(diff / 2, 0, diff / 2 + diff % 2, 0), + Alignment::End => Insets::top(diff), + }; + self.layout.bounds = bounds.inset(insets); self.layout.bounds } diff --git a/core/embed/rust/src/ui/component/painter.rs b/core/embed/rust/src/ui/component/painter.rs index 5946a110df..6887ce5871 100644 --- a/core/embed/rust/src/ui/component/painter.rs +++ b/core/embed/rust/src/ui/component/painter.rs @@ -67,3 +67,8 @@ pub fn jpeg_painter<'a>( let f = move |area: Rect| display::tjpgd::jpeg(image(), area.center() - off, scale); Painter::new(f) } + +pub fn rect_painter(fg: display::Color, bg: display::Color) -> Painter { + let f = move |area: Rect| display::rect_fill_rounded(area, fg, bg, 2); + Painter::new(f) +} diff --git a/core/embed/rust/src/ui/component/placed.rs b/core/embed/rust/src/ui/component/placed.rs index 8f709ac332..ad586a7d7b 100644 --- a/core/embed/rust/src/ui/component/placed.rs +++ b/core/embed/rust/src/ui/component/placed.rs @@ -214,13 +214,21 @@ impl Split { } } - pub const fn vertical(size: i16, spacing: i16, first: T, second: U) -> Self { + pub const fn left(size: i16, spacing: i16, first: T, second: U) -> Self { Self::new(Axis::Vertical, size, spacing, first, second) } - pub const fn horizontal(size: i16, spacing: i16, first: T, second: U) -> Self { + pub const fn right(size: i16, spacing: i16, first: T, second: U) -> Self { + Self::new(Axis::Vertical, -size, spacing, first, second) + } + + pub const fn top(size: i16, spacing: i16, first: T, second: U) -> Self { Self::new(Axis::Horizontal, size, spacing, first, second) } + + pub const fn bottom(size: i16, spacing: i16, first: T, second: U) -> Self { + Self::new(Axis::Horizontal, -size, spacing, first, second) + } } impl Component for Split diff --git a/core/embed/rust/src/ui/display/loader/circular.rs b/core/embed/rust/src/ui/display/loader/circular.rs index 000905e678..a9387df1aa 100644 --- a/core/embed/rust/src/ui/display/loader/circular.rs +++ b/core/embed/rust/src/ui/display/loader/circular.rs @@ -1,6 +1,8 @@ use crate::ui::{ - constant, display, - display::Color, + constant, + constant::{screen, LOADER_INNER, LOADER_OUTER}, + display, + display::{toif::Icon, Color}, geometry::{Offset, Point, Rect}, }; @@ -10,25 +12,35 @@ use crate::trezorhal::{ dma2d::{dma2d_setup_4bpp_over_4bpp, dma2d_start_blend, dma2d_wait_for_transfer}, }; -use crate::ui::{ - constant::{screen, LOADER_OUTER}, - display::toif::Icon, -}; - -const LOADER_SIZE: i32 = (LOADER_OUTER * 2.0) as i32; - -const OUTER: f32 = constant::LOADER_OUTER; -const INNER: f32 = constant::LOADER_INNER; const ICON_MAX_SIZE: i16 = constant::LOADER_ICON_MAX_SIZE; -const IN_INNER_ANTI: i32 = ((INNER - 0.5) * (INNER - 0.5)) as i32; -const INNER_MIN: i32 = ((INNER + 0.5) * (INNER + 0.5)) as i32; -const INNER_MAX: i32 = ((INNER + 1.5) * (INNER + 1.5)) as i32; -const INNER_OUTER_ANTI: i32 = ((INNER + 2.5) * (INNER + 2.5)) as i32; -const OUTER_OUT_ANTI: i32 = ((OUTER - 1.5) * (OUTER - 1.5)) as i32; -const OUTER_MAX: i32 = ((OUTER - 0.5) * (OUTER - 0.5)) as i32; +#[derive(Clone, Copy)] +pub struct LoaderDimensions { + in_inner_anti: i32, + inner_min: i32, + inner_max: i32, + inner_outer_anti: i32, + outer_out_anti: i32, + outer_max: i32, +} + +impl LoaderDimensions { + pub fn new(outer: i16, inner: i16) -> Self { + let outer: f32 = outer.into(); + let inner: f32 = inner.into(); + Self { + in_inner_anti: ((inner - 0.5) * (inner - 0.5)) as i32, + inner_min: ((inner + 0.5) * (inner + 0.5)) as i32, + inner_max: ((inner + 1.5) * (inner + 1.5)) as i32, + inner_outer_anti: ((inner + 2.5) * (inner + 2.5)) as i32, + outer_out_anti: ((outer - 1.5) * (outer - 1.5)) as i32, + outer_max: ((outer - 0.5) * (outer - 0.5)) as i32, + } + } +} pub fn loader_circular_uncompress( + dim: LoaderDimensions, y_offset: i16, fg_color: Color, bg_color: Color, @@ -36,20 +48,42 @@ pub fn loader_circular_uncompress( indeterminate: bool, icon: Option<(Icon, Color)>, ) { - const ICON_MAX_SIZE: i16 = constant::LOADER_ICON_MAX_SIZE; - if let Some((icon, color)) = icon { let toif_size = icon.toif.size(); if toif_size.x <= ICON_MAX_SIZE && toif_size.y <= ICON_MAX_SIZE { let mut icon_data = [0_u8; ((ICON_MAX_SIZE * ICON_MAX_SIZE) / 2) as usize]; icon.toif.uncompress(&mut icon_data); let i = Some((icon_data.as_ref(), color, toif_size)); - loader_rust(y_offset, fg_color, bg_color, progress, indeterminate, i); + loader_rust( + dim, + y_offset, + fg_color, + bg_color, + progress, + indeterminate, + i, + ); } else { - loader_rust(y_offset, fg_color, bg_color, progress, indeterminate, None); + loader_rust( + dim, + y_offset, + fg_color, + bg_color, + progress, + indeterminate, + None, + ); } } else { - loader_rust(y_offset, fg_color, bg_color, progress, indeterminate, None); + loader_rust( + dim, + y_offset, + fg_color, + bg_color, + progress, + indeterminate, + None, + ); } } @@ -60,7 +94,15 @@ pub fn loader_circular( bg_color: Color, icon: Option<(Icon, Color)>, ) { - loader_circular_uncompress(y_offset, fg_color, bg_color, progress, false, icon); + loader_circular_uncompress( + LoaderDimensions::new(LOADER_OUTER, LOADER_INNER), + y_offset, + fg_color, + bg_color, + progress, + false, + icon, + ); } pub fn loader_circular_indeterminate( @@ -70,7 +112,15 @@ pub fn loader_circular_indeterminate( bg_color: Color, icon: Option<(Icon, Color)>, ) { - loader_circular_uncompress(y_offset, fg_color, bg_color, progress, true, icon); + loader_circular_uncompress( + LoaderDimensions::new(LOADER_OUTER, LOADER_INNER), + y_offset, + fg_color, + bg_color, + progress, + true, + icon, + ); } #[inline(always)] @@ -78,8 +128,8 @@ fn get_loader_vectors(indeterminate: bool, progress: u16) -> (Point, Point) { let (start_progress, end_progress) = if indeterminate { const LOADER_INDETERMINATE_WIDTH: u16 = 100; ( - progress - LOADER_INDETERMINATE_WIDTH, - progress + LOADER_INDETERMINATE_WIDTH, + (progress + 1000 - LOADER_INDETERMINATE_WIDTH) % 1000, + (progress + LOADER_INDETERMINATE_WIDTH) % 1000, ) } else { (0, progress) @@ -114,12 +164,12 @@ fn loader_get_pixel_color_idx( inverted: bool, end_vector: Point, n_start: Point, - x_c: i16, - y_c: i16, + c: Point, center: Point, + dim: LoaderDimensions, ) -> u8 { - let y_p = -(y_c - center.y); - let x_p = x_c - center.x; + let y_p = -(c.y - center.y); + let x_p = c.x - center.x; let vx = Point::new(x_p, y_p); let n_vx = Point::new(-y_p, x_p); @@ -142,31 +192,31 @@ fn loader_get_pixel_color_idx( // - r_inner)/(r_outer-r_inner) is negligible if show_all || included { //active part - if d <= IN_INNER_ANTI { + if d <= dim.in_inner_anti { 0 - } else if d <= INNER_MIN { - ((15 * (d - IN_INNER_ANTI)) / (INNER_MIN - IN_INNER_ANTI)) as u8 - } else if d <= OUTER_OUT_ANTI { + } else if d <= dim.inner_min { + ((15 * (d - dim.in_inner_anti)) / (dim.inner_min - dim.in_inner_anti)) as u8 + } else if d <= dim.outer_out_anti { 15 - } else if d <= OUTER_MAX { - (15 - ((15 * (d - OUTER_OUT_ANTI)) / (OUTER_MAX - OUTER_OUT_ANTI))) as u8 + } else if d <= dim.outer_max { + (15 - ((15 * (d - dim.outer_out_anti)) / (dim.outer_max - dim.outer_out_anti))) as u8 } else { 0 } } else { //inactive part - if d <= IN_INNER_ANTI { + if d <= dim.in_inner_anti { 0 - } else if d <= INNER_MIN { - ((15 * (d - IN_INNER_ANTI)) / (INNER_MIN - IN_INNER_ANTI)) as u8 - } else if d <= INNER_MAX { + } else if d <= dim.inner_min { + ((15 * (d - dim.in_inner_anti)) / (dim.inner_min - dim.in_inner_anti)) as u8 + } else if d <= dim.inner_max { 15 - } else if d <= INNER_OUTER_ANTI { - (15 - ((10 * (d - INNER_MAX)) / (INNER_OUTER_ANTI - INNER_MAX))) as u8 - } else if d <= OUTER_OUT_ANTI { + } else if d <= dim.inner_outer_anti { + (15 - ((10 * (d - dim.inner_max)) / (dim.inner_outer_anti - dim.inner_max))) as u8 + } else if d <= dim.outer_out_anti { 5 - } else if d <= OUTER_MAX { - 5 - ((5 * (d - OUTER_OUT_ANTI)) / (OUTER_MAX - OUTER_OUT_ANTI)) as u8 + } else if d <= dim.outer_max { + 5 - ((5 * (d - dim.outer_out_anti)) / (dim.outer_max - dim.outer_out_anti)) as u8 } else { 0 } @@ -175,6 +225,7 @@ fn loader_get_pixel_color_idx( #[cfg(not(feature = "dma2d"))] pub fn loader_rust( + dim: LoaderDimensions, y_offset: i16, fg_color: Color, bg_color: Color, @@ -225,7 +276,7 @@ pub fn loader_rust( if use_icon && icon_area_clamped.contains(p) { let x = x_c - center.x; let y = y_c - center.y; - if (x as i32 * x as i32 + y as i32 * y as i32) <= IN_INNER_ANTI { + if (x as i32 * x as i32 + y as i32 * y as i32) <= dim.in_inner_anti { let x_i = x_c - icon_area.x0; let y_i = y_c - icon_area.y0; @@ -241,7 +292,13 @@ pub fn loader_rust( if clamped.contains(p) && !icon_pixel { let pix_c_idx = loader_get_pixel_color_idx( - show_all, inverted, end_vector, n_start, x_c, y_c, center, + show_all, + inverted, + end_vector, + n_start, + Point::new(x_c, y_c), + center, + dim, ); underlying_color = colortable[pix_c_idx as usize]; } @@ -255,6 +312,7 @@ pub fn loader_rust( #[cfg(feature = "dma2d")] pub fn loader_rust( + dim: LoaderDimensions, y_offset: i16, fg_color: Color, bg_color: Color, @@ -341,7 +399,13 @@ pub fn loader_rust( let pix_c_idx = if clamped.contains(p) { loader_get_pixel_color_idx( - show_all, inverted, end_vector, n_start, x_c, y_c, center, + show_all, + inverted, + end_vector, + n_start, + Point::new(x_c, y_c), + center, + dim, ) } else { 0 diff --git a/core/embed/rust/src/ui/display/loader/mod.rs b/core/embed/rust/src/ui/display/loader/mod.rs index 12d445a409..0d8aa3e29c 100644 --- a/core/embed/rust/src/ui/display/loader/mod.rs +++ b/core/embed/rust/src/ui/display/loader/mod.rs @@ -9,6 +9,9 @@ use core::slice::from_raw_parts; use crate::ui::display::loader::circular::{ loader_circular as determinate, loader_circular_indeterminate as indeterminate, }; +#[cfg(feature = "model_tt")] +pub use crate::ui::display::loader::circular::{loader_circular_uncompress, LoaderDimensions}; + #[cfg(not(feature = "model_tt"))] use crate::ui::display::loader::rectangular::loader_rectangular as determinate; #[cfg(not(feature = "model_tt"))] diff --git a/core/embed/rust/src/ui/model_tr/constant.rs b/core/embed/rust/src/ui/model_tr/constant.rs index e477e8a50a..eadb0f43af 100644 --- a/core/embed/rust/src/ui/model_tr/constant.rs +++ b/core/embed/rust/src/ui/model_tr/constant.rs @@ -5,8 +5,8 @@ pub const HEIGHT: i16 = 128; pub const LINE_SPACE: i16 = 1; pub const FONT_BPP: i16 = 1; -pub const LOADER_OUTER: f32 = 32_f32; -pub const LOADER_INNER: f32 = 18_f32; +pub const LOADER_OUTER: i32 = 32; +pub const LOADER_INNER: i32 = 18; pub const LOADER_ICON_MAX_SIZE: i16 = 8; pub const BACKLIGHT_NORMAL: i32 = 150; diff --git a/core/embed/rust/src/ui/model_tt/component/button.rs b/core/embed/rust/src/ui/model_tt/component/button.rs index d23131c6be..8f04c60dba 100644 --- a/core/embed/rust/src/ui/model_tt/component/button.rs +++ b/core/embed/rust/src/ui/model_tt/component/button.rs @@ -391,7 +391,7 @@ impl Button { } else { 0 }; - theme::button_bar(Split::vertical( + theme::button_bar(Split::left( width, theme::BUTTON_SPACING, left.map(|msg| { @@ -456,11 +456,11 @@ impl Button { }); let total_height = theme::BUTTON_HEIGHT + theme::BUTTON_SPACING + theme::INFO_BUTTON_HEIGHT; FixedHeightBar::bottom( - Split::horizontal( + Split::top( theme::INFO_BUTTON_HEIGHT, theme::BUTTON_SPACING, top, - Split::vertical(theme::BUTTON_WIDTH, theme::BUTTON_SPACING, left, right), + Split::left(theme::BUTTON_WIDTH, theme::BUTTON_SPACING, left, right), ), total_height, ) @@ -488,11 +488,11 @@ impl Button { let [top, middle, bottom] = words; let total_height = 3 * theme::BUTTON_HEIGHT + 2 * theme::BUTTON_SPACING; FixedHeightBar::bottom( - Split::horizontal( + Split::top( theme::BUTTON_HEIGHT, theme::BUTTON_SPACING, btn(0, top), - Split::horizontal( + Split::top( theme::BUTTON_HEIGHT, theme::BUTTON_SPACING, btn(1, middle), diff --git a/core/embed/rust/src/ui/model_tt/component/coinjoin_progress.rs b/core/embed/rust/src/ui/model_tt/component/coinjoin_progress.rs new file mode 100644 index 0000000000..6907f77c49 --- /dev/null +++ b/core/embed/rust/src/ui/model_tt/component/coinjoin_progress.rs @@ -0,0 +1,131 @@ +use core::mem; + +use crate::ui::{ + component::{ + base::Never, painter, Child, Component, ComponentExt, Empty, Event, EventCtx, Label, Split, + }, + display::loader::{loader_circular_uncompress, LoaderDimensions}, + geometry::{Alignment, Insets, Rect}, + util::animation_disabled, +}; + +use super::{theme, Frame}; + +const RECTANGLE_HEIGHT: i16 = 56; +const LABEL_TOP: i16 = 135; +const LOADER_OUTER: i16 = 39; +const LOADER_INNER: i16 = 28; +const LOADER_OFFSET: i16 = -34; +const LOADER_SPEED: u16 = 5; + +pub struct CoinJoinProgress { + value: u16, + indeterminate: bool, + content: Child, &'static str>>, + // Label is not a child since circular loader paints large black rectangle which overlaps it. + // To work around this, draw label every time loader is drawn. + label: Label, +} + +impl CoinJoinProgress +where + T: AsRef, +{ + pub fn new(text: T, indeterminate: bool) -> CoinJoinProgress> + where + T: AsRef, + { + let style = theme::label_coinjoin_progress(); + let label = Label::centered("DO NOT DISCONNECT YOUR TREZOR!", style) + .vertically_aligned(Alignment::Center); + let bg = painter::rect_painter(style.background_color, theme::BG); + let inner = (bg, label); + CoinJoinProgress::with_background(text, inner, indeterminate) + } +} + +impl CoinJoinProgress +where + T: AsRef, + U: Component, +{ + pub fn with_background(text: T, inner: U, indeterminate: bool) -> Self { + Self { + value: 0, + indeterminate, + content: Frame::left_aligned( + theme::label_title(), + "COINJOIN IN PROGRESS", + Split::bottom(RECTANGLE_HEIGHT, 0, Empty, inner), + ) + .into_child(), + label: Label::centered(text, theme::TEXT_NORMAL), + } + } +} + +impl Component for CoinJoinProgress +where + T: AsRef, + U: Component, +{ + type Msg = Never; + + fn place(&mut self, bounds: Rect) -> Rect { + self.content.place(bounds); + let label_bounds = bounds.inset(Insets::top(LABEL_TOP)); + self.label.place(label_bounds); + bounds + } + + fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option { + self.content.event(ctx, event); + self.label.event(ctx, event); + match event { + _ if animation_disabled() => { + return None; + } + Event::Attach if self.indeterminate => { + ctx.request_anim_frame(); + } + Event::Timer(EventCtx::ANIM_FRAME_TIMER) => { + self.value = (self.value + LOADER_SPEED) % 1000; + ctx.request_anim_frame(); + ctx.request_paint(); + } + Event::Progress(new_value, _new_description) => { + if mem::replace(&mut self.value, new_value) != new_value { + ctx.request_paint(); + } + } + _ => {} + } + None + } + + fn paint(&mut self) { + self.content.paint(); + loader_circular_uncompress( + LoaderDimensions::new(LOADER_OUTER, LOADER_INNER), + LOADER_OFFSET, + theme::FG, + theme::BG, + self.value, + self.indeterminate, + None, + ); + self.label.paint(); + } +} + +#[cfg(feature = "ui_debug")] +impl crate::trace::Trace for CoinJoinProgress +where + T: crate::trace::Trace, + U: Component, +{ + fn trace(&self, t: &mut dyn crate::trace::Tracer) { + t.open("CoinJoinProgress"); + t.close(); + } +} diff --git a/core/embed/rust/src/ui/model_tt/component/homescreen/mod.rs b/core/embed/rust/src/ui/model_tt/component/homescreen/mod.rs index 7b5ccd53c5..d951083bc4 100644 --- a/core/embed/rust/src/ui/model_tt/component/homescreen/mod.rs +++ b/core/embed/rust/src/ui/model_tt/component/homescreen/mod.rs @@ -62,6 +62,7 @@ where fn level_to_style(level: u8) -> (Color, Icon) { match level { + 3 => (theme::YELLOW, Icon::new(theme::ICON_COINJOIN)), 2 => (theme::VIOLET, Icon::new(theme::ICON_MAGIC)), 1 => (theme::YELLOW, Icon::new(theme::ICON_WARN)), _ => (theme::RED, Icon::new(theme::ICON_WARN)), diff --git a/core/embed/rust/src/ui/model_tt/component/mod.rs b/core/embed/rust/src/ui/model_tt/component/mod.rs index 095e407c83..7744d14c09 100644 --- a/core/embed/rust/src/ui/model_tt/component/mod.rs +++ b/core/embed/rust/src/ui/model_tt/component/mod.rs @@ -1,5 +1,6 @@ mod address_details; mod button; +mod coinjoin_progress; mod dialog; mod fido; #[rustfmt::skip] @@ -24,6 +25,7 @@ pub use button::{ Button, ButtonContent, ButtonMsg, ButtonStyle, ButtonStyleSheet, CancelConfirmMsg, CancelInfoConfirmMsg, IconText, SelectWordMsg, }; +pub use coinjoin_progress::CoinJoinProgress; pub use dialog::{Dialog, DialogMsg, IconDialog}; pub use fido::{FidoConfirm, FidoMsg}; pub use frame::{Frame, FrameMsg, NotificationFrame}; diff --git a/core/embed/rust/src/ui/model_tt/constant.rs b/core/embed/rust/src/ui/model_tt/constant.rs index 9648dd16ed..0fc5e77242 100644 --- a/core/embed/rust/src/ui/model_tt/constant.rs +++ b/core/embed/rust/src/ui/model_tt/constant.rs @@ -5,8 +5,8 @@ pub const HEIGHT: i16 = 240; pub const LINE_SPACE: i16 = 4; pub const FONT_BPP: i16 = 4; -pub const LOADER_OUTER: f32 = 60_f32; -pub const LOADER_INNER: f32 = 42_f32; +pub const LOADER_OUTER: i16 = 60; +pub const LOADER_INNER: i16 = 42; pub const LOADER_ICON_MAX_SIZE: i16 = 64; pub const BACKLIGHT_NORMAL: i32 = 150; diff --git a/core/embed/rust/src/ui/model_tt/layout.rs b/core/embed/rust/src/ui/model_tt/layout.rs index 2726072a92..9c06bc0bad 100644 --- a/core/embed/rust/src/ui/model_tt/layout.rs +++ b/core/embed/rust/src/ui/model_tt/layout.rs @@ -28,7 +28,7 @@ use crate::{ }, TextStyle, }, - Border, Component, Empty, FormattedText, Qr, Timeout, TimeoutMsg, + Border, Component, Empty, FormattedText, Never, Qr, Timeout, TimeoutMsg, }, display::{self, tjpgd::jpeg_info, toif::Icon}, geometry, @@ -46,12 +46,12 @@ use crate::{ use super::{ component::{ AddressDetails, Bip39Input, Button, ButtonMsg, ButtonStyleSheet, CancelConfirmMsg, - CancelInfoConfirmMsg, Dialog, DialogMsg, FidoConfirm, FidoMsg, Frame, FrameMsg, - HoldToConfirm, HoldToConfirmMsg, Homescreen, HomescreenMsg, HorizontalPage, IconDialog, - Lockscreen, MnemonicInput, MnemonicKeyboard, MnemonicKeyboardMsg, NotificationFrame, - NumberInputDialog, NumberInputDialogMsg, PassphraseKeyboard, PassphraseKeyboardMsg, - PinKeyboard, PinKeyboardMsg, Progress, SelectWordCount, SelectWordCountMsg, SelectWordMsg, - Slip39Input, SwipeHoldPage, SwipePage, WelcomeScreen, + CancelInfoConfirmMsg, CoinJoinProgress, Dialog, DialogMsg, FidoConfirm, FidoMsg, Frame, + FrameMsg, HoldToConfirm, HoldToConfirmMsg, Homescreen, HomescreenMsg, HorizontalPage, + IconDialog, Lockscreen, MnemonicInput, MnemonicKeyboard, MnemonicKeyboardMsg, + NotificationFrame, NumberInputDialog, NumberInputDialogMsg, PassphraseKeyboard, + PassphraseKeyboardMsg, PinKeyboard, PinKeyboardMsg, Progress, SelectWordCount, + SelectWordCountMsg, SelectWordMsg, Slip39Input, SwipeHoldPage, SwipePage, WelcomeScreen, }, constant, theme, }; @@ -336,6 +336,17 @@ where } } +impl ComponentMsgObj for (Timeout, T) +where + T: Component, +{ + fn msg_try_into_obj(&self, msg: Self::Msg) -> Result { + match msg { + TimeoutMsg::TimedOut => Ok(CANCELLED.as_obj()), + } + } +} + impl ComponentMsgObj for Qr { fn msg_try_into_obj(&self, _msg: Self::Msg) -> Result { unreachable!(); @@ -365,6 +376,16 @@ where } } +impl ComponentMsgObj for CoinJoinProgress +where + T: AsRef, + U: Component, +{ + fn msg_try_into_obj(&self, _msg: Self::Msg) -> Result { + unreachable!(); + } +} + extern "C" fn new_confirm_action(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()?; @@ -1064,9 +1085,9 @@ extern "C" fn new_confirm_coinjoin(n_args: usize, args: *const Obj, kwargs: *mut let max_feerate: StrBuffer = kwargs.get(Qstr::MP_QSTR_max_feerate)?.try_into()?; let paragraphs = Paragraphs::new([ - Paragraph::new(&theme::TEXT_NORMAL, "Maximum rounds:".into()), + Paragraph::new(&theme::TEXT_NORMAL, "Max rounds".into()), Paragraph::new(&theme::TEXT_MONO, max_rounds), - Paragraph::new(&theme::TEXT_NORMAL, "Maximum mining fee:".into()), + Paragraph::new(&theme::TEXT_NORMAL, "Max mining fee".into()), Paragraph::new(&theme::TEXT_MONO, max_feerate), ]); @@ -1390,6 +1411,30 @@ extern "C" fn new_show_progress(n_args: usize, args: *const Obj, kwargs: *mut Ma unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } } +extern "C" fn new_show_progress_coinjoin(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 indeterminate: bool = kwargs.get_or(Qstr::MP_QSTR_indeterminate, false)?; + let time_ms: u32 = kwargs.get_or(Qstr::MP_QSTR_time_ms, 0)?; + let skip_first_paint: bool = kwargs.get_or(Qstr::MP_QSTR_skip_first_paint, false)?; + + // The second type parameter is actually not used in `new()` but we need to + // provide it. + let progress = CoinJoinProgress::<_, Never>::new(title, indeterminate); + let obj = if time_ms > 0 && indeterminate { + let timeout = Timeout::new(time_ms); + LayoutObj::new((timeout, progress.map(|_msg| None)))? + } else { + LayoutObj::new(progress)? + }; + if skip_first_paint { + obj.skip_first_paint(); + } + Ok(obj.into()) + }; + unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } +} + extern "C" fn new_show_homescreen(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { let block = move |_args: &[Obj], kwargs: &Map| { let label: StrBuffer = kwargs.get(Qstr::MP_QSTR_label)?.try_into()?; @@ -1424,31 +1469,6 @@ extern "C" fn new_show_lockscreen(n_args: usize, args: *const Obj, kwargs: *mut unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } } -extern "C" fn new_show_busyscreen(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 time_ms: u32 = kwargs.get(Qstr::MP_QSTR_time_ms)?.try_into()?; - let skip_first_paint: bool = kwargs.get(Qstr::MP_QSTR_skip_first_paint)?.try_into()?; - - let obj = LayoutObj::new(Frame::left_aligned( - theme::label_title(), - title, - Dialog::new( - Paragraphs::new(Paragraph::new(&theme::TEXT_NORMAL, description).centered()), - Timeout::new(time_ms).map(|msg| { - (matches!(msg, TimeoutMsg::TimedOut)).then(|| CancelConfirmMsg::Cancelled) - }), - ), - ))?; - if skip_first_paint { - obj.skip_first_paint(); - } - Ok(obj.into()) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - extern "C" fn draw_welcome_screen() -> Obj { // No need of util::try_or_raise, this does not allocate let mut screen = WelcomeScreen::new(); @@ -1822,6 +1842,17 @@ pub static mp_module_trezorui2: Module = obj_module! { /// make sure the initial desciption has at least that amount of lines.""" Qstr::MP_QSTR_show_progress => obj_fn_kw!(0, new_show_progress).as_obj(), + /// def show_progress_coinjoin( + /// *, + /// title: str, + /// indeterminate: bool = False, + /// time_ms: int = 0, + /// skip_first_paint: bool = False, + /// ) -> object: + /// """Show progress loader for coinjoin. Returns CANCELLED after a specified time when + /// time_ms timeout is passed.""" + Qstr::MP_QSTR_show_progress_coinjoin => obj_fn_kw!(0, new_show_progress_coinjoin).as_obj(), + /// def show_homescreen( /// *, /// label: str, @@ -1842,16 +1873,6 @@ pub static mp_module_trezorui2: Module = obj_module! { /// """Homescreen for locked device.""" Qstr::MP_QSTR_show_lockscreen => obj_fn_kw!(0, new_show_lockscreen).as_obj(), - /// def show_busyscreen( - /// *, - /// title: str, - /// description: str, - /// time_ms: int, - /// skip_first_paint: bool, - /// ) -> CANCELLED: - /// """Homescreen used for indicating coinjoin in progress.""" - Qstr::MP_QSTR_show_busyscreen => obj_fn_kw!(0, new_show_busyscreen).as_obj(), - /// def draw_welcome_screen() -> None: /// """Show logo icon with the model name at the bottom and return.""" Qstr::MP_QSTR_draw_welcome_screen => obj_fn_0!(draw_welcome_screen).as_obj(), diff --git a/core/embed/rust/src/ui/model_tt/theme.rs b/core/embed/rust/src/ui/model_tt/theme.rs index 2aed27a4a4..6acf0bc816 100644 --- a/core/embed/rust/src/ui/model_tt/theme.rs +++ b/core/embed/rust/src/ui/model_tt/theme.rs @@ -152,6 +152,10 @@ pub const fn label_title() -> TextStyle { TextStyle::new(Font::BOLD, GREY_LIGHT, BG, GREY_LIGHT, GREY_LIGHT) } +pub const fn label_coinjoin_progress() -> TextStyle { + TextStyle::new(Font::BOLD, FG, YELLOW, FG, FG) +} + pub const fn button_default() -> ButtonStyleSheet { ButtonStyleSheet { normal: &ButtonStyle { diff --git a/core/mocks/generated/trezorui2.pyi b/core/mocks/generated/trezorui2.pyi index adf9c17b20..3c6257af91 100644 --- a/core/mocks/generated/trezorui2.pyi +++ b/core/mocks/generated/trezorui2.pyi @@ -422,6 +422,18 @@ def show_progress( make sure the initial desciption has at least that amount of lines.""" +# rust/src/ui/model_tt/layout.rs +def show_progress_coinjoin( + *, + title: str, + indeterminate: bool = False, + time_ms: int = 0, + skip_first_paint: bool = False, +) -> object: + """Show progress loader for coinjoin. Returns CANCELLED after a specified time when + time_ms timeout is passed.""" + + # rust/src/ui/model_tt/layout.rs def show_homescreen( *, @@ -444,17 +456,6 @@ def show_lockscreen( """Homescreen for locked device.""" -# rust/src/ui/model_tt/layout.rs -def show_busyscreen( - *, - title: str, - description: str, - time_ms: int, - skip_first_paint: bool, -) -> CANCELLED: - """Homescreen used for indicating coinjoin in progress.""" - - # rust/src/ui/model_tt/layout.rs def draw_welcome_screen() -> None: """Show logo icon with the model name at the bottom and return.""" diff --git a/core/src/apps/base.py b/core/src/apps/base.py index 8bd26fe604..3c0f421656 100644 --- a/core/src/apps/base.py +++ b/core/src/apps/base.py @@ -263,8 +263,8 @@ async def handle_UnlockPath(ctx: wire.Context, msg: UnlockPath) -> protobuf.Mess await confirm_action( ctx, "confirm_coinjoin_access", - title="Coinjoin account", - description="Do you want to allow access to your coinjoin account?", + title="Coinjoin", + description="Do you want to access your coinjoin account?", ) wire_types = (MessageType.GetAddress, MessageType.GetPublicKey, MessageType.SignTx) diff --git a/core/src/apps/bitcoin/sign_tx/bitcoin.py b/core/src/apps/bitcoin/sign_tx/bitcoin.py index e4f80c5af4..1600681a9d 100644 --- a/core/src/apps/bitcoin/sign_tx/bitcoin.py +++ b/core/src/apps/bitcoin/sign_tx/bitcoin.py @@ -13,6 +13,7 @@ from ..common import SigHashType, ecdsa_sign, input_is_external from ..ownership import verify_nonownership from ..verification import SignatureVerifier from . import helpers +from .approvers import CoinJoinApprover from .helpers import request_tx_input, request_tx_output from .progress import progress from .tx_info import OriginalTxInfo @@ -47,7 +48,9 @@ _SERIALIZED_TX_BUFFER = empty_bytearray(_MAX_SERIALIZED_CHUNK_SIZE) class Bitcoin: async def signer(self) -> None: - progress.init(self.tx_info.tx) + progress.init( + self.tx_info.tx, is_coinjoin=isinstance(self.approver, CoinJoinApprover) + ) # Add inputs to sig_hasher and h_tx_check and compute the sum of input amounts. await self.step1_process_inputs() diff --git a/core/src/apps/bitcoin/sign_tx/progress.py b/core/src/apps/bitcoin/sign_tx/progress.py index b641df8505..8e150ce590 100644 --- a/core/src/apps/bitcoin/sign_tx/progress.py +++ b/core/src/apps/bitcoin/sign_tx/progress.py @@ -16,6 +16,7 @@ class Progress: self.progress = 0 self.steps = 0 self.signing = False + self.is_coinjoin = False # We don't know how long it will take to fetch the previous transactions, # so for each one we reserve _PREV_TX_MULTIPLIER steps in the signing @@ -24,9 +25,10 @@ class Progress: # prev_tx input or output in the overall signing progress. self.prev_tx_step = 0 - def init(self, tx: SignTx) -> None: + def init(self, tx: SignTx, is_coinjoin: bool = False) -> None: self.progress = 0 self.signing = False + self.is_coinjoin = is_coinjoin # Step 1 and 2 - load inputs and outputs self.steps = tx.inputs_count + tx.outputs_count @@ -109,13 +111,16 @@ class Progress: def report_init(self) -> None: from trezor import workflow - from trezor.ui.layouts import bitcoin_progress + from trezor.ui.layouts import bitcoin_progress, coinjoin_progress + progress_layout = bitcoin_progress + if self.is_coinjoin: + progress_layout = coinjoin_progress workflow.close_others() if self.signing: - self.progress_layout = bitcoin_progress("Signing transaction") + self.progress_layout = progress_layout("Signing transaction") else: - self.progress_layout = bitcoin_progress("Loading transaction") + self.progress_layout = progress_layout("Loading transaction") def report(self) -> None: from trezor import utils diff --git a/core/src/apps/homescreen/__init__.py b/core/src/apps/homescreen/__init__.py index 2c6276bc7b..6196bfcd48 100644 --- a/core/src/apps/homescreen/__init__.py +++ b/core/src/apps/homescreen/__init__.py @@ -2,9 +2,11 @@ import storage import storage.cache import storage.device from trezor import config, wire +from trezor.enums import MessageType from trezor.ui.layouts.homescreen import Busyscreen, Homescreen, Lockscreen from apps.base import busy_expiry_ms, lock_device +from apps.common.authorization import is_set_any_session async def busyscreen() -> None: @@ -19,18 +21,20 @@ async def homescreen() -> None: notification = None notification_is_error = False - if storage.device.is_initialized() and storage.device.no_backup(): + if is_set_any_session(MessageType.AuthorizeCoinJoin): + notification = "COINJOIN AUTHORIZED" + elif storage.device.is_initialized() and storage.device.no_backup(): notification = "SEEDLESS" notification_is_error = True elif storage.device.is_initialized() and storage.device.unfinished_backup(): - notification = "BACKUP FAILED!" + notification = "BACKUP FAILED" notification_is_error = True elif storage.device.is_initialized() and storage.device.needs_backup(): - notification = "NEEDS BACKUP!" + notification = "NEEDS BACKUP" elif storage.device.is_initialized() and not config.has_pin(): - notification = "PIN NOT SET!" + notification = "PIN NOT SET" elif storage.device.get_experimental_features(): - notification = "EXPERIMENTAL MODE!" + notification = "EXPERIMENTAL MODE" await Homescreen( label=label, diff --git a/core/src/trezor/ui/layouts/tt_v2/__init__.py b/core/src/trezor/ui/layouts/tt_v2/__init__.py index 9cf93027ee..b4246544e2 100644 --- a/core/src/trezor/ui/layouts/tt_v2/__init__.py +++ b/core/src/trezor/ui/layouts/tt_v2/__init__.py @@ -1135,15 +1135,9 @@ async def request_pin_on_device( class RustProgress: def __init__( self, - title: str, - description: str | None = None, - indeterminate: bool = False, + layout: Any, ): - self.layout: Any = trezorui2.show_progress( - title=title.upper(), - indeterminate=indeterminate, - description=description or "", - ) + self.layout = layout ui.backlight_fade(ui.style.BACKLIGHT_DIM) ui.display.clear() self.layout.attach_timer_fn(self.set_timer) @@ -1160,25 +1154,41 @@ class RustProgress: ui.refresh() -def progress(message: str = "PLEASE WAIT") -> ProgressLayout: - return RustProgress(message.upper()) +def progress( + message: str = "PLEASE WAIT", + description: str | None = None, + indeterminate: bool = False, +) -> ProgressLayout: + return RustProgress( + layout=trezorui2.show_progress( + title=message.upper(), + indeterminate=indeterminate, + description=description or "", + ) + ) def bitcoin_progress(message: str) -> ProgressLayout: - return RustProgress(message.upper()) + return progress(message) + + +def coinjoin_progress(message: str) -> ProgressLayout: + return RustProgress( + layout=trezorui2.show_progress_coinjoin(title=message, indeterminate=False) + ) def pin_progress(message: str, description: str) -> ProgressLayout: - return RustProgress(message.upper(), description=description) + return progress(message, description=description) def monero_keyimage_sync_progress() -> ProgressLayout: - return RustProgress("SYNCING") + return progress("SYNCING") def monero_live_refresh_progress() -> ProgressLayout: - return RustProgress("REFRESHING", description="", indeterminate=True) + return progress("REFRESHING", indeterminate=True) def monero_transaction_progress_inner() -> ProgressLayout: - return RustProgress("SIGNING TRANSACTION", description="") + return progress("SIGNING TRANSACTION") diff --git a/core/src/trezor/ui/layouts/tt_v2/homescreen.py b/core/src/trezor/ui/layouts/tt_v2/homescreen.py index df176f3084..16478303bc 100644 --- a/core/src/trezor/ui/layouts/tt_v2/homescreen.py +++ b/core/src/trezor/ui/layouts/tt_v2/homescreen.py @@ -47,7 +47,9 @@ class Homescreen(HomescreenBase): level = 1 if notification is not None: notification = notification.rstrip("!") - if "EXPERIMENTAL" in notification: + if "COINJOIN" in notification.upper(): + level = 3 + elif "EXPERIMENTAL" in notification.upper(): level = 2 elif notification_is_error: level = 0 @@ -114,9 +116,9 @@ class Busyscreen(HomescreenBase): def __init__(self, delay_ms: int) -> None: skip = storage_cache.homescreen_shown is self.RENDER_INDICATOR super().__init__( - layout=trezorui2.show_busyscreen( - title="PLEASE WAIT", - description="Coinjoin in progress.\n\nDo not disconnect your\nTrezor.", + layout=trezorui2.show_progress_coinjoin( + title="Waiting for others", + indeterminate=True, time_ms=delay_ms, skip_first_paint=skip, ) diff --git a/tests/ui_tests/fixtures.json b/tests/ui_tests/fixtures.json index f372a324b1..de115781fc 100644 --- a/tests/ui_tests/fixtures.json +++ b/tests/ui_tests/fixtures.json @@ -715,16 +715,16 @@ "TT_binance-test_sign_tx.py::test_binance_sign_message[message0-expected_response0]": "6367706336b7063b5f8850034afb948c8e9cda571d3b40f0d69f73d743eb72e2", "TT_binance-test_sign_tx.py::test_binance_sign_message[message1-expected_response1]": "78b3b0efe134085ed595dcc859f53e39b2f044c3ae17e52ef7ff74d33303f5a9", "TT_binance-test_sign_tx.py::test_binance_sign_message[message2-expected_response2]": "9f6d84a0081925d4d7ffc43765394e2e3c065a34c3fc00b4c9d8f6dbb108796e", -"TT_bitcoin-test_authorize_coinjoin.py::test_cancel_authorization": "9903e6dc03e36e73bd36d96e8e4eb28706ae8a47015605b328c977bb39e9b202", -"TT_bitcoin-test_authorize_coinjoin.py::test_get_address": "5556ff2531268efa0eda447a1960324c78ff43d79c1f7d91d2d6ddd772275799", -"TT_bitcoin-test_authorize_coinjoin.py::test_get_public_key": "f79cc8fce59d48aa49bc791cbb00d63676dbbd616eb94b4aa0ca56a1c89cf3a0", -"TT_bitcoin-test_authorize_coinjoin.py::test_multisession_authorization": "8e0a38d46f5a28ed8c7b4608ba5f02b9c3b82a70b01c9bd7319392b0eea272dd", -"TT_bitcoin-test_authorize_coinjoin.py::test_sign_tx": "21954223b2382156eb2926136239cd983ab94fc0d7060ad87e14d35a098276ea", -"TT_bitcoin-test_authorize_coinjoin.py::test_sign_tx_large": "eb9e47aaee7b932845a0f94a9f52b3107ce1eb19ba12f63f3d7b464335508634", -"TT_bitcoin-test_authorize_coinjoin.py::test_sign_tx_migration": "d619b7d8ff6124885969c543603c49952ea07917a58fb5d80592b4f5bb5c8495", -"TT_bitcoin-test_authorize_coinjoin.py::test_sign_tx_spend": "69649e183ca47887d8363d63996a4ddac2dfc9ebd23e53c9855bb8d30bcc868a", -"TT_bitcoin-test_authorize_coinjoin.py::test_wrong_account_type": "9903e6dc03e36e73bd36d96e8e4eb28706ae8a47015605b328c977bb39e9b202", -"TT_bitcoin-test_authorize_coinjoin.py::test_wrong_coordinator": "9903e6dc03e36e73bd36d96e8e4eb28706ae8a47015605b328c977bb39e9b202", +"TT_bitcoin-test_authorize_coinjoin.py::test_cancel_authorization": "a06b2f2471bb928f36dc43e6f7ad03d8b19dadb77ed6e968830cbdf84e0ee2bc", +"TT_bitcoin-test_authorize_coinjoin.py::test_get_address": "a02623c612e54300d8f9f9d0760282f8ea9be9663b30257797eade328fa30a75", +"TT_bitcoin-test_authorize_coinjoin.py::test_get_public_key": "064eb02d220e974352247d3b7065c38dd3d4fa3d0c5571574bf0d80f369b69d1", +"TT_bitcoin-test_authorize_coinjoin.py::test_multisession_authorization": "b28769468af2ddf2e4b2e45b0bc89806a6392b082cc8953013795eb32b692890", +"TT_bitcoin-test_authorize_coinjoin.py::test_sign_tx": "a7a3307a96425a843617d341cf559d0898813ba1eec8221e911a153141aaade8", +"TT_bitcoin-test_authorize_coinjoin.py::test_sign_tx_large": "a45463660d3c6f920c7d31f6c054a39e504cb152e9eacd0f817aa081a3c7502b", +"TT_bitcoin-test_authorize_coinjoin.py::test_sign_tx_migration": "b92179f9e59ea76c6410cdfb3402efc33f05a1e75abfee842cb63380cfa4ad94", +"TT_bitcoin-test_authorize_coinjoin.py::test_sign_tx_spend": "2a086b16a7339f09db438f9a6416a48d2b4e99945a8f1365800d4373158d09a8", +"TT_bitcoin-test_authorize_coinjoin.py::test_wrong_account_type": "a06b2f2471bb928f36dc43e6f7ad03d8b19dadb77ed6e968830cbdf84e0ee2bc", +"TT_bitcoin-test_authorize_coinjoin.py::test_wrong_coordinator": "a06b2f2471bb928f36dc43e6f7ad03d8b19dadb77ed6e968830cbdf84e0ee2bc", "TT_bitcoin-test_bcash.py::test_attack_change_input": "01c388c7836bf584f7d463412ed8dfc18267173759779572b59f8a92831d587d", "TT_bitcoin-test_bcash.py::test_send_bch_change": "b33fb94f9b7a27db131a025a58e7d1e9130455b56e2a9664a6601126798c5faa", "TT_bitcoin-test_bcash.py::test_send_bch_external_presigned": "be3c96271c3fa8084f94f3108a283059b625240c8479ae8509e4e61e2e7801b1", @@ -748,7 +748,7 @@ "TT_bitcoin-test_decred.py::test_send_decred": "7aea7d3be283f95c73494d1298cf45f0914e4bd2b563b6ee06822e5475c7cdee", "TT_bitcoin-test_decred.py::test_send_decred_change": "f1ba6140197b01fcdc6f5e7657dfec8608e19cfd161ddf94273cd057afee7fe5", "TT_bitcoin-test_decred.py::test_spend_from_stake_generation_and_revocation_decred": "925e48742feacc62b02e4fcab707a3f7fe6ea8779e77e49e94a452416c844579", -"TT_bitcoin-test_descriptors.py::test_descriptors[Bitcoin-0-10025-InputScriptType.SPENDTAPROOT--ad9e3c78": "29bd3ff77013039b56947adecc7ca4971d9cdc3c6c084232a2e6cb7b61cdf322", +"TT_bitcoin-test_descriptors.py::test_descriptors[Bitcoin-0-10025-InputScriptType.SPENDTAPROOT--ad9e3c78": "9ef6e1b1d30c95b00c52957eda428f0135b67b3ac51febb571b0b37c7959a201", "TT_bitcoin-test_descriptors.py::test_descriptors[Bitcoin-0-44-InputScriptType.SPENDADDRESS-pkh-efa37663": "5a7da5c231515010c7c9355f571c2ea44743e06f4e603b11475790ac47620369", "TT_bitcoin-test_descriptors.py::test_descriptors[Bitcoin-0-49-InputScriptType.SPENDP2SHWITNESS-77f1e2d2": "16d7bd9d6a2d7f3fbd3cd4f57b4d41bd22181d1815f3f3e8ceab8c71c7a1e217", "TT_bitcoin-test_descriptors.py::test_descriptors[Bitcoin-0-84-InputScriptType.SPENDWITNESS-wpk-16507754": "33742a9a1db97e160bae2399272f9ed2d1e4457efb9f1cdda5724bda09d285e8", @@ -757,7 +757,7 @@ "TT_bitcoin-test_descriptors.py::test_descriptors[Bitcoin-1-49-InputScriptType.SPENDP2SHWITNESS-965f4fa3": "70e6bbb4990ce9185fad968524081d31f2fdd6d4663223fb798cf0f186dfef70", "TT_bitcoin-test_descriptors.py::test_descriptors[Bitcoin-1-84-InputScriptType.SPENDWITNESS-wpk-c761bcc8": "b07033a54a4115479d1fb1227ea6895d9661d4f304ca39c0636c2f32504e6eb0", "TT_bitcoin-test_descriptors.py::test_descriptors[Bitcoin-1-86-InputScriptType.SPENDTAPROOT-tr(-686799ea": "c55d9c0796573c26c4ab2ab42805fcffc51c74349ee15da19d45f849443a7c09", -"TT_bitcoin-test_descriptors.py::test_descriptors[Testnet-0-10025-InputScriptType.SPENDTAPROOT--77d3a71b": "37b2f77f05f6b55ae4af5da2343e1ddad44afe52e84b6d63d00effd233a464ee", +"TT_bitcoin-test_descriptors.py::test_descriptors[Testnet-0-10025-InputScriptType.SPENDTAPROOT--77d3a71b": "b136a9f101bd3c1f462dba38211ce88555b57f217216918388b5dda397b71f9d", "TT_bitcoin-test_descriptors.py::test_descriptors[Testnet-0-44-InputScriptType.SPENDADDRESS-pkh-a26143a7": "c1510c3bfff3a635ba9f01d3d292b795b135fd32f89edfbd211609bfb3b9fb18", "TT_bitcoin-test_descriptors.py::test_descriptors[Testnet-0-49-InputScriptType.SPENDP2SHWITNESS-195ebda5": "6a44ff76861f8b45fcf02d8231eba4c7d7be2e36acbc83245daab8bf1e139e69", "TT_bitcoin-test_descriptors.py::test_descriptors[Testnet-0-84-InputScriptType.SPENDWITNESS-wpk-68f8b526": "4668aded2297e0aaa149c1e3c81016add52c6a273df6709360f3e3f239b59be0", @@ -1729,8 +1729,8 @@ "TT_test_basic.py::test_device_id_same": "80a6e289138a604cf351a29511cf6f85e2243591317894703152787e1351a1a3", "TT_test_basic.py::test_features": "80a6e289138a604cf351a29511cf6f85e2243591317894703152787e1351a1a3", "TT_test_basic.py::test_ping": "80a6e289138a604cf351a29511cf6f85e2243591317894703152787e1351a1a3", -"TT_test_busy_state.py::test_busy_expiry": "b67d51c7c3e2ebf4318f249c48badc0fceb907fedeb99de4fc6b6d196389bf69", -"TT_test_busy_state.py::test_busy_state": "b549edd509bde87496aea924d9b9df251893d3a901afc46a5a61605abe7022fb", +"TT_test_busy_state.py::test_busy_expiry": "ba86a3b442bcc2709762a008734511cf5d7ddd715f7184e9006061b8046e2d56", +"TT_test_busy_state.py::test_busy_state": "201b1a28a61556a030f1991e5f88925f1bc3db9ef421e4bc99a323505212f7ef", "TT_test_cancel.py::test_cancel_message_via_cancel[message0]": "1bd3157d54327e33542f89dcac6c7cd23808f7c9aa1b0adb390e5fcc1fd858a5", "TT_test_cancel.py::test_cancel_message_via_cancel[message1]": "1bd3157d54327e33542f89dcac6c7cd23808f7c9aa1b0adb390e5fcc1fd858a5", "TT_test_cancel.py::test_cancel_message_via_initialize[message0]": "1bd3157d54327e33542f89dcac6c7cd23808f7c9aa1b0adb390e5fcc1fd858a5",