1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-01-14 17:31:04 +00:00

fix(core/ui): coinjoin layouts style update

[no changelog]
This commit is contained in:
Martin Milata 2023-03-24 16:54:47 +01:00
parent 77d8af1322
commit 08cad2f909
23 changed files with 439 additions and 162 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 209 B

After

Width:  |  Height:  |  Size: 378 B

View File

@ -50,7 +50,6 @@ static void _librust_qstrs(void) {
MP_QSTR_request_slip39; MP_QSTR_request_slip39;
MP_QSTR_select_word; MP_QSTR_select_word;
MP_QSTR_select_word_count; MP_QSTR_select_word_count;
MP_QSTR_show_busyscreen;
MP_QSTR_show_group_share_success; MP_QSTR_show_group_share_success;
MP_QSTR_show_homescreen; MP_QSTR_show_homescreen;
MP_QSTR_show_lockscreen; MP_QSTR_show_lockscreen;
@ -58,6 +57,7 @@ static void _librust_qstrs(void) {
MP_QSTR_show_remaining_shares; MP_QSTR_show_remaining_shares;
MP_QSTR_show_share_words; MP_QSTR_show_share_words;
MP_QSTR_show_progress; MP_QSTR_show_progress;
MP_QSTR_show_progress_coinjoin;
MP_QSTR_show_address_details; MP_QSTR_show_address_details;
MP_QSTR_attach_timer_fn; MP_QSTR_attach_timer_fn;

View File

@ -1,7 +1,7 @@
use crate::ui::{ use crate::ui::{
component::{Component, Event, EventCtx, Never}, component::{Component, Event, EventCtx, Never},
display::Font, display::Font,
geometry::{Alignment, Offset, Rect}, geometry::{Alignment, Insets, Offset, Rect},
}; };
use super::{text::TextStyle, TextLayout}; use super::{text::TextStyle, TextLayout};
@ -9,6 +9,7 @@ use super::{text::TextStyle, TextLayout};
pub struct Label<T> { pub struct Label<T> {
text: T, text: T,
layout: TextLayout, layout: TextLayout,
vertical: Alignment,
} }
impl<T> Label<T> impl<T> Label<T>
@ -19,6 +20,7 @@ where
Self { Self {
text, text,
layout: TextLayout::new(style).with_align(align), layout: TextLayout::new(style).with_align(align),
vertical: Alignment::Start,
} }
} }
@ -34,6 +36,11 @@ where
Self::new(text, Alignment::Center, style) Self::new(text, Alignment::Center, style)
} }
pub fn vertically_aligned(mut self, align: Alignment) -> Self {
self.vertical = align;
self
}
pub fn text(&self) -> &T { pub fn text(&self) -> &T {
&self.text &self.text
} }
@ -68,7 +75,13 @@ where
.with_bounds(bounds) .with_bounds(bounds)
.fit_text(self.text.as_ref()) .fit_text(self.text.as_ref())
.height(); .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 self.layout.bounds
} }

View File

@ -67,3 +67,8 @@ pub fn jpeg_painter<'a>(
let f = move |area: Rect| display::tjpgd::jpeg(image(), area.center() - off, scale); let f = move |area: Rect| display::tjpgd::jpeg(image(), area.center() - off, scale);
Painter::new(f) Painter::new(f)
} }
pub fn rect_painter(fg: display::Color, bg: display::Color) -> Painter<impl FnMut(Rect)> {
let f = move |area: Rect| display::rect_fill_rounded(area, fg, bg, 2);
Painter::new(f)
}

View File

@ -214,13 +214,21 @@ impl<T, U> Split<T, U> {
} }
} }
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) 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) 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<M, T, U> Component for Split<T, U> impl<M, T, U> Component for Split<T, U>

View File

@ -1,6 +1,8 @@
use crate::ui::{ use crate::ui::{
constant, display, constant,
display::Color, constant::{screen, LOADER_INNER, LOADER_OUTER},
display,
display::{toif::Icon, Color},
geometry::{Offset, Point, Rect}, geometry::{Offset, Point, Rect},
}; };
@ -10,25 +12,35 @@ use crate::trezorhal::{
dma2d::{dma2d_setup_4bpp_over_4bpp, dma2d_start_blend, dma2d_wait_for_transfer}, 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 ICON_MAX_SIZE: i16 = constant::LOADER_ICON_MAX_SIZE;
const IN_INNER_ANTI: i32 = ((INNER - 0.5) * (INNER - 0.5)) as i32; #[derive(Clone, Copy)]
const INNER_MIN: i32 = ((INNER + 0.5) * (INNER + 0.5)) as i32; pub struct LoaderDimensions {
const INNER_MAX: i32 = ((INNER + 1.5) * (INNER + 1.5)) as i32; in_inner_anti: i32,
const INNER_OUTER_ANTI: i32 = ((INNER + 2.5) * (INNER + 2.5)) as i32; inner_min: i32,
const OUTER_OUT_ANTI: i32 = ((OUTER - 1.5) * (OUTER - 1.5)) as i32; inner_max: i32,
const OUTER_MAX: i32 = ((OUTER - 0.5) * (OUTER - 0.5)) as 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( pub fn loader_circular_uncompress(
dim: LoaderDimensions,
y_offset: i16, y_offset: i16,
fg_color: Color, fg_color: Color,
bg_color: Color, bg_color: Color,
@ -36,20 +48,42 @@ pub fn loader_circular_uncompress(
indeterminate: bool, indeterminate: bool,
icon: Option<(Icon, Color)>, icon: Option<(Icon, Color)>,
) { ) {
const ICON_MAX_SIZE: i16 = constant::LOADER_ICON_MAX_SIZE;
if let Some((icon, color)) = icon { if let Some((icon, color)) = icon {
let toif_size = icon.toif.size(); let toif_size = icon.toif.size();
if toif_size.x <= ICON_MAX_SIZE && toif_size.y <= ICON_MAX_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]; let mut icon_data = [0_u8; ((ICON_MAX_SIZE * ICON_MAX_SIZE) / 2) as usize];
icon.toif.uncompress(&mut icon_data); icon.toif.uncompress(&mut icon_data);
let i = Some((icon_data.as_ref(), color, toif_size)); 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 { } 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 { } 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, bg_color: Color,
icon: Option<(Icon, 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( pub fn loader_circular_indeterminate(
@ -70,7 +112,15 @@ pub fn loader_circular_indeterminate(
bg_color: Color, bg_color: Color,
icon: Option<(Icon, 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)] #[inline(always)]
@ -78,8 +128,8 @@ fn get_loader_vectors(indeterminate: bool, progress: u16) -> (Point, Point) {
let (start_progress, end_progress) = if indeterminate { let (start_progress, end_progress) = if indeterminate {
const LOADER_INDETERMINATE_WIDTH: u16 = 100; const LOADER_INDETERMINATE_WIDTH: u16 = 100;
( (
progress - LOADER_INDETERMINATE_WIDTH, (progress + 1000 - LOADER_INDETERMINATE_WIDTH) % 1000,
progress + LOADER_INDETERMINATE_WIDTH, (progress + LOADER_INDETERMINATE_WIDTH) % 1000,
) )
} else { } else {
(0, progress) (0, progress)
@ -114,12 +164,12 @@ fn loader_get_pixel_color_idx(
inverted: bool, inverted: bool,
end_vector: Point, end_vector: Point,
n_start: Point, n_start: Point,
x_c: i16, c: Point,
y_c: i16,
center: Point, center: Point,
dim: LoaderDimensions,
) -> u8 { ) -> u8 {
let y_p = -(y_c - center.y); let y_p = -(c.y - center.y);
let x_p = x_c - center.x; let x_p = c.x - center.x;
let vx = Point::new(x_p, y_p); let vx = Point::new(x_p, y_p);
let n_vx = Point::new(-y_p, x_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 // - r_inner)/(r_outer-r_inner) is negligible
if show_all || included { if show_all || included {
//active part //active part
if d <= IN_INNER_ANTI { if d <= dim.in_inner_anti {
0 0
} else if d <= INNER_MIN { } else if d <= dim.inner_min {
((15 * (d - IN_INNER_ANTI)) / (INNER_MIN - IN_INNER_ANTI)) as u8 ((15 * (d - dim.in_inner_anti)) / (dim.inner_min - dim.in_inner_anti)) as u8
} else if d <= OUTER_OUT_ANTI { } else if d <= dim.outer_out_anti {
15 15
} else if d <= OUTER_MAX { } else if d <= dim.outer_max {
(15 - ((15 * (d - OUTER_OUT_ANTI)) / (OUTER_MAX - OUTER_OUT_ANTI))) as u8 (15 - ((15 * (d - dim.outer_out_anti)) / (dim.outer_max - dim.outer_out_anti))) as u8
} else { } else {
0 0
} }
} else { } else {
//inactive part //inactive part
if d <= IN_INNER_ANTI { if d <= dim.in_inner_anti {
0 0
} else if d <= INNER_MIN { } else if d <= dim.inner_min {
((15 * (d - IN_INNER_ANTI)) / (INNER_MIN - IN_INNER_ANTI)) as u8 ((15 * (d - dim.in_inner_anti)) / (dim.inner_min - dim.in_inner_anti)) as u8
} else if d <= INNER_MAX { } else if d <= dim.inner_max {
15 15
} else if d <= INNER_OUTER_ANTI { } else if d <= dim.inner_outer_anti {
(15 - ((10 * (d - INNER_MAX)) / (INNER_OUTER_ANTI - INNER_MAX))) as u8 (15 - ((10 * (d - dim.inner_max)) / (dim.inner_outer_anti - dim.inner_max))) as u8
} else if d <= OUTER_OUT_ANTI { } else if d <= dim.outer_out_anti {
5 5
} else if d <= OUTER_MAX { } else if d <= dim.outer_max {
5 - ((5 * (d - OUTER_OUT_ANTI)) / (OUTER_MAX - OUTER_OUT_ANTI)) as u8 5 - ((5 * (d - dim.outer_out_anti)) / (dim.outer_max - dim.outer_out_anti)) as u8
} else { } else {
0 0
} }
@ -175,6 +225,7 @@ fn loader_get_pixel_color_idx(
#[cfg(not(feature = "dma2d"))] #[cfg(not(feature = "dma2d"))]
pub fn loader_rust( pub fn loader_rust(
dim: LoaderDimensions,
y_offset: i16, y_offset: i16,
fg_color: Color, fg_color: Color,
bg_color: Color, bg_color: Color,
@ -225,7 +276,7 @@ pub fn loader_rust(
if use_icon && icon_area_clamped.contains(p) { if use_icon && icon_area_clamped.contains(p) {
let x = x_c - center.x; let x = x_c - center.x;
let y = y_c - center.y; 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 x_i = x_c - icon_area.x0;
let y_i = y_c - icon_area.y0; let y_i = y_c - icon_area.y0;
@ -241,7 +292,13 @@ pub fn loader_rust(
if clamped.contains(p) && !icon_pixel { if clamped.contains(p) && !icon_pixel {
let pix_c_idx = loader_get_pixel_color_idx( 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]; underlying_color = colortable[pix_c_idx as usize];
} }
@ -255,6 +312,7 @@ pub fn loader_rust(
#[cfg(feature = "dma2d")] #[cfg(feature = "dma2d")]
pub fn loader_rust( pub fn loader_rust(
dim: LoaderDimensions,
y_offset: i16, y_offset: i16,
fg_color: Color, fg_color: Color,
bg_color: Color, bg_color: Color,
@ -341,7 +399,13 @@ pub fn loader_rust(
let pix_c_idx = if clamped.contains(p) { let pix_c_idx = if clamped.contains(p) {
loader_get_pixel_color_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,
) )
} else { } else {
0 0

View File

@ -9,6 +9,9 @@ use core::slice::from_raw_parts;
use crate::ui::display::loader::circular::{ use crate::ui::display::loader::circular::{
loader_circular as determinate, loader_circular_indeterminate as indeterminate, 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"))] #[cfg(not(feature = "model_tt"))]
use crate::ui::display::loader::rectangular::loader_rectangular as determinate; use crate::ui::display::loader::rectangular::loader_rectangular as determinate;
#[cfg(not(feature = "model_tt"))] #[cfg(not(feature = "model_tt"))]

View File

@ -5,8 +5,8 @@ pub const HEIGHT: i16 = 128;
pub const LINE_SPACE: i16 = 1; pub const LINE_SPACE: i16 = 1;
pub const FONT_BPP: i16 = 1; pub const FONT_BPP: i16 = 1;
pub const LOADER_OUTER: f32 = 32_f32; pub const LOADER_OUTER: i32 = 32;
pub const LOADER_INNER: f32 = 18_f32; pub const LOADER_INNER: i32 = 18;
pub const LOADER_ICON_MAX_SIZE: i16 = 8; pub const LOADER_ICON_MAX_SIZE: i16 = 8;
pub const BACKLIGHT_NORMAL: i32 = 150; pub const BACKLIGHT_NORMAL: i32 = 150;

View File

@ -391,7 +391,7 @@ impl<T> Button<T> {
} else { } else {
0 0
}; };
theme::button_bar(Split::vertical( theme::button_bar(Split::left(
width, width,
theme::BUTTON_SPACING, theme::BUTTON_SPACING,
left.map(|msg| { left.map(|msg| {
@ -456,11 +456,11 @@ impl<T> Button<T> {
}); });
let total_height = theme::BUTTON_HEIGHT + theme::BUTTON_SPACING + theme::INFO_BUTTON_HEIGHT; let total_height = theme::BUTTON_HEIGHT + theme::BUTTON_SPACING + theme::INFO_BUTTON_HEIGHT;
FixedHeightBar::bottom( FixedHeightBar::bottom(
Split::horizontal( Split::top(
theme::INFO_BUTTON_HEIGHT, theme::INFO_BUTTON_HEIGHT,
theme::BUTTON_SPACING, theme::BUTTON_SPACING,
top, top,
Split::vertical(theme::BUTTON_WIDTH, theme::BUTTON_SPACING, left, right), Split::left(theme::BUTTON_WIDTH, theme::BUTTON_SPACING, left, right),
), ),
total_height, total_height,
) )
@ -488,11 +488,11 @@ impl<T> Button<T> {
let [top, middle, bottom] = words; let [top, middle, bottom] = words;
let total_height = 3 * theme::BUTTON_HEIGHT + 2 * theme::BUTTON_SPACING; let total_height = 3 * theme::BUTTON_HEIGHT + 2 * theme::BUTTON_SPACING;
FixedHeightBar::bottom( FixedHeightBar::bottom(
Split::horizontal( Split::top(
theme::BUTTON_HEIGHT, theme::BUTTON_HEIGHT,
theme::BUTTON_SPACING, theme::BUTTON_SPACING,
btn(0, top), btn(0, top),
Split::horizontal( Split::top(
theme::BUTTON_HEIGHT, theme::BUTTON_HEIGHT,
theme::BUTTON_SPACING, theme::BUTTON_SPACING,
btn(1, middle), btn(1, middle),

View File

@ -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<T, U> {
value: u16,
indeterminate: bool,
content: Child<Frame<Split<Empty, U>, &'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<T>,
}
impl<T, U> CoinJoinProgress<T, U>
where
T: AsRef<str>,
{
pub fn new(text: T, indeterminate: bool) -> CoinJoinProgress<T, impl Component<Msg = Never>>
where
T: AsRef<str>,
{
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<T, U> CoinJoinProgress<T, U>
where
T: AsRef<str>,
U: Component<Msg = Never>,
{
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<T, U> Component for CoinJoinProgress<T, U>
where
T: AsRef<str>,
U: Component<Msg = Never>,
{
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::Msg> {
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<T, U> crate::trace::Trace for CoinJoinProgress<T, U>
where
T: crate::trace::Trace,
U: Component,
{
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.open("CoinJoinProgress");
t.close();
}
}

View File

@ -62,6 +62,7 @@ where
fn level_to_style(level: u8) -> (Color, Icon) { fn level_to_style(level: u8) -> (Color, Icon) {
match level { match level {
3 => (theme::YELLOW, Icon::new(theme::ICON_COINJOIN)),
2 => (theme::VIOLET, Icon::new(theme::ICON_MAGIC)), 2 => (theme::VIOLET, Icon::new(theme::ICON_MAGIC)),
1 => (theme::YELLOW, Icon::new(theme::ICON_WARN)), 1 => (theme::YELLOW, Icon::new(theme::ICON_WARN)),
_ => (theme::RED, Icon::new(theme::ICON_WARN)), _ => (theme::RED, Icon::new(theme::ICON_WARN)),

View File

@ -1,5 +1,6 @@
mod address_details; mod address_details;
mod button; mod button;
mod coinjoin_progress;
mod dialog; mod dialog;
mod fido; mod fido;
#[rustfmt::skip] #[rustfmt::skip]
@ -24,6 +25,7 @@ pub use button::{
Button, ButtonContent, ButtonMsg, ButtonStyle, ButtonStyleSheet, CancelConfirmMsg, Button, ButtonContent, ButtonMsg, ButtonStyle, ButtonStyleSheet, CancelConfirmMsg,
CancelInfoConfirmMsg, IconText, SelectWordMsg, CancelInfoConfirmMsg, IconText, SelectWordMsg,
}; };
pub use coinjoin_progress::CoinJoinProgress;
pub use dialog::{Dialog, DialogMsg, IconDialog}; pub use dialog::{Dialog, DialogMsg, IconDialog};
pub use fido::{FidoConfirm, FidoMsg}; pub use fido::{FidoConfirm, FidoMsg};
pub use frame::{Frame, FrameMsg, NotificationFrame}; pub use frame::{Frame, FrameMsg, NotificationFrame};

View File

@ -5,8 +5,8 @@ pub const HEIGHT: i16 = 240;
pub const LINE_SPACE: i16 = 4; pub const LINE_SPACE: i16 = 4;
pub const FONT_BPP: i16 = 4; pub const FONT_BPP: i16 = 4;
pub const LOADER_OUTER: f32 = 60_f32; pub const LOADER_OUTER: i16 = 60;
pub const LOADER_INNER: f32 = 42_f32; pub const LOADER_INNER: i16 = 42;
pub const LOADER_ICON_MAX_SIZE: i16 = 64; pub const LOADER_ICON_MAX_SIZE: i16 = 64;
pub const BACKLIGHT_NORMAL: i32 = 150; pub const BACKLIGHT_NORMAL: i32 = 150;

View File

@ -28,7 +28,7 @@ use crate::{
}, },
TextStyle, TextStyle,
}, },
Border, Component, Empty, FormattedText, Qr, Timeout, TimeoutMsg, Border, Component, Empty, FormattedText, Never, Qr, Timeout, TimeoutMsg,
}, },
display::{self, tjpgd::jpeg_info, toif::Icon}, display::{self, tjpgd::jpeg_info, toif::Icon},
geometry, geometry,
@ -46,12 +46,12 @@ use crate::{
use super::{ use super::{
component::{ component::{
AddressDetails, Bip39Input, Button, ButtonMsg, ButtonStyleSheet, CancelConfirmMsg, AddressDetails, Bip39Input, Button, ButtonMsg, ButtonStyleSheet, CancelConfirmMsg,
CancelInfoConfirmMsg, Dialog, DialogMsg, FidoConfirm, FidoMsg, Frame, FrameMsg, CancelInfoConfirmMsg, CoinJoinProgress, Dialog, DialogMsg, FidoConfirm, FidoMsg, Frame,
HoldToConfirm, HoldToConfirmMsg, Homescreen, HomescreenMsg, HorizontalPage, IconDialog, FrameMsg, HoldToConfirm, HoldToConfirmMsg, Homescreen, HomescreenMsg, HorizontalPage,
Lockscreen, MnemonicInput, MnemonicKeyboard, MnemonicKeyboardMsg, NotificationFrame, IconDialog, Lockscreen, MnemonicInput, MnemonicKeyboard, MnemonicKeyboardMsg,
NumberInputDialog, NumberInputDialogMsg, PassphraseKeyboard, PassphraseKeyboardMsg, NotificationFrame, NumberInputDialog, NumberInputDialogMsg, PassphraseKeyboard,
PinKeyboard, PinKeyboardMsg, Progress, SelectWordCount, SelectWordCountMsg, SelectWordMsg, PassphraseKeyboardMsg, PinKeyboard, PinKeyboardMsg, Progress, SelectWordCount,
Slip39Input, SwipeHoldPage, SwipePage, WelcomeScreen, SelectWordCountMsg, SelectWordMsg, Slip39Input, SwipeHoldPage, SwipePage, WelcomeScreen,
}, },
constant, theme, constant, theme,
}; };
@ -336,6 +336,17 @@ where
} }
} }
impl<T> ComponentMsgObj for (Timeout, T)
where
T: Component<Msg = TimeoutMsg>,
{
fn msg_try_into_obj(&self, msg: Self::Msg) -> Result<Obj, Error> {
match msg {
TimeoutMsg::TimedOut => Ok(CANCELLED.as_obj()),
}
}
}
impl ComponentMsgObj for Qr { impl ComponentMsgObj for Qr {
fn msg_try_into_obj(&self, _msg: Self::Msg) -> Result<Obj, Error> { fn msg_try_into_obj(&self, _msg: Self::Msg) -> Result<Obj, Error> {
unreachable!(); unreachable!();
@ -365,6 +376,16 @@ where
} }
} }
impl<T, U> ComponentMsgObj for CoinJoinProgress<T, U>
where
T: AsRef<str>,
U: Component<Msg = Never>,
{
fn msg_try_into_obj(&self, _msg: Self::Msg) -> Result<Obj, Error> {
unreachable!();
}
}
extern "C" fn new_confirm_action(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { extern "C" fn new_confirm_action(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
let block = move |_args: &[Obj], kwargs: &Map| { let block = move |_args: &[Obj], kwargs: &Map| {
let title: StrBuffer = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?; 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 max_feerate: StrBuffer = kwargs.get(Qstr::MP_QSTR_max_feerate)?.try_into()?;
let paragraphs = Paragraphs::new([ 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_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), 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) } 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 { extern "C" fn new_show_homescreen(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
let block = move |_args: &[Obj], kwargs: &Map| { let block = move |_args: &[Obj], kwargs: &Map| {
let label: StrBuffer = kwargs.get(Qstr::MP_QSTR_label)?.try_into()?; 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) } 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 { extern "C" fn draw_welcome_screen() -> Obj {
// No need of util::try_or_raise, this does not allocate // No need of util::try_or_raise, this does not allocate
let mut screen = WelcomeScreen::new(); 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.""" /// 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(), 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( /// def show_homescreen(
/// *, /// *,
/// label: str, /// label: str,
@ -1842,16 +1873,6 @@ pub static mp_module_trezorui2: Module = obj_module! {
/// """Homescreen for locked device.""" /// """Homescreen for locked device."""
Qstr::MP_QSTR_show_lockscreen => obj_fn_kw!(0, new_show_lockscreen).as_obj(), 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: /// def draw_welcome_screen() -> None:
/// """Show logo icon with the model name at the bottom and return.""" /// """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(), Qstr::MP_QSTR_draw_welcome_screen => obj_fn_0!(draw_welcome_screen).as_obj(),

View File

@ -152,6 +152,10 @@ pub const fn label_title() -> TextStyle {
TextStyle::new(Font::BOLD, GREY_LIGHT, BG, GREY_LIGHT, GREY_LIGHT) 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 { pub const fn button_default() -> ButtonStyleSheet {
ButtonStyleSheet { ButtonStyleSheet {
normal: &ButtonStyle { normal: &ButtonStyle {

View File

@ -422,6 +422,18 @@ def show_progress(
make sure the initial desciption has at least that amount of lines.""" 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 # rust/src/ui/model_tt/layout.rs
def show_homescreen( def show_homescreen(
*, *,
@ -444,17 +456,6 @@ def show_lockscreen(
"""Homescreen for locked device.""" """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 # rust/src/ui/model_tt/layout.rs
def draw_welcome_screen() -> None: def draw_welcome_screen() -> None:
"""Show logo icon with the model name at the bottom and return.""" """Show logo icon with the model name at the bottom and return."""

View File

@ -263,8 +263,8 @@ async def handle_UnlockPath(ctx: wire.Context, msg: UnlockPath) -> protobuf.Mess
await confirm_action( await confirm_action(
ctx, ctx,
"confirm_coinjoin_access", "confirm_coinjoin_access",
title="Coinjoin account", title="Coinjoin",
description="Do you want to allow access to your coinjoin account?", description="Do you want to access your coinjoin account?",
) )
wire_types = (MessageType.GetAddress, MessageType.GetPublicKey, MessageType.SignTx) wire_types = (MessageType.GetAddress, MessageType.GetPublicKey, MessageType.SignTx)

View File

@ -13,6 +13,7 @@ from ..common import SigHashType, ecdsa_sign, input_is_external
from ..ownership import verify_nonownership from ..ownership import verify_nonownership
from ..verification import SignatureVerifier from ..verification import SignatureVerifier
from . import helpers from . import helpers
from .approvers import CoinJoinApprover
from .helpers import request_tx_input, request_tx_output from .helpers import request_tx_input, request_tx_output
from .progress import progress from .progress import progress
from .tx_info import OriginalTxInfo from .tx_info import OriginalTxInfo
@ -47,7 +48,9 @@ _SERIALIZED_TX_BUFFER = empty_bytearray(_MAX_SERIALIZED_CHUNK_SIZE)
class Bitcoin: class Bitcoin:
async def signer(self) -> None: 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. # Add inputs to sig_hasher and h_tx_check and compute the sum of input amounts.
await self.step1_process_inputs() await self.step1_process_inputs()

View File

@ -16,6 +16,7 @@ class Progress:
self.progress = 0 self.progress = 0
self.steps = 0 self.steps = 0
self.signing = False self.signing = False
self.is_coinjoin = False
# We don't know how long it will take to fetch the previous transactions, # 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 # 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. # prev_tx input or output in the overall signing progress.
self.prev_tx_step = 0 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.progress = 0
self.signing = False self.signing = False
self.is_coinjoin = is_coinjoin
# Step 1 and 2 - load inputs and outputs # Step 1 and 2 - load inputs and outputs
self.steps = tx.inputs_count + tx.outputs_count self.steps = tx.inputs_count + tx.outputs_count
@ -109,13 +111,16 @@ class Progress:
def report_init(self) -> None: def report_init(self) -> None:
from trezor import workflow 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() workflow.close_others()
if self.signing: if self.signing:
self.progress_layout = bitcoin_progress("Signing transaction") self.progress_layout = progress_layout("Signing transaction")
else: else:
self.progress_layout = bitcoin_progress("Loading transaction") self.progress_layout = progress_layout("Loading transaction")
def report(self) -> None: def report(self) -> None:
from trezor import utils from trezor import utils

View File

@ -2,9 +2,11 @@ import storage
import storage.cache import storage.cache
import storage.device import storage.device
from trezor import config, wire from trezor import config, wire
from trezor.enums import MessageType
from trezor.ui.layouts.homescreen import Busyscreen, Homescreen, Lockscreen from trezor.ui.layouts.homescreen import Busyscreen, Homescreen, Lockscreen
from apps.base import busy_expiry_ms, lock_device from apps.base import busy_expiry_ms, lock_device
from apps.common.authorization import is_set_any_session
async def busyscreen() -> None: async def busyscreen() -> None:
@ -19,18 +21,20 @@ async def homescreen() -> None:
notification = None notification = None
notification_is_error = False 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 = "SEEDLESS"
notification_is_error = True notification_is_error = True
elif storage.device.is_initialized() and storage.device.unfinished_backup(): elif storage.device.is_initialized() and storage.device.unfinished_backup():
notification = "BACKUP FAILED!" notification = "BACKUP FAILED"
notification_is_error = True notification_is_error = True
elif storage.device.is_initialized() and storage.device.needs_backup(): 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(): elif storage.device.is_initialized() and not config.has_pin():
notification = "PIN NOT SET!" notification = "PIN NOT SET"
elif storage.device.get_experimental_features(): elif storage.device.get_experimental_features():
notification = "EXPERIMENTAL MODE!" notification = "EXPERIMENTAL MODE"
await Homescreen( await Homescreen(
label=label, label=label,

View File

@ -1135,15 +1135,9 @@ async def request_pin_on_device(
class RustProgress: class RustProgress:
def __init__( def __init__(
self, self,
title: str, layout: Any,
description: str | None = None,
indeterminate: bool = False,
): ):
self.layout: Any = trezorui2.show_progress( self.layout = layout
title=title.upper(),
indeterminate=indeterminate,
description=description or "",
)
ui.backlight_fade(ui.style.BACKLIGHT_DIM) ui.backlight_fade(ui.style.BACKLIGHT_DIM)
ui.display.clear() ui.display.clear()
self.layout.attach_timer_fn(self.set_timer) self.layout.attach_timer_fn(self.set_timer)
@ -1160,25 +1154,41 @@ class RustProgress:
ui.refresh() ui.refresh()
def progress(message: str = "PLEASE WAIT") -> ProgressLayout: def progress(
return RustProgress(message.upper()) 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: 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: 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: def monero_keyimage_sync_progress() -> ProgressLayout:
return RustProgress("SYNCING") return progress("SYNCING")
def monero_live_refresh_progress() -> ProgressLayout: def monero_live_refresh_progress() -> ProgressLayout:
return RustProgress("REFRESHING", description="", indeterminate=True) return progress("REFRESHING", indeterminate=True)
def monero_transaction_progress_inner() -> ProgressLayout: def monero_transaction_progress_inner() -> ProgressLayout:
return RustProgress("SIGNING TRANSACTION", description="") return progress("SIGNING TRANSACTION")

View File

@ -47,7 +47,9 @@ class Homescreen(HomescreenBase):
level = 1 level = 1
if notification is not None: if notification is not None:
notification = notification.rstrip("!") notification = notification.rstrip("!")
if "EXPERIMENTAL" in notification: if "COINJOIN" in notification.upper():
level = 3
elif "EXPERIMENTAL" in notification.upper():
level = 2 level = 2
elif notification_is_error: elif notification_is_error:
level = 0 level = 0
@ -114,9 +116,9 @@ class Busyscreen(HomescreenBase):
def __init__(self, delay_ms: int) -> None: def __init__(self, delay_ms: int) -> None:
skip = storage_cache.homescreen_shown is self.RENDER_INDICATOR skip = storage_cache.homescreen_shown is self.RENDER_INDICATOR
super().__init__( super().__init__(
layout=trezorui2.show_busyscreen( layout=trezorui2.show_progress_coinjoin(
title="PLEASE WAIT", title="Waiting for others",
description="Coinjoin in progress.\n\nDo not disconnect your\nTrezor.", indeterminate=True,
time_ms=delay_ms, time_ms=delay_ms,
skip_first_paint=skip, skip_first_paint=skip,
) )

View File

@ -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[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[message1-expected_response1]": "78b3b0efe134085ed595dcc859f53e39b2f044c3ae17e52ef7ff74d33303f5a9",
"TT_binance-test_sign_tx.py::test_binance_sign_message[message2-expected_response2]": "9f6d84a0081925d4d7ffc43765394e2e3c065a34c3fc00b4c9d8f6dbb108796e", "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_cancel_authorization": "a06b2f2471bb928f36dc43e6f7ad03d8b19dadb77ed6e968830cbdf84e0ee2bc",
"TT_bitcoin-test_authorize_coinjoin.py::test_get_address": "5556ff2531268efa0eda447a1960324c78ff43d79c1f7d91d2d6ddd772275799", "TT_bitcoin-test_authorize_coinjoin.py::test_get_address": "a02623c612e54300d8f9f9d0760282f8ea9be9663b30257797eade328fa30a75",
"TT_bitcoin-test_authorize_coinjoin.py::test_get_public_key": "f79cc8fce59d48aa49bc791cbb00d63676dbbd616eb94b4aa0ca56a1c89cf3a0", "TT_bitcoin-test_authorize_coinjoin.py::test_get_public_key": "064eb02d220e974352247d3b7065c38dd3d4fa3d0c5571574bf0d80f369b69d1",
"TT_bitcoin-test_authorize_coinjoin.py::test_multisession_authorization": "8e0a38d46f5a28ed8c7b4608ba5f02b9c3b82a70b01c9bd7319392b0eea272dd", "TT_bitcoin-test_authorize_coinjoin.py::test_multisession_authorization": "b28769468af2ddf2e4b2e45b0bc89806a6392b082cc8953013795eb32b692890",
"TT_bitcoin-test_authorize_coinjoin.py::test_sign_tx": "21954223b2382156eb2926136239cd983ab94fc0d7060ad87e14d35a098276ea", "TT_bitcoin-test_authorize_coinjoin.py::test_sign_tx": "a7a3307a96425a843617d341cf559d0898813ba1eec8221e911a153141aaade8",
"TT_bitcoin-test_authorize_coinjoin.py::test_sign_tx_large": "eb9e47aaee7b932845a0f94a9f52b3107ce1eb19ba12f63f3d7b464335508634", "TT_bitcoin-test_authorize_coinjoin.py::test_sign_tx_large": "a45463660d3c6f920c7d31f6c054a39e504cb152e9eacd0f817aa081a3c7502b",
"TT_bitcoin-test_authorize_coinjoin.py::test_sign_tx_migration": "d619b7d8ff6124885969c543603c49952ea07917a58fb5d80592b4f5bb5c8495", "TT_bitcoin-test_authorize_coinjoin.py::test_sign_tx_migration": "b92179f9e59ea76c6410cdfb3402efc33f05a1e75abfee842cb63380cfa4ad94",
"TT_bitcoin-test_authorize_coinjoin.py::test_sign_tx_spend": "69649e183ca47887d8363d63996a4ddac2dfc9ebd23e53c9855bb8d30bcc868a", "TT_bitcoin-test_authorize_coinjoin.py::test_sign_tx_spend": "2a086b16a7339f09db438f9a6416a48d2b4e99945a8f1365800d4373158d09a8",
"TT_bitcoin-test_authorize_coinjoin.py::test_wrong_account_type": "9903e6dc03e36e73bd36d96e8e4eb28706ae8a47015605b328c977bb39e9b202", "TT_bitcoin-test_authorize_coinjoin.py::test_wrong_account_type": "a06b2f2471bb928f36dc43e6f7ad03d8b19dadb77ed6e968830cbdf84e0ee2bc",
"TT_bitcoin-test_authorize_coinjoin.py::test_wrong_coordinator": "9903e6dc03e36e73bd36d96e8e4eb28706ae8a47015605b328c977bb39e9b202", "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_attack_change_input": "01c388c7836bf584f7d463412ed8dfc18267173759779572b59f8a92831d587d",
"TT_bitcoin-test_bcash.py::test_send_bch_change": "b33fb94f9b7a27db131a025a58e7d1e9130455b56e2a9664a6601126798c5faa", "TT_bitcoin-test_bcash.py::test_send_bch_change": "b33fb94f9b7a27db131a025a58e7d1e9130455b56e2a9664a6601126798c5faa",
"TT_bitcoin-test_bcash.py::test_send_bch_external_presigned": "be3c96271c3fa8084f94f3108a283059b625240c8479ae8509e4e61e2e7801b1", "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": "7aea7d3be283f95c73494d1298cf45f0914e4bd2b563b6ee06822e5475c7cdee",
"TT_bitcoin-test_decred.py::test_send_decred_change": "f1ba6140197b01fcdc6f5e7657dfec8608e19cfd161ddf94273cd057afee7fe5", "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_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-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-49-InputScriptType.SPENDP2SHWITNESS-77f1e2d2": "16d7bd9d6a2d7f3fbd3cd4f57b4d41bd22181d1815f3f3e8ceab8c71c7a1e217",
"TT_bitcoin-test_descriptors.py::test_descriptors[Bitcoin-0-84-InputScriptType.SPENDWITNESS-wpk-16507754": "33742a9a1db97e160bae2399272f9ed2d1e4457efb9f1cdda5724bda09d285e8", "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-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-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[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-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-49-InputScriptType.SPENDP2SHWITNESS-195ebda5": "6a44ff76861f8b45fcf02d8231eba4c7d7be2e36acbc83245daab8bf1e139e69",
"TT_bitcoin-test_descriptors.py::test_descriptors[Testnet-0-84-InputScriptType.SPENDWITNESS-wpk-68f8b526": "4668aded2297e0aaa149c1e3c81016add52c6a273df6709360f3e3f239b59be0", "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_device_id_same": "80a6e289138a604cf351a29511cf6f85e2243591317894703152787e1351a1a3",
"TT_test_basic.py::test_features": "80a6e289138a604cf351a29511cf6f85e2243591317894703152787e1351a1a3", "TT_test_basic.py::test_features": "80a6e289138a604cf351a29511cf6f85e2243591317894703152787e1351a1a3",
"TT_test_basic.py::test_ping": "80a6e289138a604cf351a29511cf6f85e2243591317894703152787e1351a1a3", "TT_test_basic.py::test_ping": "80a6e289138a604cf351a29511cf6f85e2243591317894703152787e1351a1a3",
"TT_test_busy_state.py::test_busy_expiry": "b67d51c7c3e2ebf4318f249c48badc0fceb907fedeb99de4fc6b6d196389bf69", "TT_test_busy_state.py::test_busy_expiry": "ba86a3b442bcc2709762a008734511cf5d7ddd715f7184e9006061b8046e2d56",
"TT_test_busy_state.py::test_busy_state": "b549edd509bde87496aea924d9b9df251893d3a901afc46a5a61605abe7022fb", "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[message0]": "1bd3157d54327e33542f89dcac6c7cd23808f7c9aa1b0adb390e5fcc1fd858a5",
"TT_test_cancel.py::test_cancel_message_via_cancel[message1]": "1bd3157d54327e33542f89dcac6c7cd23808f7c9aa1b0adb390e5fcc1fd858a5", "TT_test_cancel.py::test_cancel_message_via_cancel[message1]": "1bd3157d54327e33542f89dcac6c7cd23808f7c9aa1b0adb390e5fcc1fd858a5",
"TT_test_cancel.py::test_cancel_message_via_initialize[message0]": "1bd3157d54327e33542f89dcac6c7cd23808f7c9aa1b0adb390e5fcc1fd858a5", "TT_test_cancel.py::test_cancel_message_via_initialize[message0]": "1bd3157d54327e33542f89dcac6c7cd23808f7c9aa1b0adb390e5fcc1fd858a5",